How do I debug an EK9 program?
← Debugging and Troubleshooting · Ref: Q246
EK9 provides several strategies for finding and preventing bugs, emphasising compile-time detection over runtime debugging. The compiler catches the majority of defects before your program ever runs.
COMPILE-TIME DEFECT PREVENTION
The compiler is your primary debugging tool. EK9's type system, flow analysis, and enforced guard patterns catch 80-90% of defects at compile time. Error messages include the file, line, column, and a specific error code. Use 'ek9 -E1' for visual error display with source snippets and carets pointing to the exact location. Use 'ek9 -h EXXXXX' to look up any error code for a detailed explanation.
REQUIRE AND ASSERT
The 'require' keyword validates preconditions at the start of methods and functions. It is always active and cannot be disabled:
require value? require count > 0
If the condition fails, an exception is thrown with the source location. Use 'assert' for postconditions and invariants inside method bodies.
DIAGNOSTIC OUTPUT
Use stdout.println() to print values during development. The $ operator converts any value to a string, and string interpolation with backtick strings makes this easy:
stdout.println(`Debug: value=${value}, count=${count}`)
DEBUG INSTRUMENTATION (-cg flag)
Compile with 'ek9 -cg' to enable debug instrumentation. This embeds source file, line, and column information into assertion messages and exception stack traces. Without -cg, assertion failures show generic messages. With -cg, they show exact source locations like './myfile.ek9:42:5'.
BLACK-BOX TESTING
EK9 has built-in black-box testing. Create an expected_output.txt file alongside your .ek9 file. Run with 'ek9 -t' and the test runner compares actual output against expected output line by line. This catches regressions without writing any test assertions.
TEST DIRECTIVE
Mark methods with @Test to create unit tests. Use assert, assertThrows, and assertDoesNotThrow inside test methods. Run all tests with 'ek9 -t myfile.ek9'.
LANGUAGE SERVER (LSP)
Run 'ek9 -ls' for real-time error detection in your editor. The LSP provides hover documentation, go-to-definition, and completions. Errors appear as you type, before you even save the file.
PLATFORM DEBUGGER
A platform debugger (edb) is planned but not yet available. The compiler generates JSR-45 SMAP data and LocalVariableTable entries so that standard JVM debuggers can step through EK9 source code. In the meantime, use diagnostic output and the testing framework.
See Q134 for try/catch error handling. See Q29 for tri-state (unset variable) semantics. See Q155 for writing unit tests. See Q247 for reading error messages. See Q252 for verbose compilation modes. See Q322 for profiling to find performance bottlenecks.
Example
defines module qa.debugging.program defines function validateScore() as pure -> score as Integer <- rtn as Boolean: false require score? require score >= 0 require score <= 100 rtn: true defines class GameScore points <- Integer() GameScore() -> initialPoints as Integer require initialPoints? require initialPoints >= 0 this.points :=: initialPoints addPoints() -> amount as Integer require amount? require amount > 0 points += amount operator $ as pure <- rtn as String: `GameScore(${points})` override operator ? as pure <- rtn as Boolean: points? defines program DebuggingDemo() stdout <- Stdout() // Diagnostic output with $ operator score <- GameScore(10) stdout.println(`Initial: ${score}`) score.addPoints(5) stdout.println(`After adding 5: ${score}`) // Validation with require valid <- validateScore(85) stdout.println(`Score 85 valid: ${valid}`) // Try/catch for error handling try badScore <- GameScore(-1) stdout.println(`Should not reach: ${badScore}`) catch -> ex as Exception stdout.println(`Caught: ${ex.reason()}`) // Guard pattern for safe access parsed <- Integer("42") if parsed? stdout.println(`Parsed value: ${parsed}`) badParsed <- Integer("not a number") if badParsed? stdout.println("Should not print") else stdout.println("Invalid input detected via ? check")
Common mistakes
E50001 — If a declared variable is never referenced after assignment, the compiler rejects it. Every variable must be used. See ek9 -h E50001 for details.
Incorrect:
unused <- validateScore(85)
Correct:
valid <- validateScore(85)
Other ways to ask this
- What debugging tools does EK9 provide?
- How do I find bugs in my EK9 code?
- What is the EK9 debugging workflow?
Coming from another language?
Java: IDE debuggers (IntelliJ, Eclipse), System.out.println, JUnit assertions, stack traces. Python: pdb debugger, print(), pytest, traceback. Rust: dbg!() macro, println!(), cargo test, LLDB/GDB. Go: fmt.Println, Delve debugger, go test. C++: GDB/LLDB, cerr, assert macro. Kotlin: IDE debuggers, println, JUnit. EK9: require/assert always active, stdout.println with $ operator, -cg debug instrumentation, built-in black-box testing, LSP real-time errors, platform debugger planned.
Keywords: println, debugging, diagnostic, error, lsp, troubleshoot, assert, trace, require, program, error-message, output, bug, debug, instrumentation, testing