From f3e4be5949163ac0b71fb4da6043937bf84a1248 Mon Sep 17 00:00:00 2001 From: Alvin <524715@vistacollege.nl> Date: Tue, 22 Jul 2025 16:17:32 +0200 Subject: [PATCH] feat: Add conditional logic (if/else) --- examples/conditionals.fem | 13 +++++++ src/interpreter.py | 40 ++++++++++++++++++-- src/lexer.py | 37 ++++++++++++++++++- src/parser.py | 78 +++++++++++++++++++++++++++++++++++---- 4 files changed, 155 insertions(+), 13 deletions(-) create mode 100644 examples/conditionals.fem diff --git a/examples/conditionals.fem b/examples/conditionals.fem new file mode 100644 index 0000000..1d1da5b --- /dev/null +++ b/examples/conditionals.fem @@ -0,0 +1,13 @@ +my_age is 25 + +Femboy Feminine my_age > 18 Femboycore + UwU Boy "You are an adult femboy!" +Periodt +Androgyny Femboycore + UwU Boy "You are a young femboy!" +Periodt + +my_number is 10 +Femboy Feminine my_number == 10 Femboycore + UwU Boy "Number is 10!" +Periodt \ No newline at end of file diff --git a/src/interpreter.py b/src/interpreter.py index c43938c..dc547c0 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -18,15 +18,36 @@ class Interpreter: def visit_Number(self, node): return node.value + def visit_String(self, node): + return node.value + def visit_BinOp(self, node): + left_val = self.visit(node.left) + right_val = self.visit(node.right) if node.op.type == 'PLUS': - return self.visit(node.left) + self.visit(node.right) + return left_val + right_val elif node.op.type == 'MINUS': - return self.visit(node.left) - self.visit(node.right) + return left_val - right_val elif node.op.type == 'MUL': - return self.visit(node.left) * self.visit(node.right) + return left_val * right_val elif node.op.type == 'DIV': - return self.visit(node.left) / self.visit(node.right) + return left_val / right_val + + def visit_Comparison(self, node): + left_val = self.visit(node.left) + right_val = self.visit(node.right) + if node.op.type == 'EQ': + return left_val == right_val + elif node.op.type == 'NEQ': + return left_val != right_val + elif node.op.type == 'GT': + return left_val > right_val + elif node.op.type == 'GTE': + return left_val >= right_val + elif node.op.type == 'LT': + return left_val < right_val + elif node.op.type == 'LTE': + return left_val <= right_val def visit_Print(self, node): value_to_print = self.visit(node.value) @@ -43,3 +64,14 @@ class Interpreter: return self.variables[var_name] else: raise NameError(f"name '{var_name}' is not defined") + + def visit_Block(self, node): + for statement in node.statements: + self.visit(statement) + + def visit_IfStatement(self, node): + condition_result = self.visit(node.condition) + if condition_result: + self.visit(node.if_block) + elif node.else_block: + self.visit(node.else_block) diff --git a/src/lexer.py b/src/lexer.py index 3c894ce..adc9886 100644 --- a/src/lexer.py +++ b/src/lexer.py @@ -44,6 +44,7 @@ class Lexer: self.pos += 1 return Token('INTEGER', int(self.text[start_pos:self.pos])) + # Operators if current_char == '+': self.pos += 1 return Token('PLUS', '+') @@ -56,14 +57,48 @@ class Lexer: if current_char == '/': self.pos += 1 return Token('DIV', '/') + if current_char == '=': + if self.pos + 1 < len(self.text) and self.text[self.pos + 1] == '=': + self.pos += 2 + return Token('EQ', '==') + if current_char == '!': + if self.pos + 1 < len(self.text) and self.text[self.pos + 1] == '=': + self.pos += 2 + return Token('NEQ', '!=') + if current_char == '>': + if self.pos + 1 < len(self.text) and self.text[self.pos + 1] == '=': + self.pos += 2 + return Token('GTE', '>=') + else: + self.pos += 1 + return Token('GT', '>') + if current_char == '<': + if self.pos + 1 < len(self.text) and self.text[self.pos + 1] == '=': + self.pos += 2 + return Token('LTE', '<=') + else: + self.pos += 1 + return Token('LT', '<') - # Match keywords + # Match keywords (longer ones first) + if re.match(r'\bFemboy Feminine\b', self.text[self.pos:]): + self.pos += len('Femboy Feminine') + return Token('FEMBOY_FEMININE', 'Femboy Feminine') if re.match(r'\bUwU Boy\b', self.text[self.pos:]): self.pos += 7 return Token('PRINT', 'UwU Boy') + if re.match(r'\bAndrogyny\b', self.text[self.pos:]): + self.pos += len('Androgyny') + return Token('ANDROGYNY', 'Androgyny') if re.match(r'\bis\b', self.text[self.pos:]): self.pos += 2 return Token('ASSIGN', 'is') + if re.match(r'\bFemboycore\b', self.text[self.pos:]): + self.pos += len('Femboycore') + return Token('FEMBOYCORE', 'Femboycore') + if re.match(r'\bPeriodt\b', self.text[self.pos:]): + self.pos += len('Periodt') + return Token('PERIODT', 'Periodt') # Match identifiers match = re.match(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', self.text[self.pos:]) diff --git a/src/parser.py b/src/parser.py index 70eed4f..e1b1473 100644 --- a/src/parser.py +++ b/src/parser.py @@ -6,12 +6,23 @@ class Number(AST): self.token = token self.value = token.value +class String(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 Comparison(AST): + def __init__(self, left, op, right): + self.left = left + self.op = op + self.right = right + class Print(AST): def __init__(self, value): self.value = value @@ -27,6 +38,16 @@ class Variable(AST): self.token = token self.value = token.value +class Block(AST): + def __init__(self, statements): + self.statements = statements + +class IfStatement(AST): + def __init__(self, condition, if_block, else_block=None): + self.condition = condition + self.if_block = if_block + self.else_block = else_block + class Parser: def __init__(self, tokens): self.tokens = tokens @@ -58,14 +79,10 @@ 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': - # 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 + if token.type == 'FEMBOY_FEMININE': + return self.parse_if_statement() + raise Exception(f"Invalid statement starting with token {token.type}") def parse_print_statement(self): @@ -82,12 +99,48 @@ class Parser: right_expr = self.expression() return Assign(left=var_node, op=assign_token, right=right_expr) + def parse_if_statement(self): + self.get_next_token() # Consume FEMBOY_FEMININE + + condition = self.expression() + + # Expect Femboycore to start the if block + if self.peek_next_token().type != 'FEMBOYCORE': + raise Exception("Expected 'Femboycore' to start if block") + self.get_next_token() # Consume FEMBOYCORE + + if_block_statements = [] + while self.peek_next_token().type != 'PERIODT': + if self.peek_next_token().type == 'EOF': + raise Exception("Unterminated if block: Expected 'Periodt'") + if_block_statements.append(self.parse_statement()) + self.get_next_token() # Consume PERIODT + if_block = Block(if_block_statements) + + else_block = None + if self.peek_next_token().type == 'ANDROGYNY': + self.get_next_token() # Consume ANDROGYNY + # Expect Femboycore to start the else block + if self.peek_next_token().type != 'FEMBOYCORE': + raise Exception("Expected 'Femboycore' to start else block") + self.get_next_token() # Consume FEMBOYCORE + + else_block_statements = [] + while self.peek_next_token().type != 'PERIODT': + if self.peek_next_token().type == 'EOF': + raise Exception("Unterminated else block: Expected 'Periodt'") + else_block_statements.append(self.parse_statement()) + self.get_next_token() # Consume PERIODT + else_block = Block(else_block_statements) + + return IfStatement(condition, if_block, else_block) + 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 + return String(token) # Now returns a String AST node elif token.type == 'ID': return Variable(token) else: @@ -102,7 +155,16 @@ class Parser: def expression(self): node = self.term() + + # Handle addition/subtraction while self.peek_next_token().type in ('PLUS', 'MINUS'): token = self.get_next_token() node = BinOp(left=node, op=token, right=self.term()) + + # Handle comparisons + if self.peek_next_token().type in ('EQ', 'NEQ', 'GT', 'GTE', 'LT', 'LTE'): + op_token = self.get_next_token() + right_node = self.expression() # Recursively parse right side of comparison + node = Comparison(left=node, op=op_token, right=right_node) + return node