feat: Implement logical operators (AND, OR, NOT)

This commit is contained in:
Alvin
2025-07-22 17:00:48 +02:00
parent 3461ed8863
commit 452088e947
4 changed files with 70 additions and 5 deletions

20
examples/logical_ops.fem Normal file
View File

@@ -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

View File

@@ -29,6 +29,16 @@ class Interpreter:
def visit_Boolean(self, node): def visit_Boolean(self, node):
return node.value 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): def visit_BinOp(self, node):
left_val = self.visit(node.left) left_val = self.visit(node.left)
right_val = self.visit(node.right) right_val = self.visit(node.right)

View File

@@ -131,6 +131,15 @@ class Lexer:
if re.match(r'\bCringe\b', self.text[self.pos:]): if re.match(r'\bCringe\b', self.text[self.pos:]):
self.pos += len('Cringe') self.pos += len('Cringe')
return Token('CRINGE', False) 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 identifiers
match = re.match(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', self.text[self.pos:]) match = re.match(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', self.text[self.pos:])

View File

@@ -16,6 +16,17 @@ class Boolean(AST):
self.token = token self.token = token
self.value = token.value 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): class BinOp(AST):
def __init__(self, left, op, right): def __init__(self, left, op, right):
self.left = left self.left = left
@@ -240,13 +251,20 @@ class Parser:
return Number(token) return Number(token)
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 == 'KAWAII' or token.type == 'CRINGE':
return Boolean(token)
elif token.type == 'ID': elif token.type == 'ID':
# Check for function call # Check for function call
if self.peek_next_token().type == 'LPAREN': # Assuming '(' is the next token for a 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 self.parse_function_call(token)
return Variable(token) return Variable(token)
elif token.type == 'KAWAII' or token.type == 'CRINGE': elif token.type == 'LPAREN': # Handle parenthesized expressions
return Boolean(token) 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: else:
raise Exception(f"Expected integer, string, boolean or identifier, got {token.type}") 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()) node = BinOp(left=node, op=token, right=self.factor())
return node return node
def expression(self): def comparison_expression(self):
node = self.term() node = self.term()
# Handle addition/subtraction # Handle addition/subtraction
@@ -268,11 +286,19 @@ class Parser:
# Handle comparisons # Handle comparisons
if self.peek_next_token().type in ('EQ', 'NEQ', 'GT', 'GTE', 'LT', 'LTE'): if self.peek_next_token().type in ('EQ', 'NEQ', 'GT', 'GTE', 'LT', 'LTE'):
op_token = self.get_next_token() 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) node = Comparison(left=node, op=op_token, right=right_node)
return 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): def parse_function_call(self, name_token):
self.get_next_token() # Consume '(' self.get_next_token() # Consume '('
arguments = [] arguments = []
@@ -284,4 +310,4 @@ class Parser:
else: else:
break # Exit loop if not COMMA (implies RPAREN or EOF) break # Exit loop if not COMMA (implies RPAREN or EOF)
self.get_next_token() # Consume ')' self.get_next_token() # Consume ')'
return FunctionCall(name_token.value, arguments) return FunctionCall(name_token.value, arguments)