These examples build on your first program. Run commands
from the hello project folder containing main.dodo. Use dodo --help for
commands, dodo help build for compiler options, and dodo --version for the
compiler version.
Follow a repeatable workflow
During development, save your files, run dodo fmt, then dodo check, then
dodo test. Run the application with dodo run when the checks pass. Use
dodo build when you need a persistent executable to distribute or debug.
Formatting changes layout; checking catches language errors; testing runs the
behaviors you assert. None of those steps substitutes for the others.
For a first project, the commands in the next table are enough. Output formats, cross-target linking, and panic hooks are advanced options you can return to when your project needs them.
Everyday commands
| Command | What it does |
|---|---|
dodo fmt |
Format the project folder recursively. |
dodo check |
Check syntax, types, ownership, and borrowing. |
dodo run |
Build a temporary executable and run it. |
dodo test |
Discover and run tests recursively, in isolated processes. |
dodo build |
Keep an executable at build/hello without a manifest. |
dodo compile |
Alias for dodo build. |
dodo targets |
List named targets from dodo.toml. |
dodo init |
Create a small project without overwriting files. |
dodo lsp |
Start the language server for an editor. |
check does not need a main function or a C toolchain. Building and running an
executable require a C toolchain and a hosted entry point: fn main(),
fn main() -> void, or fn main() -> i32.
compile writes its final output only after compilation and linking succeed.
Choose a different location with -o or --output:
dodo compile -o build/my-program
./build/my-program
On Windows, an explicit extension makes a persistent executable easy to launch
from PowerShell: dodo compile -o build/my-program.exe, then
.\build\my-program.exe. See installation if the compiler
cannot find cc; use --linker clang or set DODO_CC after configuring Clang
and the Windows C++/SDK toolchain.
run cleans up its temporary executable and returns the program’s exit status.
Arguments after -- are passed to the executable;
std/env provides access to them:
dodo run -- example-argument
For editor configuration and protocol support, see editor setup. See compiler diagnostics for error examples.
dodo test scans the current directory for test functions and explicitly
executable documentation examples. Use dodo test --list to inspect discovery,
--filter TEXT to select tests, and dodo test --help for its options. See
Test your code for assertions, companion test files, and failure
reports. Testing does not require main.dodo.
Project folders and source files
Without a manifest or input path, check, compile, build, and run select
main.dodo in the current folder. Compiler options can follow the command
directly. An explicit directory first looks for dodo.toml, otherwise selects
its main.dodo. A project requires no manifest, lockfile, package manager, or
special library folder.
dodo check
dodo run -O 2
dodo compile --emit llvm-ir
dodo run .
dodo compile path/to/project -o build/my-program
dodo run examples/hello.dodo
The entry file and its imports are loaded. Other files beside main.dodo and
unimported subfolders are not automatically included. If the selected entry is missing,
the command reports an error; it does not search parent folders, src/, or
alternate entry names. Pass an explicit source file to use another filename.
Shared code lives in ordinary imported subfolders. An imported folder combines
its immediate .dodo files into one package; those files must declare the same
package, and need no main.dodo or lib.dodo. See
projects and imports for a complete example and import rules.
Without a manifest, the default output uses the folder containing main.dodo:
a project named hello produces build/hello. This applies whether you omit the input, pass a
project folder, or pass its main.dodo explicitly. Other explicit source files
use build/<source name>. Artifact extensions are appended to the full name,
such as build/hello.ll for LLVM IR.
CLI output paths are relative to the shell’s current folder, even when compiling
another project folder. Use -o to choose a different path.
Optional configuration and CLI conveniences
Save targets, profiles, and default arguments in dodo.toml.
Use -b NAME for a named target, and --target TRIPLE for an LLVM platform.
--release works with both manifests and standalone files. Explicit source
files bypass manifests; --no-manifest bypasses a folder’s manifest.
dodo build -b server --release
dodo build --print-config
dodo help run
dodo completions bash
Long value options support both --option value and --option=value.
-O2, -oPATH, and -bNAME are accepted. --no-debug disables saved debug
information; --clear-link-args clears saved linker arguments before appending
explicit ones. -v / --verbose displays configuration and subprocess commands;
-q / --quiet suppresses progress and success messages, never program output.
Build/check status goes to stderr. Primary output, including formatted source,
configuration reports, help, and program output, goes to stdout. Invalid CLI
usage exits with 2; configuration, compilation, and test failures use 1.
run preserves the child’s exit status. fmt --check uses 1 for differences.
Each command has its own help. check accepts legacy debug, optimization, panic,
and linker options with a notice that they have no effect; saved build settings
are simply unused during checking.
Format source
fmt accepts a file or directory. With no path, it formats the current directory.
Directory inputs recursively include .dodo files, skipping hidden directories,
target, build, dist, node_modules, vendor, and symlinks, using the same
directory exclusions as test discovery. An explicitly selected file or directory
is still processed, even when its name or parent directory would be excluded
during recursive discovery.
dodo fmt main.dodo
dodo fmt .
dodo fmt --check .
dodo fmt --stdout main.dodo
--check reports files needing formatting and exits with status 1 without
writing. --stdout previews a single file without changing it. Use dodo fmt -
to read stdin and write the formatted source to stdout.
The formatter parses every input before replacing any source file. It preserves
comments and literal spellings, and automatically migrates legacy declarations,
array literals, and explicit generic calls to their canonical spellings:
name: Type, [1, 2], and function::<Type>(). Legacy forms remain accepted in
language version 0.1. See
syntax and expressions for syntax
details and the migration policy.
Choose a compiler output
compile produces an executable by default. Other output formats do not require
an entry point or an external linker:
--emit value |
Output |
|---|---|
exe |
Native executable; requires a C toolchain. |
obj |
Object file. |
asm |
Assembly. |
llvm-ir |
Textual LLVM IR. |
bitcode |
LLVM bitcode. |
dodo compile --emit llvm-ir -o build/hello.ll
dodo compile --emit obj -o build/hello.o
dodo compile --emit asm -o build/hello.s
dodo compile --emit bitcode -o build/hello.bc
These compiler artifacts contain no startup code. Use executable output when you want to run a hosted program directly.
Set optimization
Choose -O 0, 1, 2, or 3. The default is 0:
dodo compile -O 2 -o build/hello
Overflow, division, shift, conversion, and bounds checks remain active at every
level. LLVM may remove a check only when it proves the check redundant. A failed
runtime check reports its kind and source location on hosted targets, then aborts.
It does not unwind or run destructors. This reporting also works without -g.
Debug generated programs
Add -g (or --debug) to emit DWARF source locations, function signatures,
parameters, local variables, lexical scopes, and type layouts:
dodo compile main.dodo -g -O 0 -o build/hello
gdb build/hello
# In GDB:
# break main.dodo:6
# run
# next
# info locals
# backtrace
-g works with every output format and does not change the selected optimization
level. Use -O 0 for predictable stepping and variable inspection. At higher
levels, LLVM can inline functions and optimize variables away. Arrays, slices,
strings, pointers, structs, enums, Option, Result, and MaybeUninit describe
their actual target layout. Enum payload fields show physical storage; inspect
the tag before reading a payload. Compiler temporaries are hidden.
Source paths and line numbers follow imports and generic specializations back to
their definitions. Keep source files available at their recorded paths, or use
your debugger’s source-path substitution. Bundled library files are named
<stdlib>/...; map <stdlib> to this compiler version’s stdlib directory to
step through them. The compiler emits DWARF 4, with the C expression evaluator
for debugger expressions; it does not emit Windows PDB files. GDB sessions on
Linux and DWARF validation for Linux, Windows GNU, ARM, and WebAssembly objects
are covered by smoke tests. On Darwin, keep object output and use the platform
linker and dsymutil to produce a debug bundle before deleting the object.
Configure runtime failures
The default --panic auto reports a message such as
dodo: index bounds check failed at /path/main.dodo:8:12 to standard error and
calls C abort on Linux, Windows, Darwin, BSD, Solaris, and illumos targets.
It uses write (_write on Windows) and requires the target C runtime when
linking. Other targets, including bare-metal ARM and wasm32-unknown-unknown,
default to llvm.trap. Its lowering is target-dependent: LLVM may emit a trap
instruction or a call to C abort on targets without one. Trap mode therefore
does not guarantee independence from a C runtime. See the
LLVM trap documentation.
Use --panic hosted to select reporting explicitly or --panic trap to select
the target’s LLVM trap behavior, including on a hosted target triple. Embedded
applications can select an optional, non-returning board failure handler:
dodo compile firmware.dodo -g --emit obj --target thumbv7em-none-eabi \
--panic-hook board_panic -o build/firmware.o
Provide this C ABI symbol when linking the object with your board support code:
#include <stdint.h>
_Noreturn void board_panic(const char *check, const char *file,
uint32_t line, uint32_t column);
check names the failed check, and file is its source path. Both are immutable,
NUL-terminated UTF-8 strings with static lifetime. Line and column are one-based;
columns count Unicode characters. The handler owns termination: it may report
the fault, halt, or reset the device, and must never return or unwind into Dodo.
The compiler marks the handler noreturn and ends its call with LLVM
unreachable, without emitting a fallback trap or hosted reporting calls.
Returning from the handler violates this contract and is undefined behavior.
Any runtime dependencies of the handler itself are the board’s responsibility.
The selected policy applies to checked operations and ordinary assert,
assert_eq, and assert_ne failures. The hosted dodo test runner retains its
own failure reporting and trap behavior. No mode unwinds or runs destructors.
The last --panic or --panic-hook option selects the strategy.
Targets and linking
--target, --cpu, and --features select LLVM code generation. All LLVM
targets are included in the compiler. A supported code-generation target does
not imply that every hosted library package supports that operating system.
For example, std/console is not an import for a freestanding WebAssembly build.
Save this portable library as arithmetic.dodo:
package arithmetic
pub fn add(a: i32, b: i32) -> i32 {
return a + b
}
Emit a WebAssembly object file (not a standalone browser application):
dodo compile arithmetic.dodo --emit obj --target wasm32-unknown-unknown -o build/arithmetic.o
Cross-target object generation needs no host entry point or C runtime. Linking
firmware still requires the platform’s startup code, linker script, and an
appropriate linker. run executes only the host target. The repository exercises
hosted Linux GNU x86-64 and Windows x64 programs, and portable standard-library
object generation for WebAssembly and Cortex-M0. Object-generation tests do not
verify board startup or hardware execution. See
library availability
for the distinctions that affect imported APIs.
For executables, --linker selects the C linker driver, overriding DODO_CC and
the default cc. Use repeatable --link-arg options to pass arguments to it:
dodo compile --linker cc --link-arg -s -o build/hello
The argument -s in this Linux example asks the linker to strip symbols. Linker
arguments depend on the selected toolchain.
Compiler option reference
| Option | Meaning / default |
|---|---|
-o PATH, --output PATH |
Persistent artifact destination; defaults to build/<project or source name>. |
--emit KIND |
exe, obj, asm, llvm-ir, or bitcode; default exe. |
-O LEVEL, --opt-level LEVEL |
0, 1, 2, or 3; default 0. Runtime checks remain active. |
-g, --debug |
Include source-level DWARF debug information; --no-debug disables it. |
--target TRIPLE |
Select the compilation target; default compiler host. |
--cpu NAME |
Select target CPU features; default generic. |
--features LIST |
Explicit LLVM target features, for example +sse4.2. |
--linker PATH |
C linker driver; overrides DODO_CC, which overrides cc. |
--link-arg ARG |
Append a linker argument; repeat for multiple arguments. |
--panic MODE |
Runtime failure policy: auto, hosted, or trap. |
--panic-hook NAME |
Use a non-returning C ABI failure handler; see the contract above. |
-h, --help |
Show help for the selected command. |
-V, --version |
Show the compiler version. |
run accepts program arguments after --; it owns its temporary output path
and rejects attempts to select a persistent output artifact. Linker arguments
apply to executable output. The last panic policy or panic-hook option wins.
Formatting and testing have their own options, described above and in
testing.
Identify which stage failed
| Stage | Typical symptom | Next action |
|---|---|---|
| Command discovery | The shell cannot find dodo. |
Fix PATH or run the executable by its absolute path. |
| Source loading | main.dodo or an import is missing. |
Check the current directory and import paths. |
| Parsing / type checking | A diagnostic points at source code. | Read the first error and its related labels; see diagnostics. |
| Linking | The driver cannot run, or symbols/libraries are missing. | Verify the selected toolchain and required native dependencies. |
| Execution | The program returns an error code or reports a runtime check failure. | Inspect the program’s Result handling, inputs, and reported source location. |
Use dodo check path/to/file.dodo to separate language errors from linker
configuration. For runtime investigation, rebuild with -g -O 0 and keep the
source files available to your debugger.