From 08e2ea17ddf2cffbc2e846289e4dee35569232af Mon Sep 17 00:00:00 2001 From: Alvin <524715@vistacollege.nl> Date: Tue, 22 Jul 2025 16:27:04 +0200 Subject: [PATCH] feat: Implement functions (definition and calls) --- examples/functions.fem | 5 +++ src/interpreter.py | 29 ++++++++++++++++ src/lexer.py | 14 ++++++++ src/parser.py | 75 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 examples/functions.fem diff --git a/examples/functions.fem b/examples/functions.fem new file mode 100644 index 0000000..787c317 --- /dev/null +++ b/examples/functions.fem @@ -0,0 +1,5 @@ +Femboy say_hello Femboycore + UwU Boy "Hello from function!" +Periodt + +say_hello() \ No newline at end of file diff --git a/src/interpreter.py b/src/interpreter.py index af4b3fa..de793fa 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -2,6 +2,7 @@ class Interpreter: def __init__(self, ast): self.ast = ast self.variables = {} + self.functions = {} def interpret(self): for node in self.ast: @@ -79,3 +80,31 @@ class Interpreter: def visit_WhileStatement(self, node): while self.visit(node.condition): self.visit(node.body) + + def visit_FunctionDefinition(self, node): + self.functions[node.name] = { + 'parameters': node.parameters, + 'body': node.body + } + + def visit_FunctionCall(self, node): + func_name = node.name + if func_name not in self.functions: + 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 + + # Execute function body + try: + self.visit(func_info['body']) + except ReturnValue as e: + return e.value + + def visit_ReturnStatement(self, node): + raise ReturnValue(self.visit(node.value)) + +class ReturnValue(Exception): + def __init__(self, value): + self.value = value diff --git a/src/lexer.py b/src/lexer.py index 8c8ccc0..90fe9ab 100644 --- a/src/lexer.py +++ b/src/lexer.py @@ -44,6 +44,14 @@ class Lexer: self.pos += 1 return Token('INTEGER', int(self.text[start_pos:self.pos])) + # Parentheses + if current_char == '(': + self.pos += 1 + return Token('LPAREN', '(') + if current_char == ')': + self.pos += 1 + return Token('RPAREN', ')') + # Operators if current_char == '+': self.pos += 1 @@ -93,6 +101,12 @@ class Lexer: if re.match(r'\bOtokonoko\b', self.text[self.pos:]): self.pos += len('Otokonoko') return Token('OTOKONOKO', 'Otokonoko') + if re.match(r'\bFemboy\b', self.text[self.pos:]): + self.pos += len('Femboy') + return Token('FUNCTION_DEF', 'Femboy') + if re.match(r'\bFemme\b', self.text[self.pos:]): + self.pos += len('Femme') + return Token('RETURN', 'Femme') if re.match(r'\bis\b', self.text[self.pos:]): self.pos += 2 return Token('ASSIGN', 'is') diff --git a/src/parser.py b/src/parser.py index 5588931..f3098d2 100644 --- a/src/parser.py +++ b/src/parser.py @@ -53,6 +53,21 @@ class WhileStatement(AST): self.condition = condition self.body = body +class FunctionDefinition(AST): + def __init__(self, name, parameters, body): + self.name = name + self.parameters = parameters + self.body = body + +class FunctionCall(AST): + def __init__(self, name, arguments): + self.name = name + self.arguments = arguments + +class ReturnStatement(AST): + def __init__(self, value): + self.value = value + class Parser: def __init__(self, tokens): self.tokens = tokens @@ -82,8 +97,15 @@ class Parser: if token.type == 'PRINT': return self.parse_print_statement() - if token.type == 'ID' and self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type == 'ASSIGN': - return self.parse_assignment_statement() + if token.type == 'ID': + # Check for assignment + if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type == 'ASSIGN': + return self.parse_assignment_statement() + # Check for function call as a statement + if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type == 'LPAREN': + # Consume the ID token first, then parse the function call + name_token = self.get_next_token() + return self.parse_function_call(name_token) if token.type == 'FEMBOY_FEMININE': return self.parse_if_statement() @@ -91,6 +113,12 @@ class Parser: if token.type == 'OTOKONOKO': return self.parse_while_statement() + if token.type == 'FUNCTION_DEF': + return self.parse_function_definition() + + if token.type == 'RETURN': + return self.parse_return_statement() + raise Exception(f"Invalid statement starting with token {token.type}") def parse_print_statement(self): @@ -162,6 +190,34 @@ class Parser: return WhileStatement(condition, body) + def parse_function_definition(self): + self.get_next_token() # Consume FUNCTION_DEF + name_token = self.get_next_token() + if name_token.type != 'ID': + raise Exception("Expected function name (ID)") + + # For simplicity, assume no parameters for now + parameters = [] + + if self.peek_next_token().type != 'FEMBOYCORE': + raise Exception("Expected 'Femboycore' to start function body") + self.get_next_token() # Consume FEMBOYCORE + + body_statements = [] + while self.peek_next_token().type != 'PERIODT': + if self.peek_next_token().type == 'EOF': + raise Exception("Unterminated function definition: Expected 'Periodt'") + body_statements.append(self.parse_statement()) + self.get_next_token() # Consume PERIODT + body = Block(body_statements) + + return FunctionDefinition(name_token.value, parameters, body) + + def parse_return_statement(self): + self.get_next_token() # Consume RETURN + value = self.expression() + return ReturnStatement(value) + def factor(self): token = self.get_next_token() if token.type == 'INTEGER': @@ -169,6 +225,9 @@ class Parser: elif token.type == 'STRING': return String(token) # Now returns a String AST node elif token.type == 'ID': + # Check for function call + if self.peek_next_token().value == '(': # Assuming '(' is the next token for a function call + return self.parse_function_call(token) return Variable(token) else: raise Exception(f"Expected integer, string or identifier, got {token.type}") @@ -194,4 +253,14 @@ class Parser: right_node = self.expression() # Recursively parse right side of comparison node = Comparison(left=node, op=op_token, right=right_node) - return node \ No newline at end of file + return node + + 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") + return FunctionCall(name_token.value, arguments)