sexp
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.
Best use case
sexp is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
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.
Teams using sexp should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/sexp/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How sexp Compares
| Feature / Agent | sexp | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
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.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# Sexp Module
The sexp subsystem provides:
- a fast S-expression tokenizer/parser/pretty-printer in Zig, and
- typed Zig models for KiCad formats (PCB, footprint, netlist, symbol, schematic, fp_lib_table),
exposed to Python via the `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)
## Quick Start
```python
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)
```
## Relevant Files
- Zig core:
- `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)
- Python extension entrypoint:
- `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)
- Generated Python stubs (what users “see”):
- `src/faebryk/core/zig/gen/sexp/*.pyi`
- Convenience wrapper used throughout the codebase:
- `src/faebryk/libs/kicad/fileformats.py` (namespaces modules + caching + `loads/dumps`)
## Dependants (Call Sites)
- `src/faebryk/libs/kicad/fileformats.py` (primary integration layer)
- KiCad exporters and layout sync:
- `src/faebryk/exporters/pcb/kicad/*`
- `src/faebryk/exporters/pcb/layout/layout_sync.py`
- KiCad plugin workflow: `src/atopile/kicad_plugin/*`
## How to Work With / Develop / Test
### Core Concepts
- **Two-level model**:
- raw `SExp` parsing/formatting (`tokenizer.zig`, `ast.zig`)
- typed KiCad decoding/encoding (`structure.zig` + `sexp/kicad/*.zig`)
- **Python API shape**: the extension exposes per-format modules (e.g. `pcb`, `netlist`) with:
- module-level `loads(data: str) -> File`
- module-level `dumps(file: File) -> str`
- `File.free(...)` for releasing Zig-owned allocations
- **Convenience wrapper**: `faebryk.libs.kicad.fileformats.kicad` wraps these modules and provides `kicad.loads(...)`/`kicad.dumps(...)`.
### Development Workflow
1) Modify Zig:
- parsing/formatting: `src/faebryk/core/zig/src/sexp/*`
- Python exposure: `src/faebryk/core/zig/src/python/sexp/sexp_py.zig`
2) Rebuild:
- `ato dev compile` (imports `faebryk.core.zig`)
3) If you changed the API:
- verify stubs under `src/faebryk/core/zig/gen/sexp/*.pyi` update accordingly
- adjust `src/faebryk/libs/kicad/fileformats.py` if needed
### Testing
- Best practical test is round-trip:
- load a known `.kicad_pcb` / `.kicad_sch`, dump it, and ensure KiCad accepts it (formatting-sensitive).
- Zig unit tests (where present):
- `zig test src/faebryk/core/zig/src/sexp/ast.zig`
- `zig test src/faebryk/core/zig/src/sexp/structure.zig`
## Best Practices
- Prefer `faebryk.libs.kicad.fileformats.kicad` unless you explicitly need the raw module API.
- Be mindful of **shared-object caching** in `kicad.loads(...)`: path-based loads are cached and returned by reference (mutations are shared).
## Memory & Lifetime Invariants (critical)
The Python bindings duplicate the input S-expression string into a persistent allocator because parsed structs contain pointers into the input buffer.
Implications:
- Repeated `loads(...)` of large files can grow memory if you never call `free(...)` on the returned `*File`.
- The convenience wrapper currently caches loaded objects by path; do not `free(...)` cached objects unless you also invalidate the cache.Related Skills
SEXP Benchmark Strategy
## Goal
lsp
How the atopile Language Server works (pygls), how it builds per-document graphs for completion/hover/defs, and the invariants for keeping it fast and crash-proof.
solver
How the Faebryk parameter solver works (Sets/Literals, Parameters, Expressions), the core invariants enforced during mutation, and practical workflows for debugging and extending the solver. Use when implementing or modifying constraint solving, parameter bounds, or debugging expression simplification.
pyzig
How the Zig↔Python binding layer works (pyzig), including build-on-import, wrapper generation patterns, ownership rules, and where to add new exported APIs. Use when adding Zig-Python bindings, modifying native extensions, or debugging C-API interactions.
planning
Spec-driven planning for complex design tasks: when to plan, how to write specs as .ato files, and how to verify against requirements.
Package Agent
You are a package specialist.
library
How the Faebryk component library is structured, how `_F.py` is generated, and the conventions/invariants for adding new library modules. Use when adding or modifying library components, traits, or module definitions.
graph
How the Zig-backed instance graph works (GraphView/NodeReference/EdgeReference), the real Python API surface, and the invariants around allocation, attributes, and cleanup. Use when working with low-level graph APIs, memory management, or building systems that traverse the instance graph.
frontend
Frontend standards for atopile extension webviews: architecture, contracts, design system, and testing workflow.
faebryk
How Faebryk's TypeGraph works (GraphView + Zig edges), how to traverse/resolve references, and how FabLL types/traits map onto edge types. Use when working with TypeGraph traversal, edge types, or building type-aware queries.
fabll
How FabLL (faebryk.core.node) maps Python node/trait declarations into the TypeGraph + instance graph, including field/trait invariants and instantiation patterns. Use when defining new components or traits, working with the Node API, or understanding type registration.
domain-layer
Instructions for electronics-specific logic and build processes: netlists, PCBs, build steps, and exporters. Use when implementing or modifying build steps, exporters, PCB generation, or BOM/netlist output.