Detailed semantic elaboration for the Core-0 authority.
ZergLang Core-0 Semantic Reference
Status: normative elaboration of the stable Core-0 clauses
Stable citations use the clause identifiers in the sibling modular documents. This reference preserves the detailed first-edition decisions behind those clauses. If it conflicts with a stable clause, the stable clause governs.
Core-0 fixes the common object, message, type, ownership, generic, error, effect, module, authority, numeric-preset, and diagnostic substrate. It intentionally implements only ordered algorithmic execution. Workflow, computational, reactive, and optimization execution remain part of the ZergLang direction, and their keywords are reserved, but their bodies are deferred to later editions.
The complete grammar is in core-0.ebnf. This document supplies
the semantic restrictions that cannot usefully be expressed in EBNF.
1. Accepted Core-0 decisions
Core-0 makes the following choices:
- Every executable message explicitly says
algorithm.workflow,compute,state, andoptimizeare reserved domain keywords. The parser recognizes their declaration shape, after which the front end emitsZL-DOMAIN-0001; it never interprets one as ordered code. - There are two nominal object declarations. A
valueis inline/value- semantic and always sealed. Aclassis a stable, uniquely owned heap object; its bare type is the owning handle.Shared<T>andWeak<T>are explicit, opt-in shared ownership for classes. - Classes and messages are sealed by default.
openexplicitly permits class derivation or message override. An override is sealed again unless it also saysopen. - A class is constructed only through
Type.init(allocator, ...) -> Result<Self, InitError<A, E>>, where the initializer receives access to anAllocator<A>.A = Neverselects a faulting allocation policy;E = Nevermeans construction has no domain failure. - Runtime OO polymorphism uses one open concrete base class and open messages. Compile-time generic polymorphism uses structural message requirements. There is no second nominal interface/protocol hierarchy.
- Operators come from one closed token/precedence table, but eligible operator selectors are user-overloadable. An operator has no alternate named-message spelling and does not introduce general overload resolution.
- Every top-level declaration and member writes exactly one visibility:
public,module, orprivate. Every message writes its domain; Core-0 accepts only explicitalgorithm. - Recoverable failure has one source model:
Result<T, E>and prefixtry. Core-0 has neitherthrowssyntax nor exception unwinding. - Every source module has exactly one authoritative free-English
.zl.md. The AI-authority workflow always generates and checks both.zland canonical.zli; neither generated file outranks the English authority. A.zli.mdis optional additional authority for the public contract. - Every checked module pins one numeric semantic preset:
exact,modular, orapproximate. This axis is orthogonal toverified/checked/trustedverification andobserve/adaptive/sealedmaterialization. - Source is checked into a portable typed module before either native lowering or interpretation. Both engines consume the same ownership, cleanup, error, effect, dispatch, and numeric-preset semantics.
These decisions are deliberately narrower than the whole design notebook. A deferred feature is not rejected as a language direction; it is excluded from the first conformance target.
2. Checked compilation model
The normative front-end phases are:
authoritative .zl.md plus optional .zli.md
-> generated UTF-8 .zl implementation
-> lossless tokens and concrete syntax
-> canonical AST
-> resolved high-level IR
-> typed and effect-checked IR
-> ownership and initialization checked CFG
-> drop-elaborated Core IR
-> canonical generated .zli interface
-> checked portable module
|-> checked interpreter
`-> native lowering
A checked portable module records the language edition, numeric preset, types, value/class schemas, dispatch contracts, generic instances, effects, ownership operations, cleanup edges, source maps, authority hashes, and verification status. An implementation must not defer a source type, ownership, initialization, effect, or interface error until interpretation.
The format is implementable without compiler-host reflection: nodes may be stored as tagged C structs in per-module arenas, references may be integer indices, and names may be interned IDs. A serialized module begins with a schema version. Host pointers are never serialized.
3. Modules, generated code/interfaces, and English authority
3.1 Authority modules and imports
Each source module has exactly one module.zl.md. Its path beneath the package
authority root determines the dotted module path. The file is unconstrained
free English rather than ZergLang syntax. The generator emits a sibling/build
artifact module.zl whose declaration must equal that path:
authority/io/buffered.zl.md
-> generated/io/buffered.zl
-> module io.buffered;
A package manifest selects the authority root, generated-artifact root, dependencies, and numeric preset; it is outside the Core-0 source grammar. Core-0 has no partial modules, wildcard imports, aliases, re-exports, top-level executable statements, or module-level mutable state.
The generated .zl expresses each dependency as an import naming one public
top-level declaration:
import std.path.Path;
import std.io.IoError;
The final path component enters the declaration namespace. Imports do not leak a dependency’s imports. A collision is an error, and source directly imports every external declaration it names. The Core-0 module graph is acyclic; a cycle diagnostic contains the complete path. Declarations within one module are collected before bodies are resolved, so local forward references are legal.
3.2 Generated .zl and .zli
The AI-authority workflow always generates .zl and .zli, then checks both.
Neither is a handwritten authority or optional C-style header. .zl is the
complete generated implementation governed by core-0.ebnf; .zli is the
canonical generated public machine contract. .zli contains:
- public values/classes, openness, generic parameters, bases, and requirements;
- public fields, initializers, messages, operator selectors, receivers, effects, domains, result/error types, and numeric-preset dependencies; and
- complete public
enumanderrorvariant schemas.
It contains no bodies, private/module-only state, base-initializer expressions,
or executable initialization. Generated declarations are ordered canonically.
Generating the same public semantic interface produces byte-identical .zli
apart from an explicitly versioned generator header. Generated .zl may change
when the authorized generator changes while remaining semantically conformant;
its generator/model/prompt identity and parent authority hash are provenance.
Manual changes to .zl or .zli are discarded by regeneration and cannot
silently become authority. A workflow that accepts an AST or source edit first
updates the corresponding .zl.md authority (and .zli.md when public intent
changes), then regenerates and rechecks both machine files.
Consumers resolve a dependency against its generated .zli and checked module,
conventionally .zlm. The checked module additionally carries typed IR needed
for public generic instantiation and hidden layout/drop facts. A stale .zli
whose interface identity does not match .zlm is rejected.
3.3 .zl.md and .zli.md authority
Every source module has exactly one module.zl.md. It is free-English authority
for implementation intent: invariants, algorithms, safety expectations,
tradeoffs, and non-public behavior. It has no required schema or controlled
vocabulary. A missing or duplicate .zl.md is a module error, not permission to
treat generated code as the authority.
module.zli.md is optional free-English authority for the observable public
contract. It is written by a person or authorized agent and is never derived
from .zl or .zli. When both English files discuss observable behavior,
.zli.md governs the public requirement and .zl.md may refine only
implementation choices.
Free English cannot bypass syntax, type, ownership, capability, or safety
checking. Because the compiler cannot decide arbitrary prose equivalence, it
hashes both files into module provenance and exposes their alignment as named
verification obligations. A verifier or reviewer may mark an obligation
Proved, RuntimeChecked, or Assumed; the selected verification policy
decides which statuses are admissible. Code generation, repair, and semantic
diff tools treat the English files as authority rather than regenerating them
from current code.
4. Visibility, names, and message lookup
Visibility is explicit:
publicis exported through the generated.zli.moduleis visible anywhere inside the declaring module and is absent from.zli.privateis visible only in the lexical declaration that owns it. A private top-level declaration is visible only to declarations in the same.zlfile; Core-0 currently has one file per module.
Core-0 uses contextual namespaces for modules, declarations/types, and local values. A value/class name denotes its nominal type in type position and its typed descriptor object in expression position; these are contextual meanings of one declaration.
The following rules are normative:
- An import, declaration, generic parameter, receiver, parameter, or local does not shadow another visible name.
- Field access is always explicit (
receiver.field) and passes visibility plus borrow checking. There is no implicit property lookup. Selfdenotes the exact enclosing value or class.base.selector(...)is legal only in an override and begins at the immediate base implementation.- A message is resolved only from its statically known receiver; there are no unqualified free messages, argument-dependent lookup, or extension search.
- A
type: Class<Self>receiver normally denotes a class descriptor. The schema-1.9 intrinsic catalog may assign it to a closed value-construction selector such asUnit.valueorBytes.empty; this is static descriptor dispatch and never allocates or creates class identity. - An ordinary selector is its name plus declared non-receiver parameter labels. An operator selector is its token plus arity. One class chain contains at most one dispatch contract for each selector.
- Replacing an inherited open selector requires
override; usingoverridewhen no open inherited selector exists is an error.
An override preserves selector, receiver ownership, domain, parameter/result
types, closed error carrier, generic arity, and requirements. It may narrow its
effect set. It is sealed unless declared open override.
Arguments are positional. Parameter labels remain part of reflected identity;
Core-0 has no second named-argument call form. Explicit generic call arguments
use receiver.selector::<T>(...).
5. Core types, presets, and expression semantics
The compiler-provided declarations include:
Never Unit Bool
Int8 Int16 Int32 Int64
UInt8 UInt16 UInt32 UInt64 Index
Float32 Float64 Byte Text Bytes
Option<T> Result<T, E> InitError<A, E>
Array<T> Slice<T> Box<T>
Allocator<A>
Shared<T> Weak<T>
Ref<T> Mut<T> Raw<T> Class<T>
Object
All other types are nominal values, classes, enums, errors, or generic substitutions. Two nominal types are equal only when their declarations and recursively their arguments are equal. Generic constructors are invariant; the explicit class projections in section 6 are the only subtype relation.
Safe references are never null. Absence is Option<T>. There is no null
literal, implicit truth conversion, structural record compatibility, numeric
widening, or user-defined conversion. Contextual operations are limited to
literal fitting, bounded receiver reborrowing, class-base projection, and
checked generic substitution. Named non-receiver borrows use the compiler-
provided Ref::<T>.borrow(place) and Mut::<T>.borrow(place) messages.
An unsuffixed integer without an expected type is Int32; an unsuffixed float
is Float64. A string literal is immutable Text; a byte-string literal is
immutable Bytes. Literal storage resides in the module constant pool and does
not allocate at runtime. Bool is the only condition type. Index is target-
sized and cannot appear in a portable public .zli signature.
Enums and errors are closed nominal variants. match is exhaustive and rejects
duplicate/unreachable arms. Matching an owned non-Copy value consumes its
selected payload. Matching a borrow observes the payload and does not create an
exclusive borrow implicitly.
A variant value is introduced only by the descriptor form
Type.Variant(arguments...). Type names the nominal enum/error declaration,
and the variant name and positional payload must match that declaration
exactly. Even a fieldless variant uses explicit parentheses, for example
Token.End(). The expression is resolved as variant construction, not as a
user-declared message send; payload expressions evaluate left to right, and the
result has the exact nominal enum/error type.
A closed enum or error is structurally Copy exactly when every payload of
every variant is Copy; fieldless carriers are therefore Copy. Option,
Result, and InitError use the same rule. A named owned non-Copy match uses
move(name), while a temporary transfers directly. A borrowed match copies a
Copy payload; its non-Copy payload bindings are arm-scoped Ref values,
never implicit Mut values.
Every closed variant has finite statically known inline layout. An inline-owned
payload cycle is rejected unless an explicit indirection such as a class
handle, Box, or Shared breaks the cycle. All layout arithmetic is checked
against portable compiler bounds.
Operands and arguments evaluate left to right. && and || short-circuit. An
assignment validates its destination, produces the new value, drops the old
initialized value, and finally stores the new value. If production does not
complete, the old place remains initialized.
5.1 Numeric semantic presets
Every compilation selects exactly one preset and records it in .zli, .zlm,
reflection, traces, generic-instance identity, and native/interpreted ABI
validation. exact is the default when no package/build selection is given.
| Preset | Integer semantics | Floating semantics |
|---|---|---|
exact |
Overflow, underflow, invalid shift, and exceptional division produce Fault |
Strict IEEE binary32/binary64; no reassociation or contraction; NaN payload is non-semantic and canonicalized when exposed as bits |
modular |
Add, subtract, multiply, and negation wrap modulo 2^N; divide-by-zero, signed minimum divided by -1, and out-of-range shifts still produce Fault |
Same as exact |
approximate |
Same safety behavior as exact |
May reassociate, contract, vectorize, select target approximations, and vary within the operation’s reflected approximation contract |
approximate never relaxes memory, bounds, ownership, type, or integer safety.
An approximate operation without a reflected error/tolerance contract is
rejected rather than given an implementation-defined result. Backend and
numeric policy are recorded in provenance.
This numeric axis is independent of verification mode
(verified/checked/trusted) and code materialization mode
(observe/adaptive/sealed). Changing one does not imply or authorize a
change to either other axis.
6. Values, classes, ownership, and inheritance
6.1 Values
A value has inline/value semantics. It may live in a register, stack slot,
containing object, explicit Box<V>, or compiler-selected unboxed
representation. Moving it may change its storage address. It has no stable
identity merely because every value participates in the object/message model.
Every value type is sealed: it cannot extend another declaration and cannot be
extended. Its messages dispatch statically and cannot be open, abstract, or
override. User values are move-only in Core-0. Compiler-known intrinsic
scalars, shared references, and the structurally eligible closed carriers
defined by C0-TYPE-007 are implicitly Copy. User duplication uses an explicit
clone message.
A value initializer is called as Type.init(...) and returns
Result<Self, E>, using E = Never when domain construction cannot fail. It
initializes every field exactly once. It does not accept an allocator unless an
ordinary field or operation explicitly needs one.
The first executable Stage0 schema-1.8 value boundary is deliberately smaller:
one sealed nongeneric value, one public immutable exact scalar field of at most
32 bits, one public init -> Result<Self, E>, whole-local moves, and implicit
drop elaborated into explicit Core IR. Its nominal value occupies one scalar runtime word but remains move-only. A source or checked artifact requiring more
fields, mutable/non-public fields, a wider or aggregate field, a class, a
borrow, custom drop, partial field movement, or defer must fail closed rather than selecting a wider ABI or backend-specific meaning. This boundary is a
staged implementation restriction and does not narrow the eventual Core-0
value model.
The next specified, non-implemented schema-1.9 selfhost byte boundary is locked
by zerglang.selfhost-intrinsics/1. It admits multiple declarations and
fields, compiler-known rich carrier and byte-container instances, all receiver
kinds, declared effects, lexical Ref/Mut/Slice<Byte> loans, complete
control/join cleanup, and recursive aggregate drop. Engines may represent a
live rich owner with a context-scoped opaque token, but that token has no
source, serialization, reflection, comparison, or semantic-identity meaning.
The byte-exact ZLM and invocation layouts live in C0-ART-003 and
BOOT-INTRINSIC-007.
6.2 Classes
A class denotes a stable heap object. The bare type T is its unique owning
handle; it is not an inline body and does not require Box<T>. Moving T moves
the handle while the object address and identity remain stable. Destroying the
last unique handle runs the concrete destructor and releases storage using the
allocation policy recorded at construction.
A class is sealed unless written open class. An open class may extend at most
one open class and otherwise derives from the intrinsic root Object.
Class initialization has one allocation/error form:
Type.init(
allocator: Ref<Allocator<A>>,
...
) -> Result<Self, InitError<A, E>>
effects { alloc, ... }
A is the allocator’s typed failure. A = Never selects a policy whose
allocation failure is a Fault. E is the initializer’s domain failure;
E = Never means there is none. InitError<A, E> preserves which phase failed.
Named factories express alternate construction only; they do not replace the
allocation-error channel.
The most-derived Type.init performs exactly one allocation for the complete
object. A base Base.init(allocator, ...) header initializes the base subobject
inside that allocation; it does not allocate a second object. Partial
initialization destroys initialized fields and releases the allocation with the
same policy.
Class messages are sealed unless marked open. An override is sealed unless
marked open override. An abstract class and every abstract message must also
be explicitly open; a concrete derived class implements all inherited
abstract messages.
6.3 Ownership forms and borrowing
| Form | Meaning |
|---|---|
V where V is a value |
Exact owned inline value |
Box<V> |
Explicit unique stable heap storage for a value |
C where C is a class |
Unique owning handle to a stable heap object |
Shared<C> |
Opt-in reference-counted shared ownership of a class object |
Weak<C> |
Non-owning reference to Shared<C> |
Ref<T> |
Shared non-escaping borrow |
Mut<T> |
Exclusive non-escaping borrow |
Raw<T> |
Pointer usable only under the unsafe effect |
Transferring a named non-Copy owner uses move(value); fresh temporaries
transfer directly. Many overlapping shared loans or one exclusive overlapping
loan are legal. Move, assignment, and destruction conflict with every live
overlapping loan. A reborrow suspends the original mutable loan through the new
loan’s last reachable use.
The checker tracks field move paths over CFG. Use requires definite initialization on every incoming edge. A partially moved parent cannot be used as a whole until restored. Drop elaboration destroys only initialized paths and introduces drop flags at joins when necessary.
Ref<T>, Mut<T>, and Slice<T> are non-escaping in Core-0. They may be
parameters and locals, but not fields, results, variants, or captured state.
Written lifetimes and stored borrows are deferred.
Class substitution preserves stable object identity:
Derived -> Base
Shared<Derived> -> Shared<Base>
Weak<Derived> -> Weak<Base>
Ref<Derived> -> Ref<Base>
Mut<Derived> -> Mut<Base>
An owning Derived -> Base projection consumes the derived owner and produces a
base-typed owner retaining the concrete descriptor, destructor, and allocator.
There is no corresponding value subtype or generic covariance.
Construction initializes the base then direct fields in declaration order.
Destruction runs the most-derived drop body, its fields in reverse declaration
order, and then each base. drop returns Unit, cannot propagate failure, move
a field, or suspend. Structured exits traverse compiler-generated cleanup
edges; a Fault uses the selected containment profile rather than ordinary
Result propagation.
7. Generics
Core-0 generic parameters range over types only. Generic bodies use structural message requirements; there is no SFINAE, specialization, implicit requirement inference, nominal conformance declaration, or overload fallback.
Requirement satisfaction performs ordinary lookup on the substituted type and
checks receiver, domain, selector (including operator token/arity), parameters,
result, closed error carrier, and effect allowance. A satisfying effect set may
be narrower. Requirements may distinguish value construction from class
allocation by requiring their exact init result shapes.
Generic arguments are inferred only from receiver and explicit input argument
types. Return-context inference is not used; unresolved arguments use
::<...>. A generic body is checked against its requirements. Each concrete use
then substitutes types, checks satisfaction, creates a stable instance identity,
and resolves required sends. Native backends may monomorphize; the interpreter
may execute a reified checked instance. Public generic IR remains in .zlm.
8. Errors, effects, and operators
Result<T, E> is the single closed recoverable-failure carrier. Its invariant
intrinsic schema is Ok(value: T) at canonical tag zero and Err(error: E) at
canonical tag one. Nominal error declarations remain the standard domain-error
form, but E may be any finite owned non-borrow payload type, including a
structured diagnostic collection. Construction writes
Result::<T, E>.Ok(value) or Result::<T, E>.Err(error) with explicit generic
arguments. A pattern writes Ok(...), Err(...), Result.Ok(...), or
Result.Err(...) and obtains T and E only from its scrutinee.
Prefix try accepts Result<T, E> only inside a message returning
Result<U, E> and evaluates its operand once. Ok produces the selected T;
Err executes pending cleanup and returns a fresh Result::<U, E>.Err(error)
with the same exact E. Error translation is explicit; try performs no clone,
conversion, implicit union, fault translation, or handler invocation.
Every message writes an effect set. The initial Core-0 names are io, alloc,
time, random, reflect, unsafe, and abort. An effect set is unordered
and duplicate-free. A caller permits every transitive callee effect. A
capability remains a typed value; declaring an effect does not create authority.
The overloadable operator selectors are the fixed tokens:
+ - * / % << >> & | ^
== != < <= > >= !
Unary - and ! have arity one; the remaining forms have arity two, with -
also available at arity two. &&, ||, and = are control/assignment syntax
and cannot be overloaded. No declaration can introduce a token or precedence.
An operator is declared through the ordinary message grammar:
public algorithm message operator +(
self: Ref<Self>,
other: Ref<Self>
) -> Self
effects {}
{
...
}
The expression left + right is the only source spelling of that selector;
there is no callable add alias. Lookup is receiver-based and checks the fixed
arity, so operator support does not create C+±style type-overload search.
9. Deferred from Core-0
The following remain part of the broader design but are not Core-0 language features:
- workflow, compute, reactive/state, and signal-directed optimization bodies;
- suspension, async tasks, concurrency, atomics, and cross-thread sharing;
- stored/returned borrows and written lifetime parameters;
- closures and compile-time declaration generation;
- nominal interfaces, mixins, multiple inheritance, and runtime downcasts;
- open-ended operator tokens, implicit conversions, and general overloading;
- runtime hot replacement and object-schema migration; and
- untyped dynamic invocation or
eval.
Later editions introduce these through explicit syntax and checked-module schema versions. A Core-0 implementation diagnoses them instead of accepting a weaker approximation.