diff --git a/HISTORY.md b/HISTORY.md index c4cb657..0259d3c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,21 @@ History of SixtyPical ===================== +0.10 +---- + +* Can `call` and `goto` routines that are defined further down in the source code. +* The `forward` modifier can also be used to indicate that the symbol being copied + in a `copy` to a vector is a routine that is defined further down in the source. +* Initialized `word` memory locations. +* Can `copy` a literal word to a word table. +* Subtract word (constant or memory location) from word memory location. +* `trash` instruction explicitly indicates a value is no longer considered meaningful. +* `copy []+y, a` can indirectly read a byte value into the `a` register. +* Fixed bug which was preventing `if` branches to diverge in what they initialized, + if it was already initialized when going into the `if`. +* Fixed a bug which was making it crash when trying to analyze `repeat forever` loops. + 0.9 --- diff --git a/README.md b/README.md index efadf04..cdc1369 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The reference implementation can execute, analyze, and compile SixtyPical programs to 6502 machine code. SixtyPical is a work in progress. The current released version of SixtyPical -is 0.9. +is 0.10. Documentation ------------- @@ -46,16 +46,6 @@ Finish the little demo "game" where you can move a block around the screen with the joystick (i.e. bring it up to par with the original demo game that was written for SixtyPical) -### `call` routines that are defined further down in the source code - -We might have a graph of states that refer to each other and that want to `goto` -each other. Thus we need this. We have it for vectors, but we need it for `call`. - -### Allow branches to diverge in what they touch - -For example, if the routine inputs and outputs `foo`, and one branch of an `if` -sets `foo` and the other does not touch it, that should be OK. - ### `vector table` type ### `low` and `high` address operators @@ -75,20 +65,28 @@ But if you add a value ≥ N to it, it becomes invalid. This should be tracked in the abstract interpretation. (If only because abstract interpretation is the major point of this project!) -### And at some point... +### Routine-local static memory locations -* Compare word (constant or memory location) with memory location or pointer. (Maybe?) +These would not need to appear in the inputs/outputs/trashes sets of the routines +that call this routine. + +These might be forced to specify an initial value so that they can always be +assumed to be meaningful. + +### More modes for `copy` + +* don't allow `copy foo, a` probably. insist on `ld a, foo` for this. +* have `copy` instruction able to copy a byte to a user-def mem loc, etc. * `copy x, [ptr] + y` * Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA! + +### And at some point... + * Check that the buffer being read or written to through pointer, appears in approporiate inputs or outputs set. * `byte table` and `word table` of sizes other than 256 -* initialized `byte table` memory locations * always analyze before executing or compiling, unless told not to -* `trash` instruction. * `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them. -* pre-initialized `word` variables * error messages that include the line number of the source code -* have `copy` instruction able to copy a byte to a user-def mem loc, etc. * add absolute addressing in shl/shr, absolute-indexed for add, sub, etc. * check and disallow recursion. * automatic tail-call optimization (could be tricky, w/constraints?) diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 3cff7c7..85e1aa8 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -1,7 +1,7 @@ SixtyPical ========== -This document describes the SixtyPical programming language version 0.9, +This document describes the SixtyPical programming language version 0.10, both its execution aspect and its static analysis aspect (even though these are, technically speaking, separate concepts.) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 08490a0..b6405be 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -27,18 +27,38 @@ byte joy2 @ $dc00 // ---------------------------------------------------------------- pointer ptr @ 254 + +word table actor_pos word pos +word new_pos + +word table actor_delta word delta + byte button_down : 0 // effectively static-local to check_button +byte table press_fire_msg: "PRESS`FIRE`TO`PLAY" + +byte save_x +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, pos, button_down, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state - trashes a, x, y, c, z, n, v, ptr + 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 // // The constraints on these 2 vectors are kind-of sort-of big fibs. @@ -53,15 +73,23 @@ vector dispatch_game_state // vector cinv - inputs joy2, pos, button_down, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state - trashes a, x, y, c, z, n, v, ptr + 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 save_cinv - inputs joy2, pos, button_down, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state - trashes a, x, y, c, z, n, v, ptr + 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 // ---------------------------------------------------------------- // Utility Routines @@ -154,6 +182,197 @@ routine clear_screen } until z } +routine calculate_new_position + inputs pos, delta + outputs new_pos + trashes a, c, n, z, v +{ + copy pos, new_pos + st off, c + add new_pos, delta +} + +routine check_new_position_in_bounds + inputs new_pos + outputs c + trashes compare_target, a, z, n, v +{ + copy 1000, compare_target + st on, c + sub compare_target, new_pos + + if not c { + copy word 0, compare_target + st on, c + sub compare_target, new_pos + if not c { + st off, c + } else { + st on, c + } + } else { + st on, c + } +} + +routine init_game + inputs actor_pos, actor_delta + outputs actor_pos, actor_delta, pos + trashes a, y, z, n, c, v +{ + ld y, 0 + copy word 0, pos + repeat { + copy pos, actor_pos + y + copy word 40, actor_delta + y + + st off, c + add pos, word 7 + + inc y + cmp y, 16 + } until z + + ld y, 0 + copy word 0, actor_pos + y + copy word 0, actor_delta + y +} + +// ---------------------------------------------------------------- +// 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 +{ + call read_stick + + call calculate_new_position + call check_new_position_in_bounds + + if c { + copy ^screen, ptr + st off, c + add ptr, new_pos + ld y, 0 + + // check collision. + copy [ptr] + y, a + // if "collision" is with your own self, treat it as if it's blank space! + cmp a, 81 + if z { + ld a, 32 + } + cmp a, 32 + if z { + copy ^screen, ptr + st off, c + add ptr, pos + copy 32, [ptr] + y + + copy new_pos, pos + + copy ^screen, ptr + st off, c + add ptr, pos + copy 81, [ptr] + y + + st off, c + } else { + st on, c + trash n + trash a + trash z + } + + // FIXME these trashes, strictly speaking, probably shouldn't be needed, + // but currently the compiler cares too much about values that are + // initialized in one branch of an `if`, but not the other, but trashed + // at the end of the routine anyway. + trash ptr + trash y + trash a + trash v + } else { + st off, c + } +} + +// +// 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 +{ + call calculate_new_position + call check_new_position_in_bounds + + if c { + copy ^screen, ptr + st off, c + add ptr, new_pos + ld y, 0 + + // check collision. + copy [ptr] + y, a + // if "collision" is with your own self, treat it as if it's blank space! + cmp a, 82 + if z { + ld a, 32 + } + cmp a, 32 + if z { + copy ^screen, ptr + st off, c + add ptr, pos + copy 32, [ptr] + y + + copy new_pos, pos + + copy ^screen, ptr + st off, c + add ptr, pos + copy 82, [ptr] + y + + st off, c + } else { + st on, c + trash n + trash a + trash z + } + + // FIXME these trashes, strictly speaking, probably shouldn't be needed, + // but currently the compiler cares too much about values that are + // initialized in one branch of an `if`, but not the other, but trashed + // at the end of the routine anyway. + trash ptr + trash y + trash a + } else { + copy delta, compare_target + st on, c + sub compare_target, word 40 + if not z { + copy word 40, delta + } else { + copy $ffd8, delta + } + trash compare_target + } + + st off, c +} + // ---------------------------------------------------------------- // Game States // ---------------------------------------------------------------- @@ -162,49 +381,132 @@ routine clear_screen // Because these all `goto save_cinv` at the end, they must have the same signature as that routine. // -routine game_state_play - inputs joy2, pos, button_down, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state - trashes a, x, y, c, z, n, v, ptr -{ - call read_stick - - st off, c - add pos, delta - - copy ^screen, ptr - st off, c - add ptr, pos - - ld y, 0 - copy 81, [ptr] + y - - goto save_cinv -} - routine game_state_title_screen - inputs joy2, pos, button_down, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state - trashes a, x, y, c, z, n, v, ptr + 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 { - ld y, 10 + ld y, 0 repeat { - ld a, 90 + ld a, press_fire_msg + y + + st on, c + sub a, 64 // yuck. oh well + st a, screen1 + y inc y - cmp y, 20 + cmp y, 18 } until z st off, c call check_button if c { - // call clear_screen - // call init_game - copy game_state_play, dispatch_game_state + call clear_screen + call init_game + copy forward game_state_play, dispatch_game_state + + // FIXME these trashes, strictly speaking, probably shouldn't be needed, + // but currently the compiler cares too much about values that are + // initialized in one branch of an `if`, but not the other, but trashed + // at the end of the routine anyway. + trash a + trash n + trash z } else { - // This is sort of a hack. FIXME: let `if` branches diverge this much. - copy dispatch_game_state, dispatch_game_state + trash y + trash c + trash v + } + + 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 +{ + ld x, 0 + repeat { + copy actor_pos + x, pos + copy actor_delta + x, delta + + st x, save_x + + // FIXME need VECTOR TABLEs to make this happen: + // copy actor_logic, x dispatch_logic + // call indirect_jsr_logic + // For now, just check the actor ID to see what type it is, and go from there. + + cmp x, 0 + if z { + call player_logic + } else { + call enemy_logic + } + + if c { + // Player died! Want no dead! Break out of the loop (this is a bit awkward.) + call clear_screen + copy forward game_state_game_over, dispatch_game_state + ld x, 15 + st x, save_x + trash n + trash z + trash x + } else { + trash c + } + + ld x, save_x + + copy pos, actor_pos + x + copy delta, actor_delta + x + + inc x + cmp x, 16 + } until z + + 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 +{ + st off, c + call check_button + + if c { + call clear_screen + call init_game + copy game_state_title_screen, dispatch_game_state + + // FIXME these trashes, strictly speaking, probably shouldn't be needed, + // but currently the compiler cares too much about values that are + // initialized in one branch of an `if`, but not the other, but trashed + // at the end of the routine anyway. + trash a + trash n + trash z + } else { + trash y + trash c + trash v } goto save_cinv @@ -215,9 +517,13 @@ routine game_state_title_screen // ************************* routine our_cinv - inputs joy2, pos, button_down, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state - trashes a, x, y, c, z, n, v, ptr + 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 { goto dispatch_game_state } @@ -243,8 +549,6 @@ routine main copy cinv, save_cinv copy our_cinv, cinv } - // FIXME: find out why `repeat { } forever` does not analyze OK - repeat { - ld a, 0 - } until not z + + repeat { } forever } diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 34fce75..78a594b 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -89,17 +89,8 @@ class Context(object): self._writeable.add(ref) def __str__(self): - def locstr(loc): - if isinstance(loc, LocationRef): - return "{}:{}".format(loc.name, loc.type) - else: - return str(loc) - - def locsetstr(s): - return '{' + ', '.join([locstr(loc) for loc in list(s)]) + '}' - return "Context(\n _touched={},\n _meaningful={},\n _writeable={}\n)".format( - locsetstr(self._touched), locsetstr(self._meaningful), locsetstr(self._writeable) + LocationRef.format_set(self._touched), LocationRef.format_set(self._meaningful), LocationRef.format_set(self._writeable) ) def clone(self): @@ -185,22 +176,12 @@ class Analyzer(object): ) def assert_affected_within(self, name, affected, limited_to): - # We reduce the set of LocationRefs to a set of strings (their labels). - # This is necessary because currently, two LocationRefs that refer to the - # same location are not considered euqal. (But two LocationRefs with the - # same label should always be the same type.) - - affected = set([loc.name for loc in affected]) - limited_to = set([loc.name for loc in limited_to]) - - def loc_list(label_set): - return ', '.join(sorted(label_set)) - overage = affected - limited_to if not overage: return - message = 'in %s: %s are {%s} but affects {%s} which exceeds by: {%s} ' % ( - self.current_routine.name, name, loc_list(limited_to), loc_list(affected), loc_list(overage) + message = 'in %s: %s are %s but affects %s which exceeds it by: %s ' % ( + self.current_routine.name, name, + LocationRef.format_set(limited_to), LocationRef.format_set(affected), LocationRef.format_set(overage) ) raise IncompatibleConstraintsError(message) @@ -292,9 +273,15 @@ class Analyzer(object): else: self.assert_type(TYPE_WORD, dest) elif opcode == 'sub': - self.assert_type(TYPE_BYTE, src, dest) context.assert_meaningful(src, dest, FLAG_C) - context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) + if src.type == TYPE_BYTE: + self.assert_type(TYPE_BYTE, src, dest) + context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) + else: + self.assert_type(TYPE_WORD, src, dest) + context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C, FLAG_V) + context.set_touched(REG_A) + context.set_unmeaningful(REG_A) elif opcode in ('inc', 'dec'): self.assert_type(TYPE_BYTE, dest) context.assert_meaningful(dest) @@ -332,23 +319,27 @@ class Analyzer(object): # doesn't really matter if you modified it or not, coming out. for ref in context1.each_meaningful(): context2.assert_meaningful( - ref, exception_class=InconsistentInitializationError, message='initialized in block 1 but not in block 2' + ref, exception_class=InconsistentInitializationError, + message='initialized in block 1 but not in block 2 of `if {}`'.format(src) ) for ref in context2.each_meaningful(): context1.assert_meaningful( - ref, exception_class=InconsistentInitializationError, message='initialized in block 2 but not in block 1' + ref, exception_class=InconsistentInitializationError, + message='initialized in block 2 but not in block 1 of `if {}`'.format(src) ) context.set_from(context1) elif opcode == 'repeat': # it will always be executed at least once, so analyze it having # been executed the first time. self.analyze_block(instr.block, context) - context.assert_meaningful(src) + if src is not None: # None indicates 'repeat forever' + context.assert_meaningful(src) # now analyze it having been executed a second time, with the context # of it having already been executed. self.analyze_block(instr.block, context) - context.assert_meaningful(src) + if src is not None: + context.assert_meaningful(src) elif opcode == 'copy': # 1. check that their types are compatible @@ -369,7 +360,7 @@ class Analyzer(object): else: raise TypeMismatchError((src, dest)) - elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): + elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef): if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE: pass else: @@ -403,11 +394,15 @@ class Analyzer(object): context.assert_meaningful(src.ref, REG_Y) # TODO this will need to be more sophisticated. the thing ref points to is touched, as well. context.set_touched(src.ref) # TODO and REG_Y? if not, why not? + context.set_touched(dest) context.set_written(dest) elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): context.assert_meaningful(src, dest.ref, dest.index) context.set_touched(src) # TODO and dest.index? context.set_written(dest.ref) + elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef): + context.assert_meaningful(src, dest.ref, dest.index) + context.set_written(dest.ref) elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): context.assert_meaningful(src.ref, src.index, dest) context.set_touched(dest) # TODO and src.index? @@ -417,7 +412,15 @@ class Analyzer(object): context.set_written(dest) context.set_touched(REG_A, FLAG_Z, FLAG_N) - context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N) + context.set_unmeaningful(FLAG_Z, FLAG_N) + + # FIXME: this is just to support "copy [foo] + y, a". consider disallowing `a` as something + # that can be used in `copy`. should be using `st` or `ld` instead, probably. + if dest == REG_A: + context.set_touched(REG_A) + context.set_written(REG_A) + else: + context.set_unmeaningful(REG_A) elif opcode == 'with-sei': self.analyze_block(instr.block, context) @@ -439,5 +442,7 @@ class Analyzer(object): self.assert_affected_within('trashes', type_.trashes, current_type.trashes) self.has_encountered_goto = True + elif opcode == 'trash': + context.set_unmeaningful(instr.dest) else: raise NotImplementedError(opcode) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 8ac674a..1888cd7 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -6,7 +6,7 @@ from sixtypical.model import ( TYPE_BIT, TYPE_BYTE, TYPE_BYTE_TABLE, TYPE_WORD, TYPE_WORD_TABLE, BufferType, PointerType, RoutineType, VectorType, REG_A, REG_X, REG_Y, FLAG_C ) -from sixtypical.emitter import Byte, Label, Offset, LowAddressByte, HighAddressByte +from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte from sixtypical.gen6502 import ( Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative, LDA, LDX, LDY, STA, STX, STY, @@ -31,6 +31,18 @@ class Compiler(object): self.labels = {} self.trampolines = {} # Location -> Label + # helper methods + + def addressing_mode_for_index(self, index): + if index == REG_X: + return AbsoluteX + elif index == REG_Y: + return AbsoluteY + else: + raise NotImplementedError(index) + + # visitor methods + def compile_program(self, program): assert isinstance(program, Program) @@ -72,7 +84,16 @@ class Compiler(object): for defn in program.defns: if defn.initial is not None: label = self.labels[defn.name] - initial_data = Byte(defn.initial) # TODO: support other types than Byte + initial_data = None + type_ = defn.location.type + if type_ == TYPE_BYTE: + initial_data = Byte(defn.initial) + elif type_ == TYPE_WORD: + initial_data = Word(defn.initial) + elif type_ == TYPE_BYTE_TABLE: + initial_data = Table(defn.initial) + else: + raise NotImplementedError(type_) label.set_length(initial_data.size()) self.emitter.resolve_label(label) self.emitter.emit(initial_data) @@ -209,6 +230,26 @@ class Compiler(object): self.emitter.emit(SBC(Immediate(Byte(src.value)))) else: self.emitter.emit(SBC(Absolute(self.labels[src.name]))) + elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD: + if isinstance(src, ConstantRef): + dest_label = self.labels[dest.name] + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(SBC(Immediate(Byte(src.low_byte())))) + self.emitter.emit(STA(Absolute(dest_label))) + self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) + self.emitter.emit(SBC(Immediate(Byte(src.high_byte())))) + self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) + elif isinstance(src, LocationRef): + src_label = self.labels[src.name] + dest_label = self.labels[dest.name] + self.emitter.emit(LDA(Absolute(dest_label))) + self.emitter.emit(SBC(Absolute(src_label))) + self.emitter.emit(STA(Absolute(dest_label))) + self.emitter.emit(LDA(Absolute(Offset(dest_label, 1)))) + self.emitter.emit(SBC(Absolute(Offset(src_label, 1)))) + self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) + else: + raise UnsupportedOpcodeError(instr) else: raise UnsupportedOpcodeError(instr) elif opcode == 'inc': @@ -307,7 +348,7 @@ class Compiler(object): elif opcode == 'repeat': top_label = self.emitter.make_label() self.compile_block(instr.block) - if src is None: + if src is None: # indicates 'repeat forever' self.emitter.emit(JMP(Absolute(top_label))) else: cls = { @@ -344,7 +385,10 @@ class Compiler(object): else: raise NotImplementedError((src, dest)) elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): - if dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType): + if dest == REG_A and isinstance(src.ref.type, PointerType): + src_label = self.labels[src.ref.name] + self.emitter.emit(LDA(IndirectY(src_label))) + elif dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType): src_label = self.labels[src.ref.name] dest_label = self.labels[dest.name] self.emitter.emit(LDA(IndirectY(src_label))) @@ -363,33 +407,28 @@ class Compiler(object): if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE: src_label = self.labels[src.name] dest_label = self.labels[dest.ref.name] - addressing_mode = None - if dest.index == REG_X: - addressing_mode = AbsoluteX - elif dest.index == REG_Y: - addressing_mode = AbsoluteY - else: - raise NotImplementedError(dest) self.emitter.emit(LDA(Absolute(src_label))) - self.emitter.emit(STA(addressing_mode(dest_label))) + self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) self.emitter.emit(LDA(Absolute(Offset(src_label, 1)))) - self.emitter.emit(STA(addressing_mode(Offset(dest_label, 256)))) + self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) + else: + raise NotImplementedError + elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef): + if src.type == TYPE_WORD and dest.ref.type == TYPE_WORD_TABLE: + dest_label = self.labels[dest.ref.name] + self.emitter.emit(LDA(Immediate(Byte(src.low_byte())))) + self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label))) + self.emitter.emit(LDA(Immediate(Byte(src.high_byte())))) + self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256)))) else: raise NotImplementedError elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): if src.ref.type == TYPE_WORD_TABLE and dest.type == TYPE_WORD: src_label = self.labels[src.ref.name] dest_label = self.labels[dest.name] - addressing_mode = None - if src.index == REG_X: - addressing_mode = AbsoluteX - elif src.index == REG_Y: - addressing_mode = AbsoluteY - else: - raise NotImplementedError(src) - self.emitter.emit(LDA(addressing_mode(src_label))) + self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label))) self.emitter.emit(STA(Absolute(dest_label))) - self.emitter.emit(LDA(addressing_mode(Offset(src_label, 256)))) + self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256)))) self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) else: raise NotImplementedError @@ -434,5 +473,7 @@ class Compiler(object): self.emitter.emit(STA(Absolute(Offset(dest_label, 1)))) else: raise NotImplementedError(src.type) + elif opcode == 'trash': + pass else: raise NotImplementedError(opcode) diff --git a/src/sixtypical/emitter.py b/src/sixtypical/emitter.py index ffa899c..112a37b 100644 --- a/src/sixtypical/emitter.py +++ b/src/sixtypical/emitter.py @@ -48,11 +48,20 @@ class Word(Emittable): class Table(Emittable): + def __init__(self, value): + # TODO: range-checking + self.value = value + def size(self): return 256 def serialize(self, addr=None): - return chr(0) * self.size() + bytes = [] + for b in self.value: + bytes.append(chr(ord(b))) + while len(bytes) < self.size(): + bytes.append(chr(0)) + return ''.join(bytes) def __repr__(self): return "%s()" % (self.__class__.__name__) diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 2c4e730..88538b9 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -8,6 +8,9 @@ class Type(object): def __repr__(self): return 'Type(%r)' % self.name + def __str__(self): + return self.name + def __eq__(self, other): return isinstance(other, Type) and other.name == self.name @@ -86,11 +89,12 @@ class LocationRef(Ref): def __eq__(self, other): # Ordinarily there will only be one ref with a given name, # but because we store the type in here and we want to treat - # these objects as immutable, we compare the types, too. - # Not sure if very wise. - return isinstance(other, self.__class__) and ( - other.name == self.name and other.type == self.type - ) + # these objects as immutable, we compare the types, too, + # just to be sure. + equal = isinstance(other, self.__class__) and other.name == self.name + if equal: + assert other.type == self.type + return equal def __hash__(self): return hash(self.name + str(self.type)) @@ -98,9 +102,23 @@ class LocationRef(Ref): def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name) + def __str__(self): + return "{}:{}".format(self.name, self.type) + 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]) + + @classmethod + def format_set(cls, location_refs): + return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)]) + class IndirectRef(Ref): def __init__(self, ref): diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 618ea2a..81c4adf 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -23,6 +23,7 @@ class Parser(object): self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token)) for token in ('c', 'z', 'n', 'v'): self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token)) + self.backpatch_instrs = [] def lookup(self, name): if name not in self.symbols: @@ -49,19 +50,28 @@ class Parser(object): self.symbols[name] = SymEntry(routine, routine.location) routines.append(routine) self.scanner.check_type('EOF') + # now backpatch the executable types. for defn in defns: - if isinstance(defn.location.type, VectorType): - t = defn.location.type - t.inputs = set([self.lookup(w) for w in t.inputs]) - t.outputs = set([self.lookup(w) for w in t.outputs]) - t.trashes = set([self.lookup(w) for w in t.trashes]) + defn.location.backpatch_vector_labels(lambda w: self.lookup(w)) for routine in routines: - if isinstance(routine.location.type, ExecutableType): - t = routine.location.type - t.inputs = set([self.lookup(w) for w in t.inputs]) - t.outputs = set([self.lookup(w) for w in t.outputs]) - t.trashes = set([self.lookup(w) for w in t.trashes]) + routine.location.backpatch_vector_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): + 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): + raise SyntaxError('Illegal copy of non-executable "%s"' % name) + instr.src = self.symbols[name].model + return Program(defns=defns, routines=routines) def defn(self): @@ -79,8 +89,11 @@ class Parser(object): initial = None if self.scanner.consume(':'): - self.scanner.check_type('integer literal') - initial = int(self.scanner.token) + if type_ == TYPE_BYTE_TABLE and self.scanner.on_type('string literal'): + initial = self.scanner.token + else: + self.scanner.check_type('integer literal') + initial = int(self.scanner.token) self.scanner.scan() addr = None @@ -194,7 +207,9 @@ class Parser(object): return loc def indlocexpr(self): - if self.scanner.consume('['): + if self.scanner.consume('forward'): + return self.label() + elif self.scanner.consume('['): loc = self.locexpr() self.scanner.expect(']') self.scanner.expect('+') @@ -273,22 +288,25 @@ class Parser(object): self.scanner.scan() name = self.scanner.token self.scanner.scan() - if name not in self.symbols: - raise SyntaxError('Undefined routine "%s"' % name) - if not isinstance(self.symbols[name].model.type, ExecutableType): - raise SyntaxError('Illegal call of non-executable "%s"' % name) - return Instr(opcode=opcode, location=self.symbols[name].model, dest=None, src=None) + instr = Instr(opcode=opcode, location=name, dest=None, src=None) + self.backpatch_instrs.append(instr) + return instr elif self.scanner.token in ("copy",): opcode = self.scanner.token self.scanner.scan() src = self.indlocexpr() self.scanner.expect(',') dest = self.indlocexpr() - return Instr(opcode=opcode, dest=dest, src=src) + instr = Instr(opcode=opcode, dest=dest, src=src) + self.backpatch_instrs.append(instr) + return instr elif self.scanner.consume("with"): self.scanner.expect("interrupts") self.scanner.expect("off") block = self.block() return Instr(opcode='with-sei', dest=None, src=None, block=block) + elif self.scanner.consume("trash"): + dest = self.locexpr() + return Instr(opcode='trash', src=None, dest=dest) else: raise ValueError('bad opcode "%s"' % self.scanner.token) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 9b95718..963f0ff 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -89,6 +89,24 @@ If a routine modifies a location, it needs to either output it or trash it. | } = ok +If a routine reads or writes a user-define memory location, it needs to declare that too. + + | byte b1 @ 60000 + | byte b2 : 3 + | word w1 @ 60001 + | word w2 : 2000 + | + | routine main + | inputs b1, w1 + | outputs b2, w2 + | trashes a, z, n + | { + | ld a, b1 + | st a, b2 + | copy w1, w2 + | } + = ok + ### ld ### Can't `ld` from a memory location that isn't initialized. @@ -370,6 +388,20 @@ Copying to and from a word table. | } ? TypeMismatchError +You can also copy a literal word to a word table. + + | word table many + | + | routine main + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | copy 9999, many + x + | } + = ok + ### add ### Can't `add` from or to a memory location that isn't initialized. @@ -561,6 +593,60 @@ Can't `sub` to a memory location that isn't writeable. | } ? ForbiddenWriteError: a in main +You can `sub` a word constant from a word memory location. + + | word score + | routine main + | inputs a, score + | outputs score + | trashes a, c, z, v, n + | { + | st on, c + | sub score, 1999 + | } + = ok + +`sub`ing a word constant from a word memory location trashes `a`. + + | word score + | routine main + | inputs a, score + | outputs score, a + | trashes c, z, v, n + | { + | st on, c + | sub score, 1999 + | } + ? UnmeaningfulOutputError: a in main + +You can `sub` a word memory location from another word memory location. + + | word score + | word delta + | routine main + | inputs score, delta + | outputs score + | trashes a, c, z, v, n + | { + | st off, c + | sub score, delta + | } + = ok + +`sub`ing a word memory location from a word memory location trashes `a`. + + | word score + | word delta + | routine main + | inputs score, delta + | outputs score + | trashes c, z, v, n + | { + | st off, c + | sub score, delta + | } + ? ForbiddenWriteError: a in main + ### inc ### Location must be initialized and writeable. @@ -1003,6 +1089,43 @@ same constraints. | } ? UnmeaningfulReadError: a in main +### trash ### + +Trash does nothing except indicate that we do not care about the value anymore. + + | routine foo + | inputs a + | outputs x + | trashes a, z, n + | { + | st a, x + | ld a, 0 + | trash a + | } + = ok + + | routine foo + | inputs a + | outputs a, x + | trashes z, n + | { + | st a, x + | ld a, 0 + | trash a + | } + ? UnmeaningfulOutputError: a in foo + + | routine foo + | inputs a + | outputs x + | trashes a, z, n + | { + | st a, x + | trash a + | st a, x + | } + ? UnmeaningfulReadError: a in foo + ### if ### Both blocks of an `if` are analyzed. @@ -1065,6 +1188,26 @@ If a location is initialized in one block, is must be initialized in the other a | } ? InconsistentInitializationError: x +However, this only pertains to initialization. If a value is already +initialized, either because it was set previous to the `if`, or is an +input to the routine, and it is initialized in one branch, it need not +be initialized in the other. + + | routine foo + | inputs x + | outputs x + | trashes a, z, n, c + | { + | ld a, 0 + | cmp a, 42 + | if z { + | ld x, 7 + | } else { + | ld a, 23 + | } + | } + = ok + An `if` with a single block is analyzed as if it had an empty `else` block. | routine foo @@ -1175,6 +1318,15 @@ this is an error too. | } ? UnmeaningfulReadError: z in main +The body of `repeat forever` can be empty. + + | routine main + | { + | repeat { + | } forever + | } + = ok + ### copy ### Can't `copy` from a memory location that isn't initialized. @@ -1234,7 +1386,7 @@ a, z, and n are trashed, and must be declared as such | { | copy 0, lives | } - ? ForbiddenWriteError: a in main + ? ForbiddenWriteError: n in main a, z, and n are trashed, and must not be declared as outputs. @@ -1244,7 +1396,7 @@ a, z, and n are trashed, and must not be declared as outputs. | { | copy 0, lives | } - ? UnmeaningfulOutputError: a in main + ? UnmeaningfulOutputError: n in main Unless of course you subsequently initialize them. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 01b980e..91fedfa 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -117,6 +117,296 @@ Memory location with initial value. = $0810 RTS = $0811 .byte $03 +Word memory locations with explicit address, initial value. + + | word w1 @ 60001 + | word w2 : 3003 + | + | routine main + | inputs w1 + | outputs w2 + | trashes a, z, n + | { + | copy w1, w2 + | } + = $080D LDA $EA61 + = $0810 STA $081A + = $0813 LDA $EA62 + = $0816 STA $081B + = $0819 RTS + = $081A .byte $BB + = $081B .byte $0B + +Initialized byte table. + + | byte table message : "WHAT?" + | + | routine main + | inputs message + | outputs x, a, z, n + | { + | ld x, 0 + | ld a, message + x + | } + = $080D LDX #$00 + = $080F LDA $0813,X + = $0812 RTS + = $0813 .byte $57 + = $0814 PHA + = $0815 EOR ($54,X) + = $0817 .byte $3F + = $0818 BRK + = $0819 BRK + = $081A BRK + = $081B BRK + = $081C BRK + = $081D BRK + = $081E BRK + = $081F BRK + = $0820 BRK + = $0821 BRK + = $0822 BRK + = $0823 BRK + = $0824 BRK + = $0825 BRK + = $0826 BRK + = $0827 BRK + = $0828 BRK + = $0829 BRK + = $082A BRK + = $082B BRK + = $082C BRK + = $082D BRK + = $082E BRK + = $082F BRK + = $0830 BRK + = $0831 BRK + = $0832 BRK + = $0833 BRK + = $0834 BRK + = $0835 BRK + = $0836 BRK + = $0837 BRK + = $0838 BRK + = $0839 BRK + = $083A BRK + = $083B BRK + = $083C BRK + = $083D BRK + = $083E BRK + = $083F BRK + = $0840 BRK + = $0841 BRK + = $0842 BRK + = $0843 BRK + = $0844 BRK + = $0845 BRK + = $0846 BRK + = $0847 BRK + = $0848 BRK + = $0849 BRK + = $084A BRK + = $084B BRK + = $084C BRK + = $084D BRK + = $084E BRK + = $084F BRK + = $0850 BRK + = $0851 BRK + = $0852 BRK + = $0853 BRK + = $0854 BRK + = $0855 BRK + = $0856 BRK + = $0857 BRK + = $0858 BRK + = $0859 BRK + = $085A BRK + = $085B BRK + = $085C BRK + = $085D BRK + = $085E BRK + = $085F BRK + = $0860 BRK + = $0861 BRK + = $0862 BRK + = $0863 BRK + = $0864 BRK + = $0865 BRK + = $0866 BRK + = $0867 BRK + = $0868 BRK + = $0869 BRK + = $086A BRK + = $086B BRK + = $086C BRK + = $086D BRK + = $086E BRK + = $086F BRK + = $0870 BRK + = $0871 BRK + = $0872 BRK + = $0873 BRK + = $0874 BRK + = $0875 BRK + = $0876 BRK + = $0877 BRK + = $0878 BRK + = $0879 BRK + = $087A BRK + = $087B BRK + = $087C BRK + = $087D BRK + = $087E BRK + = $087F BRK + = $0880 BRK + = $0881 BRK + = $0882 BRK + = $0883 BRK + = $0884 BRK + = $0885 BRK + = $0886 BRK + = $0887 BRK + = $0888 BRK + = $0889 BRK + = $088A BRK + = $088B BRK + = $088C BRK + = $088D BRK + = $088E BRK + = $088F BRK + = $0890 BRK + = $0891 BRK + = $0892 BRK + = $0893 BRK + = $0894 BRK + = $0895 BRK + = $0896 BRK + = $0897 BRK + = $0898 BRK + = $0899 BRK + = $089A BRK + = $089B BRK + = $089C BRK + = $089D BRK + = $089E BRK + = $089F BRK + = $08A0 BRK + = $08A1 BRK + = $08A2 BRK + = $08A3 BRK + = $08A4 BRK + = $08A5 BRK + = $08A6 BRK + = $08A7 BRK + = $08A8 BRK + = $08A9 BRK + = $08AA BRK + = $08AB BRK + = $08AC BRK + = $08AD BRK + = $08AE BRK + = $08AF BRK + = $08B0 BRK + = $08B1 BRK + = $08B2 BRK + = $08B3 BRK + = $08B4 BRK + = $08B5 BRK + = $08B6 BRK + = $08B7 BRK + = $08B8 BRK + = $08B9 BRK + = $08BA BRK + = $08BB BRK + = $08BC BRK + = $08BD BRK + = $08BE BRK + = $08BF BRK + = $08C0 BRK + = $08C1 BRK + = $08C2 BRK + = $08C3 BRK + = $08C4 BRK + = $08C5 BRK + = $08C6 BRK + = $08C7 BRK + = $08C8 BRK + = $08C9 BRK + = $08CA BRK + = $08CB BRK + = $08CC BRK + = $08CD BRK + = $08CE BRK + = $08CF BRK + = $08D0 BRK + = $08D1 BRK + = $08D2 BRK + = $08D3 BRK + = $08D4 BRK + = $08D5 BRK + = $08D6 BRK + = $08D7 BRK + = $08D8 BRK + = $08D9 BRK + = $08DA BRK + = $08DB BRK + = $08DC BRK + = $08DD BRK + = $08DE BRK + = $08DF BRK + = $08E0 BRK + = $08E1 BRK + = $08E2 BRK + = $08E3 BRK + = $08E4 BRK + = $08E5 BRK + = $08E6 BRK + = $08E7 BRK + = $08E8 BRK + = $08E9 BRK + = $08EA BRK + = $08EB BRK + = $08EC BRK + = $08ED BRK + = $08EE BRK + = $08EF BRK + = $08F0 BRK + = $08F1 BRK + = $08F2 BRK + = $08F3 BRK + = $08F4 BRK + = $08F5 BRK + = $08F6 BRK + = $08F7 BRK + = $08F8 BRK + = $08F9 BRK + = $08FA BRK + = $08FB BRK + = $08FC BRK + = $08FD BRK + = $08FE BRK + = $08FF BRK + = $0900 BRK + = $0901 BRK + = $0902 BRK + = $0903 BRK + = $0904 BRK + = $0905 BRK + = $0906 BRK + = $0907 BRK + = $0908 BRK + = $0909 BRK + = $090A BRK + = $090B BRK + = $090C BRK + = $090D BRK + = $090E BRK + = $090F BRK + = $0910 BRK + = $0911 BRK + = $0912 BRK + Some instructions. | byte foo @@ -297,6 +587,16 @@ Compiling `repeat forever`. = $0810 JMP $080F = $0813 RTS +The body of `repeat forever` can be empty. + + | routine main + | { + | repeat { + | } forever + | } + = $080D JMP $080D + = $0810 RTS + Indexed access. | byte one @@ -404,6 +704,25 @@ Copy literal word to word. = $0814 STA $0819 = $0817 RTS +You can also copy a literal word to a word table. + + | word table many + | + | routine main + | inputs many + | outputs many + | trashes a, x, n, z + | { + | ld x, 0 + | copy 9999, many + x + | } + = $080D LDX #$00 + = $080F LDA #$0F + = $0811 STA $081A,X + = $0814 LDA #$27 + = $0816 STA $091A,X + = $0819 RTS + Copy vector to vector. | vector bar @@ -576,6 +895,47 @@ Adding a word memory location to another word memory location. = $081D STA $0822 = $0820 RTS +Subtracting a constant word from a word memory location. + + | word score + | routine main + | inputs score + | outputs score + | trashes a, c, z, v, n + | { + | st on, c + | sub score, 1999 + | } + = $080D SEC + = $080E LDA $081F + = $0811 SBC #$CF + = $0813 STA $081F + = $0816 LDA $0820 + = $0819 SBC #$07 + = $081B STA $0820 + = $081E RTS + +Subtracting a word memory location from another word memory location. + + | word score + | word delta + | routine main + | inputs score, delta + | outputs score + | trashes a, c, z, v, n + | { + | st on, c + | sub score, delta + | } + = $080D SEC + = $080E LDA $0821 + = $0811 SBC $0823 + = $0814 STA $0821 + = $0817 LDA $0822 + = $081A SBC $0824 + = $081D STA $0822 + = $0820 RTS + ### Buffers and Pointers Load address into pointer. @@ -645,7 +1005,7 @@ Write stored value through a pointer. = $081A STA ($FE),Y = $081C RTS -Read through a pointer. +Read through a pointer, into a byte storage location, or the `a` register. | buffer[2048] buf | pointer ptr @ 254 @@ -659,15 +1019,17 @@ Read through a pointer. | ld y, 0 | copy ^buf, ptr | copy [ptr] + y, foo + | copy [ptr] + y, a | } = $080D LDY #$00 - = $080F LDA #$1D + = $080F LDA #$1F = $0811 STA $FE = $0813 LDA #$08 = $0815 STA $FF = $0817 LDA ($FE),Y - = $0819 STA $101D - = $081C RTS + = $0819 STA $101F + = $081C LDA ($FE),Y + = $081E RTS Add a word memory location, and a literal word, to a pointer, and then read through it. Note that this is *not* range-checked. (Yet.) @@ -713,3 +1075,20 @@ Note that this is *not* range-checked. (Yet.) = $083B LDA ($FE),Y = $083D STA $1041 = $0840 RTS + +### Trash + +Trash does nothing except indicate that we do not care about the value anymore. + + | routine main + | inputs a + | outputs x + | trashes a, z, n + | { + | ld x, a + | ld a, 0 + | trash a + | } + = $080D TAX + = $080E LDA #$00 + = $0810 RTS diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 8fdebee..57007ce 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -70,6 +70,14 @@ Extern routines | @ 65487 = ok +Trash. + + | routine main { + | trash a + | trash n + | } + = ok + If with not | routine foo { @@ -178,6 +186,22 @@ User-defined locations of other types. | } = ok +Initialized byte table. + + | byte table message : "WHAT DO YOU WANT TO DO NEXT?" + | + | routine main { + | } + = ok + +Can't initialize anything but a byte table with a string. + + | word message : "WHAT DO YOU WANT TO DO NEXT?" + | + | routine main { + | } + ? SyntaxError + Can't access an undeclared memory location. | routine main { @@ -239,6 +263,19 @@ And you can't call a non-routine. | } ? SyntaxError +But you can call a routine that is yet to be defined, further on. + + | routine main { + | ld x, 0 + | ld y, 1 + | call up + | call up + | } + | routine up { + | ld a, 0 + | } + = ok + Can't define two routines with the same name. | routine main { @@ -343,6 +380,33 @@ A vector can name itself in its inputs, outputs, and trashes. | } = ok +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 inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788 + | routine main { + | with interrupts off { + | copy foo, cinv + | } + | call cinv + | } + | routine foo { + | ld a, 0 + | } + ? SyntaxError: Undefined symbol + + | vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788 + | routine main { + | with interrupts off { + | copy forward foo, cinv + | } + | call cinv + | } + | routine foo { + | ld a, 0 + | } + = ok + goto. | routine foo { @@ -353,6 +417,14 @@ goto. | } = ok + | routine main { + | goto foo + | } + | routine foo { + | ld a, 0 + | } + = ok + | vector foo | | routine main {