Ironwall Language Specification

Ironwall Lexical Specification

This document defines Ironwall's lexical boundary. The goal is to keep atomic shapes, syntax sugar, and name-closure rules closed and explicit, so that ambiguity is not deferred into later syntax and semantic stages.

1. Design Principles

  • Lexical rules must be closed, predictable, and easy to diagnose statically
  • The lexical stage accepts only a finite and explicit set of atomic shapes; it does not perform loose parsing that "guesses meaning from context"
  • Composite names related to the module system are closed at the lexical stage itself; ~ and @ are not left for later character-by-character recombination
  • Chain forms such as a.b.c are only surface syntax sugar, not an independent operator category

2. Allowed Characters

  • The set of non-whitespace characters allowed by the lexer is: ASCII letters, decimal digits, _, ., $, ^, ~, @, and the four bracket kinds
  • Whitespace serves only as a separator and carries no semantic meaning
  • Any character outside this set must be rejected directly at the lexical stage

3. Bracket Kinds

Ironwall distinguishes four kinds of brackets, and the lexer must preserve the bracket kind:

  • Parentheses ( )
  • Square brackets [ ]
  • Braces { }
  • Angle brackets < >

The four bracket kinds are not interchangeable containers. Each bracket kind corresponds to a different syntax domain.

4. Identifier Categories

4.1 ordinary identifiers

  • Regex: [a-zA-Z_][a-zA-Z0-9_]*
  • Examples: x, foo, my_var, _tmp

4.2 package path

  • Regex: seg (~ seg)*
  • Here seg must be an ordinary identifier
  • Examples: app, a~b, std~time, test~fixtures~parser_structures

4.3 package-qualified-name

  • Regex: <package-path>@<name>
  • The left side of @ must be a complete package path
  • The right side of @ must be a single ordinary identifier
  • Examples: app@main, app~cli@main, std~time@timestamp

4.4 typed atom

Ironwall accepts only postfix type spelling for typed atoms: $payload^type.

  • payload comes first and type comes after
  • type must be an ordinary identifier
  • If payload has identifier shape, it denotes a typed database reference
  • If payload has numeric shape, it denotes a typed numeric literal
  • Examples: $hello^s3, $line_break^c4, $42^i5, $3p14^f5

4.5 package-qualified typed database reference

The canonical shape of a package-qualified database reference is: <package-path>$<reference-id>^<ty>.

  • The left side must be a complete package path and may not use @
  • <reference-id> must be an ordinary identifier
  • The package-qualified shape is used only for database references, not for numeric literals
  • Therefore a~b~d$name^s3 is legal, while a~b~d$3p14^f5 must be rejected directly at the lexical stage

5. Closure Rules for $payload^type

5.1 typed database reference

When payload has ordinary-identifier shape, and the whole atom does not form a legal typed numeric literal, the atom is treated as a typed database reference.

  • Example: $hello_world^s3
  • Example: $answer_main^i5
  • Example: a~b~d$banner_title^s3

5.2 typed numeric literal

The numeric type prefixes are:

  • Signed integers: i5, i6, i7
  • Unsigned integers: u5, u6, u7
  • Floating point: f5, f6, f7
  • Complex numbers: z5, z6, z7

The digit payload rules are as follows.

5.2.1 integer payload

Legal integer payload shapes:

  • 0
  • Decimal positive integers, such as 42
  • Hexadecimal integers, such as 0x2A
  • Negative-integer encoding, such as 0neg332

Constraints:

  • Decimal positive integers may not have meaningless leading zeros; except for 0, they must start with 1-9
  • 0x must be followed by at least one hexadecimal digit
  • Negative hexadecimal spellings such as 0neg0x2A are not supported; if a negative number needs to be represented, decimal negative payload must be used
  • The role of hexadecimal payload is "a literal shape aligned with a bit-level representation", not merely an alternative decimal spelling sugar
  • That is, the intent of 0x2A is to express an integer in a bit-pattern-oriented way, not to encourage treating hexadecimal and decimal as fully equivalent surface notations that can be swapped freely
5.2.2 floating-point payload

Legal floating-point payload shapes:

  • Use p in place of the decimal point, for example 3p14
  • Support finite negative floats, for example 0neg3p14
  • Scientific notation uses ep / en, for example 3p14ep23, 3p14en20
  • Support finite negative scientific notation, for example 0neg3p14en20
  • Shapes with only an exponent and no fractional part, for example 5ep10
  • Special values: inf, 0neginf, nan

Constraints:

  • The fractional part after p may not be empty; 3p is illegal and must be written as 3p0
  • The exponent part must be a non-negative decimal integer
  • Finite negative floats use the 0neg prefix
5.2.3 complex payload

At the spec layer, z5, z6, and z7 complex literals are explicitly supported.

Their strict shape is:

0real<RealPart>img<ImagPart>

Where:

  • The payload must begin with 0real
  • img must appear exactly once
  • RealPart may not be omitted
  • ImagPart may not be omitted
  • Both RealPart and ImagPart must be legal real-number payloads
  • Legal real-number payloads include: integers, negative integers, floating point, negative floating point, scientific notation, negative scientific notation, inf, 0neginf, and nan
  • Traditional complex spellings that mix +, -, ., e, or i into the payload are not allowed

Examples:

  • $0real0neg42p32img0neg3p22^z5
  • $0real3p14img2p0^z6
  • $0realinfimg0neginf^z7

Illegal examples:

  • $0realimg1^z5
  • $0real3p14^z5
  • $3p14img2p0^z5
  • $0real3p14img2p0img1^z5

The semantics of a complex payload are those of a primitive complex literal, not a plain-text shorthand for calling z*_rect.

5.2.4 deciding between typed database reference and typed numeric literal

In the general case, the two are not ambiguous:

  • Database-reference payloads start with identifier-like shapes
  • Numeric-literal payloads mainly start with digits or keyword-like constant shapes

Therefore, in most cases, the two paths are naturally separated by lexical form.

The one exception that must be preserved explicitly is the floating-point keyword constants:

  • inf
  • nan

Although these payloads start with letters, under the f5 / f6 / f7 prefixes they must be classified as numeric literals first, not as database references.

That is:

  • $inf^f5 is a floating-point literal
  • $nan^f5 is a floating-point literal
  • $inf^s3 is still a database reference
  • $answer^i5 is still a database reference
5.2.5 examples

Legal:

  • $0^i5
  • $42^i5
  • $0neg332^i5
  • $0x2A^u5
  • $3p14^f5
  • $0neg3p14^f5
  • $3p14ep23^f6
  • $3p14en20^f7
  • $0neg3p14en20^f5
  • $inf^f5
  • $0neginf^f5
  • $nan^f5
  • $0real0neg42p32img0neg3p22^z5
  • $0real3p14img2p0^z6
  • $0realinfimg0neginf^z7
  • $hello^s3
  • a~b~d$hello^s3

Illegal:

  • 42
  • 0p0
  • $001^i5
  • $0neg0x2A^i5
  • $0realimg1^z5
  • $3p14img2p0^z5
  • $0real3p14img2p0img1^z5
  • i5$42
  • s3$hello
  • a~b~d$3p14^f5

Not allowed:

  • Bare 42
  • Bare 3p14
  • Inferring a default numeric type from context

6. Expansion of Chained Surface Sugar

At the lexical level, only one chained syntax sugar form is supported, and each segment may be only one of the following two categories:

  • An ordinary identifier
  • A package-qualified-name, such as a~b@c

$payload^ty and pkg$reference^ty do not participate in any chained expansion.

6.1 dot chains

a.b.c is expanded during lexical desugaring into nested cm_get calls.

  • a.b.c -> (cm_get (cm_get a b) c)
  • a.b.c.d -> (cm_get (cm_get (cm_get a b) c) d)
  • a~b@c.d~e@f.h~i@j -> (cm_get (cm_get a~b@c d~e@f) h~i@j)
  • This is lexical sugar for member-read semantics; later semantic processing still follows the ordinary rules of cm_get
  • A dot chain must be lexically one continuous raw chunk, so a . b, a. b, and a .b are all illegal
  • A formatter may rewrite a restorable nested cm_get chain back into a.b.c without changing semantics

6.2 illegal chain shapes

Illegal examples:

  • a-b-c
  • hello..world
  • foo.-bar
  • $hello.world^s3

7. Comment Ban

  • No comment syntax such as //, #, /* */, or ; is defined
  • When explanatory text is needed, it should be represented through typed database entries or other ordinary language data
  • Comments have no privileged lexical path

8. Examples of Illegal Shapes

The following shapes must be rejected at the lexical stage or at a very early syntax stage:

  • a~~b
  • a~@main
  • a~b@c@d
  • 1abc
  • @main
  • a~b.iw
  • $hello.world^s3
  • Bare numeric 42
  • 0real3p14img2p0
  • i5$42
  • a~b~d$3p14^f5

9. Lexical Boundary

  • Bracket kinds must be preserved lexically
  • Package paths, package-qualified-names, and typed references are each closed into a single atom
  • a.b.c is already expanded before entering later stages, and no chained atom remains

Ironwall Syntax Specification

This document describes the core syntax shapes of Ironwall. It answers only "how to write it" and does not repeat the full semantics of types and modules; those are defined separately by the type, semantics, and module specifications.

1. Root Structure

  • Every module-mode .iw source unit must have exactly one root block: {program <unit-id> ...}
  • program may appear only at the root and may not be nested inside other expressions
  • The canonical shape of unit-id is <package-path>@<unit-name>

Example:

{program app~cli@main
  (function main ([args <array s3>]) to i5 in $0^i5)
}

2. Keywords

The core syntax uses the following reserved syntactic words:

  • program
  • import
  • export
  • public
  • var
  • var_set
  • fn
  • function
  • declare
  • let
  • letrec
  • in
  • if
  • then
  • else
  • while
  • cond
  • match
  • class
  • property
  • method
  • constructor
  • generic
  • to
  • from
  • union

Language builtin names and package-exported names are defined by the builtin and module specifications respectively, and are not repeated in this section.

3. Binding Syntax

Binding positions uniformly use:

[name Type]

Rules:

  • name must be an ordinary identifier
  • Type must be written explicitly
  • Spellings such as [x] and [x _] that omit the type are illegal

4. Blocks and Order

4.1 {...} block

{e1 e2 ... eN}
  • Denotes a sequential-evaluation block
  • Returns the value of the last expression
  • An empty block is illegal

5. Variables and Assignment

5.1 var

(var [x T] expr)
  • Creates and initializes a named binding
  • var is used both for local variables and for top-level globals

5.2 var_set

(var_set x expr)
  • Reassigns an existing binding
  • Assignment to object fields does not go through var_set, but through the cm_set builtin

6. Functions

6.1 anonymous function fn

(fn ([p1 T1] [p2 T2] ...) to Ret in body)
  • fn is a first-class value
  • The parameter list and return type must both be written explicitly

6.2 named function function

(function name ([p1 T1] ...) to Ret in body)
  • function must appear at the top level
  • Named functions with the same name may form an overload set by parameter type

6.3 declare

(declare (function name ([p1 T1] ...) to Ret))
  • Declares the signature of an external function without providing an Ironwall body
  • declare may appear only at the top level

7. let

(let (([x T] e1) ([y U] e2) ...) in body)
  • The binding list is written with double parentheses
  • Every binding must carry an explicit type
  • A let body has exactly one main expression

7.1 letrec

(letrec (
  ([is_even <to bool from i5>]
    (fn ([n i5]) to bool in
      (if (eq n $0^i5) then true else (is_odd (sub n $1^i5)))))
  ([is_odd <to bool from i5>]
    (fn ([n i5]) to bool in
      (if (eq n $0^i5) then false else (is_even (sub n $1^i5)))))
) in body)
  • letrec uses the same typed-binding shape and single body as let
  • Every binding must carry an explicit function type
  • Names must be unique within one letrec binding list

8. Conditionals and Loops

8.1 if

(if cond then a else b)

8.2 while

(while condition in body)

8.3 cond

(cond
  (c1 e1)
  (c2 e2)
  (else eN)
)
  • The else branch must appear last

9. Type Syntax

9.1 function type

<to Ret from T1 T2 ...>

9.2 union type

<union T1 T2 ...>
  • A union type must contain unique immediate member types
  • Duplicate immediate members are rejected during type validation rather than deduplicated
  • Nested union syntax is allowed and denotes a nested union member, not an expanded member list

9.3 generic head

<generic Name T1 T2 ...>
  • This shape is used only in the header of a generic class or generic function declaration

10. Generic Declaration and Instantiation

10.1 generic function declaration

(function <generic id T> ([x T]) to T in x)

10.2 generic class declaration

(class <generic Box T>
  (public (property [value T]))
  (constructor ([v T]) in (cm_set self value v))
)

10.3 explicit instantiation

<id i5>
(<id i5> $42^i5)
<Box i5>
  • <name T...> denotes explicit application of one or more type arguments to a generic name; name must be an identifier
  • <>, <name>, and angle applications whose callee is a non-generic builtin (for example <array_new T> or <class_new Box T>) are invalid
  • If it is then wrapped in an outer (...), the instantiated result is being called
  • Builtin constructors keep their ordinary call shape: (array_new <array T> ...) and (class_new <Box T> ...)

11. match

(match value
  ([x T1] body1)
  ([y T2] body2)
  ...
  (else fallback)
)
  • Every typed branch begins with a typed bind
  • (else fallback) is optional; when present, it must be the unique final branch
  • Without else, the typed branches must cover every immediate union member; with else, it handles members not covered by typed branches
  • Typed branch types must not repeat, and else is illegal when typed branches already cover the entire union

12. Classes

12.1 class declaration

(class Point
  (public (property [x i5]))
  (public (property [y i5]))
  (public (method sum () to i5 in (add (cm_get self x) (cm_get self y))))
  (constructor ([x0 i5] [y0 i5]) in
    {
      (cm_set self x x0)
      (cm_set self y y0)
    }
  )
)

12.2 class member clauses

  • (property [name Type])
  • (method name ([p T] ...) to Ret in body)
  • (constructor ([p T] ...) in body)
  • (public (property [name Type]))
  • (public (method name ([p T] ...) to Ret in body))

public may appear only inside the class body of an ordinary class or generic class, and it may wrap only one property or method. Properties and methods not wrapped in public are private by default; constructors are public by default and must not be written as (public (constructor ...)).

13. Calls and Object Operations

13.1 ordinary calls

(callee arg1 arg2 ...)

For the following builtins, the frontend also accepts additional variadic surface sugar:

  • add / mul / and / or may be written with >= 2 parameters; the parser lowers them into a right-associative binary tree
  • sub may also be written with >= 2 parameters, but the parser lowers it into a left-associative binary tree
(add a b c d)

Equivalent to:

(add a (add b (add c d)))
(sub a b c d)

Equivalent to:

(sub (sub (sub a b) c) d)
  • le / lt / ge / gt / eq may be written with >= 2 parameters; semantically, they form a pairwise comparison chain joined by right-associative and
(le a b c d)

Equivalent to:

(and (le a b) (and (le b c) (le c d)))
  • This is frontend sugar, not extra runtime/builtin overloads; therefore 0 and 1 parameter forms are still illegal

13.2 object construction

(class_new Point $1^i5 $2^i5)

13.3 field reads and writes

(cm_get obj field)
(cm_set obj field expr)
  • Only the object primitive set class_new / cm_get / cm_set is accepted

Lexical sugar:

  • a.b.c is equivalent to (cm_get (cm_get a b) c)
  • a.b.c.d is equivalent to (cm_get (cm_get (cm_get a b) c) d)
  • Every segment must be an ordinary identifier or a package-qualified-name
  • a . b, a. b, and a .b are all illegal, because this sugar must be lexically a single raw chunk with no spaces
  • a-b-c is not member-read syntax

14. Typed Literal / Reference

Typed literals and typed database references accept only the following canonical shape:

$payload^type

Rules:

  • $42^i5, $3p14^f5, and $hello^s3 are all legal atoms
  • When a package-qualified database reference is needed, it must be written as pkg$reference^ty
  • pkg$reference^ty denotes only a cross-package database reference, not a numeric literal
  • Therefore a~b~d$banner^s3 is legal, but a~b~d$3p14^f5 is illegal

If a short-name database reference is not unique within the visible package set, it must be rewritten as a package-qualified database reference.

14.1 Literal-db Enum Constants

Ironwall provides lightweight enum constants through package literal-db assets. They express stable, readable numeric codes without adding a new source declaration form or runtime type.

An enum is declared in a literal-db JSON file:

{
  "package": "app~state$values",
  "enum": [
    "idle^enum",
    "running^enum",
    "stopped^enum"
  ]
}

Source code refers to members with typed database-reference syntax:

(import app~state)
(var [current i5] $running^enum)
(var [explicit i5] app~state$stopped^enum)

Array positions define zero-based ordinals: $idle^enum, $running^enum, and $stopped^enum lower to the i5 literals 0, 1, and 2. enum is not a nominal type and does not prevent values from different enum arrays from being mixed. When distinct type identity or exhaustive variants are required, use classes or union types. The complete file-format, package-visibility, and uniqueness rules are defined in iw-module.md.

15. Array Syntax

  • The builtin array type is written as <array T>
  • The related builtin call shapes are:
(array_new <array T>)
(array_new <array T> len)
(array_new <array T> len init)
(array_get xs idx)
(array_set xs idx value)
(array_length xs)
  • (array_new <array T>) creates an array of length 5; T must support zero-argument initialization, and each position is initialized independently
  • (array_new <array T> len) uses an i5 length; T must support zero-argument initialization, and each position is initialized independently
  • (array_new <array T> len init) evaluates init exactly once and fills every position with that same value; when init is a class value, every position aliases the same object

16. import and export

(import a~b~c)
  • import may appear only at the top level
  • The target of import is a package path, not a file path
(export (function score ([x i5]) to i5 in x))

(export (class Counter
  (public (property [value i5]))
  (constructor ([seed i5]) in (cm_set self value seed))
))

(export (class <generic Box T>
  (public (property [value T]))
  (constructor ([value T]) in (cm_set self value value))
))
  • export may appear only at the top level
  • (export exp) must have exactly one argument
  • export may wrap only a top-level class, generic class, function, declare, generic function, or top-level var
  • export does not change the wrapped definition's type or evaluation semantics; it only controls cross-package visibility

Ironwall Type System Specification

This document defines Ironwall's type construction, type equality, assignability, and the closure rules for generics and union types.

1. Primitive Types

The primitive types are:

  • Signed integers: i5, i6, i7
  • Unsigned integers: u5, u6, u7
  • Floating point: f5, f6, f7
  • Complex numbers: z5, z6, z7
  • Characters: c3, c4, c5
  • Strings: s3, s4, s5
  • Others: bool, unit

The naming convention is "prefix letter + exponent n". Its design intent is that 2^n represents a width grade; however, type equality still depends only on the type name itself.

2. Class Types

2.1 Ordinary classes

  • Every top-level class forms a nominal type
  • A class type is identified by its class name, not by structural equality

2.2 Generic class instances

  • Explicit instantiations such as <Pair i5 s3> and <Node i5> form concrete types
  • Generic class instances are still nominal types; both the type name and all type arguments must match

2.3 Builtin generic types

The builtin generic type is:

  • <array T>

array is a builtin runtime type, not a user-defined class.

3. Function Types

Function types are written as:

<to Ret from T1 T2 ...>

Rules:

  • Parameter count, parameter order, each parameter type, and the return type all participate in type equality
  • Zero-argument functions are still one case of function type

4. Union Types

Union types are written as:

<union T1 T2 ...>

Closure rules:

  • Union members are canonicalized at the type layer
  • Nested unions are not flattened; a nested union remains an immediate member type
  • Duplicate immediate members are a type error and must be rejected; they are not silently deduplicated
  • Member order does not affect the final notion of type equality
  • Every immediate member type inside a union must be unique within that union

Therefore, the following types must be considered equal:

  • <union i5 f5>
  • <union f5 i5>

The following type is distinct from both of the above because the nested union is preserved:

  • <union i5 <union f5 i5>>

The following type is invalid because i5 appears twice as an immediate member:

  • <union i5 f5 i5>

5. Type Equality

5.1 primitive

  • Two primitive types are equal only if their type names are exactly the same

5.2 class

  • Two class types are equal only if their class names are exactly the same

5.3 generic class / generic function instance

  • They are equal only if the generic name is the same and all type arguments are pairwise equal

5.4 function type

  • The parameter count must be the same
  • The parameter order must be the same
  • The corresponding parameter types must be equal
  • The return type must be equal

5.5 union type

  • After canonicalization, the member sequence must match exactly
  • Canonicalization sorts immediate members for equality, but does not flatten nested unions

6. Assignability

The isAssignable rule is intentionally conservative:

  • If actual and expected are type-equal, assignment is allowed
  • If expected is a union, and actual is type-equal to one of its member types, assignment is allowed
  • No other implicit assignability relation is defined

This means:

  • An i5 value may be used directly as a member value of <union i5 f5>
  • i5 is not implicitly converted to f5
  • <union i5 f5> is not implicitly narrowed to i5

7. Generics

7.1 Supported range

  • Generic classes are supported
  • Generic functions are supported
  • Generic declare is not supported
  • Type aliases are not supported

7.2 Explicit-first

  • Generic instantiation must explicitly write out all type arguments
  • The language does not provide inference that auto-fills type arguments from value arguments
  • A generic function name cannot be used as a bare value; it must first be explicitly instantiated

8. Explicit Annotation Requirements

The following positions must all carry explicit types:

  • [name Type] bindings
  • Function parameters
  • Function return types
  • Class properties
  • Top-level globals

Additional restrictions:

  • The declared type of a top-level global must be a primitive type, or a union containing at least one primitive member
  • The final value of a top-level global initializer must be a primitive payload assignable to that type

The following are not allowed:

  • Omitting the type of a let / var binding
  • Omitting the return type of a function
  • Omitting the type of a property

9. Numeric Type Rules

  • There is no default integer type and no default floating-point type
  • Numeric literals must be written as typed literals
  • There is no implicit numeric promotion such as i5 -> f5, f5 -> f6, or i5 -> i6
  • The available signatures of arithmetic and comparison builtins are determined by the builtin specification, not filled in through implicit conversion

10. unit

  • unit is both a primitive type and the spelling of its unique value
  • unit is commonly used for side-effect flows, empty results, and empty branches of types such as <union unit T>

11. Type Alias Ban

  • type alias is strictly forbidden

12. Overloading

  • Functions are overloaded by the uniqueness of the function name and parameter-type signature
  • Generic classes and generic functions are overloaded by the uniqueness of the generic name and type-parameter count

Ironwall Builtin Boundary Specification

This document describes only the language builtins of Ironwall.

1. Layering Principle

Ironwall divides available capabilities into two layers:

  • Language builtins: recognized directly by the compiler and part of the core semantics
  • std~... packages: the standard library provided through ordinary top-level definitions

This boundary must remain clear:

  • Builtins do not require import
  • Names exported from std~... must be brought into scope through the corresponding (import std~...) before they can be used directly
  • There is no special rule that says "because it comes from the base lib, it automatically becomes a builtin name"

2. Language Builtins

2.1 builtin generic type

The language-level builtin generic type is:

  • array

It is written as:

<array T>

2.2 builtin call names

The core builtin call names are:

  • add
  • sub
  • mul
  • div
  • mod
  • le
  • lt
  • ge
  • gt
  • eq
  • neq
  • not
  • and
  • or
  • xor
  • bwand
  • bwor
  • bwxor
  • ls
  • rs
  • class_new
  • cm_get
  • cm_set
  • array_new
  • array_get
  • array_set
  • array_length
  • panic
  • s3_new, s3_get, s3_set, s3_length
  • s4_new, s4_get, s4_set, s4_length
  • s5_new, s5_get, s5_set, s5_length
  • z5_new, z5_set, z5_real, z5_img
  • z6_new, z6_set, z6_real, z6_img
  • z7_new, z7_set, z7_real, z7_img
  • val_to_i5 / val_to_i6 / val_to_i7
  • val_to_u5 / val_to_u6 / val_to_u7
  • val_to_f5 / val_to_f6 / val_to_f7
  • bin_to_i5 / bin_to_i6 / bin_to_i7
  • bin_to_u5 / bin_to_u6 / bin_to_u7
  • bin_to_f5 / bin_to_f6 / bin_to_f7

Only the spellings above are accepted. Object primitives accept only class_new / cm_get / cm_set, and variable reassignment accepts only var_set.

2.3 builtin signature closure

  • The numeric arithmetic builtins add / sub / mul / div / mod support same-type operations on u5|u6|u7|i5|i6|i7|f5|f6|f7, with no cross-type promotion
  • The comparison builtins le / lt / ge / gt / eq / neq support same-type comparisons on u5|u6|u7|i5|i6|i7|f5|f6|f7 and return bool
  • The same comparison builtins also support same-type comparisons on c3|c4|c5 and return bool; their semantics are defined by single code-unit / byte ordering
  • not supports (bool) -> bool
  • and / or / xor support only bool
  • The bitwise / shift builtins bwand / bwor / bwxor / ls / rs support u5|u6|u7|i5|i6|i7, and do not support f5|f6|f7
  • s3_new / s4_new / s5_new support two signatures: (sN) -> sN and (i5, cN) -> sN
  • s3_get / s4_get / s5_get have the signature (sN, i5) -> cN
  • s3_set / s4_set / s5_set have the signature (sN, i5, cN) -> unit
  • s3_length / s4_length / s5_length have the signature (sN) -> i5
  • z5_new / z6_new / z7_new have the signature (zN) -> zN
  • z5_set / z6_set / z7_set support two signatures: (zN, zN) -> unit and (zN, fN, fN) -> unit
  • z5_real / z5_img return f5; z6_real / z6_img return f6; z7_real / z7_img return f7
  • The target of val_to_* / bin_to_* may be i5|i6|i7|u5|u6|u7|f5|f6|f7, with the same integer and floating-point families as sources; targets i5 / u5 additionally accept c3|c4|c5
  • val_to_* follows numeric-conversion semantics: float-to-integer discards the fractional part before truncating to target width, while integer-to-float may lose precision
  • bin_to_* copies the low representation bits and zero-fills high bits when the target is wider
  • c3|c4|c5 -> i5|u5 uses single code-unit / byte semantics; val_to_* and bin_to_* produce the same result for these source/target combinations
  • panic accepts (), (i5), (i6), or (s3); it reports a diagnostic and terminates abnormally at runtime, so it may appear in any expected-type position
  • The frontend surface sugar additionally accepts >= 2 argument forms for add / mul / and / or; semantically, they are lowered into a right-associative binary tree. For example, (add a b c d) is equivalent to (add a (add b (add c d)))
  • The frontend surface sugar additionally accepts >= 2 argument forms for sub; semantically, they are lowered into a left-associative binary tree. For example, (sub a b c d) is equivalent to (sub (sub (sub a b) c) d)
  • The frontend surface sugar additionally accepts >= 2 argument forms for le / lt / ge / gt / eq; semantically, they are expanded into a pairwise comparison chain and joined with right-associative and
  • The variadic surface sugar above does not change the builtin core type boundary: the 0 argument form is still illegal, not remains a standalone unary (bool) -> bool builtin, and div / mod / neq / xor / not are not automatically included in this sugar family

2.4 object and array primitives

  • Whether class_new is legal is determined by the constructor set of the target class
  • cm_get / cm_set are class-object primitives, not general library APIs
  • array_new / array_get / array_set / array_length are array primitives, not std~... package helpers
  • (array_new <array T>) creates an array of length 5; T must support zero-argument initialization, which runs independently for every position
  • (array_new <array T> length) takes an i5 length; T must support zero-argument initialization, every position receives a fresh value, and class elements invoke their zero-argument constructor separately
  • (array_new <array T> length init) evaluates init once and stores it directly in every position; class elements invoke no additional constructor and all positions alias the same object
  • s3_* / s4_* / s5_* are text primitive families, not std~... package helpers
  • z5_* / z6_* / z7_* are primitive complex copy / update / projection families, not std~... package helpers

4. Visibility and Reserved Names

In the specification:

  • Builtin names are global reserved top-level names
  • self is a reserved name
  • panic cannot be used as a user variable or declaration name; the __iw_internal_* namespace is reserved entirely for compiler-generated content
  • The std and std~... packages receive no reserved-name exemption; the builtin reserved set and self apply uniformly to every package
  • An ordinary export is not automatically added to the global reserved set merely because it comes from a std~... package

This means:

  • No package, including std and std~..., may export names such as add, array_new, s3_new, z5_real, or self
  • Names such as print, sin, and string_concat exposed by std~... through (export ...) are ordinary package names, not language builtins; val_to_* / bin_to_* are recognized directly by the compiler and require no import

Ironwall Module System Specification

This document defines Ironwall's multi-file module semantics. The core principle is that semantic identity is determined only by unit id, and that import, package export, entry selection, and global initialization are all closed under one unified set of rules.

1. Core Terms

1.1 source unit

  • A .iw file participating in module mode is a source unit
  • The language-level identity of a source unit is determined by its file-name stem

1.2 package path

  • A package path is formed by joining ordinary identifiers with ~
  • A package path may be either single-segment or multi-segment
  • Examples: app, a~b~c

1.3 unit id

  • The canonical unit id shape is <package-path>@<unit-name>
  • Examples: app@main, app~cli@main

1.4 literal db asset

  • A literal db is a package-level asset, not an anonymous JSON mapping
  • One literal-db file corresponds to one database-reference bundle in a package, not to a single reference
  • The canonical file-name shape is <package-path>$<reference-bundle>.json
  • Example: app~assets$banner.json

2. File Names and program Header

2.1 canonical file name

Under multi-file module mode, the canonical file name is:

<package-path>@<unit-name>.iw

For example:

  • a~b@date.iw
  • std~time@timestamp.iw
  • app@main.iw

2.2 canonical header

The root of the source file must be written as:

{program <package-path>@<unit-name>
  ...
}

2.3 consistency constraints

Compilation must be rejected in the following cases:

  • The file-name stem and the unit id in the program header do not match
  • A single file contains more than one root program
  • The canonical unit id is missing
  • Duplicate unit ids appear in the same semantic closure

3. Directory Semantics

  • Directories have no language-level meaning
  • If two source units in different engineering locations have the same unit id and both participate in compilation, that is a same-unit-id conflict
  • Directories are only an engineering organization mechanism, not part of language semantics

Literal-db files obey the same rule: semantic identity depends only on the file stem, not on the containing directory.

4. Top-level Structure Restrictions

Under module mode, the top level may contain only:

  • (import package-path)
  • (export top-level-definition-or-var)
  • class
  • function
  • declare
  • Generic class
  • Generic function
  • Top-level var

The following are forbidden at top level in module mode:

  • Bare top-level executable expressions
  • Non-top-level import
  • Non-top-level export
  • Non-top-level class / function / generic definitions

5. Packages and Exports

5.1 package identity

  • Package identity depends only on the package-path string itself
  • One package may be composed of multiple source units

5.2 package export set

Only named top-level definitions wrapped in (export ...) enter the ordinary package export set:

  • class
  • function
  • declare
  • Generic class
  • Generic function
  • Top-level globals

The exp in (export exp) may only be one of the top-level syntax nodes above: class, generic class, function, declare, generic function, or top-level var. export may not wrap import, var_set, let, fn, control flow, calls, literals, identifiers, or {...} blocks.

Top-level names not wrapped in export still belong to the current package and may be resolved and used by other source units in the same package, but they are not visible to other packages.

Literal-db references still form package-visible reference entries according to the literal-db file rules; they are not wrapped in (export ...).

5.3 the special status of main

  • Top-level main is a unit-local entry symbol
  • main does not enter the ordinary package export set
  • Other units may not refer to a unit's main as an ordinary exported symbol through pkg@main

6. main Rules

If a top-level function is named main, it must satisfy all of the following:

  • It must not be declare
  • It must not be generic
  • It must be at the top level
  • It must have exactly one parameter
  • That parameter must be named args
  • The parameter type must be <array s3>
  • The return type must be i5
  • At most one main may be defined in a single unit

A project may contain multiple entry units; if the entry is not unique, the entry unit must be selected explicitly.

7. import

7.1 syntax and target

(import a~b~c)
  • The target of import is a package path, not a file path and not a unit id
  • import may appear only at the top level

7.2 duplicate, missing, and unused imports

The following cases must be errors:

  • Importing the same package more than once in one unit
  • Importing a package that does not exist
  • An import that ultimately contributes no visibility to any short-name or fully-qualified cross-package resolution

Note:

  • import controls cross-package visibility and imports only the exact target package
  • import a~b does not implicitly import a~b~c or any other child package
  • A cross-package fully-qualified name such as pkg@name or pkg$reference^ty still requires pkg to be the exact package imported by the current unit
  • Using a fully-qualified name from an imported package counts as using that import

8. Name Resolution

8.1 short-name resolution order

The resolution order for an unqualified short name is:

  1. Local lexical scope
  2. The current package
  3. Exported names from imported packages
  4. Builtin names

Once one layer uniquely matches, resolution stops and later layers are not searched.

8.2 current package wins first

  • When the current package matches, the result must not be upgraded into ambiguity merely because an imported package has a symbol with the same name
  • If multiple imported packages all match the same name, an ambiguity error must be reported

8.3 fully-qualified names

The fully-qualified form for a package-exported symbol is:

<package-path>@<symbol-name>

Its meaning is:

  • Directly reference a top-level name visible from some package
  • Require the target package to be either the current package or an exact package imported by this unit
  • It may not bypass package-export rules to access non-exported names or unit-local special cases
  • Overload resolution continues only inside the same package's same-name function set

The package-qualified form of a database reference does not use @, but instead:

<package-path>$<reference-id>^<ty>

Where:

  • @ is reserved for global / class / function names in package exports
  • $ is reserved for literal-db reference names
  • They are different naming entry points and may not be mixed

If a short-name database reference is not unique within the visible package set, a package-qualified database reference must be used. The package in a package-qualified database reference must also be the current package or an exact package imported by this unit.

9. Package-level Symbol Conflicts

Ironwall adopts a single main namespace with two limited overload exceptions.

The following cases must be errors:

  • Two class definitions with the same name in one package
  • A class and an ordinary function with the same name in one package
  • A class and a global with the same name in one package
  • A class and a generic class with the same name in one package
  • A class and a generic function with the same name in one package
  • A global and a function / declare with the same name in one package
  • A global and a generic class with the same name in one package
  • A global and a generic function with the same name in one package
  • A generic class and an ordinary function / declare with the same name in one package
  • A generic function and an ordinary function / declare with the same name in one package
  • Two generic class definitions in one package with the same name and the same number of type parameters
  • Two generic function definitions in one package with the same name and the same number of type parameters
  • Two ordinary functions or declares with exactly the same signature in one package

The following cases are allowed:

  • Ordinary named functions in one package may form an overload set by signature
  • Generic class declarations in one package may form an overload set by the number of type parameters under the same name
  • Generic function declarations in one package may form an overload set by the number of type parameters under the same name
  • Different packages may export the same short name

Additional rules:

  • class, ordinary function / declare, generic class, generic function, and top-level global all share a single package-level main namespace
  • Inside this main namespace, the names of class, ordinary function / declare, and top-level global must all be pairwise distinct
  • Generic class and generic function may not reuse any of those non-generic names either
  • There are only two allowed same-name cases: ordinary named functions overloaded by function signature, and generic class / generic function overloaded by type-parameter count

9.1 literal-db rules

A literal-db file must satisfy the following:

  • The file-name stem must be <package-path>$<reference-bundle>
  • The JSON top level must be an object
  • package is a reserved, required key; its value must be a string exactly equal to the file stem
  • The position of package has no semantic meaning; it need not be the first key-value pair
  • Aside from the reserved package and enum keys, every key must have the shape referenceId^ty
  • Aside from the reserved enum value, every value must be a string; even numeric content must be encoded as a string first and then interpreted by the typed-reference rules
  • Top-level keys within one JSON file must be unique after JSON escape decoding
  • A reference-bundle name must be unique within its package; different input directories must not provide files with the same <package-path>$<reference-bundle> stem
  • Within the same package, all referenceId^ty across all db files must be globally unique

enum rules:

  • An "enum" key may declare enum members; its position has no semantic meaning and its value must be a non-empty array
  • Every array element must be a string of the form referenceId^enum and must be unique within that JSON file and its package
  • An enum ordinal is its zero-based array position; enum arrays in different db bundles each start from 0
  • referenceId^enum must not be declared as an ordinary key-value pair and may only come from an enum array
  • Source code uses an enum member through $referenceId^enum or a package-qualified reference; its static type and lowering result are the corresponding i5 literal, and enum is not a distinct runtime type

Example:

{
  "package": "app~assets$banner",
  "enum": ["idle^enum", "running^enum", "stopped^enum"],
  "hello^s3": "Hello",
  "answer^i5": "42"
}

The following cases must be errors:

  • The reserved package key is missing, its value is not a string, or its value does not match the file-name stem
  • Duplicate top-level keys appear within one JSON file, including equivalent keys written with different JSON escapes
  • The enum value is not a non-empty array, or a member is not a string of the form referenceId^enum
  • referenceId^enum is declared as an ordinary key-value pair
  • Duplicate literal-db bundle names appear within the same package
  • Duplicate literal-db entry names appear within the same package
  • Source code writes a package-qualified non-reference shape such as a~b~d$3p14^f5

10. Reserved Names

  • The language builtin top-level names form a reserved set
  • self is also a reserved name
  • The std and std~... packages receive no reserved-name exemption; no package may define a top-level export that conflicts with the builtin reserved set
  • Ordinary std~... exports remain ordinary imported-package exports and are not automatically added to the global reserved set merely because they come from the standard library
  • User packages may define non-exported internal helpers; they must still obey the same-package main namespace conflict rules, but they are not cross-package API

11. Top-level Globals

11.1 basic rules

  • A top-level var is treated as a global definition
  • A global must have both an explicit type and an initializer
  • The declared type of a global must be a primitive type, or a union containing at least one primitive member
  • If the global type is a union, the payload computed by the initializer must also be a primitive payload assignable to that union
  • Declaration-first / initialization-later style is not supported

11.2 readability and writability

  • A package may read and write its own globals
  • Visible globals from other packages may also be read and written
  • When accessing another package through either a short name or a fully-qualified name, exact import is required for visibility; fully-qualified names remove short-name ambiguity but do not bypass import

11.3 initializer constraints

A global initializer must satisfy the following:

  • The initializer must converge into a primitive payload by static semantics
  • The initializer must not read any global
  • The initializer must not call ordinary functions, generic functions, or declare
  • The initializer must not allocate heap shapes such as class / array / closure / union objects
  • The initializer must not contain while, match, or any other node that cannot be guaranteed to stay inside the static-primitive subset
  • If the initializer needs intermediate state, it may use only local let / local var with explicit types, and the values of those locals must also always remain primitive payloads

The static-primitive subset contains at least:

  • Primitive typed literals
  • Literal-db text references
  • true, false, unit
  • if, cond, {...} block
  • Local let with explicit types
  • Local var with explicit types and var_set on that local
  • Direct pure builtin calls whose results remain primitive payloads

12. Global Initialization Model

  • The semantic result of a top-level global initializer must be statically determined
  • There is no initializer read-dependency between globals; therefore no user-visible global-init dependency graph is defined
  • File discovery order, directory order, and lexicographic order have no semantic force

13. Precompiled Library

A precompiled library is a packaged set of packages. To the user, the package exports it provides participate in import, name resolution, visibility, and type checking in the same way as ordinary source package exports.

Basic rules:

  • A precompiled library does not change the semantics of package paths, unit ids, imports, or exports
  • A package inside a precompiled library must still be explicitly imported with its exact package path
  • A precompiled library exposes only its public exports; unexported names are not visible
  • In the same program, source packages and precompiled-library packages must not provide conflicting definitions for the same package identity
  • Use of a precompiled library must not depend on whether its original source can be reread

13.1 Generic limits

Exported generic classes / generic functions in a precompiled library remain generic symbols, but the usable concrete instantiations are limited by the set of instances provided by that library.

Rules:

  • When using a precompiled generic, type arguments must first converge into concrete endtypes
  • Nested generic instances must converge layer by layer from the inside out
  • A generic use is legal only when the library provides the corresponding concrete instantiation
  • If the corresponding concrete instantiation is missing, that is a static error; the user must not require consumer-side expansion of the library's internal generic definition
  • The semantic identity of a concrete instantiation must preserve the source package-qualified generic name and the complete type-argument tuple, avoiding confusion with generics from other packages or with other arities

Example:

(import lib~box)

(function use_box ([value <lib~box@Box i5>]) to i5 in
  (<lib~box@box_unwrap i5> value))

The example is legal only if lib~box provides concrete instantiations for <Box i5> and <box_unwrap i5>. If it provides only <Box s3>, the use of <Box i5> must be rejected as a static error.

14. Entry

  • If there is no top-level main, no executable entry can be generated
  • If exactly one unit defines main, it may be selected automatically as the entry
  • If multiple units define main, the entry unit must be selected explicitly

Ironwall Core Semantics Specification

This document describes Ironwall's core semantics, including scope, evaluation rules, mutability, the constraints on classes and arrays, and the error model.

1. Overall Principles

  • Explicit beats implicit
  • Static analyzability beats stacks of syntax sugar
  • Safety and auditability beat complex implicit behavior
  • The language provides no language-level exception system

2. Scope and Name Resolution

Inside a core expression, names are resolved in the following order:

  1. Local lexical scope
  2. Top-level names in the current package
  3. Top-level names in imported packages, including explicitly imported std~... packages
  4. Language builtin names

Finer package rules at the module layer are defined by the module specification.

3. Mutability

3.1 Mutable bindings

The following bindings are semantically mutable through var_set:

  • Local variables introduced by var
  • let bindings
  • letrec bindings
  • Top-level globals visible to the current unit

3.2 Immutable bindings

The following bindings are immutable:

  • Parameters of fn / function
  • Parameters inside class methods and constructors
  • self

Applying var_set to an immutable binding must be an error.

4. Visibility of let and letrec

  • let bindings take effect from left to right in written order
  • A let initializer can see only the enclosing scope and earlier bindings in the same list; it cannot forward-reference or self-reference
  • letrec first introduces every name and its explicit function type into a shared scope, then evaluates initializers in written order; an fn body in an initializer may therefore recurse or mutually recurse
  • While a letrec initializer is being evaluated, it must not immediately read or write itself or a later, uninitialized binding; references deferred inside an fn body are allowed
  • letrec accepts only function-typed bindings, requires unique names, and makes every binding visible in its body

5. Control Flow

5.1 if

  • cond must be bool
  • The then and else branch types must be equal
  • Only the selected branch is evaluated

5.2 while

  • condition must be bool
  • The condition is checked before each iteration body runs
  • The type of the whole while expression is always unit

5.3 cond

  • else must exist and must be the last branch
  • Every non-else condition must be bool
  • All branch result types must be equal

5.4 block

  • {e1 ... eN} is evaluated in written order
  • The value of the block is the value of its last expression

6. Unions and match

6.1 union member lifting

  • If T is a member of <union ...>, then a T value may be assigned directly to that union type
  • A union must carry a runtime tag

6.2 match

  • The matched value must be a union type
  • The bound type of each typed branch must correspond to an immediate union member, and no member may appear twice
  • Without else, typed branches must exhaustively cover every immediate union member
  • With else, it must be the unique final branch and handles all members not covered by typed branches; it is rejected as redundant if typed branches already cover the union
  • The result types of typed branch bodies and the else body must be equal
  • If a union member is itself a union, the outer match binds that nested union value. A second match is required to inspect the nested union's own runtime tag and payload

If a value does not satisfy the type precondition of match, that is an unrecoverable failure.

7. Classes and Objects

7.1 Basic class constraints

  • Every class must have constructors; multiple constructors are allowed, and they are overloaded by parameter uniqueness
  • Property names must be unique within a class
  • Method names must be unique within a class
  • A property and a method may not share the same name
  • Inheritance is not supported
  • Properties and methods not marked public are private by default
  • (public ...) may mark only properties and methods; constructors are public by default
  • Ordinary classes and generic classes obey the same member-visibility rules

7.2 constructor constraints

  • A constructor must initialize all properties
  • A constructor must not read a property before that property has been initialized
  • When a property is read indirectly through a method, the initialization-order requirement still applies

7.3 self

  • self is automatically bound only inside methods and constructors
  • self is an immutable binding, but its fields may be initialized or modified through cm_set

7.4 member visibility

  • Outside a class, cm_get, member-chain sugar, and method-value access may read only public properties and methods
  • Outside a class, cm_set may write only public properties
  • Inside a class method or constructor, self may read and write private properties of the same class, and may read private methods of the same class
  • After generic class instantiation, the instantiated class still preserves the property / method visibility of the source generic class
  • External access to a private member must be rejected as a static error

8. Arrays

  • <array T> is a fixed-length array
  • array_get / array_set must perform bounds checks
  • array_length returns i5
  • If a class is batch-created by array_new as an element type, that class must have a zero-argument constructor, so that the array can be built through the zero-arg constructor

9. Top-level globals

  • A top-level var in module mode denotes a global
  • A global must have both an explicit type and an initializer
  • A global type must be a primitive type, or a union containing at least one primitive member
  • A global initializer must be determined by static semantics as a primitive payload
  • A global initializer must not read other globals, and must not call user-defined functions, generic functions, or declare
  • A global initializer is restricted to control flow and builtins inside the static-primitive subset
  • As long as a global is visible to the current unit, that global may be read and written; short-name access must still obey import visibility rules

Finer module-level global rules are defined by the module specification.

10. Error Model

10.1 Static errors

The following are static diagnostics:

  • Lexical errors
  • Syntax errors
  • Type errors
  • Name-resolution ambiguities
  • Illegal top-level structure
  • Global-init cycles
  • Assignment to immutable bindings

10.2 Runtime failures

The following are unrecoverable runtime failures:

  • Array out-of-bounds access
  • Invalid union tag
  • Violated builtin preconditions
  • Other unrecoverable failures that violate execution preconditions

10.3 Exception ban

  • The language does not provide throw, try, or catch
  • Recoverable failure should be modeled with unions or other explicit data models

Ironwall C FFI Specification

This document defines Ironwall's current C FFI rules, including Ironwall calling C, C calling Ironwall, and the types and naming rules allowed across the boundary.

1. Position

Ironwall does not encourage FFI.

FFI is a temporary compromise, not Ironwall's ideal boundary. The reason is direct:

  • C does not provide the memory-safety and type-safety guarantees that Ironwall wants to provide
  • C code can read and write out of bounds, keep dangling pointers, corrupt the runtime heap, corrupt GC metadata, and free memory incorrectly
  • Once execution enters C, Ironwall's safety model can only treat C as a trusted but unsafe external world

Therefore:

  • FFI should be used only at necessary system boundaries, for existing C libraries, platform syscall wrappers, and transitional runtime glue
  • FFI should not be treated as a routine abstraction mechanism
  • FFI should not be used to bypass the Ironwall type system, GC, safety boundary, or module rules
  • A bug on the C side is a bug that can break the whole process; it is not an ordinary error that Ironwall can fully isolate

The existence of FFI is an engineering reality, not a language direction. Ironwall's long-term direction should be to reduce FFI surface area, not to expand it.

2. Core Model

C FFI has two directions:

  • Ironwall calls C: external C functions are declared in Ironwall with declare
  • C calls Ironwall: Ironwall functions with names following the export rule are exported as C-callable functions

The two directions use different ABIs:

  • declare ... clang ... uses the low-level runtime ABI, where C functions directly receive and return iw_value_t
  • iwlang export uses the C boundary ABI, where C functions use int64_t, const char *, char *, and array structs

This split is intentional:

  • When Ironwall calls C, C is treated as an internal runtime extension and must understand iw_value_t
  • When C calls Ironwall, the external caller should not depend directly on heap-object layout; cross-boundary values must use ABI representations allowed by the specification

3. Naming Rules

FFI symbols must use a full name carrying a namespace UUID and confirmation tag. Old-style bare C symbols do not conform to the spec.

3.1 names for Ironwall calling C

Format:

_<uuid>_clang_<function_name>_<tag1>

Where:

  • <uuid> is a namespace string containing only ASCII letters and digits
  • clang is fixed and indicates "this symbol is provided by C"
  • <function_name> must match the C-identifier shape: [A-Za-z_][A-Za-z0-9_]*
  • <tag1> is an 8-digit hexadecimal confirmation tag

Example:

_81af42c9d7354eb08bfe95163c04ad20_clang_iw_build_json_add_seven_c267f2a7

If the tag does not match the uuid / function name, compilation must reject it.

3.2 export names for C calling Ironwall

Format:

_<uuid>_iwlang_<function_name>_<tag1>

Where:

  • iwlang is fixed and indicates "this symbol is exported by Ironwall"
  • The other fields follow the same rules as the clang naming form

Example:

_4a8b9c0d1e2f34567890abcdef123456_iwlang_iw_export_i5_roundtrip_bca9013a

3.3 purpose of the tag

The confirmation tag is not a security key and not a permission mechanism. Its purpose is:

  • To prevent hand-written symbols from silently linking when the namespace or function name was typed incorrectly
  • To provide a low-cost consistency check for cross-language boundary names
  • To keep old-style bare symbols from silently mixing into the formal FFI spec

Users may calculate tags by the following rule, or use a naming tool that follows this rule.

3.4 confirmation-tag calculation rule

The confirmation tag is calculated with 64-bit FNV-1a.

hashText(input) is defined as follows:

  • Initial value: 14695981039346656037
  • Prime: 1099511628211
  • For each UTF-16 code unit in the input:
  • hash = hash xor code_unit
  • hash = (hash * prime) mod 2^64
  • The final output is a 16-digit lowercase hexadecimal string, left-padded with 0 if needed

The hash input for a declared C function using clang is:

<uuid>clang<function_name>

The hash input for an exported Ironwall function using iwlang is:

<uuid>iwlang<function_name>

tag1 is the last 8 hexadecimal digits of hashText(hash_input).

Example:

uuid = 81af42c9d7354eb08bfe95163c04ad20
language = clang
function_name = iw_build_json_add_seven
hash_input = 81af42c9d7354eb08bfe95163c04ad20clangiw_build_json_add_seven
hashText(hash_input) = 6d7038b4c267f2a7
tag1 = c267f2a7

Therefore the full symbol is:

_81af42c9d7354eb08bfe95163c04ad20_clang_iw_build_json_add_seven_c267f2a7

4. Ironwall Calling C

4.1 Ironwall declaration syntax

Ironwall declares C functions through declare:

(declare
  (function _81af42c9d7354eb08bfe95163c04ad20_clang_iw_build_json_add_seven_c267f2a7
    ([value i5])
    to i5))

It is then used like an ordinary function:

{program app@main
  (declare
    (function _81af42c9d7354eb08bfe95163c04ad20_clang_iw_build_json_add_seven_c267f2a7
      ([value i5])
      to i5))

  (function main ([args <array s3>]) to i5 in
    (_81af42c9d7354eb08bfe95163c04ad20_clang_iw_build_json_add_seven_c267f2a7 $35^i5))
}

4.2 C-side function signature

The current C ABI for declare is the iw_value_t ABI. A C function must directly receive and return iw_value_t:

#include <stdint.h>

typedef intptr_t iw_value_t;

static inline int64_t iw_as_i64(iw_value_t value) {
    return ((int64_t)value) >> 1;
}

static inline iw_value_t iw_from_i64(int64_t value) {
    return (iw_value_t)(intptr_t)((((uint64_t)value) << 1) | 1ULL);
}

iw_value_t _81af42c9d7354eb08bfe95163c04ad20_clang_iw_build_json_add_seven_c267f2a7(iw_value_t value) {
    int32_t raw = (int32_t)iw_as_i64(value);
    uint32_t wrapped = ((uint32_t)raw) + 7u;
    return iw_from_i64((int64_t)(int32_t)wrapped);
}

Note: iw_as_i64 / iw_from_i64 on iw_value_t describe the carrying format of the tagged immediate, not the semantic width of every integer type. The semantic width of i5 is 32-bit. If a declared C function wants to use an i5 as a native number, it must first explicitly convert it to int32_t on the C side before doing arithmetic.

4.3 unit return values

In the C ABI, Ironwall unit is still represented as iw_value_t. The C side should return iw_from_i64(0):

(declare
  (function _5e8f0a4c71d24b6fa39ce2158bd7f043_clang_iw_sys_fd_close_a14b05cf
    ([fd i5])
    to unit))
iw_value_t _5e8f0a4c71d24b6fa39ce2158bd7f043_clang_iw_sys_fd_close_a14b05cf(iw_value_t raw_fd) {
    int fd = (int)iw_as_i64(raw_fd);
    close(fd);
    return iw_from_i64(0);
}

4.4 building heap values on the C side

If a declared C function needs to return an Ironwall heap value, it must not manually allocate or forge heap objects; it must use boundary functions provided by the declared C ABI.

Public heap values that C may create include:

  • s3
  • <array i5>
  • <array s3>

The declared C ABI must provide these operations:

iw_value_t iw_make_s3(const char *data);
iw_value_t iw_make_array_i5(int64_t length);
iw_value_t iw_make_array_s3(int64_t length);

int32_t iw_array_i5_get(iw_value_t value, int64_t index);
void iw_array_i5_set(iw_value_t value, int64_t index, int32_t element_value);
int64_t iw_array_i5_length(iw_value_t value);

iw_value_t iw_array_s3_get(iw_value_t value, int64_t index);
void iw_array_s3_set(iw_value_t value, int64_t index, iw_value_t element_value);
int64_t iw_array_s3_length(iw_value_t value);

Rules:

  • iw_make_s3 must copy C string content into Ironwall s3; Ironwall must not depend on the C buffer after the function returns
  • iw_make_array_i5 / iw_make_array_s3 create fixed-length Ironwall arrays
  • Array get / set must obey Ironwall array bounds rules; out-of-bounds access is an unrecoverable runtime failure
  • The element value passed to iw_array_s3_set must be a valid Ironwall s3 value, usually created by iw_make_s3
  • C must not keep iw_value_t values returned by these functions as long-lived state across calls

Example:

{program app@main
  (declare
    (function _9a4c2e1f6b7d8c0a1234567890abcdef_clang_iw_ffi_make_array_i5_dfb65f00
      ()
      to <array i5>))

  (function main ([args <array s3>]) to i5 in
    (array_get (_9a4c2e1f6b7d8c0a1234567890abcdef_clang_iw_ffi_make_array_i5_dfb65f00) $0^i5))
}
iw_value_t _9a4c2e1f6b7d8c0a1234567890abcdef_clang_iw_ffi_make_array_i5_dfb65f00(void) {
    iw_value_t value = iw_make_array_i5(3);
    iw_array_i5_set(value, 0, 7);
    iw_array_i5_set(value, 1, 11);
    iw_array_i5_set(value, 2, 13);
    return value;
}

4.5 portable declared-C types

The declare ... clang ... direction may currently use value types from ordinary Ironwall function types, but the formal, portable, and recommended set is:

  • unit
  • bool
  • i5
  • i6
  • i7
  • u5
  • u6
  • u7
  • f5
  • f6
  • f7
  • c3
  • c4
  • c5
  • s3
  • s4
  • s5
  • z5
  • z6
  • z7
  • <array i5>
  • <array s3>

Where:

  • All values cross the declared-C ABI as iw_value_t
  • Integer, unsigned, bool, and unit are immediate iw_value_t
  • Float, text, complex, and array values are heap/reference iw_value_t
  • <array i5> and <array s3> have stable boundary operations
  • Other heap types should not be used as public C FFI API types

The following are not recommended across the declared-C boundary:

  • Class instances
  • Closures
  • Unions
  • Nested arrays
  • Generic class instances other than <array i5> / <array s3>

These types would tie C to Ironwall's internal layout, GC metadata, and runtime tags, which is poor for both safety and compatibility.

5. C Calling Ironwall

5.1 export mode

If an Ironwall function is to be exported to C, its function name must use the iwlang naming rule:

{program app@main
  (function _4a8b9c0d1e2f34567890abcdef123456_iwlang_iw_export_i5_roundtrip_bca9013a
    ([value i5])
    to i5
    in
    (add value $1^i5))
}

5.2 C-visible signatures

When C calls Ironwall, it does not use the declared-C iw_value_t ABI directly. Instead it uses the C boundary ABI:

Ironwall typeC parameter typeC return type
i5int32_tint32_t
s3iw_host_s3_view_tiw_host_s3_t
<array i5>iw_host_array_i5_tiw_host_array_i5_t
<array s3>iw_host_array_s3_view_tiw_host_array_s3_t

At present, only the types in the table above are supported for C calling Ironwall. Other types must not be used as parameters or return values of exported Ironwall functions.

The C boundary ABI uses explicit-length borrowed-input and owned-output types:

typedef struct iw_host_s3_view_t {
    const char *data;
    size_t length;
} iw_host_s3_view_t;

typedef struct iw_host_s3_t {
    char *data;
    size_t length;
} iw_host_s3_t;

#define IW_HOST_S3_LITERAL(text) ((iw_host_s3_view_t){ (text), sizeof(text) - 1u })

typedef struct iw_host_array_i5_t {
    int64_t length;
    int32_t *items;
} iw_host_array_i5_t;

typedef struct iw_host_array_s3_view_t {
    int64_t length;
    const iw_host_s3_view_t *items;
} iw_host_array_s3_view_t;

typedef struct iw_host_array_s3_t {
    int64_t length;
    iw_host_s3_t *items;
} iw_host_array_s3_t;

5.3 memory ownership

Exported functions perform copying at the C/Ironwall boundary.

Rules:

  • When C passes iw_host_s3_view_t, the boundary function copies exactly length bytes into Ironwall s3; the content need not be NUL-terminated
  • When C passes an array struct, the boundary function copies the array contents
  • When Ironwall returns s3, the boundary function allocates iw_host_s3_t.data and sets length
  • When Ironwall returns <array i5> or <array s3>, the boundary function allocates the items field inside the C array struct
  • The C caller must free heap memory returned by the C boundary

The C boundary ABI must provide corresponding free operations:

void iw_host_free_s3(iw_host_s3_t value);
void iw_host_free_array_i5(iw_host_array_i5_t value);
void iw_host_free_array_s3(iw_host_array_s3_t value);

6. GC and Safety Requirements

C FFI must obey the following rules:

  • C must not retain Ironwall heap pointers as long-lived state unless the spec explicitly allows it
  • C must not manually free Ironwall heap objects
  • C must not forge iw_value_t heap references
  • C must not modify the Ironwall heap header, runtime type tag, GC tag, or metadata table
  • If C needs to construct Ironwall heap values, it must use boundary functions allowed by the specification
  • If C needs to return unit, it must return iw_from_i64(0)
  • If C receives an owned iw_host_s3_t / host array from an exported Ironwall function, it must release it using the corresponding C boundary free operation

7. Discouraged Patterns

The following patterns do not fit Ironwall's safety position:

  • Writing large amounts of business logic in C while treating Ironwall only as glue
  • Using C to directly manipulate internal layouts of Ironwall class / closure / union values
  • Passing raw pointers, integerized addresses, or unmarked ownership across FFI
  • Treating C global state as shared mutable state outside the Ironwall type system
  • Depending on undocumented runtime struct layouts
  • Using FFI to bypass semantic rules around unit, array bounds, GC roots, or module identity

If a feature can be written in Ironwall, it should be written in Ironwall first. FFI should serve only as a narrow bridge across an unsafe external world.

Ironwall Base-Lib Specification

This document defines how Ironwall's builtin standard library is loaded, how its packages are structured, and where its public API boundary lies.

1. Overall Principles

  • Base-lib source units are not special syntax units, and they are not injected fragments in the static-check or code-generation stages
  • The base lib must fully obey the package-system specification: canonical file names, canonical program headers, ordinary import, explicit (export ...), and class-member public

2. Loading Model

  • Standard-library source units and user source units go through the same unit-id validation, explicit package export, class-member visibility, and static-check pipeline
  • It is not allowed to skip package rules, reserved-name rules, or overload rules merely because a unit comes from the base lib

3. Package Split

Builtin standard packages:

  • std~box
  • std~option
  • std~array
  • std~list
  • std~set
  • std~dict
  • std~pair
  • std~eq
  • std~ord
  • std~hash
  • std~io
  • std~gc
  • std~linux~sys
  • std~windows~sys
  • std~math
  • std~string

There is no requirement that a single aggregate package std exist. If a user wants to use names from the standard library, they must explicitly import the corresponding std~... package just as they would import any other package.

4. Support-Type Packages

4.1 std~box

std~box provides the smallest generic single-value wrapper:

  • <Box T>
  • <box_unwrap T>

Box<T> is represented as an ordinary generic class with one value property and exposes the inner value through an unwrap method.

4.2 std~option

std~option provides the smallest generic maybe-value wrapper:

  • <Option T>
  • <option_some T>
  • <option_none T>
  • <option_is_some T>
  • <option_is_none T>
  • <option_unwrap T>

Option<T> is represented as an ordinary generic class containing one <union unit T> payload.

  • is_some / is_none perform an explicit union test on the payload
  • unwrap returns the inner value in the Some branch and performs an explicit runtime abort in the None branch

4.3 std~array

std~array provides the first nominal wrapper layer around builtin <array T>:

  • <Array T>
  • <ArrayBuilder T>
  • <array_new_fill T>
  • <array_wrap T>
  • <array_builder_new T>
  • <Array_len T>
  • <ArrayBuilder_len T>

Array<T> publicly provides the following methods:

  • get
  • set
  • fill

ArrayBuilder<T> provides:

  • append
  • build

Where:

  • Array<T> wraps a fixed-length <array T> and retains a seed; array_wrap preserves the identity of the caller-provided raw array
  • ArrayBuilder<T> is represented by a growable raw array, a seed, and a logical length; capacity starts at 4 and doubles when necessary, or grows directly to the required capacity
  • build returns an Array<T> whose length exactly equals the logical length; it may reuse the backing array when capacity already matches
  • The current core surface does not provide contains, concat, sorting, reversal, extrema, or Eq<T> / Ord<T> operations

4.4 std~list

std~list provides the first nominal wrapper layer around a dynamic-length ordered container:

  • <List T>
  • <list_new T>
  • <list_len T>

List<T> publicly provides the following methods:

  • get
  • set
  • append
  • insert
  • remove
  • pop
  • clear

Where:

  • List<T> is represented as an ordinary generic class containing items, tail, seed, and length; items / tail use a <union unit <ListNode T>> node chain
  • append uses the tail pointer for tail insertion; insert, remove, and pop relink nodes directly rather than rebuilding the whole chain
  • get / set provide random-access behavior
  • remove depends on an explicit Eq<T> support object, removes only the first matching element, and aborts if no match exists
  • insert accepts only indices in 0..len; get / set / pop accept only indices in 0..len-1; out-of-range access performs an explicit runtime abort
  • clear resets both items and tail to unit and resets the length to zero
  • The current core surface does not provide contains, concat, repeat, copy, count, index, reverse, sort, or extrema helpers

4.5 std~set

std~set provides an open-addressed mutable set wrapper:

  • <Set T>
  • <set_new T>
  • <set_len T>
  • <set_contains T>

Set<T> publicly provides the following methods:

  • add
  • remove
  • discard
  • clear

Where:

  • Set<T> stores explicit Hash<T> / Eq<T> support objects plus value-slot, hash, state, size, used, and capacity storage
  • Value slots use <union unit <Box T>>; states 0/1/2 denote empty / occupied / tombstone
  • Initial capacity is 8; when used / capacity reaches 3/4, capacity doubles and values are rehashed
  • Membership compares hashes first, then uses Eq<T> on collisions
  • add / remove / discard / clear mutate in place; remove aborts when missing, while discard does nothing
  • The current core surface does not provide pop, copy, set algebra, update, or relation helpers

4.6 std~dict

std~dict provides a mutable dict wrapper represented with open-addressed slot arrays:

  • <Dict K V>
  • <dict_new K V>
  • <dict_store K V>
  • <dict_len K V>
  • <dict_contains K V>

Dict<K, V> publicly provides the following methods:

  • get
  • pop
  • clear

Where:

  • Dict<K, V> is represented as an ordinary generic class containing explicit Hash<K> / Eq<K> support objects, key/value seeds, key/value slot arrays, hash/state arrays, and size/used/capacity; it does not preserve insertion order
  • Key slots and value slots use <union unit <Box T>> to represent empty and occupied slots; the state array uses integer states to distinguish empty / occupied / tombstone
  • dict_new requires the caller to supply Hash<K>, Eq<K>, a key seed, and a value seed; initial capacity is 8, and capacity doubles with rehashing when used / capacity reaches 3/4
  • dict_store inserts or overwrites a value; overwriting an existing key does not increase size
  • get returns the caller-provided fallback when the key is missing; pop performs an explicit runtime abort when missing
  • clear preserves current capacity but rebuilds empty storage and resets size / used
  • The current core surface does not provide popitem, update, setdefault, snapshots, merge, equality, fold, map, or filter helpers

4.7 std~pair

std~pair provides the smallest generic two-tuple nominal wrapper:

  • <Pair K V>
  • <pair_first K V>
  • <pair_second K V>

Pair<K, V> is represented as an ordinary generic class with two properties, first and second.

4.8 std~eq

std~eq provides an explicit equality support object:

  • <Eq T>
  • <eq_apply T>

Eq<T> wraps one comparator of type <to bool from T T> and exposes calls through the equals method.

4.9 std~ord

std~ord provides an explicit ordering support object:

  • <Ord T>
  • <ord_compare T>

Ord<T> wraps one comparator of type <to i5 from T T>. Its compare method follows the negative / zero / positive return convention.

4.10 std~hash

std~hash provides an explicit hashing support object:

  • <Hash T>
  • <hash_apply T>

Hash<T> wraps one hasher of type <to i5 from T> and exposes calls through the hash method.

5. std~io

std~io provides basic input, text output, flushing, and small ASCII-parsing helpers.

5.1 output overloads

The following names are ordinary top-level overloads, not builtins:

  • print : s3|s4|s5 -> unit
  • println : s3|s4|s5 -> unit
  • print_stderr : s3|s4|s5 -> unit
  • println_stderr : s3|s4|s5 -> unit

5.2 flush

  • flush : () -> unit
  • flusherr : () -> unit

5.3 input and small text helpers

  • stdin_read_i5 : () -> i5
  • stdin_read_line_s3 : () -> s3
  • stdin_read_nonempty_line_s3 : () -> s3
  • stdout_write_c3 : c3 -> unit
  • stdout_write_i5_ascii : i5 -> unit
  • find_first_space_index : s3 -> i5
  • parse_i5_range : (s3, i5, i5) -> i5
  • pair_line_left_i5 : s3 -> i5
  • pair_line_right_i5 : s3 -> i5
  • pair_line_separator : s3 -> c3
  • print_i5_with_sep : (i5, c3) -> unit

parse_i5_range accumulates ASCII decimal code units in [start, end) and accepts a leading -; it is a small contest/tool-input helper, not a general validating parser. stdin_read_nonempty_line_s3 retries once when the first line is empty; it does not guarantee skipping an arbitrary number of empty lines.

6. std~gc

std~gc exposes the explicit collection entry:

  • gc_collect : () -> i5

It is an ordinary imported-package function, not syntax or an implicit safepoint. Its result is the number of heap objects reclaimed by that sweep.

7. Platform System Packages

System-boundary standard packages are explicit by target platform: std~linux~sys and std~windows~sys. Both are thin host-wrapper packages that abort directly on host-call failure instead of returning errno/result objects.

7.1 Linux (std~linux~sys)

The public std~linux~sys surface is grouped into three slices:

  • policy-aligned process / env / argv / time wrappers: sys_platform_name, sys_process_argc, sys_process_argv_s3, sys_env_get_s3, sys_env_set_s3, sys_env_unset_s3, sys_process_getpid, sys_process_spawn_s3, sys_process_spawn_stdio_s3, sys_process_wait, sys_process_kill, sys_process_id, sys_process_close, sys_process_exit, sys_process_exit_group, sys_time_unix_ms, sys_time_monotonic_ms, sys_sleep_ms
  • file / path / dir / stdio wrappers: sys_file_*, sys_fd_*, sys_path_*, sys_dir_open_s3, sys_dir_read_s3, sys_dir_close, sys_stdin_handle, sys_stdout_handle, sys_stderr_handle, sys_pipe_create, plus SysFileStat and sys_stat_* accessors
  • Linux-specific fd / network / readiness / signal primitives: sys_fd_readv_s3, sys_fd_writev_s3, sys_fd_sendfile, sys_fd_dup*, sys_fd_fcntl_*, sys_net_*, sys_epoll_*, sys_eventfd_*, sys_timerfd_*, sys_signalfd_*, sys_poll, sys_ppoll, sys_signal_*, sys_thread_gettid, sys_thread_yield

Where:

  • SysProcess is a pid-centric nominal wrapper. Callers should still pair it with sys_process_close() on Linux so the cross-platform surface remains consistent.
  • sys_file_* is the higher-level policy alias layer; sys_fd_* remains available for code that explicitly wants fd / offset / flag oriented operations.
  • sys_fd_pipe2() and sys_pipe_create() both return a length-2 <array i5> with index 0 as the read end and index 1 as the write end.
  • sys_fd_openat_* uses an explicit dir fd + relative child path model rather than an implicit AT_FDCWD helper.
  • sys_fd_fstat() / sys_file_stat() / sys_path_stat_s3() all produce nominal SysFileStat values. On Linux this structure preserves device/inode/mode/link_count/uid/gid/rdevice/size/block_size/block_count/atime_sec/mtime_sec/ctime_sec; common file-type checks should prefer sys_stat_is_regular / sys_stat_is_dir.
  • std~linux~sys does not export sys_process_fork, sys_process_execve_s3, sys_process_wait4, or sys_thread_tgkill as public wrappers.

7.2 Windows (std~windows~sys)

std~windows~sys follows the same cross-platform policy slice and adds the Windows-side handle, event, wait, and TCP socket wrappers:

  • platform / env / argv / process / time wrappers: sys_platform_name, sys_process_argc, sys_process_argv_s3, sys_env_get_s3, sys_env_set_s3, sys_env_unset_s3, sys_process_getpid, sys_process_spawn_s3, sys_process_spawn_stdio_s3, sys_process_wait, sys_process_kill, sys_process_id, sys_process_close, sys_process_exit, sys_process_abort, sys_time_unix_ms, sys_time_monotonic_ms, sys_sleep_ms
  • file / path / dir / stdio wrappers: sys_file_*, sys_fd_*, sys_path_*, sys_dir_open_s3, sys_dir_read_s3, sys_dir_close, sys_stdin_handle, sys_stdout_handle, sys_stderr_handle, sys_pipe_create, SysFileStat, sys_stat_size, sys_stat_is_regular, sys_stat_is_dir
  • Windows network / event / wait wrappers: sys_net_startup, sys_net_cleanup, sys_net_*, sys_event_create_manual, sys_event_create_auto, sys_event_set, sys_event_reset, sys_event_close, sys_wait_one, sys_wait_many, sys_wait_timeout_code
  • thread identity wrapper: sys_thread_gettid

Where:

  • Windows shares the same high-level platform/env/path/process/time policy model, but sys_process_close() really closes native handles on Windows, so portable code should not omit it.
  • Windows TCP wrappers follow the Winsock lifecycle, so callers use sys_net_startup() / sys_net_cleanup(); socket close goes through sys_net_close(), while ordinary handle/event close goes through sys_event_close().
  • std~windows~sys does not expose Linux-only raw primitives such as fork / execve / wait4 / tgkill, and it does not expose Linux-specific epoll / eventfd / timerfd / signalfd / poll surfaces.
  • Portable code should target the shared policy slice first, and depend on std~linux~sys explicitly only when Linux readiness / signal primitives are actually required.

8. std~math

std~math provides floating-point and complex-number APIs.

8.1 constants

Use explicit typed variants rather than overloading on return type:

  • pi_f5, pi_f6, pi_f7
  • tau_f5, tau_f6, tau_f7

8.2 floating-point API

The following names form ordinary overloads on f5 / f6 / f7:

  • abs
  • round
  • floor
  • ceil
  • trunc
  • sin
  • cos
  • sqrt
  • hypot
  • atan2

Where:

  • abs / sin / cos / sqrt / hypot / atan2 return floating-point values of the same type
  • round / floor / ceil / trunc return i5

8.3 complex-number API

The following names form ordinary overloads on z5 / z6 / z7:

  • znew
  • zrect
  • zreal
  • zimg
  • zadd
  • zsub
  • zmul
  • zabs
  • zarg
  • zconj
  • zproj
  • zexp
  • zlog
  • zsqrt
  • zpow

Scalar conversions named val_to_* / bin_to_* are currently resolved directly by the compiler and belong to the language-builtin boundary; they neither require nor come from (import std~math).

9. std~string

std~string provides the first nominal wrapper layer around the text primitive families:

  • StringS3
  • StringS4
  • StringS5
  • StringBuilderS3
  • StringBuilderS4
  • StringBuilderS5
  • string_len
  • string_concat
  • string_slice
  • string_builder_new
  • string_builder_len

StringBuilderS3 / StringBuilderS4 / StringBuilderS5 provide:

  • append
  • build

Where:

  • StringS3 / StringS4 / StringS5 wrap s3 / s4 / s5 respectively
  • string_concat returns a new wrapper; string_slice(value, start, end_exclusive) returns a new wrapper after resolving negative indices against the length and clamping to 0..len; empty or reversed ranges return an empty string
  • A builder stores a raw-string chunk array, chunk count, and total code-unit length; it ignores empty chunks, grows capacity geometrically from 4, and build allocates and copies a snapshot once
  • string_builder_new takes a same-family StringSN as the empty-string seed; string_builder_len returns the accumulated code-unit length
  • std~string also explicitly exports s3_empty_like, s3_slice_span_raw, s3_slice_raw, and s3_concat_raw for raw-s3 paths
  • The current core surface does not provide contains, repeat, reverse, find, count, startswith, or endswith
  • Semantics are defined by a single code-unit / byte text model; there is no Unicode normalization or grapheme-cluster handling

10. Builtin Boundary

  • std~... packages are ordinary packages, not part of the builtin name set
  • They may wrap runtime helpers exposed through declare, and may wrap language primitives, but the top-level names they expose after wrapping must still enter the ordinary package export set through (export ...)
  • These names are usable only when visible through the current package or an imported package

11. Compatibility Requirements

  • Future standard-library evolution should happen primarily by adding new std~... packages or new explicit (export ...) entries to existing std~... packages
  • Synthetic std injection, base-lib AST injection, or special static-check / codegen branches for the base lib should not be introduced