What is unusual about Xag is not what it can do. It is what it refuses to decide on your behalf.
Two marks, and only two
'…' | a name |
*…* | a written value |
word | a type, a segment of a chain, or a function being called |
A quoted thing is a name wherever you meet it. It never has to be re-read as a value because of where it happens to sit — position is never consulted.
Every declaration marks the name it gives, including a function's and a struct's. What is being named is marked; what is being used is whatever it was declared as.
fn.int64 'sum-to' [int64 'n'] { give ['n']; }There is no third mark for text versus number, because the type already answers that: *1000* is a number under int64 and four characters under str.
There are only two marks and there will only ever be two.
There is a panel here that colours Xag as you type it, once the page has loaded.
Declarations are chains
What is unusual about a name lives in the chain that declares it. Every segment but the type has a default, and the default is always the least powerful thing — so a word appears only where there was a choice.
var.mut.many.int64 'xs'That one changes, holds several, and holds 64-bit whole numbers. Drop mut and it does not change. Drop many and it holds one.
Three engines and an oracle
There are three ways to run an Xag program, and they are kept apart on purpose.
- A test interpreter — built to be obviously correct rather than fast. It walks the IR as written and does nothing clever anywhere. It is the one to believe when the engines disagree.
- A fast interpreter — it turns the graph into flat code once and then runs it without looking anything up again. It shares nothing with the test interpreter but the runtime, on purpose: two engines that borrow from each other agree about what they borrowed, and a vote between them proves nothing.
- A native backend — compiled ahead of time through LLVM.
Two engines can say that something is wrong; three can say which.
A program generator writes random Xag programs, asks every engine what they say, and when they differ reports which one is out of step with the other two.
Decimal gets a check the oracle cannot give it. Three engines calling one runtime agree about everything inside that runtime, so a mistake in the arithmetic itself is one all three would make together. So the decimal results are checked against Python's decimal — libmpdec, written by someone else from the same IBM specification, and derived from nothing here. Four hundred thousand cases agree exactly, apart from raising to a power, which the specification itself allows to be out by one in the last place.
They are also asked of a real decimal floating-point unit. No machine here has one, so the test cross-compiles a program with no operating system under it, hands it to QEMU where a kernel would go, and reads the answers back over the console. Twenty thousand sums, differences, products and quotients agree exactly, cohorts included.