diff --git a/examples/arithmetic.fem b/examples/arithmetic.fem new file mode 100644 index 0000000..ba6aacf --- /dev/null +++ b/examples/arithmetic.fem @@ -0,0 +1,3 @@ +my_number is 10 + 5 * 2 +UwU Boy my_number +UwU Boy 20 / 4 - 1 \ No newline at end of file diff --git a/src/interpreter.py b/src/interpreter.py index ef3dd9d..c43938c 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -1,6 +1,7 @@ class Interpreter: def __init__(self, ast): self.ast = ast + self.variables = {} def interpret(self): for node in self.ast: @@ -14,5 +15,31 @@ class Interpreter: def no_visit_method(self, node): raise Exception(f'No visit_{type(node).__name__} method defined') + def visit_Number(self, node): + return node.value + + def visit_BinOp(self, node): + if node.op.type == 'PLUS': + return self.visit(node.left) + self.visit(node.right) + elif node.op.type == 'MINUS': + return self.visit(node.left) - self.visit(node.right) + elif node.op.type == 'MUL': + return self.visit(node.left) * self.visit(node.right) + elif node.op.type == 'DIV': + return self.visit(node.left) / self.visit(node.right) + def visit_Print(self, node): - print(node.value) + value_to_print = self.visit(node.value) + print(value_to_print) + + def visit_Assign(self, node): + var_name = node.left.value + value = self.visit(node.right) + self.variables[var_name] = value + + def visit_Variable(self, node): + var_name = node.value + if var_name in self.variables: + return self.variables[var_name] + else: + raise NameError(f"name '{var_name}' is not defined") diff --git a/src/lexer.py b/src/lexer.py index 645c520..3c894ce 100644 --- a/src/lexer.py +++ b/src/lexer.py @@ -38,6 +38,25 @@ class Lexer: self.pos = string_end + 1 return Token('STRING', string) + if current_char.isdigit(): + start_pos = self.pos + while self.pos < len(self.text) and self.text[self.pos].isdigit(): + self.pos += 1 + return Token('INTEGER', int(self.text[start_pos:self.pos])) + + if current_char == '+': + self.pos += 1 + return Token('PLUS', '+') + if current_char == '-': + self.pos += 1 + return Token('MINUS', '-') + if current_char == '*': + self.pos += 1 + return Token('MUL', '*') + if current_char == '/': + self.pos += 1 + return Token('DIV', '/') + # Match keywords if re.match(r'\bUwU Boy\b', self.text[self.pos:]): self.pos += 7 diff --git a/src/parser.py b/src/parser.py index b51df69..70eed4f 100644 --- a/src/parser.py +++ b/src/parser.py @@ -1,6 +1,17 @@ class AST: pass +class Number(AST): + def __init__(self, token): + self.token = token + self.value = token.value + +class BinOp(AST): + def __init__(self, left, op, right): + self.left = left + self.token = self.op = op + self.right = right + class Print(AST): def __init__(self, value): self.value = value @@ -48,19 +59,19 @@ class Parser: if token.type == 'ID' and self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type == 'ASSIGN': return self.parse_assignment_statement() elif token.type == 'ID': - raise Exception(f"Unexpected identifier '{token.value}' without assignment.") + # This case should be handled by expression parsing if it's part of an expression + # For now, raise an error if it's a standalone ID not part of assignment or print + raise Exception(f"Unexpected identifier '{token.value}' without assignment or print.") + # Handle expressions as statements (e.g., just a number or an arithmetic operation) + # This might need refinement depending on what we consider a valid standalone statement + # For now, let's assume expressions are only part of print or assignment raise Exception(f"Invalid statement starting with token {token.type}") def parse_print_statement(self): self.get_next_token() # Consume PRINT token - token = self.get_next_token() - if token.type == 'STRING': - return Print(token.value) - elif token.type == 'ID': - return Print(Variable(token)) - else: - raise Exception("Expected a string or variable after 'UwU Boy'") + expr = self.expression() + return Print(expr) def parse_assignment_statement(self): var_token = self.get_next_token() @@ -68,8 +79,30 @@ class Parser: assign_token = self.get_next_token() - value_token = self.get_next_token() - if value_token.type == 'STRING': - return Assign(left=var_node, op=assign_token, right=value_token.value) + right_expr = self.expression() + return Assign(left=var_node, op=assign_token, right=right_expr) + + def factor(self): + token = self.get_next_token() + if token.type == 'INTEGER': + return Number(token) + elif token.type == 'STRING': + return token.value # Strings are literals, not AST nodes for now + elif token.type == 'ID': + return Variable(token) else: - raise Exception("Expected a string value for assignment") + raise Exception(f"Expected integer, string or identifier, got {token.type}") + + def term(self): + node = self.factor() + while self.peek_next_token().type in ('MUL', 'DIV'): + token = self.get_next_token() + node = BinOp(left=node, op=token, right=self.factor()) + return node + + def expression(self): + node = self.term() + while self.peek_next_token().type in ('PLUS', 'MINUS'): + token = self.get_next_token() + node = BinOp(left=node, op=token, right=self.term()) + return node