diff --git a/README.markdown b/README.markdown index 3831af3..cc91bcc 100644 --- a/README.markdown +++ b/README.markdown @@ -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. diff --git a/bin/sixtypical b/bin/sixtypical index 32cc6df..4125e08 100755 --- a/bin/sixtypical +++ b/bin/sixtypical @@ -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) diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 53a78f8..b21770d 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -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 [Constraints] ["@" WordConst]. Type ::= "byte" ["table"] | "vector" Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs]. - Routine ::= "routine" NewIdent Constraints (Block | "@" WordConst). + Routine ::= "routine" Ident 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 + | "goto" Ident | "if" ["not"] LocExpr Block ["else" Block] | "repeat" Block ("until" ["not"] LocExpr | "forever") | "copy" LocExpr "," LocExpr ["+" LocExpr] diff --git a/eg/goto.60p b/eg/goto.60p new file mode 100644 index 0000000..5c8c0f8 --- /dev/null +++ b/eg/goto.60p @@ -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 +} diff --git a/eg/vector.60p b/eg/vector.60p new file mode 100644 index 0000000..1e80d5f --- /dev/null +++ b/eg/vector.60p @@ -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 +} diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index a8f4591..70c06cb 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -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) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index da96c30..b47432b 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -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: { diff --git a/src/sixtypical/evaluator.py b/src/sixtypical/evaluator.py index 53c3b9d..90b9513 100644 --- a/src/sixtypical/evaluator.py +++ b/src/sixtypical/evaluator.py @@ -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 diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 198b0dd..2323213 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -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 diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 0f150f4..c4db06a 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -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... diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 156f110..a909849 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -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 diff --git a/tests/SixtyPical Execution.md b/tests/SixtyPical Execution.md index af3cf1a..1b526d0 100644 --- a/tests/SixtyPical Execution.md +++ b/tests/SixtyPical Execution.md @@ -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 diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index ed3b687..0567d2d 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -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