esque
A statically typed, tensor-first systems language that compiles to native x86-64.
Why I built it
I built esque as a learning project. I studied language design, machine language, and compilers in college but never actually implemented one, and it took me a while to admit that my understanding was almost entirely theoretical. I knew the vocabulary, parsing, IRs, codegen, optimisations like SIMD and loop unrolling, but I had never built the thing the vocabulary describes. esque is me building it, one pass at a time, all the way down to hand-emitting x86-64 and AVX2 SIMD with no LLVM underneath.
It was also my way into purely functional, tensor-primitive languages, which I
had wanted to explore for a long time. I spent years in Python doing machine
learning and lost far too many hours to bugs that came down to the language
having no real notion of tensor shapes or controlled side effects. esque is the
opposite by design: shapes live in the type system, and side effects are tracked
through @io. My goal is to grow it into a language that genuinely suits machine
learning and data processing, so that one day it can carry real ML work.
What it is
esque is a statically typed, tensor-first systems language that compiles to
native x86-64 Linux binaries. Tensors are first-class values and their shapes
are part of the type, so a shape mismatch is a compile-time error rather than a
runtime surprise. The surface syntax covers @io and @kernel attributes,
scalar types like f64, i8, and u8, pattern matching, generics over tensor
shapes, and a set of large-N loop primitives (tabulate, scan, iterate,
iterate_until, each) alongside elementwise and reduction operators over
tensors. Those tensor operations are where esque earns its keep: elementwise
math and reductions on f32 tensors lower to packed AVX2 and SSE SIMD (256-bit
eight-wide, or 128-bit four-wide) with a scalar tail, so the obvious numeric
code compiles straight to vectorised machine code.
The toolchain
Three repos, all public under the esque-lang GitHub org:
| Repo | Role |
|---|---|
esquec | The compiler, written in Go. It runs the whole pipeline: parse, type-check, then lower through its own IRs down to native x86-64 Linux ELF. |
tree-sitter-esque | The grammar plus queries for highlights, locals, and symbol tags. |
esque-lsp | The language server: hover, completion, go-to-definition, and document symbols. |
The grammar and language server drop into any editor with tree-sitter and LSP support, so wiring esque into an editor is configuration rather than a bespoke plugin.
Example
Linear regression by gradient descent. The slope a is the only state the
training loop carries, and every quantity in the step is whole-tensor, no
per-element indexing:
# Learn a slope `a` such that y ≈ a·x for a small noisy dataset.
@io fn show(p: f32) -> f32 = print_f32(p)
@io fn main() -> i32 = {
let xs = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let ys = [2.10, 3.92, 6.05, 8.10, 9.95, 12.00, 14.05, 16.00];
let lr = 0.005;
# 32 gradient-descent steps; the state is the scalar slope `a`.
let a = iterate(32, 0.0, |a|
let av = tabulate(8, |_i| a) in # broadcast a to a length-8 tensor
let res = (av .* xs) .- ys in # residuals, elementwise
let g = +/(res .* xs) in # gradient: sum of res · x
a - lr * 0.25 * g
);
print_f32(a); # learned slope, converges to ~2.0
each(tabulate(8, |_i| a) .* xs, show); # predictions
0
}
esquec is the whole toolchain, a single static binary. Type-check it, compile
it to a native ELF, and run it:
esquec check linreg.esq # type-check only, no codegen
esquec build linreg.esq -o linreg # compile + link to a native x86-64 ELF
./linreg # prints the slope, then 8 predictions
esquec build linreg.esq --emit=asm # peek at the emitted machine code (incl. SIMD)
Status
Pre-1.0 and actively developed. The compiler, grammar, and language server are public at github.com/esque-lang, and the language spec is published at esque-lang.github.io/esquec.