From 7a5ac47a70c385ebc80e3c6dd8fb82aa84ed192f Mon Sep 17 00:00:00 2001 From: Pixelthegreat <65832183+Pixelthegreat@users.noreply.github.com> Date: Fri, 16 Apr 2021 14:28:51 -0400 Subject: [PATCH 1/2] Added classes and EXIT function Added simple class syntax that can be viewed in "classes/example.myopl". Also added EXIT function which for whatever reason does not work. --- classes/basic.py | 2473 ++++++++++++++++++++++++++++++++ classes/example.myopl | 15 + classes/shell.py | 17 + classes/strings_with_arrows.py | 26 + 4 files changed, 2531 insertions(+) create mode 100644 classes/basic.py create mode 100644 classes/example.myopl create mode 100644 classes/shell.py create mode 100644 classes/strings_with_arrows.py diff --git a/classes/basic.py b/classes/basic.py new file mode 100644 index 0000000..3840da4 --- /dev/null +++ b/classes/basic.py @@ -0,0 +1,2473 @@ +####################################### +# 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_DOT = "DOT" +TT_EOF = 'EOF' + +KEYWORDS = [ + 'VAR', + 'AND', + 'OR', + 'NOT', + 'IF', + 'ELIF', + 'ELSE', + 'FOR', + 'TO', + 'STEP', + 'WHILE', + 'FUN', + 'THEN', + 'END', + 'RETURN', + 'CONTINUE', + 'BREAK', + 'CLASS', +] + +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 == '.': + tokens.append(Token(TT_DOT, 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 + + self.child = None + + 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 + + self.child = None + + 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 + + self.child = None + +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 + + self.child = None + +class VarAssignNode: + def __init__(self, var_name_tok, value_node, extra_names=[]): + self.var_name_tok = var_name_tok + self.value_node = value_node + self.extra_names = extra_names + + self.pos_start = self.var_name_tok.pos_start + self.pos_end = self.extra_names[len(self.extra_names)-1].pos_end if len(self.extra_names) > 0 else self.var_name_tok.pos_end + + self.child = None + +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 + + self.child = None + + 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 + + self.child = None + + 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 + + self.child = None + +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 + + self.child = None + +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 + + self.child = None + +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 + + self.child = None + +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 + + self.child = None + +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 + + self.child = None + +class ContinueNode: + def __init__(self, pos_start, pos_end): + self.pos_start = pos_start + self.pos_end = pos_end + + self.child = None + +class BreakNode: + def __init__(self, pos_start, pos_end): + self.pos_start = pos_start + self.pos_end = pos_end + + self.child = None + +class ClassNode: + def __init__(self, class_name_tok, body_nodes, pos_start, pos_end): + self.class_name_tok = class_name_tok + self.body_nodes = body_nodes + self.pos_start = pos_start + self.pos_end = pos_end + + self.child = None + +####################################### +# 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() + + extra_names = [] + + while self.current_tok.type == TT_DOT: + 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" + )) + + extra_names.append(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, extra_names)) + + 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 + + while self.current_tok.type == TT_DOT: + child = atom + res.register_advancement() + self.advance() + + child_ = res.register(self.call()) + if res.error: return res + + child.child = child_ + child = child_ + + 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) + + elif tok.matches(TT_KEYWORD, 'CLASS'): + class_node = res.register(self.class_node()) + if res.error: return res + return res.success(class_node) + + 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 class_node(self): + res = ParseResult() + + pos_start = self.current_tok.pos_start + + if not self.current_tok.matches(TT_KEYWORD, 'CLASS'): + return res.failure(InvalidSyntaxError( + self.current_tok.pos_start, self.current_tok.pos_end, + f"Expected 'CLASS'" + )) + + 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" + )) + + class_name_tok = self.current_tok + + res.register_advancement() + self.advance() + + if self.current_tok.type != TT_NEWLINE: + return res.failure(InvalidSyntaxError( + self.current_tok.pos_start, self.current_tok.pos_end, + f"Expected 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(ClassNode(class_name_tok, body, pos_start, self.current_tok.pos_end)) + + 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 + self.should_exit = 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 + self.should_exit = res.should_exit + 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 success_exit(self, exit_value): + self.reset() + self.should_exit = True + self.value = exit_value + 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 or + self.should_exit + ) + +####################################### +# 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 "" + + 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 Instance(Value): + def __init__(self, parent_class): + super().__init__() + self.parent_class = parent_class + self.symbol_table = None + + def copy(self): + return self + + def __repr__(self): + return f"" + +class Class(Value): + def __init__(self, name, symbol_table): + super().__init__() + self.name = name + self.symbol_table = symbol_table + + def dived_by(self, other): + if not isinstance(other, String): + return None, self.illegal_operation(other) + + value = self.symbol_table.get(other.value) + if not value: + return None, RTError( + self.pos_start, self.pos_end, + f"'{other.value}' is not defined", + self.context + ) + + return value, None + + def execute(self, args): + res = RTResult() + + exec_ctx = Context(self.name, self.context, self.pos_start) + + inst = Instance(self) + inst.symbol_table = SymbolTable(self.symbol_table) + + exec_ctx.symbol_table = inst.symbol_table + for name in self.symbol_table.symbols: + inst.symbol_table.set(name, self.symbol_table.symbols[name].copy()) + + for name in inst.symbol_table.symbols: + inst.symbol_table.symbols[name].set_context(exec_ctx) + + inst.symbol_table.set('this', inst) + inst.symbol_table.set('self', inst) + + method = inst.symbol_table.symbols[self.name] if self.name in inst.symbol_table.symbols else None + + if method == None or not isinstance(method, Function): + return res.failure(RTError( + self.pos_start, self.pos_end, + f"Function '{self.name}' not defined", + self.context + )) + + res.register(method.execute(args)) + if res.should_return(): return res + + return res.success(inst.set_context(self.context).set_pos(self.pos_start, self.pos_end)) + + def copy(self): + return self + + def __repr__(self): + return f"" + +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): + return self + + def __repr__(self): + return f"" + +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"" + + ##################################### + + 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, should_exit = 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 + )) + + if should_exit: return RTResult().success_exit(Number.null) + return RTResult().success(Number.null) + execute_run.arg_names = ["fn"] + + def execute_exit(self, exec_ctx): + return RTResult().success_exit(Number.null) + execute_exit.arg_names = [] + +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") +BuiltInFunction.exit = BuiltInFunction("exit") + +####################################### +# 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 + )) + + if node.child: + if not isinstance(value, Instance) and not isinstance(value, Class): + return res.failure(RTError( + node.pos_start, node.pos_end, + f"Value must be instance of class or class", + context + )) + + new_context = Context(value.parent_class.name, context, node.pos_start) + new_context.symbol_table = value.symbol_table + + child = res.register(self.visit(node.child, new_context)) + if res.error: return res + + value = child + + 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 + + if node.extra_names != []: + + nd = context.symbol_table.get(var_name) + prev = None + + if not nd: + return res.failure(RTError( + node.pos_start, node.pos_end, + f"'{name}' not defined", + context + )) + + for index, name_tok in enumerate(node.extra_names): + name = name_tok.value + + if not isinstance(nd, Class) and not isinstance(nd, Instance): + return res.failure(RTError( + node.pos_start, node.pos_end, + "Value must be instance of class or class", + context + )) + + prev = nd + nd = nd.symbol_table.symbols[name] if name in nd.symbol_table.symbols else None + + if not nd and index != len(node.extra_names)-1: + return res.failure(RTError( + node.pos_start, node.pos_end, + f"'{name}' not defined", + context + )) + + prev.symbol_table.set(name, value) + return res.success(value) + + 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() + + def visit_ClassNode(self, node, context): + res = RTResult() + + ctx = Context(node.class_name_tok.value, node.pos_start) + ctx.symbol_table = SymbolTable(context.symbol_table) + + res.register(self.visit(node.body_nodes, ctx)) + if res.should_return(): return res + + cls_ = Class(node.class_name_tok.value, ctx.symbol_table).set_context(context).set_pos(node.pos_start, node.pos_end) + context.symbol_table.set(node.class_name_tok.value, cls_) + return res.success(cls_) + +####################################### +# 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) +global_symbol_table.set("EXIT", BuiltInFunction.exit) + +def run(fn, text): + # Generate tokens + lexer = Lexer(fn, text) + tokens, error = lexer.make_tokens() + if error: return None, error, False + + # Generate AST + parser = Parser(tokens) + ast = parser.parse() + if ast.error: return None, ast.error, False + + # Run program + interpreter = Interpreter() + context = Context('') + context.symbol_table = global_symbol_table + result = interpreter.visit(ast.node, context) + + return result.value, result.error, result.should_exit diff --git a/classes/example.myopl b/classes/example.myopl new file mode 100644 index 0000000..55c9279 --- /dev/null +++ b/classes/example.myopl @@ -0,0 +1,15 @@ +CLASS Person + FUN Person(name) + VAR this.name = name # can also use "VAR self.name = name" + END + FUN get_name() + RETURN this.name + END +END + +VAR p1 = Person("John Stone") +PRINT(p1.get_name()) +VAR p2 = Person("James Johnson") +PRINT(p2.get_name()) + +EXIT() diff --git a/classes/shell.py b/classes/shell.py new file mode 100644 index 0000000..d7aeaeb --- /dev/null +++ b/classes/shell.py @@ -0,0 +1,17 @@ +import basic + +while True: + text = input('basic > ') + if text.strip() == "": continue + result, error, should_exit = basic.run('', text) + + if error: + print(error.as_string()) + elif result: + if len(result.elements) == 1: + print(repr(result.elements[0])) + else: + print(repr(result)) + + if should_exit: + break diff --git a/classes/strings_with_arrows.py b/classes/strings_with_arrows.py new file mode 100644 index 0000000..db0eea7 --- /dev/null +++ b/classes/strings_with_arrows.py @@ -0,0 +1,26 @@ +def string_with_arrows(text, pos_start, pos_end): + result = '' + + # Calculate indices + idx_start = max(text.rfind('\n', 0, pos_start.idx), 0) + idx_end = text.find('\n', idx_start + 1) + if idx_end < 0: idx_end = len(text) + + # Generate each line + line_count = pos_end.ln - pos_start.ln + 1 + for i in range(line_count): + # Calculate line columns + line = text[idx_start:idx_end] + col_start = pos_start.col if i == 0 else 0 + col_end = pos_end.col if i == line_count - 1 else len(line) - 1 + + # Append to result + result += line + '\n' + result += ' ' * col_start + '^' * (col_end - col_start) + + # Re-calculate indices + idx_start = idx_end + idx_end = text.find('\n', idx_start + 1) + if idx_end < 0: idx_end = len(text) + + return result.replace('\t', '') From 719b38785e11f55c6fee026c833095d729e8ffcc Mon Sep 17 00:00:00 2001 From: Pixelthegreat <65832183+Pixelthegreat@users.noreply.github.com> Date: Fri, 16 Apr 2021 19:05:30 -0400 Subject: [PATCH 2/2] Fixed EXIT function Fixed the EXIT function Also added basic string comparisons --- classes/basic.py | 20 ++++++++++++++++---- classes/example.myopl | 17 ++++++++++------- classes/shell.py | 4 ++-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/classes/basic.py b/classes/basic.py index 3840da4..6b4dde3 100644 --- a/classes/basic.py +++ b/classes/basic.py @@ -1420,10 +1420,10 @@ def success_break(self): return self def success_exit(self, exit_value): - self.reset() - self.should_exit = True - self.value = exit_value - return self + self.reset() + self.should_exit = True + self.value = exit_value + return self def failure(self, error): self.reset() @@ -1647,6 +1647,18 @@ def multed_by(self, other): else: return None, Value.illegal_operation(self, other) + def get_comparison_eq(self, other): + if isinstance(other, String): + 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, String): + return Number(int(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 diff --git a/classes/example.myopl b/classes/example.myopl index 55c9279..21bb7a3 100644 --- a/classes/example.myopl +++ b/classes/example.myopl @@ -1,15 +1,18 @@ CLASS Person FUN Person(name) - VAR this.name = name # can also use "VAR self.name = name" + VAR this.name = name END - FUN get_name() - RETURN this.name + FUN Format() + RETURN "My name is " + this.name + "." END END -VAR p1 = Person("John Stone") -PRINT(p1.get_name()) -VAR p2 = Person("James Johnson") -PRINT(p2.get_name()) +VAR inp = INPUT() +WHILE inp != "exit" THEN + VAR p = Person(inp) + PRINT(p.Format()) + VAR inp = INPUT() +END EXIT() +PRINT("This message will never ever show up! Isn't that wacky?!") diff --git a/classes/shell.py b/classes/shell.py index d7aeaeb..e7a5307 100644 --- a/classes/shell.py +++ b/classes/shell.py @@ -13,5 +13,5 @@ else: print(repr(result)) - if should_exit: - break + if should_exit: + break