mirror of
https://github.com/irmen/prog8.git
synced 2025-02-04 02:30:19 +00:00
builtin functions are now defined as BuiltinFunction in topmost scope
This commit is contained in:
parent
d299742ddf
commit
9b23bfb85c
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user