Make your own programming language in Python
unknown
python
4 years ago
63 kB
9
Indexable
####################################### # IMPORTS ####################################### from strings_with_arrows import * import string import os import math ####################################### # CONSTANTS ####################################### DIGITS = '0123456789' LETTERS = string.ascii_letters LETTERS_DIGITS = LETTERS + DIGITS ####################################### # ERRORS ####################################### class Error: def __init__(self, pos_start, pos_end, error_name, details): self.pos_start = pos_start self.pos_end = pos_end self.error_name = error_name self.details = details def as_string(self): result = f'{self.error_name}: {self.details}\n' result += f'File {self.pos_start.fn}, line {self.pos_start.ln + 1}' result += '\n\n' + string_with_arrows(self.pos_start.ftxt, self.pos_start, self.pos_end) return result class IllegalCharError(Error): def __init__(self, pos_start, pos_end, details): super().__init__(pos_start, pos_end, 'Illegal Character', details) class ExpectedCharError(Error): def __init__(self, pos_start, pos_end, details): super().__init__(pos_start, pos_end, 'Expected Character', details) class InvalidSyntaxError(Error): def __init__(self, pos_start, pos_end, details=''): super().__init__(pos_start, pos_end, 'Invalid Syntax', details) class RTError(Error): def __init__(self, pos_start, pos_end, details, context): super().__init__(pos_start, pos_end, 'Runtime Error', details) self.context = context def as_string(self): result = self.generate_traceback() result += f'{self.error_name}: {self.details}' result += '\n\n' + string_with_arrows(self.pos_start.ftxt, self.pos_start, self.pos_end) return result def generate_traceback(self): result = '' pos = self.pos_start ctx = self.context while ctx: result = f' File {pos.fn}, line {str(pos.ln + 1)}, in {ctx.display_name}\n' + result pos = ctx.parent_entry_pos ctx = ctx.parent return 'Traceback (most recent call last):\n' + result ####################################### # POSITION ####################################### class Position: def __init__(self, idx, ln, col, fn, ftxt): self.idx = idx self.ln = ln self.col = col self.fn = fn self.ftxt = ftxt def advance(self, current_char=None): self.idx += 1 self.col += 1 if current_char == '\n': self.ln += 1 self.col = 0 return self def copy(self): return Position(self.idx, self.ln, self.col, self.fn, self.ftxt) ####################################### # TOKENS ####################################### TT_INT = 'INT' TT_FLOAT = 'FLOAT' TT_STRING = 'STRING' TT_IDENTIFIER = 'IDENTIFIER' TT_KEYWORD = 'KEYWORD' TT_PLUS = 'PLUS' TT_MINUS = 'MINUS' TT_MUL = 'MUL' TT_DIV = 'DIV' TT_POW = 'POW' TT_EQ = 'EQ' TT_LPAREN = 'LPAREN' TT_RPAREN = 'RPAREN' TT_LSQUARE = 'LSQUARE' TT_RSQUARE = 'RSQUARE' TT_EE = 'EE' TT_NE = 'NE' TT_LT = 'LT' TT_GT = 'GT' TT_LTE = 'LTE' TT_GTE = 'GTE' TT_COMMA = 'COMMA' TT_ARROW = 'ARROW' TT_NEWLINE = 'NEWLINE' TT_EOF = 'EOF' KEYWORDS = [ 'VAR', 'AND', 'OR', 'NOT', 'IF', 'ELIF', 'ELSE', 'FOR', 'TO', 'STEP', 'WHILE', 'FUN', 'THEN', 'END', 'RETURN', 'CONTINUE', 'BREAK', ] class Token: def __init__(self, type_, value=None, pos_start=None, pos_end=None): self.type = type_ self.value = value if pos_start: self.pos_start = pos_start.copy() self.pos_end = pos_start.copy() self.pos_end.advance() if pos_end: self.pos_end = pos_end.copy() def matches(self, type_, value): return self.type == type_ and self.value == value def __repr__(self): if self.value: return f'{self.type}:{self.value}' return f'{self.type}' ####################################### # LEXER ####################################### class Lexer: def __init__(self, fn, text): self.fn = fn self.text = text self.pos = Position(-1, 0, -1, fn, text) self.current_char = None self.advance() def advance(self): self.pos.advance(self.current_char) self.current_char = self.text[self.pos.idx] if self.pos.idx < len(self.text) else None def make_tokens(self): tokens = [] while self.current_char != None: if self.current_char in ' \t': self.advance() elif self.current_char == '#': self.skip_comment() elif self.current_char in ';\n': tokens.append(Token(TT_NEWLINE, pos_start=self.pos)) self.advance() elif self.current_char in DIGITS: tokens.append(self.make_number()) elif self.current_char in LETTERS: tokens.append(self.make_identifier()) elif self.current_char == '"': tokens.append(self.make_string()) elif self.current_char == '+': tokens.append(Token(TT_PLUS, pos_start=self.pos)) self.advance() elif self.current_char == '-': tokens.append(self.make_minus_or_arrow()) elif self.current_char == '*': tokens.append(Token(TT_MUL, pos_start=self.pos)) self.advance() elif self.current_char == '/': tokens.append(Token(TT_DIV, pos_start=self.pos)) self.advance() elif self.current_char == '^': tokens.append(Token(TT_POW, pos_start=self.pos)) self.advance() elif self.current_char == '(': tokens.append(Token(TT_LPAREN, pos_start=self.pos)) self.advance() elif self.current_char == ')': tokens.append(Token(TT_RPAREN, pos_start=self.pos)) self.advance() elif self.current_char == '[': tokens.append(Token(TT_LSQUARE, pos_start=self.pos)) self.advance() elif self.current_char == ']': tokens.append(Token(TT_RSQUARE, pos_start=self.pos)) self.advance() elif self.current_char == '!': token, error = self.make_not_equals() if error: return [], error tokens.append(token) elif self.current_char == '=': tokens.append(self.make_equals()) elif self.current_char == '<': tokens.append(self.make_less_than()) elif self.current_char == '>': tokens.append(self.make_greater_than()) elif self.current_char == ',': tokens.append(Token(TT_COMMA, pos_start=self.pos)) self.advance() else: pos_start = self.pos.copy() char = self.current_char self.advance() return [], IllegalCharError(pos_start, self.pos, "'" + char + "'") tokens.append(Token(TT_EOF, pos_start=self.pos)) return tokens, None def make_number(self): num_str = '' dot_count = 0 pos_start = self.pos.copy() while self.current_char != None and self.current_char in DIGITS + '.': if self.current_char == '.': if dot_count == 1: break dot_count += 1 num_str += self.current_char self.advance() if dot_count == 0: return Token(TT_INT, int(num_str), pos_start, self.pos) else: return Token(TT_FLOAT, float(num_str), pos_start, self.pos) def make_string(self): string = '' pos_start = self.pos.copy() escape_character = False self.advance() escape_characters = { 'n': '\n', 't': '\t' } while self.current_char != None and (self.current_char != '"' or escape_character): if escape_character: string += escape_characters.get(self.current_char, self.current_char) else: if self.current_char == '\\': escape_character = True else: string += self.current_char self.advance() escape_character = False self.advance() return Token(TT_STRING, string, pos_start, self.pos) def make_identifier(self): id_str = '' pos_start = self.pos.copy() while self.current_char != None and self.current_char in LETTERS_DIGITS + '_': id_str += self.current_char self.advance() tok_type = TT_KEYWORD if id_str in KEYWORDS else TT_IDENTIFIER return Token(tok_type, id_str, pos_start, self.pos) def make_minus_or_arrow(self): tok_type = TT_MINUS pos_start = self.pos.copy() self.advance() if self.current_char == '>': self.advance() tok_type = TT_ARROW return Token(tok_type, pos_start=pos_start, pos_end=self.pos) def make_not_equals(self): pos_start = self.pos.copy() self.advance() if self.current_char == '=': self.advance() return Token(TT_NE, pos_start=pos_start, pos_end=self.pos), None self.advance() return None, ExpectedCharError(pos_start, self.pos, "'=' (after '!')") def make_equals(self): tok_type = TT_EQ pos_start = self.pos.copy() self.advance() if self.current_char == '=': self.advance() tok_type = TT_EE return Token(tok_type, pos_start=pos_start, pos_end=self.pos) def make_less_than(self): tok_type = TT_LT pos_start = self.pos.copy() self.advance() if self.current_char == '=': self.advance() tok_type = TT_LTE return Token(tok_type, pos_start=pos_start, pos_end=self.pos) def make_greater_than(self): tok_type = TT_GT pos_start = self.pos.copy() self.advance() if self.current_char == '=': self.advance() tok_type = TT_GTE return Token(tok_type, pos_start=pos_start, pos_end=self.pos) def skip_comment(self): self.advance() while self.current_char != '\n': self.advance() self.advance() ####################################### # NODES ####################################### class NumberNode: def __init__(self, tok): self.tok = tok self.pos_start = self.tok.pos_start self.pos_end = self.tok.pos_end def __repr__(self): return f'{self.tok}' class StringNode: def __init__(self, tok): self.tok = tok self.pos_start = self.tok.pos_start self.pos_end = self.tok.pos_end def __repr__(self): return f'{self.tok}' class ListNode: def __init__(self, element_nodes, pos_start, pos_end): self.element_nodes = element_nodes self.pos_start = pos_start self.pos_end = pos_end class VarAccessNode: def __init__(self, var_name_tok): self.var_name_tok = var_name_tok self.pos_start = self.var_name_tok.pos_start self.pos_end = self.var_name_tok.pos_end class VarAssignNode: def __init__(self, var_name_tok, value_node): self.var_name_tok = var_name_tok self.value_node = value_node self.pos_start = self.var_name_tok.pos_start self.pos_end = self.value_node.pos_end class BinOpNode: def __init__(self, left_node, op_tok, right_node): self.left_node = left_node self.op_tok = op_tok self.right_node = right_node self.pos_start = self.left_node.pos_start self.pos_end = self.right_node.pos_end def __repr__(self): return f'({self.left_node}, {self.op_tok}, {self.right_node})' class UnaryOpNode: def __init__(self, op_tok, node): self.op_tok = op_tok self.node = node self.pos_start = self.op_tok.pos_start self.pos_end = node.pos_end def __repr__(self): return f'({self.op_tok}, {self.node})' class IfNode: def __init__(self, cases, else_case): self.cases = cases self.else_case = else_case self.pos_start = self.cases[0][0].pos_start self.pos_end = (self.else_case or self.cases[len(self.cases) - 1])[0].pos_end class ForNode: def __init__(self, var_name_tok, start_value_node, end_value_node, step_value_node, body_node, should_return_null): self.var_name_tok = var_name_tok self.start_value_node = start_value_node self.end_value_node = end_value_node self.step_value_node = step_value_node self.body_node = body_node self.should_return_null = should_return_null self.pos_start = self.var_name_tok.pos_start self.pos_end = self.body_node.pos_end class WhileNode: def __init__(self, condition_node, body_node, should_return_null): self.condition_node = condition_node self.body_node = body_node self.should_return_null = should_return_null self.pos_start = self.condition_node.pos_start self.pos_end = self.body_node.pos_end class FuncDefNode: def __init__(self, var_name_tok, arg_name_toks, body_node, should_auto_return): self.var_name_tok = var_name_tok self.arg_name_toks = arg_name_toks self.body_node = body_node self.should_auto_return = should_auto_return if self.var_name_tok: self.pos_start = self.var_name_tok.pos_start elif len(self.arg_name_toks) > 0: self.pos_start = self.arg_name_toks[0].pos_start else: self.pos_start = self.body_node.pos_start self.pos_end = self.body_node.pos_end class CallNode: def __init__(self, node_to_call, arg_nodes): self.node_to_call = node_to_call self.arg_nodes = arg_nodes self.pos_start = self.node_to_call.pos_start if len(self.arg_nodes) > 0: self.pos_end = self.arg_nodes[len(self.arg_nodes) - 1].pos_end else: self.pos_end = self.node_to_call.pos_end class ReturnNode: def __init__(self, node_to_return, pos_start, pos_end): self.node_to_return = node_to_return self.pos_start = pos_start self.pos_end = pos_end class ContinueNode: def __init__(self, pos_start, pos_end): self.pos_start = pos_start self.pos_end = pos_end class BreakNode: def __init__(self, pos_start, pos_end): self.pos_start = pos_start self.pos_end = pos_end ####################################### # PARSE RESULT ####################################### class ParseResult: def __init__(self): self.error = None self.node = None self.last_registered_advance_count = 0 self.advance_count = 0 self.to_reverse_count = 0 def register_advancement(self): self.last_registered_advance_count = 1 self.advance_count += 1 def register(self, res): self.last_registered_advance_count = res.advance_count self.advance_count += res.advance_count if res.error: self.error = res.error return res.node def try_register(self, res): if res.error: self.to_reverse_count = res.advance_count return None return self.register(res) def success(self, node): self.node = node return self def failure(self, error): if not self.error or self.last_registered_advance_count == 0: self.error = error return self ####################################### # PARSER ####################################### class Parser: def __init__(self, tokens): self.tokens = tokens self.tok_idx = -1 self.advance() def advance(self): self.tok_idx += 1 self.update_current_tok() return self.current_tok def reverse(self, amount=1): self.tok_idx -= amount self.update_current_tok() return self.current_tok def update_current_tok(self): if self.tok_idx >= 0 and self.tok_idx < len(self.tokens): self.current_tok = self.tokens[self.tok_idx] def parse(self): res = self.statements() if not res.error and self.current_tok.type != TT_EOF: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Token cannot appear after previous tokens" )) return res ################################### def statements(self): res = ParseResult() statements = [] pos_start = self.current_tok.pos_start.copy() while self.current_tok.type == TT_NEWLINE: res.register_advancement() self.advance() statement = res.register(self.statement()) if res.error: return res statements.append(statement) more_statements = True while True: newline_count = 0 while self.current_tok.type == TT_NEWLINE: res.register_advancement() self.advance() newline_count += 1 if newline_count == 0: more_statements = False if not more_statements: break statement = res.try_register(self.statement()) if not statement: self.reverse(res.to_reverse_count) more_statements = False continue statements.append(statement) return res.success(ListNode( statements, pos_start, self.current_tok.pos_end.copy() )) def statement(self): res = ParseResult() pos_start = self.current_tok.pos_start.copy() if self.current_tok.matches(TT_KEYWORD, 'RETURN'): res.register_advancement() self.advance() expr = res.try_register(self.expr()) if not expr: self.reverse(res.to_reverse_count) return res.success(ReturnNode(expr, pos_start, self.current_tok.pos_start.copy())) if self.current_tok.matches(TT_KEYWORD, 'CONTINUE'): res.register_advancement() self.advance() return res.success(ContinueNode(pos_start, self.current_tok.pos_start.copy())) if self.current_tok.matches(TT_KEYWORD, 'BREAK'): res.register_advancement() self.advance() return res.success(BreakNode(pos_start, self.current_tok.pos_start.copy())) expr = res.register(self.expr()) if res.error: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected 'RETURN', 'CONTINUE', 'BREAK', 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'" )) return res.success(expr) def expr(self): res = ParseResult() if self.current_tok.matches(TT_KEYWORD, 'VAR'): res.register_advancement() self.advance() if self.current_tok.type != TT_IDENTIFIER: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected identifier" )) var_name = self.current_tok res.register_advancement() self.advance() if self.current_tok.type != TT_EQ: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected '='" )) res.register_advancement() self.advance() expr = res.register(self.expr()) if res.error: return res return res.success(VarAssignNode(var_name, expr)) node = res.register(self.bin_op(self.comp_expr, ((TT_KEYWORD, 'AND'), (TT_KEYWORD, 'OR')))) if res.error: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'" )) return res.success(node) def comp_expr(self): res = ParseResult() if self.current_tok.matches(TT_KEYWORD, 'NOT'): op_tok = self.current_tok res.register_advancement() self.advance() node = res.register(self.comp_expr()) if res.error: return res return res.success(UnaryOpNode(op_tok, node)) node = res.register(self.bin_op(self.arith_expr, (TT_EE, TT_NE, TT_LT, TT_GT, TT_LTE, TT_GTE))) if res.error: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected int, float, identifier, '+', '-', '(', '[', 'IF', 'FOR', 'WHILE', 'FUN' or 'NOT'" )) return res.success(node) def arith_expr(self): return self.bin_op(self.term, (TT_PLUS, TT_MINUS)) def term(self): return self.bin_op(self.factor, (TT_MUL, TT_DIV)) def factor(self): res = ParseResult() tok = self.current_tok if tok.type in (TT_PLUS, TT_MINUS): res.register_advancement() self.advance() factor = res.register(self.factor()) if res.error: return res return res.success(UnaryOpNode(tok, factor)) return self.power() def power(self): return self.bin_op(self.call, (TT_POW, ), self.factor) def call(self): res = ParseResult() atom = res.register(self.atom()) if res.error: return res if self.current_tok.type == TT_LPAREN: res.register_advancement() self.advance() arg_nodes = [] if self.current_tok.type == TT_RPAREN: res.register_advancement() self.advance() else: arg_nodes.append(res.register(self.expr())) if res.error: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected ')', 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'" )) while self.current_tok.type == TT_COMMA: res.register_advancement() self.advance() arg_nodes.append(res.register(self.expr())) if res.error: return res if self.current_tok.type != TT_RPAREN: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected ',' or ')'" )) res.register_advancement() self.advance() return res.success(CallNode(atom, arg_nodes)) return res.success(atom) def atom(self): res = ParseResult() tok = self.current_tok if tok.type in (TT_INT, TT_FLOAT): res.register_advancement() self.advance() return res.success(NumberNode(tok)) elif tok.type == TT_STRING: res.register_advancement() self.advance() return res.success(StringNode(tok)) elif tok.type == TT_IDENTIFIER: res.register_advancement() self.advance() return res.success(VarAccessNode(tok)) elif tok.type == TT_LPAREN: res.register_advancement() self.advance() expr = res.register(self.expr()) if res.error: return res if self.current_tok.type == TT_RPAREN: res.register_advancement() self.advance() return res.success(expr) else: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected ')'" )) elif tok.type == TT_LSQUARE: list_expr = res.register(self.list_expr()) if res.error: return res return res.success(list_expr) elif tok.matches(TT_KEYWORD, 'IF'): if_expr = res.register(self.if_expr()) if res.error: return res return res.success(if_expr) elif tok.matches(TT_KEYWORD, 'FOR'): for_expr = res.register(self.for_expr()) if res.error: return res return res.success(for_expr) elif tok.matches(TT_KEYWORD, 'WHILE'): while_expr = res.register(self.while_expr()) if res.error: return res return res.success(while_expr) elif tok.matches(TT_KEYWORD, 'FUN'): func_def = res.register(self.func_def()) if res.error: return res return res.success(func_def) return res.failure(InvalidSyntaxError( tok.pos_start, tok.pos_end, "Expected int, float, identifier, '+', '-', '(', '[', IF', 'FOR', 'WHILE', 'FUN'" )) def list_expr(self): res = ParseResult() element_nodes = [] pos_start = self.current_tok.pos_start.copy() if self.current_tok.type != TT_LSQUARE: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected '['" )) res.register_advancement() self.advance() if self.current_tok.type == TT_RSQUARE: res.register_advancement() self.advance() else: element_nodes.append(res.register(self.expr())) if res.error: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected ']', 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'" )) while self.current_tok.type == TT_COMMA: res.register_advancement() self.advance() element_nodes.append(res.register(self.expr())) if res.error: return res if self.current_tok.type != TT_RSQUARE: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected ',' or ']'" )) res.register_advancement() self.advance() return res.success(ListNode( element_nodes, pos_start, self.current_tok.pos_end.copy() )) def if_expr(self): res = ParseResult() all_cases = res.register(self.if_expr_cases('IF')) if res.error: return res cases, else_case = all_cases return res.success(IfNode(cases, else_case)) def if_expr_b(self): return self.if_expr_cases('ELIF') def if_expr_c(self): res = ParseResult() else_case = None if self.current_tok.matches(TT_KEYWORD, 'ELSE'): res.register_advancement() self.advance() if self.current_tok.type == TT_NEWLINE: res.register_advancement() self.advance() statements = res.register(self.statements()) if res.error: return res else_case = (statements, True) if self.current_tok.matches(TT_KEYWORD, 'END'): res.register_advancement() self.advance() else: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, "Expected 'END'" )) else: expr = res.register(self.statement()) if res.error: return res else_case = (expr, False) return res.success(else_case) def if_expr_b_or_c(self): res = ParseResult() cases, else_case = [], None if self.current_tok.matches(TT_KEYWORD, 'ELIF'): all_cases = res.register(self.if_expr_b()) if res.error: return res cases, else_case = all_cases else: else_case = res.register(self.if_expr_c()) if res.error: return res return res.success((cases, else_case)) def if_expr_cases(self, case_keyword): res = ParseResult() cases = [] else_case = None if not self.current_tok.matches(TT_KEYWORD, case_keyword): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected '{case_keyword}'" )) res.register_advancement() self.advance() condition = res.register(self.expr()) if res.error: return res if not self.current_tok.matches(TT_KEYWORD, 'THEN'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'THEN'" )) res.register_advancement() self.advance() if self.current_tok.type == TT_NEWLINE: res.register_advancement() self.advance() statements = res.register(self.statements()) if res.error: return res cases.append((condition, statements, True)) if self.current_tok.matches(TT_KEYWORD, 'END'): res.register_advancement() self.advance() else: all_cases = res.register(self.if_expr_b_or_c()) if res.error: return res new_cases, else_case = all_cases cases.extend(new_cases) else: expr = res.register(self.statement()) if res.error: return res cases.append((condition, expr, False)) all_cases = res.register(self.if_expr_b_or_c()) if res.error: return res new_cases, else_case = all_cases cases.extend(new_cases) return res.success((cases, else_case)) def for_expr(self): res = ParseResult() if not self.current_tok.matches(TT_KEYWORD, 'FOR'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'FOR'" )) res.register_advancement() self.advance() if self.current_tok.type != TT_IDENTIFIER: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected identifier" )) var_name = self.current_tok res.register_advancement() self.advance() if self.current_tok.type != TT_EQ: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected '='" )) res.register_advancement() self.advance() start_value = res.register(self.expr()) if res.error: return res if not self.current_tok.matches(TT_KEYWORD, 'TO'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'TO'" )) res.register_advancement() self.advance() end_value = res.register(self.expr()) if res.error: return res if self.current_tok.matches(TT_KEYWORD, 'STEP'): res.register_advancement() self.advance() step_value = res.register(self.expr()) if res.error: return res else: step_value = None if not self.current_tok.matches(TT_KEYWORD, 'THEN'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'THEN'" )) res.register_advancement() self.advance() if self.current_tok.type == TT_NEWLINE: res.register_advancement() self.advance() body = res.register(self.statements()) if res.error: return res if not self.current_tok.matches(TT_KEYWORD, 'END'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'END'" )) res.register_advancement() self.advance() return res.success(ForNode(var_name, start_value, end_value, step_value, body, True)) body = res.register(self.statement()) if res.error: return res return res.success(ForNode(var_name, start_value, end_value, step_value, body, False)) def while_expr(self): res = ParseResult() if not self.current_tok.matches(TT_KEYWORD, 'WHILE'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'WHILE'" )) res.register_advancement() self.advance() condition = res.register(self.expr()) if res.error: return res if not self.current_tok.matches(TT_KEYWORD, 'THEN'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'THEN'" )) res.register_advancement() self.advance() if self.current_tok.type == TT_NEWLINE: res.register_advancement() self.advance() body = res.register(self.statements()) if res.error: return res if not self.current_tok.matches(TT_KEYWORD, 'END'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'END'" )) res.register_advancement() self.advance() return res.success(WhileNode(condition, body, True)) body = res.register(self.statement()) if res.error: return res return res.success(WhileNode(condition, body, False)) def func_def(self): res = ParseResult() if not self.current_tok.matches(TT_KEYWORD, 'FUN'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'FUN'" )) res.register_advancement() self.advance() if self.current_tok.type == TT_IDENTIFIER: var_name_tok = self.current_tok res.register_advancement() self.advance() if self.current_tok.type != TT_LPAREN: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected '('" )) else: var_name_tok = None if self.current_tok.type != TT_LPAREN: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected identifier or '('" )) res.register_advancement() self.advance() arg_name_toks = [] if self.current_tok.type == TT_IDENTIFIER: arg_name_toks.append(self.current_tok) res.register_advancement() self.advance() while self.current_tok.type == TT_COMMA: res.register_advancement() self.advance() if self.current_tok.type != TT_IDENTIFIER: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected identifier" )) arg_name_toks.append(self.current_tok) res.register_advancement() self.advance() if self.current_tok.type != TT_RPAREN: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected ',' or ')'" )) else: if self.current_tok.type != TT_RPAREN: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected identifier or ')'" )) res.register_advancement() self.advance() if self.current_tok.type == TT_ARROW: res.register_advancement() self.advance() body = res.register(self.expr()) if res.error: return res return res.success(FuncDefNode( var_name_tok, arg_name_toks, body, True )) if self.current_tok.type != TT_NEWLINE: return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected '->' or NEWLINE" )) res.register_advancement() self.advance() body = res.register(self.statements()) if res.error: return res if not self.current_tok.matches(TT_KEYWORD, 'END'): return res.failure(InvalidSyntaxError( self.current_tok.pos_start, self.current_tok.pos_end, f"Expected 'END'" )) res.register_advancement() self.advance() return res.success(FuncDefNode( var_name_tok, arg_name_toks, body, False )) ################################### def bin_op(self, func_a, ops, func_b=None): if func_b == None: func_b = func_a res = ParseResult() left = res.register(func_a()) if res.error: return res while self.current_tok.type in ops or (self.current_tok.type, self.current_tok.value) in ops: op_tok = self.current_tok res.register_advancement() self.advance() right = res.register(func_b()) if res.error: return res left = BinOpNode(left, op_tok, right) return res.success(left) ####################################### # RUNTIME RESULT ####################################### class RTResult: def __init__(self): self.reset() def reset(self): self.value = None self.error = None self.func_return_value = None self.loop_should_continue = False self.loop_should_break = False def register(self, res): self.error = res.error self.func_return_value = res.func_return_value self.loop_should_continue = res.loop_should_continue self.loop_should_break = res.loop_should_break return res.value def success(self, value): self.reset() self.value = value return self def success_return(self, value): self.reset() self.func_return_value = value return self def success_continue(self): self.reset() self.loop_should_continue = True return self def success_break(self): self.reset() self.loop_should_break = True return self def failure(self, error): self.reset() self.error = error return self def should_return(self): # Note: this will allow you to continue and break outside the current function return ( self.error or self.func_return_value or self.loop_should_continue or self.loop_should_break ) ####################################### # VALUES ####################################### class Value: def __init__(self): self.set_pos() self.set_context() def set_pos(self, pos_start=None, pos_end=None): self.pos_start = pos_start self.pos_end = pos_end return self def set_context(self, context=None): self.context = context return self def added_to(self, other): return None, self.illegal_operation(other) def subbed_by(self, other): return None, self.illegal_operation(other) def multed_by(self, other): return None, self.illegal_operation(other) def dived_by(self, other): return None, self.illegal_operation(other) def powed_by(self, other): return None, self.illegal_operation(other) def get_comparison_eq(self, other): return None, self.illegal_operation(other) def get_comparison_ne(self, other): return None, self.illegal_operation(other) def get_comparison_lt(self, other): return None, self.illegal_operation(other) def get_comparison_gt(self, other): return None, self.illegal_operation(other) def get_comparison_lte(self, other): return None, self.illegal_operation(other) def get_comparison_gte(self, other): return None, self.illegal_operation(other) def anded_by(self, other): return None, self.illegal_operation(other) def ored_by(self, other): return None, self.illegal_operation(other) def notted(self, other): return None, self.illegal_operation(other) def execute(self, args): return RTResult().failure(self.illegal_operation()) def copy(self): raise Exception('No copy method defined') def is_true(self): return False def illegal_operation(self, other=None): if not other: other = self return RTError( self.pos_start, other.pos_end, 'Illegal operation', self.context ) class Number(Value): def __init__(self, value): super().__init__() self.value = value def added_to(self, other): if isinstance(other, Number): return Number(self.value + other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def subbed_by(self, other): if isinstance(other, Number): return Number(self.value - other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def multed_by(self, other): if isinstance(other, Number): return Number(self.value * other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def dived_by(self, other): if isinstance(other, Number): if other.value == 0: return None, RTError( other.pos_start, other.pos_end, 'Division by zero', self.context ) return Number(self.value / other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def powed_by(self, other): if isinstance(other, Number): return Number(self.value ** other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def get_comparison_eq(self, other): if isinstance(other, Number): return Number(int(self.value == other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def get_comparison_ne(self, other): if isinstance(other, Number): return Number(int(self.value != other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def get_comparison_lt(self, other): if isinstance(other, Number): return Number(int(self.value < other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def get_comparison_gt(self, other): if isinstance(other, Number): return Number(int(self.value > other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def get_comparison_lte(self, other): if isinstance(other, Number): return Number(int(self.value <= other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def get_comparison_gte(self, other): if isinstance(other, Number): return Number(int(self.value >= other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def anded_by(self, other): if isinstance(other, Number): return Number(int(self.value and other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def ored_by(self, other): if isinstance(other, Number): return Number(int(self.value or other.value)).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def notted(self): return Number(1 if self.value == 0 else 0).set_context(self.context), None def copy(self): copy = Number(self.value) copy.set_pos(self.pos_start, self.pos_end) copy.set_context(self.context) return copy def is_true(self): return self.value != 0 def __str__(self): return str(self.value) def __repr__(self): return str(self.value) Number.null = Number(0) Number.false = Number(0) Number.true = Number(1) Number.math_PI = Number(math.pi) class String(Value): def __init__(self, value): super().__init__() self.value = value def added_to(self, other): if isinstance(other, String): return String(self.value + other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def multed_by(self, other): if isinstance(other, Number): return String(self.value * other.value).set_context(self.context), None else: return None, Value.illegal_operation(self, other) def is_true(self): return len(self.value) > 0 def copy(self): copy = String(self.value) copy.set_pos(self.pos_start, self.pos_end) copy.set_context(self.context) return copy def __str__(self): return self.value def __repr__(self): return f'"{self.value}"' class List(Value): def __init__(self, elements): super().__init__() self.elements = elements def added_to(self, other): new_list = self.copy() new_list.elements.append(other) return new_list, None def subbed_by(self, other): if isinstance(other, Number): new_list = self.copy() try: new_list.elements.pop(other.value) return new_list, None except: return None, RTError( other.pos_start, other.pos_end, 'Element at this index could not be removed from list because index is out of bounds', self.context ) else: return None, Value.illegal_operation(self, other) def multed_by(self, other): if isinstance(other, List): new_list = self.copy() new_list.elements.extend(other.elements) return new_list, None else: return None, Value.illegal_operation(self, other) def dived_by(self, other): if isinstance(other, Number): try: return self.elements[other.value], None except: return None, RTError( other.pos_start, other.pos_end, 'Element at this index could not be retrieved from list because index is out of bounds', self.context ) else: return None, Value.illegal_operation(self, other) def copy(self): copy = List(self.elements) copy.set_pos(self.pos_start, self.pos_end) copy.set_context(self.context) return copy def __str__(self): return ", ".join([str(x) for x in self.elements]) def __repr__(self): return f'[{", ".join([repr(x) for x in self.elements])}]' class BaseFunction(Value): def __init__(self, name): super().__init__() self.name = name or "<anonymous>" def generate_new_context(self): new_context = Context(self.name, self.context, self.pos_start) new_context.symbol_table = SymbolTable(new_context.parent.symbol_table) return new_context def check_args(self, arg_names, args): res = RTResult() if len(args) > len(arg_names): return res.failure(RTError( self.pos_start, self.pos_end, f"{len(args) - len(arg_names)} too many args passed into {self}", self.context )) if len(args) < len(arg_names): return res.failure(RTError( self.pos_start, self.pos_end, f"{len(arg_names) - len(args)} too few args passed into {self}", self.context )) return res.success(None) def populate_args(self, arg_names, args, exec_ctx): for i in range(len(args)): arg_name = arg_names[i] arg_value = args[i] arg_value.set_context(exec_ctx) exec_ctx.symbol_table.set(arg_name, arg_value) def check_and_populate_args(self, arg_names, args, exec_ctx): res = RTResult() res.register(self.check_args(arg_names, args)) if res.should_return(): return res self.populate_args(arg_names, args, exec_ctx) return res.success(None) class Function(BaseFunction): def __init__(self, name, body_node, arg_names, should_auto_return): super().__init__(name) self.body_node = body_node self.arg_names = arg_names self.should_auto_return = should_auto_return def execute(self, args): res = RTResult() interpreter = Interpreter() exec_ctx = self.generate_new_context() res.register(self.check_and_populate_args(self.arg_names, args, exec_ctx)) if res.should_return(): return res value = res.register(interpreter.visit(self.body_node, exec_ctx)) if res.should_return() and res.func_return_value == None: return res ret_value = (value if self.should_auto_return else None) or res.func_return_value or Number.null return res.success(ret_value) def copy(self): copy = Function(self.name, self.body_node, self.arg_names, self.should_auto_return) copy.set_context(self.context) copy.set_pos(self.pos_start, self.pos_end) return copy def __repr__(self): return f"<function {self.name}>" class BuiltInFunction(BaseFunction): def __init__(self, name): super().__init__(name) def execute(self, args): res = RTResult() exec_ctx = self.generate_new_context() method_name = f'execute_{self.name}' method = getattr(self, method_name, self.no_visit_method) res.register(self.check_and_populate_args(method.arg_names, args, exec_ctx)) if res.should_return(): return res return_value = res.register(method(exec_ctx)) if res.should_return(): return res return res.success(return_value) def no_visit_method(self, node, context): raise Exception(f'No execute_{self.name} method defined') def copy(self): copy = BuiltInFunction(self.name) copy.set_context(self.context) copy.set_pos(self.pos_start, self.pos_end) return copy def __repr__(self): return f"<built-in function {self.name}>" ##################################### def execute_print(self, exec_ctx): print(str(exec_ctx.symbol_table.get('value'))) return RTResult().success(Number.null) execute_print.arg_names = ['value'] def execute_print_ret(self, exec_ctx): return RTResult().success(String(str(exec_ctx.symbol_table.get('value')))) execute_print_ret.arg_names = ['value'] def execute_input(self, exec_ctx): text = input() return RTResult().success(String(text)) execute_input.arg_names = [] def execute_input_int(self, exec_ctx): while True: text = input() try: number = int(text) break except ValueError: print(f"'{text}' must be an integer. Try again!") return RTResult().success(Number(number)) execute_input_int.arg_names = [] def execute_clear(self, exec_ctx): os.system('cls' if os.name == 'nt' else 'cls') return RTResult().success(Number.null) execute_clear.arg_names = [] def execute_is_number(self, exec_ctx): is_number = isinstance(exec_ctx.symbol_table.get("value"), Number) return RTResult().success(Number.true if is_number else Number.false) execute_is_number.arg_names = ["value"] def execute_is_string(self, exec_ctx): is_number = isinstance(exec_ctx.symbol_table.get("value"), String) return RTResult().success(Number.true if is_number else Number.false) execute_is_string.arg_names = ["value"] def execute_is_list(self, exec_ctx): is_number = isinstance(exec_ctx.symbol_table.get("value"), List) return RTResult().success(Number.true if is_number else Number.false) execute_is_list.arg_names = ["value"] def execute_is_function(self, exec_ctx): is_number = isinstance(exec_ctx.symbol_table.get("value"), BaseFunction) return RTResult().success(Number.true if is_number else Number.false) execute_is_function.arg_names = ["value"] def execute_append(self, exec_ctx): list_ = exec_ctx.symbol_table.get("list") value = exec_ctx.symbol_table.get("value") if not isinstance(list_, List): return RTResult().failure(RTError( self.pos_start, self.pos_end, "First argument must be list", exec_ctx )) list_.elements.append(value) return RTResult().success(Number.null) execute_append.arg_names = ["list", "value"] def execute_pop(self, exec_ctx): list_ = exec_ctx.symbol_table.get("list") index = exec_ctx.symbol_table.get("index") if not isinstance(list_, List): return RTResult().failure(RTError( self.pos_start, self.pos_end, "First argument must be list", exec_ctx )) if not isinstance(index, Number): return RTResult().failure(RTError( self.pos_start, self.pos_end, "Second argument must be number", exec_ctx )) try: element = list_.elements.pop(index.value) except: return RTResult().failure(RTError( self.pos_start, self.pos_end, 'Element at this index could not be removed from list because index is out of bounds', exec_ctx )) return RTResult().success(element) execute_pop.arg_names = ["list", "index"] def execute_extend(self, exec_ctx): listA = exec_ctx.symbol_table.get("listA") listB = exec_ctx.symbol_table.get("listB") if not isinstance(listA, List): return RTResult().failure(RTError( self.pos_start, self.pos_end, "First argument must be list", exec_ctx )) if not isinstance(listB, List): return RTResult().failure(RTError( self.pos_start, self.pos_end, "Second argument must be list", exec_ctx )) listA.elements.extend(listB.elements) return RTResult().success(Number.null) execute_extend.arg_names = ["listA", "listB"] def execute_len(self, exec_ctx): list_ = exec_ctx.symbol_table.get("list") if not isinstance(list_, List): return RTResult().failure(RTError( self.pos_start, self.pos_end, "Argument must be list", exec_ctx )) return RTResult().success(Number(len(list_.elements))) execute_len.arg_names = ["list"] def execute_run(self, exec_ctx): fn = exec_ctx.symbol_table.get("fn") if not isinstance(fn, String): return RTResult().failure(RTError( self.pos_start, self.pos_end, "Second argument must be string", exec_ctx )) fn = fn.value try: with open(fn, "r") as f: script = f.read() except Exception as e: return RTResult().failure(RTError( self.pos_start, self.pos_end, f"Failed to load script \"{fn}\"\n" + str(e), exec_ctx )) _, error = run(fn, script) if error: return RTResult().failure(RTError( self.pos_start, self.pos_end, f"Failed to finish executing script \"{fn}\"\n" + error.as_string(), exec_ctx )) return RTResult().success(Number.null) execute_run.arg_names = ["fn"] BuiltInFunction.print = BuiltInFunction("print") BuiltInFunction.print_ret = BuiltInFunction("print_ret") BuiltInFunction.input = BuiltInFunction("input") BuiltInFunction.input_int = BuiltInFunction("input_int") BuiltInFunction.clear = BuiltInFunction("clear") BuiltInFunction.is_number = BuiltInFunction("is_number") BuiltInFunction.is_string = BuiltInFunction("is_string") BuiltInFunction.is_list = BuiltInFunction("is_list") BuiltInFunction.is_function = BuiltInFunction("is_function") BuiltInFunction.append = BuiltInFunction("append") BuiltInFunction.pop = BuiltInFunction("pop") BuiltInFunction.extend = BuiltInFunction("extend") BuiltInFunction.len = BuiltInFunction("len") BuiltInFunction.run = BuiltInFunction("run") ####################################### # CONTEXT ####################################### class Context: def __init__(self, display_name, parent=None, parent_entry_pos=None): self.display_name = display_name self.parent = parent self.parent_entry_pos = parent_entry_pos self.symbol_table = None ####################################### # SYMBOL TABLE ####################################### class SymbolTable: def __init__(self, parent=None): self.symbols = {} self.parent = parent def get(self, name): value = self.symbols.get(name, None) if value == None and self.parent: return self.parent.get(name) return value def set(self, name, value): self.symbols[name] = value def remove(self, name): del self.symbols[name] ####################################### # INTERPRETER ####################################### class Interpreter: def visit(self, node, context): method_name = f'visit_{type(node).__name__}' method = getattr(self, method_name, self.no_visit_method) return method(node, context) def no_visit_method(self, node, context): raise Exception(f'No visit_{type(node).__name__} method defined') ################################### def visit_NumberNode(self, node, context): return RTResult().success( Number(node.tok.value).set_context(context).set_pos(node.pos_start, node.pos_end) ) def visit_StringNode(self, node, context): return RTResult().success( String(node.tok.value).set_context(context).set_pos(node.pos_start, node.pos_end) ) def visit_ListNode(self, node, context): res = RTResult() elements = [] for element_node in node.element_nodes: elements.append(res.register(self.visit(element_node, context))) if res.should_return(): return res return res.success( List(elements).set_context(context).set_pos(node.pos_start, node.pos_end) ) def visit_VarAccessNode(self, node, context): res = RTResult() var_name = node.var_name_tok.value value = context.symbol_table.get(var_name) if not value: return res.failure(RTError( node.pos_start, node.pos_end, f"'{var_name}' is not defined", context )) value = value.copy().set_pos(node.pos_start, node.pos_end).set_context(context) return res.success(value) def visit_VarAssignNode(self, node, context): res = RTResult() var_name = node.var_name_tok.value value = res.register(self.visit(node.value_node, context)) if res.should_return(): return res context.symbol_table.set(var_name, value) return res.success(value) def visit_BinOpNode(self, node, context): res = RTResult() left = res.register(self.visit(node.left_node, context)) if res.should_return(): return res right = res.register(self.visit(node.right_node, context)) if res.should_return(): return res if node.op_tok.type == TT_PLUS: result, error = left.added_to(right) elif node.op_tok.type == TT_MINUS: result, error = left.subbed_by(right) elif node.op_tok.type == TT_MUL: result, error = left.multed_by(right) elif node.op_tok.type == TT_DIV: result, error = left.dived_by(right) elif node.op_tok.type == TT_POW: result, error = left.powed_by(right) elif node.op_tok.type == TT_EE: result, error = left.get_comparison_eq(right) elif node.op_tok.type == TT_NE: result, error = left.get_comparison_ne(right) elif node.op_tok.type == TT_LT: result, error = left.get_comparison_lt(right) elif node.op_tok.type == TT_GT: result, error = left.get_comparison_gt(right) elif node.op_tok.type == TT_LTE: result, error = left.get_comparison_lte(right) elif node.op_tok.type == TT_GTE: result, error = left.get_comparison_gte(right) elif node.op_tok.matches(TT_KEYWORD, 'AND'): result, error = left.anded_by(right) elif node.op_tok.matches(TT_KEYWORD, 'OR'): result, error = left.ored_by(right) if error: return res.failure(error) else: return res.success(result.set_pos(node.pos_start, node.pos_end)) def visit_UnaryOpNode(self, node, context): res = RTResult() number = res.register(self.visit(node.node, context)) if res.should_return(): return res error = None if node.op_tok.type == TT_MINUS: number, error = number.multed_by(Number(-1)) elif node.op_tok.matches(TT_KEYWORD, 'NOT'): number, error = number.notted() if error: return res.failure(error) else: return res.success(number.set_pos(node.pos_start, node.pos_end)) def visit_IfNode(self, node, context): res = RTResult() for condition, expr, should_return_null in node.cases: condition_value = res.register(self.visit(condition, context)) if res.should_return(): return res if condition_value.is_true(): expr_value = res.register(self.visit(expr, context)) if res.should_return(): return res return res.success(Number.null if should_return_null else expr_value) if node.else_case: expr, should_return_null = node.else_case expr_value = res.register(self.visit(expr, context)) if res.should_return(): return res return res.success(Number.null if should_return_null else expr_value) return res.success(Number.null) def visit_ForNode(self, node, context): res = RTResult() elements = [] start_value = res.register(self.visit(node.start_value_node, context)) if res.should_return(): return res end_value = res.register(self.visit(node.end_value_node, context)) if res.should_return(): return res if node.step_value_node: step_value = res.register(self.visit(node.step_value_node, context)) if res.should_return(): return res else: step_value = Number(1) i = start_value.value if step_value.value >= 0: condition = lambda: i < end_value.value else: condition = lambda: i > end_value.value while condition(): context.symbol_table.set(node.var_name_tok.value, Number(i)) i += step_value.value value = res.register(self.visit(node.body_node, context)) if res.should_return() and res.loop_should_continue == False and res.loop_should_break == False: return res if res.loop_should_continue: continue if res.loop_should_break: break elements.append(value) return res.success( Number.null if node.should_return_null else List(elements).set_context(context).set_pos(node.pos_start, node.pos_end) ) def visit_WhileNode(self, node, context): res = RTResult() elements = [] while True: condition = res.register(self.visit(node.condition_node, context)) if res.should_return(): return res if not condition.is_true(): break value = res.register(self.visit(node.body_node, context)) if res.should_return() and res.loop_should_continue == False and res.loop_should_break == False: return res if res.loop_should_continue: continue if res.loop_should_break: break elements.append(value) return res.success( Number.null if node.should_return_null else List(elements).set_context(context).set_pos(node.pos_start, node.pos_end) ) def visit_FuncDefNode(self, node, context): res = RTResult() func_name = node.var_name_tok.value if node.var_name_tok else None body_node = node.body_node arg_names = [arg_name.value for arg_name in node.arg_name_toks] func_value = Function(func_name, body_node, arg_names, node.should_auto_return).set_context(context).set_pos(node.pos_start, node.pos_end) if node.var_name_tok: context.symbol_table.set(func_name, func_value) return res.success(func_value) def visit_CallNode(self, node, context): res = RTResult() args = [] value_to_call = res.register(self.visit(node.node_to_call, context)) if res.should_return(): return res value_to_call = value_to_call.copy().set_pos(node.pos_start, node.pos_end) for arg_node in node.arg_nodes: args.append(res.register(self.visit(arg_node, context))) if res.should_return(): return res return_value = res.register(value_to_call.execute(args)) if res.should_return(): return res return_value = return_value.copy().set_pos(node.pos_start, node.pos_end).set_context(context) return res.success(return_value) def visit_ReturnNode(self, node, context): res = RTResult() if node.node_to_return: value = res.register(self.visit(node.node_to_return, context)) if res.should_return(): return res else: value = Number.null return res.success_return(value) def visit_ContinueNode(self, node, context): return RTResult().success_continue() def visit_BreakNode(self, node, context): return RTResult().success_break() ####################################### # RUN ####################################### global_symbol_table = SymbolTable() global_symbol_table.set("NULL", Number.null) global_symbol_table.set("FALSE", Number.false) global_symbol_table.set("TRUE", Number.true) global_symbol_table.set("MATH_PI", Number.math_PI) global_symbol_table.set("PRINT", BuiltInFunction.print) global_symbol_table.set("PRINT_RET", BuiltInFunction.print_ret) global_symbol_table.set("INPUT", BuiltInFunction.input) global_symbol_table.set("INPUT_INT", BuiltInFunction.input_int) global_symbol_table.set("CLEAR", BuiltInFunction.clear) global_symbol_table.set("CLS", BuiltInFunction.clear) global_symbol_table.set("IS_NUM", BuiltInFunction.is_number) global_symbol_table.set("IS_STR", BuiltInFunction.is_string) global_symbol_table.set("IS_LIST", BuiltInFunction.is_list) global_symbol_table.set("IS_FUN", BuiltInFunction.is_function) global_symbol_table.set("APPEND", BuiltInFunction.append) global_symbol_table.set("POP", BuiltInFunction.pop) global_symbol_table.set("EXTEND", BuiltInFunction.extend) global_symbol_table.set("LEN", BuiltInFunction.len) global_symbol_table.set("RUN", BuiltInFunction.run) def run(fn, text): # Generate tokens lexer = Lexer(fn, text) tokens, error = lexer.make_tokens() if error: return None, error # Generate AST parser = Parser(tokens) ast = parser.parse() if ast.error: return None, ast.error # Run program interpreter = Interpreter() context = Context('<program>') context.symbol_table = global_symbol_table result = interpreter.visit(ast.node, context) return result.value, result.error
Editor is loading...