Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

StageChapters
Setup and ship MVPGetting StartedPrinciplesMVP GuideDemo and Testing
Understand the systemArchitectureLSP Features
Long-term directionDialect Reference and Net SemanticsDialect reference catalogFormatterLimitations
VS CodeVS 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

ToolPurpose
pixiManages Rust, Node (for the VS Code extension), and build tasks
GitClone 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)

  1. Build the LSP binary: pixi run build
  2. Open the extension folder: editors/vscode
  3. Install JS dependencies: npm install
  4. Press F5 to launch an Extension Development Host with the SPICE extension loaded
  5. Open demo.cir and 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:

EditorConfiguration
Neovimlspconfig custom server block with cmd = { "spice-lsp" }
Helix[language-server.spice-lsp] in languages.toml
ZedExtension or lsp settings (once published)

File extensions to associate: .cir, .sp, .spf, .net, .ckt (dialect-dependent).

If you are new to the repo, follow this order:

  1. Read Principles — know what is in and out of scope for MVP
  2. Follow MVP guide — implement or review each milestone
  3. Use Demo and testing — verify each layer before adding features
  4. Read Architecture — understand where new code belongs
  5. Skim Dialect reference and net semantics — long-term hover and connectivity goals (not MVP)

Next steps

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:

  1. Immediate syntax feedback (MVP)
  2. Jump to definitions and a useful outline (v0.2)
  3. Completion and quick in-file hover (v0.3)
  4. Consistent formatting and dialect selection (v0.4)
  5. 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

  1. Fast feedback while editing — Diagnostics feel instant on typical netlists (< 5k lines). Tree-sitter incremental parsing is the foundation.
  2. Works offline — Single static binary; no cloud services; no simulator required for IDE features.
  3. 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.
  4. Catch connectivity mistakes before simulation — Flag dangling nodes and floating nets as warnings when analysis is confident enough (v0.5).
  5. Editor-agnostic core — All language logic lives in the LSP binary. VS Code is the first client, not the only one.
  6. Testable at every layer — Parser fixtures, reference schema tests, hover snapshots, and LSP integration tests in CI.

Non-goals

Non-goalWhy
Running SPICE simulationsUse Ngspice/LTspice externally
Schematic captureNetlist text only
Auto-generating reference from PDF manualsYou author reference/ deliberately; quality over coverage on day one
Full ERC/DRCFloating-net checks are heuristic helpers, not sign-off tools
Replacing simulator errorsWe 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 .DATA value 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):

DeferredTarget phase
Dialect reference hoverv0.5
Floating / dangling node analysisv0.5
Completion, file-local hoverv0.3
Navigation, outlinev0.2
Formatterv0.4

Ship MVP first, then follow the phase order in Architecture.

Success criteria for MVP

  1. pixi run test passes parser and LSP integration tests
  2. Invalid netlist in the Extension Development Host shows a syntax diagnostic
  3. Fixing the error clears the diagnostic without restarting the editor
  4. 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.

LayerResponsibilityShips in
1. ParseTree-sitter CST, syntax diagnosticsMVP
2. IndexSymbols, scopes, cross-referencesv0.2
3. AssistCompletion, basic hover from the filev0.3
4. Deep semanticsDialect reference docs, net connectivity, formatterv0.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 / directoryRoleFirst shipped
crates/spice-lspLSP server, JSON-RPC, document storeMVP
crates/spice-parserParsing, symbol index, diagnostics, formatMVP (parse only)
crates/spice-referenceLoad and query dialect reference entriesv0.3
tree-sitter-spice/Grammar and query filesMVP
reference/Curated JSON (or YAML) per dialect — authored over timev0.5
editors/vscode/VS Code extension clientMVP
test-data/Fixtures for syntax, semantics, hover snapshotsMVP

LSP server lifecycle

  1. Client connects via stdio; sends initialize with client capabilities and dialect option.
  2. Server responds with capabilities for the current phase (MVP: incremental sync only).
  3. Document open/change updates an in-memory map of open buffers.
  4. On each change (debounced ~150 ms):
    • Re-parse with Tree-sitter
    • Run diagnostic passes for the enabled phase
    • Send textDocument/publishDiagnostics with the document version
  5. 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.
  6. 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)

  1. Parse buffer → CST
  2. Collect ERROR / MISSING nodes and hand-written checks (e.g. unclosed .subckt)
  3. Map to LSP Diagnostic (Error)

Phase 2 — Symbol index (v0.2)

Walk the CST to build:

  • Subcircuit and model definitions
  • Component instances and .param bindings

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
PhaseUser-visible outcome
MVPSyntax error squiggles in VS Code
v0.2Outline, go to definition, duplicate / undefined symbol warnings
v0.3Element and directive completion; hover on subcircuits and models in file
v0.4Format document; choose Ngspice / LTspice / HSPICE
v0.5Hover 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

MetricTarget
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 editRe-parse changed regions only

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 / featureMVPv0.2v0.3v0.4v0.5
initialize / initialized
shutdown / exit
textDocument/didOpen / didChange / didClose
textDocument/publishDiagnosticssyntax
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

SourceExampleSeverity
Unclosed .subcktmissing .ends for subcircuit XError
Parse ERROR nodeunexpected tokenError
Missing CST childexpected node listError

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 kindSPICE construct
Namespace.subckt block
Class.model
Variable.param
FieldInstance 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:

CodeExampleSeverity
spice/duplicate-nameduplicate component name 'R1'Warning
spice/unknown-modelmodel 'nfet' not definedWarning

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:

  1. Curated entry from reference/ for the active dialect (_shared fallback)
  2. File-local detail for .subckt / .model / .param symbols
  3. 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 seeAlso links

You maintain this corpus over time; the LSP only indexes and renders it. Authoring guide: Dialect reference and net semantics.

Connectivity diagnostics

CodeExampleSeverity
spice/dangling-nodenode 'bias' is connected to only one device terminalWarning
spice/floating-netnet 'internal' has no DC path to groundWarning

Published alongside syntax diagnostics in publishDiagnostics. Configurable via spiceLsp.diagnostics.* settings.

Client configuration

SettingTypeDefaultAvailable
spiceLsp.dialectstring"hspice"dialect switch (see Multi-dialect design)
spiceLsp.diagnostics.danglingNodesbooleantruev0.5+
spiceLsp.diagnostics.floatingNetsbooleantruev0.5+
spiceLsp.groundNodesstring[]["0","gnd","GND"]v0.5+
spiceLsp.trace.serverstring"off"MVP+

Testing

Each phase adds integration tests for newly advertised capabilities. MVP priorities:

  1. initialize returns expected capabilities
  2. Open invalid document → diagnostics notification
  3. Edit → updated diagnostics

v0.2 adds:

  1. documentSymbol returns hierarchical outline for .subckt blocks

  2. definition on subcircuit reference jumps to .subckt definition

  3. references on subcircuit definition lists definition + usages; includeDeclaration: false omits the definition

  4. Semantic fixtures (duplicate-instance.cir, unknown-subckt.cir) produce warning codes

  5. Rapid didChange events coalesce into a single diagnostics publish after the debounce window

  6. (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

InputOutput
LSP textDocument/formatting requestTextEdit[] covering full document
LSP textDocument/rangeFormattingTextEdit[] for selection (or expanded logical statement)
CLI spice-lsp format --check file.cirExit 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):

  1. Parse buffer to CST (reuse parser)
  2. Walk relevant nodes; compute column widths per alignment group
  3. Render each line to a string buffer
  4. Diff old vs new text → minimal TextEdit set (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+)

OptionValuesDefault
indentWidth2, 42
keywordCaseupper, lower, preserveupper
alignColumnstrue, falsetrue
maxLineWidthnumber120 (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.

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/references honors includeDeclaration
  • Marketplace extension with bundled binaries, TextMate highlighting, and restart command
  • File associations for .cir, .sp, .spf, .net, and .ckt

Current limitations

LimitationWorkaround
Ngspice-oriented parsingAvoid LTspice/HSPICE-specific syntax until v0.4
No connectivity analysisManual review; dangling/floating checks arrive in v0.5
Single-file analysis.include not followed
No formatter or completionManual 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 binarySet 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 dialectsDialect-specific parse quirks land in later phases; hover/docs already switch
Bare numeric lines outside .DATAPrefer + 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:

LimitationDetail
Single fileIgnores nets defined across .include until cross-file index exists
Ground aliasesDefaults to 0, gnd, GND; exotic ground names may need config
False positivesIntentionally open probe points may warn until suppression exists
Not full ERCDoes not check layout, EM, or foundry rules
Ideal elementsVoltage sources and unusual topologies need careful graph rules

These warnings supplement — not replace — simulator and layout review.

Dialect differences

AreaNgspiceLTspiceHSPICE
Comments*, ;, $$ common*
Directives / optionsBaseline corpusOverrides 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 didChange are debounced (~150 ms); navigation requests re-analyze on demand so the index stays current

Reporting issues

Include:

  1. Minimal netlist snippet
  2. Dialect (Ngspice / LTspice / HSPICE)
  3. 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)
.tranSyntax, parameters, units, Ngspice vs LTspice notes
.option keywordMeaning, default, valid values
M (MOSFET line)Terminal order, common parameters
{expression} in .paramAllowed 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

  1. Client sends active dialect via initializationOptions or spiceLsp.dialect setting (default hspice; command + status bar to switch — design).
  2. On textDocument/hover, the server maps the cursor CST node to a reference key (e.g. directive name, element letter, option token).
  3. Server renders Hover markdown from the entry: summary, syntax block, parameter table, examples.
  4. 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:

  1. Add or edit JSON under reference/<dialect>/ or reference/_shared/.
  2. Run pixi run reference-validate to load and exercise the embedded corpus.
  3. Run pixi run reference-docs to regenerate the Dialect reference catalog in the book.
  4. Add or update hover snapshot tests when behavior changes.
  5. Ship with the binary (corpus is embedded at compile time via the spice-reference build script).

Prefer small, focused files over one giant manual. Link related entries with seeAlso.

Phase placement

PhaseReference scope
MVPNone
v0.3Inline hover from CST + curated reference/ lookup (HSPICE overlays for .data / .dc / .op and common controls)
v0.5Broader 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

TermMeaningExample
GroundNode 0, gnd, GND, or dialect-specific ground aliasesC1 out 0 1u
Dangling nodeAppears on exactly one device terminal in the analyzed scopeNet bias only on R1 in bias with nothing on bias elsewhere
Floating netHas connections but no DC path to ground through R, L, V, I, or defined groundsUnconnected 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

CodeMessageSeverity
spice/dangling-nodenode 'bias' is connected to only one device terminalWarning
spice/floating-netnet 'internal' has no DC path to groundWarning

Attach diagnostics to the node token on the instance line when possible. Offer a single diagnostic per net, not one per terminal.

Configuration (future)

SettingDefaultEffect
spiceLsp.diagnostics.danglingNodestrueEnable dangling-node pass
spiceLsp.diagnostics.floatingNetstrueEnable floating-net pass
spiceLsp.groundNodes["0", "gnd", "GND"]Treat as ground for path search

Limitations

  • Ignores .include files 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

PhaseConnectivity scope
MVPNone
v0.2Optional: duplicate instance names only
v0.5Dangling nodes and floating nets in single-file scope

Testing

LayerTest
ReferenceSchema validation on all reference/**/*.json
ReferenceHover snapshot: fixture + cursor → markdown
Connectivitytest-data/semantic/dangling-node.cir → one spice/dangling-node
Connectivitytest-data/semantic/floating-net.cir → one spice/floating-net
LSPIntegration test publishes warnings after open

See Demo and testing.

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

  1. Edit or add JSON under reference/_shared/ or reference/<dialect>/.
  2. Run pixi run reference-validate.
  3. Run pixi run reference-docs to regenerate these pages.
  4. CI runs pixi run reference-docs-check so the book stays in sync.

Design notes: Multi-dialect support.

Dialects

DialectSetting valueEntries (effective)Catalog
Shared base16Shared
HSPICEhspice25HSPICE
Ngspicengspice16Ngspice
LTspiceltspice16LTspice

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

NameKindSummary
.acdirectiveSmall-signal AC frequency analysis
.dcdirectiveDC sweep analysis
.enddirectiveEnd of netlist
.endsdirectiveEnd a subcircuit definition
.icdirectiveSet initial node voltages
.includedirectiveInclude another netlist file
.modeldirectiveDefine a device model
.opdirectiveDC operating-point analysis
.paramdirectiveDefine a named parameter
.printdirectivePrint analysis results as a table
.subcktdirectiveBegin a subcircuit definition
.tempdirectiveSet circuit temperature(s)
.trandirectiveTransient analysis
CelementCapacitor
RelementResistor
XelementSubcircuit instance

Directives

.ac

shared.directive.ac — Small-signal AC frequency analysis

.ac {LIN|DEC|OCT} np fstart fstop
ParameterDescriptionUnits
`LINDECOCT`
npNumber of points (LIN) or points per decade/octave (DEC/OCT).
fstartStart frequency.Hz
fstopStop 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
ParameterDescriptionUnits
srcnameIndependent source, parameter, or TEMP to sweep.
startSweep start value.
stopSweep stop value.
stepIncrement between points.

Examples

  • .dc V1 0 5 0.1
  • .dc TEMP 0 100 25

.end

shared.directive.end — End of netlist

.end [comment]
ParameterDescriptionUnits
commentOptional trailing comment.

Examples

  • .end
  • .end my_circuit

.ends

shared.directive.ends — End a subcircuit definition

.ends [name]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenamePath 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumeric value or expression.

Examples

  • .param rload=1k
  • .param pi=3.14159

.print

shared.directive.print — Print analysis results as a table

.print {DC|TRAN|AC} ov1 [ov2 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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]
ParameterDescriptionUnits
tstepSuggested printing / sampling increment.s
tstopFinal simulation time.s
tstartOptional start of printing.s
tmaxOptional maximum timestep.s
UICUse initial conditions.

Examples

  • .tran 1n 100n
  • .tran 1u 1m 0 10u UIC

Elements

C

shared.element.C — Capacitor

Cname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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

NameKindSummarySource
.acdirectiveAC frequency analysis (HSPICE)dialect
.alterdirectiveRerun simulation with alternate parameters or analyses (HSPICE)dialect
.datadirectiveDefine a named data table for data-driven sweeps (HSPICE)dialect
.dcdirectiveDC analysis and sweeps (HSPICE)dialect
.enddirectiveTerminate the netlist (HSPICE)dialect
.endsdirectiveEnd a subcircuit definitionshared
.icdirectiveForce initial node voltages (HSPICE)dialect
.includedirectiveInclude an external file (HSPICE)dialect
.libdirectiveCall a named library section (HSPICE)dialect
.measuredirectiveMeasure delay, rise/fall, extrema, and other results (HSPICE)dialect
.modeldirectiveDefine a device modelshared
.nodesetdirectiveSuggest initial guesses for DC convergence (HSPICE)dialect
.noisedirectiveNoise analysis paired with .AC (HSPICE)dialect
.opdirectiveDC operating-point report (HSPICE)dialect
.optiondirectiveSet HSPICE simulation optionsdialect
.paramdirectiveDefine parameters and expressions (HSPICE)dialect
.printdirectivePrint tabulated analysis results (HSPICE)dialect
.probedirectiveSelect waveforms saved for post-processing (HSPICE)dialect
.subcktdirectiveBegin a subcircuit definitionshared
.tempdirectiveSet simulation temperature(s) (HSPICE)dialect
.tfdirectiveDC small-signal transfer function (HSPICE)dialect
.trandirectiveTransient analysis (HSPICE)dialect
CelementCapacitorshared
RelementResistorshared
XelementSubcircuit instanceshared

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
ParameterDescriptionUnits
typeLIN, DEC, or OCT frequency spacing.
npPoints (LIN) or points per decade/octave.
fstart / fstopFrequency range.Hz
SWEEP …Optional nested parameter/source sweep.
DATA=datanmData-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]
ParameterDescriptionUnits
title_stringOptional 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
ParameterDescriptionUnits
datanmData-block name referenced by DATA=datanm on .DC / .AC / .TRAN.
pnamParameter column name (must be declared with .PARAM).
pvalRow 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
ParameterDescriptionUnits
var1Primary sweep variable: independent source, element/model parameter, or TEMP.
start1 / stop1 / incr1Linear sweep bounds and step (positional or START=/STOP=/STEP=).
type npLIN|DEC|OCT with point count for parameterized / nested sweeps.
SWEEP var2 …Optional nested (second) sweep over another source or parameter.
DATA=datanmData-driven sweep using a .DATA block.
MONTE=valMonte 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]
ParameterDescriptionUnits
commentOptional 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]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenameFile 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
ParameterDescriptionUnits
filenameLibrary file path.
entrynameSection 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 …
ParameterDescriptionUnits
`TRANDCAC`
nameMeasurement result name written to the measure output.
TRIG / TARGTrigger 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
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]
ParameterDescriptionUnits
outvarOutput voltage node or pair, e.g. V(out) or V(out,ref).
srcnamIndependent source treated as the noise input reference.
intervalOptional 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 ...]]
ParameterDescriptionUnits
formatOptional report style: ALL, BRIEF, CURRENT, DEBUG, NONE, VOLTAGE.
timeOptional 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 ...]
ParameterDescriptionUnits
keywordOption 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumber, ‘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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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
ParameterDescriptionUnits
outvarOutput variable, e.g. V(out) or I(Vload).
srcnamIndependent 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]
ParameterDescriptionUnits
tstepPrinting increment.s
tstopStop time.s
tstartOptional start of printing.s
tmaxOptional maximum timestep.s
UICUse 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...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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

NameKindSummarySource
.acdirectiveSmall-signal AC frequency analysisshared
.dcdirectiveDC sweep analysisshared
.enddirectiveEnd of netlistshared
.endsdirectiveEnd a subcircuit definitionshared
.icdirectiveSet initial node voltagesshared
.includedirectiveInclude another netlist fileshared
.modeldirectiveDefine a device modelshared
.opdirectiveDC operating-point analysisshared
.paramdirectiveDefine a named parametershared
.printdirectivePrint analysis results as a tableshared
.subcktdirectiveBegin a subcircuit definitionshared
.tempdirectiveSet circuit temperature(s)shared
.trandirectiveTransient analysis (Ngspice)dialect
CelementCapacitorshared
RelementResistorshared
XelementSubcircuit instanceshared

Directives

.ac

shared.directive.ac — Small-signal AC frequency analysis

.ac {LIN|DEC|OCT} np fstart fstop
ParameterDescriptionUnits
`LINDECOCT`
npNumber of points (LIN) or points per decade/octave (DEC/OCT).
fstartStart frequency.Hz
fstopStop 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
ParameterDescriptionUnits
srcnameIndependent source, parameter, or TEMP to sweep.
startSweep start value.
stopSweep stop value.
stepIncrement between points.

Examples

  • .dc V1 0 5 0.1
  • .dc TEMP 0 100 25

.end

shared.directive.end — End of netlist

.end [comment]
ParameterDescriptionUnits
commentOptional trailing comment.

Examples

  • .end
  • .end my_circuit

.ends

shared.directive.ends — End a subcircuit definition

.ends [name]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenamePath 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumeric 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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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]
ParameterDescriptionUnits
tstepSuggested printing increment.s
tstopFinal time.s
tstartOptional start of printing.s
tmaxOptional maximum timestep.s
UICUse 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...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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

NameKindSummarySource
.acdirectiveSmall-signal AC frequency analysisshared
.dcdirectiveDC sweep analysisshared
.enddirectiveEnd of netlistshared
.endsdirectiveEnd a subcircuit definitionshared
.icdirectiveSet initial node voltagesshared
.includedirectiveInclude another netlist fileshared
.modeldirectiveDefine a device modelshared
.opdirectiveDC operating-point analysisshared
.paramdirectiveDefine a named parametershared
.printdirectivePrint analysis results as a tableshared
.subcktdirectiveBegin a subcircuit definitionshared
.tempdirectiveSet circuit temperature(s)shared
.trandirectiveTransient analysis (LTspice)dialect
CelementCapacitorshared
RelementResistorshared
XelementSubcircuit instanceshared

Directives

.ac

shared.directive.ac — Small-signal AC frequency analysis

.ac {LIN|DEC|OCT} np fstart fstop
ParameterDescriptionUnits
`LINDECOCT`
npNumber of points (LIN) or points per decade/octave (DEC/OCT).
fstartStart frequency.Hz
fstopStop 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
ParameterDescriptionUnits
srcnameIndependent source, parameter, or TEMP to sweep.
startSweep start value.
stopSweep stop value.
stepIncrement between points.

Examples

  • .dc V1 0 5 0.1
  • .dc TEMP 0 100 25

.end

shared.directive.end — End of netlist

.end [comment]
ParameterDescriptionUnits
commentOptional trailing comment.

Examples

  • .end
  • .end my_circuit

.ends

shared.directive.ends — End a subcircuit definition

.ends [name]
ParameterDescriptionUnits
nameOptional 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 ...]
ParameterDescriptionUnits
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'
ParameterDescriptionUnits
filenamePath 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 ...)
ParameterDescriptionUnits
mnameModel name referenced by instances.
typeModel 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 ...]
ParameterDescriptionUnits
nameParameter identifier.
valueNumeric 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 ...]
ParameterDescriptionUnits
`DCTRANAC`
ovOutput 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 ...]
ParameterDescriptionUnits
nameSubcircuit 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 ...]
ParameterDescriptionUnits
t1Temperature 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]
ParameterDescriptionUnits
TstepPrinting increment.s
TstopStop 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...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueCapacitance.F

Examples

  • C1 out 0 1p

R

shared.element.R — Resistor

Rname n1 n2 value [params...]
ParameterDescriptionUnits
n1Positive node.
n2Negative node.
valueResistance.ohm

Examples

  • R1 in out 1k
  • Rload out 0 50

X

shared.element.X — Subcircuit instance

Xname n1 [n2 ...] subckt_name [params...]
ParameterDescriptionUnits
n1...Nodes connected to subcircuit ports.
subckt_nameName 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:

TaskCommandPurpose
buildcargo build --releaseProduction binary
build-devcargo buildFast debug builds
testcargo test --workspaceAll unit + integration tests
test-parsercargo test -p spice-parserParser fixtures only
test-lspcargo test -p spice-lspLSP integration tests
spice-lspcargo run -p spice-lspRun language server (stdio)
fmtcargo fmt --allRust formatting
clippycargo clippy --workspace -- -D warningsLint
mdbook-buildmdbook build docsStatic doc site
mdbook-servemdbook serve docs -n 127.0.0.1 -p 3000Live doc preview
ext-installnpm install in editors/vscodeExtension deps
ext-compilenpm run compileBuild extension TS
ext-package./scripts/package-vscode-extension.shBundle local binary + .vsix
reference-validatecargo test -p spice-reference --libValidate embedded corpus
reference-docsspice-reference-catalog writeRegenerate docs/reference/ from corpus
reference-docs-checkspice-reference-catalog checkFail 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)

GitHub Actions workflow stages:

  1. pixi install
  2. pixi run fmt -- --check
  3. pixi run clippy
  4. pixi run test
  5. pixi 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:

  1. Runs pixi run mdbook-build
  2. Pushes the output to the gh-pages branch

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-pages branch (/ 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

ProblemFix
cargo: command not foundRun pixi install; use pixi run cargo
Tree-sitter build.rs failsEnsure C compiler available in pixi env (pixi add gcc on Linux)
Extension can’t find binarySet spiceLsp.serverPath in VS Code settings to absolute path
LSP hangs on startNormal for stdio servers waiting for JSON-RPC input

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:

  1. Press F5 in VS Code to open the Extension Development Host
  2. Open a .cir file with a deliberate syntax error
  3. See a diagnostic squiggle within one edit cycle
  4. Fix the error and watch the squiggle disappear
  5. Run pixi run test and 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 .DATA tables 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:

  1. Run Tree-sitter parse
  2. Walk tree for ERROR / MISSING nodes
  3. Add hand-written checks: .subckt without 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:

EventAction
initializeReturn capabilities (incremental sync)
did_openStore document, analyze, publish_diagnostics
did_changeApply edits, re-analyze, publish
did_closeRemove 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:

  1. Initialize
  2. Open invalid file
  3. Read notification until publishDiagnostics
  4. 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:

  1. pixi run build
  2. Open VS Code on editors/vscode, press F5
  3. In Extension Development Host: File → Opentest-data/invalid/unclosed-subckt.cir
  4. Point out diagnostic message
  5. Add missing .ends, save — diagnostic clears
  6. Run pixi run test in 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
  • .include file 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

PRContents
PR 1M1 + M2 + parser fixtures
PR 2M3 + M5 parser tests
PR 3M4 + LSP integration tests
PR 4M6 + M7 docs update

Each PR should keep pixi run test green (or skip tests until the crate exists, then enable).


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)

  1. Build Rust binary: pixi run build
  2. cd editors/vscode && npm install && npm run compile
  3. Open editors/vscode in VS Code
  4. Run and Debug → Launch Extension (F5)
  5. In the new [Extension Development Host] window:
    • Open test-data/invalid/unclosed-subckt.cir
    • Confirm Problems panel lists diagnostics
    • Fix syntax, confirm clearing

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:

StepActionExpected
1pixi run testAll tests pass
2F5 extensionDevelopment Host opens
3Open invalid netlistRed squiggle + Problems entry
4Edit to fixDiagnostic disappears
5Open valid netlistNo errors
6SPICE 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.


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:

FieldPurpose
engines.vscodeMinimum VS Code version
activationEventsonLanguage:spice, onCommand:spiceLsp.restartServer, onCommand:spiceLsp.setDialect
main./out/extension.js (esbuild bundle)
contributes.languagesRegister spice language id and file extensions
contributes.configurationspiceLsp.serverPath, spiceLsp.trace.server, spiceLsp.dialect
contributes.commandsspiceLsp.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 idOS / archNotes
linux-x64Linux x86_64Linked for glibc 2.31+ (Ubuntu 20.04 / Debian 11+) via Zig
linux-arm64Linux ARM64Same glibc 2.31 floor
darwin-x64macOS Intel
darwin-arm64macOS Apple Silicon
win32-x64Windows 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:

  1. spiceLsp.serverPath setting (if set)
  2. Bundled binary at bin/<platform>-<arch>/spice-lsp inside the extension
  3. Local dev paths under target/debug or target/release (F5 from this repo)
  4. spice-lsp on PATH

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):

  1. Bumps the patch version in editors/vscode/package.json and commits it to main
  2. Cross-compiles spice-lsp for all supported platform ids
  3. Assembles a single .vsix containing every platform binary
  4. Uploads the VSIX as a GitHub Actions artifact
  5. Creates a GitHub Release tagged vscode-v<version>
  6. Publishes to the VS Code Marketplace from the same package job (VSCE_PAT required)
StrategyProsCons
User PATHSimplest for local devPoor UX for end users
Setting serverPathFlexibleManual configuration
Bundle in .vsixWorks offline; Marketplace defaultLarger artifact; CI builds all platforms
Download from GitHub Releases on activateSmall VSIXRequires 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:

  1. Sign in to the Visual Studio Marketplace publisher management page with a Microsoft account.
  2. Create a publisher whose Publisher ID matches editors/vscode/package.json (AmirhosseinDavoody in this repo). The ID is permanent and must match exactly.
  3. Create a Personal Access Token in Azure DevOps (not portal.azure.com):
    1. Open https://dev.azure.com and sign in with the same Microsoft account used for the Marketplace publisher.
    2. If prompted, create a free Azure DevOps organization (any name is fine; it is only a container for the PAT).
    3. Click your profile avatar (top right) → Personal access tokens
      Direct link: https://dev.azure.com/_usersSettings/tokens
    4. + 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
    5. Create and copy the token immediately (it is shown once)
  4. In the GitHub repo: Settings → Secrets and variables → Actions → New repository secret
    • Name: VSCE_PAT
    • Value: the Azure DevOps PAT from step 3
  5. 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 (MIT matches package.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 compileout/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:

  1. Patch-bumps editors/vscode/package.json (e.g. 0.2.00.2.1)
  2. Commits chore(vscode): bump extension to <version> (via GITHUB_TOKEN, which does not re-trigger the workflow)
  3. Builds platform binaries, packages the VSIX, creates GitHub Release vscode-v<version>, and runs vsce 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 in package.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.json publisher field
  • VSCE_PAT repository secret configured
  • README.md describes bundled-binary behavior for end users
  • LICENSE aligned with repo (MIT in package.json)
  • engines.vscode set to tested minimum version

Troubleshooting

SymptomLikely causeFix
Server not startingBinary not on PATH / wrong serverPath / unsupported platformSet spiceLsp.serverPath, then SPICE LSP: Restart Server; check Output → SPICE Language Server
version 'GLIBC_2.3x' not foundHost glibc older than the binaryUpdate to a Marketplace build linked for glibc 2.31+, or build locally and set spiceLsp.serverPath
spiceLsp.restartServer / spiceLsp.setDialect not foundExtension 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 OutputExtension did not activateOpen a SPICE file or run SPICE LSP: Restart Server / Set Dialect…; check Developer: Show Running Extensions for activation errors
Extension activates with module errorsUnbundled VSIX missing node_modulesUse an esbuild-bundled release (vsce package --no-dependencies after npm run compile)
No diagnosticsWrong language idEnsure file extension maps to spice
Stale diagnosticsServer crashCheck Output → SPICE Language Server; restart server
Wrong binary archDownload mismatch / unsupported platformPick correct release asset or build from source

Beyond VS Code

The same spice-lsp binary enables other editors:

EditorIntegration
Neovimvim.lsp.enable or lspconfig custom server
Helixlanguage-server.spice-lsp in user config
ZedCommunity extension calling the binary

VS Code is the reference client; keep editor-specific code out of Rust.

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.
  • Navigation (Go to Definition & Find References):
    • Resolve references for subcircuits (.subckt) and models (.model).
    • Map parameter definitions (.param) to their usages in expressions.
  • 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), .option keywords, element types, and common expressions — authored per dialect in a reference/ 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 .subckt blocks, .model definitions, and control blocks.

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-lsp binary
  • 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 .include resolution

4.0.2 MVP milestones

#MilestoneVerification
M1Cargo workspace + pixi taskspixi run cargo build
M2Minimal Tree-sitter grammarCorpus / fixture parse tests
M3Parser → diagnostics APIpixi run cargo test -p spice-parser
M4tower-lsp stdio serverLSP integration test
M5test-data/ fixturesCI green on pixi run test
M6VS Code extensionF5 → squiggles on invalid netlist
M7Documented demo scriptDemo and testing

Detailed steps: MVP guide.

4.0.3 Demo and test strategy

LayerMethod
GrammarTree-sitter corpus + Rust fixture tests
ParserGolden diagnostics on test-data/invalid/*
LSPJSON-RPC harness over stdio (subprocess or mock client)
VS CodeExtension Development Host (F5), Problems panel
CIpixi 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:

  1. Development: spiceLsp.serverPath points at target/debug/spice-lsp
  2. Early adopters: side-load .vsix built with vsce package
  3. 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)

PhaseFocus
v0.2Symbol index, navigation, duplicate/undefined warnings
v0.3Completion, file-local hover
v0.4Formatter, dialect setting
v0.5Curated 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; powers textDocument/hover and 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 → TextEdit actions.

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

GoalDetail
User-selectable dialectVS Code setting + Command Palette command
Default = HSPICEMatches issue #16; Ngspice remains fully supported
Shared knowledge systemOne corpus drives diagnostics policy, hover, and (later) completion docs
Low-friction authoringAdding a directive/element/rule is mostly data + a test, not scattered Rust strings
Keep current Ngspice behaviorExisting 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-docs projects them to Markdown for the public site.
  • Registration table + codegen: codes.rs + proc macros produce the Rule enum and metadata accessors so “forgot to register” fails loudly.
  • CI gates: generate-all --mode check and check_docs_formatted.py reject 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 MarkupContent markdown.
  • 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 on man, 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

IdLabelInitial role
hspiceHSPICEDefault
ngspiceNgspiceCurrent parser/diagnostics baseline; keep working
ltspiceLTspiceStub 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

  1. Setting: spiceLsp.dialect — enum hspice | ngspice | ltspice, default hspice.
  2. Command: SPICE LSP: Set Dialect… — QuickPick; writes the setting (workspace if a folder is open, else user) and restarts / notifies the server.
  3. Status bar (recommended in the same slice): show current dialect; click opens the QuickPick.

Optional later (not required for #16):

  • # spice-lsp dialect=hspice file header / .spice-lsp.toml
  • Infer from path heuristics (*.sp in 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:

  1. Re-analyze all open documents with the new dialect profile.
  2. Republish diagnostics.
  3. 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

  1. Load _shared/ as base for the active dialect.
  2. Overlay reference/<dialect>/ by id / (kind, name) — dialect file wins.
  3. Missing entry → no hover / no completion doc (not an error). Gaps are filled by adding JSON.

5.4 Codegen / validation tasks

TaskPurpose
pixi run reference-validateLoad embedded corpus; unit tests for merge/lookup
pixi run reference-docsWrite mdBook pages under docs/reference/ from the corpus
pixi run reference-docs-checkCI: fail if catalog markdown drifts from JSON

Authoring workflow (add a new directive):

  1. Add/edit reference/<dialect>/directives/foo.json (or _shared/ if universal).
  2. pixi run reference-validate.
  3. pixi run reference-docs (regenerate catalog chapters).
  4. Add hover snapshot fixture under test-data/hover/<dialect>/ when needed.
  5. pixi run test.
  6. 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

Concernhspicengspiceltspice
Parse grammarCurrent line-oriented grammar (shared)SameSame
Comment styles (docs / future toggle)* primary*, ;, $$ / * (document; refine later)
Semantic diagnosticsSame engines; corpus may gate “unknown directive” listsCurrent fixturesMinimal
Hoverhspice corpus (+ _shared)ngspice corpusltspice 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

  1. Classify line / token: directive name, element type letter, .option keyword, etc. (reuse / extend CST + symbol index).
  2. Build key (dialect, kind, normalized_name).
  3. 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

ItemChange
package.json settingsspiceLsp.dialect enum, default hspice
CommandspiceLsp.setDialect → QuickPick
Status barHSPICE / Ngspice / LTspice
LanguageClient initinitializationOptions: { dialect }
Middleware / config listenerOn dialect change → DidChangeConfiguration + optional restart if needed
Marketplace READMEDocument 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.
  • DialectProfile stub; behavior still matches today’s Ngspice parser for both hspice and ngspice except 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 + starter hspice / ngspice entries (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-reference crate + validate/codegen pixi tasks.
  • Implement textDocument/hover with 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) and reference-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

LayerTests
SchemaEvery JSON entry validates; required sections present
MergeOverlay wins; shared fallback works
LSPinitialize with dialect; didChangeConfiguration republishes
HoverFixtures per dialect; missing entry → null
RegressionExisting Ngspice stdio tests run with explicit ngspice
ExtensionSetting default is hspice; command updates config (smoke / manual)

11. Risks and decisions

TopicDecision
Default dialectHSPICE per #16 (overrides earlier docs that said Ngspice default)
One grammar vs manyOne shared grammar in Phase A–B; profile flags first
Doc authoring mediumJSON under reference/, not Rust comments
External doc servicesOut of scope; optional later, opt-in only
LTspiceEnum + stub corpus early; deep support later
Breaking changeDefault dialect change may surprise Ngspice users — document prominently; one-click switch

12. Open questions (resolve during Phase A implementation)

  1. Should dialect be workspace-only or allow per-file override in v1?
  2. Do we embed the full corpus in the binary, or load from an extension-relative path for faster iteration?
  3. Minimum HSPICE starter set for Phase B (which directives matter first for the author’s flows)?
  4. 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 under ngspice
  • 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