mirror of
https://github.com/Alvin-Zilverstand/femcode.git
synced 2026-03-06 13:23:39 +01:00
feat: Add conditional logic (if/else)
This commit is contained in:
@@ -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)
|
||||
|
||||
37
src/lexer.py
37
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:])
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user