How the Zig S-expression engine and typed KiCad models work, how they are exposed to Python (pyzig_sexp), and the invariants around parsing, formatting, and freeing. Use when working with KiCad file parsing, S-expression generation, or layout sync.
cd ~/.claude/skills
git clone https://github.com/atopile/atopile.git atopile mkdir -p ~/.claude/skills/sexp
curl -fsSL https://raw.githubusercontent.com/atopile/atopile/HEAD/.claude/skills/sexp/SKILL.md \
-o ~/.claude/skills/sexp/SKILL.md The sexp subsystem provides:
pyzig_sexp extension module.Source-of-truth docs and code:
src/faebryk/core/zig/README.md (high-level overview)src/faebryk/core/zig/src/sexp/* (tokenizer/AST/structure)src/faebryk/core/zig/src/python/sexp/sexp_py.zig (Python API + critical memory rules)from pathlib import Path
from faebryk.libs.kicad.fileformats import kicad
pcb = kicad.loads(kicad.pcb.PcbFile, Path("board.kicad_pcb"))
_text = kicad.dumps(pcb)
src/faebryk/core/zig/src/sexp/tokenizer.zig (tokenization + line/column tracking)src/faebryk/core/zig/src/sexp/ast.zig (SExp tree + KiCad pretty formatting)src/faebryk/core/zig/src/sexp/structure.zig (decode/encode + error context)src/faebryk/core/zig/src/sexp/kicad/* (typed KiCad models)src/faebryk/core/zig/src/python/sexp/init.zig (exports PyInit_pyzig_sexp)src/faebryk/core/zig/src/python/sexp/sexp_py.zig (module + type binding generation)src/faebryk/core/zig/gen/sexp/*.pyisrc/faebryk/libs/kicad/fileformats.py (namespaces modules + caching + loads/dumps)src/faebryk/libs/kicad/fileformats.py (primary integration layer)src/faebryk/exporters/pcb/kicad/*src/faebryk/exporters/pcb/layout/layout_sync.pysrc/atopile/kicad_plugin/*SExp parsing/formatting (tokenizer.zig, ast.zig)structure.zig + sexp/kicad/*.zig)pcb, netlist) with:
loads(data: str) -> Filedumps(file: File) -> strFile.free(...) for releasing Zig-owned allocationsfaebryk.libs.kicad.fileformats.kicad wraps these modules and provides kicad.loads(...)/kicad.dumps(...).src/faebryk/core/zig/src/sexp/*src/faebryk/core/zig/src/python/sexp/sexp_py.zigato dev compile (imports faebryk.core.zig)src/faebryk/core/zig/gen/sexp/*.pyi update accordinglysrc/faebryk/libs/kicad/fileformats.py if needed.kicad_pcb / .kicad_sch, dump it, and ensure KiCad accepts it (formatting-sensitive).zig test src/faebryk/core/zig/src/sexp/ast.zigzig test src/faebryk/core/zig/src/sexp/structure.zigfaebryk.libs.kicad.fileformats.kicad unless you explicitly need the raw module API.kicad.loads(...): path-based loads are cached and returned by reference (mutations are shared).The Python bindings duplicate the input S-expression string into a persistent allocator because parsed structs contain pointers into the input buffer.
Implications:
loads(...) of large files can grow memory if you never call free(...) on the returned *File.free(...) cached objects unless you also invalidate the cache.Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Use when executing implementation plans with independent tasks in the current session
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when implementing any feature or bugfix, before writing implementation code
Use when creating new skills, editing existing skills, or verifying skills work before deployment