mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-02-10 08:30:38 +00:00
Merge.
This commit is contained in:
commit
ed44a8c1b9
@ -33,12 +33,17 @@ TODO
|
||||
|
||||
For 0.6:
|
||||
|
||||
* `call` vector (generates an indirect JMP.)
|
||||
* `goto` (tail call) a routine or a vector.
|
||||
* A more involved demo for the C64 — one that sets up an interrupt?
|
||||
* `call` vector (generates an JSR to a trampoline that does indirect JMP.)
|
||||
* `goto` (tail call) a a vector.
|
||||
* add routine name to error messages.
|
||||
* routines shouldn't need to be listed as inputs.
|
||||
|
||||
For 0.7:
|
||||
|
||||
* A more involved demo for the C64 — one that sets up an interrupt?
|
||||
|
||||
For 0.8:
|
||||
|
||||
* `word` type.
|
||||
* `trash` instruction.
|
||||
* zero-page memory locations.
|
||||
@ -47,7 +52,6 @@ For 0.7:
|
||||
At some point...
|
||||
|
||||
* `interrupt` routines.
|
||||
* add line number (or at least routine name) to error messages.
|
||||
* 6502-mnemonic aliases (`sec`, `clc`)
|
||||
* other handy aliases (`eq` for `z`, etc.)
|
||||
* have `copy` instruction able to copy a constant to a user-def mem loc, etc.
|
||||
|
@ -18,8 +18,8 @@ import sys
|
||||
import traceback
|
||||
|
||||
from sixtypical.parser import Parser
|
||||
from sixtypical.evaluator import eval_program
|
||||
from sixtypical.analyzer import analyze_program
|
||||
from sixtypical.evaluator import Evaluator
|
||||
from sixtypical.analyzer import Analyzer
|
||||
from sixtypical.emitter import Emitter, Byte, Word
|
||||
from sixtypical.compiler import Compiler
|
||||
|
||||
@ -50,12 +50,13 @@ if __name__ == '__main__':
|
||||
|
||||
for filename in args:
|
||||
text = open(filename).read()
|
||||
p = Parser(text)
|
||||
program = p.program()
|
||||
parser = Parser(text)
|
||||
program = parser.program()
|
||||
|
||||
if options.analyze:
|
||||
try:
|
||||
analyze_program(program)
|
||||
analyzer = Analyzer()
|
||||
analyzer.analyze_program(program)
|
||||
except Exception as e:
|
||||
if options.traceback:
|
||||
raise
|
||||
@ -88,5 +89,5 @@ if __name__ == '__main__':
|
||||
emitter.serialize(fh)
|
||||
|
||||
if options.execute:
|
||||
context = eval_program(program)
|
||||
context = Evaluator().eval_program(program)
|
||||
print str(context)
|
||||
|
@ -363,16 +363,20 @@ copy more general types of data (for example, vectors,) and it trashes the
|
||||
After execution, dest is considered initialized, and `z` and `n`, and
|
||||
`a` are considered uninitialized.
|
||||
|
||||
### goto ###
|
||||
|
||||
TBW
|
||||
|
||||
Grammar
|
||||
-------
|
||||
|
||||
Program ::= {Defn} {Routine}.
|
||||
Defn ::= Type NewIdent [Constraints] ["@" WordConst].
|
||||
Defn ::= Type Ident<new> [Constraints] ["@" WordConst].
|
||||
Type ::= "byte" ["table"] | "vector"
|
||||
Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
|
||||
Routine ::= "routine" NewIdent Constraints (Block | "@" WordConst).
|
||||
Routine ::= "routine" Ident<new> Constraints (Block | "@" WordConst).
|
||||
LocExprs::= LocExpr {"," LocExpr}.
|
||||
LocExpr ::= Register | Flag | LitByte | DefnIdent.
|
||||
LocExpr ::= Register | Flag | LitByte | Ident.
|
||||
Register::= "a" | "x" | "y".
|
||||
Flag ::= "c" | "z" | "n" | "v".
|
||||
LitByte ::= "0" ... "255".
|
||||
@ -390,7 +394,8 @@ Grammar
|
||||
| "shr" LocExpr
|
||||
| "inc" LocExpr
|
||||
| "dec" LocExpr
|
||||
| "call" RoutineIdent
|
||||
| "call" Ident<routine>
|
||||
| "goto" Ident<executable>
|
||||
| "if" ["not"] LocExpr Block ["else" Block]
|
||||
| "repeat" Block ("until" ["not"] LocExpr | "forever")
|
||||
| "copy" LocExpr "," LocExpr ["+" LocExpr]
|
||||
|
15
eg/goto.60p
Normal file
15
eg/goto.60p
Normal file
@ -0,0 +1,15 @@
|
||||
routine chrout
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
||||
|
||||
routine bar trashes a, z, n {
|
||||
ld a, 66
|
||||
call chrout
|
||||
}
|
||||
|
||||
routine main trashes a, z, n {
|
||||
ld a, 65
|
||||
call chrout
|
||||
goto bar
|
||||
}
|
10
eg/vector.60p
Normal file
10
eg/vector.60p
Normal file
@ -0,0 +1,10 @@
|
||||
vector foo outputs x trashes z, n
|
||||
|
||||
routine bar outputs x trashes z, n {
|
||||
ld x, 200
|
||||
}
|
||||
|
||||
routine main inputs bar outputs x, foo trashes a, z, n {
|
||||
copy bar, foo
|
||||
call foo
|
||||
}
|
@ -41,6 +41,10 @@ class IncompatibleConstraintsError(StaticAnalysisError):
|
||||
pass
|
||||
|
||||
|
||||
class IllegalJumpError(StaticAnalysisError):
|
||||
pass
|
||||
|
||||
|
||||
class Context():
|
||||
"""
|
||||
A location is touched if it was changed (or even potentially
|
||||
@ -126,135 +130,164 @@ class Context():
|
||||
self.set_touched(*refs)
|
||||
self.set_meaningful(*refs)
|
||||
|
||||
def analyze_program(program):
|
||||
assert isinstance(program, Program)
|
||||
routines = {r.name: r for r in program.routines}
|
||||
for routine in program.routines:
|
||||
analyze_routine(routine, routines)
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
def analyze_routine(routine, routines):
|
||||
assert isinstance(routine, Routine)
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return
|
||||
type = routine.location.type
|
||||
context = Context(type.inputs, type.outputs, type.trashes)
|
||||
analyze_block(routine.block, context, routines)
|
||||
for ref in type.outputs:
|
||||
context.assert_meaningful(ref, exception_class=UninitializedOutputError)
|
||||
for ref in context.each_touched():
|
||||
if ref not in type.outputs and ref not in type.trashes:
|
||||
raise IllegalWriteError(ref.name)
|
||||
def __init__(self):
|
||||
self.current_routine = None
|
||||
self.has_encountered_goto = False
|
||||
|
||||
def analyze_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
routines = {r.name: r for r in program.routines}
|
||||
for routine in program.routines:
|
||||
self.analyze_routine(routine, routines)
|
||||
|
||||
def analyze_block(block, context, routines):
|
||||
assert isinstance(block, Block)
|
||||
for i in block.instrs:
|
||||
analyze_instr(i, context, routines)
|
||||
def analyze_routine(self, routine, routines):
|
||||
assert isinstance(routine, Routine)
|
||||
self.current_routine = routine
|
||||
self.has_encountered_goto = False
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return
|
||||
type = routine.location.type
|
||||
context = Context(type.inputs, type.outputs, type.trashes)
|
||||
self.analyze_block(routine.block, context, routines)
|
||||
if not self.has_encountered_goto:
|
||||
for ref in type.outputs:
|
||||
context.assert_meaningful(ref, exception_class=UninitializedOutputError)
|
||||
for ref in context.each_touched():
|
||||
if ref not in type.outputs and ref not in type.trashes:
|
||||
raise IllegalWriteError(ref.name)
|
||||
self.current_routine = None
|
||||
|
||||
def analyze_block(self, block, context, routines):
|
||||
assert isinstance(block, Block)
|
||||
for i in block.instrs:
|
||||
if self.has_encountered_goto:
|
||||
raise IllegalJumpError(i)
|
||||
self.analyze_instr(i, context, routines)
|
||||
|
||||
def analyze_instr(instr, context, routines):
|
||||
assert isinstance(instr, Instr)
|
||||
opcode = instr.opcode
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
if opcode == 'ld':
|
||||
if instr.index:
|
||||
if src.type == TYPE_BYTE_TABLE and dest.type == TYPE_BYTE:
|
||||
def analyze_instr(self, instr, context, routines):
|
||||
assert isinstance(instr, Instr)
|
||||
opcode = instr.opcode
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
if opcode == 'ld':
|
||||
if instr.index:
|
||||
if src.type == TYPE_BYTE_TABLE and dest.type == TYPE_BYTE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError((src, dest))
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'st':
|
||||
if instr.index:
|
||||
if src.type == TYPE_BYTE and dest.type == TYPE_BYTE_TABLE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError((src, dest))
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest)
|
||||
elif opcode in ('add', 'sub'):
|
||||
context.assert_meaningful(src, dest, FLAG_C)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
elif opcode in ('inc', 'dec'):
|
||||
context.assert_meaningful(dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'cmp':
|
||||
context.assert_meaningful(src, dest)
|
||||
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
|
||||
elif opcode in ('and', 'or', 'xor'):
|
||||
context.assert_meaningful(src, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode in ('shl', 'shr'):
|
||||
context.assert_meaningful(dest, FLAG_C)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
||||
elif opcode == 'call':
|
||||
type = instr.location.type
|
||||
for ref in type.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
elif opcode == 'if':
|
||||
context1 = context.clone()
|
||||
context2 = context.clone()
|
||||
self.analyze_block(instr.block1, context1, routines)
|
||||
if instr.block2 is not None:
|
||||
self.analyze_block(instr.block2, context2, routines)
|
||||
# TODO may we need to deal with touched separately here too?
|
||||
# probably not; if it wasn't meaningful in the first place, it
|
||||
# doesn't really matter if you modified it or not, coming out.
|
||||
for ref in context1.each_meaningful():
|
||||
context2.assert_meaningful(ref, exception_class=InconsistentInitializationError)
|
||||
for ref in context2.each_meaningful():
|
||||
context1.assert_meaningful(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.
|
||||
self.analyze_block(instr.block, context, routines)
|
||||
|
||||
# now analyze it having been executed a second time, with the context
|
||||
# of it having already been executed.
|
||||
self.analyze_block(instr.block, context, routines)
|
||||
|
||||
# NB I *think* that's enough... but it might not be?
|
||||
elif opcode == 'copy':
|
||||
# check that their types are basically compatible
|
||||
if src.type == dest.type:
|
||||
pass
|
||||
elif isinstance(src.type, ExecutableType) and \
|
||||
isinstance(dest.type, VectorType):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError((src, dest))
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'st':
|
||||
if instr.index:
|
||||
if src.type == TYPE_BYTE and dest.type == TYPE_BYTE_TABLE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
elif src.type != dest.type:
|
||||
raise TypeMismatchError((src, dest))
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest)
|
||||
elif opcode in ('add', 'sub'):
|
||||
context.assert_meaningful(src, dest, FLAG_C)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V)
|
||||
elif opcode in ('inc', 'dec'):
|
||||
context.assert_meaningful(dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'cmp':
|
||||
context.assert_meaningful(src, dest)
|
||||
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
|
||||
elif opcode in ('and', 'or', 'xor'):
|
||||
context.assert_meaningful(src, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode in ('shl', 'shr'):
|
||||
context.assert_meaningful(dest, FLAG_C)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
||||
elif opcode == 'call':
|
||||
type = instr.location.type
|
||||
for ref in type.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
elif opcode == 'if':
|
||||
context1 = context.clone()
|
||||
context2 = context.clone()
|
||||
analyze_block(instr.block1, context1, routines)
|
||||
if instr.block2 is not None:
|
||||
analyze_block(instr.block2, context2, routines)
|
||||
# TODO may we need to deal with touched separately here too?
|
||||
# probably not; if it wasn't meaningful in the first place, it
|
||||
# doesn't really matter if you modified it or not, coming out.
|
||||
for ref in context1.each_meaningful():
|
||||
context2.assert_meaningful(ref, exception_class=InconsistentInitializationError)
|
||||
for ref in context2.each_meaningful():
|
||||
context1.assert_meaningful(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?
|
||||
elif opcode == 'copy':
|
||||
# check that their types are basically compatible
|
||||
if src.type == dest.type:
|
||||
pass
|
||||
elif isinstance(src.type, ExecutableType) and \
|
||||
isinstance(dest.type, VectorType):
|
||||
pass
|
||||
|
||||
# if dealing with routines and vectors,
|
||||
# check that they're not incompatible
|
||||
if isinstance(src.type, ExecutableType) and \
|
||||
isinstance(dest.type, VectorType):
|
||||
if not (src.type.inputs <= dest.type.inputs):
|
||||
raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs)
|
||||
if not (src.type.outputs <= dest.type.outputs):
|
||||
raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs)
|
||||
if not (src.type.trashes <= dest.type.trashes):
|
||||
raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes)
|
||||
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest)
|
||||
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
||||
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'with-sei':
|
||||
self.analyze_block(instr.block, context, routines)
|
||||
elif opcode == 'goto':
|
||||
location = instr.location
|
||||
type = location.type
|
||||
|
||||
if not isinstance(type, ExecutableType):
|
||||
raise TypeMismatchError(location)
|
||||
|
||||
# assert that the dest routine's inputs are all initialized
|
||||
for ref in type.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
|
||||
# and that this routine's trashes and output constraints are a
|
||||
# superset of the called routine's
|
||||
current_type = self.current_routine.location.type
|
||||
if not (type.outputs <= current_type.outputs):
|
||||
raise IncompatibleConstraintsError(type.outputs - current_type.outputs)
|
||||
if not (type.trashes <= current_type.trashes):
|
||||
raise IncompatibleConstraintsError(type.trashes - current_type.trashes)
|
||||
self.has_encountered_goto = True
|
||||
else:
|
||||
raise TypeMismatchError((src, dest))
|
||||
|
||||
# if dealing with routines and vectors,
|
||||
# check that they're not incompatible
|
||||
if isinstance(src.type, ExecutableType) and \
|
||||
isinstance(dest.type, VectorType):
|
||||
if not (src.type.inputs <= dest.type.inputs):
|
||||
raise IncompatibleConstraintsError(src.type.inputs - dest.type.inputs)
|
||||
if not (src.type.outputs <= dest.type.outputs):
|
||||
raise IncompatibleConstraintsError(src.type.outputs - dest.type.outputs)
|
||||
if not (src.type.trashes <= dest.type.trashes):
|
||||
raise IncompatibleConstraintsError(src.type.trashes - dest.type.trashes)
|
||||
|
||||
context.assert_meaningful(src)
|
||||
context.set_written(dest)
|
||||
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
||||
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'with-sei':
|
||||
analyze_block(instr.block, context, routines)
|
||||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
raise NotImplementedError(opcode)
|
||||
|
@ -203,6 +203,15 @@ class Compiler(object):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'goto':
|
||||
location = instr.location
|
||||
label = self.labels[instr.location.name]
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'if':
|
||||
cls = {
|
||||
False: {
|
||||
|
@ -29,144 +29,151 @@ class Context(object):
|
||||
self._store[ref.name] = value
|
||||
|
||||
|
||||
def eval_program(program):
|
||||
assert isinstance(program, Program)
|
||||
context = Context()
|
||||
for ref in (REG_A, REG_X, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C):
|
||||
context.set(ref, 0)
|
||||
main = None
|
||||
for routine in program.routines:
|
||||
context.set(routine.location, routine)
|
||||
if routine.name == 'main':
|
||||
main = routine
|
||||
eval_routine(main, context)
|
||||
return context
|
||||
class Evaluator(object):
|
||||
|
||||
def eval_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
context = Context()
|
||||
for ref in (REG_A, REG_X, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C):
|
||||
context.set(ref, 0)
|
||||
main = None
|
||||
for routine in program.routines:
|
||||
context.set(routine.location, routine)
|
||||
if routine.name == 'main':
|
||||
main = routine
|
||||
self.eval_routine(main, context)
|
||||
return context
|
||||
|
||||
def eval_routine(routine, context):
|
||||
assert isinstance(routine, Routine)
|
||||
eval_block(routine.block, context)
|
||||
def eval_routine(self, routine, context):
|
||||
assert isinstance(routine, Routine)
|
||||
self.next_routine = routine
|
||||
while self.next_routine:
|
||||
routine = self.next_routine
|
||||
self.next_routine = None
|
||||
self.eval_block(routine.block, context)
|
||||
|
||||
def eval_block(self, block, context):
|
||||
assert isinstance(block, Block)
|
||||
for i in block.instrs:
|
||||
self.eval_instr(i, context)
|
||||
if self.next_routine:
|
||||
break
|
||||
|
||||
def eval_block(block, context):
|
||||
assert isinstance(block, Block)
|
||||
for i in block.instrs:
|
||||
eval_instr(i, context)
|
||||
def eval_instr(self, instr, context):
|
||||
assert isinstance(instr, Instr)
|
||||
opcode = instr.opcode
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
|
||||
def eval_instr(instr, context):
|
||||
assert isinstance(instr, Instr)
|
||||
opcode = instr.opcode
|
||||
dest = instr.dest
|
||||
src = instr.src
|
||||
|
||||
if opcode == 'ld':
|
||||
result = context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'st':
|
||||
context.set(dest, context.get(src))
|
||||
elif opcode == 'add':
|
||||
carry = context.get(FLAG_C)
|
||||
val = context.get(src)
|
||||
now = context.get(dest)
|
||||
result = now + val + carry
|
||||
if result > 255:
|
||||
result &= 255
|
||||
context.set(FLAG_C, 1)
|
||||
if opcode == 'ld':
|
||||
result = context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'st':
|
||||
context.set(dest, context.get(src))
|
||||
elif opcode == 'add':
|
||||
carry = context.get(FLAG_C)
|
||||
val = context.get(src)
|
||||
now = context.get(dest)
|
||||
result = now + val + carry
|
||||
if result > 255:
|
||||
result &= 255
|
||||
context.set(FLAG_C, 1)
|
||||
else:
|
||||
context.set(FLAG_C, 0)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'sub':
|
||||
carry = context.get(FLAG_C)
|
||||
val = context.get(src)
|
||||
now = context.get(dest)
|
||||
result = now - val - carry
|
||||
if result < 0:
|
||||
result &= 255
|
||||
context.set(FLAG_C, 1)
|
||||
else:
|
||||
context.set(FLAG_C, 0)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'inc':
|
||||
val = context.get(dest)
|
||||
result = (val + 1) & 255
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'dec':
|
||||
val = context.get(dest)
|
||||
result = (val - 1) & 255
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'cmp':
|
||||
val = context.get(src)
|
||||
now = context.get(dest)
|
||||
result = now - val
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
if result < 0:
|
||||
result &= 255
|
||||
context.set(FLAG_C, 1)
|
||||
else:
|
||||
context.set(FLAG_C, 0)
|
||||
elif opcode == 'and':
|
||||
result = context.get(dest) & context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'or':
|
||||
result = context.get(dest) | context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'xor':
|
||||
result = context.get(dest) ^ context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'shl':
|
||||
val = context.get(dest)
|
||||
carry = context.get(FLAG_C)
|
||||
context.set(FLAG_C, 1 if val & 128 else 0)
|
||||
result = ((val << 1) + carry) & 255
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'shr':
|
||||
val = context.get(dest)
|
||||
carry = context.get(FLAG_C)
|
||||
context.set(FLAG_C, 1 if val & 1 else 0)
|
||||
result = (val >> 1) + (carry * 128)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'call':
|
||||
self.eval_routine(context.get(instr.location), context)
|
||||
elif opcode == 'goto':
|
||||
self.next_routine = context.get(instr.location)
|
||||
elif opcode == 'if':
|
||||
val = context.get(src)
|
||||
test = (val != 0) if not instr.inverted else (val == 0)
|
||||
if test:
|
||||
self.eval_block(instr.block1, context)
|
||||
elif instr.block2:
|
||||
self.eval_block(instr.block2, context)
|
||||
elif opcode == 'repeat':
|
||||
self.eval_block(instr.block, context)
|
||||
while context.get(src) == 0:
|
||||
self.eval_block(instr.block, context)
|
||||
elif opcode == 'copy':
|
||||
context.set(dest, context.get(src))
|
||||
# these are trashed; so could be anything really
|
||||
context.set(REG_A, 0)
|
||||
context.set(FLAG_Z, 0)
|
||||
context.set(FLAG_N, 0)
|
||||
elif opcode == 'with-sei':
|
||||
self.eval_block(instr.block)
|
||||
else:
|
||||
context.set(FLAG_C, 0)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'sub':
|
||||
carry = context.get(FLAG_C)
|
||||
val = context.get(src)
|
||||
now = context.get(dest)
|
||||
result = now - val - carry
|
||||
if result < 0:
|
||||
result &= 255
|
||||
context.set(FLAG_C, 1)
|
||||
else:
|
||||
context.set(FLAG_C, 0)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'inc':
|
||||
val = context.get(dest)
|
||||
result = (val + 1) & 255
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'dec':
|
||||
val = context.get(dest)
|
||||
result = (val - 1) & 255
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'cmp':
|
||||
val = context.get(src)
|
||||
now = context.get(dest)
|
||||
result = now - val
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
if result < 0:
|
||||
result &= 255
|
||||
context.set(FLAG_C, 1)
|
||||
else:
|
||||
context.set(FLAG_C, 0)
|
||||
elif opcode == 'and':
|
||||
result = context.get(dest) & context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'or':
|
||||
result = context.get(dest) | context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'xor':
|
||||
result = context.get(dest) ^ context.get(src)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'shl':
|
||||
val = context.get(dest)
|
||||
carry = context.get(FLAG_C)
|
||||
context.set(FLAG_C, 1 if val & 128 else 0)
|
||||
result = ((val << 1) + carry) & 255
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'shr':
|
||||
val = context.get(dest)
|
||||
carry = context.get(FLAG_C)
|
||||
context.set(FLAG_C, 1 if val & 1 else 0)
|
||||
result = (val >> 1) + (carry * 128)
|
||||
context.set(FLAG_Z, 1 if result == 0 else 0)
|
||||
context.set(FLAG_N, 1 if result & 128 else 0)
|
||||
context.set(dest, result)
|
||||
elif opcode == 'call':
|
||||
eval_routine(context.get(instr.location), context)
|
||||
elif opcode == 'if':
|
||||
val = context.get(src)
|
||||
test = (val != 0) if not instr.inverted else (val == 0)
|
||||
if test:
|
||||
eval_block(instr.block1, context)
|
||||
elif instr.block2:
|
||||
eval_block(instr.block2, context)
|
||||
elif opcode == 'repeat':
|
||||
eval_block(instr.block, context)
|
||||
while context.get(src) == 0:
|
||||
eval_block(instr.block, context)
|
||||
elif opcode == 'copy':
|
||||
context.set(dest, context.get(src))
|
||||
# these are trashed; so could be anything really
|
||||
context.set(REG_A, 0)
|
||||
context.set(FLAG_Z, 0)
|
||||
context.set(FLAG_N, 0)
|
||||
elif opcode == 'with-sei':
|
||||
eval_block(instr.block)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError
|
||||
|
@ -260,7 +260,7 @@ class Parser(object):
|
||||
self.scanner.scan()
|
||||
dest = self.locexpr()
|
||||
return Instr(opcode=opcode, dest=dest, src=None)
|
||||
elif self.scanner.token in ("call",):
|
||||
elif self.scanner.token in ("call", "goto"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
name = self.scanner.token
|
||||
|
@ -1134,7 +1134,7 @@ Indirect call.
|
||||
| }
|
||||
= ok
|
||||
|
||||
Calling the vector has indeed trashed stuff etc,
|
||||
Calling the vector does indeed trash the things the vector says it does.
|
||||
|
||||
| vector foo trashes x, z, n
|
||||
|
|
||||
@ -1149,7 +1149,7 @@ Calling the vector has indeed trashed stuff etc,
|
||||
| }
|
||||
? UninitializedOutputError: x
|
||||
|
||||
A goto, if present, must appear at the end of the routine.
|
||||
`goto`, if present, must be in tail position (the final instruction in a routine.)
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 200
|
||||
@ -1169,4 +1169,103 @@ A goto, if present, must appear at the end of the routine.
|
||||
| goto bar
|
||||
| ld x, 0
|
||||
| }
|
||||
? IllegalGotoError
|
||||
? IllegalJumpError
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| if z {
|
||||
| ld x, 1
|
||||
| goto bar
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| if z {
|
||||
| ld x, 1
|
||||
| goto bar
|
||||
| }
|
||||
| ld x, 0
|
||||
| }
|
||||
? IllegalJumpError
|
||||
|
||||
Can't `goto` a routine that outputs or trashes more than the current routine.
|
||||
|
||||
| routine bar trashes x, y, z, n {
|
||||
| ld x, 200
|
||||
| ld y, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| goto bar
|
||||
| }
|
||||
? IncompatibleConstraintsError
|
||||
|
||||
| routine bar outputs y trashes z, n {
|
||||
| ld y, 200
|
||||
| }
|
||||
|
|
||||
| routine main trashes x, z, n {
|
||||
| ld x, 0
|
||||
| goto bar
|
||||
| }
|
||||
? IncompatibleConstraintsError
|
||||
|
||||
Can `goto` a routine that outputs or trashes less than the current routine.
|
||||
|
||||
| routine bar trashes x, z, n {
|
||||
| ld x, 1
|
||||
| }
|
||||
|
|
||||
| routine main trashes a, x, z, n {
|
||||
| ld a, 0
|
||||
| ld x, 0
|
||||
| goto bar
|
||||
| }
|
||||
= ok
|
||||
|
||||
Indirect goto.
|
||||
|
||||
| vector foo outputs x trashes a, z, n
|
||||
|
|
||||
| routine bar outputs x trashes a, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main inputs bar outputs x trashes foo, a, z, n {
|
||||
| copy bar, foo
|
||||
| goto foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
Jumping through the vector does indeed output the things the vector says it does.
|
||||
|
||||
| vector foo trashes a, x, z, n
|
||||
|
|
||||
| routine bar trashes a, x, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine sub inputs bar trashes foo, a, x, z, n {
|
||||
| ld x, 0
|
||||
| copy bar, foo
|
||||
| goto foo
|
||||
| }
|
||||
|
|
||||
| routine main inputs bar outputs a trashes z, n {
|
||||
| call sub
|
||||
| ld a, x
|
||||
| }
|
||||
? UninitializedOutputError: x
|
||||
|
||||
Ack, I have become a bit confused...
|
||||
|
@ -274,4 +274,16 @@ Indirect call.
|
||||
| copy bar, foo
|
||||
| call foo
|
||||
| }
|
||||
= 00c0
|
||||
= 00c0wewillfixthislater
|
||||
|
||||
goto.
|
||||
|
||||
| routine bar outputs x trashes z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main outputs x trashes a, z, n {
|
||||
| ld y, 200
|
||||
| goto bar
|
||||
| }
|
||||
= 00c0a0c84c06c060a2c860
|
||||
|
@ -420,3 +420,21 @@ Indirect call.
|
||||
= x: 200
|
||||
= y: 0
|
||||
= z: 0
|
||||
|
||||
goto.
|
||||
|
||||
| routine bar outputs x trashes z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| routine main outputs x trashes a, z, n {
|
||||
| ld y, 200
|
||||
| goto bar
|
||||
| }
|
||||
= a: 0
|
||||
= c: 0
|
||||
= n: 1
|
||||
= v: 0
|
||||
= x: 200
|
||||
= y: 200
|
||||
= z: 0
|
||||
|
@ -248,3 +248,32 @@ Only vectors can be decorated with constraints like that.
|
||||
| routine main {
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
goto.
|
||||
|
||||
| routine foo {
|
||||
| ld a, 0
|
||||
| }
|
||||
| routine main {
|
||||
| goto foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
| vector foo
|
||||
|
|
||||
| routine main {
|
||||
| goto foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
| routine main {
|
||||
| goto foo
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| byte foo
|
||||
|
|
||||
| routine main {
|
||||
| goto foo
|
||||
| }
|
||||
? SyntaxError
|
||||
|
Loading…
x
Reference in New Issue
Block a user