Core-0normative target; implementation partial

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:

  1. Every executable message explicitly says algorithm. workflow, compute, state, and optimize are reserved domain keywords. The parser recognizes their declaration shape, after which the front end emits ZL-DOMAIN-0001; it never interprets one as ordered code.
  2. There are two nominal object declarations. A value is inline/value- semantic and always sealed. A class is a stable, uniquely owned heap object; its bare type is the owning handle. Shared<T> and Weak<T> are explicit, opt-in shared ownership for classes.
  3. Classes and messages are sealed by default. open explicitly permits class derivation or message override. An override is sealed again unless it also says open.
  4. A class is constructed only through Type.init(allocator, ...) -> Result<Self, InitError<A, E>>, where the initializer receives access to an Allocator<A>. A = Never selects a faulting allocation policy; E = Never means construction has no domain failure.
  5. 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.
  6. 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.
  7. Every top-level declaration and member writes exactly one visibility: public, module, or private. Every message writes its domain; Core-0 accepts only explicit algorithm.
  8. Recoverable failure has one source model: Result<T, E> and prefix try. Core-0 has neither throws syntax nor exception unwinding.
  9. Every source module has exactly one authoritative free-English .zl.md. The AI-authority workflow always generates and checks both .zl and canonical .zli; neither generated file outranks the English authority. A .zli.md is optional additional authority for the public contract.
  10. Every checked module pins one numeric semantic preset: exact, modular, or approximate. This axis is orthogonal to verified/checked/trusted verification and observe/adaptive/sealed materialization.
  11. 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 enum and error variant 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:

  • public is exported through the generated .zli.
  • module is visible anywhere inside the declaring module and is absent from .zli.
  • private is visible only in the lexical declaration that owns it. A private top-level declaration is visible only to declarations in the same .zl file; 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.
  • Self denotes 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 as Unit.value or Bytes.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; using override when 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.

Projected fromspec/editions/core-0/semantic-reference.md