1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-14 08:29:33 +00:00

Spec, parse, evaluate, and analyze repeat blocks.

This commit is contained in:
Chris Pressey 2015-10-18 13:37:35 +01:00
parent 24273f1344
commit f7eb0d48a8
9 changed files with 183 additions and 3 deletions

20
HISTORY.markdown Normal file
View File

@ -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.

View File

@ -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.

View File

@ -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 {
<block>
} until <src-memory-location>
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
.

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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