mirror of
https://github.com/Alvin-Zilverstand/femcode.git
synced 2026-03-06 02:56:56 +01:00
feat: Implement functions (definition and calls)
This commit is contained in:
5
examples/functions.fem
Normal file
5
examples/functions.fem
Normal file
@@ -0,0 +1,5 @@
|
||||
Femboy say_hello Femboycore
|
||||
UwU Boy "Hello from function!"
|
||||
Periodt
|
||||
|
||||
say_hello()
|
||||
@@ -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
|
||||
|
||||
14
src/lexer.py
14
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')
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user