Wednesday, May 28, 2025

Sanjoy Nath's Geometrifying Trigonometry Link

 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