Ryan Malloy 3bf4f6bed2 Implement Specctra DSN parser with typed board model
Ports the read path of FreeRouting's io/specctra/parser package to a
Java-free Python implementation:

- tokenizer: S-expression lexer mirroring SpecctraFileDescription.flex
  (comments, quoted strings, the string_quote IGNORE_QUOTE directive,
  case-insensitive keywords, and hash-prefixed names)
- sexp: nested S-expression tree builder
- shapes: rect/circle/polygon/path plus area-with-holes scopes
- model: typed dataclasses for layers, padstacks, images, placements,
  nets, net classes, rules, keepouts
- reader: recursive-descent scope readers producing a DsnBoard, one
  _read_* function per FreeRouting read_scope method

46 pytest cases cover the tokenizer, tree, shapes, and end-to-end
parsing against hand-crafted fixtures modeled on FreeRouting's own
test DSN files. Parses 90 of 91 upstream fixtures (the one failure is
a binary OLE file, not text DSN).
2026-07-11 15:21:18 -06:00

20 lines
1.1 KiB
Markdown

# DSN test fixtures
Hand-crafted minimal Specctra DSN files, written to exercise the parser. They
are modeled on the real fixtures shipped in the upstream FreeRouting repo
(`fixtures/empty_board.dsn` and `fixtures/SMD-routing-issue-demo.dsn`) so the
grammar and formatting match what `kicad-cli pcb export specctra-dsn` and
FreeRouting itself produce, but they were typed fresh here (no upstream file is
copied into this tree).
- `empty_board.dsn` — the smallest valid board: parser, resolution, unit, and a
structure with two signal layers and a `pcb` boundary path. Modeled on
FreeRouting `fixtures/empty_board.dsn`.
- `smd_demo.dsn` — a fuller board exercising every scope the parser handles:
quoted board/padstack/image names, `via`, default `rule` (width + clearance
with a `(type smd_smd)` pair), a `keepout`, padstacks (rect and circle pads),
images with pins, placement with `front`/rotation, nets with `Comp-Pin`
references, and a `class` with `circuit`/`use_via` and a `rule`. Modeled on
FreeRouting `fixtures/SMD-routing-issue-demo.dsn`.
</content>