Introduction
Last verified against: v0.3 — multi-dialect setting (default HSPICE), expanded HSPICE reference corpus, bare .DATA value rows parse without syntax errors, dialect-aware hover, generated mdBook dialect catalog; VS Code commands register before LSP start; Linux glibc 2.31+ binaries; Marketplace publish on each push to main
spice-lsp is a language server and formatter for SPICE circuit simulation netlists. The end goal is a VS Code extension that provides real-time diagnostics, dialect-aware documentation on hover, navigation, formatting, and connectivity warnings while editing .cir, .sp, .spf, and related files.
This book is generated with mdBook from the docs/ directory.
How to read this book
| Stage | Chapters |
|---|---|
| Setup and ship MVP | Getting Started → Principles → MVP Guide → Demo and Testing |
| Understand the system | Architecture → LSP Features |
| Long-term direction | Dialect Reference and Net Semantics → Dialect reference catalog → Formatter → Limitations |
| VS Code | VS Code Integration |
Quick setup lives in the repository README.md.
Roadmap at a glance
MVP Syntax diagnostics in VS Code
v0.2 Outline, go to definition, duplicate/undefined warnings
v0.3 Completion, file-local hover
v0.4 Formatter, dialect setting
v0.5 Curated dialect reference (hover) + dangling-node / floating-net warnings
v0.5 is where you maintain reference/<dialect>/ documentation and the LSP begins SPICE-specific semantic assistance beyond syntax. Not part of MVP.
Build the book locally
pixi run mdbook-build # render static site to docs/book/
pixi run mdbook-serve # preview at http://127.0.0.1:3000
Published site
Pushes to main that touch docs/ deploy the book to the gh-pages branch via .github/workflows/deploy-docs.yml.
https://amirhosseindavoody.github.io/spice-lsp/
Getting Started
This chapter covers environment setup and the shortest path from clone to a running language server.
Prerequisites
| Tool | Purpose |
|---|---|
| pixi | Manages Rust, Node (for the VS Code extension), and build tasks |
| Git | Clone and contribute |
You do not need a system-wide Rust install. Pixi provides the toolchain pinned in pixi.toml.
Clone and install
git clone https://github.com/amirhosseindavoody/spice-lsp.git
cd spice-lsp
pixi install
pixi install creates a reproducible environment with the Rust compiler and (once added) Node.js, mdBook, and other dev tools.
Verify the environment
pixi run rustc --version
pixi run cargo --version
Both commands should succeed and report Rust ≥ 1.96.
Build (after MVP scaffolding lands)
Once the Cargo workspace exists, build and test through pixi tasks:
pixi run build
pixi run test
Run the language server directly (it communicates over stdio — it will appear to hang; that is normal):
pixi run spice-lsp
Press Ctrl+C to stop.
Open sample netlists
Create or copy a minimal netlist for manual testing:
* demo.cir — Ngspice-style
.title Simple RC
R1 in out 1k
C1 out 0 1u
V1 in 0 DC 1
.tran 1u 1m
.end
Save as demo.cir in the repo root or under test-data/.
Editor integration (MVP)
VS Code (primary target)
- Build the LSP binary:
pixi run build - Open the extension folder:
editors/vscode - Install JS dependencies:
npm install - Press F5 to launch an Extension Development Host with the SPICE extension loaded
- Open
demo.cirand confirm diagnostics appear
Full extension setup: VS Code integration.
Other editors
Any editor with generic LSP client support can point at the spice-lsp binary:
| Editor | Configuration |
|---|---|
| Neovim | lspconfig custom server block with cmd = { "spice-lsp" } |
| Helix | [language-server.spice-lsp] in languages.toml |
| Zed | Extension or lsp settings (once published) |
File extensions to associate: .cir, .sp, .spf, .net, .ckt (dialect-dependent).
Recommended first contribution path
If you are new to the repo, follow this order:
- Read Principles — know what is in and out of scope for MVP
- Follow MVP guide — implement or review each milestone
- Use Demo and testing — verify each layer before adding features
- Read Architecture — understand where new code belongs
- Skim Dialect reference and net semantics — long-term hover and connectivity goals (not MVP)
Next steps
- MVP guide — build the smallest demoable server
- Architecture — crate layout and data flow
- Build — pixi tasks and CI
Principles
Goals, non-goals, and UX values for spice-lsp. Use this page to decide whether work belongs in MVP or a later phase.
North-star experience
A developer editing a netlist in VS Code should get:
- Immediate syntax feedback (MVP)
- Jump to definitions and a useful outline (v0.2)
- Completion and quick in-file hover (v0.3)
- Consistent formatting and dialect selection (v0.4)
- Authoritative dialect documentation on hover and warnings on bad connectivity (v0.5)
Steps 1–4 build the pipeline; step 5 is where the tool becomes a SPICE-aware assistant rather than a generic syntax checker. Details: Dialect reference and net semantics.
Goals
- Fast feedback while editing — Diagnostics feel instant on typical netlists (< 5k lines). Tree-sitter incremental parsing is the foundation.
- Works offline — Single static binary; no cloud services; no simulator required for IDE features.
- Dialect-aware, corpus-driven docs — Ngspice, LTspice, and HSPICE differ. Long term, hover and completion documentation come from a curated reference library you maintain per dialect, not hard-coded strings scattered in Rust.
- Catch connectivity mistakes before simulation — Flag dangling nodes and floating nets as warnings when analysis is confident enough (v0.5).
- Editor-agnostic core — All language logic lives in the LSP binary. VS Code is the first client, not the only one.
- Testable at every layer — Parser fixtures, reference schema tests, hover snapshots, and LSP integration tests in CI.
Non-goals
| Non-goal | Why |
|---|---|
| Running SPICE simulations | Use Ngspice/LTspice externally |
| Schematic capture | Netlist text only |
| Auto-generating reference from PDF manuals | You author reference/ deliberately; quality over coverage on day one |
| Full ERC/DRC | Floating-net checks are heuristic helpers, not sign-off tools |
| Replacing simulator errors | We front-load syntax and common semantic mistakes |
UX values
- Actionable squiggles — Clear message, stable range, stable diagnostic code (e.g.
spice/floating-net). - Graceful partial files — Incomplete subcircuits during editing must not block analysis of the rest of the buffer.
- Respect line continuations — The
+character is first-class in the grammar; HSPICE.DATAvalue rows may also continue without+. - Documentation you trust — Reference hover reads like a concise manual entry: syntax, units, examples. Missing entries show nothing rather than wrong text.
- Warn, don’t nag — Connectivity warnings are severity
Warning, configurable, and scoped to reduce false positives on intentional open nodes. - Low configuration — Sensible defaults; dialect and diagnostics toggles via settings when needed.
MVP scope boundary
MVP proves the editor pipeline only:
netlist buffer → parse → syntax diagnostics → LSP → VS Code squiggles
In MVP: syntax diagnostics, text sync, publishDiagnostics, VS Code extension.
Not in MVP (including v0.5 goals):
| Deferred | Target phase |
|---|---|
| Dialect reference hover | v0.5 |
| Floating / dangling node analysis | v0.5 |
| Completion, file-local hover | v0.3 |
| Navigation, outline | v0.2 |
| Formatter | v0.4 |
Ship MVP first, then follow the phase order in Architecture.
Success criteria for MVP
pixi run testpasses parser and LSP integration tests- Invalid netlist in the Extension Development Host shows a syntax diagnostic
- Fixing the error clears the diagnostic without restarting the editor
- A contributor can follow MVP guide and reproduce the demo
Success criteria for v0.5 (future): hover on .tran shows Ngspice reference text; test-data/semantic/dangling-node.cir produces spice/dangling-node.
Architecture
System layout for spice-lsp: crates, data flow, and how each release phase adds capability on top of the last.
Story in four layers
Every feature belongs to one of these layers. MVP ships layer 1 only; later phases stack upward.
| Layer | Responsibility | Ships in |
|---|---|---|
| 1. Parse | Tree-sitter CST, syntax diagnostics | MVP |
| 2. Index | Symbols, scopes, cross-references | v0.2 |
| 3. Assist | Completion, basic hover from the file | v0.3 |
| 4. Deep semantics | Dialect reference docs, net connectivity, formatter | v0.4–v0.5 |
Layer 4 is documented in detail in Dialect reference and net semantics.
High-level overview
┌─────────────────────────────────────────────────────────────────┐
│ Editor clients │
│ VS Code extension │ Neovim │ Helix │ other LSP clients │
└────────────┬────────────────────────────────────────────────────┘
│ JSON-RPC 2.0 over stdio (LSP)
▼
┌─────────────────────────────────────────────────────────────────┐
│ crates/spice-lsp (binary: spice-lsp) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ tower-lsp Backend │ │
│ │ • text sync, publishDiagnostics │ │
│ │ • (v0.2) symbols, definition, references │ │
│ │ • (v0.3) completion, hover (file-local) │ │
│ │ • (v0.4) formatting │ │
│ │ • (v0.5) hover from reference corpus │ │
│ └────────────────────────┬─────────────────────────────────┘ │
└───────────────────────────┼─────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌───────────────┐ ┌──────────────────┐
│ spice-parser │ │ spice-reference│ │ tree-sitter-spice│
│ parse, index, │ │ dialect docs │ │ grammar, queries │
│ diagnose, format│ │ (v0.5) │ │ │
└─────────────────┘ └───────────────┘ └──────────────────┘
Crate responsibilities
| Crate / directory | Role | First shipped |
|---|---|---|
crates/spice-lsp | LSP server, JSON-RPC, document store | MVP |
crates/spice-parser | Parsing, symbol index, diagnostics, format | MVP (parse only) |
crates/spice-reference | Load and query dialect reference entries | v0.3 |
tree-sitter-spice/ | Grammar and query files | MVP |
reference/ | Curated JSON (or YAML) per dialect — authored over time | v0.5 |
editors/vscode/ | VS Code extension client | MVP |
test-data/ | Fixtures for syntax, semantics, hover snapshots | MVP |
LSP server lifecycle
- Client connects via stdio; sends
initializewith client capabilities and dialect option. - Server responds with capabilities for the current phase (MVP: incremental sync only).
- Document open/change updates an in-memory map of open buffers.
- On each change (debounced ~150 ms):
- Re-parse with Tree-sitter
- Run diagnostic passes for the enabled phase
- Send
textDocument/publishDiagnosticswith the document version
- Hover / completion requests (v0.3+) resolve against the CST; v0.5+ also queries
spice-reference. Navigation requests re-analyze on demand so the symbol index stays current even when diagnostics are still debouncing. - Shutdown exits cleanly.
Document model
#![allow(unused)]
fn main() {
struct Document {
uri: Url,
text: String,
tree: tree_sitter::Tree,
version: i32,
// v0.2+
symbols: SymbolTable,
// v0.5+
net_graph: Option<NetGraph>,
}
}
Parser and analysis pipeline
Phase 1 — Syntax (MVP)
- Parse buffer → CST
- Collect ERROR / MISSING nodes and hand-written checks (e.g. unclosed
.subckt) - Map to LSP
Diagnostic(Error)
Phase 2 — Symbol index (v0.2)
Walk the CST to build:
- Subcircuit and model definitions
- Component instances and
.parambindings
Enables navigation, duplicate-name warnings, and undefined reference checks.
Phase 3 — Assist (v0.3)
Use the symbol index for completion and file-local hover (subcircuit pin lists, .model parameters defined in the same buffer).
Phase 4 — Format and dialect (v0.4)
Formatter engine reads CST → TextEdit list. Dialect setting selects grammar quirks and reference namespace.
Phase 5 — Reference docs and connectivity (v0.5)
Two parallel analyzers — see Dialect reference and net semantics:
Reference lookup: Map cursor token → reference/<dialect>/… entry → markdown hover.
Net graph: Build terminal graph per scope → warn on dangling nodes and floating nets.
Instance lines ──► NetGraph ──► dangling / floating diagnostics
Cursor token ──► ReferenceIndex ──► rich hover markdown
Phased rollout
flowchart TD
MVP[MVP: syntax + VS Code]
V02[v0.2: symbols + navigation]
V03[v0.3: completion + file hover]
V04[v0.4: formatter + dialect setting]
V05[v0.5: reference corpus + net checks]
MVP --> V02 --> V03 --> V04 --> V05
| Phase | User-visible outcome |
|---|---|
| MVP | Syntax error squiggles in VS Code |
| v0.2 | Outline, go to definition, duplicate / undefined symbol warnings |
| v0.3 | Element and directive completion; hover on subcircuits and models in file |
| v0.4 | Format document; choose Ngspice / LTspice / HSPICE |
| v0.5 | Hover docs for directives and options from curated reference; warnings on dangling nodes and floating nets |
VS Code extension
Thin Node client: spawns spice-lsp, forwards LSP traffic, exposes dialect and diagnostic settings. No parsing in TypeScript.
See VS Code integration.
Performance targets
| Metric | Target |
|---|---|
| Parse + syntax diagnose (5k lines) | < 50 ms |
| Full semantic pass + net graph (50k lines) | < 100 ms |
| Reference hover lookup | < 1 ms (in-memory index) |
| Incremental edit | Re-parse changed regions only |
Related reading
- Dialect reference and net semantics — v0.5 deep dive
- LSP features — method-by-method status
- MVP guide — implementation order for layer 1
- Design (internal) — full requirements
LSP Features
LSP methods spice-lsp implements or plans to implement, organized by release phase. MVP ships only syntax sync and diagnostics; richer behavior accumulates in later versions.
Capability matrix
| LSP method / feature | MVP | v0.2 | v0.3 | v0.4 | v0.5 |
|---|---|---|---|---|---|
initialize / initialized | ✓ | ||||
shutdown / exit | ✓ | ||||
textDocument/didOpen / didChange / didClose | ✓ | ||||
textDocument/publishDiagnostics | ✓ | ✓ | syntax | ||
| Syntax diagnostics | ✓ | ||||
| Duplicate / undefined symbol diagnostics | ✓ | ||||
| Dangling node / floating net diagnostics | ✓ | ||||
textDocument/documentSymbol | ✓ | ||||
textDocument/definition / references | ✓ | ||||
textDocument/completion | ✓ | ||||
textDocument/hover (dialect reference) | ✓ | curated reference/ | |||
textDocument/hover (file-local) | ✓ | subcircuit pins, in-file models | |||
textDocument/formatting | ✓ | ||||
textDocument/didSave (re-lint) | ✓ |
Full specification of reference hover and net diagnostics: Dialect reference and net semantics.
MVP
Server capabilities
{
"capabilities": {
"textDocumentSync": {
"openClose": true,
"change": 2,
"save": false
}
},
"serverInfo": { "name": "spice-lsp", "version": "0.1.0" }
}
change: 2 is Incremental sync — the client sends edit ranges, not the full buffer every keystroke.
Diagnostics arrive via server-initiated textDocument/publishDiagnostics.
Syntax diagnostics
| Source | Example | Severity |
|---|---|---|
Unclosed .subckt | missing .ends for subcircuit X | Error |
| Parse ERROR node | unexpected token | Error |
| Missing CST child | expected node list | Error |
v0.2 — Symbols and light semantics
Server capabilities
Adds outline and navigation on top of MVP sync:
{
"capabilities": {
"textDocumentSync": { "openClose": true, "change": 2 },
"documentSymbolProvider": true,
"definitionProvider": true,
"referencesProvider": true
}
}
Document symbols for outline / breadcrumbs:
| Symbol kind | SPICE construct |
|---|---|
Namespace | .subckt block |
Class | .model |
Variable | .param |
Field | Instance line |
Navigation: go to definition and find references for subcircuits, models, and parameters. textDocument/references honors context.includeDeclaration (omit definition sites when the client passes false).
Diagnostics:
| Code | Example | Severity |
|---|---|---|
spice/duplicate-name | duplicate component name 'R1' | Warning |
spice/unknown-model | model 'nfet' not defined | Warning |
Diagnostics from didChange are debounced (~150 ms) so rapid typing does not re-analyze on every keystroke. didOpen publishes immediately. Navigation handlers refresh the in-memory index on demand.
v0.3 — Completion and file-local hover
Completion contexts: element letters, directive names, in-scope model and subcircuit names, snippet templates for .tran / .subckt.
Hover (file-local only): subcircuit port order, parameters from .model / .subckt in the open buffer. No curated reference yet — that is v0.5.
Dialect selection (issue #16)
spiceLsp.dialect is hspice | ngspice | ltspice (default hspice). The VS Code command SPICE LSP: Set Dialect… and a status-bar item change it. The same dialect selects the reference corpus for hover and (later) completion docs.
Design: Multi-dialect support.
Hover
textDocument/hover resolves in order:
- Curated entry from
reference/for the active dialect (_sharedfallback) - File-local detail for
.subckt/.model/.paramsymbols - No hover
v0.4 — Formatting
Registers documentFormattingProvider. Formatter profiles may follow the active dialect later.
See Formatter.
v0.5 — Dialect reference hover and net connectivity
Reference-powered hover
When the cursor is on a directive, option, element keyword, or documented expression form, the server loads the matching entry from reference/<dialect>/ and returns markdown:
- Summary and syntax
- Parameter table with units
- Examples and
seeAlsolinks
You maintain this corpus over time; the LSP only indexes and renders it. Authoring guide: Dialect reference and net semantics.
Connectivity diagnostics
| Code | Example | Severity |
|---|---|---|
spice/dangling-node | node 'bias' is connected to only one device terminal | Warning |
spice/floating-net | net 'internal' has no DC path to ground | Warning |
Published alongside syntax diagnostics in publishDiagnostics. Configurable via spiceLsp.diagnostics.* settings.
Client configuration
| Setting | Type | Default | Available |
|---|---|---|---|
spiceLsp.dialect | string | "hspice" | dialect switch (see Multi-dialect design) |
spiceLsp.diagnostics.danglingNodes | boolean | true | v0.5+ |
spiceLsp.diagnostics.floatingNets | boolean | true | v0.5+ |
spiceLsp.groundNodes | string[] | ["0","gnd","GND"] | v0.5+ |
spiceLsp.trace.server | string | "off" | MVP+ |
Testing
Each phase adds integration tests for newly advertised capabilities. MVP priorities:
initializereturns expected capabilities- Open invalid document → diagnostics notification
- Edit → updated diagnostics
v0.2 adds:
-
documentSymbolreturns hierarchical outline for.subcktblocks -
definitionon subcircuit reference jumps to.subcktdefinition -
referenceson subcircuit definition lists definition + usages;includeDeclaration: falseomits the definition -
Semantic fixtures (
duplicate-instance.cir,unknown-subckt.cir) produce warning codes -
Rapid
didChangeevents coalesce into a single diagnostics publish after the debounce window -
(v0.5) Open semantic fixture → dangling / floating warnings; hover snapshot matches reference entry
See Demo and testing.
Formatter
Design for the SPICE netlist formatter. Not part of MVP — document the target behavior so parser and CST design stay compatible.
Goals
- Columnar alignment of instance name, nodes, model/value, and parameters
- Consistent handling of
+continuation lines - Normalized keyword and unit casing (configurable)
- Idempotent output:
format(format(x)) == format(x)
Input / output
| Input | Output |
|---|---|
LSP textDocument/formatting request | TextEdit[] covering full document |
LSP textDocument/rangeFormatting | TextEdit[] for selection (or expanded logical statement) |
CLI spice-lsp format --check file.cir | Exit 1 if not formatted; --write in place |
Formatting rules (draft)
Instance lines
Align columns within a contiguous block of instance lines:
* before
R1 in out 1k
C1 out 0 1u
X1 a b mycell
* after (aligned)
R1 in out 1k
C1 out 0 1u
X1 a b mycell
Column boundaries derive from the widest field in each column group.
Continuation lines
Standardize + continuations with fixed indent:
* before
M1 d g s b nfet W=10u L=0.18u
+ $nfin=2
* after
M1 d g s b nfet W=10u L=0.18u
+ $nfin=2
Directives
Keep dot-directives (.tran, .model, .subckt) on their own lines. Align parameters within a directive where sensible:
.model nfet nmos ( VTO=0.4 KP=200u )
Comments
- Preserve comment text; normalize spacing before inline
;/$comments - Do not reorder or remove comment lines
Architecture
The formatter lives in crates/spice-parser (or a sibling crates/spice-format if it grows large):
- Parse buffer to CST (reuse parser)
- Walk relevant nodes; compute column widths per alignment group
- Render each line to a string buffer
- Diff old vs new text → minimal
TextEditset (or full replacement for MVP formatter)
Formatting must not mutate the parse tree in place; generate text from a pretty-printer pass.
Configuration (v0.4+)
| Option | Values | Default |
|---|---|---|
indentWidth | 2, 4 | 2 |
keywordCase | upper, lower, preserve | upper |
alignColumns | true, false | true |
maxLineWidth | number | 120 (soft wrap with +) |
Plumb through LSP FormattingOptions (tabSize, insertSpaces) where compatible.
Testing
Golden-file tests in crates/spice-parser/tests/format/:
input.cir → format → compare to expected.cir
Include edge cases: empty file, comments only, deeply nested .subckt, long parameter lists.
Related
- Architecture — FormatterEngine placement
- LSP features — when formatting registers as a capability
Limitations
Known constraints and unsupported behavior. Updated as the parser and LSP mature.
Current (v0.2)
Shipped today:
- Rust crates (
spice-parser,spice-lsp), Tree-sitter grammar, and VS Code extension - Syntax diagnostics plus light semantic warnings (
spice/duplicate-name,spice/unknown-model) - Document outline, go to definition, and find references
- Debounced diagnostics on edit;
textDocument/referenceshonorsincludeDeclaration - Marketplace extension with bundled binaries, TextMate highlighting, and restart command
- File associations for
.cir,.sp,.spf,.net, and.ckt
Current limitations
| Limitation | Workaround |
|---|---|
| Ngspice-oriented parsing | Avoid LTspice/HSPICE-specific syntax until v0.4 |
| No connectivity analysis | Manual review; dangling/floating checks arrive in v0.5 |
| Single-file analysis | .include not followed |
| No formatter or completion | Manual alignment / typing until v0.3–v0.4 |
Comment toggle uses * only | ; and $ are highlighted as comments; VS Code allows one lineComment |
| No Windows arm64 bundled binary | Set spiceLsp.serverPath or put spice-lsp on PATH |
| Linux bundled binary needs glibc 2.31+ | Upgrade the host OS, or build spice-lsp locally and set spiceLsp.serverPath |
| Shared grammar for all dialects | Dialect-specific parse quirks land in later phases; hover/docs already switch |
Bare numeric lines outside .DATA | Prefer + continuations or keep value rows inside .DATA … .ENDDATA |
Post-MVP: dialect reference
The reference library under reference/ grows incrementally:
- Shared baseline covers common directives (
.subckt,.tran,.dc,.op,.ac, …) and elements (R,C,X) - HSPICE overlays expand analysis/control docs (
.data, multi-mode.dc,.op,.measure,.probe,.lib, …) — see Dialect reference catalog - LTspice remains a stub corpus; missing entry → no hover (not an error)
- Reference describes language constructs, not simulator version release notes
See Dialect reference and net semantics.
Post-MVP: connectivity analysis
Dangling-node and floating-net diagnostics are heuristic:
| Limitation | Detail |
|---|---|
| Single file | Ignores nets defined across .include until cross-file index exists |
| Ground aliases | Defaults to 0, gnd, GND; exotic ground names may need config |
| False positives | Intentionally open probe points may warn until suppression exists |
| Not full ERC | Does not check layout, EM, or foundry rules |
| Ideal elements | Voltage sources and unusual topologies need careful graph rules |
These warnings supplement — not replace — simulator and layout review.
Dialect differences
| Area | Ngspice | LTspice | HSPICE |
|---|---|---|---|
| Comments | *, ;, $ | $ common | * |
| Directives / options | Baseline corpus | Overrides in reference/ltspice/ | Overrides in reference/hspice/ |
Tri-dialect parsing and reference namespaces target v0.4–v0.5. Current parsing is Ngspice-oriented.
Parser robustness
- Error recovery may leave incomplete indexes until syntax is fixed
- Very large files still re-parse the full buffer after the debounce window
- LSP assumes UTF-8 source
Editor / LSP
- UTF-16 positions per LSP spec
- Stdio transport only
- No workspace-wide symbol search until include-graph analysis exists
- Diagnostics on
didChangeare debounced (~150 ms); navigation requests re-analyze on demand so the index stays current
Reporting issues
Include:
- Minimal netlist snippet
- Dialect (Ngspice / LTspice / HSPICE)
- Expected vs actual diagnostic or hover text
Add a fixture under test-data/ when fixing.
Dialect Reference and Net Semantics
Two major capabilities that come after MVP: a curated dialect reference the LSP consults for documentation, and net connectivity analysis that flags floating nets and dangling nodes. This chapter is the single source of truth for both; other pages link here rather than repeating detail.
Where this fits in the roadmap
MVP ──► v0.2 navigation ──► v0.3 completion ──► v0.4 formatter
│
▼
v0.5 (this chapter)
• dialect reference → hover
• net graph → floating / dangling
MVP and the early phases build the parser, symbol index, and editor pipeline. v0.5 adds semantic depth: explain SPICE constructs from your own reference files, and warn about suspicious connectivity before simulation.
Part 1 — Dialect reference library
Purpose
SPICE dialects differ in directives (.tran, .option), device syntax, and parameter names. Generic hover text is not enough long term. spice-lsp will ship with — and grow — a reference corpus you maintain: structured descriptions of commands, options, element types, and common expressions per dialect.
The LSP does not scrape simulator manuals at runtime. It looks up entries from checked-in reference data selected by the active dialect.
What users see
| Cursor on… | Hover shows (from reference) |
|---|---|
.tran | Syntax, parameters, units, Ngspice vs LTspice notes |
.option keyword | Meaning, default, valid values |
M (MOSFET line) | Terminal order, common parameters |
{expression} in .param | Allowed functions, unit conventions |
Completion (v0.3+) can attach the same entries as documentation on completion items.
Repository layout (planned)
reference/
├── schema.json # JSON Schema for reference entries
├── ngspice/
│ ├── directives/
│ │ ├── tran.json
│ │ ├── ac.json
│ │ └── option.json
│ ├── elements/
│ │ ├── R.json
│ │ └── M.json
│ └── expressions.json # shared {…} expression helpers
├── ltspice/
│ └── … # LTspice-specific overrides and additions
└── hspice/
└── …
Author HSPICE and Ngspice first (HSPICE is the extension default — see Multi-dialect design); add LTspice as the corpus grows. Entries can override or extend _shared/ where dialects agree.
Entry format (draft)
Each file describes one construct. Example reference/ngspice/directives/tran.json:
{
"id": "ngspice.directive.tran",
"kind": "directive",
"name": ".tran",
"summary": "Transient analysis",
"syntax": ".tran Tstep Tstop [Tstart [Tmax]] [UIC]",
"parameters": [
{ "name": "Tstep", "description": "Suggested printing increment.", "units": "seconds" },
{ "name": "Tstop", "description": "Final time.", "units": "seconds" }
],
"examples": [".tran 1n 100n", ".tran 1u 1m 0 10u UIC"],
"seeAlso": ["ngspice.directive.options"],
"dialect": "ngspice"
}
The Rust crate spice-reference (or a module in spice-parser) loads and indexes entries by (dialect, kind, name).
LSP integration
- Client sends active dialect via
initializationOptionsorspiceLsp.dialectsetting (defaulthspice; command + status bar to switch — design). - On
textDocument/hover, the server maps the cursor CST node to a reference key (e.g. directive name, element letter, option token). - Server renders
Hovermarkdown from the entry: summary, syntax block, parameter table, examples. - Missing entry → no hover (or a one-line fallback from the parse tree). Gaps are filled by adding reference files, not hard-coding strings in Rust.
Authoring workflow
Reference content is your ongoing work, independent of parser releases:
- Add or edit JSON under
reference/<dialect>/orreference/_shared/. - Run
pixi run reference-validateto load and exercise the embedded corpus. - Run
pixi run reference-docsto regenerate the Dialect reference catalog in the book. - Add or update hover snapshot tests when behavior changes.
- Ship with the binary (corpus is embedded at compile time via the
spice-referencebuild script).
Prefer small, focused files over one giant manual. Link related entries with seeAlso.
Phase placement
| Phase | Reference scope |
|---|---|
| MVP | None |
| v0.3 | Inline hover from CST + curated reference/ lookup (HSPICE overlays for .data / .dc / .op and common controls) |
| v0.5 | Broader dialect coverage + connectivity analysis |
| v0.5+ | LTspice / remaining HSPICE constructs grow incrementally |
Part 2 — Net connectivity analysis
Purpose
Syntax-correct netlists can still fail simulation because a node is dangling (only one connection) or floating (no DC path to ground). spice-lsp will analyze connectivity from parsed instance lines and surface these as semantic diagnostics (typically warnings).
This is not ERC/DRC and does not replace the simulator — it catches common mistakes early.
Definitions
| Term | Meaning | Example |
|---|---|---|
| Ground | Node 0, gnd, GND, or dialect-specific ground aliases | C1 out 0 1u |
| Dangling node | Appears on exactly one device terminal in the analyzed scope | Net bias only on R1 in bias with nothing on bias elsewhere |
| Floating net | Has connections but no DC path to ground through R, L, V, I, or defined grounds | Unconnected island of R–C with no voltage source or ground tie |
Exact rules are dialect-aware (e.g. which nodes count as ground, whether V with both nodes internal creates a path). Document rules per dialect in the analyzer and test with fixtures.
Architecture
CST instance lines
│
▼
┌──────────────────┐
│ NetGraph builder │ nodes ↔ terminals, per .subckt scope
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Connectivity │ dangling: degree == 1 (exclude intentional probes)
│ passes │ floating: no path to ground node set
└────────┬─────────┘
│
▼
Vec<Diagnostic> → publishDiagnostics (Warning)
Build the graph per scope: top level and inside each .subckt separately. Subcircuit ports are connections to the parent scope, not isolated graphs (v0.5.1+).
Diagnostic examples
| Code | Message | Severity |
|---|---|---|
spice/dangling-node | node 'bias' is connected to only one device terminal | Warning |
spice/floating-net | net 'internal' has no DC path to ground | Warning |
Attach diagnostics to the node token on the instance line when possible. Offer a single diagnostic per net, not one per terminal.
Configuration (future)
| Setting | Default | Effect |
|---|---|---|
spiceLsp.diagnostics.danglingNodes | true | Enable dangling-node pass |
spiceLsp.diagnostics.floatingNets | true | Enable floating-net pass |
spiceLsp.groundNodes | ["0", "gnd", "GND"] | Treat as ground for path search |
Limitations
- Ignores
.includefiles until cross-file analysis exists - Ideal voltage sources and shorted nodes need special handling
- Intentionally open probes may false-positive — allow suppress comments or config later
Phase placement
| Phase | Connectivity scope |
|---|---|
| MVP | None |
| v0.2 | Optional: duplicate instance names only |
| v0.5 | Dangling nodes and floating nets in single-file scope |
Testing
| Layer | Test |
|---|---|
| Reference | Schema validation on all reference/**/*.json |
| Reference | Hover snapshot: fixture + cursor → markdown |
| Connectivity | test-data/semantic/dangling-node.cir → one spice/dangling-node |
| Connectivity | test-data/semantic/floating-net.cir → one spice/floating-net |
| LSP | Integration test publishes warnings after open |
See Demo and testing.
Related
- Architecture — crate layout and phased rollout
- LSP features — capability matrix
- Limitations — what analysis does not cover
- Design (internal) — requirements spec
Dialect reference catalog
This section is generated from the curated JSON corpus under reference/.
The language server embeds the same data for hover (and later completion docs).
How to update
- Edit or add JSON under
reference/_shared/orreference/<dialect>/. - Run
pixi run reference-validate. - Run
pixi run reference-docsto regenerate these pages. - CI runs
pixi run reference-docs-checkso the book stays in sync.
Design notes: Multi-dialect support.
Dialects
| Dialect | Setting value | Entries (effective) | Catalog |
|---|---|---|---|
| Shared base | — | 16 | Shared |
| HSPICE | hspice | 25 | HSPICE |
| Ngspice | ngspice | 16 | Ngspice |
| LTspice | ltspice | 16 | LTspice |
Embedded corpus size: 37 raw JSON entries (shared + overlays).
Shared reference catalog
Constructs common to all dialects. Dialect pages overlay these when a matching (kind, name) exists.
Source of truth: reference/_shared/.
Index
| Name | Kind | Summary |
|---|---|---|
.ac | directive | Small-signal AC frequency analysis |
.dc | directive | DC sweep analysis |
.end | directive | End of netlist |
.ends | directive | End a subcircuit definition |
.ic | directive | Set initial node voltages |
.include | directive | Include another netlist file |
.model | directive | Define a device model |
.op | directive | DC operating-point analysis |
.param | directive | Define a named parameter |
.print | directive | Print analysis results as a table |
.subckt | directive | Begin a subcircuit definition |
.temp | directive | Set circuit temperature(s) |
.tran | directive | Transient analysis |
C | element | Capacitor |
R | element | Resistor |
X | element | Subcircuit instance |
Directives
.ac
shared.directive.ac — Small-signal AC frequency analysis
.ac {LIN|DEC|OCT} np fstart fstop
| Parameter | Description | Units |
|---|---|---|
| `LIN | DEC | OCT` |
np | Number of points (LIN) or points per decade/octave (DEC/OCT). | |
fstart | Start frequency. | Hz |
fstop | Stop frequency. | Hz |
Examples
.ac DEC 10 1k 1G.ac LIN 100 1 1Meg
.dc
shared.directive.dc — DC sweep analysis
.dc srcname start stop step
| Parameter | Description | Units |
|---|---|---|
srcname | Independent source, parameter, or TEMP to sweep. | |
start | Sweep start value. | |
stop | Sweep stop value. | |
step | Increment between points. |
Examples
.dc V1 0 5 0.1.dc TEMP 0 100 25
.end
shared.directive.end — End of netlist
.end [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional trailing comment. |
Examples
.end.end my_circuit
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
shared.directive.ic — Set initial node voltages
.ic V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage to initialize. | V |
Examples
.ic V(out)=0.ic V(in)=1.8 V(out)=0
.include
shared.directive.include — Include another netlist file
.include 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | Path to the file to include (quotes recommended). |
Examples
.include 'models.inc'.inc 'corners/tt.sp'
See also: shared.directive.model
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.op
shared.directive.op — DC operating-point analysis
.op
Examples
.op
Computes node voltages and bias points. Many simulators also print an operating point before .tran / .ac unless UIC is used.
.param
shared.directive.param — Define a named parameter
.param name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Numeric value or expression. |
Examples
.param rload=1k.param pi=3.14159
shared.directive.print — Print analysis results as a table
.print {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable, e.g. V(node), I(Vsrc), VM(node). |
Examples
.print TRAN V(out) I(Vdd).print DC V(2) V(3)
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
shared.directive.temp — Set circuit temperature(s)
.temp t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius (additional values run multi-temp analysis). | °C |
Examples
.temp 25.temp 0 25 85
.tran
shared.directive.tran — Transient analysis
.tran tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|---|---|---|
tstep | Suggested printing / sampling increment. | s |
tstop | Final simulation time. | s |
tstart | Optional start of printing. | s |
tmax | Optional maximum timestep. | s |
UIC | Use initial conditions. |
Examples
.tran 1n 100n.tran 1u 1m 0 10u UIC
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
HSPICE reference catalog
Effective documentation for the HSPICE dialect (spiceLsp.dialect = "hspice"): shared entries plus dialect overlays.
Source of truth: JSON under reference/. Hover in the editor uses the same corpus.
Index
| Name | Kind | Summary | Source |
|---|---|---|---|
.ac | directive | AC frequency analysis (HSPICE) | dialect |
.alter | directive | Rerun simulation with alternate parameters or analyses (HSPICE) | dialect |
.data | directive | Define a named data table for data-driven sweeps (HSPICE) | dialect |
.dc | directive | DC analysis and sweeps (HSPICE) | dialect |
.end | directive | Terminate the netlist (HSPICE) | dialect |
.ends | directive | End a subcircuit definition | shared |
.ic | directive | Force initial node voltages (HSPICE) | dialect |
.include | directive | Include an external file (HSPICE) | dialect |
.lib | directive | Call a named library section (HSPICE) | dialect |
.measure | directive | Measure delay, rise/fall, extrema, and other results (HSPICE) | dialect |
.model | directive | Define a device model | shared |
.nodeset | directive | Suggest initial guesses for DC convergence (HSPICE) | dialect |
.noise | directive | Noise analysis paired with .AC (HSPICE) | dialect |
.op | directive | DC operating-point report (HSPICE) | dialect |
.option | directive | Set HSPICE simulation options | dialect |
.param | directive | Define parameters and expressions (HSPICE) | dialect |
.print | directive | Print tabulated analysis results (HSPICE) | dialect |
.probe | directive | Select waveforms saved for post-processing (HSPICE) | dialect |
.subckt | directive | Begin a subcircuit definition | shared |
.temp | directive | Set simulation temperature(s) (HSPICE) | dialect |
.tf | directive | DC small-signal transfer function (HSPICE) | dialect |
.tran | directive | Transient analysis (HSPICE) | dialect |
C | element | Capacitor | shared |
R | element | Resistor | shared |
X | element | Subcircuit instance | shared |
Directives
.ac
hspice.directive.ac — AC frequency analysis (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.AC type np fstart fstop
.AC type np fstart fstop SWEEP var type2 np2 start stop
.AC DATA=datanm
| Parameter | Description | Units |
|---|---|---|
type | LIN, DEC, or OCT frequency spacing. | |
np | Points (LIN) or points per decade/octave. | |
fstart / fstop | Frequency range. | Hz |
SWEEP … | Optional nested parameter/source sweep. | |
DATA=datanm | Data-driven AC analysis. |
Examples
.AC DEC 10 1 1G.AC LIN 50 1k 100Meg.AC DATA=ac_corners
HSPICE adds nested SWEEP and DATA= forms on top of classic LIN/DEC/OCT.
See also: hspice.directive.data, hspice.directive.noise
.alter
hspice.directive.alter — Rerun simulation with alternate parameters or analyses (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.ALTER [title_string]
| Parameter | Description | Units |
|---|---|---|
title_string | Optional title for this alter case. |
Examples
.ALTER slow_corner .TEMP 125 .LIB 'models.lib' SS
An .ALTER block may contain elements and many control statements (.PARAM, .LIB, .DATA, .DC, .TRAN, .OP, …). Analysis types already used in the main deck have restrictions—see the HSPICE user guide.
See also: hspice.directive.data, hspice.directive.lib, hspice.directive.param
.data
hspice.directive.data — Define a named data table for data-driven sweeps (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.DATA datanm pnam1 [pnam2 ...]
pval1 [pval2 ...]
...
.ENDDATA
| Parameter | Description | Units |
|---|---|---|
datanm | Data-block name referenced by DATA=datanm on .DC / .AC / .TRAN. | |
pnam | Parameter column name (must be declared with .PARAM). | |
pval | Row of values; column count matches the parameter list. |
Examples
.DATA load_sweep rload 1k 10k 100k .ENDDATA- `.DATA load_sweep rload
- 1k
- 10k .ENDDATA`
.DC DATA=load_sweep
HSPICE supports inline .DATA … .ENDDATA blocks and external/MER file forms. Value rows may be bare (no leading +) or classic + continuations. Analysis statements select a block with DATA=datanm (optionally DATA=datanm(Nums)).
See also: hspice.directive.dc, shared.directive.param, hspice.directive.tran
.dc
hspice.directive.dc — DC analysis and sweeps (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.DC var1 start1 stop1 incr1
.DC var1 START=start1 STOP=stop1 STEP=incr1
.DC var1 start1 stop1 incr1 [SWEEP] var2 start2 stop2 incr2
.DC var1 type np start1 stop1
.DC DATA=datanm
.DC MONTE=val
| Parameter | Description | Units |
|---|---|---|
var1 | Primary sweep variable: independent source, element/model parameter, or TEMP. | |
start1 / stop1 / incr1 | Linear sweep bounds and step (positional or START=/STOP=/STEP=). | |
type np | LIN|DEC|OCT with point count for parameterized / nested sweeps. | |
SWEEP var2 … | Optional nested (second) sweep over another source or parameter. | |
DATA=datanm | Data-driven sweep using a .DATA block. | |
MONTE=val | Monte Carlo DC analysis. |
Examples
.DC Vgs 0 1.8 0.1.DC Vds 0 1.8 0.05 SWEEP Vgs 0 1.8 0.3.DC TEMP START=0 STOP=100 STEP=25.DC DATA=corner_table.DC MONTE=100
HSPICE extends classic SPICE .DC with keyword START/STOP/STEP, nested SWEEP, DATA= tables, and MONTE=. Prefer DATA= for multi-parameter corner tables.
See also: hspice.directive.data, shared.directive.op, hspice.directive.print
.end
hspice.directive.end — Terminate the netlist (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.END [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional comment, often the deck name. |
Examples
.END.END inverter_tb
Also closes any open .ALTER sequences. Statements after .END are ignored.
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
hspice.directive.ic — Force initial node voltages (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.IC V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node (or hierarchical path) to initialize. | V |
Examples
.IC V(out)=0.IC V(clk)=0 V(data)=1.8
Also accepted as .DCVOLT. Used with .TRAN … UIC to skip the quiescent OP and start from these voltages.
See also: hspice.directive.nodeset, hspice.directive.tran
.include
hspice.directive.include — Include an external file (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.INCLUDE 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | File path to insert (quotes recommended). |
Examples
.INCLUDE 'models/nmos.inc'.INC '../stimuli/clock.sp'
Also accepted as .INC. Prefer .LIB when selecting a named section from a library file.
See also: hspice.directive.lib
.lib
hspice.directive.lib — Call a named library section (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.LIB 'filename' entryname
| Parameter | Description | Units |
|---|---|---|
filename | Library file path. | |
entryname | Section name inside the library (.LIB entry … .ENDL). |
Examples
.LIB 'models.lib' TT.LIB '/pdk/hspice/models' FF
Library files define sections with .LIB entryname … .ENDL entryname. Nested .LIB calls are supported up to a limited depth.
See also: hspice.directive.include, shared.directive.model
.measure
hspice.directive.measure — Measure delay, rise/fall, extrema, and other results (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.MEASURE {TRAN|DC|AC} name TRIG … TARG …
.MEASURE {TRAN|DC|AC} name MAX|MIN|PP|AVG|RMS|INTEG …
| Parameter | Description | Units |
|---|---|---|
| `TRAN | DC | AC` |
name | Measurement result name written to the measure output. | |
TRIG / TARG | Trigger and target conditions (VAL=, RISE=/FALL=/CROSS=). |
Examples
.MEASURE TRAN trise TRIG V(out) VAL=0.1*vdd RISE=1 TARG V(out) VAL=0.9*vdd RISE=1.MEASURE TRAN tpd TRIG V(in) VAL=0.5 RISE=1 TARG V(out) VAL=0.5 RISE=1.MEASURE TRAN vmax MAX V(out)
Also accepted as .MEAS. Many measure forms exist (FIND/WHEN, PARAM, DERIV, etc.); TRIG/TARG and MAX/MIN cover the most common timing and peak checks.
See also: hspice.directive.tran, hspice.directive.print, hspice.directive.probe
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.nodeset
hspice.directive.nodeset — Suggest initial guesses for DC convergence (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.NODESET V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage guess for the DC solution. | V |
Examples
.NODESET V(out)=0.9.NODESET V(n1)=0 V(n2)=1.8
Unlike .IC, .NODESET is a soft hint for finding the operating point, not a hard initial condition for transient UIC.
See also: hspice.directive.ic, hspice.directive.op
.noise
hspice.directive.noise — Noise analysis paired with .AC (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.NOISE outvar srcnam [interval]
| Parameter | Description | Units |
|---|---|---|
outvar | Output voltage node or pair, e.g. V(out) or V(out,ref). | |
srcnam | Independent source treated as the noise input reference. | |
interval | Optional print interval for intermediate noise summaries. |
Examples
.NOISE V(out) Vin.NOISE V(out,0) Vin 10
Requires a companion .AC statement. Reports total output noise and equivalent input noise.
See also: hspice.directive.ac
.op
hspice.directive.op — DC operating-point report (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.OP [format [time ...]]
| Parameter | Description | Units |
|---|---|---|
format | Optional report style: ALL, BRIEF, CURRENT, DEBUG, NONE, VOLTAGE. | |
time | Optional time(s) for reporting during transient (with ALL/VOLTAGE/CURRENT/DEBUG). | s |
Examples
.OP.OP BRIEF.OP ALL 10n
HSPICE often prints bias automatically with other analyses; use .OP when you only need the operating point, or to request a formatted report at specific times.
See also: hspice.directive.dc, hspice.directive.ic, hspice.directive.nodeset
.option
hspice.directive.option — Set HSPICE simulation options
Dialect overlay (replaces shared entry with the same name).
.OPTION keyword=value [keyword=value ...]
| Parameter | Description | Units |
|---|---|---|
keyword | Option name (e.g. POST, RUNLVL). |
Examples
.OPTION POST=2.OPTION RUNLVL=5
Option keywords differ across simulators; this entry covers the .option form.
.param
hspice.directive.param — Define parameters and expressions (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.PARAM name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Number, ‘algebraic expression’, distribution function, or str(‘…’). |
Examples
.PARAM vdd=1.8 rload=10k.PARAM cload='2*cunit'.PARAM tox=agauss(3n,0.1n,3)
HSPICE allows quoted expressions and statistical distributions (GAUSS/AGAUSS/…) used with Monte Carlo. Parameters referenced by .DATA columns must be declared here.
See also: hspice.directive.data, hspice.directive.alter
hspice.directive.print — Print tabulated analysis results (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.PRINT {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable; AC forms include VM/VP/VR/VI/VDB. |
Examples
.PRINT TRAN V(out) I(Vdd).PRINT AC VM(out) VDB(out) VP(out)
See also: hspice.directive.probe, hspice.directive.measure
.probe
hspice.directive.probe — Select waveforms saved for post-processing (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.PROBE {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable to save (node voltage, branch current, etc.). |
Examples
.PROBE TRAN V(out) V(in) I(Vdd).PROBE AC VM(out) VP(out)
Pair with .OPTION POST=… . With .OPTION PROBE, only .PROBE/.PRINT/.PLOT variables are written, shrinking waveform files.
See also: hspice.directive.print, hspice.directive.option
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
hspice.directive.temp — Set simulation temperature(s) (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.TEMP t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius; multiple values run multi-temperature analysis. | °C |
Examples
.TEMP 25.TEMP -40 25 125
TEMP can also be the sweep variable on .DC / nested SWEEP.
See also: hspice.directive.dc, hspice.directive.option
.tf
hspice.directive.tf — DC small-signal transfer function (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.TF outvar srcnam
| Parameter | Description | Units |
|---|---|---|
outvar | Output variable, e.g. V(out) or I(Vload). | |
srcnam | Independent input source name. |
Examples
.TF V(out) Vin.TF I(Vmeas) Iin
Reports transfer gain plus small-signal input and output resistances.
See also: hspice.directive.op, hspice.directive.dc
.tran
hspice.directive.tran — Transient analysis (HSPICE)
Dialect overlay (replaces shared entry with the same name).
.TRAN tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|---|---|---|
tstep | Printing increment. | s |
tstop | Stop time. | s |
tstart | Optional start of printing. | s |
tmax | Optional maximum timestep. | s |
UIC | Use initial conditions. |
Examples
.TRAN 1p 10n
HSPICE commonly uses uppercase directives; spice-lsp matches case-insensitively.
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
Ngspice reference catalog
Effective documentation for the Ngspice dialect (spiceLsp.dialect = "ngspice"): shared entries plus dialect overlays.
Source of truth: JSON under reference/. Hover in the editor uses the same corpus.
Index
| Name | Kind | Summary | Source |
|---|---|---|---|
.ac | directive | Small-signal AC frequency analysis | shared |
.dc | directive | DC sweep analysis | shared |
.end | directive | End of netlist | shared |
.ends | directive | End a subcircuit definition | shared |
.ic | directive | Set initial node voltages | shared |
.include | directive | Include another netlist file | shared |
.model | directive | Define a device model | shared |
.op | directive | DC operating-point analysis | shared |
.param | directive | Define a named parameter | shared |
.print | directive | Print analysis results as a table | shared |
.subckt | directive | Begin a subcircuit definition | shared |
.temp | directive | Set circuit temperature(s) | shared |
.tran | directive | Transient analysis (Ngspice) | dialect |
C | element | Capacitor | shared |
R | element | Resistor | shared |
X | element | Subcircuit instance | shared |
Directives
.ac
shared.directive.ac — Small-signal AC frequency analysis
.ac {LIN|DEC|OCT} np fstart fstop
| Parameter | Description | Units |
|---|---|---|
| `LIN | DEC | OCT` |
np | Number of points (LIN) or points per decade/octave (DEC/OCT). | |
fstart | Start frequency. | Hz |
fstop | Stop frequency. | Hz |
Examples
.ac DEC 10 1k 1G.ac LIN 100 1 1Meg
.dc
shared.directive.dc — DC sweep analysis
.dc srcname start stop step
| Parameter | Description | Units |
|---|---|---|
srcname | Independent source, parameter, or TEMP to sweep. | |
start | Sweep start value. | |
stop | Sweep stop value. | |
step | Increment between points. |
Examples
.dc V1 0 5 0.1.dc TEMP 0 100 25
.end
shared.directive.end — End of netlist
.end [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional trailing comment. |
Examples
.end.end my_circuit
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
shared.directive.ic — Set initial node voltages
.ic V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage to initialize. | V |
Examples
.ic V(out)=0.ic V(in)=1.8 V(out)=0
.include
shared.directive.include — Include another netlist file
.include 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | Path to the file to include (quotes recommended). |
Examples
.include 'models.inc'.inc 'corners/tt.sp'
See also: shared.directive.model
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.op
shared.directive.op — DC operating-point analysis
.op
Examples
.op
Computes node voltages and bias points. Many simulators also print an operating point before .tran / .ac unless UIC is used.
.param
shared.directive.param — Define a named parameter
.param name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Numeric value or expression. |
Examples
.param rload=1k.param pi=3.14159
shared.directive.print — Print analysis results as a table
.print {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable, e.g. V(node), I(Vsrc), VM(node). |
Examples
.print TRAN V(out) I(Vdd).print DC V(2) V(3)
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
shared.directive.temp — Set circuit temperature(s)
.temp t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius (additional values run multi-temp analysis). | °C |
Examples
.temp 25.temp 0 25 85
.tran
ngspice.directive.tran — Transient analysis (Ngspice)
Dialect overlay (replaces shared entry with the same name).
.tran tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|---|---|---|
tstep | Suggested printing increment. | s |
tstop | Final time. | s |
tstart | Optional start of printing. | s |
tmax | Optional maximum timestep. | s |
UIC | Use initial conditions. |
Examples
.tran 1n 100n.tran 1u 1m 0 10u UIC
Ngspice also accepts ; and $ comments in addition to *.
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
LTspice reference catalog
Effective documentation for the LTspice dialect (spiceLsp.dialect = "ltspice"): shared entries plus dialect overlays.
Source of truth: JSON under reference/. Hover in the editor uses the same corpus.
Index
| Name | Kind | Summary | Source |
|---|---|---|---|
.ac | directive | Small-signal AC frequency analysis | shared |
.dc | directive | DC sweep analysis | shared |
.end | directive | End of netlist | shared |
.ends | directive | End a subcircuit definition | shared |
.ic | directive | Set initial node voltages | shared |
.include | directive | Include another netlist file | shared |
.model | directive | Define a device model | shared |
.op | directive | DC operating-point analysis | shared |
.param | directive | Define a named parameter | shared |
.print | directive | Print analysis results as a table | shared |
.subckt | directive | Begin a subcircuit definition | shared |
.temp | directive | Set circuit temperature(s) | shared |
.tran | directive | Transient analysis (LTspice) | dialect |
C | element | Capacitor | shared |
R | element | Resistor | shared |
X | element | Subcircuit instance | shared |
Directives
.ac
shared.directive.ac — Small-signal AC frequency analysis
.ac {LIN|DEC|OCT} np fstart fstop
| Parameter | Description | Units |
|---|---|---|
| `LIN | DEC | OCT` |
np | Number of points (LIN) or points per decade/octave (DEC/OCT). | |
fstart | Start frequency. | Hz |
fstop | Stop frequency. | Hz |
Examples
.ac DEC 10 1k 1G.ac LIN 100 1 1Meg
.dc
shared.directive.dc — DC sweep analysis
.dc srcname start stop step
| Parameter | Description | Units |
|---|---|---|
srcname | Independent source, parameter, or TEMP to sweep. | |
start | Sweep start value. | |
stop | Sweep stop value. | |
step | Increment between points. |
Examples
.dc V1 0 5 0.1.dc TEMP 0 100 25
.end
shared.directive.end — End of netlist
.end [comment]
| Parameter | Description | Units |
|---|---|---|
comment | Optional trailing comment. |
Examples
.end.end my_circuit
.ends
shared.directive.ends — End a subcircuit definition
.ends [name]
| Parameter | Description | Units |
|---|---|---|
name | Optional subcircuit name; should match .subckt. |
Examples
.ends.ends buffer
See also: shared.directive.subckt
.ic
shared.directive.ic — Set initial node voltages
.ic V(node)=value [V(node2)=value2 ...]
| Parameter | Description | Units |
|---|---|---|
V(node) | Node voltage to initialize. | V |
Examples
.ic V(out)=0.ic V(in)=1.8 V(out)=0
.include
shared.directive.include — Include another netlist file
.include 'filename'
| Parameter | Description | Units |
|---|---|---|
filename | Path to the file to include (quotes recommended). |
Examples
.include 'models.inc'.inc 'corners/tt.sp'
See also: shared.directive.model
.model
shared.directive.model — Define a device model
.model mname type (param=value ...)
| Parameter | Description | Units |
|---|---|---|
mname | Model name referenced by instances. | |
type | Model type (e.g. NMOS, PMOS, D, NPN). |
Examples
.model nmos NMOS (VTO=0.7)
.op
shared.directive.op — DC operating-point analysis
.op
Examples
.op
Computes node voltages and bias points. Many simulators also print an operating point before .tran / .ac unless UIC is used.
.param
shared.directive.param — Define a named parameter
.param name=value [name2=value2 ...]
| Parameter | Description | Units |
|---|---|---|
name | Parameter identifier. | |
value | Numeric value or expression. |
Examples
.param rload=1k.param pi=3.14159
shared.directive.print — Print analysis results as a table
.print {DC|TRAN|AC} ov1 [ov2 ...]
| Parameter | Description | Units |
|---|---|---|
| `DC | TRAN | AC` |
ov | Output variable, e.g. V(node), I(Vsrc), VM(node). |
Examples
.print TRAN V(out) I(Vdd).print DC V(2) V(3)
.subckt
shared.directive.subckt — Begin a subcircuit definition
.subckt name n1 [n2 ...] [params: p=val ...]
| Parameter | Description | Units |
|---|---|---|
name | Subcircuit name. | |
n1... | External port nodes in order. |
Examples
.subckt buffer in out.subckt inv in out
See also: shared.directive.ends
.temp
shared.directive.temp — Set circuit temperature(s)
.temp t1 [t2 ...]
| Parameter | Description | Units |
|---|---|---|
t1 | Temperature in Celsius (additional values run multi-temp analysis). | °C |
Examples
.temp 25.temp 0 25 85
.tran
ltspice.directive.tran — Transient analysis (LTspice)
Dialect overlay (replaces shared entry with the same name).
.tran <Tstep> <Tstop> [Tstart [dTmax]] [modifiers]
| Parameter | Description | Units |
|---|---|---|
Tstep | Printing increment. | s |
Tstop | Stop time. | s |
Examples
.tran 1n 100n
LTspice support is a stub corpus in v0.3; grammar remains shared.
Elements
C
shared.element.C — Capacitor
Cname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Capacitance. | F |
Examples
C1 out 0 1p
R
shared.element.R — Resistor
Rname n1 n2 value [params...]
| Parameter | Description | Units |
|---|---|---|
n1 | Positive node. | |
n2 | Negative node. | |
value | Resistance. | ohm |
Examples
R1 in out 1kRload out 0 50
X
shared.element.X — Subcircuit instance
Xname n1 [n2 ...] subckt_name [params...]
| Parameter | Description | Units |
|---|---|---|
n1... | Nodes connected to subcircuit ports. | |
subckt_name | Name of a .subckt definition. |
Examples
X1 a b buffer
Build
Pixi-managed build environment, tasks, CI, and release workflow.
Environment
All tooling flows through pixi:
pixi install # sync environment from pixi.lock
pixi run <task> # run a defined task
pixi run cargo build # ad-hoc command in the env
The workspace supports linux-64, osx-arm64, osx-64, and win-64 so release CI can install the same environment on every runner.
Add dependencies with the CLI (do not hand-edit versions):
pixi add rust=1.96
pixi add nodejs=22 # when VS Code extension work starts
pixi add mdbook # already in pixi.toml
pixi add --pypi pytest # example PyPI package
Planned pixi tasks
Add these to [tasks] in pixi.toml as the workspace grows:
| Task | Command | Purpose |
|---|---|---|
build | cargo build --release | Production binary |
build-dev | cargo build | Fast debug builds |
test | cargo test --workspace | All unit + integration tests |
test-parser | cargo test -p spice-parser | Parser fixtures only |
test-lsp | cargo test -p spice-lsp | LSP integration tests |
spice-lsp | cargo run -p spice-lsp | Run language server (stdio) |
fmt | cargo fmt --all | Rust formatting |
clippy | cargo clippy --workspace -- -D warnings | Lint |
mdbook-build | mdbook build docs | Static doc site |
mdbook-serve | mdbook serve docs -n 127.0.0.1 -p 3000 | Live doc preview |
ext-install | npm install in editors/vscode | Extension deps |
ext-compile | npm run compile | Build extension TS |
ext-package | ./scripts/package-vscode-extension.sh | Bundle local binary + .vsix |
reference-validate | cargo test -p spice-reference --lib | Validate embedded corpus |
reference-docs | spice-reference-catalog write | Regenerate docs/reference/ from corpus |
reference-docs-check | spice-reference-catalog check | Fail if catalog docs drift |
Example pixi.toml fragment:
[tasks]
build = "cargo build --release"
test = "cargo test --workspace"
spice-lsp = "cargo run -p spice-lsp"
mdbook-serve = "mdbook serve docs -n 127.0.0.1 -p 3000"
Rust workspace layout
Root Cargo.toml:
[workspace]
members = ["crates/spice-parser", "crates/spice-lsp"]
resolver = "2"
Release profile (recommended once benchmarks exist):
[profile.release]
lto = true
codegen-units = 1
Building the Tree-sitter grammar
Grammar crate under tree-sitter-spice/ is built via build.rs in spice-parser:
pixi run cargo build -p spice-parser
After grammar edits, run Tree-sitter’s test harness (once added):
pixi run cargo test -p tree-sitter-spice
# or: tree-sitter test (if CLI added via pixi)
CI (recommended)
GitHub Actions workflow stages:
pixi installpixi run fmt -- --checkpixi run clippypixi run testpixi run mdbook-build(optional, docs PRs)
Cache ~/.pixi and target/ between runs.
Release builds
Ship a single static binary per platform:
pixi run cargo build --release -p spice-lsp
# artifact: target/release/spice-lsp
Cross-compile with cross or platform matrix in CI. The Release VS Code extension workflow runs on every push to main: it patch-bumps the extension version, builds all platform binaries, packages a bundled .vsix, creates a GitHub Release, and publishes to the VS Code Marketplace (requires VSCE_PAT). See VS Code integration.
Documentation site
Build locally:
pixi run reference-docs # regenerate dialect catalog from reference/
pixi run mdbook-build
# output: docs/book/
Preview:
pixi run mdbook-serve
After editing JSON under reference/, run pixi run reference-docs so the Dialect reference catalog stays in sync. CI runs pixi run reference-docs-check.
CI and GitHub Pages
The Deploy docs workflow runs on pushes to main when docs/, reference/, crates/spice-reference/, pixi.toml, or pixi.lock change. It:
- Runs
pixi run mdbook-build - Pushes the output to the
gh-pagesbranch
GITHUB_TOKEN cannot enable GitHub Pages or set the repository Website field on this repo (API returns 403). Run the one-time setup script locally as a repository admin after the first successful deploy:
./scripts/setup-github-pages.sh
That script uses the gh CLI to:
- Point Pages at the
gh-pagesbranch (/root) - Set the repository Website field to the published URL
Published URL: https://amirhosseindavoody.github.io/spice-lsp/
Trigger a manual deploy from the Actions tab via workflow_dispatch if needed.
Troubleshooting
| Problem | Fix |
|---|---|
cargo: command not found | Run pixi install; use pixi run cargo |
Tree-sitter build.rs fails | Ensure C compiler available in pixi env (pixi add gcc on Linux) |
| Extension can’t find binary | Set spiceLsp.serverPath in VS Code settings to absolute path |
| LSP hangs on start | Normal for stdio servers waiting for JSON-RPC input |
Related
MVP Guide
Step-by-step path to a demoable minimum viable product: a VS Code window that shows live syntax diagnostics on SPICE netlists.
Time estimate is intentionally omitted — each milestone is a mergeable unit you can verify before continuing.
What MVP looks like
When done, you can:
- Press F5 in VS Code to open the Extension Development Host
- Open a
.cirfile with a deliberate syntax error - See a diagnostic squiggle within one edit cycle
- Fix the error and watch the squiggle disappear
- Run
pixi run testand have it pass in CI
Nothing else is required for MVP.
Milestone checklist
[ ] M1 Cargo workspace + empty crates
[ ] M2 Tree-sitter grammar (minimal Ngspice subset)
[ ] M3 Parser crate: parse → syntax diagnostics
[ ] M4 LSP crate: stdio server, sync, publishDiagnostics
[ ] M5 test-data fixtures + automated tests
[ ] M6 VS Code extension spawns server
[ ] M7 End-to-end demo script documented
M1 — Cargo workspace
Create the Rust workspace skeleton.
Files:
Cargo.toml # workspace root
crates/spice-parser/Cargo.toml
crates/spice-parser/src/lib.rs
crates/spice-lsp/Cargo.toml
crates/spice-lsp/src/main.rs
Root Cargo.toml:
[workspace]
members = ["crates/spice-parser", "crates/spice-lsp"]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"
crates/spice-lsp/Cargo.toml dependencies:
spice-parser = { path = "../spice-parser" }
tower-lsp = "0.20"
tokio = { version = "1", features = ["full"] }
Add pixi tasks: build, test, spice-lsp — see Build.
Verify: pixi run cargo build succeeds (empty server that exits immediately is fine).
M2 — Tree-sitter grammar (minimal)
Start small. The grammar must recognize:
- Comments (
*,;,$line comments) - Instance lines: letter + name + nodes + value/model
- Dot directives:
.subckt,.ends,.model,.tran,.end - Continuation lines starting with
+ - Bare numeric / engineering-value rows (HSPICE
.DATAtables without leading+)
Layout:
tree-sitter-spice/
├── grammar.js
├── src/ # generated parser (or via tree-sitter CLI)
├── bindings/rust/
└── queries/
└── highlights.scm # optional for MVP
Bootstrap option: If grammar authoring is blocking, use a line classifier for MVP only (regex per line type) and replace with Tree-sitter in M2.1 — but prefer Tree-sitter from the start so incremental parse is free later.
Verify: Parse test-data/valid/simple-rc.cir without ERROR nodes; parse test-data/invalid/unclosed-subckt.cir with expected ERROR/MISSING nodes.
M3 — Parser crate
crates/spice-parser/src/lib.rs exposes:
#![allow(unused)]
fn main() {
pub struct ParseResult {
pub diagnostics: Vec<Diagnostic>, // internal type, map in LSP crate
}
pub fn analyze(source: &str) -> ParseResult;
}
Implementation:
- Run Tree-sitter parse
- Walk tree for ERROR / MISSING nodes
- Add hand-written checks:
.subcktwithout matching.ends(simple stack)
Map byte offsets to line/column for LSP in the LSP crate (UTF-16 conversion helper).
Verify: pixi run cargo test -p spice-parser with fixtures in test-data/.
M4 — LSP server
crates/spice-lsp/src/main.rs using tower-lsp:
#[tokio::main]
async fn main() {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::new(|client| Backend { client, docs: ... });
Server::new(stdin, stdout, socket).serve(service).await;
}
Backend implements:
| Event | Action |
|---|---|
initialize | Return capabilities (incremental sync) |
did_open | Store document, analyze, publish_diagnostics |
did_change | Apply edits, re-analyze, publish |
did_close | Remove document |
Keep a HashMap<Url, String> (or versioned document struct) in the backend.
Verify: Integration test sends initialize + didOpen over async stdin/stdout; assert diagnostics notification JSON. See Demo and testing.
M5 — Fixtures and tests
Directory structure:
test-data/
├── valid/
│ ├── simple-rc.cir
│ └── subckt.cir
└── invalid/
├── unclosed-subckt.cir
└── bad-continuation.cir
Parser tests: For each invalid file, assert diagnostic count ≥ 1 and message substring.
LSP tests: Use tower-lsp test helpers or raw JSON-RPC frames:
- Initialize
- Open invalid file
- Read notification until
publishDiagnostics - Assert URI and range match
Verify: pixi run test green locally.
M6 — VS Code extension
Scaffold under editors/vscode/:
editors/vscode/
├── package.json
├── tsconfig.json
├── src/extension.ts
└── language-configuration.json
package.json essentials:
{
"name": "spice-lsp",
"displayName": "SPICE Language Support",
"activationEvents": ["onLanguage:spice"],
"contributes": {
"languages": [{
"id": "spice",
"extensions": [".cir", ".sp", ".spf", ".net", ".ckt"]
}],
"configuration": {
"properties": {
"spiceLsp.serverPath": {
"type": "string",
"default": "",
"description": "Path to spice-lsp binary (empty = bundled or PATH)"
}
}
}
}
}
extension.ts: Use vscode-languageclient/node to spawn the Rust binary:
const serverOptions: ServerOptions = {
command: config.get<string>("serverPath") || "spice-lsp",
args: [],
};
const client = new LanguageClient("spiceLsp", "SPICE LSP", serverOptions, clientOptions);
await client.start();
For development, set spiceLsp.serverPath to ../../target/debug/spice-lsp (absolute path in launch config).
Verify: F5 → open invalid netlist → squiggle appears.
Full detail: VS Code integration.
M7 — Demo script
Document a 2-minute demo for reviewers:
pixi run build- Open VS Code on
editors/vscode, press F5 - In Extension Development Host: File → Open →
test-data/invalid/unclosed-subckt.cir - Point out diagnostic message
- Add missing
.ends, save — diagnostic clears - Run
pixi run testin terminal to show automation
Optional: record asciinema or short screen capture for README.
What to skip until after MVP
Do not implement these before M7 is done:
- Formatter
- Completion, hover, go-to-definition, references
- Multi-dialect switching and
reference/corpus .includefile resolution- Semantic analysis (duplicate names, undefined models, dangling nodes, floating nets)
- Published VSIX / Marketplace listing (side-load is enough for MVP demo)
After MVP, follow the phase order in Architecture. Dialect reference hover and net connectivity are v0.5 — see Dialect reference and net semantics.
Suggested merge strategy
| PR | Contents |
|---|---|
| PR 1 | M1 + M2 + parser fixtures |
| PR 2 | M3 + M5 parser tests |
| PR 3 | M4 + LSP integration tests |
| PR 4 | M6 + M7 docs update |
Each PR should keep pixi run test green (or skip tests until the crate exists, then enable).
Related
- Principles — MVP scope boundary
- Architecture — where code lives
- Demo and testing
- VS Code integration
- Dialect reference and net semantics — post-MVP direction
Demo and Testing
How to manually demo spice-lsp and automate tests at each layer. Use this page before and after every milestone in the MVP guide.
Testing pyramid
┌─────────────┐
│ Manual VS │ F5 extension, eyeball squiggles
│ Code demo │
└──────┬──────┘
┌───────────┴───────────┐
│ LSP integration tests │ JSON-RPC over stdio
└───────────┬───────────┘
┌────────────────┴────────────────┐
│ Parser / grammar tests │ Fixtures in test-data/
└─────────────────────────────────┘
Lower layers run faster and should carry most coverage.
Parser tests
Location: crates/spice-parser/tests/ or inline #[cfg(test)] modules.
Pattern — golden diagnostics:
#![allow(unused)]
fn main() {
#[test]
fn unclosed_subckt_reports_error() {
let source = std::fs::read_to_string("test-data/invalid/unclosed-subckt.cir").unwrap();
let result = spice_parser::analyze(&source);
assert!(!result.diagnostics.is_empty());
assert!(result.diagnostics[0].message.contains("ends"));
}
}
Grammar tests (Tree-sitter): Corpus files under tree-sitter-spice/test/corpus/:
==========
simple RC
==========
R1 in out 1k
---
(source_file (instance_line ...))
Run: pixi run cargo test -p spice-parser
LSP integration tests
Test the binary without an editor by driving stdio.
Option A — Custom test harness
Spawn spice-lsp as a child process, write JSON-RPC messages with Content-Length headers, read responses:
#![allow(unused)]
fn main() {
// Pseudocode
let mut child = Command::new("target/debug/spice-lsp")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
write_message(&mut child, initialize_request());
let init_resp = read_message(&mut child);
assert_eq!(init_resp["result"]["capabilities"]["textDocumentSync"]["change"], 2);
write_message(&mut child, did_open("file:///test.cir", INVALID_SOURCE));
let diag = read_until_method(&mut child, "textDocument/publishDiagnostics");
assert!(!diag["params"]["diagnostics"].as_array().unwrap().is_empty());
}
Option B — tower-lsp in-process tests
Test Backend methods directly with a mock Client that records publish_diagnostics calls — faster, no subprocess.
Use both: in-process for logic, one subprocess smoke test for the full binary.
Run: pixi run cargo test -p spice-lsp
Manual LSP smoke test (no VS Code)
Use a generic LSP inspector or minimal script.
With languageclient CLI (if installed)
Some ecosystems ship an inspector; alternatively use the VS Code Output → SPICE LSP trace.
Raw JSON-RPC with Python (example)
#!/usr/bin/env python3
"""Send initialize to spice-lsp over stdio. Requires built binary on PATH."""
import json, subprocess, struct, sys
proc = subprocess.Popen(
["spice-lsp"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
def send(msg):
body = json.dumps(msg).encode()
header = f"Content-Length: {len(body)}\r\n\r\n".encode()
proc.stdin.write(header + body)
proc.stdin.flush()
def read():
headers = {}
while True:
line = proc.stdout.readline().decode()
if line in ("\r\n", "\n", ""):
break
k, v = line.split(":", 1)
headers[k.strip()] = int(v.strip())
body = proc.stdout.read(headers["Content-Length"])
return json.loads(body)
send({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
"processId": None,
"rootUri": None,
"capabilities": {},
}})
resp = read()
print(json.dumps(resp, indent=2))
assert "capabilities" in resp.get("result", {}), resp
print("OK: initialize succeeded", file=sys.stderr)
Run after build:
pixi run build
export PATH="$PWD/target/debug:$PATH"
python3 scripts/lsp_smoke.py
Add scripts/lsp_smoke.py to the repo when the server exists.
VS Code extension demo
Development host (primary demo path)
- Build Rust binary:
pixi run build cd editors/vscode && npm install && npm run compile- Open
editors/vscodein VS Code - Run and Debug → Launch Extension (F5)
- In the new [Extension Development Host] window:
- Open
test-data/invalid/unclosed-subckt.cir - Confirm Problems panel lists diagnostics
- Fix syntax, confirm clearing
- Open
Launch configuration
.vscode/launch.json in the extension folder:
{
"version": "0.2.0",
"configurations": [{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"env": {},
"preLaunchTask": "npm: compile"
}]
}
Set user/workspace setting in Development Host:
{
"spiceLsp.serverPath": "/absolute/path/to/spice-lsp/target/debug/spice-lsp"
}
Trace LSP traffic
Enable verbose logging during demo debugging:
{
"spiceLsp.trace.server": "verbose"
}
View Output panel → channel SPICE LSP (or Language Client name).
Side-load packaged extension
cd editors/vscode
npx vsce package
code --install-extension spice-lsp-0.1.0.vsix
Demo checklist for stakeholders
Use this script in reviews:
| Step | Action | Expected |
|---|---|---|
| 1 | pixi run test | All tests pass |
| 2 | F5 extension | Development Host opens |
| 3 | Open invalid netlist | Red squiggle + Problems entry |
| 4 | Edit to fix | Diagnostic disappears |
| 5 | Open valid netlist | No errors |
| 6 | SPICE LSP: Restart Server (if command added) | Server reconnects, diagnostics return |
CI expectations
Every push should run:
pixi install
pixi run test
Optional nightly or pre-release:
pixi run build --release
pixi run cargo test --release
Extension CI (when added):
cd editors/vscode && npm ci && npm run compile && npm test
Benchmarks (post-MVP)
Add criterion benches for parse + analyze on large fixtures:
pixi run cargo bench -p spice-parser
Track regressions against targets in Architecture.
Related
VS Code Integration
End goal for spice-lsp: a VS Code extension that launches the Rust language server and provides a first-class editing experience for SPICE netlists.
This chapter covers extension layout, development workflow, configuration, and publishing.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ VS Code Extension Host (Node.js) │
│ │
│ package.json ── contributes languages, config, commands │
│ extension.ts ── activates LanguageClient │
│ language-configuration.json ── comments, brackets, auto-close│
└───────────────────────────┬──────────────────────────────────┘
│ spawns process
▼
┌──────────────────────────────────────────────────────────────┐
│ spice-lsp binary (Rust, stdio JSON-RPC) │
│ initialize → didOpen/didChange → publishDiagnostics │
└──────────────────────────────────────────────────────────────┘
The extension is intentionally thin: no parsing in TypeScript. All language intelligence stays in Rust so Neovim and other clients can share the same binary.
Repository layout
editors/vscode/
├── .vscode/
│ ├── launch.json # F5 Extension Development Host
│ └── tasks.json # compile before launch
├── package.json
├── tsconfig.json
├── src/
│ └── extension.ts
├── language-configuration.json
└── README.md # Marketplace-facing extension readme
package.json
Key fields:
| Field | Purpose |
|---|---|
engines.vscode | Minimum VS Code version |
activationEvents | onLanguage:spice, onCommand:spiceLsp.restartServer, onCommand:spiceLsp.setDialect |
main | ./out/extension.js (esbuild bundle) |
contributes.languages | Register spice language id and file extensions |
contributes.configuration | spiceLsp.serverPath, spiceLsp.trace.server, spiceLsp.dialect |
contributes.commands | spiceLsp.restartServer, spiceLsp.setDialect — register first in activate; do not await LSP start before returning |
Example language contribution:
{
"languages": [{
"id": "spice",
"aliases": ["SPICE", "spice"],
"extensions": [".cir", ".sp", ".spf", ".net", ".ckt"],
"configuration": "./language-configuration.json"
}]
}
language-configuration.json
Teach VS Code comment syntax and line continuation behavior:
{
"comments": {
"lineComment": "*"
},
"brackets": [["(", ")"]],
"autoClosingPairs": [
{ "open": "(", "close": ")" }
]
}
Comment toggle uses * (language-configuration.json allows only one lineComment). ; and $ comments are highlighted by the TextMate grammar (syntaxes/spice.tmLanguage.json). Tree-sitter highlights.scm can back semantic tokens later.
extension.ts
Minimal Language Client setup. Register both palette commands before any await, then start the client in the background. If activate awaits a slow/hung client.start(), VS Code times out onCommand activation and reports command 'spiceLsp.setDialect' not found (same for Restart Server):
import * as vscode from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from "vscode-languageclient/node";
let client: LanguageClient | undefined;
async function startClient(serverPath: string) {
const serverOptions: ServerOptions = {
command: serverPath,
args: [],
transport: TransportKind.stdio,
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "spice" }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher("**/*.{cir,sp,spf,net,ckt}"),
},
};
client = new LanguageClient("spiceLsp", "SPICE Language Server", serverOptions, clientOptions);
await client.start();
}
export async function activate(context: vscode.ExtensionContext) {
const config = vscode.workspace.getConfiguration("spiceLsp");
const serverPath = config.get<string>("serverPath") || "spice-lsp";
context.subscriptions.push(
vscode.commands.registerCommand("spiceLsp.restartServer", async () => {
await client?.stop();
await startClient(serverPath);
}),
vscode.commands.registerCommand("spiceLsp.setDialect", async () => {
/* QuickPick → update spiceLsp.dialect → restart client */
}),
);
void startClient(serverPath).catch((error) => {
const message = error instanceof Error ? error.message : String(error);
void vscode.window.showErrorMessage(`Failed to start SPICE LSP: ${message}`);
});
}
export async function deactivate() {
await client?.stop();
}
Development workflow
One-time setup
pixi add nodejs=22 # if not already in pixi.toml
cd editors/vscode
npm install
Add devDependencies in package.json:
{
"devDependencies": {
"@types/vscode": "^1.90.0",
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"@vscode/vsce": "^3.0.0"
},
"dependencies": {
"vscode-languageclient": "^9.0.0"
}
}
Daily loop
# terminal 1 — Rust server
pixi run build
# terminal 2 — extension
cd editors/vscode
npm run watch # esbuild --watch
# VS Code: F5 to launch Extension Development Host
Set absolute path to debug binary in Development Host settings:
{
"spiceLsp.serverPath": "/path/to/spice-lsp/target/debug/spice-lsp"
}
Or use launch.json env / preLaunchTask to build Rust first.
Verify integration
Follow Demo and testing VS Code section.
Bundling the server binary
The Marketplace extension ships a platform-specific binary inside the .vsix under bin/<platform>-<arch>/:
| Platform id | OS / arch | Notes |
|---|---|---|
linux-x64 | Linux x86_64 | Linked for glibc 2.31+ (Ubuntu 20.04 / Debian 11+) via Zig |
linux-arm64 | Linux ARM64 | Same glibc 2.31 floor |
darwin-x64 | macOS Intel | |
darwin-arm64 | macOS Apple Silicon | |
win32-x64 | Windows x64 |
There is no win32-arm64 bundle today. Unsupported platforms must set spiceLsp.serverPath or put spice-lsp on PATH.
Linux CI builds use scripts/zig-cc-*.sh so binaries from ubuntu-latest (glibc 2.39) still load on hosts with glibc 2.31. A plain cargo build on a newer distro may require a newer glibc — use the Zig wrappers for release artifacts.
At activation, the extension resolves the binary in this order:
spiceLsp.serverPathsetting (if set)- Bundled binary at
bin/<platform>-<arch>/spice-lspinside the extension - Local dev paths under
target/debugortarget/release(F5 from this repo) spice-lsponPATH
Package locally
Build a release binary for the current platform and create a .vsix:
pixi run build
pixi run ext-package
# output: editors/vscode/spice-lsp-0.2.0.vsix
Install side-loaded:
code --install-extension editors/vscode/spice-lsp-0.2.0.vsix
CI release workflow
The Release VS Code extension workflow runs on every push to main (and on manual workflow_dispatch / vscode-v* tags):
- Bumps the patch version in
editors/vscode/package.jsonand commits it tomain - Cross-compiles
spice-lspfor all supported platform ids - Assembles a single
.vsixcontaining every platform binary - Uploads the VSIX as a GitHub Actions artifact
- Creates a GitHub Release tagged
vscode-v<version> - Publishes to the VS Code Marketplace from the same package job (
VSCE_PATrequired)
| Strategy | Pros | Cons |
|---|---|---|
| User PATH | Simplest for local dev | Poor UX for end users |
Setting serverPath | Flexible | Manual configuration |
Bundle in .vsix | Works offline; Marketplace default | Larger artifact; CI builds all platforms |
| Download from GitHub Releases on activate | Small VSIX | Requires network on first run |
The Marketplace release uses bundle in .vsix.
TextMate grammar
Syntax highlighting ships as:
editors/vscode/syntaxes/spice.tmLanguage.json
Registered in package.json:
"grammars": [{
"language": "spice",
"scopeName": "source.spice",
"path": "./syntaxes/spice.tmLanguage.json"
}]
The grammar colors * / ; / $ comments, . directives, instance lines, and numeric literals. Tree-sitter-based highlighting via nvim-treesitter is separate; VS Code can adopt semantic tokens when the LSP advertises semanticTokensProvider (future).
Marketplace listing icon: editors/vscode/media/icon.png (package.json "icon" field).
Publishing
One-time Marketplace setup
Do this once before the first CI publish succeeds:
- Sign in to the Visual Studio Marketplace publisher management page with a Microsoft account.
- Create a publisher whose Publisher ID matches
editors/vscode/package.json(AmirhosseinDavoodyin this repo). The ID is permanent and must match exactly. - Create a Personal Access Token in Azure DevOps (not portal.azure.com):
- Open https://dev.azure.com and sign in with the same Microsoft account used for the Marketplace publisher.
- If prompted, create a free Azure DevOps organization (any name is fine; it is only a container for the PAT).
- Click your profile avatar (top right) → Personal access tokens
Direct link: https://dev.azure.com/_usersSettings/tokens - + New Token:
- Name: e.g.
vscode-marketplace - Organization: All accessible organizations
- Expiration: choose a duration you are willing to rotate
- Scopes: Custom defined → enable Marketplace → Manage
- Name: e.g.
- Create and copy the token immediately (it is shown once)
- In the GitHub repo: Settings → Secrets and variables → Actions → New repository secret
- Name:
VSCE_PAT - Value: the Azure DevOps PAT from step 3
- Name:
- Confirm Marketplace listing metadata is ready in
editors/vscode/:README.md(Marketplace landing page — include a Quick start so users know what to do after install)LICENSE(MITmatchespackage.json)publisher,displayName,description,engines.vscode
Optional local dry-run before relying on CI:
pixi run build
pixi run ext-package
# output: editors/vscode/spice-lsp-<version>.vsix
# packaging fails if the esbuild bundle is missing LanguageClient or terminateProcess.sh
The extension is esbuild-bundled (npm run compile → out/extension.js with vscode-languageclient inlined). Package and publish with vsce … --no-dependencies so the VSIX does not ship node_modules.
Release from CI (automatic)
Every push to main runs Release VS Code extension:
- Patch-bumps
editors/vscode/package.json(e.g.0.2.0→0.2.1) - Commits
chore(vscode): bump extension to <version>(viaGITHUB_TOKEN, which does not re-trigger the workflow) - Builds platform binaries, packages the VSIX, creates GitHub Release
vscode-v<version>, and runsvsce publish
Manual options:
- Actions tab → Release VS Code extension → Run workflow — optional bump + publish flags
- Push a tag
vscode-v*from your machine to publish the version already inpackage.json(no auto-bump)
The workflow always uploads the .vsix as an Actions artifact.
Release manually
pixi run build
pixi run ext-package
cd editors/vscode
# bump version first if this version was already published
npm version patch --no-git-tag-version
npx vsce publish --no-dependencies --packagePath "$(ls -t *.vsix | head -1)" # requires VSCE_PAT
Pre-publish checklist:
- Publisher ID matches
package.jsonpublisherfield -
VSCE_PATrepository secret configured -
README.mddescribes bundled-binary behavior for end users -
LICENSEaligned with repo (MITinpackage.json) -
engines.vscodeset to tested minimum version
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Server not starting | Binary not on PATH / wrong serverPath / unsupported platform | Set spiceLsp.serverPath, then SPICE LSP: Restart Server; check Output → SPICE Language Server |
version 'GLIBC_2.3x' not found | Host glibc older than the binary | Update to a Marketplace build linked for glibc 2.31+, or build locally and set spiceLsp.serverPath |
spiceLsp.restartServer / spiceLsp.setDialect not found | Extension never activated, activate hung on LSP start, or Marketplace build predates the command (setDialect needs ≥ 0.2.10) | Update the extension; reload the window; open a .cir/.sp file or run the command (auto-activates). Prefer builds that register commands before awaiting client.start() |
| No SPICE Language Server in Output | Extension did not activate | Open a SPICE file or run SPICE LSP: Restart Server / Set Dialect…; check Developer: Show Running Extensions for activation errors |
| Extension activates with module errors | Unbundled VSIX missing node_modules | Use an esbuild-bundled release (vsce package --no-dependencies after npm run compile) |
| No diagnostics | Wrong language id | Ensure file extension maps to spice |
| Stale diagnostics | Server crash | Check Output → SPICE Language Server; restart server |
| Wrong binary arch | Download mismatch / unsupported platform | Pick correct release asset or build from source |
Beyond VS Code
The same spice-lsp binary enables other editors:
| Editor | Integration |
|---|---|
| Neovim | vim.lsp.enable or lspconfig custom server |
| Helix | language-server.spice-lsp in user config |
| Zed | Community extension calling the binary |
VS Code is the reference client; keep editor-specific code out of Rust.
Related
- MVP guide — M6 extension milestone
- Demo and testing
- LSP features
- Architecture
SPICE Netlist LSP and Formatter Design Document
1. Executive Summary
This document defines the system design, capabilities, and requirements for a Language Server Protocol (LSP) and Formatter tailored for SPICE (Simulation Program with Integrated Circuit Emphasis) netlists. The goal is to improve developer velocity, reduce syntax errors, and enforce stylistic consistency across analog and mixed-signal simulation workflows.
2. System Capabilities
2.1 Language Server Protocol (LSP) Capabilities
The LSP server implements the following capabilities to provide real-time IDE feedback:
- Syntax and Semantic Diagnostics:
- MVP / syntax: Missing
.ends, bad line continuations, parse errors. - v0.2: Duplicate component identifiers, undefined model/subcircuit references.
- v0.5 / connectivity: Dangling nodes (single terminal connection) and floating nets (no DC path to ground). Severity warning; configurable. See Dialect reference and net semantics.
- MVP / syntax: Missing
- Navigation (Go to Definition & Find References):
- Resolve references for subcircuits (
.subckt) and models (.model). - Map parameter definitions (
.param) to their usages in expressions.
- Resolve references for subcircuits (
- Autocomplete and Snippets:
- Offer context-aware suggestions for basic elements (R, C, L, diodes, transistors).
- Provide templates for simulation directives (e.g.,
.tran,.ac,.dc,.temp).
- Hover Documentation:
- v0.3 (file-local): Subcircuit pin order, in-file model parameters.
- v0.5 (dialect reference): Curated documentation for directives (
.tran,.ac),.optionkeywords, element types, and common expressions — authored per dialect in areference/corpus the LSP loads at runtime, not hard-coded in server logic. Coverage grows over time as you add entries for Ngspice, LTspice, and HSPICE.
- Document Outline (Symbols):
- Index hierarchical structures, isolating
.subcktblocks,.modeldefinitions, and control blocks.
- Index hierarchical structures, isolating
2.2 Formatter Capabilities
The formatting engine processes netlist files to enforce consistent layouts:
- Columnar Alignment: Align component names, nodes, model references, values, and parameters in tabular columns.
- Case Normalization: Enforce uppercase, lowercase, or camelCase formatting for keywords, control options, and unit suffixes.
- Continuation-Line Standardization: Format multi-line statements wrapped with the
+character using predictable indentation. - Comment Block Structuring: Standardize inline comments (using
;or$) and line-start comments (*).
3. System Requirements
3.1 Functional Requirements
- Dialect Support: The parser must support standard SPICE variants, specifically LTspice, Ngspice, and HSPICE syntax.
- Performance: Code diagnostics must execute in under 100ms on files up to 50,000 lines.
- Robustness: The parser must gracefully recover from syntax errors to continue indexing subsequent parts of the file.
3.2 Technical & Architectural Requirements
- Parser Technology: Implement the parser using a formal grammar parser-generator like Tree-sitter. This ensures incremental parsing capability for low-latency editing.
- Communication Protocol: Conform strictly to the official LSP specification (JSON-RPC 2.0).
- Distribution: Package the LSP as a standalone executable (compiled Go or Rust) with no external runtimes required.
4. MVP Strategy (Ship Before Full Feature Set)
The full capability list in sections 2–3 is the north star. The first deliverable is a narrow MVP that proves the pipeline in VS Code before investing in navigation, completion, or formatting.
4.0.1 MVP definition
In scope:
- Rust workspace:
spice-parser+spice-lspbinary - Tree-sitter grammar for a single dialect (Ngspice first)
- Syntax diagnostics only (parse errors, unclosed
.subckt) - LSP:
initialize, text document sync,publishDiagnostics - VS Code extension that spawns the binary over stdio
Out of scope for MVP:
- Formatter, completion, go-to-definition, references
- Dialect reference corpus and reference-powered hover
- Floating-net / dangling-node analysis
- Multi-dialect reference namespaces and
.includeresolution
4.0.2 MVP milestones
| # | Milestone | Verification |
|---|---|---|
| M1 | Cargo workspace + pixi tasks | pixi run cargo build |
| M2 | Minimal Tree-sitter grammar | Corpus / fixture parse tests |
| M3 | Parser → diagnostics API | pixi run cargo test -p spice-parser |
| M4 | tower-lsp stdio server | LSP integration test |
| M5 | test-data/ fixtures | CI green on pixi run test |
| M6 | VS Code extension | F5 → squiggles on invalid netlist |
| M7 | Documented demo script | Demo and testing |
Detailed steps: MVP guide.
4.0.3 Demo and test strategy
| Layer | Method |
|---|---|
| Grammar | Tree-sitter corpus + Rust fixture tests |
| Parser | Golden diagnostics on test-data/invalid/* |
| LSP | JSON-RPC harness over stdio (subprocess or mock client) |
| VS Code | Extension Development Host (F5), Problems panel |
| CI | pixi install && pixi run test on every push |
Manual smoke: open test-data/invalid/unclosed-subckt.cir, fix .ends, confirm diagnostic clears.
4.0.4 VS Code as primary client
Distribution path:
- Development:
spiceLsp.serverPathpoints attarget/debug/spice-lsp - Early adopters: side-load
.vsixbuilt withvsce package - General availability: Marketplace publish with platform-specific binary download or bundle
Extension architecture (thin Node client, Rust server): VS Code integration.
Post-MVP features roll out in phases documented in Architecture and LSP features. Deep semantics (reference library + net connectivity) are specified in Dialect reference and net semantics. Multi-dialect selection, corpus authoring, and hover reuse are specified in Multi-dialect support.
4.0.5 Post-MVP roadmap (summary)
| Phase | Focus |
|---|---|
| v0.2 | Symbol index, navigation, duplicate/undefined warnings |
| v0.3 | Completion, file-local hover |
| v0.4 | Formatter, dialect setting |
| v0.5 | Curated dialect reference → hover; dangling-node and floating-net diagnostics |
5. System Architecture & Implementation
5.1 Implementation Language
The LSP and formatter will be implemented in Rust to satisfy the low-latency and performance requirements (<100ms on 50k lines) while guaranteeing thread safety and memory efficiency without a garbage collector.
5.2 Architecture & Design
The system uses a classic compiler frontend architecture integrated into an event-driven JSON-RPC server:
[IDE Client]
│ (LSP over JSON-RPC 2.0 via StdIO)
▼
┌────────────────────────────────────────────────────────┐
│ LSP Server (tower-lsp) │
│ │ │
│ ├─► [Parser Engine] ─────────────────────────────┐ │
│ │ Incrementally parses buffer into Tree-sitter │ │
│ │ Concrete Syntax Tree (CST) │ │
│ │ │ │
│ ├─► [Diagnostics Analyzer] ◄─────────────────────┘ │
│ │ Syntax (MVP), symbols (v0.2), connectivity (v0.5)│
│ │ │
│ ├─► [Reference Index] ◄── reference/<dialect>/ │
│ │ v0.5: hover docs for directives, options, elems │
│ │ │
│ └─► [Formatter Engine] ◄─────────────────────────┘ │
│ v0.4: columnar alignment & continuation formatting│
└────────────────────────────────────────────────────────┘
- LSP Layer: Handles connection lifecycle, text document synchronization, and capability routing.
- Incremental Parsing: Tree-sitter maintains an active syntax tree; edits re-parse only changed ranges.
- Reference Index (v0.5): Loads structured JSON entries from
reference/per active dialect; powerstextDocument/hoverand enriches completion documentation. Maintained manually over time — see Dialect reference and net semantics. - Net Graph (v0.5): Builds terminal connectivity from instance lines; emits dangling-node and floating-net warnings.
- Formatting Pipeline (v0.4): CST → column rules →
TextEditactions.
5.3 Key Dependencies
tower-lsp: High-level LSP implementation framework for Rust built on Tokio.tree-sitter: Rust bindings to the incremental parsing library.tree-sitter-spice: Custom or community grammar for parsing SPICE dialects.serde/serde_json: Serialization and deserialization of LSP messages.clap: Robust command-line argument parser for standalone formatter CLI execution.
Multi-dialect support design
Design for issue #16: selectable SPICE dialects (default HSPICE), retained Ngspice support, a maintainable system for growing syntax/reference knowledge, and reuse of that data for hover (and later completion).
Status: Phase A (dialect switch) and Phase B (reference corpus + hover) implemented.
Related: Dialect reference and net semantics, LSP features, Architecture.
1. Goals and non-goals
Goals
| Goal | Detail |
|---|---|
| User-selectable dialect | VS Code setting + Command Palette command |
| Default = HSPICE | Matches issue #16; Ngspice remains fully supported |
| Shared knowledge system | One corpus drives diagnostics policy, hover, and (later) completion docs |
| Low-friction authoring | Adding a directive/element/rule is mostly data + a test, not scattered Rust strings |
| Keep current Ngspice behavior | Existing fixtures and diagnostics stay green under ngspice |
Non-goals (this design / first implementation slices)
- Full HSPICE / LTspice grammar parity on day one
- Scraping simulator manuals at runtime (bash-lsp
man/ explainshell style) - Per-file dialect auto-detection from content (may come later as a hint)
- Formatter dialect profiles (still v0.4+)
- Connectivity analysis (still v0.5; dialect-agnostic graph with dialect-specific ground aliases later)
2. Lessons from reference systems
2.1 Ruff (astral-sh/ruff, rules docs)
What they do well
- Single source of truth next to behavior: rule docs live as structured
///sections on the violation type;cargo dev generate-docsprojects them to Markdown for the public site. - Registration table + codegen:
codes.rs+ proc macros produce theRuleenum and metadata accessors so “forgot to register” fails loudly. - CI gates:
generate-all --mode checkandcheck_docs_formatted.pyreject missing sections / stale generated output. - Stable IDs + human names: codes and kebab-case names with redirects.
What we should not copy wholesale
- Embedding long simulator-manual prose in Rust doc comments (wrong medium for SPICE).
- A giant proc-macro registry for hundreds of rules before we need it.
- MkDocs Material as a second doc site unless we later publish a public “reference catalog.”
Takeaway for spice-lsp: treat checked-in structured data as the SSOT (like Ruff treats rule metadata), generate indexes / book pages / Rust embeds from it, and CI-check that generated artifacts match.
2.2 bash-language-server (bash-lsp/bash-language-server)
What they do well
- Layered hover: optional rich external docs (explainshell) → shell
help/man→ file-local symbol comments. - Markdown LSP contract: hover is always
MarkupContentmarkdown. - Memoization of expensive doc lookups.
- Opt-in external services (explainshell off by default).
What we should not copy
- Runtime
man/ network scrape as the primary SPICE reference (manuals are not onman, dialects diverge). - Detecting a “dialect” (shebang) without switching documentation corpora.
- Letting external docs replace file-local hover instead of stacking with it.
Takeaway for spice-lsp: keep a priority chain for hover (reference corpus → file-local CST → nothing), cache the corpus at startup, never require an external service for basic tips.
3. Product behavior
3.1 Dialects
| Id | Label | Initial role |
|---|---|---|
hspice | HSPICE | Default |
ngspice | Ngspice | Current parser/diagnostics baseline; keep working |
ltspice | LTspice | Stub corpus + setting value; grammar/rules grow later |
Unknown dialect values → error diagnostic on initialize / config change, fall back to hspice with a logged warning.
3.2 How the user chooses
- Setting:
spiceLsp.dialect— enumhspice|ngspice|ltspice, defaulthspice. - Command:
SPICE LSP: Set Dialect…— QuickPick; writes the setting (workspace if a folder is open, else user) and restarts / notifies the server. - Status bar (recommended in the same slice): show current dialect; click opens the QuickPick.
Optional later (not required for #16):
# spice-lsp dialect=hspicefile header /.spice-lsp.toml- Infer from path heuristics (
*.spin an HSPICE tree) as a suggestion only
3.3 Client ↔ server contract
initialize.initializationOptions.dialect → "hspice" | "ngspice" | "ltspice"
workspace/didChangeConfiguration → spiceLsp.dialect
Extension always sends the resolved dialect on start and on change. Server stores it per-session (workspace-wide for v1; per-document overrides later).
Changing dialect:
- Re-analyze all open documents with the new dialect profile.
- Republish diagnostics.
- Clear hover/completion caches keyed by dialect.
4. Architecture: one corpus, many consumers
┌─────────────────────────────────────┐
│ reference/ (SSOT, authored data) │
│ schema + per-dialect entries │
└──────────────┬──────────────────────┘
│
pixi run reference-codegen / validate
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
spice-reference crate docs book pages CI snapshots
(embedded index) (optional catalog) (hover / schema)
│
▼
spice-parser ←── DialectProfile (syntax flags, comment rules, …)
│
▼
spice-lsp (diagnostics, hover, later completion)
│
▼
VS Code extension (setting, command, status bar)
Principle: Rust implements mechanisms (parse, index, lookup, render). Humans author knowledge as data under reference/. Parser dialect quirks that cannot be expressed as data yet live in a small DialectProfile table in Rust, keyed by the same dialect ids.
5. Reference corpus (Ruff-inspired authoring)
5.1 Layout
Evolve the plan in § Dialect reference with an explicit shared + override model:
reference/
├── schema.json # JSON Schema for entries
├── _shared/ # constructs common across dialects
│ ├── directives/
│ │ └── subckt.json
│ └── elements/
│ └── R.json
├── hspice/
│ ├── dialect.toml # metadata: display name, aliases, comment styles
│ ├── directives/
│ │ └── option.json # HSPICE-specific or override
│ └── elements/
├── ngspice/
│ ├── dialect.toml
│ └── …
└── ltspice/
├── dialect.toml
└── …
5.2 Entry shape (v1)
{
"id": "hspice.directive.tran",
"kind": "directive",
"name": ".tran",
"summary": "Transient analysis",
"syntax": ".TRAN tstep tstop [tstart [tmax]] [UIC]",
"parameters": [
{ "name": "tstep", "description": "Printing / sampling step.", "units": "s" }
],
"examples": [".TRAN 1p 10n"],
"seeAlso": ["hspice.directive.option"],
"diagnostics": ["spice/unknown-directive"],
"since": "0.3.0",
"dialectNotes": "HSPICE accepts …"
}
Required sections (CI-enforced, Ruff-style): id, kind, name, summary, syntax.
Optional: parameters, examples, seeAlso, diagnostics, deprecated, dialectNotes.
5.3 Merge rules
- Load
_shared/as base for the active dialect. - Overlay
reference/<dialect>/byid/(kind, name)— dialect file wins. - Missing entry → no hover / no completion doc (not an error). Gaps are filled by adding JSON.
5.4 Codegen / validation tasks
| Task | Purpose |
|---|---|
pixi run reference-validate | Load embedded corpus; unit tests for merge/lookup |
pixi run reference-docs | Write mdBook pages under docs/reference/ from the corpus |
pixi run reference-docs-check | CI: fail if catalog markdown drifts from JSON |
Authoring workflow (add a new directive):
- Add/edit
reference/<dialect>/directives/foo.json(or_shared/if universal). pixi run reference-validate.pixi run reference-docs(regenerate catalog chapters).- Add hover snapshot fixture under
test-data/hover/<dialect>/when needed. pixi run test.- No Rust change unless a new kind or lookup path is needed.
This is the spice-lsp analogue of Ruff’s “add rule → docs fall out of metadata,” with JSON as the authoring surface instead of /// comments.
6. DialectProfile (syntax / semantics knobs)
Until grammars fully diverge, keep a Rust profile beside the corpus:
#![allow(unused)]
fn main() {
struct DialectProfile {
id: DialectId,
// Comments recognized for toggle / highlighting hints
line_comment_prefixes: &'static [&'static str], // e.g. hspice: ["*"], ngspice: ["*", ";", "$"]
// Directives treated as unknown → warning vs ignore
unknown_directive_severity: Severity,
// Element letter sets, continuation rules, case sensitivity, …
}
}
v1 behavior
| Concern | hspice | ngspice | ltspice |
|---|---|---|---|
| Parse grammar | Current line-oriented grammar (shared) | Same | Same |
| Comment styles (docs / future toggle) | * primary | *, ;, $ | $ / * (document; refine later) |
| Semantic diagnostics | Same engines; corpus may gate “unknown directive” lists | Current fixtures | Minimal |
| Hover | hspice corpus (+ _shared) | ngspice corpus | ltspice stub |
Later: dialect-specific Tree-sitter grammars or grammar injections only when shared tokens are insufficient (do not fork three full grammars prematurely).
7. Hover design (bash-lsp layering + corpus)
7.1 Resolution order
cursor token
1. dialect reference lookup (kind + name + active dialect)
2. file-local hover (subckt pins, in-file .model / .param) // v0.3 slice
3. null
Never call out to the network. Render markdown:
### `.tran` — Transient analysis
**Dialect:** HSPICE
.TRAN tstep tstop [tstart [tmax]] [UIC]
| Parameter | Description | Units |
|-----------|-------------|-------|
| tstep | … | s |
**Examples**
- `.TRAN 1p 10n`
7.2 Mapping cursor → entry
- Classify line / token: directive name, element type letter,
.optionkeyword, etc. (reuse / extend CST + symbol index). - Build key
(dialect, kind, normalized_name). - Lookup in embedded index; try dialect overlay then
_shared.
7.3 Same data for completion (follow-on)
Completion items attach documentation from the same entry. No parallel doc strings in Rust.
8. VS Code extension changes
| Item | Change |
|---|---|
package.json settings | spiceLsp.dialect enum, default hspice |
| Command | spiceLsp.setDialect → QuickPick |
| Status bar | HSPICE / Ngspice / LTspice |
LanguageClient init | initializationOptions: { dialect } |
| Middleware / config listener | On dialect change → DidChangeConfiguration + optional restart if needed |
| Marketplace README | Document default HSPICE; how to switch to Ngspice |
TextMate grammar stays shared initially; dialect-specific highlighting can wait.
9. Phased delivery
Phase A — Dialect switch plumbing (unblocks #16 UX)
- Setting + command + status bar; default hspice.
- Server accepts dialect; re-analyzes on change.
DialectProfilestub; behavior still matches today’s Ngspice parser for bothhspiceandngspiceexcept profile metadata / empty HSPICE corpus.- Docs: default dialect, how to switch.
Exit criteria: user can switch dialects; Ngspice fixtures pass with spiceLsp.dialect=ngspice; HSPICE default does not break open/diagnostics smoke tests.
Phase B — Reference crate + hover
- Stand up
reference/schema +_shared+ starterhspice/ngspiceentries (small set:.subckt,.ends,.model,.param,.tran,R,C,X). - Grow HSPICE overlays for analysis/control directives requested in follow-ups (
.data, multi-mode.dc,.op, plus.ac/.measure/.probe/.lib/ …). spice-referencecrate + validate/codegen pixi tasks.- Implement
textDocument/hoverwith layered resolution. - Snapshot tests per dialect.
Phase C — Dialect-sensitive diagnostics / grammar
- Unknown-directive / option lists from corpus.
- Comment / continuation profile differences.
- Split grammar only where needed; grow LTspice.
Phase D — Catalog docs
- Generate mdBook pages under
docs/reference/from the embedded corpus (spice-reference-catalog). - Pixi:
reference-docs(write) andreference-docs-check(CI drift guard). - Still one SSOT: edit JSON under
reference/, then regenerate.
Issue #16 is satisfied by Phase A + a clear path through B; B can ship in the same epic as follow-up PRs.
10. Testing strategy
| Layer | Tests |
|---|---|
| Schema | Every JSON entry validates; required sections present |
| Merge | Overlay wins; shared fallback works |
| LSP | initialize with dialect; didChangeConfiguration republishes |
| Hover | Fixtures per dialect; missing entry → null |
| Regression | Existing Ngspice stdio tests run with explicit ngspice |
| Extension | Setting default is hspice; command updates config (smoke / manual) |
11. Risks and decisions
| Topic | Decision |
|---|---|
| Default dialect | HSPICE per #16 (overrides earlier docs that said Ngspice default) |
| One grammar vs many | One shared grammar in Phase A–B; profile flags first |
| Doc authoring medium | JSON under reference/, not Rust comments |
| External doc services | Out of scope; optional later, opt-in only |
| LTspice | Enum + stub corpus early; deep support later |
| Breaking change | Default dialect change may surprise Ngspice users — document prominently; one-click switch |
12. Open questions (resolve during Phase A implementation)
- Should dialect be workspace-only or allow per-file override in v1?
- Do we embed the full corpus in the binary, or load from an extension-relative path for faster iteration?
- Minimum HSPICE starter set for Phase B (which directives matter first for the author’s flows)?
- Status bar vs Command Palette only for v1 UX?
13. Implementation checklist (when coding starts)
-
spiceLsp.dialect+spiceLsp.setDialect+ status bar - Server session dialect + config update path
-
DialectProfile+ Ngspice parity tests underngspice - Update LSP features, limitations, Marketplace README for default HSPICE
- Scaffold
reference/schema.json,_shared/,hspice/,ngspice/ - Expand HSPICE corpus:
.data,.dc(sweep modes),.op, plus common controls (.ac,.measure,.probe,.lib, …) -
spice-reference+ validate/codegen tasks - Hover provider + snapshots
- Close #16 when Phase A is shipped and Phase B is scheduled/linked
- Phase C: dialect-sensitive diagnostics / grammar splits
- Phase D: catalog docs from JSON (
docs/reference/,reference-docs/reference-docs-check)
14. References
- Issue: https://github.com/amirhosseindavoody/spice-lsp/issues/16
- Ruff rules: https://docs.astral.sh/ruff/rules/
- Ruff repo (docs codegen):
crates/ruff_dev/src/generate_docs.rs,scripts/generate_mkdocs.py,CONTRIBUTING.md(“Adding a new rule”) - bash-language-server hover:
server/src/server.ts,server/src/util/sh.ts,server/src/analyser.ts(explainshell) - Existing spice-lsp plan: Dialect reference and net semantics