Command Line in EK9

This page lists the command line options of the reference implementation of the compiler.

To compile and run a source file, type ek9 someSource.ek9. If all you have is one source file, that is all you need. On Linux, Unix and macOS, if the file has its execute bit set and ek9 is on your PATH, you can type ./someSource.ek9: the #!ek9 shebang on its first line runs the ek9 command.

The compiler does more than compile. Read packaging for projects of a few source files or thousands.

Is it a build system then?

Not quite. For pure EK9 development it takes you a long way, and for small and medium projects it may be all you need. It is designed to be driven by other build systems and CI/CD pipelines, as one step of a larger build.

Can it be used with other build systems

Yes. The compiler is designed to run inside build pipelines such as

So it has options that do not compile code but handle versioning and packaging.

With the -ls flag it runs as a language server for editors such as IntelliJ, Vim, Eclipse and VS Code. The EK9 extension for VS Code uses this mode, and adds syntax highlighting and code snippets.

Why add all those options in?

Why EK9 does it this way. Delivering software now goes well beyond compilation; the introduction explains the reasoning. Packaging, version management and deployment are basic operations for modular software, so the compiler provides them. Avoid building very large monolithic applications where you can: link parts together as separately runnable modules with specific, defined interfaces.

Command line application

The options below each have a specific purpose. To run a program and have it compiled automatically, see running programs.

Run ek9 with no source file to see the options:

$ ek9
ek9: bad command line arguments (exit 2)
ek9 <options>
        where possible options include:
        	-V The version of the compiler/runtime
        	...
        	-c Incremental compile; but don't run
        	-C Force full recompilation; but don't run
        	...
        	-e <name>=<value> set environment variable i.e. user=Steve or user='Steve Limb' for spaces
        	...
        	filename.ek9 - the main file to work with
        	-r Program to run (if EK9 file has more than one)

Options

Related options are described together.

General

-T The target architecture. It defaults to jvm (but see EK9_TARGET below), and the JVM is the supported target today; the native (LLVM) target is under development.

The environment variable EK9_TARGET sets the target so you need not pass -T every time; -T overrides it.

Environment Variables

These variables give defaults that persist across runs, alongside the command line flags.

The Settings File

Every one of these variables can be written once in the file ~/.ek9/ek9.env (the same path on macOS and Linux), one NAME=value per line, for example EK9_IDE_MEMORY=-Xmx4g. The ek9 command reads it each time it starts, so its settings reach the compiler, the IDE (including EK9 IDE.app opened from Finder, which never reads your shell's start-up files), the language and MCP servers, and every program they run. A variable already set in your environment wins over the file, and the file wins over the defaults.

EK9_ACCEPT_LICENCE=yes accepts the EK9 licence where there is no terminal to ask (CI, a Docker build), and records it once, as ek9 -accept-licence does.

Installation and Target

EK9_HOME The directory holding the compiler JAR (ek9c-jar-with-dependencies.jar). If it is not set, the wrapper looks in the directory of the ek9 executable. Use it to switch between compiler versions, for example export EK9_HOME=/opt/ek9/0.0.2.

Which Java Runs

EK9 needs Java 25 or later; a Java runtime is enough, no JDK is needed. The wrapper uses the runtime that comes with the installation when there is one: $EK9_HOME/jre/bin/java if EK9_HOME is set, otherwise jre/bin/java in the directory of the ek9 executable. If there is none it uses java on your PATH. It checks the version of the java it chose, and the same java runs both the compiler and your compiled program.

Memory Configuration

Each of these holds the options given to the Java virtual machine. A value may hold several options separated by spaces, for example "-Xmx1g -Xss4m"; each becomes its own option. No shell reads the value, so no quoting is needed, and one option cannot contain a space.

Testing
Terminal Output Control

EK9 detects what the terminal supports (colours, Unicode, emoji) from the standard environment variables. These override the detection:

Native Helpers

The ek9-watcher binary ships beside the ek9 wrapper. The IDE and the standalone MCP server (ek9 -mcp) use it to see file changes made outside them, through macOS FSEvents (about 300 ms). On Linux and Windows the watcher uses inotify / ReadDirectoryChangesW through Java NIO, so the binary is macOS only and the settings below do nothing on other platforms.

Standard discovery: if the EK9 install directory is on your PATH (so the ek9 wrapper is found), ek9-watcher is found there too, with no further setting. Without it, the watcher falls back silently to Java NIO's WatchService, which on macOS polls with a 2-second floor.

EK9_WATCHER_PATH Use this binary instead of the one discovered: for a development build while a release is on the PATH, or when the binary lives outside the directory holding ek9. For example export EK9_WATCHER_PATH=/Users/me/dev/ek9/native/fsnotifier/target/bin/ek9-watcher. The system property -Dek9.watcher.path=<path> wins when both are set, for a one-off override without changing the shell environment.

Language Server

As a Language Server, the compiler produces no executable. It runs the 'front end' of compilation (up to PRE_IR_CHECKS) and gives your editor its diagnostics as you work.

Supported Capabilities

The EK9 language server provides:

There is no Go to Declaration: EK9 has no separate declaration and definition, so there is no second place to go to.

Editor Integration

The EK9 VS Code extension gives syntax highlighting, code snippets and the language server. To use it:

  1. Install the EK9 extension from the VS Code marketplace.
  2. Put the ek9 command on your PATH.
  3. Open a folder that holds .ek9 files.

Any editor that supports LSP works too, including IntelliJ, Vim, Eclipse and Neovim. Configure it to launch ek9 -ls (or ek9 -ls -lsh for the extra hover help) as the language server for .ek9 files.

MCP Server (Model Context Protocol)

The compiler can run as an MCP server, giving AI coding assistants (Claude Code, Cursor and other MCP clients) direct access to the compiler. An assistant can compile code, look up error explanations, search the Q&A, format source and inspect symbols without leaving the conversation.

-mcp Run the compiler as an MCP server, with stdin/stdout as the JSON-RPC transport.

Available Tools

The server offers many tools: editing and refactoring, enquiries about the code, building, testing, debugging, packaging and git. ek9_query_tool_catalogue lists them all. The reference tools to start with are:

Available Resources

The server also offers two resources an assistant can read:

Configuration

Configure the assistant to launch the compiler with -mcp. For example, in a Claude Code MCP configuration:

// .mcp.json or equivalent configuration
{
  "mcpServers": {
    "ek9": {
      "command": "ek9",
      "args": ["-mcp"]
    }
  }
}

The server speaks newline-delimited JSON-RPC 2.0 over stdin/stdout and supports the standard MCP lifecycle: initialize, initialized, tools/list, tools/call, resources/list, resources/read, shutdown and exit.

ek9_help, ek9_paradigm, ek9_explain_error and ek9_ask need no compilation; they are offline reference. ek9_compile compiles the whole workspace and keeps the results for later reads of the diagnostics and symbols resources.

Debug Adapter (DAP)

-dap Run the compiler as a Debug Adapter Protocol server over stdin/stdout. DAP is the protocol VS Code and other editors use to debug every language, so ek9 -dap lets you set breakpoints in .ek9 files, inspect the call stack and local variables, and step through EK9 source. It relies on the JSR-45 SMAP and LocalVariableTable debug information the compiler emits when you build with -Cg (debug information).

You do not normally start ek9 -dap yourself: the EK9 editor extension starts it when you start a debug session. DAP does not depend on the back end. The same ek9 -dap serves the JVM target today (over JDI/JDWP) and is meant to serve the native (LLVM) target later, where the mechanism underneath differs (DWARF debug information) but the editor sees the same protocol.

A launch configuration compiles and runs your program under the debugger and sends its output to the editor's debug console; an attach configuration connects to an EK9 program already running under JDWP.

Q&A Knowledge Base

EK9 includes a Q&A knowledge base on language features, design decisions and moving from other languages, for developers learning EK9 and for AI coding assistants that need EK9 context.

Training data for language models:

$ ek9 -q  # List all Q&A by category
$ ek9 -q guard variables  # Summary + code example
$ ek9 -qd guard variables  # Full detail + code example
$ ek9 -q 50  # Get Q&A #50 directly
$ ek9 -qm generics templates  # Summary + migration context
$ ek9 -qdm generics templates  # Full detail + migration context
$ ek9 -Q > training.jsonl  # Export instruction format
$ ek9 -Qc > chat.jsonl  # Export mlx_lm chat format
$ ek9 -Qct > chat.jsonl  # Export with thinking traces
$ ek9 -Qt  # Write train/valid split to ek9_training_data/
$ ek9 -Qtt  # Write train/valid split with thinking traces
$ ek9 -Qn ./my-corpus  # Scan directory, write negative training data
$ ek9 -Qall ./my-corpus  # Scan directory, combine with Q&A training data

Compilation

These options control full and incremental builds. What makes up the source files is described in small applications, medium applications, large applications and use of libraries.

Compile Without Linking

These compile but skip the final step that makes an executable: to check that code compiles without a runnable artifact, or when another build system does the linking.

Phased Compilation

These compile up to a named compiler phase and stop, to debug the compiler pipeline or inspect intermediate results. The argument is the phase NAME (READING, PARSING, SYMBOL_DEFINITION, ... PRE_IR_CHECKS, IR_GENERATION, ...), not a number; given anything else, ek9 lists the valid names.

Dev Code

Dev code is source that is not part of your program, application or library, but is used as an example or for testing: unit tests, worked examples, templates, how-tos. It lives in the /dev directory. A user of your library does not use it in production, but will value the real examples and tests that show how the library can and should be used.

Keep these examples short and to the point, so a developer can snip one and rework it into their own application, rather than searching the internet for how to use your library.

Tests are the programs marked @Test in the /dev directory or its subdirectories (see -t below).

Error Verbosity Levels

The levels are cumulative: each includes everything below it.

Example Output

The same error at each level, compiling this source with ek9 -E<n> -c. In terminals that support colour, error text is red, code references cyan and hints green. Modern terminals show emoji icons (❌ 💡 🔧 📝 🔗); others get Unicode or ASCII instead.

Fails with E50001: not resolved

#!ek9
defines module bad.site.commandline.errorformat

  defines function

    nextCount()
      -> count as Integer
      <- result as Integer: coutn + 1

-E0 (Minimal):

$ ek9 -E0 -c commandline_errorformat.ek9
ek9: compilation failed (exit 8)
EK9     : FULL_RESOLUTION: Errors in ./commandline_errorformat.ek9
EK9     : Error   : E50001: 'coutn' on line 8 position 28: 'coutn': not resolved
             See: ek9 -h E50001
EK9     : Compilation failed

-E1 (Visual): the source line with caret markers.

$ ek9 -E1 -c commandline_errorformat.ek9
ek9: compilation failed (exit 8)
EK9     : FULL_RESOLUTION: Errors in ./commandline_errorformat.ek9
EK9     : ❌ error   :[E50001]: 'coutn' on line 8 position 28: 'coutn': not resolved
        ──â–ļ commandline_errorformat.ek9:8:29
        │
      8 │       <- result as Integer: coutn + 1
        │                             ^^^^^ 'coutn'
        │
        └─ 🔗 See: ek9 -h E50001

EK9     : Compilation failed

-E2 (Visual + Suggestions): adds "Did you mean?" with where the name is defined.

$ ek9 -E2 -c commandline_errorformat.ek9
ek9: compilation failed (exit 8)
EK9     : FULL_RESOLUTION: Errors in ./commandline_errorformat.ek9
EK9     : ❌ error   :[E50001]: 'coutn' on line 8 position 28: 'coutn': not resolved
        ──â–ļ commandline_errorformat.ek9:8:29
        │
      8 │       <- result as Integer: coutn + 1
        │                             ^^^^^ 'coutn'
        │
        ├─ 💡 help: did you mean 'count'?
        │   ──â–ļ defined at line 7
        └─ 🔗 See: ek9 -h E50001

EK9     : Compilation failed

-E3 (Verbose): the full explanation box, ending with the compiled example the error catalogue holds for the code.

$ ek9 -E3 -c commandline_errorformat.ek9
ek9: compilation failed (exit 8)
EK9     : FULL_RESOLUTION: Errors in ./commandline_errorformat.ek9
EK9     : ❌ error   :[E50001]: 'coutn' on line 8 position 28: 'coutn': not resolved
        ──â–ļ commandline_errorformat.ek9:8:29
        │
      8 │       <- result as Integer: coutn + 1
        │                             ^^^^^ 'coutn'
        │
        ├─ 💡 help: did you mean 'count'?
        │   ──â–ļ defined at line 7
        └─ 🔗 See: ek9 -h E50001

        ┏━ â„šī¸ EXPLANATION
        ┃
        ┃ [DIAGNOSIS]
        ┃ The compiler cannot find a variable or identifier with this name in
        ┃ the current scope. The name is not recognized as any declared symbol.
        ┃
        ┃ [EXAMPLES]
        ┃ Compilable examples demonstrating this error and the correct fix:
        ┃   ek9 -q 22    (How do I declare a variable in EK9?)
        ┃   ek9 -q 17    (Where is the main function in EK9?)
        ┃   ek9 -q 3     (How do I print output to the console?)
        ┃   ek9 -q 46    (How does the Dict type work in EK9?)
        ┃   ek9 -q 33    (How do I work with regular expressions?)
        ┃   ek9 -q 1380  (How do I split my code across multiple .ek9 files in one ...)
        ┃
        ┃
        ┃ [ACTION]
        ┃ 1. Check spelling - EK9 is case-sensitive
        ┃ 2. Ensure variable is declared before use
        ┃ 3. Add reference statement if the name is in another MODULE
        ┃ 4. If the name is in another FILE of the same module, that file is not in
        ┃    the build - add a package.ek9 with applyStandardIncludes (see ek9 -q 1380)
        ┃ 5. Check variable scope (may be out of scope)
        ┃
        ┃ [COMMON CAUSES]
        ┃ - Typo in variable/identifier name
        ┃ - Variable not declared in this scope
        ┃ - Missing import/reference statement
        ┃ - Declared in a sibling .ek9 file that the build does not include (same module
        ┃   is NOT enough - the package block decides which files are compiled)
        ┃ - Using variable before declaration
        ┃ - Variable declared in different scope (e.g., inner block)
        ┃ - A fully qualified name 'M::X' where module M only IMPORTS X with
        ┃   'references'. A qualified name reaches only what its module DEFINES;
        ┃   the message names where X is defined - qualify it with that module
        ┃
        ┃ [SEE ALSO]
        ┃ - E50001: identifier/variable not found (this error)
        ┃ - E50010: TYPE not found (class/record/trait names)
        ┃ - E50060: METHOD not found (method/function calls)
        ┃
        ┃ [EXAMPLE]
        ┃ This fails with E50001 (bad.ek9):
        ┃
        ┃   #!ek9
        ┃   defines module error.e50001.bad
        ┃
        ┃     defines function
        ┃
        ┃       greeting()
        ┃         -> customerName as String
        ┃         <- message as String?
        ┃         message: salutation + customerName
        ┃
        ┃ Fixed (good.ek9):
        ┃
        ┃   #!ek9
        ┃   defines module error.e50001.good
        ┃
        ┃     defines function
        ┃
        ┃       greeting()
        ┃         -> customerName as String
        ┃         <- message as String?
        ┃         salutation <- "Hello, "
        ┃         message: salutation + customerName
        ┗━━
EK9     : Compilation failed

Source Formatting

The built-in formatter puts source into canonical style (two-space indentation, consistent spacing, ordered declarations). It is safe: if formatting fails for any reason, the file is left as it was.

$ ek9 -f myfile.ek9  # Format single file
$ ek9 -F myfile.ek9  # Format all project files
$ ek9 -ff myfile.ek9  # Force format (repairs one-reading slips first, reports each)
$ ek9 -fF myfile.ek9  # Force format all project files
$ ek9 -fc myfile.ek9  # Repair compile (applies one-reading repairs to compile errors, reports each)

The formatter runs six passes, each re-parsing its result: canonical indentation from the parse tree, structural reordering (alphabetical references, declaration order), token spacing, line splitting, comment formatting, and removing the implicit org.ek9.lang:: prefix from type names. Force mode first removes a byte-order mark, normalises line endings, inserts a missing shebang, replaces layout tabs and corrects odd-width indentation.

Dependencies

-Dp Resolve all dependencies. It cleans everything first (-Cl), so a full recompile follows. It runs automatically when you change the dependencies in the source that holds the package construct.

Dependencies are resolved from your $HOME/.ek9/lib directory, unpacked or zipped. Fetching them from repo.ek9lang.org is not built yet; until it is, install a dependency with ek9 -I in its own project. Once a dependency is in your library, look at the examples, snips and tests its developer provided in its /dev directory, and lift any you want into your own code to try them out.

Packaging

Packaging for reuse does not mean the software has to be 'open source'. You can package your modules and reuse them inside your organisation, or on your own PC.

Version Numbers

These commands change the version number for you, so you can build versioning into your other processes, such as creating a branch in your source repository or triggering a new build. The package construct is essential for managing the version of EK9 software.

Features

Features are not unique to EK9; they are used widely with git repositories.

Every release must have a unique version number (see versioning). But a large team often works on the next release in groups, each on its own feature. Several features may be planned for one release, worked on at the same time but kept apart from each other.

So each group works on a feature branch. Everyone on a feature works on the same code (git enables this), and they need full automated builds and tests of their combined work. They may even deploy the feature to demonstrate it to end users, product owners, testers or marketing.

That software needs a version number, so that issues, ideas, defects and changes can be tracked against it, and the number must carry a name the teams recognise: the feature. That is why EK9's version numbering has a place for a feature name.

Some features never see the light of day; others are folded back into the main development branch. Some need more work and miss the 'cut' for the next major version, so they move to a later release. Sometimes features are merged and refined by merging git feature branches. EK9 supports all of this by letting you change the version number in a controlled way.

The 'run' options

These run your software. With no option, -r (run) is assumed. If the source has not been compiled yet, the compiler does a full build first, dependencies included, so you can write (or download) some EK9 source and run it. The first run checks and compiles it; after that, if nothing has changed, it runs without recompiling.

-t or -t1 Run all unit tests, with human-readable output. It compiles first with debugging information (-Cd) and forces -O0 (no optimisation), so coverage probes map accurately to the source. The tests are the programs marked @Test in the /dev directory and its subdirectories.

Test Output Format Options:

Coverage Options:

With -tC and -t or -t1, coverage below 80% also writes .ek9/coverage-detail.json: every uncovered method and branch with its file:line, so you can see what needs testing. -t4 writes it even when coverage is above 80%.

Profiling Options:

$ ek9 -tp myapp.ek9  # Tests + coverage + profiling summary
$ ek9 -t2p myapp.ek9  # Profiling JSON in .ek9/coverage.json
$ ek9 -t5p myapp.ek9  # Verbose coverage + profiling
$ ek9 -t6p myapp.ek9  # Full HTML with flame graph

Profiling adds to coverage and test results rather than replacing them: all three are collected in the same run. Performance Profiling explains how to read the flame graphs and hot function tables.

Group tests with @Test: "groupname"; the tests in a group run one after another. Ungrouped tests run in parallel on 75% of the available CPU cores.

The time limit: each test run (each case of a parameterised test) may take at most EK9_TEST_TIMEOUT_MS milliseconds, 30000 (30 seconds) unless you set it (see Testing below). A test still running then fails with E82001, which names the limit, and the other tests still run. A native test is killed with every process it started. The IDE's and an AI's test runs each have a JVM of their own, killed with everything it started; under ek9 -t a JVM test's run is interrupted and abandoned, and ek9 -t still ends when the other tests are done.

Test Filtering Options:

-tL lists the tests found, with their types, groups and case counts, without running them: for a CI/CD discovery step, or to check the test configuration.

-tg runs one group of tests, for example to spread groups across CI/CD runners, to separate slow tests from fast ones, or to isolate tests that need particular resources.

$ ek9 -tL myapp.ek9  # List all tests
$ ek9 -tL -t2 myapp.ek9  # List tests as JSON
$ ek9 -t -tg database myapp.ek9  # Run only database group
$ ek9 -t2 -tg network myapp.ek9  # Run network group with JSON output

Important: -t cannot be combined with -O2 or -O3 (exit code 4). Coverage needs unoptimised code, because optimisation can inline functions, remove dead code and merge statements, and each of those makes coverage wrong. -t -O0 is allowed but redundant, since -O0 is forced anyway.

$ ek9 -t myapp.ek9  # OK - O0 forced automatically
$ ek9 -t -O0 myapp.ek9  # OK - explicit but redundant
$ ek9 -t -O2 myapp.ek9  # ERROR - optimization not allowed with tests

Compiler Fuzzing

EK9 includes a grammar-based fuzzer for the compiler's own quality assurance. Compiler Fuzzing documents it in full, including the five-strand generation strategy and the HTML dashboard.

$ ek9 -fuzz 30  # 30 minutes, human-readable output
$ ek9 -fuzz0 30  # Terse CI pass/fail
$ ek9 -fuzz2 1440  # 24 hours, JSON for CD pipeline
$ ek9 -fuzz6 60  # 1 hour, HTML dashboard

-s1, -s2, -s3, -s4, -s5 Strand filter: run one or more strands only, for focused testing or ablation studies. They combine (-s1 -s2 runs Strands 1 and 2, weighted in proportion) and are valid only with -fuzz/-fuzz0/-fuzz1/-fuzz2/-fuzz6. Strand 1 is random ATN generation, Strand 2 template probes, Strand 3 random mutation, Strand 4 targeted mutation and Strand 5 template enrichment (several Q&A entries with nested control flow). Strand 4 also saves mismatch diagnostics to fuzz-crashes/strand4-mismatches/.

$ ek9 -fuzz6 5 -s4  # 5 minutes, Strand 4 only, HTML dashboard
$ ek9 -fuzz6 10 -s5  # 10 minutes, Strand 5 only, HTML dashboard
$ ek9 -fuzz 10 -s1  # 10 minutes, Strand 1 only
$ ek9 -fuzz6 30 -s1 -s2  # S1 + S2 (grammar + symbol oracle)
$ ek9 -fuzz6 30 -s1 -s2 -s3  # S1 + S2 + S3 (add mutation)

Combining strands supports ablation studies: comparing S1 alone, S1+S2 and S1+S2+S3 measures what each strand adds to error coverage and to how deep into the pipeline the generated programs reach.

Test Generation

EK9 can generate edge-case tests and mutation variants of your code from the compiler's own symbol table. Test Generation documents it in full, including the edge-case value pools and mutation categories.

$ ek9 -fuzztest mylib.ek9 mylib_tests.ek9  # Generate edge-case @Test functions
$ ek9 -fuzztest mylib.ek9 mylib_tests.ek9 -overwrite -n 50  # Replace, limit to 50
$ ek9 -fuzzmutate myapp.ek9 mutations/  # Generate mutation variants
$ ek9 -fuzzmutate myapp.ek9 mutations/ -overwrite -seed 123  # Replace, reproducible
$ ek9 -fuzzmutate myapp.ek9 mutations/ -test mytests.ek9  # Generate + kill-check

Optimisation Levels

Three levels set the balance between compile speed, debuggability and run-time speed. The optimisation passes themselves are not built yet, so today every level generates the same code; the level still decides which other options it can be combined with.

Naming -O2 or -O3 with a debug compile (-cg, -cd, -cdh, -Cg, -Cd, -Cdh, -Cdp) is an error too (exit code 4): debug information has to describe the code as written.

$ ek9 -c -O0 myapp.ek9  # Compile with no optimization
$ ek9 -c -O2 myapp.ek9  # Compile with balanced optimization
$ ek9 -c -O3 myapp.ek9  # Compile with full optimization

The 'run' options

Interactive REPL

EK9 includes an interactive REPL (Read-Eval-Print-Loop) for exploring the language, prototyping code and learning EK9 syntax.

Starting the REPL

$ ek9 -repl

The REPL has syntax highlighting, tab completion (methods too, after "."), an external editor, sessions you can save and load, buffer editing, and EK9's full error diagnostics.

REPL with Error Verbosity

Add an error verbosity flag for more feedback:

$ ek9 -repl -E2

This turns on "Did you mean?" suggestions for typos and unresolved symbols; see Error Verbosity Levels for examples.

Key Features

The REPL documentation covers every feature and command.

Using Standard Input and Output

EK9 programs can read standard input (stdin) and write standard output (stdout), so they work with Unix pipes and shell redirection, for CLI tools, filters and pipeline-based applications.

Simple Example

This program reads a line from stdin and writes it to stdout:

#!ek9
defines module site.commandline.passthrough

  defines program

    PassThrough()
      stdin <- Stdin()
      stdout <- Stdout()

      inputLine <- stdin.next()
      stdout.println(inputLine)

Running with Pipes

Pipe data into it as into any Unix program:

$ echo "Hello World" | ek9 PassThrough.ek9
Hello World

On Unix, Linux and macOS, with the shebang (#!ek9) and the execute permission set, run it directly:

$ chmod u+x PassThrough.ek9
$ echo "Hello World" | ./PassThrough.ek9
Hello World

Chaining with Unix Tools

EK9 programs fit into pipelines with the usual Unix tools:

$ cat data.txt | ek9 filter.ek9 | sort | uniq

Learn More

For Stdin, Stdout and Stderr in full, including stream pipelines and iterator-style access, see:

Exit Codes

The ek9 command's exit codes let shell scripts, build systems and CI/CD pipelines act on the result.

How Exit Codes Work

The ek9 command is a wrapper that runs the EK9 compiler (a Java program). It translates the compiler's own exit codes to the Unix convention, where exit code 0 means success:

Exit Codes You Will See

The ek9 command follows the Unix convention:

Telling an EK9 Failure from Your Program's Exit Code

Your programs keep every exit code: a program may exit 3, 8 or 11 itself. So the number alone cannot say whether ek9 myprogram.ek9 exiting 8 means the compile failed or the program chose 8.

Why EK9 does it this way. A script being replaced by an EK9 program needs exactly the exit codes it had, and in production the compiled program runs without the ek9 wrapper. So EK9 reserves no numbers for itself; it marks its own failures on stderr instead.

The first line on stderr says. Whenever EK9 itself fails (the command line, the compiler, or the generated entry point refusing its command line, 2, 4, 5, 6, or a program's arguments, 9 and 10), that line is a fixed marker naming the failure and its code, before anything else:

Fails with E50001: not resolved

#!ek9
defines module bad.site.commandline.broken

  defines function

    nextCount()
      -> count as Integer
      <- result as Integer: coutn + 1
$ ek9 -c commandline_broken.ek9
ek9: compilation failed (exit 8)
EK9     : FULL_RESOLUTION: Errors in ./commandline_broken.ek9
EK9     : Error   : E50001: 'coutn' on line 8 position 28: 'coutn': not resolved
             See: ek9 -h E50001
EK9     : Compilation failed

The markers are ek9: bad command line arguments (exit 2), file access failed (exit 3), invalid combination of command line options (exit 4), no programs to run (exit 5), program not specified, use -r (exit 6), language server failed (exit 7), compilation failed (exit 8), wrong number of program arguments (exit 9), cannot convert a program argument (exit 10), tests failed (exit 11), coverage below the required threshold (exit 12), MCP server failed (exit 13), internal compiler error (exit 14), fuzzing found compiler crashes (exit 15), debug adapter failed (exit 16), dependency resolution failed (exit 17), URL policy could not be loaded (exit 18) and licence not accepted (exit 19), each written as ek9: <reason> (exit N).

A program's own exit (a normal finish, a returned Integer, an uncaught Exception's exit code or a Panic) never carries the marker. So a script can tell the two apart: if the exit code is non-zero and the first stderr line is ek9: ... (exit N) with that same N, EK9 failed; otherwise the code is your program's. The long-running modes (-ls, -mcp, -dap, -repl) stream their stderr as a log, so if one of them fails to start, its marker comes after what it has already logged.

Exit Codes from Your EK9 Programs

With ek9 -r yourProgram.ek9 or ./yourProgram.ek9, once compilation succeeds (compiler code 0), the wrapper runs your program and returns your program's exit code.

The compiled program can also run directly (java -jar .ek9/myprogram.jar -r module::Program), as it does in production. Its entry point refuses a command line it cannot use with the ek9 command's own code for the same case, marker first: 2 for -r with no program name, 4 for a program the jar does not have, 6 when the jar holds several programs and none is named.

Dev source (a file under a dev/ directory, or a Q&A companion named QA####_dev_...) is never part of the runnable artefact, so ek9 devfile.ek9 does not run a program in it. It exits 5 (no programs to run), and the line after the marker says why: devfile.ek9 is development source: its programs run under -t, not directly. Dev code runs as @Test programs, with ek9 -t.

A program's exit status is decided in exactly one of four ways (there is no 'exit' call):

The application teardown always runs before the program exits, however it ends. In every failing case the finally blocks run first too, and one line goes to stderr (Exception in thread "main" ..., without a Java stack trace). The Exceptions section has the details and examples.

So your EK9 programs can return any exit code (0 for success, non-zero for errors), and the wrapper passes it to the shell, which is what CLI applications need to work in shell scripts and automation.

Program Exit Codes vs CLI Exit Codes

The ek9 command and your program share one set of numbers. When you run ek9 myprogram.ek9, the exit status comes from one of two stages:

So the number alone cannot say which stage produced it: a program that throws Exception("Input has no rows", 3) exits 3, the number the ek9 command uses for a file it cannot read. Tell them apart by what is written to stderr:

Shell Script Usage

In shell scripts and CI/CD pipelines, use the standard Unix exit code patterns:

$ ek9 -C myprogram.ek9
$ status=$?
$ if [ $status -eq 0 ]; then
>   echo "Compilation successful"
> elif [ $status -eq 8 ]; then
>   echo "Compilation failed"
> fi

Or the shorter form:

$ ek9 -C myprogram.ek9; status=$?
$ [ "$status" -eq 0 ] && echo "Success" || echo "Failed with exit code $status"

CI/CD Integration

Build systems and CI/CD pipelines (such as GitHub Actions, AWS CodePipeline, or Azure Pipelines) mark a build as failed on any non-zero exit code. Exit codes 8 to 18 detect compilation, program-argument, test, coverage, server start-up, internal-error, fuzzing (compiler crash found), dependency and URL policy failures in automated builds; 19 (licence not accepted) happens only at a terminal.

Summary

The EK9 CLI has many options, but to write some source and run it you need very few. On Linux and macOS, set the execute bit of your source file (chmod u+x someSource.ek9) and run it (./someSource.ek9); if the EK9 CLI is installed correctly, the code is compiled and run. On Windows, type ek9 someSource.ek9.

Conclusion

EK9 tries to bridge the gap between developing software in a compiled language and packaging, releasing and publishing it. It does so with a language that is fairly simple to read and write, performs well, and has a range of software engineering design patterns built in.

That is ambitious and technically difficult, and building the human ecosystem a programming language needs to succeed is harder still.

EK9 is large because it takes in language structure, new constructs, design patterns, APIs, versioning, building, packaging and deployment. The hope is that it gives you value as a developer, in layers, as you need them.

Only time will tell if we can succeed. Thank you for taking the time to read these documents.

Next Steps

Go back to the main page, download the compiler and some examples, and get started.