more semantic checks and codegen

This commit is contained in:
Irmen de Jong 2018-01-23 21:20:01 +01:00
parent fbf52d773f
commit 6573368a69
9 changed files with 99 additions and 72 deletions

View File

@ -14,7 +14,7 @@ import attr
from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \
SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\
SymbolName, Dereference, AddressOf, IncrDecr, AstNode, datatype_of, coerce_constant_value, \
check_symbol_definition, UndefinedSymbolError, process_expression, Label
check_symbol_definition, UndefinedSymbolError, process_expression, AugAssignment
from .plylex import SourceRef, print_bold
from .datatypes import DataType, VarType
@ -68,7 +68,6 @@ class PlyParser:
continue
if "jmp " in line or "jmp\t" in line or "rts" in line or "rti" in line:
return
print(last_stmt)
raise ParseError("last statement in a block/subroutine must be a return or goto, "
"(or %noreturn directive to silence this error)", last_stmt.sourceref)
@ -79,7 +78,8 @@ class PlyParser:
for node in module.all_nodes():
if isinstance(node, Scope):
if node.nodes and isinstance(node.parent, (Block, Subroutine)):
self._check_last_statement_is_return(node.nodes[-1])
if isinstance(node.parent, Block) and node.parent.name != "ZP":
self._check_last_statement_is_return(node.nodes[-1])
elif isinstance(node, SubCall):
if isinstance(node.target, SymbolName):
subdef = node.my_scope().lookup(node.target.name)
@ -94,6 +94,23 @@ class PlyParser:
symdef = node.my_scope().lookup(node.target.name)
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
raise ParseError("cannot modify a constant", node.sourceref)
elif isinstance(node, Assignment):
scope = node.my_scope()
for target in node.left.nodes:
if isinstance(target, SymbolName):
symdef = scope.lookup(target.name)
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
raise ParseError("cannot modify a constant", target.sourceref)
elif isinstance(node, AugAssignment):
# the assignment target must be a variable
if isinstance(node.left, SymbolName):
symdef = node.my_scope().lookup(node.left.name)
if isinstance(symdef, VarDef):
if symdef.vartype == VarType.CONST:
raise ParseError("cannot modify a constant", node.sourceref)
elif symdef.datatype not in {DataType.BYTE, DataType.WORD, DataType.FLOAT}:
raise ParseError("cannot modify that datatype ({:s}) in this way"
.format(symdef.datatype.name.lower()), node.sourceref)
previous_stmt = node
def check_subroutine_arguments(self, call: SubCall, subdef: Subroutine) -> None:
@ -171,12 +188,18 @@ class PlyParser:
self.handle_internal_error(x, "process_expressions of node {}".format(node))
elif isinstance(node, IncrDecr) and node.howmuch not in (0, 1):
_, node.howmuch = coerce_constant_value(datatype_of(node.target, node.my_scope()), node.howmuch, node.sourceref)
elif isinstance(node, VarDef):
dtype = DataType.WORD if node.vartype == VarType.MEMORY else node.datatype
try:
_, node.value = coerce_constant_value(dtype, node.value, node.sourceref)
except OverflowError as x:
raise ParseError(str(x), node.sourceref) from None
elif isinstance(node, Assignment):
lvalue_types = set(datatype_of(lv, node.my_scope()) for lv in node.left.nodes)
if len(lvalue_types) == 1:
_, newright = coerce_constant_value(lvalue_types.pop(), node.right, node.sourceref)
if isinstance(newright, (Register, LiteralValue, Expression, Dereference, SymbolName, SubCall)):
node.right = newright
node.right = newright # type: ignore
else:
raise TypeError("invalid coerced constant type", newright)
else:
@ -287,10 +310,10 @@ class PlyParser:
self._get_subroutine_usages_from_return(module.subroutine_usage, node, node.my_scope())
elif isinstance(node, Assignment):
self._get_subroutine_usages_from_assignment(module.subroutine_usage, node, node.my_scope())
print("----------SUBROUTINES IN USE-------------") # XXX
import pprint
pprint.pprint(module.subroutine_usage) # XXX
print("----------/SUBROUTINES IN USE-------------") # XXX
# print("----------SUBROUTINES IN USE-------------")
# import pprint
# pprint.pprint(module.subroutine_usage)
# print("----------/SUBROUTINES IN USE-------------")
def _get_subroutine_usages_from_subcall(self, usages: Dict[Tuple[str, str], Set[str]],
subcall: SubCall, parent_scope: Scope) -> None:

View File

@ -7,7 +7,7 @@ Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
import os
import datetime
from typing import TextIO, Callable
from typing import TextIO, Callable, no_type_check
from ..plylex import print_bold
from ..plyparse import Module, Scope, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr, AssignmentTargets
@ -28,9 +28,6 @@ class Output:
class AssemblyGenerator:
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
def __init__(self, module: Module) -> None:
self.module = module
self.cur_block = None
@ -53,12 +50,12 @@ class AssemblyGenerator:
def sanitycheck(self) -> None:
for label in self.module.all_nodes(Label):
if label.name == "start" and label.my_scope().name == "main":
if label.name == "start" and label.my_scope().name == "main": # type: ignore
break
else:
print_bold("ERROR: program entry point is missing ('start' label in 'main' block)\n")
raise SystemExit(1)
all_blocknames = [b.name for b in self.module.all_nodes(Block)]
all_blocknames = [b.name for b in self.module.all_nodes(Block)] # type: ignore
unique_blocknames = set(all_blocknames)
if len(all_blocknames) != len(unique_blocknames):
for name in unique_blocknames:
@ -119,6 +116,7 @@ class AssemblyGenerator:
out("\vjmp {:s}.start".format(self.module.main().label))
out("")
@no_type_check
def blocks(self, out: Callable) -> None:
zpblock = self.module.zeropage()
if zpblock:
@ -178,25 +176,26 @@ class AssemblyGenerator:
out("; -- end block subroutines")
out("\n\v.pend\n")
@no_type_check
def generate_statement(self, out: Callable, stmt: AstNode, scope: Scope) -> None:
if isinstance(stmt, Label):
out("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
elif isinstance(stmt, Return):
if stmt.value_A:
reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
assignment = Assignment(sourceref=stmt.sourceref) # type: ignore
reg = Register(name="A", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_A)
generate_assignment(out, assignment)
if stmt.value_X:
reg = Register(name="X", sourceref=stmt.sourceref) # type: ignore
assignment = Assignment(sourceref=stmt.sourceref) # type: ignore
reg = Register(name="X", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_X)
generate_assignment(out, assignment)
if stmt.value_Y:
reg = Register(name="Y", sourceref=stmt.sourceref) # type: ignore
assignment = Assignment(sourceref=stmt.sourceref) # type: ignore
reg = Register(name="Y", sourceref=stmt.sourceref)
assignment = Assignment(sourceref=stmt.sourceref)
assignment.nodes.append(AssignmentTargets(nodes=[reg], sourceref=stmt.sourceref))
assignment.nodes.append(stmt.value_Y)
generate_assignment(out, assignment)
@ -218,7 +217,8 @@ class AssemblyGenerator:
elif isinstance(stmt, Directive):
if stmt.name == "breakpoint":
# put a marker in the source so that we can generate a list of breakpoints later
out("\vnop\t\t; {:s} {:s}".format(self.BREAKPOINT_COMMENT_SIGNATURE, stmt.lineref))
# this label is later extracted from the label dump file to turn it into a breakpoint instruction
out("_il65_breakpoint_{:d}".format(id(stmt)))
# other directives are ignored here
else:
raise NotImplementedError("statement", stmt)

View File

@ -16,11 +16,11 @@ from . import CodeError, preserving_registers
def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None:
assert isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0
assert stmt.operator in ("++", "--")
target = stmt.target # one of Register/SymbolName/Dereference
target = stmt.target # one of Register/SymbolName/Dereference, or a VarDef
if isinstance(target, SymbolName):
symdef = scope.lookup(target.name)
if isinstance(symdef, VarDef):
target = symdef
target = symdef # type: ignore
else:
raise CodeError("cannot incr/decr this", symdef)
if stmt.howmuch > 255:

View File

@ -57,8 +57,8 @@ def generate_block_init(out: Callable, block: Block) -> None:
prev_value_a, prev_value_x = None, None
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
for vardef in block.all_nodes(VarDef):
if vardef.vartype == VarType.VAR:
vars_by_datatype[vardef.datatype].append(vardef)
if vardef.vartype == VarType.VAR: # type: ignore
vars_by_datatype[vardef.datatype].append(vardef) # type: ignore
for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value):
assert isinstance(bytevar.value, LiteralValue) and type(bytevar.value.value) is int
if bytevar.value.value != prev_value_a:
@ -99,7 +99,8 @@ def generate_block_init(out: Callable, block: Block) -> None:
out("\vbpl -")
out("\vrts\n")
for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
out("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue))
assert isinstance(fpvalue, LiteralValue)
out("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue.value))
all_string_vars = []
for svtype in STRING_DATATYPES:
all_string_vars.extend(vars_by_datatype[svtype])
@ -115,12 +116,13 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
# their actual starting values are set by the block init code.
vars_by_vartype = defaultdict(list) # type: Dict[VarType, List[VarDef]]
for vardef in block.all_nodes(VarDef):
vars_by_vartype[vardef.vartype].append(vardef)
vars_by_vartype[vardef.vartype].append(vardef) # type: ignore
out("; constants")
for vardef in vars_by_vartype.get(VarType.CONST, []):
if vardef.datatype == DataType.FLOAT:
out("\v{:s} = {}".format(vardef.name, _numeric_value_str(vardef.value)))
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
assert isinstance(vardef.value.value, int)
out("\v{:s} = {:s}".format(vardef.name, _numeric_value_str(vardef.value, True)))
elif vardef.datatype.isstring():
# a const string is just a string variable in the generated assembly
@ -130,6 +132,7 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
out("; memory mapped variables")
for vardef in vars_by_vartype.get(VarType.MEMORY, []):
# create a definition for variables at a specific place in memory (memory-mapped)
assert isinstance(vardef.value.value, int)
if vardef.datatype.isnumeric():
assert vardef.size == [1]
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), vardef.datatype.name.lower()))
@ -199,19 +202,19 @@ def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> No
def _generate_string_var(out: Callable, vardef: VarDef) -> None:
if vardef.datatype == DataType.STRING:
# 0-terminated string
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value))))
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value.value))))
elif vardef.datatype == DataType.STRING_P:
# pascal string
out("{:s}\n\v.ptext {:s}".format(vardef.name, _format_string(str(vardef.value))))
out("{:s}\n\v.ptext {:s}".format(vardef.name, _format_string(str(vardef.value.value))))
elif vardef.datatype == DataType.STRING_S:
# 0-terminated string in screencode encoding
out(".enc 'screen'")
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value), True)))
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value.value), True)))
out(".enc 'none'")
elif vardef.datatype == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
out(".enc 'screen'")
out("{:s}n\v.ptext {:s}".format(vardef.name, _format_string(str(vardef.value), True)))
out("{:s}n\v.ptext {:s}".format(vardef.name, _format_string(str(vardef.value.value), True)))
out(".enc 'none'")

View File

@ -24,7 +24,7 @@ class Assembler64Tass:
def assemble(self, inputfilename: str, outputfilename: str) -> None:
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
"--dump-labels", "--vice-labels", "-l", outputfilename+".vice-mon-list",
"-L", outputfilename+".final-asm", "--no-monitor", "--output", outputfilename, inputfilename]
"--no-monitor", "--output", outputfilename, inputfilename]
if self.format in (ProgramFormat.PRG, ProgramFormat.BASIC):
args.append("--cbm-prg")
elif self.format == ProgramFormat.RAW:
@ -45,19 +45,19 @@ class Assembler64Tass:
def generate_breakpoint_list(self, program_filename: str) -> str:
breakpoints = []
with open(program_filename + ".final-asm", "rU") as f:
vice_mon_file = program_filename + ".vice-mon-list"
with open(vice_mon_file, "rU") as f:
for line in f:
match = re.fullmatch(AssemblyGenerator.BREAKPOINT_COMMENT_DETECTOR, line, re.DOTALL)
match = re.fullmatch(r"al (?P<address>\w+) \S+_il65_breakpoint_\d+.?", line, re.DOTALL)
if match:
breakpoints.append("$" + match.group("address"))
cmdfile = program_filename + ".vice-mon-list"
with open(cmdfile, "at") as f:
with open(vice_mon_file, "at") as f:
print("; vice monitor breakpoint list now follows", file=f)
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
print("del", file=f)
for b in breakpoints:
print("break", b, file=f)
return cmdfile
return vice_mon_file
def main() -> None:

View File

@ -1,6 +1,7 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
This is the optimizer that applies various optimizations to the parse tree.
This is the optimizer that applies various optimizations to the parse tree,
eliminates statements that have no effect, optimizes calculations etc.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""

View File

@ -385,11 +385,14 @@ class Subroutine(AstNode):
raise ValueError("subroutine must have either a scope or an address, not both")
@attr.s(cmp=True, slots=True)
@attr.s(cmp=True, slots=True, repr=False)
class LiteralValue(AstNode):
# no subnodes.
value = attr.ib()
def __repr__(self) -> str:
return "<LiteralValue value={!r} at {}>".format(self.value, self.sourceref)
@attr.s(cmp=False)
class AddressOf(AstNode):

View File

@ -29,8 +29,6 @@
~ ZP {
; will be merged with other ZP blocks!
var zpvar1b = $88
; @todo should not need %noreturn because is zeropage
}
@ -133,7 +131,7 @@
; (constant) expressions
var expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
var .word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
var .byte expr_fault2 = 1 + (8/3)+sin(33) + max(1,2,3)

View File

@ -35,39 +35,38 @@
start:
%breakpoint abc,def
;c3f=444 ; @todo constant error
;c3f=c3f ; @todo constant error
;c3f=c2t ; @todo constant error
;c3f+=2.23424 ; @todo constant error
v3t++
v3t+=1
v3t+=1 ; @todo? (optimize) join with previous
v3t+=0
;v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
;v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
;v3t+=2.23425 ; @todo store as constant float with generated name, replace value node
; v4t++ ; @todo parser error
; v4t+=20 ; @todo parser error
; v4t+=2000 ; @todo parser error
; v5t++ ; @todo parser error
; v5t+=20 ; @todo parser error
; v5t+=2000 ; @todo parser error
A++
X--
A+=1
X-=2
;[AX]++
;[AX .byte]++
;[AX .word]++
;[AX .float]++
;[$ccc0]++
;[$ccc0 .byte]++
;[$ccc0 .word]++
;[$ccc0 .float]++
A+=2
A+=3
XY+=6
XY+=222
XY+=666
v3t+=1
v3t+=1 ; @todo? (optimize) join with previous
v3t=2.23424 ; @todo store as constant float with generated name, replace value node
v3t=2.23411 ; @todo store as constant float with generated name, replace value node
v3t=1.23411 + 1; @todo store as constant float with generated name, replace value node
; v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
; v3t+=2.23424 ; @todo store as constant float with generated name, replace value node
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
; v3t+=2.23411 ; @todo store as constant float with generated name, replace value node
v3t=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
v3t*=2.23424 * v3t ; @todo store as constant float with generated name, replace value node
; A++
; X--
; A+=1
; X-=2
; [AX]++
; [AX .byte]++
; [AX .word]++
; [AX .float]++
; [$ccc0]++
; [$ccc0 .byte]++
; [$ccc0 .word]++
; [$ccc0 .float]++
; A+=2
; A+=3
; XY+=6
; XY+=222
; XY+=666
return 44
}