ApasScript.

The Language

How ApasScript is written: lines and names, operators, conditions, loops, functions and errors.

Lines, names and comments

A script is a list of lines, one statement per line. name = expression works a value out and keeps it under that name; a line that is just an expression shows its value.

A line starting with # is prose -- it is shown as text, and in a PDF it becomes a paragraph. Everything after // on a code line is a comment and is ignored.

Operators

Arithmetic uses the usual symbols, and the order is the one you learnt in class: brackets, then powers, then multiplication and division, then addition and subtraction. n! is the factorial and binds tightest of all, % is the remainder (it takes the sign of the divisor, so 17 % -5 is -3; remainder(a, b) is never negative), and ^ groups from the right, so 2^3^2 is 2^9.

Comparisons and and/or/not come last, so x + 1 > 2*y compares the two sums. && and || are the same as and and or.

OperatorMeaningExample
( )grouping(2 + 3)*4 is 20
n!factorial5! is 120
^power (from the right)2^3^2 is 512
-xnegation-2^2 is -4
* / %multiply, divide, remainder7 % 3 is 1
+ -add, subtract2 + 3*4 is 14
== != < <= > >=compare (exactly)0 < x <= 1
notnotnot x > 2
andbothx > 0 and y > 0
oreitherx < 0 or x > 1

Comparisons, true and false

A comparison is true or false, and it is exact: sqrt(2) < 1.4143 is decided without rounding anything. Chains read as in maths -- 0 < x <= 1 means 0 < x and x <= 1.

Inside arithmetic a truth value counts as 1 or 0, which makes piecewise formulas easy: x*(x > 0) is x when x is positive and 0 otherwise.

if, else if, else

if (condition) { runs the lines up to its } only when the condition is true. } else if (condition) { tries another condition, and } else { catches everything left. Each brace goes at the end of its line.

Loops

for (i in 1..10) { counts from 1 to 10, both ends included. for (v in xs) { visits each item of an array or list, each character of text, or each key of a map. while (condition) { repeats as long as the condition holds.

break leaves the loop at once and continue skips to the next round. Wrap a loop in quiet { } to keep its working out of the output -- only print lines still show.

Functions

fn name(a, b) { defines a function and return gives its answer; a function without return (a drawing helper) gives no value, and y = f() on one is an error. A function can call itself, and it can be used anywhere a value can: inside a formula, in print, or handed to a solver or integrator by name (nroot(f, x, 0, 2) means the root of f(x)).

A function that is one formula can be written on one line, as in a textbook: f(x) = 2*x - 5. In a lesson's <steps> a line f(7) then shows the rule with 7 put in and the result.

Functions are values too -- a function can make and return another one, which remembers the values it was made with.

A function need not be named: fn(x) x^2 is a function value, written where it is used -- bound to a name (sq = fn(x) x^2, then sq(4)), handed to nroot, nintegrate, animate, canvas or attempt, or returned from another function. A longer body goes in braces, on one line (fn(a, b) { return a + b }) or on several lines ending with }.

use "mechanics/incline" brings in the functions and names of a module, the file mechanics/incline.as in the script library, as they are; use "mechanics/incline" as inc brings them in as inc.normalForce(...). A module runs once, quietly, in a space of its own; a name starting with __ stays inside it.

Records and methods

record Point { x, y } declares a shape. Point(3, 4) builds one, shown as Point{x: 3, y: 4}; it is a map, so p.x, keys(p) and for (k in p) work on it. fn Point.length(p) { ... } is a method: p.length() calls it with p as its first argument. Giving the wrong number of fields, a key the record has not, or a method it has not is an error that says so.

Dot syntax

x.name is another way to write name(x), and x.name(a) is name(x, a), so steps can be chained left to right. It works for built-in functions and your own alike. On a point, vector, map or result, .x, .re, .Na or .status reads that part.

When something goes wrong

A line that cannot be worked out shows Error: and a reason, and the script carries on with the next line -- one mistake never hides the rest of the output. A line that cannot even be read, like an if with a missing bracket, says what the line should look like, and its block is skipped.

Every error knows where it happened: the playground shows the line and column with the message, and the command line prints file:line:col before it.

assert(x > 0, "x must be positive") and error("no data") raise your own errors: they stop the script, up through every block and function, and show the message. attempt(f, args...) calls a function and catches whatever goes wrong: it gives {ok, value, message}, so the script can test ok and carry on.