Skip to content

Reference

CLI reference ​

echoc has five subcommands and thirty-four options. That's the whole surface, and it's one list: what you type, what --help prints, and what a refusal names all come from the same place. There is no second table that can drift.

bash
echoc run app.eco
echoc build -o app src/*.eco
echoc test --filter group:parsing
echoc clean -n
echoc lsp

The echoc CLI is the chapter with the reasoning. This page is the table.

Usage ​

bash
echoc run   [options] <sources...> [-- <program arguments>]
echoc build [options] -o <file> <sources...>
echoc test  [options] <sources...>
echoc clean [options]
echoc lsp   [options]
CommandDoesDefaults to
runcompile and run through the JIT. Nothing is written beside your sources--debug
buildcompile and link a native executable. Needs clang: bundled on Windows, otherwise the one on your PATH--release
testcompile and run the test blocks, one process each, through the JIT--debug
cleanremove what a build produced. Parses no source and runs no passn/a
lspspeak the Language Server Protocol over stdin and stdout--debug

run is the only one that takes --. clean and lsp take no source files: the workspace arrives from the client's initialize request, or from --module. test is the only one that compiles a test block at all: every other invocation drops one before it is parsed, lsp included. See Testing.

lsp publishes diagnostics, hover, go-to-definition, document symbols, find-references, workspace symbols and signature help. Completion is not in v1. Stdout is the protocol stream and nothing else; logging goes to stderr.

Name no sources at all and your program is whatever manifest the invocation points at: the one --module names, or the module.eco in your working directory. A * in a source path is expanded by echoc itself, through the same expander a manifest's #[sources:] uses.

If that manifest declares #[target:]s, it produces one program per target and --target picks between them. See More than one program.

Every option ​

compiling in the third column means run, build and test. lsp takes --module, --no-stdlib and --package-dir; everything else a compile would take is refused by name, because the server renders nothing and builds nothing.

What is built ​

OptionShortCommandsValueDefaultDoes
--output <file>-obuildpaththe target's name, or requiredwhere the executable is written. A manifest declaring targets names its own, so this overrides for one of them and is refused for several
--module <manifest>-mallpath, repeatablebuild a module from its manifest. A file or a directory holding one
--target <name>compilingname, repeatableevery program declaredwhich of the programs a manifest declares to build. run takes exactly one. On test it names a #[target: test] instead, and an unnamed one narrows nothing
--filter <spec>testtagged word, repeatableevery testwhich tests to run. A bare word is a name; group:, file: and module: are the tags. One matching nothing is refused
--build-dir <dir>compiling, cleanpathecobuild beside the manifestwhere build artifacts are written. Outranks #[build_dir:]
--package-dir <dir>compiling, lsppaththe nearest vendor/directory that holds vendored packages. #[requires: "libcurl"] resolves to <dir>/libcurl
--link <requirement>compiling<scheme>:<value>, repeatableadd a link requirement. Merged after the manifest's, so a declaration wins

How it is built ​

OptionShortCommandsValueDefaultDoes
--debugcompilingflagon for runkeep assert and the runtime checks
--releasecompilingflagon for builddrop assert and the runtime checks
--optimize <mode>compilingnone|module|wholemodulehow hard, and over how much at once
--debug-symbols-gcompilingflagoffemit DWARF. Implies --optimize none unless you stated one
--no-tbaacompilingflagoffemit no type-based alias metadata
--track-allocationscompilingflagoffcount outstanding allocations. What mem::live_allocations() needs
--check-refcountscompilingflagofftrap on releasing an already-dead object. Keeps every class box, poisoned
--no-stdlibcompiling, lspflagoffcompile without the standard library
--emit-stdlib-headercompilingflagoffregenerate the embedded stdlib header

--debug and --release are one question and so are --no-stdlib and --emit-stdlib-header. Writing both halves of either pair is a refusal, not a last-one-wins.

What it is built for ​

OptionShortCommandsValueDefaultDoes
--target-os <name>allnamethe hostevaluate #[if:] as if targeting this OS
--target-arch <name>allnamethe hostthe same for the architecture
--ios-devicebuildflagoffwith --target-os ios, emit for a physical iPhone
--define <name>allbare name, repeatabledeclare a flag #[if: NAME] can test
--target-cpu <name>compilingnamea per-platform baselinewhich CPU to select instructions for
--target-features <list>compilingcomma list of +f / -femptyfeatures to enable or disable

--define takes a bare name only. There is no NAME=value form, because a condition tests presence rather than equality.

--target-cpu is never the host by default. native has to be asked for by name, since a binary built for the host CPU is an illegal instruction on the machine next door rather than a diagnostic.

--target-os and --target-arch change what a #[if:] sees. On every subcommand except one, that is all they do: the code is still compiled for this machine.

The exception is Darwin echoc build --target-os ios. That is a real cross-compile, to the iOS simulator SDK. --ios-device switches it to a physical iPhone. echoc run --target-os ios still only picks #[if:] arms and JITs for this machine, which is how a test asserts what the iOS branch does. Other names (linux, windows, android) stay facts-only on every subcommand: there is no Linux sysroot on a Mac.

--target-os ios also selects family == darwin. There is no --target-family.

What echoc tells you ​

OptionShortCommandsValueDefaultDoes
--print <what>-pcompilingsee below, repeatabledump what the compiler built, by layer
--explain <what>compilingsee below, repeatableexplain a decision the compiler made
--diagnostics <mode>compiling, cleanauto|pretty|ascii|jsonautohow a diagnostic is drawn
--color <when>compiling, cleanauto|always|neverautocolourise diagnostics. Also spelled --colour
--silentcompiling, cleanflagoffdo not draw the progress checklist. Silences that and nothing else
--verbosetestflagofflist every test, with how long each one took
--timeout <ms>testmillisecondsnonekill a test that is still running after this many milliseconds

--verbose replaces the per-test lines with a listing: every test under the file it is written in, its group and its duration, a count and a total per file, and whatever a test printed. That last part includes a passing test's output, which is the one thing no other rendering keeps. What a terminal shows is then what a pipe records.

What clean removes ​

OptionShortCommandsValueDefaultDoes
--with-stdlibcleanflagoffalso remove the standard library's store
--dry-run-ncleanflagoffprint what would be removed, remove nothing

General ​

OptionShortCommandsDoes
--stdiolspname the stdio transport. It is the only one, so this flag and omitting it are the same thing
--help-hallthe page, or one option in full
--version-vallprint the version

Both short-circuit every other rule, so echoc build --help prints the page instead of complaining about the missing -o.

--help also takes an option name, bare and without dashes, which is where the paragraphs live:

bash
echoc build --help optimize
echoc build --help g

There are exactly seven short options: -h, -v, -o, -m, -p, -g, -n.

Value vocabularies ​

OptionValueMeans
--optimizenoneskip the per-unit pipeline
moduleoptimize each unit on its own. The only mode that leaves a cacheable object, which is why it is the default
wholemerge every unit, then optimize. Cross-module inlining, and no object stored
--printastthe AST as parsed
ast-resolvedthe AST after the semantic passes. Every deref, drop, retain and release is a node here
irthe merged whole-program LLVM IR. Implies the merge
ir-unitseach unit, as the object writer gets it
symbolsthe registered symbol table
instancesgeneric instances and rewired call sites
manifestthe named manifests as JSON, then stop. Does not resolve the graph. Cannot be combined with another --print
--explaincacheeach module's key, whether its artifact is present, and on a miss which input changed
prunewhat the JIT prune dropped. run and test only
memorylive allocations when the program ended. Implies --track-allocations
timewhere the compile spent its time, as a tree
--diagnosticsautopretty when stderr is a terminal that can draw it, ascii otherwise
prettybox drawing and colour
asciithe same layout in plain ASCII
jsonJSON Lines on stderr, one object per diagnostic then one summary. What editor tooling reads
--colorautoon when stderr is a terminal, honouring the environment below
alwayson regardless, which is what a CI log wants
neveroff regardless

--explain memory is the odd one: it changes the emitted program rather than reporting on the compile.

The mask is per value, not just per option ​

A subcommand can accept an option and still refuse one of its values. Exactly one value is narrower than its option today, because only the JIT prunes, and it is narrower to the two commands that do:

error: 'build --explain prune' is not something 'build' can answer. It accepts: cache|memory|time.

A flag a subcommand accepts and silently ignores is worse than one it rejects, and a value is no different.

-g is the near miss. run does accept it, and then tells you it can't honour it rather than refusing the invocation:

[warning] Debug Info Ignored

  '-g' produces no artifact a debugger can open on 'run': the JIT emits no object file. Use
  'echoc build -g' and open the resulting executable instead.

Refusals ​

Every one of these is exact, and every one goes to stderr followed by the usage block. The help page is the only thing that goes to stdout.

You wroteYou get
echocNo command given.
echoc frobnicate'frobnicate' is not an echoc command. Write 'run', 'build', 'test', 'clean' or 'lsp'.
echoc run --nonsenseUnknown option '--nonsense'.
echoc run --optimize hardUnknown '--optimize' value 'hard'. Expected one of: none|module|whole.
echoc build x.eco'build' needs '-o, --output <file>' - nothing here names the binary. Reported once the manifest is known, since a project declaring targets names its own
echoc build -o'-o, --output <file>' needs a value.
echoc build -o --silent'-o, --output' needs a value, and '--silent' is an option.
echoc run --silent=1'--silent' takes no value.
echoc run --debug --release'--debug' and '--release' are two answers to one question - the build mode. Write one.
echoc run --no-stdlib --emit-stdlib-headerthe same sentence, ending - the standard library. Write one.
echoc clean --explain cache'clean' does not take '--explain'.
echoc clean x.eco'clean' takes no source files. It parses none and runs no pass.
echoc build -o app x.eco -- aOnly 'run' passes arguments to the program, so '--' means nothing to 'build'.
echoc build --help with-stdlib'build' does not take '--with-stdlib'.
echoc build -oout x.eco'-oout' is not an option. A short option is one character - write '-oout' - and a long one takes two dashes.

That last one is trying to tell you to write -o out and prints the word you typed instead. It is a bug in the message, not in the rule.

Retired spellings ​

An old spelling is refused with the sentence naming its replacement, never quietly accepted and never warned about. A deprecation warning would be new output on stderr, and the end-to-end corpus byte-compares that stream.

RetiredWrite instead
-O--optimize whole
--no-optimize--optimize none
-a, --print-ast--print ast
-ar, --print-resolved-ast--print ast-resolved
--print-ir--print ir
-pu, --print-unit-ir--print ir-units
-syt, --print-symbol-table--print symbols
-pi, --print-instances--print instances
-ec, --explain-cache--explain cache
-ep, --explain-prune--explain prune
-em, --explain-memory--explain memory
-t, --timings--explain time
-ta--track-allocations
--debug-info--debug-symbols, or -g
--stdlib--with-stdlib

The full sentence for -O carries the extra clause, because it is the one where the replacement is not obvious:

error: '-O' is retired. Write '--optimize whole' for the merged whole-program build, which is what it did.

Environment variables ​

None of these are flags, and none of them enter a module's cache key.

VariableEffect
NO_COLORset and non-empty turns colour off. Wins over CLICOLOR_FORCE
CLICOLOR_FORCEset, non-empty and not 0 turns colour on
TERMexactly dumb turns colour off
WT_SESSIONpresent means box-drawing characters are safe. Windows Terminal
LC_ALL, LC_CTYPE, LANGcontaining UTF-8 or utf8 means box-drawing characters are safe. Checked in that override order
COLUMNSa positive integer overrides the width used for wrapping
XDG_CACHE_HOMEthe user cache root becomes $XDG_CACHE_HOME/echo
HOMEthe fallback root, $HOME/.cache/echo. Neither set means no user cache
SDKROOTthe SDK path handed to the link step
ECO_TRACE_MONOpresent, at any value, traces the monomorphizer per round. A debugging hatch, not a feature

Colour and box drawing are two separate answers on purpose. A Windows console draws colour and mangles --style box characters; a UTF-8 terminal behind a pipe is the other way around.

Exit status ​

0 when the compile succeeded, and for run, when your program also returned 0. 1 when anything was refused. run passes your program's exit code straight through, so std::env::exit(2) gives you 2.

test is 0 only when every test it ran passed. A test that failed, was killed by a signal, or a --filter matching nothing all give you 1.

Next ​

  • The echoc CLI for what these flags are for, rather than what they are.
  • Modules for what -m and --build-dir are pointing at.
  • Attributes for the manifest side of --link and --define.

Echo is a work in progress. Nothing here is a promise of stability.