From 452088e947797d84fb7d6023ba68640453857e78 Mon Sep 17 00:00:00 2001 From: Alvin <524715@vistacollege.nl> Date: Tue, 22 Jul 2025 17:00:48 +0200 Subject: [PATCH] feat: Implement logical operators (AND, OR, NOT) --- examples/logical_ops.fem | 20 ++++++++++++++++++++ src/interpreter.py | 10 ++++++++++ src/lexer.py | 9 +++++++++ src/parser.py | 36 +++++++++++++++++++++++++++++++----- 4 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 examples/logical_ops.fem diff --git a/examples/logical_ops.fem b/examples/logical_ops.fem new file mode 100644 index 0000000..53d173b --- /dev/null +++ b/examples/logical_ops.fem @@ -0,0 +1,20 @@ +my_true is Kawaii +my_false is Cringe + +Femboy Feminine my_true and my_false Femboycore + UwU Boy "This won't print." +Periodt +Androgyny Femboycore + UwU Boy "True AND False is False." +Periodt + +Femboy Feminine my_true or my_false Femboycore + UwU Boy "True OR False is True." +Periodt + +Femboy Feminine not my_true Femboycore + UwU Boy "This won't print." +Periodt +Androgyny Femboycore + UwU Boy "NOT True is False." +Periodt \ No newline at end of file diff --git a/src/interpreter.py b/src/interpreter.py index 93d8787..a1abfc2 100644 --- a/src/interpreter.py +++ b/src/interpreter.py @@ -29,6 +29,16 @@ class Interpreter: def visit_Boolean(self, node): return node.value + def visit_LogicalOp(self, node): + if node.op.type == 'AND': + return self.visit(node.left) and self.visit(node.right) + elif node.op.type == 'OR': + return self.visit(node.left) or self.visit(node.right) + + def visit_UnaryOp(self, node): + if node.op.type == 'NOT': + return not self.visit(node.right) + def visit_BinOp(self, node): left_val = self.visit(node.left) right_val = self.visit(node.right) diff --git a/src/lexer.py b/src/lexer.py index 05ed6e6..523059a 100644 --- a/src/lexer.py +++ b/src/lexer.py @@ -131,6 +131,15 @@ class Lexer: if re.match(r'\bCringe\b', self.text[self.pos:]): self.pos += len('Cringe') return Token('CRINGE', False) + if re.match(r'\band\b', self.text[self.pos:]): + self.pos += len('and') + return Token('AND', 'and') + if re.match(r'\bor\b', self.text[self.pos:]): + self.pos += len('or') + return Token('OR', 'or') + if re.match(r'\bnot\b', self.text[self.pos:]): + self.pos += len('not') + return Token('NOT', 'not') # 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 376e047..7126533 100644 --- a/src/parser.py +++ b/src/parser.py @@ -16,6 +16,17 @@ class Boolean(AST): self.token = token self.value = token.value +class LogicalOp(AST): + def __init__(self, left, op, right=None): + self.left = left + self.op = op + self.right = right + +class UnaryOp(AST): + def __init__(self, op, right): + self.op = op + self.right = right + class BinOp(AST): def __init__(self, left, op, right): self.left = left @@ -240,13 +251,20 @@ class Parser: return Number(token) elif token.type == 'STRING': return String(token) # Now returns a String AST node + elif token.type == 'KAWAII' or token.type == 'CRINGE': + return Boolean(token) elif token.type == 'ID': # Check for function call if self.peek_next_token().type == 'LPAREN': # Assuming '(' is the next token for a function call return self.parse_function_call(token) return Variable(token) - elif token.type == 'KAWAII' or token.type == 'CRINGE': - return Boolean(token) + elif token.type == 'LPAREN': # Handle parenthesized expressions + node = self.expression() + if self.get_next_token().type != 'RPAREN': + raise Exception("Expected ')'") + return node + elif token.type == 'NOT': # Handle NOT operator + return UnaryOp(token, self.factor()) # NOT applies to the next factor/expression else: raise Exception(f"Expected integer, string, boolean or identifier, got {token.type}") @@ -257,7 +275,7 @@ class Parser: node = BinOp(left=node, op=token, right=self.factor()) return node - def expression(self): + def comparison_expression(self): node = self.term() # Handle addition/subtraction @@ -268,11 +286,19 @@ class Parser: # 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 + right_node = self.comparison_expression() # Recursively parse right side of comparison node = Comparison(left=node, op=op_token, right=right_node) return node + def expression(self): + node = self.comparison_expression() + while self.peek_next_token().type in ('AND', 'OR'): + op_token = self.get_next_token() + right_node = self.comparison_expression() + node = LogicalOp(left=node, op=op_token, right=right_node) + return node + def parse_function_call(self, name_token): self.get_next_token() # Consume '(' arguments = [] @@ -284,4 +310,4 @@ class Parser: else: break # Exit loop if not COMMA (implies RPAREN or EOF) self.get_next_token() # Consume ')' - return FunctionCall(name_token.value, arguments) + return FunctionCall(name_token.value, arguments) \ No newline at end of file