Skip to content

Support multiple directives per row in CSV import - #141

Merged
yugui merged 3 commits into
mainfrom
claude/admiring-euler-wwvbyf
Jun 26, 2026
Merged

Support multiple directives per row in CSV import#141
yugui merged 3 commits into
mainfrom
claude/admiring-euler-wwvbyf

Conversation

@yugui

@yugui yugui commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Enable CSV import programs to emit multiple directives from a single row, and conditionally emit directives based on row data.

Summary

This change extends the emit form in csv-sexp to accept zero or more directive arguments (previously exactly one), allowing a single CSV row to produce multiple directives in order. It also introduces when and unless forms for conditional directive emission, enabling optional balance assertions, multi-entry rows, and row-level filtering without explicit if branches.

Key Changes

  • Variadic emit: (emit X Y Z) now emits directives from X, Y, and Z in order. Nil-valued directives (skipped slots) contribute nothing. If any argument soft-fails, the entire row is dropped with diagnostics, preventing partial directive sets that could leave the ledger inconsistent.

  • Conditional directives: New (when cond X) and (unless cond X) forms gate a single directive, yielding X's directive when the condition holds (or fails for unless) and a nil directive otherwise. These compose directly with variadic emit for optional secondary entries or row suppression.

  • NilDirective and EmitDirectives: Added csvbase.NilDirective() to represent a skipped emit slot and csvbase.EmitDirectives(keys...) to handle variadic emission with atomic row semantics (all-or-nothing on soft-fail).

  • Compiler support: Updated csvsexp compiler to parse multiple emit arguments, evaluate them as directive keys, and compile when/unless with both literal and dynamic conditions. Literal conditions fold at compile time; dynamic conditions use csvbase.If to select between the directive and a nil directive.

Testing

Added comprehensive test coverage:

  • TestEmitMultiple: Two directives from one row (transaction + balance)
  • TestEmitWhenOptionalSecond: Optional balance gated by condition
  • TestEmitWhenSkipsRow: Row suppression when condition fails
  • TestEmitUnless: Inverted condition semantics
  • TestEmitSoftFailDropsRow: Atomic row semantics on soft-fail
  • TestEmitDirectives_*: Builder-level tests for variadic emission and nil handling
  • Golden test case multidirective: Real-world example with optional balance assertions

Documentation

Updated doc.go to clarify emit's new variadic behavior, nil semantics, and the role of when/unless in conditional emission without explicit else branches.

https://claude.ai/code/session_01HCxDfeymPeAmaqg4KpuoEu

claude added 3 commits June 26, 2026 12:01
A CSV row sometimes needs to produce more than one directive — a
transaction plus a running-balance assertion — or none at all when a
row should be conditionally suppressed. The previous (emit X) accepted
exactly one directive, so these shapes were inexpressible even though
the underlying csvbase pipeline already returns a slice of directives
per row.

Make emit variadic: (emit d1 ... dn) emits one directive per non-nil
argument, in order. To gate individual slots without an else branch,
add (when cond X) / (unless cond X), which yield X's directive or a
nil directive (a skipped slot). Together these cover the realized
cases — one directive (unchanged), an optional second, a conditional
skip, or a fixed group — while keeping the common one-directive form
identical.

Design intent: the directive count stays statically determined, which
fits this DSL's compile-time, fixed-shape-per-row model. A first-class
directive-list value or Lisp-style cons cells were considered and
rejected: with static counts they add machinery without expressive
gain, and cons does not fit the strongly-typed value model.

A soft-fail in any emit slot drops the whole row rather than emitting a
partial set, since a half-built multi-directive group could leave the
ledger inconsistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCxDfeymPeAmaqg4KpuoEu
EmitDirective emitted exactly one directive — the single-key case of
EmitDirectives, which now exists. Keeping both is redundant surface
area for the same terminal, so drop the singular form and let callers
that emit one directive pass one key. EmitTx is retained: it accepts a
transaction key directly and stays the ergonomic terminal for the
common single-transaction shape without an AsDirective lift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCxDfeymPeAmaqg4KpuoEu
main added the (cond ...) multi-way conditional to csvsexp (#140);
this branch added variadic (emit ...) plus (when ...)/(unless ...).
Both extend the same dispatch, doc sections, and golden shape list,
so the resolution keeps every new form and testdata case side by side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCxDfeymPeAmaqg4KpuoEu
@yugui
yugui merged commit b4911c8 into main Jun 26, 2026
7 checks passed
@yugui
yugui deleted the claude/admiring-euler-wwvbyf branch June 26, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants