|
spudplate
Template scaffolding compiler for spudlang .spud files
|
A small language has small pitfalls. The ones below are the cases where spudlang's behaviour will surprise an author who has not seen the rule. Most surface as parse or validate errors, so the language tells you about them, but the error message can be cryptic if you have not met the underlying rule.
The reference pages cover the rules in their own context; this page is the consolidated list of "things that bite people".
The lexer recognises every reserved word (full list in Lexical Structure) before path-expression parsing. As a result, a directory named the same as a keyword (include, from, as, end, file, mkdir, etc.) cannot appear unquoted in a path expression.
The error you will see is "expected newline after mkdir statement", pointing at the offending keyword. The fix is always to quote the segment.
This affects more directory names than it might first seem: include is a normal C/C++ headers folder, file shows up in some toolchains, from is rare but legal as a folder name. When in doubt, quote.
The lexer reads everything between two "</tt> characters as literal bytes. There is <strong>no</strong> escape mechanism: a backslash followed by <tt>n</tt> inside a string literal is two characters, not a newline.
@icode
file "out.txt" content "line1\nline2" # writes the 11 bytes literally,
# does NOT write two lines
@endicode
To put a real newline in a string literal, embed an actual line break:
@icode
file "out.txt" content "line1 line2 "
@endicode
There is also no <tt>\\"</tt> escape. A double-quote inside a string literal closes the string. To compose a string that contains <tt>", bind the value via ask or let.
Inside a path expression, {...} interpolation is allowed only inside a quoted segment:
The error is "'{...}' interpolation is only allowed inside quoted path strings". To combine an alias with an interpolation, quote the part that needs braces:
This rule is for paths only. Inside string literals (content, default, run), {expr} interpolation works directly.
A path alias bound under a when clause must be referenced under an equivalent condition. The validator normalises bool conditions before comparing them: b, b == true, and not not b all match. But and and or are not considered commutative:
The fix is to keep the operands in the same order on every reference, or to bind the compound condition to a let first:
This is a known limitation. It may be lifted in a future version; for now, write the same condition the same way every time.
A bare ask is required: the user cannot skip it. A when-gated ask is asked only sometimes, but the variable must be bound after the statement either way. So the validator requires a default:
When use_tests is false, the default is bound and num_weeks is ready for any later code that reads it. When use_tests is true, the user is prompted as normal, and the default fills in for empty input.
copy <source> into <dest> requires <dest> to already exist. It will not create it for you. If you want a fresh directory populated from one source, use mkdir from instead.
The pattern that combines both: mkdir from to create and seed, then copy into to merge add-ons.
The interpreter tracks paths created during a single run. file ... append requires the target file to have been created earlier in the same run. Appending to a pre-existing file from outside the run is rejected on principle: spudplate never modifies files it did not create.
This is also why an aliased file path (as) is the conventional way to wire up conditional appends: the alias makes it impossible to typo a different filename.
run builds a shell command from a string expression and dispatches it via /bin/sh -c. The trust prompt shows the literal source but the evaluated string is what executes:
A malicious url such as ; rm -rf $HOME is passed straight to the shell. Defend by:
ask ... options ... to bound the input, orSee run for the full security treatment.
Without in, a run inherits the working directory of spudplate, which is wherever the user invoked it from (rarely the project subdirectory the template just created). Always pin commands that depend on a particular cwd:
A command that runs in the wrong directory often fails in confusing ways. Pinning is cheap.
Filesystem operations are queued during execution and flushed at the end. If the flush starts and operation N out of M fails, operations 1 through N-1 have already been performed and remain on disk. There is no rollback.
This usually only matters for run commands: a run failing mid-flush leaves the filesystem in a partial state. The mkdir, file, and copy operations in the queue rarely fail at flush time, since paths are validated before the flush starts.
The "all or nothing" guarantee covers the common case: a user aborting at any prompt, before the flush starts. The first prompt is the cheapest place to back out.
The default run timeout is 60 seconds. A per-statement timeout 600 overrides it for one command. There is no per-statement way to say "no timeout"; the only escape is the CLI --no-timeout flag, which applies to every run in the invocation.
If a single command genuinely needs no limit (an interactive editor, an indefinite watcher), the user has to invoke spudplate run --no-timeout.