# EX Script — Language Specification

Status: **prototype (0.1)**. This spec is normative for the compiler in this
repository. Grammar is informal but precise; the parser is the reference
implementation.

## 1. Lexical structure

- Identifiers: `[A-Za-z_][A-Za-z0-9_]*`. Reserved words below may not be
  identifiers.
- Integers: `[0-9](_?[0-9])*` with optional `-` (unary minus). Range: safe
  integers (|n| ≤ 2^53 − 1); out-of-range is a compile error.
- Floats: integer form with `.` and optional fraction/exponent
  (`3.14`, `1e9`, `2.5e-3`). A bare `1.` is invalid (use `1.0`).
- Strings: `"..."` with escapes `\n \t \r \" \\ \u{XXXX} \{` (escaped brace).
  Interpolation: `{expr}` inside double quotes parses a full expression.
- Chars: `'x'` — exactly one Unicode code point (or one escape).
- Triple-quoted strings: `"""..."""` — verbatim, no escapes except `\{`?
  Verbatim, allows newlines and quotes.
- Comments: `//` to end of line; `/* ... */` (non-nesting).
- Whitespace-insensitive (brace-based layout).

Reserved words:
`let mut fun async await if else match for in while return or raise try catch
struct enum type contract schema class import from export test is as and not
true false undefined Ok Err`

Operators: `+ - * / % == != < <= > >= && || ! ` `?. .. ..= = += -= *= /= ->
. , : ( ) [ ] { } ? !` `|` (variant separator) `::`? — no `::`; qualification
is `.`.

## 2. Program structure

```
program     := { topLevel }
topLevel    := importDecl | exportDecl | decl | testDecl
importDecl  := "import" modulePath [ "as" ident ]
             | "from" modulePath "import" ident { "," ident }
modulePath  := "std" "." ident { "." ident }          — standard library
             | "pkg" ":" ident                        — package dependency
             | "js" ":" ident                         — JavaScript module
             | stringLiteral                           — relative file
exportDecl  := "export" decl
decl        := letDecl | funDecl | structDecl | enumDecl | typeDecl
             | contractDecl | schemaDecl | classDecl
```

Each file is a module. Top-levels execute in dependency order at startup;
module init runs once.

## 3. Declarations

### let

```
letDecl := "let" ["mut"] ident [":" typeExpr] "=" expr
```

- `let` = immutable binding (compile error on reassignment).
- `let mut` = mutable; `=`/`+=`/`-=`/`*=`/`/=`/`%=` allowed.
- Type annotation optional; when present, initializer must be assignable.

### fun

```
funDecl := ["async"] "fun" ident "(" [params] ")" [ "->" typeExpr ] body
params  := param { "," param }
param   := ident ":" typeExpr
body    := "=" expr | block
```

- Return type optional (inferred). `!` marks fallible (`Result`).
- Block body: last expression is the value; `return expr` exits early.
- `async fun` returns a promise of its return type.

### struct

```
structDecl := "struct" ident "{" { ident ":" typeExpr } "}"
```

- Constructed `Name(a, b)` positionally or `Name { field: v }` by name.
- Field access `x.field`. Methods are free functions taking the struct first
  (no `impl` blocks); method-call sugar `x.m(args)` resolves to a free function
  `m(x, args)` when a `fun m(self: T, ...)` exists in scope or the module.

### enum

```
enumDecl := "enum" ident "{" ident { "," ident } "}"
```

- Values: `Name.Variant`. Runtime: frozen string. Matching is exhaustive.

### type (discriminated union)

```
typeDecl := "type" ident "=" "|"? variant { "|" variant }
variant  := ident [ "(" params ")" ]
```

- Variants without payloads are like enum members; with payloads, `Name(fields)`.
- Runtime: `{ tag: "Name", field: ... }`. Matching is exhaustive; `_` allowed.
- Construction: `Circle(1.0)` or `Circle { radius: 1.0 }`.

### contract

```
contractDecl := "contract" ident [ "<" typeParam { "," typeParam } ">" ]
                "{" { ident "(" [params] ")" "->" typeExpr } "}"
```

- Structural: any value whose shape matches satisfies the contract.
  Used for function parameter types and generic constraints.

### schema

```
schemaDecl := "schema" ident "{" { fieldDecl } "}"
fieldDecl  := ident ":" schemaType [ "(" constraints ")" ]
schemaType := "String" | "Int" | "Float" | "Bool" | "Bytes"
            | schemaType "?" | "List" "<" schemaType ">"
            | ident  (other schema/enum/union/struct types, validated structurally)
constraints:= constraint { "," constraint }
constraint := "min" ":" expr | "max" ":" expr | "minLen" ":" expr
            | "maxLen" ":" expr | "pattern" ":" stringLiteral
            | "default" ":" expr
```

- Generates static type + runtime validator + `Schema.parse(String) -> Schema!`
  + `Schema.from(Any) -> Schema!`.
- Built-in format types usable in schema fields: `Email`, `Url`, `Uuid`.
- `default` fills missing JSON keys; non-optional fields without defaults are
  required.
- `parse` accepts JSON text; `from` accepts `Any`. Errors are `Err` with a
  precise message naming the path of the offending field.

### class

```
classDecl := "class" ident "{" { classMember } "}"
classMember := ["mut"] ident ":" typeExpr "=" expr   — field
             | ["async"] "fun" ident "(" [params] ")" [":" typeExpr] body
```

- Fields private by default; `export` on a field exposes a getter.
- Methods access `this`. Classes are stateful by definition; prefer
  `struct` + free functions unless state is genuinely encapsulated.

## 4. Statements

```
block      := "{" { stmt } "}"
stmt       := letDecl | exprStmt | ifStmt | matchStmt | forStmt | whileStmt
            | returnStmt | raiseStmt | tryStmt | withStmt
exprStmt   := expr  (must have effects — pure expressions warn)
returnStmt := "return" [expr]
raiseStmt  := "raise" expr
ifStmt     := "if" expr block [ "else" ( block | ifStmt ) ]
matchStmt  := "match" expr "{" { matchArm } "}"
matchArm   := pattern "->" ( expr | block ) [","]
forStmt    := "for" pattern "in" expr block
whileStmt  := "while" expr block
tryStmt    := "try" block "catch" ident block
withStmt   := "with" "(" letDecl ")" block
```

## 5. Patterns

```
pattern   := ident                     — binding
           | "_"                       — wildcard
           | literal                   — literal match (Int/Float/String/Char/Bool)
           | "Some" "(" pattern ")"    — optional present
           | "None"                    — optional absent
           | "Ok" "(" pattern ")"      — result success
           | "Err" "(" [ident] ")"     — result failure
           | Variant [ "(" patterns ")" ]  — union variant / enum member
           | "is" typeExpr             — type test pattern
           | "(" pattern { "," pattern } ")"  — tuple
           | pattern "or" pattern      — alternative (same bindings)
```

- `if let` uses a single pattern: `if let pat = expr { } else { }`.
- Bindings introduced by patterns are narrowed per the pattern.

## 6. Expressions

```
expr        := assignment
assignment  := postfix ( "=" | "+=" | "-=" | "*=" | "/=" | "%=" ) expr
             | postfix
postfix     := primary { postfixOp }
postfixOp   := "(" [args] ")" | "[" expr "]" | "." ident | "?." ident
primary     := literal | ident | "(" expr ")" | "(" expr "," expr { "," expr } ")"
             | listLiteral | mapLiteral | structLiteral | funLiteral
             | "if" expr block ["else" (block | if)] | "match" expr "{" ... "}"
             | "await" postfix | "Ok" "(" [expr] ")" | "Err" "(" expr ")"
listLiteral := "[" [expr { "," expr }] "]"
mapLiteral  := "{" [stringLiteral ":" expr { "," ... }] "}"
structLiteral := ident "{" ident ":" expr { "," ... } "}"
funLiteral  := "fun" "(" [params] ")" [ "->" typeExpr ] ("=" expr | block)
```

Operator precedence (low → high):

```
or               lazy default (left-assoc)
||  &&           boolean (left-assoc)
== != < <= > >=  comparison
is               type test
+ -              additive
* / %            multiplicative
- not  (prefix)  unary
postfix          call [ ] . ?.
```

`not` and `and` are aliases for `!` and `&&`. `!` suffix on types only; prefix
`!` is `not`.

## 7. Types

```
typeExpr := baseType [ "?" ] [ "!" ]
baseType := "Int" | "Float" | "Bool" | "String" | "Char" | "Bytes"
          | "Void" | "Any" | "Err"
          | "List" "<" typeExpr ">"
          | "Map" "<" typeExpr "," typeExpr ">"
          | "Set" "<" typeExpr ">"
          | "(" typeExpr { "," typeExpr } ")"          — tuple
          | "(" [params] ")" "->" typeExpr             — function
          | ident [ "<" typeExpr { "," typeExpr } ">" ]  — named
```

- `T?` optional (may be `undefined`); `T!` result (may be `Ok`/`Err`).
- `Void` for no value. `Any` dynamic.
- Function types: `(A, B) -> R`; optional params not allowed (use `?` type).
  The `!` in `-> T!` marks a fallible function (return type `Result<T>`).

## 8. Semantics (normative)

1. **No null.** `null` is not a token. JS interop converts `null` to
   `undefined` at boundaries.
2. **Immutability.** `let` bindings cannot be reassigned.
3. **Optionality.** `T?` is not assignable to `T`. Access to the value of `T?`
   requires `if let`, `match`, `or`, or `.require(msg)`.
4. **Results.** `T!` values must be handled before drop: `?`, `match`,
   `or`, `if let`, `.require`. `?` and `raise` are only valid inside `!`
   functions (or `try` blocks at the JS boundary). `?` on a `Result` in an
   `async fun` propagates the error from the promise.
5. **Exhaustiveness.** `match` over unions/enums/optionals/results/bools must
   cover all shapes or include `_`. The checker proves it.
6. **Structural typing.** Assignability per §9. No nominal requirements.
7. **Generics.** Type parameters unify from arguments; `List<A>` assignable to
   `List<B>` iff `A` assignable to `B` (inserts checked statically).
8. **Narrowing.** `match`, `if let`, and `is` narrow types in their branches;
   narrowing is monotone within a branch.
9. **Async.** `await` only in `async fun` or `async` lambdas. `all`/`race`/
   `timeout` operate on futures. `all` fails fast (cancels siblings);
   `timeout(ms, fut)` fails with `Err("timed out after Nms")` on expiry.
10. **Schemas.** `parse`/`from` validate every field; on failure the `Err`
    message contains the JSON path (`users[3].email`). Non-`Any` typed data
    never passes through without validation.
11. **Modules.** Everything private unless `export`. Relative imports omit the
    `.xan` extension. Import cycles are compile errors. `std.*` modules are
    compiled from EX source by the toolchain.
12. **Tests.** `test "name" { ... }` blocks are executable; failures print the
    assertion, expected vs actual, and location. Async tests supported.
    Modules containing `test` blocks implicitly import `std.test`, so
    `expect` is available without an explicit import.
13. **Entry.** `export fun main()` (or `async`); optional `(args: List<String>)`
    parameter; `-> Int` exit code (default 0).

## 9. Assignability (normative)

`A` assignable to `B` (written `A <: B`) iff:

1. `A === B`, or
2. `B` is `Any`, or `A` is `Any`, or
3. `A = T?`, `B = U?` and `T <: U`; or `A = T`, `B = U?` and `T <: U`
   (wrapping in optional is allowed; unwrapping is not), or
4. `A = T!`, `B = U!` and `T <: U` and error types compatible; `T!` is not
   assignable to `T` (result must be handled), or
5. `A` is a union variant of `B` (union), or
6. both are function types: params of `A` assignable from params of `B`
   (contravariant), result of `A` assignable to result of `B` (covariant),
   same arity, or
7. tuples: same arity, memberwise assignable, or
8. `List`/`Set`: elementwise assignable. `Map`: key and value assignable, or
9. struct/class/schema → contract or struct: every member of `B` exists in `A`
   with assignable type; `A` may have extra members, or
10. `A` is a struct, `B` is a tuple of the same arity as `A`'s fields,
    memberwise assignable (positional compatibility), or
11. numeric literal types: `Int` and `Float` — `Int <: Float` (widening
    allowed, narrowing never implicit).

Optionality is exact in the narrowing direction: `String?` is *not* assignable
to `String` in any context (rule 3 is one-directional).

## 10. Diagnostics

Codes: `E1000–E1999` type errors; `E2000+` syntax; `E3000+` name resolution;
`E4000+` result safety; `E5000+` exhaustiveness; `E6000+` async/concurrency;
`E7000+` interop; `E8000+` tooling. Warnings `W1xxx`.

Format:

```
error[E1007]: <what>
  ┌─ file:line:col
  │
NN │ <source line>
  │ <caret> <detail>
  │
  <why — plain language>

  Possible fixes:
  1. <fix with code example>
```

## 11. Conformance

A compiler conforms to this spec when it: (a) accepts every program in the
spec examples and examples/; (b) rejects every program with a diagnostic per
§10 and §8 rules; (c) produces JS whose observable behavior matches §8 and the
Runtime Model. The test suite in `tests/` is the conformance suite.