mirror of
https://github.com/catseye/SixtyPical.git
synced 2025-02-16 15:30:26 +00:00
Implement local locations that aren't statically initialized.
This commit is contained in:
parent
81e28fa757
commit
dd29b6fd4a
@ -1,6 +1,14 @@
|
||||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
0.20
|
||||
----
|
||||
|
||||
* Fixed a bug where two local statics could be declared with
|
||||
the same name.
|
||||
* Local locations need no longer be static. If they are not
|
||||
static, they are considered uninitialized until assigned.
|
||||
|
||||
0.19
|
||||
----
|
||||
|
||||
|
20
TODO.md
20
TODO.md
@ -53,21 +53,17 @@ at different times.
|
||||
|
||||
These can co-exist with general, non-specific-table-linked `pointer` variables.
|
||||
|
||||
### Local non-statics
|
||||
### Space optimization of local non-statics
|
||||
|
||||
Somewhat related to the above, it should be possible to declare a local storage
|
||||
location which is not static.
|
||||
|
||||
In this case, it would be considered uninitialized each time the routine was
|
||||
entered.
|
||||
|
||||
So, you do not have a guarantee that it has a valid value. But you are guaranteed
|
||||
that no other routine can read or modify it.
|
||||
|
||||
It also enables a trick: if there are two routines A and B, and A never calls B
|
||||
(even indirectly), and B never calls A (even indirectly), then their locals can
|
||||
If there are two routines A and B, and A never calls B (even indirectly), and
|
||||
B never calls A (even indirectly), then their non-static locals can
|
||||
be allocated at the same space.
|
||||
|
||||
This is more an impressive trick than a really useful feature, but still.
|
||||
Impressive tricks are impressive.
|
||||
|
||||
### Locals with explicit addresses
|
||||
|
||||
A local could also be given an explicit address. In this case, two locals in
|
||||
different routines could be given the same address, and as long as the condition
|
||||
in the above paragraph holds, that's okay. (If it doesn't, the analyzer should
|
||||
|
@ -183,9 +183,14 @@ class AnalysisContext(object):
|
||||
def assert_meaningful(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
||||
for ref in refs:
|
||||
# statics are always meaningful
|
||||
if self.symtab.has_static(self.routine.name, ref.name):
|
||||
continue
|
||||
if self.symtab.has_local(self.routine.name, ref.name):
|
||||
if ref not in self._range:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
else:
|
||||
continue
|
||||
if self.is_constant(ref):
|
||||
pass
|
||||
elif isinstance(ref, LocationRef):
|
||||
@ -203,8 +208,8 @@ class AnalysisContext(object):
|
||||
def assert_writeable(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', ForbiddenWriteError)
|
||||
for ref in refs:
|
||||
# statics are always writeable
|
||||
if self.symtab.has_static(self.routine.name, ref.name):
|
||||
# locals are always writeable
|
||||
if self.symtab.has_local(self.routine.name, ref.name):
|
||||
continue
|
||||
if ref not in self._writeable:
|
||||
message = ref.name
|
||||
@ -384,8 +389,8 @@ class AnalysisContext(object):
|
||||
def max_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
elif self.symtab.has_static(self.routine.name, ref.name):
|
||||
return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range
|
||||
elif self.symtab.has_local(self.routine.name, ref.name):
|
||||
return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
|
||||
else:
|
||||
return self.symtab.fetch_global_type(ref.name).max_range
|
||||
|
||||
@ -401,8 +406,8 @@ class Analyzer(object):
|
||||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
|
||||
return self.symtab.fetch_static_type(self.current_routine.name, name)
|
||||
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
|
||||
return self.symtab.fetch_local_type(self.current_routine.name, name)
|
||||
return self.symtab.fetch_global_type(name)
|
||||
|
||||
def get_type(self, ref):
|
||||
@ -462,6 +467,14 @@ class Analyzer(object):
|
||||
self.current_routine = routine
|
||||
type_ = self.get_type_for_name(routine.name)
|
||||
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
|
||||
# register any local statics as already-initialized
|
||||
for local_name, local_symentry in self.symtab.locals.get(routine.name, {}).items():
|
||||
ref = self.symtab.fetch_local_ref(routine.name, local_name)
|
||||
if local_symentry.ast_node.initial is not None:
|
||||
context.set_meaningful(ref)
|
||||
context.set_range(ref, local_symentry.ast_node.initial, local_symentry.ast_node.initial)
|
||||
|
||||
self.exit_contexts = []
|
||||
|
||||
self.analyze_block(routine.block, context)
|
||||
@ -505,7 +518,7 @@ class Analyzer(object):
|
||||
|
||||
# if something was touched, then it should have been declared to be writable.
|
||||
for ref in context.each_touched():
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_static(routine.name, ref.name):
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_local(routine.name, ref.name):
|
||||
raise ForbiddenWriteError(routine, ref.name)
|
||||
|
||||
self.exit_contexts = None
|
||||
|
@ -59,7 +59,7 @@ class Defn(AST):
|
||||
|
||||
class Routine(AST):
|
||||
value_attrs = ('name', 'addr', 'initial',)
|
||||
children_attrs = ('statics',)
|
||||
children_attrs = ('locals',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ class Compiler(object):
|
||||
self.symtab = symtab
|
||||
self.emitter = emitter
|
||||
self.routines = {} # routine.name -> Routine
|
||||
self.routine_statics = {} # routine.name -> { static.name -> Label }
|
||||
self.routine_locals = {} # routine.name -> { local.name -> Label }
|
||||
self.labels = {} # global.name -> Label ("global" includes routines)
|
||||
self.trampolines = {} # Location -> Label
|
||||
self.current_routine = None
|
||||
@ -42,8 +42,8 @@ class Compiler(object):
|
||||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
|
||||
return self.symtab.fetch_static_type(self.current_routine.name, name)
|
||||
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
|
||||
return self.symtab.fetch_local_type(self.current_routine.name, name)
|
||||
return self.symtab.fetch_global_type(name)
|
||||
|
||||
def get_type(self, ref):
|
||||
@ -76,9 +76,9 @@ class Compiler(object):
|
||||
|
||||
def get_label(self, name):
|
||||
if self.current_routine:
|
||||
static_label = self.routine_statics.get(self.current_routine.name, {}).get(name)
|
||||
if static_label:
|
||||
return static_label
|
||||
local_label = self.routine_locals.get(self.current_routine.name, {}).get(name)
|
||||
if local_label:
|
||||
return local_label
|
||||
return self.labels[name]
|
||||
|
||||
def absolute_or_zero_page(self, label):
|
||||
@ -107,16 +107,15 @@ class Compiler(object):
|
||||
label.set_addr(routine.addr)
|
||||
self.labels[routine.name] = label
|
||||
|
||||
if hasattr(routine, 'statics'):
|
||||
self.current_routine = routine
|
||||
static_labels = {}
|
||||
for defn in routine.statics:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
static_labels[defn.name] = label
|
||||
declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label))
|
||||
self.routine_statics[routine.name] = static_labels
|
||||
self.current_routine = None
|
||||
self.current_routine = routine
|
||||
local_labels = {}
|
||||
for defn in routine.locals:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
local_labels[defn.name] = label
|
||||
declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label))
|
||||
self.routine_locals[routine.name] = local_labels
|
||||
self.current_routine = None
|
||||
|
||||
if compilation_roster is None:
|
||||
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
||||
|
@ -31,7 +31,7 @@ class ForwardReference(object):
|
||||
class SymbolTable(object):
|
||||
def __init__(self):
|
||||
self.symbols = {} # symbol name -> SymEntry
|
||||
self.statics = {} # routine name -> (symbol name -> SymEntry)
|
||||
self.locals = {} # routine name -> (symbol name -> SymEntry)
|
||||
self.typedefs = {} # type name -> Type AST
|
||||
self.consts = {} # const name -> ConstantRef
|
||||
|
||||
@ -41,25 +41,25 @@ class SymbolTable(object):
|
||||
self.symbols[name] = SymEntry(None, TYPE_BIT)
|
||||
|
||||
def __str__(self):
|
||||
return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
|
||||
return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts)
|
||||
|
||||
def has_static(self, routine_name, name):
|
||||
return name in self.statics.get(routine_name, {})
|
||||
def has_local(self, routine_name, name):
|
||||
return name in self.locals.get(routine_name, {})
|
||||
|
||||
def fetch_global_type(self, name):
|
||||
return self.symbols[name].type_
|
||||
|
||||
def fetch_static_type(self, routine_name, name):
|
||||
return self.statics[routine_name][name].type_
|
||||
def fetch_local_type(self, routine_name, name):
|
||||
return self.locals[routine_name][name].type_
|
||||
|
||||
def fetch_global_ref(self, name):
|
||||
if name in self.symbols:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
def fetch_static_ref(self, routine_name, name):
|
||||
routine_statics = self.statics.get(routine_name, {})
|
||||
if name in routine_statics:
|
||||
def fetch_local_ref(self, routine_name, name):
|
||||
routine_locals = self.locals.get(routine_name, {})
|
||||
if name in routine_locals:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
@ -76,7 +76,7 @@ class Parser(object):
|
||||
def lookup(self, name, allow_forward=False, routine_name=None):
|
||||
model = self.symtab.fetch_global_ref(name)
|
||||
if model is None and routine_name:
|
||||
model = self.symtab.fetch_static_ref(routine_name, name)
|
||||
model = self.symtab.fetch_local_ref(routine_name, name)
|
||||
if model is None and allow_forward:
|
||||
return ForwardReference(name)
|
||||
if model is None:
|
||||
@ -88,10 +88,12 @@ class Parser(object):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symtab.symbols[name] = SymEntry(ast_node, type_)
|
||||
|
||||
def declare_static(self, routine_name, name, ast_node, type_):
|
||||
def declare_local(self, routine_name, name, ast_node, type_):
|
||||
if self.symtab.fetch_local_ref(routine_name, name):
|
||||
self.syntax_error('Symbol "%s" already declared locally' % name)
|
||||
if self.symtab.fetch_global_ref(name):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
self.syntax_error('Symbol "%s" already declared globally' % name)
|
||||
self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
|
||||
# ---- symbol resolution
|
||||
|
||||
@ -306,17 +308,17 @@ class Parser(object):
|
||||
type_ = self.defn_type()
|
||||
if not isinstance(type_, RoutineType):
|
||||
self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
|
||||
statics = []
|
||||
locals_ = []
|
||||
if self.scanner.consume('@'):
|
||||
self.scanner.check_type('integer literal')
|
||||
block = None
|
||||
addr = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
else:
|
||||
statics = self.statics()
|
||||
locals_ = self.locals()
|
||||
block = self.block()
|
||||
addr = None
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics)
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_)
|
||||
|
||||
def labels(self):
|
||||
accum = []
|
||||
@ -370,13 +372,19 @@ class Parser(object):
|
||||
loc = IndexedRef(loc, offset, index)
|
||||
return loc
|
||||
|
||||
def statics(self):
|
||||
def locals(self):
|
||||
defns = []
|
||||
while self.scanner.consume('static'):
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is None:
|
||||
self.syntax_error("Static definition {} must have initial value".format(defn))
|
||||
self.declare_static(self.current_routine_name, defn.name, defn, type_)
|
||||
self.declare_local(self.current_routine_name, defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
while self.scanner.consume('local'):
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is not None:
|
||||
self.syntax_error("Local definition {} may not have initial value".format(defn))
|
||||
self.declare_local(self.current_routine_name, defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
return defns
|
||||
|
||||
|
@ -4251,7 +4251,7 @@ The new style routine definitions support typedefs.
|
||||
| }
|
||||
= ok
|
||||
|
||||
### static ###
|
||||
### locals ###
|
||||
|
||||
When memory locations are defined static to a routine, they cannot be
|
||||
directly input, nor directly output; and since they are always initialized,
|
||||
@ -4276,3 +4276,30 @@ they cannot be trashed. Thus, they really don't participate in the analysis.
|
||||
| call foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
When memory locations are defined local to a routine, but not static,
|
||||
they cannot be directly input, nor directly output; but they are considered
|
||||
uninitialized from the time the routine is called to until a value is stored
|
||||
in them.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
= ok
|
||||
|
||||
| define main routine
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t
|
||||
| {
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? UnmeaningfulReadError: t
|
||||
|
@ -746,13 +746,14 @@ Only routines can be defined in the new style.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Memory locations can be defined static to a routine.
|
||||
Memory locations can be defined local to a routine.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t : 0
|
||||
| local word w
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
@ -762,13 +763,14 @@ Memory locations can be defined static to a routine.
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| static byte t : 0
|
||||
| local word w
|
||||
| {
|
||||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
Static memory locations must always be given an initial value.
|
||||
Local static memory locations must always be given an initial value.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
@ -782,7 +784,21 @@ Static memory locations must always be given an initial value.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Name of a static cannot shadow an existing global or static.
|
||||
Local dynamic memory locations may not be given an initial value.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t : 10
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Name of a local cannot shadow an existing global or local.
|
||||
|
||||
| byte t
|
||||
|
|
||||
@ -790,7 +806,7 @@ Name of a static cannot shadow an existing global or static.
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t
|
||||
| static byte t : 10
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
@ -802,11 +818,89 @@ Name of a static cannot shadow an existing global or static.
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t
|
||||
| static byte t
|
||||
| static byte t : 10
|
||||
| static byte t : 20
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| byte t
|
||||
|
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local word w
|
||||
| local word w
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Since the names of locals are lexically local to a routine, they cannot
|
||||
appear in the inputs, outputs, trashes list of the routine.
|
||||
|
||||
| define main routine
|
||||
| inputs t
|
||||
| static byte t : 0
|
||||
| {
|
||||
| inc t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| outputs t
|
||||
| static byte t : 0
|
||||
| {
|
||||
| inc t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| trashes t
|
||||
| static byte t : 0
|
||||
| {
|
||||
| inc t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| inputs t
|
||||
| local byte t
|
||||
| {
|
||||
| inc t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| outputs t
|
||||
| local byte t
|
||||
| {
|
||||
| inc t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| define main routine
|
||||
| trashes t
|
||||
| local byte t
|
||||
| {
|
||||
| inc t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
Loading…
x
Reference in New Issue
Block a user