A command-line program
TutorialRead from stdin, write to stdout, and ship a single .wasm file.
What we build
A small running-total calculator called sum. It reads one line at a time. A number is added to the total. A few words do special things.
Along the way you will use WasiCli to read and print, a function that calls itself to repeat, tests for the logic, and bert build to make a file you can run anywhere.
You will also meet Wasmtime, a free program that runs WebAssembly files. To build a program means to turn your Bert code into such a file, once, so you can run it later without Bert.
This tutorial makes a plain WebAssembly file. That is what Bert makes unless you ask for something else.
It assumes you have bert and Wasmtime installed from the Download page, as the guide's install section describes.
Set up
Make a folder with an empty bert.toml, as in the guide:
All the code below goes in a file called sum.bert. We will grow it step by step. To test an early step, put it in its own file such as echo.bert.
Read a line
A command-line program has two channels. Standard input (stdin) is what comes in, such as what you type or pipe in. Standard output (stdout) is what the program prints.
WasiCli.readLine() reads one line from stdin. It can fail: when stdin is closed, there is no next line. So it gives back a Result, the Ok or Err value you met in the guide: either Ok(line) when there was a line, or Err(EndOfInput) when there was not. The Errors chapter shows how results work.
The line comes back without its line break. An empty line is Ok(""), which is different from Err(EndOfInput). That lets a program tell "the user pressed Enter" from "there is no more input".
WasiCli.print writes exactly what you give it, so we add the \n ourselves.
Keep reading
Bert has no loops, so a function calls itself. Here repeat reads a line, prints it, and starts over:
Two things to notice:
_ = WasiCli.print(…)prints, then throws away whatprintgives back. In a{ }block, every line but the last must give a name to something._is the name for "I do not care". The Start chapter uses it too.repeat()is the last thing the function does. So Bert can start the function over in the space it already uses, instead of taking more. That is why this can run for a million lines, or forever, without running out of memory.
Do the work
Now the actual calculator. A good habit: put the thinking in a function that does not read or print anything, and keep the reading and printing in a thin shell around it. We call the thinking part step.
step takes the current total and a line. It answers with a Reply: either Say a message and keep going with a new total, or Stop.
It matches on the trimmed line. A string pattern like "quit" is checked first. Anything else lands in text, which we try to turn into a number with Int.fromString. That gives an Ok or Err again, so we match once more.
Now the shell around it. Add this below step:
The total is not stored anywhere. Names never change in Bert, so loop passes the new total to its next call: loop(newTotal). Each round has its own total.
The 99 after quit is never read. The program stopped first.
Test it
Because step does not read or print anything, you can test it by calling it. Put tests at the bottom of sum.bert:
Each test compares the Reply with the one it expects. == compares variants by what is in them, so Say(15, "15") only equals another Say(15, "15").
Arguments and exit codes
A command-line program can also get arguments: words you type after the program's name when you start it, like Ada in wasmtime run sum.wasm Ada. WasiCli.args() returns them as a list of strings. The first item is the program itself, so the first real argument is at position 1.
Arguments only reach the program when you run the built file with Wasmtime, in the next step. bert sum.bert Ada does not pass Ada on: it stops with "unexpected extra argument". So write this step now and try it in the next one.
Let sum greet you by name. Add this above loop:
List.nth gives an Ok or an Err, so a missing argument is handled, not a crash. Call it first in _start:
Last, quit should leave with an exit code: a number the program hands back to the terminal when it ends. 0 means all went well, and any other number means something went wrong. WasiCli.exit(0) ends the program with code 0. Change the Stop arm in loop:
exit ends the program, so it can stand in either arm.
Build a .wasm file
So far bert sum.bert compiled and ran the program in one go. To keep the result, build it:
You get sum.wasm, a file of about 10 KB. Bert is no longer needed to run it. Any program that runs WebAssembly command-line programs can run it, such as Wasmtime:
The name after the file reaches the program as an argument, so it says "Hello, Ada". With no name it says "Hello!". The final bye comes from stdin running out.
The whole program
Ideas to try
- Add a
resetcommand that sets the total back to 0. - Read the whole input at once with
WasiCli.readAll()and add up every line withString.splitandList.foldl. - Read an environment variable with
WasiCli.env("NAME"). It givesErr(NotFound)when the variable is missing. - Make a
Floatversion. Remember thatFloat.fromStringreads numbers the way JSON writes them.