(* l08 - expressions with control statements *) program = [ 'CONST' { id '=' expression ';' } ] [ 'VAR' { id ':' id ';' } ] 'BEGIN' stateseq 'END' '.' . stateseq = statement { ';' statement } '.' statement = [ assignment | 'WRITE' expression | repstate | whilestate | ifstate ] . assignment = id ':=' expression . repstate = 'REPEAT' stateseq 'UNTIL' expression 'END' . whilestate = 'WHILE' expression 'DO' stateseq 'END' . ifstate = 'IF' expression 'THEN' stateseq { 'ELSIF' expression 'THEN' stateseq } [ 'ELSE' stateseq ] 'END' . expression = simplexpr [ relop simplexpr ] . simplexpr = [ '+' | '-' ] term { addop term } . term = factor { mulop factor } . factor = number | id | '(' expression ')' | '~' factor . relop = '=' | '#' | '<>' | '<' | '<=' | '>' | '>=' . addop = '+' | '-' | '|' . mulop = '*' | '/' | '&' . (* l08 = l07 + control statements 2010-08-31, v00: The most simple control statement is repeat-until because it involves an backward jump only (no fixup needed): REPEAT statseq UNTIL expr END --> start: statseq expr jpf start The other control statements require fixups: WHILE expr DO statseq END --> start: expr jpf end statseq end: jmp start IF expr0 THEN statseq0 ELSIF expr1 THEN statseq1 ELSE statseq2 END --> expr0 jpf e1 statseq0 jmp end e1: expr1 jpf e2 statseq1 jmp end e2: statseq2 end: There's no simple CASE implementation different from ELSIF. The LOOP statement requires fixup from outside, so at least Statement would require a return address -- I skip this for now. 2010-09-07: Still, boolean expressions are not implemented as conditionals. Having conditional branching it's possible now, but do I need it? Something like "( a # NIL ) & a^" and "f() & f()" is still unexpressable, so the answer is (still) "no". But I will need it some day, so how could it be implemented? factor0 & factor1 --> code( factor0 ) jpf false code( factor1 ) jpf false true: val 1 jmp end false: val 0 end: Shure, and how about constant expressions? Not only the emitted code beeing sub-optimal (compared to "val x" generated with v00), it fails on constant declarations (CONST x=TRUE&TRUE) since no emission should be done. The problem is to delay the "jpf" until the first emission from factor1. Then, all generator procedures must be capable of returning a fixup address. In other words, the generator module would need a heavy redesign -- I skip this for now. 2010-09-10, v01: Although the generator doesn't care about types, for parser simplification, the type field has been put to TItemDesc (and TType to Gen.md). 2010-09-12, v02: The strange generator interface (see plx07-04) got fixed by hiding the directly architecture dependent procedures Val, Lod and Sto behind Op* and Asg; parameters are passed as item(s). As (unexploited) side effect, loading of variables is (somewhat) delayed. 2010-09-23, v03: 1. Fixed syntax definition above (forgotten 'END' in program ). 2. Added nested comments (scanner). 3. Made jumps on constant conditions unconditional (generator). 4. Added final program listing. 5. Removed immediate execution (discussion below). Until now, immediate execution stops exactly on jumps to the reserved address Comp0.aNone. However, using the program memory for fixup address storage leads to unfixed addresses # aNone, so execution does currently not stop there. And, indeed, v00..v02 fail immediate execution on BEGIN IF FALSE THEN ELSIF TRUE THEN ELSE END END. where the jump over ELSE is taken unfixed. When introducing control statements I kept immediate execution aboard only because it came nearly for free: It just loaded the interpreter with the handling of unfixed addresses. But with ELSIF (and boolean expressions) it becomes too expensive now. - Let's discuss some possible solutions: 1. Instead of a single reserved address we could use a designated bit (LSB) for marking unfixed addresses, the other bits could (still) serve for linking (still) in program memory. But this would halve the valid address space, which I don't really like - I don't even like a single reserved address, I (still) only live with it. And it will definitely further load the interpreter and the generator as well. 2. We could keep a single invalid address while storing the fixup list somewhere else; a stack inside the generator would do for now. This way even the (potentially expensive) read access to the program store could be omitted. But will a stack always suffice? Under what conditions? 3. We could use special functions for marking unfixed commands while fixing up not only addresses but the functions as well. But that poisons the function space and further loads the interpreter. 4. Instead of triggering execution implicitely with Comp.Write() we could call Comp.Continue() from the outside; execution of unfixed commands is prevented by calling from "save places" only, i.e. at times where all commands are fixed. Unfortunately, this is generally unknown inside the generator; even at the end of a Fixup() some older commands may still be unfixed. We had to trigger at the end of Statement(). Well, then we could go just one step further, calling Exec() just at the end of the program. So, immediate execution is dropped right here. 2010-10-09, v04: As learned from PL/0, relations put their (boolean) results onto the stack and conditional jumps pop them off. For boolean expressions, however, the result is needed and must be expensively regenerated (see code above). The latter could be avoided by conditional jumps leaving the value on stack, but then it must be removed by hand at other places - not only for control statements but for boolean expressions, also. The easiest compromise is to introduce a special result register for relations (cond) together with another item mode (mCond). Conditions must be saved (put to stack) before another item may enter mCond, i.e. inside binary operations -> Op1(). Still, boolean expressions are fully evaluated. 2010-11-11, v05: Short boolean evaluation implemented. *)