Documentation/Reference

Commands, projects and libraries

On this page

Verified scope: Linux x86-64, local source projects and prebuilt BASIL libraries. Tooling requirements also describe future remote resolution/locking. Package format and ABI specify this release.

Standard-library declaration catalog : basic api works without a manifest or native SDK and supplies IDE discovery from parsed declarations.

Build and run

build/selfhost/stage3 --help
build/selfhost/stage3 help build
build/selfhost/stage3 api
build/selfhost/stage3 build hello.bas -o build/hello
build/selfhost/stage3 run --manifest examples/space-invaders/basic.toml -- --help

build without a source reads basic.toml in the working directory. --manifest selects another manifest; relative paths inside it resolve from its own directory. Default outputs are <project>/build/<name> or <name>.blib for libraries. An explicit -o/--output resolves from the invoking directory. run accepts only executables, inherits terminal input/output, waits and returns the child’s exit status. Arguments after -- go to the program, including --help.

Help is generated from the same public command declaration as parsing and works before any manifest/source access. Usage errors print relevant usage to stderr and exit 2; build/data errors exit 1. Help/version go to stdout. The compiler version is 0.1.0; package, manifest and ABI versions are separate.

Compatibility remains: [--emit-object|--emit-library|--debug|--debug-lines] source.bas > binary, and no arguments compiles stdin to stdout. A legacy .bas invocation emits bytes; build writes the output file and prints its path. The frozen compact checkpoint builds the maintained core; that core builds the public front end. Stage 2 and stage 3 compare the same public front end.

Manifest

manifest-version = 1
[project]
name = "my-app"
version = "0.1.0"
[toolchain]
compiler = ">=0.1.0, <0.2.0"
[target]
kind = "executable" # or "library"
root = "src/main.bas"
[dependencies]
Raylib = {path = "../raylib/build/raylib.blib", version = "0.1.0"}

IMPORT Raylib resolves the declaring project’s dependency alias. Alternatively, path names a source project directory containing its own basic.toml. Quoted source imports retain their existing relative-file behavior. Source projects compile from current sources each build; no stale binary cache replaces edits. Dependency aliases are ASCII BASIL identifiers, compared without case. Unresolved aliases, wrong target kinds, ambiguous aliases and conflicting builds of the same package name are diagnosed. Optional dependency versions assert an exact package version. Compiler constraints accept exact MAJOR.MINOR.PATCH or comma-separated =, <, <=, >, >= comparisons, checked transitively.

The manifest reader implements a project TOML subset, not the planned general TOML library: strings/escapes, integers, Booleans, arrays, inline tables, dotted keys, headers and comments. Arrays of tables, multiline strings, date/time and floating scalar literals are rejected. Unknown schema fields are errors; other platforms’ native tables are ignored. Manifest limit: 2 MiB. Source/import namespace rules remain those of the language.

Library manifests may declare:

[project]
name = "wrapper"
version = "0.1.0"
licenses = ["LICENSE.txt"]
[target]
kind = "library"
root = "src/wrapper.bas"
[native.linux-x86_64]
archives = ["vendor/libwrapper.a", "vendor/libupstream.a"]
system-libraries = ["SDL2", "GL", "m", "dl", "pthread"]

Declared/referenced archives and notices travel inside .blib. Native identity currently means archive basename; equal hashes merge, unequal bytes under one identity fail. This deliberately rejects ambiguous builds rather than picking one. Prebuilt transitive dependencies need no original paths or source projects. BASIL runtime state/helpers link once; archive members are selected by references. Generic templates remain checked source input for consumer specialization. Nongeneric bodies are precompiled, with checked import stubs in their interfaces. Current ABI v15 retains v14 suspension/cancellation effects, supported resource cleanup hooks, checked callables and compatible profile contracts. Native resource owners and owning containers retain source-level borrow/transfer rules across compiled libraries; see compiled resource interfaces . Scope-bound task/channel capabilities retain their source restrictions: channel owners cannot escape, and task handles have no concrete public signature spelling. Foreign C interfaces and anonymous CALLBACK/NOSUSPEND adaptation remain separate restricted boundaries.

The GTK 4 .blib and demo use this path with declared GTK/GLib system dependencies and a bundled static adapter/readiness archive.

Extracted verified interfaces/archive blobs live under the invoking project’s build/.basic/packages/<id>/. This is build output, not a remote download cache. Git/registry dependencies, lockfiles and shared incremental compilation caches remain future work. Put mutually dependent library modules in one project; cycles between independently packaged libraries are currently rejected. Library imports must be relative within their project; cross-project imports use the declared bare alias. Absolute/cross-project quoted imports are rejected when packaging rather than persisting producer-machine paths.

Public argument API

Command declares Options, Positionals and child Commands. CommandOption has Name, optional one-character Short, Description, Kind, Required, optional typed Default, and Global. CommandPositional has name, description, kind and required status. ArgumentKind is Text/IntegerValue/RealValue/BooleanValue. Use CommandFlag for a Boolean with default FALSE. Default constructors are ArgumentText, ArgumentInteger, ArgumentReal, ArgumentBoolean.

ParseArguments(command) reads process arguments and skips the executable. The explicit ParseArguments(command, arguments) overload remains pure and expects the executable to have already been excluded. CommandLineParameters() returns that independent argument list; raw CommandLineArguments() still includes index zero. Process access declares IoError; explicit parsing does not.

TextOption, IntegerOption, RealOption, BooleanOption supply ordinary typed defaults without boxing at call sites. For example, IntegerOption("frames", Default=0, Min=0, Max=360000, Help="Frame limit"). Integer bounds are inclusive and validated for defaults and supplied values. TextArgument, IntegerArgument, RealArgument, BooleanArgument read the Options/Positionals dictionaries and raise ArgumentError for missing/mismatched values; they never silently turn a typo into a default or FALSE.

The parse result is ArgumentResult.Parsed or .Help; invalid input raises ArgumentError {Message, CommandPath, Usage} (and checked StringError). ParsedArguments holds CommandPath, Options/Positionals and Forwarded with ordinary independent-value semantics. RenderHelp remains pure. PrintArgumentHelp(help) prints and returns 0; ReportArgumentError(error) prints to stderr and returns 2. Neither exits the process; the caller decides whether to return, continue, or perform additional cleanup.

For ordinary applications, declare a record instead of a command tree:

TYPE Options FORMAT CommandSettings {Name="demo",Description="A typed command"}
    Frames AS INTEGER=0 FORMAT OptionSettings {Min=0,Max=360000,Help="Frame limit"}
    Hidden AS BOOLEAN=FALSE
    Output AS OPTIONAL OF STRING=NONE FORMAT OptionSettings {Name="output",Short="o"}
END TYPE
TRY
    SELECT TYPE ParseArguments(Options {})
    CASE ArgumentResult.Help AS help
        RETURN PrintArgumentHelp(help)
    CASE ArgumentResult.Parsed AS parsed
        options=ArgumentOptions(parsed) AS Options
        PRINT options.Frames
    END SELECT
CATCH usage AS ArgumentError
    RETURN ReportArgumentError(usage)
END TRY

Defaults are ordinary record values; the supplied prototype must be initialized. Field types currently supported: STRING, INTEGER, DOUBLE, BOOLEAN and their OPTIONAL forms. Absent optional flags produce NONE; explicit empty text remains SOME(""). Options default to the lowercase physical field name. OptionSettings supports Name, Short, Help, Required and inclusive integer Min/Max. The TYPE header requires CommandSettings; format options retain ordinary nominal module identity. Shared wire names are respected internally; read/write-excluded fields and unsupported nested/resource fields are rejected. Custom converting serialization contracts are not a supported CLI type-conversion mechanism.

ArgumentOptions(parsed) AS T constructs a complete independent owner through public checked serialization adapters. Dynamic command trees may also use it for a selected typed options record (as the compiler does); missing required/nondefaulted fields and wrong scalar types remain errors. Help is still a normal result, so no fabricated default record is exposed as a successful parse. SELECT TYPE checks coverage and binds the selected fresh result without copying its payload. Subcommands/positionals/forwarding remain the dynamic Command API; the typed record convenience declares options only.

Measured project-build optimizations preserve full checks on every invocation; persistent incremental/prelude caching remains planned.

Compiler and SDK identity

basic toolchain prints JSON identifying the actual running compiler/hash, target, BASIL ABI and resolved native SDK root. It works before loading a project and can help diagnose an incomplete SDK. Incompatible .blib diagnostics show producer and consumer schema/target/ABI plus the command to rebuild the producer from source. A .blib file is not a substitute for the source needed by that rebuild command.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.