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
from .plyparse import parse_file, ParseError, Module, Directive, Block, Subroutine, Scope, VarDef, LiteralValue, \
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 .optimize import optimize
@ -74,7 +74,12 @@ class PlyParser:
# process/simplify all expressions (constant folding etc)
for block, parent in module.all_scopes():
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
def create_multiassigns(self, module: Module) -> None:
@ -212,6 +217,10 @@ class PlyParser:
self._get_subroutine_usages_from_expression(usages, expr.right, parent_scope)
elif isinstance(expr, LiteralValue):
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):
try:
symbol = parent_scope[expr.name]
@ -357,6 +366,16 @@ class PlyParser:
if sys.stderr.isatty():
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__":
description = "Compiler for IL65 language, code name 'Sick'"

View File

@ -213,7 +213,7 @@ class AssemblyGenerator:
def generate_block_init(self, block: Block) -> None:
# 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")
# @todo optimize init order (sort on value first to avoid needless register loads, etc)
self.p("\vlda #0\n\vldx #0")
@ -240,6 +240,12 @@ class AssemblyGenerator:
float_inits[variable.name] = (vname, fpbytes, vvalue)
elif variable.datatype in STRING_DATATYPES:
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:
raise CodeError("weird var datatype", variable.datatype)
if float_inits:
@ -330,8 +336,8 @@ class AssemblyGenerator:
raise CodeError("unknown variable type " + str(vardef.datatype))
if string_vars:
self.p("il65_string_vars_start")
for sv in sorted(string_vars): # 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()))
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(svar.name, len(svar.value), svar.datatype.name.lower()))
self.p("")
def _generate_string_var(self, vardef: VarDef, init: bool=False) -> None:
@ -375,8 +381,7 @@ class AssemblyGenerator:
assert rvalue is not None
if isinstance(rvalue, LiteralValue):
rvalue = rvalue.value
print("ASSIGN", lvalue, lvalue.datatype, operator, rvalue)
# @todo
print("ASSIGN", lvalue, lvalue.datatype, operator, rvalue) # @todo
class Assembler64Tass:

View File

@ -107,7 +107,7 @@ class Optimizer:
if block.scope:
for stmt in block.scope.filter_nodes(Goto):
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:
# simplified = False
# 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
"""
import ast
import sys
import ply.lex
import attr
@ -258,7 +259,7 @@ def t_STRING(t):
(?<!\\) # not preceded by a backslash
' # a literal double-quote
"""
t.value = t.value[1:-1]
t.value = ast.literal_eval(t.value)
if len(t.value) == 1:
t.type = "CHARACTER"
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 value.vartype == VarType.MEMORY:
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:
raise ExpressionEvaluationError("constant address required, not {}".format(value.__class__.__name__), expr.name.sourceref)
except LookupError as x:
raise ParseError(str(x), expr.sourceref) from None
elif isinstance(expr, SubCall):
if isinstance(expr.target, CallTarget):
funcname = expr.target.target.name
if funcname in math_functions or funcname in builtin_functions:
if isinstance(expr.target.target, SymbolName):
func_args = []
for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments):
if isinstance(a, LiteralValue):
func_args.append(a.value)
else:
func_args.append(a)
func = math_functions.get(funcname, builtin_functions.get(funcname))
try:
return func(*func_args)
except Exception as x:
raise ExpressionEvaluationError(str(x), expr.sourceref)
print("CALLTARGET", expr.target.address_of, expr.target.target) # XXX
target = expr.target.target
if isinstance(target, SymbolName): # 'function(1,2,3)'
funcname = target.name
if funcname in math_functions or funcname in builtin_functions:
if isinstance(expr.target.target, SymbolName):
func_args = []
for a in (process_constant_expression(callarg.value, sourceref, symbolscope) for callarg in expr.arguments):
if isinstance(a, LiteralValue):
func_args.append(a.value)
else:
func_args.append(a)
func = math_functions.get(funcname, builtin_functions.get(funcname))
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:
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:
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
raise NotImplementedError("weird call target", target) # XXX
else:
raise ParseError("function name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
elif not isinstance(expr, Expression):
@ -707,6 +716,8 @@ def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Sco
return expr
elif isinstance(expr, Register):
return expr
elif isinstance(expr, Dereference):
return expr
elif not isinstance(expr, Expression):
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
if expr.unary:

View File

@ -1,5 +1,6 @@
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():
@ -13,7 +14,7 @@ def test_lexer_definitions():
assert "if_cc" in reserved
test_source = """ %output prg, sys
test_source_1 = """ %output prg, sys
; c1
@ -40,8 +41,9 @@ test_source = """ %output prg, sys
}
"""
def test_lexer():
lexer.input(test_source)
lexer.input(test_source_1)
lexer.lineno = 1
tokens = list(iter(lexer))
token_types = list(t.type for t in tokens)
@ -64,8 +66,22 @@ def test_lexer():
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():
lexer.input(test_source)
lexer.input(test_source_1)
lexer.lineno = 1
filter = TokenFilter(lexer)
tokens = []
@ -88,7 +104,7 @@ def test_parser():
lexer.lineno = 1
lexer.source_filename = "sourcefile"
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 result.name == "sourcefile"
assert result.scope.name == "<sourcefile global scope>"
@ -135,3 +151,32 @@ def test_block_nodes():
assert sub2.scope is not None
assert len(sub2.scope.nodes) > 0
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 [var1]
goto [mem1] ; comment
goto $c000
goto 64738
64738(1,2) ; @todo jsr $64738 ??
return 9999() ; @todo jsr 9999 ??
return [AX]()
return [var1] () ; comment
return [mem1] ()

View File

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

View File

@ -125,7 +125,7 @@
; 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.
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 initbytea0 = &ZP.zpmem1
var .word initworda1 = &ZP.zpvar1

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
; 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

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

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"

View File

@ -1,29 +1,36 @@
%output basic
~ ZP {
var zp1_1
var zp1_2
var zp1_3
var zp1_4
const zpc1_1
const zpc1_2
var zp1_1 = 200
var zp1_2 = 200
var .word zp1_3 = $ff99
var .word zp1_4 = $ee88
const zpc1_1 = 55
const .word zpc1_2 = 2333.54566
const .float zpc1_3 = 6.54566
}
~ ZP {
var zp2_1
var zp2_2
var zp2_3
var zp2_4
const zpc2_1
const zpc2_2
var zp2_1 = 100
var zp2_2 = 100
var .word zp2_3 = $55aa
var .word zp2_4 = $66bb
const .text zpc2_1 = "hello"
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:
return
return 1.22, 46
}