Installation#

Nupp is written in Nupp. A checkout carries a stage-0 compiler already lowered to Lua, so building the real one takes a LuaJIT and nothing else.

What you need#

LuaJIT 2.1.1784535649 or newer. Generated Nupp is written in the LuaJIT 3.0 syntax that 2.1 backported — ?., ??, ?:, the bit operators, compound assignment — rather than in a lowering of it. That rolling version is the first build carrying those extensions. bin/nupp reads luajit -v and says which build is wanted, so an older interpreter fails with a sentence instead of a syntax error on a line nobody wrote.

Everything else is optional and buys one feature each:

 Component      Needed for                        Installed by
 ─────────────  ────────────────────────────────  ─────────────────────
 lua-cjson      --json output and the LSP server  luarocks install
 lunamark       nupp doc                          nupp doc
 Scintillua     highlighting in generated sites   nupp doc
 Rust toolchain building the binary host stub     rustup

That table is about a checkout. A stamped binary carries all three of the first ones already — see the binary — and needs nothing installed to check, compile, run or document a project.

From a checkout#

git clone https://github.com/nupp-lang/nupp
cd nupp
./bin/nupp build

bin/nupp is the entry point and it builds on demand: it runs build/nupp/compiler when that exists and no source is newer, and compiles the compiler first when it does not. An edit to the compiler is picked up by the next command rather than by the next person who remembers to build.

The one thing it cannot do is start from nothing, since compiling Nupp needs a Nupp compiler. bootstrap/nupp.lua is that compiler, tracked in the repository, and it is what a fresh clone uses for its first build.

Optional libraries#

Nothing to run: lunamark and Scintillua are declared in nupp.lua as dependencies of the documentation target, so the command that needs them installs them.

./bin/nupp doc

That installs both and their declared rocks into a project-local .rocks tree that bin/nupp and tests/run put on the search path. Nupp's own binaries use their pure PEG compatibility frontend for the LPeg calls in those libraries; they do not carry LPeg's C module. Two checkouts can hold different versions without either disturbing the other, and nothing lands in a global tree. See rock dependencies for declaring your own.

nupp doc needs lunamark and stops with a message when it cannot install or load it. Scintillua degrades instead: a fence in a language it cannot load renders as escaped text without highlighting.

lua-cjson comes from wherever your LuaJIT already looks:

luarocks install lua-cjson

Every command's --json output and the language server read it.

Putting nupp on PATH#

Inside a checkout, run ./bin/nupp. Everywhere else, either put the checkout's bin on your path, or build a self-contained binary.

A self-contained binary#

./bin/nupp build --target dist

That writes build/dist/nupp, a single file carrying the compiler, its standard library, and what it documents with, needing no LuaJIT installed alongside:

 Carried              How                          For
 ───────────────────  ───────────────────────────  ──────────────────────
 LuaJIT               linked into the stub         running anything
 lua-cjson            detected and linked          --json and the LSP
 Nupp PEG frontend    emitted in the payload       legacy LPeg pattern calls
 luautf8              detected and linked          nupp doc's entities
 lunamark             in the payload               nupp doc's markdown
 Scintillua (45)      in the payload               highlighting fences

The lexers are a chosen set rather than all hundred and sixty Scintillua ships — the languages a technical document actually fences, listed at the top of nupp.lua. A fence in something else renders as escaped text, which is what it does with no Scintillua at all. See distribution for the stub-and-payload format, and rock dependencies for carrying your own.

Checking that it works#

./bin/nupp check
./bin/nupp test

check type-checks the project configured in nupp.lua. test builds and runs the suite, which is plain LuaJIT with no framework behind it.

Your first project#

Two files:

hello/
├── nupp.lua
└── src/
    └── app/
        └── main.nupp

src/app/main.nupp:

nupp.lua, the project manifest:

return {
   include = { "src" },

   build = {
      outDir = "build",
      default = "app",
      targets = {
         app = {
            kind = "modules",
            entries = { "app.main" },
         },
      },
   },
}

include names the roots where modules live, so app.main resolves to src/app/main.nupp, and generated Lua keeps that path under build/.

Then, from the project root:

nupp check                  # type-check the project, writing nothing
nupp build                  # compile the default target
nupp run src/app/main.nupp  # compile and run one file

Check the whole project rather than the file you just edited. That is what lets Nupp verify module boundaries, ownership contracts, and lint settings together.

Build system documents every manifest key.

Next#