Ivy is an interpreter for an APL-like language. It is a plaything and a work in
progress.
Unlike APL, the input is ASCII and the results are exact (but see
the next paragraph). It uses exact rational arithmetic so it can
handle arbitrary precision. Values to be input may be integers (3,
-1), rationals (1/3, -45/67) or floating point values (1e3, -1.5
(representing 1000 and -3/2)).
Some functions such as sqrt are irrational. When ivy evaluates an
irrational function, the result is stored in a high-precision
floating-point number (default 256 bits of mantissa). Thus when
using irrational functions, the values have high precision but are
not exact.
Unlike in most other languages, operators always have the same
precedence and expressions are evaluated in right-associative order.
That is, unary operators apply to everything to the right, and
binary operators apply to the operand immediately to the left and
to everything to the right. Thus, 3*4+5 is 27 (it groups as 3*(4+5))
and iota 3+2 is 1 2 3 4 5 while 3+iota 2 is 4 5. A vector is a
single operand, so 1 2 3 + 3 + 3 4 5 is (1 2 3) + 3 + (3 4 5), or
7 9 11.
As a special but important case, note that 1/3, with no intervening
spaces, is a single rational number, not the expression 1 divided
by 3. This can affect precedence: 3/6*4 is 2 while 3 / 6*4 is 1/8
since the spacing turns the / into a division operator. Use parentheses
or spaces to disambiguate: 3/(6*4) or 3 /6*4.
Ivy has complex numbers, which are constructed using the unary or
binary j operator. As with rationals, the token 1j2 (the representation
of 1+2i) is a single token. The individual parts can be rational,
so 1/2j-3/2 is the complex number 0.5-1.5i and scans as a single
value.
Indexing uses [] notation: x[1], x[1; 2], and so on. Indexing by a
vector selects multiple elements: x[1 2] creates a new item from
x[1] and x[2]. An empty index slot is a shorthand for all the
elements along that dimension, so x[] is equivalent to x, and x[;3]
gives the third column of two-dimensional array x.
Only a subset of APL's functionality is implemented, but all numerical
operations are supported.
Semicolons separate multiple statements on a line. Variables are
alphanumeric and are assigned with the = operator. Assignment is
an expression.
After each successful expression evaluation, the result is stored
in the variable called _ (underscore) so it can be used in the next
expression.
The APL operators, adapted from
https://en.wikipedia.org/wiki/APL_syntax_and_symbols, and their
correspondence are listed here. The correspondence is incomplete
and inexact.
Unary operators
Binary operators
Operators and axis indicator
Type-converting operations
The constants e (base of natural logarithms) and pi (π) are pre-defined to high
precision, about 3000 decimal digits truncated according to the floating point
precision setting.
Strings are vectors of "chars", which are Unicode code points (not bytes).
Syntactically, string literals are very similar to those in Go, with back-quoted
raw strings and double-quoted interpreted strings. Unlike Go, single-quoted strings
are equivalent to double-quoted, a nod to APL syntax. A string with a single char
is just a singleton char value; all others are vectors. Thus “, "", and ” are
empty vectors, `a`, "a", and 'a' are equivalent representations of a single char,
and `ab`, `a` `b`, "ab", "a" "b", 'ab', and 'a' 'b' are equivalent representations
of a two-char vector.
Unlike in Go, a string in ivy comprises code points, not bytes; as such it can
contain only valid Unicode values. Thus in ivy "\x80" is illegal, although it is
a legal one-byte string in Go.
Strings can be printed. If a vector contains only chars, it is printed without
spaces between them.
Chars have restricted operations. Printing, comparison, indexing and so on are
legal but arithmetic is not, and chars cannot be converted automatically into other
singleton values (ints, floats, and so on). The unary operators char and code
enable transcoding between integer and char values.
Users can define unary and binary operators, which then behave just like
built-in operators. Both a unary and a binary operator may be defined for the
same name.
The syntax of a definition is the 'op' keyword, the operator and formal
arguments, an equals sign, and then the body. The names of the operator and its
arguments must be identifiers. For unary operators, write "op name arg"; for
binary write "op leftarg name rightarg". The final expression in the body is the
return value. Operators may have recursive definitions; see the paragraph
about conditional execution for an example.
The body may be a single line (possibly containing semicolons) on the same line
as the 'op', or it can be multiple lines. For a multiline entry, there is a
newline after the '=' and the definition ends at the first blank line (ignoring
spaces).
Conditional execution is done with the ":" binary conditional return operator,
which is valid only within the code for a user-defined operator. The left
operand must be a scalar. If it is non-zero, the right operand is returned as
the value of the function. Otherwise, execution continues normally. The ":"
operator has a lower precedence than any other operator; in effect it breaks
the line into two separate expressions.
Example: average of a vector (unary):
Example: n largest entries in a vector (binary):
Example: multiline operator definition (binary):
Example: primes less than N (unary):
Example: greatest common divisor (binary):
On mobile platforms only, due to I/O restrictions, user-defined operators
must be presented on a single line. Use semicolons to separate expressions:
To declare an operator but not define it, omit the equals sign and what follows.
Within a user-defined operator body, identifiers are local to the invocation
if they are assigned before being read, and global if read before being written.
To write to a global without reading it first, insert an unused read.
To remove the definition of a unary or binary user-defined operator,
Ivy accepts a number of special commands, introduced by a right paren
at the beginning of the line. Most report the current value if a new value
is not specified. For these commands, numbers are always read and printed
base 10 and must be non-negative on input.