def interpret(body: d_Body) -> Optional[d_Thing]: """Read text from a body and interpret it as ditlang code. Creates a new InterpretContext and executes statements on tokens, one after another, until EOF. Used recursively. Classes, functions, and imported dits are all interpreted recursively as new bodies with new InterpretContexts. Classes and dits are only interpreted once. Functions are re-interpreted every time they are called.""" if not body.is_ready(): return inter = InterpretContext(body) last_ret: Optional[d_Thing] = None try: at_eof = False while not at_eof: if inter.char_feed.eof(): at_eof = True # one extra iteration for the last char inter.advance_tokens() if inter.next_tok.grammar == d_Grammar.EOF: return last_ret # Only at EOF whitespace or comment else: last_ret = _statement_dispatch(inter) inter.named_statement = False except d_DitError as err: if not err.origin: _generate_origin(err, inter) raise return last_ret
def _trailing_comma(inter: InterpretContext, right: d_Grammar) -> Optional[NoReturn]: if inter.next_tok.grammar not in [d_Grammar.COMMA, right]: _missing_terminal(inter, f"Expected '{right.value}'") if inter.next_tok.grammar == d_Grammar.COMMA: inter.advance_tokens()
def _equals(inter: InterpretContext, equal_loc: CodeLocation) -> None: orig_loc = copy.deepcopy(inter.next_tok.loc) if inter.anon_tok or inter.call_tok: # prevent assignment to anonymous tokens. raise NotImplementedError if inter.dec.type_: assignee = None else: assignee = inter.curr_tok.thing inter.advance_tokens() inter.equaling = True value = _expression_dispatch(inter) inter.equaling = False if not value: if inter.named_statement: # Prevent assignment of non-anonymous statements. # Class anonName = class RealName {||} raise d_SyntaxError( "A named declaration cannot be used for assignment", orig_loc) else: raise d_CriticalError( "No value for _expression_dispatch in _equals") if assignee: assignee.set_value(value) else: try: _add_attr_wrap(inter, value=value) except d_TypeMismatchError as err: err.loc = equal_loc raise
def _bool(inter: InterpretContext) -> d_Bool: # b = true; # doThing(false); val = d_Bool() val.is_null = False val.bool_ = inter.next_tok.grammar == d_Grammar.TRUE inter.advance_tokens() return val
def _sig(inter: InterpretContext) -> Optional[d_Func]: # sig JavaScript Str ... \n func dotable_loc = None func = _sig_or_func(inter) did_dispatch = False while True: if not did_dispatch: inter.advance_tokens() did_dispatch = False gra = inter.next_tok.grammar thing = inter.next_tok.thing switches = [func.lang, func.return_, func.return_list] # listOf needs additional checking # There must be a type after it, and not before it. if func.return_list and not func.return_: # sig ... listOf ... if gra == d_Grammar.VOID: raise d_SyntaxError("Cannot have listOf void") elif gra not in TYPES and gra not in DOTABLES: raise d_SyntaxError("Expected type to follow listOf", dotable_loc) elif gra in DOTABLES: dotable_loc = copy.deepcopy(inter.next_tok.loc) if gra == d_Grammar.FUNC: return _func(inter) elif gra == d_Grammar.EOF or None not in switches: raise d_SyntaxError("Expected 'func' to follow sig") elif thing: # handles dotables and explicit Lang/Class objects _sig_thing_handler(inter, func) did_dispatch = True elif gra in TYPES or gra == d_Grammar.VOID: # sig ... Str ... # sig ... void ... _sig_assign_return(inter, func) func.return_ = prim_to_value(gra) elif gra == d_Grammar.LISTOF: # sig ... listOf ... if func.return_: raise d_SyntaxError("Unexpected 'listOf' after type") func.return_list = True elif inter.next_tok.grammar == d_Grammar.NEW_NAME: raise NotImplementedError # TODO: finish unassigned langs feature, issue #14 # We allow langs to be declared on the fly. This lets library dits # specify a language without having to import it, which would be annoying. # The lang must be more fully assigned in the dit where it will be called. lang = d_Lang() lang.is_null = False lang.parent_scope = inter.body lang.name = inter.next_tok.word inter.body.attrs[d_Variable(lang.name)] = lang func.lang = lang else: raise d_SyntaxError("Unrecognized token for signature")
def _value_equalable(inter: InterpretContext) -> Optional[d_Thing]: # Thing test; # test = 'cat'; # or, in an expression... # Thing test = 'cat'; # someFunc(test); # These code examples are applicable to most _value_X functions inter.advance_tokens() return _equalable(inter)
def _finalize_num(inter: InterpretContext, num: str) -> d_Num: # advance to terminal # num = 3.14; inter.advance_tokens() fin_num = d_Num() fin_num.is_null = False try: fin_num.num = int(num) except ValueError: fin_num.num = float(num) return fin_num
def _digit_sign(inter: InterpretContext, neg: bool) -> d_Num: # make sure the sign is being used as a positive or negative, # not for arithmetic if DIGIT.match(inter.char_feed.current()): inter.advance_tokens() return _digit(inter, neg) else: raise d_SyntaxError( "Expected digit.\nOther arithmetic ops are not yet supported.", inter.char_feed.loc, # Default uses inter.next_tok.lok )
def _listof(inter: InterpretContext) -> None: # listOf Str values; inter.advance_tokens() inter.dec.listof = True if inter.next_tok.grammar in PRIMITIVES: _primitive(inter) elif inter.next_tok.grammar in DOTABLES: _expression_dispatch(inter) else: raise d_SyntaxError("Expected type for listOf declaration")
def _return(inter: InterpretContext) -> NoReturn: orig_loc = copy.deepcopy(inter.next_tok.loc) if not isinstance(inter.body, d_Func): raise d_SyntaxError("'return' outside of function") inter.advance_tokens() value = _expression_dispatch(inter) if not value: raise NotImplementedError try: raise ReturnController(value, inter.body, orig_loc) except d_TypeMismatchError as err: err.loc = orig_loc raise
def _brace_left(inter: InterpretContext) -> d_JSON: # JSON j = { ... inter.in_json = True js = d_JSON() js.is_null = False js.json_ = {} inter.advance_tokens() while True: if inter.next_tok.grammar == d_Grammar.BRACE_RIGHT: # JSON j = { ... } inter.in_json = False inter.advance_tokens() return js elif inter.next_tok.grammar == d_Grammar.QUOTE_DOUBLE: # JSON j = { "item1": ... if inter.curr_tok.grammar not in [ d_Grammar.BRACE_LEFT, d_Grammar.COMMA ]: raise d_SyntaxError("Expected ','", inter.curr_tok.loc) name = _str(inter).str_ if inter.next_tok.grammar != d_Grammar.COLON: raise d_SyntaxError("Expected ':'", inter.next_tok.loc) else: inter.advance_tokens() ele = _expression_dispatch(inter) js.json_[name] = ele elif inter.next_tok.grammar == d_Grammar.COMMA: # JSON j = { "item1": 1, ... inter.advance_tokens() if inter.next_tok.grammar == d_Grammar.BRACE_RIGHT: raise d_SyntaxError("Trailing commas are not allowed", inter.curr_tok.loc) else: raise d_SyntaxError("Unexpected token for JSON")
def _bar_brace_left(inter: InterpretContext, body: d_Body) -> None: depth = 1 body.start_loc = copy.deepcopy(inter.char_feed.loc) while depth > 0: cur = inter.char_feed.current() + inter.char_feed.peek() if cur == d_Grammar.BAR_BRACE_LEFT.value: depth += 1 inter.advance_tokens() elif cur == d_Grammar.BAR_BRACE_RIGHT.value: depth -= 1 body.end_loc = copy.deepcopy(inter.char_feed.loc) inter.advance_tokens() else: inter.char_feed.pop()
def _new_name(inter: InterpretContext) -> None: inter.dec.name = inter.next_tok.word if not inter.dec.type_: # new name without a type declaration is only allowed with an equals # unknownName = ... inter.advance_tokens() if inter.next_tok.grammar != d_Grammar.EQUALS: raise d_NameError( f"Undefined variable '{inter.curr_tok.word}'", inter.curr_tok.loc, ) # if there is an equals, then we just pretend the declaration was 'Thing' inter.dec.type_ = d_Grammar.PRIMITIVE_THING equal_loc = copy.deepcopy(inter.next_tok.loc) _equals(inter, equal_loc) inter.dec.reset() _terminal(inter)
def _arg_list(inter: InterpretContext, right: d_Grammar) -> List[ArgumentLocation]: # someFunc('arg1', 'arg2'); # listOf Str = ['a', 'b', ['c'], 'd']; args: List[ArgumentLocation] = [] inter.advance_tokens() inter.comma_depth += 1 while True: if inter.next_tok.grammar == right: inter.comma_depth -= 1 inter.advance_tokens() return args loc = copy.deepcopy(inter.next_tok.loc) arg = _expression_dispatch(inter) if not arg: raise NotImplementedError args.append(ArgumentLocation(loc, arg)) _trailing_comma(inter, right)
def _type(inter: InterpretContext) -> None: # Str test ... # someClass test ... # Str someDotable.monkeyPatch ... # This function is reused by _primitive and _value_class inter.dec.type_ = _token_to_type(inter.curr_tok) if inter.next_tok.grammar in DUPLICABLES: raise d_SyntaxError( f"'{inter.next_tok.thing.name}' has already been declared") if inter.next_tok.grammar in NAMEABLES: _expression_dispatch(inter) else: raise d_SyntaxError("Expected a new name to follow type") if inter.next_tok.grammar == d_Grammar.NEW_NAME: # to handle the specific case when a lang is being legally redeclared # The tokens were already advanced in _value_lang. inter.advance_tokens() _equalable(inter)
def _str(inter: InterpretContext) -> d_Str: # note that _str is reused for parsing JSON element names left = inter.next_tok.grammar.value data = "" if inter.char_feed.current() != left: while True: if inter.char_feed.current() == "\n": lok = copy.deepcopy(inter.next_tok.loc) length = len(data) lok.pos += length lok.col += length raise d_SyntaxError("Unexpected EOL while reading string", lok) elif inter.char_feed.current() == d_Grammar.BACKSLASH.value: # Str test = "some\t" # Str test = 'Let\'s' escape_char = inter.char_feed.pop() inter.char_feed.pop() if escape_char in [ d_Grammar.QUOTE_DOUBLE.value, d_Grammar.QUOTE_SINGLE.value, d_Grammar.BACKSLASH.value, ]: data += escape_char elif escape_char == d_Grammar.ESCAPE_NEWLINE.value: data += "\n" elif escape_char == d_Grammar.ESCAPE_TAB.value: data += "\t" else: data += inter.char_feed.current() inter.char_feed.pop() if inter.char_feed.current() == left: break inter.advance_tokens() # next_tok is now ' " inter.advance_tokens() # next_tok is now ; , ] ) : thing = d_Str() thing.str_ = data thing.is_null = False return thing
def _dot(inter: InterpretContext) -> Token: inter.advance_tokens(False) # We want to manage the next word ourselves if inter.next_tok.grammar != d_Grammar.WORD: raise d_SyntaxError(f"'{inter.next_tok.grammar}' is not dotable") # Allows dotting of anonymous tokens and function calls. target = (inter.anon_tok or inter.call_tok or inter.prev_tok).thing if isinstance(target, d_Inst): # We need to prepare in case the instance has inherited parents target.clear_prefix_to_func() inter.dotted_inst = target elif not isinstance(target, d_Container): raise d_CriticalError(f"Expected container, got {target.public_type}") result = target.find_attr(inter.next_tok.word) if not result and inter.dotted_inst: # this.B.attribute # We are doing explicit name disambiguation, looking in parent B instead of A. # B obviously doesn't have the attribute, the instance does. # So we look again in the instance. result = inter.dotted_inst.find_attr(inter.next_tok.word) if result: # The dot had a known variable, which we just need to return if isinstance(result, d_Func) and inter.dotted_inst: # this.func() # if `func` is an inherited function, we need to remember that we # called it so the prefixes are set up correctly. inter.dotted_inst.add_func_sep() return Token(result.grammar, copy.deepcopy(inter.next_tok.loc), thing=result) else: # The name was not found in the dotted body, so its a new name. # This means we are declaring a new var in the dotted body. # AKA Monkey patching inter.next_tok.grammar = d_Grammar.NEW_NAME inter.dotted_body = target # Save for assignment return inter.next_tok
def _import(inter: InterpretContext) -> Optional[d_Dit]: # import LINK # import NAMESPACE from LINK orig_loc = copy.deepcopy(inter.next_tok.loc) name = None inter.advance_tokens(False) gra = inter.next_tok.grammar if gra != d_Grammar.WORD and gra not in STRINGABLES: raise d_SyntaxError("Expected a name or filepath string for import") if gra == d_Grammar.WORD: # import SomeName from "someFilePath.dit"; if inter.body.find_attr(inter.next_tok.word, scope_mode=True): raise d_SyntaxError( f"'{inter.next_tok.word}' has already been declared") name = inter.next_tok.word inter.advance_tokens() if inter.next_tok.grammar != d_Grammar.FROM: raise d_SyntaxError("Expected 'from'") inter.advance_tokens() dit = _import_or_pull(inter, orig_loc) dit.name = name # type: ignore return _import_or_pull_end(inter, dit, orig_loc)
def _value_clang(inter: InterpretContext) -> Optional[d_Thing]: if inter.declaring_func: # sig someLang someClass... # This class/lang is being used as a return type or language # in a function signature. inter.advance_tokens(False) if inter.next_tok.grammar == d_Grammar.DOT: return _dotable(inter) else: return inter.curr_tok.thing inter.advance_tokens(True) if (isinstance(inter.curr_tok.thing, d_Class) and inter.next_tok.grammar == d_Grammar.PAREN_LEFT): # This class is being instantiated # someClass anInstance = someClass(); return _parenable(inter) elif (isinstance(inter.curr_tok.thing, d_Lang) and inter.dec.type_ is d_Grammar.PRIMITIVE_LANG): # This lang is being legally redeclared # lang JavaScript {||} # Lang JavaScript ... return elif inter.next_tok.grammar in VALUE_CLANG_ABLES: # someClass = someOtherClass; # someLang.someLangFunc(); # This class is being dotted, equaled, etc. return _dotable(inter) elif inter.anon_tok: # class {||} someInstance = ?... this makes no sense. # This prevents using an anonymous class as a type # Triggering terminal will call missing ';' _terminal(inter) else: # someClass someInstance... # This class is being used as var type _type(inter)
def _clang(inter: InterpretContext, clang: Union[d_Class, d_Lang]) -> Optional[Union[d_Class, d_Lang]]: # class/lang NAME {||} # class/lang {||}; <- Anonymous version lang = None orig_loc = copy.deepcopy(inter.next_tok.loc) clang_name = "class" if isinstance(clang, d_Class) else "lang" inter.advance_tokens(False) if inter.next_tok.grammar not in [ d_Grammar.WORD, d_Grammar.BAR_BRACE_LEFT ]: raise d_SyntaxError(f"Expected name or body to follow {clang_name}") if inter.next_tok.grammar == d_Grammar.WORD: result = inter.body.find_attr(inter.next_tok.word, scope_mode=True) if result: if isinstance(result, d_Lang): # Langs are allowed to be redeclared # lang someLang {||} # lang someLang {||} lang = result else: raise d_SyntaxError( f"'{inter.next_tok.word}' has already been declared") clang.name = inter.next_tok.word inter.advance_tokens() # get {| if inter.next_tok.grammar != d_Grammar.BAR_BRACE_LEFT: raise d_SyntaxError(f"Expected a {clang_name} body") _bar_brace_left(inter, clang) clang.finalize() try: interpret(clang) except d_EndOfFileError as err: raise d_EndOfClangError(clang_name) from err return _handle_anon(inter, clang, orig_loc, lang) # type: ignore
def _pull(inter: InterpretContext) -> None: # pull TARGET (as REPLACEMENT), ... from LINK orig_loc = copy.deepcopy(inter.next_tok.loc) targets: List[Tuple[str, Optional[str]]] = [] langs: List[Optional[d_Lang]] = [] while True: # pull ... # pull THING, ... # pull THING as NAME, ... inter.advance_tokens(False) if inter.next_tok.grammar != d_Grammar.WORD: raise d_SyntaxError("Expected name to pull from linked dit") # pull NAME ... target = inter.next_tok.word loc = copy.deepcopy(inter.next_tok.loc) replacement = None inter.advance_tokens() if inter.next_tok.grammar == d_Grammar.AS: # pull NAME as ... inter.advance_tokens(False) if inter.next_tok.grammar != d_Grammar.WORD: raise d_SyntaxError("Expected name to replace target name") replacement = inter.next_tok.word loc = inter.next_tok.loc inter.advance_tokens() name = replacement or target result = inter.body.find_attr(name, True) if result: if isinstance(result, d_Lang): # Langauges can overwrite langs already in this dit # lang someLang {||} # pull someLang from LINK langs.append(result) else: raise d_SyntaxError(f"'{name}' has already been declared", loc) else: langs.append(None) targets.append((target, replacement)) if inter.next_tok.grammar == d_Grammar.FROM: break elif inter.next_tok.grammar == d_Grammar.COMMA: continue else: raise d_SyntaxError("Expected 'from' or ',' to follow target") inter.advance_tokens() dit = _import_or_pull(inter, orig_loc) for (tar, rep), lang in zip(targets, langs): # pull TARGET (as REPLACEMENT), ... from LINK result = dit.find_attr(tar) if not result: raise d_SyntaxError(f"'{tar}' is not a valid member of this dit") result.name = rep or tar if lang and isinstance(result, d_Lang): # explicit call to set_value, to activate Priority comparisons lang.set_value(result) result.attrs = lang.attrs # TODO: proper unassigned lang logic else: inter.body.attrs[d_Variable(result.name)] = result _import_or_pull_end(inter, dit, orig_loc)
def _primitive(inter: InterpretContext) -> None: # Str value; inter.advance_tokens() _type(inter)
def _value_inst(inter: InterpretContext) -> Optional[d_Thing]: inter.advance_tokens() return _dotable(inter)
def _value_function(inter: InterpretContext) -> Optional[d_Thing]: inter.advance_tokens() return _parenable(inter)
def _value_dit(inter: InterpretContext) -> Optional[d_Thing]: if not inter.anon_tok: inter.advance_tokens() return _dotable(inter)
def _null(inter: InterpretContext) -> d_Thing: # someThing = null; inter.advance_tokens() return d_Thing.get_null_thing()
def _func(inter: InterpretContext) -> Optional[d_Func]: # func test(Str right, Str left) {||} # func () {||} orig_loc = copy.deepcopy(inter.next_tok.loc) func = _sig_or_func(inter) if not func.return_: func.return_ = d_Grammar.VOID if not func.return_list: func.return_list = False if not func.lang: func.lang = b_Ditlang inter.advance_tokens(False) if inter.next_tok.grammar == d_Grammar.WORD: # func someName result: d_Thing = inter.body.find_attr(inter.next_tok.word, scope_mode=True) # type: ignore if result: raise d_SyntaxError(f"'{result.name}' has already been declared") else: func.name = inter.next_tok.word # Advance only if the name was there # If no name, then this is an anonymous function inter.advance_tokens() if inter.next_tok.grammar != d_Grammar.PAREN_LEFT: # func Ditlang void someName( raise d_SyntaxError("Expected parameter list") inter.advance_tokens() while True: if inter.next_tok.grammar == d_Grammar.PAREN_RIGHT: inter.advance_tokens() break param_list = False if inter.next_tok.grammar == d_Grammar.LISTOF: param_list = True inter.advance_tokens() if inter.next_tok.grammar in DOTABLES: # someName(numLib.Number result: d_Thing = _expression_dispatch(inter) # type: ignore if result.grammar == d_Grammar.VALUE_CLASS: param_type = _token_to_type(inter.curr_tok) else: mes = ("Expected class for parameter type, " f"'{result.name}' is of type '{result.public_type}'") raise d_SyntaxError(mes, inter.terminal_loc) elif inter.next_tok.grammar in PRIMITIVES: # someName(d_String param_type = _token_to_type(inter.next_tok) inter.advance_tokens(False) else: raise d_SyntaxError("Expected parameter type") if inter.next_tok.grammar != d_Grammar.WORD: raise d_SyntaxError("Expected parameter name") else: # someName(d_String someParam param_name = inter.next_tok.word result: d_Thing = inter.body.find_attr( param_name, scope_mode=True) # type: ignore if result: raise d_SyntaxError( f"'{param_name}' has already been declared") elif param_name in [p.name for p in func.parameters]: raise d_SyntaxError( f"'{param_name}' is already a parameter name") func.parameters.append(Declarable(param_type, param_name, param_list)) inter.advance_tokens() _trailing_comma(inter, d_Grammar.PAREN_RIGHT) if inter.next_tok.grammar != d_Grammar.BAR_BRACE_LEFT: raise d_SyntaxError("Expected function body") _bar_brace_left(inter, func) func.finalize() inter.declaring_func = None # type: ignore return _handle_anon(inter, func, orig_loc) # type: ignore