-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
815 lines (727 loc) · 42.7 KB
/
Copy pathindex.html
File metadata and controls
815 lines (727 loc) · 42.7 KB
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Skunk Language Reference</title>
<meta name="description" content="Skunk Language Reference">
<link rel="stylesheet" href="styles.css">
<script defer src="app.js"></script>
</head>
<body>
<div class="site-shell">
<aside class="sidebar">
<div class="brand">
<p class="eyebrow">Experimental Language Docs</p>
<h1>Skunk</h1>
<p class="brand-subtitle">Language Reference</p>
</div>
<label class="search-label" for="nav-filter">Jump to a section</label>
<input id="nav-filter" class="nav-filter" type="search" placeholder="Filter sections">
<nav id="doc-nav" class="doc-nav" aria-label="Reference navigation">
<a href="#overview">Overview</a>
<a href="#status">Status</a>
<a href="#compiler-notebook">Compiler Notebook</a>
<a href="#install">Install</a>
<a href="#toolchain">Toolchain</a>
<a href="#building-an-app">Building an App</a>
<a href="#c-interop">C Interop</a>
<a href="#standard-library">Standard Library</a>
<a href="#native-tests">Native Tests</a>
<a href="#programs-and-modules">Programs and Modules</a>
<a href="#types">Types</a>
<a href="#bindings-and-const">Bindings and Const</a>
<a href="#functions-and-control-flow">Functions and Control Flow</a>
<a href="#arrays-and-slices">Arrays and Slices</a>
<a href="#structs-and-methods">Structs and Attach</a>
<a href="#pointers-allocators-and-arenas">Pointers, Allocators, and Arenas</a>
<a href="#unsafe-memory">Unsafe Memory</a>
<a href="#windowed-2d">Windowed 2D</a>
<a href="#generics">Generics</a>
<a href="#enums-and-match">Enums and Match</a>
<a href="#traits-and-impls">Traits, Conform, Shapes</a>
<a href="#patterns-and-destructuring">Patterns and Destructuring</a>
<a href="#current-limitations">Current Limitations</a>
<a href="#design-notes">Design Notes</a>
</nav>
<div class="sidebar-note">
<p>Human-designed.</p>
<p>AI-implemented.</p>
<p>Not for critical software.</p>
</div>
</aside>
<main class="content">
<header id="overview" class="hero">
<span class="badge">LLVM-first</span>
<h1>Skunk Language Reference</h1>
<p class="lead">
Skunk is a human-designed, AI-implemented experimental programming language.
This reference covers the implemented language surface in the current compiler/runtime.
</p>
<div class="callout-grid">
<div class="callout">
<h2>Project Shape</h2>
<p>Skunk is a language-design project and compiler playground targeting LLVM.</p>
</div>
<div class="callout warning">
<h2>Safety Note</h2>
<p>Skunk is experimental and should not be used for critical, safety-sensitive, or high-reliability software.</p>
</div>
</div>
</header>
<section id="status">
<h2>Status</h2>
<p>Skunk uses one execution path: native compilation through LLVM and <code>clang</code>.</p>
<ul>
<li>Source-level syntax is defined in <code>src/syntax/grammar.pest</code>.</li>
<li>Implemented behavior is backed by parser, type-checker, source-loader, and compiler tests.</li>
<li>This page documents the currently implemented language surface rather than future ideas.</li>
</ul>
</section>
<section id="compiler-notebook">
<h2>Compiler Notebook</h2>
<p>If you are new to compilers or new to LLVM, the repository now includes a slower, beginner-oriented guide to how Skunk is built.</p>
<ul>
<li><a href="compiler-booklet.html">Compiler booklet</a>: print-friendly HTML guide with diagrams and a worked example traced through the pipeline.</li>
<li><a href="compiler-notebook.md">Compiler notebook, Part 1</a>: high-level introduction to the architecture and reading order.</li>
<li><a href="compiler-notebook-part2.md">Compiler notebook, Part 2</a>: one small Skunk program followed through parsing, checking, layouts, LLVM lowering, and native build.</li>
<li><a href="compiler-notebook-part3.md">Compiler notebook, Part 3</a>: practical guide to extending Skunk feature by feature.</li>
</ul>
</section>
<section id="install">
<h2>Install</h2>
<p>Skunk runs on macOS and Linux. It needs <code>clang</code> to link native executables: on macOS run <code>xcode-select --install</code>; on Linux install <code>clang</code> with your package manager.</p>
<p>Install the latest release with one command:</p>
<pre><code class="language-bash">curl -fsSL https://dmgcodevil.github.io/skunk/install.sh | sh</code></pre>
<p>The script detects your OS and architecture, downloads the prebuilt binary from <a href="https://github.com/dmgcodevil/skunk/releases">GitHub Releases</a>, verifies its checksum, and installs it to <code>~/.skunk/bin</code>. Set <code>SKUNK_VERSION=v0.1.0</code> before the command to pin a specific version.</p>
<p>Alternatively, download a tarball from the releases page manually, or build from source with Rust:</p>
<pre><code class="language-bash">cargo install --git https://github.com/dmgcodevil/skunk</code></pre>
<p>Verify the installation:</p>
<pre><code class="language-bash">skunk --help</code></pre>
</section>
<section id="toolchain">
<h2>Toolchain</h2>
<p>Build Skunk with Rust, then compile a Skunk entry file into a native executable.</p>
<pre><code class="language-bash">cargo build
cargo run -- compile path/to/main.skunk ./out
./out</code></pre>
<p>Compile and run through a temporary native executable with either command:</p>
<pre><code class="language-bash">cargo run -- path/to/main.skunk</code></pre>
<pre><code class="language-bash">cargo run -- run path/to/main.skunk</code></pre>
</section>
<section id="building-an-app">
<h2>Building an App</h2>
<p>Scaffold a project with <code>skunk new</code>. It creates a manifest, an entry file with a hello-world <code>main</code>, and a starter test:</p>
<pre><code class="language-bash">skunk new demo
cd demo
skunk build # compiles src/main.skunk into target/demo
./target/demo</code></pre>
<p>The project layout:</p>
<pre><code class="language-text">demo/
skunk.toml # project manifest
src/main.skunk # entry point
target/ # build output (gitignored)</code></pre>
<p><code>skunk.toml</code> configures the build. <code>libraries</code> adds <code>-l</code> linker flags and <code>frameworks</code> adds macOS frameworks, which is how programs using <code>extern "C"</code> link against native libraries:</p>
<pre><code class="language-toml">[package]
name = "demo"
entry = "src/main.skunk"
[build]
optimize = true
libraries = ["sqlite3"]
frameworks = ["Cocoa"]</code></pre>
<p>The manifest format is intentionally small. Settings unknown to the current compiler are ignored for forward compatibility and reported as warnings, so misspellings do not disappear silently.</p>
<p>Single files still work without a project: <code>skunk run file.skunk</code> and <code>skunk compile file.skunk out</code> are unchanged.</p>
</section>
<section id="c-interop">
<h2>C Interop</h2>
<p>Skunk can import functions from native libraries with an <code>extern "C"</code> declaration. The declared name is emitted as an unmangled C symbol and can be called like an ordinary Skunk function:</p>
<pre><code class="language-skunk">extern "C" function cos(value: double): double;
extern "C" function strlen(value: string): long;
function main(): void {
print(cos(0.0));
print(strlen("skunk"));
}</code></pre>
<p>The first interop version deliberately exposes a small ABI-safe surface:</p>
<table class="reference-table">
<thead>
<tr>
<th>Skunk type</th>
<th>C boundary representation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>byte</code>, <code>short</code>, <code>int</code>, <code>long</code></td>
<td>Signed 8-, 16-, 32-, and 64-bit integers</td>
</tr>
<tr>
<td><code>float</code>, <code>double</code></td>
<td>32- and 64-bit floating-point values</td>
</tr>
<tr>
<td><code>bool</code> / <code>boolean</code></td>
<td>C-compatible boolean value</td>
</tr>
<tr>
<td><code>string</code></td>
<td>C string pointer; ownership and lifetime remain part of the native API contract</td>
</tr>
<tr>
<td><code>*T</code></td>
<td>Raw pointer; aggregates may be passed behind pointers</td>
</tr>
<tr>
<td><code>void</code></td>
<td>Allowed only as a return type</td>
</tr>
</tbody>
</table>
<p>Native libraries are selected through a project manifest. Each <code>libraries</code> entry becomes <code>-l<name></code>; each <code>frameworks</code> entry becomes <code>-framework <name></code> on macOS. Both <code>skunk build</code> and project-level <code>skunk test</code> use these settings.</p>
<pre><code class="language-toml">[build]
libraries = ["sqlite3"]
frameworks = ["Cocoa"]</code></pre>
<ul>
<li>Skunk currently imports C functions; exporting Skunk functions with a C ABI is not implemented.</li>
<li>Headers are not parsed. Declarations must be written explicitly and must match the native signature.</li>
<li>Variadic functions, link-name aliases, callbacks, and aggregates passed by value are not supported yet.</li>
<li>Safe references, slices, arrays, structs, enums, function values, traits, unions, and intersections cannot cross the C ABI directly.</li>
<li>Passing pointers or strings to native code follows the native library's safety, lifetime, and ownership rules.</li>
</ul>
</section>
<section id="standard-library">
<h2>Standard Library</h2>
<p>The standard library ships embedded inside the compiler binary and is materialized on first use, so an installed <code>skunk</code> needs no separate SDK download. The <code>std.</code> import prefix is reserved: it always resolves into the SDK, never into project files.</p>
<pre><code class="language-skunk">import std.math;
function main(): void {
print(sqrt(16.0));
print(pow(2.0, 10.0));
print(max(3, 9));
}</code></pre>
<p><code>std.math</code> provides double-precision functions bound to libm through <code>extern "C"</code> (<code>sqrt</code>, <code>cbrt</code>, <code>pow</code>, <code>exp</code>, <code>log</code>, <code>log2</code>, <code>log10</code>, <code>sin</code>, <code>cos</code>, <code>tan</code>, <code>asin</code>, <code>acos</code>, <code>atan</code>, <code>atan2</code>, <code>floor</code>, <code>ceil</code>, <code>round</code>, <code>fabs</code>, <code>fmod</code>) and integer helpers written in Skunk (<code>abs</code>, <code>min</code>, <code>max</code>, <code>clamp</code>). The SDK and compiler are one artifact and share one version. See <code>examples/calculator</code> for a project that combines a custom module with <code>std.math</code>.</p>
</section>
<section id="native-tests">
<h2>Native Tests</h2>
<p>Tests are declared next to the code they cover with <code>test</code> blocks. Assertions go through the <code>Testing</code> API:</p>
<pre><code class="language-skunk">struct Point {
x: int;
y: int;
}
test "struct shorthand" {
x: int = 3;
point: Point = Point { x, y: 4 };
Testing::expect(point.x == 3);
Testing::expect_eq(4, point.y);
}</code></pre>
<p>Run tests with the <code>test</code> command. In a project directory, <code>skunk test</code> uses the manifest entry; a file path or a name filter can be given explicitly:</p>
<pre><code class="language-bash">skunk test
skunk test path/to/module.skunk
skunk test --filter shorthand</code></pre>
<p>The runner compiles tests natively: each <code>test</code> block becomes a function, a generated <code>main</code> drives them, and the process exits non-zero if any test fails. Output reports each test with its elapsed time:</p>
<pre><code class="language-text">PASS addition (0.01 ms)
FAIL struct shorthand (0.02 ms)
2 tests, 1 passed, 1 failed</code></pre>
<p>Available assertions: <code>Testing::expect(condition)</code>, <code>Testing::expect_eq(expected, actual)</code>, and <code>Testing::fail()</code>. In this first version, <code>expect_eq</code> compares <code>int</code> values; use <code>expect</code> for other types. Filters are case-sensitive substring matches. A failed assertion marks the test failed but keeps executing, so one run reports every failing assertion. A process crash still stops the remaining tests because tests currently share one native process. Test blocks are ignored by <code>skunk run</code>, <code>skunk compile</code>, and <code>skunk build</code> — they only compile during <code>skunk test</code>.</p>
</section>
<section id="programs-and-modules">
<h2>Programs and Modules</h2>
<p>Skunk supports multi-file programs through <code>module</code>, <code>import</code>, and <code>export</code>.</p>
<pre><code class="language-skunk">module app.math;
function helper(n: int): int {
return n + 1;
}
export function inc(n: int): int {
return helper(n);
}</code></pre>
<pre><code class="language-skunk">import app.math;
function main(): void {
print(inc(41));
}</code></pre>
<ul>
<li><code>import app.math;</code> resolves to <code>app/math.skunk</code> relative to the entry file directory.</li>
<li>Imported files must declare the matching <code>module</code> name.</li>
<li>If a module uses <code>export</code>, only exported top-level declarations are visible to importers.</li>
<li>If a module uses no <code>export</code>, current behavior stays all-public for compatibility.</li>
</ul>
</section>
<section id="types">
<h2>Types</h2>
<p>Skunk currently supports primitive types, fixed arrays, slices, safe references, raw pointers, function types, structs, enums, generic instantiations, transparent type aliases, union types, and trait intersection types.</p>
<table class="reference-table">
<thead>
<tr>
<th>Form</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>byte</code>, <code>short</code>, <code>int</code>, <code>long</code></td>
<td>Signed integer primitives</td>
</tr>
<tr>
<td><code>float</code>, <code>double</code></td>
<td>Floating-point primitives</td>
</tr>
<tr>
<td><code>boolean</code>, <code>char</code>, <code>string</code>, <code>void</code></td>
<td>Core built-in types</td>
</tr>
<tr>
<td><code>[N]T</code></td>
<td>Fixed-size value array</td>
</tr>
<tr>
<td><code>[]T</code></td>
<td>Slice view over contiguous elements</td>
</tr>
<tr>
<td><code>&T</code>, <code>&mut T</code></td>
<td>Safe shared or mutable reference to one value</td>
</tr>
<tr>
<td><code>*T</code></td>
<td>Raw pointer to one value for unsafe memory operations</td>
</tr>
<tr>
<td><code>*const T</code>, <code>[]const T</code></td>
<td>Read-only pointer or slice view</td>
</tr>
<tr>
<td><code>(A, B) -> C</code></td>
<td>Function type</td>
</tr>
<tr>
<td><code>Box[int]</code></td>
<td>Generic type instantiation</td>
</tr>
<tr>
<td><code>A | B</code></td>
<td>Union type accepting a value of either member type</td>
</tr>
<tr>
<td><code>Readable & Writable</code></td>
<td>Runtime trait intersection requiring every listed trait</td>
</tr>
<tr>
<td><code>type Name = T;</code></td>
<td>Transparent type alias; aliases do not create a distinct nominal type</td>
</tr>
</tbody>
</table>
<pre><code class="language-skunk">type UserId = int;
type TextOrNumber = string | int;
type ReadWrite = Readable & Writable;
type Either[T] = T | string;
type ComparableValue[T: Serializable & Comparable] = T;
export type PublicId = UserId;</code></pre>
<ul>
<li>Aliases may be concrete or generic, may declare capability or subtype bounds, and may be exported from modules.</li>
<li>Alias expansion is transparent and recursive alias cycles are rejected.</li>
<li>Intersections are currently restricted to traits. A concrete value must conform to every member trait.</li>
<li><code>&</code> binds more tightly than <code>|</code>; parentheses may be used to make composed types explicit.</li>
</ul>
<p><code>Allocator</code> and <code>Arena</code> are built-in runtime types used for explicit memory management.</p>
</section>
<section id="bindings-and-const">
<h2>Bindings and Const</h2>
<p>Skunk distinguishes between const bindings and const views.</p>
<pre><code class="language-skunk">const answer: int = 42;
function copy_into(const dst: []int, src: []const int): void {
for (i: int = 0; i < src.len; i = i + 1) {
dst[i] = src[i];
}
}</code></pre>
<ul>
<li><code>const name: T</code> makes the binding non-reassignable.</li>
<li><code>[]const T</code> and <code>*const T</code> make the viewed elements or pointee read-only.</li>
<li><code>[]T</code> is assignable to <code>[]const T</code>, but not the other way around.</li>
<li><code>const dst: []int</code> still allows <code>dst[i] = ...</code> because the binding is const, not the slice contents.</li>
</ul>
</section>
<section id="functions-and-control-flow">
<h2>Functions and Control Flow</h2>
<p>Skunk supports named functions, lambdas, closures, <code>if</code>, <code>for</code>, <code>return</code>, <code>defer</code>, and block scoping.</p>
<pre><code class="language-skunk">function add(a: int, b: int): int {
return a + b;
}
function main(): void {
arena: Arena = Arena::init(System::allocator());
defer arena.deinit();
total: int = add(5, 7);
if (total > 10) {
print(total);
}
counter: () -> int = function(): int {
total = total + 1;
return total;
};
print(counter());
}</code></pre>
<p>Closures can capture and mutate surrounding locals. Function values can be stored, returned, and passed as arguments.</p>
<p><code>defer expression;</code> schedules an expression for the end of the current lexical scope. Deferred expressions run in last-in, first-out order on both normal scope exit and <code>return</code>; their values and arguments are evaluated when the scope exits.</p>
</section>
<section id="arrays-and-slices">
<h2>Arrays and Slices</h2>
<p>Fixed arrays use value semantics. Slices are views over contiguous storage.</p>
<pre><code class="language-skunk">a: [4]int;
b: [4]int = [4]int::fill(7);
c: [4]int = [1, 2, 3, 4];
mid: []const int = c[1:3];
print(a[0]);
print(b.len);
print(mid[0]);</code></pre>
<ul>
<li><code>[N]T</code> without an initializer is zero-initialized.</li>
<li><code>[N]T::fill(value)</code> fills every element with the given value.</li>
<li>Slices support indexing, <code>.len</code>, and range slicing with omitted bounds.</li>
<li>Fixed-array and slice indexing is checked at runtime. A negative index or an index greater than or equal to <code>.len</code> stops the program with a panic such as <code>panic: index 4 out of bounds for length 4</code>.</li>
<li>Slice ranges require <code>0 <= start <= end <= len</code>; invalid ranges also panic.</li>
<li>Collection implementations whose logical length differs from their backing slice can use <code>Bounds::check(index, length)</code> before accessing storage.</li>
<li>Fixed arrays can be passed and returned by value.</li>
</ul>
</section>
<section id="structs-and-methods">
<h2>Structs and Attached Behavior</h2>
<p>Structs are data-only product types. Behavior lives in separate <code>attach</code> blocks.</p>
<pre><code class="language-skunk">struct Counter {
const seed: int;
value: int;
}
attach Counter {
function new(seed: int, value: int): Counter {
return Counter { seed, value };
}
function bump(mut self): void {
self.value = self.value + 1;
}
function get(self): int {
return self.value;
}
}
function main(): void {
counter: Counter = Counter::new(1, 4);
counter.bump();
print(counter.seed);
print(counter.get());
}</code></pre>
<p>Struct literals support field shorthand when a field name matches an in-scope binding. <code>Counter { seed, value }</code> is equivalent to <code>Counter { seed: seed, value: value }</code>. Shorthand and explicit fields may be mixed:</p>
<pre><code class="language-skunk">seed: int = 3;
value: int = 4;
counter: Counter = Counter { seed, value };
next: Counter = Counter { seed, value: value + 1 };</code></pre>
<p>Receiver mutability is explicit:</p>
<ul>
<li><code>attach Type { ... }</code> adds inherent methods to a type without declaring trait conformance.</li>
<li>Attached functions without <code>self</code> are called with <code>Type::name(...)</code> and work well for constructors and factories.</li>
<li>A shorthand field such as <code>x</code> uses the same value as <code>x: x</code>; use the explicit form for a different binding or a computed expression.</li>
<li>Struct fields may be declared <code>const</code>; they may be initialized but not reassigned later.</li>
<li><code>self</code> means the method may read but may not mutate receiver state.</li>
<li><code>mut self</code> means the method may mutate receiver state.</li>
<li><code>*const T</code> may call only read-only <code>self</code> methods.</li>
</ul>
</section>
<section id="pointers-allocators-and-arenas">
<h2>Pointers, Allocators, and Arenas</h2>
<p>Skunk uses explicit allocation. Plain values use value semantics; safe borrows use <code>&T</code> and <code>&mut T</code>; allocator-backed single objects use <code>*T</code>; allocator-backed buffers use <code>[]T</code>.</p>
<pre><code class="language-skunk">struct Point {
x: int;
y: int;
}
function make_point(alloc: Allocator): *Point {
point: *Point = Point::create(alloc);
point.x = 3;
point.y = 4;
return point;
}
function main(): void {
system_alloc: Allocator = System::allocator();
arena: Arena = Arena::init(system_alloc);
arena_alloc: Allocator = arena.allocator();
point: *Point = make_point(arena_alloc);
values: []int = []int::alloc(arena_alloc, 8);
print(point.x + values.len);
arena.deinit();
}</code></pre>
<ul>
<li><code>System::allocator()</code> returns the system allocator handle.</li>
<li><code>T::create(alloc)</code> allocates one object and returns <code>*T</code>.</li>
<li><code>[]T::alloc(alloc, len)</code> allocates a slice buffer.</li>
<li><code>alloc.destroy(ptr)</code> releases a pointer allocation.</li>
<li><code>alloc.free(slice)</code> releases a slice allocation.</li>
<li><code>Arena::init(backing)</code>, <code>arena.allocator()</code>, <code>arena.reset()</code>, and <code>arena.deinit()</code> provide arena-style lifetime management.</li>
<li><code>&T</code> shares read-only access and <code>&mut T</code> grants checked mutable access without entering <code>unsafe</code>.</li>
<li>Field access like <code>point.x</code> and method calls like <code>point.bump()</code> still auto-deref ordinary typed pointers.</li>
</ul>
</section>
<section id="unsafe-memory">
<h2>Unsafe Memory</h2>
<p>Skunk now has a small unsafe memory layer for low-level pointer work. These operations must appear inside an <code>unsafe { ... }</code> block.</p>
<pre><code class="language-skunk">function main(): void {
value: int = 41;
other: int = 0;
unsafe {
ptr: *int = &value;
print(ptr.*);
ptr.* = 42;
bytes: [4]byte;
second: *byte = *byte::offset(&bytes[0], 1);
second.* = 9;
Memory::set(&bytes[0], 7, 4);
Memory::copy(*byte::cast(&other), *byte::cast(&value), int::size_of());
}
print(int::size_of());
print(int::align_of());
}</code></pre>
<ul>
<li><code>unsafe { ... }</code> enables low-level operations the compiler cannot verify as memory-safe.</li>
<li><code>&expr</code> and <code>&mut expr</code> create safe references by default.</li>
<li>When a raw pointer is explicitly expected, the same address-of syntax feeds unsafe pointer operations such as <code>*T::cast</code>, <code>*byte::offset</code>, and <code>Memory::copy</code>.</li>
<li><code>ptr.*</code> explicitly dereferences a pointer value.</li>
<li><code>T::size_of()</code> and <code>T::align_of()</code> are safe compile-time layout queries.</li>
<li><code>*T::cast(ptr)</code> reinterprets one pointer type as another pointer type.</li>
<li><code>*byte::offset(ptr, n)</code> performs byte-wise pointer offsetting.</li>
<li><code>Memory::copy(dst, src, count)</code> and <code>Memory::set(dst, value, count)</code> operate on raw bytes.</li>
<li>Raw pointer dereferences and offsets remain unchecked; runtime bounds checks apply to fixed arrays and slices.</li>
</ul>
</section>
<section id="windowed-2d">
<h2>Windowed 2D</h2>
<p>Skunk now includes a small window/input/drawing runtime for simple 2D programs. The current implementation is macOS-first and is aimed at rectangle-based games and visual prototypes.</p>
<pre><code class="language-skunk">function main(): void {
window: Window = Window::create(800, 600, "Skunk");
for (; window.is_open(); ) {
window.poll();
if (Keyboard::is_down(window, 'q')) {
window.close();
}
window.clear(Color::rgb(8, 12, 24));
window.draw_rect(120.0, 140.0, 96.0, 64.0, Color::white());
window.present();
}
window.deinit();
}</code></pre>
<ul>
<li><code>Window::create(width, height, title)</code> creates a native window handle.</li>
<li><code>window.poll()</code> pumps OS events so keyboard and close state stay current.</li>
<li><code>window.is_open()</code>, <code>window.close()</code>, and <code>window.deinit()</code> control the window lifetime.</li>
<li><code>window.clear(color)</code> fills the framebuffer and <code>window.draw_rect(x, y, w, h, color)</code> draws clipped solid rectangles.</li>
<li><code>window.present()</code> shows the current frame and updates <code>window.delta_time()</code>.</li>
<li><code>Keyboard::is_down(window, 'w')</code> currently uses character keys; arrow keys and richer input enums can come later.</li>
<li><code>Color::rgb(r, g, b)</code>, <code>Color::rgba(r, g, b, a)</code>, and constants like <code>Color::white()</code> pack colors for drawing.</li>
<li>The repository now includes <code>examples/pong.skunk</code> as a complete example built on this API.</li>
</ul>
</section>
<section id="generics">
<h2>Generics</h2>
<p>Skunk supports generic structs, functions, enums, traits, and type aliases through monomorphization. Type parameters may have capability constraints, upper subtype bounds, lower subtype bounds, or a combination.</p>
<pre><code class="language-skunk">struct Box[T] {
value: T;
}
function wrap[T](value: T): Box[T] {
return Box[T] { value: value };
}
function accept[T <: Animal](value: T): T {
return value;
}
function widen[A, B >: A <: Animal](left: A, right: B): B {
return left;
}
function widen_where[A, B](left: A, right: B): B
where B >: A, B <: Animal {
return left;
}</code></pre>
<ul>
<li>Nested instantiations such as <code>Box[Box[int]]</code> are supported.</li>
<li>Function type argument inference works from call arguments in common cases.</li>
<li>Generic enum constructors infer type arguments from their payloads and expected return, variable, assignment, field, array-element, or concrete parameter type. For example, a function returning <code>Option[int]</code> may write <code>Option::Some(7)</code>.</li>
<li>Explicit function call type arguments are supported with forms like <code>id[int](42)</code>.</li>
<li><code>T <: Upper</code> requires the chosen type for <code>T</code> to be a subtype of <code>Upper</code>. Trait conformance and inherited traits participate in this check.</li>
<li><code>T >: Lower</code> requires the chosen type for <code>T</code> to be a supertype of <code>Lower</code>. When both bounds are present, write the lower bound first: <code>T >: Lower <: Upper</code>.</li>
<li>Bounds may move to a <code>where</code> clause and may be split into separate predicates, as in <code>where T >: Dog, T <: Animal</code>.</li>
<li>Direct argument evidence is widened to the smallest available union. For example, two direct <code>T</code> parameters receiving <code>Dog</code> and <code>Cat</code> infer <code>T</code> as <code>Cat | Dog</code>. Nested generic types remain invariant.</li>
<li>Explicit type arguments are checked exactly and are not widened automatically.</li>
<li>Numeric widening remains a value conversion, not a subtype relationship; for example, <code>int</code> does not satisfy <code>T <: long</code>.</li>
</ul>
</section>
<section id="enums-and-match">
<h2>Enums and Match</h2>
<p>Skunk supports generic enums with unit variants, tuple-style payload variants, attached behavior, and exhaustive enum-focused <code>match</code>.</p>
<pre><code class="language-skunk">enum Option[T] {
None;
Some(T);
}
attach[T] Option[T] {
function is_some(self): bool {
match (self) {
case None: {
return false;
}
case Some(value): {
return true;
}
}
}
function unwrap_or(self, fallback: T): T {
match (self) {
case None: {
return fallback;
}
case Some(value): {
return value;
}
}
}
}
function main(): void {
some: Option[int] = Option::Some(7);
none: Option[int] = Option::None();
print(some.is_some());
print(none.unwrap_or(42));
}</code></pre>
<ul>
<li><code>attach Enum { ... }</code> and <code>attach[T] Enum[T] { ... }</code> add instance methods and static functions to concrete and generic enums.</li>
<li>When context determines the complete generic type, construct variants concisely with forms like <code>Option::None()</code> and <code>Option::Some(7)</code>.</li>
<li>Explicit forms such as <code>Option[int]::Some(7)</code> remain available when inference is ambiguous.</li>
<li>Variants may carry multiple payload values, such as <code>Pair(A, B)</code>.</li>
<li><code>match</code> is exhaustiveness-checked for enums.</li>
<li>Attached functions may use the enum's type parameters. Methods that declare their own additional type parameters are not yet supported.</li>
</ul>
</section>
<section id="traits-and-impls">
<h2>Traits, Conform, and Shapes</h2>
<p>Traits work both as generic constraints and as runtime interface values. Traits may extend other traits, and shapes provide reusable structural bounds.</p>
<pre><code class="language-skunk">trait Readable {
function value(self): int;
}
trait Writer: Readable {
function write(mut self, value: int): int;
function write_twice(mut self, value: int): int {
self.write(value);
return self.write(value);
}
}
trait Resettable {
function reset(mut self): void;
}
shape WriterLike {
function write(mut self, value: int): int;
}
struct Counter {
value: int;
}
conform Writer for Counter {
function value(self): int {
return self.value;
}
function write(mut self, value: int): int {
self.value = self.value + value;
return self.value;
}
}
conform Resettable for Counter {
function reset(mut self): void {
self.value = 0;
}
}
function use_counter[T: Writer & Resettable](counter: *T): int {
counter.reset();
return counter.write(41);
}
function save[T](value: T): T
where T: Writer & Resettable {
return value;
}
function use_writer_like[T: WriterLike](writer: *T): int {
return writer.write(5);
}</code></pre>
<pre><code class="language-skunk">function main(): void {
writer: Writer = Counter { value: 1 };
print(writer.write_twice(4));
}</code></pre>
<pre><code class="language-skunk">trait Cell[T] {
function get(self): T;
function set(mut self, value: T): void;
}
struct Box[T] {
value: T;
}
conform Cell[T] for Box[T] {
function get(self): T {
return self.value;
}
function set(mut self, value: T): void {
self.value = value;
}
}
function main(): void {
cell: Cell[int] = Box[int] { value: 7 };
cell.set(42);
print(cell.get());
}</code></pre>
<ul>
<li>Trait conformance is explicit through <code>conform Trait for Type { ... }</code>.</li>
<li>Traits may declare type parameters and bounds, for example <code>trait Cell[T]</code> or <code>trait Cell[T: Serializable]</code>. Each concrete use such as <code>Cell[int]</code> receives a specialized runtime trait layout and vtable.</li>
<li>Traits may extend other traits with <code>trait Writer: Readable { ... }</code>; implementing the child trait also satisfies the parent traits.</li>
<li>Non-generic traits may provide default method bodies, and <code>conform</code> blocks only need to implement the required methods they want to customize. Generic trait default methods are not supported yet.</li>
<li>Shapes provide structural bounds, for example <code>T: WriterLike</code>, without introducing a runtime trait value.</li>
<li>Conform targets may be concrete or generic. In <code>conform Cell[T] for Box[T] { ... }</code>, the conformance binder <code>T</code> is inferred from <code>Box[T]</code>. The older explicit form <code>conform[T] Cell[T] for Box[T]</code> remains accepted.</li>
<li>Receiver mutability is part of the trait contract: a method declared with <code>mut self</code> may mutate through the receiver, so its conformance method must also declare <code>mut self</code>.</li>
<li>Generic bounds can stay inline with <code>function save[T: Writer](...)</code> or move into a <code>where</code> clause for longer signatures.</li>
<li>Bounds intersect with <code>&</code>, for example <code>where T: Writer & Resettable</code>.</li>
<li>Capability bounds use <code>:</code>; subtype bounds use <code><:</code> and <code>>:</code>. They may be combined, with a <code>where</code> clause usually giving the clearest result.</li>
<li>Trait names may be used as runtime types, such as <code>writer: Writer</code>.</li>
<li>Assigning an addressable concrete value to a trait value borrows its storage for dynamic dispatch; rvalues still box a runtime value with a vtable.</li>
</ul>
</section>
<section id="patterns-and-destructuring">
<h2>Patterns and Destructuring</h2>
<p>Skunk currently supports enum patterns in <code>match</code>, struct patterns in <code>match</code>, and standalone struct destructuring statements.</p>
<pre><code class="language-skunk">struct Point {
x: int;
y: int;
}
function sum(point: Point): int {
match (point) {
case Point { x, y }: {
return x + y;
}
}
}
function main(): void {
point: Point = Point { x: 3, y: 4 };
Point { x, y: py } = point;
print(sum(point));
print(x + py);
}</code></pre>
<ul>
<li>Struct field bindings may use aliases such as <code>y: py</code>.</li>
<li>Destructuring statements introduce local bindings in the current scope.</li>
<li>Struct pattern matching is exact-type and currently supports one case in V1.</li>
</ul>
</section>
<section id="current-limitations">
<h2>Current Limitations</h2>
<ul>
<li>No user-defined allocators yet. The current allocator and arena model is still runtime-provided.</li>
<li>The unsafe memory layer is intentionally small today: there are no raw pointer trait bounds, no arbitrary pointer arithmetic beyond <code>*byte::offset</code>, and no general <code>unsafe</code> standard library yet.</li>
<li>Struct <code>match</code> is intentionally narrow in this first pass.</li>
<li>Type-level <code>match</code> expressions are not part of the current alias V1. Union values can be stored, passed, and returned, but runtime union narrowing and union pattern matching are not implemented yet.</li>
<li>Runtime trait values still use a simple V1 representation. Lvalues reuse their existing storage, while temporaries and other rvalues are boxed when converted to trait values.</li>
</ul>
</section>
<section id="design-notes">
<h2>Design Notes</h2>
<p>The language reference should stay focused on implemented behavior. For deeper design context, see the supporting notes in this repository.</p>
<ul>
<li><a href="compiler-booklet.html">Compiler booklet</a></li>
<li><a href="compiler-notebook.md">Compiler notebook, Part 1</a></li>
<li><a href="compiler-notebook-part2.md">Compiler notebook, Part 2</a></li>
<li><a href="compiler-notebook-part3.md">Compiler notebook, Part 3</a></li>
<li><a href="pointers-and-allocators.md">Pointer and allocator design</a></li>
<li><a href="language-development.md">Language development contract</a></li>
</ul>
</section>
</main>
</div>
</body>
</html>