Documentation/Specification

BASIL language specification

On this page

Current implemented language, verified 2026-11-13. Target: Linux x86-64. Compiler: build/selfhost/stage3; implementation: selfhost/compiler/*.bas. This document describes the maintained BASIL compiler, not the frozen Rust seed.

The evidence registry maps these sections to implementation symbols, tests and the passing full report . Its compiler and source fingerprints must match before verification succeeds. Runnable examples marked example: below are checked by tools/verify_language_docs.py --examples. See the grammar for syntax and the manual for usage.

Status and authority

  • Implemented means accepted current syntax/semantics supported by source and passing tests, within the limits here. A completed subset is not full compatibility with QuickBASIC, Python, Go, Rust or the frozen bootstrap.
  • Planned means agreed direction in requirements and the checklist ; it is not accepted syntax until implemented/tested.
  • Unresolved means a decision or contract is not established. Do not infer one from an example or historical proposal.

User decisions remain the authority for intent. Code and passing tests establish what is implemented; they do not justify weakening a safety requirement. If code, this spec or requirements disagree, record/correct the discrepancy rather than silently redefining the language. Compiler ABI/layout/runtime details belong in the contributor reference , not this source-language spec.

Source, names and modules

Source and STRING use UTF-8. Source accepts an initial UTF-8 BOM and LF/CRLF/CR endings. Identifiers use ASCII letters/underscore followed by letters/digits/underscore and are case-insensitive. Apostrophe comments run to end of line. String literals use "..."; "" inside a literal denotes a quote. Backslash is literal, not a C escape. Triple quotes immediately followed by a newline start a block literal. Raw blocks preserve content/newline bytes; contextual DEDENT/TRIM modifiers remove the common exact space/tab prefix and boundary newlines, respectively, in that order. Either modifier order is accepted; duplicates fail. An f prefix enables existing checked interpolation after layout processing. Values supplied by interpolation are never dedented/trimmed. Blocks are constant strings under the ordinary constant/default rules; interpolation retains its expression effects/restrictions. Closing-line indentation is content; six quotes represent a literal triple. See exact lexical/layout rules . General dollar-suffixed variables are unsupported; traditional string-function call aliases such as MID$ are recognized.

IMPORT uses a source-relative quoted path and optional AS alias; the default alias comes from the filename. Canonical file identity prevents duplicate cycle loading. Qualified references use alias.Name. Declarations are discovered before ordinary bodies are checked, so source import cycles/forward function references work. Imported modules contain declarations, not executable initialization. Top-level entry locals are not module globals. PUBLIC/PRIVATE applies to supported module functions/SUBs, nominal types, errors, enums and constants; unmodified declarations retain public visibility. PUBLIC mutable module variables and record-member visibility are not implemented. Private declarations cannot leak through public interfaces. See imports .

Demand-loaded standard-library functions are ambient fallbacks. A module’s own function declarations take priority before overload signature matching, including function references and SPAWN. Qualified imports are exact; a local declaration with incompatible arguments does not silently select an ambient built-in. Unused functions/tables are omitted by reachability after ordinary checking; source loading may conservatively use names before types are known.

Program entry and statements

An entry source may contain statements without Main. Alternatively use a zero-arg INTEGER FUNCTION Main or SUB Main; do not combine explicit Main and entry statements. Functions may be interleaved with entry declarations/statements. Reaching the end of the implicit entry succeeds; an explicit integer return supplies an exit status.

PRINT "Hello"

Ordinary statements are line-delimited. Single-line IF has its own simple-arm rules; colon is a slice delimiter, not a general statement separator. PRINT, INPUT/LINE INPUT, assignment, calls, structured control flow and RETURN are supported. General line labels/GOTO, classes and a promise of full QuickBASIC compatibility are outside this specification.

Types, literals and operators

Type/category Current source semantics
INTEGER Signed 64-bit, checked arithmetic
BOOLEAN TRUE/FALSE, distinct from numbers
DOUBLE IEEE-754 binary64; decimal/exponent literals and explicit conversions
BYTE / UINT32 / UINT64 Unsigned 8/32/64-bit ranges; distinct exact static types
STRING Valid immutable UTF-8 value; variables may be reassigned
TYPE / ENUM / closed choice Nominal record, integer-backed enum or declared alternatives
ARRAY / LIST / SET / DICT / OPTIONAL Typed copyable containers; LIST/OPTIONAL/DICT values also support resource ownership
CALLABLE Checked function/subroutine value with independent copyable captures; NOSUSPEND checks foreign-entry effects
POINTER / CALLBACK Explicit foreign address / typed C callback; see the unsafe boundary below
Resources / task/channel types Restricted owners/capabilities with specific lifetime rules

Decimal integer literals have INTEGER typing within the accepted signed range; (-9223372036854775807-1) expresses its minimum. &H, &B, &O denote UINT64 radix literals. Decimal points/exponents produce DOUBLE. There are no general implicit numeric conversions; explicit conversion APIs define checked/fatal behavior. Counted DOUBLE FOR headers have a documented INTEGER conversion rule.

Precedence, low to high: OR; AND; comparisons; + - | XOR; * / MOD &; unary + - NOT; primary/postfix operations. Binary operators associate left within a group. AND/OR short-circuit BOOLEAN operands. Comparisons require compatible exact types. =/== and <> support generated structural equality on copyable records and collections; resources and callables reject equality. DOUBLE equality is IEEE throughout, including nested values and Equal helpers. STRING supports concatenation with + or &, exact-byte =/<>, not ordinary relational ordering. INTEGER division truncates toward zero. Checked integer/unsigned overflow or divide failure is fatal; DOUBLE arithmetic follows its floating rules. Floating math functions also return NaN/infinity for numeric domain/overflow results without a mandatory result-validation step. Finite(value AS DOUBLE) explicitly returns the unchanged finite value or NonFiniteError {Value AS DOUBLE}. Signed zero and subnormals pass. INTEGER-result SGN(DOUBLE)/SIGN(DOUBLE), INTEGER CLAMP and invalid numeric conversions remain checked; see math contracts . & | XOR are integer bitwise operations; BOOLEAN also supports XOR. ^ is not an XOR alias. Named bit/wrapping/wide operations have their own contracts .

PRINT IsNaN(SQRT(-1.0))
PRINT IsInfinite(EXP(1000.0))
PRINT Finite(42.0)
TRY
    Finite(SQRT(-1.0))
CATCH problem AS NonFiniteError
    PRINT IsNaN(problem.Value)
END TRY

Conditions require BOOLEAN. Numbers, STRINGs, collections, records and optionals have no implicit truthiness. IF LET is a distinct checked presence operation.

IF 1 THEN PRINT "invalid"

Locals, initialization and assignment

DIM accepts an initializer with inferred or explicit type. A first bare/LET assignment also declares a local if its RHS determines a complete type. Reassignment keeps the established static type/access rules. Empty constructors and NONE need enough context; they do not invent a dynamic type.

DIM name AS T can leave a copyable local, native/user resource owner or owning list uninitialized. Reading, field access, indexing, copying, ordinary/BYREF argument passing and compound updates require proven initialization. Whole-value assignment commits only after successful RHS construction. Field-by-field writes do not initialize a record. NONE is optional absence, not an uninitialized value. Initializer-free task/channel capability locals remain rejected. See definite assignment .

IF/ELSEIF/ELSE, IF LET and SELECT merge reaching arms; returning arms do not contribute. Missing default arms preserve the incoming possibility of no assignment. Mandatory DO execution and proven loop exits can establish initialization; zero-iteration paths remain conservative. Transfers in selectors affect every branch. TRY handlers conservatively start from TRY-entry initialization, rather than assuming protected assignments completed. More precise loop/failure-edge inference is future work. These conservative rejections must not be mistaken for runtime default values.

Ordinary assignment evaluates/owns its RHS, then destination indices left to right. Nested fields/list/dictionary/array destinations are supported; a slice must be last. Compound += -= *= /= MOD= &= |= XOR= evaluates the destination once, reads its old value, evaluates the RHS and commits only on success. Type/overflow rules match the corresponding operator. STRING has += and &=. Missing dictionary keys fail before a compound RHS runs; ordinary dictionary assignment inserts/replaces.

A compound RHS cannot take explicit BYREF access to the destination root. After a path selects dynamic element storage, later index/bound expressions cannot take that root BYREF either. Initial index evaluation occurs before acquiring element storage. Existing borrow/iteration exclusions still apply. These conservative rules prevent stale destinations; arbitrary effectful reborrowing is not promised.

TYPE Person
    Name AS STRING
    Age AS INTEGER
END TYPE
a=Person("Fred",56)
b=Person("Fred",56)
PRINT a=b
PRINT a==b
PRINT Compare(NaN(),NaN())=CompareResult.Unordered
TRY
    members=ToSet([NaN()])
CATCH err AS InvalidKeyError
    PRINT err.Code
END TRY
TYPE Item
    Name AS STRING
END TYPE
DIM original AS Item
original = Item { Name="Fred" }
copy = original
copy.Name = "Ada"
PRINT original.Name
PRINT copy.Name

The following read is rejected at compile time:

DIM text AS STRING
PRINT text

Failed reassignment preserves the old complete value; failed initial assignment leaves the variable uninitialized. Side effects of RHS/index functions are not rolled back. Lexical block declarations are unavailable outside their blocks; loops construct/clean block locals on each iteration.

Coordinate helpers FloorDiv/EuclideanMod/FloorToInteger provide checked floor and nonnegative-remainder semantics without changing ordinary / or MOD. Numeric Dot, Norm, Normalized, Distance, CosineSimilarity, VectorAdd/Subtract/Multiply/Divide and Scaled accept matching numeric lists/rank-one arrays. They borrow inputs and produce independent lists where applicable. Integer operations report typed NumericError before a fatal arithmetic trap; shape/zero-norm errors are distinct. Floating reductions use their documented IEEE/rounding policy and integral fractional results use explicit DOUBLE conversion. Exact contracts .

Functions, calls and generics

FUNCTION declares one success result. SUB declares none. Both may declare FAILS, call suspending ordinary functions and use RETURN ERROR. SUB permits bare RETURN or fallthrough; FUNCTION must return a matching success value on every reachable successful exit, or fail/nonreturn. No successful multiple returns are implemented in the maintained compiler. Expression-bodied FUNCTION uses = expression. FAILS LIKE composes a known operation signature or CALLABLE parameter error set; signature references execute no application code. Dependent contract grammar and limits .

STRING and aggregate equality operands preserve their evaluated values if a later operand reassigns or transfers the source. The compiler retains a snapshot where a read-only proof is unavailable; failure/cancellation clean that temporary.

Default parameters borrow read-only access to owned data without payload copies; scalars are passed as values. BYREF at declaration and call grants exclusive writable access. Scalar/STRING bindings, nested record fields, list/array elements and existing dictionary values are supported. Whole aggregate parameter rebinding remains restricted. Indexed BYREF protects its entire owner after retaining an interior address, through later arguments and the call; even disjoint indexed arguments from the same owner are conservatively rejected. Indices/keys evaluate once, missing dictionary keys fail without insertion, and mutations before a helper failure persist. Temporary roots, string characters, slices, set members and readonly bindings cannot supply mutable locations. Exact rules . Borrow lifetime includes suspension. Conflicting aliases must not mutate, relocate or destroy borrowed storage. No reference escapes into storage/tasks. BYVAL captures an independent writable value at argument evaluation (see below). A provisional TAKE formal consumes an exact resource owner: pass a fresh result or explicitly TAKE a writable local. The callee may mutate, move or reinitialize its owned parameter; cleanup covers every exit. Defaults, ownership-only overloads, owned base slicing and foreign ownership interfaces are rejected. Default/BYREF borrowing rules remain distinct.

An explicit AS BORROW T FROM parameter result is a read-only view tied to an ordinary/BYREF parameter. FROM first OR second permits either of two distinct borrowed origins; every possible owner remains protected until consumption ends. Only checked projections from the declared origins may return; locals, temporaries, consuming origins, scalar results and borrowed task results are rejected. The caller supplies a stable owner and excludes invalidation through consumption/suspension. Copyable assignment copies the view; resource assignment cannot copy it. CALLABLE spells origins as 1-based parameter positions (FROM 1 OR 2). Declarations currently accept at most two origins; nested views may compose more actual roots. See checked borrowed results .

Required parameters precede defaults. Defaults admit checked scalar constants, records/choices/errors, nested lists, empty collections, SOME/NONE and scalar static tables. Arbitrary calls and mutable values are not constant defaults. Only omitted defaults construct values, in parameter order, with independent owned storage when needed. Ordinary and BYVAL parameters support defaults; BYREF/TAKE defaults reject. Explicit arguments execute once in written order, then defaults/declaration-order marshalling apply. Positional arguments precede named Name=value arguments. Parenthesize an argument comparison that would look like a named argument. Missing/duplicate/unknown names and ambiguous overloads fail. Concrete overloads distinguish count/static parameter types, not return type alone. When error-family overloads both accept a readonly carrier, the strictly narrower compatible parameter set wins. Equivalent or incomparable signatures remain ambiguous; selection uses the static error view, not runtime descendant dispatch.

Explicit DIM may shadow an ordinary/BYVAL parameter. Its initializer sees the original parameter; subsequent code sees the new local, which may have a different type. BYREF/TAKE shadowing and duplicate locals in one scope reject. Old owners and borrow provenance remain intact. optional ?? fallback lazily unwraps one optional layer; presence includes zero/false/empty values. It associates right, below OR. Borrowed copyable payloads remain independent when ownership is needed; owned optional results or explicit TAKE can donate resource payloads. A checked readonly local may borrow a stable parameter and private fallback instead of copying. Detailed defaults, shadowing and cleanup rules .

Generic declarations use [T,...]. Exact argument bindings are cached per concrete specialization. One built-in ownership constraint on functions or generic records, WHERE T IS COPYABLE or WHERE T IS RESOURCE, can select generic overloads; general user capabilities remain future work. Recursion can reuse existing concrete types or descend into their component types; new expanding wrapper chains are rejected. Complete calls may infer missing result types from AS, a typed destination, return declaration, field assignment or known parameter. Parenthesized complete calls also receive context. Result constraints must agree with argument constraints; AS is not an unchecked cast. Hints do not impose an enclosing comparison’s Boolean type on an integer operand. Explicit bracketed function-call type arguments are not implemented. Ordinary/opaque generic records use TYPE Box[T] and concrete types Box[STRING]; named/positional construction uses Box[STRING] {...} or Box[STRING](https://github.com/geekhideout/basic/blob/main/docs/...). Functions may use Box[T] in checked inference. Type arguments are invariant, nominal and retained even when absent from fields. Generic record extension, generic errors/closed choices and generic cleanup hooks remain unsupported. Among applicable generic overloads, a parameter pattern such as Box[T] is more specific than unconstrained T; equal/incomparable patterns remain ambiguous. Implementation and boundaries . A bound generic T may construct a record as T { Field=value }; the instantiated body must pass ordinary field/type/privacy checks. See result-context boundaries .

DIM names AS LIST OF STRING
names = Empty()
other = (Empty()) AS LIST OF INTEGER
PRINT LEN(names)
PRINT LEN(other)
FUNCTION Empty[T]() AS LIST OF T
    RETURN []
END FUNCTION

Noncapturing local FUNCTION/SUB declarations require declaration before use and lexical scope; generic-parent specialization/captures remain restricted. CALLABLE signatures may contain enclosing generic type parameters, including in nested callable/collection types; they must resolve before invocation. Earlier argument types provide context for overloaded function references. Signatures support default read-only/BYREF, independent BYVAL and concrete resource-consuming TAKE parameters, positional required arguments and optional declared errors. Error contracts may widen for read-only assignment; BYREF requires exact signatures. Owned callable values retain independent cleanup and cannot be compared/hashed. Anonymous expressions use (parameters) => expression; blocks use FUNCTION(parameters) [AS type] [FAILS ...] through END FUNCTION, or SUB through END SUB. Parameter types are explicit or come from a concrete expected CALLABLE. Without a result/error annotation, the body infers consistent successful results and concrete escaping errors. Captures are independent readonly snapshots of copyable free values. Resources and scoped capabilities cannot be captured implicitly. Capture copies/drop are deterministic; there is no mutable shared capture storage, GC or reference counting. Lambdas can specialize inside generic parents. Postfix fields/list elements/returned callables use the same checked invocation/cleanup path. Anonymous foreign/NOSUSPEND adaptation and resource/borrowed captures remain unsupported. Contracts . Generic TAKE callable parameter types must already be known resources; unresolved T is conservatively rejected. Checked FAILS LIKE dependencies retain callback sets through generic calls and contained values. Readonly containers may admit compatible narrower sets; writable containers/BYREF and higher-order callable parameter signatures remain invariant. No arbitrary body-based error inference is introduced.

Records, choices, enums and optionals

Canonical OPERATOR Equal/Compare/Hash declarations belong to the exact nominal record/choice/error/enum’s defining module. Parameters borrow read-only; results are BOOLEAN, CompareResult and INTEGER/UINT64 respectively. No resource equality/hash/order operators, generics, defaults or escaping errors. Custom Equal disables generated Hash/Compare; Compare can derive equality, with a separately supplied compatible hash. Signatures, transitive effects and package identity are checked; mathematical laws remain the author’s responsibility. User code cannot obtain a hook merely by defining an ordinary matching function. Custom ordered relations are false on Unordered; custom default sorting rejects Unordered with a type-dependent error contract. NaN key checks inspect structure independently of custom equality. See canonical operators, effects, cleanup and worked example .

OPERATOR Display(value AS NominalType) AS STRING supplies checked text output for PRINT, interpolation, standard Display conversion and logging. It borrows the exact value, including resources, and returns an independent owned STRING. Its defining-module, required readonly signature and transitive purity rules match canonical operators; it does not grant equality, ordering or copying. STRING modifiers apply after rendering; numeric modifiers reject. Filtered severity calls do not invoke the hook. Cancellation uses ordinary checked BASIL call cleanup rather than equality/hash transaction deferral. There is no implicit field dump or general STRING conversion. Display rules and ABI .

Ordinary TYPE fields have nominal static types. Named construction evaluates explicit fields in written order, then omitted field defaults in declaration order; unknown/missing-without-default/duplicate fields fail. Defaults are compile-time scalar/string/enum expressions, NONE or matching empty list/set/dictionary constructors; every construction owns independent collection defaults. Nonempty aggregate defaults and effectful factories are not implemented. Forward layouts are resolved before field types. Direct containment cycles fail; indirection through supported containers/optionals permits finite recursive values. Same-type copyable records support structural = / == / <>; ordered relational operators on whole records require a custom Compare capability. TypeName(values...) constructs fields in declaration order, including trailing field defaults, after applicable factory overloads and independent base-copy conversion. Evaluated owned fields are retained until full construction commits; failure cleans partial captures. Named arguments remain function-call syntax, not positional-constructor field syntax. Comparison and constructor rules . Resource fields/reserved cleanup hooks and owning lists follow user-resource rules . Local/anonymous TYPEs remain unsupported.

Ordinary records support TYPE Customer EXTENDS Person, Audit. Storage is flat: complete base layouts in written order, followed by local fields. Inherit field defaults and field SERIALIZE/JSON/FORMAT metadata, with complete construction and one cleanup per field. TYPE-header converters/options are not inherited. Reject field hiding/collisions, repeated ancestors (including empty diamonds), cycles, and non-record participants. Forward bases are supported.

Ordinary/callable base parameters borrow the actual derived owner without copying. BYREF exclusively borrows that owner through suspension and preserves derived fields; whole-base replacement remains rejected. BaseType(value) explicitly creates an independent base value, after ordinary applicable factory overloads. No implicit owned slicing, downcast, runtime identity or container covariance is introduced. Overloads retain lexical/concrete precedence, then a uniquely nearer candidate: no argument farther and at least one nearer; ties/incomparable matches fail. Generic bodies are instantiated only after viability and base ranking. Generated serialization dispatch stays exact to preserve all concrete fields. SPAWN snapshots complete actual values and forwards base views of those snapshots; cleanup/failures/cancellation retain their actual ownership. Details .

A closed choice uses CASE declarations inside TYPE and constructs exactly one alternative. Payload projection returns OPTIONAL OF payload; payload-free projection returns BOOLEAN. Alternative fields cannot be assigned directly. Copy/cleanup follows the active payload. SELECT TYPE matches alternatives exhaustively with case-local independent payload bindings; matching rules . Recursive paths through list/dictionary containers can be represented; direct recursive choice payload layouts remain rejected. Do not infer arbitrary reference graphs.

TYPE Node
    CASE Empty
    CASE Text AS STRING
    CASE Children AS LIST OF Node
    CASE Members AS DICT OF STRING TO Node
END TYPE
DIM fields AS DICT OF STRING TO Node = DICT()
fields["name"] = Node { Text="hello" }
root = Node { Children=[Node { Members=fields }] }
IF LET children=root.Children THEN
    IF LET members=children[0].Members THEN
        IF LET text=members["name"].Text THEN
            PRINT text
        END IF
    END IF
END IF

ENUM is a distinct INTEGER-backed nominal scalar. Members require qualification; default values start at 0 and increment, with explicit checked constants allowed. Duplicate names/values, empty enums and overflow fail. Same-type equality/assignment is supported; EnumValue extracts INTEGER and EnumType(integer) checks declared membership through NumericError. Flags/IOTA/aliases and implicit numeric conversions are not supplied.

Constants and immutable tables

CONST declares checked compile-time scalar, STRING, enum or flat scalar-list values, locally or at module scope, with ordinary module visibility. Scalar constant expressions may reference other constants; dependency cycles fail. CONST Name AS LIST OF T = [value, ...] embeds an immutable table; AS may be omitted for a nonempty homogeneous list. Line breaks and a trailing comma inside the brackets are accepted. T is INTEGER, BOOLEAN, DOUBLE, BYTE, UINT32, UINT64 or one enum type. An unsigned annotation accepts in-range nonnegative INTEGER constant elements; otherwise kinds must match exactly. Empty lists need an annotation. Owned/nested table elements, resource handles, arbitrary runtime calls remain unsupported. Aggregate function defaults are supported under the checked literal/constant rules above.

A constant table has the ordinary LIST OF T read interface: checked indexing (including negative indices), slices, iteration, LEN and read-only parameters. The binding and storage cannot be mutated, passed BYREF or transferred with TAKE. Assigning/returning it as an owned list, BYVAL and task snapshots preserve independent-value semantics through ordinary copying. Slices produce independent values. No mutable alias, reference counting or implicit ownership transfer is introduced. Read-only access alone requires no runtime table construction. See implementation and limits .

BYVAL parameters own independent writable values, captured in source argument order. Copyable borrowed inputs are copied; fresh/explicitly moved owners are adopted. A resource cannot be implicitly copied. Ordinary parameters retain read-only borrowing, and BYREF retains exclusive access. Explicit TAKE also permits whole initialized writable copyable locals; it invalidates the source. See value parameter semantics .

OPTIONAL OF T contains NONE or SOME(T). Copyable IF LET payload bindings are scoped independent values: read-only when inspecting a borrowed stable optional, and writable when consuming a fresh optional result or TAKE optional. A resource optional is noncopyable: SOME adopts a fresh/explicitly moved owner, and IF LET consumes a fresh result or TAKE namedOptional, producing a writable owning binding. IF LET on a named resource optional, including stable nested fields/indices, instead loans a read-only payload for the THEN arm, excluding mutation/transfer of the root until that arm ends. The loan cannot escape or become an owner. Zero, FALSE and empty containers are present values; nested optionals distinguish SOME(NONE).

DIM value AS OPTIONAL OF STRING = NONE
IF LET text=value THEN
    PRINT text
ELSE
    PRINT "absent"
END IF
value=SOME("present")
IF LET text=value THEN
    PRINT text
END IF

Embedded values

[PUBLIC | PRIVATE] EMBED Name FROM "path" AS Type [USING Decoder] declares a readonly module value built from UTF-8 input. Paths resolve from the declaring source and stay inside its project/source tree. Raw AS STRING preserves the input bytes and supports ordinary constant contexts. USING invokes an ordinary checked visible function with STRING content, trailing parameter defaults and the AS result context, including inferred generics. Json selects DecodeJson when not shadowed by a visible function. Top-level application code/Main do not run.

Readonly calls borrow static data; owned use produces independent values. Mutation, BYREF and TAKE of the declaration are rejected. Scalars/enums, STRING, copyable records, lists/sets/dictionaries/arrays, optionals and choices are supported recursively. Resources, pointers, callables, tasks/channels and errors as successful data are rejected. Decode results retain every physical field; serialization exclusions/converters do not run again. Typed USING results are readonly runtime values, not general constant expressions.

Checked decoder/callback/cleanup closure excludes I/O, time/randomness, tasks, unsafe code and unverified native behavior. Prebuilt decoders require versioned build-effects metadata. Ordered dependencies, bounded isolated execution, concrete failure diagnostics/cleanup, packet validation, cache invalidation and source-free .blib values are implemented. Every declaration/input is validated; unused data and decoder code are omitted from executables. YAML/TOML/XML parsers remain separate work. Exact path, capability, size and cache rules , compiler/package contracts .

External text encodings

STRING stays UTF-8. Built-in EncodeText/DecodeText convert explicitly to/from LIST OF BYTE. All 14 TextEncoding profiles are implemented, including separately named GB18030_2000 and GB18030_2022; the latter preserves reciprocal scalar mappings. Error/replacement and BOM policies are explicit, strict by default. Streaming TextEncoder/TextDecoder are copyable opaque independent values; BYREF chunks exclusively borrow them. Recoverable chunk failures preserve state and return no partial result. Diagnostics/counters use byte offsets, not scalar indices. The per-chunk output limit defaults to 64 MiB; allocation failure remains fatal. Normal cleanup and scheduling apply. There is no implicit detection, locale or file/socket conversion. See functions, profiles and limits .

Literal/CONST profile arguments permit bounded dispatch elimination ; this affects emitted code/data, never the checked API, failure contracts or mapping semantics. Dynamic selectors retain all applicable profiles.

Images and text rendering

Native Canvas APIs are available without imports or external image/font libraries. Canvas owns initialized row-major straight RGBA8 pixels. Its opaque, noncopyable owner uses normal readonly/BYREF/TAKE exclusion, failure/cancellation cleanup and source-free library contracts. CloneCanvas and CanvasBytes produce independent values. Coordinates start at the top left; strict pixel access is checked, drawing clips, and returned transformations preserve the source on failure.

Implemented families include raster/path drawing, compositing, affine transforms, resampling, filters/analysis, PNG/BMP/JPEG/GIF/APNG/WebP profiles, animation and static TrueType/CFF1 fonts with reusable independent text layouts. Format and font profiles have explicit limits; AVIF/TIFF, lossy WebP writing, variable/color fonts, hint execution and color management are unsupported. Public grapheme/bidi analysis uses Unicode17; canonical normalization is internal to shaping. CanvasError is checked; file errors retain IoError. Allocation failure stays fatal. Literal/default format forwarding can prune writers; dynamic formats retain their applicable codecs. See API/ownership , formats and text profiles .

Strings, collections and arrays

LEN(STRING) counts Unicode scalar values; ByteCount counts UTF-8 bytes. STRING brackets/LEFT/RIGHT/MID use zero-based scalar positions, not grapheme clusters. STRINGs support negative scalar indexing and Python-style stepped slices, not in-place byte/element mutation. Invalid STRING indexing is a checked IndexError; invalid slice validation uses checked SliceError. Public GraphemeBoundaries and bidi analysis are available through the native text toolkit; standalone general normalization and locale tailoring remain planned. SPLIT’s omitted/empty separator splits Unicode scalars; empty text produces an empty list. See string APIs .

LIST/SET/DICT values are homogeneous static types over supported copyable values, including records, arrays, nested collections, choices and optionals. Owning resource lists/optionals/dictionaries/arrays follow the restrictions below. Assignment/copy/returned owned values remain independent. Capacity is growable and spare storage is not exposed. FOR EACH borrows its source and blocks conflicting mutation for the whole iteration. Bindings are scoped and read-only. Element references are not general escaping source values.

LIST brackets support negative indices; direct LIST bounds failures remain fatal. Stepped slice selection/replacement uses Python bounds rules and independent replacement snapshots. Checked editing APIs have their own IndexError contracts. DICT brackets require exact key/value types; missing reads/compound updates fail with MissingKey, while ordinary writes insert/replace. Find distinguishes absence from a present NONE; Get’s fallback is evaluated eagerly and does not insert. Resource dictionary values are supported with explicit owned insertion, replacement, Remove/Extract, read-only iteration and automatic cleanup. Keys remain copyable. Get returns a checked read-only view for resource values; SetDefault inserts an owned fallback and returns a view. Find returns a checked optional view: consume it read-only, usually through IF LET; it cannot become an independent owner. A missing key differs from a stored NONE. Bracket reads borrow resource values; writes adopt fresh/explicitly transferred owners and preserve RHS-before-key ordering. Nested writes retain checked exclusivity. Fallback Get borrows from the dictionary or a named resource fallback, protecting both possible origins until consumption finishes. See resource containers .

Values(BYREF table) compacts physical tombstones under exclusive access and returns a checked read-only list view in insertion order. It neither removes nor copies resources; mutation/transfer is excluded while consumed, including during iteration/suspension. Ordinary Values(table) keeps independent copyable snapshot semantics. Copyable view assignment also copies; resource view ownership escapes are rejected. No resource cleanup hook runs merely to compact live entries.

SetDefault inserts only if absent. Sets/dictionaries preserve insertion order for iteration/snapshots. Compare/Equal/Hash are structural library operations; collection equality is IEEE recursively. NaN members/keys raise InvalidKeyError before lookup/mutation, including composite keys. NaNs remain valid list/dictionary values. Infinity is valid; signed zeros are equal and hash identically. Statically NaN-free keys retain infallible validation. Exact value/key contracts . Compare returns the four-case CompareResult enum; default sorting separately places NaNs last. Enum comparators may raise UnorderedComparisonError if they report Unordered, leaving the input unchanged. INTEGER comparator compatibility remains.

ExtractAt(BYREF list,index) returns/removes an owner without copying shifted values; RemoveAt destroys the removed owner and retains its INTEGER0 return. Both validate nonnegative zero-based indices with IndexError before mutation, exclude live root loans, preserve order and support resource elements without equality.

Insert and Reverse can move resource list elements without copying them. Sort/Reverse mutate LIST via BYREF; Sorted/Reversed return independent values. STRING uses the value-returning forms. Stable comparators are supported and must satisfy their checked infallible signature. No implicit comparator captures exist.

The compiler-shipped generic library also provides Deque, Queue, BoundedDeque, PriorityQueue, SortedSet, SortedDictionary, Counter, MultiMap, BitSet and Cache. Factories use checked result context; fields are opaque, copies/snapshots are independent when copyable, and payload insertion uses BYVAL with explicit TAKE for named resources. Read-only resource access loans its owner; pop/extraction/ eviction returns ownership. Cache LRU/TTL policies retain checked monotonic time, entry/weight bounds and deterministic cleanup. Exact APIs . No resource keys, implicit synchronization or background loaders are added.

Any/All/CountWhere accept infallible read-only predicates for lists, sets and dictionaries; dictionary predicates receive key and value. Any/All short-circuit and return FALSE/TRUE respectively on empty inputs. CountWhere visits every element. Filter returns independent matching copyable values in source order; list IndexWhere/LastIndexWhere search forward/backward and return -1 if absent. FindWhere returns an independent optional first match. Resource lists/dictionary values support read-only searches/counts, but Filter/FindWhere cannot copy resources. Callbacks may suspend; source borrows and partial-output cleanup remain enforced through cancellation. Captures/error-polymorphic callbacks remain work.

ARRAY OF T is dense and owned; copyability follows T. DIM bounds are inclusive and may be multidimensional/nonzero-based; omitted lower bounds are zero. ARRAY(length) remains a zero-based length constructor. Rank is a property of the value; indexing checks coordinate count and bounds. LBOUND/UBOUND dimensions are one-based; LEN is total elements. REDIM PRESERVE retains the intersection of coordinate spaces and constructs replacement storage before commit. Invalid bounds/arithmetic are fatal. ToArray takes a BYVAL list: copyable inputs snapshot independently, resource owners require a fresh list or explicit TAKE. Resource element reads borrow; PRESERVE moves retained owners exactly once, and new cells get independent valid defaults, honoring declared record-field defaults. Resource fill expressions are rejected. Nondefaultable File elements need ToArray of initialized values; OPTIONAL File permits NONE defaults/resizing. Scope-bound capability elements remain rejected. See array contracts , including borrow and cleanup rules.

text="é🌍"
PRINT LEN(text)
PRINT ByteCount(text)
PRINT text[-1]
PRINT JOIN(SPLIT(text), "|")
PRINT Reversed(text)

Buffers, builders and byte streams

ByteBuffer/StringBuilder are opaque copyable accumulators over packed BYTE storage. Append/Extend mutate through BYREF; Clear retains capacity. ToBytes/ ToString return independent snapshots. TakeBytes/TakeString explicitly extract storage and empty the accumulator. StringBuilder accepts valid STRING/scalars; arbitrary bytes require checked decoding. Length counts bytes for ByteBuffer and Unicode scalars for StringBuilder; byte capacity never grants initialized access. Bytes returns a checked borrowed BYTE-list view tied to its owner; mutation, relocation, transfer and cleanup remain excluded through consumption/suspension.

ByteReader owns independent bytes unless explicitly moved into its BYVAL constructor. Reads/Seek/Skip validate ranges before changing cursor or output. ByteWriter reuses ByteBuffer storage, with explicit ByteOrder, checked narrow integer writes and exact binary64 bit transport. Both are ordinary independent values. Public Append/Clear/ByteCount/ReadByte/WriteByte use ordinary overload resolution; legacy list/hash/inherited-stream forms retain their behavior. Compiler-only __-prefixed storage primitives are unavailable to user source.

BufferedReader[T]/BufferedWriter[T] own File or TCPConnection endpoints; named resources require TAKE, and constructors clean consumed arguments on failure. They retain the same transfer/borrow/collection/.blib cleanup guarantees as other resource records. Reads can return short data/EOF; ReadLine distinguishes an empty line from EOF and validates UTF-8. Stream failures can follow consumed input. Write arguments are queued whole before fallible output. Flush commits short-write progress before scheduling/cancellation; after failure, retry Flush without resubmitting the argument. Automatic destruction discards pending output and closes the endpoint; explicit Flush/Finish observes output failures. Finish consumes the writer and returns its endpoint on success, cleaning it on failure.

Stdin/Stdout/Stderr are fallible File factories (IoError) that duplicate the currently inherited standard stream. Each result is a distinct noncopyable owner; explicit Close or scope/failure/cancellation cleanup closes only its duplicate, leaving the original stream open. They obey ordinary transfer/borrow rules, including buffered and task arguments. Duplicates share external stream position and status flags; they do not provide independent input or implicit synchronization. File writes retain the synchronous platform boundary. Full APIs and limits: buffer library , storage/ABI contracts .

Structured control flow

IF/ELSEIF/ELSE and single-line IF require BOOLEAN. WHILE/WEND and DO/LOOP support the documented top/bottom WHILE/UNTIL forms. INTEGER/DOUBLE FOR uses inclusive TO, STEP default 1 and start/end/step snapshots evaluated before counter assignment. A new counter is visible after NEXT; existing writable counters may be reused. Invalid zero/stalled steps and arithmetic overflow follow the checked/fatal policy. Unsigned counters and combined NEXT are unsupported. EXIT FOR/WHILE/DO exits the nearest matching loop with cleanup; EXIT SUB is a bare successful subroutine exit. CONTINUE and EXIT FUNCTION are not implemented.

Value SELECT CASE evaluates a selector once and runs the first matching arm, without fallthrough. Cases support matching alternatives, inclusive ranges and appropriate CASE IS comparisons. CASE ELSE is optional and last. Enum exhaustiveness is not inferred for definite-return checking. Scheduler SELECT is a separate form.

FOR i=0 TO 2
    SELECT CASE i
    CASE 0
        PRINT "zero"
    CASE ELSE
        PRINT i
    END SELECT
NEXT

Errors, cancellation and ownership

ERROR declarations are typed records with ordinary copyable fields and the same constant field defaults as ordinary records (including independent empty collections). Missing required fields remain errors; invalid supplied values never use a default. ERROR mapping/FORMAT clauses remain unsupported. Resource fields and direct containment cycles are rejected. Built-in IoError/NumericError/StringError have an INTEGER Code field. Error values own independent storage; payload representations belong to the internal ABI. Creating an error is an ordinary value operation. RETURN ERROR selects the failure channel; it never also exposes success. FAILS lists every error type escaping an ordinary function. Every fallible call is checked automatically, even if its successful result is discarded. Legacy inline TRY is accepted on fallible calls; it is not required and cannot mark an infallible expression. Block TRY/CATCH catches by error type, nearest first. A catch cannot re-enter itself or a sibling; forwarding targets an outer handler or the function’s contract.

Every error belongs to the implicit Error family; ERROR Child EXTENDS Parent adds one declared parent. An explicitly named parent must be an accessible error family. Inherited fields precede child fields; inherited defaults apply normally. Cycles, duplicate fields and catches hidden by an earlier ancestor catch are rejected. A parent/root catch matches descendants and retains the actual concrete error, including child fields and cleanup, through independent copies and RETURN ERROR forwarding. Only fields visible on the binding’s static type can be accessed; Error has no mandatory fields. Ordinary TYPE extension remains static. The compiler keeps FAILS LIKE formal symbolic through contained callbacks and compiled interfaces; inferred storage, mutation, AS LIKE formal forwarding and AS Shape LIKE ... owned result projections obey dependent contracts . Codec copies/factories preserve latent admitted formatter errors; their compiled transport uses canonical type keys and concrete error carriers. Wider annotations explicitly widen the contract; private rule mutation cannot erase a possible failure. Broad catches/root contracts warn with declared sets and filtered origins. Structural equality/hash/order on an open error view remains a checked unsupported operation while capability checking is completed; compare visible fields explicitly or define a checked canonical operator on the named user error. Such operators use the binding’s static family; they do not inherit dynamically or slice the concrete carrier. Carrier contract .

The implicit entry/explicit Main has a last-resort handler: cancel/join children, clean owned locals/temporaries, report the root call site/type/payload where feasible and exit 1. Explicit Main must still declare escaping errors in FAILS; the wrapper handles those declared failures. It is not a full throw-site stack trace. Unhandled ordinary function errors omitted from FAILS remain compile errors.

ERROR Missing
    Code AS INTEGER
END ERROR
TRY
    PRINT Load()
CATCH err AS Missing
    PRINT err.Code
END TRY
FUNCTION Load() AS INTEGER FAILS Missing
    RETURN ERROR Missing { Code=42 }
END FUNCTION

Copyable values have independent ownership; default input borrows do not imply copies. Copies can be elided only without changing observable value behavior. No last-use resource moves, BASIL value GC, reference counting or hidden shared mutation are introduced. Foreign components may have their own confined internals; BASIL regex uses ordinary owned BASIL fields and operation-local scratch.

Normal completion, recoverable failure and cancellation clean initialized owners and partial RHS temporaries exactly once. Structured child cleanup precedes storage it could still use. User records can define a reserved __Cleanup hook; hooks run only on complete owners, before their fields, with cancellation deferred. Library opaque resources retain RESOURCE TYPE ... CLEANUP adapter integration. Close is not automatically discovered as cleanup. Early Close leaves a valid closed state; checked finishing/commit remains explicit. Built-in resource cleanup may suspend while cancellation is deferred. Uninitialized/borrowed/transferred-away storage is not an owning value to destroy. Return-copy elimination cannot invalidate a local still needed by a failure handler.

Resource copies and undeclared escaping errors are rejected:

file=OpenFile("unused.txt", "w")
copy=file

A reserved hook also makes an ordinary record a resource. Moving a whole local is explicit; the destination becomes its sole owner:

TYPE Owner
    Name AS STRING
END TYPE
SUB __Cleanup(BYREF value AS Owner)
    PRINT "release ";value.Name
END SUB
value=Owner {Name="first"}
items=[TAKE value]
value=Owner {Name="second"}
removed=Pop(BYREF items)
PRINT removed.Name
FUNCTION Bad() AS INTEGER
    RETURN VAL("not a number")
END FUNCTION
PRINT Bad()

Cancellation is separate from typed errors and bypasses CATCH. Fatal OOM, direct LIST/ARRAY bounds, checked arithmetic and fatal runtime failures do not promise normal application cleanup. Resource records/lists and whole-local TAKE are implemented, including owning parameters, resource optionals and task results. SWAP can exchange complete writable bindings, nested record fields and list/array/ existing dictionary cells, including optional resource detachment with NONE. Swap(BYREF left,BYREF right) is the equivalent statement form. Selectors run once, left to right; commit invokes no copies, cleanup or scheduling points. Checked SwapAt validates two nonnegative list indices and fails with IndexError before mutation; direct brackets retain existing bounds/negative-index behavior. Safety and exact bounds rules . TAKE target REPLACING expression transfers the old local/immediate-field value and commits a complete replacement first; failure before commit leaves the target unchanged. Copyable replacements are independent; resources require fresh/explicitly transferred owners. The root is excluded from mutation/transfer during RHS evaluation, including suspension. Later argument failure does not undo an already committed exchange. Exact limits . Explicit TAKE also supports static record-field paths in owned writable locals. The selected complete value transfers once; its source field becomes unavailable. Disjoint fields stay usable; whole/ancestor reads, borrows and transfers require restoration. Complete field assignment restores that subtree. Default/BYREF parameters, indexed storage and records with containing own/inherited cleanup hooks cannot acquire holes. Hook-protected records require complete replacement. Branch joins retain holes on every reaching path; loops require restoration before backedges. Catch paths conservatively mark transferred roots unavailable. Field contracts and cleanup preserve independent copies, loan exclusion and exactly-once cleanup without per-record runtime masks. Resource keys/sets remain unsupported. Owned error records follow the ordinary partial- construction/cleanup rules; a caught error binding is readonly and owning.

Tasks, channels and SELECT

SPAWN returns a typed noncopyable owner handle for a function/SUB with checked positional/named arguments and ordinary constant defaults. Generic entries infer bindings from supplied arguments; missing bindings are rejected. Copyable arguments are independent snapshots; sender/receiver capabilities follow checked scope rules. Supported named whole resource owners transfer only with explicit TAKE; fresh owned constructor/function results can be adopted directly. A transferred source becomes unavailable and later argument failure cleans transferred arguments. Supplied expressions/snapshots execute in written order; omitted defaults follow. Declaration-order marshalling does not change ownership or cleanup kinds. Positional arguments precede named ones; missing/duplicate/unknown names and ambiguous entries fail. Resource defaults do not imply TAKE. General captures and borrowed/BYREF task arguments remain unsupported.

Join waits through cleanup. SUB success is TRUE and cancellation FALSE; function success is SOME(result) and cancellation NONE. Copyable results are independently copied on repeated Join. A resource result transfers once; another successful extraction raises TaskResultTaken. A successful absent resource optional returns SOME(NONE). Declared errors propagate through checked calls on repeated Join. Cancel requests cancellation without joining or erasing a prior failure. CASE COMPLETED observes readiness, not success; timeout victory cannot discard a child’s error. Scope fallback cancels/joins/releases owned handle tasks and observes outcomes; current FAILS analysis is conservative even after explicit observation.

Statement SPAWN supports the checked argument/TAKE machinery for infallible entries and automatic reaping; its compatibility collective controls do not turn managed handles into freely copyable values. WaitTasks/CancelTasks act on the current invocation’s compatibility scope. Prefer explicit handles for retained results/errors.

task=SPAWN Work("finished")
IF Join(task) THEN PRINT "joined"
SUB Work(message AS STRING)
    PRINT message
END SUB

Individual cancellation still requires joining:

task=SPAWN Work("owned argument")
Cancel(task)
IF NOT Join(task) THEN PRINT "cancelled"
SUB Work(text AS STRING)
    WHILE TRUE
        Sleep(1000)
    WEND
END SUB

A cancellation request cannot overwrite an operational error:

ERROR Broken
    Code AS INTEGER
END ERROR
TRY
    task=SPAWN Fail()
    SELECT
    CASE COMPLETED(task)
        PRINT "ready"
    END SELECT
    Cancel(task)
    Join(task)
CATCH err AS Broken
    PRINT err.Code
END TRY
SUB Fail() FAILS Broken
    RETURN ERROR Broken { Code=42 }
END SUB

Tasks use guarded stackful activations and a bounded CPU execution pool, not one OS thread per task. Compiler function-entry/loop safe points prevent ordinary user CPU loops from requiring manual Yield. This is budgeted cooperative scheduling, not a hard latency guarantee; trusted library/foreign work may delay cancellation. Canonical value callbacks defer cancellation until their enclosing operation finishes. A typed failure from that operation remains observable. Channel/I/O/foreign/resource operations not verified for worker placement remain on the coordinator. Native blocking-operation worker pools are separate.

Channels are scoped owners with SENDER/RECEIVER capabilities, typed independent messages, rendezvous/buffering and explicit close. Receive returns SOME(message), including nested NONE payloads, or NONE for closed and drained. Send failure is ChannelClosed. Endpoints cannot escape their owner’s lifetime.

DIM jobs AS CHANNEL OF STRING = Channel(1)
Send(Sender(jobs), "message")
Close(Sender(jobs))
SELECT
CASE SOME(text)=RECEIVE(Receiver(jobs))
    PRINT text
CASE NONE=RECEIVE(Receiver(jobs))
    PRINT "unexpected closure"
END SELECT
SELECT
CASE SOME(text)=RECEIVE(Receiver(jobs))
    PRINT "unexpected message"
CASE NONE=RECEIVE(Receiver(jobs))
    PRINT "closed and drained"
END SELECT

Scheduler SELECT pairs SOME/NONE receive cases, supports typed AFTER Duration and COMPLETED(handle), and starts a fresh timeout on entry. Case bindings are local; one case commits, losing registrations detach without consuming their messages, and rotating ready-case selection prevents fixed-source-order starvation under continued progress. Cancellation unregisters/disposes registrations deterministically. A positive duration or absolute deadline outside the signed nanosecond scheduler range raises DateTimeError before any wait registration. Negative and zero durations are immediately ready. Supported operand restrictions and race/cleanup tests are in SELECT . Sleep(milliseconds) uses INTEGER milliseconds; nonpositive intervals yield.

JSON and typed data adapters

The JSON API specifies standard mappings, limits, numeric conversions, field/default rules, formatter registration, files/JSON Lines and HTTP. Dynamic JsonValue is a closed value tree with exact number spellings and ordered objects. Parsing rejects malformed UTF-8/syntax, duplicate keys and trailing content. Numeric conversions are checked; independent copies/cleanup remain ordinary language operations. Recoverable JsonError includes Code/Offset/Path/Message; fatal allocation failure is separate. Root JsonError reporting includes context.

JsonObject()/JsonArray() construct independent empty values. JsonObject accepts a LIST OF JsonMember, built with JsonField(name,value); typed leaves use ToJSON and retain its checked errors. Duplicate keys keep first position and use last value. JsonPut(BYREF object,key,value) returns insertion TRUE/replacement FALSE; wrong kind raises JsonError4. Typed conversion failures leave the object unchanged. The JsonValue overload uses BYVAL snapshots; field/index loans follow ordinary BYREF exclusion. This edits the dictionary payload without whole-aggregate rebinding.

body=JsonObject([JsonField("name","BASIC"),JsonField("limit",10)])
original=body
JsonPut(BYREF body,"limit",20)
PRINT StringifyJSON(body)
PRINT JsonGetInteger(Get(original,"limit"))

EncodeJSON/DecodeJSON and ToJSON/FromJSON use checked generated typed adapters; complete-call AS/destination inference follows ordinary generic rules. Standard handling covers scalars, records/errors, enums/choices, optionals, collections and bounds-preserving arrays. Resources are rejected. Public ToValue/FromValue apply shared SERIALIZE rules/converters and ignore JSON-specific annotations/profiles. SerializationSchema and FormatMetadata expose typed metadata without object pointers; legacy ValueSchema retains the effective JSON view. See shared serialization . TypeSchema, SerializationTypeSchema and JsonTypeSchema expose recursive declared type graphs through NONE witnesses, with local references for fields, collections, generic arguments, enums/choices and converter representations. Graph fields are readonly; copied descriptor/FORMAT values remain independent. Opaque storage and field annotations stay private; unsupported/dynamic shapes are explicit. These queries describe declaration defaults, not runtime JsonCodec profiles or sampled values. Graph contracts and APIs .

TYPE headers accept SERIALIZE and JSON ENCODE WITH / DECODE WITH defaults. Fields accept NAME, READONLY, WRITEONLY, EXCLUDE, OMITNONE and per-direction converters, introduced with SERIALIZE for shared rules or JSON for JSON-specific rules. JSON properties override shared properties regardless of source order; access is one READONLY/WRITEONLY/EXCLUDE group. OMITNONE is additive. Converter precedence is JSON field, shared field, supplied JsonCodec rule, JSON type, shared type, standard handling. READONLY/WRITEONLY refer to wire direction, not BASIL mutability. Missing explicit defaults take precedence over optional NONE; missing required fields fail. Explicit null only initializes optionals. Invalid supplied data never falls back to a default. Strict unknown/disallowed fields fail unless IgnoreUnknown is selected. Duplicate keys/mapped names are not relaxed by that option. Failed assignment commits nothing.

FORMAT OptionsType { Field=constant,… } attaches a typed constant ordinary record to a TYPE header or ordinary TYPE field. Imports, field/type checking and visibility apply; duplicate annotations of one nominal options type are rejected. Validation also applies to unused annotations. Options can contain checked scalar/string/enum constants, NONE/SOME, nested records and empty collections with ordinary field defaults; resources, arbitrary calls and nonconstant values are rejected. Metadata nesting is bounded to 32. Queries return independent owned optionals. No annotation executes arbitrary build-time code. Codecs interpret their own option types and validate format-specific rules; no YAML/TOML/XML parser is implied by this syntax.

WithJsonFormat copies a profile and replaces only the selected type rule. Converter input/result representations and declared failures are checked, including an optional second read-only JsonLocation or SerializationLocation parameter in a converter signature. No global formatter registry exists. Custom error types/fields remain unchanged; converters use that explicit parameter to retain location in their own error schema. JsonError receives location automatically. Adapter failures combine the codec’s static admitted set and the selected schema converter closure; unrelated source registrations do not contribute errors. Named nongeneric/noncapturing converters are the current registration subset. Representation roots bypass rules once; their children use normal recursive selection.

HTTP helpers retain response metadata and distinguish non-2xx HTTPStatusError, 204 NONE and successful decoded values (including SOME(NONE) for JSON null). RouteJSON adapts named one-argument functions using existing buffered routing. Mutable router slots explicitly admit Error. ServeHTTPChecked propagates original typed failures; convenience server boundaries report their concrete names and send 500 where possible, and handle JsonError as 400. Limits, cancellation, owned argument/error cleanup and losing task/SELECT registrations remain enforced; JSON loops contain reviewed budgeted safe points.

Library and platform scope

Typed SQLite values/strict accessors and positional LIST OF SqlValue parameters are implemented, alongside the legacy text API. Dictionary/record parameter sources and anonymous-record convenience remain planned. SQLite requirements are not a claim all requested adapters already work.

TCP, DNS, verified HTTP/1.1 HTTPS client/server, routing/middleware and directory serving are implemented. TLS uses the existing static backend. Open/read/stat/ directory/seek use bounded file workers; writes/flush/some filesystem mutations remain synchronous. The datetime library includes civil values, checked duration/period arithmetic, explicit timezone rules, and clocks. The general public crypto suite remains planned. See HTTP , networking , platform , time/files and regex .

Regex is a built-in ordinary BASIL library with demand-loaded modules; no import, regex .blib, native regex archive or Rust runtime is required. Compiled Regex is opaque and noncopyable, ordinary calls borrow it, and explicit TAKE transfers it. Every compile/search operation declares RegexError, including compiled patterns, with checked work/storage/depth/result failures. Native follows the pinned Rust regex1.13.1/Unicode16 baseline; EcmaScript selects the explicit ES2025 Unicode-mode profile with bounded backreferences/lookaround. Pattern dialect does not change BASIL replacement/split conventions or scalar/UTF-8 result offsets. Exact APIs, limits, unsupported flags and ownership: regular expressions .

EXTERN imports supported scalar C ABI signatures from native objects/archives; EXPORT emits C wrappers. POINTER is a distinct non-owning address type. Pointer conversion/access and calls to UNSAFE FUNCTION/SUB require UNSAFE ... END UNSAFE. Ordinary type, initialization, failure and ownership checks remain enabled. CALLBACK(...) generates typed noncapturing C adapters; CALLABLE NOSUSPEND(...) provides checked nonsuspending BASIL delegates. C CALLBACK rejects escaping failures; NOSUSPEND retains declared typed failures. Both reject explicit suspension, cancellation and task starts. Automatic checkpoints in checked prebuilt callees are suppressed through the imported wrapper; missing legacy proof does not establish nonsuspension. Foreign-thread entry is rejected. OPAQUE TYPE permits public type naming/ownership while restricting construction, unmarked/private fields and extension to its defining module. Explicit PUBLIC fields are readable outside that module, with no external assignment, nested mutation, BYREF, SWAP or TAKE through them. Copyable field values can be independently copied; resource reads retain their ownership/loan restrictions. The module may write when borrowing rules permit; readonly access is not global immutability. PUBLIC/PRIVATE field modifiers currently require an ordinary opaque record. An opaque record may use private types for its hidden fields; a PUBLIC field must have a publicly nameable type. Field rules . Unsafe Main and converting an unsafe function into a safe delegate are rejected. See exact operations, legacy trusted native declarations and restrictions .

Versioned native-adapter helpers provide checked UTF-8/packed BYTE construction, matching destruction, reviewed owned record/resource construction and rich native errors. Generated private UNSAFE EXTERN BASIL adapters publish exact TAGS/placement and marshal scalar/DOUBLE/borrowed aggregate arguments. Native calls cannot retain BASIL views or suspend; foreign TAKE remains rejected. These trusted adapters extend library authoring, without making raw C EXTERN accept BASIL layouts. Public GTK helpers validate native object type and initialization-thread affinity.

The GTK 4 .blib provides opaque application/widget owners, queued BASIL event handling, models/text/menus/dialogs/drawing and optional direct noncapturing handlers. GLib prepare/poll/dispatch/release is one scheduler transaction; prepared native reads never span another BASIL activation. Native jobs retain buffers through cancellation acknowledgement, without polling or a thread per wait. GTK/GLib remain explicit system dependencies. The toolkit also provides typed images with exact encoded originals, immutable mixed-content snapshots and bounded whole-document undo/redo. Unicode-scalar image positions, staged import validation, selection/cursor restoration, checked early edit-group completion and automatic group cleanup preserve complete documents. Snapshots retain immutable native image assets; exported byte lists are independent. Decode/snapshot work is synchronous and bounded; styled tags/child anchors remain outside this API. Contract/limits . Image clipboard reads suspend through native completion descriptors; one-shot queued requests capture buffer revision/selection before dispatch and recheck before atomic commit. Typed PasteResult distinguishes image/text/empty/stale/read-only/cancelled outcomes; read failures raise GtkError and task cancellation remains cancellation. Cleanup cancels and acknowledges native completion. Ordinary editor paste is preserved. Clipboard contract . Full stylesheet owners have scoped provider cleanup; declaration CSS and row activation have checked APIs, while only application-created shortcut controllers change phase. Styles/events . Foreign C calls use a lazily allocated guarded worker stack; task guards remain. Foreign structs/varargs/fallible or BYREF C signatures, captured callback environments and arbitrary foreign-thread attachment remain unsupported. The internal BASIL ABI is not the C ABI. C-compatible object/archive output and compiled BASIL .blib imports are separate products. Package ABI v15 retains v14 source-equivalent resource ownership and cleanup hooks, nonsuspending delegates and suspension/cancellation effects across imports, and pins the public native runtime helper boundary. Incremental analysis caching remains planned. Native boundary , package ABI .

The compiler/linker and IDE are BASIL; OS/context primitives and native components still contain assembly/C or external implementations. Ordinary compilation uses the integrated emitter/linker. Declared hosted system libraries use the explicit Linux cc bridge described in project tools . Development packaging/regeneration has additional tools. A statically linked compiler is not a promise that optional native package files are embedded in its executable.

Logging

Six severity SUBs, LogTrace/LogDebug/LogInfo/LogWarn/LogError/LogFatal, accept a message pattern and formatting arguments, optionally preceded by Logger or LoggerHandle. Default output is UTC timestamp/level/message to stderr at Info. Fatal is severity only. Numbered message fields share scalar f-string modifiers; literal patterns are checked at compilation. Metadata layouts use named fields.

DefaultLogger returns a copyable service-access capability. CreateLogger returns a scoped resource owner; SetDefaultLogger(TAKE owner) transfers it to program lifetime. Handles never implicitly own/close, and closed/stale generations are checked. Configuration and output hold exclusive service custody throughout suspension. Cancellation unregisters losing waits and preserves owned cleanup. Program services close after children and ordinary locals, including on recoverable entry failure. Ordinary BASIL values keep independent assignment semantics.

Eager expressions evaluate normally. Zero-argument function values instead execute synchronously once after filtering; their actual errors propagate under ordinary checked contracts. Captures are established at construction. Destination/format failures are reported by severity calls; explicit WriteLog, configuration, Flush and Close declare LoggingError. Cancellation is distinct and fatal allocation failure has no added normal-cleanup guarantee.

LogRecord retains structured metadata. CreateLogSink accepts an independently owned context, a readonly/BYREF context+record handler and optional context finisher; AddLogSink adopts its resource. Named resource contexts require TAKE. Stateless handler and direct context+handler overloads use the same protocol. Multiple sinks run in order and remain attempted after a destination failure. A recursive call to the same logger reports a failure instead of waiting on itself. No logging thread, record queue, GC or reference counting is introduced. Unused logging and unused adapters are omitted. API/limits , compiler contracts .

Commands and project packages

basic toolchain prints version, target, BASIL ABI, actual executable/hash and resolved native SDK root as JSON without loading a project. Validated installations pin compiler/native inputs together through launcher updates. Package incompatibility errors report producer/consumer schema, target and ABI with a source rebuild hint; incompatible artifacts remain rejected. Installation details .

basic api emits a schema-1 JSON catalog of public prelude function/sub headers, including overloads, defaults and declared failures, without compiling application source or linking. It is declaration discovery, not contextual semantic completion. Catalog contract .

The public compiler uses the public typed Command/ParseArguments/RenderHelp API. Process-argument overloads skip argv[0]; explicit lists remain pure. FORMAT CommandSettings/OptionSettings derives scalar/optional record options, help and checked integer bounds. ArgumentOptions constructs the requested checked record; each extraction checks the target schema and supplied values. Strict typed getters report missing or wrong-kind values. Help/error printers return status without exiting. Usage and current field restrictions . build and run read schema-1 basic.toml manifests, resolve local source/prebuilt dependencies relative to their declaring manifests and check compiler/package versions transitively. IMPORT Alias resolves a declared project dependency; quoted imports retain relative-file semantics. Help works without loading projects; run -- ... forwards application arguments and its exit status. Compatibility stdin/byte-output compiler invocation remains. Commands and schema .

A .blib contains checked module interfaces, actual nongeneric native code, generic templates and transitive static dependencies/notices. Equal native hashes share a final input; conflicting builds are rejected. The final BASIL runtime is shared. Library suspension analysis closes implicit runtime dependencies from function roots even without an executable entry or explicit task syntax. Hosted SDKs ship all 32 supported service combinations, sharing SQLite/OpenSSL archives; normal application builds select those inputs without building runtime C code. Missing hosted components are diagnosed before the system linker runs. ABI v15 retains v14 portable native/resource ownership metadata, preserves the v12 Duration and v13 callable layouts, and uses CARRIER11 for concrete-preserving errors, checked BASIL callables with failures/suspension and codec profiles, including nested storage and owned result dependencies. BASIL NOSUSPEND may fail; C CALLBACK remains infallible. Explicit imported TAKE/BYVAL and BORROW results retain source checking for native owners, resource records and owning containers. Readonly Sender/Receiver inputs retain checked scope lifetimes; task handles/channel-owner escape and C/legacy tag resource transfers retain separate restrictions. It adds sealed embedded data and separately versioned deterministic build-effects metadata; supported v14 packages remain consumable. Legacy unknown effects cannot serve as build-time decoders. v14 readers reject v15 output. Compiled resource ownership and compatibility . Compiled profile contracts . The v2 container compresses beneficial payloads individually using native BASIL DEFLATE, leaving metadata readable; logical hashes preserve deduplication, and bounded decoding accepts v1 and v2. Generic bodies compile under consumer checks. Remote fetching, lockfiles and incremental caches are planned. Project TOML parsing is a bounded schema-specific subset, not a full TOML serialization library. Container, identity and ABI limits .

Buffer compression libraries implement DEFLATE, zlib, gzip, both LZW bit orders, and bzip2 decoding in native BASIL. Checked output bounds and format checksums precede successful return. Preset dictionaries and reusable streaming objects remain future APIs. Codec contracts .

Checked ReadUInt16LE/ReadUInt32LE/ReadUInt64LE read packed BYTE lists at arbitrary byte alignment, returning UINT32/UINT32/UINT64 respectively. Invalid offsets or insufficient logical bytes raise IndexError carrying the original offset.

Implementation limits

Current checks include at most 64 parameter words, 64 active type/expression/statement/declaration parser levels, at most 62 error tags and 127 choice alternatives. Local frames/availability facts grow with the function, including hidden task/loop/selector/error storage; variable IDs have source-derived namespaces. Linux x86-64 checks its signed-displacement frame encoding limit and probes large allocations without skipping guards. Actual stack capacity is finite; stack exhaustion remains fatal. Type leaves count as one parser level; nominal field and extension graphs use iterative traversal instead of this syntactic cap. Oversized inputs are rejected with parameter/frame/nesting diagnostics. Limit boundaries . Checked limits and platform/runtime stack capacity must not be removed without replacement safety proofs. Whole-program semantic validation still traverses the backend; reusable semantic IR/separate compilation are future architecture work. Target-bound emission is organized under explicit CPU/ABI/object-format/OS backend homes; Linux x86-64 remains the only supported target. Architecture . Stage equality is reproducibility evidence, not proof of correctness or universal numerical accuracy.

The current compiler applies bounded checked-plan optimizations after semantic checking: scalar/range simplification, expression register residency, small-call inlining/argument forwarding, checked loop ranges, frame caches and nontrapping loop computations, scalar aggregate destinations and local branch layout. These do not relax integer checks, IEEE floating behavior, independent values, explicit resource transfers or scheduling/cleanup. Optimizer budgets select ordinary checked code as the fallback, without limiting accepted programs. Implementation and measured scope . Compiler-private numeric domains use named constants ; their numeric encodings do not define additional source-language behavior.

Planned and unresolved behavior

The following are not implemented: YAML/TOML/XML, resource keys/sets, indexed field holes, multiple success returns, local/anonymous records, generic record extension/cleanup hooks, resource/borrowed lambda captures, mutable module storage, native scheduler test-clock injection, locale/timezone discovery, a general crypto suite and Go-style templates. Runtime custom decoders can use shared adapters, SerializationSchema and typed FormatMetadata queries (with legacy ValueSchema retained). Those safe adapters do not imply arbitrary mutable reflection. EMBED’s checked ordinary decoder boundary is documented above; richer format transport remains separate work. JSON’s current registration/default/streaming limits are explicit in its API .

Agreed direction and provisional syntax are centralized in requirements and the current design baseline . Outstanding work has one home in the checklist . Open schema/codec evaluation interfaces, resource-hook/base cleanup order, precise borrowed-task/capture rules, future ports and syntax decisions must remain marked unresolved rather than inferred.

Dates, times, durations and clocks

The standard prelude has opaque, validated Date, TimeOfDay, LocalDateTime, Instant, UtcOffset, OffsetTime, OffsetDateTime, ZonedDateTime, calendar component values, and immutable timezone rule snapshots. Dates use the proleptic Gregorian calendar with year zero. Instant is on the Unix timeline, without leap seconds. A local datetime needs an explicit offset or zone to be resolved to an instant. Zone gaps/overlaps require a resolution policy; the default rejects ambiguous or nonexistent times. No ambient timezone is assumed.

Duration is exact signed floor seconds plus a nanosecond fraction from zero through 999,999,999. Period represents calendar months and days. They do not implicitly convert. Scaled totals, arithmetic and construction that exceeds range raise DateTimeError. Calendar and duration values have independent value semantics. Clock is a resource; its monotonic samples and deadlines are copyable values associated with one clock domain. Cross-domain use fails. System and deterministic test clocks are available, with scheduler timeouts still on the system monotonic clock.

See datetime APIs and limits for parsers, text profiles, default JSON codecs and explicit rules-bound zoned storage. Plain datetime operations do not introduce native library dependencies. Duration’s representation change requires rebuilding compiled BASIL libraries with package ABI v12.

UUID and secure randomness

UUID is a first-party ordinary opaque record with two unsigned 64-bit halves, independent-value semantics and readonly public High/Low fields. Factories accept words, network-order bytes and checked text; version-specific construction sets its required bits. Nil/Max are ordinary values rather than NONE. Built-in value equality/order/hash, collections, canonical JSON/shared string mapping and string-style interpolation apply. Raw construction preserves unknown version/ variant bits. Version coverage, errors and clock behavior .

SecureRandomBytes and FillSecureRandom use the OS cryptographic source, separate from the PRNG. Results contain only initialized bytes. Fill stages a complete replacement and commits without suspension; failure/cancellation before commit leaves its destination unchanged. Cancellation after commit preserves the change. IoError reports OS failure; checked constructor sizes use IndexError. There is no weak fallback. Fatal allocation failure is separate. Exact semantics .

URL values and builders

The first-party URL value has independent ownership, readonly opaque fields and checked URI parsing. Raw components preserve escaped delimiters and presence; decoded accessors validate UTF-8. Builders encode decoded component data. Query lists retain order, repeats, bare flags and explicit empty values; dictionary adapters support one/many values. JoinURLPath appends paths with optional Query replacement by key; ResolveURL separately implements RFC reference resolution. Updates stage complete results before committing. HTTP consumes the same parser through stricter transport checks, and typed URL request overloads share existing request/redirect/cleanup behavior. The shared/JSON default is URI text; malformed wire values produce JsonError, standalone operations UrlError. API, encoding/slash/merge policies and explicit limits .

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.