expression

This commit is contained in:
Irmen de Jong 2018-01-10 00:44:11 +01:00
parent 0bb5f98768
commit 29060f3373
11 changed files with 466 additions and 152 deletions

View File

@ -1,7 +1,7 @@
IL65 / 'Sick' - Experimental Programming Language for 8-bit 6502/6510 microprocessors IL65 / 'Sick' - Experimental Programming Language for 8-bit 6502/6510 microprocessors
===================================================================================== =====================================================================================
*Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0* *Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE* *Software license: GNU GPL 3.0, see file LICENSE*
@ -17,6 +17,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- subroutines have enforced input- and output parameter definitions - subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs) - various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- automatic variable allocations, automatic string variables and string sharing - automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- automatic type conversions - automatic type conversions
- floating point operations - floating point operations
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these - optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
@ -24,6 +25,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
- conditional gotos - conditional gotos
- some code optimizations (such as not repeatedly loading the same value in a register)
- @todo: loops - @todo: loops
- @todo: memory block operations - @todo: memory block operations

View File

@ -11,8 +11,9 @@ import sys
import linecache import linecache
from typing import Optional, Tuple, Set, Dict, Any, no_type_check 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, \ 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
from .plylex import SourceRef, print_bold from .plylex import SourceRef, print_bold
from .optimize import optimize from .optimize import optimize
@ -72,11 +73,8 @@ class PlyParser:
def process_all_expressions(self, module: Module) -> None: def process_all_expressions(self, module: Module) -> None:
# 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():
if block.scope: for node in block.nodes:
for node in block.scope.nodes: node.process_expressions(block.scope)
if node is None:
print(block, block.scope, block.scope.nodes)
node.process_expressions()
@no_type_check @no_type_check
def create_multiassigns(self, module: Module) -> None: def create_multiassigns(self, module: Module) -> None:
@ -90,8 +88,7 @@ class PlyParser:
return assign return assign
for block, parent in module.all_scopes(): for block, parent in module.all_scopes():
if block.scope: for node in block.nodes:
for node in block.scope.nodes:
if isinstance(node, Assignment): if isinstance(node, Assignment):
if isinstance(node.right, Assignment): if isinstance(node.right, Assignment):
multi = reduce_right(node) multi = reduce_right(node)
@ -103,11 +100,11 @@ class PlyParser:
if not scope: if not scope:
return return
if len(save_dir.args) > 1: if len(save_dir.args) > 1:
raise ParseError("need zero or one directive argument", save_dir.sourceref) raise ParseError("expected zero or one directive argument", save_dir.sourceref)
if save_dir.args: if save_dir.args:
if save_dir.args[0] in ("yes", "true"): if save_dir.args[0] in ("yes", "true", True):
scope.save_registers = True scope.save_registers = True
elif save_dir.args[0] in ("no", "false"): elif save_dir.args[0] in ("no", "false", False):
scope.save_registers = False scope.save_registers = False
else: else:
raise ParseError("invalid directive args", save_dir.sourceref) raise ParseError("invalid directive args", save_dir.sourceref)
@ -120,7 +117,7 @@ class PlyParser:
for directive in block.scope.filter_nodes(Directive): for directive in block.scope.filter_nodes(Directive):
if directive.name == "output": if directive.name == "output":
if len(directive.args) != 1 or not isinstance(directive.args[0], str): if len(directive.args) != 1 or not isinstance(directive.args[0], str):
raise ParseError("need one str directive argument", directive.sourceref) raise ParseError("expected one str directive argument", directive.sourceref)
if directive.args[0] == "raw": if directive.args[0] == "raw":
block.format = ProgramFormat.RAW block.format = ProgramFormat.RAW
block.address = 0xc000 block.address = 0xc000
@ -134,7 +131,7 @@ class PlyParser:
raise ParseError("invalid directive args", directive.sourceref) raise ParseError("invalid directive args", directive.sourceref)
elif directive.name == "address": elif directive.name == "address":
if len(directive.args) != 1 or not isinstance(directive.args[0], int): if len(directive.args) != 1 or not isinstance(directive.args[0], int):
raise ParseError("need one integer directive argument", directive.sourceref) raise ParseError("expected one integer directive argument", directive.sourceref)
if block.format == ProgramFormat.BASIC: if block.format == ProgramFormat.BASIC:
raise ParseError("basic cannot have a custom load address", directive.sourceref) raise ParseError("basic cannot have a custom load address", directive.sourceref)
block.address = directive.args[0] block.address = directive.args[0]
@ -178,20 +175,19 @@ class PlyParser:
def determine_subroutine_usage(self, module: Module) -> None: def determine_subroutine_usage(self, module: Module) -> None:
module.subroutine_usage.clear() module.subroutine_usage.clear()
for block, parent in module.all_scopes(): for block, parent in module.all_scopes():
if block.scope: for node in block.nodes:
for node in block.scope.nodes:
if isinstance(node, InlineAssembly): if isinstance(node, InlineAssembly):
self._parse_asm_for_subroutine_usage(module.subroutine_usage, node, block.scope) self._get_subroutine_usages_from_asm(module.subroutine_usage, node, block.scope)
elif isinstance(node, SubCall): elif isinstance(node, SubCall):
self._parse_subcall_for_subroutine_usages(module.subroutine_usage, node, block.scope) self._get_subroutine_usages_from_subcall(module.subroutine_usage, node, block.scope)
elif isinstance(node, Goto): elif isinstance(node, Goto):
self._parse_goto_for_subroutine_usages(module.subroutine_usage, node, block.scope) self._get_subroutine_usages_from_goto(module.subroutine_usage, node, block.scope)
elif isinstance(node, Return): elif isinstance(node, Return):
self._parse_return_for_subroutine_usages(module.subroutine_usage, node, block.scope) self._get_subroutine_usages_from_return(module.subroutine_usage, node, block.scope)
elif isinstance(node, Assignment): elif isinstance(node, Assignment):
self._parse_assignment_for_subroutine_usages(module.subroutine_usage, node, block.scope) self._get_subroutine_usages_from_assignment(module.subroutine_usage, node, block.scope)
def _parse_subcall_for_subroutine_usages(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_subcall(self, usages: Dict[Tuple[str, str], Set[str]],
subcall: SubCall, parent_scope: Scope) -> None: subcall: SubCall, parent_scope: Scope) -> None:
# node.target (relevant if its a symbolname -- a str), node.arguments (list of CallArgument) # node.target (relevant if its a symbolname -- a str), node.arguments (list of CallArgument)
# CallArgument.value = expression. # CallArgument.value = expression.
@ -203,21 +199,30 @@ class PlyParser:
name = subcall.target.target name = subcall.target.target
usages[(scopename, name)].add(str(subcall.sourceref)) usages[(scopename, name)].add(str(subcall.sourceref))
for arg in subcall.arguments: for arg in subcall.arguments:
self._parse_expression_for_subroutine_usages(usages, arg.value, parent_scope) self._get_subroutine_usages_from_expression(usages, arg.value, parent_scope)
def _parse_expression_for_subroutine_usages(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_expression(self, usages: Dict[Tuple[str, str], Set[str]],
expr: Any, parent_scope: Scope) -> None: expr: Any, parent_scope: Scope) -> None:
if expr is None or isinstance(expr, (int, str, float, bool, Register)): if expr is None or isinstance(expr, (int, str, float, bool, Register)):
return return
elif isinstance(expr, SubCall): elif isinstance(expr, SubCall):
self._parse_subcall_for_subroutine_usages(usages, expr, parent_scope) self._get_subroutine_usages_from_subcall(usages, expr, parent_scope)
elif isinstance(expr, Expression): elif isinstance(expr, Expression):
self._parse_expression_for_subroutine_usages(usages, expr.left, parent_scope) self._get_subroutine_usages_from_expression(usages, expr.left, parent_scope)
self._parse_expression_for_subroutine_usages(usages, expr.right, parent_scope) self._get_subroutine_usages_from_expression(usages, expr.right, parent_scope)
elif isinstance(expr, LiteralValue):
return
elif isinstance(expr, SymbolName):
try:
symbol = parent_scope[expr.name]
if isinstance(symbol, Subroutine):
usages[(parent_scope.name, expr.name)].add(str(expr.sourceref))
except LookupError:
pass
else: else:
print("@todo parse expression for subroutine usage:", expr) # @todo raise TypeError("unknown expr type to scan for sub usages", expr, expr.sourceref)
def _parse_goto_for_subroutine_usages(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_goto(self, usages: Dict[Tuple[str, str], Set[str]],
goto: Goto, parent_scope: Scope) -> None: goto: Goto, parent_scope: Scope) -> None:
# node.target (relevant if its a symbolname -- a str), node.condition (expression) # node.target (relevant if its a symbolname -- a str), node.condition (expression)
if isinstance(goto.target.target, str): if isinstance(goto.target.target, str):
@ -227,24 +232,24 @@ class PlyParser:
return return
if isinstance(symbol, Subroutine): if isinstance(symbol, Subroutine):
usages[(parent_scope.name, symbol.name)].add(str(goto.sourceref)) usages[(parent_scope.name, symbol.name)].add(str(goto.sourceref))
self._parse_expression_for_subroutine_usages(usages, goto.condition, parent_scope) self._get_subroutine_usages_from_expression(usages, goto.condition, parent_scope)
def _parse_return_for_subroutine_usages(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_return(self, usages: Dict[Tuple[str, str], Set[str]],
returnnode: Return, parent_scope: Scope) -> None: returnnode: Return, parent_scope: Scope) -> None:
# node.value_A (expression), value_X (expression), value_Y (expression) # node.value_A (expression), value_X (expression), value_Y (expression)
self._parse_expression_for_subroutine_usages(usages, returnnode.value_A, parent_scope) self._get_subroutine_usages_from_expression(usages, returnnode.value_A, parent_scope)
self._parse_expression_for_subroutine_usages(usages, returnnode.value_X, parent_scope) self._get_subroutine_usages_from_expression(usages, returnnode.value_X, parent_scope)
self._parse_expression_for_subroutine_usages(usages, returnnode.value_Y, parent_scope) self._get_subroutine_usages_from_expression(usages, returnnode.value_Y, parent_scope)
def _parse_assignment_for_subroutine_usages(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_assignment(self, usages: Dict[Tuple[str, str], Set[str]],
assignment: Assignment, parent_scope: Scope) -> None: assignment: Assignment, parent_scope: Scope) -> None:
# node.right (expression, or another Assignment) # node.right (expression, or another Assignment)
if isinstance(assignment.right, Assignment): if isinstance(assignment.right, Assignment):
self._parse_assignment_for_subroutine_usages(usages, assignment.right, parent_scope) self._get_subroutine_usages_from_assignment(usages, assignment.right, parent_scope)
else: else:
self._parse_expression_for_subroutine_usages(usages, assignment.right, parent_scope) self._get_subroutine_usages_from_expression(usages, assignment.right, parent_scope)
def _parse_asm_for_subroutine_usage(self, usages: Dict[Tuple[str, str], Set[str]], def _get_subroutine_usages_from_asm(self, usages: Dict[Tuple[str, str], Set[str]],
asmnode: InlineAssembly, parent_scope: Scope) -> None: asmnode: InlineAssembly, parent_scope: Scope) -> None:
# asm can refer to other symbols as well, track subroutine usage # asm can refer to other symbols as well, track subroutine usage
for line in asmnode.assembly.splitlines(): for line in asmnode.assembly.splitlines():

View File

@ -104,13 +104,13 @@ def coerce_value(datatype: DataType, value: PrimitiveType, sourceref: SourceRef=
# if we're a BYTE type, and the value is a single character, convert it to the numeric value # if we're a BYTE type, and the value is a single character, convert it to the numeric value
def verify_bounds(value: PrimitiveType) -> None: def verify_bounds(value: PrimitiveType) -> None:
# if the value is out of bounds, raise an overflow exception # if the value is out of bounds, raise an overflow exception
if isinstance(value, (int, float)):
if datatype == DataType.BYTE and not (0 <= value <= 0xff): # type: ignore if datatype == DataType.BYTE and not (0 <= value <= 0xff): # type: ignore
raise OverflowError("value out of range for byte") raise OverflowError("value out of range for byte")
if datatype == DataType.WORD and not (0 <= value <= 0xffff): # type: ignore if datatype == DataType.WORD and not (0 <= value <= 0xffff): # type: ignore
raise OverflowError("value out of range for word") raise OverflowError("value out of range for word")
if datatype == DataType.FLOAT and not (FLOAT_MAX_NEGATIVE <= value <= FLOAT_MAX_POSITIVE): # type: ignore if datatype == DataType.FLOAT and not (FLOAT_MAX_NEGATIVE <= value <= FLOAT_MAX_POSITIVE): # type: ignore
raise OverflowError("value out of range for float") raise OverflowError("value out of range for float")
if datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str): if datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str):
if len(value) == 1: if len(value) == 1:
return True, char_to_bytevalue(value) return True, char_to_bytevalue(value)

View File

@ -10,6 +10,7 @@ import subprocess
import datetime import datetime
import itertools import itertools
from typing import Union, TextIO, List, Tuple, Iterator from typing import Union, TextIO, List, Tuple, Iterator
from .plylex import print_bold
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions
from .datatypes import VarType, DataType, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES from .datatypes import VarType, DataType, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES
@ -47,7 +48,17 @@ class AssemblyGenerator:
self.footer() self.footer()
def sanitycheck(self): def sanitycheck(self):
# duplicate block names? start_found = False
for block, parent in self.module.all_scopes():
for label in block.nodes:
if isinstance(label, Label) and label.name == "start" and block.name == "main":
start_found = True
break
if start_found:
break
if not start_found:
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.scope.filter_nodes(Block)] all_blocknames = [b.name for b in self.module.scope.filter_nodes(Block)]
unique_blocknames = set(all_blocknames) unique_blocknames = set(all_blocknames)
if len(all_blocknames) != len(unique_blocknames): if len(all_blocknames) != len(unique_blocknames):
@ -329,6 +340,7 @@ class AssemblyGenerator:
def generate_statement(self, stmt: AstNode) -> None: def generate_statement(self, stmt: AstNode) -> None:
if isinstance(stmt, Label): if isinstance(stmt, Label):
self.p("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref)) self.p("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
self.p("\vrts")
# @todo rest of the statement nodes # @todo rest of the statement nodes

View File

@ -40,7 +40,7 @@ def main() -> None:
if args.nooptimize: if args.nooptimize:
print_bold("not optimizing the parse tree!") print_bold("not optimizing the parse tree!")
else: else:
print("\nOptimizing parse tree.") print("\nOptimizing code.")
optimize(parsed_module) optimize(parsed_module)
print("\nGenerating assembly code.") print("\nGenerating assembly code.")
cg = AssemblyGenerator(parsed_module) cg = AssemblyGenerator(parsed_module)

View File

@ -28,8 +28,7 @@ class Optimizer:
# and augmented assignments that have no effect (A+=0) # and augmented assignments that have no effect (A+=0)
# @todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?) # @todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?)
for block, parent in self.module.all_scopes(): for block, parent in self.module.all_scopes():
if block.scope: for assignment in list(block.nodes):
for assignment in list(block.scope.nodes):
if isinstance(assignment, Assignment): if isinstance(assignment, Assignment):
assignment.left = [lv for lv in assignment.left if lv != assignment.right] assignment.left = [lv for lv in assignment.left if lv != assignment.right]
if not assignment.left: if not assignment.left:
@ -43,18 +42,16 @@ class Optimizer:
print_warning("{}: removed statement that has no effect".format(assignment.sourceref)) print_warning("{}: removed statement that has no effect".format(assignment.sourceref))
block.scope.remove_node(assignment) block.scope.remove_node(assignment)
if assignment.right >= 8 and assignment.operator in ("<<=", ">>="): if assignment.right >= 8 and assignment.operator in ("<<=", ">>="):
self.num_warnings += 1 print("{}: shifting result is always zero".format(assignment.sourceref))
print_warning("{}: shifting result is always zero".format(assignment.sourceref))
new_stmt = Assignment(left=[assignment.left], right=0, sourceref=assignment.sourceref) new_stmt = Assignment(left=[assignment.left], right=0, sourceref=assignment.sourceref)
block.scope.replace_node(assignment, new_stmt) block.scope.replace_node(assignment, new_stmt)
def combine_assignments_into_multi(self): def combine_assignments_into_multi(self):
# fold multiple consecutive assignments with the same rvalue into one multi-assignment # fold multiple consecutive assignments with the same rvalue into one multi-assignment
for block, parent in self.module.all_scopes(): for block, parent in self.module.all_scopes():
if block.scope:
rvalue = None rvalue = None
assignments = [] assignments = []
for stmt in list(block.scope.nodes): for stmt in list(block.nodes):
if isinstance(stmt, Assignment): if isinstance(stmt, Assignment):
if assignments: if assignments:
if stmt.right == rvalue: if stmt.right == rvalue:
@ -78,14 +75,12 @@ class Optimizer:
def optimize_multiassigns(self): def optimize_multiassigns(self):
# optimize multi-assign statements (remove duplicate targets, optimize order) # optimize multi-assign statements (remove duplicate targets, optimize order)
for block, parent in self.module.all_scopes(): for block, parent in self.module.all_scopes():
if block.scope: for assignment in block.nodes:
for assignment in block.scope.nodes:
if isinstance(assignment, Assignment) and len(assignment.left) > 1: if isinstance(assignment, Assignment) and len(assignment.left) > 1:
# remove duplicates # remove duplicates
lvalues = set(assignment.left) lvalues = set(assignment.left)
if len(lvalues) != len(assignment.left): if len(lvalues) != len(assignment.left):
self.num_warnings += 1 print("{}: removed duplicate assignment targets".format(assignment.sourceref))
print_warning("{}: removed duplicate assignment targets".format(assignment.sourceref))
# @todo change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any) # @todo change order: first registers, then zp addresses, then non-zp addresses, then the rest (if any)
assignment.left = list(lvalues) assignment.left = list(lvalues)

View File

@ -61,6 +61,7 @@ tokens = (
"LOGICAND", "LOGICAND",
"LOGICOR", "LOGICOR",
"LOGICNOT", "LOGICNOT",
"INTEGERDIVIDE",
"POWER", "POWER",
"LABEL", "LABEL",
"IF", "IF",
@ -73,6 +74,7 @@ literals = ['+', '-', '*', '/', '(', ')', '[', ']', '{', '}', '.', ',', '!', '?'
# regex rules for simple tokens # regex rules for simple tokens
t_INTEGERDIVIDE = r"//"
t_BITAND = r"&" t_BITAND = r"&"
t_BITOR = r"\|" t_BITOR = r"\|"
t_BITXOR = r"\^" t_BITXOR = r"\^"
@ -219,6 +221,12 @@ def t_LABEL(t):
return t return t
def t_BOOLEAN(t):
r"true|false"
t.value = t.value == "true"
return t
def t_DOTTEDNAME(t): def t_DOTTEDNAME(t):
r"[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)+" r"[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)+"
return t return t

View File

@ -5,9 +5,12 @@ This is the parser of the IL65 code, that generates a parse tree.
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 math
import builtins
import inspect
import enum import enum
from collections import defaultdict from collections import defaultdict
from typing import Union, Generator, Tuple, List, Optional, Dict from typing import Union, Generator, Tuple, List, Optional, Dict, Any, Iterable
import attr import attr
from ply.yacc import yacc from ply.yacc import yacc
from .plylex import SourceRef, tokens, lexer, find_tok_column from .plylex import SourceRef, tokens, lexer, find_tok_column
@ -26,15 +29,24 @@ class ZpOptions(enum.Enum):
CLOBBER_RESTORE = "clobber_restore" CLOBBER_RESTORE = "clobber_restore"
math_functions = {name: func for name, func in vars(math).items() if inspect.isbuiltin(func)}
builtin_functions = {name: func for name, func in vars(builtins).items() if inspect.isbuiltin(func)}
class ParseError(Exception): class ParseError(Exception):
def __init__(self, message: str, sourceref: SourceRef) -> None: def __init__(self, message: str, sourceref: SourceRef) -> None:
super().__init__(message) super().__init__(message)
self.sourceref = sourceref self.sourceref = sourceref
# @todo chain attribute, a list of other exceptions, so we can have more than 1 error at a time.
def __str__(self): def __str__(self):
return "{} {:s}".format(self.sourceref, self.args[0]) return "{} {:s}".format(self.sourceref, self.args[0])
class ExpressionEvaluationError(ParseError):
pass
start = "start" start = "start"
@ -69,9 +81,9 @@ class AstNode:
tostr(elt, level + 2) tostr(elt, level + 2)
tostr(self, 0) tostr(self, 0)
def process_expressions(self) -> None: def process_expressions(self, scope: 'Scope') -> None:
# process/simplify all expressions (constant folding etc) @todo # process/simplify all expressions (constant folding etc)
# @todo override in node types that have expression(s) # this is implemented in node types that have expression(s) and that should act on this.
pass pass
@ -115,6 +127,7 @@ class Scope(AstNode):
node.scope.parent_scope = self node.scope.parent_scope = self
def __getitem__(self, name: str) -> AstNode: def __getitem__(self, name: str) -> AstNode:
assert isinstance(name, str)
if '.' in name: if '.' in name:
# look up the dotted name starting from the topmost scope # look up the dotted name starting from the topmost scope
scope = self scope = self
@ -166,13 +179,13 @@ class Scope(AstNode):
self._populate_symboltable(newnode) self._populate_symboltable(newnode)
def validate_address(object: AstNode, attrib: attr.Attribute, value: Optional[int]): def validate_address(obj: AstNode, attrib: attr.Attribute, value: Optional[int]):
if value is None: if value is None:
return return
if isinstance(object, Block) and object.name == "ZP": if isinstance(obj, Block) and obj.name == "ZP":
raise ParseError("zeropage block cannot have custom start {:s}".format(attrib.name), object.sourceref) raise ParseError("zeropage block cannot have custom start {:s}".format(attrib.name), obj.sourceref)
if value < 0x0200 or value > 0xffff: if value < 0x0200 or value > 0xffff:
raise ParseError("invalid {:s} (must be from $0200 to $ffff)".format(attrib.name), object.sourceref) raise ParseError("invalid {:s} (must be from $0200 to $ffff)".format(attrib.name), obj.sourceref)
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
@ -185,6 +198,12 @@ class Block(AstNode):
def __attrs_post_init__(self): def __attrs_post_init__(self):
self.scope.name = self.name self.scope.name = self.name
@property
def nodes(self) -> Iterable[AstNode]:
if self.scope:
return self.scope.nodes
return []
@property @property
def label(self) -> str: def label(self) -> str:
if self.name: if self.name:
@ -205,6 +224,12 @@ class Module(AstNode):
address = attr.ib(type=int, init=False, default=0xc000, validator=validate_address) # 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 zp_options = attr.ib(type=ZpOptions, init=False, default=ZpOptions.NOCLOBBER) # can be set via directive
@property
def nodes(self) -> Iterable[AstNode]:
if self.scope:
return self.scope.nodes
return []
def all_scopes(self) -> Generator[Tuple[AstNode, AstNode], None, None]: def all_scopes(self) -> Generator[Tuple[AstNode, AstNode], None, None]:
# generator that recursively yields through the scopes (preorder traversal), yields (node, parent_node) tuples. # generator that recursively yields through the scopes (preorder traversal), yields (node, parent_node) tuples.
# it iterates of copies of the node collections, so it's okay to modify the scopes you iterate over. # it iterates of copies of the node collections, so it's okay to modify the scopes you iterate over.
@ -275,6 +300,9 @@ class Assignment(AstNode):
new_targets.append(t) new_targets.append(t)
self.left = new_targets self.left = new_targets
def process_expressions(self, scope: Scope) -> None:
self.right = process_expression(self.right, scope, self.right.sourceref)
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
class AugAssignment(AstNode): class AugAssignment(AstNode):
@ -282,6 +310,9 @@ class AugAssignment(AstNode):
operator = attr.ib(type=str) operator = attr.ib(type=str)
right = attr.ib() right = attr.ib()
def process_expressions(self, scope: Scope) -> None:
self.right = process_expression(self.right, scope, self.right.sourceref)
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
class SubCall(AstNode): class SubCall(AstNode):
@ -292,6 +323,11 @@ class SubCall(AstNode):
def __attrs_post_init__(self): def __attrs_post_init__(self):
self.arguments = self.arguments or [] self.arguments = self.arguments or []
def process_expressions(self, scope: Scope) -> None:
for callarg in self.arguments:
assert isinstance(callarg, CallArgument)
callarg.process_expressions(scope)
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
class Return(AstNode): class Return(AstNode):
@ -299,6 +335,14 @@ class Return(AstNode):
value_X = attr.ib(default=None) value_X = attr.ib(default=None)
value_Y = attr.ib(default=None) value_Y = attr.ib(default=None)
def process_expressions(self, scope: Scope) -> None:
if self.value_A is not None:
self.value_A = process_expression(self.value_A, scope, self.value_A.sourceref)
if self.value_X is not None:
self.value_X = process_expression(self.value_X, scope, self.value_X.sourceref)
if self.value_Y is not None:
self.value_Y = process_expression(self.value_Y, scope, self.value_Y.sourceref)
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
class TargetRegisters(AstNode): class TargetRegisters(AstNode):
@ -347,12 +391,9 @@ class VarDef(AstNode):
self.value = 0 self.value = 0
# note: value coercion is done later, when all expressions are evaluated # note: value coercion is done later, when all expressions are evaluated
def process_expressions(self) -> None: def process_expressions(self, scope: Scope) -> None:
if isinstance(self.value, Expression): self.value = process_expression(self.value, scope, self.sourceref)
# process/simplify all expressions (constant folding etc) # @todo assert not isinstance(self.value, Expression), "processed expression for vardef should reduce to a constant value"
# verify that the expression yields a single constant value, replace value by that value # @todo
self.value = 123 # XXX
assert not isinstance(self.value, Expression)
if self.vartype in (VarType.CONST, VarType.VAR): if self.vartype in (VarType.CONST, VarType.VAR):
try: try:
_, self.value = coerce_value(self.datatype, self.value, self.sourceref) _, self.value = coerce_value(self.datatype, self.value, self.sourceref)
@ -388,6 +429,12 @@ class Subroutine(AstNode):
scope = attr.ib(type=Scope, default=None) scope = attr.ib(type=Scope, default=None)
address = attr.ib(type=int, default=None, validator=validate_address) address = attr.ib(type=int, default=None, validator=validate_address)
@property
def nodes(self) -> Iterable[AstNode]:
if self.scope:
return self.scope.nodes
return []
def __attrs_post_init__(self): def __attrs_post_init__(self):
if self.scope and self.address is not None: if self.scope and self.address is not None:
raise ValueError("subroutine must have either a scope or an address, not both") raise ValueError("subroutine must have either a scope or an address, not both")
@ -401,6 +448,10 @@ class Goto(AstNode):
if_stmt = attr.ib(default=None) if_stmt = attr.ib(default=None)
condition = attr.ib(default=None) condition = attr.ib(default=None)
def process_expressions(self, scope: Scope) -> None:
if self.condition is not None:
self.condition = process_expression(self.condition, scope, self.condition.sourceref)
@attr.s(cmp=False, repr=False) @attr.s(cmp=False, repr=False)
class Dereference(AstNode): class Dereference(AstNode):
@ -420,6 +471,37 @@ class Dereference(AstNode):
self.datatype = self.datatype.to_enum() self.datatype = self.datatype.to_enum()
@attr.s(cmp=False, repr=False)
class LiteralValue(AstNode):
value = attr.ib()
def __repr__(self) -> str:
return repr(self.value)
@attr.s(cmp=False, repr=False)
class AddressOf(AstNode):
name = attr.ib(type=str)
@attr.s(cmp=False, repr=False)
class IncrDecr(AstNode):
target = attr.ib()
operator = attr.ib(type=str, validator=attr.validators.in_(["++", "--"]))
howmuch = attr.ib(default=1)
def __attrs_post_init__(self):
# make sure the amount is always >= 0
if self.howmuch < 0:
self.howmuch = -self.howmuch
self.operator = "++" if self.operator == "--" else "--"
@attr.s(cmp=False, repr=False)
class SymbolName(AstNode):
name = attr.ib(type=str)
@attr.s(cmp=False, slots=True, repr=False) @attr.s(cmp=False, slots=True, repr=False)
class CallTarget(AstNode): class CallTarget(AstNode):
target = attr.ib() target = attr.ib()
@ -431,11 +513,8 @@ class CallArgument(AstNode):
value = attr.ib() value = attr.ib()
name = attr.ib(type=str, default=None) name = attr.ib(type=str, default=None)
def process_expressions(self, scope: Scope) -> None:
@attr.s(cmp=False, repr=False) self.value = process_expression(self.value, scope, self.sourceref)
class UnaryOp(AstNode):
operator = attr.ib(type=str)
operand = attr.ib()
@attr.s(cmp=False, slots=True, repr=False) @attr.s(cmp=False, slots=True, repr=False)
@ -443,10 +522,187 @@ class Expression(AstNode):
left = attr.ib() left = attr.ib()
operator = attr.ib(type=str) operator = attr.ib(type=str)
right = attr.ib() right = attr.ib()
unary = attr.ib(type=bool, default=False)
processed_must_be_constant = attr.ib(type=bool, init=False, default=False) # does the expression have to be a constant value? processed_must_be_constant = attr.ib(type=bool, init=False, default=False) # does the expression have to be a constant value?
processed = attr.ib(type=bool, init=False, default=False) # has this expression been processed/simplified yet?
constant = attr.ib(type=bool, init=False, default=False) # is the processed expression a constant value?
def __attrs_post_init__(self):
assert self.operator not in ("++", "--"), "incr/decr should not be an expression"
def process_expressions(self, scope: Scope) -> None:
raise RuntimeError("should be done via parent node's process_expressions")
def evaluate_primitive_constants(self, scope: Scope) -> Union[int, float, str, bool]:
# make sure the lvalue and rvalue are primitives, and the operator is allowed
if not isinstance(self.left, (LiteralValue, int, float, str, bool)):
raise TypeError("left", self)
if not isinstance(self.right, (LiteralValue, int, float, str, bool)):
raise TypeError("right", self)
if self.operator not in {'+', '-', '*', '/', '//', '~', '<', '>', '<=', '>=', '==', '!='}:
raise ValueError("operator", self)
estr = "{} {} {}".format(repr(self.left), self.operator, repr(self.right))
try:
return eval(estr, {}, {}) # safe because of checks above
except Exception as x:
raise ExpressionEvaluationError("expression error: " + str(x), self.sourceref) from None
def print_tree(self) -> None:
def tree(expr: Any, level: int) -> str:
indent = " "*level
if not isinstance(expr, Expression):
return indent + str(expr) + "\n"
if expr.unary:
return indent + "{}{}".format(expr.operator, tree(expr.left, level+1))
else:
return indent + "{}".format(tree(expr.left, level+1)) + \
indent + str(expr.operator) + "\n" + \
indent + "{}".format(tree(expr.right, level + 1))
print(tree(self, 0))
def process_expression(value: Any, scope: Scope, sourceref: SourceRef) -> Any:
# process/simplify all expressions (constant folding etc)
if isinstance(value, Expression):
must_be_constant = value.processed_must_be_constant
else:
must_be_constant = False
if must_be_constant:
return process_constant_expression(value, sourceref, scope)
else:
return process_dynamic_expression(value, sourceref, scope)
def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> Union[int, float, str, bool]:
# the expression must result in a single (constant) value (int, float, whatever)
if expr is None or isinstance(expr, (int, float, str, bool)):
return expr
elif isinstance(expr, LiteralValue):
return expr.value
elif isinstance(expr, SymbolName):
try:
value = symbolscope[expr.name]
if isinstance(value, VarDef):
if value.vartype == VarType.MEMORY:
raise ExpressionEvaluationError("can't take a memory value, must be a constant", expr.sourceref)
value = value.value
if isinstance(value, Expression):
raise ExpressionEvaluationError("circular reference?", expr.sourceref)
elif isinstance(value, (int, float, str, bool)):
return value
else:
raise ExpressionEvaluationError("constant symbol required, not {}".format(value.__class__.__name__), expr.sourceref)
except LookupError as x:
raise ExpressionEvaluationError(str(x), expr.sourceref) from None
elif isinstance(expr, AddressOf):
assert isinstance(expr.name, SymbolName)
try:
value = symbolscope[expr.name.name]
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)
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)
else:
raise ParseError("symbol name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
else:
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
else:
raise ParseError("function name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
elif not isinstance(expr, Expression):
raise ExpressionEvaluationError("constant value required, not {}".format(expr.__class__.__name__), expr.sourceref)
if expr.unary:
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
expr.left = process_constant_expression(expr.left, left_sourceref, symbolscope)
if isinstance(expr.left, (int, float)):
try:
if expr.operator == '-':
return -expr.left
elif expr.operator == '~':
return ~expr.left # type: ignore
elif expr.operator in ("++", "--"):
raise ValueError("incr/decr should not be an expression")
raise ValueError("invalid unary operator", expr.operator)
except TypeError as x:
raise ParseError(str(x), expr.sourceref) from None
raise ValueError("invalid operand type for unary operator", expr.left, expr.operator)
else:
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
expr.left = process_constant_expression(expr.left, left_sourceref, symbolscope)
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
expr.right = process_constant_expression(expr.right, right_sourceref, symbolscope)
if isinstance(expr.left, (LiteralValue, SymbolName, int, float, str, bool)):
if isinstance(expr.right, (LiteralValue, SymbolName, int, float, str, bool)):
return expr.evaluate_primitive_constants(symbolscope)
else:
raise ExpressionEvaluationError("constant value required on right, not {}"
.format(expr.right.__class__.__name__), right_sourceref)
else:
raise ExpressionEvaluationError("constant value required on left, not {}"
.format(expr.left.__class__.__name__), left_sourceref)
def process_dynamic_expression(expr: Any, sourceref: SourceRef, symbolscope: Scope) -> Any:
# constant-fold a dynamic expression
if expr is None or isinstance(expr, (int, float, str, bool)):
return expr
elif isinstance(expr, LiteralValue):
return expr.value
elif isinstance(expr, SymbolName):
try:
return process_constant_expression(expr, sourceref, symbolscope)
except ExpressionEvaluationError:
return expr
elif isinstance(expr, AddressOf):
try:
return process_constant_expression(expr, sourceref, symbolscope)
except ExpressionEvaluationError:
return expr
elif isinstance(expr, SubCall):
try:
return process_constant_expression(expr, sourceref, symbolscope)
except ExpressionEvaluationError:
return expr
elif isinstance(expr, Register):
return expr
elif not isinstance(expr, Expression):
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
if expr.unary:
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
expr.left = process_dynamic_expression(expr.left, left_sourceref, symbolscope)
try:
return process_constant_expression(expr, sourceref, symbolscope)
except ExpressionEvaluationError:
return expr
else:
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
expr.left = process_dynamic_expression(expr.left, left_sourceref, symbolscope)
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
expr.right = process_dynamic_expression(expr.right, right_sourceref, symbolscope)
try:
return process_constant_expression(expr, sourceref, symbolscope)
except ExpressionEvaluationError:
return expr
# ----------------- PLY parser definition follows ----------------------
def p_start(p): def p_start(p):
""" """
@ -653,7 +909,7 @@ def p_literal_value(p):
| STRING | STRING
| CHARACTER | CHARACTER
| BOOLEAN""" | BOOLEAN"""
p[0] = p[1] p[0] = LiteralValue(value=p[1], sourceref=_token_sref(p, 1))
def p_subroutine(p): def p_subroutine(p):
@ -759,14 +1015,14 @@ def p_incrdecr(p):
incrdecr : assignment_target INCR incrdecr : assignment_target INCR
| assignment_target DECR | assignment_target DECR
""" """
p[0] = UnaryOp(operator=p[2], operand=p[1], sourceref=_token_sref(p, 1)) p[0] = IncrDecr(target=p[1], operator=p[2], sourceref=_token_sref(p, 1))
def p_call_subroutine(p): def p_call_subroutine(p):
""" """
subroutine_call : calltarget preserveregs_opt '(' call_arguments_opt ')' subroutine_call : calltarget preserveregs_opt '(' call_arguments_opt ')'
""" """
p[0] = SubCall(target=p[1], preserve_regs=p[2], arguments=p[4], sourceref=_token_sref(p, 1)) p[0] = SubCall(target=p[1], preserve_regs=p[2], arguments=p[4], sourceref=_token_sref(p, 3))
def p_preserveregs_opt(p): def p_preserveregs_opt(p):
@ -894,7 +1150,7 @@ def p_symbolname(p):
symbolname : NAME symbolname : NAME
| DOTTEDNAME | DOTTEDNAME
""" """
p[0] = p[1] p[0] = SymbolName(name=p[1], sourceref=_token_sref(p, 1))
def p_assignment(p): def p_assignment(p):
@ -914,7 +1170,7 @@ def p_aug_assignment(p):
precedence = ( precedence = (
('left', '+', '-'), ('left', '+', '-'),
('left', '*', '/'), ('left', '*', '/', 'INTEGERDIVIDE'),
('right', 'UNARY_MINUS', 'BITINVERT', "UNARY_ADDRESSOF"), ('right', 'UNARY_MINUS', 'BITINVERT', "UNARY_ADDRESSOF"),
('left', "LT", "GT", "LE", "GE", "EQUALS", "NOTEQUALS"), ('left', "LT", "GT", "LE", "GE", "EQUALS", "NOTEQUALS"),
('nonassoc', "COMMENT"), ('nonassoc', "COMMENT"),
@ -927,6 +1183,7 @@ def p_expression(p):
| expression '-' expression | expression '-' expression
| expression '*' expression | expression '*' expression
| expression '/' expression | expression '/' expression
| expression INTEGERDIVIDE expression
| expression LT expression | expression LT expression
| expression GT expression | expression GT expression
| expression LE expression | expression LE expression
@ -941,21 +1198,21 @@ def p_expression_uminus(p):
""" """
expression : '-' expression %prec UNARY_MINUS expression : '-' expression %prec UNARY_MINUS
""" """
p[0] = UnaryOp(operator=p[1], operand=p[2], sourceref=_token_sref(p, 1)) p[0] = Expression(left=p[2], operator=p[1], right=None, unary=True, sourceref=_token_sref(p, 1))
def p_expression_addressof(p): def p_expression_addressof(p):
""" """
expression : BITAND symbolname %prec UNARY_ADDRESSOF expression : BITAND symbolname %prec UNARY_ADDRESSOF
""" """
p[0] = UnaryOp(operator=p[1], operand=p[2], sourceref=_token_sref(p, 1)) p[0] = AddressOf(name=p[2], sourceref=_token_sref(p, 1))
def p_unary_expression_bitinvert(p): def p_unary_expression_bitinvert(p):
""" """
expression : BITINVERT expression expression : BITINVERT expression
""" """
p[0] = UnaryOp(operator=p[1], operand=p[2], sourceref=_token_sref(p, 1)) p[0] = Expression(left=p[2], operator=p[1], right=None, unary=True, sourceref=_token_sref(p, 1))
def p_expression_group(p): def p_expression_group(p):
@ -1012,7 +1269,10 @@ def p_error(p):
print('\n[ERROR DEBUG: parser state={:d} stack: {} . {} ]'.format(parser.state, stack_state_str, p)) print('\n[ERROR DEBUG: parser state={:d} stack: {} . {} ]'.format(parser.state, stack_state_str, p))
if p: if p:
sref = SourceRef(p.lexer.source_filename, p.lineno, find_tok_column(p)) sref = SourceRef(p.lexer.source_filename, p.lineno, find_tok_column(p))
p.lexer.error_function(sref, "syntax error before '{:.20s}'", str(p.value)) if p.value in ("", "\n"):
p.lexer.error_function(sref, "syntax error before end of line")
else:
p.lexer.error_function(sref, "syntax error before '{:.20s}'", str(p.value).rstrip())
else: else:
lexer.error_function(None, "syntax error at end of input", lexer.source_filename) lexer.error_function(None, "syntax error at end of input", lexer.source_filename)

View File

@ -1,7 +1,7 @@
IL65 / 'Sick' - Experimental Programming Language for 8-bit 6502/6510 microprocessors IL65 / 'Sick' - Experimental Programming Language for 8-bit 6502/6510 microprocessors
===================================================================================== =====================================================================================
*Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0* *Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE* *Software license: GNU GPL 3.0, see file LICENSE*
@ -17,6 +17,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- subroutines have enforced input- and output parameter definitions - subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs) - various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- automatic variable allocations, automatic string variables and string sharing - automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- automatic type conversions - automatic type conversions
- floating point operations - floating point operations
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these - optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
@ -24,6 +25,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
- conditional gotos - conditional gotos
- some code optimizations (such as not repeatedly loading the same value in a register)
- @todo: loops - @todo: loops
- @todo: memory block operations - @todo: memory block operations

View File

@ -1,5 +1,5 @@
from il65.plylex import lexer, tokens, find_tok_column, literals, reserved from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef
from il65.plyparse import parser, TokenFilter, Module, Subroutine, Block, Return from il65.plyparse import parser, TokenFilter, Module, Subroutine, Block, Return, Scope, VarDef, Expression, LiteralValue, Label
def test_lexer_definitions(): def test_lexer_definitions():
@ -26,6 +26,7 @@ test_source = """ %output prg, sys
; comment ; comment
var foo = 42+true
var .matrix(20,30) m = 9.234556 var .matrix(20,30) m = 9.234556
;comment2 ;comment2
@ -47,6 +48,7 @@ def test_lexer():
assert token_types == ['DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'ENDL', 'ENDL', assert token_types == ['DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'ENDL', 'ENDL',
'BITINVERT', 'NAME', 'INTEGER', '{', 'ENDL', 'BITINVERT', 'NAME', 'INTEGER', '{', 'ENDL',
'DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'ENDL', 'DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'ENDL',
'VARTYPE', 'NAME', 'IS', 'INTEGER', '+', 'BOOLEAN', 'ENDL',
'VARTYPE', 'DATATYPE', '(', 'INTEGER', ',', 'INTEGER', ')', 'NAME', 'IS', 'FLOATINGPOINT', 'ENDL', 'ENDL', 'VARTYPE', 'DATATYPE', '(', 'INTEGER', ',', 'INTEGER', ')', 'NAME', 'IS', 'FLOATINGPOINT', 'ENDL', 'ENDL',
'SUB', 'NAME', '(', ')', 'RARROW', '(', ')', '{', 'ENDL', 'RETURN', 'ENDL', '}', 'ENDL', 'ENDL', 'ENDL', 'ENDL', 'SUB', 'NAME', '(', ')', 'RARROW', '(', ')', '{', 'ENDL', 'RETURN', 'ENDL', '}', 'ENDL', 'ENDL', 'ENDL', 'ENDL',
'}', 'ENDL'] '}', 'ENDL']
@ -56,6 +58,10 @@ def test_lexer():
assert directive_token.lineno == 9 assert directive_token.lineno == 9
assert directive_token.lexpos == lexer.lexdata.index("%import") assert directive_token.lexpos == lexer.lexdata.index("%import")
assert find_tok_column(directive_token) == 10 assert find_tok_column(directive_token) == 10
bool_token = tokens[23]
assert bool_token.type == "BOOLEAN"
assert type(bool_token.value) is bool
assert bool_token.value == True
def test_tokenfilter(): def test_tokenfilter():
@ -72,6 +78,7 @@ def test_tokenfilter():
assert token_types == ['DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', assert token_types == ['DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL',
'BITINVERT', 'NAME', 'INTEGER', '{', 'ENDL', 'BITINVERT', 'NAME', 'INTEGER', '{', 'ENDL',
'DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL',
'VARTYPE', 'NAME', 'IS', 'INTEGER', '+', 'BOOLEAN', 'ENDL',
'VARTYPE', 'DATATYPE', '(', 'INTEGER', ',', 'INTEGER', ')', 'NAME', 'IS', 'FLOATINGPOINT', 'ENDL', 'VARTYPE', 'DATATYPE', '(', 'INTEGER', ',', 'INTEGER', ')', 'NAME', 'IS', 'FLOATINGPOINT', 'ENDL',
'SUB', 'NAME', '(', ')', 'RARROW', '(', ')', '{', 'ENDL', 'RETURN', 'ENDL', '}', 'ENDL', 'SUB', 'NAME', '(', ')', 'RARROW', '(', ')', '{', 'ENDL', 'RETURN', 'ENDL', '}', 'ENDL',
'}', 'ENDL'] '}', 'ENDL']
@ -93,10 +100,17 @@ def test_parser():
block = result.scope["block"] block = result.scope["block"]
assert isinstance(block, Block) assert isinstance(block, Block)
assert block.name == "block" assert block.name == "block"
assert block.nodes is block.scope.nodes
bool_vdef = block.scope.nodes[1]
assert isinstance(bool_vdef, VarDef)
assert isinstance(bool_vdef.value, Expression)
assert isinstance(bool_vdef.value.right, LiteralValue)
assert isinstance(bool_vdef.value.right.value, bool)
assert bool_vdef.value.right.value == True
assert block.address == 49152 assert block.address == 49152
sub2 = block.scope["calculate"] sub2 = block.scope["calculate"]
assert sub2 is sub assert sub2 is sub
assert sub2.lineref == "src l. 18" assert sub2.lineref == "src l. 19"
all_scopes = list(result.all_scopes()) all_scopes = list(result.all_scopes())
assert len(all_scopes) == 3 assert len(all_scopes) == 3
assert isinstance(all_scopes[0][0], Module) assert isinstance(all_scopes[0][0], Module)
@ -108,4 +122,16 @@ def test_parser():
stmt = list(all_scopes[2][0].scope.filter_nodes(Return)) stmt = list(all_scopes[2][0].scope.filter_nodes(Return))
assert len(stmt) == 1 assert len(stmt) == 1
assert isinstance(stmt[0], Return) assert isinstance(stmt[0], Return)
assert stmt[0].lineref == "src l. 19" assert stmt[0].lineref == "src l. 20"
def test_block_nodes():
sref = SourceRef("file", 1, 1)
sub1 = Subroutine(name="subaddr", param_spec=[], result_spec=[], address=0xc000, sourceref=sref)
sub2 = Subroutine(name="subblock", param_spec=[], result_spec=[],
scope=Scope(nodes=[Label(name="start", sourceref=sref)], sourceref=sref), sourceref=sref)
assert sub1.scope is None
assert sub1.nodes == []
assert sub2.scope is not None
assert len(sub2.scope.nodes) > 0
assert sub2.nodes is sub2.scope.nodes

View File

@ -10,14 +10,18 @@
%saveregisters true %saveregisters true
const num = 2 const num = 2 + max(2, 8, 3.44//3)
var var1 =2 const pi_val = 22/7 - 2.23423
var .word wvar1 = 2 + foo() ; @todo constant check error var var1 =2 + 9/4
var .word wvar2 = 2 + cos(23.2)
memory memvar = $d020
var .word test2b = &memvar
var test3 = var1
start: start:
wvar1 = 2+foo() wvar1 = 2+foo()+emptysub2
A=math.randbyte() A=math.randbyte()
A += c64.RASTER A += c64.RASTER
@ -212,11 +216,11 @@ sub emptysub () -> () {
%saveregisters %saveregisters
%breakpoint %breakpoint
return return 999990 + (2*sin(1.0)) + foo(), 999990 -1, 999999
} }
~ { ~ {
;sdfsdf ;sdfsdf
return return 999, -1, 3.445
;sdfsdf ;sdfsdf
} }