diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index afacd4c..9a6b29b 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -501,11 +501,22 @@ The sense of the test can be inverted with `not`. Grammar ------- - Program ::= {Defn} {Routine}. + Program ::= {TypeDefn} {Defn} {Routine}. + TypeDefn::= "typedef" Type Ident. Defn ::= Type Ident [Constraints] (":" Literal | "@" LitWord). - Type ::= "byte" ["table"] | "vector" + Type ::= "(" Type ")" | TypeExpr ["table" TypeSize]. + TypeExpr::= "byte" + | "word" + | "buffer" TypeSize + | "pointer" + | "vector" Type + | "routine" Constraints + . + TypeSize::= "[" LitWord "]". Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs]. - Routine ::= "routine" Ident Constraints (Block | "@" LitWord). + Routine ::= "define" Ident Type (Block | "@" LitWord). + | "routine" Ident Constraints (Block | "@" LitWord) + . LocExprs::= LocExpr {"," LocExpr}. LocExpr ::= Register | Flag | Literal | Ident. Register::= "a" | "x" | "y". diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 5e7c831..1d00584 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -2,6 +2,58 @@ // * Demo Game for SixtyPical * // **************************** +// ---------------------------------------------------------------- +// Type Definitions +// ---------------------------------------------------------------- + +// +// Type of routines (and vectors to those routines) which are called on each frame +// to implement a certain state of the game (title screen, in play, game over, etc.) +// +// This type is also used as the type for the interrupt vector, even though +// the interrupt routine saves and restores everything before being called and +// thus clearly does not actually trash all the registers. It is declared this +// way so that the game state routines, which do trash these registers, can be +// assigned to it. +// +// This type is also used as the type for the location the old interrupt vector +// is backed up to, because all the game state routines `goto` the old handler +// and the end of their own routines, so the type needs to be compatible. +// (In a good sense, it is a continuation.) +// +// Further, +// +// It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs. +// They're only there to support the fact that game states sometimes clear the +// screen, and sometimes don't. When they don't, they preserve the screen, and +// currently the way to say "we preserve the screen" is to have it as both input +// and output. There is probably a better way to do this, but it needs thought. +// + +typedef routine + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, new_pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, + actor_pos, pos, new_pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + trashes a, x, y, c, z, n, v, ptr, save_x, compare_target + game_state_routine + +// +// Routines that are called to get the new state of each actor (player, enemy, etc.) +// +// Routines that conform to this type also follow this convention: +// +// Set carry if the player perished. Carry clear otherwise. +// + +typedef routine + inputs pos, delta, joy2, screen + outputs pos, delta, new_pos, screen, c + trashes a, x, y, z, n, v, ptr, compare_target + logic_routine + // ---------------------------------------------------------------- // System Locations // ---------------------------------------------------------------- @@ -36,7 +88,7 @@ word table[256] actor_delta word delta byte button_down : 0 // effectively static-local to check_button -byte table[18] press_fire_msg: "PRESS`FIRE`TO`PLAY" +byte table[32] press_fire_msg: "PRESS`FIRE`TO`PLAY" byte save_x word compare_target @@ -44,52 +96,23 @@ word compare_target // // Points to the routine that implements the current game state. // -// It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs. -// They're only there to support the fact that game states sometimes clear the -// screen, and sometimes don't. When they don't, they preserve the screen, and -// currently the way to say "we preserve the screen" is to have it as both input -// and output. There is probably a better way to do this, but it needs thought. -// -vector dispatch_game_state - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target +vector game_state_routine + dispatch_game_state // -// The constraints on these 2 vectors are kind-of sort-of big fibs. -// They're only written this way so they can be compatible with our -// routine. In fact, CINV is an interrupt routine where it doesn't -// really matter what you trash anyway, because all registers were -/// saved by the caller (the KERNAL) and will be restored by the end -// of the code of the saved origin cinv routine that we goto. -// -// I wonder if this could be arranged somehow to be less fibby, in -// a future version of SixtyPical. +// Interrupt vector. Has same type as game states (see above.) // -vector cinv - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target - @ 788 +vector game_state_routine + cinv @ 788 -vector save_cinv - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target +// +// Location to which the old interrupt vector is saved before replacement. +// + +vector game_state_routine + save_cinv // ---------------------------------------------------------------- // Utility Routines @@ -242,14 +265,7 @@ routine init_game // Actor Logics // ---------------------------------------------------------------- -// -// Sets carry if the player perished. Carry clear otherwise. -// - -routine player_logic - inputs pos, delta, joy2, screen - outputs pos, delta, new_pos, screen, c - trashes a, x, y, z, n, v, ptr, compare_target +define player_logic logic_routine { call read_stick @@ -304,14 +320,7 @@ routine player_logic } } -// -// Sets carry if the player perished. Carry clear otherwise. -// - -routine enemy_logic - inputs pos, delta, screen - outputs pos, delta, new_pos, screen, c - trashes a, x, y, z, n, v, ptr, compare_target +define enemy_logic logic_routine { call calculate_new_position call check_new_position_in_bounds @@ -377,18 +386,7 @@ routine enemy_logic // Game States // ---------------------------------------------------------------- -// -// Because these all `goto save_cinv` at the end, they must have the same signature as that routine. -// - -routine game_state_title_screen - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target +define game_state_title_screen game_state_routine { ld y, 0 repeat { @@ -426,14 +424,7 @@ routine game_state_title_screen goto save_cinv } -routine game_state_play - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target +define game_state_play game_state_routine { ld x, 0 repeat { @@ -479,14 +470,7 @@ routine game_state_play goto save_cinv } -routine game_state_game_over - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target +define game_state_game_over game_state_routine { st off, c call check_button @@ -516,14 +500,7 @@ routine game_state_game_over // * Main Game Loop Driver * // ************************* -routine our_cinv - inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs button_down, dispatch_game_state, - actor_pos, pos, new_pos, actor_delta, delta, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, save_x, compare_target +define our_cinv game_state_routine { goto dispatch_game_state } diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 2320dbd..ae3bb1f 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 ) @@ -176,13 +176,18 @@ class Analyzer(object): (location.name, self.current_routine.name) ) - def assert_affected_within(self, name, affected, limited_to): + def assert_affected_within(self, name, affecting_type, limiting_type): + assert name in ('inputs', 'outputs', 'trashes') + affected = getattr(affecting_type, name) + limited_to = getattr(limiting_type, name) overage = affected - limited_to if not overage: return - message = 'in %s: %s are %s but affects %s which exceeds it by: %s ' % ( + message = 'in %s: %s for %s are %s\n\nbut %s affects %s\n\nwhich exceeds it by: %s ' % ( self.current_routine.name, name, - LocationRef.format_set(limited_to), LocationRef.format_set(affected), LocationRef.format_set(overage) + limiting_type, LocationRef.format_set(limited_to), + affecting_type, LocationRef.format_set(affected), + LocationRef.format_set(overage) ) raise IncompatibleConstraintsError(message) @@ -303,6 +308,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 +373,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 +386,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 +394,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, dest.type.of_type) + 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((src, dest)) else: @@ -434,18 +444,20 @@ 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) # and that this routine's trashes and output constraints are a # superset of the called routine's current_type = self.current_routine.location.type - self.assert_affected_within('outputs', type_.outputs, current_type.outputs) - self.assert_affected_within('trashes', type_.trashes, current_type.trashes) + self.assert_affected_within('outputs', type_, current_type) + self.assert_affected_within('trashes', type_, current_type) self.has_encountered_goto = True elif opcode == 'trash': diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 6396fd2..45f6543 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -17,16 +17,31 @@ class Type(object): def __hash__(self): return hash(self.name) + def backpatch_constraint_labels(self, resolver): + def resolve(w): + if not isinstance(w, basestring): + return w + return resolver(w) + 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([resolve(w) for w in self.inputs]) + self.outputs = set([resolve(w) for w in self.outputs]) + self.trashes = set([resolve(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 +52,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 +65,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 +80,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 +160,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..0a17e37 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 @@ -19,6 +19,7 @@ class Parser(object): def __init__(self, text): self.scanner = Scanner(text) self.symbols = {} # token -> SymEntry + self.typedefs = {} # token -> Type AST for token in ('a', 'x', 'y'): self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token)) for token in ('c', 'z', 'n', 'v'): @@ -35,16 +36,25 @@ class Parser(object): def program(self): defns = [] routines = [] - while self.scanner.on('byte', 'word', 'vector', 'buffer', 'pointer'): + while self.scanner.on('typedef'): + typedef = self.typedef() + typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine', + typenames.extend(self.typedefs.keys()) + while self.scanner.on(*typenames): 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() + routine = self.routine(name) + 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) @@ -52,30 +62,42 @@ class Parser(object): self.scanner.check_type('EOF') # now backpatch the executable types. + #for type_name, type_ in self.typedefs.iteritems(): + # type_.backpatch_constraint_labels(lambda w: self.lookup(w)) 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 typedef(self): + self.scanner.expect('typedef') + type_ = self.defn_type() + name = self.defn_name() + if name in self.typedefs: + raise SyntaxError('Type "%s" already declared' % name) + self.typedefs[name] = type_ + return type_ + 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 +129,43 @@ class Parser(object): self.scanner.expect(']') return size - def defn_type_and_name(self): + def defn_type(self): + type_ = None + + if self.scanner.consume('('): + type_ = self.defn_type() + self.scanner.expect(')') + return type_ + 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 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 elif self.scanner.consume('vector'): - size = None - if self.scanner.consume('table'): - size = self.defn_size() - name = self.defn_name() + type_ = self.defn_type() + if not isinstance(type_, RoutineType): + raise SyntaxError("Vectors can only be of a routine, not %r" % type_) + type_ = 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 + type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes) elif self.scanner.consume('buffer'): size = self.defn_size() - name = self.defn_name() - return BufferType(size), name + type_ = BufferType(size) + elif self.scanner.consume('pointer'): + type_ = PointerType() else: - self.scanner.expect('pointer') - name = self.defn_name() - return PointerType(), name + type_name = self.scanner.token + self.scanner.scan() + if type_name not in self.typedefs: + raise SyntaxError("Undefined type '%s'" % type_name) + type_ = self.typedefs[type_name] + + if self.scanner.consume('table'): + size = self.defn_size() + type_ = TableType(type_, size) + + return type_ def defn_name(self): self.scanner.check_type('identifier') @@ -159,11 +185,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 +199,25 @@ 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): + type_ = self.defn_type() + if not isinstance(type_, RoutineType): + raise SyntaxError("Can only define a routine, not %r" % type_) + 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..0324472 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -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 @@ -1931,16 +1941,18 @@ vector says it does. | } = ok -### Vector tables ### +### vector tables ### A vector can be copied into a vector table. - | vector one + | vector routine | outputs x | trashes a, z, n - | vector table[256] many + | one + | vector (routine | outputs x - | trashes a, z, n + | trashes a, z, n) + | table[256] 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 + | vector (routine | outputs x - | trashes a, z, n + | trashes a, z, n) + | table[256] 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 + | vector (routine + | outputs x + | trashes a, z, n) + | table[256] 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 + | vector (routine + | outputs x + | trashes a, z, n) + | table[256] many | | routine bar outputs x trashes a, z, n { | ld x, 200 @@ -2021,3 +2037,55 @@ A vector in a vector table cannot be directly called. | call many + x | } ? ValueError + +### typedef ### + +A typedef is a more-readable alias for a type. "Alias" means +that types have structural equivalence, not name equivalence. + + | typedef routine + | inputs x + | outputs x + | trashes z, n + | routine_type + | + | vector routine_type vec + | + | routine foo + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine main + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok + +The new style routine definitions support typedefs. + + | typedef routine + | inputs x + | outputs x + | trashes z, n + | routine_type + | + | vector routine_type vec + | + | define foo routine_type + | { + | inc x + | } + | + | routine main + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index af10e26..4ef66f1 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -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 @@ -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 + | vector (routine | outputs x - | trashes a, z, n + | trashes a, z, n) + | table[256] 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..1f0c759 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,36 @@ User-defined memory addresses of different types. | } = ok +Tables of different types. + + | byte table[256] tab + | word table[256] wtab + | vector (routine trashes a) table[256] vtab + | + | routine main { + | } + = ok + +Typedefs of different types. + + | typedef byte octet + | typedef octet table[256] twokay + | typedef routine trashes a game_routine + | vector game_routine start_game + | + | routine main { + | } + = ok + +Can't have two typedefs with the same name. + + | typedef byte frank + | typedef word frank + | + | routine main { + | } + ? SyntaxError + Explicit memory address. | byte screen @ 1024 @@ -189,7 +216,7 @@ User-defined locations of other types. Initialized byte table. - | byte table[28] message : "WHAT DO YOU WANT TO DO NEXT?" + | byte table[32] message : "WHAT DO YOU WANT TO DO NEXT?" | | routine main { | } @@ -314,11 +341,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 +372,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 +391,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 +411,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 +427,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 +461,7 @@ goto. | } = ok - | vector foo + | vector routine foo | | routine main { | goto foo @@ -465,3 +492,40 @@ Buffers and pointers. | copy [ptr] + y, foo | } = ok + +Routines can be defined in a new style. + + | typedef routine + | inputs x + | outputs x + | trashes z, n + | routine_type + | + | vector routine_type vec + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine main + | outputs vec + | trashes a, z, n + | { + | copy foo, vec + | } + = ok + +Only routines can be defined in the new style. + + | define foo byte table[256] + | + | routine main + | trashes a, z, n + | { + | ld a, 0 + | } + ? SyntaxError