diff --git a/Classes/Constants.py b/Classes/Constants.py index ae5a299..ced6507 100644 --- a/Classes/Constants.py +++ b/Classes/Constants.py @@ -17,14 +17,46 @@ 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_KEYWORD = 'KEYWORD' -TT_IDENTIFIER = 'IDENTIFIER' TT_EQ = 'EQ' TT_LPAREN = 'LPAREN' TT_RPAREN = 'RPAREN' -TT_EOF = 'EOF' \ No newline at end of file +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', +] diff --git a/Classes/Error.py b/Classes/Error.py index 92756ae..569776d 100644 --- a/Classes/Error.py +++ b/Classes/Error.py @@ -1,4 +1,5 @@ -from error_line import * +from Classes.error_line import * + class Error: def __init__(self, pos_start, pos_end, error_name, details): @@ -6,46 +7,42 @@ def __init__(self, pos_start, pos_end, error_name, details): 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'{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) + 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 RunTimeError(Error): +class RTError(Error): def __init__(self, pos_start, pos_end, details, context): - super().__init__(pos_start, pos_end, 'Run Time Error', details) + 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}\n' - result += \ - string_with_arrows(self.pos_start.ftxt, - self.pos_start, self.pos_end) - + 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 = "" + result = '' pos = self.pos_start ctx = self.context while ctx: - result = f'File {pos.fn}, line {pos.ln + 1}, {ctx.display_name}\n' + result + 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 recent calls: "+result \ No newline at end of file + + return 'Traceback (most recent call last):\n' + result diff --git a/Classes/Functions.py b/Classes/Functions.py new file mode 100644 index 0000000..06da1ec --- /dev/null +++ b/Classes/Functions.py @@ -0,0 +1,303 @@ +from Classes.Values import * +from Classes.Context import * +from Classes.Interpreter import Interpreter +from Classes.Symbol import * +from Classes.Run import * +import os + +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 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"" + +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 = 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") \ No newline at end of file diff --git a/Classes/Interpreter.py b/Classes/Interpreter.py new file mode 100644 index 0000000..1a0fc63 --- /dev/null +++ b/Classes/Interpreter.py @@ -0,0 +1,280 @@ +from Classes.RunTimeResult import * +from Classes.Values import * +from Classes.Constants import * +from Classes.Functions import * + +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: + def condition(): return i < end_value.value + else: + def condition(): return 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() diff --git a/Classes/Interpretor.py b/Classes/Interpretor.py deleted file mode 100644 index 7df99e6..0000000 --- a/Classes/Interpretor.py +++ /dev/null @@ -1,78 +0,0 @@ -from RunTimeResult import * -from Values import * -from Constants import * - -class Interpretor: - 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 found') - - def visit_NumberNode(self, node, context): - return RTResult().success(Number(node.tok.value).setContext(context).setPos(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(RunTimeError( - node.pos_start, node.pos_end, - f"'{var_name}' is not defined", - context - )) - - value = value.copy().setPos(node.pos_start, node.pos_end) - 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.err: - 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.err: - return res - right = res.register(self.visit(node.right_node, context)) - if res.err: - return res - error = None - if (node.op_tok.type == TT_PLUS): - result, error = left.addedTo(right) - if (node.op_tok.type == TT_MINUS): - result, error = left.subbedBy(right) - if (node.op_tok.type == TT_MUL): - result, error = left.multiTo(right) - if (node.op_tok.type == TT_DIV): - result, error = left.divideBy(right) - if (node.op_tok.type == TT_POW): - result, error = left.poweredTo(right) - if error: - return res.failure(error) - return res.success(result.setPos(node.pos_start, node.pos_end)) - - def visit_UnaryOpNode(self, node, context): - res = RTResult() - number = res.register(self.visit(node.node, context)) - if res.err: - return res - - error = None - - if node.op_tok.type == TT_MINUS: - number, error = number.multiTo(Number(-1)) - - if error: - return res.failure(error) - return res.success(number.setPos(node.pos_start, node.pos_end)) \ No newline at end of file diff --git a/Classes/Lexer.py b/Classes/Lexer.py index c8a8b89..52fe37e 100644 --- a/Classes/Lexer.py +++ b/Classes/Lexer.py @@ -1,7 +1,7 @@ -from Constants import * -from Token import * -from Error import * -from Position import * +from Classes.Constants import * +from Classes.Token import * +from Classes.Error import * +from Classes.Position import * class Lexer: def __init__(self, fn, text): @@ -22,16 +22,22 @@ def make_tokens(self): 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(Token(TT_MINUS, pos_start=self.pos)) - self.advance() + tokens.append(self.make_minus_or_arrow()) elif self.current_char == '*': tokens.append(Token(TT_MUL, pos_start=self.pos)) self.advance() @@ -41,15 +47,32 @@ def make_tokens(self): elif self.current_char == '^': tokens.append(Token(TT_POW, pos_start=self.pos)) self.advance() - elif self.current_char == '=': - tokens.append(Token(TT_EQ, 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 @@ -69,9 +92,7 @@ def make_number(self): if dot_count == 1: break dot_count += 1 - num_str += '.' - else: - num_str += self.current_char + num_str += self.current_char self.advance() if dot_count == 0: @@ -79,11 +100,102 @@ def make_number(self): 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+'_': + + 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) \ No newline at end of file + 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() \ No newline at end of file diff --git a/Classes/Nodes.py b/Classes/Nodes.py index 3bc6d61..d353e39 100644 --- a/Classes/Nodes.py +++ b/Classes/Nodes.py @@ -1,19 +1,31 @@ class NumberNode: def __init__(self, tok): self.tok = tok - self.pos_start = tok.pos_start - self.pos_end = tok.pos_end + + self.pos_start = self.tok.pos_start + self.pos_end = self.tok.pos_end def __repr__(self): return f'{self.tok}' -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 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: @@ -24,11 +36,21 @@ def __init__(self, var_name_tok): 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 @@ -40,8 +62,92 @@ 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 = self.node.pos_end + self.pos_end = node.pos_end def __repr__(self): - return f'({self.op_tok}, {self.node})' \ No newline at end of file + 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 \ No newline at end of file diff --git a/Classes/Parser.py b/Classes/Parser.py index 558b840..ab3c830 100644 --- a/Classes/Parser.py +++ b/Classes/Parser.py @@ -1,30 +1,44 @@ -from Nodes import * -from Error import * -from Constants import * +from Classes.Nodes import * +from Classes.Error import * +from Classes.Constants import * ################################################################ # 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): - if isinstance(res, ParseResult): - if res.error: - self.error = res.error - return res.node + self.last_registered_advance_count = res.advance_count + self.advance_count += res.advance_count + if res.error: + self.error = res.error + return res.node - return res + 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): - self.error = error + if not self.error or self.last_registered_advance_count == 0: + self.error = error return self ####################################### @@ -38,49 +52,259 @@ def __init__(self, tokens): self.tok_idx = -1 self.advance() - def advance(self, ): + def advance(self): self.tok_idx += 1 - if self.tok_idx < len(self.tokens): - self.current_tok = self.tokens[self.tok_idx] + 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.expr() + 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, - "Expected '+', '-', '*','/' or '^'" + "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(self.advance()) + res.register_advancement() + self.advance() factor = res.register(self.factor()) if res.error: return res return res.success(UnaryOpNode(tok, factor)) - elif tok.type in (TT_INT, TT_FLOAT): - res.register(self.advance()) + 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(self.advance()) + res.register_advancement() + self.advance() return res.success(VarAccessNode(tok)) elif tok.type == TT_LPAREN: - res.register(self.advance()) + res.register_advancement() + self.advance() expr = res.register(self.expr()) if res.error: return res if self.current_tok.type == TT_RPAREN: - res.register(self.advance()) + res.register_advancement() + self.advance() return res.success(expr) else: return res.failure(InvalidSyntaxError( @@ -88,53 +312,479 @@ def factor(self): "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 or float" + "Expected int, float, identifier, '+', '-', '(', '[', IF', 'FOR', 'WHILE', 'FUN'" )) - def term(self): - return self.bin_op(self.factor, (TT_MUL, TT_DIV, TT_POW)) + def list_expr(self): + res = ParseResult() + element_nodes = [] + pos_start = self.current_tok.pos_start.copy() - def expr(self): + 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() - if self.current_tok.type == TT_KEYWORD and self.current_tok.value == 'VAR': - res.register(self.advance()) - if self.current_tok.type != TT_IDENTIFIER: + 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, - "Expected Identifier" + f"Expected 'END'" )) - var_name = self.current_tok - res.register(self.advance()) - if self.current_tok.type != TT_EQ: + 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, - "Expected Equal(=)" + f"Expected 'END'" )) - res.register(self.advance()) - expr = res.register(self.expr()) + 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(VarAssignNode(var_name, expr)) - return self.bin_op(self.term, (TT_PLUS, TT_MINUS)) + + 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, ops): + def bin_op(self, func_a, ops, func_b=None): + if func_b == None: + func_b = func_a + res = ParseResult() - left = res.register(func()) + left = res.register(func_a()) if res.error: return res - while self.current_tok.type in ops: + 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(self.advance()) - right = res.register(func()) + 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) \ No newline at end of file + return res.success(left) diff --git a/Classes/Run.py b/Classes/Run.py new file mode 100644 index 0000000..89aaa36 --- /dev/null +++ b/Classes/Run.py @@ -0,0 +1,49 @@ +from Classes.Symbol import * +from Classes.Values import * +from Classes.Functions import BuiltInFunction +from Classes.Lexer import * +from Classes.Parser import * +from Classes.Interpreter import * + +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('') + context.symbol_table = global_symbol_table + result = interpreter.visit(ast.node, context) + + return result.value, result.error diff --git a/Classes/RunTimeResult.py b/Classes/RunTimeResult.py index 031cedd..5727f0f 100644 --- a/Classes/RunTimeResult.py +++ b/Classes/RunTimeResult.py @@ -1,17 +1,52 @@ class RTResult: def __init__(self): + self.reset() + + def reset(self): self.value = None - self.err = None + self.error = None + self.func_return_value = None + self.loop_should_continue = False + self.loop_should_break = False def register(self, res): - if res.err: - self.err = res.error + 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.err = 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 + ) + diff --git a/Classes/Symbol.py b/Classes/Symbol.py index 0c7cda3..2305b45 100644 --- a/Classes/Symbol.py +++ b/Classes/Symbol.py @@ -1,7 +1,7 @@ class SymbolTable: - def __init__(self): + def __init__(self, parent=None): self.symbols = {} - self.parent = None + self.parent = parent def get(self, name): value = self.symbols.get(name, None) diff --git a/Classes/Token.py b/Classes/Token.py index d39a6a4..8e8d025 100644 --- a/Classes/Token.py +++ b/Classes/Token.py @@ -9,7 +9,10 @@ def __init__(self, type_, value=None, pos_start=None, pos_end=None): self.pos_end.advance() if pos_end: - self.pos_end = 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: diff --git a/Classes/Values.py b/Classes/Values.py index 6a247b6..919626e 100644 --- a/Classes/Values.py +++ b/Classes/Values.py @@ -1,50 +1,284 @@ -from Error import * -class Number: - def __init__(self, value): - self.value = value - self.setPos() - self.setContext() +from Classes.Error import * +from Classes.RunTimeResult import * +import math + +class Value: + def __init__(self): + self.set_pos() + self.set_context() - def setPos(self, start_pos=None, end_pos=None): - self.pos_start = start_pos - self.pos_end = end_pos + def set_pos(self, pos_start=None, pos_end=None): + self.pos_start = pos_start + self.pos_end = pos_end return self - def setContext(self, context=None): + def set_context(self, context=None): self.context = context return self - def addedTo(self, other): + 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(self.value+other.value).setContext(self.context), None + return Number(int(self.value != other.value)).set_context(self.context), None + else: + return None, Value.illegal_operation(self, other) - def subbedBy(self, other): + def get_comparison_lt(self, other): if isinstance(other, Number): - return Number(self.value-other.value).setContext(self.context), None + return Number(int(self.value < other.value)).set_context(self.context), None + else: + return None, Value.illegal_operation(self, other) - def multiTo(self, other): + def get_comparison_gt(self, other): if isinstance(other, Number): - return Number(self.value*other.value).setContext(self.context), None + return Number(int(self.value > other.value)).set_context(self.context), None + else: + return None, Value.illegal_operation(self, other) - def divideBy(self, other): + def get_comparison_lte(self, other): if isinstance(other, Number): - if other.value != 0: - return Number(self.value/other.value).setContext(self.context), None - return None, RunTimeError(other.pos_start, other.pos_end, "Division by zero error", self.context) + return Number(int(self.value <= other.value)).set_context(self.context), None + else: + return None, Value.illegal_operation(self, other) - def poweredTo(self, other): + def get_comparison_gte(self, other): if isinstance(other, Number): - if self.value == 0 and other.value <= 0: - return None, RunTimeError(other.pos_start, other.pos_end, "Only positive power of zero is defined", self.context) - if self.value < 0 and other.value != int(other.value): - return None, RunTimeError(other.pos_start, other.pos_end, "Fractional power to negatives are defined", self.context) - return Number(self.value**other.value).setContext(self.context), None + 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.setPos(self.pos_start, self.pos_end) - copy.setContext(self.context) + 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])}]' diff --git a/Classes/grammar.txt b/Classes/grammar.txt index 7fb6e74..2dddb6b 100644 --- a/Classes/grammar.txt +++ b/Classes/grammar.txt @@ -1,8 +1,59 @@ -expr : KEYWORD:VAR IDENTIFIER EQ expr - : term ((PLUS|MINUS) term)* +statements : NEWLINE* statement (NEWLINE+ statement)* NEWLINE* -term : factor((MUL|DIV|POW) factor)* +statement : KEYWORD:RETURN expr? + : KEYWORD:CONTINUE + : KEYWORD:BREAK + : expr -factor : INT|FLOAT|IDENTIFIER - : (PLUS|MINUS) factor - : LPAREN expr RPAREN \ No newline at end of file +expr : KEYWORD:VAR IDENTIFIER EQ expr + : comp-expr ((KEYWORD:AND|KEYWORD:OR) comp-expr)* + +comp-expr : NOT comp-expr + : arith-expr ((EE|LT|GT|LTE|GTE) arith-expr)* + +arith-expr : term ((PLUS|MINUS) term)* + +term : factor ((MUL|DIV) factor)* + +factor : (PLUS|MINUS) factor + : power + +power : call (POW factor)* + +call : atom (LPAREN (expr (COMMA expr)*)? RPAREN)? + +atom : INT|FLOAT|STRING|IDENTIFIER + : LPAREN expr RPAREN + : list-expr + : if-expr + : for-expr + : while-expr + : func-def + +list-expr : LSQUARE (expr (COMMA expr)*)? RSQUARE + +if-expr : KEYWORD:IF expr KEYWORD:THEN + (statement if-expr-b|if-expr-c?) + | (NEWLINE statements KEYWORD:END|if-expr-b|if-expr-c) + +if-expr-b : KEYWORD:ELIF expr KEYWORD:THEN + (statement if-expr-b|if-expr-c?) + | (NEWLINE statements KEYWORD:END|if-expr-b|if-expr-c) + +if-expr-c : KEYWORD:ELSE + statement + | (NEWLINE statements KEYWORD:END) + +for-expr : KEYWORD:FOR IDENTIFIER EQ expr KEYWORD:TO expr + (KEYWORD:STEP expr)? KEYWORD:THEN + statement + | (NEWLINE statements KEYWORD:END) + +while-expr : KEYWORD:WHILE expr KEYWORD:THEN + statement + | (NEWLINE statements KEYWORD:END) + +func-def : KEYWORD:FUN IDENTIFIER? + LPAREN (IDENTIFIER (COMMA IDENTIFIER)*)? RPAREN + (ARROW expr) + | (NEWLINE statements KEYWORD:END) \ No newline at end of file diff --git a/codebee.py b/codebee.py deleted file mode 100644 index 4c7c7b8..0000000 --- a/codebee.py +++ /dev/null @@ -1,39 +0,0 @@ -####################################### -# IMPORTS -####################################### - -from Classes.Lexer import * -from Classes.Symbol import * -from Classes.Parser import * -from Classes.Context import * -from Classes.Interpretor import * - - -####################################### -# RUN -####################################### - - -global_symbol_table = SymbolTable() -global_symbol_table.set("null", Number(0)) - - -def run(fn, text): - # Generate tokens - lexer = Lexer(fn, text) - tokens, error = lexer.make_tokens() - if error: - return None, error - - # Generate Abstract Syntax Tree - parser = Parser(tokens) - ast = parser.parse() - - if ast.error: - return None, ast.error - - context = Context("Program") - context.symbol_table = global_symbol_table - interpretor = Interpretor() - result = interpretor.visit(ast.node, context) - return result.value, result.err diff --git a/shell.py b/shell.py index ad9c51e..d53f16f 100644 --- a/shell.py +++ b/shell.py @@ -1,9 +1,15 @@ -import codebee +import Classes.Run as codebee while True: - text = input("Codebee >> ") - result, err = codebee.run("<>", text) - if err: - print(err.as_string()) - else: - print(result) + text = input('CodeBee >> ') + if text.strip() == "": + continue + result, error = codebee.run('', text) + + if error: + print(error.as_string()) + elif result: + if len(result.elements) == 1: + print(repr(result.elements[0])) + else: + print(repr(result))