This commit is contained in:
Irmen de Jong 2018-01-12 00:55:47 +01:00
parent 534bf2f252
commit 614f90fc35
16 changed files with 146 additions and 54 deletions

View File

@ -13,7 +13,7 @@ from typing import Optional, Tuple, Set, Dict, Any, no_type_check
import attr import attr
from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \
SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\ SubCall, Goto, Return, Assignment, InlineAssembly, Register, Expression, ProgramFormat, ZpOptions,\
SymbolName, process_constant_expression, process_dynamic_expression SymbolName, Dereference, AddressOf
from .plylex import SourceRef, print_bold from .plylex import SourceRef, print_bold
from .optimize import optimize from .optimize import optimize
@ -74,7 +74,12 @@ class PlyParser:
# process/simplify all expressions (constant folding etc) # process/simplify all expressions (constant folding etc)
for block, parent in module.all_scopes(): for block, parent in module.all_scopes():
for node in block.nodes: for node in block.nodes:
node.process_expressions(block.scope) try:
node.process_expressions(block.scope)
except ParseError:
raise
except Exception as x:
self.handle_internal_error(x, "process_expressions of node {} in block {}".format(node, block.name))
@no_type_check @no_type_check
def create_multiassigns(self, module: Module) -> None: def create_multiassigns(self, module: Module) -> None:
@ -212,6 +217,10 @@ class PlyParser:
self._get_subroutine_usages_from_expression(usages, expr.right, parent_scope) self._get_subroutine_usages_from_expression(usages, expr.right, parent_scope)
elif isinstance(expr, LiteralValue): elif isinstance(expr, LiteralValue):
return return
elif isinstance(expr, Dereference):
return self._get_subroutine_usages_from_expression(usages, expr.location, parent_scope)
elif isinstance(expr, AddressOf):
return self._get_subroutine_usages_from_expression(usages, expr.name, parent_scope)
elif isinstance(expr, SymbolName): elif isinstance(expr, SymbolName):
try: try:
symbol = parent_scope[expr.name] symbol = parent_scope[expr.name]
@ -357,6 +366,16 @@ class PlyParser:
if sys.stderr.isatty(): if sys.stderr.isatty():
print("\x1b[0m", file=sys.stderr, end="", flush=True) print("\x1b[0m", file=sys.stderr, end="", flush=True)
def handle_internal_error(self, exc: Exception, msg: str="") -> None:
if sys.stderr.isatty():
print("\x1b[1m", file=sys.stderr)
print("\nERROR: internal parser error: ", exc, file=sys.stderr)
if msg:
print(" Message:", msg, end="\n\n")
if sys.stderr.isatty():
print("\x1b[0m", file=sys.stderr, end="", flush=True)
raise exc
if __name__ == "__main__": if __name__ == "__main__":
description = "Compiler for IL65 language, code name 'Sick'" description = "Compiler for IL65 language, code name 'Sick'"

View File

@ -213,7 +213,7 @@ class AssemblyGenerator:
def generate_block_init(self, block: Block) -> None: def generate_block_init(self, block: Block) -> None:
# generate the block initializer # generate the block initializer
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializers) # @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) # @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")
@ -240,6 +240,12 @@ class AssemblyGenerator:
float_inits[variable.name] = (vname, fpbytes, vvalue) float_inits[variable.name] = (vname, fpbytes, vvalue)
elif variable.datatype in STRING_DATATYPES: elif variable.datatype in STRING_DATATYPES:
string_inits.append(variable) string_inits.append(variable)
elif variable.datatype == DataType.BYTEARRAY:
pass # @todo init bytearray
elif variable.datatype == DataType.WORDARRAY:
pass # @todo init wordarray
elif variable.datatype == DataType.MATRIX:
pass # @todo init matrix
else: else:
raise CodeError("weird var datatype", variable.datatype) raise CodeError("weird var datatype", variable.datatype)
if float_inits: if float_inits:
@ -330,8 +336,8 @@ class AssemblyGenerator:
raise CodeError("unknown variable type " + str(vardef.datatype)) raise CodeError("unknown variable type " + str(vardef.datatype))
if string_vars: if string_vars:
self.p("il65_string_vars_start") self.p("il65_string_vars_start")
for sv in sorted(string_vars): # must be the same order as in the init routine!!! for svar in sorted(string_vars, key=lambda v: v.name): # must be the same order as in the init routine!!!
self.p("{:s}\v.fill {:d}+1\t\t; {}".format(sv.name, len(sv.value), sv.datatype.name.lower())) self.p("{:s}\v.fill {:d}+1\t\t; {}".format(svar.name, len(svar.value), svar.datatype.name.lower()))
self.p("") self.p("")
def _generate_string_var(self, vardef: VarDef, init: bool=False) -> None: def _generate_string_var(self, vardef: VarDef, init: bool=False) -> None:
@ -375,8 +381,7 @@ class AssemblyGenerator:
assert rvalue is not None assert rvalue is not None
if isinstance(rvalue, LiteralValue): if isinstance(rvalue, LiteralValue):
rvalue = rvalue.value rvalue = rvalue.value
print("ASSIGN", lvalue, lvalue.datatype, operator, rvalue) print("ASSIGN", lvalue, lvalue.datatype, operator, rvalue) # @todo
# @todo
class Assembler64Tass: class Assembler64Tass:

View File

@ -107,7 +107,7 @@ class Optimizer:
if block.scope: if block.scope:
for stmt in block.scope.filter_nodes(Goto): for stmt in block.scope.filter_nodes(Goto):
if isinstance(stmt.condition, Expression): if isinstance(stmt.condition, Expression):
raise NotImplementedError("optimize goto conditionals", stmt.condition) # @todo print("NOT IMPLEMENTED YET: optimize goto conditionals", stmt.condition) # @todo
# if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0: # if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0:
# simplified = False # simplified = False
# if cond.ifstatus in ("true", "ne"): # if cond.ifstatus in ("true", "ne"):

View File

@ -5,6 +5,7 @@ This is the lexer of the IL65 code, that generates a stream of tokens for the pa
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
""" """
import ast
import sys import sys
import ply.lex import ply.lex
import attr import attr
@ -258,7 +259,7 @@ def t_STRING(t):
(?<!\\) # not preceded by a backslash (?<!\\) # not preceded by a backslash
' # a literal double-quote ' # a literal double-quote
""" """
t.value = t.value[1:-1] t.value = ast.literal_eval(t.value)
if len(t.value) == 1: if len(t.value) == 1:
t.type = "CHARACTER" t.type = "CHARACTER"
if len(t.value) == 2 and t.value[0] == '\\': if len(t.value) == 2 and t.value[0] == '\\':

View File

@ -624,31 +624,40 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
if isinstance(value, VarDef): if isinstance(value, VarDef):
if value.vartype == VarType.MEMORY: if value.vartype == VarType.MEMORY:
return value.value return value.value
raise ParseError("can't take the address of this {}".format(value.__class__.__name__), expr.name.sourceref) raise ExpressionEvaluationError("taking the address of this {} isn't a constant".format(value.__class__.__name__), expr.name.sourceref)
else: else:
raise ExpressionEvaluationError("constant address required, not {}".format(value.__class__.__name__), expr.name.sourceref) raise ExpressionEvaluationError("constant address required, not {}".format(value.__class__.__name__), expr.name.sourceref)
except LookupError as x: except LookupError as x:
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):
funcname = expr.target.target.name print("CALLTARGET", expr.target.address_of, expr.target.target) # XXX
if funcname in math_functions or funcname in builtin_functions: target = expr.target.target
if isinstance(expr.target.target, SymbolName): if isinstance(target, SymbolName): # 'function(1,2,3)'
func_args = [] funcname = target.name
for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments): if funcname in math_functions or funcname in builtin_functions:
if isinstance(a, LiteralValue): if isinstance(expr.target.target, SymbolName):
func_args.append(a.value) func_args = []
else: for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments):
func_args.append(a) if isinstance(a, LiteralValue):
func = math_functions.get(funcname, builtin_functions.get(funcname)) func_args.append(a.value)
try: else:
return func(*func_args) func_args.append(a)
except Exception as x: func = math_functions.get(funcname, builtin_functions.get(funcname))
raise ExpressionEvaluationError(str(x), expr.sourceref) try:
return func(*func_args)
except Exception as x:
raise ExpressionEvaluationError(str(x), expr.sourceref)
else:
raise ParseError("symbol name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
else: else:
raise ParseError("symbol name required, not {}".format(expr.target.__class__.__name__), expr.sourceref) raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
elif isinstance(target, Dereference): # '[...](1,2,3)'
return None # XXX
elif isinstance(target, int): # '64738()'
return None # XXX
else: else:
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref) raise NotImplementedError("weird call target", target) # XXX
else: else:
raise ParseError("function name required, not {}".format(expr.target.__class__.__name__), expr.sourceref) raise ParseError("function name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
elif not isinstance(expr, Expression): elif not isinstance(expr, Expression):
@ -707,6 +716,8 @@ def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Sco
return expr return expr
elif isinstance(expr, Register): elif isinstance(expr, Register):
return expr return expr
elif isinstance(expr, Dereference):
return expr
elif not isinstance(expr, Expression): elif not isinstance(expr, Expression):
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref) raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
if expr.unary: if expr.unary:

View File

@ -1,5 +1,6 @@
from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef
from il65.plyparse import parser, TokenFilter, Module, Subroutine, Block, Return, Scope, VarDef, Expression, LiteralValue, Label from il65.plyparse import parser, TokenFilter, Module, Subroutine, Block, Return, Scope, \
VarDef, Expression, LiteralValue, Label, SubCall, CallTarget, SymbolName
def test_lexer_definitions(): def test_lexer_definitions():
@ -13,7 +14,7 @@ def test_lexer_definitions():
assert "if_cc" in reserved assert "if_cc" in reserved
test_source = """ %output prg, sys test_source_1 = """ %output prg, sys
; c1 ; c1
@ -40,8 +41,9 @@ test_source = """ %output prg, sys
} }
""" """
def test_lexer(): def test_lexer():
lexer.input(test_source) lexer.input(test_source_1)
lexer.lineno = 1 lexer.lineno = 1
tokens = list(iter(lexer)) tokens = list(iter(lexer))
token_types = list(t.type for t in tokens) token_types = list(t.type for t in tokens)
@ -64,8 +66,22 @@ def test_lexer():
assert bool_token.value == True assert bool_token.value == True
def test_lexer_strings():
lexer.input(r"'hello\tbye\n\n' '\n'")
lexer.lineno = 1
tokens = list(iter(lexer))
assert len(tokens) == 2
st = tokens[0]
assert st.type == "STRING"
assert st.value == "hello\tbye\n\n"
lexer.input(r"'hello\tbye\n\n'")
st = tokens[1]
assert st.type == "CHARACTER"
assert st.value == '\n'
def test_tokenfilter(): def test_tokenfilter():
lexer.input(test_source) lexer.input(test_source_1)
lexer.lineno = 1 lexer.lineno = 1
filter = TokenFilter(lexer) filter = TokenFilter(lexer)
tokens = [] tokens = []
@ -88,7 +104,7 @@ def test_parser():
lexer.lineno = 1 lexer.lineno = 1
lexer.source_filename = "sourcefile" lexer.source_filename = "sourcefile"
filter = TokenFilter(lexer) filter = TokenFilter(lexer)
result = parser.parse(input=test_source, tokenfunc=filter.token) result = parser.parse(input=test_source_1, tokenfunc=filter.token)
assert isinstance(result, Module) assert isinstance(result, Module)
assert result.name == "sourcefile" assert result.name == "sourcefile"
assert result.scope.name == "<sourcefile global scope>" assert result.scope.name == "<sourcefile global scope>"
@ -135,3 +151,32 @@ def test_block_nodes():
assert sub2.scope is not None assert sub2.scope is not None
assert len(sub2.scope.nodes) > 0 assert len(sub2.scope.nodes) > 0
assert sub2.nodes is sub2.scope.nodes assert sub2.nodes is sub2.scope.nodes
test_source_2 = """
~ {
999(1,2)
&zz()
}
"""
def test_parser_2():
lexer.lineno = 1
lexer.source_filename = "sourcefile"
filter = TokenFilter(lexer)
result = parser.parse(input=test_source_2, tokenfunc=filter.token)
block = result.nodes[0]
call = block.nodes[0]
assert isinstance(call, SubCall)
assert len(call.arguments) == 2
assert isinstance(call.target, CallTarget)
assert call.target.target == 999
assert call.target.address_of is False
call = block.nodes[1]
assert isinstance(call, SubCall)
assert len(call.arguments) == 0
assert isinstance(call.target, CallTarget)
assert isinstance(call.target.target, SymbolName)
assert call.target.target.name == "zz"
assert call.target.address_of is True

View File

@ -50,6 +50,10 @@ bar: goto $c000
; goto [AX()] % ; @todo error, must be return() ; goto [AX()] % ; @todo error, must be return()
goto [var1] goto [var1]
goto [mem1] ; comment goto [mem1] ; comment
goto $c000
goto 64738
64738(1,2) ; @todo jsr $64738 ??
return 9999() ; @todo jsr 9999 ??
return [AX]() return [AX]()
return [var1] () ; comment return [var1] () ; comment
return [mem1] () return [mem1] ()

View File

@ -1,4 +1,4 @@
%output prg,basic %output basic
%import c64lib %import c64lib

View File

@ -125,7 +125,7 @@
; 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 var .word initword0a = &ZP.zpmem1 ; @todo should work, reference this symbols' generated address (@todo generate address for ZP)
var .word initword0 = &ZP.zpvar1 var .word initword0 = &ZP.zpvar1
var initbytea0 = &ZP.zpmem1 var initbytea0 = &ZP.zpmem1
var .word initworda1 = &ZP.zpvar1 var .word initworda1 = &ZP.zpvar1

View File

@ -1,4 +1,4 @@
%output prg,basic %output basic
%import c64lib %import c64lib

View File

@ -1,6 +1,6 @@
; var definitions and immediate primitive data type tests ; var definitions and immediate primitive data type tests
%output prg, basic %output basic
%zp clobber %zp clobber
%import c64lib %import c64lib

View File

@ -1,4 +1,4 @@
%output prg,basic %output basic
%import c64lib %import c64lib
%import mathlib %import mathlib
@ -95,4 +95,4 @@ sub goodbye ()->() {
return return
} }
} }

View File

@ -4,7 +4,7 @@
; line 3 comment ; line 3 comment
%output basic , prg ; create a c-64 program with basic SYS call to launch it %output basic ; create a c-64 program with basic SYS call to launch it
%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

View File

@ -1,5 +1,5 @@
%output prg,basic ; create a c-64 program with basic SYS call to launch it %output basic ; create a c-64 program with basic SYS call to launch it
%import c64lib ; searched in several locations and with .ill file extension added %import c64lib ; searched in several locations and with .ill file extension added

View File

@ -1,4 +1,4 @@
%output prg,basic ; create a c-64 program with basic SYS to() launch it %output basic ; create a c-64 program with basic SYS to() launch it
%import "c64lib.ill" %import "c64lib.ill"

View File

@ -1,29 +1,36 @@
%output basic %output basic
~ ZP { ~ ZP {
var zp1_1 var zp1_1 = 200
var zp1_2 var zp1_2 = 200
var zp1_3 var .word zp1_3 = $ff99
var zp1_4 var .word zp1_4 = $ee88
const zpc1_1 const zpc1_1 = 55
const zpc1_2 const .word zpc1_2 = 2333.54566
const .float zpc1_3 = 6.54566
} }
~ ZP { ~ ZP {
var zp2_1 var zp2_1 = 100
var zp2_2 var zp2_2 = 100
var zp2_3 var .word zp2_3 = $55aa
var zp2_4 var .word zp2_4 = $66bb
const zpc2_1 const .text zpc2_1 = "hello"
const zpc2_2 const zpc2_2 = 0
} }
~ main { ~ main {
var .text hello_str = "hello there"
var .float float1 = 3.14
var .float float2 = 3.14
var .float float3 = 3.14
var .float float4 = 3.14
var .float float5 = 3.14
start: start:
return return 1.22, 46
} }