1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-08-10 06:24:56 +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. * Design Goals — coming soon.
* [SixtyPical specification](doc/SixtyPical.md) * [SixtyPical specification](doc/SixtyPical.md)
* [SixtyPical history](HISTORY.md)
* [Literate test suite for SixtyPical syntax](tests/SixtyPical Syntax.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 execution](tests/SixtyPical Execution.md)
* [Literate test suite for SixtyPical analysis](tests/SixtyPical Analysis.md) * [Literate test suite for SixtyPical analysis](tests/SixtyPical Analysis.md)
@@ -32,6 +33,7 @@ TODO
For 0.4: For 0.4:
* `if not`.
* `while` loops. * `while` loops.
* `repeat` loops. * `repeat` loops.
* explicitly-addressed memory locations * explicitly-addressed memory locations
@@ -40,7 +42,6 @@ For 0.5:
* add line number (or at least routine name) to error messages. * add line number (or at least routine name) to error messages.
* hexadecimal literals. * hexadecimal literals.
* `if not`.
* 6502-mnemonic aliases (`sec`, `clc`) * 6502-mnemonic aliases (`sec`, `clc`)
* other handy aliases (`eq` for `z`, etc.) * other handy aliases (`eq` for `z`, etc.)
* source code comments. * 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 * 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. 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 Grammar
------- -------
@@ -321,4 +343,6 @@ Grammar
| "inc" LocExpr | "inc" LocExpr
| "dec" LocExpr | "dec" LocExpr
| "call" RoutineIdent | "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(): for ref in context2.each_initialized():
context1.assert_initialized(ref, exception_class=InconsistentInitializationError) context1.assert_initialized(ref, exception_class=InconsistentInitializationError)
context.set_from(context1) 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: else:
raise NotImplementedError(opcode) raise NotImplementedError(opcode)

View File

@@ -149,5 +149,9 @@ def eval_instr(instr, context, routines):
eval_block(instr.block1, context, routines) eval_block(instr.block1, context, routines)
elif instr.block2: elif instr.block2:
eval_block(instr.block2, context, routines) 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: else:
raise NotImplementedError raise NotImplementedError

View File

@@ -167,12 +167,26 @@ class Parser(object):
def instr(self): def instr(self):
if self.scanner.consume('if'): if self.scanner.consume('if'):
inverted = False
if self.scanner.consume('not'):
inverted = True
src = self.locexpr() src = self.locexpr()
block1 = self.block() block1 = self.block()
block2 = None block2 = None
if self.scanner.consume('else'): if self.scanner.consume('else'):
block2 = self.block() 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"): elif self.scanner.token in ("ld", "add", "sub", "cmp", "and", "or", "xor"):
opcode = self.scanner.token opcode = self.scanner.token
self.scanner.scan() 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 = 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 = x: 2
= y: 0 = y: 0
= z: 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 | trashes x
| @ 65487 | @ 65487
= ok = 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