29625e7262
The cma authoring-form harness corpus had gone dead against the language as it evolved since May. The plan modelled it as merely schema-dead (missing param_modes/ret_mode); it was also drift-dead in the example BODIES. Fixed in place, with `ail check` + both test suites as the oracle: - Schema: param_modes/ret_mode completed on every fn type; existing borrow annotations preserved (data_with_match's borrow over List). - Symbol drift: `<`/`==` -> `lt`/`eq` (operator-routing); the removed `io/print_int` op -> print_str(int_to_str n) followed by a newline print, preserving the trailing newline the references' expected_stdout needs. - Ownership/ADT restructures: data_simple's reuse-as now wraps the source in a match arm (ctor must be statically visible); data_with_match's count_via_letrec + local go switched borrow->own (consume-while-borrowed under the tightened ownership analysis; head_or_zero still exercises borrow over the boxed List). - param_modes_all rewritten to own (Int) + borrow over a boxed ADT -- (borrow Int) is now a borrow-over-value error. - Two new author-facing examples (loop_sum: Loop/Recur; new_rawbuf: New) cover the Term variants that landed since May. - spec_completeness.rs: drop the deleted ParamMode::Implicit; cover Loop/Recur/New; allowlist the non-authorable Term::Intrinsic out. - spec.md section 4 rewritten to own/borrow (mandatory, no implicit) with the borrow-over-value rule; rendered/ regenerated. - mock_full_run fixture's t3 turn-2 program migrated so the harness score assertions hold; usage fields untouched. Both render/ and harness/ cargo test suites green in mock mode; no live IONOS call. Run the harness budget/reference tests with AIL_BIN pointing at target/debug/ail (ail is not on PATH in the test env).
574 lines
23 KiB
Markdown
574 lines
23 KiB
Markdown
# AILang authoring reference
|
|
|
|
## 1. Header
|
|
|
|
AILang is a small data-as-source functional language designed for an
|
|
LLM author. This document is the complete authoring reference. Every
|
|
well-formed program is a module. Read the whole thing once before you
|
|
write code; the order of sections is the order of dependencies.
|
|
|
|
## 2. Modules and the on-disk form
|
|
|
|
A module is the top-level container. It has a name (matching the file
|
|
stem), an optional list of imports, and a flat list of definitions.
|
|
Every program you write is exactly one module. Cross-module imports
|
|
are out of scope for the tasks in this experiment — every task is a
|
|
single self-contained module with an empty `imports` array.
|
|
|
|
A definition is the unit that gets a content hash. There are five
|
|
kinds of definition: function, constant, data type, class, and
|
|
instance. Their order within the module is preserved on disk but is
|
|
not load-bearing semantically — forward references are legal.
|
|
|
|
The schema of the on-disk form is fixed. The toolchain rejects any
|
|
module that does not conform.
|
|
|
|
|
|
The AIL surface is parenthesised tagged-head Lisp-style notation.
|
|
Every form begins with `(` followed by a tag identifier; positional
|
|
arguments follow; the form closes with `)`. There are no infix
|
|
operators, no operator precedence, no implicit conversions.
|
|
|
|
A module starts with `(module NAME ...)` and contains import forms
|
|
followed by definition forms. Whitespace and `;`-prefixed line
|
|
comments are insignificant; indentation is for readability only.
|
|
|
|
Identifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,
|
|
`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a
|
|
digit. String literals are double-quoted with `\"`, `\\`, `\n`, `\t`
|
|
escapes; integer literals are signed decimal; the `(unit)` keyword
|
|
denotes the unit value.
|
|
|
|
The AIL form is the printed dual of the JSON form: `ail parse
|
|
file.ail` produces the same canonical bytes as the JSON form would,
|
|
and `ail render file.ail.json` produces the AIL text. Round-trip
|
|
through this pair is gated by an integration test.
|
|
|
|
```ail
|
|
(module fn_returns_int
|
|
(fn answer
|
|
(doc "The simplest possible fn — no params, returns a fixed Int.")
|
|
(type (fn-type (params) (ret (own (con Int)))))
|
|
(params)
|
|
(body 42)))
|
|
```
|
|
|
|
## 3. Types
|
|
|
|
A type is one of four shapes. Every type position in a function
|
|
signature must be filled in; there is no implicit type inference at
|
|
the binding form. Constructor types may carry type arguments.
|
|
Functions are first-class — function types appear as parameter and
|
|
return types.
|
|
|
|
A type constructor names a base type or a user-defined ADT. The
|
|
prelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are
|
|
introduced with the `data` form (see section 6); after declaration
|
|
their name is usable wherever a type constructor is expected.
|
|
|
|
A type variable is a placeholder introduced by `forall` at the top
|
|
of a function signature. Within the signature the variable behaves
|
|
as an opaque type; the toolchain instantiates it at each call site.
|
|
|
|
A function type names parameter types, parameter modes (see section
|
|
4), a return type, a return mode, and an effect row (see section 8).
|
|
|
|
A `forall` is universal quantification: it lists the type variables
|
|
bound and the body type they appear in. Forall is only valid at the
|
|
top of a function or constant signature.
|
|
|
|
|
|
Type::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type
|
|
name. For parameterised types: `(con List Int)` reads "List of Int";
|
|
nested: `(con Map (con Str) (con Int))`.
|
|
|
|
Type::Var: a bare identifier alone in type position (no wrapper),
|
|
e.g. `a` inside a `(forall (a) ...)`. Type variables are
|
|
syntactically distinguished from type constructors by being declared
|
|
in the enclosing `forall`.
|
|
|
|
Type::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The
|
|
parameter and effect lists may be empty. Modes wrap the relevant
|
|
type: `(own T)` or `(borrow T)` — see section 4.
|
|
|
|
Type::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and
|
|
`b`. Class constraints attach as `(forall (a) (constraints (Eq a))
|
|
BODY-TYPE)`.
|
|
|
|
```ail
|
|
(module forall_polymorphic
|
|
(fn id
|
|
(doc "The polymorphic identity function. Exercises Type::Forall and Type::Var.")
|
|
(type (forall (vars a) (fn-type (params (own a)) (ret (own a)))))
|
|
(params x)
|
|
(body x)))
|
|
```
|
|
|
|
## 4. Mode annotations
|
|
|
|
Every function parameter carries a mode, and so does the return
|
|
position. There are exactly two modes. `own` means the caller
|
|
transfers ownership and the callee consumes the value; `borrow`
|
|
means the caller retains ownership and the callee may inspect but
|
|
not consume. `borrow` is legal only over a boxed type (an algebraic
|
|
data type): unboxed value types (`Int`, `Bool`, `Float`, `Unit`)
|
|
are always `own`, and borrowing one is a `borrow-over-value` error.
|
|
|
|
The return position is always `own` — a function returns a fresh
|
|
owned value; borrow-returns are not permitted.
|
|
|
|
Modes are mandatory: every parameter and the return position carries
|
|
exactly one mode. There is no default or omitted mode.
|
|
|
|
|
|
Modes are surface wrappers around the type in parameter and return
|
|
position. Inside a `(fn-type ...)`:
|
|
|
|
- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.
|
|
- `(ret (own T))` — the return is always owned.
|
|
|
|
Every parameter and the return carries a mode wrapper; there is no
|
|
bare-type (mode-less) form. `borrow` is legal only over a boxed
|
|
type: `(borrow (con Int))` is a `borrow-over-value` error — write
|
|
`(own (con Int))`. The mode is part of the fn's signature and
|
|
contributes to its content hash.
|
|
|
|
```ail
|
|
(module param_modes_all
|
|
(data List
|
|
(ctor Nil)
|
|
(ctor Cons (con Int) (con List)))
|
|
(fn f_own_value
|
|
(doc "(own Int) — unboxed value types (Int/Bool/Float/Unit) are always own; borrow over them is a borrow-over-value error.")
|
|
(type (fn-type (params (own (con Int))) (ret (own (con Int)))))
|
|
(params x)
|
|
(body x))
|
|
(fn f_borrow_boxed
|
|
(doc "(borrow List) — boxed types may be borrowed; the callee reads without consuming.")
|
|
(type (fn-type (params (borrow (con List))) (ret (own (con Int)))))
|
|
(params xs)
|
|
(body (match xs
|
|
(case (pat-ctor Nil) 0)
|
|
(case (pat-ctor Cons h t) (app + 1 (app f_borrow_boxed t)))))))
|
|
```
|
|
|
|
## 5. Functions
|
|
|
|
A function definition binds a name to a body of code. The signature
|
|
declares the parameter types, the return type, and the effect set
|
|
(if any). The body is an expression — there is no statement form.
|
|
Functions are first-class values: they may be passed to other
|
|
functions, returned, stored in data structures.
|
|
|
|
Inside a function body, four term forms appear most often: a
|
|
variable reference (the name of a parameter, a local binding, or a
|
|
top-level definition); a function application (a callee plus
|
|
positional arguments); a lambda (an anonymous function with its
|
|
own signature); a literal value (see section 9).
|
|
|
|
Lambdas carry their own parameter list, parameter types, return
|
|
type, and effect set. Lambdas may close over names from the
|
|
enclosing lexical scope; the closure is captured by value (the
|
|
captured values are reference-counted under `--alloc=rc`).
|
|
|
|
A `let` binds a name to the value of one expression for the
|
|
duration of another expression. A `letrec` is the recursive variant:
|
|
the bound name is visible inside its own body. Both are
|
|
expressions, not statements — they have a value.
|
|
|
|
|
|
Function definition: `(fn NAME (doc STRING)? (type TYPE) (params
|
|
NAME*) (body TERM))`. The `doc` clause is optional but recommended.
|
|
|
|
Term forms relevant here:
|
|
|
|
- Variable reference: a bare identifier, e.g. `x` or `+`.
|
|
- Application: `(app CALLEE ARG*)` — head is `app`, then the callee
|
|
term, then positional arguments.
|
|
- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)
|
|
(effects EFFECT*) BODY)`.
|
|
- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in
|
|
`BODY`.
|
|
- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`
|
|
— `NAME` is recursively visible inside `BODY`.
|
|
|
|
Inside any term position, an integer literal is a bare decimal
|
|
number, a string is a double-quoted string, a bool is `true` or
|
|
`false`, unit is `(unit)`.
|
|
|
|
```ail
|
|
(module fn_calls_prelude
|
|
(fn add
|
|
(doc "Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.")
|
|
(type (fn-type (params (own (con Int)) (own (con Int))) (ret (own (con Int)))))
|
|
(params x y)
|
|
(body (let s (app + x y) (clone s)))))
|
|
```
|
|
|
|
```ail
|
|
(module fn_with_lambda
|
|
(fn make_adder
|
|
(doc "Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.")
|
|
(type (fn-type (params (own (con Int))) (ret (own (fn-type (params (own (con Int))) (ret (own (con Int))))))))
|
|
(params x)
|
|
(body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))
|
|
```
|
|
|
|
## 6. Algebraic data types
|
|
|
|
A data type declaration introduces a new named type with one or
|
|
more constructors. Each constructor takes zero or more positional
|
|
argument types and produces a value of the declared type. ADTs may
|
|
be parameterised by type variables; the variables are listed before
|
|
the constructors and may appear in the constructor argument types.
|
|
|
|
A constructor is invoked at the term level by naming the type and
|
|
the constructor plus the positional arguments. The runtime
|
|
representation under `--alloc=rc` is a tagged heap cell with one
|
|
slot per constructor field; the tag identifies which constructor
|
|
was used.
|
|
|
|
Constructor names live in their own namespace, separate from
|
|
function names. The same identifier may be a function and a
|
|
constructor without conflict, though for readability the convention
|
|
is constructors are capitalised and functions are lower-case.
|
|
|
|
|
|
Type definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor
|
|
CTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary
|
|
constructors have an empty argument list.
|
|
|
|
Example: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a
|
|
monomorphic Int list with a Nil and a Cons constructor.
|
|
|
|
Constructor invocation: `(term-ctor TYPE-NAME CTOR-NAME ARG*)`.
|
|
The Form-A keyword is `term-ctor`, not `ctor` — the bare `(ctor
|
|
...)` is reserved for the inside of `(data ...)` definitions and
|
|
the parser rejects it in term position. `TYPE-NAME` precedes the
|
|
constructor name so the parser does not need a separate registry
|
|
to resolve overlapping constructor names across ADTs. Example:
|
|
`(term-ctor List Cons 1 (term-ctor List Nil))` builds a single-
|
|
element list.
|
|
|
|
```ail
|
|
(module data_simple
|
|
(data Box (vars a)
|
|
(ctor MkBox a))
|
|
(fn wrap_one
|
|
(type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))
|
|
(params src)
|
|
(body (match src
|
|
(case (pat-ctor MkBox v) (reuse-as src (term-ctor Box MkBox 1)))))))
|
|
```
|
|
|
|
## 7. Pattern matching
|
|
|
|
Pattern matching is the way to inspect an ADT value. A `match`
|
|
form takes a scrutinee expression plus one or more arms; each arm
|
|
pairs a pattern with a body. Arms are tried top-to-bottom; the
|
|
first matching arm's body is evaluated. The toolchain checks
|
|
exhaustiveness: every constructor of the scrutinee's type must be
|
|
reachable through some arm, otherwise the typechecker errors.
|
|
|
|
There are four pattern shapes. A wildcard matches anything and
|
|
binds nothing. A variable pattern matches anything and binds the
|
|
matched value to the named identifier for use inside the arm body.
|
|
A literal pattern matches only when the scrutinee is bit-equal to
|
|
the literal value; literal patterns work on Int, Bool, Str, and
|
|
Unit (Float patterns are rejected at typecheck — see section 9).
|
|
A constructor pattern matches when the scrutinee is built by the
|
|
named constructor and recursively matches the constructor's
|
|
fields.
|
|
|
|
|
|
Match expression: `(match SCRUT (case PAT BODY) (case PAT BODY)
|
|
...)`. Arms are introduced by `(case ...)`; the order matters (top-
|
|
to-bottom).
|
|
|
|
Pattern shapes:
|
|
- Wildcard: `_` — bare underscore is the wildcard pattern.
|
|
- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.
|
|
- Literal: a literal value alone, e.g. `0`, `true`, `"abc"`,
|
|
`(unit)`, matches bit-equal scrutinees.
|
|
- Constructor: `(pat-ctor CTOR-NAME SUBPAT*)` — the Form-A
|
|
pattern keyword is `pat-ctor` (not `ctor`). No `type` slot
|
|
here, because the constructor name is resolved against the
|
|
scrutinee's type.
|
|
|
|
Pattern variables introduced by a `(case PAT BODY)` are in scope
|
|
inside `BODY` and bind there only. They do not leak into sibling
|
|
arms or into code outside the `match`.
|
|
|
|
```ail
|
|
(module data_with_match
|
|
(data List
|
|
(doc "Monomorphic singly-linked Int list — boxed, recursive.")
|
|
(ctor Nil)
|
|
(ctor Cons (con Int) (con List)))
|
|
(fn head_or_zero
|
|
(doc "Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.")
|
|
(type (fn-type (params (borrow (con List))) (ret (own (con Int)))))
|
|
(params xs)
|
|
(body (match xs
|
|
(case (pat-ctor Nil) 0)
|
|
(case (pat-ctor Cons h _) h))))
|
|
(fn count_via_letrec
|
|
(doc "Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.")
|
|
(type (fn-type (params (own (con List))) (ret (own (con Int)))))
|
|
(params xs)
|
|
(body (let-rec go (params ys) (type (fn-type (params (own (con List))) (ret (own (con Int))))) (body (match ys
|
|
(case (pat-ctor Nil) 0)
|
|
(case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))
|
|
```
|
|
|
|
```ail
|
|
(module match_literal_pattern
|
|
(fn classify
|
|
(doc "Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.")
|
|
(type (fn-type (params (own (con Int))) (ret (own (con Int)))))
|
|
(params n)
|
|
(body (match n
|
|
(case (pat-lit 0) 100)
|
|
(case _ 200))))
|
|
(fn sign_if
|
|
(doc "Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.")
|
|
(type (fn-type (params (own (con Int))) (ret (own (con Int)))))
|
|
(params n)
|
|
(body (if (app lt n 0) -1 (if (app eq n 0) 0 1)))))
|
|
```
|
|
|
|
## 8. Effects
|
|
|
|
Every function type carries an effect row — a set of effect labels
|
|
that the function may raise. The two currently wired effects are
|
|
`IO` (required to call effect operations like `io/print_int`) and
|
|
`Diverge` (used for functions that may not terminate). An empty
|
|
effect row marks the function as pure.
|
|
|
|
Effect operations are reached through the `do` term, not through a
|
|
regular function application. The `do` form names the operation
|
|
(e.g. `io/print_int`) and lists the operation's arguments; the
|
|
typechecker resolves the operation against the prelude's effect-op
|
|
table, checks the arguments, and accumulates the operation's
|
|
effect label into the enclosing function's effect row.
|
|
|
|
A `seq` term sequences two effectful expressions: the left-hand
|
|
side is evaluated for its effects and its result discarded, then
|
|
the right-hand side is evaluated and its value is the value of the
|
|
whole expression. `seq` is the canonical way to perform multiple
|
|
IO operations one after the other inside a function body.
|
|
|
|
|
|
Effect row on a fn type: `(effects E1 E2 ...)` inside the
|
|
`(fn-type ...)`. Empty row: omit the `(effects)` clause or write
|
|
`(effects)`. Common effectful row: `(effects IO)`. Order is sorted
|
|
in canonical output.
|
|
|
|
Effect operation: `(do OP-NAME ARG*)` — the leading `do` head is
|
|
the parser's signal that the next identifier is an effect op, not a
|
|
function. Example: `(do io/print_int 42)` prints the integer 42
|
|
and raises the `IO` effect.
|
|
|
|
Sequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s
|
|
value; `X`'s value is discarded. Chains of sequencing are written
|
|
nested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s
|
|
value.
|
|
|
|
```ail
|
|
(module fn_with_do_seq
|
|
(fn main
|
|
(doc "Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).")
|
|
(type (fn-type (params) (ret (own (con Unit))) (effects IO)))
|
|
(params)
|
|
(body (seq (seq (do io/print_str (app int_to_str 1)) (do io/print_str "\n")) (seq (do io/print_str (app int_to_str 2)) (do io/print_str "\n"))))))
|
|
```
|
|
|
|
## 9. Literals
|
|
|
|
There are five literal shapes. An integer literal is a signed
|
|
64-bit decimal. A boolean literal is `true` or `false`. A string
|
|
literal is a UTF-8 sequence in double quotes; AILang restricts the
|
|
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
|
ASCII bytes are an authoring error. A unit literal is the value of
|
|
type Unit, written `(unit)` in AIL and `{"kind": "unit"}` in JSON.
|
|
|
|
A Float literal is an IEEE-754 binary64 value. When you author
|
|
Floats, write the decimal form (`3.14`) — the AIL parser converts
|
|
to the 64-bit bit pattern and emits the canonical 16-character
|
|
lowercase hex string into the JSON. You never author the hex
|
|
encoding by hand; the toolchain owns that representation. NaN and
|
|
±Inf are representable through the prelude constants `nan`, `inf`,
|
|
and `neg_inf` (see section 11).
|
|
|
|
Float pattern matching is rejected at typecheck. IEEE-`==` makes
|
|
Float patterns semantically dubious (NaN never matches; equality
|
|
is bit-exact, not approximate), so the language hard-rejects them
|
|
to push authors toward comparison-based dispatch.
|
|
|
|
|
|
Literal surface forms:
|
|
|
|
- Int: bare signed decimal, e.g. `42`, `-7`, `0`.
|
|
- Bool: `true` or `false` as bare keywords.
|
|
- Str: double-quoted with `\"`, `\\`, `\n`, `\t` escapes, e.g.
|
|
`"hello"`. Bytes outside ASCII printable are rejected by the
|
|
authoring rule (Decision 6 Constraint 3).
|
|
- Unit: the keyword `(unit)`.
|
|
- Float: bare decimal with at least one digit on each side of the
|
|
point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the
|
|
bit-pattern hex string when emitting JSON; you never author the
|
|
hex form.
|
|
|
|
There is no separate syntax for exponent-form floats in the v1
|
|
authoring surface; for non-finite values use the prelude constants
|
|
`nan`, `inf`, `neg_inf`.
|
|
|
|
```ail
|
|
(module floats
|
|
(const pi
|
|
(doc "Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).")
|
|
(type (con Float))
|
|
(body 3.14)))
|
|
```
|
|
|
|
```ail
|
|
(module bool_str
|
|
(fn is_true
|
|
(doc "Trivial Bool literal. Exercises Literal::Bool.")
|
|
(type (fn-type (params) (ret (own (con Bool)))))
|
|
(params)
|
|
(body true))
|
|
(fn greeting
|
|
(doc "Trivial Str literal. Exercises Literal::Str.")
|
|
(type (fn-type (params) (ret (own (con Str)))))
|
|
(params)
|
|
(body "hi"))
|
|
(fn unit_value
|
|
(doc "Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.")
|
|
(type (fn-type (params) (ret (own (con Unit)))))
|
|
(params)
|
|
(body (lit-unit))))
|
|
```
|
|
|
|
## 10. Typeclasses
|
|
|
|
A typeclass is a named bundle of operations that may be
|
|
implemented for multiple types. The class declaration lists the
|
|
methods; each instance declaration provides the bodies for a
|
|
specific type. The compiler resolves method calls at the call site
|
|
and monomorphises the implementation: there is no runtime dispatch.
|
|
|
|
A class is parameterised by a single type variable (multi-parameter
|
|
classes are not supported in v1). Methods are declared with their
|
|
full type signature; the class parameter appears as a type variable
|
|
inside the method type. Methods may carry an optional default body
|
|
that instances inherit unless they explicitly override.
|
|
|
|
Class constraints attach to polymorphic function types: a function
|
|
`forall a. (Eq a) => (a, a) -> Bool` requires its callers to
|
|
provide an `Eq` instance for `a` at the call site. The constraint
|
|
appears inside the `forall` quantifier, in the `constraints` slot.
|
|
|
|
The prelude provides four built-in classes: `Eq` (equality), `Ord`
|
|
(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,
|
|
`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,
|
|
Str, Float, and Unit are built in; you do not declare them.
|
|
|
|
|
|
Class declaration: `(class NAME (param TYVAR) (method NAME (type
|
|
METHOD-TYPE) (default BODY)?)*)`. The `default` clause is
|
|
optional; methods without a default must be supplied by every
|
|
instance.
|
|
|
|
Instance declaration: `(instance NAME (type CONCRETE-TYPE) (method
|
|
NAME BODY)*)`. The method bodies must match the class's method
|
|
signatures with the class parameter substituted to the concrete
|
|
type.
|
|
|
|
Constraints inside a forall: `(forall (a) (constraints (Eq a) (Ord
|
|
a)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually
|
|
the type is a single bound type variable but a concrete type is
|
|
also legal (typically a typecheck error unless an instance exists).
|
|
|
|
```ail
|
|
(module class_def
|
|
(class MyShow
|
|
(param a)
|
|
(doc "A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.")
|
|
(method show
|
|
(type (fn-type (params (own a)) (ret (own (con Str))))))))
|
|
```
|
|
|
|
```ail
|
|
(module instance_def
|
|
(class MyShow
|
|
(param a)
|
|
(doc "Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.")
|
|
(method show
|
|
(type (fn-type (params (own a)) (ret (own (con Str)))))))
|
|
(instance
|
|
(class MyShow)
|
|
(type (con Int))
|
|
(doc "Trivial MyShow Int — show always returns the fixed string \"int\".")
|
|
(method show
|
|
(body "int"))))
|
|
```
|
|
|
|
## 11. The prelude
|
|
|
|
The prelude provides a fixed set of operators, conversion functions,
|
|
and effect operations that are in scope without any import. The
|
|
operators are polymorphic over a small set of types and resolve at
|
|
the call site by the resolved argument type.
|
|
|
|
| Name | Type | Notes |
|
|
|------|------|------|
|
|
| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |
|
|
| `%` | `(Int, Int) -> Int` | Int only |
|
|
| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |
|
|
| `not` | `(Bool) -> Bool` | Boolean negation |
|
|
| `neg` | `forall a. (a) -> a` | Int and Float negation |
|
|
| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |
|
|
| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |
|
|
| `float_to_str` | `(Float) -> Str` | Formatted decimal |
|
|
| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |
|
|
| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |
|
|
| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |
|
|
|
|
Effect operations (reached through `do`, not `app`):
|
|
|
|
| Op | Signature | Effect |
|
|
|----|----------|-------|
|
|
| `io/print_int` | `(Int) -> Unit` | `IO` |
|
|
| `io/print_bool` | `(Bool) -> Unit` | `IO` |
|
|
| `io/print_str` | `(Str) -> Unit` | `IO` |
|
|
| `io/print_float` | `(Float) -> Unit` | `IO` |
|
|
|
|
`int_to_str` is intentionally NOT in the prelude — it is type-
|
|
installed in a future milestone but codegen-deferred pending the
|
|
heap-Str ABI work. Do not call it.
|
|
|
|
## 12. Content addressing
|
|
|
|
AILang has content-addressed identity: every top-level definition
|
|
has a 16-character hash derived from its canonical bytes. The
|
|
toolchain computes hashes on `ail parse` and `ail check`; the
|
|
author writes no hash literal in either form, and the JSON form
|
|
has no `hash` field. This is symmetric across forms and removes
|
|
hash as a form-distinguishing factor. You author code; the
|
|
toolchain owns the identity.
|
|
|
|
## 13. Out of scope, and closing directive
|
|
|
|
This reference does NOT teach: cross-module imports (every task is
|
|
a single self-contained module with an empty `imports` array),
|
|
refinement types (reserved in the schema but pass-through in this
|
|
version), point-free style (cut by Decision 6), operator
|
|
overloading (cut), syntactic sugar of any kind (cut). The four
|
|
tasks you will be given do not need any of these features.
|
|
|
|
When asked to write a module, return the complete module text and
|
|
nothing else. No markdown fences. No prose explanation. No
|
|
preamble. The harness parses your response verbatim; any wrapping
|
|
text is treated as a parse error.
|