1 ----------------------------------------------------------------------------- 2 ------------------------------------------------------------------------------ 3 -- This file is part of 'Finite Field Arithmetic', aka 'FFA'. -- 4 -- -- 5 -- (C) 2019 Stanislav Datskovskiy ( www.loper-os.org ) -- 6 -- http://wot.deedbot.org/17215D118B7239507FAFED98B98228A001ABFFC7.html -- 7 -- -- 8 -- You do not have, nor can you ever acquire the right to use, copy or -- 9 -- distribute this software ; Should you use this software for any purpose, -- 10 -- or copy and distribute it to anyone or in any manner, you are breaking -- 11 -- the laws of whatever soi-disant jurisdiction, and you promise to -- 12 -- continue doing so for the indefinite future. In any case, please -- 13 -- always : read and understand any software ; verify any PGP signatures -- 14 -- that you use - for any purpose. -- 15 -- -- 16 -- See also http://trilema.com/2015/a-new-software-licensing-paradigm . -- 17 ------------------------------------------------------------------------------ 18 ------------------------------------------------------------------------------ 19 20 -- Basics 21 with Version; use Version; 22 with OS; use OS; 23 24 -- FFA 25 with FFA; use FFA; 26 27 -- For the intrinsic equality operator on Words 28 use type FFA.Word; 29 30 -- For RNG: 31 with FFA_RNG; use FFA_RNG; 32 33 34 package body FFA_Calc is 35 36 -- Ensure that requested Peh Dimensions are permissible. Terminate if not. 37 procedure Validate_Peh_Dimensions(Dimensions : in Peh_Dimensions) is 38 begin 39 40 -- Test if proposed Width is permissible: 41 if not FFA_FZ_Valid_Bitness_P(Dimensions.Width) then 42 Eggog("Requested Invalid FZ Width, " & FFA_Validity_Rule_Doc); 43 end if; 44 45 -- Warn the operator if an unbounded Peh run has been requested: 46 if Dimensions.Life = 0 then 47 Achtung("WARNING: Life=0 enables UNBOUNDED run time;" & 48 " halting cannot be guaranteed!"); 49 end if; 50 51 end Validate_Peh_Dimensions; 52 53 54 -- Start a Peh Machine with the given Dimensions and Tape; return a Verdict. 55 function Peh_Machine(Dimensions : in Peh_Dimensions; 56 Tape : in Peh_Tapes; 57 RNG : in RNG_Device) return Peh_Verdicts is 58 59 -- The number of Words required to make a FZ of the given Bitness. 60 Wordness : Indices := Indices(Dimensions.Width / Bitness); 61 62 -------------------------------------------------------- 63 -- State -- 64 -------------------------------------------------------- 65 -- The Data Stack: 66 subtype Stack_Positions is Natural range 0 .. Dimensions.Height; 67 type Stacks is array(Stack_Positions range <>) of FZ(1 .. Wordness); 68 Stack : Stacks(Stack_Positions'Range); 69 70 -- Current top of the Data Stack: 71 SP : Stack_Positions := Stack_Positions'First; 72 73 -- Valid indices into the Tape: 74 subtype Tape_Positions is Peh_Tape_Range range Tape'First .. Tape'Last; 75 76 -- Position of the CURRENT Op on the Tape: 77 IP : Tape_Positions; 78 79 -- After an Op, will contain position of NEXT op (if = to IP -> halt) 80 IP_Next : Tape_Positions; 81 82 -- Types of Entry for the Control Stack: 83 type Call_Types is (Invalid, Subroutines, Loops); 84 85 -- Control Stack Entries: 86 type Call is 87 record 88 Why : Call_Types := Invalid; -- Which call type? 89 Ret : Tape_Positions; -- The IP we must return to after it 90 end record; 91 92 -- Control Stack; permits bidirectional motion across the Tape: 93 Control_Stack : array(ControlStack_Range) of Call; 94 95 -- Current top of the Control Stack: 96 CSP : ControlStack_Range := ControlStack_Range'First; 97 98 -- A Segment represents a particular section of Tape, for certain uses. 99 type Segment is 100 record 101 -- The Tape Position of the FIRST Symbol on the Segment: 102 L : Tape_Positions := Tape'First; -- Default: start of the Tape. 103 104 -- The Tape Position of the LAST Symbol on the Segment: 105 R : Tape_Positions := Tape'Last; -- Default: end of the Tape. 106 end record; 107 108 -- Subtypes of Segment: 109 subtype Sub_Names is Segment; -- Subroutine Names 110 subtype Sub_Bodies is Segment; -- Subroutine Bodies 111 subtype Cutouts is Segment; -- Cutout (see Ch.18 discussion) 112 113 -- Represents a Subroutine defined on this Tape: 114 type Sub_Def is 115 record 116 Name : Sub_Names; -- Name of the Subroutine. 117 Payload : Sub_Bodies; -- Body of the Subroutine. 118 end record; 119 120 -- Subroutine Table. Once defined, Subs may not be erased or altered. 121 Subs : array(Subroutine_Table_Range) of Sub_Def; 122 123 -- Position of the most recently-defined Subroutine in Subs : 124 STP : Subroutine_Table_Range := Subs'First; 125 126 -- Registers: 127 subtype RegNames is Character range 'g' .. 'z'; 128 type RegTables is array(RegNames range <>) of FZ(1 .. Wordness); 129 130 -- Ordinary Register Set (accessed if no Cutout, or when ABOVE it) 131 Registers : RegTables(RegNames'Range); 132 133 -- 'Cutout' Register Set (accessed only if IP is IN or BELOW the Cutout) 134 CO_Registers : RegTables(RegNames'Range); 135 136 -- Carry/Borrow Flag set by certain arithmetical Ops: 137 Flag : WBool := 0; 138 139 -- 'Cutout'-segregated Carry/Borrow Flag: 140 CO_Flag : WBool := 0; 141 142 -- Odometer: 143 Ticks : Natural := 0; 144 145 -- The current levels of the three types of nestable Block: 146 QuoteLevel : Natural := 0; 147 CommLevel : Natural := 0; 148 CondLevel : Natural := 0; 149 150 -- The possible Modes of the reader: 151 type Modes is (Normal, SubName, SubBody, PrefixOp); 152 153 -- Currently-active reader Mode: 154 Mode : Modes := Normal; 155 156 -- Current levels of nestable Blocks when reading a Subroutine Body: 157 SubQuoteLevel : Natural := 0; 158 SubCommLevel : Natural := 0; 159 SubCondLevel : Natural := 0; 160 161 -- Scratch for a Subroutine being proposed for lookup or internment: 162 Proposed_Sub : Sub_Def; 163 164 -- 'Cutout' Tape Segment. (See Ch.18 discussion re: when and how to use.) 165 -- If the Cutout is armed, it stays armed until Peh halts. 166 Cutout_Begun : Boolean := False; 167 Cutout_Armed : Boolean := False; 168 Cutout : Cutouts; 169 170 -- Prefix for Prefixed Operators 171 PrevC : Character := ' '; 172 173 -- Current Verdict. We run while 'Mu', Tape remains, and Ticks under max. 174 Verdict : Peh_Verdicts := Mu; 175 -------------------------------------------------------- 176 177 178 ------------ 179 -- Cutout -- 180 ------------ 181 182 -- Find whether Cutout would prohibit move from current IP to the given : 183 function Cutout_Prohibits(Position : in Tape_Positions) return Boolean is 184 begin 185 return Cutout_Armed and IP > Cutout.R and Position < Cutout.L; 186 end Cutout_Prohibits; 187 188 189 -- Find whether given a Tape Position lies inside an armed Cutout: 190 function In_Cutout(Position : in Tape_Positions) return Boolean is 191 begin 192 return Cutout_Armed and Position in Cutout.L .. Cutout.R; 193 end In_Cutout; 194 195 196 -- Determine whether to use the Cutout Registers at the current position: 197 function Use_CO_Registers return Boolean is 198 begin 199 -- If we are either BELOW or INSIDE armed Cutout : we use only the 200 -- CO_Registers alternative register file. Otherwise: use Registers. 201 return Cutout_Armed and IP <= Cutout.R; 202 end Use_CO_Registers; 203 204 205 ---------- 206 -- Zaps -- 207 ---------- 208 209 -- Zero the Data Stack and reset the SP: 210 procedure Zap_Data_Stack is 211 begin 212 -- Clear the Data Stack: 213 for i in Stack'Range loop 214 FFA_FZ_Clear(Stack(i)); 215 end loop; 216 -- Set SP to bottom: 217 SP := Stack_Positions'First; 218 end Zap_Data_Stack; 219 220 221 -- Zero all Registers (Ordinary set) : 222 procedure Zap_Ordinary_Registers is 223 begin 224 for r in RegNames'Range loop 225 FFA_FZ_Clear(Registers(r)); 226 end loop; 227 end Zap_Ordinary_Registers; 228 229 230 -- Zero all Registers (Cutout set) : 231 procedure Zap_Cutout_Registers is 232 begin 233 for r in RegNames'Range loop 234 FFA_FZ_Clear(CO_Registers(r)); 235 end loop; 236 end Zap_Cutout_Registers; 237 238 239 -- Zero all Registers in the currently-active Register Set: 240 procedure Zap_Registers is 241 begin 242 if Use_CO_Registers then 243 Zap_Cutout_Registers; 244 else 245 Zap_Ordinary_Registers; 246 end if; 247 end Zap_Registers; 248 249 250 -- Zero the currently-active Overflow Flag: 251 procedure Zap_Flag is 252 begin 253 if Use_CO_Registers then 254 CO_Flag := 0; 255 else 256 Flag := 0; 257 end if; 258 end Zap_Flag; 259 260 261 -- NO effect on Blocks, Control Stack, Tape, Verdict, Cutout, Subroutines 262 procedure Zap_Master is 263 begin 264 Zap_Data_Stack; 265 Zap_Registers; 266 Zap_Flag; 267 end Zap_Master; 268 269 270 ----------- 271 -- Eggog -- 272 ----------- 273 274 -- Report a fatal error condition at the current Symbol. 275 -- On Unixlikes, this will also end the process and return control to OS. 276 procedure E(S : in String) is 277 begin 278 Zap_Master; -- Jettison all resettable state! 279 Eggog("FATAL: Tick:" & Natural'Image(Ticks) & 280 ", IP:" & Tape_Positions'Image(IP) & 281 ", Symbol: '" & Tape(IP) & "'" & " : " & S); 282 end E; 283 284 285 ----------- 286 -- Walls -- 287 ----------- 288 289 -- Determine whether we are currently at the last Symbol on the Tape: 290 function Last_Tape_Symbol return Boolean is 291 begin 292 return IP = Tape_Positions'Last; 293 end Last_Tape_Symbol; 294 295 296 -- Certain Ops are NOT permitted to occur as the final Op on a Tape: 297 function Next_IP_On_Tape return Tape_Positions is 298 begin 299 -- Check if we are in fact on the last Symbol of the Tape: 300 if Last_Tape_Symbol then 301 E("This Op requires a succeeding Tape Position, " 302 & "but it is at the end of the Tape!"); 303 end if; 304 -- ... Otherwise, return the immediate successor Tape Position: 305 return IP + 1; 306 end Next_IP_On_Tape; 307 308 309 -- Determine whether we have reached the given limit of Life: 310 function Exhausted_Life return Boolean is 311 -- If Life = 0, we are in "immortal" mode. Otherwise mortal: 312 MustDie : Boolean := 313 (Dimensions.Life /= 0) and (Ticks = Dimensions.Life); 314 begin 315 if MustDie then 316 Achtung("WARNING: Exhausted Life (" 317 & Natural'Image(Ticks) & " ticks )"); 318 end if; 319 return MustDie; 320 end Exhausted_Life; 321 322 323 ---------------- 324 -- Data Stack -- 325 ---------------- 326 327 -- Determine whether the Data Stack is Not Empty: 328 function Data_Stack_Not_Empty return Boolean is 329 begin 330 return SP /= Stack'First; 331 end Data_Stack_Not_Empty; 332 333 334 -- Raise the SP up by one: 335 procedure Push is 336 begin 337 if SP = Stack_Positions'Last then 338 E("Stack Overflow!"); 339 else 340 SP := SP + 1; 341 end if; 342 end Push; 343 344 345 -- Discard the Top of the Data Stack: 346 procedure Drop is 347 begin 348 FFA_FZ_Clear(Stack(SP)); 349 SP := SP - 1; 350 end Drop; 351 352 353 -- Check whether the Data Stack has the necessary N items: 354 procedure Want(N : in Positive) is 355 begin 356 if SP < N then 357 E("Stack Underflow!"); 358 end if; 359 end Want; 360 361 362 --------- 363 -- I/O -- 364 --------- 365 366 -- Slide a new hex digit into the FZ on top of the Data Stack 367 procedure Ins_Hex_Digit(Digit : in Nibble) is 368 Overflow : WBool := 0; 369 begin 370 371 -- Insert the given nibble, and detect any overflow: 372 FFA_FZ_Insert_Bottom_Nibble(N => Stack(SP), 373 D => Digit, 374 Overflow => Overflow); 375 376 -- Constants which exceed the Width are forbidden: 377 if Overflow = 1 then 378 E("Constant Exceeds Bitness!"); 379 end if; 380 381 end; 382 383 384 -- Emit an ASCII representation of N to the terminal 385 procedure Print_FZ(N : in FZ) is 386 S : String(1 .. FFA_FZ_ASCII_Length(N)); -- Mandatorily, exact size 387 begin 388 FFA_FZ_To_Hex_String(N, S); -- Convert N to ASCII hex 389 Write_String(S); -- Print the result to stdout 390 Write_Newline; -- Print newline, for clarity. 391 end Print_FZ; 392 393 394 ------------------ 395 -- Debug Traces -- 396 ------------------ 397 398 -- Print the bounds of a Tape Segment for Debug: 399 procedure Print_Segment(S : in Segment) is 400 begin 401 Write_String("(" & Tape_Positions'Image(S.L) & 402 "," & Tape_Positions'Image(S.R) & " )"); 403 end Print_Segment; 404 405 406 -- Print a Debug Trace (used in 'QD') : 407 procedure Print_Trace is 408 begin 409 -- For clarity in cases where the Tape has already produced output: 410 Write_Newline; 411 412 -- Print Data Stack Trace: 413 Write_String("Data Stack:"); 414 Write_Newline; 415 for i in reverse Stack'First + 1 .. SP loop 416 Write_String(" " & Stack_Positions'Image(i) & " : "); 417 Print_FZ(Stack(i)); 418 end loop; 419 420 -- Print Control Stack Trace: 421 Write_String("Control Stack:"); 422 Write_Newline; 423 for i in reverse Control_Stack'First + 1 .. CSP loop 424 Write_String(" " & ControlStack_Range'Image(i) & " :"); 425 Write_String(" Return IP:" 426 & Stack_Positions'Image(Control_Stack(i).Ret)); 427 Write_String(" Call Type: "); 428 case Control_Stack(i).Why is 429 when Subroutines => 430 Write_String("Subroutine"); 431 when Loops => 432 Write_String("Loop"); 433 when others => 434 Write_String("INVALID"); 435 end case; 436 Write_Newline; 437 end loop; 438 439 -- Print All Registers: 440 Write_String("Registers:"); 441 Write_Newline; 442 -- We will not print the Cutout Register Set unless it is active: 443 for r in RegNames'Range loop 444 if Use_CO_Registers then 445 -- If the Cutout Register Set is currently active: 446 Write_String(" (C)" & r & " : "); 447 Print_FZ(CO_Registers(r)); 448 else 449 -- If the Ordinary Register Set is currently active: 450 Write_String(" " & r & " : "); 451 Print_FZ(Registers(r)); 452 end if; 453 end loop; 454 455 -- Print Subroutine Table: 456 Write_String("Subroutines:"); 457 Write_Newline; 458 -- Walk the Subroutine Table from first to last valid entry: 459 for i in Subs'First + 1 .. STP loop 460 declare 461 -- The current Sub in the Subroutine Table being examined: 462 S : Sub_Def := Subs(i); 463 -- The Name of the current Sub: 464 S_Name : String := String(Tape(S.Name.L .. S.Name.R)); 465 begin 466 Write_String(" " & Subroutine_Table_Range'Image(i) 467 & " : '" & S_Name & "' "); 468 Print_Segment(S.Payload); 469 if Cutout_Armed then 470 -- Indicate whether Sub is uncallable here because of Cutout: 471 if Cutout_Prohibits(S.Payload.L) then 472 Write_String(" (Guarded)"); 473 -- Indicate whether Sub lies INSIDE the Cutout: 474 elsif In_Cutout(S.Payload.R) then 475 Write_String(" (Cutout)"); 476 end if; 477 end if; 478 Write_Newline; 479 end; 480 end loop; 481 482 Write_String("Cutout: "); 483 -- Print Cutout bounds, if Cutout is armed: 484 if Cutout_Armed then 485 Write_String("Armed: "); 486 Print_Segment(Cutout); 487 else 488 Write_String("NONE"); 489 end if; 490 Write_Newline; 491 492 -- Print active Overflow-Flag, then Ticks and IP: 493 494 if Use_CO_Registers then 495 Write_String("Flag (CO) :" & WBool'Image(CO_Flag)); 496 else 497 Write_String("Flag :" & WBool'Image(Flag)); 498 end if; 499 500 Write_Newline; 501 Write_String("Ticks :" & Natural'Image(Ticks)); 502 Write_Newline; 503 Write_String("IP :" & Tape_Positions'Image(IP)); 504 Write_Newline; 505 end Print_Trace; 506 507 508 ------------------- 509 -- Control Stack -- 510 ------------------- 511 512 -- Determine whether the Control Stack is Not Empty: 513 function Control_Stack_Not_Empty return Boolean is 514 begin 515 return CSP /= Control_Stack'First; 516 end Control_Stack_Not_Empty; 517 518 519 -- Construct a Call and push it to the Control Stack: 520 procedure Control_Push(Call_Type : in Call_Types; 521 Return_IP : in Tape_Positions) is 522 begin 523 -- First, test for Overflow of Control Stack: 524 if CSP = Control_Stack'Last then 525 E("Control Stack Overflow!"); 526 end if; 527 -- Push a Call with given parameters to the Control Stack: 528 CSP := CSP + 1; 529 Control_Stack(CSP) := (Why => Call_Type, Ret => Return_IP); 530 end Control_Push; 531 532 533 -- Pop an IP from the Control Stack, and verify expected Call Type: 534 function Control_Pop(Expected_Type : in Call_Types) 535 return Tape_Positions is 536 C : Call; 537 begin 538 -- First, test for Underflow of Control Stack: 539 if CSP = Control_Stack'First then 540 E("Control Stack Underflow!"); 541 end if; 542 -- Pop from Control Stack: 543 C := Control_Stack(CSP); 544 Control_Stack(CSP).Why := Invalid; 545 CSP := CSP - 1; 546 -- Now, see whether it was NOT the expected type. If so, eggog: 547 if C.Why /= Expected_Type then 548 declare 549 CT : constant array(Call_Types) of String(1 .. 10) 550 := (" INVALID ", "Subroutine", "Loop state"); 551 begin 552 E("Currently in a " & CT(C.Why) & "; but this Op exits a " 553 & CT(Expected_Type) & " !"); 554 end; 555 end if; 556 -- ... The Call was of the expected type, so return it: 557 return C.Ret; 558 end Control_Pop; 559 560 561 ----------------- 562 -- Subroutines -- 563 ----------------- 564 565 -- Find Subroutine with supplied Name in Subroutine Table, if it exists: 566 function Lookup_Subroutine(Name : in Sub_Names) 567 return Subroutine_Table_Range is 568 -- Number of Symbols in the Name of the current Proposed Subroutine: 569 Sub_Name_Length : Positive := 1 + Name.R - Name.L; 570 begin 571 -- Enforce minimum Subroutine Name length: 572 if Sub_Name_Length < Subr_Min_Name_Length then 573 E("Proposed Name is" & Positive'Image(Sub_Name_Length) & 574 " Symbols long, but the shortest permitted Name length is" & 575 Positive'Image(Subr_Min_Name_Length) & " !"); 576 end if; 577 -- Walk the Subroutine Table from first to last valid entry: 578 for i in Subs'First + 1 .. STP loop 579 declare 580 -- The current Sub in the Subroutine Table being examined: 581 S : Sub_Def := Subs(i); 582 -- Number of Symbols in the Name of S: 583 S_Name_Length : Positive := 1 + S.Name.R - S.Name.L; 584 begin 585 -- If the lengths of the Names match: 586 if Sub_Name_Length = S_Name_Length then 587 -- If the two Names are actually equal: 588 if Tape(Name.L .. Name.R) = Tape(S.Name.L .. S.Name.R) then 589 return i; -- Return the table index of the located Sub 590 end if; 591 end if; 592 end; 593 end loop; 594 -- Name was not found in Subroutine Table; return the zero position: 595 return Subs'First; 596 end Lookup_Subroutine; 597 598 599 -- Attempt to intern the given Subroutine into the Subroutines Table: 600 procedure Intern_Subroutine(Sub : in Sub_Def) is 601 -- Position of the current Proposed Sub in Sub Table: 602 Index : Subroutine_Table_Range := Lookup_Subroutine(Sub.Name); 603 -- To DEFINE a Sub, it must NOT have existed in Sub Table. 604 605 -- Name of the Proposed Sub (for eggogs) : 606 S_Name : String := String(Tape(Sub.Name.L .. Sub.Name.R)); 607 begin 608 -- If a Sub with this Name already exists, eggog: 609 if Index /= Subs'First then 610 E("Attempted to redefine Subroutine '" & S_Name & "' !"); 611 end if; 612 -- Definitions are prohibited inside Loops or Sub calls: 613 if Control_Stack_Not_Empty then 614 E("Attempted to define Subroutine '" 615 & S_Name & "' while inside a Loop or Subroutine!"); 616 end if; 617 -- If the Subroutine Table is full, eggog: 618 if STP = Subs'Last then 619 E("Cannot define the Subroutine '" & S_Name 620 & ": the Subroutine Table is Full!"); 621 end if; 622 -- Finally, intern the Proposed Subroutine into the Sub Table: 623 STP := STP + 1; 624 Subs(STP) := Sub; 625 end Intern_Subroutine; 626 627 628 -- Invoke a given Subroutine: 629 procedure Invoke_Subroutine(Sub : in Sub_Def) is 630 begin 631 -- Push the Call to Control Stack: 632 Control_Push(Call_Type => Subroutines, Return_IP => Next_IP_On_Tape); 633 -- Next instruction will be the first Symbol of the Sub's Body: 634 IP_Next := Sub.Payload.L; 635 end Invoke_Subroutine; 636 637 638 -- Attempt to invoke a Subroutine with the supplied name: 639 procedure Invoke_Named_Subroutine(Name : in Sub_Names) is 640 -- Position of the current Proposed Sub in Sub Table: 641 Index : Subroutine_Table_Range := Lookup_Subroutine(Name); 642 -- To invoke a Sub, it MUST exist in the Sub Table. 643 644 -- Name of the Proposed Sub (for eggogs) : 645 S_Name : String := String(Tape(Name.L .. Name.R)); 646 begin 647 -- If no defined Subroutine has this Name, eggog: 648 if Index = Subs'First then 649 E("Invoked Undefined Subroutine '" & S_Name & "' !"); 650 end if; 651 -- Otherwise, proceed to the invocation: 652 declare 653 -- The Sub Table Entry we successfully looked up: 654 Sub : Sub_Def := Subs(Index); 655 begin 656 -- Recursion is prohibited in Peh Tapes. Detect it: 657 if IP in Sub.Payload.L .. Sub.Payload.R then 658 E("Recursive invocation in Subroutine '" 659 & S_Name & "' is prohibited!"); 660 end if; 661 -- Prohibit Subroutines whose definitions end AFTER the current IP: 662 if IP < Sub.Payload.R then 663 E("Cannot invoke Subroutine '" & S_Name & 664 "' before the position where it is defined!"); 665 end if; 666 -- Proceed to invoke the Subroutine: 667 Invoke_Subroutine(Sub); 668 end; 669 end Invoke_Named_Subroutine; 670 671 672 -- Invoke the nearest Subroutine defined to the LEFT of the current IP: 673 procedure Invoke_Left_Subroutine is 674 -- Position of the Subroutine to be invoked (Subs'First if none) 675 Index : Subroutine_Table_Range := Subs'First; 676 begin 677 -- Find the nearest invocable Sub (i.e. to the LEFT of current IP) : 678 -- Walk starting from the LAST Sub in Subs, down to the FIRST: 679 for i in reverse Subs'First + 1 .. STP loop 680 -- If a Sub's definition ended PRIOR TO the current IP: 681 if Subs(i).Payload.R < IP then 682 -- Save that Sub's table index: 683 Index := i; 684 -- If we found a Sub that met the condition, stop walking: 685 exit when Index /= Subs'First; 686 end if; 687 end loop; 688 -- If no Subs have been defined prior to current IP, then eggog: 689 if Index = Subs'First then 690 E("No Subroutines were defined prior to this position!"); 691 end if; 692 -- Proceed to invoke the selected Sub: 693 Invoke_Subroutine(Subs(Index)); 694 end Invoke_Left_Subroutine; 695 696 697 --------- 698 -- Peh -- 699 --------- 700 701 -- For all Ops which entail Division: ensure that a Divisor is not zero: 702 procedure MustNotZero(D : in FZ) is 703 begin 704 if FFA_FZ_ZeroP(D) = 1 then 705 E("Division by Zero!"); 706 end if; 707 end MustNotZero; 708 709 ------------------------------------------------------------------------ 710 711 -- Execute a Normal Op 712 procedure Op_Normal(C : in Character) is 713 714 -- Over/underflow output from certain ops 715 F : Word; 716 717 begin 718 719 case C is 720 721 ------------ 722 -- Blocks -- 723 ------------ 724 725 -- Enter Comment Block: Symbols will be ignored until matching ')' 726 when '(' => 727 CommLevel := 1; 728 729 -- Exit a Comment Block (but if we're here, we aren't in one!) 730 when ')' => 731 E("Mismatched close-comment parenthesis !"); 732 733 -- Enter a Quote Block: Symbols will print until matching ']' 734 when '[' => 735 QuoteLevel := 1; 736 737 -- Exit a Quote Block (but if we're here, we aren't in one!) 738 when ']' => 739 E("Mismatched close-quote bracket !"); 740 741 -- Enter a Conditional branch: 742 when '{' => 743 Want(1); 744 if FFA_FZ_ZeroP(Stack(SP)) = 1 then 745 -- Enter a 'taken' branch. 746 -- All subsequent Symbols will be ignored until matching '}'. 747 CondLevel := 1; 748 end if; 749 Drop; 750 751 -- Exit from a ~non-taken~ Conditional branch: 752 -- ... we push a 0, to suppress the 'else' clause: 753 when '}' => 754 Push; 755 FFA_WBool_To_FZ(0, Stack(SP)); 756 757 ---------------- 758 -- Immediates -- 759 ---------------- 760 761 -- These operate on the FZ ~currently~ at top of the stack; 762 -- and this means that the stack may NOT be empty. 763 764 when '0' .. '9' => 765 Want(1); 766 Ins_Hex_Digit(Character'Pos(C) - Character'Pos('0')); 767 768 when 'A' .. 'F' => 769 Want(1); 770 Ins_Hex_Digit(10 + Character'Pos(C) - Character'Pos('A')); 771 772 when 'a' .. 'f' => 773 Want(1); 774 Ins_Hex_Digit(10 + Character'Pos(C) - Character'Pos('a')); 775 776 ------------------------- 777 -- Fetch from Register -- 778 ------------------------- 779 when 'g' .. 'z' => 780 -- Put value of Register on stack 781 Push; 782 if Use_CO_Registers then 783 Stack(SP) := CO_Registers(C); -- use Cutout Register set 784 else 785 Stack(SP) := Registers(C); -- use ordinary set 786 end if; 787 788 ------------------ 789 -- Stack Motion -- 790 ------------------ 791 792 -- Push a 0 onto the stack 793 when '.' => 794 Push; 795 FFA_FZ_Clear(Stack(SP)); 796 797 -- Dup 798 when '"' => 799 Want(1); 800 Push; 801 Stack(SP) := Stack(SP - 1); 802 803 -- Drop 804 when '_' => 805 Want(1); 806 Drop; 807 808 -- Swap 809 when ''' => 810 Want(2); 811 FFA_FZ_Swap(Stack(SP), Stack(SP - 1)); 812 813 -- Over 814 when '`' => 815 Want(2); 816 Push; 817 Stack(SP) := Stack(SP - 2); 818 819 ---------------- 820 -- Predicates -- 821 ---------------- 822 823 -- Equality 824 when '=' => 825 Want(2); 826 FFA_WBool_To_FZ(FFA_FZ_EqP(X => Stack(SP), 827 Y => Stack(SP - 1)), 828 Stack(SP - 1)); 829 Drop; 830 831 -- Less-Than 832 when '<' => 833 Want(2); 834 FFA_WBool_To_FZ(FFA_FZ_LessThanP(X => Stack(SP - 1), 835 Y => Stack(SP)), 836 Stack(SP - 1)); 837 Drop; 838 839 -- Greater-Than 840 when '>' => 841 Want(2); 842 FFA_WBool_To_FZ(FFA_FZ_GreaterThanP(X => Stack(SP - 1), 843 Y => Stack(SP)), 844 Stack(SP - 1)); 845 Drop; 846 847 ---------------- 848 -- Arithmetic -- 849 ---------------- 850 851 -- Subtract 852 when '-' => 853 Want(2); 854 FFA_FZ_Subtract(X => Stack(SP - 1), 855 Y => Stack(SP), 856 Difference => Stack(SP - 1), 857 Underflow => F); 858 859 -- If we are in the Cutout, write the CO_Flag instead of Flag: 860 if Use_CO_Registers then 861 CO_Flag := FFA_Word_NZeroP(F); 862 else 863 Flag := FFA_Word_NZeroP(F); 864 end if; 865 Drop; 866 867 -- Add 868 when '+' => 869 Want(2); 870 FFA_FZ_Add(X => Stack(SP - 1), 871 Y => Stack(SP), 872 Sum => Stack(SP - 1), 873 Overflow => F); 874 875 -- If we are in the Cutout, write the CO_Flag instead of Flag: 876 if Use_CO_Registers then 877 CO_Flag := FFA_Word_NZeroP(F); 878 else 879 Flag := FFA_Word_NZeroP(F); 880 end if; 881 Drop; 882 883 -- Divide and give Quotient and Remainder 884 when '\' => 885 Want(2); 886 MustNotZero(Stack(SP)); 887 FFA_FZ_IDiv(Dividend => Stack(SP - 1), 888 Divisor => Stack(SP), 889 Quotient => Stack(SP - 1), 890 Remainder => Stack(SP)); 891 892 -- Divide and give Quotient only 893 when '/' => 894 Want(2); 895 MustNotZero(Stack(SP)); 896 FFA_FZ_Div(Dividend => Stack(SP - 1), 897 Divisor => Stack(SP), 898 Quotient => Stack(SP - 1)); 899 Drop; 900 901 -- Divide and give Remainder only 902 when '%' => 903 Want(2); 904 MustNotZero(Stack(SP)); 905 FFA_FZ_Mod(Dividend => Stack(SP - 1), 906 Divisor => Stack(SP), 907 Remainder => Stack(SP - 1)); 908 Drop; 909 910 -- Multiply, give bottom and top halves 911 when '*' => 912 Want(2); 913 FFA_FZ_Multiply(X => Stack(SP - 1), 914 Y => Stack(SP), 915 XY_Lo => Stack(SP - 1), 916 XY_Hi => Stack(SP)); 917 918 -- Square, give bottom and top halves 919 when 'S' => 920 Want(1); 921 Push; 922 FFA_FZ_Square(X => Stack(SP - 1), 923 XX_Lo => Stack(SP - 1), 924 XX_Hi => Stack(SP)); 925 926 -- Greatest Common Divisor (GCD) 927 when 'G' => 928 Want(2); 929 930 -- Note that GCD(0,0) is not factually zero, or unique. 931 -- But it is permissible to define it as zero. 932 -- (See Ch. 15 discussion.) 933 934 FFA_FZ_Greatest_Common_Divisor(X => Stack(SP - 1), 935 Y => Stack(SP), 936 Result => Stack(SP - 1)); 937 Drop; 938 939 ----------------- 940 -- Bitwise Ops -- 941 ----------------- 942 943 -- Bitwise-And 944 when '&' => 945 Want(2); 946 FFA_FZ_And(X => Stack(SP - 1), 947 Y => Stack(SP), 948 Result => Stack(SP - 1)); 949 Drop; 950 951 -- Bitwise-Or 952 when '|' => 953 Want(2); 954 FFA_FZ_Or(X => Stack(SP - 1), 955 Y => Stack(SP), 956 Result => Stack(SP - 1)); 957 Drop; 958 959 -- Bitwise-Xor 960 when '^' => 961 Want(2); 962 FFA_FZ_Xor(X => Stack(SP - 1), 963 Y => Stack(SP), 964 Result => Stack(SP - 1)); 965 Drop; 966 967 -- Bitwise-Not (1s-Complement) 968 when '~' => 969 Want(1); 970 FFA_FZ_Not(Stack(SP), Stack(SP)); 971 972 ----------- 973 -- Other -- 974 ----------- 975 976 -- Push a FZ of RNGolade onto the stack 977 when '?' => 978 Push; 979 FFA_FZ_Clear(Stack(SP)); 980 FZ_Random(RNG, Stack(SP)); 981 982 -- mUx 983 when 'U' => 984 Want(3); 985 FFA_FZ_Mux(X => Stack(SP - 2), 986 Y => Stack(SP - 1), 987 Result => Stack(SP - 2), 988 Sel => FFA_FZ_NZeroP(Stack(SP))); 989 Drop; 990 Drop; 991 992 -- Find the position of eldest nonzero bit, if any exist 993 when 'W' => 994 Want(1); 995 declare 996 -- Find the measure ( 0 if no 1s, or 1 .. FZBitness ) 997 Measure : FZBit_Index := FFA_FZ_Measure(Stack(SP)); 998 begin 999 -- Put on top of stack 1000 FFA_FZ_Clear(Stack(SP)); 1001 FFA_FZ_Set_Head(Stack(SP), Word(Measure)); 1002 end; 1003 1004 -- Put the Overflow flag on the stack 1005 when 'O' => 1006 Push; 1007 -- If we are in the Cutout, read CO_Flag instead of Flag: 1008 if Use_CO_Registers then 1009 FFA_WBool_To_FZ(CO_Flag, Stack(SP)); 1010 else 1011 FFA_WBool_To_FZ(Flag, Stack(SP)); 1012 end if; 1013 1014 -- Print the FZ on the top of the stack 1015 when '#' => 1016 Want(1); 1017 Print_FZ(Stack(SP)); 1018 Drop; 1019 1020 -- Put the Peh Program Version on the stack, 1021 -- followed by FFA Program Version. 1022 when 'V' => 1023 Push; 1024 Push; 1025 -- Peh Version: 1026 FFA_FZ_Clear(Stack(SP - 1)); 1027 FFA_FZ_Set_Head(Stack(SP - 1), Word(Peh_K_Version)); 1028 -- FFA Version: 1029 FFA_FZ_Clear(Stack(SP)); 1030 FFA_FZ_Set_Head(Stack(SP), Word(FFA_K_Version)); 1031 1032 -- Constant-Time Miller-Rabin Test on N using the given Witness. 1033 -- Witness will be used as-is if it conforms to the valid range, 1034 -- i.e. 2 <= Witness <= N - 2; else will be transformed into a 1035 -- valid Witness via modular arithmetic. 1036 -- Outputs ONE if N WAS FOUND composite; ZERO if NOT FOUND. 1037 -- Handles degenerate cases of N that M-R per se cannot eat: 1038 -- N=0, N=1: ALWAYS 'FOUND COMPOS.'; 2, 3 - ALWAYS 'NOT FOUND'. 1039 -- If N is Even and not equal to 2, N is ALWAYS 'FOUND COMPOS.' 1040 -- For ALL other N, the output is equal to that of the M-R test. 1041 -- At most 1/4 of all possible Witnesses will be 'liars' for 1042 -- a particular composite N , i.e. fail to attest to its 1043 -- compositivity. 1044 when 'P' => 1045 Want(2); 1046 declare 1047 MR_Result : WBool := 1048 FFA_FZ_MR_Composite_On_Witness(N => Stack(SP - 1), 1049 Witness => Stack(SP)); 1050 begin 1051 FFA_WBool_To_FZ(MR_Result, Stack(SP - 1)); 1052 end; 1053 Drop; 1054 1055 -------------- 1056 -- Prefixes -- 1057 -------------- 1058 1059 when 1060 'Q' -- 'Quit...' 1061 | 1062 'Z' -- 'Zap...' 1063 | 1064 'L' -- 'Left...' 1065 | 1066 'R' -- 'Right...' 1067 | 1068 'M' -- 'Modular...' 1069 | 1070 '$' -- Pop top of Stack into the following Register... 1071 => 1072 -- Set the Prefixed Op Mode. Next Symbol is treated as prefixed: 1073 Mode := PrefixOp; 1074 1075 ----------- 1076 -- Loops -- 1077 ----------- 1078 1079 -- Begin Loop: Push IP (i.e. of THIS Op) to Control Stack. 1080 when ':' => 1081 Control_Push(Call_Type => Loops, Return_IP => IP); 1082 1083 -- Conditional End Loop: Pop top of Stack, and... 1084 -- ... if ZERO: simply discard the top of the Control Stack. 1085 -- ... if NONZERO: pop top of Control Stack and make it next IP. 1086 when ',' => 1087 Want(1); 1088 declare 1089 Loop_Position : Tape_Positions := Control_Pop(Loops); 1090 Trigger : WBool := FFA_FZ_NZeroP(Stack(SP)); 1091 begin 1092 -- If Trigger is active, re-enter the Loop: 1093 if Trigger = 1 then 1094 IP_Next := Loop_Position; 1095 end if; 1096 end; 1097 -- ... otherwise, continue normally. 1098 Drop; 1099 1100 ----------------- 1101 -- Subroutines -- 1102 ----------------- 1103 1104 -- Return from a Subroutine: 1105 when ';' => 1106 -- Next instruction will be at the saved Return Position: 1107 IP_Next := Control_Pop(Subroutines); 1108 1109 -- Indicate the start of a Subroutine Name, e.g. @SubName 1110 -- ... if DEFINING a NEW Subroutine: is followed by @body; 1111 -- ... if INVOKING EXISTING Subroutine: is followed by ! 1112 when '@' => 1113 -- Save the NEXT IP as the first Symbol of the proposed Name: 1114 Proposed_Sub.Name.L := Next_IP_On_Tape; 1115 -- Enter the Name mode: 1116 Mode := SubName; 1117 -- We will remain in Name mode until we see a @ or ! . 1118 1119 -- '!' invokes a previously-defined Subroutine: 1120 -- ... If found after @Name was given, the syntax is: @SubName! 1121 -- ... If found in THIS context, with no @Name , then invokes 1122 -- the nearest Subroutine defined to the LEFT of this IP. 1123 -- NO Sub defined to the RIGHT of the current IP may be invoked. 1124 when '!' => 1125 Invoke_Left_Subroutine; 1126 1127 --------------------------------------------------------- 1128 -- Reserved Ops, i.e. ones we have not defined yet: -- 1129 --------------------------------------------------------- 1130 when 'H' | 'I' | 'J' | 'K' | 'N' | 'T' | 'X' | 'Y' => 1131 1132 E("This Operator is not defined yet: " & C); 1133 --------------------------------------------------------- 1134 1135 ---------- 1136 -- NOPs -- 1137 ---------- 1138 1139 -- Unprintables and spaces DO NOTHING. 1140 -- (However: they occupy space, consume Life, clear Prefix.) 1141 when others => 1142 null; 1143 1144 end case; 1145 1146 end Op_Normal; 1147 1148 ------------------------------------------------------------------------ 1149 1150 -- Execute a Prefixed Op 1151 procedure Op_Prefixed(Prefix : in Character; 1152 O : in Character) is 1153 1154 -- Report an attempt to execute an undefined Prefix Op: 1155 procedure Undefined_Prefix_Op is 1156 begin 1157 E("Undefined Prefix Op: '" & Prefix & O & "'"); 1158 end Undefined_Prefix_Op; 1159 1160 begin 1161 1162 -- Which Prefix Op? 1163 case Prefix is 1164 1165 --------------------------------------------------------- 1166 -- Quit... (See Ch. 17 discussion) 1167 when 'Q' => 1168 1169 -- .. Quit how? 1170 case O is 1171 1172 -- ... with a 'Yes' Verdict: 1173 when 'Y' => 1174 -- Prohibited from within a loop or Subroutine: 1175 if Control_Stack_Not_Empty then 1176 E("Attempted to proclaim a 'Yes' Verdict" & 1177 " inside a Loop or Subroutine!"); 1178 end if; 1179 Verdict := Yes; 1180 1181 -- ... with a 'No' Verdict: 1182 when 'N' => 1183 Verdict := No; 1184 1185 -- ... with a 'Mu' Verdict: (permitted, but discouraged) 1186 when 'M' => 1187 IP_Next := IP; -- Force a 'Mu' Termination 1188 1189 -- ... with Debug Trace, and a 'Mu' Verdict: 1190 when 'D' => 1191 Print_Trace; 1192 IP_Next := IP; -- Force a 'Mu' Termination 1193 1194 -- ... with an explicit Tape-triggered fatal EGGOG! 1195 -- The 'QE' curtain call is intended strictly to signal 1196 -- catastrophic (e.g. iron) failure from within a Tape 1197 -- program ('cosmic ray' scenario) where a ~hardwired 1198 -- mechanism~ of any kind appears to have done something 1199 -- unexpected; or to abort on a failed test of the RNG; 1200 -- or similar hard-stop scenarios, where either physical 1201 -- iron, or basic FFA routine must be said to have failed, 1202 -- and the continued use of the system itself - dangerous. 1203 -- The use of 'QE' for any other purpose is discouraged; 1204 -- please do not use it to indicate failed decryption etc. 1205 when 'E' => 1206 -- Hard-stop with this eggog: 1207 E("Tape-triggered CATASTROPHIC ERROR! " & 1208 "Your iron and/or your build of Peh, " & 1209 "may be defective! Please consult " & 1210 "the author of this Tape."); 1211 1212 -- ... Unknown (Eggog): 1213 when others => 1214 Undefined_Prefix_Op; 1215 1216 end case; 1217 1218 --------------------------------------------------------- 1219 -- Zap... 1220 when 'Z' => 1221 1222 -- .. Zap what? 1223 case O is 1224 1225 -- ... Registers: 1226 when 'R' => 1227 -- If in Cutout, will zap only Cutout set of regs 1228 Zap_Registers; 1229 1230 -- ... Data Stack: 1231 when 'D' => 1232 Zap_Data_Stack; 1233 1234 -- ... Overflow Flag (if in Cutout, zaps CO_Flag) : 1235 when 'F' => 1236 Zap_Flag; 1237 1238 -- ... All Zappable State: 1239 when 'A' => 1240 Zap_Master; 1241 1242 when others => 1243 Undefined_Prefix_Op; 1244 1245 end case; 1246 1247 --------------------------------------------------------- 1248 -- Write into Register... 1249 when '$' => 1250 1251 -- Eggog if operator gave us a garbage Register name: 1252 if O not in RegNames then 1253 E("There is no Register '" & O & "' !"); 1254 end if; 1255 1256 -- Selected Register exists; move top FZ on stack into it: 1257 Want(1); 1258 if Use_CO_Registers then 1259 CO_Registers(O) := Stack(SP); -- use Cutout Register set 1260 else 1261 Registers(O) := Stack(SP); -- use ordinary set 1262 end if; 1263 Drop; 1264 1265 --------------------------------------------------------- 1266 -- Left... 1267 when 'L' => 1268 1269 -- Which L-op? 1270 case O is 1271 1272 -- ... Shift : 1273 when 'S' => 1274 Want(2); 1275 declare 1276 -- Number of bit positions to shift by: 1277 ShiftCount : FZBit_Index 1278 := FZBit_Index(FFA_FZ_Get_Head(Stack(SP))); 1279 begin 1280 FFA_FZ_Quiet_ShiftLeft(N => Stack(SP - 1), 1281 ShiftedN => Stack(SP - 1), 1282 Count => ShiftCount); 1283 end; 1284 Drop; 1285 1286 -- ... Rotate : 1287 when 'R' => 1288 E("Left-Rotate not yet defined!"); 1289 1290 -- ... 'Cutout' : 1291 -- Mark the LEFT SIDE of the 'Cutout' Tape segment; 1292 -- The Tape IN OR PRIOR to it will retain the ability to 1293 -- move directly into points PRIOR to THIS position 1294 -- on the Tape (i.e. where THIS Op had executed). 1295 -- Ops on Tape AFTER 'RC' mark can move INTO Cutout, 1296 -- but NOT directly into any position PRIOR to it. 1297 -- If 'LC' is executed, a 'RC' MUST occur before Tape end. 1298 -- FATAL if a 'LC' or 'RC' Op had previously executed. 1299 when 'C' => 1300 -- Eggog if we have ALREADY begun the Cutout somewhere: 1301 if Cutout_Begun then 1302 E("'LC' Op may only execute ONCE on a Tape!"); 1303 end if; 1304 -- Cutout defs are prohibited inside loops or Sub calls: 1305 if Control_Stack_Not_Empty then 1306 E("Attempted to execute 'LC' (Left-Cutout)" & 1307 " inside a Loop or Subroutine!"); 1308 end if; 1309 -- Set the START of the Cutout, and mark it 'begun': 1310 Cutout_Begun := True; 1311 Cutout.L := IP; 1312 1313 -- ... Unknown (Eggog): 1314 when others => 1315 Undefined_Prefix_Op; 1316 1317 end case; 1318 --------------------------------------------------------- 1319 -- Right... 1320 when 'R' => 1321 1322 -- Which R-op? 1323 case O is 1324 1325 -- ... Shift: 1326 when 'S' => 1327 Want(2); 1328 declare 1329 -- Number of bit positions to shift by: 1330 ShiftCount : FZBit_Index 1331 := FZBit_Index(FFA_FZ_Get_Head(Stack(SP))); 1332 begin 1333 FFA_FZ_Quiet_ShiftRight(N => Stack(SP - 1), 1334 ShiftedN => Stack(SP - 1), 1335 Count => ShiftCount); 1336 end; 1337 Drop; 1338 1339 -- ... Rotate: 1340 when 'R' => 1341 E("Right-Rotate not yet defined!"); 1342 1343 -- 'Right-Multiply', give only lower half of the product XY 1344 when '*' => 1345 Want(2); 1346 FFA_FZ_Low_Multiply(X => Stack(SP - 1), 1347 Y => Stack(SP), 1348 XY => Stack(SP - 1)); 1349 Drop; 1350 1351 -- ... 'Cutout' : 1352 -- Mark the RIGHT SIDE of the 'Cutout' Tape segment that 1353 -- began with 'LC', and permanently arms the Cutout. 1354 -- After THIS position, no IP_Next may be set which 1355 -- directly transfers control to a point PRIOR to 'LC'. 1356 -- FATAL if no 'LC' had executed to mark the LEFT SIDE. 1357 when 'C' => 1358 -- Eggog if we never marked the beginning with 'LC': 1359 if not Cutout_Begun then 1360 E("'RC' Op found, but no there was no prior 'LC' !"); 1361 end if; 1362 -- Eggog if we have already armed the Cutout: 1363 if Cutout_Armed then 1364 E("'RC' Op found, but the Cutout is already armed!"); 1365 end if; 1366 -- Cutout defs are prohibited inside loops or Sub calls: 1367 if Control_Stack_Not_Empty then 1368 E("Attempted to execute 'RC' (Right-Cutout)" & 1369 " inside a Loop or Subroutine!"); 1370 end if; 1371 -- Otherwise proceed to complete and arm the Cutout: 1372 Cutout.R := IP; 1373 Cutout_Armed := True; 1374 1375 -- ... Unknown (Eggog): 1376 when others => 1377 Undefined_Prefix_Op; 1378 1379 end case; 1380 --------------------------------------------------------- 1381 -- Modular... 1382 when 'M' => 1383 1384 -- Which M-op? 1385 case O is 1386 1387 -- ... Multiplication (Conventional) : 1388 when '*' => 1389 Want(3); 1390 MustNotZero(Stack(SP)); 1391 FFA_FZ_Modular_Multiply(X => Stack(SP - 2), 1392 Y => Stack(SP - 1), 1393 Modulus => Stack(SP), 1394 Product => Stack(SP - 2)); 1395 Drop; 1396 Drop; 1397 1398 -- ... Squaring (Conventional) : 1399 when 'S' => 1400 Want(2); 1401 MustNotZero(Stack(SP)); 1402 FFA_FZ_Modular_Square(X => Stack(SP - 1), 1403 Modulus => Stack(SP), 1404 Product => Stack(SP - 1)); 1405 Drop; 1406 1407 -- ... Exponentiation (Barrettronic) : 1408 when 'X' => 1409 Want(3); 1410 MustNotZero(Stack(SP)); 1411 FFA_FZ_Modular_Exponentiate(Base => Stack(SP - 2), 1412 Exponent => Stack(SP - 1), 1413 Modulus => Stack(SP), 1414 Result => Stack(SP - 2)); 1415 Drop; 1416 Drop; 1417 1418 -- ... Unknown (Eggog): 1419 when others => 1420 Undefined_Prefix_Op; 1421 1422 end case; 1423 --------------------------------------------------------- 1424 -- ... Unknown: (impossible per mechanics, but must handle case) 1425 when others => 1426 E("Undefined Prefix: " & Prefix); 1427 1428 end case; 1429 1430 end Op_Prefixed; 1431 1432 ------------------------------------------------------------------------ 1433 1434 -- Process a character in a proposed Subroutine Name: 1435 procedure SubName_Symbol(C : in Character) is 1436 begin 1437 case C is 1438 -- Attempt to INVOKE the named Subroutine: 1439 when '!' => 1440 -- Detect attempt to invoke a Sub with no Name: 1441 if IP = Proposed_Sub.Name.L then 1442 E("Attempted to invoke a nameless Subroutine!"); 1443 end if; 1444 -- Exit the Sub Name mode and enter Normal mode: 1445 Mode := Normal; 1446 -- Attempt to invoke the subroutine: 1447 Invoke_Named_Subroutine(Proposed_Sub.Name); 1448 1449 -- Attempt to read a body for a Subroutine Definition: 1450 when '@' => 1451 -- Detect attempt to define a Sub with no Name: 1452 if IP = Proposed_Sub.Name.L then 1453 E("Attempted to define a nameless Subroutine!"); 1454 end if; 1455 -- Save NEXT IP as the beginning of the proposed Body: 1456 Proposed_Sub.Payload.L := Next_IP_On_Tape; 1457 -- Exit the Name mode and enter Sub Body mode: 1458 Mode := SubBody; 1459 1460 -- Any permissible Symbol in a Subroutine Name: 1461 when '0' .. '9' | 'A' .. 'Z' | 'a' .. 'z' | '-' | '_' => 1462 -- Save IP as the potential end of the proposed Sub Name: 1463 Proposed_Sub.Name.R := IP; 1464 1465 when others => 1466 E("Symbol '" & C & "' is prohibited in a Subroutine Name !"); 1467 end case; 1468 end SubName_Symbol; 1469 1470 ------------------------------------------------------------------------ 1471 1472 -- Process a character in a proposed Subroutine Body: 1473 procedure SubBody_Symbol(C : in Character) is 1474 1475 -- Name of Proposed Subroutine (for eggogs) : 1476 Name : String 1477 := String(Tape(Proposed_Sub.Name.L .. Proposed_Sub.Name.R)); 1478 1479 begin 1480 case C is 1481 -- Subroutine Terminator: 1482 when ';' => 1483 -- Only takes effect if NOT in a Comment or Quote Block: 1484 if SubCommLevel = 0 and SubQuoteLevel = 0 then 1485 if SubCondLevel /= 0 then 1486 E("Conditional Return in Subroutine: '" 1487 & Name & "' is Prohibited!" & 1488 " (Please check for unbalanced '{'.)'"); 1489 end if; 1490 -- Now, Sub-Comm, Quote, and Cond levels are 0. 1491 -- The ';' becomes last Symbol of the new Sub's Body. 1492 -- Test for attempt to define a Sub with a null Body: 1493 if IP = Proposed_Sub.Payload.L then 1494 E("Null Body in Subroutine: '" & Name 1495 & "' is prohibited!"); 1496 end if; 1497 -- Intern this new Sub definition: 1498 Proposed_Sub.Payload.R := IP; 1499 -- Exit the Sub Body mode and enter Normal mode: 1500 Mode := Normal; 1501 -- Attempt to intern the Proposed Subroutine: 1502 Intern_Subroutine(Proposed_Sub); 1503 end if; 1504 1505 -- Begin-Comment inside a Subroutine Body: 1506 when '(' => 1507 SubCommLevel := SubCommLevel + 1; 1508 1509 -- End-Comment inside a Subroutine Body: 1510 when ')' => 1511 -- If cannot drop Sub Comment level: 1512 if SubCommLevel = 0 then 1513 E("Unbalanced ')' in Body of Subroutine: '" 1514 & Name & "' !"); 1515 end if; 1516 SubCommLevel := SubCommLevel - 1; 1517 1518 -- Begin-Quote inside a Subroutine Body: 1519 when '[' => 1520 -- Ignore if Commented: 1521 if SubCommLevel = 0 then 1522 SubQuoteLevel := SubQuoteLevel + 1; 1523 end if; 1524 1525 -- End-Quote inside a Subroutine Body: 1526 when ']' => 1527 -- Ignore if Commented: 1528 if SubCommLevel = 0 then 1529 -- If cannot drop Sub Quote level: 1530 if SubQuoteLevel = 0 then 1531 E("Unbalanced ']' in Body of Subroutine: '" 1532 & Name & "' !"); 1533 end if; 1534 SubQuoteLevel := SubQuoteLevel - 1; 1535 end if; 1536 1537 -- Begin-Conditional inside a Subroutine Body: 1538 when '{' => 1539 -- Ignore if Commented or Quoted: 1540 if SubCommLevel = 0 and SubQuoteLevel = 0 then 1541 SubCondLevel := SubCondLevel + 1; 1542 end if; 1543 1544 -- End-Conditional inside a Subroutine Body: 1545 when '}' => 1546 -- Ignore if Commented or Quoted: 1547 if SubCommLevel = 0 and SubQuoteLevel = 0 then 1548 -- If cannot drop Sub Conditional level: 1549 if SubCondLevel = 0 then 1550 E("Unbalanced '}' in Body of Subroutine: '" 1551 & Name & "' !"); 1552 end if; 1553 SubCondLevel := SubCondLevel - 1; 1554 end if; 1555 1556 -- All other Symbols have no special effect in Sub Body : 1557 when others => 1558 null; -- Stay in Body mode until we see the ';'. 1559 end case; 1560 end SubBody_Symbol; 1561 1562 1563 ------------------------------------------------------------------------ 1564 1565 -- All Peh Symbols begin their processing here : 1566 procedure Op(C : in Character) is 1567 begin 1568 1569 -- See whether we are inside a 'Block' : 1570 1571 -- ... in a Comment block: 1572 if CommLevel > 0 then 1573 case C is 1574 when ')' => -- Drop a nesting level: 1575 CommLevel := CommLevel - 1; 1576 when '(' => -- Add a nesting level: 1577 CommLevel := CommLevel + 1; 1578 when others => 1579 null; -- Other symbols have no effect at all 1580 end case; 1581 1582 -- ... in a Quote block: 1583 elsif QuoteLevel > 0 then 1584 case C is 1585 when ']' => -- Drop a nesting level: 1586 QuoteLevel := QuoteLevel - 1; 1587 when '[' => -- Add a nesting level: 1588 QuoteLevel := QuoteLevel + 1; 1589 when others => 1590 null; -- Other symbols have no effect on the level 1591 end case; 1592 1593 -- If we aren't the mode-exiting ']', print current symbol: 1594 if QuoteLevel > 0 then 1595 Write_Char(C); 1596 end if; 1597 1598 --- ... in a ~taken~ Conditional branch: 1599 elsif CondLevel > 0 then 1600 case C is 1601 when '}' => -- Drop a nesting level: 1602 CondLevel := CondLevel - 1; 1603 1604 -- If we exited the Conditional as a result, 1605 -- we push a 1 to trigger the possible 'else' clause: 1606 if CondLevel = 0 then 1607 Push; 1608 FFA_WBool_To_FZ(1, Stack(SP)); 1609 end if; 1610 1611 when '{' => -- Add a nesting level: 1612 CondLevel := CondLevel + 1; 1613 1614 when others => 1615 null; -- Other symbols have no effect on the level 1616 end case; 1617 1618 else 1619 --- ... we are not inside a 'Block' : 1620 1621 case Mode is 1622 1623 --- ... a character in a proposed Subroutine Name: 1624 when SubName => 1625 SubName_Symbol(C); 1626 1627 --- ... a character in a proposed Subroutine Body: 1628 when SubBody => 1629 SubBody_Symbol(C); 1630 1631 --- ... the second character of a Prefixed Op: 1632 when PrefixOp => 1633 -- Drop prefix-op hammer, until another prefix-op cocks it: 1634 Mode := Normal; 1635 1636 -- Dispatch this op, where prefix is the preceding character 1637 Op_Prefixed(Prefix => PrevC, O => C); 1638 1639 -- This is a Normal Op... 1640 when Normal => 1641 -- ... so proceed with the normal rules: 1642 Op_Normal(C); 1643 1644 -- Save the current Symbol as a possible prefix: 1645 PrevC := C; 1646 1647 end case; 1648 1649 end if; 1650 end Op; 1651 1652 ------------------------------------------------------------------------ 1653 1654 ----------------------------- 1655 -- Start of Tape Execution -- 1656 ----------------------------- 1657 1658 begin 1659 -- Reset all resettable state: 1660 Zap_Master; 1661 Zap_Cutout_Registers; 1662 1663 -- Execution begins with the first Op on the Tape: 1664 IP := Tape_Positions'First; 1665 1666 loop 1667 1668 -- If current Op is NOT the last Op on the Tape: 1669 if not Last_Tape_Symbol then 1670 1671 -- ... then default successor of the current Op is the next one: 1672 IP_Next := IP + 1; 1673 1674 else 1675 1676 -- ... but if no 'next' Op exists, or quit-with-Mu, we stay put: 1677 IP_Next := IP; -- ... this will trigger an exit from the loop. 1678 1679 end if; 1680 1681 -- Advance Odometer for every Op (incl. prefixes, in comments, etc) : 1682 Ticks := Ticks + 1; 1683 1684 -- Execute the Op at the current IP: 1685 Op(Tape(IP)); 1686 1687 -- Halt when... 1688 exit when 1689 Verdict /= Mu or -- Got a Verdict, or... 1690 IP_Next = IP or -- Reached the end of the Tape, or... 1691 Exhausted_Life; -- Exhausted Life. 1692 1693 -- If the Cutout has been armed on this Tape, then enforce it: 1694 if Cutout_Prohibits(IP_Next) then 1695 E("Attempted movement to IP:" & Tape_Positions'Image(IP_Next) & 1696 " violates the Cutout!"); 1697 end if; 1698 1699 -- We did not halt yet, so select the IP of the next Op to fetch: 1700 IP := IP_Next; 1701 1702 end loop; 1703 1704 -- At this point, the Tape has halted. 1705 1706 ------------------------------------------------------------------ 1707 -- Termination in a Mode other than 'Normal' triggers a Eggog Verdict: 1708 1709 case Mode is 1710 1711 -- Unclosed Subroutine Name at Tape's End: 1712 when SubName => 1713 E("The Subroutine Name at IP:" 1714 & Tape_Positions'Image(Proposed_Sub.Name.L) 1715 & " is Unterminated!"); 1716 1717 -- Unclosed Subroutine Body at Tape's End: 1718 when SubBody => 1719 E("The Body of Subroutine: '" 1720 & String(Tape(Proposed_Sub.Name.L .. Proposed_Sub.Name.R)) 1721 & "' is Unterminated!"); 1722 1723 -- Incomplete Prefix Op at Tape's End: 1724 when PrefixOp => 1725 E("Prefix Op: '" & PrevC & "' is Unterminated at End of Tape!"); 1726 1727 -- This is the expected Mode at Tape's End: 1728 when Normal => 1729 null; 1730 1731 end case; 1732 1733 -- Unclosed Cutout triggers a Eggog Verdict: 1734 if Cutout_Begun and not Cutout_Armed then 1735 E("The Cutout declaration 'LC' at IP:" 1736 & Tape_Positions'Image(Cutout.L) & " is Unterminated!"); 1737 end if; 1738 1739 ------------------------------------------------------------------ 1740 -- The following types of Unclosed Blocks trigger a Warning: 1741 1742 if CommLevel > 0 then 1743 Achtung("WARNING: Tape terminated with an unclosed Comment!"); 1744 end if; 1745 1746 if QuoteLevel > 0 then 1747 Achtung("WARNING: Tape terminated with an unclosed Quote!"); 1748 end if; 1749 1750 if CondLevel > 0 then 1751 Achtung("WARNING: Tape terminated with an unclosed Conditional!"); 1752 end if; 1753 1754 ------------------------------------------------------------------ 1755 -- Non-empty stacks, after Tape has halted, also trigger a Warning: 1756 1757 -- Warn operator if we terminated with a non-empty Control Stack. 1758 -- This situation ought to be considered poor style in a Peh Tape; 1759 -- for clarity, Verdicts should be returned from a place near 1760 -- the visually-apparent end of a Tape. However, this is not mandatory. 1761 if Control_Stack_Not_Empty then 1762 Achtung("WARNING: Tape terminated inside a Loop or Subroutine!"); 1763 end if; 1764 1765 -- Warn operator if we terminated with a non-empty Data Stack: 1766 if Data_Stack_Not_Empty then 1767 Achtung("WARNING: Tape terminated with a non-empty Data Stack!"); 1768 end if; 1769 1770 ------------------------------------------------------------------ 1771 1772 -- We're done with the Tape and any Warnings, so clear the state: 1773 Zap_Master; 1774 Zap_Cutout_Registers; 1775 1776 -- Return the Verdict: 1777 return Verdict; 1778 1779 end Peh_Machine; 1780 1781 end FFA_Calc;