summaryrefslogtreecommitdiff
path: root/README.org
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-06-18 14:19:24 +0200
committercashmere <cashmere@autistici.org>2026-06-18 14:19:24 +0200
commit6eb05a7c25c0c3796bdc5564c035395c5e46c570 (patch)
tree4cb3397fabe155dff549c44f9ce7a2c357e0194f /README.org
parent9d99f7e7da8e349ea30719ed204ad87f1e34fdad (diff)
downloadcsh-6eb05a7c25c0c3796bdc5564c035395c5e46c570.tar.gz
initialize base structureHEADmaster
Diffstat (limited to 'README.org')
-rw-r--r--README.org264
1 files changed, 263 insertions, 1 deletions
diff --git a/README.org b/README.org
index 5f602ba..bd6c473 100644
--- a/README.org
+++ b/README.org
@@ -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.