Repository navigation
Toolbox: a decompile verb, completing the compile/decompile round-trip CLI - #66
Conversation
- decompile <file.nap> [-o output.td] [--system-type=T] [--stdout] fronts the existing Decompiler, the inverse of compile; direction defaults swap the extension. - compile gains --bare (compile the .td as the complete byte specification, no CAN+NSR sentinels - the mode under which nap -> td -> nap round-trips byte-exact) and --system-type=naplps|prodigy|telidon. - compile no longer tokenizes the source twice. - Verified: beer.nap -> decompile -> compile --bare reproduces the original bytes exactly.
- compile parses once instead of tokenizing twice; a source beginning with the decompiler's "// Decompiled from .nap" header warns unless --bare, since the round-trip only reproduces bytes in bare mode. - Wrong-direction guards by extension: decompile refuses a .td input and compile refuses a .nap input, each pointing at the other verb. NAPLPS is a permissive byte format - source text decodes to a "valid" run of text-drawing commands, so wrong-direction input cannot be detected from the decode itself; the extension is the honest signal. - Decompile aborts (nonzero) only on unrecoverable parse errors - an opcode absent from the in-use table, a truncated definition. A clean decode is NOT proof the input was NAPLPS, so decode warnings are summarized to stderr as a best-effort note rather than passed off as a faithful reconstruction. - Diagnostics print to stderr with nonzero exits across the argument-validation and lexer/parser/compiler error paths; the byte-exact nap -> td -> --bare nap round-trip holds across the whole Examples corpus.
FoxCouncil
left a comment
There was a problem hiding this comment.
Two things to fix before merge: an unhandled crash on an empty --output= for compile, and a silent clobber of an existing .td on the default decompile path. Details inline.
Verified the round-trip claim independently through the CLI. 374/374 .nap files in Examples/ come back byte-identical from decompile then compile --bare, the forced-Prodigy dirs included, and the 10 that don't are .td sources the direction guard correctly rejects. The --stdout path is byte-exact too, and decompiled output is pure ASCII across the whole corpus so there is no console-encoding hazard. Dropping the double tokenize and aborting on lex errors is safe, the Lexer only ever emits Error severity. The Decompiler doc correction is accurate, the old text was stale. The nullable system type is behavior-neutral in tree since no caller passes one.
|
|
||
| outputPath ??= IOPath.ChangeExtension(inputPath, ".nap"); | ||
|
|
||
| if (!toStdout && OutputCollidesWithInput(inputPath, outputPath)) |
There was a problem hiding this comment.
Reject an empty --output= / -o= value in TryParseConvertOptions.
This call sits outside the try, so compile s.td --output= throws an unhandled ArgumentException out of GetFullPath and exits 0xE0434352 with a stack trace. The same input on decompile exits 1 with a clean Error: The path is empty. because the collision check there is inside the try. The description says every argument-validation path exits nonzero with a stderr diagnostic; this one doesn't.
| return 0; | ||
| } | ||
|
|
||
| outputPath ??= IOPath.ChangeExtension(inputPath, ".td"); |
There was a problem hiding this comment.
Refuse the implicit target when it already exists, or gate it behind --force.
decompile foo.nap with no -o silently overwrites foo.td. Examples/ has nine such pairs (star, clock, hello, house, snowflake, spirograph, palette_demo, bas, brs) where the .td is hand-authored with comments. Ran it against a copy of star.nap and the hand-written header is gone, replaced by the decompiler banner, exit 0, no warning. OutputCollidesWithInput protects the input file but not an unrelated existing one.
| Console.WriteLine(" Telidraw export building.nap --palette-anim --loop --frames=300 anim.gif"); | ||
| Console.WriteLine(" Telidraw compile drawing.td -o drawing.nap"); | ||
| Console.WriteLine(" Telidraw decompile picture.nap # writes picture.td"); | ||
| Console.WriteLine(" Telidraw decompile picture.nap --stdout | less"); |
There was a problem hiding this comment.
This pipe doesn't work on Windows. Telidraw.csproj is WinExe, so a console shell doesn't wait on the process and the pipe is unreliable. Pre-existing, and it hits export and diff the same way, just newly advertised here.
- An empty -o / --output= value no longer crashes: it reached GetFullPath outside the try and threw an unhandled ArgumentException; TryParseConvertOptions now rejects it like any bad argument (nonzero, stderr), matching the decompile side. - A DEFAULTED output target (foo.nap -> foo.td) refuses to clobber an existing file - several Examples .td/.nap are hand-authored - unless --force/-f; an explicit -o remains the caller's choice and overwrites. - The input/output collision check now resolves symlinks (RealPath), so an -o that aliases the input through a link is caught, not just ./.. normalization; comparison is case-insensitive only on Windows, ordinal elsewhere, so `compile Foo.td -o foo.td` is not falsely rejected on Linux. - A stray positional after options (`compile in.td --bare extra.td`) now reports "the input file must come before options" instead of the misleading "unknown option"; genuine unknown flags still report as such. - An explicit system type on compile (the CLI's --system-type path) now has a test: bare compiling a coordinate-bearing source under Telidon vs NAPLPS produces different bytes, exercising the previously-untested ApplySystemDefaults branch and forced re-parse; null behaves as NAPLPS. - Help is accurate: --system-type is documented per-direction (compile sets the target, decompile forces how an ambiguous stream decodes), --force is listed, and --stdout notes its Windows GUI-subsystem caveat on export too; success messages use ASCII -> not U+2192.
|
Thanks for the independent round-trip verification and the precise repros - all six are fixed in the amended commit.
954/954 non-VR/Perf tests pass. Two things I noticed during the pass that are pre-existing in other verbs, out of scope for this convert CLI - flagging so they're not lost, happy to send a separate PR:
|
FoxCouncil
left a comment
There was a problem hiding this comment.
Re-verified the fixed head locally. All six are genuinely fixed: the empty --output= exits 1 with a clean diagnostic on both verbs, the defaulted decompile target refuses and --force overrides it, the path comparison is platform-correct, the help text carries the pipe caveat, the blank line is gone. Corpus round-trip still comes back 374/374 byte-identical and 91/91 Telidraw tests pass.
One regression rode in with the clobber fix, plus two small things. Details inline.
…e the arrow - The --force clobber guard is decompile-only again: compile's defaulted .nap is a build artifact and refusing the second run broke the ordinary edit-compile loop; decompile keeps the guard because its defaulted target can be hand-authored .td source. Help and doc comments say so explicitly; --force stays accepted on compile as a no-op. - CompileBare_ExplicitSystemType_ShapesEncoding now includes a raw statement, so _emittedRaw is set and Compile() reaches the forced FromBytes re-parse with the explicit system type as well as the ApplySystemDefaults branch (execution verified: a temporary throw inside the re-parse branch fails the test). The byte-equality assertions do not discriminate a byte-preserving re-parse regression; the doc comment claims only what runs. - The success-message arrow is back to U+2192; the ASCII swap was an unrequested drive-by.
The Toolbox already exposes
compile,export,info, anddiff; this adds the missing inverse ofcompile-decompile(nap -> td) - so the.tdlanguage has a full command-line round-trip, using the existingTelidraw.Decompilerthe library already ships and tests.What it does
decompile <file.nap> [-o out.td] [--system-type=T] [--stdout]turns a NAPLPS stream back into editable Telidraw source.compileis refactored to parse once (it tokenized twice), and warns when handed a source that starts with the decompiler's own header unless--bare(only bare mode reproduces bytes exactly).The invariant:
nap -> decompile -> compile --bare -> napis byte-identical across the wholeExamplescorpus (356/356), Prodigy and Telidon files included. That round-trip doubles as an integration test of the compiler/decompiler pair.Robustness / honesty (hardened after an adversarial pass):
decompilerefuses a.td,compilerefuses a.nap, each pointing at the other verb. NAPLPS is a permissive byte format (arbitrary bytes decode to a "valid" run of text-drawing commands), so wrong-direction input genuinely cannot be detected from the decode itself; the extension is the honest signal.decompileaborts (nonzero) only on unrecoverable parse errors (an opcode absent from the in-use table, a truncated definition). A clean decode is not proof the input was NAPLPS, so decode warnings are summarized to stderr as a best-effort note rather than passed off as a faithful reconstruction.Scope: desktop only (
#if !BROWSER); touchesTelidraw/Program.csand theTelidraw.Compiler/Decompilersupport they need - 3 files. Full non-VR suite green.