Sans Language Reference

Sans is a compiled programming language optimized for AI code generation. It compiles to native code via LLVM with performance comparable to C, Rust, and Go.

Getting Started

sans build hello.sans && ./hello

Sans compiles to native binaries via LLVM. Write a .sans file, build it, and run.

Types

TypeShortDescription
IntI64-bit signed integer
FloatF64-bit floating point
BoolBBoolean (true / false)
StringSUTF-8 string
Array<T>Dynamic growable array
MapMHash map with string keys
Result<T>R<T>Success or error value
JsonValueOpaque JSON value
HttpResponseHTTP client response
HttpServerHTTP server socket
HttpRequestHTTP server request
Sender<T>Channel sender
Receiver<T>Channel receiver
Mutex<T>Mutual exclusion lock
JoinHandleThread handle

User-defined types: struct, enum, trait.

Variable Declaration

x = 42              // immutable (type inferred)
x := 0              // mutable
let x Int = 42      // explicit type (verbose, optional)
let mut x = 0       // verbose mutable (optional)

Global Variables

g counter = 0       // global mutable variable

inc() I {
  counter = counter + 1
  counter
}

Globals are mutable and accessible from any function. Declared at the top level with g.

Function Definition

// Full form
fn add(a Int, b Int) Int { a + b }

// Compact form (preferred)
add(a:I b:I) I = a + b

// Implicit return type (defaults to Int)
main() { 0 }

Control Flow

// Ternary
result = x > 0 ? x * 2 : x * -1

// If-else block
if condition {
    body
} else {
    body
}

// While loop
while condition { body }

// For-in loop
for item in array { body }

// Loop control
while cond {
    if done { break }       // exit loop immediately
    if skip { continue }    // skip to next iteration
}
for x in arr {
    if x == 0 { continue }  // works in for-in too
    if x < 0 { break }
}

// Match expression
match value {
    EnumName::Variant1 => expr1,
    EnumName::Variant2(x) => x + 1,
}

Iterator Chains

Array methods return arrays, so they chain without .collect():

a.map(|x:I| I { x * 2 }).filter(|x:I| B { x > 3 })

// New methods
a.any(|x:I| B { x > 3 })      // B — true if any match
a.find(|x:I| B { x > 3 })     // first match or 0
a.enumerate()                   // [(I I)] index-value tuples
a.zip(b)                        // [(I I)] paired tuples
a.sort()                        // in-place sort (integers)
a.reverse()                     // in-place reverse
a.join(",")                    // "1,2,3" — join to string
a.slice(1, 3)                  // sub-array [start..end)

// String methods
s.upper()  s.lower()            // case conversion
s.index_of("sub")               // position or -1
s.char_at(0)  s.get(0)         // single character as string
s.repeat(3)                     // "abcabcabc"

// Math & range
abs(-5)  min(3, 7)  max(3, 7)
range(5)                          // [0 1 2 3 4]
range(2, 5)                       // [2 3 4]

Operators

CategoryOperators
Arithmetic+, -, *, /, %
Comparison==, !=, <, >, <=, >=
Boolean&&, ||, !
Assignment=, :=, +=, -=, *=, /=, %=
Special?: (ternary), ? (try/propagate), ! (postfix unwrap), [] (index)

Maps

Hash map with string keys. Constructor: M() or map().

m = M()
m.set("x" 10)
m.set("y" 20)
m.get("x")       // 10
m.has("z")       // false
m.len()           // 2
m.keys()          // ["x" "y"]

Map Methods

MethodSignatureDescription
set(key, val)(S, I) -> ISet key-value pair
get(key)(S) -> IGet value, 0 if missing
has(key)(S) -> BCheck if key exists
len()() -> INumber of entries
keys()() -> [S]Array of all keys
vals()() -> [I]Array of all values

String Interpolation & Slicing

name = "Sans"
msg = "Hello {name}!"    // "Hello Sans!"

Expression Interpolation

Full expressions are supported inside {}:

x = 10
"result is {x + 1}"     // "result is 11"
"len is {a.len()}"      // method calls
"sum is {x * 2 + 3}"    // arithmetic

String Slicing

Slice strings with [start:end] syntax (desugars to .substring()):

s = "hello world"
s[0:5]    // "hello"
s[6:]     // "world" (to end)
s[:5]     // "hello" (from start)

Tuples

Tuples are fixed-size, ordered collections of values that can have different types. No commas — space-separated like arrays.

// Tuple literal (no commas)
t = (1 "hello" true)

// Access by index
t.0   // 1
t.1   // "hello"
t.2   // true

// Tuple type annotation
pair(a:I b:I) (I I) = (a b)

// Multi-return via tuples
p = pair(10 20)
p.0 + p.1  // 30

Single expressions in parens are grouping, not tuples: (1 + 2) evaluates to 3, not a 1-tuple.

Structs

struct Point { x I, y I }

make_point(x:I y:I) Point = Point { x: x, y: y }

main() {
    pt = Point { x: 10, y: 20 }
    p(str(pt.x + pt.y))
    0
}

Enums

enum Shape {
    Circle(I),
    Rect(I, I),
}

area(s Shape) I = match s {
    Shape::Circle(r) => r * r * 3,
    Shape::Rect(w h) => w * h,
}

Traits & Generics

trait Describable {
    fn describe(self) I
}

impl Describable for Point {
    fn describe(self) I { self.x + self.y }
}

identity<T>(x T) T = x

Lambdas & Closures

Lambda expressions are anonymous functions. They implicitly capture variables from their enclosing scope.

// Non-capturing lambda
f = |x:I| I { x + 10 }
f(5)  // 15

// Multiple parameters
add = |a:I b:I| I { a + b }

// Used with map
nums = [1 2 3 4 5]
doubled = nums.map(|x:I| I { x * 2 })

// Implicit capture from enclosing scope
multiplier = 3
scaled = nums.map(|x:I| I { x * multiplier })

Error Handling

divide(a:I b:I) R<I> = b == 0 ? err("div/0") : ok(a / b)

main() {
    r = divide(10 3)
    r.is_ok ? r! : 0
}

Error Propagation (? operator)

The ? operator unwraps a Result<T> or early-returns the error:

safe_div(a:I b:I) R<I> {
  b == 0 ? err("div by zero") : ok(a / b)
}

compute(x:I) R<I> {
  r = safe_div(x 2)?    // unwraps ok(5), or returns err early
  ok(r + 1)
}

x? desugars to: if x.is_err() { return x } followed by x! (unwrap).

Modules

// math.sans
add(a:I b:I) = a + b

// main.sans
import "math"

main() {
    result = math.add(3 4)
    result
}

Concurrency

worker(tx Sender<Int>) I {
    tx.send(42)
    0
}

main() {
    let (tx rx) = channel<I>()
    spawn worker(tx)
    val = rx.recv
    val
}

Built-in Functions

I/O

FunctionAliasSignature
print(value)p(String|Int|Float|Bool) -> Int
file_read(path)fr(String) -> String
file_write(path, content)fw(String, String) -> Int
file_append(path, content)fa(String, String) -> Int
file_exists(path)fe(String) -> Bool

Type Conversion

FunctionAliasSignature
int_to_string(n)str(Int) -> String
string_to_int(s)stoi(String) -> Int
int_to_float(n)itof(Int) -> Float
float_to_int(f)ftoi(Float) -> Int
float_to_string(f)ftos(Float) -> String

JSON

FunctionAliasSignature
json_object()jo() -> JsonValue
json_array()ja() -> JsonValue
json_string(s)js(String) -> JsonValue
json_int(n)ji(Int) -> JsonValue
json_bool(b)jb(Bool) -> JsonValue
json_null()jn() -> JsonValue
json_parse(s)jp(String) -> JsonValue
json_stringify(v)jfy(JsonValue) -> String

HTTP Client

FunctionAliasSignature
http_get(url)hg(String) -> HttpResponse
http_post(url, body, ct)hp(String, String, String) -> HttpResponse

HTTP Server

FunctionAliasSignature
http_listen(port)listen(Int) -> HttpServer
https_listen(port, cert, key)hl_s(Int, String, String) -> HttpServer
serve(port, handler)(Int, Fn) -> Int
serve_tls(port, cert, key, handler)(Int, String, String, Fn) -> Int
stream_write(writer, data)(Int, String) -> Int
stream_end(writer)(Int) -> Int
signal_handler(signum)(Int) -> Int
signal_check()() -> Int
spoll(fd, timeout_ms)(Int, Int) -> Int
ws_send(ws, msg)(Int, String) -> Int
ws_recv(ws)(Int) -> String
ws_close(ws)(Int) -> Int
serve_file(req, dir)(HttpRequest, String) -> Int
url_decode(s)(String) -> String
path_segment(path, idx)(String, Int) -> String

serve_file(req, dir) serves a static file from dir matching the request path. Handles content-type detection, 404 for missing files, and directory traversal protection.

url_decode(s) decodes a URL-encoded string (e.g. %20 to space, + to space).

path_segment(path, idx) extracts the segment at index idx from a URL path. path_segment("/api/users/42" 2) returns "42".

serve(port, handler) starts a production server with auto-threading, HTTP/1.1 keep-alive, automatic gzip compression, and graceful shutdown. Each connection spawns a thread. The handler receives an HttpRequest and should call respond or respond_stream. On SIGINT/SIGTERM, the server stops accepting new connections, lets in-flight requests finish, and exits cleanly.

req.respond_stream(status) sends chunked HTTP headers and returns a writer. Use stream_write(w, data) to send chunks and stream_end(w) to finalize.

signal_handler(signum) registers a signal handler that sets a global flag. signal_check() returns 1 if the signal was received. spoll(fd, timeout_ms) polls a file descriptor for readability with timeout, returning 1 if ready, 0 otherwise.

Automatic Gzip Compression

respond() automatically gzip-compresses response bodies when all conditions are met:

No code changes needed — compression is transparent. Opt out with:

req.set_header("X-No-Compress" "1")
req.respond(200 large_body)

WebSocket

req.is_ws_upgrade() returns 1 if the request is a WebSocket upgrade request. req.upgrade_ws() performs the handshake and returns a WebSocket handle.

ws_send(ws, msg) sends a text frame. ws_recv(ws) receives the next text frame (handles ping/pong automatically, returns "" on close). ws_close(ws) sends a close frame and closes the socket.

handle(req:I) I {
  req.is_ws_upgrade() ? {
    ws = req.upgrade_ws()
    msg := ws_recv(ws)
    while slen(msg) > 0 {
      ws_send(ws "echo: " + msg)
      msg = ws_recv(ws)
    }
    ws_close(ws)
  } : {
    req.respond(200 "WebSocket server")
  }
}

main() I {
  serve(8080 fptr("handle"))
}

CORS

FunctionSignatureDescription
cors(req, origin)(HttpRequest, String) -> IntSet CORS headers with given origin. Call before respond.
cors_all(req)(HttpRequest) -> IntSet CORS headers with wildcard origin (*).
srv = listen(8080)
while true {
    req = srv.accept
    cors_all(req)
    req.respond(200 "ok")
}

Logging

FunctionAliasSignature
log_debug(msg)ld(String) -> Int
log_info(msg)li(String) -> Int
log_warn(msg)lw(String) -> Int
log_error(msg)le(String) -> Int
log_set_level(n)ll(Int) -> Int

Log levels: 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR

Low-Level Primitives

These enable Sans to replace its own C runtime. Pointers are stored as Int (i64).

Memory

FunctionSignatureDescription
alloc(size)(Int) -> Intmalloc, returns pointer
dealloc(ptr)(Int) -> Intfree
ralloc(ptr, size)(Int, Int) -> Intrealloc
mcpy(dst, src, n)(Int, Int, Int) -> Intmemcpy
mcmp(a, b, n)(Int, Int, Int) -> Intmemcmp
slen(ptr)(Int) -> Intstrlen on raw pointer
load8(ptr)(Int) -> Intload byte (0-255)
store8(ptr, val)(Int, Int) -> Intstore byte
load16(ptr)(Int) -> Intload 16-bit value
store16(ptr, val)(Int, Int) -> Intstore 16-bit value
load32(ptr)(Int) -> Intload 32-bit value
store32(ptr, val)(Int, Int) -> Intstore 32-bit value
load64(ptr)(Int) -> Intload 64-bit value
store64(ptr, val)(Int, Int) -> Intstore 64-bit value
strstr(haystack, needle)(Int, Int) -> Intfind substring, 0 if not found
bswap16(val)(Int) -> Intbyte-swap 16-bit (htons)
exit(code)(Int) -> Intexit process
system(cmd) / sys(cmd)(String) -> Intrun shell command, return exit code
gzip_compress(data, len)(Int, Int) -> Intgzip-compress data; returns ptr to [compressed_ptr, compressed_len]

Arena Allocator

Phase-based bump allocator. All allocations between arena_begin() and arena_end() are freed at once. Arenas nest up to 8 deep.

FunctionSignatureDescription
arena_begin()() -> IntPush a new arena onto the stack
arena_alloc(size)(Int) -> IntBump-allocate from the current arena (8-byte aligned)
arena_end()() -> IntPop and free all memory in the current arena

I/O

FunctionSignatureDescription
wfd(fd, msg)(Int, String) -> Intwrite string to file descriptor

SSL (Advanced)

Low-level TLS/SSL bindings. For most use cases, prefer https_listen.

FunctionSignatureDescription
ssl_ctx(cert, key)(String, String) -> IntCreate SSL context from cert/key file paths
ssl_accept(ctx, fd)(Int, Int) -> IntPerform TLS handshake on accepted socket fd
ssl_read(ssl, buf, len)(Int, Int, Int) -> IntRead bytes from TLS connection
ssl_write(ssl, buf, len)(Int, Int, Int) -> IntWrite bytes to TLS connection
ssl_close(ssl)(Int) -> IntShut down TLS connection and free SSL object

Sockets

FunctionSignatureDescription
sock(domain, type, proto)(Int, Int, Int) -> Intsocket()
sbind(fd, port)(Int, Int) -> Intbind to port
slisten(fd, backlog)(Int, Int) -> Intlisten()
saccept(fd)(Int) -> Intaccept()
srecv(fd, buf, len)(Int, Int, Int) -> Intrecv()
ssend(fd, buf, len)(Int, Int, Int) -> Intsend()
sclose(fd)(Int) -> Intclose()
rbind(fd, addr, len)(Int, Int, Int) -> Intraw bind()
rsetsockopt(fd, level, opt, val, len)(Int, Int, Int, Int, Int) -> Intraw setsockopt()

Curl

FunctionSignatureDescription
cinit()() -> Intcurl_easy_init
csets(h, opt, val)(Int, Int, String) -> Intsetopt with string
cseti(h, opt, val)(Int, Int, Int) -> Intsetopt with long
cperf(h)(Int) -> Intcurl_easy_perform
cclean(h)(Int) -> Intcurl_easy_cleanup
cinfo(h, info, buf)(Int, Int, Int) -> Intcurl_easy_getinfo
curl_slist_append(slist, str)(Int, Int) -> Intappend to curl header list
curl_slist_free(slist)(Int) -> Intfree curl header list

Function Pointers

FunctionSignatureDescription
fptr("name")(String) -> Intget pointer to named function
fcall(ptr, arg)(Int, Int) -> Intcall function pointer with 1 arg
fcall2(ptr, a, b)(Int, Int, Int) -> Intcall function pointer with 2 args
fcall3(ptr, a, b, c)(Int, Int, Int, Int) -> Intcall function pointer with 3 args

Pointer Access

FunctionSignatureDescription
ptr(s)(String|Map|Array) -> Intget raw i64 pointer of string, map, or array
char_at(s, i)(String, Int) -> Intread byte at index i (shorthand for load8(ptr(s) + i))

Map Operations

Explicit Map built-ins. Use these when a Map is stored as Int (e.g. from load64) and method dispatch cannot determine the correct type.

FunctionSignatureDescription
mget(map, key)(Int, String) -> Intget value from Map by key (0 if not found)
mset(map, key, val)(Int, String, Int) -> Intset key-value pair in Map
mhas(map, key)(Int, String) -> Intcheck if Map contains key (1=yes, 0=no)

File I/O

FunctionSignatureDescription
read_file(path)(String) -> Stringread entire file to string
write_file(path, content)(String, String) -> Intwrite string to file
args()() -> Array<String>get command-line arguments

Error Handling

FunctionSignature
ok(value)(T) -> Result<T>
err(message)(String) -> Result<_>

Methods by Type

Array<T>

MethodSignatureNotes
push(value)(T) -> IntAppend element
pop() -> TRemove and return last
get(index) or [index](Int) -> TRead element
set(index, value)(Int, T) -> IntWrite element
len() -> IntLength
remove(index)(Int) -> TRemove at index
contains(value)(T) -> BoolCheck membership
map(fn)((T) -> U) -> Array<U>Transform elements
filter(fn)((T) -> Bool) -> Array<T>Filter elements
any(fn)((T) -> Bool) -> BoolTrue if any element matches
find(fn)((T) -> Bool) -> TFirst match, or 0
enumerate() -> Array<(Int, T)>Index-value tuples
zip(other)(Array<U>) -> Array<(T, U)>Paired tuples

String

MethodAliasSignature
len() -> Int
substring(start, end)(Int, Int) -> String
trim() -> String
starts_with(prefix)sw(String) -> Bool
ends_with(suffix)ew(String) -> Bool
contains(needle)(String) -> Bool
split(delimiter)(String) -> Array<String>
replace(old, new)(String, String) -> String

JsonValue

MethodSignature
get(key)(String) -> JsonValue
get_index(index)(Int) -> JsonValue
get_string() -> String
get_int() -> Int
get_bool() -> Bool
len() -> Int
type_of() -> String
set(key, value)(String, JsonValue) -> Int
push(value)(JsonValue) -> Int

HttpResponse

MethodSignature
status() -> Int
body() -> String
header(name)(String) -> String
ok() -> Bool

HttpServer / HttpRequest

TypeMethodSignatureNotes
HttpServeraccept() -> HttpRequest
HttpRequestpath() -> String
HttpRequestmethod() -> String
HttpRequestbody() -> String
HttpRequestheader(name)(String) -> StringGet request header value (case-insensitive)
HttpRequestset_header(name, value)(String, String) -> IntAdd custom response header (call before respond)
HttpRequestquery(name)(String) -> StringGet query parameter value by name
HttpRequestpath_only() -> StringPath without query string
HttpRequestcontent_length() -> IntGet Content-Length as int
HttpRequestcookie(name)(String) -> StringGet cookie value from Cookie header
HttpRequestform(name)(String) -> StringParse form field from POST body (URL-encoded or multipart)
HttpRequestrespond(status, body)(Int, String) -> IntDefaults to text/html content-type
HttpRequestrespond(status, body, content_type)(Int, String, String) -> IntExplicit content-type
HttpRequestrespond_json(status, body)(Int, String) -> IntJSON response (sets Content-Type: application/json)
HttpRequestrespond_stream(status)(Int) -> IntChunked streaming response, returns writer handle
HttpRequestis_ws_upgrade() -> IntReturns 1 if WebSocket upgrade request
HttpRequestupgrade_ws() -> IntPerform WS handshake, return WebSocket handle

Result<T>

MethodSignatureNotes
is_ok() -> Bool
is_err() -> Bool
unwrap or !() -> TExits on error
unwrap_or(default)(T) -> T
error() -> String

Concurrency Types

TypeMethodSignature
Sender<T>send(value)(T) -> Int
Receiver<T>recv() -> T
Mutex<T>lock() -> T
Mutex<T>unlock(value)(T) -> Int
JoinHandlejoin() -> Int

Self-Hosting

Sans is self-hosted: the compiler and runtime are both written in Sans.

Runtime (100% Sans, zero C)

All built-in capabilities are implemented in Sans using low-level primitives (alloc, load8/store8, mcpy, sockets, etc.). The runtime/ directory contains: server.sans, json.sans, string_ext.sans, array_ext.sans, map.sans, ssl.sans, http.sans, curl.sans, arena.sans, result.sans, functional.sans.

Compiler (written in Sans)

compiler/ contains a full Sans compiler (~11,600 LOC across 7 modules): lexer, parser, typeck, IR, codegen, main. It compiles to LLVM IR via llc, then links with clang.

Bootstrap stages: stage 0 (Rust-compiled) → stage 1 (self-compiled once) → stage 2 (self-compiled twice) → stage 3 (fixed point).

Builtin Names

The compiler has builtin mappings for a large set of names. User-defined functions take precedence over builtins of the same name. Builtin names include:

p, print, str, stoi, itof, ftoi, ftos, fr, fw, fa, fe, file_read, file_write, file_exists, listen, serve, serve_file, serve_tls, alloc, dealloc, load8/16/32/64, store8/16/32/64, mcpy, slen, wfd, exit, system, ok, err, map, M, jp, jfy, jo, ja, sock, saccept, args, signal_handler, signal_check, and all others listed in the reference.

Known Limitations