symbol table fixes

This commit is contained in:
Irmen de Jong 2018-01-12 23:25:00 +01:00
parent 614f90fc35
commit e6804b2bf9
6 changed files with 29 additions and 21 deletions

View File

@ -31,12 +31,12 @@ class PlyParser:
self.check_directives(module) self.check_directives(module)
self.process_imports(module) self.process_imports(module)
self.create_multiassigns(module) self.create_multiassigns(module)
self.check_and_merge_zeropages(module)
self.process_all_expressions(module) self.process_all_expressions(module)
if not self.parsing_import: if not self.parsing_import:
# these shall only be done on the main module after all imports have been done: # these shall only be done on the main module after all imports have been done:
self.apply_directive_options(module) self.apply_directive_options(module)
self.determine_subroutine_usage(module) self.determine_subroutine_usage(module)
self.check_and_merge_zeropages(module)
except ParseError as x: except ParseError as x:
self.handle_parse_error(x) self.handle_parse_error(x)
if self.parse_errors: if self.parse_errors:
@ -60,6 +60,7 @@ class PlyParser:
zeropage.scope.add_node(node, 0) zeropage.scope.add_node(node, 0)
elif isinstance(node, VarDef): elif isinstance(node, VarDef):
zeropage.scope.add_node(node) zeropage.scope.add_node(node)
print("ADDED ZP VAR", node) # XXX
else: else:
raise ParseError("only variables and directives allowed in zeropage block", node.sourceref) raise ParseError("only variables and directives allowed in zeropage block", node.sourceref)
else: else:
@ -72,7 +73,11 @@ class PlyParser:
@no_type_check @no_type_check
def process_all_expressions(self, module: Module) -> None: def process_all_expressions(self, module: Module) -> None:
# process/simplify all expressions (constant folding etc) # process/simplify all expressions (constant folding etc)
encountered_blocks = set()
for block, parent in module.all_scopes(): for block, parent in module.all_scopes():
if block.name in encountered_blocks:
raise ValueError("block names not unique:", block.name)
encountered_blocks.add(block.name)
for node in block.nodes: for node in block.nodes:
try: try:
node.process_expressions(block.scope) node.process_expressions(block.scope)
@ -266,7 +271,7 @@ class PlyParser:
if len(splits) == 2: if len(splits) == 2:
for match in re.finditer(r"(?P<symbol>[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]): for match in re.finditer(r"(?P<symbol>[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]):
name = match.group("symbol") name = match.group("symbol")
if name[0] == '$': if name[0] == '$' or "." not in name:
continue continue
try: try:
symbol = parent_scope[name] symbol = parent_scope[name]
@ -274,7 +279,8 @@ class PlyParser:
pass pass
else: else:
if isinstance(symbol, Subroutine): if isinstance(symbol, Subroutine):
usages[(parent_scope.name, symbol.name)].add(str(asmnode.sourceref)) namespace, name = name.rsplit(".", maxsplit=2)
usages[(namespace, name)].add(str(asmnode.sourceref))
def check_directives(self, module: Module) -> None: def check_directives(self, module: Module) -> None:
for node, parent in module.all_scopes(): for node, parent in module.all_scopes():
@ -329,7 +335,7 @@ class PlyParser:
# append the imported module's contents (blocks) at the end of the current module # append the imported module's contents (blocks) at the end of the current module
for imported_module in imported: for imported_module in imported:
for block in imported_module.scope.filter_nodes(Block): for block in imported_module.scope.filter_nodes(Block):
module.scope.nodes.append(block) module.scope.add_node(block)
def import_file(self, filename: str) -> Tuple[Module, int]: def import_file(self, filename: str) -> Tuple[Module, int]:
sub_parser = PlyParser(parsing_import=True) sub_parser = PlyParser(parsing_import=True)

View File

@ -127,6 +127,7 @@ class AssemblyGenerator:
self.p("{:s}\t.proc\n".format(zpblock.label)) self.p("{:s}\t.proc\n".format(zpblock.label))
self.generate_block_init(zpblock) self.generate_block_init(zpblock)
self.generate_block_vars(zpblock) self.generate_block_vars(zpblock)
# there's no code in the zero page block.
self.p("\v.pend\n") self.p("\v.pend\n")
for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0): for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0):
if block.name == "ZP": if block.name == "ZP":
@ -149,8 +150,8 @@ class AssemblyGenerator:
self.p("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address))) self.p("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address)))
self.p("; end external subroutines\n") self.p("; end external subroutines\n")
for stmt in block.scope.nodes: for stmt in block.scope.nodes:
if isinstance(stmt, (VarDef, Directive)): if isinstance(stmt, (VarDef, Directive, Subroutine)):
continue # should have been handled already continue # should have been handled already or will be later
self.generate_statement(stmt) self.generate_statement(stmt)
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start": if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start":
# make sure the main.start routine clears the decimal and carry flags as first steps # make sure the main.start routine clears the decimal and carry flags as first steps
@ -215,12 +216,13 @@ class AssemblyGenerator:
# generate the block initializer # generate the block initializer
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer) # @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer)
self.p("_il65_init_block\v; (re)set vars to initial values") self.p("_il65_init_block\v; (re)set vars to initial values")
# @todo optimize init order (sort on value first to avoid needless register loads, etc)
self.p("\vlda #0\n\vldx #0") self.p("\vlda #0\n\vldx #0")
float_inits = {} float_inits = {}
string_inits = [] string_inits = []
prev_value = 0 prev_value = 0
for variable in [vd for vd in block.scope.filter_nodes(VarDef) if vd.vartype == VarType.VAR]: vardefs = [vd for vd in block.scope.filter_nodes(VarDef) if vd.vartype == VarType.VAR]
# @todo optimize init order (sort on value first to avoid needless register loads, etc)
for variable in vardefs:
vname = variable.name vname = variable.name
vvalue = variable.value vvalue = variable.value
if variable.datatype == DataType.BYTE: if variable.datatype == DataType.BYTE:

View File

@ -53,6 +53,8 @@ start = "start"
@attr.s(cmp=False, slots=True, frozen=False) @attr.s(cmp=False, slots=True, frozen=False)
class AstNode: class AstNode:
sourceref = attr.ib(type=SourceRef) sourceref = attr.ib(type=SourceRef)
# when evaluating an expression, does it have to be a constant value?:
processed_expr_must_be_constant = attr.ib(type=bool, init=False, default=False)
@property @property
def lineref(self) -> str: def lineref(self) -> str:
@ -410,8 +412,8 @@ class VarDef(AstNode):
raise ParseError("constant value assignment is missing", raise ParseError("constant value assignment is missing",
attr.evolve(self.sourceref, column=self.sourceref.column+len(self.name))) attr.evolve(self.sourceref, column=self.sourceref.column+len(self.name)))
# if the value is an expression, mark it as a *constant* expression here # if the value is an expression, mark it as a *constant* expression here
if isinstance(self.value, Expression): if isinstance(self.value, AstNode):
self.value.processed_must_be_constant = True self.value.processed_expr_must_be_constant = True
elif self.value is None and self.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT): elif self.value is None and self.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
self.value = 0 self.value = 0
# note: value coercion is done later, when all expressions are evaluated # note: value coercion is done later, when all expressions are evaluated
@ -423,7 +425,7 @@ class VarDef(AstNode):
try: try:
_, self.value = coerce_value(self.datatype, self.value, self.sourceref) _, self.value = coerce_value(self.datatype, self.value, self.sourceref)
except (TypeError, OverflowError) as x: except (TypeError, OverflowError) as x:
raise ParseError(str(x), self.sourceref) from None raise ParseError("processed expression vor vardef is not a constant value: " + str(x), self.sourceref) from None
@attr.s(cmp=False, slots=True, repr=False) @attr.s(cmp=False, slots=True, repr=False)
@ -548,7 +550,6 @@ class Expression(AstNode):
operator = attr.ib(type=str) operator = attr.ib(type=str)
right = attr.ib() right = attr.ib()
unary = attr.ib(type=bool, default=False) unary = attr.ib(type=bool, default=False)
processed_must_be_constant = attr.ib(type=bool, init=False, default=False) # does the expression have to be a constant value?
def __attrs_post_init__(self): def __attrs_post_init__(self):
assert self.operator not in ("++", "--"), "incr/decr should not be an expression" assert self.operator not in ("++", "--"), "incr/decr should not be an expression"
@ -586,8 +587,8 @@ class Expression(AstNode):
def process_expression(value: Any, scope: Scope, sourceref: SourceRef) -> Any: def process_expression(value: Any, scope: Scope, sourceref: SourceRef) -> Any:
# process/simplify all expressions (constant folding etc) # process/simplify all expressions (constant folding etc)
if isinstance(value, Expression): if isinstance(value, AstNode):
must_be_constant = value.processed_must_be_constant must_be_constant = value.processed_expr_must_be_constant
else: else:
must_be_constant = False must_be_constant = False
if must_be_constant: if must_be_constant:
@ -631,7 +632,6 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
raise ParseError(str(x), expr.sourceref) from None raise ParseError(str(x), expr.sourceref) from None
elif isinstance(expr, SubCall): elif isinstance(expr, SubCall):
if isinstance(expr.target, CallTarget): if isinstance(expr.target, CallTarget):
print("CALLTARGET", expr.target.address_of, expr.target.target) # XXX
target = expr.target.target target = expr.target.target
if isinstance(target, SymbolName): # 'function(1,2,3)' if isinstance(target, SymbolName): # 'function(1,2,3)'
funcname = target.name funcname = target.name

View File

@ -125,10 +125,10 @@
; taking the address of things from the ZP will work even when it is a var ; taking the address of things from the ZP will work even when it is a var
; because zp-vars get assigned a specific address (from a pool). Also, it's a byte. ; because zp-vars get assigned a specific address (from a pool). Also, it's a byte.
var .word initword0a = &ZP.zpmem1 ; @todo should work, reference this symbols' generated address (@todo generate address for ZP) var .word initword0a = &ZP.zpmem1
var .word initword0 = &ZP.zpvar1 var .word initword0 = &ZP.zpvar1 ; @todo should work, reference this symbols' generated address (@todo generate address for ZP)
var initbytea0 = &ZP.zpmem1 var initbytea0 = &ZP.zpmem1
var .word initworda1 = &ZP.zpvar1 var .word initworda1 = &ZP.zpvar1 ; @todo should work, reference this symbols' generated address (@todo generate address for ZP)
; (constant) expressions ; (constant) expressions

View File

@ -1,6 +1,6 @@
; floating point tests ; floating point tests
%output prg, basic %output basic
%import c64lib %import c64lib

View File

@ -8,7 +8,7 @@
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp %zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~ main $0a00 ~ main
{ {
; this is the main block with the start routine. ; this is the main block with the start routine.
@ -178,7 +178,7 @@ somelabel1:
} }
~ fidget $1000 ~ fidget $0c00
{ {
subroutine: subroutine: