Rak
PublicA general-purpose programming language built mainly for cybersecurity usage, written in Rust.
Rak is a general-purpose programming language written in Rust, designed mainly for cybersecurity usage. The project covers the compiler, IDE support, and documentation — and it is closing in on its first full version.
The language already ships real tools written in it: NetAudit (a network auditor), a simple ad blocker, and early GUI/HTML experiments like the ipv4-to-ipv6 converter. Suggestions and new ideas are welcome on GitHub as development heads toward the full release.
| Name | Size |
|---|---|
| .cargo , directory | |
| .github , directory | |
| dist , directory | |
| docs , directory | |
| examples , directory | |
| fuzz , directory | |
| ide , directory | |
| rak-setup , directory | |
| rakc , directory | |
| rakpkg , directory | |
| scripts , directory | |
| stdlib , directory | |
| vscode-rak , directory | |
| .gitignore | 464 B |
| Cargo.lock | 164.3 KB |
| Cargo.toml | 1.9 KB |
| DESIGN.md | 31.9 KB |
| LICENSE | 11.1 KB |
| README.md | 35.2 KB |
Rak
A programming language for hackers, OSINT investigators, and systems programmers. Build desktop GUI apps, backend SQL servers, shareable packages, and self-host the compiler. All in Rak.
Hex is a first-class type. The bytecode VM runs about 6x faster than the tree-walker. There's a SQL engine written in Rak itself, and a Rak interpreter written in Rak.
What's new in 0.7.2
- DAP debugger server —
rakc dap <file>speaks the Debug Adapter Protocol over stdio (Content-Length framing). The VM runs the target on a worker thread while a reader handles requests:setBreakpoints/remove,continue,next,stepIn,stepOut,stackTrace,scopes/variables,setVariable, and fullexceptionInfo— line-granular breakpoint gating via alast-hit-lineguard. Wire it up from VS Code (the bundledrakextension), Neovim (nvim-dap), or any DAP client. - Stdlib batteries — everyday data modules on both backends: time &
datetime (
time_now,time_fmt,time_parse,time_parts,time_add,time_diff,date_today), randomness (rand_seed,rand_int,rand_float,rand_bytes,rand_hex,rand_choice,rand_shuffle), CSV (csv_parse,csv_stringify), YAML (yaml_parse), and archives (gzip_compress,gzip_decompress,zip_list,zip_read,zip_write). - OSINT pack — WHOIS lookups and parsing (
whois_lookup/whois_parse), certificate-transparency subdomain enumeration (ct_subdomains), a hand-rolled YARA-lite scanning engine (yara_scan: hex wildcards,nocase,at/in,all-of/any-of/none-of, boolean conditions), and Markdown evidence reports (report_markdown). Docs:docs/content/osint.md, demo:examples/osint_demo.rak. - Language core:
inoperator — membership checks work on strings (substring/char), arrays, tuples, maps (key lookup), and byte sequences. - Destructuring
let—let (a, b) = pair,let [x, y, z] = list,let Point { x, y } = obj, nested andlet mutforms; arity mismatches raise a catchable error. - Slice and negative indexing —
a[1..3],a[..2],a[3..],a[-2..]slices,a[-1]reads anda[-1] = vwrites from the end, with out-of-bounds raises on both backends. - The VM caught up.
rakc vmnow runs what the interpreter runs:try/catchandexpr?(structured errors, LIFO defers before the handler), method dispatch on structs/enums (obj.method(...)viaimplblocks), struct literals and enum constructors, and the full pattern language inmatch— struct patterns (Point { x, y }), enum variants, binary bytes patterns, or-patterns, ranges,Some/None/Ok/Err, and guards. - Operator overloading —
impl Add/Sub/Mul/Div/Rem/Eq/Compare/Neg for Twithfn add(self, o)-style methods, dispatched on both backends before the built-in arithmetic (a + b,a == b,a < b,-aon your types). - Assignment on the VM —
arr[i] = v,map[k] = v,s.field = v,x += 1, and multi-assigna, b = x, y(copy-on-write, mutability rules enforced). - Iterator builtins —
zip,enumerate,skip,fold,reduce,any,all,flat_map,take_while, pluskeys/values/has/get, on both backends. for (k, v) in mapand indexedfor (i, x) in arrwork on the VM (Op::IterItemsmaterializes any iterable; maps always yield pairs).if let/while let/do { } while/ labeledbreak/continue('outer: for ... { break 'outer }) run on the VM.- binstruct bitfields —
u4-style non-byte-aligned fields, packed LSB-first and round-tripped throughdecode/encodeon both backends. rakc fmt— AST-based formatter (--write,--checkfor CI).rakc lint— advisory checks (unused vars, shadowing, unreachable code, missingpub fnreturn types, duplicate imports;--denyfor CI).
Install
The custom installer is an interactive TUI wizard that installs rakc, rakpkg, and the Rak IDE, edits PATH, sets RAK_PATH, creates shortcuts + .rak associations, and installs man pages + shell completions. It runs in net-install (downloads the latest release) or offline-bundle mode, and supports --uninstall / --list / --yes for scripting.
Linux:
curl -fsSL https://raw.githubusercontent.com/Louiml/Rak/main/dist/install.sh | bash
# non-interactive:
curl -fsSL https://raw.githubusercontent.com/Louiml/Rak/main/dist/install.sh | bash -s -- --yes --install rakc,rakpkg,ide --scope user
Windows (PowerShell):
iwr -useb https://raw.githubusercontent.com/Louiml/Rak/main/dist/install.ps1 | iex
Or download the setup binary directly from the latest release (rak-setup-linux-x86_64 or rak-setup-windows-x86_64.exe) and run it.
Menu items: rakc → bin + PATH, rakpkg → bin + PATH, IDE → portable dir / system location, Set RAK_PATH, Shortcuts + .rak association, Man pages + shell completions. Install scope: user (default, no privileges) or system. A ~/.rak/manifest.json records every action for clean uninstall/upgrade.
The Tauri installers (NSIS/MSI on Windows, .deb/AppImage on Linux) remain on the release page for IDE-only users who want the OS-native installer.
Quick start
dump "Hello, World"
scan "127.0.0.1" {
range: [0x0016, 0x0050]
} {
if open {
dump fmt("Port 0x{:04X}", port)
}
}
What you get
The language started as an OSINT scripting tool. It grew into something bigger.
Forensic Structs & evidence provenance. Declare a binary wire format once with binstruct and get both a decoder and an encoder for free (round-trip). Every decoded value is wrapped in an evidence<T> provenance tag carrying where/when/how it was collected, so report(...) emits a chain-of-custody-cited findings report. No other language bakes provenance into the value model — this only makes sense in a hex-first, OSINT-first language.
Bytecode VM. rakc vm runs bytecode. Benchmarks show about 6x speedup over the tree-walking interpreter. Run rakc bench file.rak to see both.
Data pipelines. A |> pipeline operator, regex literals (/\d+/g) with method syntax, and binary pattern matching over byte slices — built for OSINT log and PCAP triage.
Type system. Signed and unsigned ints (i8 through u64), floats (f32, f64),
typed literals like 42i32 and 3.14f64, a first-class char, base literals
(0b1010/0o755/0xFF), tuples, Result/Option, modules, closures with lexical
scoping, generics, traits with method dispatch, pattern matching (including
binary byte-pattern matching, plus user enum variants), and string interpolation
with f"hello {name}". A static type checker behind rakc check catches bad
annotations and non-exhaustive matches before you run anything.
Concurrency. spawn launches a thread, thread_join waits for it, channel() gives you a sender/receiver pair. TCP networking built in with net_listen, net_accept, tcp_read, tcp_write. Async I/O via a Tokio runtime: async fn/await, http_get_async, tcp_probe.
SQL server in Rak. A full SQL engine, lexer through executor, written in the language itself. It serves queries over TCP. See examples/sql_server.rak.
Self-hosting. examples/self_host.rak is a Rak interpreter written in Rak. It lexes, parses, and evaluates real Rak source code.
Error handling. try/catch/raise, the ? operator, Result and Option types. Lexer and parser errors report line and column. rakc check file.rak prints diagnostics in the format IDEs expect.
Lexical closures. Free variables resolve at the definition site, not the call site. Recursive closures like let f = fn(n) { if n < 2 { return n } return f(n-1) + f(n-2) } just work.
Real JSON. json_parse returns native arrays, maps, ints, floats, bools, nil. json_stringify serializes back to a string. No more hand-rolling JSON in your SQL server.
Build standalone executables. rakc build file.rak produces a self-contained binary with the source embedded. On Windows it's a .exe. On Linux it gets chmod 755. No Rak installation needed on the target machine.
GUI windows (incomplete). With --features gui, Rak scripts can open a native desktop window rendering HTML, CSS, and JS via WebView2 on Windows. Treat it as a display-only preview: gui_update/gui_title/gui_close do not affect the window yet, JavaScript cannot call back into Rak, closing the window ends the process, and Linux does not work. See GUI windows.
Package manager. rakpkg is a CLI for Git-based shareable Rak packages. Initialize, add dependencies from GitHub repos, install, run, and build. The manifest is a Rak file with let bindings.
Foreign Function Interface (FFI). Call native C functions in .so/.dll/.dylib libraries. Declare bindings with extern "C" { ... } (resolved against the platform default C library, or an explicit from "path") or load dynamically with ffi_load and lib.call. Marshal raw memory with ffi_alloc/ffi_write/ffi_read/ffi_cstr_to_string/ffi_string_to_cstr/ffi_free. Works on both the interpreter and the bytecode VM.
extern "C" {
fn abs(n: i32) -> i32
}
dump abs(-42) // 42
let libc = ffi_load("libc.so.6") // or "ucrtbase.dll" / "libSystem.dylib"
dump libc.call("abs", [-9]) // 9
libc.close()
let buf = ffi_alloc(4)
ffi_write(buf, 0, 0x41)
dump ffi_cstr_to_string(buf) // "A"
ffi_free(buf)
Memory-mapped files. Map huge PCAP / log files into memory and inspect them zero-copy. mmap_open maps a file; mmap_slice returns a view that keeps the mapping alive and reads directly from the mapped pages. Single-byte indexing, byte search, and line scanning work on both the interpreter and the bytecode VM; range slicing and binary pattern matching over slices work on the interpreter.
let m = mmap_open("trace.pcap", "r")
dump mmap_size(m)
let hdr = mmap_slice(m, 0, 8)
dump hdr[0] // first byte (zero-copy)
dump mmap_find(m, "\xff\xd8\xff") // byte search -> offset
for (off, len) in mmap_lines_off(m, "\n") {
dump string(mmap_slice(m, off, len))
}
Async event loop. async fn, await, and Tokio-backed async I/O. http_get_async / tcp_probe / tcp_connect_async run on a lazily-started multi-thread Tokio runtime and return a Future; await blocks until it resolves. Works on both the interpreter (deferred async fn bodies) and the bytecode VM (Op::Await).
async fn probe(host, port) {
let open = await tcp_probe(host, port, 200)
return open
}
dump await probe("127.0.0.1", 80)
let body = await http_get_async("https://example.com")
dump string(body)
Raw sockets & packet forging. Build IPv4/TCP/UDP headers with correct ones-complement checksums and send them on a raw socket. The packet builders are pure computation and run anywhere; net_raw_send/recv need CAP_NET_RAW/Administrator (unix) and return a Result.
let pkt = net_raw_tcp_syn("10.0.0.5", "10.0.0.10", 12345, 80)
dump len(pkt) // 40 bytes
dump pkt[0] // 0x45 (IPv4)
dump pkt[33] // 0x02 (SYN flag)
dump net_raw_send(pkt) // Ok(40) on a privileged unix box, Err(...) otherwise
Protocol parsers (DNS / TLS / PCAP). Hand-rolled DNS wire-format builder/parser + UDP query (no external DNS crate — works air-gapped). TLS ClientHello SNI extraction and DER cert-chain parsing via x509-parser. PCAP offline capture behind the pcap cargo feature (libpcap/Npcap). All return Results so they degrade gracefully offline / without the feature.
dump dns_query("example.com", "A") // Ok({answers: [{name, type, ttl, rdata}, ...], truncated})
let q = dns_build("example.com", "A") // raw query bytes (offline)
let info = tls_parse_client_hello(bytes) // {sni, ciphers}
let certs = tls_parse_cert_chain(der) // [{subject, issuer}, ...]
dump pcap_open("capture.pcap") // Ok(<pcap>) or Err(...)
Compile-time macros. macro name($params) { body } defines an AST-expanding template; name!(args) splices the argument expressions into the body's $param placeholders before evaluation. Expanded in the frontend, so both backends see the expanded code. const NAME = expr binds a compile-time constant.
macro add1(x: expr) { $x + 1 }
dump add1!(41) // 42
macro pair(a: expr, b: expr) { [$a, $b] }
dump pair!(1, 2) // [1, 2]
const MAX_LEN = 256
dump MAX_LEN
Forensic Structs & evidence provenance
A declarative wire-format DSL (binstruct) plus a provenance-typed value
(evidence<T>) — the OSINT differentiator. Declare a binary layout once and
get both a decoder and an encoder for free (round-trip); every decoded
value is wrapped in an evidence tag carrying where/when/how it was collected, so
report(...) emits a chain-of-custody-cited findings report. No other language
bakes provenance into the value model — this only makes sense in a hex-first,
OSINT-first language.
binstruct DnsHeader {
id: u16be
flags: u16be
qdcount: u16be
ancount: u16be
nscount: u16be
arcount: u16be
}
let q = dns_build("example.com", "A")
let h = DnsHeader.decode(q) // -> evidence<struct> with provenance
dump h.id // 0x1234
dump h.qdcount // 1
let back = DnsHeader.encode(h) // round-trip back to bytes
Field types: u8/u16/u32/u64 and signed i8..i64, each with an optional
be/le endianness suffix (default big-endian); bytes(n) for a fixed run of
raw bytes; rest for the trailing remainder; and a nested binstruct name for a
Ref field. Works on both the interpreter and the bytecode VM (compile-time
codegen to per-struct native-fn globals — no new VM opcodes).
Evidence provenance:
let ip = evidence<string> from "93.184.216.34" // root tag
let answers = cite(dns_query("example.com", "A"), "dns_query", "example.com")
dump report(ip, answers) // numbered assertions + cited sources (tool/target/ts)
dump provenance(ip) // {tool, target, ts, raw_offset, raw_len, parent}
dump strip_evidence(ip) // 93.184.216.34
evidence<T> from expr— wrap a value in a root provenance tag.cite(value, tool?, target?)— wrap a value, chainingparentto any existing evidence so provenance merges transitively.report(evidence, ...)— render a Markdown-style report with numbered, source-cited assertions (the chain-of-custody output an investigator needs).provenance(value)— the provenance chain as a map.strip_evidence(value)— drop the provenance wrapper, return the inner value.
Field access and indexing transparently unwrap evidence, so
evidence<struct>.field reads through to the inner struct.
Build a standalone executable
rakc build examples/hello.rak # produces hello.exe on Windows, hello on Linux
./hello # [DUMP] Hello, World
GUI windows
Incomplete in 8.0.0. Requires cargo build -p rakc --features gui.
let html = "<h1 style='color:#22c55e;text-align:center;margin-top:40px'>Hello from Rak!</h1>"
let win = gui_open("Rak GUI Demo", html, 600, 400)
gui_wait()
gui_open returns a window ID and gui_wait() blocks. That is all that
currently works. Specifically:
gui_update,gui_title,gui_closeare declared but do nothing yet.gui.rsstoresHashMap<i64, ()>and throws away theWindowandWebViewhandles, so there is nothing to update.gui_callbackandwindow.rak_call(fn, args)do not work. The IPC handler receives the message and discards it, so JavaScript cannot call into Rak yet.- Closing the window ends the process, because tao's
runcallsprocess::exitwhen the last window closes.gui_wait()does not return and code after it is unreachable. - Linux does not work:
gui_openbuilds the event loop on a spawned thread, which trips tao's main-thread assertion. - The VM has no GUI natives; use
rakc run.
Windows only, display only. The rewrite that fixes all of the above is tracked
in docs/V8-ROADMAP.md.
Package manager
rakpkg init mylib # creates package.rak + lib.rak
rakpkg add user/repo # git clone into .rak/packages/
rakpkg install # install all deps from package.rak
rakpkg run # run the entry point via rakc run
rakpkg build # build to standalone executable via rakc build
rakpkg list # list installed packages
rakpkg remove mylib # remove a package
The manifest is a Rak file:
let name = "mylib"
let version = "0.1.0"
let deps = { net: "user/rak-net", crypto: "user/rak-crypto" }
let entry = "lib.rak"
Import installed packages by file path or by name (Python-style). See the Imports & exports section below for the full syntax.
use "./packages/mylib/lib.rak" // file-path (back-compat)
import mylib // name -> searches dir, ./packages/, RAK_PATH
Imports & exports
Rak has a Python-style module system with explicit pub/export, name-based
resolution, from ... import, directory packages, import-once caching, and
circular-import support. Works on both the interpreter and the bytecode VM.
// Whole-module import (binds the module; access via m.x)
import "./math.rak" // file path -> binds "math"
import math // name: searches dir, ./packages/, RAK_PATH for math.rak
import math as m // alias
use math // back-compat: same as import
// from-import (binds names directly)
from math import add
from math import add as plus, mul as times
from math import * // all exports; local bindings win on clash
// Directory packages: `import pkg` runs pkg/init.rak; `import pkg.sub`
// runs pkg/init.rak + pkg/sub.rak (interpreter; use `from pkg.sub import x`
// on the VM).
import pkg
import pkg.sub
// Exports — both `pub` and `export` mark an item as exported (let/fn/const/
// struct/enum/macro):
pub let PI = 3.14
export fn add(a, b) { return a + b }
pub const MAX = 256
// Re-exports
pub use math // re-export all of math from this module
pub use {add, mul} from math // re-export named
Module resolution for a name m: the importing file's directory, then
./packages/, then the RAK_PATH env var (; on Windows, : on Unix),
trying m.rak then m/init.rak. Modules are imported once (cached); a
circular import returns the partially-initialized module (Python semantics).
SQL server
rakc run examples/sql_server.rak # listens on 127.0.0.1:18393
rakc run examples/sql_client.rak # CREATE, INSERT, SELECT, UPDATE, DELETE
The engine supports CREATE TABLE, INSERT, SELECT with WHERE and column projection, UPDATE, DELETE, and DROP. Storage is file-backed with a simple line format. The server handles each connection inline, parsing SQL and returning JSON.
Self-hosting
rakc run examples/self_host.rak # a Rak interpreter written in Rak
It reads Rak source, lexes it, parses it, and evaluates it. The output of 1 + 2 * 3 is 7. The output of let x = 5; let y = 7; dump x * y - 1 is 34.
Language syntax
Variables
let target = "192.168.1.1"
let port = 0x0050
let mut counter = 0
counter = counter + 1
let pi = 3.14f64
let n = 42i32
Functions
fn add(a, b) {
return a + b
}
dump add(0x10, 0x20)
Control flow
if port == 0x0050 {
dump "HTTP"
} else {
dump "Other"
}
for x in [1, 2, 3] { dump x }
while counter < 0x0A { counter = counter + 1 }
loop { break }
Hex arithmetic
let sig = 0xDEADBEEF
let mask = 0xFF00FF00
dump fmt("0x{:08X}", sig & mask)
Error handling
let v = Ok(42)? // unwrap, propagate Err
try {
raise "boom"
} catch e {
dump e
}
String interpolation
let name = "Rak"
dump f"hello {name}!"
Tuples and structs
let p = (1, 2)
dump p.1
struct Point { x: int, y: int }
let o = Point { x: 1, y: 2 }
dump o.x
Pattern matching
match 2 {
1 => { dump "one" },
2 => { dump "two" },
_ => { dump "other" }
}
Pipeline operator
x |> f desugars to f(x), and x |> f(a, b) to f(x, a, b). Chaining is
left-associative, so data |> parse |> load is load(parse(data)). Works on
both the interpreter and the bytecode VM.
fn inc(n) { return n + 1 }
fn dbl(n) { return n * 2 }
dump 5 |> inc |> dbl // 12
dump 3 |> add(10) // 13
Regex literals
/pattern/flags with flags i (case-insensitive), m (multi-line), s
(dotall), x (extended). A / after a value is division; a / in operand
position starts a regex, so a / b and let r = /\d+/g both work.
let re = /\d+/g
dump re.is_match("abc123") // true
dump re.find_all("a1 b22 c333") // [1, 22, 333]
dump (/\s+/g).replace("a b c", "_") // a_b_c
// Free-function form (also works on the VM):
dump regex_find_all(/[a-z]+/g, "a1bc2def") // [a, bc, def]
Binary pattern matching
Match a bytes value against byte literals (hex, int, or char) with a
trailing .. to match the rest. Useful for magic-number sniffing.
fn sniff(data: bytes) {
match data {
[0x89, 'P', 'N', 'G', ..] => { return "png" },
[0xFF, 0xD8, 0xFF, ..] => { return "jpeg" },
['%', 'P', 'D', 'F', ..] => { return "pdf" },
_ => { return "unknown" },
}
}
dump sniff(b"\x89PNG\x0d\x0a\x1a\x0a") // png
Traits (Display, Iterable, Index, IndexMut)
The receiver is passed as the first argument of each method. Display::fmt
drives dump, Iterable::iter drives for x in target, Index::index /
IndexMut::set drive obj[key] reads and writes.
struct Point { x: int, y: int }
impl Display for Point {
fn fmt(self) { return fmt("({}, {})", self.x, self.y) }
}
dump Point { x: 3, y: 4 } // (3, 4)
struct Range { lo: int, hi: int }
impl Iterable for Range {
fn iter(self) {
let out = []; let i = self.lo
while i <= self.hi { out = push(out, i); i = i + 1 }
return out
}
}
let s = 0
for n in Range { lo: 1, hi: 5 } { s = s + n }
dump s // 15
Characters and base literals
let c: char = 'a'
let hebrew: char = 'א'
let alpha = '\u{03B1}' // α
let bin = 0b1010 // 10
let oct = 0o755 // 493
dump 0xA == 10 // true
Generic functions
fn identity<T>(value: T) -> T { return value }
dump identity<int>(42) // 42
dump identity<string>("hi") // hi
dump identity(99) // inferred: 99
Defer
defer f() runs in LIFO order when the current function exits. The last one
registered runs first. Same on the interpreter and the VM.
fn work() {
defer dump "cleanup-last"
defer dump "cleanup-first"
}
work() // cleanup-first, then cleanup-last
Tests and type checking
test "addition works" {
assert add(2, 3) == 5
assert_eq(add(2, 3), 5)
expect_error(fn() { raise "boom" })
}
rakc test tests/math.rak # PASS/FAIL, exit non-zero on failure
rakc check file.rak # type mismatches, non-exhaustive matches
The full design and implementation plan for these plus FFI, memory-mapped
files, macros, an async event loop, raw sockets, and DNS/TLS/PCAP parsers is
in docs/rak-features-spec.md.
Standard library
TCP networking
let listener = net_listen("127.0.0.1:18392")
let conn = net_accept(listener)
let stream = conn.0
tcp_write(stream, "hello\n")
dump tcp_read_line(stream)
tcp_close(stream)
Concurrency
let h = spawn(fn() { return 42 })
dump thread_join(h)
let (tx, rx) = channel()
chan_send(tx, "hi")
dump chan_recv(rx)
Strings, arrays, and math
dump replace("hello", "l", "L") // heLLo
dump find("hello", "lo") // 3, or -1 if not found
dump starts_with("hello", "he") // true
dump ends_with("hello", "lo") // true
dump slice("hello", 1, 4) // ell
dump repeat("ab", 3) // ababab
dump trim_start(" hi") // hi
dump reverse("abc") // cba
dump reverse([1, 2, 3]) // [3, 2, 1]
dump sum([1, 2, 3, 4]) // 10
dump max(3, 9, 2) // 9
dump min(3, 9, 2) // 2
dump abs(-5) // 5
dump sqrt(16) // 4
dump pow(2, 10) // 1024
dump clamp(15, 0, 10) // 10
JSON
let j = json_parse("{\"user\": \"admin\", \"id\": 7}")
dump j.user // admin
dump j.id // 7
dump json_stringify(j) // {"user":"admin","id":7}
OSINT (where it started)
for port in scan_ports("127.0.0.1", { range: [0x0016, 0x0050] }) {
dump fmt("Open: 0x{:04X}", port)
}
dump md5("password")
dump sha256("secret")
dump hex_encode("ABC")
dump html_links(html)
Cryptography (native — no FFI)
hmac_sha256, AES-256-GCM (aes_gcm_encrypt / aes_gcm_decrypt), and Ed25519
(ed25519_keypair, ed25519_sign, ed25519_verify). All take and return
bytes/string and are available on both the interpreter and the VM.
dump hmac_sha256("key", "data")
let key = b"\x00\x01...\x1f" // 32 bytes
let nonce = b"\x00\x01...\x0b" // 12 bytes
let ct = aes_gcm_encrypt(key, nonce, b"payload")
dump aes_gcm_decrypt(key, nonce, ct) // b"payload"
let pair = ed25519_keypair(seed)
let sig = ed25519_sign(pair.1, b"msg")
dump ed25519_verify(pair.0, sig, b"msg") // true
DNS toolkit
Beyond dns_query/dns_build/dns_parse, the investigation API:
dns_resolve(host), dns_reverse(ip) (real PTR, v4 + v6), dns_records(host)
(all record types), and dns_walk(domain, prefixes).
dump dns_resolve("example.com") // all A + AAAA addresses
dump dns_reverse("8.8.8.8") // [dns.google]
for r in dns_records("example.com") { dump r.type }
dump dns_walk("example.com", ["www", "mail", "api"])
Process API
process_spawn(cmd, args), process_wait(pid), process_stdout(pid),
process_stderr(pid), process_kill(pid).
let pid = process_spawn("cmd", ["/c", "echo", "hi"])
process_wait(pid)
dump process_stdout(pid)
HTTP server (pull-based)
A minimal HTTP/1.1 server. Requests are queued and read on the main thread, so handlers can use arbitrary Rak logic (closures, etc.). Works on both backends.
http_server_start("127.0.0.1", 8080)
loop {
let req = http_server_poll() // nil when idle; never blocks
if req == nil { sleep(10) }
else {
if req.path == "/users" {
http_server_respond(req.id, 200, { "Content-Type": "application/json" }, "{\"ok\":true}")
} else {
http_server_respond(req.id, 404, {}, "not found")
}
}
}
http_server_start(addr, port) binds and queues parsed requests (returns the
bound port); http_server_poll() returns
{id, method, path, query, headers, body} or nil; http_server_respond(id, status, headers, body) writes the response; http_server_stop() cleans up.
String-keyed map literals ({ "Content-Type": "application/json" }) are now
supported for JSON-style maps.
WebSocket
RFC 6455 framing + handshake built on the existing TCP stream handle.
ws_connect(url) performs the client handshake; ws_handshake(stream) replies
to a client Upgrade request on a net_accept connection; ws_send(stream, data, mask) sends a text frame (client→server frames must be masked, servers pass
false); ws_recv(stream) returns {opcode, payload} or nil; ws_close(stream)
sends a close frame. Interpreter-only (the VM has no TCP layer).
// server: accept + handshake + echo
let list = net_listen("127.0.0.1:19001")
let (stream, _) = net_accept(list)
ws_handshake(stream)
loop {
let m = ws_recv(stream)
if m == nil { break }
if m.opcode == 1 { ws_send(stream, m.payload, false) }
}
// client
let ws = ws_connect("ws://127.0.0.1:19001/echo")
ws_send(ws, "hello", true)
dump string(ws_recv(ws).payload) // hello
Structured logging (machine-readable)
log_level(level), log_init(path?), and log_info / log_warn / log_error
/ log_debug. Each call emits one JSON line (default stdout, or append-only
file) — greppable with jq.
log_level("debug")
log_info("scan_start", { host: "127.0.0.1", ports: [80, 443] })
Secrets API
secret_get(name), secret_set(name, value), secret_persist(name, value)
(0600 file), secret_delete(name), secret_ls(). Resolution: session → env
var → durable store. Never logged.
secret_set("API_KEY", "abc123")
dump secret_get("API_KEY")
CLI
rakc run <file> Run a Rak script on the interpreter
rakc vm <file> Run on the bytecode VM
rakc build <file> Build a standalone executable
rakc bench <file> Benchmark interpreter vs VM
rakc check <file> Static type check; print diagnostics with line/column
rakc test [file] Run test blocks (--filter NAME, --verbose)
rakc lex <file> Print tokens
rakc parse <file> Print AST
rakc repl Start an interactive REPL
rakc lsp Start the language server (stdio)
rakc bindgen <h> Generate Rak bindings from a C header
rakc --version
rakpkg init [name] Create a new package
rakpkg add <user/repo> Add a package from GitHub
rakpkg install Install all dependencies
rakpkg run Run the entry point
rakpkg build Build to a standalone executable
rakpkg list List installed packages
rakpkg remove <name> Remove a package
Tooling
REPL
rakc repl starts an interactive session. State persists between lines, so variables and functions stay defined. Unclosed blocks continue on the next line. Commands start with :.
rak> let x = 10
rak> dump x
[DUMP] 10
rak> fn add(a, b) { return a + b }
rak> dump add(3, 4)
[DUMP] 7
rak> :ast 1 + 2
rak> :help
:vars shows defined variables. :ast <expr> prints the AST. :bytecode <expr> prints the compiled chunk. :clear resets state. :quit exits.
Language server
rakc lsp is a stdio language server. It reports parser and lexer diagnostics, offers keyword, type, and builtin completion, shows hover text for identifiers, and jumps to fn, let, struct, and enum definitions.
Build it with cargo build --release --features lsp. The vscode-rak/ directory has a VS Code extension with a TextMate grammar that pairs with it. For Neovim, point nvim-lspconfig at rakc lsp for .rak files.
C header bindgen
rakc bindgen header.h -o bindings.rak reads a C header and generates a Rak file with a function per C function (calling extern_call) and a struct per C struct. Pointers map to u64, char* maps to string, numeric types map to i8 through u64 and f32/f64.
cargo build --release --features bindgen
rakc bindgen sdl.h -o sdl.rak
The generated functions call extern_call, which returns an error until a Rust wrapper is linked into the standard library. The wrapper is where the actual FFI linking happens.
Platform support
Windows and Linux. On Windows, the GUI uses WebView2 (ships with Edge) and is display-only; Linux GUI does not work in 8.0.0. Note that the feature is per-crate, so it needs -p rakc --features gui, not a workspace-wide --features gui. rakc build produces .exe on Windows and an executable with chmod 755 on Linux. The IDE ships as NSIS/MSI on Windows and .deb/AppImage on Linux via GitHub Actions CI, alongside the custom rak-setup installer (net-install + offline bundle) on both.
Project structure
Rak/
├── rakc/ Compiler, interpreter, VM, GUI
│ └── src/
│ ├── lexer.rs Tokenizer (logos)
│ ├── parser.rs Recursive descent parser
│ ├── ast.rs AST definitions
│ ├── interpreter.rs Tree-walking interpreter + stdlib builtins
│ ├── bytecode.rs Opcode set and chunk
│ ├── compiler.rs AST to bytecode
│ ├── vm.rs Stack-based bytecode VM
│ ├── gui.rs WebView2/WebKitGTK window management
│ ├── repl.rs Interactive REPL
│ ├── lsp.rs Language server
│ └── bindgen.rs C header bindgen
├── stdlib/ Rust native stdlib (net, crypto, encoding, recon, web, file, json, log, process, secrets, http_server, websocket)
├── rakpkg/ Package manager CLI
├── rak-setup/ Custom interactive installer (TUI wizard: net-install + offline)
├── dist/ One-liner bootstraps (install.sh / install.ps1), man pages, completions
├── ide/ Tauri + Next.js IDE
├── .github/workflows/ CI (Linux .deb + AppImage builds)
├── examples/
│ ├── hello.rak
│ ├── quick.rak
│ ├── osint_scan.rak
│ ├── echo_server.rak TCP echo server
│ ├── sql_server.rak SQL server in Rak
│ ├── sql_client.rak
│ ├── self_host.rak Rak interpreter in Rak
│ ├── bench.rak VM benchmark
│ ├── gui_demo.rak GUI window demo
│ ├── json_demo.rak JSON parse and stringify
│ ├── closures.rak Lexical closures and recursion
│ ├── vm_features.rak Map, tuple, index, interp, match on the VM
│ ├── pipeline.rak Pipeline operator (|>)
│ ├── regex.rak Regex literals and matching
│ ├── binary_patterns.rak Binary byte-pattern matching
│ ├── traits.rak Display/Iterable/Index trait protocols
│ ├── ffi.rak FFI: extern bindings + raw memory (interpreter + VM)
│ ├── ffi_dynamic.rak FFI dynamic loader (lib.call/lib.sym/lib.close)
│ ├── mmap.rak Memory-mapped files: zero-copy slice/search/lines
│ ├── async.rak Async event loop: async fn / await / tcp_probe
│ ├── net_raw.rak Raw sockets: forge IPv4/TCP/UDP packets
│ ├── parsers.rak DNS / TLS / PCAP wire-format parsers
│ ├── macros.rak Compile-time macros: macro / name! / const
│ ├── import_demo.rak Python-style import / from / export (with mymod/ package)
│ ├── forensic_structs.rak binstruct decode/encode round-trip + evidence provenance
│ └── stdlib_demo.rak String, array, and math builtins
Build from source
git clone https://github.com/Louiml/Rak.git
cd Rak
cargo build --release # rakc + rakpkg
cargo build --release -p rakc --features gui # rakc with GUI support
cd ide && npm install && npx tauri build # IDE
Linux requires libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev, and librsvg2-dev.
Tech stack
The compiler is Rust. Logos handles lexing. The parser is hand-written recursive descent. Two backends: a tree-walking interpreter and a stack-based bytecode VM.
The standard library uses ureq for HTTP, scraper for HTML parsing, md-5/sha1/sha2 for hashing, std::net for TCP, std::thread and mpsc for concurrency, libloading for FFI, memmap2 for memory-mapped files, and tokio for the async event loop. Real JSON via serde_json.
The IDE is Tauri v2, Next.js, TypeScript, and Tailwind CSS. It runs Rak scripts as child processes, streams output, and has an integrated terminal.
License
Apache-2.0
Author
Louiml (Ryuzaki)