A small JavaScript-inspired language for building APIs, written in C, so programs run as a native compiled binary — no JS runtime needed. Ships with an Express-style HTTP server built into the language.
Single-file C, no third-party deps (just libm + the OS sockets lib).
cc -O2 -o contrast contrast.c -lm # gcc / clang / *nix
zig cc -O2 -o contrast.exe contrast.c -lws2_32 # zig on Windows (self-contained)On Windows you can also just run build.bat.
./contrast example.con # run a file
./contrast api.con # start the API server demo
./contrast # start the REPLIn the REPL, bare expressions print their value (=> ...), multi-line blocks (fn, if, while) continue with a ... prompt until braces balance, and a runtime error reports the line without killing the session.
con replaces var/let/const:
con one = 1
con text = "Text"
one = 2 // reassignment
log("test") // normal output
warn("warning") // yellow
err("error") // red
contrast("#000000") // WCAG contrast ratio + recommended text color
len(x) // length of an array or string
push(arr, value) // append to an array, returns new length
contrast(color) returns the ratio (a number), so you can store it:
con r = contrast("#3498db")
fn declares a function; return yields a value. Functions are first-class values (pass them around, recurse, close over scope):
fn add(a, b) { return a + b }
fn fib(n) {
if < n <= 1 > { return n }
return fib(n - 1) + fib(n - 2)
}
fn apply(f, x) { return f(x) } // f is a function value
Literals, indexing, and index assignment:
con nums = [1, 2, 3]
push(nums, 4) // [1, 2, 3, 4]
nums[0] = 10 // [10, 2, 3, 4]
log(nums[2], len(nums))
Strings can be indexed too: "Text"[0] is "T".
Object literals use { key: value }; read/write with . or ["key"]:
con user = { name: "Ada", age: 36 }
log(user.name) // Ada
user.age = 37
user["role"] = "dev"
(A { ... } that doesn't start with key: is a function block instead.)
Route handlers are blocks { ... } with req and res in scope:
get("/", {
res.send("Olá Mundo!")
})
get("/json", {
res.json({ name: "contrast", fast: true })
})
post("/email", {
res.status(201).json({ ok: true, received: req.body })
})
put("/user", { res.json({ updated: true }) })
delete("/movie",{ res.status(204).send() })
listen(3000, { log("server ready") })
- Routes:
get,post,put,delete— each takes(path, handler). reqfields:req.method,req.path,req.url,req.query,req.body.resmethods:res.send(x)(string → HTML, object/array → JSON),res.json(x),res.status(code)(chainable),res.set(name, value).listen(port, callback)starts the blocking server; the callback runs once.- A handler can also be a named
fn(req, res) { ... }.
A condition goes inside < >. Inside it, = means equality:
if < text = "Text" > {
log("matches")
} else {
err("no match")
}
con i = 0
while < i <= 2 > {
log(i)
i = i + 1
}
- arithmetic:
+ - * / %(+also concatenates strings) - equality:
=or==,!= - comparison:
<=,>=(<and>are condition delimiters) - logical:
&&,||,!
number, string, true, false, null, array, object, function
Comments: // ...
contrast.c is a tree-walking interpreter: lexer -> recursive-descent parser -> AST -> evaluator. The HTTP server uses raw BSD/Winsock sockets, parses the request line + headers, builds the req/res objects, and dispatches to the matching route.