make engine # fetch the verified engine release this tree pins
make
The engine is a separate project, acquired rather than carried. make engine
downloads one built for your platform and checks it against the digest
tools/engine/engine.pin.xon records; make
puts the x-bin binary where the wrapper and the test runners expect it.
Nothing is compiled, and no C toolchain is required.
From nothing, in one command:
curl -fsSL https://raw.githubusercontent.com/jonruttan/x-lang/main/bootstrap.sh | sh
To build an engine from source instead, or to point this tree at one you already have, see Build in the README.
The simplest way to start:
sh x.sh
This loads the x-lang standard library and drops into a REPL:
> (+ 1 2)
3
> (def greeting "hello")
> greeting
"hello"The prompt is > . Results are printed after each expression. Nil results print nothing.
Leave the session with (quit) or ctrl-d. Ctrl-c cancels a half-typed
multi-line form and returns a fresh prompt (at an empty prompt it ends the
session). (help) shows the documentation
index — see Exploration below.
The arrow keys work, and so do the readline chords: ctrl-a and ctrl-e for the ends of the line, ctrl-w and ctrl-k to kill, ctrl-y to put it back, Up and Down for history that outlives the session, Tab to complete any documented name. What you type is coloured as you type it. It is built in — there is nothing to install and nothing to wrap the session in. docs/repl.md is the reference.
For the full-stack dialect (xenon) with the numeric tower, the compiler, and POSIX:
sh x.sh -l xe
The REPL prints every result automatically. A file does not — it runs
top to bottom and produces output only where you ask for it. Put this in
hello.x:
; hello.x -- Hello world
(display "Hello from x-lang!")
(newline)Run it:
sh x.sh -f hello.x
Hello from x-lang!
display writes a value without quotes; write writes it in read-back form
(so strings keep their quotes); newline emits a line break. -f evaluates
the file and exits. To load a file and then land in the REPL with its
definitions available, use -F:
sh x.sh -F hello.x
This file is examples/x/hello.x; the rest of
examples/ builds up from here.
For a single expression there is no need for a file at all. -c evaluates
one and exits, and repeats to run several in order:
sh x.sh -q -c '(display "Hello from x-lang!")' -c '(newline)'
A program arriving on stdin runs the same way, which is what makes x-lang usable from a pipe:
echo '(display "Hello from x-lang!")' | sh x.sh -q
-q drops the banner. Neither form prints a result on its own — like a
file, and unlike the REPL, they produce output only where you ask for it.
Integers, strings, and characters are self-evaluating:
> 42
42
> "hello"
"hello"
> #\a
aThe pair is the fundamental compound structure. A list is a chain of pairs terminated by nil ():
> (pair 1 2)
(1 . 2)
> (pair 1 (pair 2 (pair 3 ())))
(1 2 3)
> (list 1 2 3)
(1 2 3)
> (first (list 1 2 3))
1
> (rest (list 1 2 3))
(2 3)def binds a name in the current environment:
> (def x 10)
> (def double (fn (_ n) (* n 2)))
> (double x)
20fn creates an applicative (a function that evaluates its arguments). The first parameter is always the self-reference (conventionally _), followed by the actual parameters:
> (def square (fn (_ n) (* n n)))
> (square 5)
25
> (def factorial
(fn (self n)
(if (<= n 1) 1 (* n (self (- n 1))))))
> (factorial 10)
3628800The self-reference enables anonymous recursion — name it self (or anything) to call the function from within its own body, or _ when not needed.
> (if (> 3 2) "yes" "no")
"yes"
> (match
((> x 100) "big")
((> x 10) "medium")
(#t "small"))
"small"do evaluates a sequence of expressions and returns the last:
> (do
(def a 1)
(def b 2)
(+ a b))
3> (let ((x 10) (y 20)) (+ x y))
30x-lang’s evaluation model is distinctive. All C-level primitives are fexprs: they receive their arguments unevaluated and choose what to evaluate.
fn creates an applicative — arguments are evaluated before the body runs (like a normal function)op creates an operative — arguments are passed unevaluated, and the caller’s environment is available> (def my-if
(op (test then else) e
(if (eval test e)
(eval then e)
(eval else e))))
> (my-if (> 3 2) "yes" "no")
"yes"op binds the unevaluated argument tree and the caller’s environment e. The body decides what to evaluate and when. This is how all core forms (if, def, match, do) are implemented – they are ordinary operatives, not special forms the evaluator knows about.
wrap and unwrap convert between applicative and operative behaviour:
> (def my-add (wrap (op (a b) e (+ (eval a e) (eval b e)))))
> (my-add 1 2)
3The standard library provides a rich set of list operations:
> (map (fn (_ x) (* x x)) (list 1 2 3 4 5))
(1 4 9 16 25)
> (filter (fn (_ x) (> x 2)) (list 1 2 3 4 5))
(3 4 5)
> (fold + 0 (list 1 2 3 4 5))
15
> (List sort < (list 3 1 4 1 5 9))
(1 1 3 4 5 9)
> (List zip (list 1 2 3) (list "a" "b" "c"))
((1 . "a") (2 . "b") (3 . "c"))Everything the default dialect ships is ready without ceremony — vectors, for instance:
> (def v (Vector make 3 0))
> v
#(0 0 0)Capabilities the dialect does not pre-load are one import away:
> (import x/type/hash)
> (Hash ->hex (Hash fnv-1a "hello"))
"a430d84680aabd0b"In the xenon dialect, the numeric tower is pre-loaded:
> (Num expt 2 100)
1267650600228229401496703205376
> (+ 1/3 1/6)
1/2
> (* 2.0 3.14)
6.28
> (+ 1+2i 3+4i)
4+6iUse help to look up documentation for any bound symbol:
> (help '+)Use modules to list all registered modules:
> (modules)| Dialect | Load Command | Use Case |
|---|---|---|
| helium | sh x.sh |
General programming, scripting, learning (the default) |
| xenon | sh x.sh -l xe |
Numeric computing, full-stack applications |
| radon | sh x.sh -l rn |
Systems programming, OS interaction |
make doc-x, then ref/x/index.md)