Skip to main content

sattline-parser

Standalone parser, AST, and transformer for ABB SattLine.

This package owns the Lark grammar, the strict single-file syntax behavior, the AST models, and the SLTransformer, all in one self-contained, installable package.

Features

  • Lark LALR parser for SattLine sources (grammar in grammar/sattline.lark)
  • Parses plain text and files (.s, .g, .l, .x, .y, .z and any other extension; the parser is content-based, not extension-based)
  • Strict, no-silent-fallback parsing: unknown compressed markers and unexpected transformer structures raise errors instead of being silently rewritten or dropped
  • Structural comments ((* ... *), nested) preserved as role-tagged AST nodes
  • Automatic compressed-source decoding with full source provenance: AST spans and error locations always refer to the original source (SourceSpan carries character offsets plus line/column)
  • Lexically aware decoding: string literals and comments are protected, so syntax-looking text inside them is never rewritten
  • Error reporting with line/column locations in the original source (describe_parse_error)
  • AST models in sattline_parser.models
  • SLTransformer tree transformer in sattline_parser.transformer
  • Standalone fuzz harness with hard subprocess timeouts and corpus regression
  • Zero runtime dependencies beyond lark and regex

Install

pip install sattline-parser

Requires Python 3.13+.

Usage

Parse a source file

from pathlib import Path
from sattline_parser import parse_source_file

basepicture = parse_source_file(Path("program.s"))

Parse source text

from sattline_parser import parse_source_text

source = open("program.x", encoding="utf-8").read()
basepicture = parse_source_text(source)

parse_source_file and parse_source_text both return a BasePicture (the module-level model) with the full AST attached.

Choosing an entry point

parse_source_file is for when you have a path on disk. It handles the file I/O for you: it reads the file with an encoding fallback (utf-8, then cp1252, then latin-1) and passes the path along so error messages can name the source file.

parse_source_text is for when you already hold the source as a string: a snippet, an editor buffer, a response from an API, or content read by your own code. The two are interchangeable in behavior; parse_source_file(path) is equivalent to parse_source_text(path.read_text(...)) plus the encoding fallback and path-aware error reporting. Start with parse_source_file when you have a path, parse_source_text otherwise.

Both entry points handle cleanup automatically, so you do not need to pre-process the source:

  • Comments are parsed structurally ((* ... *), including nested ones) and preserved on the AST as CodeComment nodes.
  • Compressed sources are detected and decoded automatically.

The exposed helpers below exist for the rare case where you are building tooling that needs the intermediate stages (for example, to tokenize, diff, or re-emit sources). For ordinary parsing you can ignore them.

For power users: handle compressed sources

from sattline_parser import is_compressed, preprocess_sl_text, preprocess_source

if is_compressed(source):
    decoded, mapping = preprocess_sl_text(source)
    # ... or, when you need original-source provenance:
    doc = preprocess_source(source)
    doc.original_text, doc.normalized_text  # preprocessed vs original

is_compressed answers whether the text uses the compressed encoding. preprocess_sl_text decodes it, returning the decoded text plus the marker substitution table (the seed mapping that was applied). That mapping is not a position map — use preprocess_source for provenance. preprocess_source returns a SourceDocument carrying the original text, the decoded text, and a per-character map from decoded offsets back to the original source; parse_source_text uses it internally so every AST span and diagnostic points into the original source.

Decoding is lexically aware: string literals and (* ... *) comments are protected before any transformation runs, so #markers and other syntax-looking text inside them are never rewritten. An unknown compressed marker raises PreprocessError instead of being silently replaced with whitespace.

Since both parse entry points already detect and decode compressed sources, these helpers are only needed by tooling that must decode without parsing (for example, saving a plain-text copy) or by the fuzz harness that drives the decoder with adversarial inputs.

Report errors with source locations

from sattline_parser import create_parser, describe_parse_error, parse_source_text

parser = create_parser()
try:
    parse_source_text(source, parser=parser)
except Exception as exc:
    details = describe_parse_error(exc, source)
    print(f"{details.line}:{details.column} {details.message}")

What is the AST good for?

parse_source_file and parse_source_text return a BasePicture, a tree of Python objects that mirrors the structure of the program. Once you have it, you can inspect and walk the program as data instead of as text.

For readers new to ASTs (abstract syntax trees): the parser does not give you the flat file back; it gives you structured objects. basepicture.program_name is the program name, basepicture.moduletype_defs is the list of type definitions, basepicture.submodules is the tree of nested modules, and so on. You can read fields, iterate lists, and check conditions directly in Python. For a small program this prints:

from pathlib import Path
from sattline_parser import parse_source_file

basepicture = parse_source_file(Path("program.s"))
print(basepicture.program_name)
print([submodule.header.name for submodule in basepicture.submodules])
print(len(basepicture.submodules), "submodules")
program
['Controller1', 'Controller2']
2 submodules

Think of the AST as a structured, machine-readable view of the program, that tools (a linter, a refactorer, an editor, a report generator) can walk without re-parsing the text. Since an AST is just nested objects, answering questions about the program becomes ordinary Python.

Development

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q                                   # full configured suite
ruff check src tests scripts
ruff format --check src tests scripts
pyright src tests
bandit -q -r src -x tests -c pyproject.toml
pip-audit
python scripts/check_branch_coverage.py     # line >= 100%, branch >= 93%

Line coverage is enforced at 100%; branch coverage is measured and gated by scripts/check_branch_coverage.py (both enforced in CI). The full test suite is run by CI via pytest (the project's configured testpaths), including the packaging tests in tests/test_packaging.py that live outside tests/parser.

Fuzzing runs in three tiers, each enforced separately:

  • Deterministic corpus regression — every fixture in tests/fixtures/corpus/ must parse or produce an expected invalid-input error; runs in normal CI.
  • PR fuzz smoke — a small number of random inputs must not crash the parser; runs in normal CI.
  • Continuous fuzzing — long-running, coverage-guided fuzzing via ClusterFuzzLite (.github/workflows/fuzz.yml).

The fuzz harness enforces timeouts with a worker subprocess (killed on timeout, reused across inputs) and classifies expected invalid-input errors (UnexpectedInput, PreprocessError) separately from internal bugs — a ValueError/TypeError from the transformer is treated as a crash, not as ordinary invalid input.

Dependencies

The declared runtime dependencies are lark[interegular]>=1.3.1,<2 and regex. CI tests both the locked minimum (lark 1.3.1) and the latest lark that satisfies the range, so the supported range is actually exercised. Installs use uv.lock for reproducibility.

Project layout

  • src/sattline_parser/grammar/ : Lark grammar and constants
  • src/sattline_parser/models/ : AST models
  • src/sattline_parser/transformer/ : transformer mixins and SLTransformer
  • src/sattline_parser/api.py : public entry points
  • src/sattline_parser/fuzz_harness.py : standalone fuzzing

License

MIT. Copyright (c) 2025 Søren H. Johansen.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sattline_parser-2026.8.4.tar.gz (78.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sattline_parser-2026.8.4-py3-none-any.whl (87.2 kB view details)

Uploaded Python 3

File details

Details for the file sattline_parser-2026.8.4.tar.gz.

File metadata

  • Download URL: sattline_parser-2026.8.4.tar.gz
  • Upload date:
  • Size: 78.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for sattline_parser-2026.8.4.tar.gz
Algorithm Hash digest
SHA256 c794dc047e410e1cdfc3c94a9eda33903e661873113d9dbb4cf5264af2a6396c
MD5 9a0267d6984b04ab58bbbc8de7d7b1de
BLAKE2b-256 937d09a6269e91d2f62b120b16650d1bef6e0f368db4c29e090a30afe024cd6c

See more details on using hashes here.

Provenance

The following attestation bundles were made for sattline_parser-2026.8.4.tar.gz:

Publisher: publish.yml on SorenHJohansen/sattline-parser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sattline_parser-2026.8.4-py3-none-any.whl.

File metadata

File hashes

Hashes for sattline_parser-2026.8.4-py3-none-any.whl
Algorithm Hash digest
SHA256 e62216a7779627bfa8dc1fc414769fa7da916b3e947e017e31f5fa3724e37493
MD5 062f0e4bbc475a4b4819625da6517c59
BLAKE2b-256 b18475aa3ef09e3e8cce374a6c581a45e3ca2500a2cbd7d044bcd93bdcfb2c78

See more details on using hashes here.

Provenance

The following attestation bundles were made for sattline_parser-2026.8.4-py3-none-any.whl:

Publisher: publish.yml on SorenHJohansen/sattline-parser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

2026.8.4 This release

2 files

2026.8.3

2 files

2026.8.1

2 files

2026.8

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page