mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-11-25 23:49:17 +00:00
Merge branch 'develop-0.17' into remove-legacy-syntax
This commit is contained in:
commit
0c63de9351
@ -5,6 +5,11 @@ History of SixtyPical
|
||||
----
|
||||
|
||||
* `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.
|
||||
* `sixtypical` no longer writes the compiled binary to standard
|
||||
output. The `--output` command-line argument should be given
|
||||
|
34
TODO.md
34
TODO.md
@ -1,20 +1,16 @@
|
||||
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
|
||||
of is for implementing 16-bit `cmp` in an efficient way. Maybe we should see if we
|
||||
can get by with 16-bit `cmp` instead though.
|
||||
This is because this immediately makes things harder (that is, effectively
|
||||
impossible) to analyze.
|
||||
|
||||
The problem is that once a byte is extracted, putting it back into a word is awkward.
|
||||
The address operators have to modify a destination in a special way. That is, when
|
||||
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.
|
||||
16-bit `cmp` also benefits from some special differences between `cmp`
|
||||
and `sub` on 6502, so it would be nice to capture them.
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
Check that the buffer being read or written to through pointer, appears in appropriate
|
||||
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.
|
||||
|
||||
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
|
||||
appear elsewhere.)
|
||||
|
||||
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 a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
|
||||
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?
|
||||
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.
|
||||
Since "the supervisor" has stored values on the stack, we should be able to trash them
|
||||
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.
|
||||
|
@ -33,9 +33,11 @@
|
||||
typedef routine
|
||||
inputs joy2, press_fire_msg, dispatch_game_state,
|
||||
actor_pos, actor_delta, actor_logic,
|
||||
player_died,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs dispatch_game_state,
|
||||
actor_pos, actor_delta, actor_logic,
|
||||
player_died,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
|
||||
game_state_routine
|
||||
@ -45,13 +47,13 @@ typedef routine
|
||||
//
|
||||
// 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
|
||||
inputs pos, delta, joy2, screen
|
||||
outputs pos, delta, new_pos, screen, c
|
||||
trashes a, x, y, z, n, v, ptr
|
||||
inputs pos, delta, joy2, screen, player_died
|
||||
outputs pos, delta, new_pos, screen, player_died
|
||||
trashes a, x, y, z, n, v, c, ptr
|
||||
logic_routine
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@ -87,6 +89,8 @@ word new_pos
|
||||
word table[256] actor_delta
|
||||
word delta
|
||||
|
||||
byte player_died
|
||||
|
||||
vector logic_routine table[256] actor_logic
|
||||
vector logic_routine dispatch_logic
|
||||
|
||||
@ -241,7 +245,7 @@ define check_new_position_in_bounds routine
|
||||
|
||||
routine init_game
|
||||
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
|
||||
{
|
||||
ld y, 0
|
||||
@ -259,9 +263,11 @@ routine init_game
|
||||
} until z
|
||||
|
||||
ld y, 0
|
||||
copy word 0, actor_pos + y
|
||||
copy word 40, actor_pos + y
|
||||
copy word 0, actor_delta + y
|
||||
copy player_logic, actor_logic + y
|
||||
|
||||
st y, player_died
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@ -301,10 +307,9 @@ define player_logic logic_routine
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 81, [ptr] + y
|
||||
|
||||
st off, c
|
||||
} else {
|
||||
st on, c
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
}
|
||||
|
||||
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
|
||||
@ -314,8 +319,6 @@ define player_logic logic_routine
|
||||
trash ptr
|
||||
trash y
|
||||
trash v
|
||||
} else {
|
||||
st off, c
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,10 +354,6 @@ define enemy_logic logic_routine
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 82, [ptr] + y
|
||||
|
||||
st off, c
|
||||
} else {
|
||||
st on, c
|
||||
}
|
||||
|
||||
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
|
||||
@ -373,8 +372,6 @@ define enemy_logic logic_routine
|
||||
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
|
||||
{
|
||||
ld x, 0
|
||||
st x, player_died
|
||||
for x up to 15 {
|
||||
copy actor_pos + x, pos
|
||||
copy actor_delta + x, delta
|
||||
@ -420,18 +418,19 @@ define game_state_play game_state_routine
|
||||
save x {
|
||||
copy actor_logic + x, 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 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
|
||||
}
|
||||
|
||||
|
@ -550,6 +550,8 @@ class Analyzer(object):
|
||||
context.invalidate_range(dest)
|
||||
elif opcode == 'call':
|
||||
type = instr.location.type
|
||||
if not isinstance(type, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location)
|
||||
if isinstance(type, VectorType):
|
||||
type = type.of_type
|
||||
for ref in type.inputs:
|
||||
|
@ -37,14 +37,16 @@ class AST(object):
|
||||
def all_children(self):
|
||||
for attr in self.children_attrs:
|
||||
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
|
||||
for subchild in child.all_children():
|
||||
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):
|
||||
|
@ -18,20 +18,6 @@ class Type(object):
|
||||
def __hash__(self):
|
||||
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_BYTE = Type('byte', max_range=(0, 255))
|
||||
@ -41,11 +27,11 @@ TYPE_WORD = Type('word', max_range=(0, 65535))
|
||||
|
||||
class RoutineType(Type):
|
||||
"""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.inputs = inputs or set()
|
||||
self.outputs = outputs or set()
|
||||
self.trashes = trashes or set()
|
||||
self.inputs = inputs
|
||||
self.outputs = outputs
|
||||
self.trashes = trashes
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
|
||||
|
@ -18,6 +18,14 @@ class SymEntry(object):
|
||||
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):
|
||||
def __init__(self):
|
||||
self.symbols = {} # token -> SymEntry
|
||||
@ -45,7 +53,6 @@ class Parser(object):
|
||||
def __init__(self, context, text, filename):
|
||||
self.context = context
|
||||
self.scanner = Scanner(text, filename)
|
||||
self.backpatch_instrs = []
|
||||
|
||||
def syntax_error(self, msg):
|
||||
self.scanner.syntax_error(msg)
|
||||
@ -67,6 +74,44 @@ class Parser(object):
|
||||
def clear_statics(self):
|
||||
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
|
||||
|
||||
def program(self):
|
||||
@ -91,28 +136,9 @@ class Parser(object):
|
||||
routines.append(routine)
|
||||
self.scanner.check_type('EOF')
|
||||
|
||||
# now backpatch the executable types.
|
||||
#for type_name, type_ in self.context.typedefs.items():
|
||||
# type_.backpatch_constraint_labels(lambda w: self.lookup(w))
|
||||
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)
|
||||
program = Program(self.scanner.line_number, defns=defns, routines=routines)
|
||||
self.resolve_symbols(program)
|
||||
return program
|
||||
|
||||
def typedef(self):
|
||||
self.scanner.expect('typedef')
|
||||
@ -252,7 +278,11 @@ class Parser(object):
|
||||
outputs = set(self.labels())
|
||||
if self.scanner.consume('trashes'):
|
||||
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):
|
||||
type_ = self.defn_type()
|
||||
@ -302,23 +332,19 @@ class Parser(object):
|
||||
accum.append(self.locexpr())
|
||||
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'):
|
||||
return self.const()
|
||||
elif forward:
|
||||
else:
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
loc = self.context.fetch(name)
|
||||
if loc is not None:
|
||||
if loc:
|
||||
return loc
|
||||
else:
|
||||
return name
|
||||
else:
|
||||
loc = self.lookup(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
return ForwardReference(name)
|
||||
|
||||
def indlocexpr(self, forward=False):
|
||||
def indlocexpr(self):
|
||||
if self.scanner.consume('['):
|
||||
loc = self.locexpr()
|
||||
self.scanner.expect(']')
|
||||
@ -329,10 +355,10 @@ class Parser(object):
|
||||
loc = self.locexpr()
|
||||
return AddressRef(loc)
|
||||
else:
|
||||
return self.indexed_locexpr(forward=forward)
|
||||
return self.indexed_locexpr()
|
||||
|
||||
def indexed_locexpr(self, forward=False):
|
||||
loc = self.locexpr(forward=forward)
|
||||
def indexed_locexpr(self):
|
||||
loc = self.locexpr()
|
||||
if not isinstance(loc, str):
|
||||
index = None
|
||||
if self.scanner.consume('+'):
|
||||
@ -427,17 +453,15 @@ class Parser(object):
|
||||
self.scanner.scan()
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=name, dest=None, src=None)
|
||||
self.backpatch_instrs.append(instr)
|
||||
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None)
|
||||
return instr
|
||||
elif self.scanner.token in ("copy",):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
src = self.indlocexpr(forward=True)
|
||||
src = self.indlocexpr()
|
||||
self.scanner.expect(',')
|
||||
dest = self.indlocexpr()
|
||||
instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src)
|
||||
self.backpatch_instrs.append(instr)
|
||||
return instr
|
||||
elif self.scanner.consume("with"):
|
||||
self.scanner.expect("interrupts")
|
||||
|
@ -196,6 +196,35 @@ If a routine reads or writes a user-define memory location, it needs to declare
|
||||
| }
|
||||
= 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 ###
|
||||
|
||||
Can't `ld` from a memory location that isn't initialized.
|
||||
|
@ -404,24 +404,6 @@ Can't call routine that hasn't been defined.
|
||||
| }
|
||||
? 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.
|
||||
|
||||
| define main routine {
|
||||
@ -589,13 +571,6 @@ goto.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| byte foo
|
||||
|
|
||||
| define main routine {
|
||||
| goto foo
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Buffers and pointers.
|
||||
|
||||
| buffer[2048] buf
|
||||
|
Loading…
Reference in New Issue
Block a user