feat: Implement functions (definition and calls)

This commit is contained in:
Alvin
2025-07-22 16:27:04 +02:00
parent 65e53daa66
commit 08e2ea17dd
4 changed files with 120 additions and 3 deletions

5
examples/functions.fem Normal file
View File

@@ -0,0 +1,5 @@
Femboy say_hello Femboycore
UwU Boy "Hello from function!"
Periodt
say_hello()

View File

@@ -2,6 +2,7 @@ class Interpreter:
def __init__(self, ast): def __init__(self, ast):
self.ast = ast self.ast = ast
self.variables = {} self.variables = {}
self.functions = {}
def interpret(self): def interpret(self):
for node in self.ast: for node in self.ast:
@@ -79,3 +80,31 @@ class Interpreter:
def visit_WhileStatement(self, node): def visit_WhileStatement(self, node):
while self.visit(node.condition): while self.visit(node.condition):
self.visit(node.body) 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

View File

@@ -44,6 +44,14 @@ class Lexer:
self.pos += 1 self.pos += 1
return Token('INTEGER', int(self.text[start_pos:self.pos])) 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 # Operators
if current_char == '+': if current_char == '+':
self.pos += 1 self.pos += 1
@@ -93,6 +101,12 @@ class Lexer:
if re.match(r'\bOtokonoko\b', self.text[self.pos:]): if re.match(r'\bOtokonoko\b', self.text[self.pos:]):
self.pos += len('Otokonoko') self.pos += len('Otokonoko')
return Token('OTOKONOKO', '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:]): if re.match(r'\bis\b', self.text[self.pos:]):
self.pos += 2 self.pos += 2
return Token('ASSIGN', 'is') return Token('ASSIGN', 'is')

View File

@@ -53,6 +53,21 @@ class WhileStatement(AST):
self.condition = condition self.condition = condition
self.body = body 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: class Parser:
def __init__(self, tokens): def __init__(self, tokens):
self.tokens = tokens self.tokens = tokens
@@ -82,8 +97,15 @@ class Parser:
if token.type == 'PRINT': if token.type == 'PRINT':
return self.parse_print_statement() return self.parse_print_statement()
if token.type == 'ID' and self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1].type == 'ASSIGN': if token.type == 'ID':
return self.parse_assignment_statement() # 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': if token.type == 'FEMBOY_FEMININE':
return self.parse_if_statement() return self.parse_if_statement()
@@ -91,6 +113,12 @@ class Parser:
if token.type == 'OTOKONOKO': if token.type == 'OTOKONOKO':
return self.parse_while_statement() 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}") raise Exception(f"Invalid statement starting with token {token.type}")
def parse_print_statement(self): def parse_print_statement(self):
@@ -162,6 +190,34 @@ class Parser:
return WhileStatement(condition, body) 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): def factor(self):
token = self.get_next_token() token = self.get_next_token()
if token.type == 'INTEGER': if token.type == 'INTEGER':
@@ -169,6 +225,9 @@ class Parser:
elif token.type == 'STRING': elif token.type == 'STRING':
return String(token) # Now returns a String AST node return String(token) # Now returns a String AST node
elif token.type == 'ID': 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) return Variable(token)
else: else:
raise Exception(f"Expected integer, string or identifier, got {token.type}") raise Exception(f"Expected integer, string or identifier, got {token.type}")
@@ -195,3 +254,13 @@ class Parser:
node = Comparison(left=node, op=op_token, right=right_node) node = Comparison(left=node, op=op_token, right=right_node)
return node 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)