diff --git a/HISTORY.markdown b/HISTORY.markdown new file mode 100644 index 0000000..49c4764 --- /dev/null +++ b/HISTORY.markdown @@ -0,0 +1,20 @@ +History of SixtyPical +===================== + +0.1 +--- + +Initial inspired-but-messy version implemented in Haskell. + +0.2 +--- + +A complete reboot of SixtyPical 0.1. The reference implementation was +rewritten in Python. The language was much simplified. The aim was to get the +analysis completely right before adding more sophisticated and useful features +in future versions. + +0.3 +--- + +Added ability to compile to 6502 machine code and output a `PRG` file. diff --git a/README.markdown b/README.markdown index e0aee04..4e14878 100644 --- a/README.markdown +++ b/README.markdown @@ -21,6 +21,7 @@ Documentation * Design Goals — coming soon. * [SixtyPical specification](doc/SixtyPical.md) +* [SixtyPical history](HISTORY.md) * [Literate test suite for SixtyPical syntax](tests/SixtyPical Syntax.md) * [Literate test suite for SixtyPical execution](tests/SixtyPical Execution.md) * [Literate test suite for SixtyPical analysis](tests/SixtyPical Analysis.md) @@ -32,6 +33,7 @@ TODO For 0.4: +* `if not`. * `while` loops. * `repeat` loops. * explicitly-addressed memory locations @@ -40,7 +42,6 @@ For 0.5: * add line number (or at least routine name) to error messages. * hexadecimal literals. -* `if not`. * 6502-mnemonic aliases (`sec`, `clc`) * other handy aliases (`eq` for `z`, etc.) * source code comments. diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 7fe764f..868743b 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -293,6 +293,28 @@ it is treated like an empty block. * It is illegal if any location initialized at the end of the true-branch is not initialized at the end of the false-branch, and vice versa. +### repeat ### + + repeat { + + } until + +Executes the block repeatedly until the src (observed at the end of the +execution of the block) is non-zero. The block is always executed as least +once. + +* It is illegal if any memory location is uninitialized at the exit of + the loop when that memory location is initialized at the start of + the loop. + +To simulate a "while" loop, use an `if` internal to the block, like + + repeat { + cmp y, 25 + if z { + } + } until z + Grammar ------- @@ -321,4 +343,6 @@ Grammar | "inc" LocExpr | "dec" LocExpr | "call" RoutineIdent - | "if" LocExpr Block ["else" Block]. + | "if" ["not"] LocExpr Block ["else" Block] + | "repeat" Block "until" ["not"] LocExpr + . diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 656bf2b..a885d50 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -185,5 +185,15 @@ def analyze_instr(instr, context, routines): for ref in context2.each_initialized(): context1.assert_initialized(ref, exception_class=InconsistentInitializationError) context.set_from(context1) + elif opcode == 'repeat': + # it will always be executed at least once, so analyze it having + # been executed the first time. + analyze_block(instr.block, context, routines) + + # now analyze it having been executed a second time, with the context + # of it having already been executed. + analyze_block(instr.block, context, routines) + + # NB I *think* that's enough... but it might not be? else: raise NotImplementedError(opcode) diff --git a/src/sixtypical/evaluator.py b/src/sixtypical/evaluator.py index 07c108f..19c9a52 100644 --- a/src/sixtypical/evaluator.py +++ b/src/sixtypical/evaluator.py @@ -149,5 +149,9 @@ def eval_instr(instr, context, routines): eval_block(instr.block1, context, routines) elif instr.block2: eval_block(instr.block2, context, routines) + elif opcode == 'repeat': + eval_block(instr.block, context, routines) + while context.get(src) == 0: + eval_block(instr.block, context, routines) else: raise NotImplementedError diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index d5b1b2c..4d459a8 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -167,12 +167,26 @@ class Parser(object): def instr(self): if self.scanner.consume('if'): + inverted = False + if self.scanner.consume('not'): + inverted = True src = self.locexpr() block1 = self.block() block2 = None if self.scanner.consume('else'): block2 = self.block() - return Instr(opcode='if', dest=None, src=src, block1=block1, block2=block2) + return Instr(opcode='if', dest=None, src=src, + block1=block1, block2=block2, inverted=inverted) + elif self.scanner.consume('repeat'): + inverted = False + src = None + block = self.block() + if self.scanner.consume('until'): + if self.scanner.consume('not'): + inverted = True + src = self.locexpr() + return Instr(opcode='repeat', dest=None, src=src, + block=block, inverted=inverted) elif self.scanner.token in ("ld", "add", "sub", "cmp", "and", "or", "xor"): opcode = self.scanner.token self.scanner.scan() diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 1a1f5b4..8aae69d 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -779,3 +779,56 @@ An `if` with a single block is analyzed as if it had an empty `else` block. | } | } = ok + +### repeat ### + +Repeat loop. + + | routine main + | outputs x, y, n, z, c + | { + | ld x, 0 + | ld y, 15 + | repeat { + | inc x + | inc y + | cmp x, 10 + | } until z + | } + = ok + +You can initialize something inside the loop that was uninitialized outside. + + | routine main + | outputs x, y, n, z, c + | { + | ld x, 0 + | repeat { + | ld y, 15 + | inc x + | cmp x, 10 + | } until z + | } + = ok + +But you can't UNinitialize something at the end of the loop that you need +initialized at the start. + + | routine foo + | trashes y + | { + | } + | + | routine main + | outputs x, y, n, z, c + | { + | ld x, 0 + | ld y, 15 + | repeat { + | inc x + | inc y + | call foo + | cmp x, 10 + | } until z + | } + ? UninitializedAccessError: y diff --git a/tests/SixtyPical Execution.md b/tests/SixtyPical Execution.md index 968e7f8..aa614be 100644 --- a/tests/SixtyPical Execution.md +++ b/tests/SixtyPical Execution.md @@ -369,3 +369,22 @@ If. = x: 2 = y: 0 = z: 0 + +Repeat loop. + + | routine main { + | ld x, 0 + | ld y, 15 + | repeat { + | inc x + | inc y + | cmp x, 10 + | } until z + | } + = a: 0 + = c: 0 + = n: 0 + = v: 0 + = x: 10 + = y: 25 + = z: 1 diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index b852528..a424180 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -39,3 +39,38 @@ Extern routines | trashes x | @ 65487 = ok + +If with not + + | routine foo { + | ld y, 0 + | cmp y, 10 + | if not z { + | inc y + | cmp y, 10 + | } + | } + = ok + +Repeat loop + + | routine foo { + | ld y, 0 + | repeat { + | inc y + | cmp y, 10 + | } until z + | } + = ok + +"While" loop + + | routine foo inputs y { + | repeat { + | cmp y, 10 + | if not z { + | inc y + | } + | } + | } + = ok