Analyse rules statically¶
Before any report is loaded you can ask a rule what it reads: which tables and cells, which filing indicators, which functions. That is how you index a rule set by table, find the rules affected by a template change, or decide which tables a report must contain.
The expression tree¶
parse returns a tree of frozen dataclasses from
dpmxl.ast. Every node has a half-open source
span, so you can always show the original text of an operand.
from dpmxl import parse
source = (
"with {tC_01.00, c*, default: 0}: {r0010} = {r0020} + sum({tC_02.00, r0010-0050})"
)
tree = parse(source)
comparison = tree.body
print(type(tree).__name__, type(comparison).__name__, comparison.op)
print(source[slice(*comparison.right.span)])
walk yields every node, depth first, and
children a node's direct children (including the
default of a selection, a with context and a typed parameter):
from dpmxl.ast import Call, walk
print([node.name for node in walk(tree) if isinstance(node, Call)])
The language reference names the node each
construct parses to. Analytic calls such as rank(x over (…)) are
Analytic nodes, not Calls. parse_program
returns a Program whose statements are expressions, Assignments
(name := e) and PersistentAssignments ({target} <- e).
Selections with their context¶
A selection inside with inherits the context's table, axes and options.
references returns every selection completed
by its context, as Reference objects in source
order:
from dpmxl import references
source = "with {tC_01.00, c*, default: 0}: {r0010} = {r0020} + {g_G1, r0010} + {o_v123}"
for reference in references(parse(source)):
print(
f"{reference.kind:12} {reference.code:8} {reference.spec.text():36} {source[slice(*reference.span)]}"
)
table C_01.00 {tC_01.00, r0010, c*, default: 0} {r0010}
table C_01.00 {tC_01.00, r0020, c*, default: 0} {r0020}
table_group G1 {gG1, r0010, c*, default: 0} {g_G1, r0010}
operation v123 {ov123, c*, default: 0} {o_v123}
kind is table, table_group or operation; for the latter two
spec.table is None and code names the group or operation. Typed
parameters ({p_Name, …}) are not selections and are not listed; targets of
persistent assignments are.
Tables and filing indicators¶
tables lists the distinct table codes the
cell selections read. Given a provider with a table_group method, a group
read counts as reading every table of the group:
from dpmxl import MemoryProvider, tables
tree = parse(source)
print(tables(tree))
print(tables(tree, MemoryProvider([], {}, table_groups={"G1": ["C_03.00", "C_04.00"]})))
reported_templates lists the
templates a rule tests with {v_…}, typically in its precondition:
from dpmxl import reported_templates
print(reported_templates(parse("{v_C_01.00} and not {v_C_02.00}")))
Index a rule set by table¶
import json
from collections import defaultdict
from pathlib import Path
from dpmxl import parse_program
by_table = defaultdict(list)
for rule in json.loads(Path("examples/rules.json").read_text()):
for table in tables(parse_program(rule["expression"])):
by_table[table].append(rule["code"])
print(dict(by_table))
dpm-xl check --references prints the same selections for a rule file on the
command line (CLI reference).