0x0 Language Guide
This guide documents the language slice implemented by
compiler/main.0x0. It is descriptive, not aspirational: a construct
belongs here only when the current compiler parses and emits it.
Source Files
A 0x0 file is a sequence of parenthesized forms. The conventional first form is
the module declaration:
(⊙ main)
The compiler currently uses the module name when emitting OISA. If no module
form is present, the module name defaults to main.
File-based compiler entry points accept import forms:
(↥ "import-helper.0x0")
(↥ "pkg:core-map")
↥ loads another .0x0 source file before compiling the importing file. The
path is a text literal. Relative paths are resolved from the importing source
file's directory; absolute paths are used as written. Imported functions
participate in semantic validation and can be called by the root file. The root
file's ⊙ form still controls the emitted module name.
Paths beginning with pkg: resolve through the package lockfile. For example,
(↥ "pkg:core-map") reads the dep core-map ... entry from 0x0.lock and
loads that local dependency path.
Imports may also declare an alias:
(↥ "import-helper.0x0" helper)
With an alias, imported function definitions are exposed as alias.name symbols
inside the importing compile unit. Imported modules may declare exported
functions with ↦:
(↦ public-add another-public-function)
If a module has no ↦ form, all of its functions are exported for namespaced
imports. If it has one or more ↦ forms, only those functions may be called
through the import alias. Non-exported functions remain available inside the
imported module for its own implementation.
This is the first multi-file slice, not the final package system. The repository
now has a checked 0x0.pkg manifest and generated 0x0.lock for local path
dependencies. Package imports resolve local lockfile dependencies, but imports
do not yet provide full path normalization.
Line comments start with ; and continue to the next newline:
;;; Module-level comment.
;; Function or section comment.
; Local implementation note.
Comments are ignored by the lexer. They must not change emitted OISA or runtime
behavior.
Functions
Functions use the ƒ form:
(ƒ add
(∷ (→ I64 I64 I64))
(a b)
(+ a b))
The implemented function shape is:
(ƒ name
optional-annotations...
(param...)
body...)
Annotations are accepted with the ∷ form. The emitter still skips annotation
nodes when generating OISA, C, and ELF, but the compiler now validates the
implemented contract slice before emission:
- the arrow form must use
→; - annotation arity must match the function parameter count when the return type
is concrete;
- calls to typed 0x0 functions must use the declared arity;
- builtin calls must use the supported arity;
- simple argument and return mismatches are rejected when both sides can be
inferred by the current checker.
Annotations therefore describe checked source contracts for the current slice,
not runtime metadata:
(∷ (→ ArgType... ResultType))
Functions may also include a doc annotation before the parameter list:
(doc "Return the sum of two integers.")
doc annotations are parsed as source metadata and skipped by the OISA, C, and
ELF backends. This lets libraries and frameworks carry documentation inside the
language surface without changing runtime behavior.
Functions may also include a capability annotation:
(cap pure)
The current capability names are pure, io, file, network, and process.
Unannotated functions default to io. A pure function may call pure functions
and pure builtins, but it may not call read-stdin, argv, env, read-file,
write-file, print, panic, or another function whose capability is not
pure. file is
used by safe file wrappers in lib/core/file.0x0. network and process are
accepted source contracts for framework/runtime boundaries; the current runtime
does not yet expose socket or subprocess builtins. Capability annotations are
skipped by emitters after validation, like type and documentation annotations.
Multiple body expressions are evaluated in order. The final expression is the
function result. An empty body emits Unit.
Semantic Checks
Before any backend emits output, the compiler validates:
- top-level forms: only
⊙,↥,ƒ, and top-leveldocforms are accepted; - module names and function names must be symbols;
- function names must be unique;
- parameter lists must contain unique symbols;
- local symbols must resolve to parameters or earlier
≔bindings; - call heads must be symbols;
- user function calls must resolve to declared functions and match arity;
- current builtin calls must match the backend-supported arity;
- obvious type mismatches against
I64,Text,Bool, andUnitcontracts
are rejected.
ifconditions must be knownBoolvalues when the condition type is
inferable. Any remains permissive.
notrequires a knownBoolargument, and=/!=reject operands whose
inferred concrete types differ. Any remains permissive.
purecapability annotations reject calls to effectful builtins and any
function whose capability is not pure.
This is not yet a complete type system. The checker deliberately treats
unresolved complex cases as Any instead of pretending to prove them.
Values
The current source language supports:
I64integer literals, including negative integer syntax.Textstring literals with\n,\t,\r,\", and\\escapes.- Symbol references for parameters, local bindings, and known constants.
Unit, represented as nil at runtime.trueandfalse, represented as integer truth values in the current ELF
value/tag slice.
- Lists through
list,cons,head,tail,empty?,len,nth, and
reverse.
The first source libraries in lib/core define conventional list-backed
Option, Result, map, byte-list, JSON-field, error, safe path, and safe file
helpers. See docs/core-library.html for their current source-level
representations.
Control Forms
if has exactly three operands:
(if condition then-expression else-expression)
The current truth rule treats nil and false as false in the C compatibility
runtime. The ELF slice uses the value register for branch tests in the supported
examples and compiler path.
≔ introduces local bindings:
(≔ ((name value)
(other value))
body...)
Bindings are evaluated in order, and the body is evaluated with the new names in
scope.
do evaluates expressions in sequence and returns the last one:
(do
(write-file "build/out.txt" "ok")
42)
Builtins
The current compiler/runtime surface includes these builtins:
- Arithmetic and comparison:
+,-,*,/,=,!=,<,>,<=,>=,
not.
- Lists:
list,cons,head,tail,empty?,len,nth,reverse. - Text:
text-len,text-at,text-slice,text-cat,text-eq?,
int-to-text, text-to-int, is-space?, is-digit?, is-alpha?.
- IO and process boundary:
read-stdin,argv,env,read-file,
write-file, print, panic.
read-stdin reads standard input until EOF and returns it as Text.
argv returns the process arguments after the executable name as a list of
Text values. env reads one environment variable by name and returns its
value as Text, or "" when the variable is not present.
print writes shown values separated by spaces and then a newline. The final
main result printer writes integer and text results directly.
The OISA and C compatibility paths expose a dynamic Value runtime. The direct
ELF path implements the value/tag subset needed by the compiler and examples,
including bounded read-stdin, process argv/env, callable print for
integer/text values, and startup/runtime printing for the final main result.
The direct ELF runtime follows ABI 0.1 from docs/abi.html: function results
return as payload rax plus tag r15; up to six arguments travel as payload
and tag register pairs; Text is NUL-terminated UTF-8; lists are 24-byte cons
nodes; runtime panic exits with status 1; and direct ELF calls with more
than six arguments are rejected.
Example
(⊙ recursive-let)
(ƒ loop
(∷ (→ I64 I64 I64))
(n acc)
(if (= n 0)
acc
(loop (- n 1) (+ acc 7))))
(ƒ main
(∷ (→ Unit I64))
(_)
(≔ ((start 6))
(loop start 0)))
This returns 42.
Compatibility Rules
Every accepted source construct must pass the same checks:
- It parses through
compiler/main.0x0. - It passes the compiler-owned semantic checks.
- The source compiler and generated OISA compiler emit the same OISA.
- The generated native compiler emits the same OISA.
- The C compatibility backend and direct ELF backend run the examples with the
same output.
make docs-checkpasses the source documentation rules.- The compiler self-host gate reaches
stage2.oisa == stage3.oisa.