This commit is contained in:
Irmen de Jong 2018-02-02 22:42:09 +01:00
parent 43a59817bf
commit 76755cf57d
11 changed files with 71 additions and 32 deletions

View File

@ -21,9 +21,10 @@ class CompileError(Exception):
class PlyParser:
def __init__(self, imported_module: bool=False) -> None:
def __init__(self, *, enable_floats: bool=False, imported_module: bool=False) -> None:
self.parse_errors = 0
self.imported_module = imported_module
self.floats_enabled = enable_floats
def parse_file(self, filename: str) -> Module:
print("parsing:", filename)
@ -31,6 +32,7 @@ class PlyParser:
try:
module = parse_file(filename, self.lexer_error)
self.check_directives(module)
self.apply_directive_options(module)
module.scope.define_builtin_functions()
self.process_imports(module)
self.check_and_merge_zeropages(module)
@ -38,11 +40,11 @@ class PlyParser:
if not self.imported_module:
# the following shall only be done on the main module after all imports have been done:
self.check_all_symbolnames(module)
self.apply_directive_options(module)
self.determine_subroutine_usage(module)
self.all_parents_connected(module)
self.semantic_check(module)
self.coerce_values(module)
self.check_floats_enabled(module)
self.allocate_zeropage_vars(module)
except ParseError as x:
self.handle_parse_error(x)
@ -70,6 +72,18 @@ class PlyParser:
raise ParseError("last statement in a block/subroutine must be a return or goto, "
"(or %noreturn directive to silence this error)", last_stmt.sourceref)
def check_floats_enabled(self, module: Module) -> None:
if self.floats_enabled:
return
for node in module.all_nodes():
if isinstance(node, LiteralValue):
if type(node.value) is float:
raise ParseError("floating point numbers not enabled via option", node.sourceref)
elif isinstance(node, VarDef):
if node.datatype == DataType.FLOAT:
raise ParseError("floating point numbers not enabled via option", node.sourceref)
def coerce_values(self, module: Module) -> None:
for node in module.all_nodes():
try:
@ -206,10 +220,10 @@ class PlyParser:
# allocate zeropage variables to the available free zp addresses
if not module.scope.nodes:
return
zpnode = module.scope.nodes[0]
if zpnode.name != "ZP":
zpnode = module.zeropage()
if zpnode is None:
return
zeropage = Zeropage(module.zp_options)
zeropage = Zeropage(module.zp_options, self.floats_enabled)
for vardef in zpnode.all_nodes(VarDef):
if vardef.datatype.isstring():
raise ParseError("cannot put strings in the zeropage", vardef.sourceref)
@ -274,6 +288,8 @@ class PlyParser:
elif directive.args[0] == "basic":
node.format = ProgramFormat.BASIC
node.address = 0x0801
elif directive.args[0] == "enable_floats":
self.floats_enabled = module.floats_enabled = True
else:
raise ParseError("invalid directive args", directive.sourceref)
elif directive.name == "address":
@ -451,6 +467,8 @@ class PlyParser:
self.parse_errors += import_parse_errors
else:
raise FileNotFoundError("missing il65lib")
if sum(m.floats_enabled for m in imported):
self.floats_enabled = module.floats_enabled = True
# append the imported module's contents (blocks) at the end of the current module
for block in (node for imported_module in imported
for node in imported_module.scope.nodes
@ -501,7 +519,8 @@ class Zeropage:
SCRATCH_W1 = 0xfb # $fb/$fc
SCRATCH_W2 = 0xfd # $fd/$fe
def __init__(self, options: ZpOptions) -> None:
def __init__(self, options: ZpOptions, enable_floats: bool) -> None:
self.floats_enabled = enable_floats
self.free = [] # type: List[int]
self.allocations = {} # type: Dict[int, Tuple[str, DataType]]
if options in (ZpOptions.CLOBBER_RESTORE, ZpOptions.CLOBBER):
@ -539,6 +558,8 @@ class Zeropage:
elif vardef.datatype == DataType.WORD:
size = 2
elif vardef.datatype == DataType.FLOAT:
if not self.floats_enabled:
raise TypeError("floating point numbers not enabled via option")
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = 5
elif vardef.datatype == DataType.BYTEARRAY:
@ -564,7 +585,7 @@ class Zeropage:
for candidate in range(min(self.free), max(self.free)+1):
if sequential_free(candidate):
return make_allocation(candidate)
raise CompileError("ERROR: no more free space in ZP to allocate {:d} sequential bytes".format(size))
raise CompileError("ERROR: no free space in ZP to allocate {:d} sequential bytes".format(size))
def available(self) -> int:
return len(self.free)

View File

@ -28,8 +28,9 @@ class Output:
class AssemblyGenerator:
def __init__(self, module: Module) -> None:
def __init__(self, module: Module, enable_floats: bool) -> None:
self.module = module
self.floats_enabled = enable_floats
self.cur_block = None
self.output = None # type: Output
@ -175,6 +176,8 @@ class AssemblyGenerator:
out("")
out("; -- end block subroutines")
if block.scope.float_const_values:
if not self.floats_enabled:
raise CodeError("floating point numbers not enabled via option")
# generate additional float constants that are used in floating point expressions
out("\n; -- float constants")
for name, value in block.scope.float_const_values.items():
@ -210,7 +213,7 @@ class AssemblyGenerator:
out(stmt.assembly)
out("\v; end inline asm, " + stmt.lineref + "\n")
elif isinstance(stmt, IncrDecr):
generate_incrdecr(out, stmt, scope)
generate_incrdecr(out, stmt, scope, self.floats_enabled)
elif isinstance(stmt, Goto):
generate_goto(out, stmt)
elif isinstance(stmt, SubCall):

View File

@ -13,7 +13,7 @@ from ..datatypes import DataType, REGISTER_BYTES
from . import CodeError, preserving_registers
def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None:
def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope, floats_enabled: bool) -> None:
assert isinstance(stmt.howmuch, (int, float)) and stmt.howmuch >= 0
assert stmt.operator in ("++", "--")
if stmt.howmuch == 0:
@ -184,6 +184,8 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None:
out("\vdec {:s}+1".format(what_str))
out("+")
elif target.datatype == DataType.FLOAT:
if not floats_enabled:
raise CodeError("floating point numbers not enabled via option")
if stmt.howmuch == 1.0:
# special case for +/-1
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
@ -209,5 +211,14 @@ def generate_incrdecr(out: Callable, stmt: IncrDecr, scope: Scope) -> None:
else:
raise CodeError("cannot in/decrement memory of type " + str(target.datatype), stmt.howmuch)
elif isinstance(target, Dereference):
if target.datatype == DataType.BYTE:
pass # @todo
elif target.datatype == DataType.WORD:
pass # @todo
elif target.datatype == DataType.FLOAT:
pass # @todo
else:
raise CodeError("cannot inc/decrement dereferenced " + str(target.datatype), stmt)
else:
raise CodeError("cannot in/decrement", target) # @todo support more such as [dereference]++
raise CodeError("cannot inc/decrement", target) # @todo support more such as [dereference]++

View File

@ -5,6 +5,8 @@
; ;
; indent format: TABS, size=8
%output enable_floats
~ c64 {
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP

View File

@ -64,6 +64,7 @@ def main() -> None:
description = "Compiler for IL65 language, code name 'Sick'"
ap = argparse.ArgumentParser(description=description)
ap.add_argument("-o", "--output", help="output directory")
ap.add_argument("-f", "--enablefloat", action="store_true", help="enable C64 (mflpt5) floating point operations")
ap.add_argument("-no", "--nooptimize", action="store_true", help="do not optimize the parse tree")
ap.add_argument("-sv", "--startvice", action="store_true", help="autostart vice x64 emulator after compilation")
ap.add_argument("sourcefile", help="the source .ill/.il65 file to compile")
@ -79,7 +80,7 @@ def main() -> None:
start = time.perf_counter()
print("\nParsing program source code.")
parser = PlyParser()
parser = PlyParser(enable_floats=args.enablefloat)
parsed_module = parser.parse_file(args.sourcefile)
if parsed_module:
if args.nooptimize:
@ -88,7 +89,7 @@ def main() -> None:
print("\nOptimizing code.")
optimize(parsed_module)
print("\nGenerating assembly code.")
cg = AssemblyGenerator(parsed_module)
cg = AssemblyGenerator(parsed_module, args.enablefloat)
cg.generate(assembly_filename)
assembler = Assembler64Tass(parsed_module.format)
assembler.assemble(assembly_filename, program_filename)

View File

@ -339,8 +339,8 @@ class Optimizer:
if not usages and sub.parent.name + '.' + sub.name not in never_remove:
sub.parent.remove_node(sub)
num_discarded += 1
if num_discarded:
print("discarded {:d} unused subroutines".format(num_discarded))
# if num_discarded:
# print("discarded {:d} unused subroutines".format(num_discarded))
@no_type_check
def optimize_goto_compare_with_zero(self) -> None:

View File

@ -301,6 +301,7 @@ class Module(AstNode):
format = attr.ib(type=ProgramFormat, init=False, default=ProgramFormat.PRG) # can be set via directive
address = attr.ib(type=int, init=False, default=0xc000, validator=validate_address) # can be set via directive
zp_options = attr.ib(type=ZpOptions, init=False, default=ZpOptions.NOCLOBBER) # can be set via directive
floats_enabled = attr.ib(type=bool, init=False, default=False) # can be set via directive
@property
def scope(self) -> Scope:

View File

@ -1,5 +0,0 @@
from il65.compile import PlyParser
def test_compiler():
pass # @todo

View File

@ -1,6 +0,0 @@
from il65.optimize import Optimizer
def test_optimizer():
pass # @todo

View File

@ -7,7 +7,7 @@ from il65.datatypes import DataType
def test_zp_names():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.NOCLOBBER)
zp = Zeropage(ZpOptions.NOCLOBBER, False)
with pytest.raises(AssertionError):
zp.allocate(VarDef(name="", vartype="memory", datatype=DataType.BYTE, sourceref=sref))
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
@ -20,7 +20,7 @@ def test_zp_names():
def test_zp_noclobber_allocation():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.NOCLOBBER)
zp = Zeropage(ZpOptions.NOCLOBBER, True)
assert zp.available() == 9
with pytest.raises(CompileError):
# in regular zp there aren't 5 sequential bytes free
@ -35,9 +35,18 @@ def test_zp_noclobber_allocation():
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
def test_zp_float_enable():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.CLOBBER, False)
with pytest.raises(TypeError):
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
zp = Zeropage(ZpOptions.CLOBBER, True)
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
def test_zp_clobber_allocation():
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.CLOBBER)
zp = Zeropage(ZpOptions.CLOBBER, True)
assert zp.available() == 239
loc = zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
assert loc > 3 and loc not in zp.free
@ -63,7 +72,7 @@ def test_zp_clobber_allocation():
def test_zp_efficient_allocation():
# free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
sref = SourceRef("test", 1, 1)
zp = Zeropage(ZpOptions.NOCLOBBER)
zp = Zeropage(ZpOptions.NOCLOBBER, False)
assert zp.available() == 9
assert 0x2a == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
assert 0x52 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))

View File

@ -2,7 +2,7 @@
~ main {
; var .float flt
;var .float flt
; var bytevar = 22 + 23 ; @todo constant-fold before semantic check
; var .float initfloat1 = -1.234e-14 ; @todo constant-fold before semantic check / fix float parse?
; var .float initfloat2 = -555.666 ; @todo constant-fold before semantic check / fix float parse?
@ -20,7 +20,9 @@ start:
[border] ++; @todo suport incr/decr on deref constant
[$d020] ++ ; @todo suport incr/decr on deref memory
[XY] ++
[X] ++
return 44
return 44.123
}