Skip to content

dpmxl.ast

The expression tree: frozen node classes with source spans. Language names the node each construct parses to.

dpmxl.ast

Abstract syntax tree for DPM-XL expressions.

Every node keeps the half-open source span it was parsed from, so callers can show the original operand text next to an evaluated value without re-printing the expression.

Span module-attribute

Span: TypeAlias = tuple[int, int]

Half-open (start, end) character offsets into the parsed source.

AxisName module-attribute

AxisName: TypeAlias = Literal['rows', 'columns', 'sheets']

Name of a closed axis field of SelectionSpec.

AXIS_PREFIXES module-attribute

AXIS_PREFIXES: dict[str, AxisName] = {
    "r": "rows",
    "c": "columns",
    "s": "sheets",
}

Axis letter (r, c, s) to its SelectionSpec field name.

AXIS_KEYS module-attribute

AXIS_KEYS: dict[AxisName, str] = {
    "rows": "r",
    "columns": "c",
    "sheets": "s",
}

SelectionSpec axis field name to the record key it produces.

MISSING module-attribute

MISSING = _Missing()

Marker for a default that was not written; it is falsy and differs from a written default: null.

AxisSelector dataclass

AxisSelector(
    wildcard: bool = False,
    parts: tuple[str | tuple[str, str], ...] = (),
    *,
    span: Span = (0, 0),
    spans: tuple[Span, ...] = (),
)

Selected coordinates on one table axis.

parts holds single codes ("0010") and inclusive ranges (("0010", "0050")) as written, without the axis prefix. A range may repeat the axis letter before its upper bound: r0010-r0050 is ("0010", "0050"). The letter is dropped only when the lower bound does not start with it, so a code that does is read as written: rr1-rr10 is ("r1", "rr10"); write rr1-r10 for ("r1", "r10").

A parsed selector keeps its source positions; like node spans they take no part in equality, hashing or repr, and selectors built directly have none.

Attributes:

  • wildcard (bool) –

    Whether the selector is * (every coordinate).

  • parts (tuple[str | tuple[str, str], ...]) –

    Single codes and (low, high) ranges.

  • span (Span) –

    Source span of the whole selector, such as r0010-0050 or (r0010, r0020); (0, 0) when built directly.

  • spans (tuple[Span, ...]) –

    Source span of each part, in the order of parts; empty for a wildcard or a selector built directly.

Examples:

>>> from dpmxl import parse_selection
>>> rows = parse_selection("tT, (r0010, r0020-0050)").rows
>>> rows.parts, rows.span, rows.spans
(('0010', ('0020', '0050')), (4, 23), ((5, 10), (12, 22)))

wildcard class-attribute instance-attribute

wildcard: bool = False

parts class-attribute instance-attribute

parts: tuple[str | tuple[str, str], ...] = ()

span class-attribute instance-attribute

span: Span = field(
    default=(0, 0), compare=False, repr=False, kw_only=True
)

spans class-attribute instance-attribute

spans: tuple[Span, ...] = field(
    default=(), compare=False, repr=False, kw_only=True
)

is_single property

is_single: bool

Whether the selector names exactly one coordinate.

text

text(prefix: str) -> str

Render the selector in DPM-XL syntax.

Returns:

  • str ( str ) –

    For example r*, r0010 or (r0010, r0020-0050).

SelectionSpec dataclass

SelectionSpec(
    table: str | None = None,
    rows: AxisSelector | None = None,
    columns: AxisSelector | None = None,
    sheets: AxisSelector | None = None,
    default: Literal_ | SetLiteral | _Missing = MISSING,
    interval: bool | None = None,
    table_group: str | None = None,
    operation: str | None = None,
    *,
    source_span: Span = (0, 0),
)

Source, axis and option components of a {...} selection.

The source is one of table (tC_01.00), table_group (gG_01) or operation (o_v1234, another operation's result); the others are None.

Attributes:

  • table (str | None) –

    Table code, such as C_01.00.

  • rows (AxisSelector | None) –

    Row selector, or None when not written.

  • columns (AxisSelector | None) –

    Column selector, or None.

  • sheets (AxisSelector | None) –

    Sheet selector, or None.

  • default (Literal_ | SetLiteral | _Missing) –

    The default: literal, or MISSING when not written.

  • interval (bool | None) –

    The interval: option, or None when not written.

  • table_group (str | None) –

    Table group code of a {g_...} selection.

  • operation (str | None) –

    Operation code of an {o_...} reference.

  • source_span (Span) –

    Source span of the table, table group or operation reference, such as tC_01.00; (0, 0) when there is none or the selection was built directly. Like the selectors' spans it takes no part in equality, hashing or repr, and merged_into keeps the span of the source it keeps.

table class-attribute instance-attribute

table: str | None = None

rows class-attribute instance-attribute

rows: AxisSelector | None = None

columns class-attribute instance-attribute

columns: AxisSelector | None = None

sheets class-attribute instance-attribute

sheets: AxisSelector | None = None

default class-attribute instance-attribute

default: Literal_ | SetLiteral | _Missing = MISSING

interval class-attribute instance-attribute

interval: bool | None = None

table_group class-attribute instance-attribute

table_group: str | None = None

operation class-attribute instance-attribute

operation: str | None = None

source_span class-attribute instance-attribute

source_span: Span = field(
    default=(0, 0), compare=False, repr=False, kw_only=True
)

merged_into

merged_into(context: SelectionSpec) -> SelectionSpec

Return this selection completed by an inherited with context.

Returns:

  • SelectionSpec ( SelectionSpec ) –

    Components written here win over the context; a

  • SelectionSpec –

    source written here replaces the context's source.

Examples:

>>> from dpmxl import parse_selection
>>> context = parse_selection("tC_01.00, c0010, interval: true")
>>> parse_selection("r0020").merged_into(context).text()
'{tC_01.00, r0020, c0010, interval: true}'

axis

axis(name: AxisName) -> AxisSelector | None

Return the selector for one axis.

Returns:

  • AxisSelector | None –

    AxisSelector | None: None when the axis was not written.

text

text() -> str

Render the selection in DPM-XL syntax.

Returns:

  • str ( str ) –

    A normalized {...} selection.

Examples:

>>> from dpmxl import parse_selection
>>> parse_selection("t_C_01.00 , r10-50, default: 0").text()
'{tC_01.00, r10-50, default: 0}'

Node dataclass

Node(*, span: Span = (0, 0))

Base class for expression nodes.

Every node has a public span: the half-open (start, end) character offsets of its source in the parsed expression, so expression[start:end] is the node's text. A parenthesized operand's span includes the parentheses; a clause's span runs from its operand to the closing ]. Spans take no part in equality or hashing. Nodes built directly default to (0, 0).

span class-attribute instance-attribute

span: Span = field(
    default=(0, 0), compare=False, kw_only=True
)

Literal_ dataclass

Literal_(
    value: Decimal | str | bool | date | Item | None,
    kind: Literal[
        "number",
        "string",
        "boolean",
        "date",
        "item",
        "null",
    ],
    *,
    span: Span = (0, 0),
)

Bases: Node

A constant: number, string, boolean, date, item or null.

Attributes:

  • value (Decimal | str | bool | date | Item | None) –

    The constant: Decimal, str, bool, date (or datetime), Item or None.

  • kind (Literal['number', 'string', 'boolean', 'date', 'item', 'null']) –

    number, string, boolean, date, item or null.

value instance-attribute

value: Decimal | str | bool | date | Item | None

kind instance-attribute

kind: Literal[
    "number", "string", "boolean", "date", "item", "null"
]

Item dataclass

Item(category: str | None, code: str)

An enumerated item literal such as [eba_qEC:qx01].

category is the namespace part without the eba_ prefix; it is None for a bare legacy element reference such as [ei86].

Attributes:

  • category (str | None) –

    Category without eba_, such as qEC, or None.

  • code (str) –

    Item code, such as qx01.

category instance-attribute

category: str | None

code instance-attribute

code: str

__post_init__

__post_init__() -> None

Drop the eba_ prefix from a category given directly.

parse classmethod

parse(text: str) -> Item

Parse prefix:code or a bare code.

Returns:

  • Item ( Item ) –

    Normalized item identity; the eba_ prefix is dropped.

Examples:

>>> Item.parse("eba_qEC:qx01")
Item(category='qEC', code='qx01')
>>> Item.parse("eba_qEC:qx01").matches(Item.parse("qx01"))
True

matches

matches(other: Item) -> bool

Compare two items, ignoring a category missing on either side.

Returns:

  • bool ( bool ) –

    Whether both name the same item.

__str__

__str__() -> str

Render category:code or the bare code.

Returns:

  • str ( str ) –

    The item text.

Selection dataclass

Selection(spec: SelectionSpec, *, span: Span = (0, 0))

Bases: Node

A {...} cell selection.

Attributes:

spec instance-attribute

TableReported dataclass

TableReported(table: str, *, span: Span = (0, 0))

Bases: Node

{v_TABLE}: true when the report declares the template.

Attributes:

  • table (str) –

    The template or table code.

table instance-attribute

table: str

Parameter dataclass

Parameter(name: str, *, span: Span = (0, 0))

Bases: Node

{vName}: a report-level parameter such as ReportingLevel.

Attributes:

  • name (str) –

    Parameter name without the v prefix.

name instance-attribute

name: str

TypedParameter dataclass

TypedParameter(
    name: str,
    type: str,
    default: Literal_ | SetLiteral | _Missing = MISSING,
    *,
    span: Span = (0, 0),
)

Bases: Node

{p_Name, type[, default: value]}: a typed report parameter.

type is one of PARAMETER_TYPES in lower case.

Attributes:

  • name (str) –

    Parameter name without the p_ prefix.

  • type (str) –

    The declared type.

  • default (Literal_ | SetLiteral | _Missing) –

    The default: value, or MISSING.

name instance-attribute

name: str

type instance-attribute

type: str

default class-attribute instance-attribute

default: Literal_ | SetLiteral | _Missing = MISSING

Name dataclass

Name(name: str, *, span: Span = (0, 0))

Bases: Node

A bare identifier: a key component, period unit or parameter name.

Attributes:

  • name (str) –

    The identifier, without backticks.

name instance-attribute

name: str

SetLiteral dataclass

SetLiteral(items: tuple[Node, ...], *, span: Span = (0, 0))

Bases: Node

A set literal {a, b, ...} or the empty set {}.

Attributes:

  • items (tuple[Node, ...]) –

    The member expressions.

items instance-attribute

items: tuple[Node, ...]

Unary dataclass

Unary(
    op: Literal["+", "-", "not"],
    operand: Node,
    *,
    span: Span = (0, 0),
)

Bases: Node

Prefix +, - or not.

Attributes:

  • op (Literal['+', '-', 'not']) –

    The operator.

  • operand (Node) –

    The operand.

op instance-attribute

op: Literal['+', '-', 'not']

operand instance-attribute

operand: Node

Binary dataclass

Binary(
    op: str, left: Node, right: Node, *, span: Span = (0, 0)
)

Bases: Node

Arithmetic, comparison or boolean operator.

Attributes:

  • op (str) –

    The operator as written canonically: + - * / & = != < <= > >=, and, or or xor (<> is read as !=).

  • left (Node) –

    Left operand.

  • right (Node) –

    Right operand.

op instance-attribute

op: str

left instance-attribute

left: Node

right instance-attribute

right: Node

In dataclass

In(operand: Node, values: Node, *, span: Span = (0, 0))

Bases: Node

operand in set.

values is a SetLiteral for in {...}, otherwise any set-valued expression such as a set-* parameter or union(...).

Attributes:

operand instance-attribute

operand: Node

values instance-attribute

values: Node

If dataclass

If(
    condition: Node,
    then: Node,
    otherwise: Node | None,
    *,
    span: Span = (0, 0),
)

Bases: Node

if c then a [else b] endif.

Attributes:

condition instance-attribute

condition: Node

then instance-attribute

then: Node

otherwise instance-attribute

otherwise: Node | None

Call dataclass

Call(
    name: str,
    args: tuple[Node, ...],
    group_by: tuple[str, ...] | None = None,
    *,
    span: Span = (0, 0),
)

Bases: Node

Function or aggregate call, optionally with group by keys.

Attributes:

  • name (str) –

    Canonical function name (see canonical_function).

  • args (tuple[Node, ...]) –

    Arguments in order.

  • group_by (tuple[str, ...] | None) –

    Key names of group by, or None without it.

name instance-attribute

name: str

args instance-attribute

args: tuple[Node, ...]

group_by class-attribute instance-attribute

group_by: tuple[str, ...] | None = None

WindowBound dataclass

WindowBound(
    kind: Literal[
        "unbounded preceding",
        "preceding",
        "current data point",
        "following",
        "unbounded following",
    ],
    offset: int = 0,
    period: str | None = None,
)

One end of an analytic window.

kind is unbounded preceding, preceding, current data point, following or unbounded following; offset counts data points, or for a range window units of the order key, or of period (A, S, Q, M, W, D) for a date key.

Attributes:

  • kind (Literal['unbounded preceding', 'preceding', 'current data point', 'following', 'unbounded following']) –

    The bound kind, as above.

  • offset (int) –

    Number of data points or order-key units.

  • period (str | None) –

    Period code of a date offset, or None.

kind instance-attribute

kind: Literal[
    "unbounded preceding",
    "preceding",
    "current data point",
    "following",
    "unbounded following",
]

offset class-attribute instance-attribute

offset: int = 0

period class-attribute instance-attribute

period: str | None = None

Window dataclass

Window(
    unit: Literal["data points", "range"],
    start: WindowBound,
    end: WindowBound,
)

data points|range between start and end.

Attributes:

unit instance-attribute

unit: Literal['data points', 'range']

start instance-attribute

start: WindowBound

end instance-attribute

Analytic dataclass

Analytic(
    name: str,
    operand: Node,
    partition_by: tuple[str, ...] = (),
    order_by: tuple[tuple[str, bool], ...] = (),
    window: Window | None = None,
    *,
    span: Span = (0, 0),
)

Bases: Node

name(operand over (partition by … order by … window)).

name is rank or an aggregate's call name (sum, count, avg, max, min, median). order_by pairs each key with whether it sorts descending. The result keeps the operand's records.

Attributes:

  • name (str) –

    rank or the aggregate's call name.

  • operand (Node) –

    The aggregated expression.

  • partition_by (tuple[str, ...]) –

    Partition key names.

  • order_by (tuple[tuple[str, bool], ...]) –

    (key, descending) pairs.

  • window (Window | None) –

    The window clause, or None when not written.

name instance-attribute

name: str

operand instance-attribute

operand: Node

partition_by class-attribute instance-attribute

partition_by: tuple[str, ...] = ()

order_by class-attribute instance-attribute

order_by: tuple[tuple[str, bool], ...] = ()

window class-attribute instance-attribute

window: Window | None = None

Where dataclass

Where(
    operand: Node, condition: Node, *, span: Span = (0, 0)
)

Bases: Node

operand [where condition] filters records by key components.

Attributes:

operand instance-attribute

operand: Node

condition instance-attribute

condition: Node

Get dataclass

Get(operand: Node, name: str, *, span: Span = (0, 0))

Bases: Node

operand [get name] replaces values by a key component.

Attributes:

  • operand (Node) –

    The expression.

  • name (str) –

    Key component whose value becomes the record's value.

operand instance-attribute

operand: Node

name instance-attribute

name: str

Sub dataclass

Sub(
    operand: Node,
    name: str,
    value: Node,
    *,
    span: Span = (0, 0),
)

Bases: Node

operand [sub name = value] fixes and removes a key component.

Attributes:

operand instance-attribute

operand: Node

name instance-attribute

name: str

value instance-attribute

value: Node

Rename dataclass

Rename(
    operand: Node,
    pairs: tuple[tuple[str, str], ...],
    *,
    span: Span = (0, 0),
)

Bases: Node

operand [rename a to b, ...] renames key components.

Attributes:

  • operand (Node) –

    The expression.

  • pairs (tuple[tuple[str, str], ...]) –

    (old, new) key names.

operand instance-attribute

operand: Node

pairs instance-attribute

pairs: tuple[tuple[str, str], ...]

With dataclass

With(
    context: SelectionSpec,
    body: Node,
    condition: Node | None = None,
    *,
    span: Span = (0, 0),
)

Bases: Node

with {context} [where condition]: body.

The optional condition filters every selection evaluated in the body.

Attributes:

context instance-attribute

context: SelectionSpec

body instance-attribute

body: Node

condition class-attribute instance-attribute

condition: Node | None = None

Assignment dataclass

Assignment(name: str, value: Node, *, span: Span = (0, 0))

Bases: Node

name := value: a temporary result later statements refer to.

Attributes:

  • name (str) –

    The temporary's name.

  • value (Node) –

    The assigned expression.

name instance-attribute

name: str

value instance-attribute

value: Node

PersistentAssignment dataclass

PersistentAssignment(
    target: Node, value: Node, *, span: Span = (0, 0)
)

Bases: Node

{target} <- value: a computed result for a cell or variable.

target is a Selection ({tT, r0010, c0010}) or a Parameter ({vX}, also written {v_X}).

Attributes:

target instance-attribute

target: Node

value instance-attribute

value: Node

Program dataclass

Program(
    statements: tuple[Node, ...], *, span: Span = (0, 0)
)

Bases: Node

Statements separated by ;; the last one gives the result.

parse_program returns one; parse returns a single expression instead.

Attributes:

statements instance-attribute

statements: tuple[Node, ...]

children

children(node: Node) -> tuple[Node, ...]

Return a node's direct child expressions in source order.

A selection's or with context's default and a typed parameter's default are children too, so walk reaches every literal.

Returns:

  • tuple[Node, ...] –

    tuple[Node, ...]: Child nodes; literals and other leaves have none.

Examples:

>>> from dpmxl import parse
>>> [type(child).__name__ for child in children(parse("{tT} + 1"))]
['Selection', 'Literal_']

walk

walk(node: Node) -> list[Node]

Return the node and all descendants in pre-order.

Returns:

  • list[Node] –

    list[Node]: Every node of the tree.

Examples:

>>> from dpmxl import parse
>>> expression = "with {tT, default: 0}: {r0010} > {r0020} * 1.1"
>>> [type(node).__name__ for node in walk(parse(expression))]
['With', 'Literal_', 'Binary', 'Selection', 'Binary', 'Selection', 'Literal_']
>>> [
...     expression[slice(*node.span)]
...     for node in walk(parse(expression))
...     if type(node).__name__ == "Selection"
... ]
['{r0010}', '{r0020}']