about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/celpy/celparser.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/celpy/celparser.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/celpy/celparser.py')
-rw-r--r--.venv/lib/python3.12/site-packages/celpy/celparser.py402
1 files changed, 402 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/celpy/celparser.py b/.venv/lib/python3.12/site-packages/celpy/celparser.py
new file mode 100644
index 00000000..020621e6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/celpy/celparser.py
@@ -0,0 +1,402 @@
+# SPDX-Copyright: Copyright (c) Capital One Services, LLC
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2020 Capital One Services, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and limitations under the License.
+
+"""
+CEL Parser.
+
+See  https://github.com/google/cel-spec/blob/master/doc/langdef.md
+
+https://github.com/google/cel-cpp/blob/master/parser/Cel.g4
+
+https://github.com/google/cel-go/blob/master/parser/gen/CEL.g4
+
+Builds a parser from the supplied cel.lark grammar.
+
+..  todo:: Consider embedding the ``cel.lark`` file as a triple-quoted literal.
+
+    This means fixing a LOT of \\'s. But it also eliminates a data file from the installation.
+
+Example::
+
+    >>> from celpy.celparser import CELParser
+    >>> p = CELParser()
+    >>> text2 = 'type(null)'
+    >>> ast2 = p.parse(text2)
+    >>> print(ast2.pretty().replace("\t","   "))  # doctest: +NORMALIZE_WHITESPACE
+    expr
+      conditionalor
+        conditionaland
+          relation
+            addition
+              multiplication
+                unary
+                  member
+                    primary
+                      ident_arg
+                        type
+                        exprlist
+                          expr
+                            conditionalor
+                              conditionaland
+                                relation
+                                  addition
+                                    multiplication
+                                      unary
+                                        member
+                                          primary
+                                            literal    null
+
+
+"""
+import re
+from pathlib import Path
+from typing import Any, List, Optional, cast
+
+import lark.visitors
+from lark import Lark, Token, Tree  # noqa: F401
+from lark.exceptions import (LexError, ParseError, UnexpectedCharacters,
+                             UnexpectedToken)
+
+
+class CELParseError(Exception):
+    def __init__(
+            self,
+            *args: Any,
+            line: Optional[int] = None,
+            column: Optional[int] = None) -> None:
+        super().__init__(*args)
+        self.line = line
+        self.column = column
+
+
+class CELParser:
+    """Wrapper for the CEL parser and the syntax error messages."""
+    CEL_PARSER: Optional[Lark] = None
+
+    def __init__(self) -> None:
+        if CELParser.CEL_PARSER is None:
+            CEL_grammar = (Path(__file__).parent / "cel.lark").read_text()
+            CELParser.CEL_PARSER = Lark(
+                CEL_grammar,
+                parser="lalr",
+                start="expr",
+                debug=True,
+                g_regex_flags=re.M,
+                lexer_callbacks={'IDENT': self.ambiguous_literals},
+                propagate_positions=True,
+            )
+
+    @staticmethod
+    def ambiguous_literals(t: Token) -> Token:
+        """Resolve a grammar ambiguity between identifiers and literals"""
+        if t.value == "true":
+            return Token("BOOL_LIT", t.value)
+        elif t.value == "false":
+            return Token("BOOL_LIT", t.value)
+        return t
+
+    def parse(self, text: str) -> Tree:
+        if CELParser.CEL_PARSER is None:
+            raise TypeError("No grammar loaded")  # pragma: no cover
+        self.text = text
+        try:
+            return CELParser.CEL_PARSER.parse(self.text)
+        except (UnexpectedToken, UnexpectedCharacters) as ex:
+            message = ex.get_context(text)
+            raise CELParseError(message, *ex.args, line=ex.line, column=ex.column)
+        except (LexError, ParseError) as ex:  # pragma: no cover
+            message = ex.args[0].splitlines()[0]
+            raise CELParseError(message, *ex.args)
+
+    def error_text(
+            self,
+            message: str,
+            line: Optional[int] = None,
+            column: Optional[int] = None) -> str:
+        source = self.text.splitlines()[line - 1] if line else self.text
+        message = (
+            f"ERROR: <input>:{line or '?'}:{column or '?'} {message}\n"
+            f"    | {source}\n"
+            f"    | {(column - 1) * '.' if column else ''}^\n"
+        )
+        return message
+
+
+class DumpAST(lark.visitors.Visitor_Recursive):
+    """Dump a CEL AST creating a close approximation to the original source."""
+
+    @classmethod
+    def display(cls_, ast: lark.Tree) -> str:
+        d = cls_()
+        d.visit(ast)
+        return d.stack[0]
+
+    def __init__(self) -> None:
+        self.stack: List[str] = []
+
+    def expr(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            cond = self.stack.pop()
+            self.stack.append(
+                f"{cond} ? {left} : {right}"
+            )
+
+    def conditionalor(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            self.stack.append(
+                f"{left} || {right}"
+            )
+
+    def conditionaland(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            self.stack.append(
+                f"{left} && {right}"
+            )
+
+    def relation(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            self.stack.append(
+                f"{left} {right}"
+            )
+
+    def relation_lt(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} < ")
+
+    def relation_le(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} <= ")
+
+    def relation_gt(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} > ")
+
+    def relation_ge(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} >= ")
+
+    def relation_eq(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} == ")
+
+    def relation_ne(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} != ")
+
+    def relation_in(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} in ")
+
+    def addition(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            self.stack.append(
+                f"{left} {right}"
+            )
+
+    def addition_add(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} + ")
+
+    def addition_sub(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} - ")
+
+    def multiplication(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            self.stack.append(
+                f"{left} {right}"
+            )
+
+    def multiplication_mul(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} * ")
+
+    def multiplication_div(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} / ")
+
+    def multiplication_mod(self, tree: lark.Tree) -> None:
+        left = self.stack.pop()
+        self.stack.append(f"{left} % ")
+
+    def unary(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 1:
+            return
+        else:
+            right = self.stack.pop()
+            left = self.stack.pop()
+            self.stack.append(
+                f"{left} {right}"
+            )
+
+    def unary_not(self, tree: lark.Tree) -> None:
+        self.stack.append("!")
+
+    def unary_neg(self, tree: lark.Tree) -> None:
+        self.stack.append("-")
+
+    def member_dot(self, tree: lark.Tree) -> None:
+        right = cast(lark.Token, tree.children[1]).value
+        if self.stack:
+            left = self.stack.pop()
+            self.stack.append(f"{left}.{right}")
+
+    def member_dot_arg(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 3:
+            exprlist = self.stack.pop()
+        else:
+            exprlist = ""
+        right = cast(lark.Token, tree.children[1]).value
+        if self.stack:
+            left = self.stack.pop()
+            self.stack.append(f"{left}.{right}({exprlist})")
+        else:
+            self.stack.append(f".{right}({exprlist})")
+
+    def member_index(self, tree: lark.Tree) -> None:
+        right = self.stack.pop()
+        left = self.stack.pop()
+        self.stack.append(f"{left}[{right}]")
+
+    def member_object(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 2:
+            fieldinits = self.stack.pop()
+        else:
+            fieldinits = ""
+        left = self.stack.pop()
+        self.stack.append(f"{left}{{{fieldinits}}}")
+
+    def dot_ident_arg(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 2:
+            exprlist = self.stack.pop()
+        else:
+            exprlist = ""
+        left = cast(lark.Token, tree.children[0]).value
+        self.stack.append(f".{left}({exprlist})")
+
+    def dot_ident(self, tree: lark.Tree) -> None:
+        left = cast(lark.Token, tree.children[0]).value
+        self.stack.append(f".{left}")
+
+    def ident_arg(self, tree: lark.Tree) -> None:
+        if len(tree.children) == 2:
+            exprlist = self.stack.pop()
+        else:
+            exprlist = ""
+
+        left = cast(lark.Token, tree.children[0]).value
+        self.stack.append(f"{left}({exprlist})")
+
+    def ident(self, tree: lark.Tree) -> None:
+        self.stack.append(cast(lark.Token, tree.children[0]).value)
+
+    def paren_expr(self, tree: lark.Tree) -> None:
+        if self.stack:
+            left = self.stack.pop()
+            self.stack.append(f"({left})")
+
+    def list_lit(self, tree: lark.Tree) -> None:
+        if self.stack:
+            left = self.stack.pop()
+            self.stack.append(f"[{left}]")
+
+    def map_lit(self, tree: lark.Tree) -> None:
+        if self.stack:
+            left = self.stack.pop()
+            self.stack.append(f"{{{left}}}")
+        else:
+            self.stack.append("{}")
+
+    def exprlist(self, tree: lark.Tree) -> None:
+        items = ", ".join(reversed(list(self.stack.pop() for _ in tree.children)))
+        self.stack.append(items)
+
+    def fieldinits(self, tree: lark.Tree) -> None:
+        names = cast(List[lark.Token], tree.children[::2])
+        values = cast(List[lark.Token], tree.children[1::2])
+        assert len(names) == len(values)
+        pairs = reversed(list((n.value, self.stack.pop()) for n, v in zip(names, values)))
+        items = ", ".join(f"{n}: {v}" for n, v in pairs)
+        self.stack.append(items)
+
+    def mapinits(self, tree: lark.Tree) -> None:
+        """Note reversed pop order for values and keys."""
+        keys = tree.children[::2]
+        values = tree.children[1::2]
+        assert len(keys) == len(values)
+        pairs = reversed(list(
+            {'value': self.stack.pop(), 'key': self.stack.pop()}
+            for k, v in zip(keys, values)
+        ))
+        items = ", ".join(f"{k_v['key']}: {k_v['value']}" for k_v in pairs)
+        self.stack.append(items)
+
+    def literal(self, tree: lark.Tree) -> None:
+        if tree.children:
+            self.stack.append(cast(lark.Token, tree.children[0]).value)
+
+
+def tree_dump(ast: Tree) -> str:
+    """Dumps the AST to approximate the original source"""
+    d = DumpAST()
+    d.visit(ast)
+    return d.stack[0]
+
+
+if __name__ == "__main__":  # pragma: no cover
+    # A minimal sanity check.
+    # This is a smoke test for the grammar to expose shift/reduce or reduce/reduce conflicts.
+    # It will produce a RuntimeWarning because it's not the proper main program.
+    p = CELParser()
+
+    text = """
+    account.balance >= transaction.withdrawal
+    || (account.overdraftProtection
+    && account.overdraftLimit >= transaction.withdrawal  - account.balance)
+    """
+    ast = p.parse(text)
+    print(ast)
+
+    d = DumpAST()
+    d.visit(ast)
+    print(d.stack)
+
+    text2 = """type(null)"""
+    ast2 = p.parse(text2)
+    print(ast2.pretty())