Parser

Parsing of QVR domain-specific language syntax.

parser

Parser for the quivers DSL.

The lexer/parser pipeline is delegated to panproto via the qvr tree-sitter grammar registered in panproto-grammars-all. The public parse entry point consumes .qvr source bytes and returns a Module of dataclass AST nodes.

This package's submodules group the walker logic by topic:

  • ._registry for the panproto registry singleton, ParseError, and the _Tree view that every walker reads from.
  • ._helpers for the low-level helpers _required_text, _walk_options, and _walk_return_pattern.
  • .expressions for type / space / morphism-expression / let-arith walkers.
  • .axes for axis-role and morphism-prior walkers.
  • .program_steps for program-block step walkers.
  • .statements for the top-level _walk_statement dispatcher and every per-declaration walker (object, morphism, kernel, deduction, contraction, signature, encoder, decoder, loss, ...).
  • .core for the public parse / parse_file entry points and _attach_docs.

Every public name is re-exported here so from quivers.dsl.parser import X keeps working unchanged.

ParseError

Bases: Exception

Raised when the .qvr source fails to parse or wrap into AST nodes.

parse

parse(source: str | bytes, file_path: str = '<source>') -> Module

Parse .qvr source bytes into a Module.

Source code in src/quivers/dsl/parser/core.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def parse(source: str | bytes, file_path: str = "<source>") -> Module:
    """Parse `.qvr` source bytes into a `Module`."""
    if isinstance(source, str):
        source_bytes = source.encode("utf-8")
    else:
        source_bytes = source

    schema = _registry().parse_with_protocol("qvr", source_bytes, file_path)
    tree = _Tree(schema, source_bytes)

    root_id = next(
        (v.id for v in schema.vertices if v.kind == "source_file"),
        None,
    )
    if root_id is None:
        raise ParseError(f"panproto schema has no source_file vertex for {file_path}")

    statements: list[Statement] = []
    pending_docs: list[str] = []
    for child in tree.positional(root_id):
        ckind = tree.kind(child)
        if ckind == "line_comment":
            # plain `# ...` comments are dropped at parse time
            continue
        if ckind == "doc_comment":
            # `## ...` doc comments are accumulated; attached to the
            # next statement that carries a docs field.
            text = tree.text(child)
            stripped = text[2:].lstrip() if text.startswith("##") else text
            pending_docs.append(stripped.rstrip())
            continue
        result = _walk_statement(tree, child)
        results = result if isinstance(result, list) else [result]
        if pending_docs:
            docs = tuple(pending_docs)
            results = [_attach_docs(s, docs) for s in results]
            pending_docs = []
        statements.extend(results)
    return Module(statements=tuple(statements))

parse_file

parse_file(path: str | Path) -> Module

Parse a .qvr file at path.

Source code in src/quivers/dsl/parser/core.py
74
75
76
77
def parse_file(path: str | Path) -> Module:
    """Parse a `.qvr` file at `path`."""
    p = Path(path)
    return parse(p.read_bytes(), str(p))