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 _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 _import_or_pull(inter: InterpretContext, orig_loc: CodeLocation) -> d_Dit: # The next token is always the link. We just need to get the dit and return it. # import LINK # import NAMESPACE from LINK # pull ... from LINK dit = d_Dit() dit.is_null = False if inter.next_tok.grammar in [d_Grammar.NULL, d_Grammar.VALUE_NULL]: raise d_SyntaxError("Cannot import from null") elif inter.next_tok.grammar not in STRINGABLES: raise d_SyntaxError("Expected a filepath string for import") value = _expression_dispatch(inter) if inter.dec.name and not inter.equaling: dit.path = inter.dec.name inter.dec.name = None # type: ignore elif not value: raise NotImplementedError elif isinstance(value, d_Str): dit.path = value.str_ else: raise d_SyntaxError(f"Expected str value, not {value.public_type}") dit.finalize() try: interpret(dit) except d_DitError as err: err.add_trace(dit.path, orig_loc, "import") raise return dit
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 _sig_thing_handler(inter: InterpretContext, func: d_Func) -> None: thing = _expression_dispatch(inter) if not thing: raise NotImplementedError elif isinstance(thing, d_Lang): if func.lang: raise d_SyntaxError("Language was already assigned") func.lang = thing elif isinstance(thing, d_Class): _sig_assign_return(inter, func) func.return_ = thing else: mes = ("Expected Class or Lang, " f"'{thing.name}' is of type '{thing.public_type}'") raise d_SyntaxError(mes, inter.terminal_loc)
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 get_token(self, find_word: bool = True) -> Token: if self.eof: return _handle_eof(self) res = _clear_whitespace_and_comments(self) if res: return res res = _find_double_chars(self) if res: return res res = _find_single_chars(self) if res: return res res = _find_digit(self) if res: return res res = _find_words(self, find_word) if res: return res raise d_SyntaxError(f"Unrecognized token '{self.char_feed.current()}'")
def _equalable(inter: InterpretContext) -> Optional[d_Thing]: if (isinstance(inter.curr_tok.thing, d_Lang) and inter.prev_tok.grammar == d_Grammar.PRIMITIVE_LANG): # Since Langs can be redeclared, we just remove the type_ # and pretend it was never there in the first place. # Lang JavaScript ... inter.dec.type_ = None # type: ignore if inter.dec.type_ and not inter.dec.name: # _new_name should have been found, not an existing variable. # Str existingValue ... raise d_SyntaxError( f"'{inter.curr_tok.thing.name}' has already been declared") elif inter.next_tok.grammar == d_Grammar.EQUALS: # Assign existing or new variables # Str value = ... equal_loc = copy.deepcopy(inter.next_tok.loc) _equals(inter, equal_loc) inter.dec.reset() elif inter.dec.type_ and not inter.equaling: # create variable without assignment # Str count; _add_attr_wrap(inter) inter.dec.reset() return _terminal(inter)
def get_prop(self, name: str) -> str: res = self.find_attr(name) if not res: raise d_MissingPropError(self.name, name) elif not isinstance(res, d_Str): raise d_SyntaxError( f"All properties must be strings. Lang {self.name} with property {name}" ) return res.str_
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 _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 _get_func_args(inter: InterpretContext, func: d_Func) -> None: func.call_loc = copy.deepcopy(inter.curr_tok.loc) arg_locs = _arg_list(inter, d_Grammar.PAREN_RIGHT) miss = abs(len(func.parameters) - len(arg_locs)) name = func.pub_name() for param, arg_loc in zip_longest(func.parameters, arg_locs): param: Declarable arg_loc: ArgumentLocation if not arg_loc: raise d_SyntaxError(f"{name} missing {miss} required arguments") elif not param: # TODO: implement proper k-args functionality raise d_SyntaxError(f"{name} given {miss} too many arguments") else: res = check_value(arg_loc.thing, param) if res: o = f"{name} expected '{res.expected}', got '{res.actual}'{res.extra}" raise d_TypeMismatchError(o, arg_loc.loc) func.add_attr(param, arg_loc.thing, use_ref=True)
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 _make(inter: InterpretContext) -> d_Func: class_: d_Class = inter.curr_tok.thing # type: ignore make = class_.find_attr(MAKE) if not make: raise d_SyntaxError(f"Class '{class_.name}' does not define a Make") elif isinstance(make, d_Func): func: d_Func = make inst = d_Inst() inst.is_null = False inst.parent = class_ func.add_attr(Declarable(class_, THIS), inst, use_ref=True) return func else: raise NotImplementedError
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 _digit(inter: InterpretContext, neg: bool = False) -> d_Num: num = inter.next_tok.word num = "-" + num if neg else num lead_zero = num == "0" frac = False exp = False while True: cur = inter.char_feed.current() if DIGIT.match(cur): # 3 if lead_zero and len(num) == 1: raise d_SyntaxError("Leading zeros are not allowed") num += cur inter.char_feed.pop() lead_zero = False elif cur == ".": # 3. if frac == True: raise d_SyntaxError("Invalid fraction syntax") num += cur inter.char_feed.pop() frac == True elif cur == "e" or cur == "E": # 3e ... 10 if exp == True: raise d_SyntaxError("Invalid exponent syntax") num += cur inter.char_feed.pop() exp == True elif cur == "-" or cur == "+": # -3 num += cur inter.char_feed.pop() else: break return _finalize_num(inter, num)
def _run_func(inter: InterpretContext, func: d_Func) -> Token: try: if func.is_built_in: if func.name == "getConfig": _handle_get_config(inter, func) else: func.py_func(func) elif func.lang is b_Ditlang: interpret(func) else: if not func.code: raise ReturnController(d_Thing.get_null_thing(), func, func.call_loc) _job_loop(GuestDaemonJob(JobType.CALL_FUNC, func)) except d_CodeError as err: err.loc = func.call_loc # err.set_origin( # inter.body.path, func.call_loc, inter.char_feed.get_line(func.call_loc) # ) raise err except d_DitError as err: err.add_trace(func.path, func.call_loc, func.name) raise err except ReturnController as ret: if func.lang is not b_Ditlang: job = GuestDaemonJob(JobType.RETURN_KEYWORD, func) run_job(job) return ret.token except d_TypeMismatchError as mis: raise NotImplementedError else: if func.return_ and func.return_ != d_Grammar.VOID: raise d_SyntaxError(f"{func.pub_name()} expected a return", func.call_loc) elif func.name == MAKE: thing = func.find_attr(THIS) if not thing: raise NotImplementedError return Token(d_Grammar.VALUE_INST, func.call_loc, thing=thing) else: # func ended without 'return' keyword return Token(d_Grammar.NULL, func.call_loc)
def _missing_terminal(inter: InterpretContext, message: str) -> NoReturn: tok = inter.curr_tok target = tok.loc code = inter.char_feed.get_line(target) if isinstance(tok.grammar.value, str): length = len(tok.grammar.value) # class, Str, = elif tok.thing: length = len(tok.thing.name) # Object names else: length = len(tok.word) # New Names # Shift locaton to end of token target.pos += length target.col += length err = d_SyntaxError(message, target) err.set_origin(inter.body.path, code) raise err
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 _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 _illegal_statement(inter: InterpretContext) -> NoReturn: raise d_SyntaxError("Illegal start of statement")
def _sig_assign_return(inter: InterpretContext, func: d_Func) -> None: if func.return_: raise d_SyntaxError("Return type was already assigned") func.return_list = bool(func.return_list)
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 _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
def _not_implemented(inter: InterpretContext) -> NoReturn: raise d_SyntaxError("This keyword is reserved for later development")
def _illegal_expression(inter: InterpretContext) -> NoReturn: raise d_SyntaxError("Illegal start of expression")