Tutorial: a word-count CLI
Build a small, real NURL program end to end.
This tutorial builds a wc-style tool that counts lines, words, and characters in a file. It introduces each piece
of syntax as it comes up. Make sure NURL is installed first. The
finished program is presented also in the repository as examples/wordcount.nu
Start with the shape
Every program has one main, and a struct to hold the three numbers
we're computing:
: Stats {
i lines
i words
i chars
}
@ main → i {
^ 0
}: Stats { ... } declares a struct with three i (64-bit integer)
fields. See Structs, enums, and traits.
Read the file
Command-line arguments come from nurl_argv_count / nurl_argv_get, and
nurl_read_file reads a whole file into a string:
@ main → i {
: i argc ( nurl_argv_count )
? < argc 2 {
( nurl_print `Usage: wc <file>\n` )
^ 1
} {}
: s filename ( nurl_argv_get 1 )
: s content ( nurl_read_file filename )
^ 0
}? cond then else is the ternary. We use it here for its side effect (an
early return), with an empty {} else block. nurl_read_file prints an
error and exits the process if the file cannot open, so there is no
Option/Result to unwrap here. See Error handling for
the pattern where a function's own return type carries the failure
instead.
Walk the string
count_stats scans the text one character at a time with a while loop.
It tracks whether the scan is now inside a word. The scan reads the text
through a byte view from stdlib/core/slice.nu, so add that import at the
top of the file:
$ `stdlib/core/slice.nu`
@ count_stats s text → Stats {
: ( Slice u ) bytes ( slice_of_str text )
: i len ( slice_len [u] bytes )
: ~ i lines 0
: ~ i words 0
: ~ i chars len
: ~ b in_word F
: ~ i idx 0
~ < idx len {
: i ch ( slice_byte bytes idx )
// ch 10 is the newline character code (\n)
? == ch 10 {
= lines + lines 1
= in_word F
} {
? | == ch 32 | == ch 9 == ch 13 {
= in_word F
} {
? ! in_word {
= words + words 1
= in_word T
} {}
}
}
= idx + idx 1
}
? & > len 0 != ( slice_byte bytes - len 1 ) 10 {
= lines + lines 1
} {}
^ @ Stats { lines words chars }
}A few things worth noticing:
slice_of_strmeasures the string once;slice_bytethen reads byteidxin constant time, and reads 0 past the end. (nurl_str_get text idxreads a byte too, but measures the string from its start on every call — a loop over a file with it is quadratic in the file's size, andnurlcwarns about exactly that shape.)~ < idx len { ... }is the while loop: prefix condition, then body. See Control flow.10,32,9,13are raw character codes (newline, space, tab, carriage return). NURL has no character literal syntax, so comparisons go against the byte value directly.| == ch 32 | == ch 9 == ch 13is the n-ary-or pattern from Control flow:|is strictly binary, so you write a 3-way OR as two|operators.@ Stats { lines words chars }builds the return value field by field, in declaration order.
Print the result and wire it together
@ print_stats Stats st s filename → v {
( nurl_print ` ` ) ( nurl_print_int . st lines )
( nurl_print ` ` ) ( nurl_print_int . st words )
( nurl_print ` ` ) ( nurl_print_int . st chars )
( nurl_print ` ` ) ( nurl_print filename )
( nurl_print `\n` )
}. st lines reads the lines field off the st struct parameter — see
Structs, enums, and traits. Now call
both from main:
@ main → i {
: i argc ( nurl_argv_count )
? < argc 2 { ( nurl_print `Usage: wc <file>\n` ) ^ 1 } {}
: s filename ( nurl_argv_get 1 )
: s content ( nurl_read_file filename )
: Stats st ( count_stats content )
( print_stats st filename )
^ 0
}Compile and run
Compile and run the program to make it read its own source code and count:
nurl wc.nu # or ./nurl.sh wc.nu in a source checkout
./wc wc.nuOr run the two compiler stages manually:
nurlc wc.nu > /tmp/wc.ll
clang /tmp/wc.ll ~/.nurl/lib/runtime.native.o -lm -lpthread -o /tmp/wc
/tmp/wc wc.nuSee Compiling and running for what each stage does and the available flags.
What this covered
Prefix function calls, mutable (~) vs. immutable bindings, while loops,
ternaries used for control flow, structs, and field access. Nothing here touches ownership explicitly,
because none of these values need heap allocation.
Memory and ownership covers what changes once a
program starts to allocate.
Next
- Language basics and Control flow for the full reference of this tutorial.
- Standard library for what else is available beyond
nurl_read_file/nurl_print. - More runnable programs, from FizzBuzz to an HTTP server, are in
examples/.
Last updated on