Check rule syntax in CI¶
A repository of rules (exported from a DPM release, or written by hand)
should never hold an expression that does not parse. dpm-xl check parses
every expression and precondition of a rule source and exits with status 1
when any of them fails. With --strict it also fails on other errors, such
as an unknown function or a call with the wrong operands, and with
--layouts it checks tables and coordinates too
(CLI reference).
With the CLI¶
A syntax error names the rule and points at the offending text:
$ dpm-xl check "with {tC_01.00, c0010}: {r0010} = {r0020} +* {r0030}"
argument: error: unexpected '*' where an operand is expected
with {tC_01.00, c0010}: {r0010} = {r0020} +* {r0030}
^
0/1 expressions parsed; 1 error
$ echo $?
1
In a rule file the source is <code>:expression or <code>:precondition
(JSON rule files) or line <n>:expression (text files). --json prints the
same as a document, with the zero-based character start and end of each
problem, for annotating a pull request:
$ dpm-xl check "if {tC_01.00, r0010} > 0 then true" --json
{
"schema_version": 1,
"parsed": 0,
"total": 1,
"errors": [
{
"source": "argument",
"message": "expected 'endif', found end of expression",
"position": 34,
"end": 34
}
],
"diagnostics": [
{
"source": "argument",
"severity": "error",
"code": "syntax",
"message": "expected 'endif', found end of expression",
"start": 34,
"end": 34,
"subject": null,
"suggestion": null
}
]
}
A missing rule file is an error
An argument that reads as a path (it ends in .json or .txt, or
holds a / without any brackets, operators or spaces) but names no
file fails with error: no such rule file: ... and exit status 3, so a
mistyped path cannot pass as an expression.
A GitHub Actions job¶
name: Rules
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: test -f rules/rules.json
- run: uvx dpm-xl check rules/rules.json --strict
Pin the version (uvx dpm-xl@1.0.0 ...) if you want a new release to be a
deliberate change.
With pytest¶
A test can check more than syntax: that every rule only refers to tables your
release defines, for example. parse_program
accepts everything dpm-xl check accepts, and
tables lists the tables an expression selects
from.
import json
from pathlib import Path
from dpmxl import DpmXlSyntaxError, parse_program, tables
KNOWN_TABLES = {"C_01.00", "C_27.00"}
def check_rules(path):
problems = []
for rule in json.loads(Path(path).read_text()):
for part in ("expression", "precondition"):
if not rule.get(part):
continue
try:
tree = parse_program(rule[part])
except DpmXlSyntaxError as error:
problems.append(f"{rule['code']} {part}: {error.message}")
continue
unknown = set(tables(tree)) - KNOWN_TABLES
if unknown:
problems.append(
f"{rule['code']} {part}: unknown tables {sorted(unknown)}"
)
return problems
print(check_rules("examples/rules.json"))
In a test, assert not check_rules("rules/rules.json") fails the job and
lists every problem.
What parses¶
dpm-xl parses every construct of the DPM-XL grammar and every EBA validation
rule and precondition of DPM releases 3.4–4.4, except two EBA expressions with
typos (trueendif, ornot). A few readings differ from the grammar on
purpose; Language
lists them.