From de6c96fef87d8f347f0ec9f668d3eb0609ee32be Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 12:27:29 +0000 Subject: [PATCH 01/37] Prep for developing version 0.10. --- README.md | 2 +- doc/SixtyPical.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index efadf04..4bc9d38 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 (not released yet). Documentation ------------- 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.) From 8dc44673fac744c60d87dae12dfaec0df0fb9481 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 12:45:47 +0000 Subject: [PATCH 02/37] LocationRefs are equal if names/type are; this needed __str__ tho. --- src/sixtypical/analyzer.py | 27 ++++----------------------- src/sixtypical/model.py | 10 ++++++++++ tests/SixtyPical Analysis.md | 4 ++-- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 34fce75..8e55ae9 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) diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 2c4e730..6b72321 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 @@ -98,9 +101,16 @@ 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) + @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/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 9b95718..2673c91 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1234,7 +1234,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 +1244,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. From 60df16262581682f490626ce57d6a54b0eebf515 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 12:51:19 +0000 Subject: [PATCH 03/37] Refactor: common method to backpatch labels. --- src/sixtypical/model.py | 7 +++++++ src/sixtypical/parser.py | 12 ++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 6b72321..230ea0b 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -107,6 +107,13 @@ class LocationRef(Ref): def is_constant(self): return isinstance(self.type, RoutineType) + def backpatch_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)]) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 618ea2a..eca633b 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -51,17 +51,9 @@ class Parser(object): 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_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_labels(lambda w: self.lookup(w)) return Program(defns=defns, routines=routines) def defn(self): From 45bc4bd0a05c85a7c6d113c516462bc2b0613e78 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 12:54:16 +0000 Subject: [PATCH 04/37] Tighten assumption when comparing LocationRefs. --- src/sixtypical/model.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 230ea0b..3488661 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -89,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)) From 19dd089a03e7d91c847f101366c812502174075d Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 13:17:00 +0000 Subject: [PATCH 05/37] Allow `call` and `goto` routines defined further down in the source. --- HISTORY.md | 5 +++++ README.md | 5 ----- src/sixtypical/model.py | 2 +- src/sixtypical/parser.py | 33 ++++++++++++++++++++++++++------- tests/SixtyPical Syntax.md | 21 +++++++++++++++++++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index c4cb657..f4386fd 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,11 @@ History of SixtyPical ===================== +0.10 +---- + +* Can `call` and `goto` routines that are defined further down in the source code. + 0.9 --- diff --git a/README.md b/README.md index 4bc9d38..21dec0f 100644 --- a/README.md +++ b/README.md @@ -46,11 +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` diff --git a/src/sixtypical/model.py b/src/sixtypical/model.py index 3488661..88538b9 100644 --- a/src/sixtypical/model.py +++ b/src/sixtypical/model.py @@ -108,7 +108,7 @@ class LocationRef(Ref): def is_constant(self): return isinstance(self.type, RoutineType) - def backpatch_labels(self, resolver): + def backpatch_vector_labels(self, resolver): if isinstance(self.type, ExecutableType): t = self.type t.inputs = set([resolver(w) for w in t.inputs]) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index eca633b..4e46177 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -29,6 +29,27 @@ class Parser(object): raise SyntaxError('Undefined symbol "%s"' % name) return self.symbols[name].model + def backpatch_call_labels(self, block): + """Backpatches labels in call and goto instructions.""" + if block is None: + return + for instr in block.instrs: + if instr.opcode == 'if': + self.backpatch_call_labels(instr.block1) + self.backpatch_call_labels(instr.block2) + elif instr.opcode == 'repeat': + self.backpatch_call_labels(instr.block) + elif instr.opcode == 'with-sei': + self.backpatch_call_labels(instr.block) + elif instr.opcode in ('call', 'goto'): + if isinstance(instr.location, basestring): + 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 + # --- grammar productions def program(self): @@ -51,9 +72,10 @@ class Parser(object): self.scanner.check_type('EOF') # now backpatch the executable types. for defn in defns: - defn.location.backpatch_labels(lambda w: self.lookup(w)) + defn.location.backpatch_vector_labels(lambda w: self.lookup(w)) for routine in routines: - routine.location.backpatch_labels(lambda w: self.lookup(w)) + routine.location.backpatch_vector_labels(lambda w: self.lookup(w)) + self.backpatch_call_labels(routine.block) return Program(defns=defns, routines=routines) def defn(self): @@ -265,11 +287,8 @@ 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) + # this will be backpatched + return Instr(opcode=opcode, location=name, dest=None, src=None) elif self.scanner.token in ("copy",): opcode = self.scanner.token self.scanner.scan() diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 8fdebee..961997b 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -239,6 +239,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 { @@ -353,6 +366,14 @@ goto. | } = ok + | routine main { + | goto foo + | } + | routine foo { + | ld a, 0 + | } + = ok + | vector foo | | routine main { From dbbd99ffe54baee2b19558ac4917729251ffd02d Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 13:54:35 +0000 Subject: [PATCH 06/37] Change how backpatching instructions is implemented. --- README.md | 5 +++++ src/sixtypical/parser.py | 38 ++++++++++++++------------------------ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 21dec0f..d0160c8 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,11 @@ 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) +### `copy` (to vectors) 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 other things. + ### Allow branches to diverge in what they touch For example, if the routine inputs and outputs `foo`, and one branch of an `if` diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 4e46177..e27b10b 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -23,33 +23,13 @@ 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: raise SyntaxError('Undefined symbol "%s"' % name) return self.symbols[name].model - def backpatch_call_labels(self, block): - """Backpatches labels in call and goto instructions.""" - if block is None: - return - for instr in block.instrs: - if instr.opcode == 'if': - self.backpatch_call_labels(instr.block1) - self.backpatch_call_labels(instr.block2) - elif instr.opcode == 'repeat': - self.backpatch_call_labels(instr.block) - elif instr.opcode == 'with-sei': - self.backpatch_call_labels(instr.block) - elif instr.opcode in ('call', 'goto'): - if isinstance(instr.location, basestring): - 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 - # --- grammar productions def program(self): @@ -70,12 +50,21 @@ 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: defn.location.backpatch_vector_labels(lambda w: self.lookup(w)) for routine in routines: routine.location.backpatch_vector_labels(lambda w: self.lookup(w)) - self.backpatch_call_labels(routine.block) + 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 + return Program(defns=defns, routines=routines) def defn(self): @@ -287,8 +276,9 @@ class Parser(object): self.scanner.scan() name = self.scanner.token self.scanner.scan() - # this will be backpatched - return Instr(opcode=opcode, location=name, 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() From b7b28830d7fe8c119b46145b2a39b8f9f364c4a9 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 14:09:17 +0000 Subject: [PATCH 07/37] Introducing a new pseudo-opcode is rarely an elegant solution. --- HISTORY.md | 1 + README.md | 5 ----- src/sixtypical/parser.py | 17 +++++++++++++++++ tests/SixtyPical Syntax.md | 22 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f4386fd..f754c66 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,7 @@ History of SixtyPical ---- * Can `call` and `goto` routines that are defined further down in the source code. +* `assign`, a form of `copy` that can copy (to a vector) a routine that is defined further down. 0.9 --- diff --git a/README.md b/README.md index d0160c8..21dec0f 100644 --- a/README.md +++ b/README.md @@ -46,11 +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) -### `copy` (to vectors) 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 other things. - ### Allow branches to diverge in what they touch For example, if the routine inputs and outputs `foo`, and one branch of an `if` diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index e27b10b..878d9ec 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -64,6 +64,14 @@ class Parser(object): 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 ('assign',): + 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 assign of non-executable "%s"' % name) + instr.src = self.symbols[name].model + instr.opcode = 'copy' return Program(defns=defns, routines=routines) @@ -286,6 +294,15 @@ class Parser(object): self.scanner.expect(',') dest = self.indlocexpr() return Instr(opcode=opcode, dest=dest, src=src) + elif self.scanner.token == 'assign': + opcode = self.scanner.token + self.scanner.scan() + src = self.label() + self.scanner.expect(',') + dest = self.indlocexpr() + 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") diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 961997b..91d3913 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -356,6 +356,28 @@ 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*, in order to do this currently, one needs to use the special opcode +form `assign`, which is equivalent to `copy` except that the routine need not +have already appeared in the program. + + | vector cinv + | inputs cinv, a + | outputs cinv, x + | trashes a, x, z, n + | @ 788 + | + | routine main { + | with interrupts off { + | assign foo, cinv + | } + | call cinv + | } + | routine foo { + | ld a, 0 + | } + = ok + goto. | routine foo { From 0194d37bbd3fd0f64c463450a70037f490692fec Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 14:23:34 +0000 Subject: [PATCH 08/37] `forward` modifier on location expression, instead of `assign`. --- HISTORY.md | 3 ++- src/sixtypical/parser.py | 16 +++++----------- tests/SixtyPical Syntax.md | 27 ++++++++++++++++----------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f754c66..884a343 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,7 +5,8 @@ History of SixtyPical ---- * Can `call` and `goto` routines that are defined further down in the source code. -* `assign`, a form of `copy` that can copy (to a vector) a routine that is defined further down. +* 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. 0.9 --- diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 878d9ec..46468bf 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -64,14 +64,13 @@ class Parser(object): 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 ('assign',): + 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 assign of non-executable "%s"' % name) + raise SyntaxError('Illegal copy of non-executable "%s"' % name) instr.src = self.symbols[name].model - instr.opcode = 'copy' return Program(defns=defns, routines=routines) @@ -205,7 +204,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('+') @@ -293,13 +294,6 @@ class Parser(object): src = self.indlocexpr() self.scanner.expect(',') dest = self.indlocexpr() - return Instr(opcode=opcode, dest=dest, src=src) - elif self.scanner.token == 'assign': - opcode = self.scanner.token - self.scanner.scan() - src = self.label() - self.scanner.expect(',') - dest = self.indlocexpr() instr = Instr(opcode=opcode, dest=dest, src=src) self.backpatch_instrs.append(instr) return instr diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 91d3913..544bafa 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -356,20 +356,25 @@ 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*, in order to do this currently, one needs to use the special opcode -form `assign`, which is equivalent to `copy` except that the routine need not -have already appeared in the program. +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 - | + | vector cinv inputs cinv, a outputs cinv, x trashes a, x, z, n @ 788 | routine main { | with interrupts off { - | assign foo, cinv + | 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 | } From 7d11273c6c02c487783980a45d8522f88672dc78 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 14:40:32 +0000 Subject: [PATCH 09/37] Slightly less of a hack. Not very much. But more illustrative. --- eg/proto-game.60p | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 08490a0..18ff33c 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -204,7 +204,7 @@ routine game_state_title_screen copy game_state_play, dispatch_game_state } else { // This is sort of a hack. FIXME: let `if` branches diverge this much. - copy dispatch_game_state, dispatch_game_state + copy forward game_state_title_screen, dispatch_game_state } goto save_cinv From 3417fd96c7c6221a953cd498d0b102e54fd32532 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 14:55:57 +0000 Subject: [PATCH 10/37] Apparently this hack was only needed previously due to a bug? --- eg/proto-game.60p | 3 --- tests/SixtyPical Analysis.md | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 18ff33c..c1d73d5 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -202,9 +202,6 @@ routine game_state_title_screen // call clear_screen // call init_game copy game_state_play, dispatch_game_state - } else { - // This is sort of a hack. FIXME: let `if` branches diverge this much. - copy forward game_state_title_screen, dispatch_game_state } goto save_cinv diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 2673c91..42e9f6b 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1065,6 +1065,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 From 0145c6d34fdd4f05e8592da052f99647d7588050 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 14:59:20 +0000 Subject: [PATCH 11/37] I guess we can note that, even though not 100% sure why it was. --- HISTORY.md | 2 ++ README.md | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 884a343..becf069 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,6 +7,8 @@ History of SixtyPical * 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. +* Fixed bug which was preventing `if` branches to diverge in what they initialized, + if it was already initialized when going into the `if`. 0.9 --- diff --git a/README.md b/README.md index 21dec0f..ab72cbf 100644 --- a/README.md +++ b/README.md @@ -46,11 +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) -### 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 From 4854077cce707b6b3be32d8775d7efcde356aa5a Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 15:18:59 +0000 Subject: [PATCH 12/37] Correctly analyze `repeat { ... } forever` loops. --- HISTORY.md | 1 + eg/proto-game.60p | 6 ++---- src/sixtypical/analyzer.py | 6 ++++-- src/sixtypical/compiler.py | 2 +- tests/SixtyPical Analysis.md | 9 +++++++++ tests/SixtyPical Compilation.md | 10 ++++++++++ 6 files changed, 27 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index becf069..f5dc81c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,7 @@ History of SixtyPical in a `copy` to a vector is a routine that is defined further down in the source. * 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/eg/proto-game.60p b/eg/proto-game.60p index c1d73d5..bfa0cdc 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -240,8 +240,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 8e55ae9..b678342 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -324,12 +324,14 @@ class Analyzer(object): # 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 diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 8ac674a..1bce2b9 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -307,7 +307,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 = { diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 42e9f6b..0c0998b 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1195,6 +1195,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. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 01b980e..2823eb8 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -297,6 +297,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 From 63f75a26b496435d836895f917607d9b4f45c3b4 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 15:34:51 +0000 Subject: [PATCH 13/37] Initialized `word` type memory locations. --- HISTORY.md | 1 + README.md | 1 - src/sixtypical/compiler.py | 11 +++++++++-- tests/SixtyPical Analysis.md | 18 ++++++++++++++++++ tests/SixtyPical Compilation.md | 20 ++++++++++++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f5dc81c..d542246 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,6 +7,7 @@ History of SixtyPical * 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. * 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. diff --git a/README.md b/README.md index ab72cbf..2e117eb 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ This should be tracked in the abstract interpretation. * 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. diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 1bce2b9..5a0952d 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, Label, Offset, LowAddressByte, HighAddressByte from sixtypical.gen6502 import ( Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative, LDA, LDX, LDY, STA, STX, STY, @@ -72,7 +72,14 @@ 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) + else: + raise NotImplementedError(type_) label.set_length(initial_data.size()) self.emitter.resolve_label(label) self.emitter.emit(initial_data) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 0c0998b..5c8262b 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. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 2823eb8..ae21a7e 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -117,6 +117,26 @@ 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 + Some instructions. | byte foo From 50390b0787e484520507baf3eb5302c36f3621ea Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 16:04:59 +0000 Subject: [PATCH 14/37] Can `copy` a literal word to a word table. --- HISTORY.md | 1 + src/sixtypical/analyzer.py | 5 ++++- src/sixtypical/compiler.py | 16 ++++++++++++++++ tests/SixtyPical Analysis.md | 14 ++++++++++++++ tests/SixtyPical Compilation.md | 19 +++++++++++++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d542246..5b87b64 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,6 +8,7 @@ History of SixtyPical * 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. * 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. diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index b678342..496a8f9 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -352,7 +352,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: @@ -391,6 +391,9 @@ class Analyzer(object): 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? diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 5a0952d..8a6feed 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -383,6 +383,22 @@ class Compiler(object): self.emitter.emit(STA(addressing_mode(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] + 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(Immediate(Byte(src.low_byte())))) + self.emitter.emit(STA(addressing_mode(dest_label))) + self.emitter.emit(LDA(Immediate(Byte(src.high_byte())))) + self.emitter.emit(STA(addressing_mode(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] diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 5c8262b..5a9e1cb 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -388,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. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index ae21a7e..cc80189 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -434,6 +434,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 From f87bcf5ee46d395d200a38ccc15f2df602d355b8 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 12 Dec 2017 16:41:49 +0000 Subject: [PATCH 15/37] Initial work on initialized byte tables. --- src/sixtypical/compiler.py | 4 +- src/sixtypical/emitter.py | 11 +- src/sixtypical/parser.py | 7 +- tests/SixtyPical Compilation.md | 270 ++++++++++++++++++++++++++++++++ tests/SixtyPical Syntax.md | 16 ++ 5 files changed, 304 insertions(+), 4 deletions(-) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 8a6feed..6032ed3 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, Word, 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, @@ -78,6 +78,8 @@ class Compiler(object): 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()) 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/parser.py b/src/sixtypical/parser.py index 46468bf..58f81db 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -89,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 diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index cc80189..3284931 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -137,6 +137,276 @@ Word memory locations with explicit address, initial value. = $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 diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 544bafa..74a1551 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -178,6 +178,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 { From 75cc1f6b25a54041a654f489ac7ec427e756eb4c Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 12:14:26 +0000 Subject: [PATCH 16/37] Factor out utility method for selecting addressing mode. --- src/sixtypical/compiler.py | 45 +++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 6032ed3..50b46da 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -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) @@ -372,49 +384,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] - 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(Immediate(Byte(src.low_byte())))) - 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(Immediate(Byte(src.high_byte())))) - 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, 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 From 9c7082db6e74dc5605ed71779a0622eafefaa4e1 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 12:24:11 +0000 Subject: [PATCH 17/37] Display message on-screen at start of game. --- eg/proto-game.60p | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index bfa0cdc..f9076c0 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -29,14 +29,16 @@ byte joy2 @ $dc00 pointer ptr @ 254 word pos word delta + byte button_down : 0 // effectively static-local to check_button +byte table press_fire_msg: "PRESS`FIRE`TO`PLAY" // // Points to the routine that implements the current game state. // vector dispatch_game_state - inputs joy2, pos, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state outputs delta, pos, screen, screen1, button_down, dispatch_game_state trashes a, x, y, c, z, n, v, ptr @@ -53,13 +55,13 @@ vector dispatch_game_state // vector cinv - inputs joy2, pos, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state outputs delta, pos, screen, screen1, button_down, dispatch_game_state trashes a, x, y, c, z, n, v, ptr @ 788 vector save_cinv - inputs joy2, pos, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state outputs delta, pos, screen, screen1, button_down, dispatch_game_state trashes a, x, y, c, z, n, v, ptr @@ -163,7 +165,7 @@ routine clear_screen // routine game_state_play - inputs joy2, pos, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state outputs delta, pos, screen, screen1, button_down, dispatch_game_state trashes a, x, y, c, z, n, v, ptr { @@ -183,16 +185,20 @@ routine game_state_play } routine game_state_title_screen - inputs joy2, pos, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state outputs delta, pos, screen, screen1, button_down, dispatch_game_state trashes a, x, y, c, z, n, v, ptr { - 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 @@ -212,7 +218,7 @@ routine game_state_title_screen // ************************* routine our_cinv - inputs joy2, pos, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state outputs delta, pos, screen, screen1, button_down, dispatch_game_state trashes a, x, y, c, z, n, v, ptr { From d9c9dab9e784c5fca75d4be12bd4f8e6576f77db Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 12:32:24 +0000 Subject: [PATCH 18/37] Clear the screen when starting the game. --- eg/proto-game.60p | 49 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index f9076c0..89977de 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -36,10 +36,18 @@ byte table press_fire_msg: "PRESS`FIRE`TO`PLAY" // // 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, press_fire_msg, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs delta, pos, button_down, dispatch_game_state, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr // @@ -55,14 +63,18 @@ vector dispatch_game_state // vector cinv - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs delta, pos, button_down, dispatch_game_state, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr @ 788 vector save_cinv - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs delta, pos, button_down, dispatch_game_state, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr // ---------------------------------------------------------------- @@ -165,8 +177,10 @@ routine clear_screen // routine game_state_play - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs delta, pos, button_down, dispatch_game_state, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr { call read_stick @@ -185,8 +199,10 @@ routine game_state_play } routine game_state_title_screen - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs delta, pos, button_down, dispatch_game_state, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr { ld y, 0 @@ -205,9 +221,14 @@ routine game_state_title_screen call check_button if c { - // call clear_screen + call clear_screen // call init_game copy game_state_play, dispatch_game_state + ld a, 0 // FIXME we shouldn't need to. + ld y, 0 // FIXME we shouldn't need to. + st off, c // FIXME we shouldn't need to. + } else { + ld a, 0 // FIXME we shouldn't need to. } goto save_cinv @@ -218,8 +239,10 @@ routine game_state_title_screen // ************************* routine our_cinv - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state - outputs delta, pos, screen, screen1, button_down, dispatch_game_state + inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs delta, pos, button_down, dispatch_game_state, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr { goto dispatch_game_state From 1478db0fb48fec0c1311b8f4cfcb68159454dc6a Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 13:14:10 +0000 Subject: [PATCH 19/37] Use word tables, in game, to store the actors' positions and deltas. --- eg/proto-game.60p | 112 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 89977de..87725fc 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -27,12 +27,18 @@ byte joy2 @ $dc00 // ---------------------------------------------------------------- pointer ptr @ 254 + +word table actor_pos word 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 + // // Points to the routine that implements the current game state. // @@ -44,9 +50,11 @@ byte table press_fire_msg: "PRESS`FIRE`TO`PLAY" // vector dispatch_game_state - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs delta, pos, button_down, dispatch_game_state, + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr @@ -63,17 +71,21 @@ vector dispatch_game_state // vector cinv - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs delta, pos, button_down, dispatch_game_state, + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr @ 788 vector save_cinv - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs delta, pos, button_down, dispatch_game_state, + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr @@ -169,18 +181,12 @@ routine clear_screen } // ---------------------------------------------------------------- -// Game States +// Actor Logics // ---------------------------------------------------------------- -// -// 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, press_fire_msg, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs delta, pos, button_down, dispatch_game_state, - screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 +routine player_logic + inputs pos, delta, joy2, screen + outputs pos, delta, screen trashes a, x, y, c, z, n, v, ptr { call read_stick @@ -194,14 +200,68 @@ routine game_state_play ld y, 0 copy 81, [ptr] + y +} + +routine enemy_logic + inputs pos, delta, joy2, screen + outputs pos, delta, screen + trashes a, x, y, c, z, n, v, ptr +{ +} + +// ---------------------------------------------------------------- +// Game States +// ---------------------------------------------------------------- + +// +// Because these all `goto save_cinv` at the end, they must have the same signature as that routine. +// + +routine game_state_play + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + trashes a, x, y, c, z, n, v, ptr +{ + 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 + + cmp x, 0 + if z { + call player_logic + } else { + call enemy_logic + } + + 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_title_screen - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs delta, pos, button_down, dispatch_game_state, + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr { @@ -239,9 +299,11 @@ routine game_state_title_screen // ************************* routine our_cinv - inputs joy2, pos, button_down, press_fire_msg, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs delta, pos, button_down, dispatch_game_state, + inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, + screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs button_down, dispatch_game_state, save_x, + actor_pos, pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr { From 5f535c963ebc7b0c15fa88a522c2e3dc9378e742 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 14:07:59 +0000 Subject: [PATCH 20/37] Subtract word (constant or memory location) from word memory location. --- HISTORY.md | 1 + src/sixtypical/analyzer.py | 10 ++++-- src/sixtypical/compiler.py | 20 ++++++++++++ tests/SixtyPical Analysis.md | 54 +++++++++++++++++++++++++++++++++ tests/SixtyPical Compilation.md | 41 +++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 5b87b64..05a91d9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,7 @@ History of SixtyPical 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. * 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. diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 496a8f9..f85355a 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -273,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) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 50b46da..3b8149b 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -230,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': diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 5a9e1cb..3fb6116 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -593,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. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 3284931..da0b9ba 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -895,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. From 42c7e3006de4915b7e6587e4732fae9546cd192d Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 14:29:24 +0000 Subject: [PATCH 21/37] Calculate new position instead of updating to it immediately. --- eg/proto-game.60p | 105 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 20 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 87725fc..a2d0c1f 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -30,6 +30,7 @@ pointer ptr @ 254 word table actor_pos word pos +word new_pos word table actor_delta word delta @@ -51,10 +52,10 @@ byte save_x vector dispatch_game_state inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, actor_delta, delta, + actor_pos, pos, new_pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 outputs button_down, dispatch_game_state, save_x, - actor_pos, pos, actor_delta, delta, + 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 @@ -72,20 +73,20 @@ vector dispatch_game_state vector cinv inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, actor_delta, delta, + actor_pos, pos, new_pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 outputs button_down, dispatch_game_state, save_x, - actor_pos, pos, actor_delta, delta, + 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 @ 788 vector save_cinv inputs joy2, button_down, press_fire_msg, dispatch_game_state, save_x, - actor_pos, pos, actor_delta, delta, + actor_pos, pos, new_pos, actor_delta, delta, screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 outputs button_down, dispatch_game_state, save_x, - actor_pos, pos, actor_delta, delta, + 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 @@ -180,26 +181,90 @@ 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 compare_new_pos +// { +// lda >new_position +// cmp >compare_target +// if beq { +// lda Date: Wed, 13 Dec 2017 15:23:06 +0000 Subject: [PATCH 22/37] `trash` indicates a value is no longer considered meaningful. --- HISTORY.md | 1 + README.md | 1 - src/sixtypical/analyzer.py | 2 ++ src/sixtypical/compiler.py | 2 ++ src/sixtypical/parser.py | 3 +++ tests/SixtyPical Analysis.md | 37 +++++++++++++++++++++++++++++++++ tests/SixtyPical Compilation.md | 17 +++++++++++++++ tests/SixtyPical Syntax.md | 8 +++++++ 8 files changed, 70 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 05a91d9..0c66e7f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,6 +10,7 @@ History of SixtyPical * 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. * 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. diff --git a/README.md b/README.md index 2e117eb..863cdb3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ This should be tracked in the abstract interpretation. * `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. * 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. diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index f85355a..e193c46 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -431,5 +431,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 3b8149b..99eb5cc 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -470,5 +470,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/parser.py b/src/sixtypical/parser.py index 58f81db..81c4adf 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -305,5 +305,8 @@ class Parser(object): 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 3fb6116..963f0ff 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1089,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. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index da0b9ba..9382fb9 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -1073,3 +1073,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 74a1551..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 { From 7c3e1ae62c252259afa461e6a9188f75ac34efb6 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 15:30:20 +0000 Subject: [PATCH 23/37] Use `trash` to avoid writing code that's only to please the analyzer! --- eg/proto-game.60p | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index a2d0c1f..769a902 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -258,12 +258,14 @@ routine player_logic copy 81, [ptr] + y copy new_pos, pos - ld a, 0 // FIXME shouldn't be needed - } else { - copy ^screen, ptr // FIXME shouldn't be needed - ld y, 0 // FIXME shouldn't be needed - ld a, 0 // FIXME shouldn't be needed - add a, 0 // FIXME shouldn't be needed + // 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 } } @@ -349,11 +351,17 @@ routine game_state_title_screen call clear_screen // call init_game copy game_state_play, dispatch_game_state - ld a, 0 // FIXME we shouldn't need to. - ld y, 0 // FIXME we shouldn't need to. - st off, c // FIXME we shouldn't need to. + + // 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 { - ld a, 0 // FIXME we shouldn't need to. + trash y + trash c } goto save_cinv From 7593da7b18030feb5fa5dce2675e7207e20455c1 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 15:53:43 +0000 Subject: [PATCH 24/37] Implement check_new_position_in_bounds. --- eg/proto-game.60p | 73 ++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 769a902..6a02fb3 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -39,6 +39,7 @@ 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. @@ -54,10 +55,10 @@ 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, save_x, + 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 + 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. @@ -75,20 +76,20 @@ 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, save_x, + 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 + trashes a, x, y, c, z, n, v, ptr, save_x, compare_target @ 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, save_x, + 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 + trashes a, x, y, c, z, n, v, ptr, save_x, compare_target // ---------------------------------------------------------------- // Utility Routines @@ -191,39 +192,27 @@ routine calculate_new_position add new_pos, delta } -// routine compare_new_pos -// { -// lda >new_position -// cmp >compare_target -// if beq { -// lda Date: Wed, 13 Dec 2017 16:05:18 +0000 Subject: [PATCH 25/37] Correct the implementation of check_new_position_in_bounds. --- eg/proto-game.60p | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 6a02fb3..d53c111 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -206,12 +206,12 @@ routine check_new_position_in_bounds st on, c sub compare_target, new_pos if not c { - st on, c - } else { st off, c + } else { + st on, c } } else { - st off, c + st on, c } } From f9dc730f88274bd54e3c7065005bfd6ba5c01ae4 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 16:11:02 +0000 Subject: [PATCH 26/37] `copy []+y, a` to indirectly read byte into the `a` register. --- HISTORY.md | 1 + eg/proto-game.60p | 4 +++- src/sixtypical/compiler.py | 5 ++++- tests/SixtyPical Compilation.md | 10 ++++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 0c66e7f..0259d3c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,6 +11,7 @@ History of SixtyPical * 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. diff --git a/eg/proto-game.60p b/eg/proto-game.60p index d53c111..7ffd61c 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -235,7 +235,9 @@ routine player_logic add ptr, new_pos ld y, 0 - // copy [ptr] + y, a + // check collision. + copy [ptr] + y, a + // cmp a, 32 // if z { // copy new_pos, pos diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index 99eb5cc..1888cd7 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -385,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))) diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index 9382fb9..91fedfa 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -1005,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 @@ -1019,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.) From b477c5e786e77e0bb34890843dc2c4f3458d4f58 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 16:18:36 +0000 Subject: [PATCH 27/37] Don't trash `a` in `copy` if `a` is the dest of the `copy`, for now. --- eg/proto-game.60p | 21 ++++++++++----------- src/sixtypical/analyzer.py | 8 +++++++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 7ffd61c..83a53e5 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -237,17 +237,16 @@ routine player_logic // check collision. copy [ptr] + y, a - - // cmp a, 32 - // if z { - // copy new_pos, pos - // copy 81, [ptr] + y - // } else { - // // copy game_state_game_over, dispatch_game_state - // } - - copy 81, [ptr] + y - copy new_pos, pos + cmp a, 32 + if z { + copy new_pos, pos + copy 81, [ptr] + y + } else { + // copy game_state_game_over, dispatch_game_state + 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 diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index e193c46..1272055 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -392,6 +392,7 @@ 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) @@ -409,7 +410,12 @@ 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_unmeaningful(REG_A) elif opcode == 'with-sei': self.analyze_block(instr.block, context) From c84473224b58619cdd72654698ab9c1fc5443bc4 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 16:23:28 +0000 Subject: [PATCH 28/37] Some TODO notes. --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 863cdb3..77db653 100644 --- a/README.md +++ b/README.md @@ -65,18 +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 * `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them. * 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?) From 1d8f1af964c70ba733a6943f408f6c701cbc8842 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 13 Dec 2017 17:00:21 +0000 Subject: [PATCH 29/37] Fix bug in copy []+y, a. "Hero" no longer leaves a trail. --- eg/proto-game.60p | 8 +++++++- src/sixtypical/analyzer.py | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 83a53e5..d1760a4 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -239,8 +239,14 @@ routine player_logic copy [ptr] + y, a cmp a, 32 if z { - copy new_pos, pos copy 81, [ptr] + y + + copy ^screen, ptr + st off, c + add ptr, pos + copy 32, [ptr] + y + + copy new_pos, pos } else { // copy game_state_game_over, dispatch_game_state trash n diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 1272055..32c61e5 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -414,7 +414,10 @@ class Analyzer(object): # 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: + if dest == REG_A: + context.set_touched(REG_A) + context.set_written(REG_A) + else: context.set_unmeaningful(REG_A) elif opcode == 'with-sei': From cfb094513ff594959b8959f401696a96f0dc9fa7 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 14 Dec 2017 10:13:47 +0000 Subject: [PATCH 30/37] Set up obstacles in game. --- eg/proto-game.60p | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index d1760a4..8b552ca 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -215,6 +215,29 @@ routine check_new_position_in_bounds } } +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 // ---------------------------------------------------------------- @@ -270,6 +293,12 @@ routine enemy_logic outputs pos, delta, screen trashes a, x, y, c, z, n, v, ptr { + copy ^screen, ptr + st off, c + add ptr, pos + ld y, 0 + + copy 82, [ptr] + y } // ---------------------------------------------------------------- @@ -296,9 +325,10 @@ routine game_state_play st x, save_x - ////// FIXME need VECTOR TABLEs to make this happen: - ////// copy actor_logic, x dispatch_logic - ////// call indirect_jsr_logic + // 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 { @@ -345,7 +375,7 @@ routine game_state_title_screen if c { call clear_screen - // call init_game + call init_game copy game_state_play, dispatch_game_state // FIXME these trashes, strictly speaking, probably shouldn't be needed, @@ -358,6 +388,7 @@ routine game_state_title_screen } else { trash y trash c + trash v } goto save_cinv From 0452d5f2c8bf0359fa8fba1c00864baed0a73778 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 14 Dec 2017 10:47:57 +0000 Subject: [PATCH 31/37] Add game_state_game_over. Needs a bit of work, though. --- eg/proto-game.60p | 75 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 8b552ca..77e9877 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -243,8 +243,10 @@ routine init_game // ---------------------------------------------------------------- routine player_logic - inputs pos, delta, joy2, screen - outputs pos, delta, new_pos, screen + inputs pos, delta, joy2, screen, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 + outputs pos, delta, new_pos, screen, dispatch_game_state, + screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr, compare_target { call read_stick @@ -270,8 +272,11 @@ routine player_logic copy 32, [ptr] + y copy new_pos, pos + trash y + trash c } else { - // copy game_state_game_over, dispatch_game_state + call clear_screen + copy forward game_state_game_over, dispatch_game_state trash n trash a trash z @@ -285,6 +290,9 @@ routine player_logic trash y trash a trash v + trash c + } else { + trash c } } @@ -309,6 +317,51 @@ routine enemy_logic // 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 +{ + ld y, 0 + repeat { + ld a, press_fire_msg + y + + st on, c + sub a, 64 // yuck. oh well + + st a, screen1 + y + inc y + cmp y, 18 + } until z + + st off, c + call check_button + + if c { + 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 { + 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, @@ -349,7 +402,7 @@ routine game_state_play goto save_cinv } -routine game_state_title_screen +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 @@ -358,25 +411,13 @@ routine game_state_title_screen screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 trashes a, x, y, c, z, n, v, ptr, save_x, compare_target { - ld y, 0 - repeat { - ld a, press_fire_msg + y - - st on, c - sub a, 64 // yuck. oh well - - st a, screen1 + y - inc y - 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 + 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 From be3591ae4464ea6be90f5dd2a687652833b536d2 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 14 Dec 2017 11:04:19 +0000 Subject: [PATCH 32/37] If player perishes, set a flag that game state will use to change. --- eg/proto-game.60p | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 77e9877..2bb6ce1 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -242,12 +242,14 @@ routine init_game // Actor Logics // ---------------------------------------------------------------- +// +// Sets carry if the player perished. Carry clear otherwise. +// + routine player_logic - inputs pos, delta, joy2, screen, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - outputs pos, delta, new_pos, screen, dispatch_game_state, - screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 - trashes a, x, y, c, z, n, v, ptr, compare_target + 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 @@ -272,11 +274,11 @@ routine player_logic copy 32, [ptr] + y copy new_pos, pos - trash y - trash c + + st off, c } else { - call clear_screen - copy forward game_state_game_over, dispatch_game_state + st on, c + st off, c // FIXME this branch seems to be called even when there is no collision. why? trash n trash a trash z @@ -290,16 +292,19 @@ routine player_logic trash y trash a trash v - trash c } else { - trash c + st off, c } } +// +// Sets carry if the player perished. Carry clear otherwise. +// + routine enemy_logic inputs pos, delta, joy2, screen - outputs pos, delta, screen - trashes a, x, y, c, z, n, v, ptr + outputs pos, delta, screen, c + trashes a, x, y, z, n, v, ptr { copy ^screen, ptr st off, c @@ -307,6 +312,8 @@ routine enemy_logic ld y, 0 copy 82, [ptr] + y + + st off, c } // ---------------------------------------------------------------- @@ -390,6 +397,19 @@ routine game_state_play 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 From d69766afbe1b4f8f3401e31e51cde204b10c3ab2 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 14 Dec 2017 11:54:39 +0000 Subject: [PATCH 33/37] Marginally more informative error message. --- src/sixtypical/analyzer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 32c61e5..78a594b 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -319,11 +319,13 @@ 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': From e2f61faeae3ed90ccb28ff1bc15bc302de577f55 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 14 Dec 2017 11:59:09 +0000 Subject: [PATCH 34/37] Fix bug in game. --- eg/proto-game.60p | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 2bb6ce1..b805f59 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -264,10 +264,13 @@ routine player_logic // 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 81, [ptr] + y - copy ^screen, ptr st off, c add ptr, pos @@ -275,10 +278,14 @@ routine player_logic copy new_pos, pos + copy ^screen, ptr + st off, c + add ptr, pos + copy 81, [ptr] + y + st off, c } else { st on, c - st off, c // FIXME this branch seems to be called even when there is no collision. why? trash n trash a trash z From 439b827e92a16602b1f9b7f68e9134814ac050bb Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 14 Dec 2017 12:05:44 +0000 Subject: [PATCH 35/37] Almost make the bouncing obstacles happen. It's... interesting. --- eg/proto-game.60p | 61 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index b805f59..371b699 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -309,16 +309,61 @@ routine player_logic // routine enemy_logic - inputs pos, delta, joy2, screen - outputs pos, delta, screen, c - trashes a, x, y, z, n, v, ptr + inputs pos, delta, screen + outputs pos, delta, new_pos, screen, c + trashes a, x, y, z, n, v, ptr, compare_target { - copy ^screen, ptr - st off, c - add ptr, pos - ld y, 0 + call calculate_new_position + call check_new_position_in_bounds - copy 82, [ptr] + y + 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 word 0, compare_target + sub compare_target, delta + copy compare_target, delta + trash compare_target + } st off, c } From 7a9b7d07190e22209dae7630433f473f012d1367 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Mon, 8 Jan 2018 12:10:59 +0000 Subject: [PATCH 36/37] Reverse delta of obstacles in a more conventional way upon bounce. --- eg/proto-game.60p | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/eg/proto-game.60p b/eg/proto-game.60p index 371b699..b6405be 100644 --- a/eg/proto-game.60p +++ b/eg/proto-game.60p @@ -359,9 +359,14 @@ routine enemy_logic trash y trash a } else { - copy word 0, compare_target - sub compare_target, delta - copy compare_target, delta + 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 } From c786bc4e0c3ed6234fde5af6392b55390f186739 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Mon, 8 Jan 2018 12:21:38 +0000 Subject: [PATCH 37/37] Prep for release of 0.10. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77db653..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.10 (not released yet). +is 0.10. Documentation -------------