diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 2320dbd..505bd4e 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -3,7 +3,7 @@ from sixtypical.ast import Program, Routine, Block, Instr from sixtypical.model import ( TYPE_BYTE, TYPE_WORD, - TableType, BufferType, PointerType, VectorType, ExecutableType, RoutineType, + TableType, BufferType, PointerType, VectorType, RoutineType, ConstantRef, LocationRef, IndirectRef, IndexedRef, AddressRef, REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C ) @@ -303,6 +303,8 @@ class Analyzer(object): context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C) elif opcode == 'call': type = instr.location.type + if isinstance(type, VectorType): + type = type.of_type for ref in type.inputs: context.assert_meaningful(ref) for ref in type.outputs: @@ -366,8 +368,11 @@ class Analyzer(object): 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): pass - elif (isinstance(src.type, ExecutableType) and isinstance(dest.ref.type, TableType) and - ExecutableType.executable_types_compatible(src.type, dest.ref.type.of_type)): + elif (isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and + RoutineType.executable_types_compatible(src.type.of_type, dest.ref.type.of_type)): + pass + elif (isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and + RoutineType.executable_types_compatible(src.type, dest.ref.type.of_type)): pass else: raise TypeMismatchError((src, dest)) @@ -376,7 +381,7 @@ class Analyzer(object): if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD: pass elif (isinstance(src.ref.type, TableType) and isinstance(dest.type, VectorType) and - ExecutableType.executable_types_compatible(src.ref.type.of_type, dest.type)): + RoutineType.executable_types_compatible(src.ref.type.of_type, dest.type.of_type)): pass else: raise TypeMismatchError((src, dest)) @@ -384,10 +389,10 @@ class Analyzer(object): elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef): if src.type == dest.type: pass - elif isinstance(src.type, ExecutableType) and isinstance(dest.type, VectorType): - self.assert_affected_within('inputs', src.type.inputs, dest.type.inputs) - self.assert_affected_within('outputs', src.type.outputs, dest.type.outputs) - self.assert_affected_within('trashes', src.type.trashes, dest.type.trashes) + elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType): + self.assert_affected_within('inputs', src.type.inputs, dest.type.of_type.inputs) + self.assert_affected_within('outputs', src.type.outputs, dest.type.of_type.outputs) + self.assert_affected_within('trashes', src.type.trashes, dest.type.of_type.trashes) else: raise TypeMismatchError((src, dest)) else: @@ -434,10 +439,12 @@ class Analyzer(object): location = instr.location type_ = location.type - if not isinstance(type_, ExecutableType): + if not isinstance(type_, (RoutineType, VectorType)): raise TypeMismatchError(location) # assert that the dest routine's inputs are all initialized + if isinstance(type_, VectorType): + type_ = type_.of_type for ref in type_.inputs: context.assert_meaningful(ref) diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 6396fd2..88cd080 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -17,16 +17,27 @@ class Type(object): def __hash__(self): return hash(self.name) + def backpatch_constraint_labels(self, resolver): + if isinstance(self, TableType): + self.of_type.backpatch_constraint_labels(resolver) + elif isinstance(self, VectorType): + self.of_type.backpatch_constraint_labels(resolver) + elif isinstance(self, RoutineType): + self.inputs = set([resolver(w) for w in self.inputs]) + self.outputs = set([resolver(w) for w in self.outputs]) + self.trashes = set([resolver(w) for w in self.trashes]) + TYPE_BIT = Type('bit') TYPE_BYTE = Type('byte') TYPE_WORD = Type('word') -class ExecutableType(Type): - """Used for routines and vectors.""" - def __init__(self, name, inputs=None, outputs=None, trashes=None): - self.name = name + +class RoutineType(Type): + """This memory location contains the code for a routine.""" + def __init__(self, inputs=None, outputs=None, trashes=None): + self.name = 'routine' self.inputs = inputs or set() self.outputs = outputs or set() self.trashes = trashes or set() @@ -37,7 +48,7 @@ class ExecutableType(Type): ) def __eq__(self, other): - return isinstance(other, ExecutableType) and ( + return isinstance(other, RoutineType) and ( other.name == self.name and other.inputs == self.inputs and other.outputs == self.outputs and @@ -50,7 +61,11 @@ class ExecutableType(Type): @classmethod def executable_types_compatible(cls_, src, dest): """Returns True iff a value of type `src` can be assigned to a storage location of type `dest`.""" - if isinstance(src, ExecutableType) and isinstance(dest, VectorType): + if isinstance(src, VectorType): + src = src.of_type + if isinstance(dest, VectorType): + dest = dest.of_type + if isinstance(src, RoutineType) and isinstance(dest, RoutineType): # TODO: I'm sure we can replace some of these with subset-containment, but that requires thought return ( src.inputs == dest.inputs and @@ -61,16 +76,22 @@ class ExecutableType(Type): return False -class RoutineType(ExecutableType): - """This memory location contains the code for a routine.""" - def __init__(self, **kwargs): - super(RoutineType, self).__init__('routine', **kwargs) +class VectorType(Type): + """This memory location contains the address of some other type (currently, only RoutineType).""" + def __init__(self, of_type): + self.name = 'vector' + self.of_type = of_type + def __repr__(self): + return '%s(%r)' % ( + self.__class__.__name__, self.of_type + ) -class VectorType(ExecutableType): - """This memory location contains the address of a routine.""" - def __init__(self, **kwargs): - super(VectorType, self).__init__('vector', **kwargs) + def __eq__(self, other): + return self.name == other.name and self.of_type == other.of_type + + def __hash__(self): + return hash(self.name) ^ hash(self.of_type) class TableType(Type): @@ -135,18 +156,6 @@ class LocationRef(Ref): def is_constant(self): return isinstance(self.type, RoutineType) - def backpatch_vector_labels(self, resolver): - if isinstance(self.type, ExecutableType): - t = self.type - t.inputs = set([resolver(w) for w in t.inputs]) - t.outputs = set([resolver(w) for w in t.outputs]) - t.trashes = set([resolver(w) for w in t.trashes]) - if isinstance(self.type, TableType) and isinstance(self.type.of_type, ExecutableType): - t = self.type.of_type - t.inputs = set([resolver(w) for w in t.inputs]) - t.outputs = set([resolver(w) for w in t.outputs]) - t.trashes = set([resolver(w) for w in t.trashes]) - @classmethod def format_set(cls, location_refs): return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)]) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index f80616d..6905280 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -3,7 +3,7 @@ from sixtypical.ast import Program, Defn, Routine, Block, Instr from sixtypical.model import ( TYPE_BIT, TYPE_BYTE, TYPE_WORD, - RoutineType, VectorType, ExecutableType, TableType, BufferType, PointerType, + RoutineType, VectorType, TableType, BufferType, PointerType, LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef, ) from sixtypical.scanner import Scanner @@ -35,16 +35,20 @@ class Parser(object): def program(self): defns = [] routines = [] - while self.scanner.on('byte', 'word', 'vector', 'buffer', 'pointer'): + while self.scanner.on('byte', 'word', 'table', 'vector', 'buffer', 'pointer'): # 'routine', defn = self.defn() name = defn.name if name in self.symbols: raise SyntaxError('Symbol "%s" already declared' % name) self.symbols[name] = SymEntry(defn, defn.location) defns.append(defn) - while self.scanner.on('routine'): - routine = self.routine() - name = routine.name + while self.scanner.on('define', 'routine'): + if self.scanner.consume('define'): + name = self.scanner.token + self.scanner.scan() + else: + routine = self.legacy_routine() + name = routine.name if name in self.symbols: raise SyntaxError('Symbol "%s" already declared' % name) self.symbols[name] = SymEntry(routine, routine.location) @@ -53,29 +57,30 @@ class Parser(object): # now backpatch the executable types. for defn in defns: - defn.location.backpatch_vector_labels(lambda w: self.lookup(w)) + defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w)) for routine in routines: - routine.location.backpatch_vector_labels(lambda w: self.lookup(w)) + routine.location.type.backpatch_constraint_labels(lambda w: self.lookup(w)) for instr in self.backpatch_instrs: if instr.opcode in ('call', 'goto'): name = instr.location if name not in self.symbols: raise SyntaxError('Undefined routine "%s"' % name) - if not isinstance(self.symbols[name].model.type, ExecutableType): + if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)): raise SyntaxError('Illegal call of non-executable "%s"' % name) instr.location = self.symbols[name].model if instr.opcode in ('copy',) and isinstance(instr.src, basestring): name = instr.src if name not in self.symbols: raise SyntaxError('Undefined routine "%s"' % name) - if not isinstance(self.symbols[name].model.type, ExecutableType): + if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)): raise SyntaxError('Illegal copy of non-executable "%s"' % name) instr.src = self.symbols[name].model return Program(defns=defns, routines=routines) def defn(self): - type_, name = self.defn_type_and_name() + type_ = self.defn_type() + name = self.defn_name() initial = None if self.scanner.consume(':'): @@ -107,39 +112,28 @@ class Parser(object): self.scanner.expect(']') return size - def defn_type_and_name(self): + def defn_type(self): if self.scanner.consume('byte'): - type_ = TYPE_BYTE - if self.scanner.consume('table'): - size = self.defn_size() - type_ = TableType(type_, size) - name = self.defn_name() - return type_, name + return TYPE_BYTE elif self.scanner.consume('word'): - type_ = TYPE_WORD - if self.scanner.consume('table'): - size = self.defn_size() - type_ = TableType(type_, size) - name = self.defn_name() - return type_, name + return TYPE_WORD + elif self.scanner.consume('table'): + size = self.defn_size() + type_ = self.defn_type() + return TableType(type_, size) elif self.scanner.consume('vector'): - size = None - if self.scanner.consume('table'): - size = self.defn_size() - name = self.defn_name() + type_ = self.defn_type() + # TODO: assert that it's a routine type + return VectorType(type_) + elif self.scanner.consume('routine'): (inputs, outputs, trashes) = self.constraints() - type_ = VectorType(inputs=inputs, outputs=outputs, trashes=trashes) - if size is not None: - type_ = TableType(type_, size) - return type_, name + return RoutineType(inputs=inputs, outputs=outputs, trashes=trashes) elif self.scanner.consume('buffer'): size = self.defn_size() - name = self.defn_name() - return BufferType(size), name + return BufferType(size) else: self.scanner.expect('pointer') - name = self.defn_name() - return PointerType(), name + return PointerType() def defn_name(self): self.scanner.check_type('identifier') @@ -159,11 +153,12 @@ class Parser(object): trashes = set(self.labels()) return (inputs, outputs, trashes) - def routine(self): + def legacy_routine(self): self.scanner.expect('routine') name = self.scanner.token self.scanner.scan() (inputs, outputs, trashes) = self.constraints() + type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes) if self.scanner.consume('@'): self.scanner.check_type('integer literal') block = None @@ -172,10 +167,26 @@ class Parser(object): else: block = self.block() addr = None - location = LocationRef( - RoutineType(inputs=inputs, outputs=outputs, trashes=trashes), - name + location = LocationRef(type_, name) + return Routine( + name=name, block=block, addr=addr, + location=location ) + + def routine(self): + name = self.scanner.token + self.scanner.scan() + type_ = self.defn_type() + # TODO assert that it's a routine + if self.scanner.consume('@'): + self.scanner.check_type('integer literal') + block = None + addr = int(self.scanner.token) + self.scanner.scan() + else: + block = self.block() + addr = None + location = LocationRef(type_, name) return Routine( name=name, block=block, addr=addr, location=location diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 51032b7..270efe3 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -243,7 +243,7 @@ Can't `st` a `word` type. Storing to a table, you must use an index. | byte one - | byte table[256] many + | table[256] byte many | | routine main | outputs one @@ -256,7 +256,7 @@ Storing to a table, you must use an index. = ok | byte one - | byte table[256] many + | table[256] byte many | | routine main | outputs many @@ -269,7 +269,7 @@ Storing to a table, you must use an index. ? TypeMismatchError | byte one - | byte table[256] many + | table[256] byte many | | routine main | outputs one @@ -282,7 +282,7 @@ Storing to a table, you must use an index. ? TypeMismatchError | byte one - | byte table[256] many + | table[256] byte many | | routine main | outputs many @@ -297,7 +297,7 @@ Storing to a table, you must use an index. The index must be initialized. | byte one - | byte table[256] many + | table[256] byte many | | routine main | outputs many @@ -334,7 +334,7 @@ Reading from a table, you must use an index. | } ? TypeMismatchError - | byte table[256] many + | table[256] byte many | | routine main | outputs many @@ -347,7 +347,7 @@ Reading from a table, you must use an index. | } ? TypeMismatchError - | byte table[256] many + | table[256] byte many | | routine main | outputs many @@ -360,7 +360,7 @@ Reading from a table, you must use an index. | } = ok - | byte table[256] many + | table[256] byte many | | routine main | inputs many @@ -374,7 +374,7 @@ Reading from a table, you must use an index. The index must be initialized. - | byte table[256] many + | table[256] byte many | | routine main | inputs many @@ -388,7 +388,7 @@ The index must be initialized. Copying to and from a word table. | word one - | word table[256] many + | table[256] word many | | routine main | inputs one, many @@ -402,7 +402,7 @@ Copying to and from a word table. = ok | word one - | word table[256] many + | table[256] word many | | routine main | inputs one, many @@ -415,7 +415,7 @@ Copying to and from a word table. ? TypeMismatchError | word one - | word table[256] many + | table[256] word many | | routine main | inputs one, many @@ -429,7 +429,7 @@ Copying to and from a word table. You can also copy a literal word to a word table. - | word table[256] many + | table[256] word many | | routine main | inputs many @@ -1593,10 +1593,11 @@ Read through a pointer. Routines are constants. You need not, and in fact cannot, specify a constant as an input to, an output of, or as a trashed value of a routine. - | vector vec + | vector routine | inputs x | outputs x | trashes z, n + | vec | | routine foo | inputs x @@ -1615,10 +1616,11 @@ as an input to, an output of, or as a trashed value of a routine. | } ? ConstantConstraintError: foo in main - | vector vec + | vector routine | inputs x | outputs x | trashes z, n + | vec | | routine foo | inputs x @@ -1636,10 +1638,11 @@ as an input to, an output of, or as a trashed value of a routine. | } ? ConstantConstraintError: foo in main - | vector vec + | vector routine | inputs x | outputs x | trashes z, n + | vec | | routine foo | inputs x @@ -1660,10 +1663,11 @@ as an input to, an output of, or as a trashed value of a routine. You can copy the address of a routine into a vector, if that vector is declared appropriately. - | vector vec + | vector routine | inputs x | outputs x | trashes z, n + | vec | | routine foo | inputs x @@ -1683,10 +1687,11 @@ declared appropriately. But not if the vector is declared inappropriately. - | vector vec + | vector routine | inputs y | outputs y | trashes z, n + | vec | | routine foo | inputs x @@ -1707,10 +1712,11 @@ But not if the vector is declared inappropriately. "Appropriately" means, if the routine affects no more than what is named in the input/output sets of the vector. - | vector vec + | vector routine | inputs a, x | outputs x | trashes a, z, n + | vec | | routine foo | inputs x @@ -1730,10 +1736,11 @@ in the input/output sets of the vector. Routines are read-only. - | vector vec + | vector routine | inputs x | outputs x | trashes z, n + | vec | | routine foo | inputs x @@ -1753,7 +1760,9 @@ Routines are read-only. Indirect call. - | vector foo outputs x trashes z, n + | vector routine + | outputs x trashes z, n + | foo | | routine bar outputs x trashes z, n { | ld x, 200 @@ -1767,7 +1776,7 @@ Indirect call. Calling the vector does indeed trash the things the vector says it does. - | vector foo trashes x, z, n + | vector routine trashes x, z, n foo | | routine bar trashes x, z, n { | ld x, 200 @@ -1867,7 +1876,7 @@ Can `goto` a routine that outputs or trashes less than the current routine. Indirect goto. - | vector foo outputs x trashes a, z, n + | vector routine outputs x trashes a, z, n foo | | routine bar outputs x trashes a, z, n { | ld x, 200 @@ -1882,8 +1891,9 @@ Indirect goto. Jumping through the vector does indeed trash, or output, the things the vector says it does. - | vector foo + | vector routine | trashes a, x, z, n + | foo | | routine bar | trashes a, x, z, n { @@ -1905,9 +1915,9 @@ vector says it does. | } ? UnmeaningfulReadError: x in main - | vector foo + | vector routine | outputs x - | trashes a, z, n + | trashes a, z, n foo | | routine bar | outputs x @@ -1935,12 +1945,14 @@ vector says it does. A vector can be copied into a vector table. - | vector one + | vector routine | outputs x | trashes a, z, n - | vector table[256] many + | one + | table[256] vector routine | outputs x | trashes a, z, n + | many | | routine bar outputs x trashes a, z, n { | ld x, 200 @@ -1959,12 +1971,14 @@ A vector can be copied into a vector table. A vector can be copied out of a vector table. - | vector one + | vector routine | outputs x | trashes a, z, n - | vector table[256] many + | one + | table[256] vector routine | outputs x | trashes a, z, n + | many | | routine bar outputs x trashes a, z, n { | ld x, 200 @@ -1983,9 +1997,10 @@ A vector can be copied out of a vector table. A routine can be copied into a vector table. - | vector table[256] many - | outputs x - | trashes a, z, n + | table[256] vector routine + | outputs x + | trashes a, z, n + | many | | routine bar outputs x trashes a, z, n { | ld x, 200 @@ -2003,9 +2018,10 @@ A routine can be copied into a vector table. A vector in a vector table cannot be directly called. - | vector table[256] many - | outputs x - | trashes a, z, n + | table[256] vector routine + | outputs x + | trashes a, z, n + | many | | routine bar outputs x trashes a, z, n { | ld x, 200 diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index af10e26..3effdf2 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -139,7 +139,7 @@ Word memory locations with explicit address, initial value. Initialized byte table. Bytes allocated, but beyond the string, are 0's. - | byte table[8] message : "WHAT?" + | table[8] byte message : "WHAT?" | | routine main | inputs message @@ -352,7 +352,7 @@ The body of `repeat forever` can be empty. Indexed access. | byte one - | byte table[256] many + | table[256] byte many | | routine main | outputs many @@ -371,8 +371,8 @@ Indexed access. Byte tables take up 256 bytes in memory. - | byte table[256] tab1 - | byte table[256] tab2 + | table[256] byte tab1 + | table[256] byte tab2 | | routine main | inputs tab1 @@ -458,7 +458,7 @@ Copy literal word to word. You can also copy a literal word to a word table. - | word table[256] many + | table[256] word many | | routine main | inputs many @@ -477,8 +477,8 @@ You can also copy a literal word to a word table. Copy vector to vector. - | vector bar - | vector baz + | vector routine bar + | vector routine baz | | routine main | inputs baz @@ -495,7 +495,7 @@ Copy vector to vector. Copy routine to vector, inside an `interrupts off` block. - | vector bar + | vector routine bar | | routine foo | inputs x @@ -527,7 +527,7 @@ Copy routine to vector, inside an `interrupts off` block. Copy word to word table and back, with both `x` and `y` as indexes. | word one - | word table[256] many + | table[256] word many | | routine main | inputs one, many @@ -568,7 +568,7 @@ Copy word to word table and back, with both `x` and `y` as indexes. Indirect call. - | vector foo outputs x trashes z, n + | vector routine outputs x trashes z, n foo | | routine bar outputs x trashes z, n { | ld x, 200 @@ -608,12 +608,14 @@ goto. Copying to and from a vector table. - | vector one + | vector routine | outputs x | trashes a, z, n - | vector table[256] many + | one + | table[256] vector routine | outputs x | trashes a, z, n + | many | | routine bar outputs x trashes a, z, n { | ld x, 200 diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 8f046b6..256d510 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -135,10 +135,7 @@ User-defined memory addresses of different types. | byte byt | word wor - | vector vec trashes a - | byte table[256] tab - | word table[256] wtab - | vector table[256] vtab trashes a + | vector routine trashes a vec | buffer[2048] buf | pointer ptr | @@ -146,6 +143,16 @@ User-defined memory addresses of different types. | } = ok +Tables of different types. + + | table[256] byte tab + | table[256] word wtab + | table[256] vector routine trashes a vtab + | + | routine main { + | } + = ok + Explicit memory address. | byte screen @ 1024 @@ -178,7 +185,7 @@ Cannot have both initial value and explicit address. User-defined locations of other types. - | byte table[256] screen @ 1024 + | table[256] byte screen @ 1024 | word r1 | word r2 @ 60000 | word r3 : 2000 @@ -189,7 +196,7 @@ User-defined locations of other types. Initialized byte table. - | byte table[28] message : "WHAT DO YOU WANT TO DO NEXT?" + | table[28] byte message : "WHAT DO YOU WANT TO DO NEXT?" | | routine main { | } @@ -291,7 +298,7 @@ Can't define two routines with the same name. Declaring byte and word table memory location. - | byte table[256] tab + | table[256] byte tab | | routine main { | ld x, 0 @@ -302,7 +309,7 @@ Declaring byte and word table memory location. = ok | word one - | word table[256] many + | table[256] word many | | routine main { | ld x, 0 @@ -314,11 +321,11 @@ Declaring byte and word table memory location. Declaring and calling a vector. - | vector cinv + | vector routine | inputs a | outputs x | trashes a, x, z, n - | @ 788 + | cinv @ 788 | | routine foo { | ld a, 0 @@ -345,11 +352,11 @@ Only vectors can be decorated with constraints like that. Constraints set may only contain labels. - | vector cinv + | vector routine | inputs a | outputs 200 | trashes a, x, z, n - | @ 788 + | cinv @ 788 | | routine foo { | ld a, 0 @@ -364,11 +371,11 @@ Constraints set may only contain labels. A vector can name itself in its inputs, outputs, and trashes. - | vector cinv + | vector routine | inputs cinv, a | outputs cinv, x | trashes a, x, z, n - | @ 788 + | cinv @ 788 | | routine foo { | ld a, 0 @@ -384,11 +391,11 @@ A vector can name itself in its inputs, outputs, and trashes. A routine can be copied into a vector before the routine appears in the program, *however*, it must be marked as such with the keyword `forward`. - | vector cinv + | vector routine | inputs cinv, a | outputs cinv, x | trashes a, x, z, n - | @ 788 + | cinv @ 788 | routine main { | with interrupts off { | copy foo, cinv @@ -400,11 +407,11 @@ A routine can be copied into a vector before the routine appears in the program, | } ? SyntaxError: Undefined symbol - | vector cinv + | vector routine | inputs cinv, a | outputs cinv, x | trashes a, x, z, n - | @ 788 + | cinv @ 788 | routine main { | with interrupts off { | copy forward foo, cinv @@ -434,7 +441,7 @@ goto. | } = ok - | vector foo + | vector routine foo | | routine main { | goto foo