lispexp 0.1.1

A pure-Rust reader (lexer + parser) for S-expression syntax across many Lisp dialects
Documentation
# Code-vs-data walker: a pruning visitor with a binary class and quasiquote-depth flip rules

## Context

Consumers such as lisplens do syntactic, code-vs-data-aware project search
(lisplens ADR-0010): descend into code, skip quoted data. lispexp already models
the distinction (quote → data; quasiquote → data except nested unquote, which
flips back to code) via `Prefixed` datums, so consumers should not reimplement
the flip logic. What was missing is a ready-made traversal that surfaces the
classification, and a decided ruling for every prefix — not just quote and
quasiquote.

## Decision

**A pruning visitor is the core primitive, not a plain iterator.** The walker
invokes a callback `visit(&Datum, Class) -> Descend | Skip` so a consumer can
prune an entire subtree (the actual search need: don't descend into quoted
data). An ergonomic pre-order `Iterator` adapter may be layered on later, but the
visitor is primary because pruning cannot be expressed by a bare iterator.

**`Class` is binary — `Code | Data`.** "Discarded" and "inert" nodes collapse to
`Data` (a code search prunes them all the same); a third class isn't worth the
complexity. The classification criterion is uniformly "can this be evaluated?"

**Prefix ruling table** (default: top-level and list items are `Code`):

- `Quote` inner → `Data` (deep); `Quasiquote` inner → `Data` with unquotes
  flipping back per depth.
- `VarQuote`/`FunctionQuote` (`#'foo`) → `Code` (a resolved var/function
  reference); `Deref` (`@x`), `Splice` (Janet `;x`), `ReadEval` (`#.x`),
  `HashFn` (`#(...)`) → `Code`.
- `HashLiteral` (`#(1 2 3)`, `#u8(...)`, tagged `#inst …`) → `Data`;
  `LabelRef` (`#n#`) → `Data`; `Discard` (`#_`/`#;`) → `Data`.
- `Meta` (`^m x`), `Mutable` (Janet `@{}`), `Label` inner → **context-transparent**
  (inherit the parent's class).
- `ReaderConditional`: guarded forms are context-transparent; the feature-test
  expression is `Data`.

**Nested quasiquote uses a depth counter, with quote as an absolute barrier.**
The walker carries a quasiquote-depth along the path: `Quasiquote` `+1`,
`Unquote`/`UnquoteSplicing` `-1`; a node is `Code` iff depth `== 0` *and* not
under a hard `Quote`. `Quote` establishes an absolute `Data` region that unquote
cannot escape (quote is not quasiquote). An unmatched unquote is clamped (never
negative) and keeps the surrounding class, consistent with fault-tolerant,
reader-only structure. This classifies double-unquote (`,,c` → `Code`)
correctly, which a boolean flag cannot.

## Consequences

- Consumers get correct, ready-made code-vs-data traversal with pruning, and
  never reimplement the flip rules.
- The ruling table and depth model are public semantics consumers will depend
  on; the binary class and the "can this be evaluated?" criterion keep it
  predictable.
- Consistent with reader-only scope (ADR-0001) and the reader-macro model
  (ADR-0002/ADR-0016): the walker interprets structure already in the tree, and
  evaluates nothing.