1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2025-02-20 13:29:09 +00:00

Merge branch 'develop-0.17' into remove-legacy-syntax

This commit is contained in:
Chris Pressey 2018-09-07 17:49:03 +01:00
commit 0c63de9351
9 changed files with 148 additions and 130 deletions

View File

@ -5,6 +5,11 @@ History of SixtyPical
---- ----
* `save X, Y, Z { }` now allowed as a shortcut for nested `save`s. * `save X, Y, Z { }` now allowed as a shortcut for nested `save`s.
* If the name in a location expression isn't found in the symbol
table, a forward reference will _always_ be generated; and forward
references in _all_ operations will be resolved after parsing.
* As a consequence, trying to call or goto a non-routine-typed symbol
is now an analysis error, not a syntax error.
* Split TODO off into own file. * Split TODO off into own file.
* `sixtypical` no longer writes the compiled binary to standard * `sixtypical` no longer writes the compiled binary to standard
output. The `--output` command-line argument should be given output. The `--output` command-line argument should be given

34
TODO.md
View File

@ -1,20 +1,16 @@
TODO for SixtyPical TODO for SixtyPical
=================== ===================
### `low` and `high` address operators ### 16-bit `cmp`
To turn `word` type into `byte`. This is because we don't actually want `low` and `high` address operators
that turn `word` type into `byte`.
Trying to remember if we have a compelling case for this or now. The best I can think This is because this immediately makes things harder (that is, effectively
of is for implementing 16-bit `cmp` in an efficient way. Maybe we should see if we impossible) to analyze.
can get by with 16-bit `cmp` instead though.
The problem is that once a byte is extracted, putting it back into a word is awkward. 16-bit `cmp` also benefits from some special differences between `cmp`
The address operators have to modify a destination in a special way. That is, when and `sub` on 6502, so it would be nice to capture them.
you say `st a, >word`, you are updating `word` to be `word & $ff | a << 8`, somelike.
Is that consistent with `st`? Well, probably it is, but we have to explain it.
It might make more sense, then, for it to be "part of the operation" instead of "part of
the reference"; something like `st.hi x, word`; `st.lo y, word`. Dunno.
### Save values to other-than-the-stack ### Save values to other-than-the-stack
@ -27,17 +23,12 @@ Allow
Which uses some other storage location instead of the stack. A local static Which uses some other storage location instead of the stack. A local static
would be a good candidate for such. would be a good candidate for such.
### Make all symbols forward-referencable
Basically, don't do symbol-table lookups when parsing, but do have a more formal
"symbol resolution" (linking) phase right after parsing.
### Associate each pointer with the buffer it points into ### Associate each pointer with the buffer it points into
Check that the buffer being read or written to through pointer, appears in appropriate Check that the buffer being read or written to through pointer, appears in appropriate
inputs or outputs set. inputs or outputs set.
In the analysis, when we obtain a pointer, we need to record, in contect, what buffer In the analysis, when we obtain a pointer, we need to record, in context, what buffer
that pointer came from. that pointer came from.
When we write through that pointer, we need to set that buffer as written. When we write through that pointer, we need to set that buffer as written.
@ -78,8 +69,8 @@ error.
More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot More generally, define a block as having zero or one `goto`s at the end. (and `goto`s cannot
appear elsewhere.) appear elsewhere.)
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can. If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
The constraints should iron out the same both ways. if the block is in tail position. The constraints should iron out the same both ways.
And - once we have this - why do we need `goto` to be in tail position, strictly? And - once we have this - why do we need `goto` to be in tail position, strictly?
As long as the routine has consistent type context every place it exits, that should be fine. As long as the routine has consistent type context every place it exits, that should be fine.
@ -91,3 +82,8 @@ Search a searchlist of include paths. And use them to make libraries of routine
One such library routine might be an `interrupt routine` type for various architectures. One such library routine might be an `interrupt routine` type for various architectures.
Since "the supervisor" has stored values on the stack, we should be able to trash them Since "the supervisor" has stored values on the stack, we should be able to trash them
with impunity, in such a routine. with impunity, in such a routine.
### Line numbers in analysis error messages
For analysis errors, there is a line number, but it's the line of the routine
after the routine in which the analysis error occurred. Fix this.

View File

@ -33,9 +33,11 @@
typedef routine typedef routine
inputs joy2, press_fire_msg, dispatch_game_state, inputs joy2, press_fire_msg, dispatch_game_state,
actor_pos, actor_delta, actor_logic, actor_pos, actor_delta, actor_logic,
player_died,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs dispatch_game_state, outputs dispatch_game_state,
actor_pos, actor_delta, actor_logic, actor_pos, actor_delta, actor_logic,
player_died,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4 screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
game_state_routine game_state_routine
@ -45,13 +47,13 @@ typedef routine
// //
// Routines that conform to this type also follow this convention: // Routines that conform to this type also follow this convention:
// //
// Set carry if the player perished. Carry clear otherwise. // Set player_died to 1 if the player perished. Unchanged otherwise.
// //
typedef routine typedef routine
inputs pos, delta, joy2, screen inputs pos, delta, joy2, screen, player_died
outputs pos, delta, new_pos, screen, c outputs pos, delta, new_pos, screen, player_died
trashes a, x, y, z, n, v, ptr trashes a, x, y, z, n, v, c, ptr
logic_routine logic_routine
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@ -87,6 +89,8 @@ word new_pos
word table[256] actor_delta word table[256] actor_delta
word delta word delta
byte player_died
vector logic_routine table[256] actor_logic vector logic_routine table[256] actor_logic
vector logic_routine dispatch_logic vector logic_routine dispatch_logic
@ -241,7 +245,7 @@ define check_new_position_in_bounds routine
routine init_game routine init_game
inputs actor_pos, actor_delta, actor_logic inputs actor_pos, actor_delta, actor_logic
outputs actor_pos, actor_delta, actor_logic outputs actor_pos, actor_delta, actor_logic, player_died
trashes pos, a, y, z, n, c, v trashes pos, a, y, z, n, c, v
{ {
ld y, 0 ld y, 0
@ -259,9 +263,11 @@ routine init_game
} until z } until z
ld y, 0 ld y, 0
copy word 0, actor_pos + y copy word 40, actor_pos + y
copy word 0, actor_delta + y copy word 0, actor_delta + y
copy player_logic, actor_logic + y copy player_logic, actor_logic + y
st y, player_died
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@ -301,10 +307,9 @@ define player_logic logic_routine
st off, c st off, c
add ptr, pos add ptr, pos
copy 81, [ptr] + y copy 81, [ptr] + y
st off, c
} else { } else {
st on, c ld a, 1
st a, player_died
} }
// FIXME these trashes, strictly speaking, probably shouldn't be needed, // FIXME these trashes, strictly speaking, probably shouldn't be needed,
@ -314,8 +319,6 @@ define player_logic logic_routine
trash ptr trash ptr
trash y trash y
trash v trash v
} else {
st off, c
} }
} }
@ -351,10 +354,6 @@ define enemy_logic logic_routine
st off, c st off, c
add ptr, pos add ptr, pos
copy 82, [ptr] + y copy 82, [ptr] + y
st off, c
} else {
st on, c
} }
// FIXME these trashes, strictly speaking, probably shouldn't be needed, // FIXME these trashes, strictly speaking, probably shouldn't be needed,
@ -373,8 +372,6 @@ define enemy_logic logic_routine
copy $ffd8, delta copy $ffd8, delta
} }
} }
st off, c
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@ -408,6 +405,7 @@ define game_state_title_screen game_state_routine
define game_state_play game_state_routine define game_state_play game_state_routine
{ {
ld x, 0 ld x, 0
st x, player_died
for x up to 15 { for x up to 15 {
copy actor_pos + x, pos copy actor_pos + x, pos
copy actor_delta + x, delta copy actor_delta + x, delta
@ -420,18 +418,19 @@ define game_state_play game_state_routine
save x { save x {
copy actor_logic + x, dispatch_logic copy actor_logic + x, dispatch_logic
call dispatch_logic call dispatch_logic
if c {
// Player died! Want no dead!
call clear_screen
copy game_state_game_over, dispatch_game_state
}
} }
copy pos, actor_pos + x copy pos, actor_pos + x
copy delta, actor_delta + x copy delta, actor_delta + x
} }
ld a, player_died
if not z {
// Player died! Want no dead!
call clear_screen
copy game_state_game_over, dispatch_game_state
}
goto save_cinv goto save_cinv
} }

View File

@ -550,6 +550,8 @@ class Analyzer(object):
context.invalidate_range(dest) context.invalidate_range(dest)
elif opcode == 'call': elif opcode == 'call':
type = instr.location.type type = instr.location.type
if not isinstance(type, (RoutineType, VectorType)):
raise TypeMismatchError(instr, instr.location)
if isinstance(type, VectorType): if isinstance(type, VectorType):
type = type.of_type type = type.of_type
for ref in type.inputs: for ref in type.inputs:

View File

@ -37,14 +37,16 @@ class AST(object):
def all_children(self): def all_children(self):
for attr in self.children_attrs: for attr in self.children_attrs:
for child in self.attrs[attr]: for child in self.attrs[attr]:
if child is not None:
yield child
for subchild in child.all_children():
yield subchild
for attr in self.child_attrs:
child = self.attrs[attr]
if child is not None:
yield child yield child
for subchild in child.all_children(): for subchild in child.all_children():
yield subchild yield subchild
for attr in self.child_attrs:
child = self.attrs[attr]
yield child
for subchild in child.all_children():
yield subchild
class Program(AST): class Program(AST):

View File

@ -18,20 +18,6 @@ class Type(object):
def __hash__(self): def __hash__(self):
return hash(self.name) return hash(self.name)
def backpatch_constraint_labels(self, resolver):
def resolve(w):
if not isinstance(w, str):
return w
return resolver(w)
if isinstance(self, TableType):
self.of_type.backpatch_constraint_labels(resolver)
elif isinstance(self, VectorType):
self.of_type.backpatch_constraint_labels(resolver)
elif isinstance(self, RoutineType):
self.inputs = set([resolve(w) for w in self.inputs])
self.outputs = set([resolve(w) for w in self.outputs])
self.trashes = set([resolve(w) for w in self.trashes])
TYPE_BIT = Type('bit', max_range=(0, 1)) TYPE_BIT = Type('bit', max_range=(0, 1))
TYPE_BYTE = Type('byte', max_range=(0, 255)) TYPE_BYTE = Type('byte', max_range=(0, 255))
@ -41,11 +27,11 @@ TYPE_WORD = Type('word', max_range=(0, 65535))
class RoutineType(Type): class RoutineType(Type):
"""This memory location contains the code for a routine.""" """This memory location contains the code for a routine."""
def __init__(self, inputs=None, outputs=None, trashes=None): def __init__(self, inputs, outputs, trashes):
self.name = 'routine' self.name = 'routine'
self.inputs = inputs or set() self.inputs = inputs
self.outputs = outputs or set() self.outputs = outputs
self.trashes = trashes or set() self.trashes = trashes
def __repr__(self): def __repr__(self):
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % ( return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (

View File

@ -18,6 +18,14 @@ class SymEntry(object):
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model) return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
class ForwardReference(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
class ParsingContext(object): class ParsingContext(object):
def __init__(self): def __init__(self):
self.symbols = {} # token -> SymEntry self.symbols = {} # token -> SymEntry
@ -45,7 +53,6 @@ class Parser(object):
def __init__(self, context, text, filename): def __init__(self, context, text, filename):
self.context = context self.context = context
self.scanner = Scanner(text, filename) self.scanner = Scanner(text, filename)
self.backpatch_instrs = []
def syntax_error(self, msg): def syntax_error(self, msg):
self.scanner.syntax_error(msg) self.scanner.syntax_error(msg)
@ -67,6 +74,44 @@ class Parser(object):
def clear_statics(self): def clear_statics(self):
self.context.statics = {} self.context.statics = {}
# ---- symbol resolution
def resolve_symbols(self, program):
# This could stand to be better unified.
def backpatch_constraint_labels(type_):
def resolve(w):
if not isinstance(w, ForwardReference):
return w
return self.lookup(w.name)
if isinstance(type_, TableType):
backpatch_constraint_labels(type_.of_type)
elif isinstance(type_, VectorType):
backpatch_constraint_labels(type_.of_type)
elif isinstance(type_, RoutineType):
type_.inputs = set([resolve(w) for w in type_.inputs])
type_.outputs = set([resolve(w) for w in type_.outputs])
type_.trashes = set([resolve(w) for w in type_.trashes])
for defn in program.defns:
backpatch_constraint_labels(defn.location.type)
for routine in program.routines:
backpatch_constraint_labels(routine.location.type)
def resolve_fwd_reference(obj, field):
field_value = getattr(obj, field, None)
if isinstance(field_value, ForwardReference):
setattr(obj, field, self.lookup(field_value.name))
elif isinstance(field_value, IndexedRef):
if isinstance(field_value.ref, ForwardReference):
field_value.ref = self.lookup(field_value.ref.name)
for node in program.all_children():
if isinstance(node, SingleOp):
resolve_fwd_reference(node, 'location')
resolve_fwd_reference(node, 'src')
resolve_fwd_reference(node, 'dest')
# --- grammar productions # --- grammar productions
def program(self): def program(self):
@ -91,28 +136,9 @@ class Parser(object):
routines.append(routine) routines.append(routine)
self.scanner.check_type('EOF') self.scanner.check_type('EOF')
# now backpatch the executable types. program = Program(self.scanner.line_number, defns=defns, routines=routines)
#for type_name, type_ in self.context.typedefs.items(): self.resolve_symbols(program)
# type_.backpatch_constraint_labels(lambda w: self.lookup(w)) return program
for defn in defns:
defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
for routine in routines:
routine.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
for instr in self.backpatch_instrs:
if instr.opcode in ('call', 'goto'):
name = instr.location
model = self.lookup(name)
if not isinstance(model.type, (RoutineType, VectorType)):
self.syntax_error('Illegal call of non-executable "%s"' % name)
instr.location = model
if instr.opcode in ('copy',) and isinstance(instr.src, str):
name = instr.src
model = self.lookup(name)
if not isinstance(model.type, (RoutineType, VectorType)):
self.syntax_error('Illegal copy of non-executable "%s"' % name)
instr.src = model
return Program(self.scanner.line_number, defns=defns, routines=routines)
def typedef(self): def typedef(self):
self.scanner.expect('typedef') self.scanner.expect('typedef')
@ -252,7 +278,11 @@ class Parser(object):
outputs = set(self.labels()) outputs = set(self.labels())
if self.scanner.consume('trashes'): if self.scanner.consume('trashes'):
trashes = set(self.labels()) trashes = set(self.labels())
return (inputs, outputs, trashes) return (
set([ForwardReference(n) for n in inputs]),
set([ForwardReference(n) for n in outputs]),
set([ForwardReference(n) for n in trashes])
)
def routine(self, name): def routine(self, name):
type_ = self.defn_type() type_ = self.defn_type()
@ -302,23 +332,19 @@ class Parser(object):
accum.append(self.locexpr()) accum.append(self.locexpr())
return accum return accum
def locexpr(self, forward=False): def locexpr(self):
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'): if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
return self.const() return self.const()
elif forward: else:
name = self.scanner.token name = self.scanner.token
self.scanner.scan() self.scanner.scan()
loc = self.context.fetch(name) loc = self.context.fetch(name)
if loc is not None: if loc:
return loc return loc
else: else:
return name return ForwardReference(name)
else:
loc = self.lookup(self.scanner.token)
self.scanner.scan()
return loc
def indlocexpr(self, forward=False): def indlocexpr(self):
if self.scanner.consume('['): if self.scanner.consume('['):
loc = self.locexpr() loc = self.locexpr()
self.scanner.expect(']') self.scanner.expect(']')
@ -329,10 +355,10 @@ class Parser(object):
loc = self.locexpr() loc = self.locexpr()
return AddressRef(loc) return AddressRef(loc)
else: else:
return self.indexed_locexpr(forward=forward) return self.indexed_locexpr()
def indexed_locexpr(self, forward=False): def indexed_locexpr(self):
loc = self.locexpr(forward=forward) loc = self.locexpr()
if not isinstance(loc, str): if not isinstance(loc, str):
index = None index = None
if self.scanner.consume('+'): if self.scanner.consume('+'):
@ -427,17 +453,15 @@ class Parser(object):
self.scanner.scan() self.scanner.scan()
name = self.scanner.token name = self.scanner.token
self.scanner.scan() self.scanner.scan()
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=name, dest=None, src=None) instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None)
self.backpatch_instrs.append(instr)
return instr return instr
elif self.scanner.token in ("copy",): elif self.scanner.token in ("copy",):
opcode = self.scanner.token opcode = self.scanner.token
self.scanner.scan() self.scanner.scan()
src = self.indlocexpr(forward=True) src = self.indlocexpr()
self.scanner.expect(',') self.scanner.expect(',')
dest = self.indlocexpr() dest = self.indlocexpr()
instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src)
self.backpatch_instrs.append(instr)
return instr return instr
elif self.scanner.consume("with"): elif self.scanner.consume("with"):
self.scanner.expect("interrupts") self.scanner.expect("interrupts")

View File

@ -196,6 +196,35 @@ If a routine reads or writes a user-define memory location, it needs to declare
| } | }
= ok = ok
### call ###
You can't call a non-routine.
| byte up
|
| routine main outputs x, y trashes z, n {
| ld x, 0
| ld y, 1
| call up
| }
? TypeMismatchError: up
| routine main outputs x, y trashes z, n {
| ld x, 0
| ld y, 1
| call x
| }
? TypeMismatchError: x
Nor can you goto a non-routine.
| byte foo
|
| routine main {
| goto foo
| }
? TypeMismatchError: foo
### ld ### ### ld ###
Can't `ld` from a memory location that isn't initialized. Can't `ld` from a memory location that isn't initialized.

View File

@ -404,24 +404,6 @@ Can't call routine that hasn't been defined.
| } | }
? SyntaxError ? SyntaxError
And you can't call a non-routine.
| byte up
|
| define main routine {
| ld x, 0
| ld y, 1
| call up
| }
? SyntaxError
| define main routine {
| ld x, 0
| ld y, 1
| call x
| }
? SyntaxError
But you can call a routine that is yet to be defined, further on. But you can call a routine that is yet to be defined, further on.
| define main routine { | define main routine {
@ -589,13 +571,6 @@ goto.
| } | }
? SyntaxError ? SyntaxError
| byte foo
|
| define main routine {
| goto foo
| }
? SyntaxError
Buffers and pointers. Buffers and pointers.
| buffer[2048] buf | buffer[2048] buf