diff options
| author | cashmere <cashmere@autistici.org> | 2026-06-18 14:19:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-06-18 14:19:24 +0200 |
| commit | 6eb05a7c25c0c3796bdc5564c035395c5e46c570 (patch) | |
| tree | 4cb3397fabe155dff549c44f9ce7a2c357e0194f /README.org | |
| parent | 9d99f7e7da8e349ea30719ed204ad87f1e34fdad (diff) | |
| download | csh-master.tar.gz | |
Diffstat (limited to 'README.org')
| -rw-r--r-- | README.org | 264 |
1 files changed, 263 insertions, 1 deletions
@@ -1 +1,263 @@ -#+title: README +#+title: csh +#+author: cashmere +#+property: header-args:c3 :mkdirp yes :comments no + +* Overview + +A C3 playground. =c3c= compiles source files directly, with no =project.json=. =just= drives the builds, =shell.nix= pins the toolchain through =npins=. The code blocks in this file tangle to =src/= with =org-babel-tangle=. + +* Quick start + +#+begin_src sh +nix-shell # c3c, just, valgrind, pinned via npins +just run # tangle, then compile and run src/main.c3 +#+end_src + +* Layout + +#+begin_example +. +├── README.org # source: prose and c3 blocks +├── shell.nix # toolchain: c3c, just, valgrind, pinned via npins +├── npins/ # nixpkgs pin (same revision as the nixos config) +├── .justfile # tangle, build, run, sanitizer targets +├── src/ # tangled c3 (generated, gitignored) +└── build/ # output binaries (gitignored) +#+end_example + +* Targets + +| Recipe | What it does | +|----------------+----------------------------------------------------------| +| just tangle | regenerate src/*.c3 from this README | +| just run | tangle, then compile and run src/main.c3 | +| just build | safe debug build (-O0 -g, bounds, null, overflow checks) | +| just release | -O5, all checks off, no backtrace | +| just asan | run under AddressSanitizer (use-after-free, leaks, OOB) | +| just tsan | run under ThreadSanitizer (data races) | +| just example X | compile and run a named example, e.g. just example tcp | +| just valgrind | build, then run under valgrind | +| just clean | remove build/ and the generated src/ | + +* The program + +=src/main.c3= covers iteration and error handling. + +Module and imports: + +#+begin_src c3 :tangle src/main.c3 +module csh; + +import std::io; +#+end_src + +** foreach and ranges + +=foreach= gives the index first, the value second. =&= binds the value by reference, =foreach_r= iterates backwards. There is no numeric range loop, so a macro with a trailing =@body= block adds one: + +#+begin_src c3 :tangle src/main.c3 +macro @range(usz start, usz end; @body(i)) { + for (usz i = start; i < end; i++) @body(i); +} + +macro @step(usz start, usz end, usz by; @body(i)) { + for (usz i = start; i < end; i += by) @body(i); +} +#+end_src + +Packaged as a =.c3l= library, helpers like these carry across projects. + +** Errors + +Each function returns an Optional (=T?=) and propagates failure with =!=. A single handler at the top sinks it. =~= turns a fault into an Optional, =??= supplies a default. + +#+begin_src c3 :tangle src/main.c3 +faultdef BAD_PORT; + +fn int? parse_port(String s) { + int port = s.to_int()!; + if (port < 1 || port > 65535) return BAD_PORT~; + return port; +} +#+end_src + +=tour= propagates, it never inspects an error itself: + +#+begin_src c3 :tangle src/main.c3 +fn void? tour() { + String[] protocols = { "irc", "ssh", "tcp" }; + foreach (i, p : protocols) io::printfn("%d: %s", i, p); + + @range(0, 3; usz n) io::printfn("range %d", n); + @step(0, 10, 2; usz n) io::printfn("step %d", n); + + int port = parse_port("6667")!; + io::printfn("port %d ok", port); +} +#+end_src + +=main= is the single sink. Replace =6667= with =99999= or =nope= to take the failure path: + +#+begin_src c3 :tangle src/main.c3 +fn int main() { + if (catch err = tour()) { + io::eprintfn("error: %s", err); + return 1; + } + return 0; +} +#+end_src + +* Iterating your own types + +=foreach= works on any type implementing =@operator(len)= and =@operator([])=. Add =@operator(&[])= for =foreach (&x : ...)=. + +#+begin_src c3 :tangle src/iter.c3 +module csh::iter; + +import std::io; + +struct Ring { + int[4] data; +} + +fn usz Ring.len(&self) @operator(len) => 4; +fn int Ring.get(&self, usz i) @operator([]) => self.data[i]; + +fn void main() { + Ring r = { { 10, 20, 30, 40 } }; + foreach (i, v : r) io::printfn("ring[%d] = %d", i, v); + + int[3] xs = { 1, 2, 3 }; + foreach (&x : xs) *x *= 10; + foreach_r (x : xs) io::printfn("rev %d", x); +} +#+end_src + +* Collections + +Generic, allocator explicit, freed with =defer=. Pass =mem= for the heap or =tmem= for the temp arena. + +#+begin_src c3 :tangle src/store.c3 +module csh::store; + +import std::io; +import std::collections::list; +import std::collections::map; + +fn void main() { + List{String} names; + names.init(mem); + defer names.free(); + names.push("cashmere"); + names.push("tsoding"); + foreach (n : names) io::printfn("name: %s", n); + + HashMap{String, int} ages; + ages.init(mem); + defer ages.free(); + ages["cashmere"] = 27; + io::printfn("age: %d", ages["cashmere"]!!); +} +#+end_src + +* Crypto and random + +=std::math::random= covers PRNGs, =std::hash::sha256= covers digests. Both ship in the standard library. + +#+begin_src c3 :tangle src/digest.c3 +module csh::digest; + +import std::io; +import std::hash::sha256; +import std::math::random; + +fn void main() { + int roll = random::rand(6) + 1; + io::printfn("dice: %d", roll); + + char[32] sum = sha256::hash("cashmere"); + io::print("sha256(cashmere) = "); + foreach (b : sum) io::printf("%02x", b); + io::printn(""); +} +#+end_src + +* Networking + +A one-shot TCP echo server on =std::net::tcp=. Client and server both live in the standard library. Run =just example tcp=, then talk to it with =nc 127.0.0.1 6667=. This is the skeleton for an IRC or telnet bot. + +#+begin_src c3 :tangle src/tcp.c3 +module csh::echo; + +import std::io; +import std::net::tcp; + +fn void? serve() { + TcpServerSocket server = tcp::listen("127.0.0.1", 6667, 10)!; + io::printn("listening on 127.0.0.1:6667"); + + TcpSocket sock = tcp::accept(&server)!; + char[256] buf; + usz n = sock.read(&buf)!; + sock.write(buf[:n])!; + sock.close()!; +} + +fn int main() { + if (catch err = serve()) { + io::eprintfn("error: %s", err); + return 1; + } + return 0; +} +#+end_src + +* Safety + +C3 does not check memory safety at compile time. Bugs are caught at runtime through sanitizers, and prevented through convention, the way curl stays correct: strict conventions and CI that runs every sanitizer. + +The sanitizers are LLVM backed and one flag away: + +#+begin_src sh +c3c compile-run src/main.c3 --sanitize=address # use-after-free, double-free, OOB, leaks +c3c compile-run src/main.c3 --sanitize=thread # data races +c3c compile-run src/main.c3 --sanitize=memory # reads of uninitialised memory +#+end_src + +AddressSanitizer includes LeakSanitizer, so =just asan= covers both use-after-free and leaks. =--safe=yes= (the default at =-O0= and =-O1=) adds bounds, null, overflow and contract checks. + +Develop in safe mode. Gate CI on =just asan= and =just tsan=. Keep =tmem= and =@pool= allocations inside their scope, and pair every =mem= allocation with =defer free()=. + +* Standard library + +C3 ships these: + +| Need | Module | +|-------------+-----------------------------------------------------------------| +| random | std::math::random (PCG, Xoshiro), std::crypto::entropy (CSPRNG) | +| crypto | std::crypto (AES, ChaCha20, Ed25519, Argon2, Keccak) | +| hashing | std::hash (sha256, sha512, blake3, hmac, siphash, crc32) | +| tcp/udp | std::net::tcp, std::net::udp (client and server, blocking) | +| json/base64 | std::encoding | +| collections | std::collections (list, hashmap, deque, channels) | +| threads | std::threads (pools and channels) | + +No HTTP and no TLS ship in std. For those, bind a C library. + +* C interop + +C3 is ABI compatible with C. Bind a library with =extern fn= declarations and a =-l= link flag. + +#+begin_src c3 +extern fn void puts(char*); +extern fn void init_window(int w, int h, char* t) @extern("InitWindow"); +#+end_src + +#+begin_src sh +c3c compile-run src/main.c3 -l raylib -l m -l dl -l pthread -l GL -l X11 +#+end_src + +* Notes + +Targets =c3c= 0.8.x. C3 is pre-1.0, so fault and Optional syntax shifts between releases. If a block stops compiling, check it against the docs for the pinned version. |
