"""Parser for GLSL source AST construction."""
from .OpenglAst import (
ShaderNode,
VariableNode,
AssignmentNode,
FunctionNode,
ArrayAccessNode,
BinaryOpNode,
UnaryOpNode,
ReturnNode,
FunctionCallNode,
IfNode,
ForNode,
WhileNode,
DoWhileNode,
MemberAccessNode,
TernaryOpNode,
StructNode,
SwitchNode,
CaseNode,
BlockNode,
NumberNode,
PostfixOpNode,
BreakNode,
ContinueNode,
DiscardNode,
)
TYPE_TOKENS = {
"VOID",
"BOOL",
"INT",
"UINT",
"FLOAT",
"DOUBLE",
"VECTOR",
"MATRIX",
"SAMPLER2D",
"SAMPLER3D",
"SAMPLERCUBE",
"SAMPLER1D",
"SAMPLER1DARRAY",
"SAMPLER1DSHADOW",
"SAMPLER1DARRAYSHADOW",
"SAMPLER2DARRAY",
"SAMPLER2DARRAYSHADOW",
"SAMPLERCUBEARRAY",
"SAMPLERCUBEARRAYSHADOW",
"SAMPLER2DSHADOW",
"SAMPLER2DRECT",
"SAMPLER2DRECTSHADOW",
"SAMPLERBUFFER",
"SAMPLERCUBESHADOW",
"SAMPLER2DMS",
"SAMPLER2DMSARRAY",
"ISAMPLER1D",
"ISAMPLER2D",
"ISAMPLER3D",
"ISAMPLERCUBE",
"ISAMPLER1DARRAY",
"ISAMPLER2DARRAY",
"ISAMPLERCUBEARRAY",
"ISAMPLER2DRECT",
"ISAMPLERBUFFER",
"ISAMPLER2DMS",
"ISAMPLER2DMSARRAY",
"USAMPLER1D",
"USAMPLER2D",
"USAMPLER3D",
"USAMPLERCUBE",
"USAMPLER1DARRAY",
"USAMPLER2DARRAY",
"USAMPLERCUBEARRAY",
"USAMPLER2DRECT",
"USAMPLERBUFFER",
"USAMPLER2DMS",
"USAMPLER2DMSARRAY",
"IMAGE1D",
"IMAGE2D",
"IMAGE3D",
"IMAGECUBE",
"IMAGE1DARRAY",
"IMAGE2DARRAY",
"IMAGECUBEARRAY",
"IMAGE2DRECT",
"IMAGEBUFFER",
"IMAGE2DMS",
"IMAGE2DMSARRAY",
"IIMAGE1D",
"IIMAGE2D",
"IIMAGE3D",
"IIMAGECUBE",
"IIMAGE1DARRAY",
"IIMAGE2DARRAY",
"IIMAGECUBEARRAY",
"IIMAGE2DRECT",
"IIMAGEBUFFER",
"IIMAGE2DMS",
"IIMAGE2DMSARRAY",
"UIMAGE1D",
"UIMAGE2D",
"UIMAGE3D",
"UIMAGECUBE",
"UIMAGE1DARRAY",
"UIMAGE2DARRAY",
"UIMAGECUBEARRAY",
"UIMAGE2DRECT",
"UIMAGEBUFFER",
"UIMAGE2DMS",
"UIMAGE2DMSARRAY",
"ATOMIC_UINT",
}
QUALIFIER_TOKENS = {
"IN",
"OUT",
"INOUT",
"UNIFORM",
"CONST",
"ATTRIBUTE",
"VARYING",
"BUFFER",
"SHARED",
"READONLY",
"WRITEONLY",
"COHERENT",
"VOLATILE",
"RESTRICT",
"FLAT",
"SMOOTH",
"NOPERSPECTIVE",
"CENTROID",
"SAMPLE",
"PATCH",
"INVARIANT",
"PRECISE",
"SUBROUTINE",
"LOWP",
"MEDIUMP",
"HIGHP",
}
ASSIGNMENT_TOKENS = {
"EQUALS": "=",
"PLUS_EQUALS": "+=",
"MINUS_EQUALS": "-=",
"MULTIPLY_EQUALS": "*=",
"DIVIDE_EQUALS": "/=",
"MOD_EQUALS": "%=",
"ASSIGN_AND": "&=",
"ASSIGN_OR": "|=",
"ASSIGN_XOR": "^=",
"ASSIGN_SHIFT_LEFT": "<<=",
"ASSIGN_SHIFT_RIGHT": ">>=",
}
[docs]
class GLSLParser:
"""Parse GLSL tokens into the OpenGL backend shader AST."""
def __init__(self, tokens, shader_type="vertex"):
"""Initialize the parser for a token stream and shader stage."""
self.tokens = tokens or [("EOF", "")]
self.shader_type = shader_type
self.index = 0
self.current_token = self.tokens[self.index]
[docs]
def advance(self):
"""Advance to the next token, falling back to EOF at the end."""
self.index += 1
if self.index < len(self.tokens):
self.current_token = self.tokens[self.index]
else:
self.current_token = ("EOF", "")
[docs]
def eat(self, token_type):
"""Consume the current token when it matches ``token_type``."""
if self.current_token[0] == token_type:
self.advance()
else:
raise SyntaxError(
f"Expected {token_type}, got {self.current_token[0]} ({self.current_token[1]})"
)
[docs]
def peek(self, offset=1):
"""Return a lookahead token without advancing the parser."""
idx = self.index + offset
if idx < len(self.tokens):
return self.tokens[idx]
return ("EOF", "")
[docs]
def skip_newlines(self):
"""Advance past newline tokens that separate GLSL declarations."""
while self.current_token[0] == "NEWLINE":
self.advance()
[docs]
def parse(self):
"""Parse the complete token stream into a shader node."""
shader = self.parse_shader()
if self.current_token[0] != "EOF":
self.eat("EOF")
return shader
[docs]
def parse_shader(self):
"""Parse top-level GLSL declarations, functions, and layout blocks."""
io_variables = []
uniforms = []
constants = []
global_variables = []
functions = []
structs = []
preprocessor = []
layouts = []
while self.current_token[0] != "EOF":
self.skip_newlines()
if self.current_token[0] == "EOF":
break
if self.current_token[0] == "HASH":
preprocessor.append(self.parse_preprocessor())
continue
if self.current_token[0] == "PRECISION":
precision_stmt = self.parse_precision_statement()
if precision_stmt:
preprocessor.append(precision_stmt)
continue
if self.current_token[0] == "STRUCT":
struct_node, extra_vars = self.parse_struct()
structs.append(struct_node)
for var in extra_vars:
global_variables.append(var)
continue
layout = None
if self.current_token[0] == "LAYOUT":
layout = self.parse_layout_qualifier()
qualifiers = self.parse_qualifiers()
if layout is not None and self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
layouts.append({"layout": layout, "qualifiers": qualifiers})
continue
if (
qualifiers
and self.current_token[0] == "IDENTIFIER"
and self.peek(1)[0] == "SEMICOLON"
):
name = self.current_token[1]
self.eat("IDENTIFIER")
self.eat("SEMICOLON")
global_variables.append(
VariableNode("", name, qualifiers=qualifiers, layout=layout)
)
continue
if self.current_token[0] == "IDENTIFIER" and self.peek(1)[0] == "LBRACE":
struct_node, block_vars = self.parse_interface_block(qualifiers, layout)
structs.append(struct_node)
for var in block_vars:
lowered = {q.lower() for q in var.qualifiers or []}
if "uniform" in lowered:
uniforms.append(var)
elif "in" in lowered or "out" in lowered or "inout" in lowered:
io_variables.append(var)
else:
global_variables.append(var)
continue
if self.current_token[0] == "STRUCT":
struct_node, extra_vars = self.parse_struct()
structs.append(struct_node)
for var in extra_vars:
global_variables.append(var)
continue
if (
self.current_token[0] in TYPE_TOKENS
or self.current_token[0] == "IDENTIFIER"
):
type_name = self.parse_type()
if (
self.current_token[0] == "IDENTIFIER"
and self.peek(1)[0] == "LPAREN"
):
function = self.parse_function(type_name)
functions.append(function)
continue
declarations = self.parse_variable_declarations(
type_name, qualifiers=qualifiers, layout=layout
)
for var in declarations:
lowered = {q.lower() for q in var.qualifiers or []}
if "uniform" in lowered:
uniforms.append(var)
elif "const" in lowered:
constants.append(var)
elif "in" in lowered or "out" in lowered or "inout" in lowered:
io_variables.append(var)
else:
global_variables.append(var)
continue
# Skip unexpected tokens to avoid infinite loop
self.advance()
shader = ShaderNode(
functions=functions,
structs=structs,
global_variables=global_variables,
uniforms=uniforms,
io_variables=io_variables,
constant=constants,
shader_type=self.shader_type,
preprocessor=preprocessor,
layouts=layouts,
)
return shader
def parse_preprocessor(self):
self.eat("HASH")
tokens = ["#"]
while self.current_token[0] not in ("NEWLINE", "EOF"):
tokens.append(self.current_token[1])
self.advance()
if self.current_token[0] == "NEWLINE":
self.advance()
if len(tokens) > 1:
return "#" + " ".join(tokens[1:]).strip()
return "#"
def parse_precision_statement(self):
parts = [self.current_token[1]]
self.eat("PRECISION")
while self.current_token[0] != "SEMICOLON" and self.current_token[0] != "EOF":
parts.append(self.current_token[1])
self.advance()
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return " ".join(parts).strip() + ";"
def parse_layout_qualifier(self):
qualifiers = {}
self.eat("LAYOUT")
self.eat("LPAREN")
while self.current_token[0] != "RPAREN":
if self.current_token[0] in ("IDENTIFIER", "IN", "OUT") or (
self.current_token[0] in QUALIFIER_TOKENS
):
key = self.current_token[1]
self.advance()
value = None
if self.current_token[0] == "EQUALS":
self.eat("EQUALS")
value = self.parse_layout_value()
qualifiers[key] = value
else:
raise SyntaxError(
f"Unexpected token in layout qualifier: {self.current_token}"
)
if self.current_token[0] == "COMMA":
self.eat("COMMA")
self.eat("RPAREN")
return qualifiers
def parse_layout_value(self):
if self.current_token[0] == "NUMBER":
value = self.current_token[1]
self.eat("NUMBER")
return value
if self.current_token[0] == "IDENTIFIER":
value = self.current_token[1]
self.eat("IDENTIFIER")
return value
raise SyntaxError(f"Expected layout qualifier value, got {self.current_token}")
def parse_qualifiers(self):
qualifiers = []
while self.current_token[0] in QUALIFIER_TOKENS:
if self.current_token[0] == "SUBROUTINE":
self.advance()
if self.current_token[0] == "LPAREN":
self.eat("LPAREN")
if self.current_token[0] in TYPE_TOKENS:
type_name = self.current_token[1]
self.advance()
elif self.current_token[0] == "IDENTIFIER":
type_name = self.current_token[1]
self.advance()
else:
raise SyntaxError(
f"Expected subroutine type, got {self.current_token}"
)
self.eat("RPAREN")
qualifiers.append(f"subroutine({type_name})")
else:
qualifiers.append("subroutine")
continue
qualifiers.append(self.current_token[1])
self.advance()
return qualifiers
def parse_type(self):
if self.current_token[0] in TYPE_TOKENS:
type_name = self.current_token[1]
self.advance()
return type_name
if self.current_token[0] == "IDENTIFIER":
type_name = self.current_token[1]
self.eat("IDENTIFIER")
return type_name
raise SyntaxError(f"Expected type, got {self.current_token}")
def parse_variable_declarations(
self, type_name, qualifiers=None, layout=None, consume_semicolon=True
):
variables = []
while True:
if self.current_token[0] != "IDENTIFIER":
raise SyntaxError(
f"Expected identifier in declaration, got {self.current_token}"
)
name = self.current_token[1]
self.eat("IDENTIFIER")
array_size = None
if self.current_token[0] == "LBRACKET":
self.eat("LBRACKET")
if self.current_token[0] != "RBRACKET":
array_size = self.parse_expression()
self.eat("RBRACKET")
value = None
if self.current_token[0] == "EQUALS":
self.eat("EQUALS")
value = self.parse_expression()
var = VariableNode(
type_name,
name,
value=value,
qualifiers=qualifiers or [],
array_size=array_size,
layout=layout,
)
lowered = {q.lower() for q in qualifiers or []}
if "in" in lowered:
var.io_type = "IN"
if "out" in lowered:
var.io_type = "OUT"
if "inout" in lowered:
var.io_type = "INOUT"
if "const" in lowered:
var.is_const = True
variables.append(var)
if self.current_token[0] == "COMMA":
self.eat("COMMA")
continue
break
if consume_semicolon:
if self.current_token[0] != "SEMICOLON":
raise SyntaxError(
f"Expected ';' after declaration, got {self.current_token}"
)
self.eat("SEMICOLON")
return variables
def parse_struct(self):
self.eat("STRUCT")
name = self.current_token[1]
self.eat("IDENTIFIER")
self.eat("LBRACE")
members = []
while self.current_token[0] != "RBRACE":
self.skip_newlines()
if self.current_token[0] == "RBRACE":
break
if self.current_token[0] in QUALIFIER_TOKENS:
qualifiers = self.parse_qualifiers()
else:
qualifiers = []
member_type = self.parse_type()
if self.current_token[0] != "IDENTIFIER":
raise SyntaxError(
f"Expected identifier in struct field, got {self.current_token}"
)
member_name = self.current_token[1]
self.eat("IDENTIFIER")
array_size = None
if self.current_token[0] == "LBRACKET":
self.eat("LBRACKET")
if self.current_token[0] != "RBRACKET":
array_size = self.parse_expression()
self.eat("RBRACKET")
self.eat("SEMICOLON")
members.append(
VariableNode(
member_type,
member_name,
qualifiers=qualifiers,
array_size=array_size,
)
)
self.eat("RBRACE")
variables = []
if self.current_token[0] == "IDENTIFIER":
variables = self.parse_variable_declarations(name, qualifiers=[])
else:
self.eat("SEMICOLON")
return StructNode(name, members), variables
def parse_interface_block(self, qualifiers, layout):
block_name = self.current_token[1]
self.eat("IDENTIFIER")
self.eat("LBRACE")
members = []
while self.current_token[0] != "RBRACE":
self.skip_newlines()
if self.current_token[0] == "RBRACE":
break
member_qualifiers = self.parse_qualifiers()
member_type = self.parse_type()
if self.current_token[0] != "IDENTIFIER":
raise SyntaxError(
f"Expected identifier in interface block, got {self.current_token}"
)
member_name = self.current_token[1]
self.eat("IDENTIFIER")
array_size = None
if self.current_token[0] == "LBRACKET":
self.eat("LBRACKET")
if self.current_token[0] != "RBRACKET":
array_size = self.parse_expression()
self.eat("RBRACKET")
self.eat("SEMICOLON")
members.append(
VariableNode(
member_type,
member_name,
qualifiers=member_qualifiers,
array_size=array_size,
)
)
self.eat("RBRACE")
instance_name = None
array_size = None
if self.current_token[0] == "IDENTIFIER":
instance_name = self.current_token[1]
self.eat("IDENTIFIER")
if self.current_token[0] == "LBRACKET":
self.eat("LBRACKET")
if self.current_token[0] != "RBRACKET":
array_size = self.parse_expression()
self.eat("RBRACKET")
self.eat("SEMICOLON")
struct_node = StructNode(block_name, members)
block_vars = []
if instance_name:
block_vars.append(
VariableNode(
block_name,
instance_name,
qualifiers=qualifiers,
array_size=array_size,
layout=layout,
)
)
else:
for member in members:
member.qualifiers = list(member.qualifiers or []) + list(
qualifiers or []
)
member.layout = layout
block_vars.append(member)
return struct_node, block_vars
def parse_function(self, return_type):
name = self.current_token[1]
qualifier = None
if name == "main":
qualifier = self.shader_type
self.eat("IDENTIFIER")
params = self.parse_parameters()
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return FunctionNode(return_type, name, params, body=[])
self.eat("LBRACE")
body = self.parse_block()
self.eat("RBRACE")
qualifiers = [qualifier] if qualifier else []
return FunctionNode(return_type, name, params, body, qualifiers=qualifiers)
def parse_parameters(self):
self.eat("LPAREN")
params = []
if self.current_token[0] != "RPAREN":
while True:
qualifiers = self.parse_qualifiers()
param_type = self.parse_type()
param_name = self.current_token[1]
self.eat("IDENTIFIER")
array_size = None
if self.current_token[0] == "LBRACKET":
self.eat("LBRACKET")
if self.current_token[0] != "RBRACKET":
array_size = self.parse_expression()
self.eat("RBRACKET")
params.append(
VariableNode(
param_type,
param_name,
qualifiers=qualifiers,
array_size=array_size,
)
)
if self.current_token[0] == "COMMA":
self.eat("COMMA")
continue
break
self.eat("RPAREN")
return params
def parse_block(self):
statements = []
while self.current_token[0] not in ("RBRACE", "EOF"):
self.skip_newlines()
if self.current_token[0] in ("RBRACE", "EOF"):
break
stmt = self.parse_statement()
if isinstance(stmt, list):
statements.extend(stmt)
elif stmt is not None:
statements.append(stmt)
return statements
def parse_statement(self):
self.skip_newlines()
if self.current_token[0] in ("RBRACE", "EOF"):
return None
if self.current_token[0] == "LBRACE":
self.eat("LBRACE")
block = self.parse_block()
self.eat("RBRACE")
return BlockNode(block)
if self.current_token[0] == "IF":
return self.parse_if_statement()
if self.current_token[0] == "FOR":
return self.parse_for_loop()
if self.current_token[0] == "WHILE":
return self.parse_while_loop()
if self.current_token[0] == "DO":
return self.parse_do_while_loop()
if self.current_token[0] == "SWITCH":
return self.parse_switch_statement()
if self.current_token[0] == "RETURN":
return self.parse_return_statement()
if self.current_token[0] == "BREAK":
self.eat("BREAK")
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return BreakNode()
if self.current_token[0] == "CONTINUE":
self.eat("CONTINUE")
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return ContinueNode()
if self.current_token[0] == "DISCARD":
self.eat("DISCARD")
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return DiscardNode()
if (
self.current_token[0] in QUALIFIER_TOKENS
or self.current_token[0] in TYPE_TOKENS
):
qualifiers = self.parse_qualifiers()
type_name = self.parse_type()
return self.parse_variable_declarations(type_name, qualifiers=qualifiers)
if self.current_token[0] == "IDENTIFIER" and self.peek(1)[0] == "IDENTIFIER":
type_name = self.parse_type()
return self.parse_variable_declarations(type_name, qualifiers=[])
expr = self.parse_expression()
if self.current_token[0] in ASSIGNMENT_TOKENS:
op = ASSIGNMENT_TOKENS[self.current_token[0]]
self.eat(self.current_token[0])
right = self.parse_expression()
if self.current_token[0] != "SEMICOLON":
raise SyntaxError(
f"Expected ';' after assignment, got {self.current_token}"
)
self.eat("SEMICOLON")
return AssignmentNode(expr, right, op)
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return expr
raise SyntaxError(f"Expected ';' after expression, got {self.current_token}")
def parse_if_statement(self):
self.eat("IF")
self.eat("LPAREN")
condition = self.parse_expression()
self.eat("RPAREN")
self.eat("LBRACE")
if_body = self.parse_block()
self.eat("RBRACE")
else_body = None
else_if_chain = []
while self.current_token[0] == "ELSE" and self.peek(1)[0] == "IF":
self.eat("ELSE")
self.eat("IF")
self.eat("LPAREN")
else_if_condition = self.parse_expression()
self.eat("RPAREN")
self.eat("LBRACE")
else_if_body = self.parse_block()
self.eat("RBRACE")
else_if_chain.append((else_if_condition, else_if_body))
if self.current_token[0] == "ELSE":
self.eat("ELSE")
self.eat("LBRACE")
else_body = self.parse_block()
self.eat("RBRACE")
node = IfNode(condition, if_body, else_body)
if else_if_chain:
node.else_if_chain = else_if_chain
return node
def parse_for_loop(self):
self.eat("FOR")
self.eat("LPAREN")
init = None
if self.current_token[0] != "SEMICOLON":
if (
self.current_token[0] in QUALIFIER_TOKENS
or self.current_token[0] in TYPE_TOKENS
):
qualifiers = self.parse_qualifiers()
type_name = self.parse_type()
init_decls = self.parse_variable_declarations(
type_name, qualifiers=qualifiers, consume_semicolon=False
)
init = init_decls[0] if init_decls else None
else:
init = self.parse_expression()
if self.current_token[0] in ASSIGNMENT_TOKENS:
op = ASSIGNMENT_TOKENS[self.current_token[0]]
self.eat(self.current_token[0])
right = self.parse_expression()
init = AssignmentNode(init, right, op)
self.eat("SEMICOLON")
condition = None
if self.current_token[0] != "SEMICOLON":
condition = self.parse_expression()
self.eat("SEMICOLON")
update = None
if self.current_token[0] != "RPAREN":
update = self.parse_expression()
if self.current_token[0] in ASSIGNMENT_TOKENS:
op = ASSIGNMENT_TOKENS[self.current_token[0]]
self.eat(self.current_token[0])
right = self.parse_expression()
update = AssignmentNode(update, right, op)
self.eat("RPAREN")
self.eat("LBRACE")
body = self.parse_block()
self.eat("RBRACE")
return ForNode(init, condition, update, body)
def parse_while_loop(self):
self.eat("WHILE")
self.eat("LPAREN")
condition = self.parse_expression()
self.eat("RPAREN")
self.eat("LBRACE")
body = self.parse_block()
self.eat("RBRACE")
return WhileNode(condition, body)
def parse_do_while_loop(self):
self.eat("DO")
self.eat("LBRACE")
body = self.parse_block()
self.eat("RBRACE")
self.eat("WHILE")
self.eat("LPAREN")
condition = self.parse_expression()
self.eat("RPAREN")
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return DoWhileNode(body, condition)
def parse_switch_statement(self):
self.eat("SWITCH")
self.eat("LPAREN")
expression = self.parse_expression()
self.eat("RPAREN")
self.eat("LBRACE")
cases = []
default_statements = None
while self.current_token[0] not in ("RBRACE", "EOF"):
self.skip_newlines()
if self.current_token[0] in ("RBRACE", "EOF"):
break
if self.current_token[0] == "CASE":
cases.append(self.parse_case_statement())
elif self.current_token[0] == "DEFAULT":
self.eat("DEFAULT")
self.eat("COLON")
default_statements = []
while self.current_token[0] not in (
"CASE",
"DEFAULT",
"RBRACE",
"EOF",
):
self.skip_newlines()
if self.current_token[0] in ("CASE", "DEFAULT", "RBRACE", "EOF"):
break
stmt = self.parse_statement()
if isinstance(stmt, list):
default_statements.extend(stmt)
elif stmt is not None:
default_statements.append(stmt)
else:
raise SyntaxError(
f"Unexpected token in switch statement: {self.current_token}"
)
self.eat("RBRACE")
return SwitchNode(expression, cases, default_statements)
def parse_case_statement(self):
self.eat("CASE")
value = self.parse_expression()
self.eat("COLON")
statements = []
while self.current_token[0] not in ("CASE", "DEFAULT", "RBRACE", "EOF"):
self.skip_newlines()
if self.current_token[0] in ("CASE", "DEFAULT", "RBRACE", "EOF"):
break
stmt = self.parse_statement()
if isinstance(stmt, list):
statements.extend(stmt)
elif stmt is not None:
statements.append(stmt)
return CaseNode(value, statements)
def parse_return_statement(self):
self.eat("RETURN")
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return ReturnNode()
value = self.parse_expression()
if self.current_token[0] == "SEMICOLON":
self.eat("SEMICOLON")
return ReturnNode(value)
def parse_expression(self):
self.skip_newlines()
return self.parse_ternary()
def parse_ternary(self):
expr = self.parse_logical_or()
if self.current_token[0] == "QUESTION":
self.eat("QUESTION")
true_expr = self.parse_expression()
self.eat("COLON")
false_expr = self.parse_expression()
return TernaryOpNode(expr, true_expr, false_expr)
return expr
def parse_logical_or(self):
expr = self.parse_logical_and()
while self.current_token[0] == "LOGICAL_OR":
op = self.current_token[1]
self.eat("LOGICAL_OR")
right = self.parse_logical_and()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_logical_and(self):
expr = self.parse_bitwise_or()
while self.current_token[0] == "LOGICAL_AND":
op = self.current_token[1]
self.eat("LOGICAL_AND")
right = self.parse_bitwise_or()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_bitwise_or(self):
expr = self.parse_bitwise_xor()
while self.current_token[0] == "BITWISE_OR":
op = self.current_token[1]
self.eat("BITWISE_OR")
right = self.parse_bitwise_xor()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_bitwise_xor(self):
expr = self.parse_bitwise_and()
while self.current_token[0] == "BITWISE_XOR":
op = self.current_token[1]
self.eat("BITWISE_XOR")
right = self.parse_bitwise_and()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_bitwise_and(self):
expr = self.parse_equality()
while self.current_token[0] == "BITWISE_AND":
op = self.current_token[1]
self.eat("BITWISE_AND")
right = self.parse_equality()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_equality(self):
expr = self.parse_relational()
while self.current_token[0] in ("EQUAL", "NOT_EQUAL"):
op = self.current_token[1]
self.eat(self.current_token[0])
right = self.parse_relational()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_relational(self):
expr = self.parse_shift()
while self.current_token[0] in (
"LESS_THAN",
"LESS_EQUAL",
"GREATER_THAN",
"GREATER_EQUAL",
):
op = self.current_token[1]
self.eat(self.current_token[0])
right = self.parse_shift()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_shift(self):
expr = self.parse_additive()
while self.current_token[0] in ("SHIFT_LEFT", "SHIFT_RIGHT"):
op = self.current_token[1]
self.eat(self.current_token[0])
right = self.parse_additive()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_additive(self):
expr = self.parse_multiplicative()
while self.current_token[0] in ("PLUS", "MINUS"):
op = self.current_token[1]
self.eat(self.current_token[0])
right = self.parse_multiplicative()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_multiplicative(self):
expr = self.parse_unary()
while self.current_token[0] in ("MULTIPLY", "DIVIDE", "MOD"):
op = self.current_token[1]
self.eat(self.current_token[0])
right = self.parse_unary()
expr = BinaryOpNode(expr, op, right)
return expr
def parse_unary(self):
if self.current_token[0] in ("PLUS", "MINUS", "LOGICAL_NOT", "BITWISE_NOT"):
op = self.current_token[1]
self.eat(self.current_token[0])
operand = self.parse_unary()
return UnaryOpNode(op, operand)
if self.current_token[0] in ("INCREMENT", "DECREMENT"):
op = self.current_token[1]
self.eat(self.current_token[0])
operand = self.parse_unary()
return UnaryOpNode(op, operand)
return self.parse_postfix()
def parse_postfix(self):
expr = self.parse_primary()
while True:
if (
self.current_token[0] == "LBRACKET"
and self.peek(1)[0] == "RBRACKET"
and self.peek(2)[0] == "LPAREN"
and isinstance(expr, VariableNode)
):
self.eat("LBRACKET")
self.eat("RBRACKET")
expr = VariableNode("", f"{expr.name}[]")
continue
if self.current_token[0] == "LBRACKET":
self.eat("LBRACKET")
index = self.parse_expression()
self.eat("RBRACKET")
expr = ArrayAccessNode(expr, index)
continue
if self.current_token[0] == "DOT":
self.eat("DOT")
member = self.current_token[1]
self.eat("IDENTIFIER")
expr = MemberAccessNode(expr, member)
continue
if self.current_token[0] == "LPAREN":
args = self.parse_call_arguments()
expr = FunctionCallNode(expr, args)
continue
if self.current_token[0] in ("INCREMENT", "DECREMENT"):
op = self.current_token[1]
self.eat(self.current_token[0])
expr = PostfixOpNode(expr, op)
continue
break
return expr
def parse_call_arguments(self):
self.eat("LPAREN")
args = []
if self.current_token[0] != "RPAREN":
args.append(self.parse_expression())
while self.current_token[0] == "COMMA":
self.eat("COMMA")
args.append(self.parse_expression())
self.eat("RPAREN")
return args
def parse_primary(self):
self.skip_newlines()
if self.current_token[0] == "LPAREN":
self.eat("LPAREN")
expr = self.parse_expression()
self.eat("RPAREN")
return expr
if self.current_token[0] == "NUMBER":
value = self.current_token[1]
self.eat("NUMBER")
return NumberNode(value)
if self.current_token[0] in ("TRUE", "FALSE"):
value = self.current_token[1]
self.advance()
return value
if (
self.current_token[0] in TYPE_TOKENS
or self.current_token[0] == "IDENTIFIER"
):
name = self.current_token[1]
self.advance()
return VariableNode("", name)
if self.current_token[0] in ("STRING", "CHAR_LITERAL"):
value = self.current_token[1]
self.advance()
return value
raise SyntaxError(f"Unexpected token in expression: {self.current_token}")