nupp command#
One executable holds every tool. Each command carries its own --help, which is the text nupp help <command> prints, and this page shows that text beside the output the command writes.
nupp check
nupp build
nupp run src/main.nuppThe commands, in the order nupp help lists them:
init: create a project from a templateast: dump a Nupp file's parsed syntax treeaot: show what the@aotfunctions in a file compile tobc: show the bytecode a Nupp file compiles tocheck: type-check source without emitting Luafmt: format Nupp sourcebuild: build source files or a configured project targetclean: remove build outputs configured innupp.luatasks: list or inspect project tasks fromnupp.lualints: list the lints and the level each runs atownership-audit: list foreign pointer contracts and unsafe assertion sitesexplain: describe a diagnostic code, with an example either wayreference: list or print a focused Nupp reference chaptercompletions: print a shell completion scripttest: build and run project teststest-runner: run test suites with the bundled runnercoverage: run tests and write a source coverage reporttask: build, then run a named task fromnupp.luadoc: generate API documentation from source commentsfixpoint: verify a byte-identical self-hosting rebuildrun: compile and run a Nupp or Lua programimport-c: generate typed Nupp bindings from a C headermigrate: migrate typed foreign source into gradual Nuppexport-c: export canonical C declarations for Nupp structsrock: create and package typed LuaRocks librarieslsp: language-server and semantic source operationsversion: print the compiler versionhelp: show general or command-specific help
Universal options#
Three options are appended to every command's grammar, so no command can be the one that forgot them:
--color[=WHEN]: always, never, or auto, the default--no-color: the same as--color=never-h,--help: show that command's help
--color never consumes the next argument. A bare --color means always, and a value is attached with =, which is what keeps the argument after it from being read as the value:
nupp check --color=never src/greet.nupp--color and --no-color land on one key, so asking for color and refusing it on the same command line is an error rather than last-wins.
A given --color decides on its own. With none, NO_COLOR refuses escapes, CLICOLOR_FORCE asks for them, and what is left is whether the stream is a terminal that understands them, which TERM=dumb says it is not. JSON output is never colored.
test, test-runner, task, rock and the lsp group hand their arguments to another program or parse them per operation, so the three are not appended to them. Each takes -h, and lsp declares a grammar of its own.
JSON and schemas#
Every command that produces structured data takes --format json, shortened to --json, and --schema, which prints the JSON Schema of that output:
nupp check --json
nupp check --schemainit, ast, aot, bc, check, fmt, build, clean, tasks, lints, ownership-audit, explain, doc, fixpoint, import-c, export-c and version take all three, and so does every lsp operation. reference names its formats markdown, skill and json instead. coverage, test, test-runner and run take --json and --schema with no --format, because the JSON each writes is one particular artifact rather than a rendering of the whole result. completions, task and rock produce no structured result and take neither.
A test runs each command for real and validates its output against that command's own --schema, so a schema cannot drift from what the command emits.
--format, --json and --text share one setting, and giving two of them is an error. No option repeats unless it says so, and three do: --set on init, and -Zno-opt on build and run.
A command writes its JSON as one line with no ordering guarantee across keys. The json blocks on this page are indented so they can be read; the text blocks are the bytes the command wrote.
Exit codes#
Every command answers with one of three statuses:
0: success1: the work was attempted and failed2: usage error, such as an unknown option or the wrong argument count
A usage error is settled before any work starts, names the argument it could not use, and points at that command's help:
nupp: unknown option --colour
Try 'nupp help check' for more information.Example project#
Every example below runs in this project, except where a section says it runs in Nupp's own repository because it needs a test suite or a compiler to build:
greeter/
├── nupp.lua
└── src/
├── greet.nupp
└── main.nuppreturn {
include = { "src" },
build = {
outDir = "build",
default = "app",
targets = {
app = {
kind = "modules",
description = "Build the greeter",
entries = { "main" },
},
},
},
tasks = {
greet = {
description = "Print a greeting",
argv = { "nupp", "run", "src/main.nupp" },
},
},
}local greet = require("greet")
print(greet.greet("world"))Command reference#
init#
Create a project from a template
Usage:
nupp init [TEMPLATE] [DIRECTORY]
nupp init --from PATH [DIRECTORY]
nupp init --list
Options:
--name NAME Project name; defaults to the directory basename
--set KEY=VALUE Set a template variable; may be given more than once
--from PATH Use a template directory on disk
--rev REV Commit, tag or branch for a repository template
--list List the built-in templates and exit
--yes Do not ask before writing a repository template
--dry-run Print what would be written and write nothing
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
With no TEMPLATE, the built-in `app`. A name with no slash is a built-in,
a path beginning with `.`, `/` or `~` is a directory, and `owner/repo`, optionally
followed by a path within it and by `@rev`, is a repository on GitHub; a full URL is
used as given.
A repository template is fetched with git, named by the commit it resolved to, and
confirmed before anything is written. Its post-init steps are reduced to `git init`:
`check`, `build` and `test` all load the scaffolded `nupp.lua`, which is ordinary
unrestricted Lua, so running them would execute code that was just downloaded.The built-in templates travel inside the compiler, so this works with no network and no checkout:
Built-in templates:
app A runnable program, with a test and a task to start it
lib A typed library packaged as a LuaRocks rocknupp init with no arguments writes the app template into a directory named for it. Naming the directory names the project:
Created greeter from built-in template app
Next:
cd greeter
nupp check
nupp test greeter/
├── .gitignore
├── README.md
├── nupp.lua
├── src/
│ ├── greeting.nupp
│ └── main.nupp
└── tests/
└── run.luaThat project checks, builds, tests and runs as it stands, which is what the template is for.
Template sources#
A TEMPLATE argument is read as text, never by looking at the filesystem, so the same argument means the same thing in every directory.
| Argument | Resolves to |
|---|---|
app |
a built-in template of that name |
./x, ../x, /x, ~/x |
a directory on disk |
owner/repo |
https://github.com/owner/repo |
owner/repo@v1.2.0 |
the same, at that revision |
owner/repo/games/topdown |
that repository's games/topdown directory |
https://..., git@... |
used as given, with --rev for a revision |
--from PATH forces a directory, for the case where a local path looks like a repository name. A name with no slash that matches no built-in is refused by name rather than guessed at as a repository.
Writing a template#
A template is a directory tree with one template.lua at its root, which is not copied. Every other file is carried, .git at any depth is not, and ${name} is replaced in both file contents and path components, so src/${moduleName}.nupp becomes a file named for the project. Write $${ for a literal ${.
return {
description = "A runnable program",
variables = {
name = {pattern = "^[a-z0-9][a-z0-9_-]*$", invalid = "lowercase, please"},
author = {description = "Author", default = "unknown"},
},
raw = { "assets/**" },
after = { "git" },
}name, moduleName (the name with its hyphens and underscores removed) and directory are always defined. A template may declare name to constrain it, but its value comes from --name or the directory. Anything else is declared here or it cannot be used, and is supplied with --set KEY=VALUE.
raw names globs copied byte for byte, for assets that are not text. after names post-init steps from a closed set: git, check, build and test.
Fetched template limits#
A repository template is confirmed before anything is written, and a run with nothing at the terminal to answer is refused rather than assumed. --yes accepts it unread, and either way its after steps are reduced to git init, so the scaffolded project is read before any of it runs.
nupp init owner/repo@v1.2.0 game --yesDive deeper
The reduced step list is not caution about template.lua, which is loaded in a sandbox with no io, os, require or load in it. It is that check, build and test all load the nupp.lua that was just scaffolded, and a manifest is ordinary unrestricted Lua. A template allowed to ask for check could put its payload in the manifest instead, and the sandbox would be decoration.
ast#
Dump a Nupp file's parsed syntax tree
Usage:
nupp ast [--format text|json] <file>
Options:
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
The parser produces a lossless concrete syntax tree. Text output is an indented
outline with quoted tokens; JSON includes structural children, tokens, trivia,
locations, and parse errors. A recovered tree is still printed when parsing
fails.The tree is the one Grammar defines, kept lossless down to trivia. A file that does not parse still prints the tree recovery reached, and then exits 1.
chunk
block
localFuncStmt
local "local"
function "function"
name "greet"
funcbody
( "("
param
name "name"
: ":"
tname
name "string"
) ")"
: ":"
tname
name "string"
block
returnStmt
return "return"
binop
string
string "\"Hello, \""
.. ".."
name
name "name"
end "end"
returnStmt
return "return"
tableExpr
{ "{"
fieldNamed
name "greet"
= "="
name
name "greet"
} "}"
eof ""aot#
Show what the @aot functions in a file compile to
Usage:
nupp aot [--emit ir|c|spirv|wgsl|asm|binding] [--check] [--function NAME] [--target TRIPLE] [--features TIER] <file>
Reports what the ahead-of-time backend produces for one file, without writing it. A native build emits the same artifacts under `aot = "emit-c"` or `aot = "require"`; Lua 5.1 Wasm applications use `emit-wasm` or `require-wasm`.
Options:
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--emit ARTIFACT Print one artifact: ir, c, spirv, wgsl, asm, or binding
--function NAME Show only this function, named as the source or the symbol
spells it
--check Exit non-zero for a map loop that wanted lanes and ran one
iteration at a time
--target TRIPLE The target triple to compile for; the host's by default
--features TIER The CPU feature tier to promise: baseline, avx2, avx512f,
neon, scalar, or simd128
--library PATH Where the compiled object will be found, for the generated
binding
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this helpThe bare command says what the backend decided for every @aot function in the file: how much arithmetic each loop does per byte it touches, and which gang it was lowered to, if any.
bench/kernel-subset-spike/mandelbrot.nupp: mandelbrot, 5.19 operations per byte (83 over 16), f64x4, 4 lanes--emit prints one artifact. ir is the verified IR with the lane body beside the scalar one it was rewritten from, c is the generated C, asm is the instructions that C became, and binding is the Nupp module that stands in front of it.
--emit asm compiles the generated C with the flags a build compiles this tier's translation unit with, and stops one step before the assembler encodes it. It is the answer to what the C compiler did, which is the last thing between a lowering decision and the machine and the one thing nothing else in the tree reports. Each symbol is headed by what it is -- the compiled body, the forced-scalar oracle it is differentially tested against, a Lua wrapper, the registrar, a layout reporter, or a helper the compiler declined to inline -- and by a count of the listing under it:
-- src/kernel.nupp, aarch64-apple-darwin, neon, Apple clang version 21.0.0
-- ks_scale (scale), kernel: 50 instructions, 11 vector, 4 loads, 4 stores, 11 branches, 0 calls, 0 stack
cbz x2, LBB1_14
LBB1_6:
ldp q1, q2, [x9, #-32]
fmul.4s v1, v1, v0[0]
stp q1, q2, [x10, #-32]
subs x11, x11, #16
b.ne LBB1_6total is exact. The other counts are a rule per architecture over the mnemonic and the operand shape, coarse on purpose: what they support is comparing two runs of one kernel, not modelling the machine. Scalar floating point is deliberately not counted as vector on either architecture, since a loop that fell back to one element at a time is what a vector count is usually being read to detect, and stack counts instructions whose memory operand is the frame, which is the closest thing to a spill count that reading instructions can give.
--function narrows that to one body, named either as the source spells it or as the symbol does. With --features, which selects the tier the C is both lowered and compiled for, that is a repeatable command for one function at one tier -- so what a change did to the emitted instructions can be compared across runs rather than re-derived by hand. Two runs are comparable when the compiler is: --json reports it, alongside the flags it was given and the per-symbol counts. There are instruction rules for aarch64 and x86-64; another architecture is refused rather than reported with empty counts.
--check covers the same category bc --check does: a performance property no answer depends on, which an ordinary edit can quietly take away. It distinguishes three outcomes and fails on one. A loop that lowered is fine, and so is a loop that declined, whether because the arithmetic per byte says lanes will not pay or because the source wrote @aot(vectorize = false). A loop that wanted lanes and ran one iteration at a time exits 1, naming the construct that stopped it:
nupp: advance ran one iteration at a time
src/particles.nupp:39:5: aot: a nested numeric loop is not lane-controlled yetWhen a loop declines because it does too little arithmetic and its traffic is through fields of consecutive structs, the text report also suggests projecting the hot fields from nupp.mem.soa column storage. This is guidance rather than a failed check: the scalar body remains the selected implementation.
See AOT vectorization for how a gang is chosen and for the tiers --features names.
bc#
Show the bytecode a Nupp file compiles to
Usage:
nupp bc [--check] [--prologue] [--format text|json] <file>
Options:
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--check Report bytecode a loop cannot compile, and exit non-zero for
it
--prologue Include the generated runtime preamble, folded away by
default
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
Source lines are shown against the instructions they produced. The generated
runtime preamble all lands on line 1 and is folded away unless `--prologue`
asks for it.
`--check` marks every instruction LuaJIT cannot record that sits inside a loop.
It exits 1 when every repeatable path reaches one, because that loop cannot
complete a root trace. A blocker reached on only some paths remains visible as
advice without claiming every path stays interpreted.Generated Lua keeps source line numbers one to one, so the listing shows the file that was written rather than the file that was generated:
-- chunk, lines 0-6
... 44 instructions of runtime preamble
3 | end
0044 FNEW 4 7 ; greet.nupp:1
5 | return {greet = greet}
0045 TDUP 5 8
0046 TSETS 4 5 9 ; "greet"
0047 UCLO 0 => 0048
0048 RET1 5 2
-- function, lines 1-3
1 | local function greet(name: string): string
0000 FUNCF 3
2 | return "Hello, " .. name
0001 KSTR 1 0 ; "Hello, "
0002 MOV 2 0
0003 CAT 1 1 2
0004 RET1 1 2Building a function is the usual thing --check finds. LuaJIT has no recording for it, so the loop holding one aborts recording, is blacklisted after enough attempts, and then runs interpreted however hot it gets. Nothing else reports that, because the program's answers do not change.
It reads further than the two source lints, which see what was written rather than what was generated: loop-invariant-closure reports a function that could be lifted out of its loop unchanged, and jit-loop-closure, off until a project asks for it, reports one that reads the iteration and so cannot be. Neither says anything about a closure the compiler's own lowerings put in a loop, which is what this reads.
check#
Type-check source without emitting Lua
Usage:
nupp check [--strict] [--dialect DIALECT] [--target NAME] [--platform NAME|all] [--format text|json] [file...]
Options:
--strict Treat strict checker rules as errors
--dialect DIALECT Source-lowering dialect: luajit (default), luajit-compat
or lua51
--target NAME Check a named manifest target
--platform NAME Check one configured binary platform, or all
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
With no files, checks the default target from nupp.lua. Also reports a `timing` object naming how many modules were reused from the cache versus rechecked, and which modules cost the most of the wall-clock time either way -- see docs/reference/diagnostics.md.A file's extension decides the floor it is held to, with .nupp strict and .g.nupp, .d.nupp and .lua gradual. --strict overrides that, holding every file to the strict floor whatever it is called: unknown variables are errors, and module exports need annotations. --target names a manifest target and cannot be combined with explicit files. --dialect overrides the target's source-lowering dialect, or selects one for explicitly named files.
A clean project writes nothing and exits 0. With local shout: number = greet("world") added to src/greet.nupp:
src/greet.nupp:6:23: error: NUPP2001: cannot initialize shout: string is not a number
6 | local shout: number = greet("world")
| ^~~~~See strictness.md for what the strict floor holds a file to, and strictness.md for which extension carries it.
Diagnostics as JSON#
Every diagnostic carries the position, the code, the severity, and the docs anchor that explain and the reference share:
{
"ok": false,
"dialect": "luajit",
"diagnostics": [
{
"code": "NUPP2001",
"severity": "error",
"message": "cannot initialize shout: string is not a number",
"file": "src/greet.nupp",
"range": {
"start": {"line": 6, "column": 23, "offset": 128},
"end": {"line": 6, "column": 28, "offset": 133}
},
"notes": [],
"related": [],
"fixes": [],
"docs": "docs/reference/diagnostics.md#code-families"
}
]
}A lint carries its lint name and a help line as well, and a fix carries the edits that apply it. See Diagnostics for what a diagnostic holds and Lints for the levels.
ok says whether the check ran and found nothing wrong. It is false both for a project that reported an error and for a run that never got as far as checking: a manifest the command could not use ends the run before any file is read, and an empty diagnostics cannot tell that apart from a clean project on its own.
Check timing#
With no files named, --json also carries a timing object, so a repeat check that feels slow can be read rather than waited out. It is the shape build publishes, minus the parts only generation charges time to:
{
"ok": true,
"dialect": "luajit",
"diagnostics": [],
"timing": {
"totalMs": 8.4,
"compiledModules": 0,
"reusedModules": 2,
"phases": [{"name": "check", "durationMs": 3.1}],
"slowest": []
}
}compiledModules is how many modules this run actually reparsed and rechecked, and reusedModules is how many it answered from the last run's cache without looking at again. compiledModules = 0 is the answer to trust that nothing was redone.
slowest ranks modules by wall-clock time spent either way, longest first. Confirming a reused module's cache entry is still valid costs time too, so a check that stayed slow on an unchanged project still names a module to look at rather than asking to be trusted. timing is absent when a diagnostic stopped the check, the same as it is for build, since a run that did not finish has no account of itself to give.
fmt#
Format Nupp source
Usage:
nupp fmt [-w|--write] [--check] [--no-method-parens] [--width N] [--format text|json] [file...]
With files named, each is formatted to stdout, or rewritten with --write.
With none, the project is the subject: every .nupp and .d.nupp under the
manifest's include roots, minus the build output. The files that are not
formatted are listed and the exit status is 1, so a build can gate on it;
--write formats them and lists what it changed.
--check asks that question of whatever it was given, so a build can gate on
the files a change touched. Nothing is written and nothing goes to stdout but
the list; the exit status is 1 if it is not empty.
Options:
-w, --write Rewrite files in place instead of writing to stdout
--check Report which files are not formatted; write nothing
--no-method-parens Leave obj:m{...} and obj:m"..." written without
parentheses, instead of adding them
--width N Code column past which a line breaks, at least 20
(default 120)
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
--json always reports the list, whichever form was asked for, and separates a
file that could not be formatted from one that merely is not.
Absolute references to declared modules and their types become explicit local
imports. Module operations use a module binding; type-only references use an
erased selection. Declarations used in both positions receive value and type bindings.
Name collisions receive a numeric suffix. Compiler intrinsics and explicit require
calls keep their spelling and placement.
A method call left in its sugar form, obj:m{...} or obj:m"...", is given its
parentheses back, obj:m({...}) and obj:m("..."). --no-method-parens leaves it
as written, and so does a manifest with fmt = { methodParens = false }; the
flag wins if both are given.
--width sets the code column past which a line breaks; the default is 120,
unchanged from before this was a flag. Docblock text keeps wrapping at 88
columns regardless.With a file named, the formatted source goes to stdout. Given an unformatted src/messy.nupp:
local function greet(name: string): string
return "Hello, " .. name
end
return {greet = greet}With none, the project is the subject and the answer is the list of files that are not formatted, which is what a build gates on:
src/messy.nupp{
"ok": false,
"written": false,
"unformatted": ["src/messy.nupp"],
"failed": []
}See fmt.md for the rules the formatter applies and for the fmt table in nupp.lua.
build#
Build source files or a configured project target
Usage:
nupp build [--strict] [--dialect DIALECT] [-O<n>] [--target NAME] [--platform NAME|all] [--standalone] [--out-dir DIR] [-q] [--format text|json]
nupp build [--strict] [--dialect DIALECT] [-O<n>] [-o DIR] [-q] [--format text|json] <file...>
Options:
--target NAME Build a named manifest target
--platform NAME Build one configured binary platform, or all
--standalone Link native FFI and AOT code into the binary host
--out-dir DIR Override the manifest target's output directory
-o DIR Output directory for explicit source-file builds
--strict Treat strict checker rules as errors
--dialect DIALECT Source-lowering dialect: luajit (default), luajit-compat
or lua51
-O0, -O1, -O2 Optimization level; ad-hoc builds default to -O0,
deliverable targets to -O2
--remarks Report what the optimizer did and what it declined to do
-Zno-opt=CODE Turn off one pass, named by its stable code, to bisect a
miscompile. Unstable: the spelling may change or go away
--progress[=WHEN] When to report progress and timing on standard error:
always, never, or auto (default), which reports only to a
terminal
-q, --quiet Report no progress or timing; the same as --progress=never
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
Manifest target options cannot be combined with explicit source files.
Use 'nupp tasks' to discover target names and configuration.
The level is part of the build key, so changing it rebuilds rather than
mixing artifacts compiled at two different levels. See docs/learn/performance/index.md.
--json reports the same diagnostics as 'nupp check --json' alongside what the
build wrote, so one call answers both what went wrong and what landed. It also
reports bounded materialization facts: provider, schema, fingerprint, backend,
sizes, runtime features and ABI versions, and a timing object saying where the
build's wall-clock time went and which modules cost the most of it.
To a terminal, a build says which module it is on while it compiles and then
how long it took, what it spent that on, and its slowest modules. To anything
else it stays quiet, so a script reading the output sees what it always saw.
NUPP_PROGRESS says what --progress says, for the builds nothing passes a flag
to -- including the rebuild bin/nupp runs before every other command.Output directories#
-o is for explicit source-file builds and --out-dir overrides a manifest target's output directory. They are different options, and using one in the other's mode is an error:
nupp build --target app --out-dir dist
nupp build -o dist src/greet.nuppOptimization levels#
The default depends on what is being built. An ad-hoc file build stays at -O0, which performs no rewrite at all and maps output one-to-one to source. A binary, bundle, or component target is a release artifact and builds at -O2 unless its manifest entry declares its own level with optimize = 0|1|2; an explicit -O on the command line wins over both. The compiler builds itself optimized the same way, through optimize = 2 on its own target. -O1 and -O2 currently run the same eight passes, OPT-1 through OPT-8.
nupp build -O1 --remarks
nupp build -O1 -Zno-opt=OPT-3--remarks reports what the optimizer did and what it declined to do. -Zno-opt=CODE turns off one pass by its stable code, and the -Z prefix marks it as unstable: the option's name may change or go away. See performance.md for what each pass rewrites and what it measured.
Dive deeper
-Zno-opt reaches the build key as well, not only -O. That is what makes it a usable bisection tool: turning a pass off and building again produces a tree compiled entirely without it, rather than one where whichever modules happened to be cached still carry it.
Progress and timing#
To a terminal, a build says which module it is on, on one line it rewrites in place, and then how long the whole thing took, which activities that time went to, and the modules that cost the most of it:
built compiler in 18.9s: 164 compiled, 0 reused
check 16.1s generate 952ms
slowest
nupp.compiler.gen 1.9s
nupp.mem.heap 699ms
nupp.compiler.check.calls 664msOne activity is current at a time, so the second line's parts add up to the whole rather than overlapping. A module is charged for its own checking and generation and not for the dependencies its check pulled in, so the list names where the time went rather than whichever module happened to be reached first.
To anything that is not a terminal a successful build still writes nothing, so a script reading its output sees what it always saw. --progress=always asks for the report anyway, -q refuses it, and NUPP_PROGRESS says the same thing for the builds nothing passes a flag to, including the rebuild bin/nupp runs before every other command.
JSON report#
--json says both what failed and what landed, and carries the same timing as data rather than as a report:
{
"ok": true,
"target": "app",
"dialect": "luajit",
"written": ["build/greet.lua", "build/main.lua"],
"diagnostics": [],
"materializations": [],
"timing": {
"totalMs": 412.7,
"compiledModules": 2,
"reusedModules": 0,
"phases": [{"name": "check", "durationMs": 331.2}],
"slowest": [{"module": "main", "durationMs": 208.4}]
}
}It also reports bounded materialization facts under materializations: provider, schema, fingerprint, backend, sizes, runtime features, and ABI versions.
clean#
Remove build outputs configured in nupp.lua
Usage:
nupp clean [--target NAME] [--platform NAME|all] [--dry-run] [--format text|json]
Options:
--target NAME Clean only the named build target
--platform NAME Clean one configured binary platform, or all
--dry-run Print output paths without removing them
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
With no target, cleans every configured target output. Paths outside the
project and paths that resolve to the project root are always rejected.--dry-run names the paths and removes nothing, which is how to see what a clean is about to reach:
would remove buildremoved build--platform narrows one target's outputs and so requires --target; giving it alone is a usage error. See Build system for what a build leaves in the output directory.
tasks#
List or inspect project tasks from nupp.lua
Usage:
nupp tasks [--format text|json]
nupp tasks <name> [--format text|json]
Options:
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
With no name, lists build targets plus the test and configured self-host actions.
With a name, prints the task's effective configuration.The list marks the default build target:
app (default) - Build the greeter
greet - Print a greetingNaming one prints its effective configuration, inherited manifest defaults included:
Name: app
Default: yes
Description: Build the greeter
Kind: modules
Category: build
Command: nupp build --target app
Output directory: build
Entries:
- main
Resources:
(none)
Dependencies:
(none)See tasks.md for the manifest shape, and task for running one.
lints#
List the lints and the level each runs at
Usage:
nupp lints [--format text|json]
Levels are off, note, warning and error; only an error stops a build. A
project moves one in nupp.lua by name or by category:
lints = { ["missing-require"] = "warning", style = "off" }
A statement waves one away with @allow("missing-require"). See
docs/reference/lints.md.
Options:
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this helpThe text table has no code column; --json includes code, default, and moved.
lint category level summary
customary-operator style warning a customary operator where Lua has a word
deprecated suspicious warning use of an API marked deprecated
discarded-result suspicious warning a call with nothing to do but return has its result dropped
else-if style warning a conditional chain written as separate ifs
exhaustiveness correctness warning a dispatch leaves members of a closed set unhandled
gradual-projection suspicious warning an associated type was erased because inference did not reach its head
jit-boundary suspicious warning an FFI boundary cannot safely run on a compiled trace
jit-callback suspicious warning a C callback left on the JIT
jit-loop-closure performance off a loop builds a function and so never compiles
loop-invariant-closure suspicious warning a loop builds the same function every iteration
missing-require correctness error a project module is used without being required
positional-record-construction style warning a record built by field order rather than by naming its fields
reifiable-record performance off a record whose fields would all live in C memory
string-pointer suspicious warning a pointer taken from a Lua string
undocumented-raise suspicious warning a documented function raises without saying so
unused-binding suspicious warning a local is declared and nothing reads itSee Lints for moving one in nupp.lua, and Lints for waving one away at a statement.
ownership-audit#
List foreign pointer contracts and unsafe assertion sites
Usage:
nupp ownership-audit [--format text|json] [--regions] [file...]
Options:
--regions Include automatic cleanup regions
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
With no files, scans Nupp sources under src. The report enumerates trusted C contracts and explicit unsafe regions; it does not verify foreign implementations.The report is the list of places where the checker is trusting something it cannot see: a foreign declaration's contract, and every unsafe do region. Given src/block.nupp:
cdef struct block
size: integer
end
cdef function blockCreate(size: integer): block*
cdef function blockFree(takes b: block*)
local function blockNew(size: integer): affine(block*, blockFree)
return blockCreate(size)
end
local function sizeOf(borrows b: block*): integer
unsafe do
return b.size
end
end
return {new = blockNew, free = blockFree, sizeOf = sizeOf}Foreign ownership contracts
src/block.nupp:5 blockCreate
result 1: block*
src/block.nupp:8 blockFree
parameter 1 b: block* takes
Unsafe assertion sites
src/block.nupp:15:5 unsafe assertion regionThe audit reports what the foreign boundary states. blockCreate returns a plain block* because a cdef declaration cannot say it produces an owner; the obligation begins at blockNew, which is ordinary Nupp and so is not a foreign contract to enumerate.
explain#
Describe a diagnostic code, with an example either way
Usage:
nupp explain <code> [--format text|json]
nupp explain --list
Options:
--list List the codes with a worked example
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
Every diagnostic written by --json carries the same `docs` anchor this
reports, so a reader holding a diagnostic can reach the reference without
being told where it is.
A code with no worked example still resolves through its family, and says so
with `family: true`, rather than an example being invented to fit it.NUPP2119 A declaration does not say where it lives
A declaration is file-local (`local`), a member of a table (`record m.R`), or a
project global (`global`). Plain Lua would have made it a global silently; Nupp
asks instead, because a name that means one thing here and another elsewhere is
worth one word to prevent.
Reports it:
record Loose
id: integer
end
return Loose
Does not:
local record Loose
id: integer
end
return Loose
Related: NUPP2106, NUPP2120
Reference: docs/reference/diagnostics.md#diagnostic-indexreference#
List or print a focused Nupp reference chapter
Usage:
nupp reference [language|cli|performance|all] [--format markdown|skill|json] [-o PATH]
nupp reference --section NAME | --for CODE
With no chapter, lists the available focused references and the sections
inside them. `all` is the complete Nupp reference, meant to be pasted whole.
A chapter is thousands of words. `--section` prints one section, named by its
heading or by any `docs` pointer at it, and `--for` prints whichever sections
explain a diagnostic code -- which is what a reader holding one actually has.
nupp reference cli
nupp reference language
nupp reference --section affine-resources
nupp reference --section docs/learn/language/modules.md#modules
nupp reference --for NUPP2004
nupp reference cli --format skill -o .claude/skills/nupp-cli/SKILL.md
nupp reference performance --format skill -o .claude/skills/nupp-performance/SKILL.md
nupp reference --format skill -o .claude/skills/nupp/SKILL.md
Options:
--format FORMAT Output format: markdown (default), skill, or json
--skill Shorthand for --format skill
--json Shorthand for --format json
--section NAME Print one section, by heading or by a docs pointer at it
--for CODE Print whichever sections explain that diagnostic code
-o, --output PATH Write to this file rather than to standard output
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
The skill's description is what a harness keeps in context permanently;
the body loads when something is actually being written. The documentation site
instead presents the same subjects as focused pages for human browsing.With no chapter, it lists what there is to print:
Nupp reference chapters
language Nupp syntax, types, runtime constructs, lints, and diagnostics.
cli Nupp commands, JSON contracts, testing, and coverage workflows.
performance Nupp trace checking, sampling, abort analysis, zones, and benchmark workflow.
Language sections
gradual-typing-over-luajit
declaring-things
types
functions
…
CLI sections
…
Performance sections
…
Run `nupp reference <chapter>` for one chapter, `nupp reference all` for the complete reference,
`nupp reference --section <name>` for one section, or `nupp reference --for <CODE>` for
whichever sections explain a diagnostic.Printing one section#
A chapter is thousands of words, and language is over thirteen thousand, so a reader who knows which construct they are asking about should not have to load the rest of it. --section prints one:
nupp reference --section types
nupp reference --section affine-resourcesA section is named by its heading or by any docs pointer at it, and everything before the # is ignored, so the anchor a diagnostic already carries can be followed straight through:
nupp reference --section docs/learn/language/modules.md#modulesThat prints the same section as --section modules. --for CODE goes the other way and prints whichever sections explain a diagnostic, which is what a reader holding one actually has:
nupp reference --for NUPP2004A code that no section covers says so and points at explain, which is where every code answers. The listing above names every section, so nothing has to be guessed at.
Chapter guarantees#
The chapters are generated from the compiler, so they cannot describe a construct the compiler does not have. Their examples compile in the test suite, and every cited diagnostic code resolves through nupp explain.
Dive deeper
The reference is compiled into the binary rather than fetched, which is the reason it is a command and not only a page on a website. A reader holding a reference from a different version is worse off than one holding none, because nothing in what they are reading tells them it does not match the compiler they are running. Printing it from the binary makes the two the same artifact.
completions#
Print a shell completion script
Usage:
nupp completions <bash|zsh|fish>
Options:
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this helpThe script is generated from the same command grammar that parses arguments and renders help, so a new command and its options appear in it without a second edit. Install it for the shell that runs nupp:
# Bash: add this to ~/.bashrc.
eval "$(nupp completions bash)"
# Zsh: write _nupp into a directory on fpath.
nupp completions zsh > "${fpath[1]}/_nupp"
# Fish
nupp completions fish > ~/.config/fish/completions/nupp.fish# Bash completion for nupp; generated from nupp.compiler.cli.spec.
_nupp() {
local cur prev command options
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD - 1]}"
if (( COMP_CWORD == 1 )); then
COMPREPLY=( $(compgen -W 'init ast aot bc check fmt build clean tasks lints ownership-audit explain reference completions test test-runner coverage task doc fixpoint run import-c migrate export-c rock lsp help' -- "$cur") )
return 0
fi
command="${COMP_WORDS[1]}"
case "$command" in
init)test#
Build and run project tests
Usage:
nupp test [args...]
Options:
--json Ask the test command for one JSON document instead of
progress text
--verbose Ask the test command to show output from passing tests
--color[=WHEN] Color both compiler and test output: always, never, or
auto
--no-color Never color compiler or test output
--timings Ask the test command for its whole timing report rather
than the slowest few. `--timings=N` asks for N rows,
and `--timings=0` for none
--group NAME Run the suites a named group covers. Repeatable, and
comma-separated. `--list-groups` names them
--exclude SUITE Leave these suites out, which is how a later broad run
stops repeating what an earlier focused one already ran
--exclude-group NAME Leave every suite a named group covers out
--lane WHICH Keep only one execution lane: `shared` is what a Nupp
worker can run beside other suites in one process,
`isolated` is what needs a process of its own
--list-suites Print the suites this selection would run, and run none
of them
--list-groups Print every named group and the suites it covers
-h, --help Show this help
--schema Print the JSON Schema of --json output and exit
Additional arguments are appended to the bundled runner or test.argv from
nupp.lua. Use '--' before a test argument named --help.
--json is passed along to the test command rather than interpreted here, since
the arguments past this point are that command's. --schema describes what the
bundled runner writes for it.These examples run in Nupp's own repository, whose runner takes a suite name. Progress prints . when a unit passes, S when all of it is skipped, and E when it contains a failure. A parallel unit is a suite slice rather than one test, which keeps large runs compact; the failure report still names every failing test:
........
8 tests, 8 passed, 0 skipped, 0 failed (1972.3ms)
Timing: 2.0s wall, 2.0s of suite work
slowest suites wall load hooks cases tests
elseiftest 2.0s 24ms 0ms 1.9s 8
slowest tests wall
elseiftest / canBeAllowedByNameOrCode 427ms
elseiftest / allowsAdjacentConditionsThatCouldBothHol 411ms
elseiftest / offersAMachineApplicableFix 276ms
elseiftest / flagsAdjacentMutuallyExclusiveConditions 273ms
elseiftest / allowsAdditionalStatementsInTheElse 154ms
elseiftest / allowsAnAnnotatedNestedIf 137ms
elseiftest / canBeTurnedOff 135ms
elseiftest / flagsAnElseContainingOnlyAnIf 134msThe timing report is the slowest fifteen of each by default; --timings asks for all of them, --timings=N for N, and --timings=0 for none.
With --json the progress marks go to stderr and one document stays on stdout:
{
"ok": true,
"total": 8,
"passed": 8,
"skipped": 0,
"failed": 0,
"durationMs": 1830.0378417969,
"tests": [
{"suite": "elseiftest", "name": "allowsAnAnnotatedNestedIf",
"file": "tests/elseiftest.lua", "line": 130,
"status": "passed", "durationMs": 142.4580078125}
],
"suites": [
{"suite": "elseiftest", "durationMs": 1830.037841796875,
"loadMs": 24.1259765625, "hooksMs": 0.394775390625,
"casesMs": 1805.449462890625, "tests": 8,
"slowestCase": "canBeAllowedByNameOrCode",
"slowestCaseMs": 384.158935546875}
],
"shards": []
}A failing record carries the message and the file and line the error came from. suites says what a suite cost beyond its cases -- compiling or loading it, and its beforeAll -- and shards is one record per worker process, empty here because a single named suite runs in one.
test-runner#
Run test suites with the bundled runner
Usage:
nupp test-runner [suite...] [options]
Options:
--json Write one JSON test report instead of progress text
--verbose Show output captured from passing tests
--jobs N Use N parallel workers
--timings[=N] Show every timing, or only the N slowest suites and
cases
--color[=WHEN] Color output: always, never, or auto
--no-color Never color output
--group NAME Run the suites a named group covers. Repeatable, and
comma-separated. `--list-groups` names them
--exclude SUITE Leave these suites out, which is how a later broad run
stops repeating what an earlier focused one already ran
--exclude-group NAME Leave every suite a named group covers out
--lane WHICH Keep only one execution lane: `shared` is what a Nupp
worker can run beside other suites in one process,
`isolated` is what needs a process of its own
--list-suites Print the suites this selection would run, and run none
of them
--list-groups Print every named group and the suites it covers
-h, --help Show this help
--schema Print the JSON Schema of --json output and exit
Discovers tests/*test.lua and tests/*test.nupp. Each suite returns a
table of test functions and may define beforeAll, afterAll, beforeEach, and
afterEach hooks. A suite name selects one or more files without their extension.Run nupp test for the normal build-then-test path. test-runner is the manifest command Nupp's templates select, and is useful directly when the artifact under test is already current.
coverage#
Run tests and write a source coverage report
Usage:
nupp coverage [--out DIR] [--json] [test arguments...]
nupp coverage --report-json [--out DIR]
Options:
--out DIR Directory for the HTML, JSON, and LCOV report
--json Write the aggregate summary as JSON
--report-json Print an existing full JSON report; do not run tests
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
Coverage uses a separate build/coverage artifact, so normal generated
Lua and its cache are never instrumented. The static report opens at
build/reports/coverage/index.html and also writes coverage.json and lcov.info.
`--report-json` prints the complete machine-readable coverage.json already in
the report directory, without rebuilding or rerunning tests.Run in Nupp's own repository, over one suite:
........
8 tests, 8 passed, 0 skipped, 0 failed (23389.0ms)
coverage: lines 16.80%, functions 16.38%, branches 10.19%
coverage: report written to build/reports/coverage/index.htmlSee Testing for the report's contents and for what a custom runner has to do.
task#
Build, then run a named task from nupp.lua
Usage:
nupp task <name> [args...]
Options:
-h, --help Show this help
Runs tasks.<name> from nupp.lua: builds tasks.<name>.build first if it names
one, then execs tasks.<name>.argv from tasks.<name>.cwd (the project root by
default) with any arguments after <name> appended.
See `nupp tasks` for the configured list.Hello, worldThe exit code is the task command's own. See tasks.md for declaring a task and for the build it runs first.
doc#
Generate API documentation from source comments
Usage:
nupp doc [site|markdown|json|both] [-o PATH] [--target NAME] [--title TITLE] [--all] [--format text|json] [path...]
Options:
-o, --output PATH Output file or directory
--target NAME Document a named manifest target
--title TITLE Documentation title
--all Include private declarations
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
The first argument may name the format: site, markdown (or md), json, or both.
With none, the manifest's configured format is used, and site if it has none.
--format names the shape of this command's own report and is unrelated to the
documentation format, which is the positional word.A successful run writes nothing to the terminal. --json names what it wrote:
{
"ok": true,
"format": "markdown",
"output": "docs/api.md",
"files": ["docs/api.md"]
}See doc.md for the docblock tags it reads and for configuring a documentation target.
fixpoint#
Verify a byte-identical self-hosting rebuild
Usage:
nupp fixpoint [--emit-stage0 PATH] [--format text|json]
nupp fixpoint --binary [--format text|json]
By default the stage-zero compiler builds stage one, stage one builds
stage two, stage two builds stage three, and stages two and three must be byte
identical. Stage zero is a published release rather than a file in the tree, so
it is meant to differ from the current compiler and nothing compares it with one;
the first stage is what absorbs that difference.
--emit-stage0 writes the stage-zero bundle the verified compiler composes, which
is the artifact a release publishes for the next checkout to start from.
--binary makes the same claim about packaging: the target named by
selfHost.binary is stamped, and the binary that comes out stamps another
identical to itself. It is what the payload format's determinism rests on.
Options:
--emit-stage0 PATH Write the verified stage-zero bundle here
--binary Verify the packaged binary instead of the compiler
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this helpEvery stage is built into its own directory, and the last two are compared file by file. The line names the stage zero the run started from, since which one that was is the part a reader cannot see:
fixpoint ok: /path/to/stage0/nupp.lua rebuilds itself byte-identicallyStage one is not compared with anything. Stage zero is a different compiler on purpose -- a published release, named by selfHost.bootstrap -- so what stage one contains is these sources as that release emits them, and the claim starts at stage two. A mismatch keeps every stage for inspection and exits 1.
--emit-stage0 additionally writes the stage-zero bundle the verified compiler composes. It is the artifact a release publishes for the next checkout to start from, and nothing here reads it back. See Build system for what selfHost configures.
--binary names the target it stamped and the size the two runs agreed on. A mismatch keeps both stages for inspection and exits 1. See Distribution for the payload format whose determinism this verifies.
run#
Compile and run a Nupp or Lua program
Usage:
nupp run [--strict] [-O<n>] [--watch] [--profile[=MS]] [--profile-out PATH]
[--jit-aborts[=PATH]] [--json] <file> [args...]
Options:
--strict Treat strict checker rules as errors
-O0, -O1, -O2 Optimization level; ad-hoc builds default to -O0,
deliverable targets to -O2
--remarks Report what the optimizer did and what it declined to do
-Zno-opt=CODE Turn off one pass, named by its stable code, to bisect a
miscompile. Unstable: the spelling may change or go away
--watch Keep named function identities patchable at cooperative
poll points
--profile[=MS] Sample the program every MS milliseconds (default 10)
--profile-out PATH Where the samples go (default profile.out)
--jit-aborts[=PATH] Record where the JIT gave up (default jit-aborts.csv)
--json Write --jit-aborts as structured JSON instead of CSV
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
Program arguments are passed to the loaded chunk. Use '--' before a file name
that starts with a dash.
--profile writes collapsed-stack text: one line per stack, frames separated by
semicolons, then the sample count. speedscope.app, FlameGraph.pl and inferno
all read it directly. Frames are prefixed by the zone path that was open, so a
program that calls nupp.profile.zone reports itself in its own terms, and the leaf
carries the VM state most of its samples were in: N compiled, I interpreted,
C in a C function, G collecting, J compiling.
--jit-aborts answers the question a sampler cannot: whether the hot code was
compiled at all. It writes CSV, one row per place the compiler gave up, with a
blacklisted trace, permanently demoted to the interpreter, ranked first.
Both cover the program only: the session opens once the file has compiled and
closes when it returns, so the compiler's own work stays out of the report. A
program that fails still writes what was collected before it did. Each reports
a summary line on stderr.
--watch is development-only and always uses -O0. A long-running program calls
nupp.hotreload.poll() at a safe loop or request boundary.
The poll scans inputs, stages a valid changed-body patch, commits it,
and leaves the last good generation running after diagnostics or a required
restart. Programs that never return to such a boundary cannot reload
cooperatively.The first non-option argument is the program; everything after it goes to the program, options included. Arguments arrive as the chunk's varargs. A .nupp file is compiled first; anything else is loaded as Lua directly.
Hello, worldProfiling a run#
--profile and --jit-aborts take an attached value or none, never the next argument, so a program name after either is still the program:
nupp run --profile=2 --profile-out hot.txt src/main.nupp
nupp run --jit-aborts src/main.nuppThe defaults are 10 ms, profile.out, and jit-aborts.csv. With --json the abort default becomes jit-aborts.json, and every site carries both the raw VM detail and its stable normalized reason identity.
import-c#
Generate typed Nupp bindings from a C header
Usage:
nupp import-c [-o FILE] [-l NAME|--lib NAME] [--bridge-out FILE] [--inspect] [--format text|json] <header.h>
Options:
-o FILE Write the generated module to FILE
-l, --lib NAME Name the native library loaded by the bindings
--bridge-out FILE Emit C wrappers for eligible static inline functions
--inspect Report declaration dispositions without writing output
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this helpIt writes a committed, hand-editable module of cdef declarations. Without -o it writes <header basename>.nupp into the current directory. Given native/mini.h:
struct mini_point {
double x;
double y;
};
double mini_length(const struct mini_point *point);
int mini_version(void);The command reports the path it wrote:
src/mini.nupp-- generated by nupp import-c from mini.h
-- committed and hand-editable: fix or extend freely, re-import
-- only when the header changes.
cdef struct mini_point
x: number
y: number
end
cdef function mini_length(point: const mini_point*?): number from "mini"
cdef function mini_version(): int32 from "mini"
return { mini_point = mini_point, mini_length = mini_length, mini_version = mini_version }Every imported pointer is nullable unless you edit the generated module to add a stronger reviewed contract. Fixed arrays retain their bounds, function pointers remain typed in fields, parameters, results and pointer nesting, and a typedef-named anonymous aggregate keeps its typedef identity. A declaration the importer cannot type is left out with a readable -- import-c: skipped comment.
Inspecting a header#
--inspect reports what each declaration would lower to and writes nothing, neither the default module nor an -o path:
nupp import-c native/image.h --inspect
nupp import-c native/image.h --inspect --json
nupp import-c --schemaText inspection prints direct N, bridged N, skipped N. JSON also carries the warning strings and structured dispositions, where direct, type-only, bridge-inline, bridge-macro and skipped distinguish the lowering available. A skipped static inline function carries reason: "bridge-required".
Bridging static inline functions#
A static inline definition has no symbol for FFI to load, so ask for wrappers beside the editable module:
nupp import-c native/image.h \
--lib build/lib/libimage.so \
--bridge-out build/generated/image_bridge.c \
-o src/native/image.nuppOn success, text mode prints the Nupp output path. JSON reports this shape:
{
"ok": true,
"output": "src/native/image.nupp",
"bridgeOutput": "build/generated/image_bridge.c",
"warnings": [],
"direct": 1,
"bridged": 2,
"skipped": 0,
"dispositions": [
{"name": "image_version", "kind": "direct"},
{
"name": "image_triple",
"kind": "bridge-inline",
"symbol": "__nupp_bridge_..."
}
]
}The command emits C but does not compile it. Build that file into the exact library named by --lib, with the header's include paths, definitions and C flags. Function-like macros cannot be typed on this command line; configure their explicit signatures under a C dependency's bindings.macros. Combining --bridge-out with --inspect previews which inline functions would bridge but still writes no file.
See c-interop.md for complete direct and bridge headers, a runnable header-only manifest, macro recipe types, emitted C, ownership refinements, and supported limits.
migrate#
Migrate typed foreign source into gradual Nupp
Usage:
nupp migrate [--check] [--json] [--dialect auto|luacats|emmy|luadoc] FILE...
Options:
--check Print the migration plan without changing files
--dialect DIALECT Resolve ambiguous comment spellings for this migration
(default auto)
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
The file extension selects the migrator. Annotated `.lua` becomes the
same module at `.g.nupp`; unsupported extensions are refused rather than guessed.
Without --check, the destination is written atomically and checked before the source
is removed. An existing destination is never replaced.The annotated-Lua guide describes always-on comment ingestion and the shared command/editor migration planner. --check reports the complete plan without writing or removing files.
export-c#
Export canonical C declarations for Nupp structs
Usage:
nupp export-c -o FILE [--target NAME] [--format text|json] <source.nupp>... <module.Declaration>...
Options:
-o, --output FILE Write the generated header to FILE
--target NAME Use a named manifest build target
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
The selected build target supplies layoutTarget. When it has none, the
compiler host is used. Header generation itself invokes no C compiler.The command writes one target-specific header from selected exported structs and cdef functions. It includes transitive layout dependencies, stable C names, layout assertions, and the typed declarations used by native wrappers. See c-interop.md for what a struct has to be for a header to be exportable from it.
rock#
Package and check typed LuaRocks libraries
Usage:
nupp rock pack [rockspec]
nupp rock test [rockspec]
Options:
-h, --help Show this help
A Nupp rock installs runtime Lua normally and carries matching public
declarations in its versioned `nupp/` directory. `pack` validates and builds that
layout; `test` installs the result into a fresh tree and checks a fresh consumer.
`nupp init lib <name>` writes a project already in that shape.Starting one is init with the built-in lib template:
Created string-tools from built-in template lib
Next:
cd string-tools
nupp check
nupp testThe rock keeps its hyphens and the module drops them, since the module name is what require is given:
string-tools/
├── nupp.lua
├── string-tools-dev-1.rockspec
├── src/
│ └── stringtools.nupp
├── nupp/
│ └── stringtools.d.nupp
└── tests/
└── run.luaSee LuaRocks for the declaration's contents and for publishing one.
lsp#
Language-server and semantic source operations
Usage:
nupp lsp [root]
nupp lsp serve [root]
nupp lsp inspect [options] <file> <line> <column>
nupp lsp definition [options] <file> <line> <column>
nupp lsp implementation [options] <file> <line> <column>
nupp lsp references [options] [--include-declaration] <file> <line> <column>
nupp lsp symbols [options] [--file FILE] [pattern]
nupp lsp rename [options] [-w|--write] <file> <line> <column> <new-name>
nupp lsp actions [options] [--only quickfix|refactor] <file> <line> <column>
nupp lsp trace-check [options] <file> <line> <column>
Options:
--root DIR Project root (default: current directory)
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of an operation's --json output; ask
one operation, as `nupp lsp <operation> --schema`
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
--include-declaration
references only: Include the declaration among the
references
--file FILE symbols only: Search one document instead of the workspace
-w, --write rename only: Apply the rename instead of previewing it
--only KIND actions only: Narrow the results to quickfix or refactor
With no operation, or with only a root, runs the language server over stdio for
compatibility. `serve` names that mode explicitly. Source positions are 1-based
byte line and column numbers, matching compiler diagnostics. Rename previews by
default and changes files only with --write.inspect describes the symbol under a position:
{
"symbol": {
"name": "greet",
"kind": "variable",
"type": "function(name: string): string",
"range": {
"start": {"line": 3, "column": 13, "offset": 45},
"end": {"line": 3, "column": 18, "offset": 50}
}
}
}definition answers with the site a name was bound at:
{
"definition": {
"file": "src/main.nupp",
"range": {
"start": {"line": 1, "column": 7, "offset": 7},
"end": {"line": 1, "column": 12, "offset": 12}
}
}
}references answers semantically rather than by name, and adds the declaration with --include-declaration:
{
"declarationIncluded": true,
"references": [
{"file": "src/greet.nupp",
"range": {"start": {"line": 2, "column": 16, "offset": 44},
"end": {"line": 2, "column": 21, "offset": 49}}},
{"file": "src/greet.nupp",
"range": {"start": {"line": 6, "column": 17, "offset": 122},
"end": {"line": 6, "column": 22, "offset": 127}}}
]
}rename previews as a diff and changes files only with --write:
--- src/greet.nupp
+++ src/greet.nupp
@@ 2:16 @@
-greet
+hello
@@ 6:17 @@
-greet
+helloactions lists what can be applied at a position, and --json carries the edits with each one. On the else of a file whose conditional the else-if lint reports:
quickfix: write `elseif`symbols searches the workspace, or one document with --file. Each line is the kind, the name, and where it was declared:
function greet src/greet.nupp:2:16trace-check asks the same question for one function that bc --check asks for a file, reporting the LuaJIT trace blockers and risks the reason catalog knows about it:
nupp lsp trace-check --json src/greet.nupp 2 16nupp help lsp shows a merged option list; each of --include-declaration, --file, --only and --write belongs to exactly one operation. Every operation answers --schema with its own.
version#
Print the compiler version
Usage:
nupp version [--format text|json]
nupp --version
Options:
--format FORMAT Output format: text (default) or json
--json Shorthand for --format json
--text Shorthand for --format text
--schema Print the JSON Schema of --json output and exit
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
The text form is the single line `nupp VERSION`, which is what an install
script should read. `nupp --version` prints the same line.The text form is one line and stays one line, in the shape an install script or a packaging recipe already expects:
nupp 0.0.2A version with a -dev suffix is a checkout between releases rather than a release. A release is a tag, the tag is built from the commit that set the number, and release CI runs the archived binary and compares what it prints to the tag before publishing it.
--json adds the interpreter underneath, which is LuaJIT for a release binary and plain Lua for the portable compiler:
{"version": "0.0.2", "runtime": "LuaJIT 2.1.1234567890"}Answering costs nothing but starting the process: the number is a string in the compiler and reaching it loads no part of the compiler it names. That makes it the cheapest way to tell whether an install works at all.
help#
Show general or command-specific help
Usage:
nupp help [command]
Options:
--color[=WHEN] When to color output: always, never, or auto (default)
--no-color Never color output; the same as --color=never
-h, --help Show this help
With no command, prints the command list.Bare nupp prints the same list:
Nupp compiler and project tool
Usage:
nupp <command> [options]
nupp help [command]
nupp --version
Commands:
init Create a project from a template
ast Dump a Nupp file's parsed syntax tree
aot Show what the @aot functions in a file compile to
bc Show the bytecode a Nupp file compiles to
check Type-check source without emitting Lua
fmt Format Nupp source
build Build source files or a configured project target
clean Remove build outputs configured in nupp.lua
tasks List or inspect project tasks from nupp.lua
lints List the lints and the level each runs at
ownership-audit List foreign pointer contracts and unsafe assertion sites
explain Describe a diagnostic code, with an example either way
reference List or print a focused Nupp reference chapter
completions Print a shell completion script
test Build and run project tests
test-runner Run test suites with the bundled runner
coverage Run tests and write a source coverage report
task Build, then run a named task from nupp.lua
doc Generate API documentation from source comments
fixpoint Verify a byte-identical self-hosting rebuild
run Compile and run a Nupp or Lua program
import-c Generate typed Nupp bindings from a C header
migrate Migrate typed foreign source into gradual Nupp
export-c Export canonical C declarations for Nupp structs
rock Package and check typed LuaRocks libraries
lsp Language-server and semantic source operations
version Print the compiler version
help Show general or command-specific help
Run 'nupp help <command>' for command-specific options.