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.
Files changed (78) hide show
  1. pbi_parsers/__init__.py +9 -0
  2. pbi_parsers/base/__init__.py +7 -0
  3. pbi_parsers/base/lexer.py +127 -0
  4. pbi_parsers/base/tokens.py +61 -0
  5. pbi_parsers/dax/__init__.py +22 -0
  6. pbi_parsers/dax/exprs/__init__.py +107 -0
  7. pbi_parsers/dax/exprs/_base.py +46 -0
  8. pbi_parsers/dax/exprs/_utils.py +45 -0
  9. pbi_parsers/dax/exprs/add_sub.py +73 -0
  10. pbi_parsers/dax/exprs/add_sub_unary.py +72 -0
  11. pbi_parsers/dax/exprs/array.py +75 -0
  12. pbi_parsers/dax/exprs/column.py +56 -0
  13. pbi_parsers/dax/exprs/comparison.py +76 -0
  14. pbi_parsers/dax/exprs/concatenation.py +73 -0
  15. pbi_parsers/dax/exprs/div_mul.py +75 -0
  16. pbi_parsers/dax/exprs/exponent.py +67 -0
  17. pbi_parsers/dax/exprs/function.py +102 -0
  18. pbi_parsers/dax/exprs/hierarchy.py +68 -0
  19. pbi_parsers/dax/exprs/identifier.py +46 -0
  20. pbi_parsers/dax/exprs/ins.py +67 -0
  21. pbi_parsers/dax/exprs/keyword.py +60 -0
  22. pbi_parsers/dax/exprs/literal_number.py +46 -0
  23. pbi_parsers/dax/exprs/literal_string.py +45 -0
  24. pbi_parsers/dax/exprs/logical.py +76 -0
  25. pbi_parsers/dax/exprs/measure.py +44 -0
  26. pbi_parsers/dax/exprs/none.py +30 -0
  27. pbi_parsers/dax/exprs/parens.py +61 -0
  28. pbi_parsers/dax/exprs/returns.py +76 -0
  29. pbi_parsers/dax/exprs/table.py +51 -0
  30. pbi_parsers/dax/exprs/variable.py +68 -0
  31. pbi_parsers/dax/formatter.py +215 -0
  32. pbi_parsers/dax/lexer.py +222 -0
  33. pbi_parsers/dax/main.py +63 -0
  34. pbi_parsers/dax/parser.py +66 -0
  35. pbi_parsers/dax/tokens.py +54 -0
  36. pbi_parsers/dax/utils.py +120 -0
  37. pbi_parsers/pq/__init__.py +17 -0
  38. pbi_parsers/pq/exprs/__init__.py +98 -0
  39. pbi_parsers/pq/exprs/_base.py +33 -0
  40. pbi_parsers/pq/exprs/_utils.py +31 -0
  41. pbi_parsers/pq/exprs/add_sub.py +59 -0
  42. pbi_parsers/pq/exprs/add_sub_unary.py +57 -0
  43. pbi_parsers/pq/exprs/and_or_expr.py +60 -0
  44. pbi_parsers/pq/exprs/array.py +53 -0
  45. pbi_parsers/pq/exprs/arrow.py +50 -0
  46. pbi_parsers/pq/exprs/column.py +42 -0
  47. pbi_parsers/pq/exprs/comparison.py +62 -0
  48. pbi_parsers/pq/exprs/concatenation.py +61 -0
  49. pbi_parsers/pq/exprs/div_mul.py +59 -0
  50. pbi_parsers/pq/exprs/each.py +41 -0
  51. pbi_parsers/pq/exprs/ellipsis_expr.py +28 -0
  52. pbi_parsers/pq/exprs/function.py +63 -0
  53. pbi_parsers/pq/exprs/identifier.py +77 -0
  54. pbi_parsers/pq/exprs/if_expr.py +70 -0
  55. pbi_parsers/pq/exprs/is_expr.py +54 -0
  56. pbi_parsers/pq/exprs/keyword.py +40 -0
  57. pbi_parsers/pq/exprs/literal_number.py +31 -0
  58. pbi_parsers/pq/exprs/literal_string.py +31 -0
  59. pbi_parsers/pq/exprs/meta.py +54 -0
  60. pbi_parsers/pq/exprs/negation.py +52 -0
  61. pbi_parsers/pq/exprs/none.py +22 -0
  62. pbi_parsers/pq/exprs/not_expr.py +39 -0
  63. pbi_parsers/pq/exprs/parens.py +43 -0
  64. pbi_parsers/pq/exprs/record.py +58 -0
  65. pbi_parsers/pq/exprs/row.py +54 -0
  66. pbi_parsers/pq/exprs/row_index.py +57 -0
  67. pbi_parsers/pq/exprs/statement.py +67 -0
  68. pbi_parsers/pq/exprs/try_expr.py +55 -0
  69. pbi_parsers/pq/exprs/type_expr.py +78 -0
  70. pbi_parsers/pq/exprs/variable.py +52 -0
  71. pbi_parsers/pq/formatter.py +13 -0
  72. pbi_parsers/pq/lexer.py +219 -0
  73. pbi_parsers/pq/main.py +63 -0
  74. pbi_parsers/pq/parser.py +65 -0
  75. pbi_parsers/pq/tokens.py +81 -0
  76. pbi_parsers-0.7.8.dist-info/METADATA +66 -0
  77. pbi_parsers-0.7.8.dist-info/RECORD +78 -0
  78. 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