Your first project¶
This guide walks through creating, developing, testing, and sharing a Nerv application. It assumes you have installed Nerv. If the syntax is new to you, start with the language guide.
The project¶
We will build vars, a command-line program that prints an environment variable:
Given an environment variable named NERV_GUIDE_NAME containing lucy, it prints:
The standard library handles arguments and environment access. A small local library will format the output, giving us a dependency to develop and test.
Creating a project¶
In a directory where you keep projects, run:
The command creates this layout:
Nerv.toml contains the package name, version, and dependencies. Nerv.lock records resolved local packages. src/main.nerv contains your application and an initial test block. .gitignore excludes generated target/ output.
Nerv does not generate a README, a separate test directory, or a GitHub workflow. You can add those when your project needs them. Commit Nerv.toml and Nerv.lock with your source.
Running the project¶
Open src/main.nerv. Its generated program is:
Run it:
Nerv builds a native executable and starts it. It prints Hello from Nerv. The final 0 in main becomes the process exit status; it is not printed. Build output goes under target/nerv/debug/.
src/main.nerv is the default application entry file. Projects with a custom entry can set entry under [package] in Nerv.toml.
Adding dependencies¶
Nerv currently supports local path dependencies. There is no registry or nerv add command. Create a sibling library from inside vars:
You now have vars and pairs beside one another. Replace pairs/src/lib.nerv with:
fn format_pair(name: string, value: string) -> string
nerv.string.concat(nerv.string.concat(name, "="), value)
test "formats a name and value"
assert(format_pair("hello", "world") == "hello=world")
test "formats an empty value"
assert(format_pair("EMPTY", "") == "EMPTY=")
The format_pair function joins a name, an equals sign, and a value. The test blocks exercise ordinary and empty values.
Edit vars/Nerv.toml:
[package]
name = "vars"
version = "0.1.0"
[dependencies]
pairs = { path = "../pairs" }
The path is relative to the manifest, so ../pairs selects the sibling package. Run this from vars to resolve the change:
This updates Nerv.lock. Commit the new lockfile with the manifest change. Afterward, nerv check --locked verifies the existing resolution without updating it. If you edit a manifest again, run without --locked to refresh the lock.
The lock records package versions and manifest checksums. It does not freeze the source of a local library; editing that library recompiles it. Keep related packages in version control and preserve their relative paths when sharing source. Registry versions, version ranges, Git URLs, and automatic dependency updates are not supported.
Using dependencies¶
Replace vars/src/main.nerv with:
import nerv.std
import pairs
fn print_pair(name: string, value: string) -> i64
println(pairs.format_pair(name, value))
0
fn missing(name: string) -> i64
stderr(nerv.string.concat("Variable not found: ", name))
stderr("\n")
1
fn main() -> i64
let args = arguments()
if nerv.arrays.len(args) != 3
println("Usage: vars get <name>")
return 1
if args[1] != "get"
println("Usage: vars get <name>")
return 1
let name = args[2]
match environment(name)
Some(value) => print_pair(name, value)
None => missing(name)
import nerv.std loads the embedded standard-library helpers. import pairs resolves the library declared in your manifest. pairs.format_pair(...) selects its function explicitly.
arguments() returns an array whose first element is the executable path. We expect two more elements: get and a name. Check the length before indexing. Arguments after the compiler command's -- are passed to the application.
environment(name) returns Some(value) or None. The match prints a value and exits successfully when it exists. A missing variable prints to standard error and returns status 1. An empty variable is still a present value.
Set a value and try it:
Both print NERV_GUIDE_NAME=lucy. Running nerv run without program arguments prints the usage message and exits with status 1.
Testing your code¶
Nerv has built-in test blocks and assertions. No test-runner dependency is needed. From vars, create a tests directory and add:
import pairs
test "formats a pair through a dependency"
assert(pairs.format_pair("hello", "world") == "hello=world")
test "keeps an empty value"
assert(pairs.format_pair("EMPTY", "") == "EMPTY=")
Run the application's tests:
Nerv discovers test blocks in the application's source and under tests/. It runs files in separate processes. A failed assertion stops its file, while later files still run. The reported totals count files rather than individual assertions.
Dependency tests do not run automatically as part of the application's suite. Run the library's own tests explicitly:
Functions are currently public without an export modifier. An internal directory has no special visibility enforcement. Organize helpers by module, and treat the library's documented functions as its supported API.
Before committing, format and check the application:
Sharing your program¶
Build an optimized executable:
Run it directly:
Share that executable with someone using a compatible system of the same operating system and architecture. They do not need Nerv, LLVM, or the pairs source. This program uses only operating-system facilities; programs that read external files must distribute those files separately.
A native build targets the machine running the compiler. Nerv does not currently offer a cross-compilation target flag. Build and test separately for each platform you want to distribute. Keep the default generic CPU setting for a wider range of machines, and check the platform baselines.
For source distribution, include both vars and pairs, their manifests, and their lockfiles. You can omit target/.
Example files¶
The complete example is also available as individual files:
Keep the sibling layout, then run nerv check once to generate lockfiles. For additional options, see commands and projects.