AKSO Script is a very simple untyped functional sandboxed programming language meant for following applications:
- Calculations and mild interactivity in conference registration forms
- Calculations and mild interactivity in survey forms
- JSON filter template values
High-level design features:
- almost all runtime errors are statically identifiable
- case where this does not apply: function arity
- editable with a GUI
- small runtime
- no mutability, almost completely deterministic
- sources of nondeterminism are time-related functions in the standard library and form variables
Scripts are composed of “definitions” at the top level. Each definition can be thought of as a function taking zero or more arguments, though they will often act like variables.
Scripts are represented as a Javascript object containing all definitions.
{
_a: { t: 'n', v: 5 },
_c: { t: 'c', f: '+', a: ['_a', '@input-key-name'] },
_e: { t: 'm', v: [[1, 2, 3], [4, 5, 6]] },
something: { t: 'c', f: 'index', a: ['_e', '@other-input-name', '_a'] },
}
Identifiers may be prefixed:
@
to indicate that this references the value of a form field. This may benull
. Hence definitions may not start with this letter._
to indicate that this is a variable that was not explicitly created by the user and may be manipulated arbitrarily by the editor. These will usually have non-human-readable names. Additionally, these cannot be referenced from child scopes (e.g. function bodies).
The =
identifier is special and indicates a function’s return value.
Additional key v
, contains the value this definition will evaluate to (except u
, which does not have this key).
n
is always real and neverNaN
orInfinity
.m
values are n-dimensional arrays.
These definitions associate the definition name with the value, i.e. define a constant. These can be thought of as zero-argument functions.
Constructs a list. Additional keys:
v
: list of definition names to be used as list contents
This creates a possibly heterogenous list consisting of the contents of the referenced definitions.
Calls a definition. Additional keys:
f
: definition namea
: optional list of definition names to be used as function arguments
These can be thought of as function calls. Calling a definition that is not explicitly a function simply copies its value. Calling a function applies each argument to the result in sequence.
Calling a function or referencing a definition with the incorrect number of arguments is an error (if the target definition is not a function, the number of arguments should always be zero).
Defines a function. Additional keys:
p
: list of parameter namesb
: function body. a nested definitions object, with a=
key.
Functions capture the scope in which they were defined. Definitions may be shadowed by the body or parameters. The priority when resolving names is as follows:
- Parameters
- Function body
- Parent scopes wrt. definition (starting with the closest)
Matches a list of conditions (like a switch). Additional keys:
m
: an array of objects with following keys:c
: optional definition name (condition)v
: definition name (value)
Each condition/value pair in m
is traversed sequentially, and the value of the first condition that is strictly equal to the boolean value true
will be returned. If the condition is not given, it will always match. If no condition matches, the result is null
.
Short notation in parentheses.
null
(u
): created byu
bool
(b
): created byb
number
(n
): created byn
string
(s
): created bys
array<T>
(T[]
): created bym
andl
func(...)
(f()
): created byf
. Arity must be fixed. May have type variables (denoted here with$
) on the left-hand side to match any type. Example with arity 2:f((n, n) -> n, ($a, $b) -> u)
(a|b)
: union of typea
andb
(implicitly created by runtime branching)
There are several more runtime types than types available in syntax:
timestamp
: the timestamp type stores a single point in real time, created using stdlib functions
See AKSO Script reference implementation.
These functions are available in the global scope. All functions (except comparison and logic operators) will return null if an input is null.
All operations in this section will return null if an argument is not a number.
Let A = f((n, n) -> n, ($a, $b) -> u)
and B = f((n) -> n, ($a) -> u)
+
/-
/*
//
/^
:A
: basic arithmetic- division by 0 is 0
- 0^0 is 1
mod a b
:A
: modulo. Will flip the monoid if b is negative. Will return 0 if b is 0.floor a
:B
: rounds to the closest integer in the -Infinity directionceil a
:B
: rounds to the closest integer in the +Infinity directionround a
:B
: rounds to the closest integertrunc a
:B
: rounds to the closest integer towards 0sign a
:B
: returns the sign, either -1, 0, or 1abs a
:B
: returns the magnitude of the argument
==
/!=
/>
/<
/>=
/<=
:f(($a, $b) -> b)
: comparison operators- using ordered comparison on types that are not a number or string always evaluates to false
- using ordered comparison on two different types always evaluates to false
- comparing two different types always evaluates to false
and
/or
/not
/xor
:f(($a, $b) -> b)
: boolean logic- if one argument is not a boolean, always evaluates to false
Some functions will also work with types that are not strings or arrays (such as map f a
which will simply return length a
times f
if f
is not a function).
++ a b
: concatenates a and b- if either argument is an array, then strings are treated as an array of code points, and other data types are wrapped in a single-element array.
- if neither argument is an array, then all arguments will be stringified
map f a
:f((f($a) -> $b, $a[]) -> $b[], (f($a) -> $b, $a) -> $b, ($a, $b[]) -> $a[], ($a, $b) -> $a)
: maps f over aflat_map f a
: maps f over a and concatenates the resultsfold f r a
: left-folds a with f, and uses r as the initial valuefold1 f a
: like fold, but uses first item as initial value- will return null if a is empty or not a string or array
filter f a
: filters a with f- will return null if a is not a string or array
index a b
: returns the item at index b inside a- will return null if the index is out of bounds
- will return null if b is not an integer
find_index a b
: returns the leftmost index of item b inside a- will return null if no such item is found
- will return null if a is not a string or array
length a
: returns length of a- will return null if a is not a string or array
contains a b
: returns if a contains b- will return false if a is not a string or array
- will return false if a is a string and b is not
head a b
: splits a at index b and returns the first parttail a b
: splits a at index b and returns the second part
Convenience functions:
sum a
: returns the sum of amin a
: returns the minimum value of amax a
: returns the maximum value of aavg a
: returns the arithmetic mean of amed a
: returns the median of asort a
: returns a sorted version of a, or null
date_sub t a b
: returns the signed difference between a and b interpreted as RFC3339 date strings, or null. t determines the type of difference; may be one of 'days', 'weeks', 'months', 'years'date_add t a b
: interprets a as an RFC3339 date string, or returns null. Adds b of t, and returns the resulting date string. t may be one of 'days', 'weeks', 'months', 'years'date_today
: returns the current datedate_fmt a
: returns the formatted version of a interpreted as a RFC3339 date string, or nulldate_get t a
: returns field t (y
,M
, ord
) of date string a- out-of-bounds numbers should either return what it says in the string or overflow correctly
date_set t a b
: returns date string a with field t (y
,M
, ord
) set to b- this should handle out-of-bounds numbers correctly, e.g. setting date to -1 should underflow into the previous month
ts_now
:timestamp
: current timestamptz_utc
:n
: utc time zone offset (always zero)tz_local
:n
: local time zone offset (in minutes)
ts_from_unix
:f((n) -> timestamp, ($a) -> u)
: creates a timestamp from a unix timestamp (in seconds)ts_to_unix
:f((timestamp) -> n, ($a) -> u)
: returns the unix timestamp (in seconds) for the given timestampts_from_date a tz h m s
:f((s, s|n, n, n, n) -> (timestamp|u), ($a, $b, $c, $d, $e) -> u)
returns a timestamp with the given date a, time zone offset tz (IANA name, or in minutes), hours h, minutes m, and seconds sts_to_date a tz
:f((timestamp, s|n) -> s, ($a, $b) -> u)
returns the date of timestamp a for the given time zone offset tz (in minutes)ts_parse a
:f((s) -> (timestamp|u), ($a) -> u)
attempts to parse a timestamp from the given string. Must support RFC3339.ts_to_string a
:f((timestamp) -> s, ($a) -> u)
formats the timestamp as RFC3339ts_fmt a
:f((timestamp) -> s, ($a) -> u)
formats the timestamp in a human-readable format
ts_add t a b
:f((s, timestamp, n) -> (timestamp|u), ($a, $b, $c) -> u)
: adds b of t to a. t may be one of:s
: secondsm
: minutesh
: hoursd
: daysw
: weeksM
: monthsy
: years
ts_sub t a b
:f((s, timestamp, timestamp) -> (n|u), ($a, $b, $c) -> u)
: returns the signed difference between a and b in the given unitt
(seets_add
for possible units)ts_get t tz a
:f((s, s|n, timestamp) -> (n|u), ($a, $b, $c) -> u)
: returns field t (seets_add
; exceptw
) for timestamp a in time zone offset tz (IANA name, or in minutes)ts_set t tz a b
:f((s, s|n, timestamp, n) -> (timestamp|u), ($a, $b, $c, $d) -> u)
: returns a with field t (seets_add
; exceptw
) set to b in time zone offset tz (IANA name, or in minutes)
currency_fmt a b
: returns b (interpreted as smallest currency unit, e.g. cents) formatted in currency a, where a is a string like 'USD'country_fmt a
: if a is an ISO 639-1 country code (case insensitive), returns the country name. Otherwise nullphone_fmt a
: if a is a phone number string, then returns a formatted version. Otherwise nullid a
: returns a
Script evaluation may panic in which case an error dialog should be shown to the user. These errors can be identified via simple static analysis.
Causes:
- referencing an unknown declaration
- naming a definition with a leading
@
- unknown types of definitions
- calling with an incorrect number of arguments