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:
20
HISTORY.markdown
Normal file
20
HISTORY.markdown
Normal 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.
|
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
.
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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()
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user