A programming language built around high performance, helpful error messages, and Rust-style memory management. There is no int on its own, because there is no size to assume. Programs are compiled ahead of time, either to native code or to a form run by an AOT interpreter.
Reading a program
Items sit next to each other and are used in order. Nothing is concatenated into a third thing, so there is no + for text.
# Chains, defaults, and a loop.
const.int64 'LIMIT' = [*10*];
fn.int64 'sum-to' [int64 'n'] {
var.mut.int64 'total' = [*0*];
loop.range.int64 'i' = [*1*, 'n'] {
set 'total' = ['total' + 'i'];
}
give ['total'];
}
START {
var.int64 's' = [sum-to['LIMIT']];
print.stdout[str:*sum to * 'LIMIT' str:* = * 's' \n];
}
xagc run counting.xagsum to 10 = 55What a name owns, and what it lends
Ownership is Rust's, checked when the program is compiled, with no garbage collector under it. A transfer is spelled at the call site, so a reader never has to work out from a function's signature that a value has left.
# Lending for reading, lending for writing, and handing over for good.
fn.int64 'size' [ref.str 'text'] {
give [count['text']];
}
fn.nothing 'shout' [refmut.str 'text'] {
set 'text' = ['text' *!*];
}
fn.nothing 'keep' [str 'text'] {
print.stdout[str:*kept: * 'text' \n];
}
# Two things are lent and one comes back, so the loan is named.
fn.ref.'life'.str 'longer' [ref.'life'.str 'a', ref.'life'.str 'b'] {
if count['a'] >== count['b'] {
give ['a'];
} else {
give ['b'];
}
}
START {
var.mut.str 'greeting' = [*hello*];
var.str 'reply' = [*hi*];
# Lent for reading. 'greeting' still holds what it held.
print.stdout[str:*size: * size[ref 'greeting'] \n];
# Lent for writing, which is why the chain says `mut`.
shout[refmut 'greeting'];
print.stdout[str:*after: * 'greeting' \n];
# The answer is borrowed from whichever of the two was longer.
var.ref.str 'winner' = [longer[ref 'greeting', ref 'reply']];
print.stdout[str:*longer: * 'winner' \n];
# Handed over for good. 'spare' holds nothing after this line.
var.str 'spare' = [*spare*];
keep[move 'spare'];
}
xagc run borrowing.xagsize: 5
after: hello!
longer: hello!
kept: spareWhen it has something to say
Take the program above, hand a value over for good, and then ask for it again. This is what comes back — not a paraphrase of it:
Hello, I think there may be thing(s) wrong with your code. I'm sorry, if I'm wrong.
file: moved.xag, line: 11, column: 18 (moved.xag:11:18)
`'greeting'` was moved, and holds nothing now.
11 | print.stdout['greeting' \n];
| ^^^^^^^^^^ used here
10 | keep[move 'greeting'];
| ^^^^^^^^^^^^^^^ but it was handed over here
Error code: E0403
Rule(s) broken: a name holds its value until it is moved, and then holds nothing
Tip(s): what was moved is somewhere else now, and there is only ever one of it.
1 error.
If I am wrong about any of that, please tell me: https://github.com/Artificial-IntelligenceAI/Xag-lang/issues
A diagnostic that points at the wrong thing, or names a rule the program did not break, is a bug of the same kind as miscompiling — the compiler is telling the reader something untrue either way.
Building it
The compiler is written in C++20 against LLVM's native C++ API. It needs LLVM 23 or newer, CMake and Ninja.
git clone https://github.com/Artificial-IntelligenceAI/Xag-lang.git
cd Xag-lang
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja -C build
./build/xagc run examples/counting.xagxagc run runs a program on the test interpreter, xagc fast on the fast one, and xagc build compiles it ahead of time and writes an executable beside it. xagc check checks a program without running it.
There is no install yet. Building from source is the way to run it.