pbi-parsers 0.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pbi_parsers/__init__.py +9 -0
- pbi_parsers/base/__init__.py +7 -0
- pbi_parsers/base/lexer.py +127 -0
- pbi_parsers/base/tokens.py +61 -0
- pbi_parsers/dax/__init__.py +22 -0
- pbi_parsers/dax/exprs/__init__.py +107 -0
- pbi_parsers/dax/exprs/_base.py +46 -0
- pbi_parsers/dax/exprs/_utils.py +45 -0
- pbi_parsers/dax/exprs/add_sub.py +73 -0
- pbi_parsers/dax/exprs/add_sub_unary.py +72 -0
- pbi_parsers/dax/exprs/array.py +75 -0
- pbi_parsers/dax/exprs/column.py +56 -0
- pbi_parsers/dax/exprs/comparison.py +76 -0
- pbi_parsers/dax/exprs/concatenation.py +73 -0
- pbi_parsers/dax/exprs/div_mul.py +75 -0
- pbi_parsers/dax/exprs/exponent.py +67 -0
- pbi_parsers/dax/exprs/function.py +102 -0
- pbi_parsers/dax/exprs/hierarchy.py +68 -0
- pbi_parsers/dax/exprs/identifier.py +46 -0
- pbi_parsers/dax/exprs/ins.py +67 -0
- pbi_parsers/dax/exprs/keyword.py +60 -0
- pbi_parsers/dax/exprs/literal_number.py +46 -0
- pbi_parsers/dax/exprs/literal_string.py +45 -0
- pbi_parsers/dax/exprs/logical.py +76 -0
- pbi_parsers/dax/exprs/measure.py +44 -0
- pbi_parsers/dax/exprs/none.py +30 -0
- pbi_parsers/dax/exprs/parens.py +61 -0
- pbi_parsers/dax/exprs/returns.py +76 -0
- pbi_parsers/dax/exprs/table.py +51 -0
- pbi_parsers/dax/exprs/variable.py +68 -0
- pbi_parsers/dax/formatter.py +215 -0
- pbi_parsers/dax/lexer.py +222 -0
- pbi_parsers/dax/main.py +63 -0
- pbi_parsers/dax/parser.py +66 -0
- pbi_parsers/dax/tokens.py +54 -0
- pbi_parsers/dax/utils.py +120 -0
- pbi_parsers/pq/__init__.py +17 -0
- pbi_parsers/pq/exprs/__init__.py +98 -0
- pbi_parsers/pq/exprs/_base.py +33 -0
- pbi_parsers/pq/exprs/_utils.py +31 -0
- pbi_parsers/pq/exprs/add_sub.py +59 -0
- pbi_parsers/pq/exprs/add_sub_unary.py +57 -0
- pbi_parsers/pq/exprs/and_or_expr.py +60 -0
- pbi_parsers/pq/exprs/array.py +53 -0
- pbi_parsers/pq/exprs/arrow.py +50 -0
- pbi_parsers/pq/exprs/column.py +42 -0
- pbi_parsers/pq/exprs/comparison.py +62 -0
- pbi_parsers/pq/exprs/concatenation.py +61 -0
- pbi_parsers/pq/exprs/div_mul.py +59 -0
- pbi_parsers/pq/exprs/each.py +41 -0
- pbi_parsers/pq/exprs/ellipsis_expr.py +28 -0
- pbi_parsers/pq/exprs/function.py +63 -0
- pbi_parsers/pq/exprs/identifier.py +77 -0
- pbi_parsers/pq/exprs/if_expr.py +70 -0
- pbi_parsers/pq/exprs/is_expr.py +54 -0
- pbi_parsers/pq/exprs/keyword.py +40 -0
- pbi_parsers/pq/exprs/literal_number.py +31 -0
- pbi_parsers/pq/exprs/literal_string.py +31 -0
- pbi_parsers/pq/exprs/meta.py +54 -0
- pbi_parsers/pq/exprs/negation.py +52 -0
- pbi_parsers/pq/exprs/none.py +22 -0
- pbi_parsers/pq/exprs/not_expr.py +39 -0
- pbi_parsers/pq/exprs/parens.py +43 -0
- pbi_parsers/pq/exprs/record.py +58 -0
- pbi_parsers/pq/exprs/row.py +54 -0
- pbi_parsers/pq/exprs/row_index.py +57 -0
- pbi_parsers/pq/exprs/statement.py +67 -0
- pbi_parsers/pq/exprs/try_expr.py +55 -0
- pbi_parsers/pq/exprs/type_expr.py +78 -0
- pbi_parsers/pq/exprs/variable.py +52 -0
- pbi_parsers/pq/formatter.py +13 -0
- pbi_parsers/pq/lexer.py +219 -0
- pbi_parsers/pq/main.py +63 -0
- pbi_parsers/pq/parser.py +65 -0
- pbi_parsers/pq/tokens.py +81 -0
- pbi_parsers-0.7.8.dist-info/METADATA +66 -0
- pbi_parsers-0.7.8.dist-info/RECORD +78 -0
- pbi_parsers-0.7.8.dist-info/WHEEL +4 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
4
|
+
|
5
|
+
from ._base import Expression
|
6
|
+
from ._utils import lexer_reset
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from pbi_parsers.dax.parser import Parser
|
10
|
+
|
11
|
+
|
12
|
+
class ColumnExpression(Expression):
|
13
|
+
"""Represents a column of a table in DAX.
|
14
|
+
|
15
|
+
Examples:
|
16
|
+
Table[Column]
|
17
|
+
'Table Name'[Column Name]
|
18
|
+
|
19
|
+
"""
|
20
|
+
|
21
|
+
table: Token
|
22
|
+
column: Token
|
23
|
+
|
24
|
+
def __init__(self, table: Token, column: Token) -> None:
|
25
|
+
self.table = table
|
26
|
+
self.column = column
|
27
|
+
|
28
|
+
def pprint(self) -> str:
|
29
|
+
return f"""
|
30
|
+
Column (
|
31
|
+
{self.table.text},
|
32
|
+
{self.column.text}
|
33
|
+
)""".strip()
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
@lexer_reset
|
37
|
+
def match(cls, parser: "Parser") -> "ColumnExpression | None":
|
38
|
+
table, column = parser.consume(), parser.consume()
|
39
|
+
if table.tok_type not in {
|
40
|
+
TokenType.SINGLE_QUOTED_IDENTIFIER,
|
41
|
+
TokenType.UNQUOTED_IDENTIFIER,
|
42
|
+
}:
|
43
|
+
return None
|
44
|
+
if column.tok_type != TokenType.BRACKETED_IDENTIFIER:
|
45
|
+
return None
|
46
|
+
return ColumnExpression(table=table, column=column)
|
47
|
+
|
48
|
+
def children(self) -> list[Expression]: # noqa: PLR6301
|
49
|
+
"""Returns a list of child expressions."""
|
50
|
+
return []
|
51
|
+
|
52
|
+
def position(self) -> tuple[int, int]:
|
53
|
+
return self.table.text_slice.start, self.column.text_slice.end
|
54
|
+
|
55
|
+
def full_text(self) -> str:
|
56
|
+
return self.table.text_slice.full_text
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import textwrap
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
5
|
+
|
6
|
+
from ._base import Expression
|
7
|
+
from ._utils import lexer_reset
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from pbi_parsers.dax.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class ComparisonExpression(Expression):
|
14
|
+
"""Represents a boolean comparison expression.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
1 <> 2
|
18
|
+
func() = 3
|
19
|
+
4 > 5
|
20
|
+
6 <= 7
|
21
|
+
|
22
|
+
"""
|
23
|
+
|
24
|
+
operator: Token
|
25
|
+
left: Expression
|
26
|
+
right: Expression
|
27
|
+
|
28
|
+
def __init__(self, operator: Token, left: Expression, right: Expression) -> None:
|
29
|
+
self.operator = operator
|
30
|
+
self.left = left
|
31
|
+
self.right = right
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
@lexer_reset
|
35
|
+
def match(cls, parser: "Parser") -> "ComparisonExpression | None":
|
36
|
+
from . import EXPRESSION_HIERARCHY, any_expression_match # noqa: PLC0415
|
37
|
+
|
38
|
+
skip_index = EXPRESSION_HIERARCHY.index(ComparisonExpression)
|
39
|
+
|
40
|
+
left_term = any_expression_match(parser=parser, skip_first=skip_index + 1)
|
41
|
+
operator = parser.consume()
|
42
|
+
|
43
|
+
if not left_term:
|
44
|
+
return None
|
45
|
+
if operator.tok_type not in {
|
46
|
+
TokenType.EQUAL_SIGN,
|
47
|
+
TokenType.NOT_EQUAL_SIGN,
|
48
|
+
TokenType.COMPARISON_OPERATOR,
|
49
|
+
}:
|
50
|
+
return None
|
51
|
+
|
52
|
+
right_term = any_expression_match(parser=parser, skip_first=skip_index)
|
53
|
+
if right_term is None:
|
54
|
+
msg = f"Expected a right term after operator {operator.text}, found: {parser.peek()}"
|
55
|
+
raise ValueError(msg)
|
56
|
+
return ComparisonExpression(operator=operator, left=left_term, right=right_term)
|
57
|
+
|
58
|
+
def pprint(self) -> str:
|
59
|
+
left_str = textwrap.indent(self.left.pprint(), " " * 10)[10:]
|
60
|
+
right_str = textwrap.indent(self.right.pprint(), " " * 10)[10:]
|
61
|
+
return f"""
|
62
|
+
Bool (
|
63
|
+
operator: {self.operator.text},
|
64
|
+
left: {left_str},
|
65
|
+
right: {right_str}
|
66
|
+
)""".strip()
|
67
|
+
|
68
|
+
def children(self) -> list[Expression]:
|
69
|
+
"""Returns a list of child expressions."""
|
70
|
+
return [self.left, self.right]
|
71
|
+
|
72
|
+
def position(self) -> tuple[int, int]:
|
73
|
+
return self.left.position()[0], self.right.position()[1]
|
74
|
+
|
75
|
+
def full_text(self) -> str:
|
76
|
+
return self.operator.text_slice.full_text
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import textwrap
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
5
|
+
|
6
|
+
from ._base import Expression
|
7
|
+
from ._utils import lexer_reset
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from pbi_parsers.dax.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class ConcatenationExpression(Expression):
|
14
|
+
"""Represents a concatenation operator on two elements.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
"Hello" & " World"
|
18
|
+
Table[Column] & " Text"
|
19
|
+
|
20
|
+
"""
|
21
|
+
|
22
|
+
operator: Token
|
23
|
+
left: Expression
|
24
|
+
right: Expression
|
25
|
+
|
26
|
+
def __init__(self, operator: Token, left: Expression, right: Expression) -> None:
|
27
|
+
self.operator = operator
|
28
|
+
self.left = left
|
29
|
+
self.right = right
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
@lexer_reset
|
33
|
+
def match(cls, parser: "Parser") -> "ConcatenationExpression | None":
|
34
|
+
from . import EXPRESSION_HIERARCHY, any_expression_match # noqa: PLC0415
|
35
|
+
|
36
|
+
skip_index = EXPRESSION_HIERARCHY.index(ConcatenationExpression)
|
37
|
+
|
38
|
+
left_term = any_expression_match(parser=parser, skip_first=skip_index + 1)
|
39
|
+
operator = parser.consume()
|
40
|
+
|
41
|
+
if not left_term:
|
42
|
+
return None
|
43
|
+
if operator.tok_type != TokenType.AMPERSAND_OPERATOR:
|
44
|
+
return None
|
45
|
+
|
46
|
+
right_term = any_expression_match(parser=parser, skip_first=skip_index)
|
47
|
+
if right_term is None:
|
48
|
+
msg = f"Expected a right term after operator {operator.text}, found: {parser.peek()}"
|
49
|
+
raise ValueError(msg)
|
50
|
+
return ConcatenationExpression(
|
51
|
+
operator=operator,
|
52
|
+
left=left_term,
|
53
|
+
right=right_term,
|
54
|
+
)
|
55
|
+
|
56
|
+
def pprint(self) -> str:
|
57
|
+
left_str = textwrap.indent(self.left.pprint(), " " * 10).lstrip()
|
58
|
+
right_str = textwrap.indent(self.right.pprint(), " " * 10).lstrip()
|
59
|
+
return f"""
|
60
|
+
Concat (
|
61
|
+
left: {left_str},
|
62
|
+
right: {right_str}
|
63
|
+
)""".strip()
|
64
|
+
|
65
|
+
def children(self) -> list[Expression]:
|
66
|
+
"""Returns a list of child expressions."""
|
67
|
+
return [self.left, self.right]
|
68
|
+
|
69
|
+
def position(self) -> tuple[int, int]:
|
70
|
+
return self.left.position()[0], self.right.position()[1]
|
71
|
+
|
72
|
+
def full_text(self) -> str:
|
73
|
+
return self.operator.text_slice.full_text
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import textwrap
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
5
|
+
|
6
|
+
from ._base import Expression
|
7
|
+
from ._utils import lexer_reset
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from pbi_parsers.dax.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class DivMulExpression(Expression):
|
14
|
+
"""Represents an multiplication or division expression.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
2 * 3
|
18
|
+
4 / 5
|
19
|
+
6 * 7
|
20
|
+
8 / func(1, 2)
|
21
|
+
|
22
|
+
"""
|
23
|
+
|
24
|
+
operator: Token
|
25
|
+
left: Expression
|
26
|
+
right: Expression
|
27
|
+
|
28
|
+
def __init__(self, operator: Token, left: Expression, right: Expression) -> None:
|
29
|
+
self.operator = operator
|
30
|
+
self.left = left
|
31
|
+
self.right = right
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
@lexer_reset
|
35
|
+
def match(cls, parser: "Parser") -> "DivMulExpression | None":
|
36
|
+
from . import EXPRESSION_HIERARCHY, any_expression_match # noqa: PLC0415
|
37
|
+
|
38
|
+
skip_index = EXPRESSION_HIERARCHY.index(DivMulExpression)
|
39
|
+
|
40
|
+
left_term = any_expression_match(parser=parser, skip_first=skip_index + 1)
|
41
|
+
operator = parser.consume()
|
42
|
+
|
43
|
+
if not left_term:
|
44
|
+
return None
|
45
|
+
if operator.tok_type not in {TokenType.MULTIPLY_SIGN, TokenType.DIVIDE_SIGN}:
|
46
|
+
return None
|
47
|
+
|
48
|
+
right_term = any_expression_match(parser=parser, skip_first=skip_index)
|
49
|
+
if right_term is None:
|
50
|
+
msg = f"Expected a right term after operator {operator.text}, found: {parser.peek()}"
|
51
|
+
raise ValueError(msg)
|
52
|
+
return DivMulExpression(operator=operator, left=left_term, right=right_term)
|
53
|
+
|
54
|
+
def pprint(self) -> str:
|
55
|
+
op_str = {
|
56
|
+
TokenType.MULTIPLY_SIGN: "Mul",
|
57
|
+
TokenType.DIVIDE_SIGN: "Div",
|
58
|
+
}[self.operator.tok_type]
|
59
|
+
left_str = textwrap.indent(self.left.pprint(), " " * 10)[10:]
|
60
|
+
right_str = textwrap.indent(self.right.pprint(), " " * 10)[10:]
|
61
|
+
return f"""
|
62
|
+
{op_str} (
|
63
|
+
left: {left_str},
|
64
|
+
right: {right_str}
|
65
|
+
)""".strip()
|
66
|
+
|
67
|
+
def children(self) -> list[Expression]:
|
68
|
+
"""Returns a list of child expressions."""
|
69
|
+
return [self.left, self.right]
|
70
|
+
|
71
|
+
def position(self) -> tuple[int, int]:
|
72
|
+
return self.left.position()[0], self.right.position()[1]
|
73
|
+
|
74
|
+
def full_text(self) -> str:
|
75
|
+
return self.operator.text_slice.full_text
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import textwrap
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from pbi_parsers.dax.tokens import TokenType
|
5
|
+
|
6
|
+
from ._base import Expression
|
7
|
+
from ._utils import lexer_reset
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from pbi_parsers.dax.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class ExponentExpression(Expression):
|
14
|
+
"""Represents the exponentiation operation in DAX.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
2 ^ 3
|
18
|
+
func() ^ 4
|
19
|
+
|
20
|
+
"""
|
21
|
+
|
22
|
+
base: Expression
|
23
|
+
power: Expression
|
24
|
+
|
25
|
+
def __init__(self, base: Expression, power: Expression) -> None:
|
26
|
+
self.base = base
|
27
|
+
self.power = power
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
@lexer_reset
|
31
|
+
def match(cls, parser: "Parser") -> "ExponentExpression | None":
|
32
|
+
from . import EXPRESSION_HIERARCHY, any_expression_match # noqa: PLC0415
|
33
|
+
|
34
|
+
skip_index = EXPRESSION_HIERARCHY.index(ExponentExpression)
|
35
|
+
|
36
|
+
base = any_expression_match(parser=parser, skip_first=skip_index + 1)
|
37
|
+
operator = parser.consume()
|
38
|
+
|
39
|
+
if not base:
|
40
|
+
return None
|
41
|
+
if operator.tok_type != TokenType.EXPONENTIATION_SIGN:
|
42
|
+
return None
|
43
|
+
|
44
|
+
power = any_expression_match(parser=parser, skip_first=skip_index)
|
45
|
+
if power is None:
|
46
|
+
msg = f"Expected a power term after operator {operator.text}, found: {parser.peek()}"
|
47
|
+
raise ValueError(msg)
|
48
|
+
return ExponentExpression(base=base, power=power)
|
49
|
+
|
50
|
+
def pprint(self) -> str:
|
51
|
+
base_str = textwrap.indent(self.base.pprint(), " " * 10).lstrip()
|
52
|
+
power_str = textwrap.indent(self.power.pprint(), " " * 10).lstrip()
|
53
|
+
return f"""
|
54
|
+
Exponent (
|
55
|
+
base: {base_str},
|
56
|
+
power: {power_str}
|
57
|
+
)""".strip()
|
58
|
+
|
59
|
+
def children(self) -> list[Expression]:
|
60
|
+
"""Returns a list of child expressions."""
|
61
|
+
return [self.base, self.power]
|
62
|
+
|
63
|
+
def position(self) -> tuple[int, int]:
|
64
|
+
return self.base.position()[0], self.power.position()[1]
|
65
|
+
|
66
|
+
def full_text(self) -> str:
|
67
|
+
return self.base.full_text()
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import textwrap
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
5
|
+
|
6
|
+
from ._base import Expression
|
7
|
+
from ._utils import lexer_reset
|
8
|
+
from .none import NoneExpression
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from pbi_parsers.dax.parser import Parser
|
12
|
+
|
13
|
+
|
14
|
+
class FunctionExpression(Expression):
|
15
|
+
"""Represents a function call in DAX.
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
SUM(Table[Column])
|
19
|
+
CALCULATE(SUM(Table[Column]), FILTER(Table, Table[Column] > 0))
|
20
|
+
|
21
|
+
"""
|
22
|
+
|
23
|
+
name_parts: list[Token] # necessary for function names with periods
|
24
|
+
args: list[Expression]
|
25
|
+
parens: tuple[Token, Token] # left and right parentheses
|
26
|
+
|
27
|
+
def __init__(self, name_parts: list[Token], args: list[Expression], parens: tuple[Token, Token]) -> None:
|
28
|
+
self.name_parts = name_parts
|
29
|
+
self.args = args
|
30
|
+
self.parens = parens
|
31
|
+
|
32
|
+
def pprint(self) -> str:
|
33
|
+
args = ",\n".join(arg.pprint() for arg in self.args)
|
34
|
+
args = textwrap.indent(args, " " * 10)[10:]
|
35
|
+
return f"""
|
36
|
+
Function (
|
37
|
+
name: {"".join(x.text for x in self.name_parts)},
|
38
|
+
args: {args}
|
39
|
+
) """.strip()
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def _match_function_name(cls, parser: "Parser") -> list[Token] | None:
|
43
|
+
name_parts = [parser.consume()]
|
44
|
+
if name_parts[0].tok_type != TokenType.UNQUOTED_IDENTIFIER:
|
45
|
+
return None
|
46
|
+
|
47
|
+
while parser.peek().tok_type != TokenType.LEFT_PAREN:
|
48
|
+
period, name = parser.consume(), parser.consume()
|
49
|
+
if name.tok_type != TokenType.UNQUOTED_IDENTIFIER:
|
50
|
+
return None
|
51
|
+
if period.tok_type != TokenType.PERIOD:
|
52
|
+
return None
|
53
|
+
name_parts.extend((period, name))
|
54
|
+
|
55
|
+
return name_parts
|
56
|
+
|
57
|
+
@classmethod
|
58
|
+
@lexer_reset
|
59
|
+
def match(cls, parser: "Parser") -> "FunctionExpression | None":
|
60
|
+
from . import any_expression_match # noqa: PLC0415
|
61
|
+
|
62
|
+
args: list[Expression] = []
|
63
|
+
name_parts = cls._match_function_name(parser)
|
64
|
+
if name_parts is None:
|
65
|
+
return None
|
66
|
+
|
67
|
+
left_paren = parser.consume()
|
68
|
+
if left_paren.tok_type != TokenType.LEFT_PAREN:
|
69
|
+
return None
|
70
|
+
|
71
|
+
while not cls.match_tokens(parser, [TokenType.RIGHT_PAREN]):
|
72
|
+
arg = any_expression_match(parser)
|
73
|
+
if arg is not None:
|
74
|
+
args.append(arg)
|
75
|
+
elif parser.peek().tok_type == TokenType.COMMA:
|
76
|
+
args.append(NoneExpression())
|
77
|
+
else:
|
78
|
+
msg = f"Unexpected token sequence: {parser.peek()}, {parser.index}"
|
79
|
+
raise ValueError(msg)
|
80
|
+
|
81
|
+
if not cls.match_tokens(parser, [TokenType.RIGHT_PAREN]):
|
82
|
+
assert parser.consume().tok_type == TokenType.COMMA
|
83
|
+
|
84
|
+
right_paren = parser.consume()
|
85
|
+
if right_paren.tok_type != TokenType.RIGHT_PAREN:
|
86
|
+
msg = f"Expected a right parenthesis, found: {right_paren}"
|
87
|
+
raise ValueError(msg)
|
88
|
+
|
89
|
+
return FunctionExpression(name_parts=name_parts, args=args, parens=(left_paren, right_paren))
|
90
|
+
|
91
|
+
def function_name(self) -> str:
|
92
|
+
return "".join(x.text for x in self.name_parts)
|
93
|
+
|
94
|
+
def children(self) -> list[Expression]:
|
95
|
+
"""Returns a list of child expressions."""
|
96
|
+
return self.args
|
97
|
+
|
98
|
+
def position(self) -> tuple[int, int]:
|
99
|
+
return self.parens[0].text_slice.start, self.parens[1].text_slice.end
|
100
|
+
|
101
|
+
def full_text(self) -> str:
|
102
|
+
return self.parens[0].text_slice.full_text
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
4
|
+
|
5
|
+
from ._base import Expression
|
6
|
+
from ._utils import lexer_reset
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from pbi_parsers.dax.parser import Parser
|
10
|
+
|
11
|
+
|
12
|
+
class HierarchyExpression(Expression):
|
13
|
+
"""Represents a hierarchy in DAX.
|
14
|
+
|
15
|
+
Examples:
|
16
|
+
Table[Column].[Level]
|
17
|
+
'Table Name'[Column Name].[Level Name]
|
18
|
+
|
19
|
+
"""
|
20
|
+
|
21
|
+
table: Token
|
22
|
+
column: Token
|
23
|
+
level: Token
|
24
|
+
|
25
|
+
def __init__(self, table: Token, column: Token, level: Token) -> None:
|
26
|
+
self.table = table
|
27
|
+
self.column = column
|
28
|
+
self.level = level
|
29
|
+
|
30
|
+
def pprint(self) -> str:
|
31
|
+
return f"""
|
32
|
+
Hierarchy (
|
33
|
+
table: {self.table.text},
|
34
|
+
column: {self.column.text},
|
35
|
+
level: {self.level.text}
|
36
|
+
)""".strip()
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
@lexer_reset
|
40
|
+
def match(cls, parser: "Parser") -> "HierarchyExpression | None":
|
41
|
+
table, column, period, level = (
|
42
|
+
parser.consume(),
|
43
|
+
parser.consume(),
|
44
|
+
parser.consume(),
|
45
|
+
parser.consume(),
|
46
|
+
)
|
47
|
+
if table.tok_type not in {
|
48
|
+
TokenType.SINGLE_QUOTED_IDENTIFIER,
|
49
|
+
TokenType.UNQUOTED_IDENTIFIER,
|
50
|
+
}:
|
51
|
+
return None
|
52
|
+
if column.tok_type != TokenType.BRACKETED_IDENTIFIER:
|
53
|
+
return None
|
54
|
+
if period.tok_type != TokenType.PERIOD:
|
55
|
+
return None
|
56
|
+
if level.tok_type != TokenType.BRACKETED_IDENTIFIER:
|
57
|
+
return None
|
58
|
+
return HierarchyExpression(table=table, column=column, level=level)
|
59
|
+
|
60
|
+
def children(self) -> list[Expression]: # noqa: PLR6301
|
61
|
+
"""Returns a list of child expressions."""
|
62
|
+
return []
|
63
|
+
|
64
|
+
def position(self) -> tuple[int, int]:
|
65
|
+
return self.table.text_slice.start, self.level.text_slice.end
|
66
|
+
|
67
|
+
def full_text(self) -> str:
|
68
|
+
return self.table.text_slice.full_text
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from pbi_parsers.dax.tokens import Token, TokenType
|
4
|
+
|
5
|
+
from ._base import Expression
|
6
|
+
from ._utils import lexer_reset
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from pbi_parsers.dax.parser import Parser
|
10
|
+
|
11
|
+
|
12
|
+
class IdentifierExpression(Expression):
|
13
|
+
"""Represents a simple identifier of a variable.
|
14
|
+
|
15
|
+
Examples:
|
16
|
+
VariableName
|
17
|
+
AnotherVariableName
|
18
|
+
|
19
|
+
"""
|
20
|
+
|
21
|
+
name: Token
|
22
|
+
|
23
|
+
def __init__(self, name: Token) -> None:
|
24
|
+
self.name = name
|
25
|
+
|
26
|
+
def pprint(self) -> str:
|
27
|
+
return f"""
|
28
|
+
Identifier ({self.name.text})""".strip()
|
29
|
+
|
30
|
+
@classmethod
|
31
|
+
@lexer_reset
|
32
|
+
def match(cls, parser: "Parser") -> "IdentifierExpression | None":
|
33
|
+
name = parser.consume()
|
34
|
+
if name.tok_type != TokenType.UNQUOTED_IDENTIFIER:
|
35
|
+
return None
|
36
|
+
return IdentifierExpression(name=name)
|
37
|
+
|
38
|
+
def children(self) -> list[Expression]: # noqa: PLR6301
|
39
|
+
"""Returns a list of child expressions."""
|
40
|
+
return []
|
41
|
+
|
42
|
+
def position(self) -> tuple[int, int]:
|
43
|
+
return self.name.text_slice.start, self.name.text_slice.end
|
44
|
+
|
45
|
+
def full_text(self) -> str:
|
46
|
+
return self.name.text_slice.full_text
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import textwrap
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from pbi_parsers.dax.tokens import TokenType
|
5
|
+
|
6
|
+
from ._base import Expression
|
7
|
+
from ._utils import lexer_reset
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from pbi_parsers.dax.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class InExpression(Expression):
|
14
|
+
"""Represents an IN check.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
1 IN {1, 2, 3}
|
18
|
+
"text" IN {"text", "other text"}
|
19
|
+
|
20
|
+
"""
|
21
|
+
|
22
|
+
value: Expression
|
23
|
+
array: Expression
|
24
|
+
|
25
|
+
def __init__(self, value: Expression, array: Expression) -> None:
|
26
|
+
self.value = value
|
27
|
+
self.array = array
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
@lexer_reset
|
31
|
+
def match(cls, parser: "Parser") -> "InExpression | None":
|
32
|
+
from . import EXPRESSION_HIERARCHY, any_expression_match # noqa: PLC0415
|
33
|
+
|
34
|
+
skip_index = EXPRESSION_HIERARCHY.index(InExpression)
|
35
|
+
|
36
|
+
left_term = any_expression_match(parser=parser, skip_first=skip_index + 1)
|
37
|
+
operator = parser.consume()
|
38
|
+
|
39
|
+
if not left_term:
|
40
|
+
return None
|
41
|
+
if operator.tok_type != TokenType.IN:
|
42
|
+
return None
|
43
|
+
|
44
|
+
right_term = any_expression_match(parser=parser, skip_first=skip_index)
|
45
|
+
if right_term is None:
|
46
|
+
msg = f"Expected a right term after operator {operator.text}, found: {parser.peek()}"
|
47
|
+
raise ValueError(msg)
|
48
|
+
return InExpression(value=left_term, array=right_term)
|
49
|
+
|
50
|
+
def pprint(self) -> str:
|
51
|
+
value_str = textwrap.indent(self.value.pprint(), " " * 11)[11:]
|
52
|
+
array_str = textwrap.indent(self.array.pprint(), " " * 11)[11:]
|
53
|
+
return f"""
|
54
|
+
In (
|
55
|
+
value: {value_str},
|
56
|
+
array: {array_str}
|
57
|
+
)""".strip()
|
58
|
+
|
59
|
+
def children(self) -> list[Expression]:
|
60
|
+
"""Returns a list of child expressions."""
|
61
|
+
return [self.value, self.array]
|
62
|
+
|
63
|
+
def position(self) -> tuple[int, int]:
|
64
|
+
return self.value.position()[0], self.array.position()[1]
|
65
|
+
|
66
|
+
def full_text(self) -> str:
|
67
|
+
return self.value.full_text()
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from pbi_parsers.dax.tokens import KEYWORD_MAPPING, Token, TokenType
|
4
|
+
|
5
|
+
from ._base import Expression
|
6
|
+
from ._utils import lexer_reset
|
7
|
+
from .function import FunctionExpression
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from pbi_parsers.dax.parser import Parser
|
11
|
+
|
12
|
+
|
13
|
+
class KeywordExpression(Expression):
|
14
|
+
"""Represents a keyword in DAX.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
TRUE
|
18
|
+
FALSE
|
19
|
+
|
20
|
+
"""
|
21
|
+
|
22
|
+
name: Token
|
23
|
+
|
24
|
+
def __init__(self, name: Token) -> None:
|
25
|
+
self.name = name
|
26
|
+
|
27
|
+
def pprint(self) -> str:
|
28
|
+
return f"""
|
29
|
+
Keyword ({self.name.text})""".strip()
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
@lexer_reset
|
33
|
+
def match(cls, parser: "Parser") -> "KeywordExpression | FunctionExpression | None":
|
34
|
+
name = parser.consume()
|
35
|
+
if name.tok_type not in KEYWORD_MAPPING.values():
|
36
|
+
return None
|
37
|
+
if name.text.lower() in {"true", "false"}:
|
38
|
+
p1 = parser.peek()
|
39
|
+
p2 = parser.peek(1)
|
40
|
+
if (p1.tok_type, p2.tok_type) == (TokenType.LEFT_PAREN, TokenType.RIGHT_PAREN):
|
41
|
+
# This is a special case for boolean keywords with parentheses.
|
42
|
+
# IDK why microsoft made TRUE() a function too
|
43
|
+
left_paren = parser.consume()
|
44
|
+
right_paren = parser.consume()
|
45
|
+
return FunctionExpression(
|
46
|
+
name_parts=[name],
|
47
|
+
args=[],
|
48
|
+
parens=(left_paren, right_paren),
|
49
|
+
)
|
50
|
+
return KeywordExpression(name=name)
|
51
|
+
|
52
|
+
def children(self) -> list[Expression]: # noqa: PLR6301
|
53
|
+
"""Returns a list of child expressions."""
|
54
|
+
return []
|
55
|
+
|
56
|
+
def position(self) -> tuple[int, int]:
|
57
|
+
return self.name.text_slice.start, self.name.text_slice.end
|
58
|
+
|
59
|
+
def full_text(self) -> str:
|
60
|
+
return self.name.text_slice.full_text
|