Skip to content

padl / padr: optional pad-char arg for zero-pad / dot-leader#303

Merged
danieljohnmorris merged 6 commits into
mainfrom
fix/padl-padr-pad-char
May 16, 2026
Merged

padl / padr: optional pad-char arg for zero-pad / dot-leader#303
danieljohnmorris merged 6 commits into
mainfrom
fix/padl-padr-pad-char

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

padl and padr previously hard-coded space as the pad char. That makes the
common zero-padding pattern (sortable numeric keys, fixed-column log lines)
clunky enough that personas reach for cat + manual string-building, which
costs tokens and retries. This adds an optional 3rd arg: padl s w pc /
padr s w pc, pad char defaults to space when omitted.

Manifesto framing: the cost of a missing 3rd arg here was one extra builtin
call and a few lines of glue per occurrence, multiplied by every persona who
hit it. Adding it costs two opcodes, four lines on the SPEC table, and zero
back-compat risk because the 2-arg form keeps its existing bytecode shape.

Repro

Before:

padl "42" 5         -- "   42"   (space-padded, OK)
padl "42" 5 "0"     -- ILO-T006: padl expects 2 args, got 3

After:

padl "42" 5 "0"     -- "00042"
padr "x" 4 "."      -- "x..."
padl "hi" 5 "·"     -- "···hi"     (1 Unicode scalar = 1 width unit)
padl "x" 5 "ab"     -- ILO-R009: padl pad char must be a 1-character string

What is in the diff

Six commits, one per layer so each is reviewable on its own:

  1. vm new OP_PADLC / OP_PADRC (167 / 168) using the existing 2-instruction
    data-word encoding (precedent: OP_SLC, OP_CLAMP, OP_RGXSUB). Added to
    find_block_leaders. New jit_padlc / jit_padrc helpers + 5 lib unit tests
    covering happy path, multichar reject, non-string reject, Unicode scalar.
  2. cranelift dispatch in both compile_cranelift.rs (AOT) and
    jit_cranelift.rs (JIT): helper FuncId, declaration, classifier list,
    dispatch + skip_next, JIT symbol registration. AOT picks the symbols up
    from libilo.a via the linker.
  3. interpreter tree-walker accepts 2 or 3 args, validates pad char as a
    1-Unicode-scalar string with ILO-R009.
  4. verify padl / padr joined to the arity-overload list. Third arg
    type-checked against t (ILO-T013 if it isn't).
  5. test 7 new cross-engine tests covering zero-pad, dot-pad,
    already-wider, Unicode scalar, multichar reject, empty reject, 2-arg
    back-compat.
  6. docs SPEC.md table + examples/pad.ilo with new -- run / -- out
    assertions (picked up by tests/examples_engines.rs). ai.txt and
    skills/ilo/SKILL.md regenerated from SPEC.md via build.rs.

Test plan

  • cargo test --release --features cranelift (5215 passed, 0 failed, 45 ignored)
  • cargo clippy --release --features cranelift --lib -- -W clippy::all clean
  • tests/regression_pad.rs 16/16 across tree, VM, Cranelift JIT
  • AOT cranelift tests pass with fresh target/release/libilo.a
  • examples/pad.ilo -- run / -- out assertions pass on every engine
  • 6-pass rust-review (ownership, error handling, anti-patterns, perf, unsafe, idioms) actioned: repeat_n modernisation, identical-block merge, SAFETY comment on new as_heap_ref()

Follow-ups

None. Cranelift's nil-on-bad-input behaviour for pad-char (mirrors the
existing engine-divergence on negative width) is the same gap the existing
pad_negative_width_errors test already calls out as a deferred follow-up;
harmonising the two together is out of scope for this PR.

@danieljohnmorris danieljohnmorris force-pushed the fix/padl-padr-pad-char branch from cebd7fb to 14005bd Compare May 16, 2026 12:17
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 83.09179% with 35 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/vm/compile_cranelift.rs 16.66% 15 Missing ⚠️
src/vm/mod.rs 90.76% 12 Missing ⚠️
src/interpreter/mod.rs 68.00% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris force-pushed the fix/padl-padr-pad-char branch from 14005bd to b9088f3 Compare May 16, 2026 12:22
Two-instruction sequence carrying a 4th register in the data word for the
pad-char operand. Mirrors the existing OP_SLC / OP_CLAMP / OP_RGXSUB
shape so find_block_leaders and the JIT prefetcher already know how to
skip the trailing data word.

Pad char must be a 1-Unicode-scalar string; multi-char, empty, and
non-text raise a Type error with span. Default behaviour for the 2-arg
form is unchanged (OP_PADL / OP_PADR still emit a 3-register
instruction with no data word, padding with spaces).

Includes JIT helpers (jit_padlc / jit_padrc) for the Cranelift bridge
and 5 lib tests covering happy path, multichar reject, non-string
reject, and a Unicode scalar pad char.
Wires the new opcodes through both Cranelift compilers (JIT for
--run-cranelift, ObjectModule for ahead-of-time). Both need: a helper
FuncId on HelperFuncs, declare_helper for the 4-arg signature, the
opcode added to the non-numeric output classifier, the opcode pair in
the dispatch loop with skip_next=true so the data-word instruction
isn't decoded as an opcode, and (JIT only) symbol registration so
JITBuilder can resolve jit_padlc / jit_padrc at runtime.

AOT picks up the symbols from libilo.a via the standard linker because
jit_padlc / jit_padrc are exposed with #[unsafe(no_mangle)] pub(crate)
extern "C".
Extends the tree-walker's padl / padr branch to accept 2 or 3 args.
Third arg, when present, must be a 1-Unicode-scalar string; anything
else is an ILO-R009 with the same wording the JIT helper uses for the
analogous failure modes.

Default 2-arg behaviour (pad with space) is unchanged.
Adds padl / padr to the existing arity-overload list (get / rd / wr /
post / map / fld) so calls with 3 args type-check. Third arg, when
present, must be compatible with t; an incompatible type raises
ILO-T013 with arg-3-specific wording.
Seven new tests run on every engine (tree, VM, Cranelift) covering:

- zero-pad numeric (the persona-reported use case)
- right-pad with dots
- already-wider short-circuit with custom pad char
- Unicode scalar pad char (catches byte-vs-char confusion)
- multi-char pad reject (tree + VM error; Cranelift returns nil per
  the same engine-divergence as the negative-width case above)
- empty pad string reject
- 2-arg form still pads with space (back-compat lock-in)
SPEC.md gets two new rows for the 3-arg overload with example pad
chars (0 for sortable zero-padded keys, . for dot-leader alignment).
examples/pad.ilo gets zp and dr helpers plus updated row to show
zero-padded numeric output; tests/examples_engines.rs picks them up
automatically and runs the new -- run / -- out assertions on every
engine.

ai.txt and skills/ilo/SKILL.md regenerate from SPEC.md via build.rs;
both are git-tracked and idempotence-checked in CI.
@danieljohnmorris danieljohnmorris force-pushed the fix/padl-padr-pad-char branch from b9088f3 to 1ccbc61 Compare May 16, 2026 12:32
@danieljohnmorris danieljohnmorris merged commit e62cdb7 into main May 16, 2026
4 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/padl-padr-pad-char branch May 16, 2026 12:32
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.

1 participant