Skip to content

vesche/a

Repository files navigation

a

A programming language by AI, for AI.

Every design choice -- grammar, keywords, type system, builtins -- optimizes for what matters when AI writes and reads code: zero ambiguity, explicit data flow, token efficiency, and batteries included.

Quick Start

./build.sh                  # bootstrap the native CLI + language server
./a run program.a           # compile and run in one step
./a build program.a -o out  # compile to native binary
./a test tests/native/      # run test suite
./a cc program.a            # emit C to stdout
./a-lsp                     # language server (JSON-RPC over stdio)

Build

Requirements: gcc (or any C99 compiler). Supported platforms: Linux x86_64, Linux arm64, macOS arm64.

Pre-built binaries are available on the Releases page.

# Option 1: Bootstrap from C (no Rust required)
./bootstrap/build.sh        # gcc only -- builds ./a from pre-generated C

# Option 2: Full build (requires Rust)
./build.sh                  # builds ./a from source via the Rust VM

# Option 3: Rust VM only
cargo build --release

Usage

# Native CLI (fast, self-contained)
./a run program.a           # compile and run (cached -- instant on repeat)
./a build program.a         # compile to native binary
./a build program.a -o out  # compile to native binary with custom name
./a eval "2 + 3"            # evaluate an expression
./a cc program.a            # emit C to stdout
./a test tests/native/      # find test_*.a files, compile, run, report
./a lsp                     # build the language server binary (./a-lsp)
./a cache clean             # clear the compilation cache
./a-lsp                     # start language server (stdio, for editors)

# Shebang support
#!/usr/bin/env a run        # add to top of .a file, chmod +x, run directly

# Rust VM
a run program.a             # execute (bytecode VM)
a test tests/test_re.a      # run test_* functions with pass/fail reporting
a check program.a           # type check
a fmt program.a             # format
a ast program.a             # dump AST as JSON

The language in 60 seconds

use std.strings

ty DirEntry = {name: str, is_dir: bool}

fn find_files(dir: str, ext: str) -> [str] effects [io] {
  let entries: [DirEntry] = fs.ls(dir)
  let mut out = []
  for e in entries {
    let path = str.concat(str.concat(dir, "/"), e.name)
    if e.is_dir {
      for f in find_files(path, ext) {
        out = push(out, f)
      }
    } else {
      if str.ends_with(e.name, ext) {
        out = push(out, path)
      }
    }
  }
  ret out
}

fn main() -> void effects [io] {
  let files = sort(find_files(".", ".a"))
  println("found {len(files)} files")
  for f in files {
    let result = try { io.read_file(f)? }
    if is_ok(result) {
      let lines = len(str.lines(unwrap(result)))
      let short = str.replace(f, "./", "")
      println("  {strings.pad_right(short, 30, \" \")} {lines} lines")
    }
  }
}

This is real code. It runs. It recursively walks a directory, reads files, counts lines, and prints a formatted report.

What it does

130+ builtins covering everything an agent needs (plus native compilation to C):

Domain Operations
Filesystem fs.ls, fs.mkdir, fs.rm, fs.mv, fs.cp, fs.glob, fs.exists, fs.is_dir, fs.is_file, fs.cwd, fs.abs, io.read_file, io.write_file
HTTP client http.get, http.post, http.put, http.patch, http.delete (returns {status, body, headers}) -- in-process via POSIX sockets + platform TLS (macOS SecureTransport, Linux OpenSSL)
HTTP streaming http.stream(url, body, headers), http.stream_read(h), http.stream_close(h) -- incremental line-by-line response reading for SSE/streaming APIs
WebSocket ws.connect(url), ws.send(h, msg), ws.recv(h), ws.close(h) -- RFC 6455 client with masking, ping/pong, ws:// and wss://
HTTP server http.serve(port, handler) -- POSIX sockets, closure handler receives {method, path, headers, body}, returns {status, headers, body}; http.serve_static(port, dir) for file serving
Database db.open(path) (or ":memory:"), db.exec(db, sql), db.query(db, sql, params) with ? binding, db.close(db) -- bundled SQLite, zero setup
Shell exec(cmd) returns {stdout, stderr, code}
Subprocess proc.spawn(cmd), proc.write(h, data), proc.read_line(h), proc.kill(h) -- bidirectional pipe I/O with long-lived child processes
MCP mcp.server, mcp.add_tool, mcp.add_resource, mcp.serve (server); mcp.connect, mcp.list_tools, mcp.call_tool, mcp.close (client) -- JSON-RPC 2.0 over stdio (via stdlib)
JSON json.parse, json.stringify, json.pretty
Data formats yaml.parse/stringify, toml.parse/stringify, html.parse/select/text, url.parse/build/encode/decode (via stdlib modules)
LLM client llm.chat(provider, model, messages, options) -- unified API for OpenAI, Anthropic, Google AI with retry, tool use, normalized responses; llm.stream(provider, model, messages, on_chunk, options) for token-by-token streaming (via stdlib)
Strings str.split, str.join, str.contains, str.replace, str.trim, str.upper, str.lower, str.starts_with, str.ends_with, str.chars, str.slice, str.lines (14 ops)
Arrays sort, reverse_arr, contains, push, slice, map, filter, reduce, each, sort_by, find, any, all, flat_map, min_by, max_by, enumerate, zip, take, drop, unique, chunk, len
Maps map.get, map.set, map.keys, map.values, map.has
Error handling try { }, ? operator, Ok/Err constructors, unwrap, unwrap_or, is_ok, is_err, expect, pattern matching on Results
Concurrency spawn, await, await_all, parallel_map, parallel_each, timeout
Process args(), exit(code), eprintln()
Stdin io.read_stdin(), io.read_line(), io.read_bytes(n), io.flush()
Environment env.get, env.set, env.all
Compression compress.deflate, compress.inflate (raw deflate), compress.gzip, compress.gunzip (gzip format) -- via bundled miniz
UUID uuid.v4() -- cryptographic random UUID v4 generation via /dev/urandom
Signals signal.on(name, handler) -- register handlers for SIGINT, SIGTERM, SIGHUP, SIGUSR1, SIGUSR2 (native CLI only)
Introspection type_of, int, float, to_str, char_code, from_code, is_alpha, is_digit, is_alnum

Standard library with 24 modules:

use std.math                  # max, min, clamp, pow, sum, range
use std.strings               # repeat, pad_left, pad_right, reverse, center
use std.testing               # assert_eq, assert_true, report
use std.cli                   # red, green, yellow, bold, dim (ANSI colors)
use std.re                    # match_full, test, search, find_all, replace, split
use std.meta                  # parse, emit, walk, search, transform, analyze, generate
use std.path                  # join, dirname, basename, extension, stem, normalize
use std.datetime              # now, timestamp, format, iso, add_days, diff_ms
use std.hash                  # sha256, md5, sha256_file, quick
use std.encoding              # base64_encode, base64_decode, hex, url_encode
use std.csv                   # parse, parse_records, stringify, escape_field
use std.yaml                  # parse, stringify -- YAML 1.2 subset (mappings, sequences, scalars)
use std.toml                  # parse, stringify -- TOML (tables, arrays of tables, all value types)
use std.html                  # parse, select, text -- HTML DOM tree with CSS selector queries
use std.url                   # parse, encode, decode, build -- full URL structure parsing
use std.llm                   # chat(provider, model, msgs, opts) -- unified LLM client (OpenAI, Anthropic, Google)
use std.mcp                   # MCP server + client -- JSON-RPC 2.0 over stdio, tool/resource registration
use std.agent                 # retry, batch, pipeline, timeout, rate_limit -- operational primitives
use std.log                   # info, warn, error, debug, set_level -- structured JSON logging to stderr
use std.uuid                  # v4() -- UUID generation
use std.args                  # spec, flag, option, positional, parse -- declarative CLI argument parsing
use std.template              # render(template, vars) with {{var}}, {{#if}}, {{#each}}
use std.compiler.lexer        # tokenize "a" source into token arrays
use std.compiler.parser       # parse token arrays into tagged-map ASTs
use std.compiler.ast          # AST node constructors and accessors
use std.compiler.compiler     # compile ASTs to bytecode
use std.compiler.cgen          # compile ASTs to C source code (native compilation)
use std.compiler.emitter      # pretty-print ASTs back to "a" source
use std.compiler.serialize    # serialize/deserialize compiled programs
use std.lexer                 # legacy tokenizer

Self-hosting

The "a" compiler and CLI are fully self-hosting. The native ./a binary compiles its own source code to produce a working copy of itself:

./build.sh                            # bootstrap: VM -> C -> native ./a
./a build src/cli.a -o a2             # self-hosting: ./a compiles itself
./a2 build src/cli.a -o a3            # a2 compiles itself too
./a3 run examples/hello.a            # a3 works

The C code generator compiles itself -- including the lexer, parser, and AST modules -- into ~7,900 lines of C with reference-counted ownership, goto-based cleanup epilogues, and 130+ native builtins. gcc compiles that C into a freestanding native binary with zero Rust dependency. A pre-generated bootstrap/cli.c is committed to the repo, so a clean checkout can build the language with just gcc -- no Rust or cargo required. All 22 standard library modules compile natively. Closures, lambdas, HOFs, pattern matching, try/catch, destructuring, I/O, module imports, the pipe operator, C FFI (extern fn), memory management, SHA-256/MD5 hashing, HTTP client, JSON stringify, compression (deflate/gzip), subprocess pipes, and POSIX time/fs/env all compile natively. Clean under AddressSanitizer.

Fixed point reached: the native compiler compiles its own source and produces byte-identical output. The language exists independently.

Module precompilation: use statements automatically cache compiled bytecode to .ac files, making subsequent imports near-instant.

Metaprogramming toolkit: std.meta provides parse, emit, walk, search, transform, analyze, and generate -- full programmatic access to the AST for code generation, refactoring, and analysis.

Native compilation

"a" programs compile to native executables through C code generation. The code generator (std/compiler/cgen.a) is written entirely in "a" -- it uses the self-hosted parser to produce an AST, walks it, and emits equivalent C with automatic memory management. All values are reference-counted with ownership semantics: zero-initialized locals, retain on copy, release at scope exit via goto-based cleanup epilogues (single cleanup label per function, 44% code reduction vs inline release). Lambdas are lifted to top-level C functions with captured environment arrays. Error handling uses setjmp/longjmp for try/? semantics with correct tail-expression capture. C FFI via extern fn declarations generates type-marshalling shim wrappers automatically. The C runtime (~3,700 lines) includes POSIX I/O, filesystem ops, shell execution, subprocess pipes, JSON parse/stringify, SHA-256/MD5 hashing, HTTP/1.1 client (in-process POSIX sockets + platform TLS), HTTP streaming, WebSocket client (RFC 6455), HTTP server (POSIX sockets), SQLite (bundled amalgamation), zlib-compatible compression (bundled miniz), POSIX time, environment management, arena allocator, and mark-and-sweep GC.

164x faster: fib(35) runs in 0.17s native vs 28s on the bytecode VM.

Concurrency

Structured concurrency with isolated task parallelism. No shared mutable state -- each spawned task runs in its own VM with a copy of the program:

fn fib(n: int) -> int {
  if n < 2 { ret n }
  ret fib(n - 1) + fib(n - 2)
}

fn main() -> void {
  let results = parallel_map([35, 36, 37, 38], fn(n) { ret fib(n) })
  println("results: {results}")

  let h = spawn(fn() { ret fib(40) })
  let val = await(h)
  println("fib(40) = {val}")

  let result = timeout(1000, fn() { ret fib(30) })
  println("with timeout: {result}")
}

Design for machines

Principle Implementation
Unambiguous grammar LL(1), context-free, explicit delimiters, no operator precedence surprises
Explicit where it matters Effect system (effects [io]), function signatures, pre/post contracts; type inference handles the rest
Token efficient 2-3 char keywords (fn, ty, let, ret, mut), type inference, multi-line expressions
Local reasoning Module functions are self-contained, complete signatures, no implicit imports
Structured errors All errors are JSON: {"kind":"TypeError","message":"...","span":{"line":5,"col":3}}
Canonical formatting a fmt produces one true form, no style debates

Tools written in "a"

Tool Lines Description
examples/eval.a 1036 full self-interpreter with imports, interpolation, module loading
examples/boot.a 686 bootstrap interpreter -- "a" interpreting itself interpreting itself
examples/inspect.a 449 code intelligence -- call graphs, dead code, complexity
examples/self_lexer.a 420 tokenizer for "a", written in "a"
examples/parse_file.a 382 recursive descent parser for "a"
examples/lines.a 263 universal line processor for Unix pipelines
examples/project_stats.a 199 codebase analyzer with composition charts
examples/todo_scan.a 145 finds TODO/FIXME/HACK annotations across a codebase
examples/search.a 105 regex-powered file search with colored output
examples/resilient.a 91 error-resilient HTTP workflow with retries
examples/api_workflow.a 61 multi-step API orchestration
examples/parallel_fetch.a 54 concurrent URL fetching with timeouts
examples/site_gen.a 100 static site generator using path, datetime, hash, csv, template, encoding (runs natively)
examples/api.a 20 JSON API server -- HTTP server with routing, JSON responses, echo endpoint
examples/crud.a 40 CRUD API -- HTTP + SQLite with parameterized queries, create/read/delete users
examples/test_formats.a 115 tests for YAML, TOML, HTML, URL modules -- round-trip, selectors, edge cases
examples/chat.a 20 LLM chat -- one-shot conversation with any provider in ~15 lines
examples/stream_chat.a 25 streaming LLM -- token-by-token streaming chat with live output
examples/mcp_server.a 33 MCP server -- file search tool with tool + resource registration, JSON-RPC stdio
examples/mcp_client.a 55 MCP client -- connects to any MCP server, lists tools, calls first tool
examples/cli_demo.a 27 CLI parsing -- declarative argument parsing with flags, options, positionals, auto-help
examples/agent.a 60 agentic loop -- tool-using LLM agent: define tools, handle calls, iterate
examples/test_llm.a 130 tests for LLM module internals -- request building, response parsing, tool calls
examples/gen_tests.a 46 metaprogramming: auto-generate test scaffolds from source
src/cli.a ~175 native CLI driver -- run, build, cc, test, lsp subcommands; self-hosting (compiles itself)
src/lsp.a ~720 language server -- LSP over stdio with diagnostics, completion (105+ builtins), hover, go-to-definition (cross-module)
std/compiler/cgen.a ~1,870 C code generator -- self-hosting native compiler via C with memory management (closures, HOFs, pipes, try/catch, destructuring, spread, pattern matching, I/O, module inlining, import aliases, retain/release, goto cleanup, 105+ builtins, three-stage bootstrap)

Stats

Rust runtime ~10,000 lines across 8 modules
C runtime ~3,800 lines (runtime.h + runtime.c) + bundled SQLite3, miniz
"a" source ~19,500 lines across 93 files
Standard library 24 modules, 400+ functions, ~8,700 lines
Test suites 30 suites + cgen test script, 510+ native tests, ~4,500 lines
Examples & tools 36 programs, ~6,200 lines

Editor support

Language server: ./a-lsp is a native LSP server written in "a" itself. It provides:

  • Diagnostics -- parse errors on every keystroke (red squiggles)
  • Completion -- 130+ builtins with signatures, keywords, user functions, 20+ stdlib modules
  • Hover -- function signatures for builtins and user-defined functions
  • Go-to-definition -- in-file and cross-module (resolves use imports to source files)

Build with ./build.sh or ./a lsp, then configure your editor to run ./a-lsp as the language server for *.a files.

Syntax highlighting for VS Code / Cursor is included in editors/vscode. See REFERENCE.md for the complete language reference.

About

a programming language

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors