def __init__(self, printfunc=print): self.printfunc = printfunc # The following structures must # already be defined here so that they are retained for shell # input (which is split on several ast.Programs). # Everything that can be accessed with a name/identifier, is stored # either in globals or locals. # Globals is a dictionary that maps global names to instances. It # contains modules, typedefs, functions. # Locals is an array which acts as a stack for local variables. # Top-level statements behave like being encapsulated in an implicit # main()-function, i.e. their variables are not global! # I think that we could always use a self.locals.append() whenever # new names are pushed on the stack (function arguments and in let # statements) but it looks more high-level to access the variables # always in the same way, which is: self.locals[symbol_tree.get_index()]. # To enable this when declaring variables, we use a list that # automatically grows. #self.globals : typing.Dict[str, ] self.locals = StackList() # Modules/Imports, custom types and function definitions are accessed through # the ast.TranslationUnit directly. The evaluator can access/read the symbol table # to identify stuff. # Here, initialized empty, filled with content when evaluating. self.current_unit = TranslationUnitRef( ast.TranslationUnit([], collections.OrderedDict(), collections.OrderedDict(), [], {})) # All imported modules self.modules: typing.Dict[str, ast.TranslationUnit] = {} # self.symbol_tree = SymbolTree()
def __init__( self, symbol_table_snapshot=None, modules: typing.Optional[typing.Dict[str, ast.TranslationUnit]] = None): self.symbol_tree = SymbolTree(symbol_table_snapshot) self.modules: typing.Dict[str, ast.TranslationUnit] = modules if isinstance( modules, dict) else {}
def resolve_function_interface(self, function: ast.FunctionDefinition, unit: ast.TranslationUnit): parameters = bongtypes.TypeList([]) returns = bongtypes.TypeList([]) for param_name, param_type in zip(function.parameter_names, function.parameter_types): typ = self.resolve_type(param_type, unit, function) parameters.append(typ) SymbolTree(function.symbol_tree_snapshot)[param_name] = typ for ret in function.return_types: returns.append(self.resolve_type(ret, unit, function)) unit.symbols_global[function.name] = bongtypes.Function( parameters, returns)
def parse_function_definition(self) -> ast.FunctionDefinition: toks = TokenList() # FUNC foo (bar : int) : str { ... } if not toks.add(self.match(token.FUNC)): raise Exception("Expected function definition.") # func FOO (bar : int) : str { ... } if not toks.add(self.match(token.IDENTIFIER)): raise ParseException("Expected function name.") name = self.peek(-1).lexeme if name in self.symbols_global: raise ParseException(f"Name '{name}' already exists in symbol table. Function definition impossible.") # Register function name before parsing parameter names (no parameter name should have the function name!) self.symbols_global[name] = bongtypes.UnknownType() # ( if not toks.add(self.match(token.LPAREN)): raise ParseException("Expected ( to start the parameter list.") # Parameters parameter_names, parameter_types = self.parse_parameters() # ) if not toks.add(self.match(token.RPAREN)): raise ParseException("Expected ) to end the parameter list.") # Return types return_types : typing.List[ast.BongtypeIdentifier] = [] if toks.add(self.match(token.COLON)): self.check_eof("Return type list expected.") return_types.append(self.parse_type()) while toks.add(self.match(token.COMMA)): return_types.append(self.parse_type()) # { if not self.peek().type == token.LBRACE: raise ParseException("Expected function body.") # New local symbol table (tree) for statement block # We could just store the global symbol table in the object because # it will always be the same. But remembering the previous symbol # table here theoretically allows to parse function definitions inside # other functions (the local symbol table would be properly restored # then). global_symbol_tree = self.symbol_tree self.symbol_tree = SymbolTree() # Parameters for param,typ in zip(parameter_names,parameter_types): if param in self.symbol_tree: raise ParseException(f"Argument name '{param}' appears twice in function definition") self.symbol_tree.register(param, bongtypes.UnknownType()) # Snapshot before block is parsed (this changes the state of the tree) func_symbol_tree_snapshot = self.symbol_tree.take_snapshot() # Function body body = self.block_stmt() # Restore symbol table/tree self.symbol_tree = global_symbol_tree return ast.FunctionDefinition(toks, name, parameter_names, parameter_types, return_types, body, func_symbol_tree_snapshot)
def __init__(self, lexer, snapshot=None, basepath=None): self.lexer = lexer self.basepath = basepath if basepath != None else os.getcwd() self.symbols_global : typing.Dict[str, bongtypes.BaseNode] = {} self.symbol_tree = SymbolTree() if snapshot != None: # When restoring the global dictionary, we need to copy the dict. # Otherwise, we change the snapshot that the caller (the repl) # will (most probably) reuse. self.symbols_global = snapshot[0].copy() # overwrite self.symbol_tree.restore_snapshot(snapshot[1]) # restore else: # Only when initializing symbol tables for the first time, register # builtin stuff for bfuncname, bfunc in bong_builtins.functions.items(): self.symbols_global[bfuncname] = bongtypes.BuiltinFunction(bfunc[1]) for btypename, btype in bongtypes.basic_types.items(): self.symbols_global[btypename] = bongtypes.Typedef(btype())