The Axis Language

Axis is the expression-oriented, strongly-typed scripting language used by the Atlas software. This page is the complete language reference, converted from atlas.help.

1. Introduction

The atlas program provides access to the Atlas library through an expression-oriented interface. Rather than entering a dialogue to set parameters (as in the older Fokko program), all parameters are values that can be computed, stored in variables, and passed to functions.

The language is called Axis. It is a general-purpose programming language developed within the Atlas project. The Atlas-specific types and functions are built on top of it.

Scripts are loaded with:

atlas> <script.at

This loads the file if not already loaded. Scripts in the standard atlas-scripts directory can be referenced by name alone.

2. Comparison with Fokko

The older Fokko program enters a dialogue whenever a parameter must be set. In atlas, such parameters are constructed as expressions:

atlas> set ic = inner_class("B5.A3.T1", [[1,0,0]/2,[0,1,1]/2], "scc")
Identifier ic: InnerClass
atlas> block_sizes(ic)
Value:
|  0,  0,   0,  0,  0,   0,  0,    1 |
|  3, 75, 390, 21,525,2730,4026,28182 |

The variable ic is then reusable. One could equally pass the inner class directly without storing it. This flexibility — multiple reusable variables, programming constructs, scriptability — is the fundamental difference from Fokko.

3. Design Principles

Axis is expression-based: text entered designates values to be computed. The design is close to functional programming:

Axis is strictly typed: expressions are type-checked before evaluation. Most errors are caught early with specific messages. Types are almost always inferred — you only need to specify types for function arguments.

Names are introduced with set (permanent) or let ... in (local). Assignment uses :=; the type of a variable cannot change after introduction.

Keywords

quit set let in begin end if then else elif fi and or not next do dont from downto while for od case esac rec_fun true false die break return set_type whattype showall forget

Reserved type names

void int rat string bool vec mat ratvec LieType RootDatum WeylElt InnerClass RealForm CartanClass KGBElt Block Split KType KTypePol Param ParamPol

4. Types

Primitive types

TypeRepresents
boolTruth values: true or false
intUnbounded-precision integers
ratRational numbers (reduced fraction of two unbounded integers)
stringCharacter string (double-quoted; cannot span lines)
vecVector of 32-bit integers
matMatrix of 32-bit integers (column-major)
ratvecRational vector: 64-bit integer numerator vector, integer denominator
LieTypeLie type such as D4.A2.T1
RootDatumRoot datum specifying a connected complex reductive group
WeylEltElement of the Weyl group associated to a root datum
InnerClassInner class of real forms (root datum with involution)
RealFormReal form within an inner class
CartanClassClass of Cartan subgroups within an inner class
KGBEltElement of \(K \backslash G/B\) for some real form
BlockBlock for a pair of real forms at dual inner classes
SplitSplit integer \(a + b \cdot s\) where \(s^2 = 1\)
KTypeStandard \(K\)-representation or its irreducible quotient
KTypePolSplit-linear combination of final \(K\)-types
ParamStandard module or its irreducible quotient
ParamPolVirtual module: Param values with Split coefficients

Type errors are caught before evaluation:

atlas> block_sizes(GL(5))
Type error: found RootDatum while InnerClass was needed.

To inspect overloads of a name:

atlas> whattype Lie_type ?
Overloaded instances of Lie_type
  string->LieType
  RootDatum->LieType
  (int,int)->LieType

Composite types

NotationMeaning
[t]Array of elements of type t
(t0,t1)2-tuple with components of types t0 and t1
(t0|t1)Union: value is either of type t0 or of type t1
void0-tuple (no useful value; result type of print functions)
(t0->t1)Function from t0 to t1
(t0,t1->t2)Function taking (t0,t1) returning t2
(->t0)Function with no arguments returning t0

Function call syntax

Three equivalent syntaxes:

Mixed forms also work: f(x,y).g and x.f(y) (the latter calls f(x), which must return a function, then calls that with y).

Arrays, selection, and slicing

Array displays: [1,-4,18]. Individual element: v[i] (0-indexed). Counting from end: v~[0] (last), v~[1] (second-to-last), etc.

Slices use a colon. An index specifies a boundary between elements, so v[a:b] contains elements at positions a through b-1:

v[:3]        { first 3 elements }
v[3:]        { all except first 3 }
v[2~:]       { last 2 elements }
v[:2~]       { all except last 2 }
v~[:]        { all elements reversed }
"reverse!"~[:] { string reversal: "!esrever" }

Matrix entries: A[i,j] (row, column). Matrix column slicing works like vector slicing. For complex matrix operations (transposition, block extraction, etc.) see swiss_matrix_knife in the built-in reference.

ParamPol acts as an associative array: P[q] returns the Split coefficient of the Param value q in P.

Tuples and pattern matching

Tuple displays use parentheses: (a,b,c). Unlike arrays, components may have different types.

Pattern matching decomposes tuples when binding names:

atlas> set (q,r) = 22 \% 7   { Euclidean division: q=quotient, r=remainder }
Identifiers q: int, r: int
atlas> q
Value: 3

Skip unwanted components with a blank: let (x,) = z in x takes only the first component of a pair. Pattern matching is available in set, let, and function parameter lists.

5. Implicit Conversions

When types do not match exactly, Axis inserts implicit conversions automatically — but only when the target type is unambiguous from context.

intrat (denominator 1)
vec[int] (32-bit pack/unpack)
ratvec[rat]
vecratvec (denominator 1)
mat[vec] (columns)
mat[[int]] (columns)
stringLieType
intSplit (as (k,0))
InnerClassRootDatum (inheritance)
RealFormInnerClass (inheritance)
RealFormRootDatum (inheritance)
ParamParamPol (convert to final params)
ParamKType (\(\nu\) component forgotten)
KTypeKTypePol

A cast forces a specific type by prefixing with type::

atlas> ratvec: [1, 1/2, 1/3, 1/4]
Value: [ 12, 6, 4, 3 ]/12
atlas> mat: [[1,2,4],[3,5,7]]
Value:
| 1, 3 |
| 2, 5 |
| 4, 7 |

Specify a matrix by rows with |:

atlas> [ 1,2,4 | 3,5,7 | 0,-1,6 ]

The empty list [] always needs a cast: [ratvec]:[].

6. Defining and Modifying Identifiers

Global identifiers: set name = expr. Local identifiers: let name = expr in body. Assignment: name := expr. Type cannot change.

atlas> set initial (int n) = let i=0 in while i<n do i:=i+1 od
Defined initial: (int->[int])

Compound assignment: x +:= y means x := x+y, and similarly for any operator. Component assignments: v[i]:=x, m[i,j]:=x.

Prefix a name with ! to forbid assignment (constant binding):

set !s = Split:(0,1)
Value semantics

Two names never share an object. Assigning to one name never affects another. Function calls are strictly by value. let vi=v[i] in vi:=3 does not modify v[i].

Type definitions

New type abbreviations with field names use set_type:

atlas> set_type Gaussian_int = (int re, int im)

This defines re and im as projector functions and enables field assignment z.re:=4. For union types, tags become injector functions:

atlas> set_type Maybe<T> = (void none | T some)

General type definitions (in square brackets) allow mutually recursive types:

atlas> set_type [ tree=(void nil|tree_node node),
               tree_node=(tree left,int key,tree right) ]

Remove a type definition with forget tree.

7. Defining Functions and Operators

atlas> set f (LieType lt) = void:
: > let rd=simply_connected(lt)
L > then ic=inner_class(rd,"s")
L > then rf=quasisplit_form(ic), drf=dual_quasisplit_form(ic)
L > in print_block(block(rf,drf))
Defined f: (LieType->)

The void: cast clarifies intent but is not required — Axis infers return types. The multi-line prompt changes: : > after =, L > inside let. New operators are defined identically.

Overloading

All function names are stored in an overload table. Multiple definitions with different argument types coexist; Axis selects among them by argument type at each call site:

atlas> set f(int n) = n+1
Added definition [2] of f: (int->int)

If argument types exactly match an existing definition, it is replaced:

Redefined inner_class: (RootDatum,string->InnerClass)

Overloading is refused when two argument types would create ambiguous calls. Use forget name@type to remove a specific overload before adding the new one.

Anonymous functions

Write a parenthesised parameter list, optional return type, colon, and body:

atlas> g( (int n): n+1 )
Value: [ 1, 3, 6 ]

Resolving overloaded symbols

To use a function name as a first-class value, append @type:

atlas> g(f@int)      { pass the (int->int) overload of f as a value }
atlas> +@(int,int)   { integer addition as a value }

To store a function value in a global variable (separate from overloads), use name : function:

atlas> f : (int n): 2*n+3
Identifier f: (int->int)

An identifier declared with name : type (no value yet) introduces an uninitialised variable of that type — useful when mutually dependent functions need to reference each other before both are defined.

8. Control Structures

Sequenced expressions

Semicolons evaluate left-to-right and return the last value. next returns the first value:

let i=0 in i next i:=3    { returns 0 }
a:=1; b:=2; a+b           { returns 3 }

begin ... end is equivalent to parentheses for grouping long sequences.

Conditional expressions

if condition then A else B fi
if condition then A elif ... then ... else B fi
if condition else B then A fi   { same as first form — puts minor case first }

The else branch may be omitted (defaults to void). Logical connectives and, or, not short-circuit and cannot be overloaded or redefined.

Integer case expressions

case I
in E_0, E_1, ..., E_{n-1}
else F          { optional: selected when I < 0 or I >= n }
esac

Without else, out-of-range values wrap modulo \(n\). With both then and else, negative values select then and too-large values select else. Useful idiom for sign testing:

case k then F_neg in F_zero else F_pos esac

Union case (discrimination)

Process a union-type value by handling each variant. Tags come from a set_type definition:

case solve(system)
| none:              error("no solution")
| solution(v,d,M):  v/d         { particular rational solution }
esac

An else branch handles any unspecified variants. Unlike integer case, branches are not separate function bodies, so break and return can cross variant boundaries.

While loops

Loops collect body values into a list; if the result is unused it is not constructed (no overhead):

let i=0 in while i<n do i:=i+1 od      { [1,2,...,n] }
let i=0 in while i<n do i next i:=i+1 od  { [0,1,...,n-1] }

Variables from let in the condition stay in scope in the body. Euclid's algorithm elegantly:

while let r=a%b in r!=0 do a:=b; b:=r od; b

A condition true immediately before do may be omitted. Use dont for a branch that always terminates without contributing to the list (equivalent to false do die).

Counted for loops

for i : n do i od             { [0,1,...,n-1] }
for i : n from 1 do i od      { [1,2,...,n] }
for i : n ~ do i od           { [n-1,...,1,0]  (~ reverses traversal) }
for :n do 1 od                { n ones (index omitted) }

~ before do reverses the traversal; ~ before od reverses assembly of results. The two can be combined.

Iteration over composite values

for e in V do ... od           { iterate over elements of array/vec/string/mat }
for e@i in V do ... od         { with 0-based index i available }
for c@p in pol do ... od       { over nonzero terms of a ParamPol }

For a ParamPol, c is the Split coefficient and p is the Param value at each nonzero term. For matrices, iteration is over columns.

Concatenating nested loops

Apply .## after the outer od to flatten nested results into a single list:

for i:4 do for j:i+1 do (i,j) od od.##
{ [(0,0),(1,0),(1,1),(2,0),(2,1),(2,2),(3,0),(3,1),(3,2),(3,3)] }

Syntactic sugar: omit the inner do, or use if ... do ... fi as a Boolean-counted loop:

for i:4 for j:i+1 do (i,j) od od        { same as above }
for d in list if d%2=1 do d fi od       { keep only odd elements }
for i:9 for j:10 if gcd(i,j)=1 do (i,j) fi od od  { coprime pairs }

Forced exit: break and return

break exits the innermost loop without including the current iteration in the result. Repeat for outer loops: break break breaks from two levels.

return expr exits the innermost user-defined function, returning expr.

die and error(message) terminate evaluation with a runtime error. Use error to provide a diagnostic message; die is for branches that should be unreachable.

9. Recursion

Use rec_fun before the function name. The return type must be specified explicitly:

atlas> set rec_fun fac (int n) = int:
: > if n<=0 then 1 else n*fac(n-1) fi
Defined fac: (int->int)

Better practice: keep the recursive function local, with the global function handling preconditions:

set fac (int n) =
  if n<0 then error("factorial of negative number ",n) fi;
  let rec_fun f(int n) = int: if n<=0 then 1 else n*f(n-1) fi
  in f(n)

For maximum efficiency (build the recursive function only once, not on every call):

set fac =
  let rec_fun f(int n) = int: if n<=0 then 1 else n*f(n-1) fi
  in (int n): if n<0 then error("negative") else f(n) fi

10. Splitting Commands Across Lines

A command continues to the next line automatically when:

A backslash \ as the last non-space character forces continuation by joining the next line (even within a string or token).

11. Output, Input, and Redirection

Redirect output to a file for the duration of one command:

atlas> > output.txt block_sizes(ic)        { create/overwrite }
atlas> >>output.txt print_KL_list(rf,drf)  { append }

Read commands from a file:

atlas> <filename.at     { load; skips if already loaded }
atlas> <<filename.at    { load unconditionally (useful after editing) }

Files can include other files. Any error closes all open input files and returns control to the terminal. Atlas tracks loaded files and suppresses re-loading (use << to force a reload after editing).

The file search path is given by the input_path variable ([string]), settable from the command line with --path=dir.

12. Miscellaneous Commands

atlas> set verbose           { enable extra diagnostics during type analysis }
atlas> set quiet             { disable extra diagnostics }
atlas> showall               { print all defined identifiers and functions }
atlas> whattype expr         { type-check expr, print type (no evaluation) }
atlas> whattype + ?          { print all overloads of + }
atlas> forget name           { remove a global identifier }
atlas> forget f@(int,int)    { remove a specific overload }
atlas> quit                  { exit the interpreter }

13. Syntax Summary

Every construct in Axis is an expression — there is no statement/expression distinction. Expressions yield a value (possibly void) and may have side effects.

Operator precedence (weakest to strongest)

orLogical or (short-circuit, not overloadable)
andLogical and (short-circuit, not overloadable)
notLogical not (prefix only)
= != < > <= >=Relational operators
+ -Addition, subtraction
* / \ % \%Multiplication, division, integer quotient, remainder
^Exponentiation (right-associative)
# ~Length / append; complement / reverse

Association is left-to-right except ^ (right-to-left). Some parsing examples:

-x+y      { (-x)+y }
-a%p      { -(a%p) }
a+-b^c    { a+((-b)^c) }
a^-1      { a^(-1) }

Closed expressions (never need extra parentheses)

if * then * fi                             { abbreviates: if * then * else () fi }
if * then * else * fi
if * then * elif * then ... else * fi
if * else * then * fi                      { reversed branch order }
case * in *,*,... esac                     { integer case }
case * in *,*,... else * esac              { with default }
case * | tag(p): * | ... esac             { union discrimination }
case * | (p).tag: * | ... | else * esac   { with default branch }
while * do * od
for pattern in * do * od
for pattern@ident in * do * od
for ident : * do * od
for ident : * from * do * od
if * do * fi                               { Boolean-counted loop }
( * )                                      { parenthesised expression }
begin * end                                { same as ( * ) }
( *,*,... )                                { tuple display }
[ *,... ]                                  { row (array) display }
[ *,... | *,... | ... ]                   { matrix by rows }
← Documentation Overview Built-in Functions →