Skip to content

CLI and Execution Model ​

Run path: tokenize → parse → analyze → interpret. The CLI wraps that pipeline.

bash
elang file.echo
elang file.echo --plain

Pipeline ​

  1. Lexer → tokens
  2. Parser → AST
  3. Analyzer → scopes / types / names
  4. Interpreter → run the AST

CLI ​

Run a file ​

bash
elang program.echo

Plain output mode ​

bash
elang program.echo --plain

--plain drops Rich panels for simple text diagnostics.

Known quirk (product bug, not a docs fiction): when an abort includes a call stack, plain mode may print a doubled header Stack: Stack: because the printer prefixes Stack: onto a body that already starts with Stack:. Rich (non-plain) output does not show that doubled title the same way. See Errors and Troubleshooting.

Require std imports ​

bash
elang --require-std program.echo
elang check --require-std program.echo
elang builtins --require-std

Hides peeled stdlib names (httpGet, readFile, regexMatch, …) from the prelude so programs must import … from "std/…". Default remains dual-path (prelude + std/…). Core names like say stay. See module semantics (2.0.7).

Version ​

bash
elang --version
elang -V

Prints Echo <version> and exits 0.

List prelude builtins ​

bash
elang builtins
elang builtins --count

Prints every prelude builtin name (sorted), one per line — same set as builtin_names() / the generated builtin inventory. --count prints only the number. First argv token must be the word builtins; elang builtins.echo still runs that file.

Top-level elang -h lists subcommands: check, test, fmt, lint, builtins, std, lsp, ide.

Desktop IDE ​

bash
elang ide
elang ide hello.echo

Opens a small Tk window: editor with line numbers and syntax highlighting, Run, output pane, Open/Save. Runs the buffer in-process (no save required for programs without import). Needs Tkinter (usual on macOS/Windows Python installers; on Homebrew: brew install python-tk@3.13, matching your Python version). Interactive ask / readLine are limited in v1 — prefer non-interactive programs. First argv token must be the word ide; elang ide.echo still runs that file.

List install-tree std modules ​

bash
elang std
elang std --exports
elang std --count
elang std --path

Prints std/… modules from the Echo install tree (one name per line by default). First argv token must be the word std; elang std.echo still runs that file.

Language server (thin stub) ​

bash
elang lsp
elang lsp --require-std

Stdio JSON-RPC language server (2.1.2+). Publishes analyzer diagnostics on open/change, answers builtin hover, and resolves goto-definition for local + imported symbols (2.1.3). Full document sync only. The VS Code / Cursor extension starts this server by default (2.1.4, echo.lsp.enabled). Task Problem matchers remain a fallback. Not a full IDE language-server product.

Analyze without running ​

bash
elang check program.echo
elang check src/
elang check program.echo --plain

check lexes, parses, and analyzes each file and its import graph. It does not execute.

  • Directory → recursive *.echo. Explicit file paths always check.
  • Success: silence, exit 0. Failure: print every diagnostic, exit 1 after all paths (does not stop at the first bad file).
  • Missing path → exit 1. No path → help, exit 2.
  • First argv token must be the word check; elang check.echo still runs a file named check.echo.
  • --plain disables Rich panels.
  • Editor: Echo: Check file / Echo: Check workspace (--plain) → Problems panel.

REPL ​

bash
elang
elang --plain

No source file → REPL. --plain uses the same plain diagnostics as file runs.

One process is one session: variables, functions, and type aliases persist across echo> submissions. Builtins stay available. use mut in a later fn can assign a name declared earlier in the session.

Continuation: keeps reading while { is unclosed or a triple-quoted string is open; shows ... until complete. One-line if true { say(1); } works.

import name from "module"; resolves .echo files relative to the cwd (sibling, nested, or ./ / ../ forms), or from the Echo install tree for std/…. Loads with the module loader and merges bindings into the session. Re-import does not re-initialize the module.

Empty lines at a fresh echo> are ignored. Syntax / semantic / runtime errors print an Echo diagnostic and return to the prompt — no process kill, no Python traceback, no drop of earlier successful bindings. Failed analysis does not execute. A runtime error does not roll back the interpreter (x: int = 1; bad(); may leave x).

Quit: exit(code) or EOF (Ctrl-D → exit 0). Session state dies with the process. Ctrl-C cancels the current submission and returns to echo> .

Run tests ​

bash
elang test program.echo
elang test path/foo_test.echo
elang test tests/
elang test program.echo --plain
elang test tests/ --run '*Add*'
elang test path/foo_test.echo -run testAdd --plain
elang test tests/ --json
elang test tests/ --run '*Add*' --json --plain

Native test runner. No test "name" { } syntax.

Discovery

  • Directory → recursive *_test.echo. Explicit file path always runs (even if not *_test.echo).
  • No path → help, exit 2.
  • First argv token must be test; elang test.echo still runs that file.

Units

  • Zero-arg top-level fn testXxx() → each is a unit (remaining top-level runs once as setup).
  • No such functions → the file is one unit.

-run / --run PATTERN

  • Keeps testXxx function units whose names match a case-sensitive glob (*, exact names).
  • Does not filter file names. Non-matching units are skipped, not failed.
  • File with no matching units is skipped (setup does not run) and does not fail.
  • Quote globs in the shell (--run '*Add*'). Without -run, every unit runs.

--json

  • One JSON object on stdout instead of ok / FAIL lines and the summary.
  • Composes with -run and --plain (JSON has no color).
  • Fields: passed, failed, skipped, units[] with name, passed / skipped, and on failure message + location when present.
  • Exit codes stay 0 / 1 / 2.

Assertions

  • expect / expectEq / expectNeq record and continue under this runner (E2826–E2828). Outside elang test they abort like assert.
  • assert / fail still abort the current unit; later units still run.
  • Parse / load errors fail that file as a unit.

Exit

  • Process exit 0 if every unit passed, 1 if any failed (2 for usage).
  • exit(0) in a unit passes it; any other exit(n) fails it. Runner exit is not the program's exit(n).

Human report (one passing unit, one failing expectEq):

text
ok   path/foo_test.echo::testAdd
FAIL path/foo_test.echo::testSub
     Error[E2827]: sub
     expected 3, got 4
     --> path/foo_test.echo:4:5
1 passed, 1 failed

The --> file:line:col line is printed for expect failures that carry a location. Paths and line numbers match your files.

--json sample for the same run:

json
{
  "passed": 1,
  "failed": 1,
  "skipped": 0,
  "units": [
    {"name": "path/foo_test.echo::testAdd", "passed": true},
    {
      "name": "path/foo_test.echo::testSub",
      "passed": false,
      "message": "Error[E2827]: sub",
      "location": "path/foo_test.echo:4:5"
    }
  ]
}

Program say output is shown. Editor can run elang test --plain as a task.

Format source files ​

bash
elang fmt program.echo
elang fmt src/
elang fmt program.echo --check
elang fmt program.echo --plain

Rewrites sources in place to the canonical layout (4-space indent, comments kept, else if flattening, parentheses from operator precedence).

  • Directory → recursive *.echo.
  • --check: print paths that would change, exit 1; clean → silence, exit 0.
  • Parse errors match elang check; file is not written.
  • No path → help, exit 2. First token must be fmt; elang fmt.echo still runs that file.
  • Editor: Format Document / Format workspace.

Side effects of formatting: number literals may respell (1e3 → 1000.0). Strings wrap from raw lexemes and are not re-escaped, so quote style may change ('hello' → "hello").

Lint source files ​

bash
elang lint program.echo
elang lint src/
elang lint program.echo --plain

Style and convention findings. Does not run the program. Not a second typechecker — semantic errors stay elang check.

  • Directory → recursive *.echo.
  • Finding line: path:line:col: rule: message. Any finding → exit 1; clean → silence, exit 0.
  • Parse errors match elang check.
  • No path → help, exit 2. First token must be lint; elang lint.echo still runs that file.
  • Editor task with --plain → Problems panel.
RuleWhat it flags
unused-localdeclared local, parameter, or loop variable never read
unused-functionfunction never called in this file, not exported, and not a zero-arg test* entry
unused-importimported name never used
comparison-to-bool== true / != false and the other bool-literal comparisons
redundant-by-oneexplicit by 1 on for (formatter omits it)
empty-blockempty if / else body or empty function body
shadow-builtindeclared name shadows a builtin
test-namingtop-level fn that looks like a test but is not a zero-arg testXxx unit
self-assignx = x or x = x + 0 / x = 0 + x
unreachable-after-faillater statement in the same block after fail(...) or return

Unused parameters are unused-local. unused-export and redundant-parens are not rules (exports are for other files; grouping parens are not kept on the AST).

Notes ​

  • The file passed to the CLI is the entry module when it contains import.
  • Errors are reported by category: syntax, semantic, name, type, argument, index, mutation, or execution.
  • The browser playground uses a restricted Host: allow_files=False, allow_run=False, allow_http=False, and an empty environment. Denied builtins abort with E2801. The CLI defaults to allowing those operations. Details: Host builtins.

See Also ​

Echo is in active development. The docs reflect the current implementation.