Command Line

Everything is done with one command, refingere: it checks a project, and can then run it, test it, compile it, and keep doing so as files change. This page lists its options and explains its output. Getting Started has the download - one file for each platform, with nothing else to install - and walks through the same commands step by step.

The command

Every option is optional, and they can be given in any order. With none at all, it prints its help.

OptionDoes
<folder or file> The project: a folder, whose .rfn files and subfolders are one program, or a single file. Relative to the folder the command runs in. Without it, the current folder.
--checkChecks every file and reports problems. Nothing runs.
--format, -f Formats every source file in the project in place - or the one file given - the way Refingere is written: two spaces per level, one blank line between definitions, single spaces between words. Nothing is compiled. Only whitespace ever moves, and a file that doesn't parse is left as it is and named. The same formatter as the editor's - see Formatting.
--root=<folder or file>The project, given as an option rather than last: rfn -f --root=shop/main.rfn.
--expand-in-place[=<file>[:<line>]] After a clean check, replaces each macro use - every one in the project, a file's, or a line's - in its file by what it stands for, one level deep. What a program macro gives goes to .refingere/expanded. See Expanding a macro in place.
--edits With --expand-in-place: write nothing, and print the edits as JSON instead - each with its file, its start and end as a line and a column (both from 1, the column in code points) and its new text; then what each program macro gives, and anything that couldn't be expanded. What an editor runs.
--run After a clean check, compiles the project to JavaScript (or WebAssembly, with --wasm) and calls the @main function of the folder. What follows -- is the program's own arguments, &args.
--test After a clean check, compiles the project's @test functions that are in no stage, and runs them. --test=e2e,service runs those in the stages it names as well, and --test-all runs every test - see Testing. --test-name=<name> runs just the test a run reports as <name>, whatever its stages - once for each test, since a name can hold commas; it's how an editor runs the one test it's on. With --run too, only the tests run, not @main.
--errors-only With --test or --test-all, lists only the tests that fail, then the tally - no line for each one that passes.
--update-snapshots With --test or --test-all, a snapshot check whose text differs from the kept one keeps the new text instead of failing - see Snapshot tests. The program runs with REFINGERE_UPDATE_SNAPSHOTS=1 in its environment, which is what turns it on for a program built with --tests too.
--bench Compiles the project's @bench functions that are in no stage to time, instead of @main. --bench=slow,e2e times those in the stages it names as well, and --bench-all every one. With --run, times them and measures the memory a call allocates - with --js, on a WebAssembly build of them first - then, when the folder has an @main, runs it once under a profiler and lists the functions it spent its time in. See Benchmarking.
--profile Runs @main and the @bench and @test functions in no stage twice - once sampled for where the time goes, once counting every call and every allocation, always compiled to WebAssembly - and writes what it found as a website: .refingere/profile in the project's folder, or profile in --out. --profile=slow,e2e takes those stages' too, --profile-all every one. It runs the program itself, without --run. See Profiling.
--bench-whole--bench, timing the project's tests too - each called over and over as an @bench function is, after the benchmarks. The tests are picked by their stages the way --bench=... and --bench-all pick benchmarks; async tests are left out. For a project with tests but no benchmarks yet.
--watch Does all of the above, then stays open and does it again whenever a .rfn file under the project changes. Stop it with Ctrl+C. -w is short for it.
--wasm, --js Compiles the project to WebAssembly or JavaScript - written to --out, or to standard output without it. With --run or --test, picks what the program is compiled to before it runs. See Compiling to WebAssembly and Compiling to JavaScript.
--native Compiles the project to WebAssembly, then that to a native executable with Wasmer - written to --out as program, or named for the project in the current folder. With --run, runs the executable. See Native executables.
--tests What --test is made of: compiles the tests instead of @main, without running them - so --tests --wasm --out=out writes a module that runs them. --tests=... and --tests-all pick them as --test=... and --test-all do.
--out=<folder> Where results are written instead of printed - the compiled program, and what any of the compiler's steps asked for gave.
--js-host With --wasm, compiles the program for a JavaScript host, the way --run and --web do, without running it: its @js functions, a server @serve starts and Gpu reach the host, and program.js.json beside program.wasm says what it needs from it.
--web The project is a website: a folder with an index.html whose scripts import Refingere straight from its .rfn files. Serves it on http://localhost:3000, built again on every save, with open pages reloading themselves - a build that fails shows its errors in the page instead. With --compile, builds the finished site into its dist/ folder instead; with --js, its Refingere is compiled to JavaScript rather than WebAssembly. Bun bundles the page. See Websites.
--port=<port>Where --web serves the site: rfn --web --port=8080.
--new Creates a project from a template, the way bun create does: --new="my-site" names its folder, and --template=website or --template=program picks what it starts as. Either one left out is asked for. The folder has to be new, or empty.
--progress Shows what the compiler is doing while it works: one line on stderr - a spinner, the step it's on (Lex, Typecheck, Comptime, ...) and the file - drawn over itself in place. Anything else written clears it first, and it's cleared when the run ends, so only the usual output is left. Before --run starts the program, it's gone too.
--stats Once the run's done, shows how long each step took as a table on stderr - apart for the program's own files, the standard library's, and the work on both together (compiling the whole program, running it) - with a total for each.
--no-colorShows problems without terminal colours. Setting NO_COLOR does the same.
--library=<folder> The standard library programs are compiled with - $REFINGERE_LIBRARY by default, which the downloaded command sets to the copy it unpacks into ~/.cache/refingere (%LOCALAPPDATA%\refingere on Windows) on its first run.
--help, -hLists every option.

Compiling to WebAssembly

WebAssembly is Refingere's compilation target, chosen web first: it runs in every browser, and the same modules run outside one through WASI - see Design Notes.

With --wasm --out=<folder> the program becomes <folder>/program.wasm - a WASI program that wasmtime, or any other WASI runtime, runs without the compiler. Running it calls @main; built with --tests, it runs every @test and prints the same report as --test. A program that calls JavaScript - its own @js declarations, SQLite, or an @serve server - also gets program.js.json, saying what it needs from its JavaScript host; run it with rfn --wasm --run, which provides one. See JavaScript and TypeScript.

Run with --run, or on Bun, a program has threads: @parallel and Promise.parallel work runs on them, and a --tests build runs its tests side by side, one per core. REFINGERE_THREADS=<n> sets how many, and 0 runs everything on one thread. A WASI runtime has no threads to give, so there the same work runs on the program's own thread, one piece after another - with the same results. See Threads.

&stdout and &stderr are the process's standard output and error, and &logger writes its lines to &stderr. A @main that ends with an error prints it to standard error and exits with status 1. A mistake such as adding a number to text prints error: ... and exits with status 70. A compiled program reads files through the folders it was given: --run gives it the current folder and /; when running a module yourself, pass them to wasmtime with --dir=..

Every builtin type's members come from a standard library compiled into the program with it, and unused memory is reclaimed as the program runs. Values carry their type at runtime rather than having it checked beforehand: every number keeps its own type, so a U8 past 255 is an OverflowError (or wraps or clamps, under +% or +|), and mixing two number types without an as stops the program.

Native executables

--native takes the WebAssembly one step further, to an executable for the machine it's built on - one file that runs with nothing installed, not Bun, not a WASI runtime. The module is compiled ahead of time to machine code by Wasmer, and linked by the C compiler with Wasmer's runtime into a small launcher, which gives the program what --run does: its arguments, the environment, standard input and output, and the current folder and / to read and write files through.

It needs Wasmer - curl https://get.wasmer.io -sSfL | sh installs it in ~/.wasmer, or $WASMER_DIR says where it is - and a C compiler, cc, or the one $CC names. The executable carries Wasmer's runtime, so even a small program is about 30 MB. Only a plain WASI program can be one: a program that calls JavaScript - its own @js declarations, or an @serve server - needs a JavaScript host, and is refused. The library's own JavaScript, such as Command and SQLite, gives back an error saying so when it's called.

Compiling to JavaScript

--run and --test compile the program to JavaScript unless --wasm says otherwise, and run it on Bun - the copy inside the downloaded command, so nothing else is needed. --js --out=<folder> writes it as one ES module, <folder>/program.mjs, which Node and Bun run as it is. The program behaves the same either way - it prints the same text, ends with the same errors and exits with the same status - because both are compiled from the same syntax tree, with the same standard library and the same value model: every number keeps its own type, and the exact number arithmetic is the same code.

Run directly, the module calls @main - or, built with --tests, every @test - with the process's arguments as &args. It imports its runtime from the compiler's own copy, so it runs on the machine that compiled it. It reads files through node:fs, relative paths from the current folder. Its calls nest as deep as the JavaScript engine's stack allows - about ten thousand in Node, more in Bun - and deeper recursion stops it with error: stack overflow, as the WebAssembly backend's own limit does.

Looking inside the compiler

The compiler works in steps - collecting the files, lexing, parsing, linking names, type checking, lowering to an intermediate language, expanding macros, optimizing and compiling (see The Compiler). Each step has a flag that shows what it made: --files, --tokens, --ast, --analyze, --typecheck, --ir, --optimized-ir and the rest that --help lists. Any of them can be given together; the run goes as far as the last. Their results are printed, or with --out written for each file, and --from=<step> starts from there, reading the step before it back from --out for every file that hasn't changed since.

Reading the output

The output comes in the same order every time:

  • The program, with --run - what it prints, and when @main ended with an error, Program errored: ....
  • The tests, with --test - a PASS or FAIL line for each test as it finishes, a failure with its error, then how many passed.
  • The check - No errors found. (left out with --run, where the program's own output shows it built), or every problem found, then Program errored: ... saying how many files have errors.

When the check finds errors, neither the program nor the tests run - the same way a compiler wouldn't run a program that failed to build.

Errors and warnings

Each problem is reported with its file, line and column, and the line itself with a ^ under the spot. They're grouped by file, errors first, then warnings - such as a use of something @deprecated, or a @testHelper outside a test - then informational notes. Warnings and notes don't stop the program from running.

Exit status

The command exits with status 0 when everything it was asked to do worked, and 1 when the check found errors, a test failed, the program ended with an error, or the project couldn't be read. A CI pipeline can run rfn --test to fail the build on any of them.

Common problems

  • This has no @main function to compile - --run needs a function marked @main directly in the project's folder, not a folder inside it.
  • no such file '...' - the folder or file doesn't exist, relative to where the command was run. Run it from the folder above the project, or give a full path.
  • No @test functions found. - --test found nothing marked @test in the project.

The repository's command

Built from source, the compiler's repository has a second command, written in TypeScript - bun run src/index.ts. It differs in a few options - port= rather than --port=, root= beside --root=, and --target=js for --js; see Building from Source.

It also debugs: --debug builds the program to JavaScript with a source map back to its .rfn files and runs it on Node, waiting for a debugger to attach on port 9229 - VS Code, or Chrome's chrome://inspect - which then stops on breakpoints put on lines of Refingere. --debug=nowait starts at once, --debug=build only builds it and prints where, and with --test it's the tests that run. The VS Code extension's debugger runs it for you - see Editor Support.