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.
Questions
I've seen languages with only positives. No negatives.
They most likely have flaws, but weren't written down.
To be honest too, we didn't write down all flaws of Xag, but we did write down the most major. At least we wrote them down. We don't want you to regret our language.
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.