summaryrefslogtreecommitdiff
path: root/README.org
blob: bd6c473a63d2a03dcf058c19300037fb771c05f8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#+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.