mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
more semantic checks and codegen
This commit is contained in:
parent
fbf52d773f
commit
6573368a69
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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'")
|
||||
|
||||
|
||||
|
12
il65/main.py
12
il65/main.py
@ -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:
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
59
todo.ill
59
todo.ill
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user