https://docs.google.com/document/d/11q7w1dEEUPMu_Li39Z0QKAWkyqSzGbdxIDzb_QTIB4g/edit?usp=sharing
import re
# --- AST Node Definitions ---
# These classes represent the different types of nodes in our Abstract Syntax Tree.
# Each node will hold information relevant for geometric construction.
class ASTNode:
"""Base class for all AST nodes."""
pass
class NumberNode(ASTNode):
"""Represents a numerical literal (e.g., 2, 3.14)."""
def __init__(self, value):
self.value = float(value) # Store numbers as floats for precision
def __repr__(self):
return f"Number({self.value})"
class VariableNode(ASTNode):
"""Represents a variable (e.g., A, x, alpha).
In GT, these might represent input lengths or angles."""
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Variable('{self.name}')"
class FunctionCallNode(ASTNode):
"""Represents a trigonometric function call (e.g., sin(A), cos(B)).
In GT, these are 'triangle constructors'."""
def __init__(self, func_name, argument):
self.func_name = func_name.lower() # Store function names in lowercase
self.argument = argument # The argument is itself an ASTNode
def __repr__(self):
return f"FunctionCall('{self.func_name}', {self.argument})"
class BinaryOperationNode(ASTNode):
"""Represents a binary arithmetic operation (e.g., A + B, X * Y).
In GT, these correspond to specific geometric combination protocols."""
def __init__(self, operator, left, right):
self.operator = operator
self.left = left # Left operand (ASTNode)
self.right = right # Right operand (ASTNode)
def __repr__(self):
return f"BinOp('{self.operator}', {self.left}, {self.right})"
# --- Lexer (Tokenizer) ---
# Breaks the input string into a stream of tokens.
class Lexer:
def __init__(self, expression):
self.expression = expression
self.tokens = []
self.current_pos = 0
# Define token types and their regular expressions
self.token_specs = [
('NUMBER', r'\d+(\.\d*)?'),
('FUNCTION', r'sin|cos|tan|cot|sec|csc'),
('VARIABLE', r'[a-zA-Z_][a-zA-Z0-9_]*'), # Allows multi-char variables like 'alpha'
('OPERATOR', r'[+\-*/]'),
('LPAREN', r'\('),
('RPAREN', r'\)'),
('WHITESPACE', r'\s+')
]
# Compile a single regex for efficiency
self.token_regex = re.compile('|'.join('(?P<%s>%s)' % pair for pair in self.token_specs))
def tokenize(self):
"""Generates a list of tokens from the expression."""
for match in self.token_regex.finditer(self.expression):
kind = match.lastgroup
value = match.group(kind)
if kind != 'WHITESPACE': # Ignore whitespace tokens
self.tokens.append({'kind': kind, 'value': value})
return self.tokens
# --- Parser ---
# Builds the AST from the token stream. Uses a recursive descent approach
# with operator precedence parsing (similar to the Shunt-Yard algorithm concept).
class GeoTrigParser:
def __init__(self):
self.tokens = []
self.current_token_index = 0
# Operator precedence map (higher value means higher precedence)
self.precedence = {
'+': 1,
'-': 1,
'*': 2,
'/': 2
}
def parse(self, expression):
"""
Main parsing method. Tokenizes the expression and then builds the AST.
"""
lexer = Lexer(expression)
self.tokens = lexer.tokenize()
self.current_token_index = 0
if not self.tokens:
return None # Empty expression
# Start parsing from the highest level (expression)
ast = self._parse_expression()
# After parsing, ensure all tokens have been consumed
if self.current_token_index < len(self.tokens):
raise SyntaxError(f"Unparsed tokens remaining: {self.tokens[self.current_token_index:]}")
return ast
def _peek(self):
"""Looks at the next token without consuming it."""
if self.current_token_index < len(self.tokens):
return self.tokens[self.current_token_index]
return None
def _consume(self, expected_kind=None):
"""Consumes the current token and moves to the next.
Optionally checks if the consumed token matches an expected kind."""
if self.current_token_index >= len(self.tokens):
raise SyntaxError("Unexpected end of expression")
token = self.tokens[self.current_token_index]
if expected_kind and token['kind'] != expected_kind:
raise SyntaxError(f"Expected {expected_kind} but got {token['kind']} '{token['value']}'")
self.current_token_index += 1
return token
def _parse_expression(self, min_precedence=0):
"""
Parses an expression, handling operator precedence.
This is a recursive function that builds the AST for binary operations.
"""
# Parse the left-hand side (a primary expression)
left = self._parse_primary()
while True:
next_token = self._peek()
# If no more tokens, or not an operator, or operator has lower precedence, break
if not next_token or next_token['kind'] != 'OPERATOR' or \
self.precedence.get(next_token['value'], 0) < min_precedence:
break
operator_token = self._consume('OPERATOR')
operator = operator_token['value']
current_precedence = self.precedence[operator]
# Recursively parse the right-hand side with higher precedence
# This ensures correct order of operations (e.g., multiplication before addition)
right = self._parse_expression(current_precedence + 1)
# Create a BinaryOperationNode for the current operation
left = BinaryOperationNode(operator, left, right)
return left
def _parse_primary(self):
"""
Parses the most basic elements of an expression:
numbers, variables, function calls, or parenthesized sub-expressions.
"""
token = self._peek()
if not token:
raise SyntaxError("Unexpected end of expression, expected a primary element (number, variable, function, or parenthesis)")
if token['kind'] == 'NUMBER':
return NumberNode(self._consume('NUMBER')['value'])
elif token['kind'] == 'VARIABLE':
return VariableNode(self._consume('VARIABLE')['value'])
elif token['kind'] == 'LPAREN':
self._consume('LPAREN') # Consume '('
expr = self._parse_expression() # Recursively parse the inner expression
self._consume('RPAREN') # Consume ')'
return expr
elif token['kind'] == 'FUNCTION':
func_token = self._consume('FUNCTION')
self._consume('LPAREN') # Consume '(' for function argument
argument = self._parse_expression() # The argument can be another expression
self._consume('RPAREN') # Consume ')' for function argument
return FunctionCallNode(func_token['value'], argument)
else:
raise SyntaxError(f"Unexpected token type or value: {token['kind']} '{token['value']}'")
# --- Example Usage ---
if __name__ == "__main__":
parser = GeoTrigParser()
expressions = [
"sin(A)",
"cos(B) + sin(C)",
"tan(X) * (cos(Y) - Z)",
"A / sin(B)",
"(sin(alpha) + cos(beta)) * 2",
"sin(alpha + beta)", # Argument can be an expression
"10 * sin(30) + cos(0)", # With numbers
"sin(X) / (Y - cos(Z))",
"alpha" # Simple variable
]
print("--- Parsing Results (Abstract Syntax Tree) ---")
for expr_str in expressions:
try:
print(f"\nExpression: '{expr_str}'")
ast_root = parser.parse(expr_str)
print(f"AST: {ast_root}")
# --- Conceptual Interpretation of the AST for Geometric Construction ---
# This is a highly simplified 'interpreter' to show how the AST
# could be used to generate geometric protocols.
# In a real GT system, this would involve complex geometric logic.
def interpret_for_geometry(node):
if isinstance(node, NumberNode):
return f"GEOMETRIC_LENGTH({node.value})"
elif isinstance(node, VariableNode):
return f"GEOMETRIC_VARIABLE_INPUT('{node.name}')"
elif isinstance(node, FunctionCallNode):
arg_protocol = interpret_for_geometry(node.argument)
return f"CONSTRUCT_{node.func_name.upper()}_TRIANGLE({arg_protocol})"
elif isinstance(node, BinaryOperationNode):
left_protocol = interpret_for_geometry(node.left)
right_protocol = interpret_for_geometry(node.right)
return f"COMBINE_GEOMETRIC_OBJECTS_VIA_{node.operator.upper()}({left_protocol}, {right_protocol})"
else:
return "UNKNOWN_GEOMETRIC_PROTOCOL"
if ast_root:
geometric_protocol_idea = interpret_for_geometry(ast_root)
print(f"Conceptual Geometric Protocol: {geometric_protocol_idea}")
except SyntaxError as e:
print(f"Error parsing '{expr_str}': {e}")
except Exception as e:
print(f"An unexpected error occurred for '{expr_str}': {e}")
No comments:
Post a Comment