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
- -v Verbose mode: what the compiler is doing in each phase.
- -V The version of the compiler.
- -accept-licence Accept the EK9 licence (LICENSE.txt in your EK9 folder) and record it, without being asked, then run the rest of the command (ek9 -accept-licence -c main.ek9; on its own it just records) - for scripts, CI and container images. The first time ek9 runs at a terminal it shows the licence and asks instead; the IDE asks on its first start. One acceptance, in ~/.ek9/licence-accepted.json, counts for all of them until the licence changes.
- -withdraw-licence Withdraw your acceptance of the EK9 licence, if you change your mind. You may then no longer use EK9; programs you have already built or passed on are not affected, and ek9 asks you to accept again next time. The IDE offers the same under Help, Licenses.
- -show-licences <program> [<directory>] Show the licence files a program EK9 compiled carries with it: the EK9 runtime's licence (the Apache License 2.0), the runtime's NOTICE, and the notices of the third-party code inside the program. Name the program's jar (ek9 -show-licences .ek9/myprogram.jar) or, for the native back end, its executable. With a directory, the three files are written there instead (ek9 -show-licences .ek9/myprogram.jar licences). When you pass a program on, pass these on with it. Reading them never asks you to accept the EK9 licence first.
- -dv Debug verbose mode: internal debug information during compilation, such as phase timings, symbol table dumps and intermediate results. Mainly for compiler developers.
- -di DI/AOP analysis: a compact injection wiring map of application registrations, injection sites and aspect proxies. Add -E3 for the full detail: the wiring map, aspect method coverage and guidance.
- -Up Check for a newer compiler and upgrade to it. This is not built yet: today it only reports that it would check.
- -h Help for all the options.
- -h Exxxx The full explanation of one error code. For example, ek9 -h E50001 gives the diagnosis, fixes, examples and related codes for "not resolved".
- -h <TypeName> The full API of a built-in type: constructors, methods and operators. For example ek9 -h String, ek9 -h List, ek9 -h Dict.
- -H Every help keyword, by topic: the types, keywords and error codes that have help.
- -m <path> Load another message pack (a zip file) of error messages, DI guidance and Q&A examples. What the pack holds overrides the built-in entries; anything else falls back to the defaults. Researchers can try different message styles or Q&A content without changing the compiler. Combine it with -h Exxxx, -E3 or -q to see the overridden content.
- -e <name>=<value> Define an environment variable, for example user=Steve, or user=\'Steve Limb\' if the value has spaces.
-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.
- Blank lines and lines starting with # are ignored; a leading export is allowed, so a line copied from ~/.zshrc works.
- One pair of surrounding quotes is removed from a value (EK9_COMPILER_MEMORY="-Xmx1g -Xss4m"); nothing else is interpreted, so $HOME stays as written.
- Only names starting EK9_ are used. Any other line is reported as a warning, naming its line number, and ignored.
- If the file does not exist, the defaults apply.
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.
- EK9_COMPILER_MEMORY The options of the compiler itself; the default is -Xmx512m. Raise the heap for large projects or several compilations in parallel, for example export EK9_COMPILER_MEMORY="-Xmx1024m".
- EK9_IDE_MEMORY The options of the IDE (ek9 -ide). If it is not set, the IDE uses EK9_COMPILER_MEMORY, and if neither is set the default is -Xmx2g, for example export EK9_IDE_MEMORY="-Xmx4g".
- EK9_APPLICATION_MEMORY The options of your compiled application when it runs; the default is -Xmx512m. Set it to what your application needs, for example export EK9_APPLICATION_MEMORY="-Xmx2048m -Xss4m".
Testing
- EK9_TEST_TIMEOUT_MS The longest one test run may take, in milliseconds; the default is 30000. A test still running then fails with E82001. It must be a whole number greater than 0; any other value is reported once and the default is used. The same value governs ek9 -t and the IDE's test runs (the IDE's settings page edits it in ~/.ek9/ek9.env), for example export EK9_TEST_TIMEOUT_MS=60000.
Terminal Output Control
EK9 detects what the terminal supports (colours, Unicode, emoji) from the standard environment variables. These override the detection:
- EK9_PLAIN_TEXT When set to any value, all output is plain ASCII: no colours, no Unicode box-drawing characters and no emoji. Use it when piping output to a file or on a limited terminal, for example export EK9_PLAIN_TEXT=1.
- NO_COLOR When set, turns colour off, following the no-color.org convention many command line tools use. Unicode and emoji are still used if the terminal supports them.
- FORCE_COLOR When set, forces colour even when the terminal is not detected as colour-capable. Use it in CI/CD pipelines that show ANSI colours but do not set a standard TERM.
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.
- -ls Run the compiler as a language server only, over stdin/stdout.
- -lsh Add EK9 language hover help, including for operators such as :=, with links to the matching sections of this site. Useful when you are new to EK9.
Supported Capabilities
The EK9 language server provides:
- Diagnostics Compilation errors as you type and on save. The workspace is compiled again when a file is opened or changed; if one compile takes too long, the server compiles on save only.
- Hover The type of any symbol. With -lsh, keywords and operators also show language help with links to the documentation.
- Completion EK9 keywords and built-in type names, matched fuzzily: sw suggests switch.
- Go to Definition From a use of a symbol to its definition.
- Find References Every use of a symbol across the workspace.
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:
- Install the EK9 extension from the VS Code marketplace.
- Put the ek9 command on your PATH.
- 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:
- ek9_version The compiler version. It takes no parameters, so it also checks the server is responding.
- ek9_help Keyword or type help, as ek9 -h <keyword> gives. Omit
keywordfor the index of every help page, or pass a keyword or type name (keyword: "String",keyword: "switch") for its API or usage. Case is resolved for you, sokeyword: "list"gives theListtype's page. - ek9_explain_error The full explanation of an error code (
errorCode: "E05030"): the diagnosis, common causes, fixes and examples, as ek9 -h Exxxx gives. - ek9_paradigm EK9's paradigm summary, the key design decisions that differ from mainstream languages,
as ek9 -h -E4 gives. Optional
withExamplesappends the compiled inline examples. - ek9_ask Search the Q&A with a question in plain words. Parameters:
query(required),mode("summary","detailed"or"migration") andmaxResults(default 3). It returns the matching questions with answers and compilable examples. - ek9_compile Compile the workspace. Parameters include
phaseUpTo(the phase to stop after, default"PRE_IR_CHECKS"),errorLevel(0–3, default 3 for the full explanations) andincludeDev(include dev code, default false). It returns diagnostics with line:column locations and explanations. Where a slip has only one reading it repairs the file and compiles again, listing each repair; passrepair: falseto only compile. - ek9_format Rewrite a file (
file) in canonical style, as the -f pipeline does.
Available Resources
The server also offers two resources an assistant can read:
- ek9://diagnostics The diagnostics from the last ek9_compile call, as a JSON array of
objects with file, line, column, severity, error code and message. Filter it by file name, for example
ek9://diagnostics/myfile.ek9. - ek9://symbols/{moduleName} The symbol table of a compiled module, as a JSON array of objects with name, kind (CLASS, RECORD, TRAIT, FUNCTION and so on) and type. It needs an ek9_compile call first.
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.
- -q Every Q&A entry, by category.
- -q <words> Search the entries: a short summary with a compilable example. Matching is fuzzy and resolves synonyms, so you need not know the exact wording.
- -q <number> One entry by its number; ek9 -q 23 is the entry on basic types.
- -qd <words> The full answer, every section, with a compilable example.
- -qm <words> The summary plus a migration section comparing the feature in Java, Python, Rust, Go, Kotlin and other languages, for developers coming to EK9 from another language.
- -qdm <words> -qd and -qm together: the full answer and the migration comparison.
- -qp <words> Search the protocol entries (delegation, escalation and intent guidance for AI agents); -qp alone lists them.
Training data for language models:
- -Q Every Q&A entry as JSON Lines on stdout, one object per line with the question, answer, example, keywords and migration context, in instruction format for fine-tuning.
- -Qc The same as mlx_lm chat JSON Lines: each line a
messagesarray withsystem,userandassistantroles and an EK9 expert system prompt. Cross-references (such as "See Q23 for types") are replaced by inline summaries. Made for Qwen3 LoRA fine-tuning with--mask-prompt. - -Qct As -Qc, with Qwen3 thinking traces where an entry has one: a
"thinking"field in an entry's JSON header puts<think>...</think>reasoning before the answer. - -Qt Write an mlx_lm training directory,
ek9_training_data/train.jsonlandek9_training_data/valid.jsonl, split 90/10 by category. Every phrasing of one question stays in the same split, so nothing leaks between them. Use it directly withmlx_lm.lora --data ./ek9_training_data. - -Qtt As -Qt, with Qwen3 thinking traces where available.
- -Qn <dir> Scan a directory (recursively) for
.ek9files with JSON metadata headers and@Errordirectives, and write negative training data toek9_negative_training_data/train.jsonlandvalid.jsonl, split 90/10 by error category. Each file gives one to three entries: a code review (“what’s wrong?”), an error fix (“how do I fix Exxxxx?”) and a comparison (“how does EK9 differ from Java?”). The@Errordirectives are stripped from the code, so a model never learns the directive syntax: those directives are compiler-development tooling, not for ordinary programs, which use only@Test(see the error index). Any directory of annotated.ek9files will do, not only the compiler's test corpus. - -Qall <dir> As -Qn, merged with the built-in Q&A, into
ek9_combined_training_data/train.jsonlandvalid.jsonl.
$ 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.
- -c Incremental compile; don't run. If no source has changed since the last build, nothing is compiled; once one has, the whole project is compiled again.
- -cg As -c, with debugging information.
- -cd As -c, with all dev code and debugging information.
- -Cl Clean everything, dependencies included.
- -C Full recompile; don't run. Every source file is compiled, changed or not.
- -Cg As -C, with debugging information.
- -Cd As -C, with all dev code and debugging information.
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.
- -ch Incremental compile, no link.
- -cdh As -ch, with all dev code.
- -Ch Full recompile, no link.
- -Cdh As -Ch, with all dev code.
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.
- -Cp <PHASE> Full recompile up to the named phase. For example, ek9 -Cp IR_GENERATION file.ek9 compiles up to and including IR generation.
- -Cdp <PHASE> As -Cp, with all dev code.
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.
- -E0 Minimal (the default): compact single-line messages, for scripts and CI/CD pipelines.
- -E1 Visual: a Rust/Elm-style display with the source line and caret markers (^^^^^) under the exact location.
- -E2 Visual plus suggestions: "Did you mean?" for typos, by fuzzy string matching (Levenshtein distance), showing up to three similar names when a symbol, method or type is not resolved.
- -E3 Rich: a full explanation of how to fix the problem. Each message ends with a
See: ek9 -h E#####pointer (run it for the full explanation of the code) and a structured box of common causes, fixes and distinctions. Use it while learning EK9 or for a hard type error. - -E4 Paradigm summary: EK9's key design decisions and built-in capabilities that differ from
mainstream languages: no break/continue/return, tri-state semantics, closed types, automatic operators for
enumerations and records, naming conventions, complexity limits and assignment operators. Use
ek9 -h -E4for the overview, or add it to a compile to see the summary beside the errors. - -E5 Full AI bootstrap: -E4 plus the whole of
forAI.json, a structured reference to every language feature, excluded keyword, operator, construct and design reason. Runek9 -h -E5once to learn EK9, then use-E3while you develop. It is meant for AI coding assistants that need the whole language in context. - -E6 Local agent briefing: the much smaller
forAI-local.json, an operational briefing for fine-tuned local models running insideek9 -ai. It holds only what the local model needs at run time: its role, the Oracle query methods, the intent vocabulary, the delegation and escalation protocols, the terrain rules and reminders of the rules most often broken despite training. The EK9 language itself is in the model's fine-tuned weights, not repeated here. Inspect it withek9 -h -E6.
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
Download commandline_errorformat.ek9
-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.
- -f Format one file. It must parse first.
- -F Format every .ek9 file in the project.
- -ff Force format one file. It first repairs what stops the file parsing, where the fault has exactly one reading, then formats. The repairs cover layout (a byte-order mark, CRLF line endings, a missing or wrong #!ek9, tabs used for layout, an odd base indent width) and slips carried over from other languages (comma-separated parameters on one -> line, add(name as String) parameters on a declaration, a defines block at column 0, a default operator that is not last, a stray trailing bracket, &&/||, a trailing : after a condition, # and C block comments, List<String>, Main() @Test, Type?: value). Every repair is reported. A slip with more than one reading (%: mod or rem?) is named, never guessed, and nothing is renamed: a misspelt keyword is left for you to correct, and a tab inside a String is kept. The file is written only if the result parses.
- -fF Force format every project file.
- -fc Repair compile one file. Where -ff repairs only what stops a file parsing, -fc compiles the file in its project and applies every repair the compiler's errors give exactly one reading: the parse repairs of -ff first, then, for example, a trait listed again that another listed trait or the super class already has (E02090). It compiles again after each pass until the file is clean or nothing more applies. The repaired file is written and every repair is reported, as -ff reports them; whatever is still wrong is then reported exactly as -ch reports it, and the exit code is that of a compile. It does not format, never renames anything, and leaves a file it cannot make parse unchanged. Only the named file is repaired; an error in another project file is reported, not touched.
$ 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.
- -P Package, ready for an artefact server or library. Packaging is gated on quality: EK9 first
runs your unit tests (from the /dev area) and enforces the 80% code-coverage threshold. A failing test
stops packaging (exit code 11), and coverage below 80% stops it (exit code 12); only when the tests pass and
coverage is met is the package made. A source that holds no code at all, a pure
package manifest, has nothing to test and packages as it is. A program
cannot itself carry a
@Test, so a publishable program must be decomposed into functions that are tested. - -I Install the packaged software in your local library ($HOME/.ek9/lib). It packages first, so the same test and coverage gate applies.
- -Gk Generate signing keys for deploying packages to an artefact server; needed only to publish to a remote server.
- -D Deploy to an artefact server. It packages (-P) first and generates keys (-Gk) if needed, and the package must be publishable (publicAccess, a version and a license). You will need an account on the remote server and your $HOME/.ek9/config and $HOME/.ek9/credentials set up. The upload itself is not built yet: today -D packages, generates the keys and prepares the signed hash.
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.
- -IV [major|minor|patch|build] Increment one part of the release vector. EK9 works out the next value; you choose which part.
- -SV major.minor.patch Set a specific value, such as 6.8.1-0 (setting always zeros the build number). Use it when you create a branch in your repository and need the code to use the same numbering.
- -SF major.minor.patch-feature Set a specific value for a feature, such as 6.8.0-specials-0 (setting always zeros the build number). As -SV, for feature branches.
- -PV Print the current version, such as 6.8.1-0, or 6.8.0-specials-0 for a feature, so you need not grep for it.
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:
- -t or -t1: Human-readable output with visual formatting (default)
- -t0: Terse output (minimal summary for scripting and CI pass/fail checks)
- -t2: JSON output (structured format for AI/tool integration)
- -t3: JUnit XML output (for CI/CD integration with Jenkins, GitHub Actions, GitLab)
- -t4: Detailed coverage output (human-readable plus detailed coverage report)
- -t5: Verbose/full coverage (shows both covered and uncovered items in detail)
- -t6: HTML coverage report (generates interactive .ek9/coverage/index.html with SVG charts, module breakdown, source views, search, and dark/light theme support)
Coverage Options:
- -tC: Display coverage results after test execution
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:
- -tp, -t1p through -t6p: append p to any test format to enable performance profiling. Records call counts, timing, and percentile distributions (p50, p95, p99) per function. Profiling is reported with the coverage results, so p implies -tC.
- -tp, -t0p, -t1p: the coverage summary, then the top functions by self time
- -t2p: writes .ek9/test-results.json and .ek9/coverage.json; the profiling section, with full statistical metrics, is in coverage.json, for tools and AI assistants to read
- -t3p: writes .ek9/test-results.xml and .ek9/coverage.xml; the XML carries no profiling data, so use -t2p or -t6p to read it
- -t6p: HTML report with flame graph, hot function table (11 columns including min/max and percentiles), and per-function profiling badges in source view
$ 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 - List all tests without running them (respects -t0/-t1/-t2/-t3 format)
- -tg <group> - Filter tests to run only those in the specified group
-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.
- -fuzztest source.ek9 output.ek9 Generate edge-case @Test functions for a source file. It reads the symbol table, generates boundary and edge-case tests and compile-checks each one. The output is a compilable .ek9 file of @Test functions for an AI or a person to review. It refuses if the output file exists (use -overwrite to replace it).
- -fuzzmutate source.ek9 mutations/ Generate mutation variants to assess test quality: single-point mutations that compile but change behaviour. The output is a directory of .ek9 variants with a manifest.txt. It refuses if the directory exists (use -overwrite to replace it).
- -test tests.ek9 Run a mutation kill-check against a test file, with -fuzzmutate, to check your tests detect the mutations. Each variant is compiled and tested: a "killed" mutation means your tests caught the change; a "survived" one shows a gap in your tests.
- -overwrite Allow -fuzztest and -fuzzmutate to overwrite an existing output file or directory. Without it they refuse, so nothing is lost by accident.
- -seed <n> A seed for -fuzztest or -fuzzmutate: the same seed generates the same tests or mutations, so results can be reproduced.
- -n <n> The maximum number of candidates for -fuzztest (default 200) or -fuzzmutate (default 100).
$ 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.
- -O0 No optimisation: the fastest compile and the most debuggable code, with every coverage probe and all debug information kept. Running tests (-t) forces it, so coverage maps accurately.
- -O2 Minimal optimisation, the default: meant for safe optimisations that do not get in the way of debugging.
- -O3 Full optimisation, for run-time speed: meant for aggressive optimisations such as function inlining and dead code elimination. It cannot be used with tests (-t), because it would make coverage wrong.
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
- -d port Run with a debugger agent (JDWP) listening on the port; compile with debugging information first. Attach the EK9 IDE, an editor through ek9 -dap, or an AI assistant through ek9 -mcp's debug tools.
- -r Run normally (assumed if no other option is given).
- -repl Start the interactive REPL (Read-Eval-Print-Loop). -E0 to -E3 set the error verbosity it starts with (a higher level starts at -E3); in the session, :error N sets 0 to 3 (any other level is refused). The REPL documentation covers every feature.
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
- Syntax highlighting: keywords, types, strings, numbers and comments each have their own colour.
- Tab completion: keywords, types, variables, and methods after ".".
- Mode switching: /function, /class, /record, /trait and /type for definitions.
- External editor: :edit opens $VISUAL or $EDITOR on the session source.
- Sessions: :save and :load keep your work.
- Buffer editing: :edit N, :del N, :ins N and :undo change single lines.
- Hover help: Ctrl+H shows a symbol's type.
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)
Download commandline_passthrough.ek9
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:
- Stdin documentation - Reading from standard input
- Stdout documentation - Writing to standard output
- Stderr documentation - Error output channel
- Complete CLI application example - a practical worked example
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:
- Compiler code 0: the compiler has printed a command to run your program. The wrapper runs it and returns whatever exit code your EK9 program returns.
- Compiler code 1: the operation succeeded and there is nothing to run (for example -C compile only, -V version, -Gk generate keys). The wrapper returns 0.
- Compiler codes 2 to 17: the compiler hit an error. The wrapper returns the same code, which names the failure.
Exit Codes You Will See
The ek9 command follows the Unix convention:
- 0 Success: an operation completed (compilation, versioning, packaging and so on), or your program ran and returned 0.
- 1 Cannot execute: the system could not run the EK9 compiler or your compiled program. Check that EK9 is installed properly and that you have the permissions you need; the details go to stderr. (A program that ran can also exit 1; see below.)
- 2 Invalid parameters: the command line could not be parsed.
- 3 File access error: the file was not found or could not be read (permissions). This code is only for file system access, never for errors in your code.
- 4 Invalid combination of parameters: each is valid, but not together.
- 5 No programs found: the EK9 file has no programs to run.
- 6 Program not specified: the file has more than one program; name the one to run with -r.
- 7 The language server failed to start (-ls).
- 8 Compilation failed: the source has syntax, parse, type or other errors, including a file that exists but holds invalid EK9. The error messages go to stderr.
- 9 Wrong number of program arguments: the program needs a set number of arguments and was given a different number. The generated code checks this when it starts.
- 10 Cannot convert an argument: a program argument could not be converted to the type of its parameter (for example "notInteger" where an Integer is required). The generated code checks this when it starts.
- 11 Tests failed: one or more @Test programs failed their assertions, when running tests with -t, -t0, -t1, -t2 or -t3.
- 12 Coverage insufficient: every test passed, but code coverage is below the required 80%. This separates test failures (11) from too little testing. The threshold is checked even without -tC.
- 13 The MCP server failed to start (-mcp, see Model Context Protocol).
- 14 Internal compiler error: the compiler itself failed with an uncaught error, most often OutOfMemoryError when the JVM heap is too small for what is being compiled. The build did not complete: output artefacts are missing or partial. This differs from 8, where the compiler worked and rejected your source. If you see 14, raise the compiler heap (EK9_COMPILER_MEMORY) and retry before looking at your code.
- 15 Fuzzing found compiler crashes: a -fuzz run (any output format) generated programs that crashed the compiler, and the crash files are in ./fuzz-crashes/. It is deliberately not 11 (a unit test failed), so a CI step can gate on the exit code alone. A fuzz run that finds no crash exits 0.
- 16 The debug adapter failed to start (-dap, the Debug Adapter Protocol).
- 17 Dependency resolution failed: a package named in deps or devDeps was not found (neither unpacked nor zipped in ~/.ek9/lib, and there is no remote repository to fetch it from yet), or the dependencies cannot be made consistent (a circular dependency, or a semantic version breach). The report names the dependency and its version, the file:line that declares it and where it looked. Your source and command line may both be fine: install the dependency (ek9 -I in its own project) or correct its version.
- 18 The URL policy could not be loaded as your program started: EK9_URL_POLICY names a file that is missing, unreadable or invalid, or EK9_URL_POLICY_STRICT is set with no file. The program does not run; stderr names the file, the line and the reason (see the URL policy).
- 19 The EK9 licence was not accepted: ek9 showed it at the terminal and the answer was not yes, so nothing was run. Without a terminal it is never asked; accept it there with EK9_ACCEPT_LICENCE=yes or ek9 -accept-licence.
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
Download commandline_broken.ek9
$ 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):
- It finishes normally: 0.
- It declares a return, which must be an Integer (E07590): the value it returns, once the application teardown has run. An unset return exits 1.
- An Exception that nothing catches reaches it: that exception's own exit code, set with
Exception("reason", 3), or 1 if it has none. Only the exception that reaches the main program counts; the exit code of a cause it wraps is not used. To end with a code of your choosing, throw an Exception carrying it and do not catch it. - A Panic: always 1. Only the language raises a Panic: a failed
require, a constrained-type value out of range, an unset for-range bound. User code cannot create a Panic (E50060), so it cannot throw one, cannot catch one (E04030), and cannot choose its code.
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:
- The ek9 command: when it cannot compile or start your program, it exits with one of its own codes listed above (for example 3 file access, 8 compilation failed, 9/10 the program arguments do not fit). Your program never ran.
- Your program: when it ran, the ek9 command passes its exit status through unchanged.
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:
- When the ek9 command itself fails, its first stderr line is
ek9: <reason> (exit N)(see the failure marker). Your program's exit never carries that line, so a non-zero status without it is your program's own. - An uncaught Exception or Panic from your program also prints its own line,
Exception in thread "main" ...; a compile failure prints EK9 error lines instead. - Compile first with ek9 -C myprogram.ek9: a non-zero exit from that step is always the ek9 command's.
- Under ek9 -t a failing
assertfails its test and the ek9 command exits 11: the test runner's code, not a status from your program.
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.