diff --git a/examples/functions_with_params.fem b/examples/functions_with_params.fem new file mode 100644 index 0000000..83f38fc --- /dev/null +++ b/examples/functions_with_params.fem @@ -0,0 +1,12 @@ +Femboy add_numbers(a, b) Femboycore + Femme a + b +Periodt + +result is add_numbers(10, 5) +UwU Boy result + +Femboy greet(name) Femboycore + UwU Boy "Hello, " + name +Periodt + +greet("World") \ No newline at end of file diff --git a/src/interpreter.py b/src/interpreter.py index de793fa..7edcf4f 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -1,9 +1,13 @@ class Interpreter: def __init__(self, ast): self.ast = ast - self.variables = {} + self.scope_stack = [{}] self.functions = {} + @property + def current_scope(self): + return self.scope_stack[-1] + def interpret(self): for node in self.ast: self.visit(node) @@ -57,14 +61,15 @@ class Interpreter: def visit_Assign(self, node): var_name = node.left.value value = self.visit(node.right) - self.variables[var_name] = value + self.current_scope[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") + # Search up the scope stack for the variable + for scope in reversed(self.scope_stack): + if var_name in scope: + return scope[var_name] + raise NameError(f"name '{var_name}' is not defined") def visit_Block(self, node): for statement in node.statements: @@ -93,14 +98,27 @@ class Interpreter: raise NameError(f"Function '{func_name}' is not defined") func_info = self.functions[func_name] - # For simplicity, no arguments are passed for now - # In a real interpreter, you'd push a new scope and bind arguments to parameters + + # Evaluate arguments + evaluated_arguments = [self.visit(arg) for arg in node.arguments] + + # Create a new scope for the function call + new_scope = {} + for i, param_name in enumerate(func_info['parameters']): + new_scope[param_name] = evaluated_arguments[i] + + self.scope_stack.append(new_scope) # Execute function body try: self.visit(func_info['body']) except ReturnValue as e: + self.scope_stack.pop() # Pop the function scope return e.value + finally: + # Ensure scope is popped even if no return or an error occurs + if len(self.scope_stack) > 1: # Don't pop global scope + self.scope_stack.pop() def visit_ReturnStatement(self, node): raise ReturnValue(self.visit(node.value)) diff --git a/src/lexer.py b/src/lexer.py index 90fe9ab..5364672 100644 --- a/src/lexer.py +++ b/src/lexer.py @@ -31,11 +31,13 @@ class Lexer: if current_char == '"': self.pos += 1 - string_end = self.text.find('"', self.pos) - if string_end == -1: - self.error() - string = self.text[self.pos:string_end] - self.pos = string_end + 1 + start_string = self.pos + while self.pos < len(self.text) and self.text[self.pos] != '"': + self.pos += 1 + if self.pos == len(self.text): + self.error() # Unterminated string + string = self.text[start_string:self.pos] + self.pos += 1 # Consume closing quote return Token('STRING', string) if current_char.isdigit(): @@ -65,6 +67,9 @@ class Lexer: if current_char == '/': self.pos += 1 return Token('DIV', '/') + if current_char == ',': + self.pos += 1 + return Token('COMMA', ',') if current_char == '=': if self.pos + 1 < len(self.text) and self.text[self.pos + 1] == '=': self.pos += 2 @@ -133,4 +138,4 @@ class Lexer: tokens.append(token) if token.type == 'EOF': break - return tokens + return tokens \ No newline at end of file diff --git a/src/parser.py b/src/parser.py index f3098d2..114826e 100644 --- a/src/parser.py +++ b/src/parser.py @@ -196,8 +196,25 @@ class Parser: if name_token.type != 'ID': raise Exception("Expected function name (ID)") - # For simplicity, assume no parameters for now + print(f"After function name: {self.peek_next_token()}") + + # Parse parameters parameters = [] + if self.peek_next_token().type == 'LPAREN': + self.get_next_token() # Consume '(' + print(f"After LPAREN: {self.peek_next_token()}") + while self.peek_next_token().type != 'RPAREN': + param_token = self.get_next_token() + if param_token.type != 'ID': + raise Exception("Expected parameter name (ID)") + parameters.append(param_token.value) + print(f"After parameter {param_token.value}: {self.peek_next_token()}") + if self.peek_next_token().type == 'COMMA': + self.get_next_token() # Consume ',' + print(f"After COMMA: {self.peek_next_token()}") + # No 'elif' here, the loop condition handles the RPAREN + self.get_next_token() # Consume ')' + print(f"After RPAREN: {self.peek_next_token()}") if self.peek_next_token().type != 'FEMBOYCORE': raise Exception("Expected 'Femboycore' to start function body") @@ -258,9 +275,12 @@ class Parser: def parse_function_call(self, name_token): self.get_next_token() # Consume '(' arguments = [] - # For simplicity, assume no arguments for now - if self.peek_next_token().value == ')': - self.get_next_token() # Consume ')' - else: - raise Exception("Expected ')' after function call") + if self.peek_next_token().type != 'RPAREN': + while True: + arguments.append(self.expression()) + if self.peek_next_token().type == 'COMMA': + self.get_next_token() # Consume ',' + else: + break # Exit loop if not COMMA (implies RPAREN or EOF) + self.get_next_token() # Consume ')' return FunctionCall(name_token.value, arguments)