# EK9 — Full Language Reference for AI > Structured reference for AI coding assistants working with EK9 Generated from `forAI.json`. The concise index is at https://ek9.io/llms.txt. The Q&A corpus is published separately at https://ek9.io/qa/ (machine-readable: https://ek9.io/qa/qa.jsonl). ## Excluded Features - **Description**: Features deliberately excluded from EK9 based on production bug evidence - **Features** - **Keyword**: break - **Error Code**: E01070 - **Error URL**: https://ek9.io/errors.html#E01070 - **Reason**: 15% of C# bugs (Microsoft 2011), 200+ Linux kernel CVEs - **Alternative**: Stream pipelines with 'head' or guards - **Example** - **Wrong**: ```ek9 for item in items if isMatch(item) break ``` - **Correct**: ```ek9 result <- cat items | filter by isMatch | head 1 | collect as List of String ``` - **Documentation**: https://ek9.io/flowControl.html#streams - **Keyword**: continue - **Error Code**: E01071 - **Error URL**: https://ek9.io/errors.html#E01071 - **Reason**: 15% of C# bugs (Microsoft 2011), 3x higher defect density - **Alternative**: Stream pipelines with 'filter' or 'reject' - **Example** - **Wrong**: ```ek9 for item in items if not isValid(item) continue process(item) ``` - **Correct**: ```ek9 valid <- cat items | filter by isValid | collect as List of String for item in valid process(item) ``` - **Documentation**: https://ek9.io/flowControl.html#streams - **Keyword**: return - **Error Code**: E01072 - **Error URL**: https://ek9.io/errors.html#E01072 - **Reason**: Apple SSL bug (2014), 23% of resource leaks (Google study) - **Alternative**: Return declarations with '<-' and declaration guards - **Example** - **Wrong**: ```ek9 process() -> content as String <- result as String? if not content? return "" ``` - **Correct**: ```ek9 process() -> content as String <- result as String: String() if content? result: transform(content) ``` - **Documentation**: https://ek9.io/flowControl.html#guards - **Keyword**: null - **Error Code**: E01073 - **Error URL**: https://ek9.io/errors.html#E01073 - **Reason**: Billion-dollar mistake (Hoare), #1 Java exception - **Alternative**: Tri-state semantics (absent/unset/set) with '?' operator - **Example** - **Wrong**: ```ek9 name <- null ``` - **Correct**: ```ek9 name <- String() // Creates UNSET String if name? process(name) // Safe - guaranteed set ``` - **Documentation**: https://ek9.io/basics.html#tri-state - **Keyword**: ; - **Name**: semicolon - **Error Code**: E01074 - **Error URL**: https://ek9.io/errors.html#E01074 - **Reason**: Indentation-based syntax (like Python) - **Alternative**: Simply remove - EK9 uses newlines and indentation - **Example** - **Wrong**: ```ek9 name <- "Steve"; ``` - **Correct**: ```ek9 name <- "Steve" ``` - **Documentation**: https://ek9.io/structure.html#indentation - **Keyword**: new - **Error Code**: E01075 - **Error URL**: https://ek9.io/errors.html#E01075 - **Reason**: EK9 uses TypeName() directly without 'new' keyword - **Alternative**: Call type constructor directly: Person() instead of new Person() - **Example** - **Wrong**: ```ek9 person <- new Person("Steve") ``` - **Correct**: ```ek9 person <- Person("Steve") ``` - **Documentation**: https://ek9.io/basics.html#construction - **Keyword**: goto - **Error Code**: E01076 - **Error URL**: https://ek9.io/errors.html#E01076 - **Reason**: Dijkstra (1968) 'Go To Statement Considered Harmful', Apple SSL bug (2014) - **Alternative**: Use structured control flow: try/finally, guards - **Example** - **Wrong**: ```ek9 if error goto cleanup ``` - **Correct**: ```ek9 try riskyOperation() finally cleanup() ``` - **Documentation**: https://ek9.io/flowControl.html#structured - **Keyword**: def - **Error Code**: E01077 - **Error URL**: https://ek9.io/errors.html#E01077 - **Reason**: EK9 defines functions by name directly without keyword (Python syntax excluded) - **Note**: Written where a function is declared, 'def greet(name as String)' is a plain syntax error; E01077 is reported where 'def name()' appears as a statement - **Alternative**: Start with function name directly, use -> for parameters, <- for returns - **Example** - **Wrong**: ```ek9 def greet(name as String) stdout.println("Hello " + name) ``` - **Correct**: ```ek9 greet() -> name as String stdout <- Stdout() stdout.println("Hello " + name) ``` - **Documentation**: https://ek9.io/functions.html - **Keyword**: elif - **Error Code**: E01078 - **Error URL**: https://ek9.io/errors.html#E01078 - **Reason**: EK9 uses 'else if' as two words for readability (Python syntax excluded) - **Alternative**: Use 'else if' (two separate words) - **Example** - **Wrong**: ```ek9 if x > high big() elif x > low medium() ``` - **Correct**: ```ek9 if x > high big() else if x > low medium() ``` - **Documentation**: https://ek9.io/flowControl.html#if - **Keyword**: None - **Error Code**: E01079 - **Error URL**: https://ek9.io/errors.html#E01079 - **Reason**: Python's None equivalent - EK9 uses tri-state semantics instead - **Alternative**: Use TypeName() for unset values, '?' operator to check if set - **Example** - **Wrong**: ```ek9 name <- None ``` - **Correct**: ```ek9 name <- String() // Creates UNSET String if name? process(name) ``` - **Documentation**: https://ek9.io/basics.html#tri-state - **Keyword**: self - **Error Code**: E01080 - **Error URL**: https://ek9.io/errors.html#E01080 - **Reason**: EK9 uses 'this' instead of 'self' (Java/C++/JavaScript convention) - **Alternative**: Use 'this' keyword, or omit when unambiguous - **Example** - **Wrong**: ```ek9 greet() stdout <- Stdout() stdout.println("I am " + self.name) ``` - **Correct**: ```ek9 greet() stdout <- Stdout() stdout.println("I am " + this.name) // Or simply: "I am " + name ``` - **Documentation**: https://ek9.io/classes.html#this - **Keyword**: close - **Error Code**: E01094 - **Error URL**: https://ek9.io/errors.html#E01094 - **Reason**: Every close is generated, so a hand-written close could only close a value twice or under an owner still using it (double close, use after close) - **Alternative**: A local is a try-with-resources resource (or stays unnamed, and its statement closes it); a type's own closeable properties are closed by 'default operator close' or the chain after a written 'operator close'. 'x.close()' is E81060. - **Example** - **Wrong**: ```ek9 operator close as pure close conn ``` - **Correct**: ```ek9 default operator close // closes conn, then the super class's close ``` - **Documentation**: https://ek9.io/errors.html#E01094 - **Keyword**: listComprehension - **Name**: Python list comprehension - **Pattern**: [x for x in items] - **Error Code**: E01081 - **Error URL**: https://ek9.io/errors.html#E01081 - **Reason**: EK9 uses stream pipelines for composability and lazy evaluation - **Alternative**: Define function for transformation, use 'cat collection | map with fn | collect as List' pattern - **Example** - **Wrong**: ```ek9 squares <- [x * x for x in numbers] ``` - **Correct**: ```ek9 defines function squareIt() as pure -> n as Integer <- result as Integer: n * n defines program Squares() numbers <- [1, 2, 3] squares <- cat numbers | map with squareIt | collect as List of Integer Stdout().println($squares) ``` - **Documentation**: https://ek9.io/streamsAndPipelines.html - **Keyword**: multipleArgumentArrows - **Name**: multiple '->' arrows for parameters - **Pattern**: ```ek9 -> param1 as T1 -> param2 as T2 ``` - **Error Code**: E01082 - **Error URL**: https://ek9.io/errors.html#E01082 - **Reason**: EK9 uses ONE '->' with indented block for multiple parameters - **Alternative**: Use single '->' followed by indented parameter block - **Example** - **Wrong**: ```ek9 badFunction() -> param1 as String -> param2 as Integer <- result as String: `${param1} ${param2}` ``` - **Correct**: ```ek9 goodFunction() -> param1 as String param2 as Integer <- result as String: `${param1} ${param2}` ``` - **Documentation**: https://ek9.io/functions.html#parameters - **Keyword**: multipleReturnArrows - **Name**: multiple '<-' arrows for returns - **Pattern**: ```ek9 <- result1 as T1 <- result2 as T2 ``` - **Error Code**: E01083 - **Error URL**: https://ek9.io/errors.html#E01083 - **Reason**: EK9 functions have exactly ONE return declaration - use records for multiple values - **Alternative**: Create a record type and return a single instance containing multiple fields - **Example** - **Wrong**: ```ek9 badFunction() -> input as String <- result1 as String: input <- result2 as Integer: length input ``` - **Correct**: ```ek9 defines record ReturnValues text as String: String() count as Integer: Integer() ReturnValues() -> text as String count as Integer this.text: text this.count: count default operator defines function goodFunction() -> input as String <- rtn as ReturnValues: ReturnValues(input, length input) ``` - **Documentation**: https://ek9.io/functions.html#returns - **Keyword**: instanceof - **Name**: type checking - **Reason**: Breaks polymorphism, creates brittle code, causes maintenance bugs - **Alternative**: Use 'dispatcher' methods for type-specific behavior - **Note**: There is NO way to check types at runtime in EK9 - this is by design - **Documentation**: https://ek9.io/advancedClassMethods.html#dispatcher - **Keyword**: casting - **Name**: type casting - **Reason**: Breaks type safety, enables ClassCastException-style bugs - **Alternative**: Use 'dispatcher' methods - compiler routes to correct overload - **Note**: There is NO way to cast types in EK9 - this is by design - **Documentation**: https://ek9.io/advancedClassMethods.html#dispatcher - **Keyword**: const - **Name**: constant data modifier - **Reason**: EK9 takes a fundamentally different approach: the constraint is on the OPERATION, not the data - **Alternative**: Mark functions/methods 'as pure' — compiler prevents all mutation in pure contexts at compile time - **Note**: Data is NEVER const in EK9. Immutability is contextual: the same List is unmutatable in a pure function but fully mutable in a non-pure function. This preserves the Liskov Substitution Principle — Java's immutable collections break LSP by throwing runtime UnsupportedOperationException - **Documentation**: https://ek9.io/basics.html#pure - **Keyword**: final - **Name**: final/sealed modifier - **Reason**: EK9 types are CLOSED by default (no 'final' needed). For data immutability, use 'as pure' on operations - **Alternative**: Types are closed by default — use 'as open' to allow extension. Use 'as pure' for mutation control - **Note**: Two concepts combined in other languages: (1) Extension prevention — EK9 types are closed by default, use 'as open' to opt in. (2) Data immutability — use 'as pure' on functions/methods, not 'final' on data - **Documentation**: https://ek9.io/classes.html - **Keyword**: immutable - **Name**: immutable data types - **Reason**: EK9 has NO immutable List, Dict, record, or any data type — and never will. ALL types are mutable - **Alternative**: Mark functions/methods 'as pure' — compiler prevents mutation at compile time, not runtime - **Note**: Java's immutable List breaks the Liskov Substitution Principle by throwing UnsupportedOperationException at runtime. EK9 prevents mutation at COMPILE TIME instead. The same object can be: unmutatable when passed to a pure function, fully mutable when passed to a non-pure function. Immutability is contextual, not permanent - **Documentation**: https://ek9.io/basics.html#pure - **Keyword**: classBraces - **Name**: Java/C# class definition with braces and access modifiers - **Reason**: EK9 class definitions use INDENTATION (like Python), not braces. Fields are ALWAYS private (no access modifier keyword). Methods do NOT use 'public', 'private', 'protected', 'void', or 'this.' prefix. Method signatures use '-> input as Type' for parameters and '<- result as Type' for return values. NO 'new Type()' for construction — just 'Type()'. - **Alternative**: Use indentation-based EK9 class syntax. Fields declared bare. Constructor name matches class name. Methods use '-> input as Type' / '<- result as Type'. Field access without 'this.' prefix. - **Example** - **Wrong**: ```ek9 defines class UniqueNames { private List names = new List(); public void add(String name) { if (!names.contains(name)) { names.add(name); } } public int count() { return names.size(); } } ``` - **Correct**: ```ek9 defines class UniqueNames names as List of String? UniqueNames() names: List() of String add() -> name as String if not name in names names += name count() as pure <- rtn as Integer: names.length() default operator ? ``` - **Rule**: EK9 has NO { } braces, NO 'public/private/protected', NO 'void', NO 'this.', NO 'new'. Indentation is the structure. Fields are bare names. Constructor identical to class name. '-> ' for params, '<- ' for return. - **Documentation**: https://ek9.io/structure.html#class - **Keyword**: switchCStyle - **Name**: C/Java switch with parens, braces, colons, and return statements - **Reason**: EK9 switch uses indentation (no braces), no parens around argument, no colons after case labels. Switch CAN BE an expression — its result assigned to a variable. Multiple values per case via comma: 'case 1, 2, 3'. NO 'return' statement — assign to declared return variable: 'rtn: value'. NO fall-through (each case is independent by design). - **Alternative**: Use the EK9 indentation-based switch with bare 'case x' (no colon). Body of each case indents. Assign result to a variable for switch-as-expression, or assign to outer return variable for switch-as-statement. - **Example** - **Wrong**: ```ek9 function findLabel(code: Integer): String { switch(code) { case 1: return "active"; case 2: return "inactive"; default: return "unknown"; } } ``` - **Correct**: ```ek9 findLabel() as pure -> code as Integer <- rtn as String? switch code case 1 rtn: "active" case 2 rtn: "inactive" default rtn: "unknown" ``` - **Rule**: EK9 switch: NO parens around argument, NO braces, NO colons after case/default labels, NO 'return' statement. Body of each case indents under bare 'case x'. Switch can be an expression: 'result <- switch x' with new variable, or assign in each case to an outer return variable. - **Documentation**: https://ek9.io/flowControl.html#switch - **Keyword**: lowercasePrimitives - **Name**: Java/C# lowercase primitive type names - **Reason**: EK9 primitive types are PascalCase: Integer, String, Boolean, Float, Date, Money. There are NO 'int', 'string', 'bool', 'float' lowercase primitives. EK9 wraps these as proper objects with built-in operators (==, <=>, $, #?, ?, etc.) and tri-state semantics (absent/unset/set). - **Alternative**: Use PascalCase EK9 type names: Integer not int, String not string, Boolean not bool, Float not float. These are full objects with operator support, not primitive values. - **Example** - **Wrong**: ```ek9 function add(a: int, b: int): int { return a + b; } ``` - **Correct**: ```ek9 add() as pure -> a as Integer b as Integer <- rtn as Integer: a + b ``` - **Rule**: EK9 primitive type names are PascalCase (Integer, String, Boolean, Float, Date). Lowercase 'int'/'string'/'bool'/'float' do NOT exist. The PascalCase types are full objects with built-in operators and tri-state semantics. - **Documentation**: https://ek9.io/standardTypes.html ## Dispatcher Pattern - **Description**: EK9's replacement for instanceof/casting - the ONLY way to handle type-specific behavior - **Critical Rule**: There is NO instanceof and NO casting in EK9. Dispatcher is the ONLY mechanism. - **How It Works**: Mark a method 'as dispatcher', provide overloaded methods for specific types, compiler generates dispatch logic - **Why Methods Only**: Functions cannot be overloaded (names must be unique in module), so dispatcher only works on methods - **Single Dispatch** - **Description**: Dispatch on one parameter's runtime type - **Example**: ```ek9 render() as dispatcher -> shape as Shape shape.draw() render() -> circle as Circle Stdout().println(`Circle of radius ${circle.radius()}`) ``` - **Double Dispatch** - **Description**: Dispatch on two parameters' runtime types (visitor pattern replacement) - **Example**: ```ek9 intersect() as dispatcher -> s1 as Shape s2 as Shape <- result as Intersection: NoIntersection() intersect() -> s1 as Circle s2 as Circle <- result as Intersection: ArcIntersection() ``` - **Ambiguity Detection** - **Description**: Compiler detects ambiguous dispatch and requires explicit resolution - **Example**: ```ek9 If Square implements T1 and T2, and you have render(T1) and render(T2), compiler requires render(Square) to disambiguate ``` - **Fallback Behavior**: If no specific type match exists, the dispatcher's own implementation is called as default - **Related Errors** - E07120 - E07820 - E05170 - E05180 - **Documentation**: https://ek9.io/advancedClassMethods.html#dispatcher ## Tri State Semantics - **Description**: EK9's tri-state object model replaces null - **States** - **Name**: absent - **Meaning**: Object doesn't exist - **Example**: ```ek9 Dict key not found ``` - **Name**: unset - **Meaning**: Object exists but has no meaningful value - **Example**: ```ek9 String() creates unset String ``` - **Name**: set - **Meaning**: Object exists with valid, usable value - **Example**: ```ek9 String("hello") is set ``` - **Critical Rule**: Collections (List, Dict) are ALWAYS set when created, even if empty. Only primitives (String, Integer) are unset when empty. - **Operator**: ? - **Operator Meaning**: Calls _isSet() method to check if value is set - **Documentation**: https://ek9.io/basics.html#tri-state ## Closed By Default - **Description**: EK9 types are closed by default (like Kotlin) - **Error Code**: E05030 - **Error URL**: https://ek9.io/errors.html#E05030 - **Never Extendable** - List - Dict - DictEntry - MutexLock - Optional - PriorityQueue - Result - **How To Make Extendable**: Declare with 'as open' modifier - **Correct Pattern**: Use composition/delegation instead of inheritance - **Example** - **Wrong**: ```ek9 MyList extends List of String ``` - **Correct**: ```ek9 MyList items as List of String: List() of String add() -> item as String items += item default operator ? ``` - **Documentation**: https://ek9.io/inheritance.html#closed-by-default ## Access Modifiers - **Description**: EK9 access modifier rules vary by declaration kind - fields, methods, and operators each have different rules - **Critical Rule**: Fields are ALWAYS private in classes/components (no syntax to change this). Only METHODS support public/protected/private modifiers. - **Fields** - **Rule**: Fields (properties) have NO access modifier in the grammar - **Class Fields**: Always private - subclasses cannot access parent fields directly - **Record Fields**: Always public - records are data structures with public access - **Component Fields**: Always private - same as class fields - **Trait Fields**: Traits cannot have fields - **Package Properties**: Always public within module scope - **No Protected Fields**: There is no way to declare a protected field - this is deliberate to avoid the fragile base class problem - **Subclass Access**: Use accessor methods (public or protected) to expose parent state to subclasses - **Methods** - **Rule**: Methods support optional access modifiers - **Syntax**: (OVERRIDE | DEFAULT)? accessModifier? identifier ... - **Default Access**: Public (no modifier needed) - **Options** - public - protected - private - **Protected Methods**: Accessible from subclasses and same-hierarchy types - **Private Methods**: Accessible only within the declaring type - **Operators** - **Rule**: Operators have NO access modifier - always public - **Syntax**: (OVERRIDE | DEFAULT)? OPERATOR operator ... - **Rationale**: Operators define the public contract of a type - **Example** - **Wrong**: ```ek9 defines class MyClass as open protected name <- String() //WRONG: grammar error, fields cannot have access modifiers ``` - **Correct**: ```ek9 defines class MyClass as open name <- String() //Field is always private protected getName() as pure //Use protected method for subclass access <- rtn as String: name default operator ? ChildClass extends MyClass describe() <- rtn as String: getName() //Access parent state via method ``` - **Error Codes** - **E06180**: Field or method not accessible from this context (private access violation) - **Documentation**: https://ek9.io/advancedClassMethods.html ## Assignment Operators - **Description**: Three distinct assignment operators with precise semantics - **Operators** - **Operator**: <- - **Name**: Declaration - **Semantics**: Create NEW variable + first assignment - **Use Case**: First-time assignment - **Operator**: := - **Name**: Assignment - **Semantics**: Assign to EXISTING variable - **Use Case**: Reassignment - **Operator**: :=? - **Name**: Assign-If-Unset - **Semantics**: Fill the variable only if it is UNSET; the right side is not evaluated otherwise (a :=? b is a := a ?: b). It always leaves an object in the target - present, maybe unset, never absent - so it initialises a variable declared with no value where it always runs - **Use Case**: Conditional initialization, defaults - **Names**: One name per symbol: x? = set-check; <- = declaration (in a control-flow head: declaration guard); := = assignment (blind in a head - add then x?); :=? = assign-if-unset; ?: = coalesce (Elvis); ? >=? = comparison coalescing (returns an operand); guard = enter only if set (Swift's if let). There is no ?= operator and no assignment yields a value - **Documentation**: https://ek9.io/basics.html#assignments ## Guard Expressions - **Description**: Guards combine assignment with null/isSet checking. The part of a control-flow head before then/with is the pre-flow: it runs exactly once, before the body (a while/do-while re-tests only its condition) - **Syntax**: if variable <- expression - **Meaning**: Execute block only if expression result is SET - **Works In** - if - switch - for - while - do - try - **Operators** - **Operator**: <- - **In Head**: declare a NEW variable, enter only if it is SET - **Example**: ```ek9 if name <- getName() ``` - **Operator**: := then x? - **In Head**: re-point an EXISTING variable (an unset result overwrites it), then the explicit check enters only if it is now SET; parenthesise any further condition - **Example**: ```ek9 if name := getName() then name? and (name <> "guest") ``` - **Operator**: :=? - **In Head**: assign only if the variable is UNSET (the right side is not even evaluated otherwise), enter if it is SET afterwards - **Example**: ```ek9 if cache :=? compute() ``` - **Operator**: := - **In Head**: blind assignment, no set-check: an if needs its own condition (E07340) - **Example**: ```ek9 if count := getCount() then count > 0 ``` - **Boolean Truth Guard**: In an if / else if, a <- or :=? head whose variable is declared exactly Boolean enters only when it is SET AND TRUE (if allowed <- permitted(user) skips false as well as unset). switch keeps enter-if-set so case false stays reachable; while, do, for and try too - **Later Else Blocks**: Every later else if / else of an if chain runs because a <- guard failed, so reading that guard's variable there before assigning it is E08270 - **Switch For Try Do**: These have no then x? gate: assign first, then wrap the construct - x := f() then if x? around switch x - or use <- when the variable is only needed inside - **As Expression**: An assignment never yields a value: write x := f() then ok <- x? to know whether the new value is set. To keep x when f() is unset write x := f() ?: x (?? passes an unset value through). Rejected: x :=? on an always-set x (E08095), a := head with no condition (E07340 - write if x := f() then x?) - **Examples** - **Construct**: if - **Code**: ```ek9 if name <- getName() stdout.println(name) ``` - **Construct**: for - **Code**: ```ek9 for config <- getConfig() then item in items process(config, item) ``` - **Construct**: while - **Code**: ```ek9 while conn <- getActiveConnection() then conn.hasMore() transferData(conn) ``` - **Documentation**: https://ek9.io/flowControl.html#guards ## Quality Metrics - **Description**: Compile-time enforced quality thresholds - **Metrics** - **Name**: Cyclomatic Complexity - **Abbreviation**: CC - **Good**: <=10 - **Monitor**: 11-15 - **Warning**: 16-25 - **Limit**: 45 - **Error Code**: E11010 - **Error URL**: https://ek9.io/errors.html#E11010 - **Name**: Cognitive Complexity - **Abbreviation**: Cog - **Good**: <=10 - **Monitor**: 11-15 - **Warning**: 16-25 - **Limit**: 35 - **Error Code**: E11021 - **Error URL**: https://ek9.io/errors.html#E11021 - **Name**: Nesting Depth - **Good**: <=2 - **Monitor**: 3 - **Warning**: 4-5 - **Limit**: 6 - **Error Code**: E11011 - **Error URL**: https://ek9.io/errors.html#E11011 - **Name**: Bracket Nesting - **Description**: '(', '[' and '{' together, checked as the source is read - before it is parsed - **Good**: <=3 - **Monitor**: 4-6 - **Warning**: 7-10 - **Limit**: 10 - **Error Code**: E11011 - **Error URL**: https://ek9.io/errors.html#E11011 - **Name**: Block Nesting - **Description**: Indentation levels from the module down, checked as the source is read - before it is parsed - **Good**: <=12 - **Monitor**: 13-16 - **Warning**: 17-20 - **Limit**: 20 - **Error Code**: E11011 - **Error URL**: https://ek9.io/errors.html#E11011 - **Name**: Operators per Expression - **Description**: One expression ends at a comma or at the end of its statement; checked before parsing. Coalescing chains are held to 3 (2 for ? <=? >=?) by expression complexity - **Good**: <=7 - **Monitor**: 8-14 - **Warning**: 15-20 - **Limit**: 20 - **Error Code**: E11013 - **Error URL**: https://ek9.io/errors.html#E11013 - **Name**: Member Accesses per Expression - **Description**: '.' in one expression, as in a.b().c(); checked before parsing. A module's dotted name is not counted - **Good**: <=3 - **Monitor**: 4-7 - **Warning**: 8-20 - **Limit**: 20 - **Error Code**: E11013 - **Error URL**: https://ek9.io/errors.html#E11013 - **Name**: Statement Count - **Good**: <=50 - **Monitor**: 51-100 - **Warning**: 101-150 - **Limit**: 150 - **Error Code**: E11012 - **Error URL**: https://ek9.io/errors.html#E11012 - **Name**: Cohesion (LCOM4) - **Good**: <=3 - **Monitor**: 4-5 - **Warning**: 6-8 - **Limit**: 8 - **Error Code**: E11014 - **Error URL**: https://ek9.io/errors.html#E11014 - **Name**: Coupling (Ce) - **Good**: <=5 - **Monitor**: 6-8 - **Warning**: 9-12 - **Limit**: 12 - **Error Code**: E11015 - **Error URL**: https://ek9.io/errors.html#E11015 - **Name**: Inheritance Depth - **Good**: <=2 - **Monitor**: 3 - **Warning**: 4 - **Limit**: 4 - **Error Code**: E11019 - **Error URL**: https://ek9.io/errors.html#E11019 - **Name**: Coverage - **Good**: >=80% - **Monitor**: 60-79% - **Warning**: <60% - **Limit**: 80% for publishing - **Documentation**: https://ek9.io/quality.html ## Banned Identifiers - **Description**: Variable names E11031 rejects (case-insensitive) - rendered at build time from the compiler's NonDescriptiveNames, the single source; never hand-edit this array. - **Banned** - **Name**: temp - **Defect Multiplier**: 3.1 - **Name**: tmp - **Defect Multiplier**: 3.1 - **Name**: flag - **Defect Multiplier**: 3.4 - **Name**: flg - **Defect Multiplier**: 3.4 - **Name**: data - **Defect Multiplier**: 2.8 - **Name**: dat - **Defect Multiplier**: 2.8 - **Name**: object - **Defect Multiplier**: 3.0 - **Name**: obj - **Defect Multiplier**: 3.0 - **Name**: value - **Defect Multiplier**: 2.9 - **Name**: val - **Defect Multiplier**: 2.9 - **Name**: buffer - **Defect Multiplier**: 2.7 - **Name**: buf - **Defect Multiplier**: 2.7 - **Name**: arg - **Also Bans**: arg followed by digits (arg0, arg1, ...) - **Reason**: names a position, not a role - **Exceptions** - Single-character names (x, y, i, j, T, K, V) are always allowed - **Error Code**: E11031 - **Documentation**: https://ek9.io/quality.html#naming-quality ## Code Quality Enforcement - **Description**: Compile-time code quality rules unique to EK9. These exist in NO other language — AI will violate them on every attempt unless explicitly told. Ordered by AI violation frequency. - **String Interpolation Mandate** - **Error Code**: E11068 - **Rule**: 3+ part string concatenation is a compile error - **Rationale**: Forces readable interpolation, prevents unbounded concatenation chains - **Example** - **Wrong**: ```ek9 result <- first + " " + last ``` - **Correct**: ```ek9 result <- `${first} ${last}` ``` - **Note**: 2-part concatenation (a + b) is allowed. 3+ parts must use backtick interpolation. - **Named Argument Enforcement** - **Rules** - **Error Code**: E11062 - **Rule**: 4+ positional arguments require named arguments - **Example** - **Wrong**: ```ek9 connect("db", 5432, "reports", 30) ``` - **Correct**: ```ek9 connect(host: "db", port: 5432, database: "reports", timeoutSeconds: 30) ``` - **Error Code**: E11061 - **Rule**: 2+ Boolean literal arguments require named arguments - **Example** - **Wrong**: ```ek9 configure(true, false) ``` - **Correct**: ```ek9 configure(verbose: true, dryRun: false) ``` - **Syntax**: paramName: value - **Note**: No other language mandates named arguments. AI training data contains zero examples of this constraint. - **Magic Literal Detection** - **Rules** - **Error Code**: E11064 - **Rule**: Bare numeric literal in comparison is a compile error - **Example** - **Wrong**: ```ek9 if retries > 3 timeout() ``` - **Correct**: ```ek9 maxRetries <- 3 if retries > maxRetries timeout() ``` - **Error Code**: E11065 - **Rule**: Same String literal 3+ times per file or 4+ per module requires a named value (numeric literals are not counted for repetition) - **Example** - **Wrong**: ```ek9 orderStatus <- "pending" paymentStatus <- "pending" shippingStatus <- "pending" ``` - **Correct**: ```ek9 pending <- "pending" orderStatus <- pending paymentStatus <- pending shippingStatus <- pending ``` - **Exempt Values** - 0 - 1 - -1 - 0.0 - 1.0 - **Exempt Types** - Boolean - Character - RegEx - Binary - **Note**: Extract numeric literals to named constants in 'defines constant' block. - **Operator Keyword Shadowing** - **Error Code**: E11032 - **Rule**: Variable names that shadow operator keywords are banned - **Banned** - empty - length - contains - abs - sqrt - close - matches - **Fix**: Use descriptive alternatives: foundItems, isAvailable, searchPattern, textLength - **Note**: Complements bannedIdentifiers (E11031) which covers non-descriptive names like temp, data, flag. - **Discarded Return Values** - **Rules** - **Error Code**: E11050 - **Rule**: Discarded operator return is a compile error - **Example**: ```ek9 price.+(tax) //ERROR: return value discarded ``` - **Error Code**: E11051 - **Rule**: Discarded pure method/function return is a compile error - **Example**: ```ek9 calculateTotal(items) //ERROR: return value discarded ``` - **Error Code**: E11052 - **Rule**: Discarded Result/Optional return silently swallows errors - **Example**: ```ek9 parseJSON(input) //ERROR: Result must be checked ``` - **Fix**: Always capture return values: total <- calculateTotal(items) - **Note**: Many languages allow ignoring return values. EK9 treats this as a bug. - **Tautological Expressions** - **Rules** - **Error Code**: E08080 - **Rule**: Self-assignment: x := x - **Error Code**: E08081 - **Rule**: Self-comparison: x == x - **Error Code**: E08082 - **Rule**: Constant comparison: 1 == 1 - **Error Code**: E08083 - **Rule**: Constant arithmetic: x - x, x / x - **Error Code**: E08084 - **Rule**: Redundant Boolean: b == true (use just b) - **Error Code**: E08085 - **Rule**: Logical tautology: a or not a - **Note**: AI copy-paste patterns frequently generate tautological expressions. - **Confusingly Similar Names** - **Error Code**: E11030 - **Rule**: Variables differing by 1-2 characters (Levenshtein distance) with same type are banned - **Examples** - data/dat - userId/usersId - count/counts - **Exemptions** - Different types - Constructor field shadowing - Loop counters - **Research**: 2.7x higher defect density (Butler et al., ICPC 2010) - **Class Design Anti Patterns** - **Rules** - **Error Code**: E11022 - **Rule**: Class with 4+ service fields and 0 data fields should be 'defines component' - **Error Code**: E11023 - **Rule**: 70%+ trait methods manually overridden — use 'by' delegation instead - **Error Code**: E11024 - **Rule**: 'by' delegation + 3+ services — split into class + component - **Error Code**: E11025 - **Rule**: 3+ services AND 3+ data fields — 'God Class', must decompose - **DI Injection Limits** - **Rules** - **Error Code**: E11040 - **Rule**: Max 4 injection fields per component - **Error Code**: E11041 - **Rule**: Similar dependencies should use a facade - **Error Code**: E11042 - **Rule**: Max 12 component registrations in a single application - **Error Code**: E11043 - **Rule**: Max 4 distinct injected types within a single method (fires at 5) - **Fix**: Split large components into focused sub-components with facades. - **Data Clump Detection** - **Error Code**: E11053 - **Rule**: 3+ callables sharing 4+ parameters with matching types/names must extract to a record - **Fix**: Create a record containing the repeated parameters and pass the record instead. - **Type Traversal Limit** - **Error Code**: E11054 - **Rule**: Max 3 type transitions in a method chain (Law of Demeter) - **Example** - **Wrong**: ```ek9 city <- order.getCustomer().getAddress().getRegion().getCity() ``` - **Correct**: ```ek9 city <- order.customerCity() ``` - **Fix**: Delegate through intermediate methods to limit coupling. - **Constant Organization** - **Rules** - **Error Code**: E11066 - **Rule**: Constants scattered across 3+ files in a module must be consolidated - **Error Code**: E11067 - **Rule**: 2+ files with 10+ total constants need a dedicated constants file - **Fix**: Use a single 'defines constant' block per module. - **Unused Capture Detection** - **Error Code**: E11018 - **Rule**: Dynamic function/class captures a variable but never uses it - **Fix**: Remove unused captures from the closure scope. - **Reference Ordering** - **Error Code**: E11026 - **Rule**: References (imports) must be sorted alphabetically - **Research**: 5-10% reduction in merge conflicts. ## Compilation - **Description**: EK9 compilation commands and flags - **Incremental** - **Compile**: ek9 -c main.ek9 - **Compile Debug**: ek9 -cg main.ek9 - **Compile Dev Debug**: ek9 -cd main.ek9 - **Full** - **Recompile**: ek9 -C main.ek9 - **Recompile Debug**: ek9 -Cg main.ek9 - **Recompile Dev Debug**: ek9 -Cd main.ek9 - **Clean All**: ek9 -Cl main.ek9 - **Run** - **Default**: ek9 main.ek9 - **Specific Program**: ek9 -r ProgramName main.ek9 - **Shebang**: ./main.ek9 - **Optimization** - **O0**: No optimization (forced with tests) - **O1**: Light optimization - **O2**: Standard optimization (incompatible with -t) - **O3**: Aggressive optimization (incompatible with -t) - **Error Levels** - **E0**: Minimal - single line - **E1**: Visual - Rust-style source display with caret markers - **E2**: Visual + 'Did you mean?' fuzzy matching suggestions - **E3**: Rich - full AI-oriented explanations with diagnosis, rationale, and examples - **E4**: Paradigm - concise summary of key design decisions (no break/continue/return, tri-state, closed types, automatic operators) - **E5**: Bootstrap - E4 paradigm summary plus complete forAI.json reference dump. Use once to learn EK9, then switch to E3 - **E6**: Local agent briefing - slim forAI-local.json operational reference for fine-tuned local LLMs in ek9 -ai. Contains role, Oracle, intents, protocol, terrain, and top reminders (~1900 tokens) - **Help** - **General Help**: ek9 -h - **Keyword Help**: ek9 -h - **Error Code Help**: ek9 -h Exxxx (e.g., ek9 -h E50001 — shows diagnosis, fix actions, examples for any error code) - **List All Keywords**: ek9 -H - **Question Answer**: ek9 -q (EK9-focused: answer + code example) - **Question By ID**: ek9 -q (fetch specific Q&A by ID, e.g., ek9 -q 45) - **Question Migration**: ek9 -qm (migration-focused: answer + language comparisons) - **List All Questions**: ek9 -q - **AI Note**: Use 'ek9 -q ' for EK9 syntax and examples. Use 'ek9 -q ' to follow cross-links (e.g., 'See Q45'). Use 'ek9 -qm ' when comparing EK9 to other languages. Use 'ek9 -h Exxxx' to diagnose compiler errors. - **DI Analysis** - **Compact**: ek9 -di main.ek9 (summary: registration counts, injection sites, health status) - **Rich**: ek9 -di -E3 main.ek9 (full wiring map, injection sites, aspect chains, structured guidance) - **AI Note**: Run 'ek9 -di -E3' BEFORE modifying DI wiring to understand current registrations, ordering, and injection field counts - **Target**: ek9 -T java main.ek9 (Java is only target currently) - **Verbose**: ek9 -v -c main.ek9 - **AI Recommendation**: Use -E5 once to bootstrap, then -E3 for ongoing development. Use 'ek9 -h Exxxx' to diagnose any error. Use 'ek9 -di -E3' before modifying DI wiring. Use 'ek9 -q ' for EK9 syntax/examples, 'ek9 -qm ' for language comparisons. - **Documentation**: https://ek9.io/commandline.html ## Testing - **Description**: EK9 testing commands, output formats, and coverage - **Commands** - **Run Tests**: ek9 -t main.ek9 - **List Tests**: ek9 -tL main.ek9 - **Run Group**: ek9 -tg groupname main.ek9 - **Show Coverage**: ek9 -tC main.ek9 - **Debug**: ek9 -d port main.ek9 - **Output Formats** - **T0**: Terse - for scripting and CI pipelines - **T1**: Human-readable (default with -t) - **T2**: JSON - programmatic analysis and AI tool integration - **T3**: JUnit XML - CI/CD integration - **T4**: Detailed coverage report - writes .ek9/coverage-detail.json with file:line for uncovered methods/branches - **T5**: Verbose coverage - lists BOTH covered AND uncovered items (for verification and debugging) - **T6**: Interactive HTML coverage report - writes .ek9/coverage/index.html with SVG charts, module breakdown, source views, dark/light theme, search, mobile-responsive - **Test Discovery**: Programs marked with @Test in /dev directory and subdirectories are auto-discovered - **Test Groups**: @Test: "groupname" syntax allows selective test execution with -tg - **Coverage** - **Threshold**: 80% - automatic detailed report to .ek9/coverage-detail.json when below - **View Results**: ek9 -tC main.ek9 - **Constraint**: -t cannot be combined with -O2 or -O3 optimization flags - **AI Recommendation**: Use -t2 for JSON output in automated workflows, -t for human-readable during development - **Documentation**: https://ek9.io/commandline.html#testing ## Packaging - **Description**: EK9 dependency management, versioning, packaging, and deployment - **Dependencies** - **Resolve**: ek9 -Dp main.ek9 - **Repository**: repo.ek9lang.org - **Local Cache**: $HOME/.ek9/lib - **Note**: Triggers clean before resolving; dependencies declared in package construct - **Versioning** - **Print**: ek9 -PV main.ek9 - **Increment**: ek9 -IV major|minor|patch|build main.ek9 - **Set**: ek9 -SV major.minor.patch main.ek9 - **Set Feature**: ek9 -SF major.minor.patch-feature main.ek9 - **Format**: major.minor.patch-build (e.g., 6.8.1-0) or major.minor.patch-feature-build (e.g., 6.8.0-specials-0) - **Package**: ek9 -P main.ek9 - **Install Local**: ek9 -I main.ek9 - **Deploy** - **Command**: ek9 -D main.ek9 - **Note**: Triggers -P and -Gk automatically - **Generate Keys**: ek9 -Gk - **Config File**: $HOME/.ek9/config - **Credentials File**: $HOME/.ek9/credentials - **AI Recommendation**: Use -IV patch for bug fixes, -IV minor for new features, -IV major for breaking changes - **Documentation**: https://ek9.io/commandline.html#packaging ## Project Structure - **Description**: EK9 project directory structure and conventions - **Layout** - **Main.ek9**: Main source file with package construct defining module, version, and dependencies - **.ek9/**: Generated build artifacts directory (auto-created by compiler) - **Dev/**: Development code — test programs (@Test), examples, templates. Not included in library packaging - **$HOME/.ek9/lib/**: Global dependency cache — resolved libraries stored here - **$HOME/.ek9/config**: Artifact server settings for deployment - **$HOME/.ek9/credentials**: Authentication details for artifact server - **Package Construct** - **Description**: The package construct in main source declares project metadata and dependencies - **Example**: ```ek9 defines package version <- 1.0.0-0 description <- "My EK9 library" deps <- { "org.example.lib": "2.0.0-0" } ``` - **Dev Directory** - **Description**: Programs in /dev are auto-discovered for testing - **Convention**: Test files use @Test annotation, groups use @Test: "groupname" - **Excluded**: Dev code is not included when the package is consumed as a library - **Documentation**: https://ek9.io/packaging.html ## Language Server - **Description**: EK9 Language Server Protocol (LSP) integration - **Start**: ek9 -ls main.ek9 - **Start With Hover**: ek9 -lsh main.ek9 - **Protocol**: stdin/stdout LSP protocol - **Vscode Extension**: Available for VSCode with syntax highlighting and real-time error reporting - **Hover Help**: -lsh enables hover documentation for operators and symbols - **Documentation**: https://ek9.io/commandline.html#languageserver ## REPL - **Description**: EK9 interactive Read-Eval-Print Loop - **Start**: ek9 -repl - **Start With Suggestions**: ek9 -repl -E2 - **Modes** - /function - /class - /record - /trait - /type - **Commands** - **Edit**: :edit — opens $VISUAL or $EDITOR for multi-line editing - **Edit Line**: :edit N — edit line N in buffer - **Delete**: :del N — delete line N - **Insert**: :ins N — insert before line N - **Undo**: :undo — undo last change - **Save**: :save — save session - **Load**: :load — load previous session - **Hover Help**: Ctrl+H shows symbol information in REPL - **Documentation**: https://ek9.io/commandline.html#repl ## Environment - **Description**: EK9 environment variables and runtime configuration - **Variables** - **EK9_COMPILER_MEMORY**: Compiler heap size (default: -Xmx512m), e.g., export EK9_COMPILER_MEMORY="-Xmx1024m" - **EK9_APPLICATION_MEMORY**: Application heap size (default: -Xmx512m), e.g., export EK9_APPLICATION_MEMORY="-Xmx2048m" - **EK9_TARGET**: Target architecture (default: java) - **EK9_PLAIN_TEXT**: Force plain text error output (no colors, no unicode) - **Stdin Stdout** - **Description**: EK9 programs integrate with Unix pipes and redirection - **Examples** - echo "Hello" | ek9 program.ek9 - ek9 filter.ek9 < input.txt > output.txt - cat data.txt | ek9 filter.ek9 | sort | uniq - **Classes**: Stdin(), Stdout(), Stderr() - **Set Env Flag**: ek9 -e name=value main.ek9 - **Documentation**: https://ek9.io/commandline.html ## Exit Codes - **Description**: EK9 process exit codes for CI/CD integration and scripting. TWO LAYERS: the 'ek9' native wrapper (normal usage) follows Unix convention — 0 means success. The underlying compiler jar (java -jar ek9c-jar-with-dependencies.jar) speaks an internal protocol the wrapper translates; callers invoking the jar DIRECTLY (AI agents, scripts) must read jarProtocol, NOT the wrapper view. - **Jar Protocol** - **0**: A run-command was printed to stdout — the caller must exec it; the program's own exit code then passes through untouched - **1**: SUCCESS with nothing to run (e.g. -c/-C compile-only, -V, -Gk) — silent by Unix convention; the wrapper maps this to 0. Direct-jar callers: exit 1 + no output = the operation WORKED - **2+**: Error conditions — identical to the wrapper view below - **14**: Internal compiler error. Listed separately because 1 is the JVM's default exit status for an uncaught exception in main, and 1 also means SUCCESS here — before this code existed, a crashed compile was indistinguishable from a clean one. - **Codes** - **0**: Success (wrapper view; jar: run-command printed — see jarProtocol) - **1**: Program's own exit code / wrapper exec failure (jar: success-nothing-to-run) - **2**: Invalid parameters - **3**: File access error - **4**: Invalid parameter combination - **5**: No programs found in source (also: the file is development source - under dev/ or a QA####_dev_ companion - which is never in the runnable artefact: stderr says is development source: its programs run under -t, not directly) - **6**: Multiple programs exist — use -r to specify - **7**: Language Server failed to start - **8**: Compilation failed - **9**: Wrong number of program arguments - **10**: Argument type conversion failed - **11**: Test execution failed - **12**: Tests pass but coverage below required threshold - **13**: MCP server failed to start - **14**: Internal compiler error — an uncaught Throwable (e.g. OutOfMemoryError). The compile did NOT complete; artefacts are absent or partial. Distinct from 8 (compilation failed: the compiler worked, your source did not) — 14 means the compiler itself failed. Raise EK9_COMPILER_MEMORY and retry. - **15**: -fuzz found one or more compiler crashes (crash files in ./fuzz-crashes/). Distinct from 11 (a unit test failed) so CI can gate a fuzz run on the exit code alone - **16**: DAP debug adapter failed to start (-dap) - **17**: A package dependency could not be resolved - not in ~/.ek9/lib (unpacked or zipped) and no remote repository yet, or the dependencies are circular or breach semantic versioning. The report names the dependency and version, the file:line declaring it and where it looked. Distinct from 2 (bad command line) - **Failure Marker**: When EK9 ITSELF fails (any code 2-17, including the generated entry point's 2/4/5/6 when it refuses its own command line - '-r' with no name, a program the jar does not have, none named among several - and 9/10 for arguments that do not fit the program), the FIRST stderr line is 'ek9: (exit N)', e.g. 'ek9: compilation failed (exit 8)'. A program's own exit (any code, 3/8/11 included) never carries it. Non-zero exit + first stderr line 'ek9: ... (exit N)' with the same N = EK9 failed; otherwise the code is the program's. - **Program Exit Status**: When the wrapper runs YOUR program, the process status is the program's: 0 on normal completion; the returned Integer if the program declares one (unset -> 1); for an Exception nothing catches, its own exitCode (e.g. Exception("Fatal", 3) exits 3), else 1; for a Panic, always 1. All after finally blocks and teardown have run. Only the exception that reaches the top counts: a wrapper without a code exits 1 even if its cause has one. Panic is raised only by the language (failed require, constrained-type breach, unset for-range bound): user code cannot create it (Panic(...) is E50060), throw it, catch it (E04030), or choose its code - to fail with a chosen code, throw an Exception carrying it and do not catch it. There is no exit() call. See ek9 -q 1431. - **Program Versus CLI Exit Codes**: A program's exit status shares its numbers with the ek9 command's own codes (3, 8, 11 ...), so the number alone cannot say which stage produced it. A compile failure prints EK9 error lines and the program never runs; when EK9 ITSELF fails its first stderr line is 'ek9: (exit N)' (a program's own exit never carries it), and an uncaught Exception/Panic prints 'Exception in thread "main" ...'. 'ek9 -C' first separates the stages. Under -t a failed assert fails its test and ek9 exits 11 (the test runner's code). See ek9 -q 1437. - **Ci Example**: ek9 -C main.ek9 && ek9 -t2 main.ek9 > test-results.json && ek9 -D main.ek9 - **Documentation**: https://ek9.io/commandline.html ## Development Workflow - **Description**: Complete EK9 development lifecycle from creation to deployment - **Steps** - **Phase**: create - **Description**: Create source file with package construct defining module, version, and dependencies - **Command**: touch myapp.ek9 - **Phase**: develop - **Description**: Iterative development with incremental compilation - **Command**: ek9 -c myapp.ek9 - **Phase**: diAnalysis - **Description**: Inspect DI wiring before modifying injection (only if using components/applications) - **Command**: ek9 -di -E3 myapp.ek9 - **Phase**: errorDiagnosis - **Description**: Look up any compiler error for full diagnosis and fix actions - **Command**: ek9 -h Exxxx - **Phase**: run - **Description**: Compile and execute the program - **Command**: ek9 myapp.ek9 - **Phase**: test - **Description**: Run all test programs in /dev directory - **Command**: ek9 -t myapp.ek9 - **Phase**: coverage - **Description**: Check test coverage meets 80% threshold - **Command**: ek9 -tC myapp.ek9 - **Phase**: resolveDeps - **Description**: Resolve dependencies from repo.ek9lang.org - **Command**: ek9 -Dp myapp.ek9 - **Phase**: version - **Description**: Increment version before release - **Command**: ek9 -IV minor myapp.ek9 - **Phase**: package - **Description**: Create deployment package - **Command**: ek9 -P myapp.ek9 - **Phase**: installLocal - **Description**: Install to local library for use by other projects - **Command**: ek9 -I myapp.ek9 - **Phase**: deploy - **Description**: Deploy to artifact server (generates keys if needed) - **Command**: ek9 -D myapp.ek9 - **Documentation**: https://ek9.io/commandline.html ## Stream Pipelines - **Description**: EK9's replacement for loops needing break/continue. NOTE: for/while/do-while loops still exist for iteration with mutation, side effects, and I/O - **Sources** - **Name**: cat - **Purpose**: Create stream from collection, iterator, or enumeration - **Name**: for - **Purpose**: Range-based source: for i in 1 ... 10 - **Intermediate Operations** - **Name**: filter - **Purpose**: Keep items matching predicate (opposite of reject) - **Name**: reject - **Purpose**: Remove items matching predicate (opposite of filter) - **Name**: map - **Purpose**: Transform each item to a different type - **Name**: sort - **Purpose**: Order items using comparison function - **Name**: group - **Purpose**: Group ADJACENT items with == keys into Lists (sort first; optionally: group by key); must see every item - **Name**: split - **Purpose**: Start a new List at each item the predicate marks as a boundary; must see every item - **Name**: join - **Purpose**: Fold all items into one with a two-argument function; must see every item - **Name**: uniq - **Purpose**: Keep the first of each == item anywhere in the stream - global, not consecutive (optionally: uniq by key) - **Name**: flatten - **Purpose**: Expand nested collections: List of List -> individual items - **Name**: head - **Purpose**: Take first N items and stop pulling, back to the nearest sort/tail/join (replaces break) - **Name**: tail - **Purpose**: Take last N items; must see every item - **Name**: skip - **Purpose**: Discard first N items (replaces continue with counter) - **Name**: tee - **Purpose**: Side-copy into variable, continue pipeline; lazy - records only what flows past it (a later head limits it) - **Name**: call - **Purpose**: Invoke function delegate on each item - **Name**: async - **Purpose**: Invoke function delegate asynchronously - **Terminals** - **Name**: > sink - **Purpose**: Direct output to collection or Stdout - **Name**: >> collection - **Purpose**: Append to existing collection - **Name**: collect as Type - **Purpose**: Materialize into new collection - **Replaces Imperative Patterns** - **Break**: head N (stop after N items) - **Continue**: filter by condition (skip non-matching) - **Loop With Set**: uniq (deduplicate without manual tracking) - **Nested Loops**: group by key | map with transform | flatten - **Examples** - **Description**: Find first match (replaces loop + break) - **Code**: ```ek9 result <- cat items | filter by isMatch | head 1 | collect as List of String ``` - **Description**: Skip invalid items (replaces continue) - **Code**: ```ek9 outputs <- cat items | filter by isValid | map with transform | collect as List of Output ``` - **Description**: Sort, group, and process - **Code**: ```ek9 cat library | sort by comparingAuthor | group by authorId | filter with sufficientBooks | map by orderOnPublishedDate | flatten > stdout ``` - **Description**: Collect all enum values - **Code**: ```ek9 allSuits <- cat CardSuit | collect as List of CardSuit ``` - **Documentation**: https://ek9.io/streamsAndPipelines.html ## Purity Rules - **Description**: EK9's pragmatic purity model - compile-time enforcement limiting internal state mutation - **Syntax**: functionName() as pure - **Clarification** - **Not Haskell Purity**: EK9 'pure' controls INTERNAL STATE MUTATION, not all side effects like Haskell - **Io Is Separate**: I/O is essential and handled via IO types - not what 'pure' restricts - **What Pure Prevents**: Unexpected internal state changes that cause bugs, race conditions, unpredictable behavior - **Why Println Works In Pure**: println doesn't mutate program state - it's an external I/O operation, a different concern - **What Pure Enforces** - **Can Only Call Pure Functions**: Pure functions/methods can only call other pure functions/methods (transitive) - **No Direct Reassignment**: Cannot use := or : to reassign variables - only :=? (assign if unset) allowed - **Cannot Modify Parameters**: Cannot reassign or mutate incoming parameters (even :=? not allowed) - **Cannot Mutate Fields**: Cannot modify instance fields/properties (except in constructors) - **Can Read Fields**: Can read fields and call pure methods on them - **Exceptions** - **Return Variable**: Return variable (<- rtn) can be assigned multiple times - **Unset Variables**: Variables that are UNSET can be assigned with :=? - **Constructors**: Constructors can initialize fields with :=? (they MUST initialize state) - **Loop Variables**: System-managed loop variables (for i in ...) are allowed - **Constructor Purity** - **Rule**: All constructors in a class must have CONSISTENT purity (all pure or all non-pure) - **Error Code**: E05190 - **Pattern**: Use :=? to initialize properties in pure constructors - **Error Codes** - **E08130**: NONE_PURE_CALL_IN_PURE_SCOPE - calling non-pure from pure context - **E08100**: NO_PURE_REASSIGNMENT - direct := assignment in pure context - **E05190**: MIX_OF_PURE_AND_NOT_PURE_CONSTRUCTORS - inconsistent constructor purity - **E05150**: SUPER_IS_PURE - non-pure function extending pure abstract - **E05160**: SUPER_IS_NOT_PURE - pure function extending non-pure abstract - **E08110**: NO_INCOMING_ARGUMENT_REASSIGNMENT - modifying incoming parameters - **Practical Guidance** - **When To Use Pure**: Mark pure if it doesn't mutate any field/property state - **Benefit**: Pure functions are deterministic, testable, and safe for concurrent execution - **Pattern**: Use more local variables (like LLVM SSA) instead of reassignment - **Subtlety** - **Creating Vs Calling**: Creating a non-pure function in pure context is OK - but CALLING it is NOT - **Example**: ```ek9 calc :=? () is Calculation as function (result: number * 2) // OK - creation is pure doubled <- calc(22) // ERROR E08130 - calling a non-pure function ``` - **Documentation**: https://ek9.io/basics.html#pure ## Sanitized Types - **Description**: EK9's compile-time security model for input validation - 'Rejection at the Source' - **Critical Rule**: The 'sanitized' modifier is ONLY valid on String parameters in function/method signatures - **Philosophy** - **Name**: Rejection at the Source - **Meaning**: Sanitize external inputs at system entry points ONLY - once inside trusted boundary, data is clean - **Benefit**: Security happens at compile-time through parameter declaration, not runtime checks scattered through code - **Parameter Semantics** - **Critical Insight**: Sanitized parameters are effectively PASS-BY-VALUE, not pass-by-reference - **Explanation**: EK9 normally uses pass-by-reference (changes to parameter affect original). For sanitized parameters, a NEW copy is created (if safe) or an UNSET value is returned (if threat detected). The original is NEVER exposed. - **Why No Aliasing**: Because we always create a copy, aliasing the parameter would expose a reference that doesn't represent the sanitization semantics. Direct assignment is blocked to highlight this pass-by-value behavior. - **Where Allowed** - **Allowed** - Function parameters - Method parameters - Operator parameters - Constructor parameters - Program parameters - Text method parameters - **Not Allowed** - Variables declarations - Return types - Capture variables - Call sites - Extern declaration parameters (E07944) - **Where Cleaned** - **Rule**: The function or method that DECLARES the sanitized parameter cleans it on entry, before any of its code runs - **Routes Covered** - Direct call - Call through a super type or trait - Operator and switch case - Stream stage, sink or tee - Dispatcher handler - Callback from built-in code (e.g. Optional.whenPresent) - Program command-line argument - **Caller**: Writes nothing - 'f(sanitized x)' is E07942 - **Safe Value**: The receiver's own copy - the caller's String is never shared - **Threat**: An unset String, plus one log line per matching detector - **Overrides** - **Rule**: sanitized is part of the contract, like pure: an override may ADD it but never DROP it - **Dropping**: E07940 - applies to class methods and trait implementations (against EVERY parent declaring the method: the super and each trait), functions that are 'is' a super function, dispatcher handlers (against the dispatcher entry point - a handler is reached before the entry point's body runs) and text locales - **Adding**: Allowed - the override cleans its own input on every route, including calls through the super type - **Extern Declarations** - **Rule**: sanitized is rejected on an extern parameter (E07944) - the compiler never generates an extern body, so it could not clean it - **Fix**: Clean the input in the external implementation (InputSanitizer in Java), or wrap the extern call in an EK9 function with a sanitized parameter - **Threat Types** - **Name**: SQL_INJECTION - **Severity**: 8 - **Description**: SQL injection patterns - **Name**: SQL_INJECTION_SIGNATURE - **Severity**: 8 - **Description**: libinjection-style SQL detection - **Name**: COMMAND_INJECTION - **Severity**: 9 - **Description**: Shell/command injection - **Name**: PATH_TRAVERSAL - **Severity**: 6 - **Description**: Directory traversal (../) - **Name**: XSS - **Severity**: 7 - **Description**: Cross-site scripting - **Name**: XXE - **Severity**: 8 - **Description**: XML External Entity - **Name**: SSTI - **Severity**: 7 - **Description**: Server-Side Template Injection - **Logging Formats** - **Description**: Controlled by EK9_SANITIZER_LOG_FORMAT environment variable - **Formats** - **Name**: JSON - **Default**: true - **Description**: Universal JSON format for any log shipper - **Name**: ECS - **Description**: Elastic Common Schema - for Elasticsearch, Splunk, Datadog - **Name**: CEF - **Description**: Common Event Format - for ArcSight, Azure Sentinel, QRadar - **Name**: SIMPLE - **Description**: Simple bracket format for testing/scripts - **Name**: SILENT - **Description**: Suppresses all output - for testing - **Input Sanitizer Class** - **Description**: Programmatic access to sanitization for manual validation - **Methods** - **Name**: sanitize(input) - **Returns**: Safe input or UNSET if threat - **Name**: isSafe(input) - **Returns**: Boolean true/false/unset - **Name**: detectThreat(input) - **Returns**: Threat type string or UNSET if safe - **Name**: sanitizeWithoutPathChecks(input) - **Returns**: For environment variables with legitimate paths - **Customization**: Cannot be directly modified. Companies must extend InputSanitizer and call methods manually. - **Examples** - **Correct** - **Function Parameter**: ```ek9 processInput() -> input as sanitized String <- rtn as String: "rejected" if input? rtn: `accepted: ${input}` // cleaned on entry - an attack arrives unset ``` - **Manual Sanitization**: ```ek9 sanitizer <- InputSanitizer() if clean <- sanitizer.sanitize(userInput) process(clean) ``` - **Wrong** - **Direct Assignment**: local <- sanitizedParam // ERROR: Cannot alias sanitized parameter - **Variable Declaration**: name <- sanitized getValue() // ERROR E07943: Not on variables - **Capture Variable**: made: (prefix: sanitized rawInput) is Formatter as pure function // ERROR E07941: Not in captures - **Call Site**: process(sanitized value) // ERROR E07942: Not at call site - **Override Drop**: ```ek9 override handle() -> input as String // ERROR E07940: the parent declares 'input as sanitized String' ``` - **Extern Parameter**: ```ek9 defines extern module lib defines function store() -> input as sanitized String // ERROR E07944: an extern body is never generated <- rtn as String? ``` - **Error Codes** - **E07910**: SANITIZED_PARAMETER_TYPE_INVALID - sanitized on a non-String parameter - **E07920**: SANITIZED_ONLY_ON_PARAMETERS - sanitized outside an incoming parameter - **E07930**: CANNOT_ASSIGN_FROM_SANITIZED - direct assignment from sanitized parameter (aliasing) - **E07940**: SANITIZED_MODIFIER_CANNOT_BE_DROPPED - an override dropped sanitized (adding it is allowed) - **E07941**: SANITIZED_NOT_ALLOWED_IN_CAPTURE - sanitized on captured variable - **E07942**: SANITIZED_NOT_ALLOWED_AT_CALL_SITE - sanitized used at a call site - **E07943**: SANITIZED_NOT_ALLOWED_IN_VARIABLE_DECLARATION - sanitized on a variable declaration - **E07944**: SANITIZED_NOT_ALLOWED_ON_EXTERN - sanitized on a parameter of an extern declaration - **Documentation**: https://ek9.io/sanitized.html ## Method Resolution - **Description**: EK9's cost-based method matching algorithm for overloaded methods - **Critical Rule**: Methods are overloadable, functions are NOT - function names must be unique per module - **Cost Based Matching** - **Description**: Lower cost = better match. Percentage = 100.0 - totalCost - **Costs** - **Name**: ZERO_COST - **Value**: 0.0 - **Meaning**: Exact type match - perfect - **Name**: SUPER_COST - **Value**: 0.05 - **Meaning**: Match via superclass (per level) - **Name**: TRAIT_COST - **Value**: 0.1 - **Meaning**: Match via trait (less specific than superclass) - **Name**: COERCION_COST - **Value**: 0.5 - **Meaning**: Type promotion via #^ operator required - **Name**: HIGH_COST - **Value**: 20.0 - **Meaning**: Match to 'Any' type - last resort only - **Name**: INVALID_COST - **Value**: -1.0 - **Meaning**: No match possible - **Why Trait Costs More Than Super**: A class can implement many traits but only extends one superclass - superclass relationship is more specific - **Ambiguity Detection** - **Description**: Two methods are ambiguous if their percentage match is within 0.001 tolerance - **Why Small Tolerance**: Cost calculations use multipliers for multiple parameters, creating finer granularity - **Error Code**: E06140 - **Example** - **Methods**: methodA(C1, C2) and methodA(C2, C1) - **Call**: methodA(c2, c3) where c3 extends C2 extends C1 - **Cost A**: c2→C1 (0.05) + c3→C2 (0.05) = 0.10 - **Cost B**: c2→C2 (0.0) + c3→C1 (0.10) = 0.10 - **Result**: AMBIGUOUS - both have identical cost - **Any Type Handling** - **Description**: 'Any' is the universal base type - root of EK9's type hierarchy - **Cost**: HIGH_COST (20.0) - deliberately expensive to ensure specific types always preferred - **Pattern**: Many built-in types have both specific and Any overloads - **Example**: ```ek9 _eq(List other) costs 0.0, _eq(Any other) costs 20.0 - List version always wins ``` - **Return Type Matching** - **Description**: Return type compatibility is pass/fail only - no cost impact - **Rule**: If return type specified in search and incompatible, method is rejected (not cost-adjusted) - **No Automatic Promotions** - **Critical Rule**: EK9 has NO hidden/automatic type promotions - **Explanation**: Everything is an object. Integer has explicit #^ promotion operator to Float - **Benefit**: No surprises - all type conversions are visible in the code - **Function Resolution** - **Description**: Functions CANNOT be overloaded - names must be unique per module - **Error If Duplicate**: E02010 (DUPLICATE_NAME) - **Resolution**: Simple parameter signature validation - no cost-based matching needed - **Error Codes** - **E06140**: METHOD_AMBIGUOUS - multiple methods match with same cost - **E50060**: METHOD_NOT_RESOLVED - no matching method found - **E06270**: FUNCTION_PARAMETER_MISMATCH - function called with wrong parameters - **Documentation**: https://ek9.io/methods.html ## Operator Semantics - **Description**: EK9 operator behavior - mutating vs non-mutating - **Critical Warning**: Mutating operators change the object in place and return nothing (++ and -- are statements, E07950) - so b <- a makes an ALIAS, not a copy, and a mutation through either name is seen through both - **Mutating Operators** - **Description**: These operators MUTATE the target object in place; they return nothing, so they are statements - **Operators** - **Symbol**: ++ - **Method**: _inc - **Note**: x++ mutates x in place - a statement, never part of an expression (y <- x++ is E07950) - **Symbol**: -- - **Method**: _dec - **Note**: x-- mutates x in place - a statement - **Symbol**: += - **Method**: _addAss - **Note**: Mutates in place - **Symbol**: -= - **Method**: _subAss - **Note**: Mutates in place - **Symbol**: *= - **Method**: _mulAss - **Note**: Mutates in place - **Symbol**: /= - **Method**: _divAss - **Note**: Mutates in place - **Symbol**: :=: - **Method**: _copy - **Note**: Copy INTO the target's own object, each type at its own level: a value copies its value, a List copies its element references; the generated default :=: is SHALLOW (each field refers to the source's object); a deep or selective copy is a hand-written operator :=: (ek9 -q deep or selective copy) - **Symbol**: :^: - **Method**: _replace - **Note**: Replace content - **Symbol**: :~: - **Method**: _merge - **Note**: Merge: never overwrites a set value; fills whatever is unset or missing, element by element, recursing into compatible nested structures; Lists append, Sets take the union - **Aliasing Danger** - **Example**: ```ek9 x <- Integer(5) y <- x // y and x are NOW THE SAME OBJECT! x++ // mutates that one object in place // x = 6, y = 6 (both reference the same Integer); y <- Integer(x) makes an independent copy ``` - **Warning**: This is DIFFERENT from C/C++/Java, where y = x copies a primitive value - **Bindings And Objects** - **Rule**: b <- a makes b refer to the SAME object as a (b++ moves a). For an independent value construct one: b <- Integer(a) - **Parameters**: An incoming parameter's BINDING cannot be reassigned (E08110), but it is NOT read-only: in non-pure code the object can be mutated in place (p += 1, list.add(x)) and the caller sees it, and it can be aliased and the alias reassigned - **Read Only**: Read-only is what pure provides: a pure body cannot mutate or reassign (E08100/E08120) and may call only pure code (E08130), so the guarantee carries through everything it calls - **Non Mutating Operators** - **Description**: These operators return NEW objects - safe for functional programming - **Operators** - **Symbol**: + - **Method**: _add - **Symbol**: - - **Method**: _sub - **Symbol**: * - **Method**: _mul - **Symbol**: / - **Method**: _div - **Symbol**: - (unary) - **Method**: _negate - **Symbol**: abs - **Method**: _abs - **Symbol**: == - **Method**: _eq - **Symbol**: <> - **Method**: _neq - **Symbol**: < - **Method**: _lt - **Symbol**: > - **Method**: _gt - **Symbol**: <= - **Method**: _le - **Symbol**: >= - **Method**: _ge - **Symbol**: <=> - **Method**: _cmp - **Documentation**: https://ek9.io/operators.html ## Operator Precedence - **Description**: The convention of C, Java and Python (Steve, 2026-10-07). Tightest binding first; within a level operators associate left unless marked right. - **Levels** - postfix ++ -- ! ?, the prefixes $ $$ #^ #< #> #? length abs sqrt ~, and 'is empty' - ^ (right-associative: 2 ^ 3 ^ 2 is 2 ^ (3 ^ 2) = 512) - unary - (below ^: -x ^ 2 is -(x ^ 2), and a literal is no exception: -2 ^ 2 is -(2 ^ 2) = -4; write (-2) ^ 2 to square -2. Every postfix binds above it: -x is empty is -(x is empty)) - ?: ?? ? >=? (one coalescing level, right-associative; a mix with another operator, ?? included, needs parentheses, E08269) - * / mod rem - + - - << >> - <=> <~> - < <= > >= matches contains in, and 'in a ... b' (name in names == expected compares the membership) - == <> != - not (below the comparisons: not age == limit is not (age == limit), not name in names is not (name in names); it is never an operand of another operator unless parenthesised, except and/xor/or: a == (not b), never a == not b, and $ (not b), never $ not b (E08273); a and not b needs none; a negation is never applied to a negation - not, ~ and unary - are one operator, parenthesised or not: not not b, not (not b), ~~b, ~not b, - -x, -(-x), -not b are E08274 - write b (x), or one negation; -(a - b) and a - -b are legal) - and - xor - or (a or b and c is a or (b and c)) - the ternary <- ... else (right-associative) - **Documentation**: https://ek9.io/operators.html#precedence ## Coalescing Operators - **Description**: Operators that gracefully handle tri-state (absent/unset/set). Each of ??, ?:, ?, >=? (and the ternary) returns ONE of its operands, so the operands must share a type: the same type or a subtype, one promoting (#^) to the other, or their nearest common super/trait - never an invented Any (E50030 at the operator). ??, ?: and the ? >=? - **Name**: Comparison Coalescing - **Meaning**: Return one of the OPERANDS (never a Boolean): the set one when only one is set; the lesser (? >=?) when both are set; unset when neither is. Both operands are always evaluated. Needs <=> on the type (E07620) - **Example**: ```ek9 lowest <- first 0 - require firstName? and lastName? - require not true - **Semantics**: When require fails, it indicates a serious unrecoverable defect. There is NO recovery — execution terminates. By design, require failures cannot drive control flow. - **Use Cases** - Constructor argument validation: require id? - Method preconditions: require amount > 0 - Invariant checking: require balance >= 0 - Defensive default constructor blocking: require not true - **Assert** - **Purpose**: Test assertions in @Test programs — throws exception if false - **Syntax**: assert expression - **Note**: Use in @Test programs for test validation. Similar to require but specifically for test assertions. - **Documentation**: https://ek9.io/testing.html#require ## Syntax - **Indentation**: 2 spaces (not tabs) - **No Semicolons**: true - **No Curly Braces**: true - **Declaration**: name <- value - **Typed Declaration**: name as Type: value - **Assignment**: name: value - **Assign If Unset**: name :=? value - **Generic Construction**: names <- List() of String - the parentheses come before the type arguments; where the target's type is declared a bare construction is completed from it: names as List of String: List() - **Comments** - **Single Line**: // comment - **Multi Line**: ## Common AI Mistakes - **Description**: Frequent mistakes AI assistants make when generating EK9 code - **Syntax Mistakes** - **Parentheses After Type Arguments** - **Description**: Writing a generic's '()' after its type arguments - **Error Code**: E06210 - **Severity**: COMPILE_ERROR - **Wrong**: ```ek9 names <- List of String() ``` - **Correct**: ```ek9 names <- List() of String ``` - **Rule**: The parentheses belong to the generic's name, before 'of': 'G() of T'. With a declared type, 'names as List of String: List()' needs no type arguments at all; '<-' declares no type, so it always needs them. - **Multiple Arrows** - **Description**: Using multiple -> for multiple parameters - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 myFunc() -> name as String -> count as Integer ``` - **Correct**: ```ek9 myFunc() -> name as String count as Integer ``` - **Rule**: Single -> on its own line, then each parameter indented below - **Curly Braces For Blocks** - **Description**: Using {} for code blocks like C/Java - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 if condition { doSomething() } ``` - **Correct**: ```ek9 if condition doSomething() ``` - **Rule**: Curly braces {} are ONLY for Dict literals: {1: "one", 2: "two"} - **Unclosed Backtick String** - **Description**: Opening a String with a backtick and closing it with a double quote - **Error Code**: E01093 - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 rtn: `${count} items" ``` - **Correct**: ```ek9 rtn: `${count} items` ``` - **Rule**: Close a String with the delimiter that opened it. A backtick String never ends at a '"', so it runs to the end of the file - E01093 is reported at the backtick that opened it. - **Square Brackets As Arrays** - **Description**: Thinking [] creates arrays - **Severity**: MISCONCEPTION - **Correct**: ```ek9 numbers <- [1, 2, 3] //a List of Integer, NOT an array ``` - **Rule**: EK9 has no arrays - [] is shorthand for List literals - **Python Colon After Statements** - **Description**: Adding : after if/for/while like Python - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 if condition: ``` - **Correct**: ```ek9 if condition ``` - **Rule**: No colon after control flow statements - just newline and indent - **Lambda Syntax** - **Description**: Using lambda/arrow function syntax from other languages - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 fn = (x) => x * 2 ``` - **Correct**: ```ek9 doubler <- () is Transformer as function rtn: number * 2 ``` - **Rule**: EK9 uses 'dynamic functions': (captures) is FunctionType as function - **C Style For Loop** - **Description**: Using C-style for(i=0; i<10; i++) - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 for (i = 0; i < 10; i++) ``` - **Correct**: ```ek9 for i in 0 ... 9 ``` - **Rule**: Use 'for variable in range' or 'for item in collection' - **Enum Values On One Line** - **Description**: Enum values go one per line — several values on one line, even comma-separated, do not parse - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 CardSuit Hearts, Diamonds, Clubs, Spades ``` - **Correct**: ```ek9 CardSuit Hearts Diamonds Clubs Spades ``` - **Rule**: Each enum value on its own line at one indent level deeper than the type name (a trailing comma after a value is allowed: 'Hearts,'), never several values on one line - **Cat With Range** - **Description**: Using 'cat' with a numeric range — 'cat' takes collections or types, not ranges - **Severity**: PARSE_FAILURE - **Wrong**: ```ek9 cat 1 ... 10 | map by transform | collect as List of Integer ``` - **Correct**: ```ek9 squares <- for i in 1 ... 10 | map with transform | collect as List of Integer ``` - **Rule**: 'cat' streams from collections (cat myList) or enumeration types (cat Colour). Use 'for i in start ... end' to generate ranges in stream pipelines. - **Aggregate Construction Rules** - **Description**: Mandatory rules for declaring records / classes / components — most AI-generated test fixtures violate one of these on the first try. Each rule is enforced by the EK9 compiler with a specific error code. - **Field Initialisation Required** - **Description**: Every property/field on a record / class / component must be initialised at declaration OR through a developer-coded constructor - **Error Codes** - E08070 - E07170 - E08180 - **Rule**: Fields of form 'name as Type' must have an initialiser: 'name as Type: defaultExpression'. The `?` marker syntax 'name as Type?' indicates 'allowed unset', NOT 'no default needed' — it still requires the field to be set via constructor, and the no-argument constructor must then be made private: 'default private Point()' (E07175). - **Wrong**: ```ek9 defines record Point x as Float y as Float ``` - **Correct**: ```ek9 defines record Point x as Float: 0.0 y as Float: 0.0 default operator ``` - **Alternative Correct**: ```ek9 defines record Point x as Float? y as Float? default private Point() Point() -> xArg as Float yArg as Float this.x: xArg this.y: yArg default operator ``` - **Is Set Operator Required** - **Description**: Aggregates (record / class / component) with declared fields MUST provide the '?' (isSet) operator — required by EK9's tri-state semantics. Applies equally to records AND classes — it is NOT a record-only rule. - **Error Codes** - E07200 - E07235 - **Rule**: Either use 'default operator ?' (compiler synthesises from field-? AND of all fields) or 'override operator ? as pure' with custom body. Records can additionally use bare 'default operator' which synthesises ? plus every other defaultable operator at once. Classes can mix 'default operator ?' with custom operators in any order subject to the member-ordering rule. - **Wrong**: ```ek9 defines class Greeter name as String: "hi" greet() as pure <- result as String: name ``` - **Correct**: ```ek9 defines class Greeter name as String: "hi" greet() as pure <- result as String: name default operator ? ``` - **Concrete Record Needs A Field** - **Description**: A record exists only to carry data (it cannot have methods, E07290), so a CONCRETE record with no fields, counting inherited ones, is a compile error. An abstract record may have none: it is the root of a record hierarchy. A sub-record that inherits fields needs none of its own. - **Error Code**: E07237 - **Rule**: Give the record the field(s) it carries; use a class or component if it carries behaviour (a class or component with no fields is set by being present); mark it 'as abstract' if it is the polymorphic root; use an enumeration for a marker of a fixed set of values. - **Wrong**: ```ek9 defines record Reading default operator ``` - **Correct**: ```ek9 defines record Reading celsius <- 21.5 default operator ``` - **Abstract Operator Only Without Fields** - **Description**: A sub-type's 'default operator $' / '$$' / '#?' below an ABSTRACT super operator covers only the sub-type's own fields, so an abstract '$', '$$' or '#?' on a type that HAS fields (counting inherited ones) is a compile error at the abstract operator - the fields would silently be left out. A field-less abstract root, or a trait, may keep the operator abstract. - **Error Code**: E07185 - **Rule**: Where the data is, give '$' / '$$' / '#?' a body or 'default operator X' (a hand-written body when the fields are not to be exposed); keep the operator abstract only on a field-less root or a trait. - **Wrong**: ```ek9 defines class Shape as abstract name as String: String() operator $ as pure abstract <- rtn as String? default operator ? ``` - **Correct**: ```ek9 defines class Shape as abstract name as String: String() default operator $ default operator ? ``` - **Sub Type Adding Fields Declares Its Own Operators** - **Description**: A type that ADDS fields must declare its own '$', '$$' and ':=:' wherever it would otherwise inherit one from a type above it (as E07235 requires of '?'): the inherited operator was written for the super's fields and cannot know the new ones. A 'default operator' calls the NEAREST implementation above it, so a field-less type in the middle of a hierarchy needs no operators of its own. Not for '#?', '==' or '<=>': sameness is declared once, at the root. - **Error Code**: E07238 - **Rule**: On every type that adds fields below a type with '$', '$$' or ':=:', add 'default operator $' (and '$$', ':=:') - or the bare 'default operator' - or write the operator by hand. - **Wrong**: ```ek9 defines class Base as open id as Integer: 1 default operator ? default operator $ Leaf extends Base name as String: "leaf" default operator ? ``` - **Correct**: ```ek9 defines class Base as open id as Integer: 1 default operator ? default operator $ Leaf extends Base name as String: "leaf" default operator ? default operator $ ``` - **Member Ordering Fields Methods Operators** - **Description**: EK9 enforces a strict body-member ordering inside classes / records / components: ALL fields first, THEN methods, THEN operators. Mixing the three categories or putting methods before fields is a parse-level error. - **Error Code**: E01086 - **Rule**: Body order: fields → methods → operators. Within each band the order is flexible; between bands the rule is absolute. - **Wrong**: ```ek9 defines class Greeter default operator ? name as String: "hi" greet() as pure <- result as String: name ``` - **Correct**: ```ek9 defines class Greeter name as String: "hi" greet() as pure <- result as String: name default operator ? ``` - **Minimal Compiling Aggregate Template** - **Description**: The smallest aggregate fixture that compiles cleanly under the EK9 quality gate. Use as the starting template when writing test fixtures or example code. - **Record**: ```ek9 #!ek9 defines module com.example.geo defines record Point x as Float: 0.0 y as Float: 0.0 default operator //EOF ``` - **Class**: ```ek9 #!ek9 defines module com.example.greet defines class Greeter greeting as String: "Hello" greet() as pure <- result as String: greeting default operator ? //EOF ``` - **Note**: Records can use bare 'default operator' to get all defaults at once; classes typically need at least 'default operator ?'. Both must satisfy fieldInitialisationRequired and memberOrderingFieldsMethodsOperators. - **Semantic Mistakes** - **Using Excluded Keywords** - **Description**: Using break, continue, return, null - **Error Codes** - E01070 - E01071 - E01072 - E01073 - **Rule**: These keywords do not exist - see $.excludedFeatures - **Instanceof Or Casting** - **Description**: Trying to check or cast types at runtime - **Rule**: No instanceof, no casting - use dispatcher pattern only - **Automatic Type Promotion** - **Description**: Assuming Integer converts to Float automatically - **Rule**: All promotions explicit via #^ operator: intVal#^ - **Overloading Functions** - **Description**: Creating multiple functions with same name - **Error Code**: E01040 - **Rule**: Functions cannot be overloaded - only methods can - **Extending Closed Types** - **Description**: Extending List, Dict, Optional, etc. - **Error Code**: E05030 - **Rule**: Built-in generic types are closed - use composition - **Reassigning In Pure** - **Description**: Using := in pure functions - **Error Code**: E08100 - **Rule**: Only :=? (assign if unset) allowed in pure contexts - **Aliasing Sanitized** - **Description**: Direct assignment from sanitized parameter - **Error Code**: E07930 - **Rule**: Cannot alias sanitized params - pass-by-value semantics - **Increment In Expression** - **Description**: Using x++ in expressions like y <- x++ - **Error Code**: E07950 - **Rule**: ++/-- are statement-only, not expression operators - **Manual JSON Serialization** - **Description**: Writing custom JSON conversion instead of using default operator $$ - **Rule**: Use 'default operator $$' on records/classes for auto-generated JSON. List of T also has $$ operator. - **Example** - **Wrong**: ```ek9 toJSON() -> c as Customer <- rtn as String: `{ "name": "${c.name}" }` ``` - **Correct**: ```ek9 Customer name as String: String() default operator ? default operator $$ //Usage: json <- $$customer ``` - **Manual String Serialization** - **Description**: Writing custom String conversion instead of using default operator $ - **Rule**: Use 'default operator $' for auto-generated string representation - **Example** - **Wrong**: ```ek9 toString() -> c as Customer <- rtn as String: `${c.name} ${c.surname}` ``` - **Correct**: ```ek9 Customer name as String: String() surname as String: String() default operator ? default operator $ //Usage: str <- $customer ``` - **Unused Trait Definition** - **Description**: Defining a trait but implementing its operations as standalone functions instead of a class - **Rule**: If you define a trait, always implement it with 'class X with trait of TraitName'. Never define a trait and then ignore it. - **Severity**: DESIGN_FLAW - **Unused Parameter** - **Description**: Declaring a parameter that is never referenced in the function/method body - **Error Code**: E08091 - **Rule**: Every declared parameter must be used. Remove unused parameters or use them in the body. - **Exemptions** - Abstract methods/functions (no body) - Override methods (signature forced by parent) - Dispatcher methods (must accept dispatched type) - Operators (fixed semantics) - Functions explicitly extending another function - Methods on parameterised types (signature forced by generic contract) - **Severity**: COMPILE_ERROR - **Operator Question Must Be Pure** - **Description**: operator ? must ALWAYS be declared 'as pure' — it is a query, never mutates - **Error Code**: E07500 - **Wrong**: ```ek9 override operator ? <- rtn as Boolean: name? ``` - **Correct**: ```ek9 override operator ? as pure <- rtn as Boolean: name? ``` - **Rule**: operator ? checks if a value is set — this is inherently pure. The compiler requires 'as pure' on every ? operator declaration. - **Severity**: COMPILE_ERROR - **Enum Switch Exhaustiveness And Default** - **Description**: Switch expression on enum requires BOTH all enum cases AND a default clause - **Error Codes** - E07310 - E07330 - **Wrong**: ```ek9 label <- switch status <- rtn as String? case Status.Active rtn: "active" default rtn: "other" ``` - **Correct**: ```ek9 label <- switch status <- rtn as String? case Status.Active rtn: "active" case Status.Inactive rtn: "inactive" default rtn: "unknown" ``` - **Rule**: When switching on an enumeration as an EXPRESSION (<- rtn), list ALL enum values explicitly AND include a default. E07310 fires if cases are missing, E07330 fires if default is missing. - **Severity**: COMPILE_ERROR - **Copy Constructor In Pure Context** - **Description**: Calling a copy constructor like TypeName(existingVar) inside a pure method when the constructor is not pure - **Error Code**: E08130 - **Wrong**: ```ek9 status() as pure <- rtn as ConnectionStatus: ConnectionStatus(currentStatus) ``` - **Correct**: ```ek9 status() <- rtn as ConnectionStatus: ConnectionStatus(currentStatus) // OR if method must be pure, return the field directly if semantics allow it ``` - **Rule**: Copy constructors may not be marked pure. If you need to return a copy from a pure method, either remove 'as pure' from the method, or find a pure alternative. Use 'ek9 -h ' to check if the copy constructor is pure. - **Severity**: COMPILE_ERROR - **Positional Arguments Must Be Named** - **Description**: Passing 4+ arguments positionally, or two or more literal true/false, without naming them - **Error Code**: E11062 - **Wrong**: ```ek9 line <- OrderLine(9.99, 2, 0.1, 0.2) configure(true, false) ``` - **Correct**: ```ek9 line <- OrderLine(price: 9.99, quantity: 2, discount: 0.1, taxRate: 0.2) configure(verbose: true, dryRun: false) ``` - **Rule**: E11062 at 4+ arguments; E11061 at 2+ LITERAL true/false whatever the arity, so two arguments is enough — a Boolean-typed VARIABLE is not a literal and does not count. E06240: all named or none. E06250: each name must be the DECLARED parameter name, in declaration order — read the signature ('ek9 -h ', or the Oracle) rather than invent a label. - **Severity**: COMPILE_ERROR - **Invented Member Or Type Names** - **Description**: Calling a method, or naming a type or function, that was never read — guessed from another language - **Error Code**: E50060 - **Wrong**: ```ek9 total <- order.getTotl() list.add(item) ``` - **Correct**: ```ek9 total <- order.getTotal() list += item ``` - **Rule**: E50001 'not resolved', E50010 'type not resolved' and E50060 'method/function not resolved' are the commonest failures of all. Read the API first ('ek9 -h ', or the Oracle's effective-API enquiry). When it does happen the diagnostic names the nearest real candidates — at -E2 in the message ('did you mean ...?'), and as a 'suggestions' list on a tool refusal — so resend with one of those rather than guess a second time. - **Severity**: COMPILE_ERROR - **Double Quoted Interpolation** - **Description**: Writing "${count} items" - a double-quoted String never interpolates (double quotes DO in Kotlin, Groovy, Dart, PHP and the shells) - **Error Code**: E07901 - **Wrong**: ```ek9 stdout.println("${count} items") ``` - **Correct**: ```ek9 stdout.println(`${count} items`) ``` - **Rule**: Only a backtick String interpolates ${...}. A double-quoted String holding ${ is a compile error, and the error spells out both rewrites. For the literal characters write \${ in a backtick String - `\${count} items` - because \$ is not an escape in a double-quoted String. In a backtick String a $ that opens no interpolation is escaped the same way: `cost \$5 for ${count}`. - **Severity**: COMPILE_ERROR - **Assignment Confusion** - **Description**: EK9 has multiple assignment forms - **Forms** - **Syntax**: name <- value - **Meaning**: Declaration with type INFERENCE (most common) - **Syntax**: name as Type: value - **Meaning**: Declaration with EXPLICIT type, assign with : - **Syntax**: name as Type := value - **Meaning**: Declaration with EXPLICIT type, assign with := - **Syntax**: name: value - **Meaning**: REASSIGNMENT to existing variable - **Syntax**: name := value - **Meaning**: REASSIGNMENT to existing variable - **Syntax**: name :=? value - **Meaning**: Assign ONLY if currently unset - **Key Point**: <- is NOT the only way to declare - it's just the type-inference form ## Error Code Index - **Description**: Lookup from error code to forAI.json path — AI assistants use this to find structured guidance for any compiler error - **E01070**: excludedFeatures.break - **E01071**: excludedFeatures.continue - **E01072**: excludedFeatures.return - **E01073**: excludedFeatures.null - **E01074**: excludedFeatures.semicolon - **E01075**: excludedFeatures.new - **E01076**: excludedFeatures.goto - **E01077**: excludedFeatures.def - **E01078**: excludedFeatures.elif - **E01079**: excludedFeatures.None - **E01080**: excludedFeatures.self - **E01081**: excludedFeatures.listComprehension - **E01082**: excludedFeatures.multipleArgumentArrows - **E01083**: excludedFeatures.multipleReturnArrows - **E01094**: excludedFeatures.close - **E05030**: closedByDefault - **E11010**: qualityMetrics.cyclomaticComplexity - **E11021**: qualityMetrics.cognitiveComplexity - **E11011**: qualityMetrics.nestingDepth - **E11012**: qualityMetrics.statementCount - **E11013**: qualityMetrics.operatorsPerExpression - **E11014**: qualityMetrics.cohesion - **E11015**: qualityMetrics.coupling - **E11019**: qualityMetrics.inheritanceDepth - **E11031**: bannedIdentifiers - **E08070**: commonAIMistakes.aggregateConstructionRules.fieldInitialisationRequired - **E07170**: commonAIMistakes.aggregateConstructionRules.fieldInitialisationRequired - **E08180**: commonAIMistakes.aggregateConstructionRules.fieldInitialisationRequired - **E07200**: commonAIMistakes.aggregateConstructionRules.isSetOperatorRequired - **E07235**: commonAIMistakes.aggregateConstructionRules.isSetOperatorRequired - **E07237**: commonAIMistakes.aggregateConstructionRules.concreteRecordNeedsAField - **E07185**: commonAIMistakes.aggregateConstructionRules.abstractOperatorOnlyWithoutFields - **E07238**: commonAIMistakes.aggregateConstructionRules.subTypeAddingFieldsDeclaresItsOwnOperators - **E01086**: commonAIMistakes.aggregateConstructionRules.memberOrderingFieldsMethodsOperators - **E08100**: purityRules - **E08110**: purityRules - **E05150**: purityRules - **E05160**: purityRules - **E05190**: purityRules - **E07910**: sanitizedTypes - **E07920**: sanitizedTypes - **E07930**: sanitizedTypes - **E07940**: sanitizedTypes - **E07941**: sanitizedTypes - **E07942**: sanitizedTypes - **E07943**: sanitizedTypes - **E07944**: sanitizedTypes - **E06140**: methodResolution - **E50060**: methodResolution - **E06270**: methodResolution - **E01040**: commonAIMistakes.semanticMistakes.overloadingFunctions - **E07950**: commonAIMistakes.semanticMistakes.incrementInExpression - **E07901**: commonAIMistakes.semanticMistakes.doubleQuotedInterpolation - **E01093**: commonAIMistakes.syntaxMistakes.unclosedBacktickString - **E08091**: commonAIMistakes.semanticMistakes.unusedParameter - **E07120**: dispatcherPattern - **E07820**: dispatcherPattern - **E05170**: dispatcherPattern - **E05180**: dispatcherPattern - **E06180**: accessModifiers - **E07310**: commonAIMistakes.semanticMistakes.enumSwitchExhaustivenessAndDefault - **E07330**: commonAIMistakes.semanticMistakes.enumSwitchExhaustivenessAndDefault - **E07500**: commonAIMistakes.semanticMistakes.operatorQuestionMustBePure - **E08130**: commonAIMistakes.semanticMistakes.copyConstructorInPureContext - **E08080**: codeQualityEnforcement.tautologicalExpressions - **E08081**: codeQualityEnforcement.tautologicalExpressions - **E08082**: codeQualityEnforcement.tautologicalExpressions - **E08083**: codeQualityEnforcement.tautologicalExpressions - **E08084**: codeQualityEnforcement.tautologicalExpressions - **E08085**: codeQualityEnforcement.tautologicalExpressions - **E11018**: codeQualityEnforcement.unusedCaptureDetection - **E11022**: codeQualityEnforcement.classDesignAntiPatterns - **E11023**: codeQualityEnforcement.classDesignAntiPatterns - **E11024**: codeQualityEnforcement.classDesignAntiPatterns - **E11025**: codeQualityEnforcement.classDesignAntiPatterns - **E11026**: codeQualityEnforcement.referenceOrdering - **E11030**: codeQualityEnforcement.confusinglySimilarNames - **E11032**: codeQualityEnforcement.operatorKeywordShadowing - **E11040**: codeQualityEnforcement.diInjectionLimits - **E11041**: codeQualityEnforcement.diInjectionLimits - **E11042**: codeQualityEnforcement.diInjectionLimits - **E11043**: codeQualityEnforcement.diInjectionLimits - **E11050**: codeQualityEnforcement.discardedReturnValues - **E11051**: codeQualityEnforcement.discardedReturnValues - **E11052**: codeQualityEnforcement.discardedReturnValues - **E11053**: codeQualityEnforcement.dataClumpDetection - **E11054**: codeQualityEnforcement.typeTraversalLimit - **E11061**: codeQualityEnforcement.namedArgumentEnforcement - **E11062**: codeQualityEnforcement.namedArgumentEnforcement - **E11064**: codeQualityEnforcement.magicLiteralDetection - **E11065**: codeQualityEnforcement.magicLiteralDetection - **E11066**: codeQualityEnforcement.constantOrganization - **E11067**: codeQualityEnforcement.constantOrganization - **E11068**: codeQualityEnforcement.stringInterpolationMandate ## AI Quick Reference - **Description**: Most commonly needed pages for AI assistants - **For Errors**: https://ek9.io/errors.html - **For Quality Issues**: https://ek9.io/quality.html - **For Flow Control**: https://ek9.io/flowControl.html - **For Streams**: https://ek9.io/streamsAndPipelines.html - **For Types**: https://ek9.io/builtInTypes.html - **For Testing**: https://ek9.io/testing.html - **For Commands**: https://ek9.io/commandline.html - **For Packaging**: https://ek9.io/packaging.html - **For Dependencies**: https://ek9.io/packaging.html#dependencies - **For Comprehensive Guide**: https://ek9.io/forAI.html ## Oracle Knowledge Base - **Description**: The EK9 Oracle is an in-process knowledge base of compiler-validated Q&A entries with working code snippets. Both frontier and local LLMs MUST consult the Oracle before writing code. - **What It Contains** - **QA Entries**: 565+ Q&A entries across 49 categories (growing toward 2000+) - **Code Snippets**: Every Q&A contains working EK9 code that compiles against the current compiler - **Migration Context**: Language comparison notes for developers coming from Java, Python, C#, etc. - **Error Patterns**: Common mistakes with error codes and corrected examples - **How To Query** - **By Keywords**: ek9 -q — BM25F-ranked search across questions, answers, keywords - **By ID**: ek9 -q — fetch specific Q&A by ID (e.g., ek9 -q 45) - **With Migration**: ek9 -qm — includes language comparison context in results - **By Protocol**: ek9 -qp — search protocol-specific Q&A entries for agent coordination - **List All**: ek9 -q — list all categories and entry counts - **Oracle First Workflow** - **Description**: The Oracle enables a design-first workflow that inverts the industry-standard generate-then-fix cycle - **Steps** - DECOMPOSE: Break the user's request into design components (file I/O, validation, DI, etc.) - CONSULT: Query the Oracle for each component — ask-oracle returns proven, compiled snippets - ASSESS: Check coverage — if all components have snippets, proceed. If gaps exist, report them - COMPOSE: Assemble the solution from Oracle snippets, adapting to the specific requirement - VERIFY: Compile and expect near-clean result (1-2 fix cycles, not 8) - **Why This Works**: Oracle snippets are compiler-validated against the current compiler version. Starting from proven patterns means the first attempt is usually correct. Code-first approaches that guess from general training data take 5-8x more compile cycles. - **Token Savings**: An Oracle-first approach that starts from snippets needs 1-2 compile cycles. A code-first approach needs 8. Over a session with 10 tasks, that is 15 compile cycles vs 80. - **Critical Rule**: ALWAYS consult the Oracle before writing EK9 code. The Oracle contains patterns that are guaranteed to compile. Generating from general training data wastes tokens on doomed implementations. ## AI Agent Architecture - **Description**: When running inside ek9 -ai, LLMs operate in a two-tier system with structured delegation and escalation protocols - **Two Tier Model** - **Frontier**: Cloud LLM (e.g., Claude) — handles architectural reasoning, complex multi-file analysis, design decisions - **Local**: Local LLM (e.g., Qwen2.5-Coder-32B) — handles routine compile-fix loops, Oracle consultation, symbol lookups, test execution - **Ratio**: 70-80% of work is handled locally (zero token cost), 20-30% by frontier (targeted, high-value reasoning) - **Three Message Types** - **Delegation** - **Direction**: Frontier -> Local - **Purpose**: Assign a scoped task (investigation, implementation, or validation) - **Key Fields** - taskId - delegationType - objective - questions/instructions - constraints.responseBudget - terrainBrief - **Protocol Query**: ek9 -qp delegation brief format - **Escalation** - **Direction**: Local -> Frontier - **Purpose**: Request help when stuck (repeated failure, low confidence, out of scope, oracle gap) - **Key Fields** - taskId - originalObjective - escalationReason - currentState - attemptHistory - specificQuestion - **Critical Rule**: Escalation requests must be self-contained — the frontier should be able to help without re-investigating - **Protocol Query**: ek9 -qp escalation request format - **Progress** - **Direction**: Local -> Frontier (fire-and-forget) - **Purpose**: Lightweight status update during long delegations (under 100 tokens) - **Key Fields** - taskId - progress.phase - confidence - estimatedCompletionSeconds - **Protocol Query**: ek9 -qp progress update format - **Intent Vocabulary** - **Description**: Local agents express actions as intents — the in-process infrastructure resolves them against the symbol table, Oracle, and compiler APIs. Never generate shell commands. - **Core Intents** - compile - read-file - write-file - edit-file - search - ask-oracle - type-help - run-tests - **Discovery Intents** - find-definition - find-references - list-methods - list-operators - list-constructors - list-properties - get-type - get-signature - type-hierarchy - find-implementations - list-subtypes - list-module-symbols - list-modules - method-resolution - **Critical Rule**: Discovery intents resolve from the in-process symbol table in ~0.01ms with zero I/O. ALWAYS prefer discovery intents over search for structural queries (finding definitions, listing methods, tracing hierarchies). The symbol table has this information indexed and typed — it cannot produce false positives. - **Protocol Query**: ek9 -qp intent vocabulary ## Bootstrap Sequence - **Description**: What both frontier and local LLMs should do when starting an ek9 -ai session - **Steps** - **Step**: 1 - **Action**: Load this forAI.json reference - **Purpose**: Understand EK9's excluded features, tri-state semantics, closed types, quality metrics, and operator semantics - **Step**: 2 - **Action**: Know the Oracle exists and how to query it - **Purpose**: 565+ Q&A entries with compiler-validated code snippets are available via ask-oracle intent or ek9 -q. Always consult before coding. - **Step**: 3 - **Action**: Know the intent vocabulary - **Purpose**: 22 intents available — 8 core + 14 discovery. Discovery intents use the in-process symbol table (~0.01ms). Never fork to shell for structural queries. - **Step**: 4 - **Action**: Know the delegation protocol - **Purpose**: Frontier delegates via structured briefs, local agents respond with compressed findings. Escalation when stuck. Protocol Q&A available via ek9 -qp. - **Step**: 5 - **Action**: Know the compiler is in-process - **Purpose**: Compilation is a Java method call (~80ms), not fork+exec (~2000ms). The JVM is warm, bootstrap is cached. Re-compile freely — it costs nothing. - **For Frontier LLM**: You coordinate. Delegate investigation and routine implementation to local agents. Retain architectural reasoning and novel design. Set terrain context in delegation briefs. - **For Local LLM**: You execute. Consult the Oracle before coding. Use discovery intents for structural queries. Stay within response budgets. Escalate when stuck — do not guess. - **In Process Advantage**: The EK9 compiler runs in the same JVM. Symbol table queries cost ~0.01ms. Oracle queries cost ~3ms. Compilation costs ~80ms. There is no fork/exec, no JVM cold start, no stderr parsing. Everything is structured JSON from typed APIs. ## AI Workflow Guidance - **Description**: Guidance for AI assistants to produce better EK9 code - **Plan First** - **Description**: Always create a plan before writing EK9 code - **Rule**: Present the plan to the user and ask for feedback before implementing - **Questions To Ask** - Do you want standalone functions, or a trait/class design with operators? - Should I use 'default operator' for auto-generated $, $$, :=:, <=> operators (==, <> and the other comparisons are generated from <=>)? - Do you need a component/application (DI) architecture, or a simpler program? - Should records use 'default operator $$' for JSON serialization? - **Design Pattern Selection** - **Description**: Choose the right EK9 pattern for the task - **Patterns** - **Name**: Functions only - **When**: Simple utilities, stateless transformations, scripts - **Example**: ```ek9 String formatting, math operations, data transformation ``` - **Name**: Trait + Class - **When**: CRUD operations, stateful services, polymorphic behavior - **Example**: ```ek9 Database access, customer management, repository pattern ``` - **Rule**: If you define a trait, you MUST implement it with a class - never abandon a trait for standalone functions - **Name**: Component + Application - **When**: Dependency injection, service wiring, enterprise architecture - **Example**: ```ek9 Web applications, microservices, multi-component systems ``` - **Name**: Record + default operators - **When**: Data transfer objects, API responses, configuration, value objects - **Example**: ```ek9 Customer record, OrderLine, Config ``` - **Rule**: Always consider 'default operator $$' for JSON and 'default operator $' for string display - **Operator Awareness** - **Description**: EK9 records and classes can have operators - don't write functions for what operators provide - **Critical Rule**: Before writing a function that converts to JSON or String, check if 'default operator $$' or 'default operator $' already does this - **Auto Generated Operators** - **$$**: JSON serialization - produces JSON for all fields automatically - **$**: String representation - produces display string for all fields - **?**: isSet check - returns true only if ALL fields are set (and the super is set) - **:=:**: Shallow copy - each field of the target refers to the same object as the source's; write operator :=: yourself for a deep or selective copy - **==**: Equality comparison - **<=>**: Three-way comparison - **#?**: Hash code - **Example** - **Wrong**: ```ek9 customerToJSON() -> c as Customer <- rtn as String: `{ "name": "${c.name}" }` ``` - **Correct**: ```ek9 //On the Customer record, add: default operator $$ //Then use: $$customer or $$(listOfCustomers) ``` - **Trait Implementation** - **Description**: Traits define interfaces - they must be implemented - **Critical Rule**: If you define a trait, you MUST create a class 'with trait of' that implements it. Never define a trait and then use standalone functions instead. - **Example** - **Wrong**: ```ek9 defines trait CustomerAccess operator += -> customer as Customer defines function addCustomer() //WRONG: standalone function instead of trait impl -> customers as List of Customer customer as Customer ``` - **Correct**: ```ek9 defines trait CustomerAccess operator += -> customer as Customer defines class CustomerStore with trait of CustomerAccess customers as List of Customer: List() override operator += -> customer as Customer customers += customer default operator ? ``` - **Proactive DI Workflow** - **Description**: Before adding or modifying dependency injection, inspect the current DI landscape to avoid compile errors - **When**: Any task involving: adding injection fields, registering components, wiring applications, adding aspects - **Steps** - **Step**: 1 - **Action**: Run 'ek9 -di -E3 main.ek9' to see the current wiring map - **Purpose**: Understand existing registrations, their order, injection sites, and aspect proxies - **Step**: 2 - **Action**: Check if an application block exists - **Purpose**: If no application exists, you must create one — adding injection fields alone will fail - **Step**: 3 - **Action**: Count existing injection fields on the target component - **Purpose**: Components with 5+ injection fields trigger E11040 (excessive coupling) — use a facade instead - **Step**: 4 - **Action**: Identify where to insert new registrations in the ordering - **Purpose**: Dependencies must be registered BEFORE dependents — E08200 fires if order is wrong - **Step**: 5 - **Action**: Add 'default operator ?' to any component with fields - **Purpose**: Components with properties require operator ? for set/unset semantics — E07235 fires if missing - **Common Errors** - **E07235**: Component has fields but no 'default operator ?' — add it after methods - **E07236**: Dynamic class captures variables as fields — add 'default operator ?' at end of body - **E08190**: Circular dependency — extract shared logic into a third component - **E08200**: Registration ordering — reorder so dependencies come first - **E08210**: Missing registration — add registration for the injected abstract type - **E11040**: Too many injection fields — split into smaller components or use a facade - **Error Lookup**: Use 'ek9 -h Exxxx' for full diagnosis, fix actions, and examples for any error code - **Error Diagnosis** - **Description**: When a compiler error occurs, use the built-in tools to understand and fix it - **Steps** - Read the error code (e.g., E50001) from the compiler output - Run 'ek9 -h E50001' for full diagnosis with fix actions and examples - For DI-related errors (E08xxx, E11xxx), also run 'ek9 -di -E3' for the wiring map - Use 'ek9 -q ' to find related Q&A entries - **AI Note**: The 'ek9 -h Exxxx' output is structured as [DIAGNOSIS], [ACTION], [SEE ALSO] — designed for AI parsing ## Inline Examples - **Description**: Self-contained EK9 code examples for AI bootstrapping. These examples are complete and verified to compile. - **Usage**: AI assistants can use these examples directly to understand EK9 syntax and patterns without external file access. - **Reminder**: Use 'ek9 -h ' for help on topics, 'ek9 -h Exxxx' for error diagnosis, 'ek9 -di -E3' for DI wiring map, 'ek9 -q ' for Q&A lookup. - **Hello World** - **Pattern**: basic-program-structure - **Description**: The canonical Hello World - minimal EK9 program structure - **Keywords** - program - module - stdout - println - <- - **Code**: ```ek9 #!ek9 defines module introduction defines program HelloWorld() //Stdout is a built-in type - no import required //The <- operator: declares 'stdout' AND assigns the new Stdout instance //Type inference: compiler knows stdout is type Stdout stdout <- Stdout() stdout.println("Hello, World") //EOF ``` - **Simple Function** - **Pattern**: function-with-parameters - **Description**: Function with parameters and return value - shows -> for input, <- for output - **Keywords** - function - -> - <- - pure - **Code**: ```ek9 #!ek9 defines module example.functions defines function //Single parameter, single return greet() as pure -> name as String <- message as String: "Hello, " + name //Multiple parameters - use indented block after single -> add() as pure -> a as Integer b as Integer <- result as Integer: a + b defines program Demo() stdout <- Stdout() stdout.println(greet("World")) stdout.println(`Sum: ${add(2, 3)}`) //EOF ``` - **Simple Class** - **Pattern**: basic-class-structure - **Description**: Class with properties, constructor, methods, and operator overloading - **Keywords** - class - method - property - operator - $ - **Code**: ```ek9 #!ek9 defines module example.classes defines class Person //Properties are always private firstName <- String() lastName <- String() //Constructor Person() -> firstName as String lastName as String this.firstName :=: firstName this.lastName :=: lastName //Method fullName() as pure <- rtn as String: `${firstName} ${lastName}` //Operator $ for string conversion operator $ as pure <- rtn as String: fullName() //Operator ? for isSet check default operator ? defines program Demo() stdout <- Stdout() person <- Person("Steve", "Limb") stdout.println($person) //EOF ``` - **Class Extension** - **Pattern**: class-extension-hierarchy - **Description**: Abstract and open classes - EK9 types are closed by default - **Keywords** - extends - override - abstract - open - **Code**: ```ek9 #!ek9 defines module example.inheritance defines class //Abstract classes are automatically open Shape as abstract name <- String() Shape() -> name as String this.name :=? name area() as pure abstract <- rtn as Float? operator $ as pure <- rtn as String: name ?: "Unknown" default operator ? //Concrete class extending abstract - closed by default Circle extends Shape radius <- Float() Circle() -> radius as Float super("Circle") this.radius :=? radius override area() as pure <- rtn as Float: 3.14159 * radius * radius default operator ? default operator $ //Concrete but marked 'as open' for extension Rectangle extends Shape as open width <- Float() height <- Float() Rectangle() -> width as Float height as Float super("Rectangle") this.width :=? width this.height :=? height override area() as pure <- rtn as Float: width * height default operator ? default operator $ //Can extend Rectangle because it's 'as open' Square extends Rectangle Square() -> side as Float super(side, side) defines program Demo() stdout <- Stdout() shapes <- [Circle(5.0), Rectangle(4.0, 3.0), Square(2.0)] for shape in shapes stdout.println(`${shape}: area = ${shape.area()}`) //EOF ``` - **Abstract Function** - **Pattern**: abstract-function-polymorphism - **Description**: Abstract functions as interfaces for polymorphic behavior - **Keywords** - function - abstract - pure - is - **Code**: ```ek9 #!ek9 defines module example.polymorphism defines function //Abstract function - defines interface mathOperation() as pure abstract -> x as Float y as Float <- result as Float? //Concrete implementations add() is mathOperation as pure -> x as Float y as Float <- result as Float: x + y subtract() is mathOperation as pure -> x as Float y as Float <- result as Float: x - y multiply() is mathOperation as pure -> x as Float y as Float <- result as Float: x * y defines program Demo() stdout <- Stdout() //Functions can be stored and called polymorphically for op in [add, subtract, multiply] stdout.println(`Result: ${op(10.0, 5.0)}`) //EOF ``` - **Trait Composition** - **Pattern**: trait-with-delegation - **Description**: Traits for composition, delegation with 'by', and 'allow only' restriction - **Keywords** - trait - with - by - delegate - allow only - **Code**: ```ek9 #!ek9 defines module example.traits defines trait //Simple trait Printable print() abstract <- rtn as String? //Trait inheriting from another Loggable with trait of Printable log() abstract <- rtn as String? //Trait with 'allow only' restriction SecureData allow only EncryptedMessage, SignedMessage data() abstract <- rtn as String? defines class //Class implementing trait Message with trait of Printable content <- String() Message() -> content as String this.content :=? content override print() <- rtn as String: content default operator ? //Only these can implement SecureData EncryptedMessage with trait of SecureData override data() <- rtn as String: "[encrypted]" SignedMessage with trait of SecureData override data() <- rtn as String: "[signed]" //Class implementing a trait that inherits another: log() and the inherited print() LoggingMessage with trait of Loggable message as Printable: Message("default") LoggingMessage() -> message as Printable this.message: message override print() <- rtn as String: message.print() override log() <- rtn as String: `[LOG] ${print()}` default operator ? //Delegation with 'by' - every Printable method is answered by 'printer' ProxyMessage with trait of Printable by printer printer as Printable: Message("default") ProxyMessage() -> printer as Printable this.printer: printer default operator ? defines program Demo() stdout <- Stdout() msg <- LoggingMessage(Message("Hello")) stdout.println(msg.log()) proxy <- ProxyMessage(Message("Delegated")) stdout.println(proxy.print()) //EOF ``` - **Enumeration** - **Pattern**: enumeration-with-operators - **Description**: Enumerations with built-in operators, iteration, and switch integration. CRITICAL: Enumerations automatically get 24 operators from a simple value list declaration - no 'default operator' directives needed. Do NOT write comparison, conversion, lookup, or serialization functions for enumerations - they are all built-in. - **Keywords** - type - enum - for - switch - cat - **AI Warning**: EK9 enumerations are NOT like Java/Python/C# enums. From just a value list (e.g., Colour with Red, Green, Blue), you get 24 operators automatically. Do NOT write: compareEnums() (use < > <= >= <=>), enumToString() (use $), enumToJson() (use $$), findByName() (use Colour(name)), getFirst/getLast() (use #< #>), isValid() (use Colour(name)?), getAllValues() (use cat Colour | collect). ALL of these are built-in. - **Automatic Operators** - **Same Type Comparison** - **Operators** - == - <> - < - > - <= - >= - <=> - **Description**: Compare two enum values by declaration order (ordinal). Red < Blue is true if Red declared before Blue. - **Count**: 7 - **String Param Comparison** - **Operators** - == - <> - < - > - <= - >= - <=> - **Description**: Compare enum to a String directly. colour == "Red" works without conversion. - **Count**: 7 - **String Conversion** - **Operators** - $ - #^ - **Description**: $ returns the value name as String. #^ promotes to String (same result for enums). - **Count**: 2 - **JSON Serialization** - **Operators** - $$ - **Description**: $$ returns JSON representation of the enum value name. - **Count**: 1 - **Hash Code** - **Operators** - #? - **Description**: Ordinal-based hash code, suitable for Dict keys. - **Count**: 1 - **Is Set** - **Operators** - ? - **Description**: Tri-state check. Colour() is unset, Colour.Red is set. - **Count**: 1 - **First Last** - **Operators** - #< - #> - **Description**: #< returns first declared value, #> returns last declared value. - **Count**: 2 - **Constructors** - **Signatures** - Colour() - Colour(Colour) - Colour(String) - **Description**: Default (creates unset), copy, and construct-from-string (unset if no match). - **Count**: 3 - **Total Auto Generated**: 24 - **Code**: ```ek9 #!ek9 defines module example.enumerations defines type CardSuit Hearts Diamonds Clubs Spades CardRank Two Three Four Five Six Seven Eight Nine Ten Jack Queen King Ace defines program Demo() stdout <- Stdout() //Access enum values hearts <- CardSuit.Hearts clubs <- CardSuit.Clubs //Comparison operators - ALL built-in, do NOT write comparison functions stdout.println(`hearts < clubs: ${hearts < clubs}`) stdout.println(`hearts == clubs: ${hearts == clubs}`) stdout.println(`hearts <=> clubs: ${hearts <=> clubs}`) //String-param comparison - compare directly to String stdout.println(`hearts == Hearts: ${hearts == "Hearts"}`) //String conversion ($) and JSON ($$) - do NOT write toString/toJson stdout.println(`Suit: ${$hearts}`) stdout.println(`JSON: ${$$hearts}`) //Hash code (#?) - do NOT write hashCode functions stdout.println(`Hash: ${#?hearts}`) //First (#<) and Last (#>) of any value's enumeration - do NOT write getFirst/getLast stdout.println(`First: ${#< hearts}`) stdout.println(`Last: ${#> hearts}`) //Construct from string - do NOT write findByName functions validSuit <- CardSuit("Clubs") require validSuit? invalidSuit <- CardSuit("Invalid") require ~invalidSuit? //Is-set check - do NOT write isValid functions require hearts? unknownSuit <- CardSuit() require ~unknownSuit? //Iterate all enum values - do NOT write getAllValues stdout.println("All suits:") for suit in CardSuit stdout.println($suit) //Switch on enum for rank in CardRank message <- switch rank <- rtn as String? case CardRank.Ace rtn: `Wow, an ${rank}!` case > CardRank.Ten rtn: `Face card: ${rank}` default rtn: `Number: ${rank}` stdout.println(message) //Stream pipeline with enum suits <- cat CardSuit | collect as List of CardSuit require suits? //EOF ``` - **Constants** - **Pattern**: constant-declarations - **Description**: Native literal types for constants - all built-in types supported - **Keywords** - constant - literal - Integer - Float - Duration - Date - **Code**: ```ek9 #!ek9 defines module example.constants defines constant //Boolean literals limitRetries <- true updateLog <- false //Character and String delimiter <- ':' author <- "Steve Limb" //Numbers maxRetries <- 10 minTemp <- -2 Pi <- 3.14159 bitMask <- 0b01110001 //Time literals noon <- 12:00 justAfterNoon <- 12:00:01 //Duration literals (ISO 8601) maxDuration <- P3Y2M6DT12H15M6S twoYears <- P2Y twoHours <- PT2H timeout <- 250ms //Date and DateTime millennium <- 2000-01-01 created <- 2018-01-31T01:30:00-05:00 //Money and Colour maxPayment <- 300000#USD defaultColour <- #AB6F2B //Dimension twoMeters <- 2m //RegEx matchSteves <- /[S|s]te(?:ven?|phen)/ //EOF ``` - **If Statement** - **Pattern**: if-statement-comprehensive - **Description**: If statements with guards, declarations, and range checks - **Keywords** - if - else - when - guard - <- - := - **Code**: ```ek9 #!ek9 defines module example.flowcontrol defines function getTemperature() -> country as String <- temperature <- Integer() if country == "GB" temperature :=? 20 else if country == "US" temperature :=? 75 getScore() <- rtn as Integer: 9 defines program Demo() stdout <- Stdout() //Simple if score <- getScore() limit <- 10 if score < limit stdout.println("Less than 10") //If-else chain target <- 9 result <- String() if score > target result: "Too High" else if score < target result: "Too Low" else result: "Just Right" stdout.println(result) //'when' is synonym for 'if' when score < limit stdout.println("Still less than 10") //Guard with declaration - variable scoped to if block //Block only executes if getTemperature returns SET value when reading <- getTemperature("GB") with reading > limit stdout.println(`UK temp: ${reading}`) //Re-point an existing variable, check it is set, then the condition hot <- 50 existingTemp <- Integer() if existingTemp := getTemperature("US") then existingTemp? and (existingTemp > hot) stdout.println(`US is hot: ${existingTemp}`) //Range check - literals in a range need names too x <- 15 upper <- 20 if x in limit ... upper stdout.println("x is between 10 and 20") small <- 5 if x not in 1 ... small stdout.println("x is not between 1 and 5") //EOF ``` - **Switch Statement** - **Pattern**: switch-statement-comprehensive - **Description**: Switch with case, expressions, comparison operators, and guards - NO fallthrough - **Keywords** - switch - given - case - when - default - **Code**: ```ek9 #!ek9 defines module example.switch defines type Status Active Pending Closed defines function getCurrentTemp() <- rtn <- 20 getScore() <- rtn <- 25 defines program Demo() stdout <- Stdout() //'switch' and 'given' are synonyms //'case' and 'when' are synonyms //NO fallthrough - each case is independent (by design) //Basic switch status <- Status.Active switch status case Status.Active stdout.println("Processing...") case Status.Pending stdout.println("Waiting...") case Status.Closed stdout.println("Done") default stdout.println("Unknown") //Switch as expression - returns a value score <- getScore() result <- given score <- rtn as String? when < 10 rtn: "Low" when 20, 21, 22, 23, 24 rtn: "Perfect" when > 30 rtn: "High" default rtn: "OK" stdout.println(`Result: ${result}`) //Multiple values in one case (replaces fallthrough) day <- 3 dayType <- switch day <- rtn as String? case 1, 7 rtn: "Weekend" case 2, 3, 4, 5, 6 rtn: "Weekday" default rtn: "Unknown" stdout.println(`Day type: ${dayType}`) //Switch with comparison operators multiplier <- 5 category <- switch score <- rtn as String? case < 12 rtn: "Moderate" case > 10*multiplier rtn: "Very High" case getCurrentTemp(), 21, 22 rtn: "Perfect" default rtn: "Suitable" stdout.println(`Category: ${category}`) //Switch with declaration guard //The body runs only when the guard is set, so the result needs a value for when it is not text <- switch reading <- getCurrentTemp() with reading <- rtn as String: "No reading" case < 15 rtn: `Cold: ${reading}` case > 25 rtn: `Hot: ${reading}` default rtn: `Nice: ${reading}` stdout.println(text) //EOF ``` - **For Loop** - **Pattern**: for-loop-comprehensive - **Description**: For loops with ranges, collections, iterators - NO break/continue - **Keywords** - for - in - by - range - ... - **Code**: ```ek9 #!ek9 defines module example.forloop defines program Demo() stdout <- Stdout() //Range iteration (inclusive) stdout.println("1 to 5:") for i in 1 ... 5 stdout.println($i) //Range with step stdout.println("1 to 10 by 2:") for i in 1 ... 10 by 2 stdout.println($i) //Descending range stdout.println("10 down to 1:") for i in 10 ... 1 by -1 stdout.println($i) //Collection iteration items <- ["Alpha", "Beta", "Charlie"] for item in items stdout.println(item) //String iteration (characters) for char in "Hello" stdout.println(char) //Iterator directly iter <- items.iterator() for item in iter stdout.println(`Via iterator: ${item}`) //Float range for f in 1.0 ... 5.0 by 0.5 stdout.println($f) //Time range start <- 09:00 finish <- 17:00 for t in start ... finish by PT1H stdout.println($t) //NO break/continue in EK9! //Use streams instead: //cat items | filter by predicate | head //EOF ``` - **While Loop** - **Pattern**: while-loop-comprehensive - **Description**: While and do-while loops with guards and as expressions - **Keywords** - while - do - with - then - **Code**: ```ek9 #!ek9 defines module example.whileloop defines program Demo() stdout <- Stdout() //Basic while items <- ['A', 'B', 'C'] iter <- items.iterator() while iter.hasNext() item <- iter.next() stdout.println(item) //While re-pointing an existing variable - ALWAYS assigns a fresh iterator; the check runs every pass while iter := items.iterator() then iter? and iter.hasNext() item <- iter.next() stdout.println(`Again: ${item}`) //While with declaration - variable scoped to loop while newIter <- items.iterator() then newIter.hasNext() item <- newIter.next() stdout.println(`New: ${item}`) //Do-while - body executes at least once resetIter <- items.iterator() if resetIter.hasNext() do item <- resetIter.next() stdout.println(`Do: ${item}`) while resetIter.hasNext() //While as expression - returns value loops <- 10 result <- while complete <- false with not complete <- rtn <- 0 rtn++ complete: rtn == loops stdout.println(`While result: ${result}`) //Do-while as expression fewerLoops <- 5 result2 <- do complete <- false <- rtn <- 0 rtn++ complete: rtn == fewerLoops while not complete stdout.println(`Do result: ${result2}`) //EOF ``` - **Try Catch** - **Pattern**: exception-handling-comprehensive - **Description**: Try/catch/finally with guards, custom exceptions, and dispatcher pattern - **Keywords** - try - catch - handle - finally - throw - Exception - **Code**: ```ek9 #!ek9 defines module example.exceptions defines constant maxNumber <- 100 defines class //Custom exception with additional field ValidationException extends Exception fieldName <- String() ValidationException() -> message as String fieldName as String super(message, 1) this.fieldName :=? fieldName fieldName() as pure <- rtn as String: fieldName override operator $ as pure <- rtn as String: `${reason()} [field: ${fieldName}]` default operator ? default operator $$ Processor process() -> number as Integer <- rtn as String: String() if number < 0 throw ValidationException("Negative not allowed", "number") else if number > maxNumber throw Exception("Too large", 2) else rtn: `Processed: ${number}` //Dispatcher for type-specific exception handling handleError() as dispatcher -> ex as Exception <- rtn as String: $ex handleError() -> ex as ValidationException <- rtn as String: `Validation failed: ${ex.fieldName()}` defines program Demo() stdout <- Stdout() stderr <- Stderr() processor <- Processor() for number in [-1, 50, 150] try result <- processor.process(number) stdout.println(result) catch -> ex as Exception //Dispatcher routes to specific handler stderr.println(processor.handleError(ex)) finally stdout.println(`Finished processing ${number}`) //Try with resource auto-close try -> input <- TextFile("test.txt").input() cat input > stdout handle -> ex as Exception stderr.println($ex) finally stdout.println("File auto-closed") //EOF ``` - **Streams** - **Pattern**: stream-pipelines - **Description**: Stream pipelines replace loops with break/continue - cat, filter, map, head, collect - **Keywords** - cat - | - filter - map - head - tail - collect - > - **Code**: ```ek9 #!ek9 defines module example.streams defines function isEven() as pure -> n as Integer <- rtn as Boolean: n mod 2 == 0 double() as pure -> n as Integer <- rtn as Integer: n * 2 formatItem() as pure -> n as Integer <- rtn as String: `Item: ${n}` joinWithComma() as pure -> a as String b as String <- rtn as String: a? and b? <- `${a}, ${b}` : String() defines program Demo() stdout <- Stdout() numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] //Output directly to stdout stdout.println("All numbers:") cat numbers > stdout //Filter - replaces continue pattern stdout.println("Even numbers:") cat numbers | filter by isEven > stdout //Map - transform each item stdout.println("Doubled:") cat numbers | map by double > stdout //Head - replaces break pattern (take first N) stdout.println("First 3:") cat numbers | head 3 > stdout //Tail - last N items stdout.println("Last 3:") cat numbers | tail 3 > stdout //Skip - skip first N stdout.println("Skip first 5:") cat numbers | skip 5 > stdout //Chain multiple operations stdout.println("Even, doubled, first 3:") cat numbers | filter by isEven | map by double | head 3 > stdout //Collect into List evens <- cat numbers | filter by isEven | collect as List of Integer stdout.println(`Collected evens: ${evens}`) //Collect into String with join formatted <- cat numbers | map by formatItem | join with joinWithComma | collect as String stdout.println(`Joined: ${formatted}`) //Range as stream source stdout.println("Range stream:") for i in 1 ... 5 | map by double > stdout //EOF ``` - **Generics** - **Pattern**: generic-types - **Description**: Generic/parameterized types - List of T, Optional of T, custom generics - **Keywords** - of - List - Dict - Optional - generic - constrain - **Code**: ```ek9 #!ek9 defines module example.generics defines function //Generic function identity() of type T -> item as T <- rtn as T: item //Generic over any T that has a > operator - checked when it is parameterised findMax() of type T -> a as T b as T <- rtn as T: a > b <- a else b defines class //Generic class Container of type T item as T? Container() item :=? T() Container() -> item as T this.item :=? item get() as pure <- rtn as T: item set() -> newItem as T item :=: newItem override operator ? as pure <- rtn as Boolean: item? defines program Demo() stdout <- Stdout() //Built-in generic types strings <- List() of String strings += "Hello" strings += "World" stdout.println(`List: ${strings}`) //Dict (key-value) ages <- Dict() of (String, Integer) ages += DictEntry("Steve", 30) ages += DictEntry("John", 25) stdout.println(`Dict: ${ages}`) //Optional maybeValue <- Optional(42) if maybeValue? stdout.println(`Optional has: ${maybeValue.get()}`) noValue <- Optional() of String stdout.println(`Empty optional is set: ${noValue?}`) //Custom generic container <- Container("test") stdout.println(`Container: ${container.get()}`) //Generic functions result <- identity("Hello") stdout.println(`Identity: ${result}`) //Two T parameters cannot infer T: parameterise first, then call maxOfIntegers <- findMax() of Integer max <- maxOfIntegers(10, 20) stdout.println(`Max: ${max}`) //EOF ``` - **Dynamic Functions** - **Pattern**: dynamic-functions-and-closures - **Description**: Dynamic functions with capture - EK9's approach to closures/lambdas. Syntax: '(captures) is/extends AbstractFn as function'. For generic functions: '() extends Function of (In, Out) as pure function'. 'as function' is required on the first alternative or when extending parameterised generic functions. Capture can be unnamed '(threshold)' or named '(lo: minValue)'. Single-line form: '(capture) is fn as pure (rtn: expr)'. CAPTURE SEMANTICS: captures hold the pointer value at capture time — each closure has its own pointer slot, so outer pointer rebinding ('outer := newValue') does NOT affect the closure's captured value. BUT mutating the captured object via '+=', ':=:', ':~:', etc. mutates the SHARED object visible to the caller. For an isolated snapshot, copy via ':=:' or construct a new instance via the type's copy constructor BEFORE capture. Note: 'default operator :=:' is a SHALLOW copy - each field of the copy refers to the same object, so it is no snapshot of mutable fields; write 'operator :=:' yourself for a deep or selective copy (ek9 -q deep or selective copy). See operators.html#copy_principle. Same issue Java has with reference types — EK9 makes the rebind-vs-copy distinction grammatical (':=' vs ':=:'). See QA1296 for capture isolation patterns. - **Keywords** - dynamic - function - capture - is - as function - extends - Predicate - Function - **Code**: ```ek9 #!ek9 defines module example.dynamic defines function //Abstract function to implement predicate() as pure abstract -> number as Integer <- rtn as Boolean? transformer() as pure abstract -> number as Integer <- rtn as Integer? defines program Demo() stdout <- Stdout() threshold <- 10 multiplier <- 3 //Dynamic function capturing 'threshold' — 'as pure function' because predicate is pure greaterThan <- (threshold) is predicate as pure function rtn: number > threshold //Dynamic function capturing 'multiplier' multiply <- (multiplier) is transformer as pure function rtn: number * multiplier //Test the dynamic functions for n in [5, 10, 15] if greaterThan(n) stdout.println(`${n} > ${threshold}: ${multiply(n)}`) //Inline lambda-like syntax (single expression) lessThan <- (threshold) is predicate as pure (rtn: number < threshold) //Multiple captures with named parameters inRange <- (lo: 5, hi: 15) is predicate as pure (rtn: number > lo and number < hi) //Functions as values in list — including inRange predicates <- [ (threshold) is predicate as pure (rtn: number == threshold), greaterThan, lessThan, inRange ] for pred in predicates stdout.println(`10 matches: ${pred(10)}`) //Extending built-in generic functions (Predicate of T, Function of (In, Out)) theValues <- [1, 2, 3, 4, 5] //Indented form with generic Predicate odd <- () extends Predicate of Integer as pure function r: t mod 2 == 0 //Single-line form with generic Function asString <- () extends Function of (Integer, String) as pure function (r : $t) asFloat <- () extends Function of (Integer, Float) as pure function (r : t) captured <- List() of String allAsFloat <- List() of Float cat theValues | tee by asFloat then in allAsFloat | reject odd | map with asString | tee captured > stdout //EOF ``` - **Dynamic Classes** - **Pattern**: dynamic-classes-and-traits - **Description**: Dynamic classes with capture and trait composition. Three forms: (1) Trait-based: '(captures) trait of TraitName as class' or '() with trait of TraitName as class' — both forms valid; 'with' is a stylistic addition. (2) Tuple/named: 'TupleName(captures) as class' — creates a reusable type with methods and operators. (3) Generic extension: '() extends GenericClass of Type as class' — note 'extends' on a dynamic class works ONLY for generic parameterization ('of T'); it cannot extend a non-generic concrete class. For non-generic extension + traits combos, author a named top-level class instead. Dynamic classes generally end with 'default operator ?' (or explicit 'operator ?'); when extending a class that already provides a concrete 'operator ?', the dynamic class inherits it and need not redeclare. Multi-trait delegation: 'trait of T1 by impl1, T2 by impl2' delegates trait methods to captured implementations. CAPTURE SEMANTICS: captures hold the pointer value at capture time — each instance has its own pointer slots, so outer pointer rebinding does NOT affect captured fields. BUT mutating the captured object via '+=', ':=:', ':~:', etc. mutates the SHARED object visible to the caller. For an isolated snapshot, copy via ':=:' or copy constructor BEFORE capture. Note: 'default operator :=:' is a SHALLOW copy - each field of the copy refers to the same object, so it is no snapshot of mutable fields; write 'operator :=:' yourself for a deep or selective copy (ek9 -q deep or selective copy). See operators.html#copy_principle. Same issue as Java reference types — EK9 makes the rebind-vs-copy distinction grammatical (':=' vs ':=:'). See QA1296 for capture isolation patterns. - **Keywords** - dynamic - class - trait - as class - by - capture - tuple - with trait of - **Code**: ```ek9 #!ek9 defines module example.dynamicclasses defines trait Describable describe() abstract <- rtn as String? Printable format() abstract <- rtn as String? defines class //Generic abstract class for extension Processor of type T as abstract process() as abstract -> item as T <- rtn as String? override operator ? as pure <- rtn as Boolean: true defines program Demo() stdout <- Stdout() //--- Form 1: Trait-based dynamic class --- //Simple trait implementation with capture greeting <- "Hello" greeter <- (greeting) with trait of Describable as class override describe() <- rtn as String: greeting default operator ? stdout.println(greeter.describe()) //Named capture (field name differs from source variable) messageText <- "Welcome" printer <- (msg: messageText) with trait of Printable as class override format() <- rtn as String: msg default operator ? stdout.println(printer.format()) //--- Multi-trait delegation with 'by' --- //Compose traits by delegating to captured implementations combo <- (greeter, printer) trait of Describable by greeter, Printable by printer default operator ? //combo.describe() delegates to greeter, combo.format() delegates to printer stdout.println(combo.describe()) stdout.println(combo.format()) //--- Mixed: delegate some traits, implement others inline --- mixed <- (printer) trait of Describable, Printable by printer override describe() <- rtn as String: `Mixed: ${format()}` default operator ? stdout.println(mixed.describe()) //--- Form 2: Tuple/named dynamic class --- //Creates a reusable type 'PersonTuple' with methods and operators personName <- "Steve" born <- 1970-01-01 person <- PersonTuple(name: personName, dob: born) as class personsName() <- rtn as String: name operator $ as pure <- rtn as String: `${name} born ${dob}` default operator ? stdout.println($person) stdout.println(person.personsName()) //Tuple type is reusable — can call constructors and use in functions person2 <- PersonTuple("Jane", 1985-06-15) stdout.println($person2) //--- Form 3: Generic extension dynamic class --- //Extend a parameterised generic class inline stringProcessor <- () extends Processor of String as class override process() -> item as String <- rtn as String: `Processed: ${item}` stdout.println(stringProcessor.process("data")) //EOF ``` - **Ternary Operators** - **Pattern**: ternary-and-coalescing - **Description**: Ternary conditional, null coalescing, elvis, min/max selection operators - **Keywords** - <- - : - ?? - ?: - ? - **Code**: ```ek9 #!ek9 defines module example.ternary defines program Demo() stdout <- Stdout() //Ternary conditional: condition <- trueValue : falseValue x <- 15 threshold <- 10 result <- x > threshold <- "big" : "small" stdout.println(`x is ${result}`) //isSet check in condition name <- String() display <- name? <- name : "Anonymous" stdout.println(`Display: ${display}`) //Null coalescing ?? - returns left unless it is ABSENT; value1 is present but UNSET, so it passes through value1 <- String() value2 <- "Default" coalesced <- value1 ?? value2 stdout.println(`Coalesced is set: ${coalesced?}`) //Elvis ?: - returns left only if SET, so this is how to default an unset value elvis <- value1 ?: value2 stdout.println(`Elvis: ${elvis}`) //Min selection ? - return larger value maxVal <- a >? b stdout.println(`Max: ${maxVal}`) //Works with unset values - returns the SET one unsetNum <- Integer() setNum <- 42 selected <- unsetNum . Records can have ALL operators (unlike structs in other languages). Use 'default operator' for auto-generation. - **Keywords** - record - :=: - :~: - <~> - <=> - extends - $$ - $ - default - **Code**: ```ek9 #!ek9 defines module example.records defines type //Constrained type PositiveId as Integer constrain as > 0 defines record //Base record IdRecord as abstract id <- PositiveId() IdRecord() -> id as PositiveId require id? this.id :=? id operator $ as pure <- rtn as String: $id override operator ? as pure <- rtn as Boolean: id? //Extended record with manual operators Person extends IdRecord firstName as String: String() lastName as String: String() Person() -> id as PositiveId firstName as String lastName as String super(id) this.firstName :=? firstName this.lastName :=? lastName //Copy operator - copy each field through its own :=: operator :=: -> other as Person id :=: other.id firstName :=: other.firstName lastName :=: other.lastName //Merge operator - only copy unset fields operator :~: -> other as Person if not id? id :=: other.id if not firstName? firstName :=: other.firstName if not lastName? lastName :=: other.lastName //Fuzzy match - similarity score operator <~> as pure -> other as Person <- rtn as Integer: (firstName <~> other.firstName) + (lastName <~> other.lastName) //Comparison - last name, then first name; == < > and the rest are generated from it operator <=> as pure -> other as Person <- rtn as Integer: lastName <=> other.lastName if rtn == 0 rtn: firstName <=> other.firstName //A hand-written <=> needs a hand-written #? hashing what it compares operator #? as pure <- rtn as Integer: #? `${lastName} ${firstName}` override operator $ as pure <- rtn as String: `${super.$()} ${firstName} ${lastName}` override operator ? as pure <- rtn as Boolean: super.?() and firstName? and lastName? //Record using 'default operator' for auto-generated implementations //Records can have ALL operators - don't write manual conversion functions! Customer name as String: String() email as String: String() age as Integer: Integer() Customer() -> name as String email as String age as Integer this.name :=? name this.email :=? email this.age :=? age //Auto-generated JSON: {"name":"...","email":"...","age":...} default operator $$ //Auto-generated String display default operator $ //Auto-generated shallow copy, isSet, comparison and hashcode (== <> < > are generated from <=>) default operator :=: default operator ? default operator <=> default operator #? defines program Demo() stdout <- Stdout() steve <- "Steve" limb <- "Limb" p1 <- Person(PositiveId(1), steve, limb) p2 <- Person(PositiveId(2), "Stephen", limb) //Fuzzy match similarity <- p1 <~> p2 stdout.println(`Similarity: ${similarity}`) //Copy p3 <- Person() p3 :=: p1 stdout.println(`Copied: ${p3}`) //Merge partial records partial1 <- Person(PositiveId(1), steve, String()) partial2 <- Person(PositiveId(1), String(), limb) partial1 :~: partial2 stdout.println(`Merged: ${partial1}`) //Customer with default operators - NO manual JSON functions needed! c1 <- Customer(steve, "steve@example.com", 30) //$$record produces JSON automatically json <- $$c1 stdout.println(`JSON: ${json}`) //$record produces String automatically stdout.println(`String: ${c1}`) //Works on Lists too - $$ on List of Customer produces JSON array customers <- [c1, Customer("Jane", "jane@example.com", 25)] stdout.println(`All: ${$$customers}`) //EOF ``` - **Sanitization** - **Pattern**: sanitized-input - **Description**: Input sanitization for security - 'sanitized' modifier on String parameters - **Keywords** - sanitized - String - security - InputSanitizer - **Code**: ```ek9 #!ek9 defines module example.security defines function //Sanitized parameter - automatically validated //Block only executes if input passes security checks processInput() -> input as sanitized String <- rtn as String: String() if input? //Safe to use - guaranteed clean rtn: `Processed: ${input}` //Manual sanitization using InputSanitizer manualCheck() -> userInput as String <- rtn as String: String() sanitizer <- InputSanitizer() if clean <- sanitizer.sanitize(userInput) rtn: `Manual clean: ${clean}` defines program Demo() stdout <- Stdout() stderr <- Stderr() //Safe input safeInput <- "Hello World" result <- processInput(safeInput) stdout.println(result) //Potentially malicious input (SQL injection attempt) badInput <- "'; DROP TABLE users; --" result2 <- processInput(badInput) if result2? stdout.println(result2) else stderr.println("Input rejected as unsafe") //Manual sanitization with threat detection sanitizer <- InputSanitizer() threat <- sanitizer.detectThreat(badInput) if threat? stderr.println(`Threat detected: ${threat}`) //EOF ``` - **Dispatcher Pattern** - **Pattern**: dispatcher-double-dispatch - **Description**: Dispatcher pattern - EK9's replacement for instanceof/casting - **Keywords** - dispatcher - method - polymorphism - override - **Code**: ```ek9 #!ek9 defines module example.dispatcher defines class //Base shape class Shape as abstract name() as pure abstract <- rtn as String? Circle extends Shape radius <- Float() Circle() -> radius as Float this.radius :=? radius override name() as pure <- rtn as String: "Circle" radius() as pure <- rtn as Float: radius default operator ? Square extends Shape side <- Float() Square() -> side as Float this.side :=? side override name() as pure <- rtn as String: "Square" side() as pure <- rtn as Float: side default operator ? //Renderer uses dispatcher for type-specific behavior Renderer //Dispatcher method - entry point render() as dispatcher -> shape as Shape <- rtn as String: `Unknown shape: ${shape.name()}` //Specific handler for Circle render() -> shape as Circle <- rtn as String: `Circle with radius ${shape.radius()}` //Specific handler for Square render() -> shape as Square <- rtn as String: `Square with side ${shape.side()}` defines program Demo() stdout <- Stdout() renderer <- Renderer() shapes <- [Circle(5.0), Square(3.0)] //Compiler routes to correct overload automatically //NO instanceof, NO casting needed! for shape in shapes stdout.println(renderer.render(shape)) //EOF ``` - **Rest Service** - **Pattern**: rest-web-service - **Description**: REST service with HTTP method mapping - GET, POST, DELETE, PATCH, PUT via operators - **Keywords** - service - REST - GET - POST - DELETE - PATCH - PUT - PATH - REQUEST - CONTENT - CONTEXT - HTTPResponse - HTTPContext - **Code**: ```ek9 #!ek9 defines module example.restservice defines function //Helper to create a non-cacheable HTTP response base plainResponse() <- rtn as HTTPResponse: () with trait of HTTPResponse override cacheControl() as pure <- rtn as String: "no-store,max-age=0" override contentType() as pure <- rtn as String: "application/json" override contentLanguage() as pure <- rtn as String: "en" default operator ? defines service //Service mapped to /items URI path Items :/items open //GET with path parameter - maps to /items/{item-id} byId() as GET for :/{item-id} -> id as String :=: PATH "item-id" <- response as HTTPResponse: (id: id, base: plainResponse()) with trait of HTTPResponse by base override status() as pure <- rtn as Integer: id? <- 200 : 404 override content() <- rtn as Bytes: Bytes() default operator ? //POST - operator += maps to HTTP POST operator += :/ -> request as HTTPRequest :=: REQUEST <- response as HTTPResponse: (request: request, base: plainResponse()) with trait of HTTPResponse by base status as Integer: 201 override content() <- rtn as Bytes: request.content() override status() as pure <- rtn as Integer: status default operator ? //DELETE - operator -= maps to HTTP DELETE operator -= :/{id} -> id as String <- response as HTTPResponse: (base: plainResponse()) with trait of HTTPResponse by base override status() as pure <- rtn as Integer: 204 default operator ? //PATCH - operator :~: maps to HTTP PATCH (merge) operator :~: :/{id} -> id as String content as String :=: CONTENT <- response as HTTPResponse: (base: plainResponse()) with trait of HTTPResponse by base override status() as pure <- rtn as Integer: 204 default operator ? //PUT - operator :^: maps to HTTP PUT (replace) operator :^: :/{id} -> id as String content as String :=: CONTENT <- response as HTTPResponse: (base: plainResponse()) with trait of HTTPResponse by base override status() as pure <- rtn as Integer: 200 default operator ? //GET all items at root URI listAll() :/ <- response as HTTPResponse: (base: plainResponse()) with trait of HTTPResponse by base override content() <- rtn as Bytes: Bytes("[]", "utf-8") default operator ? //EOF ``` - **Text Properties** - **Pattern**: text-i18n-localization - **Description**: Text constructs for internationalization - locale-specific templates with string interpolation - **Keywords** - text - locale - i18n - interpolation - backtick - template - **Code**: ```ek9 #!ek9 defines module example.text defines record Person firstName as String: String() lastName as String: String() Person() -> firstName as String lastName as String this.firstName :=? firstName this.lastName :=? lastName operator $ as pure <- rtn as String: `${firstName} ${lastName}` default operator ? defines text for "en" WelcomePage //Backticks for interpolated template strings greeting() -> person as Person `Welcome ${person.firstName} ${person.lastName}` //Quotes for literal (non-interpolated) text mainMessage() "Welcome to our application." //Escaped $ with \$ for literal dollar sign priceInfo() -> amount as String `Total: \$${amount}` Validator tooShort() -> input as String `The value '${input}' is too short` defines text for "de" WelcomePage greeting() -> person as Person `Willkommen ${person.firstName} ${person.lastName}` mainMessage() "Willkommen in unserer Anwendung." priceInfo() -> amount as String `Gesamt: \$${amount}` Validator tooShort() -> input as String `Der Wert '${input}' ist zu kurz` defines program Demo() stdout <- Stdout() person <- Person("Steve", "Limb") //Runtime locale selection via constructor argument english <- WelcomePage("en") stdout.println(english.greeting(person)) stdout.println(english.mainMessage()) stdout.println(english.priceInfo("42.00")) //Switch to German locale deutsch <- WelcomePage("de") stdout.println(deutsch.greeting(person)) //Validation messages follow same locale pattern validator <- Validator("en") stdout.println(validator.tooShort("Hi")) //EOF ``` - **Component Application** - **Pattern**: component-application-di - **Description**: Components, applications, and dependency injection - register bindings and inject with '!' - **Keywords** - component - application - register - injection - DI - **Code**: ```ek9 #!ek9 defines module example.components defines trait Logger log() abstract -> message as String DataStore save() abstract -> content as String <- rtn as Boolean? load() abstract -> id as String <- rtn as String? defines component //Abstract component - defines injection interface AppLogger as abstract logger() as pure abstract <- rtn as Logger? //Concrete component - provides implementation ConsoleLogger is AppLogger override logger() as pure <- rtn as Logger: () with trait of Logger override log() -> message as String Stdout().println(message) default operator ? //Abstract data store component AppDataStore as abstract store() as pure abstract <- rtn as DataStore? //In-memory implementation MemoryDataStore is AppDataStore override store() as pure <- rtn as DataStore: () with trait of DataStore override save() -> content as String <- rtn as Boolean: content? override load() -> id as String <- rtn as String: `Data for ${id}` default operator ? defines application //Application wires components together //register creates singleton bindings: implementation as abstract type MyApp register ConsoleLogger() as AppLogger register MemoryDataStore() as AppDataStore defines program //Program with DI - 'with application of' enables injection Demo() with application of MyApp //The '!' suffix marks fields for dependency injection appLogger as AppLogger! dataStore as AppDataStore! logger <- appLogger.logger() store <- dataStore.store() logger.log("Starting application") if store.save("test payload") logger.log("Data saved successfully") result <- store.load("item-1") if result? logger.log(`Loaded: ${result}`) //EOF ``` - **Transaction Management** - **Pattern**: transaction-management - **Description**: Transaction trait for ACID-compliant operations; usable as a try-with-resources resource, but close does not roll back - roll back explicitly - **Keywords** - transaction - commit - rollback - Closeable - ACID - try-with-resources - Transaction - **Code**: ```ek9 #!ek9 defines module example.transaction defines class //Implement the Transaction trait - its commit() and rollback() do nothing by default //Transaction extends Closeable, so it can be the resource of a try block DatabaseTransaction with trait of Transaction committed <- false override commit() committed: true override rollback() committed: false override isCommitted() as pure <- rtn as Boolean: Boolean(committed) default operator ? defines function failingUpdate() -> txn as DatabaseTransaction if not txn.isCommitted() throw Exception("update refused") defines program Demo() stdout <- Stdout() //close is called at the end of the try, but it does NOT roll back: //operator close is pure, so it cannot call rollback() - roll back explicitly try -> txn <- DatabaseTransaction() stdout.println("Processing...") txn.commit() stdout.println(`Committed: ${txn.isCommitted()}`) catch -> ex as Exception stdout.println(`Failed: ${ex}`) //A closeable held in a named local is a try resource; the inner try rolls it back on failure try -> second <- DatabaseTransaction() try failingUpdate(second) second.commit() catch -> ex as Exception second.rollback() stdout.println(`Rolled back after: ${ex.reason()}, committed: ${second.isCommitted()}`) //EOF ``` - **Networking Types** - **Pattern**: networking-types-and-security - **Description**: Network types: IPAddress, CIDR, Hostname, Networking utility, HTTPContext trait, Cookie record, SecurityGate and CORSPolicy abstract functions - **Keywords** - IPAddress - CIDR - Hostname - Networking - HTTPContext - Cookie - SecurityGate - CORSPolicy - network - IP - HTTP - security - **Types** - **IP Address**: IPv4/IPv6 address. Predicates: isV4, isV6, isLoopback, isPrivate, isMulticast, isLinkLocal, isAnyLocal, version. Literal parsing only (no DNS). Zone IDs rejected. - **CIDR**: Network range (address/prefix). Methods: networkAddress, prefixLength, contains (operator) for IPAddress, includesNetwork for CIDR, isV4, isV6. Host bits silently normalized. - **Hostname**: RFC 1123 hostname. Methods: labels (returns List of String), resolve (returns IPAddress). IDN support, auto-lowercase, trailing dot stripped. - **Networking**: Stateless gateway. Constants: loopbackV4/V6, anyV4/V6, privateA/B/C, loopbackV4Net. Env gateway: ipFromEnv, cidrFromEnv, hostnameFromEnv. Composition: cidrOf, resolve. - **HTTP Context**: HTTP request context trait. Methods: principal, isAuthenticated, isSecure, remoteAddress, requestId, header(name), contentType, withPrincipal(identity), cookie(name). Always set. - **Cookie**: HTTP cookie record with secure defaults. Fields: name, cookieValue, maxAge, path, domain, secure (true), httpOnly (true), sameSite (true=Strict). RFC 6265 encoding via $ operator. - **Security Gate**: Abstract function: (HTTPContext) -> HTTPContext?. Returns enriched context on success, unset on rejection (401). Default rejects all. Use: MyGate is SecurityGate. - **CORS Policy**: Abstract function: (String) -> Boolean?. Returns true = origin allowed, false = rejected, unset = no decision (rejects). Default rejects all. ## Keyword Index - **Description**: Quick lookup of keywords to relevant inline examples - **Program** - helloWorld - simpleFunction - **Module** - helloWorld - constants - **Function** - simpleFunction - abstractFunction - **Class** - simpleClass - classExtension - dynamicFunctions - dynamicClasses - **Record** - records - **Trait** - traitComposition - dynamicClasses - **Type** - enumeration - constants - **Enum** - enumeration - **Extends** - classExtension - records - **Override** - classExtension - traitComposition - **Abstract** - abstractFunction - classExtension - **Pure** - simpleFunction - abstractFunction - **If** - ifStatement - **Switch** - switchStatement - enumeration - **Given** - switchStatement - **For** - forLoop - enumeration - **While** - whileLoop - **Try** - tryCatch - **Catch** - tryCatch - **Finally** - tryCatch - **Cat** - streams - **Filter** - streams - **Map** - streams - **Head** - streams - **Tail** - streams - **Collect** - streams - **Pipe** - streams - **Stream** - streams - **Of** - generics - **List** - generics - streams - **Dict** - generics - **Optional** - generics - **Generic** - generics - **Dynamic** - dynamicFunctions - dynamicClasses - **Capture** - dynamicFunctions - dynamicClasses - **Closure** - dynamicFunctions - dynamicClasses - **Tuple** - dynamicClasses - **By** - dynamicClasses - **With Trait Of** - dynamicClasses - **Ternary** - ternaryOperators - **Coalesce** - ternaryOperators - **??** - ternaryOperators - **?:** - ternaryOperators - **Dispatcher** - dispatcherPattern - tryCatch - **Sanitized** - sanitization - **Constant** - constants - **Operator** - simpleClass - records - enumeration - **Guard** - ifStatement - switchStatement - whileLoop - **Service** - restService - **REST** - restService - **GET** - restService - **POST** - restService - **DELETE** - restService - **PATCH** - restService - **PUT** - restService - **PATH** - restService - **REQUEST** - restService - **CONTENT** - restService - **HTTP Response** - restService - **HTTP Request** - restService - **HTTP Context** - restService - networkingTypes - **CONTEXT** - restService - **IP Address** - networkingTypes - **CIDR** - networkingTypes - **Hostname** - networkingTypes - **Networking** - networkingTypes - **Cookie** - networkingTypes - **Security Gate** - networkingTypes - **CORS Policy** - networkingTypes - **Network** - networkingTypes - **Text** - textProperties - **I18n** - textProperties - **Locale** - textProperties - **Interpolation** - textProperties - **Template** - textProperties - **Component** - componentApplication - **Application** - componentApplication - **Register** - componentApplication - **Injection** - componentApplication - **DI** - componentApplication - **Transaction** - transactionManagement - componentApplication - **Transaction** - transactionManagement - **Commit** - transactionManagement - **Rollback** - transactionManagement - **ACID** - transactionManagement - **$** - simpleClass - records - **$$** - records - **Default** - simpleClass - records - enumeration - **JSON** - records - restService - **Default Operator** - simpleClass - records ## MCP Oracle Tools - **Description**: MCP tools available via ek9 -mcp or ek9 -ide for AI agents to query the compiler. All tools return structured JSON. - **Discovery** - **Name**: ek9_find_construct - **Description**: Find type constructs (classes, traits, records, functions, components) by unqualified name across all compiled modules - **Parameters** - **Name**: string (required) — the unqualified type name to search for - **Returns**: Array of matches with fullyQualifiedName, moduleName, genus, sourceFile, line - **Usage**: Use when you know a type name but not its module or file location - **Location Based** - **Name**: ek9_query_symbol - **Description**: Query symbol information at a specific source location - **Parameters** - **File**: string - **Line**: integer - **Returns**: Symbol name, type, genus, category, squirrelled metadata - **Name**: ek9_query_construct - **Description**: Query complete structure of class/record/trait/component at a location - **Parameters** - **File**: string - **Line**: integer - **Returns**: Fields, methods, operators, constructors, inheritance, metrics - **Name**: ek9_query_type_hierarchy - **Description**: Query type hierarchy including super chain, traits, discovered subtypes, and source locations - **Parameters** - **File**: string - **Line**: integer - **Returns**: Super chain, all traits, sealed status, discovered subtypes with source locations, type parameters - **Name**: ek9_query_scope - **Description**: Query scope chain and active guards at a location - **Parameters** - **File**: string - **Line**: integer - **Returns**: Scope chain, active guards, purity constraints, variable visibility - **Name**: ek9_query_dispatchers - **Description**: Query dispatcher handler methods for a dispatcher class - **Parameters** - **File**: string - **Line**: integer - **Returns**: Dispatcher status, dispatch methods, handler list, sealed type info - **Name**: ek9_query_flow - **Description**: Query function/method complexity and flow analysis metrics - **Parameters** - **File**: string - **Line**: integer - **Returns**: Cyclomatic complexity, statement count, nesting depth, purity, expression complexity - **Name**: ek9_query_call_graph - **Description**: Query call graph showing callers and callees of a function/method - **Parameters** - **File**: string - **Line**: integer - **Returns**: Callers, callees, parameters, return type, purity - **Name**: ek9_query_effective_api - **Description**: Query effective API of a type including inherited methods - **Parameters** - **File**: string - **Line**: integer - **Returns**: All callable methods (own + inherited), operators, total count - **Name**: ek9_query_module - **Description**: Query module-level constructs listing - **Parameters** - **File**: string - **Line**: integer - **Returns**: Module name, all constructs with name/genus/line, references - **Name**: ek9_query_pipeline - **Description**: Query stream pipeline analysis - **Parameters** - **File**: string - **Line**: integer - **Returns**: Pipeline stages, source type, terminal operation, element types - **Name**: ek9_query_references - **Description**: Find all references to a symbol across all compiled modules - **Parameters** - **File**: string - **Line**: integer - **Returns**: Declaration location, type references, extends, trait implementations, field types - **Program Level** - **Name**: ek9_query_diagnostics - **Description**: Query compilation diagnostics (errors, warnings) - **Parameters** - **Returns**: Diagnostic list with file, line, severity, message, error code - **Name**: ek9_query_workspace - **Description**: Query workspace structure and source file listing - **Parameters** - **Returns**: Source files, module names, compilation state - **Name**: ek9_query_compilation_state - **Description**: Query current compilation state and phase - **Parameters** - **Returns**: Current phase, compilation status, error count - **Name**: ek9_query_di_wiring - **Description**: Query DI wiring: applications, registrations, injection sites, programs, health - **Parameters** - **Application**: string (optional) — filter to specific application - **Returns**: Applications with registrations, injection sites, satisfaction status, health verdict - **Name**: ek9_query_module_dependencies - **Description**: Query module dependency graph with cross-module references and cycle detection - **Parameters** - **Include Builtins**: boolean (optional, default false) - **Returns**: Modules, dependency edges with kinds and counts, graph metrics - **Utility** - **Name**: ek9_compile - **Description**: Compile the workspace (required before query tools) - **Parameters** - **Returns**: Compilation result with phase reached and error count - **Name**: ek9_version - **Description**: Get EK9 compiler version - **Parameters** - **Returns**: Version string - **Name**: ek9_help - **Description**: Get help on EK9 topics, types, or error codes - **Parameters** - **Topic**: string - **Returns**: Help text for the topic - **Name**: ek9_explain_error - **Description**: Get detailed explanation for an error code - **Parameters** - **Code**: ```ek9 string ``` - **Returns**: Error diagnosis, fix actions, examples - **Name**: ek9_paradigm - **Description**: Get EK9 language paradigm summary - **Parameters** - **Returns**: Design decisions, excluded features, philosophy - **Name**: ek9_ask - **Description**: Search Q&A knowledge base - **Parameters** - **Query**: string - **Returns**: Matching Q&A entries with code examples - **Name**: ek9_format - **Description**: Format EK9 source file - **Parameters** - **File**: string - **Returns**: Formatted source ## Oracle Editing Tools - **Description**: Oracle editing tools that modify EK9 source code. AI agents call these instead of generating EK9 syntax directly. Each tool returns a Changeset with preview, conflicts, and source edits. The concept-to-tool pipeline: (1) ask Q&A about concept, (2) receive syntax + oracleToolHint, (3) call editing tool to produce correct code. - **Principle**: The AI handles intent (what to create/modify); the Oracle handles correctness (EK9 syntax, indentation, insertion position, operator signatures). The model never generates EK9 tokens from its own weights for structural operations. - **Implemented** - **Name**: ek9_add_field - **Description**: Add a typed field to a class, record, or component with genus-specific rules - **Parameters** - **Construct Name**: string — unqualified type name - **Field Name**: string — new field name - **Type Name**: string — field type (e.g., String, Integer) - **Default Value**: string (optional) — initial value (required for records) - **Raw Declaration**: string (alternative) — full EK9 field declaration (e.g., 'name as String!') - **Genus Rules**: Records: public, must have default. Classes: private, default optional. Components: use '!' suffix for injection. - **Validation**: Grammar-validated via ANTLR parser (FieldDeclarationValidator). Rejects duplicates. - **Name**: ek9_add_method - **Description**: Add a method to a class, trait, component, or service with correct signature and body - **Parameters** - **Construct Name**: string — unqualified type name - **Name**: string — method name - **Parameters**: array of {name, type} — method parameters - **Return Type**: string (optional) — return type - **Pure**: boolean — mark as pure - **Is Abstract**: boolean — abstract (traits/abstract classes only) - **Override**: boolean — override inherited method - **Generates**: Method declaration with parameters, return value declaration, require statements for params, and '//AI: Implement' comment in body for local LLM to fill. - **Validation**: Checks genus supports methods. Rejects duplicate signatures (name + parameter types). - **Name**: ek9_add_operator - **Description**: Add operators to classes/records in three modes: all defaults, single default, or full skeleton - **Parameters** - **Construct Name**: string — unqualified type name - **Mode**: 'all_defaults' | 'default' | 'skeleton' - **Operator Symbol**: string (for default/skeleton) — operator symbol (e.g., '==', '$', '?', '<=>') - **Modes** - **All_defaults**: Inserts 'default operator' — compiler generates all standard operators - **Default**: Inserts 'default operator X' — compiler generates single operator - **Skeleton**: Generates full field-based implementation with correct operator signature - **Skeleton Operators**: ?, $, ==, <>, <=>, #?, :=: — each with field-by-field logic. Auto-detects override from super chain. - **Name**: ek9_add_constructor - **Description**: Add constructor to class/record with 5 modes and purity enforcement - **Parameters** - **Construct Name**: string — unqualified type name - **Mode**: 'private_no_arg' | 'default_all' | 'skeleton_all' | 'from_fields' | 'make_all_pure' - **Field Names**: array of string (for from_fields mode) — subset of fields - **Pure**: boolean (for skeleton_all mode) - **Modes** - **Private_no_arg**: Blocks default construction: 'default private ConstructorName()' - **Default_all**: Compiler-generated from all fields (no body) - **Skeleton_all**: Explicit body with ':=:' copy per field - **From_fields**: Constructor from field subset, auto-determines purity - **Make_all_pure**: Retrofits 'as pure' on all existing constructors - **Purity Rule**: If any existing constructor is pure, all new ones must be pure. - **Name**: ek9_expand_default_operator - **Description**: Expand 'default operator' or 'default operator X' into full skeleton body - **Parameters** - **Construct Name**: string — unqualified type name - **Operator Symbol**: string — specific operator, or 'all' for batch expansion - **Cursor Line**: integer — line containing the default operator declaration - **Generates**: Full field-based operator implementation replacing the default declaration. - **Name**: ek9_collapse_to_default_operator - **Description**: Replace explicit operator implementation with 'default operator X' (inverse of expand) - **Parameters** - **Construct Name**: string — unqualified type name - **Operator Symbol**: string — operator to collapse - **Cursor Line**: integer — line containing the operator declaration - **Defaultable Operators** - <=> - == - <> - < - <= - > - >= - ? - $ - $$ - #? - :=: - **Name**: ek9_organize_references - **Description**: Remove unused type references from the references block (like 'optimize imports') - **Parameters** - **File**: string — source file path - **Generates**: Deletes unused reference lines. Removes entire references block if all unused. - **Name**: ek9_toggle_block_comment - **Description**: Toggle EK9 block comments ('') around a line range - **Parameters** - **Start Line**: integer — first line (1-based) - **End Line**: integer — last line (1-based) - **Generates**: Adds markers if uncommented, removes if commented. Rejects nesting. - **Name**: ek9_surround_with - **Description**: Wrap selected lines in a control flow construct with correct indentation and //AI: placeholder comments - **Parameters** - **Source Text**: string — full source text - **File Name**: string — source file name - **Start Line**: integer — first line of selection (1-based) - **End Line**: integer — last line of selection (1-based) - **Type**: 'IF' | 'IF_ELSE' | 'TRY_CATCH' | 'TRY_CATCH_FINALLY' | 'FOR_RANGE' | 'WHILE' - **Generates**: Re-indents selected lines +2 spaces inside the control flow wrapper. Adds '//AI:' comments at each placeholder (condition, else branch, catch handler, finally cleanup, range bounds). - **Note**: Critical in EK9 where indentation defines scope — manual re-indentation is error-prone. - **Designed Now Shipped** - **Note**: Where the tools this section once listed as designed went: all but one shipped, mostly under different names. The full live list (180 tools) is ek9_query_tool_catalogue, or 'ek9 -tool' on the command line. - **Ek9_scaffold**: ek9_new_construct (programs: ek9_add_program; applications: ek9_add_application) - **Ek9_implement**: ek9_implement_methods (adopting a trait the class does not yet declare: ek9_adopt_trait) - **Ek9_add_member**: ek9_add_method / ek9_add_field / ek9_add_constructor / ek9_add_operator - **Ek9_rename_symbol**: ek9_rename_symbol - **Ek9_generate_dispatcher**: ek9_generate_dispatcher - **Ek9_generate_delegation**: ek9_generate_delegation - **Ek9_convert_to_stream**: ek9_advise_loop (advises; never rewrites a loop automatically) - **Ek9_convert_to_guard**: ek9_apply_guard_pattern - **Ek9_insert_guard**: ek9_apply_guard_pattern - **Ek9_make_pure**: ek9_change_signature / ek9_fix_purity_contract - **Ek9_insert_coalescing**: ek9_insert_coalescing - **Ek9_insert_pipeline**: not built: ek9_advise_loop says when a loop reads better as a stream pipeline - **Ek9_insert_dynamic_function**: ek9_create_dynamic_function - **Ek9_insert_dynamic_class**: ek9_create_dynamic_class