From 1eaec602e3122de750bc86118c2b395370160d99 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 6 Mar 2018 14:39:47 +0000 Subject: [PATCH] The exception object now contains an AST node, renders name. --- src/sixtypical/analyzer.py | 82 +++++++++++----------- tests/SixtyPical Analysis.md | 130 +++++++++++++++++------------------ 2 files changed, 104 insertions(+), 108 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 07ec9b4..d3b977b 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -10,11 +10,16 @@ from sixtypical.model import ( class StaticAnalysisError(ValueError): - def __init__(self, line_number, message): - super(StaticAnalysisError, self).__init__(line_number, message) + def __init__(self, ast, message): + super(StaticAnalysisError, self).__init__(ast, message) def __str__(self): - return "{} (Line {})".format(self.args[1], self.args[0]) + ast = self.args[0] + message = self.args[1] + if isinstance(ast, Routine): + return "{} (in {}, line {})".format(message, ast.name, ast.line_number) + else: + return "{} (line {})".format(message, ast.line_number) class UnmeaningfulReadError(StaticAnalysisError): @@ -98,19 +103,19 @@ class Context(object): for ref in inputs: if ref.is_constant(): - raise ConstantConstraintError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) + raise ConstantConstraintError(self.routine, ref.name) self._range[ref] = ref.max_range() output_names = set() for ref in outputs: if ref.is_constant(): - raise ConstantConstraintError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) + raise ConstantConstraintError(self.routine, ref.name) output_names.add(ref.name) self._writeable.add(ref) for ref in trashes: if ref.is_constant(): - raise ConstantConstraintError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) + raise ConstantConstraintError(self.routine, ref.name) if ref.name in output_names: - raise InconsistentConstraintsError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) + raise InconsistentConstraintsError(self.routine, ref.name) self._writeable.add(ref) def __str__(self): @@ -143,10 +148,10 @@ class Context(object): pass elif isinstance(ref, LocationRef): if ref not in self._range: - message = '%s in %s' % (ref.name, self.routine.name) + message = ref.name if kwargs.get('message'): message += ' (%s)' % kwargs['message'] - raise exception_class(self.routine.line_number, message) + raise exception_class(self.routine, message) elif isinstance(ref, IndexedRef): self.assert_meaningful(ref.ref, **kwargs) self.assert_meaningful(ref.index, **kwargs) @@ -160,10 +165,10 @@ class Context(object): if routine_has_static(self.routine, ref): continue if ref not in self._writeable: - message = '%s in %s' % (ref.name, self.routine.name) + message = ref.name if kwargs.get('message'): message += ' (%s)' % kwargs['message'] - raise exception_class(self.routine.line_number, message) + raise exception_class(self.routine, message) def assert_in_range(self, inside, outside): # FIXME there's a bit of I'm-not-sure-the-best-way-to-do-this-ness, here... @@ -180,7 +185,7 @@ class Context(object): outside_range = (0, outside.type.size-1) if inside_range[0] < outside_range[0] or inside_range[1] > outside_range[1]: - raise RangeExceededError(self.routine.line_number, + raise RangeExceededError(self.routine, "Possible range of {} {} exceeds acceptable range of {} {}".format( inside, inside_range, outside, outside_range ) @@ -240,9 +245,7 @@ class Analyzer(object): def assert_type(self, type, *locations): for location in locations: if location.type != type: - raise TypeMismatchError(self.current_routine.line_number, '%s in %s' % - (location.name, self.current_routine.name) - ) + raise TypeMismatchError(self.current_routine, location.name) def assert_affected_within(self, name, affecting_type, limiting_type): assert name in ('inputs', 'outputs', 'trashes') @@ -251,13 +254,13 @@ class Analyzer(object): overage = affected - limited_to if not overage: return - message = 'in %s: %s for %s are %s\n\nbut %s affects %s\n\nwhich exceeds it by: %s ' % ( - self.current_routine.name, name, + message = '%s for %s are %s\n\nbut %s affects %s\n\nwhich exceeds it by: %s ' % ( + name, limiting_type, LocationRef.format_set(limited_to), affecting_type, LocationRef.format_set(affected), LocationRef.format_set(overage) ) - raise IncompatibleConstraintsError(message) + raise IncompatibleConstraintsError(self.current_routine, message) def analyze_program(self, program): assert isinstance(program, Program) @@ -297,22 +300,21 @@ class Analyzer(object): # even if we goto another routine, we can't trash an output. for ref in trashed: if ref in type_.outputs: - raise UnmeaningfulOutputError(routine.line_number, '%s in %s' % (ref.name, routine.name)) + raise UnmeaningfulOutputError(routine, ref.name) if not self.has_encountered_goto: for ref in type_.outputs: context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError) for ref in context.each_touched(): if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): - message = '%s in %s' % (ref.name, routine.name) - raise ForbiddenWriteError(routine.line_number, message) + raise ForbiddenWriteError(routine, ref.name) self.current_routine = None def analyze_block(self, block, context): assert isinstance(block, Block) for i in block.instrs: if self.has_encountered_goto: - raise IllegalJumpError(i) + raise IllegalJumpError(i, i) self.analyze_instr(i, context) def analyze_instr(self, instr, context): @@ -338,9 +340,7 @@ class Analyzer(object): if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == TYPE_BYTE: pass else: - raise TypeMismatchError(instr.line_number, '%s and %s in %s' % - (src.ref.name, dest.name, self.current_routine.name) - ) + raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name)) context.assert_meaningful(src, src.index) context.assert_in_range(src.index, src.ref) elif isinstance(src, IndirectRef): @@ -348,12 +348,10 @@ class Analyzer(object): if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) context.assert_meaningful(src.ref, REG_Y) elif src.type != dest.type: - raise TypeMismatchError(instr.line_number, '%s and %s in %s' % - (src.name, dest.name, self.current_routine.name) - ) + raise TypeMismatchError(instr, '{} and {}'.format(src.name, dest.name)) else: context.assert_meaningful(src) context.copy_range(src, dest) @@ -363,7 +361,7 @@ class Analyzer(object): if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.ref.type, TYPE_BYTE): pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) context.assert_meaningful(dest.index) context.assert_in_range(dest.index, dest.ref) context.set_written(dest.ref) @@ -372,13 +370,11 @@ class Analyzer(object): if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE: pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) context.assert_meaningful(dest.ref, REG_Y) context.set_written(dest.ref) elif src.type != dest.type: - raise TypeMismatchError(instr.line_number, '%r and %r in %s' % - (src, dest, self.current_routine.name) - ) + raise TypeMismatchError(instr, '{} and {}'.format(src, name)) else: context.set_written(dest) context.assert_meaningful(src) @@ -447,7 +443,7 @@ class Analyzer(object): context.set_unmeaningful(ref) elif opcode == 'copy': if dest == REG_A: - raise ForbiddenWriteError(instr.line_number, "{} cannot be used as destination for copy".format(dest)) + raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest)) # 1. check that their types are compatible @@ -455,17 +451,17 @@ class Analyzer(object): if isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef): if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD): @@ -477,7 +473,7 @@ class Analyzer(object): RoutineType.executable_types_compatible(src.type, dest.ref.type.of_type)): pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) context.assert_in_range(dest.index, dest.ref) elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): @@ -487,7 +483,7 @@ class Analyzer(object): RoutineType.executable_types_compatible(src.ref.type.of_type, dest.type.of_type)): pass else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) context.assert_in_range(src.index, src.ref) elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef): @@ -498,9 +494,9 @@ class Analyzer(object): self.assert_affected_within('outputs', src.type, dest.type.of_type) self.assert_affected_within('trashes', src.type, dest.type.of_type) else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) else: - raise TypeMismatchError(instr.line_number, (src, dest)) + raise TypeMismatchError(instr, (src, dest)) # 2. check that the context is meaningful @@ -532,7 +528,7 @@ class Analyzer(object): type_ = location.type if not isinstance(type_, (RoutineType, VectorType)): - raise TypeMismatchError(instr.line_number, location) + raise TypeMismatchError(instr, location) # assert that the dest routine's inputs are all initialized if isinstance(type_, VectorType): diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 84111c8..42d2173 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -42,7 +42,7 @@ If a routine declares it outputs a location, that location should be initialized | { | ld x, 0 | } - ? UnmeaningfulOutputError: a in main + ? UnmeaningfulOutputError: a | routine main | inputs a @@ -73,7 +73,7 @@ If a routine modifies a location, it needs to either output it or trash it. | { | ld x, 0 | } - ? ForbiddenWriteError: x in main + ? ForbiddenWriteError: x | routine main | outputs x, z, n @@ -96,7 +96,7 @@ This is true regardless of whether it's an input or not. | { | ld x, 0 | } - ? ForbiddenWriteError: x in main + ? ForbiddenWriteError: x | routine main | inputs x @@ -127,14 +127,14 @@ If a routine trashes a location, this must be declared. | { | trash x | } - ? ForbiddenWriteError: x in foo + ? ForbiddenWriteError: x | routine foo | outputs x | { | trash x | } - ? UnmeaningfulOutputError: x in foo + ? UnmeaningfulOutputError: x If a routine causes a location to be trashed, this must be declared in the caller. @@ -162,7 +162,7 @@ If a routine causes a location to be trashed, this must be declared in the calle | { | call trash_x | } - ? ForbiddenWriteError: x in foo + ? ForbiddenWriteError: x | routine trash_x | trashes x, z, n @@ -176,7 +176,7 @@ If a routine causes a location to be trashed, this must be declared in the calle | { | call trash_x | } - ? UnmeaningfulOutputError: x in foo + ? UnmeaningfulOutputError: x (in foo, line 12) If a routine reads or writes a user-define memory location, it needs to declare that too. @@ -214,7 +214,7 @@ Can't `ld` from a memory location that isn't initialized. | { | ld a, x | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x Can't `ld` to a memory location that doesn't appear in (outputs ∪ trashes). @@ -246,14 +246,14 @@ Can't `ld` to a memory location that doesn't appear in (outputs ∪ trashes). | { | ld a, 0 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a | routine main | trashes a, n | { | ld a, 0 | } - ? ForbiddenWriteError: z in main + ? ForbiddenWriteError: z Can't `ld` a `word` type. @@ -265,7 +265,7 @@ Can't `ld` a `word` type. | { | ld a, foo | } - ? TypeMismatchError: foo and a in main + ? TypeMismatchError: foo and a ### st ### @@ -286,7 +286,7 @@ Can't `st` from a memory location that isn't initialized. | { | st x, lives | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x Can't `st` to a memory location that doesn't appear in (outputs ∪ trashes). @@ -312,7 +312,7 @@ Can't `st` to a memory location that doesn't appear in (outputs ∪ trashes). | { | st 0, lives | } - ? ForbiddenWriteError: lives in main + ? ForbiddenWriteError: lives Can't `st` a `word` type. @@ -646,7 +646,7 @@ Can't `add` from or to a memory location that isn't initialized. | st off, c | add a, lives | } - ? UnmeaningfulReadError: lives in main + ? UnmeaningfulReadError: lives | byte lives | routine main @@ -657,7 +657,7 @@ Can't `add` from or to a memory location that isn't initialized. | st off, c | add a, lives | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a Can't `add` to a memory location that isn't writeable. @@ -668,7 +668,7 @@ Can't `add` to a memory location that isn't writeable. | st off, c | add a, 0 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a You can `add` a word constant to a word memory location. @@ -694,7 +694,7 @@ You can `add` a word constant to a word memory location. | st off, c | add score, 1999 | } - ? UnmeaningfulOutputError: a in main + ? UnmeaningfulOutputError: a To be sure, `add`ing a word constant to a word memory location trashes `a`. @@ -707,7 +707,7 @@ To be sure, `add`ing a word constant to a word memory location trashes `a`. | st off, c | add score, 1999 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a You can `add` a word memory location to another word memory location. @@ -735,7 +735,7 @@ You can `add` a word memory location to another word memory location. | st off, c | add score, delta | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a You can `add` a word memory location, or a constant, to a pointer. @@ -765,7 +765,7 @@ You can `add` a word memory location, or a constant, to a pointer. | add ptr, delta | add ptr, word 1 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a ### sub ### @@ -790,7 +790,7 @@ Can't `sub` from or to a memory location that isn't initialized. | st off, c | sub a, lives | } - ? UnmeaningfulReadError: lives in main + ? UnmeaningfulReadError: lives | byte lives | routine main @@ -801,7 +801,7 @@ Can't `sub` from or to a memory location that isn't initialized. | st off, c | sub a, lives | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a Can't `sub` to a memory location that isn't writeable. @@ -812,7 +812,7 @@ Can't `sub` to a memory location that isn't writeable. | st off, c | sub a, 0 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a You can `sub` a word constant from a word memory location. @@ -838,7 +838,7 @@ You can `sub` a word constant from a word memory location. | st on, c | sub score, 1999 | } - ? UnmeaningfulOutputError: a in main + ? UnmeaningfulOutputError: a You can `sub` a word memory location from another word memory location. @@ -866,7 +866,7 @@ You can `sub` a word memory location from another word memory location. | st off, c | sub score, delta | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a ### inc ### @@ -878,7 +878,7 @@ Location must be initialized and writeable. | { | inc x | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x | routine main | inputs x @@ -886,7 +886,7 @@ Location must be initialized and writeable. | { | inc x | } - ? ForbiddenWriteError: x in main + ? ForbiddenWriteError: x | routine main | inputs x @@ -908,7 +908,7 @@ Can't `inc` a `word` type. | { | inc foo | } - ? TypeMismatchError: foo in main + ? TypeMismatchError: foo ### dec ### @@ -920,7 +920,7 @@ Location must be initialized and writeable. | { | dec x | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x | routine main | inputs x @@ -928,7 +928,7 @@ Location must be initialized and writeable. | { | dec x | } - ? ForbiddenWriteError: x in main + ? ForbiddenWriteError: x | routine main | inputs x @@ -950,7 +950,7 @@ Can't `dec` a `word` type. | { | dec foo | } - ? TypeMismatchError: foo in main + ? TypeMismatchError: foo ### cmp ### @@ -970,14 +970,14 @@ Some rudimentary tests for cmp. | { | cmp a, 4 | } - ? ForbiddenWriteError: c in main + ? ForbiddenWriteError: c | routine main | trashes z, c, n | { | cmp a, 4 | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a ### and ### @@ -997,14 +997,14 @@ Some rudimentary tests for and. | { | and a, 4 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a | routine main | trashes z, n | { | and a, 4 | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a ### or ### @@ -1024,14 +1024,14 @@ Writing unit tests on a train. Wow. | { | or a, 4 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a | routine main | trashes z, n | { | or a, 4 | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a ### xor ### @@ -1051,14 +1051,14 @@ Writing unit tests on a train. Wow. | { | xor a, 4 | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a | routine main | trashes z, n | { | xor a, 4 | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a ### shl ### @@ -1078,7 +1078,7 @@ Some rudimentary tests for shl. | { | shl a | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a | routine main | inputs a @@ -1086,7 +1086,7 @@ Some rudimentary tests for shl. | { | shl a | } - ? UnmeaningfulReadError: c in main + ? UnmeaningfulReadError: c ### shr ### @@ -1106,7 +1106,7 @@ Some rudimentary tests for shr. | { | shr a | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: a | routine main | inputs a @@ -1114,7 +1114,7 @@ Some rudimentary tests for shr. | { | shr a | } - ? UnmeaningfulReadError: c in main + ? UnmeaningfulReadError: c ### call ### @@ -1134,7 +1134,7 @@ initialized. | { | call foo | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x Note that if you call a routine that trashes a location, you also trash it. @@ -1153,7 +1153,7 @@ Note that if you call a routine that trashes a location, you also trash it. | ld x, 0 | call foo | } - ? ForbiddenWriteError: lives in main + ? ForbiddenWriteError: lives | byte lives | @@ -1190,7 +1190,7 @@ You can't output a value that the thing you called trashed. | ld x, 0 | call foo | } - ? UnmeaningfulOutputError: lives in main + ? UnmeaningfulOutputError: lives ...unless you write to it yourself afterwards. @@ -1241,7 +1241,7 @@ calling it. | call foo | ld a, x | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x If a routine trashes locations, they are uninitialized in the caller after calling it. @@ -1266,7 +1266,7 @@ calling it. | call foo | ld a, x | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x Calling an extern is just the same as calling a defined routine with the same constraints. @@ -1294,7 +1294,7 @@ same constraints. | { | call chrout | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a | routine chrout | inputs a @@ -1308,7 +1308,7 @@ same constraints. | call chrout | ld x, a | } - ? UnmeaningfulReadError: a in main + ? UnmeaningfulReadError: a ### trash ### @@ -1334,7 +1334,7 @@ Trash does nothing except indicate that we do not care about the value anymore. | ld a, 0 | trash a | } - ? UnmeaningfulOutputError: a in foo + ? UnmeaningfulOutputError: a | routine foo | inputs a @@ -1345,7 +1345,7 @@ Trash does nothing except indicate that we do not care about the value anymore. | trash a | st a, x | } - ? UnmeaningfulReadError: a in foo + ? UnmeaningfulReadError: a ### if ### @@ -1510,7 +1510,7 @@ trashes {`a`, `b`}. | trash x | } | } - ? ForbiddenWriteError: x in foo + ? ForbiddenWriteError: x (in foo, line 10) | routine foo | inputs a, x, z @@ -1522,7 +1522,7 @@ trashes {`a`, `b`}. | trash x | } | } - ? ForbiddenWriteError: a in foo + ? ForbiddenWriteError: a (in foo, line 10) ### repeat ### @@ -1575,7 +1575,7 @@ initialized at the start. | cmp x, 10 | } until z | } - ? UnmeaningfulReadError: y in main + ? UnmeaningfulReadError: y And if you trash the test expression (i.e. `z` in the below) inside the loop, this is an error too. @@ -1592,7 +1592,7 @@ this is an error too. | copy one, two | } until z | } - ? UnmeaningfulReadError: z in main + ? UnmeaningfulReadError: z The body of `repeat forever` can be empty. @@ -1624,7 +1624,7 @@ Can't `copy` from a memory location that isn't initialized. | { | copy x, lives | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x Can't `copy` to a memory location that doesn't appear in (outputs ∪ trashes). @@ -1652,7 +1652,7 @@ Can't `copy` to a memory location that doesn't appear in (outputs ∪ trashes). | { | copy 0, lives | } - ? ForbiddenWriteError: lives in main + ? ForbiddenWriteError: lives a, z, and n are trashed, and must be declared as such @@ -1662,7 +1662,7 @@ a, z, and n are trashed, and must be declared as such | { | copy 0, lives | } - ? ForbiddenWriteError: n in main + ? ForbiddenWriteError: n a, z, and n are trashed, and must not be declared as outputs. @@ -1672,7 +1672,7 @@ a, z, and n are trashed, and must not be declared as outputs. | { | copy 0, lives | } - ? UnmeaningfulOutputError: n in main + ? UnmeaningfulOutputError: n Unless of course you subsequently initialize them. @@ -1901,7 +1901,7 @@ as an input to, an output of, or as a trashed value of a routine. | { | copy foo, vec | } - ? ConstantConstraintError: foo in main + ? ConstantConstraintError: foo | vector routine | inputs x @@ -1923,7 +1923,7 @@ as an input to, an output of, or as a trashed value of a routine. | { | copy foo, vec | } - ? ConstantConstraintError: foo in main + ? ConstantConstraintError: foo | vector routine | inputs x @@ -1945,7 +1945,7 @@ as an input to, an output of, or as a trashed value of a routine. | { | copy foo, vec | } - ? ConstantConstraintError: foo in main + ? ConstantConstraintError: foo You can copy the address of a routine into a vector, if that vector is declared appropriately. @@ -2074,7 +2074,7 @@ Calling the vector does indeed trash the things the vector says it does. | copy bar, foo | call foo | } - ? UnmeaningfulOutputError: x in main + ? UnmeaningfulOutputError: x `goto`, if present, must be in tail position (the final instruction in a routine.) @@ -2200,7 +2200,7 @@ vector says it does. | call sub | ld a, x | } - ? UnmeaningfulReadError: x in main + ? UnmeaningfulReadError: x | vector routine | outputs x