builtin functions are now defined as BuiltinFunction in topmost scope

This commit is contained in:
Irmen de Jong 2018-01-30 22:30:05 +01:00
parent d299742ddf
commit 9b23bfb85c
3 changed files with 102 additions and 18 deletions

View File

@ -31,6 +31,7 @@ class PlyParser:
try:
module = parse_file(filename, self.lexer_error)
self.check_directives(module)
module.scope.define_builtin_functions()
self.process_imports(module)
self.check_all_symbolnames(module)
self.create_multiassigns(module)
@ -431,7 +432,6 @@ class PlyParser:
if not filename:
raise ParseError("imported file not found", directive.sourceref)
imported_module, import_parse_errors = self.import_file(filename)
imported_module.scope.parent_scope = module.scope
imported.append(imported_module)
self.parse_errors += import_parse_errors
if not self.imported_module:
@ -439,7 +439,6 @@ class PlyParser:
filename = self.find_import_file("il65lib", module.sourceref.file)
if filename:
imported_module, import_parse_errors = self.import_file(filename)
imported_module.scope.parent_scope = module.scope
imported.append(imported_module)
self.parse_errors += import_parse_errors
else:

View File

@ -30,7 +30,7 @@ class ZpOptions(enum.Enum):
CLOBBER_RESTORE = "clobber_restore"
math_functions = {name: func for name, func in vars(math).items() if inspect.isbuiltin(func)}
math_functions = {name: func for name, func in vars(math).items() if inspect.isbuiltin(func) and name != "pow"}
builtin_functions = {name: func for name, func in vars(builtins).items() if inspect.isbuiltin(func)}
@ -121,7 +121,6 @@ class Scope(AstNode):
nodes = attr.ib(type=list, init=True) # requires nodes in __init__
symbols = attr.ib(init=False)
name = attr.ib(init=False) # will be set by enclosing block, or subroutine etc.
parent_scope = attr.ib(init=False, default=None) # will be wired up later
_save_registers = attr.ib(type=bool, default=None, init=False)
@property
@ -137,6 +136,13 @@ class Scope(AstNode):
def save_registers(self, save: bool) -> None:
self._save_registers = save
@property
def parent_scope(self) -> Optional['Scope']:
parent_scope = self.parent
while parent_scope and not isinstance(parent_scope, Scope):
parent_scope = parent_scope.parent
return parent_scope
def __attrs_post_init__(self):
# populate the symbol table for this scope for fast lookups via scope.lookup("name") or scope.lookup("dotted.name")
self.symbols = {}
@ -147,28 +153,38 @@ class Scope(AstNode):
def _populate_symboltable(self, node: AstNode) -> None:
if isinstance(node, (Label, VarDef)):
if node.name in self.symbols:
raise ParseError("symbol already defined at {}".format(self.symbols[node.name].sourceref), node.sourceref)
raise ParseError("symbol '{}' already defined at {}".format(node.name, self.symbols[node.name].sourceref), node.sourceref)
self.symbols[node.name] = node
if isinstance(node, Subroutine):
elif isinstance(node, (Subroutine, BuiltinFunction)):
if node.name in self.symbols:
raise ParseError("symbol already defined at {}".format(self.symbols[node.name].sourceref), node.sourceref)
raise ParseError("symbol '{}' already defined at {}".format(node.name, self.symbols[node.name].sourceref), node.sourceref)
self.symbols[node.name] = node
if node.scope:
node.scope.parent_scope = self
if isinstance(node, Block):
elif isinstance(node, Block):
if node.name:
if node.name != "ZP" and node.name in self.symbols:
raise ParseError("symbol already defined at {}".format(self.symbols[node.name].sourceref), node.sourceref)
raise ParseError("symbol '{}' already defined at {}"
.format(node.name, self.symbols[node.name].sourceref), node.sourceref)
self.symbols[node.name] = node
node.scope.parent_scope = self
@no_type_check
def define_builtin_functions(self) -> None:
for name, func in math_functions.items():
f = BuiltinFunction(name=name, func=func, sourceref=self.sourceref)
self.add_node(f)
for name, func in builtin_functions.items():
f = BuiltinFunction(name=name, func=func, sourceref=self.sourceref)
self.add_node(f)
def lookup(self, name: str) -> AstNode:
assert isinstance(name, str)
if '.' in name:
# look up the dotted name starting from the topmost scope
scope = self
while scope.parent_scope:
scope = scope.parent_scope
node = self
while node.parent:
if isinstance(node.parent, Scope):
scope = node.parent
node = node.parent
for namepart in name.split('.'):
if isinstance(scope, (Block, Subroutine)):
scope = scope.scope
@ -182,8 +198,11 @@ class Scope(AstNode):
# find the name in nested scope hierarchy
if name in self.symbols:
return self.symbols[name]
if self.parent_scope:
return self.parent_scope.lookup(name)
parent_scope = self.parent
while parent_scope and not isinstance(parent_scope, Scope):
parent_scope = parent_scope.parent
if parent_scope:
return parent_scope.lookup(name)
raise UndefinedSymbolError("undefined symbol: " + name)
def remove_node(self, node: AstNode) -> None:
@ -387,6 +406,15 @@ class DatatypeNode(AstNode):
}[self.name]
@attr.s(cmp=False, repr=False)
class BuiltinFunction(AstNode):
# This is a pseudo-node that will be artificially injected in the top-most scope,
# to represent all supported built-in functions or math-functions.
# No child nodes.
name = attr.ib(type=str)
func = attr.ib(type=callable)
@attr.s(cmp=False, repr=False)
class Subroutine(AstNode):
# one subnode: the Scope.

View File

@ -1,8 +1,10 @@
import math
import pytest
from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef
from il65.plyparse import parser, connect_parents, TokenFilter, Module, Subroutine, Block, IncrDecr, Scope, \
VarDef, Register, ExpressionWithOperator, LiteralValue, Label, SubCall, Dereference
from il65.datatypes import DataType
VarDef, Register, ExpressionWithOperator, LiteralValue, Label, SubCall, Dereference,\
BuiltinFunction, UndefinedSymbolError
from il65.datatypes import DataType, VarType
def lexer_error(sourceref: SourceRef, fmtstring: str, *args: str) -> None:
@ -296,3 +298,58 @@ def test_incrdecr():
IncrDecr(operator="??", sourceref=sref)
i = IncrDecr(operator="++", sourceref=sref)
assert i.howmuch == 1
def test_symbol_lookup():
sref = SourceRef("test", 1, 1)
var1 = VarDef(name="var1", vartype="const", datatype=DataType.WORD, sourceref=sref)
var1.value = LiteralValue(value=42, sourceref=sref)
var1.value.parent = var1
var2 = VarDef(name="var2", vartype="const", datatype=DataType.FLOAT, sourceref=sref)
var2.value = LiteralValue(value=123.456, sourceref=sref)
var2.value.parent = var2
label1 = Label(name="outerlabel", sourceref=sref)
label2 = Label(name="innerlabel", sourceref=sref)
scope_inner = Scope(nodes=[
label2,
var2
], level="block", sourceref=sref)
scope_inner.name = "inner"
var2.parent = label2.parent = scope_inner
scope_outer = Scope(nodes=[
label1,
var1,
scope_inner
], level="block", sourceref=sref)
scope_outer.name = "outer"
scope_outer.define_builtin_functions()
var1.parent = label1.parent = scope_inner.parent = scope_outer
assert scope_inner.parent_scope is scope_outer
assert scope_outer.parent_scope is None
assert label1.my_scope() is scope_outer
assert var1.my_scope() is scope_outer
assert scope_inner.my_scope() is scope_outer
assert label2.my_scope() is scope_inner
assert var2.my_scope() is scope_inner
with pytest.raises(LookupError):
scope_outer.my_scope()
with pytest.raises(UndefinedSymbolError):
scope_inner.lookup("unexisting")
with pytest.raises(UndefinedSymbolError):
scope_outer.lookup("unexisting")
assert scope_inner.lookup("innerlabel") is label2
assert scope_inner.lookup("var2") is var2
assert scope_inner.lookup("outerlabel") is label1
assert scope_inner.lookup("var1") is var1
with pytest.raises(UndefinedSymbolError):
scope_outer.lookup("innerlabel")
with pytest.raises(UndefinedSymbolError):
scope_outer.lookup("var2")
assert scope_outer.lookup("var1") is var1
assert scope_outer.lookup("outerlabel") is label1
math_func = scope_inner.lookup("sin")
assert isinstance(math_func, BuiltinFunction)
assert math_func.name == "sin" and math_func.func is math.sin
builtin_func = scope_inner.lookup("max")
assert isinstance(builtin_func, BuiltinFunction)
assert builtin_func.name == "max" and builtin_func.func is max