Karl logo karl docs / specs

1. Scope and Sources

This page is the implementation-facing summary. Canonical normative specifications are in: SPECS/language.md, SPECS/interpreter.md, SPECS/stream.md, and SPECS/process.md.

2. Grammar and Parsing

Karl is expression-based. Assignments, loops, and recover blocks are expressions that produce values. Parsing is Pratt-style with explicit operator precedence.

2.1 Core Grammar (EBNF)

Canonical source: SPECS/language.md. This EBNF is the practical parser-facing subset used by docs readers.

program         = { statement } ;

statement       = let_stmt | expr_stmt ;
let_stmt        = "let" pattern "=" expr [ ";" ] ;
expr_stmt       = expr [ ";" ] ;

expr            = if_expr
                | match_expr
                | for_expr
                | lambda_expr
                | loop_ctrl
                | assign ;

if_expr         = "if" expr block [ "else" ( if_expr | block | expr ) ] ;
match_expr      = "match" expr "{" { match_arm } "}" ;
match_arm       = "case" pattern [ "if" expr ] "->" expr ;
for_expr        = "for" expr [ "with" for_bindings ] block [ "then" then_block ] ;
for_bindings    = binding { "," binding } ;
binding         = pattern "=" expr ;
then_block      = block | expr ;

lambda_expr     = binder "->" expr ;
binder          = pattern | "(" [ pattern { "," pattern } ] ")" ;
loop_ctrl       = "break" [ expr ] | "continue" ;

assign          = logic_or | lvalue assign_op expr ;
lvalue          = IDENT { ( "." IDENT | "[" expr "]" ) } ;
assign_op       = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ;
logic_or        = pipe_expr { "||" pipe_expr } ;
pipe_expr       = logic_and { "|" logic_and } ;
logic_and       = equality { "&&" equality } ;
equality        = comparison { ( "==" | "!=" | "eqv" ) comparison } ;
comparison      = range { ( "<" | "<=" | ">" | ">=" ) range } ;
range           = add [ ".." add [ "step" add ] ] ;
add             = mul { ( "+" | "-" ) mul } ;
mul             = unary { ( "*" | "/" | "%" ) unary } ;

unary           = ( "!" | "-" ) unary
                | wait_expr
                | import_expr
                | spawn_expr
                | postfix ;
wait_expr       = "wait" unary ;
import_expr     = "import" STRING ;
spawn_expr      = ( "&" | "spawn" ) spawn_target ;
spawn_target    = call_expr
                | pipe_expr
                | "{" [ call_expr { "," call_expr } ] "}" ;

postfix         = call_expr [ "?" expr ] ;
call_expr       = primary { call | member | index | inc_dec } ;
call            = "(" [ expr { "," expr } [ "," ] ] ")" ;
member          = "." ( IDENT | "then" ) ;
index           = "[" expr "]" ;
inc_dec         = "++" | "--" ;

primary         = literal | IDENT | "_" | "(" expr ")" | block | object | array | query_expr | race_expr | struct_init ;
block           = "{" { statement } [ expr ] "}" ;
object          = "{" [ object_entry { "," object_entry } [ "," ] ] "}" ;
object_entry    = IDENT [ ":" expr ] | "..." expr ;
array           = "[" [ expr { "," expr } [ "," ] ] "]" ;
race_expr       = ( "!&" | "race" ) "{" [ call_expr { "," call_expr } ] "}" ;
query_expr      = "from" IDENT "in" expr { "where" expr } [ "orderby" expr ] "select" expr ;
struct_init     = IDENT object ;

pattern         = "_" | literal | IDENT | range_pattern | object_pattern | array_pattern | tuple_pattern ;
object_pattern  = "{" [ pattern_entry { "," pattern_entry } [ "," ] ] "}" ;
pattern_entry   = IDENT [ ":" pattern ] ;
array_pattern   = "[" [ pattern { "," pattern } ] [ "," "..." pattern ] [ "," ] "]" ;
tuple_pattern   = "(" [ pattern { "," pattern } ] [ "," ] ")" ;
range_pattern   = literal ".." literal ;
literal         = NUMBER | STRING | CHAR | "true" | "false" | "null" | "()" ;

2.2 Notable parser constraints

3. Evaluation Semantics

4. Concurrency Model

let t = & (() -> work())()
let u = spawn (() -> work())() // alias
let value = wait t

let first = wait !& {
  (() -> slow())(),
  (() -> fast())(),
}

5. Stream Model

Streams are lazy and pull-driven. A pipeline is executed by attaching a sink, or by member reads on stream values.

5.1 Stream value kinds

5.2 Pipe operator

5.3 Stream member reads

let s = read("events.log", { type: TEXT, }) | lines()

for true with pair = s.read() {
    let [line, eof] = pair
    if eof { break () }
    log(line)
    pair = s.read()
} then ()

5.4 Backpressure

Sinks pull from upstream. Slow sinks slow the whole chain, preventing unbounded upstream drift. Runtime must not add implicit buffering that breaks this property.

6. Process Model

Process APIs expose external command execution as first-class handles. proc is non-blocking handle creation; run is blocking capture convenience.

let p = proc("kubectl", "logs", "-f", "deploy/api", {
    stdout: PIPE,
    stderr: NULL,
    stdoutType: TEXT,
})

let [line, eof] = p.stdout.read()
let st = wait p

6.1 Process forms

6.2 Process members

7. Error Model

Karl uses explicit recovery with postfix ?. Recover blocks receive implicit error with kind and message.

let cfg = fromJson(raw) ? {
    log(error.kind, error.message)
    { mode: "safe", }
}

8. Runtime Availability

Area CLI Native Bench (WASM)
Core language / arrays / maps / stringsYesYes
Tasks / channels / raceYesYes
Streams from channelsYesYes
Filesystem (readFile, writeFile, read(...))YesNo
Processes (proc, run, exec sink)YesNo
SQL built-insYesNo
signalWatchYesNo
httpServe serverYesNo

9. Current Constraints