almac language report

ortfero

1. introduction

almac (algorithmic language for machine computation) is a small imperative programming language for the aldan system. the language emphasises a small grammar, explicit memory layout, value semantics for aggregates, and explicit propagation of faults.

a compilation unit is a module. a module is a sequence of declarations followed by an optional implementation section of definitions. a small fixed set of primitive types, predefined procedures, and standard modules is available at every top level; they are described in appendix b.

2. syntax

the syntax of almac is specified in an extended backus-naur form. a production has the form

production = expression .

the meta-symbols are

identifiers that begin with a lower-case letter denote non-terminals. terminals are either quoted literals or the token names introduced in section 3.

3. vocabulary

a token is a keyword, an identifier, a number, a character, a string, an operator, or a delimiter. tokens are separated by blanks, line breaks, and comments. a comment begins with either the character # or the digraph -- and extends to the end of the line.

an identifier is a letter followed by any sequence of letters and digits. the underscore counts as a letter.

ident = letter { letter | digit } .

the following 28 words are reserved and may not be used as identifiers:

as      asm      break      const           continue   defer
else    enum     false      fault           fn         for
if      implementation      import          inline     mod
none    on       raise      raises          record     return
then    true     type       union           var

a number is either an integer literal, a hexadecimal literal, or a floating-point literal.

integer = digit { digit } .
hex     = "0" "x" hexdigit { hexdigit } .
fp      = digit { digit } "." digit { digit } .

a character literal is a single byte enclosed in apostrophes and has type u8. a string literal is a sequence of bytes enclosed in quotation marks and has type string. a short-string literal is an abbreviated form for strings that contain no whitespace: a leading apostrophe followed by the string content, with no closing apostrophe. its content ends at the first whitespace character or at one of the closing structural tokens , ) ] } . the characters ' " ` ( [ { inside a short-string are errors — they almost always indicate a typo and the verbose "..." form should be used instead. examples:

'hello
'out/build.alm
'http://x.io:8080/q

short-string and character literals share the leading apostrophe; they are distinguished by the byte that immediately follows the content. 'X' (one byte then closing apostrophe) is a character literal of value X; 'Xy is a short-string of content "Xy".

the boolean literals are true and false. the literal none denotes the nil pointer and implicitly converts to any pointer type and to address.

operators and delimiters are

=  ==  !=  <  <=  >  >=  +  -  *  /  +=  -=
^  &  |  .  ,  ;  :  (  )  [  ]  {  }

the token ^ denotes a pointer prefix when placed before a type and a dereference when placed after a value. the token & marks an out-parameter in a signature and the argument supplied for one. the token | separates the variants of an enumeration or a tagged union. a compound statement is opened by : and closed by ; (see 9.3). each simple statement — assignment, return, defer, raise, break, continue — is itself terminated by ;. the closing ; of a compound is distinct from the terminator of its last statement, so a body with one statement looks like `: stmt; ;`. the ; also separates the condition from the step in a for loop (see 9.5).

a minus sign immediately followed by a digit begins a negative number literal; a minus sign immediately followed by another minus sign begins a line comment; a minus sign followed by any other character denotes binary subtraction or unary negation. an expression of the form x-5 must therefore be written with a blank between the operator and the digit.

4. declarations and scope rules

every identifier must be declared before it is used. declarations at the top level of a module are visible throughout the module; if they precede the keyword implementation they are also exported to importers. a scope is opened by the body of a function; nested scopes may redefine an outer name for the remainder of the body.

declaration = functionproto | typedecl | vardecl | constdecl
            | faultdecl .

a function prototype consists of a name and a signature with no body and binds the name to a function value with no code address. a later matching definition supplies the address. non-matching later definitions replace the binding entirely; see 10.3.

5. constant declarations

a constant declaration binds a name to a compile-time value.

constdecl = "const" ident "=" constexpr .
constexpr = "true" | "false" | "none"
          | integer | hex | fp | character | string
          | qualident .

if the right-hand side is a type identifier, the constant takes the size of the type in bytes and has type s64. if it is another constant identifier, the new constant receives that constant's value and type.

6. type declarations

a type declaration binds a name to a type.

typedecl = "type" ident type .
type     = qualident
         | "^" type
         | "[" [ constexpr ] "]" type
         | recordtype
         | enumtype
         | uniontype
         | functiontype .

a named type may refer to itself only through a pointer.

6.1 basic types

almac has the following built-in scalar types.

to any other integer type where one is required

6.2 array types

an array is a fixed-length sequence of elements of uniform type.

arraytype = "[" constexpr "]" type .

the length is a positive s64 constant. elements are laid out contiguously and the array's alignment is the alignment of its element type.

6.3 slice types

a slice is a fat pointer consisting of a data address and an element count. its size is 16 bytes and its alignment is 8.

slicetype = "[" "]" type .

an expression of array type is implicitly convertible to a slice of the same element type where one is required — in an assignment, an argument position, or an explicit as conversion.

6.4 record types

a record is a fixed sequence of named fields. the field list, a recdef, is a possibly empty comma-separated list of name-and-type pairs opened by a colon and closed by a semicolon.

recordtype = "record" recdef .
recdef     = ":" [ vardefs ] ";" .
vardefs    = vardef { "," vardef } .
vardef     = ident type .

fields are laid out in order with natural alignment and the record itself is aligned to the strictest alignment of any of its fields. a record may contain pointers to itself.

6.5 pointer types

a pointer value is either none or the address of a value of its base type. its size is 8 bytes.

pointertype = "^" type .

dereferencing, written p^, yields the pointed-to value as an lvalue.

6.6 enumeration and union types

an enumeration lists a fixed set of named variants separated by the bar operator. its underlying representation is u8 and distinct from any other enumeration. at least one variant is required.

enumtype = "enum" ident { "|" ident } .

a tagged union is a discriminated variant type. variants are separated by the bar operator; each variant carries an optional record of payload fields, written as a recdef (see 6.4). the discriminant tag is stored at offset zero; the payload area follows, aligned to the strictest alignment among the variants, and its size is the maximum payload size. at least one variant is required.

uniontype    = "union" unionvariant { "|" unionvariant } .
unionvariant = ident [ recdef ] .

for a union value u the expression u.tag yields the tag as a value of the union's internal enumeration, and u.v yields the payload of variant v as an lvalue. writing to u.tag changes the active variant.

6.7 function types

a function type describes a call signature.

functiontype = "fn" signature .
signature    = [ type ] "(" [ param { "," param } ] ")"
               [ "inline" ] [ "raises" ] .
param        = ident [ "&" ] type .

the leading type is the return type and may be omitted when the function returns nothing. the inline marker is admissible only in function definitions, not in first-class function types; see 10.2. the raises marker is part of the function type: a function value of raising type may be called only from a raising function.

7. variable declarations

a variable declaration introduces a named location of a given type.

vardecl = "var" ident type .

module-scope variables reside in the module's bss segment and are initialised to zero. function-scope variables reside on the activation frame and must be assigned before use; they are not implicitly initialised.

8. expressions

an expression denotes the computation of a value. many expressions are also lvalues when their principal operand is.

8.1 operands

primary = atom | ifexpr .
atom    = "none" | "true" | "false"
        | integer | hex | fp | character | string
        | qualident
        | "(" expr ")" .
ifexpr  = "if" expr "then" exprorcmpd [ "else" exprorcmpd ] .
exprorcmpd = expr | compound .
qualident = ident [ "." ident ] .

a qualified identifier selects a name from an imported module, a record field, a tagged-union variant, or an enumeration variant.

an index, a slice, a field selection, and a dereference may be applied as postfix operators to any primary and combined freely. a trailing comma-separated list of arguments after the postfix chain is a function call (see 9.2). an argument is an atom optionally followed by a postfix chain of indices, fields, and dereferences — binary operators and nested calls must still be parenthesised.

postfix = primary { index | field | deref } [ args ] .
args    = arg { "," arg } .
arg     = atom { index | field | deref } [ "&" ] .
index   = "[" expr [ ":" expr ] "]" .
field   = "." ident .
deref   = "^" .

an index of the form a[lo:hi] applied to an array or slice produces a slice that borrows the same storage, starting at lo and ending before hi. applied to a b64, the same form extracts the bit-field of width hi minus lo starting at bit lo.

8.2 operators

the operators of almac are organised in the following precedence classes, from highest to lowest:

arithmetic and relational operators require both operands to have the same type after implicit coercion.

8.2.1 logical operators

logical conjunction and disjunction are provided by the predefined procedures and, or, and not.

and ( b0, b1, ... )         short-circuit conjunction
or  ( b0, b1, ... )         short-circuit disjunction
not ( b )                   negation

all arguments and results are of type bool. and and or evaluate their arguments left to right and stop at the first false or true argument respectively.

8.2.2 arithmetic operators

the binary operators +, -, *, and / apply to integer and f64 operands; the operator mod applies only to integer operands. the unary - is available for s64, integer, and f64.

for u8 operands, the result of a binary arithmetic operator is truncated to 8 bits.

8.2.3 bitfield operators

for a value b of type b64 and integer expressions i and j the following forms are defined.

further bit manipulation is exported by the standard module bit; see appendix b.

8.2.4 relations

the six relational operators compare two values of the same type and yield bool. for f64 the comparison follows ieee-754 ordering; for u8 it is unsigned; for all other integer types it is signed.

8.2.5 type conversions

an explicit conversion has the form

cast = unary [ "as" type ] .

the permitted conversions are

a value of type u8 keeps its low 8 bits; longer integer targets are sign-extended from s64 and zero-extended from u8.

implicit conversions occur in assignment, argument passing, and comparison: integer literals adopt the type they are compared or assigned against, arrays convert to slices where a slice is required, and none converts to any pointer type or to address.

9. statements

statements denote actions. a statement sequence is opened by a colon and closed by a semicolon. within the sequence each simple statement — assignment, return, defer, raise, break, continue — is terminated by its own semicolon. statements are then juxtaposed without any separator between them, and the closing semicolon of the compound is distinct from the terminator of the last statement. a compound-form statement (for, if) ends at the closing semicolon of its body and contributes no further semicolon. an empty compound, written `: ;`, is permitted, and the colon introduces no new scope.

compound = ":" [ stmts ] ";" .
stmts    = stmt { stmt } .
stmt     = return | defer | raise | for | break | continue | if | ( expr ";" ) .
return   = "return" expr ";" .
defer    = "defer" expr ";" .
raise    = "raise" ident ";" .
break    = "break" ";" .
continue = "continue" ";" .

a function body with a single statement is therefore written `: stmt; ;`. the form is consistent across nesting: every body close is an explicit `;`, never merged with the terminator of the last statement, so the parser is unambiguous and ill-formed inputs such as `if cond: a;` (missing the body close) are rejected with a clear error rather than absorbed into the next statement.

9.1 assignments

assignment is an expression in almac. it has the form

assign = compare [ ( "=" | "+=" | "-=" ) compare ] .

and its result is the value of the right-hand side. chained assignment is not permitted.

the left-hand side of an assignment must be an lvalue: a local variable, a module variable, or a calculated address obtained through indexing, field selection, or dereference. aggregates of array or record type are not assignable as a whole; they must be updated field by field.

a slice assignment copies only the data pointer and the length; the elements themselves are not duplicated.

the compound assignments += and -= apply to scalar operands only.

9.2 function calls

a function call is a postfix operator on a function-valued primary. arguments follow the callee directly — there is no surrounding parenthesis pair. an arg list ends at the first token that is not a comma or an atom-starter (literal, qualident, or `(`).

args = arg { "," arg } .
arg  = atom { index | field | deref } [ "&" ] .
atom = "none" | "true" | "false"
     | integer | hex | fp | character | string
     | qualident
     | "(" expr ")" .

each argument is an atom optionally followed by a chain of non-call postfix operators: indexing `[i]`, field selection `.field`, and dereference `^`. binary operators and nested calls are not allowed without parentheses. for example, `f a + b` is parsed as `(f a) + b`; to call f with the sum, write `f (a + b)`. but `f a[i]`, `f a.x.y`, and `f a^.x` are all valid and pass the respective derived lvalue as the argument.

a zero-argument call has no syntactic form in the arg list. instead, when a postfix expression resolves to a function value and the surrounding context expects a non-function value, the parser auto-calls the function with zero arguments. so `last = clock.now` calls `clock.now` and assigns the result; `fp = clock.now`, where `fp` has a function type, keeps `clock.now` as a function value without calling.

a parameter declared with & must be supplied with a writable lvalue, and that lvalue must itself be followed by & at the call site. a call to a function whose signature includes raises propagates the callee's fault through the call site; such a call may appear only within a raising function.

9.3 statement sequences

a compound is a `:` ... `;` sequence of statements. it introduces no new scope.

9.4 if statements

if         = "if" expr "then" stmtorcmpd [ "else" stmtorcmpd ] .
stmtorcmpd = stmt | compound .

the keyword `then` separates the condition from the branch. each branch is either a compound `: stmts ;` or a single statement. a single simple statement carries its own `;` terminator; a single compound-form statement (another `for`/`if`) ends at its own body close. the closing `;` of the last branch's compound (or the terminator of the last branch's statement) serves as the if's overall terminator — no extra `;` is required after.

a dangling `else` binds to the nearest unmatched `if`. to attach an `else` to an outer `if`, wrap the inner branch in a compound: `if a then: if b then c;; else d;` attaches the `else` to `a`, while `if a then if b then c; else d;` attaches it to `b`.

the condition is of type bool. an if construct is also an expression (see 8 / appendix `ifexpr`). when used as an expression every branch must be present and all branches must yield values of the same type. each branch is either a bare expression or a compound `: stmts ;`; a bare expression yields its own value, a compound yields the value of its last statement. an expression-statement built from an if expression follows the `(expr ";")` rule and so carries an additional `;` terminator — for example `x = if y > 0 then 1 else 2;`.

9.5 for statements

the for statement is the only looping construct. both the condition and the step are optional.

forstmt = "for" [ expr [ ";" expr ] ] compound .

if the condition is omitted the loop is infinite. if a step is supplied — separated from the condition by a semicolon — it is evaluated after each iteration of the body and before the next condition check; it is the target of continue.

inside the body the statements break and continue exit the enclosing loop or proceed to the next iteration respectively. they are not permitted outside a loop.

for i != 10; i += 1 :
  say i;
;

9.6 return statements

return = "return" expr ";" .

the expression must be compatible with the function's declared return type. all registered defer actions run before the return completes.

a function with no declared return type returns nothing. the expression following return must still be supplied; use the literal none, whose type is none and is compatible with any no-result function. the conventional void-return form is therefore

return none;

9.7 defer statements

defer = "defer" expr ";" .

a defer statement registers an expression to be evaluated later, at function return or at fault propagation. registered expressions are replayed in last-in first-out order. at most eight defers are permitted per function.

fn open_and_read(path string) raises
  var f file.id:
  file.open path, f&;
  defer file.close f&;
  ...
;

(each `;` terminates one statement; the final `;` on its own line closes the compound.)

9.8 raise statements and fault handlers

raise     = "raise" ident ";" .
faultdecl = "fault" ident { "," ident } .
handler   = "on" ident ":" stmts .

a fault declaration introduces one or more named faults. their numeric identifiers are allocated on first declaration and shared across the whole program. several faults are predeclared; see appendix b.

a raise statement abandons the current function and marks the fault as active; all registered defer expressions are replayed before control transfers. the raises marker is required if and only if the function raises a fault or calls another raising function: omitting it where it is required is an error, and so is including it where it is not.

within a function body, after the statements and before the body's closing `;`, a series of on clauses may handle specific faults. a handler is `on ident:` followed by a statement sequence that runs until the next `on` clause or the body's closing `;`; the handlers share the body's close rather than carrying their own.

fn parse_byte u8(s string) raises
  v u8:
  byte.parse s, v&;
  return v;
  on invalid_format:
    return 0;
;

a handler runs in the caller's frame; completion of a handler converts the fault into a regular return. faults for which no handler applies continue to propagate to the caller.

10. function declarations

a function declaration associates a name with a signature and a body.

fndef     = "fn" ident signature [ vardefs ]
            ( body | asmbody ) .
body      = ":" [ stmts ] { handler } ";" .
handler   = "on" ident ":" stmts .
vardefs   = vardef { "," vardef } .
vardef    = ident type .
asmbody   = "asm" compound .

local variables follow the signature directly as a bare comma- separated list of name-and-type pairs. there is no `var` keyword here — `var` is reserved for module-level vardecls — so a leading `var` after a signature signals that this `fn` is a forward declaration and the `var` belongs to the enclosing scope. locals are allocated on the activation frame with natural alignment and are not initialised.

a function may not return an aggregate (array, record, slice, or tagged union) by value. to return such a value, pass an out-parameter.

10.1 formal parameters

a parameter list is a possibly empty sequence of bindings of the form ident [ & ] type. scalar parameters — bool, integer, f64, pointer, and address — are passed by value. aggregate parameters — array, record, slice, and tagged union — are passed by reference, independently of the & annotation.

the & annotation additionally marks the parameter as an out-parameter. an out-parameter must be supplied at the call site with a writable lvalue followed by &; the callee is free to modify the backing storage. a parameter without & is immutable inside the body of the callee.

10.2 inline functions

a function whose signature ends with inline is expanded at the call site rather than called conventionally. an inline function may not declare locals and may not contain return, defer, raise, for, break, or continue.

10.3 forward declarations

a declaration consisting of a signature alone (see section 4) binds the name to a function value with no address. a subsequent definition of the same name with an identical signature patches the forward binding with its code address, frame size, and inline flag. a definition of the same name with a different signature replaces the binding entirely and is not a patch.

forward declarations are permitted in the implementation section as well as the public section; a `fn` followed by neither a locals list nor a `:`/`asm` body is treated as a forward declaration, with what follows belonging to the enclosing scope.

10.4 assembly bodies

a function body may be replaced by an asm block. the contents of the block are passed verbatim to the rv64 assembler and compiled to risc-v instructions. the surrounding prologue and epilogue are emitted around the block. the concrete assembly syntax is outside the scope of this report.

fn nop() asm: nop;

10.5 predefined functions

a number of functions and intrinsics are predeclared in every module; they are enumerated in appendix b.

11. modules

a module is the unit of compilation and of namespacing.

module = [ importlist ]
         { declaration }
         [ "implementation" { definition } ] .

11.1 interface and implementation

every declaration that precedes the implementation keyword is public and is exported under its name. declarations and definitions placed after implementation are private and are accessible only within the module.

11.2 imports

an import list, if present, names other modules to load before compilation of the current module begins.

importlist = "import" string { "," string } .

each string is a path to a source file, without the .alm extension. a path that begins with / is absolute; otherwise it is resolved relative to the work directory of the module that contains the import. the local name under which the imported module is bound is the basename of the path — the segment after the last /, or the whole path if there is none.

a member of an imported module m is accessed by qualification: its exported type t is written m.t, its exported function f is written m.f. standard modules registered by the host are bound by basename, so import "file" resolves to the predeclared file module without reading anything from disk. recursive imports are rejected.

11.3 standard modules

the standard modules are documented in appendix b. they are implemented by the host and made available at every module's top level as if they had been imported.

appendix a — syntax

module       = [ importlist ] { declaration }
                 [ "implementation" { definition } ] .
importlist   = "import" string { "," string } .

declaration  = functionproto | vardecl | constdecl | typedecl
             | faultdecl .
definition   = fndef | declaration .

functionproto = "fn" ident signature .
vardecl       = "var" vardef .
constdecl     = "const" ident "=" atom .
typedecl      = "type" ident type .
faultdecl     = "fault" ident { "," ident } .
vardef        = ident type .

fndef        = "fn" ident signature [ vardefs ]
               ( body | asmbody ) .
asmbody      = "asm" compound .
signature    = [ type ] "(" [ param { "," param } ] ")"
               [ "inline" ] [ "raises" ] .
vardefs      = vardef { "," vardef } .
param        = ident [ "&" ] type .

type         = qualident
             | "^" type
             | "[" [ constexpr ] "]" type
             | "record" recdef
             | "enum" ident { "|" ident }
             | "union" unionvariant { "|" unionvariant }
             | "fn" signature .
recdef       = ":" [ vardefs ] ";" .
unionvariant = ident [ recdef ] .
qualident    = ident [ "." ident ] .

body         = ":" [ stmts ] { handler } ";" .
handler      = "on" ident ":" stmts .
compound     = ":" [ stmts ] ";" .
stmt         = return | defer | raise | for | break | continue | if | ( expr ";" ) .
stmts        = stmt { stmt } .
return       = "return" expr ";" .
defer        = "defer" expr ";" .
raise        = "raise" ident ";" .
for          = "for" [ expr [ ";" expr ] ] compound .
if           = "if" expr "then" stmtorcmpd [ "else" stmtorcmpd ] .
break        = "break" ";" .
continue     = "continue" ";" .
stmtorcmpd   = stmt | compound .

expr         = assign .
assign       = compare [ ( "=" | "+=" | "-=" ) compare ] .
compare      = addition [ relation addition ] .
relation     = "==" | "!=" | "<" | "<=" | ">" | ">=" .
addition     = multiply { ( "+" | "-" ) multiply } .
multiply     = cast { ( "*" | "/" | "mod" ) cast } .
cast         = unary [ "as" type ] .
unary        = [ "-" ] postfix .
postfix      = primary { index | field | deref } [ args ] .
args         = arg { "," arg } .
arg          = atom { index | field | deref } [ "&" ] .
index        = "[" expr [ ":" expr ] "]" .
field        = "." ident .
deref        = "^" .
primary      = atom | ifexpr .
atom         = "none" | "true" | "false"
             | integer | hex | fp | character | string
             | qualident
             | "(" expr ")" .
ifexpr       = "if" expr "then" exprorcmpd [ "else" exprorcmpd ] .
exprorcmpd   = expr | compound .

ident        = letter { letter | digit } .
letter       = "a" ... "z" | "_" .
digit        = "0" ... "9" .
hexdigit     = digit | "a" ... "f" .
integer      = digit { digit } .
hex          = "0" "x" hexdigit { hexdigit } .
fp           = digit { digit } "." digit { digit } .
character    = "'" any-char-except-quote "'" .
string       = '"' { any-char-except-quote } '"' .
comment      = ( "#" | "--" ) { any-char-except-newline } newline .

appendix b — predefined entities

b.1 built-in types

b.2 built-in faults

b.3 predefined procedures

the following procedures are in scope at the top level of every module.

fn new address (n s64)                  allocate n bytes, return address
fn dispose (p& address)                 free a block allocated by new
fn length s64 (x)                       element count of array or slice
fn and bool (b0, b1, ...)               short-circuit conjunction
fn or bool (b0, b1, ...)                short-circuit disjunction
fn not bool (b bool)                    negation
fn say (v0, v1, ...)                    format and print, newline at end
fn sayin (v0, v1, ...)                  format and print, no newline
fn ask (prompt, buf&, a0&, ...)         prompt, read one line, parse
fn cd string (path string) raises       change work directory
fn ls []string () raises                list entries of work directory
fn mkdir (path string) raises           create a directory
fn rmdir (path string) raises           remove an empty directory
fn rm (path string) raises              remove a file
fn cat (path string) raises             print file contents to output
fn cp (src string, dst string) raises   copy a file

the procedures cd, ls, mkdir, rmdir, rm, cat, and cp raise operation_failed when the underlying platform call fails.

b.4 standard modules

the following modules are part of the standard environment. each name is the name that may appear in an import list and that qualifies its exported members. the signatures below describe the public interface of each module — the types, constants, and functions visible to importers.

byte — single-byte memory access and conversion.

fn peek u8 (p address)
fn poke (p address, v u8)
fn format string (buf& string, v u8) raises
fn parse u8 (s string) raises

bytes — operations on byte buffers viewed as slices of u8. copy is a forward memcpy and is unsafe on overlapping ranges where the destination follows the source; move is a memmove that picks copy direction from the pointer ordering and is safe for any overlap.

fn clear (buf& string)
fn copy string (src string, dst& string)
fn move string (src string, dst& string)
fn equal bool (a string, b string)
fn compare s64 (a string, b string)
fn format string (buf& string, v string) raises
fn parse string (buf& string, src string) raises

bit — bitfield operations on b64.

fn shil b64 (b b64, n s64)
fn shir b64 (b b64, n s64)
fn incl b64 (b b64, lo s64, hi s64)
fn excl b64 (b b64, lo s64, hi s64)

signed — s64 arithmetic, conversion, and limits.

fn abs s64 (x s64)
fn min s64 (a s64, b s64)
fn max s64 (a s64, b s64)
fn round s64 (x f64)
fn format string (buf& string, v s64) raises
fn parse s64 (s string) raises

const lowest = -9223372036854775808
const highest = 9223372036854775807

float — ieee-754 arithmetic, conversion, and constants.

fn abs f64 (x f64)
fn sqrt f64 (x f64)
fn min f64 (a f64, b f64)
fn max f64 (a f64, b f64)
fn round f64 (x f64)
fn cbrt f64 (x f64)
fn trunc f64 (x f64)
fn frac f64 (x f64)
fn ln f64 (x f64)
fn exp f64 (x f64)
fn sin f64 (x f64)
fn cos f64 (x f64)
fn tan f64 (x f64)
fn arcsin f64 (x f64)
fn arccos f64 (x f64)
fn arctan f64 (x f64)
fn arctan2 f64 (y f64, x f64)
fn is_infinity bool (x f64)
fn is_nan bool (x f64)
fn is_finite bool (x f64)
fn decompose (x f64, mantissa& f64, exp& f64)
fn compose f64 (mantissa f64, exp f64)
fn format string (buf& string, v f64) raises
fn parse f64 (s string) raises

const pi = 3.141592653589793
const e = 2.718281828459045
const epsilon = 2.220446049250313e-16
const nan        # ieee-754 quiet nan
const infinity   # ieee-754 +infinity
const lowest     # most-negative finite f64
const highest    # most-positive finite f64

boolean — boolean conversion.

fn format string (buf& string, v bool) raises
fn parse bool (s string) raises

random — pseudo-random number generation. the state record encapsulates a single 64-bit splitmix64 seed.

type state record { seed s64 }

fn seed (s& state, n s64)
fn float f64 (s& state)
fn boolean bool (s& state)
fn range s64 (s& state, lo s64, hi s64) raises

clock — monotonic timing.

type tick s64

fn now tick ()
fn elapsed_ns s64 (t tick)
fn elapsed_us s64 (t tick)
fn elapsed_ms s64 (t tick)

time — wall-clock time and calendar decomposition. usecs counts microseconds since the unix epoch.

type usecs s64
type calendar record {
  year s64,
  month s64,
  day s64,
  hour s64,
  minute s64,
  second s64,
  weekday s64,
  yearday s64,
}

fn now usecs()
fn decompose (t usecs, c& calendar) raises
fn compose usecs (c calendar)

file — file input and output. a file id is an opaque handle returned by open, create, append, or replace and consumed by the other operations.

type id s64

fn open id (path string) raises          -- open_failed
fn create id (path string) raises        -- open_failed
fn append id (path string) raises        -- open_failed
fn replace id (path string) raises       -- open_failed
fn seek (h id, offset s64) raises        -- seek_failed
fn read string (h id, buf& string) raises -- read_failed
fn write string (h id, src string) raises -- write_failed
fn close (h id)
fn is_exist bool (path string) raises
fn size s64 (path string) raises         -- open_failed
fn remove (path string) raises           -- operation_failed
fn copy (src string, dst string) raises  -- operation_failed

directory — directory operations and path inspection.

fn make (path string) raises
fn remove (path string) raises
fn is_exist bool (path string) raises
fn is_file bool (path string) raises
fn is_dir bool (path string) raises

marshal — generic textual serialisation. both procedures are variadic over any almac value.

fn to string (buf& string, v0, v1, ...) raises
fn from (src string, x0&, x1&, ...) raises

screen — keyboard input and screen output on an alternate-screen buffer. poll_key returns a key value whose bit layout is the key code in bits 0..6, a control-key flag at bit 7, a key-down bit at bit 8, and the shift, ctrl, and alt modifier bits at bits 9, 10, and 11 respectively; the predicate functions decode those bits. The text cursor follows screen.at and printable writes; show_cursor and hide_cursor toggle its visibility.

type key s64

fn save bool ()
fn restore ()
fn clear ()
fn show_cursor ()
fn hide_cursor ()
fn at (col s64, row s64)
fn poke (col s64, row s64, ch u8)
fn poll_key key ()
fn key_code u8 (k key)
fn is_control_key bool (k key)
fn is_key_down bool (k key)
fn is_key_up bool (k key)
fn is_shift_pressed bool (k key)
fn is_ctrl_pressed bool (k key)
fn is_alt_pressed bool (k key)

const width  = 135                 -- text columns, s64
const height = 45                  -- text rows, s64
const key_arrow_up    = 1          -- control-key code, u8
const key_arrow_down  = 2
const key_arrow_left  = 3
const key_arrow_right = 4
const key_home        = 5
const key_end         = 6
const key_page_up     = 7
const key_backspace   = 8
const key_tab         = 9
const key_enter       = 10
const key_page_down   = 11
const key_esc         = 27

sound — audio playback.

fn play (samples []u8) raises
fn play_file (path string) raises

task — cooperative multitasking. yield hands control to the scheduler so other ready tasks can run; on hosts without a scheduler it is a no-op.

fn yield ()