Using Dodo Dodo 0.1.4

Use the command line

Format, check, run, and compile Dodo projects; select output formats and target platforms.

On this page

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.

Type to search all documentation.

Keyboard shortcuts

Search documentation
Ctrl K or /
Move through results
↑ ↓
Open selected result
Enter
Close a dialog
Esc
Show these shortcuts
?