All Work

Rak

Public

A general-purpose programming language built mainly for cybersecurity usage, written in Rust.

Rust 2026 Updated 2 hours ago Programming LanguageCompilerCybersecurityIDE louiml.github.io/Rak/

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.

README.md

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 full exceptionInfo — line-granular breakpoint gating via a last-hit-line guard. Wire it up from VS Code (the bundled rak extension), 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: in operator — 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 and let mut forms; arity mismatches raise a catchable error.
  • Slice and negative indexing — a[1..3], a[..2], a[3..], a[-2..] slices, a[-1] reads and a[-1] = v writes from the end, with out-of-bounds raises on both backends.
  • The VM caught up. rakc vm now runs what the interpreter runs: try/catch and expr? (structured errors, LIFO defers before the handler), method dispatch on structs/enums (obj.method(...) via impl blocks), struct literals and enum constructors, and the full pattern language in match — 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 T with fn add(self, o)-style methods, dispatched on both backends before the built-in arithmetic (a + b, a == b, a < b, -a on your types).
  • Assignment on the VM — arr[i] = v, map[k] = v, s.field = v, x += 1, and multi-assign a, b = x, y (copy-on-write, mutability rules enforced).
  • Iterator builtins — zip, enumerate, skip, fold, reduce, any, all, flat_map, take_while, plus keys/values/has/get, on both backends.
  • for (k, v) in map and indexed for (i, x) in arr work on the VM (Op::IterItems materializes any iterable; maps always yield pairs).
  • if let / while let / do { } while / labeled break/continue ('outer: for ... { break 'outer }) run on the VM.
  • binstruct bitfields — u4-style non-byte-aligned fields, packed LSB-first and round-tripped through decode/encode on both backends.
  • rakc fmt — AST-based formatter (--write, --check for CI).
  • rakc lint — advisory checks (unused vars, shadowing, unreachable code, missing pub fn return types, duplicate imports; --deny for 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, chaining parent to 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_close are declared but do nothing yet. gui.rs stores HashMap<i64, ()> and throws away the Window and WebView handles, so there is nothing to update.
  • gui_callback and window.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 run calls process::exit when the last window closes. gui_wait() does not return and code after it is unreachable.
  • Linux does not work: gui_open builds 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)