-
-
Save snopoke/b67352a911ccc96b8c1ef4080a27d04a to your computer and use it in GitHub Desktop.
Grammar for CommCare Query Language
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import dataclasses | |
| from typing import List, Iterable | |
| from lark import Lark, Transformer, v_args | |
| ccql_grammar = r""" | |
| ?start: b_expr+ | |
| ?b_expr: expression (BOP expression)* | |
| ?expression: c_expr | |
| |group | |
| |set_expr | |
| |function | |
| c_expr: (NAME|ancestor|set_expr) OP (value|function) | |
| ?group: "(" b_expr+ ")" | |
| ?set_expr: set "." set_ops | |
| set_ops: function ("." function)* | |
| ?set: "{" b_expr "}" | |
| ancestor: NAME ("/" NAME)+ | |
| function: NAME "(" [args] ")" | |
| args: arg ("," arg)* | |
| ?arg: value|b_expr | |
| ?value: STRING|INT|FLOAT|NAME | |
| BOP.1: "and"i|"or"i | |
| NAME: LETTER ("_"|"-"|LETTER|DIGIT)* | |
| SET_RESOLVER: "count"|"exists" | |
| SET_JOIN: "subcase"|"parent" | |
| OP: ">"|">="|"<"|"<="|"="|"!=" | |
| STRING: ("\""|"'") _STRING_ESC_INNER ("\""|"'") | |
| %import common._STRING_ESC_INNER | |
| %import common.FLOAT | |
| %import common.INT | |
| %import common.DIGIT | |
| %import common.LETTER | |
| %import common.WS | |
| %ignore WS | |
| """ | |
| @dataclasses.dataclass | |
| class BinaryExpression: | |
| left: object | |
| op: str | |
| right: object | |
| @dataclasses.dataclass | |
| class SetExpression: | |
| expr: BinaryExpression | |
| ops: Iterable["Function"] | |
| @dataclasses.dataclass | |
| class Function: | |
| name: str | |
| args: list | |
| @dataclasses.dataclass | |
| class Ancestor: | |
| lineage: Iterable[str] | |
| field: str | |
| def _value(self, token): | |
| return token.value | |
| def _values(self, *args): | |
| return args | |
| @v_args(inline=True) | |
| class CCQLTransformer(Transformer): | |
| INT = int | |
| FLOAT = float | |
| NAME = _value | |
| OP = _value | |
| STRING = _value | |
| BOP = _value | |
| FUNC = _value | |
| args = _values | |
| set_eval = _values | |
| set_ops = _values | |
| def b_expr(self, left, op, right): | |
| return BinaryExpression(left, op, right) | |
| def c_expr(self, left, op, right): | |
| return BinaryExpression(left, op, right) | |
| def function(self, name, args): | |
| return Function(name, args) | |
| def set_expr(self, expr, *ops): | |
| return SetExpression(expr, ops) | |
| def ancestor(self, *args): | |
| return Ancestor(args[:-1], args[-1]) | |
| parser = Lark(ccql_grammar, start='start', parser='lalr') | |
| tests = [ | |
| "age > 3", | |
| "age = 3 and name = 'bob'", | |
| "(age > 3 and age < 10) and region = 'chamonix'", | |
| "dob > date('2018-01-03')", | |
| "not(age > 10)", | |
| "age < 3 or (age > 10 and not(danger = 1))", | |
| "name = 'bob' and {age > 3 and name != 'bob'}.subcase('parent').exists()", | |
| "parent/parent/age = 4" | |
| ] | |
| for t in tests: | |
| tree = parser.parse(t) | |
| print(t) | |
| # print(tree) | |
| print(tree.pretty()) | |
| print(CCQLTransformer().transform(tree)) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
"name = 'bob' and {age > 3 and name != 'bob'}.subcase('parent').exists()"