1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-02-19 20:30:45 +00:00

The exception object now contains an AST node, renders name.

This commit is contained in:
Chris Pressey 2018-03-06 14:39:47 +00:00
parent 7023fb9c1d
commit 1eaec602e3
2 changed files with 104 additions and 108 deletions

View File

@ -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):

View File

@ -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