vanta-lang 0.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. vanta_lang-0.28.0/CHANGELOG.md +786 -0
  2. vanta_lang-0.28.0/LICENSE +21 -0
  3. vanta_lang-0.28.0/MANIFEST.in +11 -0
  4. vanta_lang-0.28.0/PKG-INFO +306 -0
  5. vanta_lang-0.28.0/README.md +285 -0
  6. vanta_lang-0.28.0/ROADMAP.md +58 -0
  7. vanta_lang-0.28.0/compiler/__init__.py +2 -0
  8. vanta_lang-0.28.0/compiler/ast_nodes.py +241 -0
  9. vanta_lang-0.28.0/compiler/codegen_cpp.py +995 -0
  10. vanta_lang-0.28.0/compiler/driver.py +741 -0
  11. vanta_lang-0.28.0/compiler/formats.py +53 -0
  12. vanta_lang-0.28.0/compiler/lexer.py +184 -0
  13. vanta_lang-0.28.0/compiler/loader.py +98 -0
  14. vanta_lang-0.28.0/compiler/parser.py +532 -0
  15. vanta_lang-0.28.0/compiler/tokens.py +47 -0
  16. vanta_lang-0.28.0/compiler/type_checker.py +1446 -0
  17. vanta_lang-0.28.0/compiler/typesys.py +130 -0
  18. vanta_lang-0.28.0/docs/ARCHITECTURE.md +194 -0
  19. vanta_lang-0.28.0/docs/LANGUAGE_REFERENCE.md +1798 -0
  20. vanta_lang-0.28.0/docs/TUTORIAL.md +1161 -0
  21. vanta_lang-0.28.0/examples/assets/ball.png +0 -0
  22. vanta_lang-0.28.0/examples/assets/ball_spin.png +0 -0
  23. vanta_lang-0.28.0/examples/assets/blip.wav +0 -0
  24. vanta_lang-0.28.0/examples/assets/boom.wav +0 -0
  25. vanta_lang-0.28.0/examples/assets/brick.png +0 -0
  26. vanta_lang-0.28.0/examples/assets/brick.wav +0 -0
  27. vanta_lang-0.28.0/examples/assets/enemy.png +0 -0
  28. vanta_lang-0.28.0/examples/assets/gen_assets.py +221 -0
  29. vanta_lang-0.28.0/examples/assets/hit.wav +0 -0
  30. vanta_lang-0.28.0/examples/assets/laser.png +0 -0
  31. vanta_lang-0.28.0/examples/assets/laser.wav +0 -0
  32. vanta_lang-0.28.0/examples/assets/lose.wav +0 -0
  33. vanta_lang-0.28.0/examples/assets/paddle.bmp +0 -0
  34. vanta_lang-0.28.0/examples/assets/ship_anim.png +0 -0
  35. vanta_lang-0.28.0/examples/assets/tiles.png +0 -0
  36. vanta_lang-0.28.0/examples/gamekit/README.md +304 -0
  37. vanta_lang-0.28.0/examples/gamekit/anim.vnt +62 -0
  38. vanta_lang-0.28.0/examples/gamekit/audio.vnt +214 -0
  39. vanta_lang-0.28.0/examples/gamekit/collide.vnt +46 -0
  40. vanta_lang-0.28.0/examples/gamekit/fonts/LICENSE.md +13 -0
  41. vanta_lang-0.28.0/examples/gamekit/fonts/tom-thumb.ttf +0 -0
  42. vanta_lang-0.28.0/examples/gamekit/gamepad.vnt +115 -0
  43. vanta_lang-0.28.0/examples/gamekit/gfx.vnt +37 -0
  44. vanta_lang-0.28.0/examples/gamekit/image.vnt +98 -0
  45. vanta_lang-0.28.0/examples/gamekit/input.vnt +21 -0
  46. vanta_lang-0.28.0/examples/gamekit/rng.vnt +19 -0
  47. vanta_lang-0.28.0/examples/gamekit/sdl.vnt +98 -0
  48. vanta_lang-0.28.0/examples/gamekit/text.vnt +160 -0
  49. vanta_lang-0.28.0/pyproject.toml +36 -0
  50. vanta_lang-0.28.0/runtime/vanta_runtime.hpp +818 -0
  51. vanta_lang-0.28.0/setup.cfg +4 -0
  52. vanta_lang-0.28.0/setup.py +35 -0
  53. vanta_lang-0.28.0/tests/test_cli.py +98 -0
  54. vanta_lang-0.28.0/tests/test_codegen.py +890 -0
  55. vanta_lang-0.28.0/tests/test_integration.py +2399 -0
  56. vanta_lang-0.28.0/tests/test_lexer.py +97 -0
  57. vanta_lang-0.28.0/tests/test_loader.py +99 -0
  58. vanta_lang-0.28.0/tests/test_parser.py +272 -0
  59. vanta_lang-0.28.0/tests/test_type_checker.py +2176 -0
  60. vanta_lang-0.28.0/vanta_lang.egg-info/PKG-INFO +306 -0
  61. vanta_lang-0.28.0/vanta_lang.egg-info/SOURCES.txt +62 -0
  62. vanta_lang-0.28.0/vanta_lang.egg-info/dependency_links.txt +1 -0
  63. vanta_lang-0.28.0/vanta_lang.egg-info/entry_points.txt +2 -0
  64. vanta_lang-0.28.0/vanta_lang.egg-info/top_level.txt +1 -0
@@ -0,0 +1,786 @@
1
+ # Changelog
2
+
3
+ Notable changes to Vanta, newest first. Versions correspond to git tags.
4
+
5
+ ## v0.28.0 — 2026-10-20
6
+
7
+ - Installable Vanta. `pip install vanta-lang` (or `pipx install vanta-lang`)
8
+ now installs the `vanta` command without a checkout. A `setup.py` build hook
9
+ copies `runtime/vanta_runtime.hpp` and the game kit into the wheel as
10
+ `compiler/runtime/` and `compiler/templates/`, and `MANIFEST.in` keeps the
11
+ same files in the sdist. The driver resolves either layout, so a checkout and
12
+ an installed wheel behave the same.
13
+ - `vanta doctor` reports the Python and C++ compiler versions, the resolved
14
+ runtime header and game kit paths, compiles a one-line runtime probe, and
15
+ makes a best-effort SDL2 / SDL2_ttf / SDL2_image check. Missing SDL only
16
+ warns: non-game programs need nothing beyond the C++ compiler.
17
+ - `vanta new` keeps working from an installed wheel because the starter points
18
+ at the game kit inside the installed compiler.
19
+ - Packaging checks. CI gains a `package` job that builds the wheel and sdist,
20
+ runs `twine check`, installs the wheel into a fresh venv, and exercises
21
+ `--version`, `doctor`, a hello program, `new`, `check`, and a headless game
22
+ run on Linux and Windows. `.github/workflows/release.yml` repeats the smoke
23
+ test on version tags, attaches the wheel and sdist to the GitHub release, and
24
+ publishes to PyPI through Trusted Publishing when the `PUBLISH_PYPI`
25
+ repository variable is set.
26
+ - Metadata is release-ready: SPDX `MIT`, license files, classifiers, project
27
+ URLs, and `setuptools>=77`. 588 tests total.
28
+ - Docs: README and the tutorial lead with `pipx install vanta-lang`,
29
+ `vanta doctor`, and the installed-command workflow; the reference gains an
30
+ installing/running section and the architecture notes describe the build
31
+ hook and layout resolution.
32
+
33
+ ## v0.27.0 — 2026-10-20
34
+
35
+ - Breakout polish. `examples/breakout.vnt` now has three walls of 4, 5, and 6
36
+ rows (125 bricks total), with diagonal gaps in the later walls. Clearing a
37
+ wall serves the next one; clearing the third wins. The score carries across
38
+ walls, and the HUD shows the current level and best score.
39
+ - Pause. P or Space freezes the paddle, ball, collisions, and ball animation
40
+ while events and drawing keep running; a centered `PAUSED` label shows the
41
+ state. Esc and the close button still quit. `gamekit/sdl.vnt` gains the
42
+ `KEY_SPACE` and `KEY_P` symbols.
43
+ - Score persistence. The best score is read from `breakout.score` at startup
44
+ and only rewritten when the run beats it. The optional second program
45
+ argument changes the score path, so tests and headless runs stay out of the
46
+ working tree; `breakout.score` is git-ignored.
47
+ - Per-sound gain. `gamekit/audio.vnt` gains
48
+ `sound_load_gain(audio, path, gain)`: it loads a WAV like `sound_load` and
49
+ scales every mono S16 sample in place. Gain is clamped to 0..1, so it
50
+ attenuates without clipping; `sound_load` remains the full-volume shortcut.
51
+ - Floating-point determinism. The driver now passes `-ffp-contract=off` to
52
+ GCC/Clang (and `/fp:strict` to MSVC), so `f32` expressions are not
53
+ contracted into fused multiply-adds. The breakout autopilot therefore
54
+ reaches the same frame 5969 on x86-64 and Apple arm64; without the flag the
55
+ FMA-capable Mac took 7070 frames.
56
+ - The breakout headless probe now freezes the three-wall clear
57
+ (`cleared 125 lives 3 level 3 best 125 frames 5969`) and then loads a higher
58
+ score in a one-frame run to prove it is not overwritten. The sound probe
59
+ loads a stereo file at half gain and checks the folded samples scale from
60
+ 6000 to 3000, then loads it at gain 2.0 and checks the clamp keeps the
61
+ original 6000 peak. 586 tests total.
62
+ - Docs: README, tutorial, reference, roadmap, and the kit comments describe
63
+ levels, pause, persistence, and gain. The frozen pong and strike runs are
64
+ unchanged; breakout's frozen run deliberately records the new mode.
65
+
66
+ ## v0.26.0 — 2026-10-20
67
+
68
+ - `str` now owns its storage automatically. It is an immutable,
69
+ reference-counted value: literals and command-line arguments are zero-copy
70
+ static views, while `str_concat`, `to_str_i32`, `to_str_f32`, `file_read`,
71
+ and `StrBuilder.to_str()` return owned buffers. Copies share the buffer and
72
+ the last copy frees it.
73
+ - The fixed 64 KiB process-lifetime string pool is gone. Long-running string
74
+ loops no longer exhaust a global limit, `file_read` reads arbitrarily large
75
+ files (up to the `i32` length limit), and `str_pool_reset()` remains only as
76
+ a compatibility no-op that invalidates nothing.
77
+ - `file_read` and `file_write` stay byte-exact: embedded NUL bytes are kept in
78
+ the returned `str`, counted by `str_len`, and written by `file_write`. C
79
+ functions and `print_str` still see a `const char*` and stop at the first
80
+ NUL.
81
+ - The C boundary is unchanged for existing code: extern declarations keep
82
+ `str` as `const char*`, direct calls convert at the call site, and a C
83
+ function returning `str` has its result copied into an owned value. Small
84
+ generated adapters let extern functions with `str` signatures be used as
85
+ `func(str)` values and let Vanta functions/builtins with `str` signatures be
86
+ passed to C callbacks.
87
+ - `Arena` now constructs and destroys non-trivial element types, and
88
+ `arena.reset()` runs those destructors. `alloc::<str>`, arrays of `str`, and
89
+ structs containing `str` therefore work without leaking or double-freeing.
90
+ - 15 new tests (13 net, after replacing the two pool-exhaustion tests): large
91
+ file reads, pool-free allocation loops, string reset compatibility,
92
+ builder/arena lifetime, arena string destruction, owned `List<str>`, const
93
+ strings, embedded NUL round-trips, extern `str` arguments, extern `str`
94
+ function values, and C callbacks with `str` arguments and returns. 586
95
+ total.
96
+ - Documentation: the reference, tutorial, architecture notes, README, roadmap,
97
+ and file-I/O example all describe owned strings; every new snippet was
98
+ executed with its output frozen.
99
+
100
+ ## v0.25.0 — 2026-10-20
101
+
102
+ - Checked pointer access: `p.at(i, size)` reads, writes, and compound-assigns
103
+ through a `ptr<T>` after checking `0 <= i < size`. The pointer has no length
104
+ of its own, so the caller supplies the accessible count. `ptr<void>` is
105
+ rejected, nested array elements work
106
+ (`rows.at(row, row_count).at(column)`), and the new `examples/ptr_at.vnt`
107
+ demonstrates the read/write paths.
108
+ - An out-of-bounds pointer index stops the program with
109
+ `vanta: pointer index I out of bounds (size N) at line L`. The raw `p[i]`,
110
+ `ptr_cast::<T>(p)`, and `ptr_offset(p, n)` escape hatches are unchanged, and
111
+ `size` remains a programmer promise — null is still `ptr_is_null`'s job.
112
+ - 10 new tests: pointer `at` reads/writes/compound assignment, nested array
113
+ access, `ptr<void>` rejection, size type checking, the `let`-pointer and
114
+ temporary-pointer write rules, generated-code shape, the new example, and
115
+ out-of-bounds read/write failures. 573 total.
116
+
117
+ ## v0.24.1 — 2026-10-20
118
+
119
+ - `vanta game.vnt` is shorthand for `vanta run game.vnt`: a bare source path
120
+ now compiles and runs. Installing the checkout once with
121
+ `python -m pip install -e .` creates the `vanta` command, so games launch
122
+ without repeating `python main.py` or spelling out link flags.
123
+ - `vanta new game.vnt` writes a playable starter — a paddle, a falling block,
124
+ three lives, and a frame-limited mode for headless smoke tests. Its imports
125
+ point at the checkout's game kit relative to the new file.
126
+ - The driver links SDL2, SDL2_ttf, and SDL2_image automatically when it sees
127
+ their extern block names. Explicit `--link` values still work and are
128
+ deduplicated; custom C libraries still use `--include-dir`, `--lib-dir`,
129
+ and `--link`. On Windows the compiler's own `bin` directory is added to the
130
+ child `PATH`, so the SDL DLLs are found without a manual environment setup.
131
+ - `vanta build --bundle` (Windows) writes `build/<name>/<name>.exe`, walks the
132
+ executable's PE imports with `objdump`, and copies every non-system DLL it
133
+ loads — SDL2, SDL2_ttf, SDL2_image, and their codec dependencies — beside
134
+ the executable. It also copies `examples/assets` and the kit font, so the
135
+ bundled example games run from a folder with only Windows on `PATH`.
136
+ - Compile failures now print a platform-specific install hint for missing SDL
137
+ development libraries (MSYS2 package names on Windows, `brew` on macOS, and
138
+ `apt-get` on Linux), and MSVC users are told why the MinGW import libraries
139
+ cannot be used.
140
+ - 7 new tests: command normalization, auto-link detection and ordering, custom
141
+ externs staying unlinked, explicit-link deduplication, starter generation,
142
+ overwrite protection, and a Windows bundle that runs with a system-only
143
+ `PATH`. 563 total.
144
+
145
+ ## v0.24.0 — 2026-10-20
146
+
147
+ - The fourth playable game. `examples/strike.vnt` is a one-screen wave
148
+ shooter: left/right arrows (or A/D, or a gamepad stick/D-pad) move the
149
+ ship, the cannon fires on a fixed cadence, and three waves of six saucers
150
+ descend, drift, and bounce off the walls. A bolt destroys a saucer; a
151
+ saucer that touches the ship or slips past the bottom costs one of three
152
+ lives. Clear all eighteen to win. The ship is an animated four-frame
153
+ strip, the saucers are one white texture tinted per wave, the HUD is
154
+ `text_draw`, and the shot/hit/boom sounds are WAV files. With no input the
155
+ autopilot lines up under the lowest saucer and clears the game without
156
+ losing a life at frame 1020 — the suite freezes those counts. The game
157
+ imports nine of the ten kit modules (all but `rng.vnt`) and links SDL2,
158
+ SDL2_ttf, and SDL2_image.
159
+ - `gamekit/collide.vnt`, the tenth module, is pure Vanta with no externs and
160
+ no SDL dependency: `collide_rects`, `collide_point`, `collide_circles`,
161
+ and `collide_circle_rect`. Rectangles are `(x, y, w, h)`; edges that only
162
+ touch are not overlaps, `collide_point` is half-open, and the circle tests
163
+ compare squared distances, so none of them calls `sqrt`. The pure module
164
+ gets its own headless test on every machine with a C++ compiler.
165
+ - New generated assets: `ship_anim.png` (four 24x24 frames), `enemy.png`,
166
+ `laser.png`, and `laser.wav` / `hit.wav` / `boom.wav`, all from
167
+ `examples/assets/gen_assets.py`.
168
+ - No compiler changes: the kit and the game are plain Vanta. The new tests
169
+ are the collision semantics and the frozen strike run, for 556 total.
170
+ - Documentation: the game-kit chapters in the tutorial and reference now
171
+ cover ten modules and collision, the README lists the fourth game and the
172
+ ten-module kit, and the game-kit README documents the new helpers. Every
173
+ new snippet was executed with its output frozen.
174
+
175
+ ## v0.23.0 — 2026-10-20
176
+
177
+ - `extern` functions can return function values. A signature like `func
178
+ signal(sig: i32, handler: func(i32) -> void) -> func(i32) -> void` now
179
+ compiles: the generated `extern "C"` declaration folds the declarator into
180
+ the name (`void(*signal(int32_t, void(*)(int32_t)))(int32_t)`) instead of
181
+ emitting a bare function type where C needs a name. Returned values are
182
+ ordinary function values — call them, store them, or hand them back to C.
183
+ Pointer-to-function returns (`ptr<func(...)>`) and nested function-typed
184
+ returns (`func() -> func() -> i32`) spell correctly too; an array or list
185
+ of function values is still rejected as an extern return, since C cannot
186
+ return those by value. `examples/extern_return.vnt` with
187
+ `examples/extern_return_lib.c` is the buildable demo: C picks a function,
188
+ returns null for an unknown id, and the program calls, compares, and
189
+ null-tests the result.
190
+ - Function-value equality. `==` and `!=` compare function values by address;
191
+ values of the same type are accepted, different signatures are a compile
192
+ error, and ordering is rejected with a message pointing at `==`/`!=`. A
193
+ null built with `ptr_null::<func(...)>()` compares equal to any null of its
194
+ type, and `ptr_is_null` now accepts function values as well as pointers, so
195
+ the explicit null test needs no cast or sentinel. Struct equality follows:
196
+ a function-valued field no longer blocks comparing the whole struct, because
197
+ the field compares by address like any function value. Still no closures:
198
+ equality compares plain function pointers.
199
+ - Tests: 11 new cases cover the checker, generated declarations (including
200
+ nested and pointer-to-function returns), equality emission, struct equality
201
+ with a function field, and a real C fixture that hands function pointers
202
+ back across the boundary. 554 total.
203
+ - Docs: the reference's Function values section gains the equality rules and
204
+ a frozen snippet, its Extern section gains a runnable `signal` example, and
205
+ the tutorial, README, and version banners follow. Every new snippet is
206
+ executed with its output frozen; the frozen pong and breakout runs are
207
+ unchanged.
208
+
209
+ ## v0.22.0 — 2026-10-19
210
+
211
+ - Sprite animation. `gamekit/anim.vnt` (a ninth module) is a frame clock over
212
+ `tex_draw_src` for a horizontal strip of equal frames. `anim_new(count, fw,
213
+ fh, fps)` describes the strip; `anim_tick(anim, dt)` advances the clock by
214
+ the time the game hands it and *returns* the updated clock — structs are
215
+ values, so `anim = anim_tick(anim, dt)` is the idiom — the frame wraps, and
216
+ `anim_draw` blits the current frame through `tex_draw_src`. Nothing reads a
217
+ wall clock, a dead texture draws nothing, and an empty strip (zero frames)
218
+ stays on frame 0. The assets script grows `ball_spin.png` (four 10x10
219
+ frames, a highlight orbiting the ball), and `examples/breakout.vnt` now
220
+ spins its ball from it with a fixed 1/60 s step. The animation is purely
221
+ visual: the physics is untouched, and the frozen headless run still prints
222
+ exactly `cleared 40 lives 3 frames 2064`.
223
+ - Text sprites. `gamekit/text.vnt` gains `TextSprite` — the render-once path
224
+ for text that redraws every frame. `text_sprite` renders one string into a
225
+ texture (same colours as `text_draw`, with `text_sprite_ok` as the test and
226
+ a dead sprite for a failed font or an empty string), `text_sprite_draw`
227
+ blits it with no TTF call at all, and `text_sprite_free` destroys it;
228
+ `text_draw` is unchanged. The alternative — a bounded cache keyed by
229
+ (font, size, string, colour) — was weighed and rejected: the kit has no
230
+ hidden state to put it in, pool strings can be invalidated under a cache,
231
+ and an explicit handle is the kit's shape with the same measured win.
232
+ - The measured before/after ships as `examples/text_bench.vnt`: one 24 pt
233
+ string, both paths, counts stable and milliseconds the machine's. On the
234
+ development machine (headless dummy driver, software renderer) 40000 draws
235
+ went from ~130 ms via `text_draw` to ~4 ms via one sprite — about 30x. A
236
+ smoke test asserts the two output lines' shape and never the numbers, the
237
+ rule being that timings are never frozen. 3 new tests (543 total).
238
+ - Docs: the tutorial gains an Animated sprites section and the render-once
239
+ paragraph in Text on screen; the reference's kit chapter lists nine
240
+ modules, and both READMEs follow. Every new snippet is executed with its
241
+ output frozen, and the frozen runs of pong and breakout are re-verified
242
+ unchanged.
243
+
244
+ ## v0.21.0 — 2026-10-19
245
+
246
+ - Game controllers. `gamekit/gamepad.vnt` (an eighth module) wraps SDL2's
247
+ `SDL_GameController` API: `pad_open(index)` returns a `Pad`, with `pad_ok`
248
+ as the test, `pad_attached` for the unplug check, and `pad_button` /
249
+ `pad_axis` polling the current state. A missing controller — none plugged
250
+ in, or an index out of range — opens *dead*, the same shape as a dead
251
+ `Screen`, `Tex`, or `Sound`: every other call is a guarded no-op, so a
252
+ keyboard-only machine stays playable. `pad_open` starts SDL's refcounted
253
+ gamecontroller subsystem and `pad_close` stops it, so pads close before
254
+ `screen_close`'s `SDL_Quit`. Axes stay raw — sticks span -32768..32767
255
+ with 0 at rest, triggers 0..32767 — and the `PAD_BUTTON_*` / `PAD_AXIS_*`
256
+ constants name the standard controls, extended share/paddle/touchpad
257
+ buttons included. This is polling, not events: the event loop stays
258
+ keyboard-focused.
259
+ - `examples/sdl_gamepad.vnt` (raw C calls, SDL2 core only) opens a controller
260
+ by index and polls A, d-pad-left, the left stick, and the left trigger;
261
+ with no controller it reports the dead path and exits cleanly. The two new
262
+ headless tests open index 9999 — out of range on every machine — so their
263
+ frozen output is hardware-independent, and the existing games still build
264
+ without the module. 2 new tests (540 total).
265
+ - The tutorial gains a Gamepads section (dead-handle semantics, raw axis
266
+ ranges, polling vs events), the reference adds the module to the kit
267
+ chapter, and both READMEs count eight modules. The new snippets join the
268
+ frozen-output doc checks.
269
+
270
+ ## v0.20.0 — 2026-10-19
271
+
272
+ - Sprites. `gamekit/image.vnt` (a seventh module) wraps SDL2_image:
273
+ `tex_load` decodes a PNG or BMP file into a `Tex` — a handle plus the
274
+ decoded size; a missing or unreadable file comes back dead, so `tex_ok`
275
+ is the test. `tex_draw` blits at natural size, `tex_draw_src` blits one
276
+ rectangle of the texture (sprite-sheet frames), and `tex_draw_ex` rotates
277
+ by a `f64` angle in degrees and/or flips with the new `SDL_FLIP_*`
278
+ constants, about the texture's own centre — and none of the three take a
279
+ scratch rect: SDL copies the rectangles during the call, so the helpers
280
+ build them on a tiny transient arena. `tex_tint` and `tex_alpha` set a
281
+ colour and an opacity multiplied into every later draw of that texture.
282
+ Importing the module means linking SDL2_image; `sdl.vnt` gains
283
+ `SDL_QueryTexture`, `SDL_RenderCopyEx`, the colour/alpha mods, and
284
+ `SDL_DestroyTexture` (moved there from `text.vnt`, where it would have
285
+ collided in the flat namespace).
286
+ - Sound files. `gamekit/audio.vnt` gains `sound_load` / `sound_ok` /
287
+ `sound_play` / `sound_free`: 16-bit PCM WAV at 44.1 kHz, mono or stereo,
288
+ decoded by `SDL_LoadWAV_RW` into a buffer SDL allocates. Stereo is folded
289
+ to mono in place at load, so the `Sound`'s byte count halves; any other
290
+ rate, sample format, or channel count loads as a dead sound, and playing
291
+ or freeing one is a no-op — games stay quiet on machines with no device.
292
+ The game holds the `Sound` across plays and hands the buffer back with
293
+ `sound_free`.
294
+ - `examples/sdl_texture.vnt` (raw C calls, SDL2_image): loads PNG and BMP
295
+ files, draws tinted, draws a sheet region flipped, rotates 90 degrees,
296
+ and checks every draw by reading pixels back from a forced software
297
+ renderer — exact on every machine, headless included.
298
+ `examples/sdl_sound.vnt` loads `examples/assets/blip.wav`, prints the
299
+ file's spec, queues it, and drains the device. The seven assets (three
300
+ PNGs, one BMP, three WAVs) are generated by the committed
301
+ `examples/assets/gen_assets.py` — nothing is vendored.
302
+ - `examples/breakout.vnt` is the third playable game and the worked example
303
+ for both additions: a four-row wall tinted over one white brick texture,
304
+ a ball and paddle loaded from files, and WAV hits. With no key held the
305
+ paddle auto-plays, so the headless run is one exact trajectory: it clears
306
+ all 40 bricks with all three lives intact at frame 2064, and the test suite
307
+ freezes `cleared 40 lives 3 frames 2064` (the frame cap is only a
308
+ backstop).
309
+ - CI installs SDL2_image on all three platforms, so the new tests run
310
+ everywhere; the sprite and sound-file snippets join the frozen-output doc
311
+ checks. 5 new tests (538 total).
312
+
313
+ ## v0.19.0 — 2026-10-19
314
+
315
+ - SDL2 audio. `examples/sdl_audio.vnt` plays a synthesized tone through the
316
+ device's *queue* API — `SDL_OpenAudioDevice` with a hand-built
317
+ `SDL_AudioSpec` (its `callback` field a true C function-pointer type),
318
+ `SDL_QueueAudio`, `SDL_PauseAudioDevice`, `SDL_GetQueuedAudioSize` — and
319
+ `examples/sdl_audio_cb.vnt` puts a Vanta function in the spec's `callback`
320
+ field instead, so SDL runs it on its audio thread; callback state travels
321
+ through the `userdata` pointer, as with any C callback. Under SDL's dummy
322
+ driver both run headless in real time, which is how the tests cover them.
323
+ - `ptr_null::<T>()` builds the null pointer explicitly — the value C APIs ask
324
+ *for*, where `ptr_is_null` only tests one C hands back. `T` may be a data
325
+ type (`void` gives the anonymous `ptr<void>`) or a function type, which
326
+ makes it a null callback: the empty slot a queue-mode `SDL_AudioSpec`
327
+ carries (`SDL_OpenAudioDevice` also wants NULL, not `""`, for "the default
328
+ device"). Like the other pointer builtins it takes no arguments; there is
329
+ still no `nil` literal.
330
+ - The game kit gains `gamekit/audio.vnt` (six modules now): `audio_open` /
331
+ `audio_ok` / `audio_close`, `audio_play_tone(aud, freq, ms, buf, capacity)`
332
+ — fills a caller-owned `i16` buffer with a square wave, queues it, and
333
+ returns the samples queued (0 for a dead device, nonsense values, or an
334
+ SDL error; a tone longer than the buffer is cut short) — plus
335
+ `audio_pending` (the bytes still queued) and `audio_wait`. Failure is
336
+ quiet: with no working device every call does nothing, so games keep
337
+ running silently. The kit ships no sound files; tones are generated.
338
+ - `examples/pong.vnt` beeps: 880 Hz on a player paddle hit, 660 Hz on an AI
339
+ hit, and low/high tones on points. The beeps are fire-and-forget, so the
340
+ headless run is unchanged (`player 0 ai 5 frames 576`).
341
+ - The audio tests need no library beyond SDL2 core, so CI gains no packages;
342
+ they run under `SDL_AUDIODRIVER=dummy`, whose device paces consumption in
343
+ real time, so queueing and draining stay deterministic. Examples:
344
+ `sdl_audio.vnt`, `sdl_audio_cb.vnt`, `gamekit/audio.vnt`. 12 new tests
345
+ (533 total).
346
+
347
+ ## v0.18.0 — 2026-10-19
348
+
349
+ - `ptr_offset(p, n)` moves a pointer `n` elements with its element type kept
350
+ (`p + n` in the generated C++). The offset must be an integer (`i32`;
351
+ narrow values promote) and may be negative; `ptr<void>` is rejected —
352
+ `cannot offset a ptr<void> (it has no element size); ptr_cast it to a
353
+ typed pointer first`. C out-parameters are the motivating case:
354
+ `TTF_SizeUTF8(font, text, &w, &h)` reaches its second slot with
355
+ `ptr_offset(scratch, 1)` instead of a second allocation.
356
+ - Text rendering, through SDL_ttf. `examples/sdl_text.vnt` draws with the
357
+ raw C API — `TTF_Init` / `TTF_OpenFont`, `TTF_SizeUTF8`,
358
+ `TTF_RenderUTF8_Blended` into a texture, `SDL_RenderCopy` — with
359
+ `SDL_Color` declared as a Vanta struct of four `u8` channels passed to C
360
+ by value (the layout was verified against a C mirror).
361
+ - The game kit gains `gamekit/text.vnt`: `font_open` / `font_ok` /
362
+ `font_close`, `text_measure` (writes width and height into a caller
363
+ scratch block), `text_draw(screen, font, x, y, text, scratch, r, g, b)`,
364
+ and `text_quit`. The scratch layout is 10 `i32` slots: `[0] w`, `[1] h`,
365
+ `[2..5]` source rect, `[6..9]` destination rect — one `arena.alloc`
366
+ covers measure and draw. A font that failed to open measures 0x0 and
367
+ draws nothing instead of crashing.
368
+ - `examples/pong.vnt` now draws its score as text (the pip rectangles are
369
+ gone) and links `SDL2_ttf`; the headless run is unchanged
370
+ (`player 0 ai 5 frames 576`).
371
+ - The kit ships a font: `examples/gamekit/fonts/tom-thumb.ttf`, a 3x5
372
+ pixel monospace face (CC0/CC-BY 3.0; credits in `fonts/LICENSE.md`), so
373
+ examples, docs, and CI depend on no system fonts.
374
+ - CI installs SDL_ttf everywhere (`libsdl2-ttf-dev`, `sdl2_ttf`,
375
+ `mingw-w64-x86_64-SDL2_ttf`); the SDL_ttf tests skip when it is missing,
376
+ like the SDL2 tests. Examples: `ptr_offset.vnt`, `sdl_text.vnt`,
377
+ `gamekit/text.vnt`. 14 new tests (521 total).
378
+
379
+ ## v0.17.0 — 2026-10-18
380
+
381
+ - Narrow integers: `i8`, `u8`, `i16`, `u16`. They store their exact width —
382
+ a `u8` field is one byte (`uint8_t` in the generated C++) — and read as
383
+ `i32` in arithmetic, C's integer promotion. Assigning any integer into a
384
+ narrow slot keeps the low bits, but a *literal* that does not fit is a
385
+ compile-time error (`initializer: value 300 out of range for u8`), and the
386
+ same check covers literals beside a narrow value (`x == 300`). Narrow types
387
+ work everywhere a type is written: fields, arrays, `ptr<u16>`, `List<u8>`,
388
+ and `extern` signatures. Pushing to a narrow `List` element type now emits
389
+ an explicit template argument so wider values still convert.
390
+ - `ptr_cast::<T>(p)` reinterprets a pointer's element type
391
+ (`reinterpret_cast` semantics, unchecked): view the same memory as another
392
+ type, or give a `ptr<void>` from C a useful element type again. Any data
393
+ type is a legal target, `void` included (erasing the type); function-type
394
+ targets are rejected (`ptr_cast cannot target a function type`).
395
+ - `for x in xs` iterates a `List<T>` or an array element by element. The loop
396
+ variable is a read-only copy, one per iteration; `break`/`continue` mean the
397
+ same as in range loops; anything else is a compile error (`cannot iterate
398
+ over ptr<i32>; for-each supports arrays and Lists`). A list's length is
399
+ re-read every pass, so do not grow one mid-walk.
400
+ - Struct equality: `==`/`!=` on user structs compare field by field — numbers
401
+ by value, `str` by content, arrays element-wise, nested structs
402
+ recursively. A field with no equality (pointer, function value, `List`,
403
+ `Quat`/`Matrix4`) is a compile error naming the path (`cannot compare struct
404
+ 'Outer' with '==': field 'inner.p' (ptr<i32>) has no equality`). Every
405
+ comparable struct gets non-member `operator==`/`!=` emitted — all
406
+ declarations before all definitions, so nesting order does not matter.
407
+ - Enum types may now appear in `extern` signatures (parameters, returns,
408
+ `ptr<Enum>`, `List<Enum>`); the generated C++ names the fixed-`int32_t`
409
+ enum and forward-declares it ahead of the `extern "C"` block, so the C side
410
+ declares `int32_t` or a matching C enum. The old "pass `enum_to_i32(...)`
411
+ instead" limitation is gone.
412
+ - Examples: `narrow_ints.vnt` plus a C fixture (exact-width parameters and a
413
+ C mirror struct reading a Vanta struct's bytes for the layout check),
414
+ `foreach.vnt`, `struct_eq.vnt`, `ptr_cast.vnt`, and `enums_extern.vnt` plus
415
+ a C fixture. 52 new tests (507 total).
416
+
417
+ ## v0.16.0 — 2026-10-18
418
+
419
+ - C callbacks: an `extern` parameter typed `func(...)` is now a C function
420
+ pointer, so C libraries can call back into Vanta code. Pass any function
421
+ value — a Vanta function or another `extern`. The generated C++ spells the
422
+ callback inline where it appears (`int32_t(*v_f)(int32_t)` in a
423
+ declaration, `int32_t(**v_slot)(int32_t)` through a `ptr<func(...)>`), so
424
+ no aliases are needed before the namespace.
425
+ - An `extern` cannot return a function type yet: the error says so and
426
+ suggests wrapping the C function in a shim (`extern function 'get' cannot
427
+ return a function type yet; wrap it in a C shim that calls it`).
428
+ - Codegen fix: struct types in extern signatures are now namespace-qualified
429
+ and forward-declared ahead of the `extern "C"` block, so
430
+ `func f(p: ptr<State>)` in an `extern` compiles (previously the bare
431
+ `State` name was emitted before any declaration).
432
+ - `examples/callbacks.vnt` + `examples/callbacks_lib.c`: a callback C calls
433
+ twice (`f(f(x))`) and a visitor with a `ptr<i32>` user-data pointer.
434
+ A libc `atexit` test exercises callbacks on every platform. 8 new tests
435
+ (455 total).
436
+
437
+ ## v0.15.0 — 2026-10-18
438
+
439
+ - `List<T>` can be edited in place: `xs.insert(i, v)` inserts at index `i`,
440
+ shifting the rest right (`i` may be `len(xs)`, the same as `push`), and
441
+ `xs.remove(i)` removes and returns the element at `i`, shifting the rest
442
+ left. Out-of-range indexes stop the program with
443
+ `vanta: insert index I out of bounds (size N) at line L` /
444
+ `vanta: remove index I out of bounds (size N) at line L`.
445
+ - `xs.sort(cmp)` sorts a list in place with a comparator — a function value
446
+ `func(T, T) -> i32` returning negative, zero, or positive for less-than,
447
+ equal, greater-than. It is a stable, `O(n log n)` merge sort: elements the
448
+ comparator calls equal keep their order.
449
+ - The comparator is fully checked: a missing, wrongly-typed, or wrong-arity
450
+ comparator is a compile error naming the expected signature, e.g.
451
+ `'sort' expects a comparator func(i32, i32) -> i32, got func(i32, i32) -> bool`.
452
+ - Codegen fix: function-pointer aliases may now mention structs by value —
453
+ struct forward declarations are emitted before the aliases — so comparators
454
+ over `List<Struct>` work.
455
+ - `examples/lists.vnt` now demonstrates insert, remove, and a comparator
456
+ sort (stable ties included); 17 new tests (447 total).
457
+
458
+ ## v0.14.0 — 2026-10-18
459
+
460
+ - Added the game kit: `examples/gamekit/` packages the SDL2 plumbing as four
461
+ importable modules — `sdl.vnt` (`extern` declarations, `EV_*`/`KEY_*`
462
+ constants, the `Screen` lifecycle), `input.vnt` (event accessors),
463
+ `gfx.vnt` (clear, filled rect, outline, present), and `rng.vnt` (bounded
464
+ integers and floats, random signs).
465
+ - `examples/pong.vnt`, the second playable game, is built on the kit: W/S
466
+ or Up/Down against a tracking AI, wall bounces, contact-point steering,
467
+ first to 5 wins. `match` picks the serve direction.
468
+ - Pong runs headless like the other SDL examples: a frame-limit argument
469
+ plus `SDL_VIDEODRIVER=dummy`. The frozen headless run: the AI wins 5-0 in
470
+ frame 576. `gamekit/rng.vnt` has no SDL dependency, so it gets its own
471
+ test that runs on every machine with a C++ compiler.
472
+ - No compiler changes: the kit is plain Vanta, and the language needed
473
+ nothing new — struct fields typed `ptr<void>` (the `Screen` handle), a
474
+ failure value carried by C's own NULLs instead of a nil literal, and
475
+ ordinary imports all already worked.
476
+ - 2 new tests (430 total).
477
+
478
+ ## v0.13.0 — 2026-10-18
479
+
480
+ - Added `match`: branch on an enum value with `Enum.Member:` arms (several
481
+ members per arm, commas between them) and an optional `else:` fallback.
482
+ Arms run top to bottom; the first match wins. `examples/match.vnt` walks
483
+ a little state machine:
484
+
485
+ ```vanta
486
+ match g.state:
487
+ State.Menu:
488
+ print_str("fresh start")
489
+ State.Playing, State.Paused:
490
+ print_str("in game")
491
+ else:
492
+ print_str("somewhere else")
493
+ ```
494
+
495
+ - The checker proves coverage: when every member has an arm, no `else` is
496
+ needed, and a missing member is a compile error that names the members
497
+ it needs — add a member to an enum later and every match that must
498
+ handle it says so. A member used by two arms, a member of a different
499
+ enum, and a misspelled member are errors too.
500
+ - `match` arms have their own scope, and `break`/`continue` inside an arm
501
+ still control the enclosing loop: `match` is not a loop, and codegen
502
+ keeps it that way (an if/else chain rather than a C++ `switch`). The
503
+ matched value is evaluated exactly once.
504
+ - `match` on numbers, strings, or structs is a compile error; it is a
505
+ statement, not an expression. `match` joins the reserved keywords.
506
+ - 25 new tests (428 total).
507
+
508
+ ## v0.12.0 — 2026-10-18
509
+
510
+ - SDL2 support, shown end to end. `examples/sdl_window.vnt` opens a window
511
+ and clears it to a colour; `examples/sdl_game.vnt` is the first playable
512
+ Vanta game — move the paddle with Left/Right (or A/D) to catch falling
513
+ coins, Esc or the close button quits. Both are pure `extern`
514
+ declarations, so no SDL headers are involved at compile time:
515
+
516
+ ```powershell
517
+ python main.py run examples/sdl_game.vnt --lib-dir C:/msys64/mingw64/lib --link SDL2
518
+ ```
519
+
520
+ - Pointers widen to `ptr<void>` implicitly, matching C's conversion to
521
+ `void*`: any `ptr<T>` is accepted where `ptr<void>` is expected, which
522
+ is how opaque C handles (`SDL_Window`, `SDL_Renderer`, ...) are typed.
523
+ The narrowing direction stays an error, and a `ptr<void>` cannot be
524
+ indexed — it has no element type.
525
+ - New builtin `ptr_is_null(p) -> bool`: compares any pointer (including
526
+ `ptr<void>`) against null so failed calls can be handled. Pointer
527
+ bindings can be reassigned with `mut`, and user functions may take and
528
+ return pointers.
529
+ - Fixed: functions returning pointers never compiled — the implicit
530
+ default return was invalid C++ (`T*{}`); a fallthrough now yields
531
+ `nullptr`. Same bug class as the v0.11.0 `str` fix.
532
+ - Both SDL examples take an optional frame-limit argument and run headless
533
+ under `SDL_VIDEODRIVER=dummy`, so the test suite builds, links, and runs
534
+ them for real; CI installs SDL2 on Linux, macOS, and Windows. 20 new
535
+ tests (403 total).
536
+
537
+ ## v0.11.0 — 2026-10-18
538
+
539
+ - Added enums: `enum Name:` declares a fixed set of named members, numbered
540
+ from 0 in declaration order and written `Name.Member` (e.g.
541
+ `State.Playing`). An enum is a first-class type — variables, struct
542
+ fields, function parameters and returns, arrays, `List<T>`, function
543
+ values, and imported modules all accept enum values unchanged.
544
+ - Values compare with `==` and `!=`. Ordering comparisons on enums, and
545
+ compares between different enums or against numbers, are compile errors.
546
+ `enum_to_i32(v)` returns a member's number for printing, arithmetic, or
547
+ passing to an `extern` function (extern signatures cannot take enum types
548
+ directly yet). Enum values cannot be constructed (`State(1)` is an error),
549
+ and enum types have no fields — `s.Menu` suggests `State.Menu`.
550
+ - Fixed: functions returning `str` never compiled — the implicit default
551
+ return that codegen appends was invalid C++ (`return const char*{};`).
552
+ A function that falls through now yields the empty string.
553
+ - Fixed: a type declared after a function of the same name (`func P()` then
554
+ `struct P:`) is now reported as a collision instead of silently shadowing
555
+ the function.
556
+ - New example `examples/enums.vnt`: a tiny state machine switching on a
557
+ `State` enum, plus `enum_to_i32` and iteration over an array of members;
558
+ plus 42 tests (383 total).
559
+
560
+ ## v0.10.0 — 2026-10-18
561
+
562
+ - Added `StrBuilder`, an arena-backed string builder: `arena.str_builder()`
563
+ creates one growing buffer, `append_str` / `append_i32` / `append_u32` /
564
+ `append_f32` / `append_f64` / `append_bool` append (formatted exactly like
565
+ the matching `print_*`), `len(b)` reads the byte count, `clear()` resets it
566
+ for reuse, and `to_str()` returns the current contents as a `str` — copied
567
+ into a fresh arena block, so the result stays stable across later appends,
568
+ growth, and `clear()`. Growth doubles from 32 bytes, and the fixed string
569
+ pool is never touched.
570
+ - Like `List<T>`, a builder is a handle: copies alias the same buffer, appends
571
+ work through a plain `let`, and helpers can take a `StrBuilder` parameter
572
+ and append to the caller's builder. `arena.reset()` invalidates the builder
573
+ and the strings taken from it.
574
+ - New example `examples/str_builder.vnt`: building a line once, reusing the
575
+ buffer with `clear()`, and a helper that appends through its parameter;
576
+ plus 28 tests (341 total).
577
+
578
+ ## v0.9.0 — 2026-10-18
579
+
580
+ - Added dynamic lists: `List<T>`, a growable array backed by an `Arena`.
581
+ `arena.list::<T>()` creates an empty list, `xs.push(v)` appends (growing by
582
+ doubling from a capacity of 4), `xs.pop()` removes the last element, and
583
+ `len(xs)` reads the size. Element access reuses the v0.7.0 checked
584
+ `xs.at(i)` — reads and writes, with out-of-bounds errors naming the list
585
+ size and source line — plus the unchecked `xs[i]` form.
586
+ - A list is a handle: copies alias the same storage, and passing one to a
587
+ function lets the function append to the caller's list. Since a list is an
588
+ indirection like `ptr<T>`, element writes and `push`/`pop` work through a
589
+ plain `let`; `mut` only controls rebinding. `List<void>` and `List<Arena>`
590
+ are compile errors, and `arena.reset()` invalidates the arena's lists.
591
+ - New example `examples/lists.vnt`: particles in a list, integrated through a
592
+ function call, with checked reads and writes and a pop; plus 39 tests
593
+ (313 total).
594
+
595
+ ## v0.8.0 — 2026-10-18
596
+
597
+ - Added trigonometry: `sin`, `cos`, and `tan` for `f32` and `f64`, plus
598
+ `atan2_f32`/`atan2_f64` — the angle of the vector `(x, y)`, the usual
599
+ "heading toward a point" operation. Angles are radians, and the f32/f64
600
+ pairs follow the existing `sqrt_f32`/`sqrt_f64` naming. They are callable
601
+ like any builtin and usable as function values.
602
+ - Extended `examples/arrays_and_math.vnt` with a 45-degree unit vector and
603
+ its heading recovered through `atan2`, plus 7 tests (274 total).
604
+
605
+ ## v0.7.0 — 2026-10-18
606
+
607
+ - Added bounds-checked array access: `xs.at(i)` checks the index against the
608
+ fixed size of `xs` before touching memory. Reads return the element, and
609
+ writes (`xs.at(i) = v`, including compound forms) obey the normal mutability
610
+ rules. An out-of-range or negative index prints
611
+ `vanta: index I out of bounds (size N) at line L` to stderr and exits 1.
612
+ Plain `xs[i]` stays unchecked.
613
+ - `at` composes with nested arrays — `g.at(y).at(x)` reads and
614
+ `g.at(y).at(x) = v` writes — and rejects `ptr<T>` receivers with "a pointer
615
+ has no length", since pointers have no size to check against.
616
+ - Codegen emits `vanta::at_checked` for reads and `vanta::at_checked_ref` (a
617
+ real reference, so writes land in the array) for writes, carrying the
618
+ access's source line into the failure message.
619
+ - Added `examples/bounds.vnt`, plus 29 tests (267 total).
620
+
621
+ ## v0.6.0 — 2026-10-18
622
+
623
+ - Functions are first-class values. A `func(P1, P2) -> R` type names any
624
+ function with that signature (omit the arrow for `void`), and values can be
625
+ stored in variables, passed as arguments, returned, kept in struct fields and
626
+ arrays, or allocated from an arena. The same call syntax invokes them:
627
+ `f(x)`, `handlers[0](x)`, `op.run(x)`, `pick()(x)`.
628
+ - Values are plain function pointers — there are no closures, so nothing is
629
+ captured. Vanta functions (emitted with their `fn_` prefix), `extern`
630
+ functions, and builtins like `print_str` all work as values.
631
+ - Rejected on purpose: `len`/`print_fmt`/`write_fmt`/`assert` and type names as
632
+ values, `==`/`!=` on function values, function values in `const`
633
+ initializers, and function types in `extern` signatures (C callbacks come
634
+ later).
635
+ - Local bindings now shadow functions of the same name in call position, so
636
+ the canonical `func apply(f: func(i32) -> i32, ...)` works even when a
637
+ function named `f` exists.
638
+ - Codegen emits one `using FnPtrN = R(*)(...)` alias per distinct function
639
+ type, innermost first; type names matching `FnPtr<digits>` are reserved.
640
+ - Added `examples/dispatch.vnt`, plus 51 tests (238 total).
641
+
642
+ ## v0.5.0 — 2026-10-18
643
+
644
+ - 2D arrays work end to end. Nested array types and literals (`[[i32, 8], 4]`)
645
+ already parsed, but writes through pointers and struct fields were rejected;
646
+ assignment targets are now validated by walking the whole storage chain, so
647
+ arena-backed grids (`arena.alloc::<[i32, 8]>(h)`) and `mut` struct grids are
648
+ writable at any depth while `let` bindings and parameters stay immutable.
649
+ - Array literals and call argument lists may span lines: the lexer ignores
650
+ newlines inside `[...]` and `(...)`.
651
+ - Added `examples/grid.vnt`: a stack checkerboard, a struct-held heightmap,
652
+ and an arena-backed dynamic grid, plus 11 tests (187 total).
653
+
654
+ ## v0.4.0 — 2026-10-18
655
+
656
+ - Added `import "path.vnt"`. An import merges another file's declarations
657
+ into the program: paths resolve relative to the importing file, imports are
658
+ transitive, and each file is loaded once, so diamonds are fine. Import
659
+ cycles and missing files are compile errors. Declarations merge into the
660
+ existing flat namespace — imported functions, structs, constants, and
661
+ externs are used by their bare names. `import` is now a reserved word.
662
+ - Errors inside imported files now name the file that contains them: every
663
+ AST position carries its source file, and `CheckError` prefers it.
664
+ - Added `examples/imports_demo.vnt` with its module
665
+ `examples/imports_demo_lib.vnt`, plus 12 tests (176 total).
666
+ - New `compiler/loader.py` owns import expansion; `_frontend_only` now goes
667
+ through it.
668
+
669
+ ## v0.3.1 — 2026-10-18
670
+
671
+ - Added `file_exists(path)`, `file_write(path, data)`, and `file_read(path)`.
672
+ `file_read` allocates from the string pool (valid until `str_pool_reset()`)
673
+ and returns `""` on any failure; a file too large for the pool fails loudly
674
+ with a specific message instead of truncating. `file_write` returns the byte
675
+ count or `-1` on failure.
676
+ - Added `examples/file_io.vnt`: reads a file named on the command line and
677
+ writes a copy to `<path>.copy`, reporting both byte counts.
678
+
679
+ ## v0.3.0 — 2026-10-18
680
+
681
+ - Added `extern "lib" { ... }` declarations. Extern functions are declared
682
+ without bodies, type-checked like any other function, and called under their
683
+ original names; the generated C++ puts them in an `extern "C" {}` block
684
+ outside `namespace vanta_user`, so the linker sees unmangled symbols.
685
+ - Added `--include-dir`, `--lib-dir`, and `--link` (all repeatable) to `build`
686
+ and `run`: `-I`/`-L`/`-l` on GCC-style toolchains, `/I`/`/LIBPATH:`/`<lib>.lib`
687
+ on MSVC.
688
+ - Added `examples/extern_demo.vnt` with its C side in
689
+ `examples/extern_demo_lib.c`, plus an end-to-end integration test that
690
+ compiles the C library, links it, and checks the output.
691
+ - Repository history: the tags v0.1.1 through v0.2.5 were re-cut so that each
692
+ tagged commit reports its own version from `--version`; the stored version
693
+ string had lagged the tags before v0.2.2's sync and again until v0.2.6.
694
+
695
+ ## v0.2.6 — 2026-10-18
696
+
697
+ - Added `print_fmt(fmt, args...)` and `write_fmt(fmt, args...)`. Placeholders are
698
+ `{}` and are resolved at compile time: the type checker counts them and checks
699
+ each argument's type, and the code generator turns each one into the matching
700
+ printf conversion. A literal/integer mismatched format is a compile error, not
701
+ a runtime surprise.
702
+ - Format literals support `{{` and `}}` for literal braces, and `%` is escaped
703
+ automatically so a stray percent cannot corrupt the output.
704
+ - Added a process-lifetime string pool with `str_concat`, `str_len`,
705
+ `to_str_i32`, `to_str_f32`, `to_str_bool`, and `str_pool_reset`. Exhaustion
706
+ fails loudly rather than corrupting memory.
707
+ - Added `str_to_i32` and `str_to_f32`, without which command-line arguments
708
+ could not be used as numbers.
709
+ - Added `examples/report.vnt`.
710
+ - New `compiler/formats.py` holds the format parser shared by the checker and
711
+ the code generator.
712
+
713
+ ## v0.2.5 — 2026-10-18
714
+
715
+ - Added `arg_count()` and `arg_str(i)`; the generated `main` now receives
716
+ `argc`/`argv` and hands them to the runtime
717
+ - `python main.py run prog.vnt a b` now passes `a b` through to the program
718
+
719
+ ## v0.2.4 — 2026-10-18
720
+
721
+ - Added `examples/life.vnt` — Conway's Game of Life on a 40x20 torus, drawn as a
722
+ character grid
723
+
724
+ ## v0.2.3 — 2026-10-18
725
+
726
+ - Added `write_i32`, `write_u32`, `write_f32`, `write_f64`, `write_bool`, and
727
+ `write_str` — like their `print_*` counterparts but with no trailing newline
728
+ - Registered `print_newline()`, which had been sitting unreachable in the runtime
729
+ - Added `abs_i32`, `abs_f32`, `min_i32`, `max_i32`, `min_f32`, `max_f32`,
730
+ `floor_f32`, `ceil_f32`
731
+
732
+ ## v0.2.2 — 2026-10-18
733
+
734
+ - Added `rand_f32()` — xorshift32, returns `f32` in `[0, 1)`
735
+ - Added `rand_seed(seed: i32)` to select a random stream
736
+ - Random state is fixed at startup, so programs are reproducible until seeded
737
+ - Added 7 tests (type checker, codegen, and an end-to-end determinism test)
738
+ - Fixed: `compiler.VERSION` still reported `0.1.0` and the generated C++ header
739
+ hardcoded `0.2.0`. Both now report `0.2.2`, and the header reads the shared
740
+ `VERSION` constant instead of a literal, so it cannot drift again.
741
+
742
+ ## v0.2.1 — 2026-10-17
743
+
744
+ - Added `examples/boids.vnt` — 30 boids on a 100x100 torus, deterministic init
745
+ - Added an end-to-end integration test asserting the four quadrant counts sum
746
+ to 30, so the simulation cannot silently stop placing every boid
747
+
748
+ ## v0.2.0 — 2026-10-17
749
+
750
+ - Added the `str` type, with escape sequences `\t`, `\n`, `\"`, `\\`
751
+ - Added `print_str`
752
+ - `==` and `!=` on `str` compare contents via `strcmp`, not pointers
753
+ - Added `Vec2.normalized` and `Quat.dot`
754
+ - Added `translation`, `scale`, and `from_quat` builtins returning `Matrix4`
755
+
756
+ ## v0.1.3 — 2026-10-17
757
+
758
+ - Added fixed-size arrays: `[T, N]` types, array literals, indexing, and `len(xs)`
759
+ - Added Quat math: `Quat * Quat`, `Quat * Vec3`
760
+ - Added Matrix4 extras: `Matrix4 * Matrix4`, `Matrix4 * Vec3`
761
+ - Documented `examples/arrays_and_math.vnt`
762
+
763
+ ## v0.1.2 — 2026-10-17
764
+
765
+ - Added `const` declarations (compile-time values, emitted as C++ `constexpr`)
766
+ - Added `assert(cond)`
767
+ - Added return-type inference when `-> T` is omitted
768
+ - Added 3-argument `range(start, end, step)`
769
+ - Added element-wise `Vec2` / `Vec3` equality and inequality
770
+ - Fixed mixed-literal return-type inference
771
+
772
+ ## v0.1.1 — 2026-10-17
773
+
774
+ - Added compound assignment `+=`, `-=`, `*=`, `/=`
775
+ - Added `break` and `continue`, with loop-context checking
776
+ - Added `examples/loops.vnt`
777
+
778
+ ## v0.1.0 — 2026-10-17
779
+
780
+ - Initial working compiler:
781
+ source → lex → parse → type-check → C++17 → native binary
782
+ - Types: `i32`, `u32`, `f32`, `f64`, `bool`, `void`, `ptr<T>`
783
+ - Structs, `component`, and `system` declarations
784
+ - Non-copyable stack-scoped `Arena` allocator
785
+ - Built-in `Vec2`, `Vec3`, `Matrix4`, `Quat`
786
+ - Examples: `examples/vector_math.vnt`, `examples/physics.vnt`