feat(compiler): list literals, typed compound assignment, file:line diagnostics
- `[a, b, c]` list literals (E_LIST → emit_list); static_type learns slice-element, `new T`, list, string and literal kinds - `x op= y` lowers through the same path as `x = x op y` (emit_bin_vals): fixed `*=`/`/=` use the Q16.16 64-bit paths, string `+=` concatenates, int→long widens; unary `-` keeps a fixed operand's type (arith_ty) - one `unescape()` table for "strings", 'chars' and `interpolation`; `'\''`, `'\\'`, `'\"'` no longer read as 0; unterminated char literals and unexpected characters are errors instead of silently skipped - every diagnostic is `file:line: error: msg` (g_parse_file / g_err_file, Node.file + Node.line set by node()); tok_desc() in expectation errors; duplicate `function` names and unknown `phase` names are reported in source terms (phase_id used to default unknown phases to Overlay) - interpolation holes skip braces inside string literals - hand-IR preludes move from the user `@fn_` prefix to `@lp_` so a user `is_ws` / `str_eq` / `path_join` no longer collides at link time - `@ClearColor(expr)` accepts any constant expression; `Os.pid()` added (docs page + inventory); `str_starts()` in support/str - main.ludic: `else if` flag ladder, char literals, stale script comments - examples/lang/operators.ludic covers all of the above; os.ludic covers Os.pid; docs pages for Os.pid and the Overlay phase; ten changesets - reseeded: selfhost/ludicc.seed.ll is the new compiler's own fixpoint Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
ad548840c7
commit
647dfec334
88 changed files with 30081 additions and 29179 deletions
3
changes/char-escapes.md
Normal file
3
changes/char-escapes.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
Character literals accept the same escapes as strings (`'\''`, `'\\'` and `'\"'` were silently read as 0); an unterminated character literal is now an error.
|
||||||
3
changes/clearcolor-expr.md
Normal file
3
changes/clearcolor-expr.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: feat
|
||||||
|
`@ClearColor(expr)` takes any constant expression, so a named palette colour (`@ClearColor(COLOR_FLOOR)`) works as well as a hex literal.
|
||||||
3
changes/compound-assign-types.md
Normal file
3
changes/compound-assign-types.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
`x += y` / `-=` / `*=` / `/=` now lower exactly like `x = x op y`: a Q16.16 `fixed` multiplies and divides through the 64-bit path, a string `+=` concatenates, and an `int` added to a `long` widens (they previously emitted raw integer arithmetic on the LLVM type).
|
||||||
3
changes/diagnostics-file-line.md
Normal file
3
changes/diagnostics-file-line.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
Every compiler diagnostic is reported as `file:line: error: message` — the file the line really lives in, even through imports — so editors can jump to it. Unexpected characters are errors (they used to be skipped silently), and defining one `function` twice is reported in source terms instead of failing in the IR assembler.
|
||||||
3
changes/formatter-brackets.md
Normal file
3
changes/formatter-brackets.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
`ludic-fmt` keeps `rows[i]`, `new []int`, `s[a..b]`, `emit(…)`, `~x` and list-literal braces tight, and recognises `<< >> & | ^ ~` as operators.
|
||||||
3
changes/interp-braces.md
Normal file
3
changes/interp-braces.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
A `{` or `}` inside a string literal within an interpolation hole (`` `{f("{")}` ``) is text, not structure; the hole scanner used to miscount it.
|
||||||
3
changes/list-literals.md
Normal file
3
changes/list-literals.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
`[a, b, c]` list literals build a slice in place; the first element fixes the element type, later elements must match, and `[]` is an error (use `new []T`). Tables of records read as `[Row { … }, Row { … }]`.
|
||||||
3
changes/os-pid.md
Normal file
3
changes/os-pid.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
`Os.pid()` returns the process id, for scratch files that concurrent runs of one tool must not share.
|
||||||
3
changes/prelude-symbol-prefix.md
Normal file
3
changes/prelude-symbol-prefix.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
Hand-written runtime preludes (string, Os.*, Fs.*, Crypto.*, …) now live under their own `@lp_` symbol prefix, so a user `function` named `is_ws`, `str_eq`, `path_join` and the like no longer collides with them at link time.
|
||||||
3
changes/unary-minus-fixed.md
Normal file
3
changes/unary-minus-fixed.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
Unary minus keeps its operand type: `-f` on a `fixed` is a `fixed` (it was typed `int`, which broke mixed arithmetic and comparisons).
|
||||||
|
|
@ -9,7 +9,7 @@ tip: Declare a clear colour so the Render phase auto-clears + auto-presents for
|
||||||
order: 62
|
order: 62
|
||||||
---
|
---
|
||||||
|
|
||||||
<code>@ClearColor(N)</code> declares the framebuffer clear colour (a <code>0x00RRGGBB</code> integer) so the engine owns the per-frame clear and present: at the top of the Render phase it clears the framebuffer to <code>N</code>, and after all Render handlers run it presents the frame — so a game's Render handler carries no <code>Screen.clear</code> / <code>Screen.show</code> boilerplate, and the colour is configured declaratively rather than written in the handler body. It is opt-in: a program with no <code>@ClearColor</code> is byte-for-byte identical (it clears and presents itself, or the light system owns the present). Place it on the program's Render handler (or any declaration — it is program-wide).
|
<code>@ClearColor(N)</code> declares the framebuffer clear colour (a <code>0x00RRGGBB</code> integer) so the engine owns the per-frame clear and present: at the top of the Render phase it clears the framebuffer to <code>N</code>, and after all Render handlers run it presents the frame — so a game's Render handler carries no <code>Screen.clear</code> / <code>Screen.show</code> boilerplate, and the colour is configured declaratively rather than written in the handler body. It is opt-in: a program with no <code>@ClearColor</code> is byte-for-byte identical (it clears and presents itself, or the light system owns the present). Place it on the program's Render handler (or any declaration — it is program-wide). The argument is any constant expression — a hex literal, a `Color.*` name, or a `const` from your palette.
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program ClearColorDemo {
|
program ClearColorDemo {
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,19 @@ program TitleMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Boot phase Start {
|
handler Boot phase Start {
|
||||||
title_font = font_load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||||
ui_build()
|
ui_build()
|
||||||
ui_open(UI_MainMenu)
|
Ui.open(UI_MainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Navigate phase Update {
|
handler Navigate phase Update {
|
||||||
ui_tick(Input.key())
|
Ui.tick(Input.key())
|
||||||
if ui_clicked(UI_Quit) { quit() }
|
if Ui.clicked(UI_Quit) { quit() }
|
||||||
}
|
}
|
||||||
|
|
||||||
handler DrawWorld phase Render {
|
handler DrawWorld phase Render {
|
||||||
Screen.clear(Color.MidnightBlue)
|
Screen.clear(Color.MidnightBlue)
|
||||||
ui_render()
|
Ui.render()
|
||||||
Screen.show()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
docs/language/os/os-pid.md
Normal file
23
docs/language/os/os-pid.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
id: os-pid
|
||||||
|
name: Os.pid
|
||||||
|
category: os
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Os.pid
|
||||||
|
sig: Os.pid() -> int
|
||||||
|
tip: This process's id.
|
||||||
|
order: 18
|
||||||
|
ns: Os
|
||||||
|
member: pid
|
||||||
|
---
|
||||||
|
|
||||||
|
The operating system's id for this process — unique among the processes alive right now, so it is the natural suffix for scratch files that concurrent runs of the same tool must not share.
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program Pid {
|
||||||
|
entry {
|
||||||
|
let scratch = `{Os.temp_dir()}/mytool_{Os.pid()}.log`
|
||||||
|
print(len(scratch) > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
41
docs/language/phases/phase-overlay.md
Normal file
41
docs/language/phases/phase-overlay.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
id: phase-overlay
|
||||||
|
name: Overlay
|
||||||
|
category: phases
|
||||||
|
kind: phase
|
||||||
|
tokens: Overlay
|
||||||
|
sig: phase Overlay
|
||||||
|
tip: The HUD pass — runs after Render and after every engine-owned drawing system.
|
||||||
|
order: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
`Overlay` runs after `Render` and after every engine-owned drawing system (sprites, lights, the scene's `shows` menu), so a `handler … phase Overlay` paints on top of the finished world: health bars, score, prompts, a boss bar. With `@ClearColor` the engine owns the clear and the present, so an `Overlay` handler only draws — no `Screen.clear`, no `Screen.show`. Like `Render`, it should read the world and never change it.
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program OverlayExample {
|
||||||
|
property Position { column: int = 0, row: int = 0 }
|
||||||
|
model Player { Position }
|
||||||
|
|
||||||
|
const TILE_SIZE: int = 16
|
||||||
|
var score: int = 0
|
||||||
|
|
||||||
|
handler PlacePlayer phase Start {
|
||||||
|
spawn Player { Position { column: 5, row: 6 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ClearColor(Color.MidnightBlue)
|
||||||
|
handler DrawWorld phase Render {
|
||||||
|
for (current_position) in query [Position, {Player}] {
|
||||||
|
Screen.fill_rectangle(
|
||||||
|
x: current_position.column * TILE_SIZE,
|
||||||
|
y: current_position.row * TILE_SIZE,
|
||||||
|
width: TILE_SIZE, height: TILE_SIZE, color: Color.LimeGreen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler DrawHud phase Overlay {
|
||||||
|
Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1)
|
||||||
|
Screen.draw_number(x: 52, y: 4, value: score, color: Color.Gold, scale: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -9,7 +9,7 @@ tip: Declare a retained widget tree as data; the engine lays it out and draws it
|
||||||
order: 50
|
order: 50
|
||||||
---
|
---
|
||||||
|
|
||||||
A <code>ui</code> block declares a <b>retained</b> widget tree as data — panels, labels and buttons — and hands layout, drawing and keyboard focus to the engine, so you describe the interface once instead of repainting it every frame. Widget types are `panel` (a container with optional skin/background/border), `col`/`row` (pure stacks), `label`, `button` (focusable), `image` and `spacer`, and their props are evaluated at build time, so `font: title_font` reads a value the program set first. Each `id: Name` mints a `UI_Name` handle you drive from handlers: call `ui_build()` then `ui_open(UI_MainMenu)` at start, `ui_tick(Input.key())` each update to move focus and activate, and `ui_clicked(UI_Name)` to react. Draw it during `Render` with `ui_render()` between `Screen.clear` and `Screen.show()`.
|
A <code>ui</code> block declares a <b>retained</b> widget tree as data — panels, labels and buttons — and hands layout, drawing and keyboard focus to the engine, so you describe the interface once instead of repainting it every frame. Widget types are `panel` (a container with optional skin/background/border), `col`/`row` (pure stacks), `label`, `button` (focusable), `image` and `spacer`, and their props are evaluated at build time, so `font: title_font` reads a value the program set first. Each `id: Name` mints a `UI_Name` handle you drive from handlers: call `Ui.build()` then `Ui.open(UI_MainMenu)` at start, `Ui.tick(Input.key())` each update to move focus and activate, and `Ui.clicked(UI_Name)` to react. Draw it during `Render` with `Ui.render()` between `Screen.clear` and `Screen.show()`.
|
||||||
|
|
||||||
```ludic
|
```ludic
|
||||||
program TitleScreen {
|
program TitleScreen {
|
||||||
|
|
@ -24,19 +24,19 @@ program TitleScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Boot phase Start {
|
handler Boot phase Start {
|
||||||
title_font = font_load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
title_font = Font.load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||||
ui_build()
|
Ui.build()
|
||||||
ui_open(UI_MainMenu)
|
Ui.open(UI_MainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Navigate phase Update {
|
handler Navigate phase Update {
|
||||||
ui_tick(Input.key())
|
Ui.tick(Input.key())
|
||||||
if ui_clicked(UI_Quit) { quit() }
|
if Ui.clicked(UI_Quit) { quit() }
|
||||||
}
|
}
|
||||||
|
|
||||||
handler DrawWorld phase Render {
|
handler DrawWorld phase Render {
|
||||||
Screen.clear(Color.MidnightBlue)
|
Screen.clear(Color.MidnightBlue)
|
||||||
ui_render()
|
Ui.render()
|
||||||
Screen.show()
|
Screen.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ over the compiler's built-in loopback transport with zero foreign code.
|
||||||
| [`lang/toggle.ludic`](lang/toggle.ludic) | enable/disable at the three ECS scopes. |
|
| [`lang/toggle.ludic`](lang/toggle.ludic) | enable/disable at the three ECS scopes. |
|
||||||
| [`lang/scenes.ludic`](lang/scenes.ludic) | One active scene at a time, handlers grouped into layers. |
|
| [`lang/scenes.ludic`](lang/scenes.ludic) | One active scene at a time, handlers grouped into layers. |
|
||||||
| [`lang/strings.ludic`](lang/strings.ludic) | Strings as values: compare, join, interpolate, slice. |
|
| [`lang/strings.ludic`](lang/strings.ludic) | Strings as values: compare, join, interpolate, slice. |
|
||||||
|
| [`lang/operators.ludic`](lang/operators.ludic) | Compound assignment on every type, unary minus, char escapes, list literals. |
|
||||||
| [`lang/rng_demo.ludic`](lang/rng_demo.ludic) | The `Random.*` extensions (value/int/sign). |
|
| [`lang/rng_demo.ludic`](lang/rng_demo.ludic) | The `Random.*` extensions (value/int/sign). |
|
||||||
| [`lang/time_demo.ludic`](lang/time_demo.ludic) | `Time.frame` / `elapsed` / `delta`. |
|
| [`lang/time_demo.ludic`](lang/time_demo.ludic) | `Time.frame` / `elapsed` / `delta`. |
|
||||||
| [`lang/offline_rewards.ludic`](lang/offline_rewards.ludic) | A worked idle-game example over the Time/Date/Duration stdlib. |
|
| [`lang/offline_rewards.ludic`](lang/offline_rewards.ludic) | A worked idle-game example over the Time/Date/Duration stdlib. |
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ program RPG2D {
|
||||||
# query is the decorator; each bound property is addressed by its own name.
|
# query is the decorator; each bound property is addressed by its own name.
|
||||||
@Queries(these: [Transform { scale > 0 }, Velocity { dx > 0 or dy > 0 }], on: Actor)
|
@Queries(these: [Transform { scale > 0 }, Velocity { dx > 0 or dy > 0 }], on: Actor)
|
||||||
handler Move phase Update {
|
handler Move phase Update {
|
||||||
Transform.x = Transform.x + Velocity.dx
|
Transform.x += Velocity.dx
|
||||||
Transform.y = Transform.y + Velocity.dy
|
Transform.y += Velocity.dy
|
||||||
}
|
}
|
||||||
|
|
||||||
handler Report phase Render {
|
handler Report phase Render {
|
||||||
|
|
|
||||||
40
examples/lang/operators.ludic
Normal file
40
examples/lang/operators.ludic
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# operators.ludic — compound assignment (`+= -= *= /=`) is exactly `x = x op y`,
|
||||||
|
# whatever the type: Q16.16 `fixed` multiplies through the 64-bit path, strings
|
||||||
|
# concatenate, an `int` added to a `long` widens. Unary minus keeps its operand's
|
||||||
|
# type, character literals accept the same escapes as strings, and `[a, b, c]`
|
||||||
|
# builds a slice in place. Running it prints: 1 2 3 4 5 6 7 8 9 10
|
||||||
|
program Operators {
|
||||||
|
entry {
|
||||||
|
var f: fixed = 1.5
|
||||||
|
f *= 2.0
|
||||||
|
if f == 3.0 { print(1) } # fixed *= is a Q16.16 multiply, not a raw mul
|
||||||
|
f /= 4.0
|
||||||
|
if f == 0.75 { print(2) }
|
||||||
|
let neg: fixed = -f
|
||||||
|
if neg + f == 0.0 { print(3) } # -fixed is still a fixed
|
||||||
|
|
||||||
|
var s = "ab"
|
||||||
|
s += "cd"
|
||||||
|
if s == "abcd" { print(4) } # string += concatenates
|
||||||
|
|
||||||
|
var n = 40
|
||||||
|
n += 2
|
||||||
|
n -= 1
|
||||||
|
n *= 2
|
||||||
|
n /= 3
|
||||||
|
if n == 27 { print(5) }
|
||||||
|
|
||||||
|
var big: long = 1
|
||||||
|
big += n # int widens into the long
|
||||||
|
if big == 28 { print(6) }
|
||||||
|
|
||||||
|
if '\'' == 39 and '\\' == 92 and '\n' == 10 { print(7) } # char escapes mirror string escapes
|
||||||
|
if "q\"b\\c" == `q"b\\c` { print(8) } # "…" and `…` share one escape table
|
||||||
|
if `{Text.length("{")}{Text.length("}")}` != "11" { print(0) } # braces inside a hole's string are text
|
||||||
|
|
||||||
|
let primes = [2, 3, 5, 7] # a list literal is a fresh slice
|
||||||
|
if len(primes) == 4 and primes[3] == 7 { print(9) }
|
||||||
|
let names = ["ember", "depths"]
|
||||||
|
if names[1] == "depths" { print(10) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ program SceneDemo {
|
||||||
on exit { print(2) }
|
on exit { print(2) }
|
||||||
layer Main {
|
layer Main {
|
||||||
handler Tick phase Update {
|
handler Tick phase Update {
|
||||||
counter = counter + 1
|
counter += 1
|
||||||
print(100 + counter)
|
print(100 + counter)
|
||||||
if counter >= 2 { become Play }
|
if counter >= 2 { become Play }
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ program SceneDemo {
|
||||||
on exit { print(4) }
|
on exit { print(4) }
|
||||||
layer World {
|
layer World {
|
||||||
handler Step phase Update {
|
handler Step phase Update {
|
||||||
counter = counter + 1
|
counter += 1
|
||||||
print(200 + counter)
|
print(200 + counter)
|
||||||
if counter >= 2 { quit() }
|
if counter >= 2 { quit() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# strings.ludic — strings are values: compare with ==/!=, join with +, or (best)
|
# strings.ludic — strings are values: compare with ==/!=, join with +, or (best)
|
||||||
# interpolate with `backticks {expr}`. Running it prints: 1 2 3 4 5 6 7
|
# interpolate with `backticks {expr}`. Running it prints: 1 2 3 4 5 6 7 8 9
|
||||||
program Strings {
|
program Strings {
|
||||||
entry {
|
entry {
|
||||||
let greeting = "hello"
|
let greeting = "hello"
|
||||||
|
|
@ -13,7 +13,7 @@ program Strings {
|
||||||
# build up a string in a loop
|
# build up a string in a loop
|
||||||
var line = ""
|
var line = ""
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < 3 { line = line + "ab"; i = i + 1 }
|
while i < 3 { line += "ab"; i += 1 }
|
||||||
if line == "ababab" { print(4) }
|
if line == "ababab" { print(4) }
|
||||||
|
|
||||||
if (greeting + who) != greeting { print(5) }
|
if (greeting + who) != greeting { print(5) }
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# host input, so instead of fixed answers we assert the invariants that must hold
|
# host input, so instead of fixed answers we assert the invariants that must hold
|
||||||
# regardless of where it runs: an env var round-trips, env_or falls back when a
|
# regardless of where it runs: an env var round-trips, env_or falls back when a
|
||||||
# var is unset, unset removes it, args() agrees with arg_count(), and the host
|
# var is unset, unset removes it, args() agrees with arg_count(), and the host
|
||||||
# facts and known folders are all non-empty. Running it prints: 1 2 3 4 5 6 7 8 9 10 11 12 13
|
# facts and known folders are all non-empty. Running it prints: 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||||
program Os {
|
program Os {
|
||||||
entry {
|
entry {
|
||||||
# environment variables round-trip through set/get
|
# environment variables round-trip through set/get
|
||||||
|
|
@ -30,8 +30,11 @@ program Os {
|
||||||
if len(Os.save_dir("MyGame")) > 0 { print(11) }
|
if len(Os.save_dir("MyGame")) > 0 { print(11) }
|
||||||
if len(Os.temp_dir()) > 0 { print(12) }
|
if len(Os.temp_dir()) > 0 { print(12) }
|
||||||
|
|
||||||
|
# the process id is a live, positive OS handle
|
||||||
|
if Os.pid() > 0 { print(13) }
|
||||||
|
|
||||||
# diagnostics go to stderr, so they never disturb this program's stdout
|
# diagnostics go to stderr, so they never disturb this program's stdout
|
||||||
Os.stderr_write("os.ludic: hello from stderr\n")
|
Os.stderr_write("os.ludic: hello from stderr\n")
|
||||||
print(13)
|
print(14)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ function ns_lower(s: pointer) -> pointer {
|
||||||
let n = cstr_len(s)
|
let n = cstr_len(s)
|
||||||
let b = bytes(n + 1)
|
let b = bytes(n + 1)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < n { var c = s[i]; if (c >= 65) and (c <= 90) { c = c + 32 }; b[i] = c; i = i + 1 }
|
while i < n { var c = s[i]; if (c >= 'A') and (c <= 'Z') { c += 32 }; b[i] = c; i += 1 }
|
||||||
b[n] = 0
|
b[n] = 0
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
@ -886,7 +886,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
if (nsfn != null) {
|
if (nsfn != null) {
|
||||||
let names = param_labels(nsfn)
|
let names = param_labels(nsfn)
|
||||||
var pi2 = 0
|
var pi2 = 0
|
||||||
while pi2 < len(names) { push(labels, names[pi2]); pi2 = pi2 + 1 }
|
while pi2 < len(names) { push(labels, names[pi2]); pi2 += 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
||||||
|
|
@ -914,7 +914,7 @@ function emit_variant_new(en: Node, ord: int, args: []Node) -> Val {
|
||||||
let p = nreg(); emit(" "); emit(p); emit(" = getelementptr inbounds i8, ptr "); emit(box)
|
let p = nreg(); emit(" "); emit(p); emit(" = getelementptr inbounds i8, ptr "); emit(box)
|
||||||
emit(", i32 "); emit(itoa(8 * (k + 1))); emit("\n")
|
emit(", i32 "); emit(itoa(8 * (k + 1))); emit("\n")
|
||||||
emit(" store "); emit(llty(pty)); emit(" "); emit(cv); emit(", ptr "); emit(p); emit("\n")
|
emit(" store "); emit(llty(pty)); emit(" "); emit(cv); emit(", ptr "); emit(p); emit("\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
return val(box, en.s)
|
return val(box, en.s)
|
||||||
}
|
}
|
||||||
|
|
@ -1008,9 +1008,9 @@ function emit_call(e: Node) -> Val {
|
||||||
if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through
|
if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
if (llty(a.ty) == "ptr") { return a }
|
if (llty(a.ty) == "ptr") { return a }
|
||||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @fn_long_str(i64 {a.code})`), "string") }
|
if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @lp_long_str(i64 {a.code})`), "string") }
|
||||||
g_uses_intstr = true
|
g_uses_intstr = true
|
||||||
return val(emit_bind(`call ptr @fn_int_str(i32 {a.code})`), "string")
|
return val(emit_bind(`call ptr @lp_int_str(i32 {a.code})`), "string")
|
||||||
}
|
}
|
||||||
if (name == "print") { # print(x): a value + newline (string, long, or int)
|
if (name == "print") { # print(x): a value + newline (string, long, or int)
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
|
|
@ -1230,7 +1230,7 @@ function emit_call(e: Node) -> Val {
|
||||||
let eargs = new []pointer
|
let eargs = new []pointer
|
||||||
let eatys = new []pointer
|
let eatys = new []pointer
|
||||||
var ei = 0
|
var ei = 0
|
||||||
while ei < len(e.kids) { let v = emit_expr(e.kids[ei]); push(eargs, v.code); push(eatys, v.ty); ei = ei + 1 }
|
while ei < len(e.kids) { let v = emit_expr(e.kids[ei]); push(eargs, v.code); push(eatys, v.ty); ei += 1 }
|
||||||
let erl = llty(ext.ty)
|
let erl = llty(ext.ty)
|
||||||
emit(" ")
|
emit(" ")
|
||||||
var erreg = "0"
|
var erreg = "0"
|
||||||
|
|
@ -1240,7 +1240,7 @@ function emit_call(e: Node) -> Val {
|
||||||
while ei < len(eargs) {
|
while ei < len(eargs) {
|
||||||
if ei > 0 { emit(", ") }
|
if ei > 0 { emit(", ") }
|
||||||
emit(llty(eatys[ei])); emit(" "); emit(eargs[ei])
|
emit(llty(eatys[ei])); emit(" "); emit(eargs[ei])
|
||||||
ei = ei + 1
|
ei += 1
|
||||||
}
|
}
|
||||||
emit(")\n")
|
emit(")\n")
|
||||||
return val(erreg, ext.ty)
|
return val(erreg, ext.ty)
|
||||||
|
|
@ -1270,7 +1270,7 @@ function emit_call(e: Node) -> Val {
|
||||||
let v = emit_expr(e.kids[i])
|
let v = emit_expr(e.kids[i])
|
||||||
var pty = v.ty
|
var pty = v.ty
|
||||||
if (i < len(ptys)) { pty = ptys[i] }
|
if (i < len(ptys)) { pty = ptys[i] }
|
||||||
push(args, coerce_code(v, pty)); push(atys, pty); i = i + 1
|
push(args, coerce_code(v, pty)); push(atys, pty); i += 1
|
||||||
}
|
}
|
||||||
let rl = llty(fn2.ty)
|
let rl = llty(fn2.ty)
|
||||||
emit(" ")
|
emit(" ")
|
||||||
|
|
@ -1281,7 +1281,7 @@ function emit_call(e: Node) -> Val {
|
||||||
while i < len(args) {
|
while i < len(args) {
|
||||||
if i > 0 { emit(", ") }
|
if i > 0 { emit(", ") }
|
||||||
emit(llty(atys[i])); emit(" "); emit(args[i])
|
emit(llty(atys[i])); emit(" "); emit(args[i])
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(")\n")
|
emit(")\n")
|
||||||
return val(rreg, fn2.ty)
|
return val(rreg, fn2.ty)
|
||||||
|
|
@ -1299,13 +1299,14 @@ function emit_expr(e: Node) -> Val {
|
||||||
let lo = emit_expr(e.b)
|
let lo = emit_expr(e.b)
|
||||||
let hi = emit_expr(e.c)
|
let hi = emit_expr(e.c)
|
||||||
g_uses_strslice = true
|
g_uses_strslice = true
|
||||||
return val(emit_bind(`call ptr @fn_str_slice(ptr {base.code}, i32 {lo.code}, i32 {hi.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_slice(ptr {base.code}, i32 {lo.code}, i32 {hi.code})`), "string")
|
||||||
}
|
}
|
||||||
if e.kind == E_STR { return val(emit_str_const(e.s), "string") }
|
if e.kind == E_STR { return val(emit_str_const(e.s), "string") }
|
||||||
if e.kind == E_NEW {
|
if e.kind == E_NEW {
|
||||||
if is_slice_ty(e.s) { return emit_new_slice(e.s) }
|
if is_slice_ty(e.s) { return emit_new_slice(e.s) }
|
||||||
return emit_new_struct(e.s, e.a)
|
return emit_new_struct(e.s, e.a)
|
||||||
}
|
}
|
||||||
|
if e.kind == E_LIST { return emit_list(e) } # [a, b, c] -> a fresh slice
|
||||||
if e.kind == E_ID {
|
if e.kind == E_ID {
|
||||||
let li = loc_find(e.s)
|
let li = loc_find(e.s)
|
||||||
if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) }
|
if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) }
|
||||||
|
|
@ -1372,8 +1373,10 @@ function emit_expr(e: Node) -> Val {
|
||||||
if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") }
|
if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") }
|
||||||
if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") }
|
if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") }
|
||||||
}
|
}
|
||||||
if (e.s == ("-")) { return val(emit_bind(`sub i32 0, {a.code}`), "int") }
|
# negation keeps the operand's type: -x on a fixed is still a fixed (the Q16.16
|
||||||
if (e.s == "~") { return val(emit_bind(`xor i32 {a.code}, -1`), "int") }
|
# bit pattern negates like any two's-complement int)
|
||||||
|
if (e.s == ("-")) { return val(emit_bind(`sub i32 0, {a.code}`), arith_ty(a.ty)) }
|
||||||
|
if (e.s == "~") { return val(emit_bind(`xor i32 {a.code}, -1`), arith_ty(a.ty)) }
|
||||||
let c = emit_bind(`icmp eq i32 {a.code}, 0`)
|
let c = emit_bind(`icmp eq i32 {a.code}, 0`)
|
||||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ var g_uses_uuidrt: bool = false # Uuid.* was emitted -> emit the UUID runtime (
|
||||||
var g_uses_noisert: bool = false # Noise.* was emitted -> emit the fixed-point noise runtime
|
var g_uses_noisert: bool = false # Noise.* was emitted -> emit the fixed-point noise runtime
|
||||||
var g_uses_logrt: bool = false # Log.* was emitted -> emit the log level register + console sink
|
var g_uses_logrt: bool = false # Log.* was emitted -> emit the log level register + console sink
|
||||||
var g_uses_osrt: bool = false # Os.* (prelude-backed methods) was emitted -> emit the Os runtime
|
var g_uses_osrt: bool = false # Os.* (prelude-backed methods) was emitted -> emit the Os runtime
|
||||||
|
var g_uses_pid: bool = false # Os.pid() was emitted -> declare libc getpid
|
||||||
var g_uses_unicodert: bool = false # Unicode.* was emitted -> emit the UTF-8 runtime
|
var g_uses_unicodert: bool = false # Unicode.* was emitted -> emit the UTF-8 runtime
|
||||||
var g_uses_fsrt: bool = false # Fs.*/Path.*/Mime.* was emitted -> emit the filesystem runtime
|
var g_uses_fsrt: bool = false # Fs.*/Path.*/Mime.* was emitted -> emit the filesystem runtime
|
||||||
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civil<->epoch conversions
|
||||||
|
|
@ -56,7 +57,7 @@ var g_prog_user_end: int = 0 # count of prog decls from the user's source (b
|
||||||
# as the line table dumped at exit.
|
# as the line table dumped at exit.
|
||||||
function cov_slot(line: int) -> int {
|
function cov_slot(line: int) -> int {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_cov_lines) { if g_cov_lines[i] == line { return i }; i = i + 1 }
|
while i < len(g_cov_lines) { if g_cov_lines[i] == line { return i }; i += 1 }
|
||||||
push(g_cov_lines, line)
|
push(g_cov_lines, line)
|
||||||
return len(g_cov_lines) - 1
|
return len(g_cov_lines) - 1
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +98,7 @@ var g_cur_scene: Node = null # scene owning the handler being emitted, for `beco
|
||||||
|
|
||||||
function find_scene(name: pointer) -> Node {
|
function find_scene(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_scenes) { if (g_scenes[i].s == name) { return g_scenes[i] }; i = i + 1 }
|
while i < len(g_scenes) { if (g_scenes[i].s == name) { return g_scenes[i] }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,14 +108,14 @@ function emith(s: pointer) -> void { buf_puts(head, s) }
|
||||||
# stack slots MUST live in the entry block (an alloca in a loop walks the stack
|
# stack slots MUST live in the entry block (an alloca in a loop walks the stack
|
||||||
# off its end), so they go into a per-function buffer spliced in at entry.
|
# off its end), so they go into a per-function buffer spliced in at entry.
|
||||||
function emit_alloca(llt: pointer) -> pointer {
|
function emit_alloca(llt: pointer) -> pointer {
|
||||||
let r = `%t{itoa(ll_t)}`; ll_t = ll_t + 1
|
let r = `%t{itoa(ll_t)}`; ll_t += 1
|
||||||
buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n")
|
buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n")
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
# "%t<n>" fresh register
|
# "%t<n>" fresh register
|
||||||
function nreg() -> pointer { let r = `%t{itoa(ll_t)}`; ll_t = ll_t + 1; return r }
|
function nreg() -> pointer { let r = `%t{itoa(ll_t)}`; ll_t += 1; return r }
|
||||||
function lbl(pfx: pointer) -> pointer { let r = (pfx + itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r }
|
function lbl(pfx: pointer) -> pointer { let r = (pfx + itoa(ll_lbl)); ll_lbl += 1; return r }
|
||||||
|
|
||||||
# Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/
|
# Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/
|
||||||
# slice) is a pointer; void is void.
|
# slice) is a pointer; void is void.
|
||||||
|
|
@ -132,17 +133,17 @@ function llty(t: pointer) -> pointer {
|
||||||
return "ptr"
|
return "ptr"
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_slice_ty(t: pointer) -> bool { return t[0] == 91 and t[1] == 93 } # "[]"
|
function is_slice_ty(t: pointer) -> bool { return t[0] == '[' and t[1] == ']' } # "[]"
|
||||||
function slice_elem(t: pointer) -> pointer { return t[2..len(t)] }
|
function slice_elem(t: pointer) -> pointer { return t[2..len(t)] }
|
||||||
|
|
||||||
function find_arch(name: pointer) -> Node {
|
function find_arch(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function find_comp(name: pointer) -> Node {
|
function find_comp(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
# every record is a `property` with a %Cmp_ layout of named fields — whether it
|
# every record is a `property` with a %Cmp_ layout of named fields — whether it
|
||||||
|
|
@ -152,12 +153,12 @@ function layout_ty(name: pointer) -> pointer { return (("%Cmp_") + name) }
|
||||||
|
|
||||||
function field_index(s: Node, fname: pointer) -> int {
|
function field_index(s: Node, fname: pointer) -> int {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(s.kids) { if (s.kids[i].s == fname) { return i }; i = i + 1 }
|
while i < len(s.kids) { if (s.kids[i].s == fname) { return i }; i += 1 }
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
function field_type(s: Node, fname: pointer) -> pointer {
|
function field_type(s: Node, fname: pointer) -> pointer {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(s.kids) { if (s.kids[i].s == fname) { return s.kids[i].ty }; i = i + 1 }
|
while i < len(s.kids) { if (s.kids[i].s == fname) { return s.kids[i].ty }; i += 1 }
|
||||||
return "int"
|
return "int"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +169,7 @@ function find_global(name: pointer) -> Node {
|
||||||
let d = prog[i]
|
let d = prog[i]
|
||||||
if d.kind == N_VAR and (d.s == name) { return d }
|
if d.kind == N_VAR and (d.s == name) { return d }
|
||||||
if d.kind == N_CONST and (d.s == name) { return d }
|
if d.kind == N_CONST and (d.s == name) { return d }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -181,9 +182,9 @@ function enum_ordinal(ename: pointer, vname: pointer) -> int {
|
||||||
let d = prog[i]
|
let d = prog[i]
|
||||||
if d.kind == N_ENUM and (d.s == ename) {
|
if d.kind == N_ENUM and (d.s == ename) {
|
||||||
var j = 0
|
var j = 0
|
||||||
while j < len(d.kids) { if (d.kids[j].s == vname) { return j }; j = j + 1 }
|
while j < len(d.kids) { if (d.kids[j].s == vname) { return j }; j += 1 }
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# LC1: the compiler owns `EndReason` — the reason bound by a reason-carrying
|
# LC1: the compiler owns `EndReason` — the reason bound by a reason-carrying
|
||||||
# teardown (`@OnDespawn(M, reason: r)`). Each despawn site passes one of these.
|
# teardown (`@OnDespawn(M, reason: r)`). Each despawn site passes one of these.
|
||||||
|
|
@ -192,24 +193,24 @@ function enum_ordinal(ename: pointer, vname: pointer) -> int {
|
||||||
if (vname == "SceneExit") { return 1 } # a scene tearing down its owned entities
|
if (vname == "SceneExit") { return 1 } # a scene tearing down its owned entities
|
||||||
if (vname == "Quit") { return 2 } # program shutdown
|
if (vname == "Quit") { return 2 } # program shutdown
|
||||||
}
|
}
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
# an enum none of whose variants carries a payload: a plain int-valued naming layer
|
# an enum none of whose variants carries a payload: a plain int-valued naming layer
|
||||||
function is_bare_enum(name: pointer) -> bool {
|
function is_bare_enum(name: pointer) -> bool {
|
||||||
let en = find_enum(name)
|
let en = find_enum(name)
|
||||||
if (en == null) { return false }
|
if (en == null) { return false }
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(en.kids) { if len(en.kids[i].kids) > 0 { return false }; i = i + 1 }
|
while i < len(en.kids) { if len(en.kids[i].kids) > 0 { return false }; i += 1 }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
function find_prefab(name: pointer) -> Node {
|
function find_prefab(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_PREFAB and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_PREFAB and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function has_prefabs() -> bool {
|
function has_prefabs() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_PREFAB { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_PREFAB { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
# does any component declare a `countdown` field?
|
# does any component declare a `countdown` field?
|
||||||
|
|
@ -217,14 +218,14 @@ function has_countdowns() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
let d = prog[i]
|
let d = prog[i]
|
||||||
if d.kind == N_COMP { var f = 0; while f < len(d.kids) { if (d.kids[f].ty == "countdown") { return true }; f = f + 1 } }
|
if d.kind == N_COMP { var f = 0; while f < len(d.kids) { if (d.kids[f].ty == "countdown") { return true }; f += 1 } }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
function find_fn(name: pointer) -> Node {
|
function find_fn(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,12 +239,12 @@ var g_var_ord: int = 0
|
||||||
|
|
||||||
function find_enum(name: pointer) -> Node {
|
function find_enum(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ENUM and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_ENUM and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function enum_is_tagged(en: Node) -> bool {
|
function enum_is_tagged(en: Node) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(en.kids) { if len(en.kids[i].kids) > 0 { return true }; i = i + 1 }
|
while i < len(en.kids) { if len(en.kids[i].kids) > 0 { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
# the tagged enum whose type name is `ty`, or null (a plain enum / non-enum type
|
# the tagged enum whose type name is `ty`, or null (a plain enum / non-enum type
|
||||||
|
|
@ -257,8 +258,8 @@ function tagged_enum_of(ty: pointer) -> Node {
|
||||||
# the ordinal of variant `vname` within enum `en`, or -1.
|
# the ordinal of variant `vname` within enum `en`, or -1.
|
||||||
function variant_ordinal(en: Node, vname: pointer) -> int {
|
function variant_ordinal(en: Node, vname: pointer) -> int {
|
||||||
var j = 0
|
var j = 0
|
||||||
while j < len(en.kids) { if (en.kids[j].s == vname) { return j }; j = j + 1 }
|
while j < len(en.kids) { if (en.kids[j].s == vname) { return j }; j += 1 }
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
# find the tagged enum that declares a variant named `vname` (used to resolve a
|
# find the tagged enum that declares a variant named `vname` (used to resolve a
|
||||||
# bare constructor like `Door(3)` / `Empty`); sets g_var_ord to its ordinal.
|
# bare constructor like `Door(3)` / `Empty`); sets g_var_ord to its ordinal.
|
||||||
|
|
@ -274,7 +275,7 @@ function variant_enum(vname: pointer) -> Node {
|
||||||
if ord >= 0 { g_var_ord = ord; return d }
|
if ord >= 0 { g_var_ord = ord; return d }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +283,7 @@ function variant_enum(vname: pointer) -> Node {
|
||||||
function enum_max_arity(en: Node) -> int {
|
function enum_max_arity(en: Node) -> int {
|
||||||
var m = 0
|
var m = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(en.kids) { let a = len(en.kids[i].kids); if a > m { m = a }; i = i + 1 }
|
while i < len(en.kids) { let a = len(en.kids[i].kids); if a > m { m = a }; i += 1 }
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
# byte size of a variant box: an 8-byte tag slot + one 8-byte slot per payload
|
# byte size of a variant box: an 8-byte tag slot + one 8-byte slot per payload
|
||||||
|
|
@ -296,7 +297,7 @@ function enum_box_size(en: Node) -> int { return 8 * (1 + enum_max_arity(en)) }
|
||||||
# C/Rust/Zig library binding — the same seam NETWORKING-DESIGN §5 names.
|
# C/Rust/Zig library binding — the same seam NETWORKING-DESIGN §5 names.
|
||||||
function find_extern(name: pointer) -> Node {
|
function find_extern(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_EXTERN and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_EXTERN and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,7 +314,7 @@ function computed_expr(prop: pointer, field: pointer) -> Node {
|
||||||
if (prop == null) { return null }
|
if (prop == null) { return null }
|
||||||
let key = `{prop}.{field}`
|
let key = `{prop}.{field}`
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_computed) { if (g_computed[i].s == key) { return g_computed[i].a }; i = i + 1 }
|
while i < len(g_computed) { if (g_computed[i].s == key) { return g_computed[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
# best-effort static type of an expression (for computed-field lookup; emits nothing)
|
# best-effort static type of an expression (for computed-field lookup; emits nothing)
|
||||||
|
|
@ -337,6 +338,19 @@ function static_type(e: Node) -> pointer {
|
||||||
let bt = static_type(e.a)
|
let bt = static_type(e.a)
|
||||||
if (bt != null) { let s = layout_node(bt); if (s != null) { return field_type(s, e.s) } }
|
if (bt != null) { let s = layout_node(bt); if (s != null) { return field_type(s, e.s) } }
|
||||||
}
|
}
|
||||||
|
if e.kind == E_INDEX { # slice[i] -> the element type
|
||||||
|
let bt = static_type(e.a)
|
||||||
|
if (bt != null) and is_slice_ty(bt) { return slice_elem(bt) }
|
||||||
|
}
|
||||||
|
if e.kind == E_NEW { return e.s } # new T / new []T
|
||||||
|
if e.kind == E_LIST and len(e.kids) > 0 { # [a, b] -> a slice of the first's type
|
||||||
|
let et = static_type(e.kids[0])
|
||||||
|
if (et != null) { return "[]" + et }
|
||||||
|
}
|
||||||
|
if e.kind == E_STR or e.kind == E_SLICE { return "string" }
|
||||||
|
if e.kind == E_INT { return "int" }
|
||||||
|
if e.kind == E_FLOAT { return "fixed" }
|
||||||
|
if e.kind == E_BOOL { return "bool" }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,7 +364,7 @@ function register_onspawn(model: pointer, body: Node) -> void {
|
||||||
}
|
}
|
||||||
function onspawn_body(model: pointer) -> Node {
|
function onspawn_body(model: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_onspawn) { if (g_onspawn[i].s == model) { return g_onspawn[i].a }; i = i + 1 }
|
while i < len(g_onspawn) { if (g_onspawn[i].s == model) { return g_onspawn[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,7 +382,7 @@ function register_ondespawn(model: pointer, body: Node, reason: pointer) -> void
|
||||||
}
|
}
|
||||||
function ondespawn_body(model: pointer) -> Node {
|
function ondespawn_body(model: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_ondespawn) { if (g_ondespawn[i].s == model) { return g_ondespawn[i].a }; i = i + 1 }
|
while i < len(g_ondespawn) { if (g_ondespawn[i].s == model) { return g_ondespawn[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function register_onattach(prop: pointer, body: Node) -> void {
|
function register_onattach(prop: pointer, body: Node) -> void {
|
||||||
|
|
@ -376,7 +390,7 @@ function register_onattach(prop: pointer, body: Node) -> void {
|
||||||
}
|
}
|
||||||
function onattach_body(prop: pointer) -> Node {
|
function onattach_body(prop: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_onattach) { if (g_onattach[i].s == prop) { return g_onattach[i].a }; i = i + 1 }
|
while i < len(g_onattach) { if (g_onattach[i].s == prop) { return g_onattach[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,7 +403,7 @@ function register_ondetach(prop: pointer, body: Node) -> void {
|
||||||
}
|
}
|
||||||
function ondetach_body(prop: pointer) -> Node {
|
function ondetach_body(prop: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_ondetach) { if (g_ondetach[i].s == prop) { return g_ondetach[i].a }; i = i + 1 }
|
while i < len(g_ondetach) { if (g_ondetach[i].s == prop) { return g_ondetach[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,12 +415,12 @@ function register_onenable(prop: pointer, body: Node) -> void { let n = node(N_B
|
||||||
function register_ondisable(prop: pointer, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, n) }
|
function register_ondisable(prop: pointer, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, n) }
|
||||||
function onenable_body(prop: pointer) -> Node {
|
function onenable_body(prop: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_onenable) { if (g_onenable[i].s == prop) { return g_onenable[i].a }; i = i + 1 }
|
while i < len(g_onenable) { if (g_onenable[i].s == prop) { return g_onenable[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function ondisable_body(prop: pointer) -> Node {
|
function ondisable_body(prop: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_ondisable) { if (g_ondisable[i].s == prop) { return g_ondisable[i].a }; i = i + 1 }
|
while i < len(g_ondisable) { if (g_ondisable[i].s == prop) { return g_ondisable[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,7 +436,7 @@ var g_cancel_addr: pointer = null # EV3: address of the current cancellable di
|
||||||
function register_event(n: Node) -> void { push(g_events, n) }
|
function register_event(n: Node) -> void { push(g_events, n) }
|
||||||
function find_event(name: pointer) -> Node {
|
function find_event(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_events) { if (g_events[i].s == name) { return g_events[i] }; i = i + 1 }
|
while i < len(g_events) { if (g_events[i].s == name) { return g_events[i] }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function register_onlisten(evt: pointer, body: Node) -> void {
|
function register_onlisten(evt: pointer, body: Node) -> void {
|
||||||
|
|
@ -454,12 +468,12 @@ function ensure_event_empty(name: pointer) -> void {
|
||||||
var g_toggled_layers: []pointer
|
var g_toggled_layers: []pointer
|
||||||
function note_toggled_layer(name: pointer) -> void {
|
function note_toggled_layer(name: pointer) -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_toggled_layers) { if (g_toggled_layers[i] == name) { return }; i = i + 1 }
|
while i < len(g_toggled_layers) { if (g_toggled_layers[i] == name) { return }; i += 1 }
|
||||||
push(g_toggled_layers, name)
|
push(g_toggled_layers, name)
|
||||||
}
|
}
|
||||||
function is_toggled_layer(name: pointer) -> bool {
|
function is_toggled_layer(name: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_toggled_layers) { if (g_toggled_layers[i] == name) { return true }; i = i + 1 }
|
while i < len(g_toggled_layers) { if (g_toggled_layers[i] == name) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -474,11 +488,11 @@ function loc_reset() -> void { nloc = 0 }
|
||||||
function loc_push(name: pointer, r: pointer, ty: pointer) -> void {
|
function loc_push(name: pointer, r: pointer, ty: pointer) -> void {
|
||||||
if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty; loc_mut[nloc] = 1 }
|
if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty; loc_mut[nloc] = 1 }
|
||||||
else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty); push(loc_mut, 1) }
|
else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty); push(loc_mut, 1) }
|
||||||
nloc = nloc + 1
|
nloc += 1
|
||||||
}
|
}
|
||||||
function loc_set_mut(m: int) -> void { if nloc > 0 { loc_mut[nloc - 1] = m } }
|
function loc_set_mut(m: int) -> void { if nloc > 0 { loc_mut[nloc - 1] = m } }
|
||||||
function loc_find(name: pointer) -> int {
|
function loc_find(name: pointer) -> int {
|
||||||
var i = nloc - 1
|
var i = nloc - 1
|
||||||
while i >= 0 { if (loc_name[i] == name) { return i }; i = i - 1 }
|
while i >= 0 { if (loc_name[i] == name) { return i }; i -= 1 }
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function emit_params_sig(d: Node) -> void {
|
||||||
while i < len(d.kids) {
|
while i < len(d.kids) {
|
||||||
if i > 0 { emit(", ") }
|
if i > 0 { emit(", ") }
|
||||||
emit(llty(d.kids[i].ty)); emit(" %arg_"); emit(d.kids[i].s)
|
emit(llty(d.kids[i].ty)); emit(" %arg_"); emit(d.kids[i].s)
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ function emit_fn(d: Node) -> void {
|
||||||
let slot = emit_alloca(llty(p.ty))
|
let slot = emit_alloca(llty(p.ty))
|
||||||
emit(" store "); emit(llty(p.ty)); emit(" %arg_"); emit(p.s); emit(", ptr "); emit(slot); emit("\n")
|
emit(" store "); emit(llty(p.ty)); emit(" %arg_"); emit(p.s); emit(", ptr "); emit(slot); emit("\n")
|
||||||
loc_push(p.s, slot, p.ty)
|
loc_push(p.s, slot, p.ty)
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit_block(d.a)
|
emit_block(d.a)
|
||||||
if not g_term { emit(" br label %ret\n") }
|
if not g_term { emit(" br label %ret\n") }
|
||||||
|
|
@ -99,7 +99,7 @@ function emit_test_runner() -> void {
|
||||||
emith("@L_test_fail = internal global i32 0\n")
|
emith("@L_test_fail = internal global i32 0\n")
|
||||||
emith("@.fmt_test_sum = private unnamed_addr constant [28 x i8] c\"== %d passed, %d failed ==\\0A\\00\"\n")
|
emith("@.fmt_test_sum = private unnamed_addr constant [28 x i8] c\"== %d passed, %d failed ==\\0A\\00\"\n")
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_tests) { emit_test_fn(g_tests[i], i); i = i + 1 }
|
while i < len(g_tests) { emit_test_fn(g_tests[i], i); i += 1 }
|
||||||
if g_uses_expect { emith("@.fmt_expect = private unnamed_addr constant [22 x i8] c\"%s (got %d, want %d)\\0A\\00\"\n") }
|
if g_uses_expect { emith("@.fmt_expect = private unnamed_addr constant [22 x i8] c\"%s (got %d, want %d)\\0A\\00\"\n") }
|
||||||
|
|
||||||
ll_t = 0; ll_lbl = 0
|
ll_t = 0; ll_lbl = 0
|
||||||
|
|
@ -129,7 +129,7 @@ function emit_test_runner() -> void {
|
||||||
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr {failmsg})\n`)
|
emit(` call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr {failmsg})\n`)
|
||||||
emit(` br label %{ldone}\n`)
|
emit(` br label %{ldone}\n`)
|
||||||
emit(`{ldone}:\n`)
|
emit(`{ldone}:\n`)
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
let totf = emit_bind("load i32, ptr %failed")
|
let totf = emit_bind("load i32, ptr %failed")
|
||||||
let passed = emit_bind(`sub i32 {itoa(len(g_tests))}, {totf}`)
|
let passed = emit_bind(`sub i32 {itoa(len(g_tests))}, {totf}`)
|
||||||
|
|
@ -139,7 +139,29 @@ function emit_test_runner() -> void {
|
||||||
emit(` ret i32 {rc}\n}\n`)
|
emit(` ret i32 {rc}\n}\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# two `function`s with one name would collide in the object file; say so in
|
||||||
|
# source terms (and name both files) instead of leaving it to the IR assembler.
|
||||||
|
function check_duplicate_fns() -> void {
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
let d = prog[i]
|
||||||
|
if d.kind == N_FN {
|
||||||
|
var j = i + 1
|
||||||
|
while j < len(prog) {
|
||||||
|
let o = prog[j]
|
||||||
|
if o.kind == N_FN and (o.s == d.s) {
|
||||||
|
g_err_file = o.file; g_err_line = o.line
|
||||||
|
perr(`function '{d.s}' is defined twice (first in {d.file}:{itoa(d.line)})`)
|
||||||
|
}
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function emit_program() -> void {
|
function emit_program() -> void {
|
||||||
|
check_duplicate_fns()
|
||||||
head = buf_new()
|
head = buf_new()
|
||||||
code = buf_new()
|
code = buf_new()
|
||||||
g_uses_str = false
|
g_uses_str = false
|
||||||
|
|
@ -167,7 +189,7 @@ function emit_program() -> void {
|
||||||
emit_fn(prog[i])
|
emit_fn(prog[i])
|
||||||
g_cov_active = true
|
g_cov_active = true
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit_global_init_fn() # @L_init_globals: the non-constant `var` initializers
|
emit_global_init_fn() # @L_init_globals: the non-constant `var` initializers
|
||||||
if len(g_events) > 0 { emit_event_fns() } # EV0: @ev_<E> event-dispatch functions
|
if len(g_events) > 0 { emit_event_fns() } # EV0: @ev_<E> event-dispatch functions
|
||||||
|
|
@ -182,31 +204,32 @@ function emit_program() -> void {
|
||||||
else { if has_systems() and has_entry() { # N5: game owns its loop via `entry`
|
else { if has_systems() and has_entry() { # N5: game owns its loop via `entry`
|
||||||
emit_game_defs() # system fns, hooks, tick helpers
|
emit_game_defs() # system fns, hooks, tick helpers
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i += 1 }
|
||||||
}
|
}
|
||||||
else { if has_systems() { emit_game_main() } # the auto frame loop
|
else { if has_systems() { emit_game_main() } # the auto frame loop
|
||||||
else {
|
else {
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i += 1 }
|
||||||
} } }
|
} } }
|
||||||
if g_uses_world_despawn { emit_world_despawn_fn() } # #84: @fn_world_despawn, after all World.despawn / esys_bounds-kill uses are seen
|
if g_uses_world_despawn { emit_world_despawn_fn() } # #84: @fn_world_despawn, after all World.despawn / esys_bounds-kill uses are seen
|
||||||
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
|
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
|
||||||
if g_uses_str { emit_str_prelude() } # @fn_str_eq / @fn_str_concat, after all uses are seen
|
if g_uses_str { emit_str_prelude() } # @lp_str_eq / @lp_str_concat, after all uses are seen
|
||||||
if g_uses_intstr { emit_int_str() } # @fn_int_str, for string(int) in interpolation
|
if g_uses_intstr { emit_int_str() } # @lp_int_str, for string(int) in interpolation
|
||||||
if g_uses_longstr { emit_long_str() } # @fn_long_str, for string(long) / long interpolation
|
if g_uses_longstr { emit_long_str() } # @lp_long_str, for string(long) / long interpolation
|
||||||
if g_uses_strslice { emit_str_slice() } # @fn_str_slice, for s[a..b]
|
if g_uses_strslice { emit_str_slice() } # @lp_str_slice, for s[a..b]
|
||||||
if g_uses_mathrt { emit_math_prelude() } # @fn_fx_sqrt / @fn_fx_sin + the sine table
|
if g_uses_mathrt { emit_math_prelude() } # @lp_fx_sqrt / @lp_fx_sin + the sine table
|
||||||
if g_uses_textrt { emit_text_prelude() } # @fn_str_upper/lower/trim/repeat/pad builders
|
if g_uses_textrt { emit_text_prelude() } # @lp_str_upper/lower/trim/repeat/pad builders
|
||||||
if g_uses_textrt2 { emit_text2_prelude() } # @fn_str_replace/join/split builders
|
if g_uses_textrt2 { emit_text2_prelude() } # @lp_str_replace/join/split builders
|
||||||
if g_uses_hashrt { emit_hash_prelude() } # @fn_hash_fnv1a / @fn_hash_crc32 byte hashers
|
if g_uses_hashrt { emit_hash_prelude() } # @lp_hash_fnv1a / @lp_hash_crc32 byte hashers
|
||||||
if g_uses_cryptort { emit_crypto_prelude() } # @fn_sha256_hex / @fn_hmac_sha256_hex + constant-time compare + CSPRNG
|
if g_uses_cryptort { emit_crypto_prelude() } # @lp_sha256_hex / @lp_hmac_sha256_hex + constant-time compare + CSPRNG
|
||||||
if g_uses_uuidrt { emit_uuid_prelude() } # @fn_uuid_v4 / @fn_uuid_v7 / parse / equals (over the crypto CSPRNG)
|
if g_uses_uuidrt { emit_uuid_prelude() } # @lp_uuid_v4 / @lp_uuid_v7 / parse / equals (over the crypto CSPRNG)
|
||||||
if g_uses_noisert { emit_noise_prelude() } # @fn_noise_value2/perlin2/simplex2/fbm2/cellular2 (Q16.16)
|
if g_uses_noisert { emit_noise_prelude() } # @lp_noise_value2/perlin2/simplex2/fbm2/cellular2 (Q16.16)
|
||||||
if g_uses_logrt { emit_log_prelude() } # @L_log_level + @fn_log_emit (levelled stderr sink)
|
if g_uses_logrt { emit_log_prelude() } # @L_log_level + @lp_log_emit (levelled stderr sink)
|
||||||
if g_uses_osrt { emit_os_prelude() } # @fn_os_args/platform/arch/save_dir/... (libc env + uname)
|
if g_uses_osrt { emit_os_prelude() } # @lp_os_args/platform/arch/save_dir/... (libc env + uname)
|
||||||
if g_uses_unicodert { emit_unicode_prelude() } # @fn_uni_len/valid/decode/case/truncate/grapheme (UTF-8)
|
if g_uses_pid { emith("declare i32 @getpid()\n") }
|
||||||
|
if g_uses_unicodert { emit_unicode_prelude() } # @lp_uni_len/valid/decode/case/truncate/grapheme (UTF-8)
|
||||||
if g_uses_fsrt { emit_fs_prelude() } # @fn_fs_*/fn_path_*/fn_mime_* (libc + string ops)
|
if g_uses_fsrt { emit_fs_prelude() } # @fn_fs_*/fn_path_*/fn_mime_* (libc + string ops)
|
||||||
if g_uses_datert { emit_datetime_prelude() } # @fn_days_from_civil / @fn_civil_from_days conversions
|
if g_uses_datert { emit_datetime_prelude() } # @lp_days_from_civil / @lp_civil_from_days conversions
|
||||||
if g_uses_panic { # panic/assert: located abort to stderr
|
if g_uses_panic { # panic/assert: located abort to stderr
|
||||||
emith("declare i32 @fprintf(ptr, ptr, ...)\n")
|
emith("declare i32 @fprintf(ptr, ptr, ...)\n")
|
||||||
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
|
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
|
||||||
|
|
@ -234,7 +257,7 @@ function emit_cov_runtime() -> void {
|
||||||
while i < n {
|
while i < n {
|
||||||
if i > 0 { emith(", ") }
|
if i > 0 { emith(", ") }
|
||||||
emith("i32 "); emith(itoa(g_cov_lines[i]))
|
emith("i32 "); emith(itoa(g_cov_lines[i]))
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emith("]\n")
|
emith("]\n")
|
||||||
emith("@L_cov_hits = internal global ["); emith(sn); emith(" x i32] zeroinitializer\n")
|
emith("@L_cov_hits = internal global ["); emith(sn); emith(" x i32] zeroinitializer\n")
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ function emit_try_body(b: Node, slot: pointer) -> void {
|
||||||
let v = emit_expr(st.a)
|
let v = emit_expr(st.a)
|
||||||
emit(` store i32 {coerce_code(v, "int")}, ptr {slot}\n`)
|
emit(` store i32 {coerce_code(v, "int")}, ptr {slot}\n`)
|
||||||
} else { emit_stmt(st) }
|
} else { emit_stmt(st) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,9 +127,9 @@ function to_long(v: Val) -> pointer {
|
||||||
function emit_str_op(op: pointer, a: Val, b: Val) -> Val {
|
function emit_str_op(op: pointer, a: Val, b: Val) -> Val {
|
||||||
g_uses_str = true
|
g_uses_str = true
|
||||||
if (op == ("+")) {
|
if (op == ("+")) {
|
||||||
return val(emit_bind(`call ptr @fn_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
||||||
}
|
}
|
||||||
let r = emit_bind(`call i32 @fn_str_eq(ptr {a.code}, ptr {b.code})`)
|
let r = emit_bind(`call i32 @lp_str_eq(ptr {a.code}, ptr {b.code})`)
|
||||||
if (op == ("!=")) {
|
if (op == ("!=")) {
|
||||||
let c = emit_bind(`icmp eq i32 {r}, 0`)
|
let c = emit_bind(`icmp eq i32 {r}, 0`)
|
||||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||||
|
|
@ -137,56 +137,72 @@ function emit_str_op(op: pointer, a: Val, b: Val) -> Val {
|
||||||
return val(r, "bool")
|
return val(r, "bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# the result type of a unary arithmetic operator on a value of type `ty`: fixed
|
||||||
|
# and long survive, every other 32-bit scalar (int/bool/enum) collapses to int
|
||||||
|
function arith_ty(ty: pointer) -> pointer {
|
||||||
|
if (ty == "fixed") or (ty == "long") { return ty }
|
||||||
|
return "int"
|
||||||
|
}
|
||||||
|
|
||||||
function emit_bin(e: Node) -> Val {
|
function emit_bin(e: Node) -> Val {
|
||||||
if (e.s == "and") or (e.s == "or") { return emit_logic(e) }
|
if (e.s == "and") or (e.s == "or") { return emit_logic(e) }
|
||||||
let a = emit_expr(e.a)
|
let a = emit_expr(e.a)
|
||||||
let b = emit_expr(e.b)
|
let b = emit_expr(e.b)
|
||||||
|
let isnull = e.a.kind == E_NULL or e.b.kind == E_NULL
|
||||||
|
return emit_bin_vals(e.s, a, b, isnull)
|
||||||
|
}
|
||||||
|
|
||||||
|
# lower `a <op> b` on two already-evaluated operands. Shared by binary expressions
|
||||||
|
# and compound assignment (`x += y` is exactly `x = x + y`), so both agree on
|
||||||
|
# string concatenation, Q16.16 multiply/divide, and int->long promotion.
|
||||||
|
# `isnull` marks a comparison against the literal null (pointer identity, not
|
||||||
|
# string content).
|
||||||
|
function emit_bin_vals(op: pointer, a: Val, b: Val, isnull: bool) -> Val {
|
||||||
# strings are pointer-typed, so any `+` with a pointer operand is concatenation,
|
# strings are pointer-typed, so any `+` with a pointer operand is concatenation,
|
||||||
# and `==`/`!=` between pointers is content comparison — except `x == null`,
|
# and `==`/`!=` between pointers is content comparison — except `x == null`,
|
||||||
# which is a pointer-identity test and falls through to the icmp below.
|
# which is a pointer-identity test and falls through to the icmp below.
|
||||||
let ptrish = (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr")
|
let ptrish = (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr")
|
||||||
let isnull = e.a.kind == E_NULL or e.b.kind == E_NULL
|
|
||||||
if ptrish {
|
if ptrish {
|
||||||
if (e.s == ("+")) { return emit_str_op("+", a, b) }
|
if (op == ("+")) { return emit_str_op("+", a, b) }
|
||||||
if ((e.s == ("==")) or (e.s == ("!="))) and not isnull { return emit_str_op(e.s, a, b) }
|
if ((op == ("==")) or (op == ("!="))) and not isnull { return emit_str_op(op, a, b) }
|
||||||
}
|
}
|
||||||
let fx = (a.ty == "fixed") or (b.ty == "fixed")
|
let fx = (a.ty == "fixed") or (b.ty == "fixed")
|
||||||
# a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to
|
# a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to
|
||||||
# i64: the other side widens with sext, and the result stays `long`.
|
# i64: the other side widens with sext, and the result stays `long`.
|
||||||
let lng = ((llty(a.ty) == "i64") or (llty(b.ty) == "i64")) and not fx and not ptrish
|
let lng = ((llty(a.ty) == "i64") or (llty(b.ty) == "i64")) and not fx and not ptrish
|
||||||
if is_cmp(e.s) {
|
if is_cmp(op) {
|
||||||
var ac = a.code; var bc = b.code
|
var ac = a.code; var bc = b.code
|
||||||
var ct = "i32"
|
var ct = "i32"
|
||||||
if fx { ac = to_fixed(a); bc = to_fixed(b) }
|
if fx { ac = to_fixed(a); bc = to_fixed(b) }
|
||||||
else { if lng { ct = "i64"; ac = to_long(a); bc = to_long(b) }
|
else { if lng { ct = "i64"; ac = to_long(a); bc = to_long(b) }
|
||||||
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } } # `p == null`, str/record identity
|
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } } # `p == null`, str/record identity
|
||||||
let c = emit_bind(`icmp {cmp_code(e.s)} {ct} {ac}, {bc}`)
|
let c = emit_bind(`icmp {cmp_code(op)} {ct} {ac}, {bc}`)
|
||||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||||
}
|
}
|
||||||
if lng {
|
if lng {
|
||||||
let al = to_long(a); let bl = to_long(b)
|
let al = to_long(a); let bl = to_long(b)
|
||||||
return val(emit_bind(`{arith_code(e.s)} i64 {al}, {bl}`), "long")
|
return val(emit_bind(`{arith_code(op)} i64 {al}, {bl}`), "long")
|
||||||
}
|
}
|
||||||
if fx {
|
if fx {
|
||||||
let af = to_fixed(a); let bf = to_fixed(b)
|
let af = to_fixed(a); let bf = to_fixed(b)
|
||||||
if (e.s == ("*")) {
|
if (op == ("*")) {
|
||||||
let a64 = emit_bind(`sext i32 {af} to i64`)
|
let a64 = emit_bind(`sext i32 {af} to i64`)
|
||||||
let b64 = emit_bind(`sext i32 {bf} to i64`)
|
let b64 = emit_bind(`sext i32 {bf} to i64`)
|
||||||
let m = emit_bind(`mul i64 {a64}, {b64}`)
|
let m = emit_bind(`mul i64 {a64}, {b64}`)
|
||||||
let sh = emit_bind(`ashr i64 {m}, 16`)
|
let sh = emit_bind(`ashr i64 {m}, 16`)
|
||||||
return val(emit_bind(`trunc i64 {sh} to i32`), "fixed")
|
return val(emit_bind(`trunc i64 {sh} to i32`), "fixed")
|
||||||
}
|
}
|
||||||
if (e.s == ("/")) {
|
if (op == ("/")) {
|
||||||
let a64 = emit_bind(`sext i32 {af} to i64`)
|
let a64 = emit_bind(`sext i32 {af} to i64`)
|
||||||
let ash = emit_bind(`shl i64 {a64}, 16`)
|
let ash = emit_bind(`shl i64 {a64}, 16`)
|
||||||
let b64 = emit_bind(`sext i32 {bf} to i64`)
|
let b64 = emit_bind(`sext i32 {bf} to i64`)
|
||||||
let dv = emit_bind(`sdiv i64 {ash}, {b64}`)
|
let dv = emit_bind(`sdiv i64 {ash}, {b64}`)
|
||||||
return val(emit_bind(`trunc i64 {dv} to i32`), "fixed")
|
return val(emit_bind(`trunc i64 {dv} to i32`), "fixed")
|
||||||
}
|
}
|
||||||
let r = emit_bind(`{arith_code(e.s)} i32 {af}, {bf}`)
|
let r = emit_bind(`{arith_code(op)} i32 {af}, {bf}`)
|
||||||
return val(r, "fixed")
|
return val(r, "fixed")
|
||||||
}
|
}
|
||||||
let r = emit_bind(`{arith_code(e.s)} i32 {a.code}, {b.code}`)
|
let r = emit_bind(`{arith_code(op)} i32 {a.code}, {b.code}`)
|
||||||
return val(r, "int")
|
return val(r, "int")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,7 +213,7 @@ function emit_bin(e: Node) -> Val {
|
||||||
# oblivious to whether the caller used names.
|
# oblivious to whether the caller used names.
|
||||||
function args_are_named(e: Node) -> bool {
|
function args_are_named(e: Node) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(e.kids) { if e.kids[i].kind == E_FINIT { return true }; i = i + 1 }
|
while i < len(e.kids) { if e.kids[i].kind == E_FINIT { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
function reorder_named(e: Node, labels: []pointer) -> void {
|
function reorder_named(e: Node, labels: []pointer) -> void {
|
||||||
|
|
@ -205,7 +221,7 @@ function reorder_named(e: Node, labels: []pointer) -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(e.kids) {
|
while i < len(e.kids) {
|
||||||
if e.kids[i].kind != E_FINIT { perr("named and positional arguments cannot be mixed in one call") }
|
if e.kids[i].kind != E_FINIT { perr("named and positional arguments cannot be mixed in one call") }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
if len(e.kids) != len(labels) { perr("wrong number of arguments") }
|
if len(e.kids) != len(labels) { perr("wrong number of arguments") }
|
||||||
let out = new []Node
|
let out = new []Node
|
||||||
|
|
@ -213,10 +229,10 @@ function reorder_named(e: Node, labels: []pointer) -> void {
|
||||||
while li < len(labels) {
|
while li < len(labels) {
|
||||||
var found: Node = null
|
var found: Node = null
|
||||||
var k = 0
|
var k = 0
|
||||||
while k < len(e.kids) { if (e.kids[k].s == labels[li]) { found = e.kids[k] }; k = k + 1 }
|
while k < len(e.kids) { if (e.kids[k].s == labels[li]) { found = e.kids[k] }; k += 1 }
|
||||||
if (found == null) { perr(`no argument named {labels[li]}`) }
|
if (found == null) { perr(`no argument named {labels[li]}`) }
|
||||||
push(out, found.a)
|
push(out, found.a)
|
||||||
li = li + 1
|
li += 1
|
||||||
}
|
}
|
||||||
e.kids = out
|
e.kids = out
|
||||||
}
|
}
|
||||||
|
|
@ -224,14 +240,13 @@ function reorder_named(e: Node, labels: []pointer) -> void {
|
||||||
function param_labels(fn: Node) -> []pointer {
|
function param_labels(fn: Node) -> []pointer {
|
||||||
let out = new []pointer
|
let out = new []pointer
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].s) }; i = i + 1 }
|
while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].s) }; i += 1 }
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
function param_types(fn: Node) -> []pointer {
|
function param_types(fn: Node) -> []pointer {
|
||||||
let out = new []pointer
|
let out = new []pointer
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].ty) }; i = i + 1 }
|
while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].ty) }; i += 1 }
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,19 @@ function hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n }
|
||||||
# emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX
|
# emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX
|
||||||
function emit_str_const(s: pointer) -> pointer {
|
function emit_str_const(s: pointer) -> pointer {
|
||||||
let name = `@.str{itoa(ll_str)}`
|
let name = `@.str{itoa(ll_str)}`
|
||||||
ll_str = ll_str + 1
|
ll_str += 1
|
||||||
let n = len(s)
|
let n = len(s)
|
||||||
emith(name); emith(" = private unnamed_addr constant [")
|
emith(name); emith(" = private unnamed_addr constant [")
|
||||||
emith(itoa(n + 1)); emith(" x i8] c\"")
|
emith(itoa(n + 1)); emith(" x i8] c\"")
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let c = s[i]
|
let c = s[i]
|
||||||
if c == 34 or c == 92 or c < 32 or c > 126 {
|
if c == '"' or c == CH_BACKSLASH or c < ' ' or c > '~' {
|
||||||
buf_putc(head, 92) # backslash
|
buf_putc(head, CH_BACKSLASH) # \XX hex escape
|
||||||
buf_putc(head, hexdig(c / 16))
|
buf_putc(head, hexdig(c / 16))
|
||||||
buf_putc(head, hexdig(c % 16))
|
buf_putc(head, hexdig(c % 16))
|
||||||
} else { buf_putc(head, c) }
|
} else { buf_putc(head, c) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emith("\\00\"\n")
|
emith("\\00\"\n")
|
||||||
return name
|
return name
|
||||||
|
|
@ -64,7 +64,7 @@ function emit_global_init_fn() -> void {
|
||||||
let v = emit_expr(d.a)
|
let v = emit_expr(d.a)
|
||||||
emit(" store "); emit(llty(d.ty)); emit(" "); emit(coerce_code(v, d.ty)); emit(", ptr @g_"); emit(d.s); emit("\n")
|
emit(" store "); emit(llty(d.ty)); emit(" "); emit(coerce_code(v, d.ty)); emit(", ptr @g_"); emit(d.s); emit("\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret void\n")
|
emit(" ret void\n")
|
||||||
code = saved
|
code = saved
|
||||||
|
|
@ -149,7 +149,7 @@ function emit_header() -> void {
|
||||||
if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`property {d.s} is also a property of the engine runtime; choose another name`) }
|
if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`property {d.s} is also a property of the engine runtime; choose another name`) }
|
||||||
perr(`property {d.s} is declared twice`)
|
perr(`property {d.s} is declared twice`)
|
||||||
}
|
}
|
||||||
j = j + 1
|
j += 1
|
||||||
}
|
}
|
||||||
emith(layout_ty(d.s)); emith(" = type { ")
|
emith(layout_ty(d.s)); emith(" = type { ")
|
||||||
if len(d.kids) == 0 { emith("i32") }
|
if len(d.kids) == 0 { emith("i32") }
|
||||||
|
|
@ -157,11 +157,11 @@ function emit_header() -> void {
|
||||||
while f < len(d.kids) {
|
while f < len(d.kids) {
|
||||||
if f > 0 { emith(", ") }
|
if f > 0 { emith(", ") }
|
||||||
emith(llty(d.kids[f].ty))
|
emith(llty(d.kids[f].ty))
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emith(" }\n")
|
emith(" }\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# globals (vars) — aggregates/pointers default to null, scalars to 0
|
# globals (vars) — aggregates/pointers default to null, scalars to 0
|
||||||
i = 0
|
i = 0
|
||||||
|
|
@ -174,14 +174,14 @@ function emit_header() -> void {
|
||||||
if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`variable {d.s} is also a variable of the engine runtime; choose another name`) }
|
if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`variable {d.s} is also a variable of the engine runtime; choose another name`) }
|
||||||
perr(`variable {d.s} is declared twice`)
|
perr(`variable {d.s} is declared twice`)
|
||||||
}
|
}
|
||||||
j = j + 1
|
j += 1
|
||||||
}
|
}
|
||||||
emith("@g_"); emith(d.s); emith(" = internal global ")
|
emith("@g_"); emith(d.s); emith(" = internal global ")
|
||||||
emith(llty(d.ty)); emith(" ")
|
emith(llty(d.ty)); emith(" ")
|
||||||
emith(global_init(d))
|
emith(global_init(d))
|
||||||
emith("\n")
|
emith("\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,11 +198,11 @@ function emit_extern_decls() -> void {
|
||||||
while f < len(d.kids) {
|
while f < len(d.kids) {
|
||||||
if f > 0 { emith(", ") }
|
if f > 0 { emith(", ") }
|
||||||
emith(llty(d.kids[f].ty))
|
emith(llty(d.kids[f].ty))
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emith(")\n")
|
emith(")\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ function emit_extern_decls() -> void {
|
||||||
# until a mismatch or a shared terminator; str_concat measures both, mallocs
|
# until a mismatch or a shared terminator; str_concat measures both, mallocs
|
||||||
# len+len+1, copies each half, and NUL-terminates. @malloc is always declared.
|
# len+len+1, copies each half, and NUL-terminates. @malloc is always declared.
|
||||||
function emit_str_prelude() -> void {
|
function emit_str_prelude() -> void {
|
||||||
emith("define i32 @fn_str_eq(ptr %a, ptr %b) {\n")
|
emith("define i32 @lp_str_eq(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n br label %loop\n")
|
emith("entry:\n br label %loop\n")
|
||||||
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n")
|
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n")
|
||||||
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %i\n")
|
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %i\n")
|
||||||
|
|
@ -222,7 +222,7 @@ function emit_str_prelude() -> void {
|
||||||
emith("cont:\n %i1 = add i32 %i, 1\n br label %loop\n")
|
emith("cont:\n %i1 = add i32 %i, 1\n br label %loop\n")
|
||||||
emith("ret1:\n ret i32 1\nret0:\n ret i32 0\n}\n")
|
emith("ret1:\n ret i32 1\nret0:\n ret i32 0\n}\n")
|
||||||
|
|
||||||
emith("define ptr @fn_str_concat(ptr %a, ptr %b) {\n")
|
emith("define ptr @lp_str_concat(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n br label %al\n")
|
emith("entry:\n br label %al\n")
|
||||||
emith("al:\n %ia = phi i32 [ 0, %entry ], [ %ia1, %alb ]\n")
|
emith("al:\n %ia = phi i32 [ 0, %entry ], [ %ia1, %alb ]\n")
|
||||||
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %ia\n %cca = load i8, ptr %pa\n")
|
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %ia\n %cca = load i8, ptr %pa\n")
|
||||||
|
|
@ -253,7 +253,7 @@ function emit_str_prelude() -> void {
|
||||||
# (string interpolation of a number). Writes digits from the end of a 24-byte
|
# (string interpolation of a number). Writes digits from the end of a 24-byte
|
||||||
# buffer, prepends '-' for negatives, and returns a pointer into the buffer.
|
# buffer, prepends '-' for negatives, and returns a pointer into the buffer.
|
||||||
function emit_int_str() -> void {
|
function emit_int_str() -> void {
|
||||||
emith("define ptr @fn_int_str(i32 %n0) {\n")
|
emith("define ptr @lp_int_str(i32 %n0) {\n")
|
||||||
emith("entry:\n %buf = call ptr @malloc(i64 24)\n")
|
emith("entry:\n %buf = call ptr @malloc(i64 24)\n")
|
||||||
emith(" %isneg = icmp slt i32 %n0, 0\n %neg = sub i32 0, %n0\n")
|
emith(" %isneg = icmp slt i32 %n0, 0\n %neg = sub i32 0, %n0\n")
|
||||||
emith(" %n = select i1 %isneg, i32 %neg, i32 %n0\n")
|
emith(" %n = select i1 %isneg, i32 %neg, i32 %n0\n")
|
||||||
|
|
@ -276,7 +276,7 @@ function emit_int_str() -> void {
|
||||||
# once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is
|
# once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is
|
||||||
# at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits.
|
# at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits.
|
||||||
function emit_long_str() -> void {
|
function emit_long_str() -> void {
|
||||||
emith("define ptr @fn_long_str(i64 %n0) {\n")
|
emith("define ptr @lp_long_str(i64 %n0) {\n")
|
||||||
emith("entry:\n %buf = call ptr @malloc(i64 24)\n")
|
emith("entry:\n %buf = call ptr @malloc(i64 24)\n")
|
||||||
emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n")
|
emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n")
|
||||||
emith(" %n = select i1 %isneg, i64 %neg, i64 %n0\n")
|
emith(" %n = select i1 %isneg, i64 %neg, i64 %n0\n")
|
||||||
|
|
@ -298,7 +298,7 @@ function emit_long_str() -> void {
|
||||||
# s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into
|
# s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into
|
||||||
# any program that slices a string. Mallocs (b-a)+1, copies, terminates.
|
# any program that slices a string. Mallocs (b-a)+1, copies, terminates.
|
||||||
function emit_str_slice() -> void {
|
function emit_str_slice() -> void {
|
||||||
emith("define ptr @fn_str_slice(ptr %s, i32 %start, i32 %end) {\n")
|
emith("define ptr @lp_str_slice(ptr %s, i32 %start, i32 %end) {\n")
|
||||||
emith("entry:\n %len = sub i32 %end, %start\n %sz = add i32 %len, 1\n")
|
emith("entry:\n %len = sub i32 %end, %start\n %sz = add i32 %len, 1\n")
|
||||||
emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @malloc(i64 %sz64)\n br label %loop\n")
|
emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @malloc(i64 %sz64)\n br label %loop\n")
|
||||||
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %body ]\n")
|
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %body ]\n")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ function rec_field(rec: Node, fname: pointer) -> Node {
|
||||||
while i < len(rec.kids) {
|
while i < len(rec.kids) {
|
||||||
let fi = rec.kids[i]
|
let fi = rec.kids[i]
|
||||||
if (fi.s == fname) { return fi.a }
|
if (fi.s == fname) { return fi.a }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ function emit_new_struct(name: pointer, rec: Node) -> Val {
|
||||||
let ov = rec_field(rec, fd.s) # explicit override wins over the default
|
let ov = rec_field(rec, fd.s) # explicit override wins over the default
|
||||||
if (ov != null) { let dv = emit_expr(ov); v = dv.code }
|
if (ov != null) { let dv = emit_expr(ov); v = dv.code }
|
||||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
return val(obj, name)
|
return val(obj, name)
|
||||||
}
|
}
|
||||||
|
|
@ -78,9 +78,32 @@ function emit_len(e: Node) -> Val {
|
||||||
|
|
||||||
function emit_push(e: Node) -> Val {
|
function emit_push(e: Node) -> Val {
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
let el = slice_elem(s.ty)
|
emit_push_into(s.code, llty(slice_elem(s.ty)), emit_expr(e.kids[1]))
|
||||||
let elt = llty(el)
|
return val("0", "void")
|
||||||
let h = s.code
|
}
|
||||||
|
|
||||||
|
# `[a, b, c]` — a fresh slice holding the elements in order. The element type
|
||||||
|
# is the first element's; an empty literal has none, so it is spelled `new []T`.
|
||||||
|
function emit_list(e: Node) -> Val {
|
||||||
|
if len(e.kids) == 0 { perr("an empty list literal has no element type: write `new []T` instead") }
|
||||||
|
let first = emit_expr(e.kids[0])
|
||||||
|
let ty = "[]" + first.ty
|
||||||
|
let elt = llty(first.ty)
|
||||||
|
let s = emit_new_slice(ty)
|
||||||
|
emit_push_into(s.code, elt, first)
|
||||||
|
var i = 1
|
||||||
|
while i < len(e.kids) {
|
||||||
|
let v = emit_expr(e.kids[i])
|
||||||
|
if not (v.ty == first.ty) { perr(`list literal mixes element types {first.ty} and {v.ty}`) }
|
||||||
|
emit_push_into(s.code, elt, v)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
# append the already-emitted value `v` (of LLVM element type `elt`) to the slice
|
||||||
|
# whose header is `h`, growing the storage when it is full
|
||||||
|
function emit_push_into(h: pointer, elt: pointer, v: Val) -> void {
|
||||||
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
|
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
|
||||||
let l = emit_bind(`load i32, ptr {lp}`)
|
let l = emit_bind(`load i32, ptr {lp}`)
|
||||||
let c = emit_bind(`load i32, ptr {cp}`)
|
let c = emit_bind(`load i32, ptr {cp}`)
|
||||||
|
|
@ -100,12 +123,10 @@ function emit_push(e: Node) -> Val {
|
||||||
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
|
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
|
||||||
emit(" br label %"); emit(put); emit("\n")
|
emit(" br label %"); emit(put); emit("\n")
|
||||||
emit(put); emit(":\n")
|
emit(put); emit(":\n")
|
||||||
let v = emit_expr(e.kids[1])
|
|
||||||
let data = emit_bind(`load ptr, ptr {dp}`)
|
let data = emit_bind(`load ptr, ptr {dp}`)
|
||||||
let slot = nreg()
|
let slot = nreg()
|
||||||
emit(" "); emit(slot); emit(" = getelementptr inbounds "); emit(elt); emit(", ptr "); emit(data); emit(", i32 "); emit(l); emit("\n")
|
emit(" "); emit(slot); emit(" = getelementptr inbounds "); emit(elt); emit(", ptr "); emit(data); emit(", i32 "); emit(l); emit("\n")
|
||||||
emit(" store "); emit(elt); emit(" "); emit(v.code); emit(", ptr "); emit(slot); emit("\n")
|
emit(" store "); emit(elt); emit(" "); emit(v.code); emit(", ptr "); emit(slot); emit("\n")
|
||||||
let l1 = emit_bind(`add i32 {l}, 1`)
|
let l1 = emit_bind(`add i32 {l}, 1`)
|
||||||
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
||||||
return val("0", "void")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ function emit_block(b: Node) -> void {
|
||||||
while i < len(b.kids) {
|
while i < len(b.kids) {
|
||||||
if g_term { return }
|
if g_term { return }
|
||||||
emit_stmt(b.kids[i])
|
emit_stmt(b.kids[i])
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,6 +15,11 @@ function store_at(lt: pointer, v: pointer, addr: pointer) -> void {
|
||||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# the binary operator behind a compound assignment token: "+=" -> "+"
|
||||||
|
function compound_op(tok: pointer) -> pointer {
|
||||||
|
return tok[0..1]
|
||||||
|
}
|
||||||
|
|
||||||
function emit_assign(st: Node) -> void {
|
function emit_assign(st: Node) -> void {
|
||||||
# resolve the target's address and type
|
# resolve the target's address and type
|
||||||
let t = st.a
|
let t = st.a
|
||||||
|
|
@ -39,17 +44,16 @@ function emit_assign(st: Node) -> void {
|
||||||
else { perr("bad assignment target") } }
|
else { perr("bad assignment target") } }
|
||||||
}
|
}
|
||||||
let lt = llty(ty)
|
let lt = llty(ty)
|
||||||
let rv = emit_expr(st.b)
|
var rv = emit_expr(st.b)
|
||||||
|
if not (st.s == "=") {
|
||||||
|
# `x op= y` is `x = x op y`: reload the target and lower through the same
|
||||||
|
# path as a binary expression, so fixed `*=`, string `+=` and long promotion
|
||||||
|
# all behave exactly like their spelled-out forms.
|
||||||
|
let cur = val(emit_bind(`load {lt}, ptr {addr}`), ty)
|
||||||
|
rv = emit_bin_vals(compound_op(st.s), cur, rv, false)
|
||||||
|
}
|
||||||
var v = coerce_code(rv, ty)
|
var v = coerce_code(rv, ty)
|
||||||
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
|
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
|
||||||
if not (st.s == "=") {
|
|
||||||
let cur = emit_bind(`load {lt}, ptr {addr}`)
|
|
||||||
var opc = "add"
|
|
||||||
if (st.s == ("-=")) { opc = "sub" }
|
|
||||||
if (st.s == ("*=")) { opc = "mul" }
|
|
||||||
if (st.s == ("/=")) { opc = "sdiv" }
|
|
||||||
v = emit_bind(`{opc} {lt} {cur}, {v}`)
|
|
||||||
}
|
|
||||||
store_at(lt, v, addr)
|
store_at(lt, v, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +127,7 @@ function emit_return(st: Node) -> void {
|
||||||
|
|
||||||
function arm_is_default(arm: Node) -> bool {
|
function arm_is_default(arm: Node) -> bool {
|
||||||
var p = 0
|
var p = 0
|
||||||
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p = p + 1 }
|
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +151,7 @@ function emit_bind_payload(en: Node, pat: Node, sv: Val) -> void {
|
||||||
let lv = emit_bind(`load {lt}, ptr {p}`)
|
let lv = emit_bind(`load {lt}, ptr {p}`)
|
||||||
let slot = emit_alloca(lt); store_at(lt, lv, slot)
|
let slot = emit_alloca(lt); store_at(lt, lv, slot)
|
||||||
loc_push(pat.kids[k].s, slot, pty)
|
loc_push(pat.kids[k].s, slot, pty)
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +182,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
|
||||||
covered = covered | (1 << ord)
|
covered = covered | (1 << ord)
|
||||||
let c = emit_bind(`icmp eq i32 {tag}, {itoa(ord)}`)
|
let c = emit_bind(`icmp eq i32 {tag}, {itoa(ord)}`)
|
||||||
if first { acc = c; first = false } else { acc = emit_bind(`or i1 {acc}, {c}`) }
|
if first { acc = c; first = false } else { acc = emit_bind(`or i1 {acc}, {c}`) }
|
||||||
p = p + 1
|
p += 1
|
||||||
}
|
}
|
||||||
let bodyl = lbl("mbody"); let nextl = lbl("marm")
|
let bodyl = lbl("mbody"); let nextl = lbl("marm")
|
||||||
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
|
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
|
||||||
|
|
@ -190,7 +194,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
|
||||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||||
emit(nextl); emit(":\n"); g_term = false
|
emit(nextl); emit(":\n"); g_term = false
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
if (deflt != null) { emit_block(deflt.a) }
|
if (deflt != null) { emit_block(deflt.a) }
|
||||||
else {
|
else {
|
||||||
|
|
@ -199,7 +203,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
|
||||||
var m = 0
|
var m = 0
|
||||||
while m < len(en.kids) {
|
while m < len(en.kids) {
|
||||||
if (covered & (1 << m)) == 0 { perr(`match on {en.s} is not exhaustive: variant {en.kids[m].s} is unhandled (add it or a _ arm)`) }
|
if (covered & (1 << m)) == 0 { perr(`match on {en.s} is not exhaustive: variant {en.kids[m].s} is unhandled (add it or a _ arm)`) }
|
||||||
m = m + 1
|
m += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +230,7 @@ function emit_match(st: Node) -> void {
|
||||||
let c = emit_bind(`icmp eq i32 {sv.code}, {pv.code}`)
|
let c = emit_bind(`icmp eq i32 {sv.code}, {pv.code}`)
|
||||||
if first { acc = c; first = false }
|
if first { acc = c; first = false }
|
||||||
else { acc = emit_bind(`or i1 {acc}, {c}`) }
|
else { acc = emit_bind(`or i1 {acc}, {c}`) }
|
||||||
p = p + 1
|
p += 1
|
||||||
}
|
}
|
||||||
let bodyl = lbl("mbody"); let nextl = lbl("marm")
|
let bodyl = lbl("mbody"); let nextl = lbl("marm")
|
||||||
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
|
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
|
||||||
|
|
@ -235,7 +239,7 @@ function emit_match(st: Node) -> void {
|
||||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||||
emit(nextl); emit(":\n"); g_term = false
|
emit(nextl); emit(":\n"); g_term = false
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
if (deflt != null) { emit_block(deflt.a) }
|
if (deflt != null) { emit_block(deflt.a) }
|
||||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||||
|
|
@ -256,14 +260,14 @@ function emit_emit(st: Node) -> Val {
|
||||||
let fd = ev.kids[f]
|
let fd = ev.kids[f]
|
||||||
var av: Node = null # the caller's value for this field, if given
|
var av: Node = null # the caller's value for this field, if given
|
||||||
var j = 0
|
var j = 0
|
||||||
while j < len(st.a.kids) { if (st.a.kids[j].s == fd.s) { av = st.a.kids[j].a }; j = j + 1 }
|
while j < len(st.a.kids) { if (st.a.kids[j].s == fd.s) { av = st.a.kids[j].a }; j += 1 }
|
||||||
let lt = llty(fd.ty)
|
let lt = llty(fd.ty)
|
||||||
var code = "0"
|
var code = "0"
|
||||||
if (lt == "ptr") { code = "null" }
|
if (lt == "ptr") { code = "null" }
|
||||||
if (av != null) { let v = emit_expr(av); code = v.code }
|
if (av != null) { let v = emit_expr(av); code = v.code }
|
||||||
else { if (fd.a != null) { let dv = emit_expr(fd.a); code = dv.code } } # declared default
|
else { if (fd.a != null) { let dv = emit_expr(fd.a); code = dv.code } } # declared default
|
||||||
push(fcodes, code); push(ftys, lt)
|
push(fcodes, code); push(ftys, lt)
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
# N4: a remote event (@ToServer/@ToClients) serializes its payload as
|
# N4: a remote event (@ToServer/@ToClients) serializes its payload as
|
||||||
# [i32 event_id][packed fields] and net_send in its direction — the far side's
|
# [i32 event_id][packed fields] and net_send in its direction — the far side's
|
||||||
|
|
@ -276,8 +280,8 @@ function emit_emit(st: Node) -> Val {
|
||||||
while k < len(ev.kids) {
|
while k < len(ev.kids) {
|
||||||
let dp = nreg(); emit(" "); emit(dp); emit(" = getelementptr inbounds i8, ptr @L_sendbuf, i32 "); emit(itoa(off)); emit("\n")
|
let dp = nreg(); emit(" "); emit(dp); emit(" = getelementptr inbounds i8, ptr @L_sendbuf, i32 "); emit(itoa(off)); emit("\n")
|
||||||
emit(" store "); emit(ftys[k]); emit(" "); emit(fcodes[k]); emit(", ptr "); emit(dp); emit("\n")
|
emit(" store "); emit(ftys[k]); emit(" "); emit(fcodes[k]); emit(", ptr "); emit(dp); emit("\n")
|
||||||
off = off + net_field_ibytes(ev.kids[k].ty)
|
off += net_field_ibytes(ev.kids[k].ty)
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
var peer = "0"
|
var peer = "0"
|
||||||
if (ev.ty == "toclients") { peer = "-1" } # broadcast (loopback ignores the peer id)
|
if (ev.ty == "toclients") { peer = "-1" } # broadcast (loopback ignores the peer id)
|
||||||
|
|
@ -292,7 +296,7 @@ function emit_emit(st: Node) -> Val {
|
||||||
while g < len(fcodes) {
|
while g < len(fcodes) {
|
||||||
if g > 0 { buf_puts(args, ", ") }
|
if g > 0 { buf_puts(args, ", ") }
|
||||||
buf_puts(args, ftys[g]); buf_puts(args, " "); buf_puts(args, fcodes[g])
|
buf_puts(args, ftys[g]); buf_puts(args, " "); buf_puts(args, fcodes[g])
|
||||||
g = g + 1
|
g += 1
|
||||||
}
|
}
|
||||||
if ev.ival == 1 {
|
if ev.ival == 1 {
|
||||||
let r = nreg()
|
let r = nreg()
|
||||||
|
|
@ -304,6 +308,7 @@ function emit_emit(st: Node) -> Val {
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_stmt(st: Node) -> void {
|
function emit_stmt(st: Node) -> void {
|
||||||
|
g_err_file = st.file; g_err_line = st.line # so a lowering error names its statement
|
||||||
emit_cov_hit(st.line) # --coverage: bump this line's hit counter (no-op otherwise)
|
emit_cov_hit(st.line) # --coverage: bump this line's hit counter (no-op otherwise)
|
||||||
if st.kind == S_LET {
|
if st.kind == S_LET {
|
||||||
var ty = st.ty
|
var ty = st.ty
|
||||||
|
|
@ -347,6 +352,6 @@ function emit_stmt(st: Node) -> void {
|
||||||
function loop_push(cont: pointer, brk: pointer) -> void {
|
function loop_push(cont: pointer, brk: pointer) -> void {
|
||||||
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
|
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
|
||||||
else { push(cnt_lbl, cont); push(brk_lbl, brk) }
|
else { push(cnt_lbl, cont); push(brk_lbl, brk) }
|
||||||
nloop = nloop + 1
|
nloop += 1
|
||||||
}
|
}
|
||||||
function loop_pop() -> void { nloop = nloop - 1 }
|
function loop_pop() -> void { nloop -= 1 }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const MAX_ENT: int = 1024
|
||||||
|
|
||||||
function has_systems() -> bool {
|
function has_systems() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i += 1 }
|
||||||
# a game with no hand-written handler but a well-known engine component still
|
# a game with no hand-written handler but a well-known engine component still
|
||||||
# runs a frame loop — the engine owns the system that ticks that component
|
# runs a frame loop — the engine owns the system that ticks that component
|
||||||
# (#43/#47). Treat it as a systems game so the loop / tick helpers are emitted.
|
# (#43/#47). Treat it as a systems game so the loop / tick helpers are emitted.
|
||||||
|
|
@ -16,7 +16,7 @@ function has_systems() -> bool {
|
||||||
}
|
}
|
||||||
function has_models() -> bool {
|
function has_models() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_ARCH { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_ARCH { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
# does the program declare a well-known engine component? Each one is auto-ticked
|
# does the program declare a well-known engine component? Each one is auto-ticked
|
||||||
|
|
@ -30,7 +30,7 @@ function uses_engine_systems() -> bool {
|
||||||
# Motion / Light2D; packages append via @EngineSystem), or an Occluder (which
|
# Motion / Light2D; packages append via @EngineSystem), or an Occluder (which
|
||||||
# feeds the Light2D pass without an esys of its own).
|
# feeds the Light2D pass without an esys of its own).
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_esys_comp) { if find_comp(g_esys_comp[i]) != null { return true }; i = i + 1 }
|
while i < len(g_esys_comp) { if find_comp(g_esys_comp[i]) != null { return true }; i += 1 }
|
||||||
if find_comp("Occluder") != null { return true }
|
if find_comp("Occluder") != null { return true }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ function uses_engine_systems() -> bool {
|
||||||
# compiler's auto frame loop. A game with handlers and no entry uses the auto-loop.
|
# compiler's auto frame loop. A game with handlers and no entry uses the auto-loop.
|
||||||
function has_entry() -> bool {
|
function has_entry() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_MAIN { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_MAIN { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
# Does this program run the ECS? A property alone no longer answers that — the
|
# Does this program run the ECS? A property alone no longer answers that — the
|
||||||
|
|
@ -82,11 +82,11 @@ function emit_ecs_storage() -> void {
|
||||||
# one enabled-flag global per model and per handler (default enabled)
|
# one enabled-flag global per model and per handler (default enabled)
|
||||||
if c.kind == N_ARCH { emith(`@ME_{c.s} = internal global i32 1\n`) }
|
if c.kind == N_ARCH { emith(`@ME_{c.s} = internal global i32 1\n`) }
|
||||||
if c.kind == N_SYS { emith(`@HE_{c.s} = internal global i32 1\n`) }
|
if c.kind == N_SYS { emith(`@HE_{c.s} = internal global i32 1\n`) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# one enabled-flag global per toggled layer (default shown)
|
# one enabled-flag global per toggled layer (default shown)
|
||||||
var li = 0
|
var li = 0
|
||||||
while li < len(g_toggled_layers) { emith(`@LE_{g_toggled_layers[li]} = internal global i32 1\n`); li = li + 1 }
|
while li < len(g_toggled_layers) { emith(`@LE_{g_toggled_layers[li]} = internal global i32 1\n`); li += 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
# L_reset(e): clear every has-flag and the archetype kind for entity e
|
# L_reset(e): clear every has-flag and the archetype kind for entity e
|
||||||
|
|
@ -100,7 +100,7 @@ function emit_ecs_allocator() -> void {
|
||||||
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
|
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
|
||||||
emit(" store i8 0, ptr "); emit(hn); emit("\n")
|
emit(" store i8 0, ptr "); emit(hn); emit("\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" %k = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
emit(" %k = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
|
||||||
emit(" store i32 0, ptr %k\n")
|
emit(" store i32 0, ptr %k\n")
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ function emit_engine_systems_for_phase(phase: pointer) -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_esys_comp) {
|
while i < len(g_esys_comp) {
|
||||||
if (g_esys_phase[i] == phase) { emit_one_engine_system(g_esys_comp[i], g_esys_fn[i]) }
|
if (g_esys_phase[i] == phase) { emit_one_engine_system(g_esys_comp[i], g_esys_fn[i]) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
if (phase == "Update") {
|
if (phase == "Update") {
|
||||||
# Tween.* fluent handles (#48): advanced each Update tick when the game uses
|
# Tween.* fluent handles (#48): advanced each Update tick when the game uses
|
||||||
|
|
@ -100,7 +100,7 @@ function emit_countdown_system() -> void {
|
||||||
if d.kind == N_COMP {
|
if d.kind == N_COMP {
|
||||||
var any = false
|
var any = false
|
||||||
var f = 0
|
var f = 0
|
||||||
while f < len(d.kids) { if (d.kids[f].ty == "countdown") { any = true }; f = f + 1 }
|
while f < len(d.kids) { if (d.kids[f].ty == "countdown") { any = true }; f += 1 }
|
||||||
if any {
|
if any {
|
||||||
let sk = itoa(k); let nk = itoa(k + 1)
|
let sk = itoa(k); let nk = itoa(k + 1)
|
||||||
emit("c"); emit(sk); emit(":\n")
|
emit("c"); emit(sk); emit(":\n")
|
||||||
|
|
@ -121,13 +121,13 @@ function emit_countdown_system() -> void {
|
||||||
emit(" %fn"); emit(fk); emit(" = select i1 %fp"); emit(fk); emit(", i32 %fd"); emit(fk); emit(", i32 %fv"); emit(fk); emit("\n")
|
emit(" %fn"); emit(fk); emit(" = select i1 %fp"); emit(fk); emit(", i32 %fd"); emit(fk); emit(", i32 %fv"); emit(fk); emit("\n")
|
||||||
emit(" store i32 %fn"); emit(fk); emit(", ptr %fa"); emit(fk); emit("\n")
|
emit(" store i32 %fn"); emit(fk); emit(", ptr %fa"); emit(fk); emit("\n")
|
||||||
}
|
}
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emit(" br label %c"); emit(nk); emit("\n")
|
emit(" br label %c"); emit(nk); emit("\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit("c"); emit(itoa(k)); emit(":\n br label %next\n")
|
emit("c"); emit(itoa(k)); emit(":\n br label %next\n")
|
||||||
emit("next:\n %i1 = add i32 %i, 1\n br label %loop\ndone:\n ret void\n}\n\n")
|
emit("next:\n %i1 = add i32 %i, 1\n br label %loop\ndone:\n ret void\n}\n\n")
|
||||||
|
|
@ -154,7 +154,7 @@ function emit_prefab_fn(pre: Node) -> void {
|
||||||
}
|
}
|
||||||
function emit_prefab_fns() -> void {
|
function emit_prefab_fns() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_PREFAB { emit_prefab_fn(prog[i]) }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_PREFAB { emit_prefab_fn(prog[i]) }; i += 1 }
|
||||||
g_uses_str = true
|
g_uses_str = true
|
||||||
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
|
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
|
||||||
let fbody = buf_new()
|
let fbody = buf_new()
|
||||||
|
|
@ -167,7 +167,7 @@ function emit_prefab_fns() -> void {
|
||||||
if d.kind == N_PREFAB {
|
if d.kind == N_PREFAB {
|
||||||
let lit = node(E_STR); lit.s = d.s
|
let lit = node(E_STR); lit.s = d.s
|
||||||
let sv = emit_expr(lit)
|
let sv = emit_expr(lit)
|
||||||
let same = emit_bind(`call i32 @fn_str_eq(ptr %name, ptr {sv.code})`)
|
let same = emit_bind(`call i32 @lp_str_eq(ptr %name, ptr {sv.code})`)
|
||||||
let hit = emit_bind(`icmp ne i32 {same}, 0`)
|
let hit = emit_bind(`icmp ne i32 {same}, 0`)
|
||||||
let yes = lbl("pf"); let no = lbl("pfn")
|
let yes = lbl("pf"); let no = lbl("pfn")
|
||||||
emit(" br i1 "); emit(hit); emit(", label %"); emit(yes); emit(", label %"); emit(no); emit("\n")
|
emit(" br i1 "); emit(hit); emit(", label %"); emit(yes); emit(", label %"); emit(no); emit("\n")
|
||||||
|
|
@ -176,7 +176,7 @@ function emit_prefab_fns() -> void {
|
||||||
emit(" ret i32 "); emit(r); emit("\n")
|
emit(" ret i32 "); emit(r); emit("\n")
|
||||||
emit(no); emit(":\n")
|
emit(no); emit(":\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 -1\n")
|
emit(" ret i32 -1\n")
|
||||||
code = saved
|
code = saved
|
||||||
|
|
@ -209,11 +209,20 @@ function phase_id(phase: pointer) -> int {
|
||||||
if phase == "LateUpdate" { return 3 }
|
if phase == "LateUpdate" { return 3 }
|
||||||
if phase == "Render" { return 4 }
|
if phase == "Render" { return 4 }
|
||||||
if phase == "Start" { return 5 }
|
if phase == "Start" { return 5 }
|
||||||
if phase == "OnQuit" { return 6 }
|
if phase == "OnQuit" { return 6 } # the @OnQuit hook's internal slot, never written as `phase OnQuit`
|
||||||
if phase == "Overlay" { return 7 } # HUD/UI pass after the engine drew sprites
|
if phase == "Overlay" { return 7 } # HUD/UI pass after the engine drew sprites
|
||||||
|
perr(`unknown phase '{phase}'`) # the parser rejects these; a desugaring bug if we get here
|
||||||
return 7
|
return 7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# the phases a handler may declare — the frame loop's buckets, in the order the
|
||||||
|
# frame runs them (Start once, then Input … Overlay every tick)
|
||||||
|
function is_phase_name(name: pointer) -> bool {
|
||||||
|
if name == "Start" or name == "Input" or name == "FixedUpdate" or name == "Update" { return true }
|
||||||
|
if name == "LateUpdate" or name == "Render" or name == "Overlay" { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
# --- binary-module glue (issue #64) ------------------------------------------
|
# --- binary-module glue (issue #64) ------------------------------------------
|
||||||
# A module compiled with --emit-module carries no main and no world table (the
|
# A module compiled with --emit-module carries no main and no world table (the
|
||||||
# consumer owns them). It references the host's reflection ABI, so we (1) declare
|
# consumer owns them). It references the host's reflection ABI, so we (1) declare
|
||||||
|
|
@ -248,7 +257,7 @@ function emit_module_glue() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_mod_sys_fn) {
|
while i < len(g_mod_sys_fn) {
|
||||||
emit(" call i32 @ludic_register_system(ptr @fn_"); emit(g_mod_sys_fn[i]); emit(", i32 "); emit(itoa(phase_id(g_mod_sys_phase[i]))); emit(")\n")
|
emit(" call i32 @ludic_register_system(ptr @fn_"); emit(g_mod_sys_fn[i]); emit(", i32 "); emit(itoa(phase_id(g_mod_sys_phase[i]))); emit(")\n")
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret void\n}\n\n")
|
emit(" ret void\n}\n\n")
|
||||||
# run @__ludic_mod_init at image load (dyld runs constructors before main)
|
# run @__ludic_mod_init at image load (dyld runs constructors before main)
|
||||||
|
|
@ -309,12 +318,12 @@ function emit_calls_for_phase(phase: pointer) -> void {
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
let d = prog[i]
|
let d = prog[i]
|
||||||
if d.kind == N_SYS and (d.ty == phase) and (d.c == null) { emit_call_one(d) }
|
if d.kind == N_SYS and (d.ty == phase) and (d.c == null) { emit_call_one(d) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# any scene-owned handlers in this phase? gate them on one @L_scene snapshot.
|
# any scene-owned handlers in this phase? gate them on one @L_scene snapshot.
|
||||||
var has_sc = false
|
var has_sc = false
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_SYS and (d.ty == phase) and (d.c != null) { has_sc = true }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_SYS and (d.ty == phase) and (d.c != null) { has_sc = true }; i += 1 }
|
||||||
if has_sc {
|
if has_sc {
|
||||||
let cs = emit_bind("load i32, ptr @L_scene")
|
let cs = emit_bind("load i32, ptr @L_scene")
|
||||||
i = 0
|
i = 0
|
||||||
|
|
@ -328,7 +337,7 @@ function emit_calls_for_phase(phase: pointer) -> void {
|
||||||
emit_call_one(d)
|
emit_call_one(d)
|
||||||
emit(" br label %"); emit(skip); emit("\n"); emit(skip); emit(":\n")
|
emit(" br label %"); emit(skip); emit("\n"); emit(skip); emit(":\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit_engine_systems_for_phase(phase) # engine-owned systems run after every user handler
|
emit_engine_systems_for_phase(phase) # engine-owned systems run after every user handler
|
||||||
|
|
@ -340,7 +349,7 @@ function emit_calls_for_phase(phase: pointer) -> void {
|
||||||
function emit_scene_menu_render() -> void {
|
function emit_scene_menu_render() -> void {
|
||||||
var any = false
|
var any = false
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_scenes) { if (g_scenes[i].ty != null) { any = true }; i = i + 1 }
|
while i < len(g_scenes) { if (g_scenes[i].ty != null) { any = true }; i += 1 }
|
||||||
if not any { return }
|
if not any { return }
|
||||||
let cs = emit_bind("load i32, ptr @L_scene")
|
let cs = emit_bind("load i32, ptr @L_scene")
|
||||||
i = 0
|
i = 0
|
||||||
|
|
@ -353,7 +362,7 @@ function emit_scene_menu_render() -> void {
|
||||||
emit(run); emit(":\n call void @fn_rt_ui_render()\n")
|
emit(run); emit(":\n call void @fn_rt_ui_render()\n")
|
||||||
emit(" br label %"); emit(skip); emit("\n"); emit(skip); emit(":\n")
|
emit(" br label %"); emit(skip); emit("\n"); emit(skip); emit(":\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,7 +402,7 @@ function emit_scene_hooks() -> void {
|
||||||
g_cur_scene = sc
|
g_cur_scene = sc
|
||||||
emit_scene_fn(sc.s, "enter", sc.a, sc.ty)
|
emit_scene_fn(sc.s, "enter", sc.a, sc.ty)
|
||||||
emit_scene_fn(sc.s, "exit", sc.b, sc.ty)
|
emit_scene_fn(sc.s, "exit", sc.b, sc.ty)
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
g_cur_scene = null
|
g_cur_scene = null
|
||||||
if len(g_scenes) > 0 { emit_scene_leave_fn() } # a program without scenes has no @L_scene
|
if len(g_scenes) > 0 { emit_scene_leave_fn() } # a program without scenes has no @L_scene
|
||||||
|
|
@ -413,7 +422,7 @@ function emit_scene_leave_fn() -> void {
|
||||||
emit(" br i1 %is"); emit(k); emit(", label %leave"); emit(k); emit(", label %next"); emit(k); emit("\n")
|
emit(" br i1 %is"); emit(k); emit(", label %leave"); emit(k); emit(", label %next"); emit(k); emit("\n")
|
||||||
emit("leave"); emit(k); emit(":\n call void @scene_exit_"); emit(sc.s); emit("()\n ret void\n")
|
emit("leave"); emit(k); emit(":\n call void @scene_exit_"); emit(sc.s); emit("()\n ret void\n")
|
||||||
emit("next"); emit(k); emit(":\n")
|
emit("next"); emit(k); emit(":\n")
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret void\n}\n\n")
|
emit(" ret void\n}\n\n")
|
||||||
}
|
}
|
||||||
|
|
@ -447,7 +456,7 @@ function emit_despawn_hooks() -> void {
|
||||||
emit(buf_str(falloc))
|
emit(buf_str(falloc))
|
||||||
emit(buf_str(fbody))
|
emit(buf_str(fbody))
|
||||||
emit("}\n\n")
|
emit("}\n\n")
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit_despawn_all_fn()
|
emit_despawn_all_fn()
|
||||||
}
|
}
|
||||||
|
|
@ -475,7 +484,7 @@ function emit_world_despawn_fn() -> void {
|
||||||
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 %e, i32 0)\n") }
|
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 %e, i32 0)\n") }
|
||||||
emit(" br label %next"); emit(si); emit("\n")
|
emit(" br label %next"); emit(si); emit("\n")
|
||||||
emit("next"); emit(si); emit(":\n")
|
emit("next"); emit(si); emit(":\n")
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(g_events) > 0 { emit(" call void @ludic_sweep_entity(i32 %e)\n") } # EV5: drop entity-scoped listeners
|
if len(g_events) > 0 { emit(" call void @ludic_sweep_entity(i32 %e)\n") } # EV5: drop entity-scoped listeners
|
||||||
|
|
@ -512,7 +521,7 @@ function emit_despawn_all_fn() -> void {
|
||||||
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 %i, i32 %reason)\n") }
|
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 %i, i32 %reason)\n") }
|
||||||
emit(" br label %next"); emit(si); emit("\n")
|
emit(" br label %next"); emit(si); emit("\n")
|
||||||
emit("next"); emit(si); emit(":\n")
|
emit("next"); emit(si); emit(":\n")
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" br label %cont\n")
|
emit(" br label %cont\n")
|
||||||
emit("cont:\n %i1 = add i32 %i, 1\n br label %loop\n")
|
emit("cont:\n %i1 = add i32 %i, 1\n br label %loop\n")
|
||||||
|
|
@ -557,7 +566,7 @@ function emit_event_fns() -> void {
|
||||||
while t < len(ev.kids) {
|
while t < len(ev.kids) {
|
||||||
if t > 0 { emith(", ") }
|
if t > 0 { emith(", ") }
|
||||||
emith(llty(ev.kids[t].ty))
|
emith(llty(ev.kids[t].ty))
|
||||||
t = t + 1
|
t += 1
|
||||||
}
|
}
|
||||||
if ev.ival == 1 { if len(ev.kids) > 0 { emith(", ") }; emith("i32") }
|
if ev.ival == 1 { if len(ev.kids) > 0 { emith(", ") }; emith("i32") }
|
||||||
emith(" }\n")
|
emith(" }\n")
|
||||||
|
|
@ -629,7 +638,7 @@ function emit_event_fns() -> void {
|
||||||
let slot = emit_alloca(lt)
|
let slot = emit_alloca(lt)
|
||||||
emit(" store "); emit(lt); emit(" %p"); emit(itoa(f)); emit(", ptr "); emit(slot); emit("\n")
|
emit(" store "); emit(lt); emit(" %p"); emit(itoa(f)); emit(", ptr "); emit(slot); emit("\n")
|
||||||
loc_push(fd.s, slot, fd.ty)
|
loc_push(fd.s, slot, fd.ty)
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
# cancellable: zero the flag and expose its address to `cancel` in the listeners
|
# cancellable: zero the flag and expose its address to `cancel` in the listeners
|
||||||
var caddr = null
|
var caddr = null
|
||||||
|
|
@ -642,7 +651,7 @@ function emit_event_fns() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_onlisten) {
|
while i < len(g_onlisten) {
|
||||||
if (g_onlisten[i].s == en) { nloc = base; g_term = false; emit_block(g_onlisten[i].a) }
|
if (g_onlisten[i].s == en) { nloc = base; g_term = false; emit_block(g_onlisten[i].a) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# the open half: walk the foreign callback array in registration order
|
# the open half: walk the foreign callback array in registration order
|
||||||
if not g_term {
|
if not g_term {
|
||||||
|
|
@ -684,13 +693,13 @@ function emit_event_fns() -> void {
|
||||||
while g < len(ev.kids) {
|
while g < len(ev.kids) {
|
||||||
if g > 0 { emit(", ") }
|
if g > 0 { emit(", ") }
|
||||||
emit(llty(ev.kids[g].ty)); emit(" %p"); emit(itoa(g))
|
emit(llty(ev.kids[g].ty)); emit(" %p"); emit(itoa(g))
|
||||||
g = g + 1
|
g += 1
|
||||||
}
|
}
|
||||||
emit(") {\nentry:\n")
|
emit(") {\nentry:\n")
|
||||||
emit(buf_str(falloc))
|
emit(buf_str(falloc))
|
||||||
emit(buf_str(fbody))
|
emit(buf_str(fbody))
|
||||||
emit("}\n\n")
|
emit("}\n\n")
|
||||||
e = e + 1
|
e += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# EV5: @ludic_sweep_entity(owner) — remove every entity-scoped listener owned by
|
# EV5: @ludic_sweep_entity(owner) — remove every entity-scoped listener owned by
|
||||||
|
|
@ -719,8 +728,7 @@ function emit_event_fns() -> void {
|
||||||
emit("swd"); emit(sk); emit(":\n store i32 0, ptr %ci\n")
|
emit("swd"); emit(sk); emit(":\n store i32 0, ptr %ci\n")
|
||||||
if (e2 + 1) < len(g_events) { emit(" br label %sw"); emit(itoa(e2 + 1)); emit("\n") }
|
if (e2 + 1) < len(g_events) { emit(" br label %sw"); emit(itoa(e2 + 1)); emit("\n") }
|
||||||
else { emit(" ret void\n") }
|
else { emit(" ret void\n") }
|
||||||
e2 = e2 + 1
|
e2 += 1
|
||||||
}
|
}
|
||||||
emit("}\n\n")
|
emit("}\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ function emit_machine(st: Node) -> void {
|
||||||
s = emit_bind(`call i32 @fn_rt_reg(i32 {regv.code})`)
|
s = emit_bind(`call i32 @fn_rt_reg(i32 {regv.code})`)
|
||||||
}
|
}
|
||||||
if nmach < len(mach_stk) { mach_stk[nmach] = st } else { push(mach_stk, st) }
|
if nmach < len(mach_stk) { mach_stk[nmach] = st } else { push(mach_stk, st) }
|
||||||
nmach = nmach + 1
|
nmach += 1
|
||||||
let endl = lbl("smend")
|
let endl = lbl("smend")
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(st.kids) {
|
while i < len(st.kids) {
|
||||||
|
|
@ -50,11 +50,11 @@ function emit_machine(st: Node) -> void {
|
||||||
emit_block(state.a)
|
emit_block(state.a)
|
||||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||||
emit(nxt); emit(":\n"); g_term = false
|
emit(nxt); emit(":\n"); g_term = false
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||||
emit(endl); emit(":\n"); g_term = false
|
emit(endl); emit(":\n"); g_term = false
|
||||||
nmach = nmach - 1
|
nmach -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# `become Name` — a machine state transition when Name is a state of an
|
# `become Name` — a machine state transition when Name is a state of an
|
||||||
|
|
@ -66,7 +66,7 @@ function emit_become(st: Node) -> void {
|
||||||
let m = mach_stk[nmach - 1]
|
let m = mach_stk[nmach - 1]
|
||||||
var target: Node = null
|
var target: Node = null
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i = i + 1 }
|
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i += 1 }
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
let gv = machine_var(m.a)
|
let gv = machine_var(m.a)
|
||||||
let sv = machine_state_value(m, target)
|
let sv = machine_state_value(m, target)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
function find_arch_id(name: pointer) -> int {
|
function find_arch_id(name: pointer) -> int {
|
||||||
var i = 0; var n = 1
|
var i = 0; var n = 1
|
||||||
while i < len(prog) { if prog[i].kind == N_ARCH { if (prog[i].s == name) { return n }; n = n + 1 }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_ARCH { if (prog[i].s == name) { return n }; n += 1 }; i += 1 }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ function emit_query(st: Node) -> void {
|
||||||
let ip = emit_alloca("i32")
|
let ip = emit_alloca("i32")
|
||||||
store_at("i32", "0", ip)
|
store_at("i32", "0", ip)
|
||||||
if nself < len(self_stk) { self_stk[nself] = ip } else { push(self_stk, ip) }
|
if nself < len(self_stk) { self_stk[nself] = ip } else { push(self_stk, ip) }
|
||||||
nself = nself + 1
|
nself += 1
|
||||||
|
|
||||||
let cond = lbl("qcond"); let body = lbl("qbody"); let nxt = lbl("qnext"); let endl = lbl("qend")
|
let cond = lbl("qcond"); let body = lbl("qbody"); let nxt = lbl("qnext"); let endl = lbl("qend")
|
||||||
emit(" br label %"); emit(cond); emit("\n")
|
emit(" br label %"); emit(cond); emit("\n")
|
||||||
|
|
@ -54,7 +54,7 @@ function emit_query(st: Node) -> void {
|
||||||
let keep = lbl("qt")
|
let keep = lbl("qt")
|
||||||
emit(" br i1 "); emit(ok); emit(", label %"); emit(keep); emit(", label %"); emit(nxt); emit("\n")
|
emit(" br i1 "); emit(ok); emit(", label %"); emit(keep); emit(", label %"); emit(nxt); emit("\n")
|
||||||
emit(keep); emit(":\n")
|
emit(keep); emit(":\n")
|
||||||
t = t + 1
|
t += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# bind the requested components to the loop variables, in order
|
# bind the requested components to the loop variables, in order
|
||||||
|
|
@ -70,10 +70,10 @@ function emit_query(st: Node) -> void {
|
||||||
let vslot = emit_alloca("ptr")
|
let vslot = emit_alloca("ptr")
|
||||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||||
loc_push(st.kids[vi].s, vslot, tm.s)
|
loc_push(st.kids[vi].s, vslot, tm.s)
|
||||||
vi = vi + 1
|
vi += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t = t + 1
|
t += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# optional where-clause
|
# optional where-clause
|
||||||
|
|
@ -97,5 +97,5 @@ function emit_query(st: Node) -> void {
|
||||||
store_at("i32", i3, ip)
|
store_at("i32", i3, ip)
|
||||||
emit(" br label %"); emit(cond); emit("\n")
|
emit(" br label %"); emit(cond); emit("\n")
|
||||||
emit(endl); emit(":\n"); g_term = false
|
emit(endl); emit(":\n"); g_term = false
|
||||||
nself = nself - 1
|
nself -= 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ var g_off: pointer = null # current byte-offset register, buffer mode
|
||||||
|
|
||||||
function emit_io(fn2: pointer, p: pointer, bytes: pointer) -> void {
|
function emit_io(fn2: pointer, p: pointer, bytes: pointer) -> void {
|
||||||
if (g_snap_mode == "file") {
|
if (g_snap_mode == "file") {
|
||||||
let r = `%io{itoa(g_iok)}`; g_iok = g_iok + 1
|
let r = `%io{itoa(g_iok)}`; g_iok += 1
|
||||||
emit(" "); emit(r); emit(" = call i64 @"); emit(fn2); emit("(ptr "); emit(p); emit(", i64 1, i64 "); emit(bytes); emit(", ptr %f)\n")
|
emit(" "); emit(r); emit(" = call i64 @"); emit(fn2); emit("(ptr "); emit(p); emit(", i64 1, i64 "); emit(bytes); emit(", ptr %f)\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ function emit_io(fn2: pointer, p: pointer, bytes: pointer) -> void {
|
||||||
let noff = `%ioff{itoa(g_iok)}`
|
let noff = `%ioff{itoa(g_iok)}`
|
||||||
emit(" "); emit(noff); emit(" = add i64 "); emit(g_off); emit(", "); emit(bytes); emit("\n")
|
emit(" "); emit(noff); emit(" = add i64 "); emit(g_off); emit(", "); emit(bytes); emit("\n")
|
||||||
g_off = noff
|
g_off = noff
|
||||||
g_iok = g_iok + 1
|
g_iok += 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
# buffer mode: dst/src is %buf + g_off, copy `bytes`, then advance the cursor
|
# buffer mode: dst/src is %buf + g_off, copy `bytes`, then advance the cursor
|
||||||
|
|
@ -40,7 +40,7 @@ function emit_io(fn2: pointer, p: pointer, bytes: pointer) -> void {
|
||||||
let noff = `%ioff{itoa(g_iok)}`
|
let noff = `%ioff{itoa(g_iok)}`
|
||||||
emit(" "); emit(noff); emit(" = add i64 "); emit(g_off); emit(", "); emit(bytes); emit("\n")
|
emit(" "); emit(noff); emit(" = add i64 "); emit(g_off); emit(", "); emit(bytes); emit("\n")
|
||||||
g_off = noff
|
g_off = noff
|
||||||
g_iok = g_iok + 1
|
g_iok += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_snapshot_blocks(fn2: pointer) -> void {
|
function emit_snapshot_blocks(fn2: pointer) -> void {
|
||||||
|
|
@ -57,7 +57,7 @@ function emit_snapshot_blocks(fn2: pointer) -> void {
|
||||||
# round-trips ownership (like @L_kind). Gated, so non-@Owned snapshots are unchanged.
|
# round-trips ownership (like @L_kind). Gated, so non-@Owned snapshots are unchanged.
|
||||||
if net_has_owned() { emit_io(fn2, "@L_owner_arr", "%nalive") }
|
if net_has_owned() { emit_io(fn2, "@L_owner_arr", "%nalive") }
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, `@g_{prog[i].s}`, "4") }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, `@g_{prog[i].s}`, "4") }; i += 1 }
|
||||||
var ci = 0
|
var ci = 0
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
|
|
@ -67,9 +67,9 @@ function emit_snapshot_blocks(fn2: pointer) -> void {
|
||||||
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
|
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
|
||||||
emit_io(fn2, `@S_{c}`, csz)
|
emit_io(fn2, `@S_{c}`, csz)
|
||||||
emit_io(fn2, `@H_{c}`, me)
|
emit_io(fn2, `@H_{c}`, me)
|
||||||
ci = ci + 1
|
ci += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ function emit_hook_with_self(e: pointer, body: Node) -> void {
|
||||||
let sslot = emit_alloca("i32")
|
let sslot = emit_alloca("i32")
|
||||||
emit(" store i32 "); emit(e); emit(", ptr "); emit(sslot); emit("\n")
|
emit(" store i32 "); emit(e); emit(", ptr "); emit(sslot); emit("\n")
|
||||||
if nself < len(self_stk) { self_stk[nself] = sslot } else { push(self_stk, sslot) }
|
if nself < len(self_stk) { self_stk[nself] = sslot } else { push(self_stk, sslot) }
|
||||||
nself = nself + 1
|
nself += 1
|
||||||
emit_block(body)
|
emit_block(body)
|
||||||
nself = nself - 1
|
nself -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
||||||
|
|
@ -30,7 +30,7 @@ function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
||||||
if (lt == "ptr") { v = "null" }
|
if (lt == "ptr") { v = "null" }
|
||||||
if (fd.a != null) { let dv = emit_expr(fd.a); v = dv.code }
|
if (fd.a != null) { let dv = emit_expr(fd.a); v = dv.code }
|
||||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
# per-spawn overrides
|
# per-spawn overrides
|
||||||
if (rec != null) {
|
if (rec != null) {
|
||||||
|
|
@ -44,7 +44,7 @@ function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
|
||||||
let dv = emit_expr(fi.a)
|
let dv = emit_expr(fi.a)
|
||||||
emit(" store "); emit(llty(field_type(c, fi.s))); emit(" "); emit(dv.code); emit(", ptr "); emit(addr); emit("\n")
|
emit(" store "); emit(llty(field_type(c, fi.s))); emit(" "); emit(dv.code); emit(", ptr "); emit(addr); emit("\n")
|
||||||
}
|
}
|
||||||
j = j + 1
|
j += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# @OnAttach(Property) runs once the property is attached and seeded
|
# @OnAttach(Property) runs once the property is attached and seeded
|
||||||
|
|
@ -74,7 +74,7 @@ function emit_bind_props(model: Node, e: pointer) -> void {
|
||||||
let vslot = emit_alloca("ptr")
|
let vslot = emit_alloca("ptr")
|
||||||
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
|
||||||
loc_push(pname, vslot, pname)
|
loc_push(pname, vslot, pname)
|
||||||
c = c + 1
|
c += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ function emit_bind_props(model: Node, e: pointer) -> void {
|
||||||
function spawn_record(n: Node, cn: pointer) -> Node {
|
function spawn_record(n: Node, cn: pointer) -> Node {
|
||||||
if (n == null) { return null }
|
if (n == null) { return null }
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(n.kids) { if (n.kids[i].s == cn) { return n.kids[i].a }; i = i + 1 }
|
while i < len(n.kids) { if (n.kids[i].s == cn) { return n.kids[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
# a prefab's preset fields with the spawn's own overrides after them (later wins)
|
# a prefab's preset fields with the spawn's own overrides after them (later wins)
|
||||||
|
|
@ -91,9 +91,9 @@ function merge_records(base: Node, over: Node) -> Node {
|
||||||
if (over == null) { return base }
|
if (over == null) { return base }
|
||||||
let m = node(E_REC)
|
let m = node(E_REC)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(base.kids) { push(m.kids, base.kids[i]); i = i + 1 }
|
while i < len(base.kids) { push(m.kids, base.kids[i]); i += 1 }
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(over.kids) { push(m.kids, over.kids[i]); i = i + 1 }
|
while i < len(over.kids) { push(m.kids, over.kids[i]); i += 1 }
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ function spawn_model(name: pointer) -> pointer {
|
||||||
let p = find_prefab(n)
|
let p = find_prefab(n)
|
||||||
if (p == null) { return n }
|
if (p == null) { return n }
|
||||||
n = p.ty
|
n = p.ty
|
||||||
depth = depth + 1
|
depth += 1
|
||||||
if depth > 16 { perr(`prefab {name}: the chain of prefabs never reaches a model`) }
|
if depth > 16 { perr(`prefab {name}: the chain of prefabs never reaches a model`) }
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
|
@ -132,7 +132,7 @@ function emit_spawn(st: Node) -> pointer {
|
||||||
while c < len(arch.kids) {
|
while c < len(arch.kids) {
|
||||||
let cn = arch.kids[c].s
|
let cn = arch.kids[c].s
|
||||||
emit_init_component(e, cn, merge_records(prefab_record(st.s, cn), spawn_record(st, cn)))
|
emit_init_component(e, cn, merge_records(prefab_record(st.s, cn), spawn_record(st, cn)))
|
||||||
c = c + 1
|
c += 1
|
||||||
}
|
}
|
||||||
let ob = onspawn_body(model) # @OnSpawn(Model) hook runs after init
|
let ob = onspawn_body(model) # @OnSpawn(Model) hook runs after init
|
||||||
if (ob != null) {
|
if (ob != null) {
|
||||||
|
|
@ -147,7 +147,7 @@ function emit_spawn(st: Node) -> pointer {
|
||||||
if (find_event(sev) != null) { emit(" call void @ev_"); emit(sev); emit("(i32 "); emit(e); emit(")\n") }
|
if (find_event(sev) != null) { emit(" call void @ev_"); emit(sev); emit("(i32 "); emit(e); emit(")\n") }
|
||||||
} else {
|
} else {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a); i = i + 1 }
|
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a); i += 1 }
|
||||||
}
|
}
|
||||||
return e # the new entity id (for ludic_spawn_<M>)
|
return e # the new entity id (for ludic_spawn_<M>)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ function emit_despawn(st: Node) -> void {
|
||||||
let dev = `model_{mname}_despawn` # EV1: @Public despawn event
|
let dev = `model_{mname}_despawn` # EV1: @Public despawn event
|
||||||
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 "); emit(v.code); emit(", i32 0)\n") }
|
if (find_event(dev) != null) { emit(" call void @ev_"); emit(dev); emit("(i32 "); emit(v.code); emit(", i32 0)\n") }
|
||||||
emit(" br label %"); emit(no); emit("\n"); emit(no); emit(":\n")
|
emit(" br label %"); emit(no); emit("\n"); emit(no); emit(":\n")
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(g_events) > 0 { emit(" call void @ludic_sweep_entity(i32 "); emit(v.code); emit(")\n") } # EV5: drop entity-scoped listeners
|
if len(g_events) > 0 { emit(" call void @ludic_sweep_entity(i32 "); emit(v.code); emit(")\n") } # EV5: drop entity-scoped listeners
|
||||||
|
|
@ -229,7 +229,7 @@ function emit_detach(st: Node) -> void {
|
||||||
# flag). A bare `<Model>` / `<Handler>` flips a global enabled flag.
|
# flag). A bare `<Model>` / `<Handler>` flips a global enabled flag.
|
||||||
function find_sys(name: pointer) -> Node {
|
function find_sys(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { let d = prog[i]; if d.kind == N_SYS and (d.s == name) { return d }; i = i + 1 }
|
while i < len(prog) { let d = prog[i]; if d.kind == N_SYS and (d.s == name) { return d }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
function emit_toggle(st: Node) -> void {
|
function emit_toggle(st: Node) -> void {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ function ui_wtype(w: Node) -> int {
|
||||||
}
|
}
|
||||||
function ui_prop(w: Node, key: pointer) -> Node {
|
function ui_prop(w: Node, key: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(w.b.kids) { if (w.b.kids[i].s == key) { return w.b.kids[i].a }; i = i + 1 }
|
while i < len(w.b.kids) { if (w.b.kids[i].s == key) { return w.b.kids[i].a }; i += 1 }
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
# a widget's props plus the text props (font, size, fg, align) it inherits from the
|
# a widget's props plus the text props (font, size, fg, align) it inherits from the
|
||||||
|
|
@ -25,7 +25,7 @@ function ui_props_with_inherited(i: int) -> []Node {
|
||||||
let w = uiw[i]
|
let w = uiw[i]
|
||||||
let out = new []Node
|
let out = new []Node
|
||||||
var p = 0
|
var p = 0
|
||||||
while p < len(w.b.kids) { push(out, w.b.kids[p]); p = p + 1 }
|
while p < len(w.b.kids) { push(out, w.b.kids[p]); p += 1 }
|
||||||
let keys = new []pointer
|
let keys = new []pointer
|
||||||
push(keys, "font"); push(keys, "size"); push(keys, "fg"); push(keys, "align")
|
push(keys, "font"); push(keys, "size"); push(keys, "fg"); push(keys, "align")
|
||||||
var k = 0
|
var k = 0
|
||||||
|
|
@ -34,11 +34,11 @@ function ui_props_with_inherited(i: int) -> []Node {
|
||||||
var a = uiw_parent[i]
|
var a = uiw_parent[i]
|
||||||
while a >= 0 {
|
while a >= 0 {
|
||||||
let v = ui_prop(uiw[a], keys[k])
|
let v = ui_prop(uiw[a], keys[k])
|
||||||
if (v != null) { let fi = node(E_FINIT); fi.s = keys[k]; fi.a = v; push(out, fi); a = 0 - 1 }
|
if (v != null) { let fi = node(E_FINIT); fi.s = keys[k]; fi.a = v; push(out, fi); a = -1 }
|
||||||
else { a = uiw_parent[a] }
|
else { a = uiw_parent[a] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
@ -46,19 +46,19 @@ function ui_flatten(w: Node, parent: int) -> void {
|
||||||
let idx = len(uiw)
|
let idx = len(uiw)
|
||||||
push(uiw, w); push(uiw_parent, parent)
|
push(uiw, w); push(uiw_parent, parent)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(w.kids) { ui_flatten(w.kids[i], idx); i = i + 1 }
|
while i < len(w.kids) { ui_flatten(w.kids[i], idx); i += 1 }
|
||||||
}
|
}
|
||||||
function ui_flatten_all() -> void {
|
function ui_flatten_all() -> void {
|
||||||
uiw = new []Node; uiw_parent = new []int; ui_roots = new []int
|
uiw = new []Node; uiw_parent = new []int; ui_roots = new []int
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)); ui_flatten(prog[i].a, 0 - 1) }
|
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)); ui_flatten(prog[i].a, -1) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function has_ui() -> bool {
|
function has_ui() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,19 +69,19 @@ function ui_index_of(nm: pointer) -> int {
|
||||||
let u = 0; var bi = 0
|
let u = 0; var bi = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
if prog[i].kind == N_UI and prog[i].ival == 1 { if (prog[i].s == s) { return ui_roots[bi] }; bi = bi + 1 }
|
if prog[i].kind == N_UI and prog[i].ival == 1 { if (prog[i].s == s) { return ui_roots[bi] }; bi += 1 }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(uiw) {
|
while i < len(uiw) {
|
||||||
let idp = ui_prop(uiw[i], "id")
|
let idp = ui_prop(uiw[i], "id")
|
||||||
if (idp != null) { if idp.kind == E_ID and (idp.s == s) { return i } }
|
if (idp != null) { if idp.kind == E_ID and (idp.s == s) { return i } }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
function is_ui_ident(nm: pointer) -> bool {
|
function is_ui_ident(nm: pointer) -> bool {
|
||||||
return len(nm) > 3 and nm[0] == 85 and nm[1] == 73 and nm[2] == 95 # "UI_"
|
return len(nm) > 3 and nm[0] == 'U' and nm[1] == 'I' and nm[2] == '_' # "UI_"
|
||||||
}
|
}
|
||||||
|
|
||||||
function ll_ui_set(idx: int, key: int, val: pointer) -> void {
|
function ll_ui_set(idx: int, key: int, val: pointer) -> void {
|
||||||
|
|
@ -92,7 +92,7 @@ function ui_prop_key(k: pointer) -> int {
|
||||||
if (k == "pad") { return 7 }; if (k == "gap") { return 8 }; if (k == "bg") { return 9 }; if (k == "fg") { return 10 }
|
if (k == "pad") { return 7 }; if (k == "gap") { return 8 }; if (k == "bg") { return 9 }; if (k == "fg") { return 10 }
|
||||||
if (k == "border") { return 11 }; if (k == "grow") { return 13 }; if (k == "font") { return 14 }; if (k == "size") { return 15 }
|
if (k == "border") { return 11 }; if (k == "grow") { return 13 }; if (k == "font") { return 14 }; if (k == "size") { return 15 }
|
||||||
if (k == "inset") { return 16 }; if (k == "focus") { return 17 }
|
if (k == "inset") { return 16 }; if (k == "focus") { return 17 }
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_ui_build() -> void {
|
function emit_ui_build() -> void {
|
||||||
|
|
@ -116,7 +116,7 @@ function emit_ui_build() -> void {
|
||||||
let pr = props[p]
|
let pr = props[p]
|
||||||
let k = pr.s
|
let k = pr.s
|
||||||
let v = pr.a
|
let v = pr.a
|
||||||
if (k == "id") or (k == "goto") { p = p + 1; continue }
|
if (k == "id") or (k == "goto") { p += 1; continue }
|
||||||
if (k == "text") {
|
if (k == "text") {
|
||||||
let sv = emit_expr(v)
|
let sv = emit_expr(v)
|
||||||
emit(" call void @fn_rt_ui_static_text(i32 "); emit(itoa(i)); emit(", ptr "); emit(sv.code); emit(")\n")
|
emit(" call void @fn_rt_ui_static_text(i32 "); emit(itoa(i)); emit(", ptr "); emit(sv.code); emit(")\n")
|
||||||
|
|
@ -138,9 +138,9 @@ function emit_ui_build() -> void {
|
||||||
if key == 4 or key == 5 { ll_ui_set(i, 6, "1") }
|
if key == 4 or key == 5 { ll_ui_set(i, 6, "1") }
|
||||||
}
|
}
|
||||||
} } }
|
} } }
|
||||||
p = p + 1
|
p += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
code = saved
|
code = saved
|
||||||
emit("define void @ui_build() {\nentry:\n")
|
emit("define void @ui_build() {\nentry:\n")
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ function emit_world_table() -> void {
|
||||||
# and prop_id fall through to this table for a prop id >= NC.
|
# and prop_id fall through to this table for a prop id >= NC.
|
||||||
var ncomp = 0
|
var ncomp = 0
|
||||||
var ci0 = 0
|
var ci0 = 0
|
||||||
while ci0 < len(prog) { if prog[ci0].kind == N_COMP { ncomp = ncomp + 1 }; ci0 = ci0 + 1 }
|
while ci0 < len(prog) { if prog[ci0].kind == N_COMP { ncomp += 1 }; ci0 += 1 }
|
||||||
let NC = itoa(ncomp)
|
let NC = itoa(ncomp)
|
||||||
emith("@dyn_count = global i32 0\n")
|
emith("@dyn_count = global i32 0\n")
|
||||||
emith("@dynS = global [32 x ptr] zeroinitializer\n") # storage base per dyn component
|
emith("@dynS = global [32 x ptr] zeroinitializer\n") # storage base per dyn component
|
||||||
|
|
@ -43,9 +43,9 @@ function emit_world_table() -> void {
|
||||||
emit(" br i1 %e"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
emit(" br i1 %e"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||||
emit("h"); emit(sk); emit(":\n ret i32 "); emit(sk); emit("\n")
|
emit("h"); emit(sk); emit(":\n ret i32 "); emit(sk); emit("\n")
|
||||||
emit("n"); emit(sk); emit(":\n")
|
emit("n"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# EV7: not a compile-time component — search the dynamic (mod-registered) names
|
# EV7: not a compile-time component — search the dynamic (mod-registered) names
|
||||||
emit(" %dpi = alloca i32\n store i32 0, ptr %dpi\n br label %dpl\n")
|
emit(" %dpi = alloca i32\n store i32 0, ptr %dpi\n br label %dpl\n")
|
||||||
|
|
@ -72,13 +72,13 @@ function emit_world_table() -> void {
|
||||||
emit(" br i1 %fe"); emit(fk); emit(", label %fh"); emit(fk); emit(", label %fn"); emit(fk); emit("\n")
|
emit(" br i1 %fe"); emit(fk); emit(", label %fh"); emit(fk); emit(", label %fn"); emit(fk); emit("\n")
|
||||||
emit("fh"); emit(fk); emit(":\n ret i32 "); emit(itoa(f)); emit("\n")
|
emit("fh"); emit(fk); emit(":\n ret i32 "); emit(itoa(f)); emit("\n")
|
||||||
emit("fn"); emit(fk); emit(":\n")
|
emit("fn"); emit(fk); emit(":\n")
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 -1\n")
|
emit(" ret i32 -1\n")
|
||||||
emit("pn"); emit(sk); emit(":\n")
|
emit("pn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 -1\n}\n\n")
|
emit(" ret i32 -1\n}\n\n")
|
||||||
|
|
||||||
|
|
@ -115,13 +115,13 @@ function emit_world_table() -> void {
|
||||||
emit(" %gr"); emit(fk); emit(" = sext i32 %gl"); emit(fk); emit(" to i64\n ret i64 %gr"); emit(fk); emit("\n")
|
emit(" %gr"); emit(fk); emit(" = sext i32 %gl"); emit(fk); emit(" to i64\n ret i64 %gr"); emit(fk); emit("\n")
|
||||||
} }
|
} }
|
||||||
emit("gk"); emit(fk); emit(":\n")
|
emit("gk"); emit(fk); emit(":\n")
|
||||||
fj = fj + 1
|
fj += 1
|
||||||
}
|
}
|
||||||
emit(" ret i64 0\n")
|
emit(" ret i64 0\n")
|
||||||
emit("gn"); emit(sk); emit(":\n")
|
emit("gn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# EV7: prop id >= NC -> a mod-registered component; index its flat storage
|
# EV7: prop id >= NC -> a mod-registered component; index its flat storage
|
||||||
emit(" %gdyn = sub i32 %p, "); emit(NC); emit("\n")
|
emit(" %gdyn = sub i32 %p, "); emit(NC); emit("\n")
|
||||||
|
|
@ -158,13 +158,13 @@ function emit_world_table() -> void {
|
||||||
emit(" store i32 %sw"); emit(fk); emit(", ptr %sa"); emit(fk); emit("\n ret void\n")
|
emit(" store i32 %sw"); emit(fk); emit(", ptr %sa"); emit(fk); emit("\n ret void\n")
|
||||||
} }
|
} }
|
||||||
emit("sk"); emit(fk); emit(":\n")
|
emit("sk"); emit(fk); emit(":\n")
|
||||||
fj = fj + 1
|
fj += 1
|
||||||
}
|
}
|
||||||
emit(" ret void\n")
|
emit(" ret void\n")
|
||||||
emit("gn"); emit(sk); emit(":\n")
|
emit("gn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# EV7: prop id >= NC -> a mod-registered component
|
# EV7: prop id >= NC -> a mod-registered component
|
||||||
emit(" %sdyn = sub i32 %p, "); emit(NC); emit("\n")
|
emit(" %sdyn = sub i32 %p, "); emit(NC); emit("\n")
|
||||||
|
|
@ -186,9 +186,9 @@ function emit_world_table() -> void {
|
||||||
emit(" %hv"); emit(sk); emit(" = load i8, ptr %hp"); emit(sk); emit("\n")
|
emit(" %hv"); emit(sk); emit(" = load i8, ptr %hp"); emit(sk); emit("\n")
|
||||||
emit(" %hr"); emit(sk); emit(" = zext i8 %hv"); emit(sk); emit(" to i32\n ret i32 %hr"); emit(sk); emit("\n")
|
emit(" %hr"); emit(sk); emit(" = zext i8 %hv"); emit(sk); emit(" to i32\n ret i32 %hr"); emit(sk); emit("\n")
|
||||||
emit("gn"); emit(sk); emit(":\n")
|
emit("gn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
# EV7: prop id >= NC -> a mod-registered component's has-flag array
|
# EV7: prop id >= NC -> a mod-registered component's has-flag array
|
||||||
emit(" %hdyn = sub i32 %p, "); emit(NC); emit("\n")
|
emit(" %hdyn = sub i32 %p, "); emit(NC); emit("\n")
|
||||||
|
|
@ -234,9 +234,9 @@ function emit_world_table() -> void {
|
||||||
emit(" br i1 %mde"); emit(sk); emit(", label %mdh"); emit(sk); emit(", label %mdn"); emit(sk); emit("\n")
|
emit(" br i1 %mde"); emit(sk); emit(", label %mdh"); emit(sk); emit(", label %mdn"); emit(sk); emit("\n")
|
||||||
emit("mdh"); emit(sk); emit(":\n ret i32 "); emit(itoa(find_arch_id(prog[i].s))); emit("\n")
|
emit("mdh"); emit(sk); emit(":\n ret i32 "); emit(itoa(find_arch_id(prog[i].s))); emit("\n")
|
||||||
emit("mdn"); emit(sk); emit(":\n")
|
emit("mdn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 -1\n}\n\n")
|
emit(" ret i32 -1\n}\n\n")
|
||||||
|
|
||||||
|
|
@ -263,7 +263,7 @@ function emit_world_table() -> void {
|
||||||
emit(buf_str(fbody))
|
emit(buf_str(fbody))
|
||||||
emit("}\n\n")
|
emit("}\n\n")
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit("define i32 @ludic_spawn(i32 %m) {\nentry:\n")
|
emit("define i32 @ludic_spawn(i32 %m) {\nentry:\n")
|
||||||
k = 0; i = 0
|
k = 0; i = 0
|
||||||
|
|
@ -274,9 +274,9 @@ function emit_world_table() -> void {
|
||||||
emit(" br i1 %sm"); emit(sk); emit(", label %sh"); emit(sk); emit(", label %sn"); emit(sk); emit("\n")
|
emit(" br i1 %sm"); emit(sk); emit(", label %sh"); emit(sk); emit(", label %sn"); emit(sk); emit("\n")
|
||||||
emit("sh"); emit(sk); emit(":\n %sr"); emit(sk); emit(" = call i32 @ludic_spawn_"); emit(m); emit("()\n ret i32 %sr"); emit(sk); emit("\n")
|
emit("sh"); emit(sk); emit(":\n %sr"); emit(sk); emit(" = call i32 @ludic_spawn_"); emit(m); emit("()\n ret i32 %sr"); emit(sk); emit("\n")
|
||||||
emit("sn"); emit(sk); emit(":\n")
|
emit("sn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 -1\n}\n\n")
|
emit(" ret i32 -1\n}\n\n")
|
||||||
|
|
||||||
|
|
@ -309,7 +309,7 @@ function emit_world_reflect_enum() -> void {
|
||||||
# count the compile-time components (== the first dynamic prop id)
|
# count the compile-time components (== the first dynamic prop id)
|
||||||
var ncomp = 0
|
var ncomp = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_COMP { ncomp = ncomp + 1 }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_COMP { ncomp += 1 }; i += 1 }
|
||||||
let NC = itoa(ncomp)
|
let NC = itoa(ncomp)
|
||||||
|
|
||||||
# ludic_prop_count() -> total property count (compile-time + mod-registered)
|
# ludic_prop_count() -> total property count (compile-time + mod-registered)
|
||||||
|
|
@ -326,9 +326,9 @@ function emit_world_reflect_enum() -> void {
|
||||||
emit(" br i1 %pe"); emit(sk); emit(", label %ph"); emit(sk); emit(", label %pn"); emit(sk); emit("\n")
|
emit(" br i1 %pe"); emit(sk); emit(", label %ph"); emit(sk); emit(", label %pn"); emit(sk); emit("\n")
|
||||||
emit("ph"); emit(sk); emit(":\n ret ptr "); emit(sc); emit("\n")
|
emit("ph"); emit(sk); emit(":\n ret ptr "); emit(sc); emit("\n")
|
||||||
emit("pn"); emit(sk); emit(":\n")
|
emit("pn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" %dyn = sub i32 %i, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
|
emit(" %dyn = sub i32 %i, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
|
||||||
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %pbad, label %pdyn\n")
|
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %pbad, label %pdyn\n")
|
||||||
|
|
@ -345,9 +345,9 @@ function emit_world_reflect_enum() -> void {
|
||||||
emit(" br i1 %fce"); emit(sk); emit(", label %fch"); emit(sk); emit(", label %fcn"); emit(sk); emit("\n")
|
emit(" br i1 %fce"); emit(sk); emit(", label %fch"); emit(sk); emit(", label %fcn"); emit(sk); emit("\n")
|
||||||
emit("fch"); emit(sk); emit(":\n ret i32 "); emit(itoa(len(c.kids))); emit("\n")
|
emit("fch"); emit(sk); emit(":\n ret i32 "); emit(itoa(len(c.kids))); emit("\n")
|
||||||
emit("fcn"); emit(sk); emit(":\n")
|
emit("fcn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
|
emit(" %dyn = sub i32 %p, "); emit(NC); emit("\n %dc = load i32, ptr @dyn_count\n")
|
||||||
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %fcbad, label %fcdyn\n")
|
emit(" %ib = icmp uge i32 %dyn, %dc\n br i1 %ib, label %fcbad, label %fcdyn\n")
|
||||||
|
|
@ -370,13 +370,13 @@ function emit_world_reflect_enum() -> void {
|
||||||
emit(" br i1 %fnfe"); emit(fk); emit(", label %fnfh"); emit(fk); emit(", label %fnfn"); emit(fk); emit("\n")
|
emit(" br i1 %fnfe"); emit(fk); emit(", label %fnfh"); emit(fk); emit(", label %fnfn"); emit(fk); emit("\n")
|
||||||
emit("fnfh"); emit(fk); emit(":\n ret ptr "); emit(fc); emit("\n")
|
emit("fnfh"); emit(fk); emit(":\n ret ptr "); emit(fc); emit("\n")
|
||||||
emit("fnfn"); emit(fk); emit(":\n")
|
emit("fnfn"); emit(fk); emit(":\n")
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emit(" ret ptr "); emit(empty); emit("\n")
|
emit(" ret ptr "); emit(empty); emit("\n")
|
||||||
emit("fnn"); emit(sk); emit(":\n")
|
emit("fnn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret ptr "); emit(empty); emit("\n}\n\n")
|
emit(" ret ptr "); emit(empty); emit("\n}\n\n")
|
||||||
|
|
||||||
|
|
@ -396,13 +396,13 @@ function emit_world_reflect_enum() -> void {
|
||||||
emit(" br i1 %ftfe"); emit(fk); emit(", label %ftfh"); emit(fk); emit(", label %ftfn"); emit(fk); emit("\n")
|
emit(" br i1 %ftfe"); emit(fk); emit(", label %ftfh"); emit(fk); emit(", label %ftfn"); emit(fk); emit("\n")
|
||||||
emit("ftfh"); emit(fk); emit(":\n ret ptr "); emit(tc); emit("\n")
|
emit("ftfh"); emit(fk); emit(":\n ret ptr "); emit(tc); emit("\n")
|
||||||
emit("ftfn"); emit(fk); emit(":\n")
|
emit("ftfn"); emit(fk); emit(":\n")
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emit(" ret ptr "); emit(tint); emit("\n")
|
emit(" ret ptr "); emit(tint); emit("\n")
|
||||||
emit("ftn"); emit(sk); emit(":\n")
|
emit("ftn"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret ptr "); emit(tint); emit("\n}\n\n")
|
emit(" ret ptr "); emit(tint); emit("\n}\n\n")
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +431,7 @@ function emit_tick_helpers() -> void {
|
||||||
function emit_game_defs() -> void {
|
function emit_game_defs() -> void {
|
||||||
emit_system_registry() # #64: @ludic_register_system + the registry globals
|
emit_system_registry() # #64: @ludic_register_system + the registry globals
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i += 1 }
|
||||||
emit_despawn_hooks()
|
emit_despawn_hooks()
|
||||||
emit_scene_hooks()
|
emit_scene_hooks()
|
||||||
if has_countdowns() { emit_countdown_system() } # `countdown` fields (see emit_game.ludic)
|
if has_countdowns() { emit_countdown_system() } # `countdown` fields (see emit_game.ludic)
|
||||||
|
|
@ -454,7 +454,7 @@ function emit_game_main() -> void {
|
||||||
var si = 0
|
var si = 0
|
||||||
while si < len(g_scenes) {
|
while si < len(g_scenes) {
|
||||||
if (g_scenes[si].ival == g_start_scene) { emit(" call void @scene_enter_"); emit(g_scenes[si].s); emit("()\n") }
|
if (g_scenes[si].ival == g_start_scene) { emit(" call void @scene_enter_"); emit(g_scenes[si].s); emit("()\n") }
|
||||||
si = si + 1
|
si += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (find_event("program_start") != null) { emit(" call void @ev_program_start()\n") } # EV1: @Public @OnStart
|
if (find_event("program_start") != null) { emit(" call void @ev_program_start()\n") } # EV1: @Public @OnStart
|
||||||
|
|
@ -502,7 +502,8 @@ function emit_game_main() -> void {
|
||||||
# with no @ClearColor is byte-identical (it clears/shows itself, or the light
|
# with no @ClearColor is byte-identical (it clears/shows itself, or the light
|
||||||
# system owns the present).
|
# system owns the present).
|
||||||
if g_has_clear_color and (find_fn("rt_clear") != null) {
|
if g_has_clear_color and (find_fn("rt_clear") != null) {
|
||||||
emit(" call void @fn_rt_clear(i32 "); emit(itoa(g_clear_color)); emit(")\n")
|
let colour = emit_expr(g_clear_color) # a literal, a const, or a Color.Name
|
||||||
|
emit(" call void @fn_rt_clear(i32 "); emit(colour.code); emit(")\n")
|
||||||
}
|
}
|
||||||
emit_calls_for_phase("Render")
|
emit_calls_for_phase("Render")
|
||||||
# Overlay: HUD / menus drawn after every engine Render system (sprites, lights), so
|
# Overlay: HUD / menus drawn after every engine Render system (sprites, lights), so
|
||||||
|
|
|
||||||
|
|
@ -233,4 +233,3 @@ function color_lookup(name: pointer) -> int {
|
||||||
if (name == "Orchid2") { return 0xAF69EF }
|
if (name == "Orchid2") { return 0xAF69EF }
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,19 +45,19 @@ function emit_crypto_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_cryptort = true
|
g_uses_cryptort = true
|
||||||
if (meth == "sha256") { # SHA-256 -> 64-char hex string
|
if (meth == "sha256") { # SHA-256 -> 64-char hex string
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_sha256_hex(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_sha256_hex(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "hmac_sha256") { # HMAC-SHA256 -> 64-char hex string
|
if (meth == "hmac_sha256") { # HMAC-SHA256 -> 64-char hex string
|
||||||
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`), "string")
|
return val(emit_bind(`call ptr @lp_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "hex") { # lowercase hex of a string's bytes
|
if (meth == "hex") { # lowercase hex of a string's bytes
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_str_hex(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_hex(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "ct_equal") { # constant-time string equality -> bool
|
if (meth == "ct_equal") { # constant-time string equality -> bool
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call i32 @fn_ct_streq(ptr {a.code}, ptr {b.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_ct_streq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||||
}
|
}
|
||||||
# random_bytes(n) / random_hex(n): n bytes from the OS CSPRNG, returned as a
|
# random_bytes(n) / random_hex(n): n bytes from the OS CSPRNG, returned as a
|
||||||
# 2n-char lowercase hex string. A digest of raw bytes can contain NUL and a
|
# 2n-char lowercase hex string. A digest of raw bytes can contain NUL and a
|
||||||
|
|
@ -65,21 +65,21 @@ function emit_crypto_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "random_bytes") or (meth == "random_hex") {
|
if (meth == "random_bytes") or (meth == "random_hex") {
|
||||||
let n = emit_expr(e.kids[0])
|
let n = emit_expr(e.kids[0])
|
||||||
let n64 = emit_bind(`sext i32 {n.code} to i64`)
|
let n64 = emit_bind(`sext i32 {n.code} to i64`)
|
||||||
return val(emit_bind(`call ptr @fn_random_hex(i64 {n64})`), "string")
|
return val(emit_bind(`call ptr @lp_random_hex(i64 {n64})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "random_u32") { # one CSPRNG-drawn 32-bit int
|
if (meth == "random_u32") { # one CSPRNG-drawn 32-bit int
|
||||||
return val(emit_bind(`call i32 @fn_random_u32()`), "int")
|
return val(emit_bind(`call i32 @lp_random_u32()`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "base64") { # standard base64 (RFC 4648) of a string's bytes
|
if (meth == "base64") { # standard base64 (RFC 4648) of a string's bytes
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_base64(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_base64(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
# verify_hmac(key, msg, mac): recompute HMAC-SHA256(key, msg) and compare it to
|
# verify_hmac(key, msg, mac): recompute HMAC-SHA256(key, msg) and compare it to
|
||||||
# the supplied hex `mac` in constant time. This is the safe way to check a MAC —
|
# the supplied hex `mac` in constant time. This is the safe way to check a MAC —
|
||||||
# `==` would leak, byte by byte, how much of a forged MAC was correct.
|
# `==` would leak, byte by byte, how much of a forged MAC was correct.
|
||||||
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let mac = emit_expr(e.kids[2])
|
let k = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let mac = emit_expr(e.kids[2])
|
||||||
let computed = emit_bind(`call ptr @fn_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`)
|
let computed = emit_bind(`call ptr @lp_hmac_sha256_hex(ptr {k.code}, ptr {m.code})`)
|
||||||
return val(emit_bind(`call i32 @fn_ct_streq(ptr {computed}, ptr {mac.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_ct_streq(ptr {computed}, ptr {mac.code})`), "bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_crypto_prelude — the SHA-256 / HMAC-SHA256 runtime, emitted once per program
|
# emit_crypto_prelude — the SHA-256 / HMAC-SHA256 runtime, emitted once per program
|
||||||
|
|
@ -91,13 +91,13 @@ function emit_crypto_prelude() -> void {
|
||||||
emith("@sha256_K = private unnamed_addr constant [64 x i32] [i32 1116352408, i32 1899447441, i32 -1245643825, i32 -373957723, i32 961987163, i32 1508970993, i32 -1841331548, i32 -1424204075, i32 -670586216, i32 310598401, i32 607225278, i32 1426881987, i32 1925078388, i32 -2132889090, i32 -1680079193, i32 -1046744716, i32 -459576895, i32 -272742522, i32 264347078, i32 604807628, i32 770255983, i32 1249150122, i32 1555081692, i32 1996064986, i32 -1740746414, i32 -1473132947, i32 -1341970488, i32 -1084653625, i32 -958395405, i32 -710438585, i32 113926993, i32 338241895, i32 666307205, i32 773529912, i32 1294757372, i32 1396182291, i32 1695183700, i32 1986661051, i32 -2117940946, i32 -1838011259, i32 -1564481375, i32 -1474664885, i32 -1035236496, i32 -949202525, i32 -778901479, i32 -694614492, i32 -200395387, i32 275423344, i32 430227734, i32 506948616, i32 659060556, i32 883997877, i32 958139571, i32 1322822218, i32 1537002063, i32 1747873779, i32 1955562222, i32 2024104815, i32 -2067236844, i32 -1933114872, i32 -1866530822, i32 -1538233109, i32 -1090935817, i32 -965641998]\n")
|
emith("@sha256_K = private unnamed_addr constant [64 x i32] [i32 1116352408, i32 1899447441, i32 -1245643825, i32 -373957723, i32 961987163, i32 1508970993, i32 -1841331548, i32 -1424204075, i32 -670586216, i32 310598401, i32 607225278, i32 1426881987, i32 1925078388, i32 -2132889090, i32 -1680079193, i32 -1046744716, i32 -459576895, i32 -272742522, i32 264347078, i32 604807628, i32 770255983, i32 1249150122, i32 1555081692, i32 1996064986, i32 -1740746414, i32 -1473132947, i32 -1341970488, i32 -1084653625, i32 -958395405, i32 -710438585, i32 113926993, i32 338241895, i32 666307205, i32 773529912, i32 1294757372, i32 1396182291, i32 1695183700, i32 1986661051, i32 -2117940946, i32 -1838011259, i32 -1564481375, i32 -1474664885, i32 -1035236496, i32 -949202525, i32 -778901479, i32 -694614492, i32 -200395387, i32 275423344, i32 430227734, i32 506948616, i32 659060556, i32 883997877, i32 958139571, i32 1322822218, i32 1537002063, i32 1747873779, i32 1955562222, i32 2024104815, i32 -2067236844, i32 -1933114872, i32 -1866530822, i32 -1538233109, i32 -1090935817, i32 -965641998]\n")
|
||||||
|
|
||||||
# rotate a 32-bit word right by %n (1..31)
|
# rotate a 32-bit word right by %n (1..31)
|
||||||
emith("define i32 @fn_rotr32(i32 %x, i32 %n) {\n")
|
emith("define i32 @lp_rotr32(i32 %x, i32 %n) {\n")
|
||||||
emith(" %r = lshr i32 %x, %n\n %m = sub i32 32, %n\n %l = shl i32 %x, %m\n %o = or i32 %r, %l\n ret i32 %o\n}\n")
|
emith(" %r = lshr i32 %x, %n\n %m = sub i32 32, %n\n %l = shl i32 %x, %m\n %o = or i32 %r, %l\n ret i32 %o\n}\n")
|
||||||
|
|
||||||
# SHA-256 of %len bytes at %msg -> the 32 raw digest bytes at %out. Pads into a
|
# SHA-256 of %len bytes at %msg -> the 32 raw digest bytes at %out. Pads into a
|
||||||
# fresh malloc'd buffer (append 0x80, zero-fill, 64-bit big-endian bit length),
|
# fresh malloc'd buffer (append 0x80, zero-fill, 64-bit big-endian bit length),
|
||||||
# then runs the standard 64-round compression over each 512-bit block.
|
# then runs the standard 64-round compression over each 512-bit block.
|
||||||
emith("define void @fn_sha256_buf(ptr %msg, i64 %len, ptr %out) {\n")
|
emith("define void @lp_sha256_buf(ptr %msg, i64 %len, ptr %out) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %H = alloca [8 x i32]\n %W = alloca [64 x i32]\n")
|
emith(" %H = alloca [8 x i32]\n %W = alloca [64 x i32]\n")
|
||||||
emith(" %a = alloca i32\n %b = alloca i32\n %c = alloca i32\n %d = alloca i32\n %e = alloca i32\n %f = alloca i32\n %g = alloca i32\n %h = alloca i32\n")
|
emith(" %a = alloca i32\n %b = alloca i32\n %c = alloca i32\n %d = alloca i32\n %e = alloca i32\n %f = alloca i32\n %g = alloca i32\n %h = alloca i32\n")
|
||||||
|
|
@ -149,10 +149,10 @@ function emit_crypto_prelude() -> void {
|
||||||
emith("w2c:\n %xi = load i64, ptr %ip\n %xilt = icmp slt i64 %xi, 64\n br i1 %xilt, label %w2b, label %compinit\n")
|
emith("w2c:\n %xi = load i64, ptr %ip\n %xilt = icmp slt i64 %xi, 64\n br i1 %xilt, label %w2b, label %compinit\n")
|
||||||
emith("w2b:\n")
|
emith("w2b:\n")
|
||||||
emith(" %im15 = sub i64 %xi, 15\n %pm15 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im15\n %w15 = load i32, ptr %pm15\n")
|
emith(" %im15 = sub i64 %xi, 15\n %pm15 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im15\n %w15 = load i32, ptr %pm15\n")
|
||||||
emith(" %r7 = call i32 @fn_rotr32(i32 %w15, i32 7)\n %r18 = call i32 @fn_rotr32(i32 %w15, i32 18)\n %sh3 = lshr i32 %w15, 3\n")
|
emith(" %r7 = call i32 @lp_rotr32(i32 %w15, i32 7)\n %r18 = call i32 @lp_rotr32(i32 %w15, i32 18)\n %sh3 = lshr i32 %w15, 3\n")
|
||||||
emith(" %x01 = xor i32 %r7, %r18\n %s0 = xor i32 %x01, %sh3\n")
|
emith(" %x01 = xor i32 %r7, %r18\n %s0 = xor i32 %x01, %sh3\n")
|
||||||
emith(" %im2 = sub i64 %xi, 2\n %pm2 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im2\n %w2v = load i32, ptr %pm2\n")
|
emith(" %im2 = sub i64 %xi, 2\n %pm2 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im2\n %w2v = load i32, ptr %pm2\n")
|
||||||
emith(" %r17 = call i32 @fn_rotr32(i32 %w2v, i32 17)\n %r19 = call i32 @fn_rotr32(i32 %w2v, i32 19)\n %sh10 = lshr i32 %w2v, 10\n")
|
emith(" %r17 = call i32 @lp_rotr32(i32 %w2v, i32 17)\n %r19 = call i32 @lp_rotr32(i32 %w2v, i32 19)\n %sh10 = lshr i32 %w2v, 10\n")
|
||||||
emith(" %x02 = xor i32 %r17, %r19\n %s1 = xor i32 %x02, %sh10\n")
|
emith(" %x02 = xor i32 %r17, %r19\n %s1 = xor i32 %x02, %sh10\n")
|
||||||
emith(" %im16 = sub i64 %xi, 16\n %pm16 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im16\n %w16 = load i32, ptr %pm16\n")
|
emith(" %im16 = sub i64 %xi, 16\n %pm16 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im16\n %w16 = load i32, ptr %pm16\n")
|
||||||
emith(" %im7 = sub i64 %xi, 7\n %pm7 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im7\n %w7 = load i32, ptr %pm7\n")
|
emith(" %im7 = sub i64 %xi, 7\n %pm7 = getelementptr [64 x i32], ptr %W, i64 0, i64 %im7\n %w7 = load i32, ptr %pm7\n")
|
||||||
|
|
@ -176,7 +176,7 @@ function emit_crypto_prelude() -> void {
|
||||||
emith(" %av = load i32, ptr %a\n %bv = load i32, ptr %b\n %cvv = load i32, ptr %c\n %dv = load i32, ptr %d\n")
|
emith(" %av = load i32, ptr %a\n %bv = load i32, ptr %b\n %cvv = load i32, ptr %c\n %dv = load i32, ptr %d\n")
|
||||||
emith(" %ev = load i32, ptr %e\n %fv = load i32, ptr %f\n %gv = load i32, ptr %g\n %hv = load i32, ptr %h\n")
|
emith(" %ev = load i32, ptr %e\n %fv = load i32, ptr %f\n %gv = load i32, ptr %g\n %hv = load i32, ptr %h\n")
|
||||||
# S1 = rotr(e,6) ^ rotr(e,11) ^ rotr(e,25); ch = (e & f) ^ (~e & g)
|
# S1 = rotr(e,6) ^ rotr(e,11) ^ rotr(e,25); ch = (e & f) ^ (~e & g)
|
||||||
emith(" %e6 = call i32 @fn_rotr32(i32 %ev, i32 6)\n %e11 = call i32 @fn_rotr32(i32 %ev, i32 11)\n %e25 = call i32 @fn_rotr32(i32 %ev, i32 25)\n")
|
emith(" %e6 = call i32 @lp_rotr32(i32 %ev, i32 6)\n %e11 = call i32 @lp_rotr32(i32 %ev, i32 11)\n %e25 = call i32 @lp_rotr32(i32 %ev, i32 25)\n")
|
||||||
emith(" %S1a = xor i32 %e6, %e11\n %S1 = xor i32 %S1a, %e25\n")
|
emith(" %S1a = xor i32 %e6, %e11\n %S1 = xor i32 %S1a, %e25\n")
|
||||||
emith(" %ef = and i32 %ev, %fv\n %ne = xor i32 %ev, -1\n %neg = and i32 %ne, %gv\n %ch = xor i32 %ef, %neg\n")
|
emith(" %ef = and i32 %ev, %fv\n %ne = xor i32 %ev, -1\n %neg = and i32 %ne, %gv\n %ch = xor i32 %ef, %neg\n")
|
||||||
emith(" %kp = getelementptr [64 x i32], ptr @sha256_K, i64 0, i64 %ri\n %kv = load i32, ptr %kp\n")
|
emith(" %kp = getelementptr [64 x i32], ptr @sha256_K, i64 0, i64 %ri\n %kv = load i32, ptr %kp\n")
|
||||||
|
|
@ -184,7 +184,7 @@ function emit_crypto_prelude() -> void {
|
||||||
# temp1 = h + S1 + ch + K[i] + W[i]
|
# temp1 = h + S1 + ch + K[i] + W[i]
|
||||||
emith(" %t1a = add i32 %hv, %S1\n %t1b = add i32 %t1a, %ch\n %t1c = add i32 %t1b, %kv\n %temp1 = add i32 %t1c, %wvr\n")
|
emith(" %t1a = add i32 %hv, %S1\n %t1b = add i32 %t1a, %ch\n %t1c = add i32 %t1b, %kv\n %temp1 = add i32 %t1c, %wvr\n")
|
||||||
# S0 = rotr(a,2) ^ rotr(a,13) ^ rotr(a,22); maj = (a&b) ^ (a&c) ^ (b&c)
|
# S0 = rotr(a,2) ^ rotr(a,13) ^ rotr(a,22); maj = (a&b) ^ (a&c) ^ (b&c)
|
||||||
emith(" %a2r = call i32 @fn_rotr32(i32 %av, i32 2)\n %a13 = call i32 @fn_rotr32(i32 %av, i32 13)\n %a22 = call i32 @fn_rotr32(i32 %av, i32 22)\n")
|
emith(" %a2r = call i32 @lp_rotr32(i32 %av, i32 2)\n %a13 = call i32 @lp_rotr32(i32 %av, i32 13)\n %a22 = call i32 @lp_rotr32(i32 %av, i32 22)\n")
|
||||||
emith(" %S0a = xor i32 %a2r, %a13\n %S0 = xor i32 %S0a, %a22\n")
|
emith(" %S0a = xor i32 %a2r, %a13\n %S0 = xor i32 %S0a, %a22\n")
|
||||||
emith(" %ab = and i32 %av, %bv\n %ac = and i32 %av, %cvv\n %bc = and i32 %bv, %cvv\n %mj1 = xor i32 %ab, %ac\n %maj = xor i32 %mj1, %bc\n")
|
emith(" %ab = and i32 %av, %bv\n %ac = and i32 %av, %cvv\n %bc = and i32 %bv, %cvv\n %mj1 = xor i32 %ab, %ac\n %maj = xor i32 %mj1, %bc\n")
|
||||||
emith(" %temp2 = add i32 %S0, %maj\n")
|
emith(" %temp2 = add i32 %S0, %maj\n")
|
||||||
|
|
@ -218,37 +218,37 @@ function emit_crypto_prelude() -> void {
|
||||||
emith("freeb:\n call void @free(ptr %buf)\n ret void\n}\n")
|
emith("freeb:\n call void @free(ptr %buf)\n ret void\n}\n")
|
||||||
|
|
||||||
# one hex digit (0..15) -> its lowercase ASCII byte
|
# one hex digit (0..15) -> its lowercase ASCII byte
|
||||||
emith("define i8 @fn_hex_digit(i32 %d) {\n")
|
emith("define i8 @lp_hex_digit(i32 %d) {\n")
|
||||||
emith(" %lt = icmp ult i32 %d, 10\n %base = select i1 %lt, i32 48, i32 87\n %v = add i32 %base, %d\n %c = trunc i32 %v to i8\n ret i8 %c\n}\n")
|
emith(" %lt = icmp ult i32 %d, 10\n %base = select i1 %lt, i32 48, i32 87\n %v = add i32 %base, %d\n %c = trunc i32 %v to i8\n ret i8 %c\n}\n")
|
||||||
|
|
||||||
# hex-encode %n bytes at %in -> a fresh null-terminated 2n-char string
|
# hex-encode %n bytes at %in -> a fresh null-terminated 2n-char string
|
||||||
emith("define ptr @fn_hex_encode(ptr %in, i64 %n) {\n")
|
emith("define ptr @lp_hex_encode(ptr %in, i64 %n) {\n")
|
||||||
emith("entry:\n %ip = alloca i64\n %olen = shl i64 %n, 1\n %olen1 = add i64 %olen, 1\n %s = call ptr @malloc(i64 %olen1)\n store i64 0, ptr %ip\n br label %c\n")
|
emith("entry:\n %ip = alloca i64\n %olen = shl i64 %n, 1\n %olen1 = add i64 %olen, 1\n %s = call ptr @malloc(i64 %olen1)\n store i64 0, ptr %ip\n br label %c\n")
|
||||||
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %n\n br i1 %lt, label %bdy, label %done\n")
|
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %n\n br i1 %lt, label %bdy, label %done\n")
|
||||||
emith("bdy:\n %pp = getelementptr i8, ptr %in, i64 %i\n %byte = load i8, ptr %pp\n %bz = zext i8 %byte to i32\n")
|
emith("bdy:\n %pp = getelementptr i8, ptr %in, i64 %i\n %byte = load i8, ptr %pp\n %bz = zext i8 %byte to i32\n")
|
||||||
emith(" %hi = lshr i32 %bz, 4\n %lo = and i32 %bz, 15\n %hc = call i8 @fn_hex_digit(i32 %hi)\n %lc = call i8 @fn_hex_digit(i32 %lo)\n")
|
emith(" %hi = lshr i32 %bz, 4\n %lo = and i32 %bz, 15\n %hc = call i8 @lp_hex_digit(i32 %hi)\n %lc = call i8 @lp_hex_digit(i32 %lo)\n")
|
||||||
emith(" %oi = shl i64 %i, 1\n %o0 = getelementptr i8, ptr %s, i64 %oi\n store i8 %hc, ptr %o0\n %oi1 = add i64 %oi, 1\n %o1 = getelementptr i8, ptr %s, i64 %oi1\n store i8 %lc, ptr %o1\n")
|
emith(" %oi = shl i64 %i, 1\n %o0 = getelementptr i8, ptr %s, i64 %oi\n store i8 %hc, ptr %o0\n %oi1 = add i64 %oi, 1\n %o1 = getelementptr i8, ptr %s, i64 %oi1\n store i8 %lc, ptr %o1\n")
|
||||||
emith(" %in1 = add i64 %i, 1\n store i64 %in1, ptr %ip\n br label %c\n")
|
emith(" %in1 = add i64 %i, 1\n store i64 %in1, ptr %ip\n br label %c\n")
|
||||||
emith("done:\n %tp = getelementptr i8, ptr %s, i64 %olen\n store i8 0, ptr %tp\n ret ptr %s\n}\n")
|
emith("done:\n %tp = getelementptr i8, ptr %s, i64 %olen\n store i8 0, ptr %tp\n ret ptr %s\n}\n")
|
||||||
|
|
||||||
# SHA-256 of a null-terminated string -> 64-char hex
|
# SHA-256 of a null-terminated string -> 64-char hex
|
||||||
emith("define ptr @fn_sha256_hex(ptr %s) {\n")
|
emith("define ptr @lp_sha256_hex(ptr %s) {\n")
|
||||||
emith("entry:\n %dig = alloca [32 x i8]\n %len = call i64 @strlen(ptr %s)\n %dp = getelementptr [32 x i8], ptr %dig, i64 0, i64 0\n")
|
emith("entry:\n %dig = alloca [32 x i8]\n %len = call i64 @strlen(ptr %s)\n %dp = getelementptr [32 x i8], ptr %dig, i64 0, i64 0\n")
|
||||||
emith(" call void @fn_sha256_buf(ptr %s, i64 %len, ptr %dp)\n %hex = call ptr @fn_hex_encode(ptr %dp, i64 32)\n ret ptr %hex\n}\n")
|
emith(" call void @lp_sha256_buf(ptr %s, i64 %len, ptr %dp)\n %hex = call ptr @lp_hex_encode(ptr %dp, i64 32)\n ret ptr %hex\n}\n")
|
||||||
|
|
||||||
# hex of a whole null-terminated string's bytes
|
# hex of a whole null-terminated string's bytes
|
||||||
emith("define ptr @fn_str_hex(ptr %s) {\n")
|
emith("define ptr @lp_str_hex(ptr %s) {\n")
|
||||||
emith(" %n = call i64 @strlen(ptr %s)\n %h = call ptr @fn_hex_encode(ptr %s, i64 %n)\n ret ptr %h\n}\n")
|
emith(" %n = call i64 @strlen(ptr %s)\n %h = call ptr @lp_hex_encode(ptr %s, i64 %n)\n ret ptr %h\n}\n")
|
||||||
|
|
||||||
# xor 64 bytes of %src with the byte %pad into %dst (the HMAC key padding step)
|
# xor 64 bytes of %src with the byte %pad into %dst (the HMAC key padding step)
|
||||||
emith("define void @fn_xor64(ptr %dst, ptr %src, i32 %pad) {\n")
|
emith("define void @lp_xor64(ptr %dst, ptr %src, i32 %pad) {\n")
|
||||||
emith("entry:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %c\n")
|
emith("entry:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %c\n")
|
||||||
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 64\n br i1 %lt, label %b, label %d\n")
|
emith("c:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 64\n br i1 %lt, label %b, label %d\n")
|
||||||
emith("b:\n %sp = getelementptr i8, ptr %src, i64 %i\n %sv = load i8, ptr %sp\n %sz = zext i8 %sv to i32\n %xr = xor i32 %sz, %pad\n %xb = trunc i32 %xr to i8\n %dp = getelementptr i8, ptr %dst, i64 %i\n store i8 %xb, ptr %dp\n %in = add i64 %i, 1\n store i64 %in, ptr %ip\n br label %c\n")
|
emith("b:\n %sp = getelementptr i8, ptr %src, i64 %i\n %sv = load i8, ptr %sp\n %sz = zext i8 %sv to i32\n %xr = xor i32 %sz, %pad\n %xb = trunc i32 %xr to i8\n %dp = getelementptr i8, ptr %dst, i64 %i\n store i8 %xb, ptr %dp\n %in = add i64 %i, 1\n store i64 %in, ptr %ip\n br label %c\n")
|
||||||
emith("d:\n ret void\n}\n")
|
emith("d:\n ret void\n}\n")
|
||||||
|
|
||||||
# HMAC-SHA256(key, msg) -> 64-char hex (RFC 2104, block size 64).
|
# HMAC-SHA256(key, msg) -> 64-char hex (RFC 2104, block size 64).
|
||||||
emith("define ptr @fn_hmac_sha256_hex(ptr %key, ptr %msg) {\n")
|
emith("define ptr @lp_hmac_sha256_hex(ptr %key, ptr %msg) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %k0 = alloca [64 x i8]\n %inner = alloca [32 x i8]\n %outbuf = alloca [96 x i8]\n %fin = alloca [32 x i8]\n")
|
emith(" %k0 = alloca [64 x i8]\n %inner = alloca [32 x i8]\n %outbuf = alloca [96 x i8]\n %fin = alloca [32 x i8]\n")
|
||||||
emith(" %klen = call i64 @strlen(ptr %key)\n %mlen = call i64 @strlen(ptr %msg)\n")
|
emith(" %klen = call i64 @strlen(ptr %key)\n %mlen = call i64 @strlen(ptr %msg)\n")
|
||||||
|
|
@ -256,24 +256,24 @@ function emit_crypto_prelude() -> void {
|
||||||
# K0: a key longer than the block is replaced by its own hash; otherwise it is
|
# K0: a key longer than the block is replaced by its own hash; otherwise it is
|
||||||
# right-zero-padded to 64 bytes.
|
# right-zero-padded to 64 bytes.
|
||||||
emith(" %big = icmp ugt i64 %klen, 64\n br i1 %big, label %hashk, label %copyk\n")
|
emith(" %big = icmp ugt i64 %klen, 64\n br i1 %big, label %hashk, label %copyk\n")
|
||||||
emith("hashk:\n call void @fn_sha256_buf(ptr %key, i64 %klen, ptr %k0p)\n br label %pads\n")
|
emith("hashk:\n call void @lp_sha256_buf(ptr %key, i64 %klen, ptr %k0p)\n br label %pads\n")
|
||||||
emith("copyk:\n call ptr @memcpy(ptr %k0p, ptr %key, i64 %klen)\n br label %pads\n")
|
emith("copyk:\n call ptr @memcpy(ptr %k0p, ptr %key, i64 %klen)\n br label %pads\n")
|
||||||
emith("pads:\n")
|
emith("pads:\n")
|
||||||
# inner = SHA-256( (K0 ^ ipad) || msg ), ipad = 0x36
|
# inner = SHA-256( (K0 ^ ipad) || msg ), ipad = 0x36
|
||||||
emith(" %inlen = add i64 64, %mlen\n %inbuf = call ptr @malloc(i64 %inlen)\n")
|
emith(" %inlen = add i64 64, %mlen\n %inbuf = call ptr @malloc(i64 %inlen)\n")
|
||||||
emith(" call void @fn_xor64(ptr %inbuf, ptr %k0p, i32 54)\n")
|
emith(" call void @lp_xor64(ptr %inbuf, ptr %k0p, i32 54)\n")
|
||||||
emith(" %inmsg = getelementptr i8, ptr %inbuf, i64 64\n call ptr @memcpy(ptr %inmsg, ptr %msg, i64 %mlen)\n")
|
emith(" %inmsg = getelementptr i8, ptr %inbuf, i64 64\n call ptr @memcpy(ptr %inmsg, ptr %msg, i64 %mlen)\n")
|
||||||
emith(" %innerp = getelementptr [32 x i8], ptr %inner, i64 0, i64 0\n call void @fn_sha256_buf(ptr %inbuf, i64 %inlen, ptr %innerp)\n call void @free(ptr %inbuf)\n")
|
emith(" %innerp = getelementptr [32 x i8], ptr %inner, i64 0, i64 0\n call void @lp_sha256_buf(ptr %inbuf, i64 %inlen, ptr %innerp)\n call void @free(ptr %inbuf)\n")
|
||||||
# digest = SHA-256( (K0 ^ opad) || inner ), opad = 0x5c
|
# digest = SHA-256( (K0 ^ opad) || inner ), opad = 0x5c
|
||||||
emith(" %outp = getelementptr [96 x i8], ptr %outbuf, i64 0, i64 0\n call void @fn_xor64(ptr %outp, ptr %k0p, i32 92)\n")
|
emith(" %outp = getelementptr [96 x i8], ptr %outbuf, i64 0, i64 0\n call void @lp_xor64(ptr %outp, ptr %k0p, i32 92)\n")
|
||||||
emith(" %outmsg = getelementptr i8, ptr %outbuf, i64 64\n call ptr @memcpy(ptr %outmsg, ptr %innerp, i64 32)\n")
|
emith(" %outmsg = getelementptr i8, ptr %outbuf, i64 64\n call ptr @memcpy(ptr %outmsg, ptr %innerp, i64 32)\n")
|
||||||
emith(" %finp = getelementptr [32 x i8], ptr %fin, i64 0, i64 0\n call void @fn_sha256_buf(ptr %outp, i64 96, ptr %finp)\n")
|
emith(" %finp = getelementptr [32 x i8], ptr %fin, i64 0, i64 0\n call void @lp_sha256_buf(ptr %outp, i64 96, ptr %finp)\n")
|
||||||
emith(" %hex = call ptr @fn_hex_encode(ptr %finp, i64 32)\n ret ptr %hex\n}\n")
|
emith(" %hex = call ptr @lp_hex_encode(ptr %finp, i64 32)\n ret ptr %hex\n}\n")
|
||||||
|
|
||||||
# constant-time equality of two null-terminated strings. Length is not secret,
|
# constant-time equality of two null-terminated strings. Length is not secret,
|
||||||
# so an unequal length returns early; equal-length inputs are compared with a
|
# so an unequal length returns early; equal-length inputs are compared with a
|
||||||
# data-independent XOR-accumulate that never short-circuits.
|
# data-independent XOR-accumulate that never short-circuits.
|
||||||
emith("define i32 @fn_ct_streq(ptr %a, ptr %b) {\n")
|
emith("define i32 @lp_ct_streq(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n %accp = alloca i32\n %ip = alloca i64\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eqlen = icmp eq i64 %la, %lb\n br i1 %eqlen, label %go, label %ne\n")
|
emith("entry:\n %accp = alloca i32\n %ip = alloca i64\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eqlen = icmp eq i64 %la, %lb\n br i1 %eqlen, label %go, label %ne\n")
|
||||||
emith("ne:\n ret i32 0\n")
|
emith("ne:\n ret i32 0\n")
|
||||||
emith("go:\n store i32 0, ptr %accp\n store i64 0, ptr %ip\n br label %c\n")
|
emith("go:\n store i32 0, ptr %accp\n store i64 0, ptr %ip\n br label %c\n")
|
||||||
|
|
@ -295,7 +295,7 @@ function emit_secure_rand_prelude() -> void {
|
||||||
|
|
||||||
# fill %n bytes at %out from the OS CSPRNG. If /dev/urandom cannot be opened the
|
# fill %n bytes at %out from the OS CSPRNG. If /dev/urandom cannot be opened the
|
||||||
# buffer is zeroed (documented degraded mode — e.g. wasm), never left uninit.
|
# buffer is zeroed (documented degraded mode — e.g. wasm), never left uninit.
|
||||||
emith("define void @fn_secure_bytes(ptr %out, i64 %n) {\n")
|
emith("define void @lp_secure_bytes(ptr %out, i64 %n) {\n")
|
||||||
emith("entry:\n call ptr @memset(ptr %out, i32 0, i64 %n)\n")
|
emith("entry:\n call ptr @memset(ptr %out, i32 0, i64 %n)\n")
|
||||||
emith(" %fp = call ptr @fopen(ptr @.ludic_urandom, ptr @.ludic_rbmode)\n")
|
emith(" %fp = call ptr @fopen(ptr @.ludic_urandom, ptr @.ludic_rbmode)\n")
|
||||||
emith(" %isnull = icmp eq ptr %fp, null\n br i1 %isnull, label %fail, label %ok\n")
|
emith(" %isnull = icmp eq ptr %fp, null\n br i1 %isnull, label %fail, label %ok\n")
|
||||||
|
|
@ -303,13 +303,13 @@ function emit_secure_rand_prelude() -> void {
|
||||||
emith("fail:\n ret void\n}\n")
|
emith("fail:\n ret void\n}\n")
|
||||||
|
|
||||||
# %n secure bytes -> a fresh 2n-char lowercase hex string
|
# %n secure bytes -> a fresh 2n-char lowercase hex string
|
||||||
emith("define ptr @fn_random_hex(i64 %n) {\n")
|
emith("define ptr @lp_random_hex(i64 %n) {\n")
|
||||||
emith("entry:\n %buf = call ptr @malloc(i64 %n)\n call void @fn_secure_bytes(ptr %buf, i64 %n)\n")
|
emith("entry:\n %buf = call ptr @malloc(i64 %n)\n call void @lp_secure_bytes(ptr %buf, i64 %n)\n")
|
||||||
emith(" %hex = call ptr @fn_hex_encode(ptr %buf, i64 %n)\n call void @free(ptr %buf)\n ret ptr %hex\n}\n")
|
emith(" %hex = call ptr @lp_hex_encode(ptr %buf, i64 %n)\n call void @free(ptr %buf)\n ret ptr %hex\n}\n")
|
||||||
|
|
||||||
# one CSPRNG-drawn i32 (little-endian assembly of four secure bytes)
|
# one CSPRNG-drawn i32 (little-endian assembly of four secure bytes)
|
||||||
emith("define i32 @fn_random_u32() {\n")
|
emith("define i32 @lp_random_u32() {\n")
|
||||||
emith("entry:\n %b = alloca [4 x i8]\n %bp = getelementptr [4 x i8], ptr %b, i64 0, i64 0\n call void @fn_secure_bytes(ptr %bp, i64 4)\n")
|
emith("entry:\n %b = alloca [4 x i8]\n %bp = getelementptr [4 x i8], ptr %b, i64 0, i64 0\n call void @lp_secure_bytes(ptr %bp, i64 4)\n")
|
||||||
emith(" %p0 = getelementptr i8, ptr %bp, i64 0\n %c0 = load i8, ptr %p0\n %z0 = zext i8 %c0 to i32\n")
|
emith(" %p0 = getelementptr i8, ptr %bp, i64 0\n %c0 = load i8, ptr %p0\n %z0 = zext i8 %c0 to i32\n")
|
||||||
emith(" %p1 = getelementptr i8, ptr %bp, i64 1\n %c1 = load i8, ptr %p1\n %z1 = zext i8 %c1 to i32\n %s1 = shl i32 %z1, 8\n")
|
emith(" %p1 = getelementptr i8, ptr %bp, i64 1\n %c1 = load i8, ptr %p1\n %z1 = zext i8 %c1 to i32\n %s1 = shl i32 %z1, 8\n")
|
||||||
emith(" %p2 = getelementptr i8, ptr %bp, i64 2\n %c2 = load i8, ptr %p2\n %z2 = zext i8 %c2 to i32\n %s2 = shl i32 %z2, 16\n")
|
emith(" %p2 = getelementptr i8, ptr %bp, i64 2\n %c2 = load i8, ptr %p2\n %z2 = zext i8 %c2 to i32\n %s2 = shl i32 %z2, 16\n")
|
||||||
|
|
@ -319,7 +319,7 @@ function emit_secure_rand_prelude() -> void {
|
||||||
# standard base64 (RFC 4648, '+' '/' alphabet, '=' padding). The input is copied
|
# standard base64 (RFC 4648, '+' '/' alphabet, '=' padding). The input is copied
|
||||||
# into a zero-padded buffer rounded up to a multiple of 3, so the 3-byte group
|
# into a zero-padded buffer rounded up to a multiple of 3, so the 3-byte group
|
||||||
# loop never reads past the string; trailing '=' are written per the remainder.
|
# loop never reads past the string; trailing '=' are written per the remainder.
|
||||||
emith("define ptr @fn_base64(ptr %s) {\n")
|
emith("define ptr @lp_base64(ptr %s) {\n")
|
||||||
emith("entry:\n %n = call i64 @strlen(ptr %s)\n")
|
emith("entry:\n %n = call i64 @strlen(ptr %s)\n")
|
||||||
emith(" %n2 = add i64 %n, 2\n %grp = udiv i64 %n2, 3\n %bufn = mul i64 %grp, 3\n")
|
emith(" %n2 = add i64 %n, 2\n %grp = udiv i64 %n2, 3\n %bufn = mul i64 %grp, 3\n")
|
||||||
emith(" %olen = mul i64 %grp, 4\n %olen1 = add i64 %olen, 1\n %out = call ptr @malloc(i64 %olen1)\n")
|
emith(" %olen = mul i64 %grp, 4\n %olen1 = add i64 %olen, 1\n %out = call ptr @malloc(i64 %olen1)\n")
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
# (UTC), matching Time.now().
|
# (UTC), matching Time.now().
|
||||||
# The two civil<->epoch conversions are Howard Hinnant's public-domain algorithms
|
# The two civil<->epoch conversions are Howard Hinnant's public-domain algorithms
|
||||||
# (chrono-compatible, proleptic Gregorian), emitted once per program as the
|
# (chrono-compatible, proleptic Gregorian), emitted once per program as the
|
||||||
# @fn_days_from_civil / @fn_civil_from_days prelude and gated by g_uses_datert.
|
# @lp_days_from_civil / @lp_civil_from_days prelude and gated by g_uses_datert.
|
||||||
# v1 is UTC-only with no leap seconds; instants are assumed non-negative (dates
|
# v1 is UTC-only with no leap seconds; instants are assumed non-negative (dates
|
||||||
# at or after 1970). Timezones, format/parse and a game-controlled simulated
|
# at or after 1970). Timezones, format/parse and a game-controlled simulated
|
||||||
# clock are tracked follow-ups.
|
# clock are tracked follow-ups.
|
||||||
|
|
@ -42,7 +42,7 @@ function date_component(ed: pointer, which: int) -> pointer {
|
||||||
let yp = emit_alloca("i32")
|
let yp = emit_alloca("i32")
|
||||||
let mp = emit_alloca("i32")
|
let mp = emit_alloca("i32")
|
||||||
let dp = emit_alloca("i32")
|
let dp = emit_alloca("i32")
|
||||||
emit(` call void @fn_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
|
emit(` call void @lp_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
|
||||||
var p = yp
|
var p = yp
|
||||||
if (which == 1) { p = mp }
|
if (which == 1) { p = mp }
|
||||||
if (which == 2) { p = dp }
|
if (which == 2) { p = dp }
|
||||||
|
|
@ -60,7 +60,7 @@ function emit_date_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "new") { # new(year, month, day) -> Date (epoch-day)
|
if (meth == "new") { # new(year, month, day) -> Date (epoch-day)
|
||||||
g_uses_datert = true
|
g_uses_datert = true
|
||||||
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call i32 @fn_days_from_civil(i32 {y.code}, i32 {m.code}, i32 {d.code})`), "int")
|
return val(emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {m.code}, i32 {d.code})`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "year") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 0), "int") }
|
if (meth == "year") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 0), "int") }
|
||||||
if (meth == "month") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 1), "int") }
|
if (meth == "month") { let ed = emit_expr(e.kids[0]); return val(date_component(ed.code, 1), "int") }
|
||||||
|
|
@ -84,13 +84,13 @@ function emit_date_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "days_in_month") { # length of (year, month) = next month's day 0
|
if (meth == "days_in_month") { # length of (year, month) = next month's day 0
|
||||||
g_uses_datert = true
|
g_uses_datert = true
|
||||||
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
let y = emit_expr(e.kids[0]); let m = emit_expr(e.kids[1])
|
||||||
let this = emit_bind(`call i32 @fn_days_from_civil(i32 {y.code}, i32 {m.code}, i32 1)`)
|
let this = emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {m.code}, i32 1)`)
|
||||||
let dec = emit_bind(`icmp eq i32 {m.code}, 12`) # December rolls over to next January
|
let dec = emit_bind(`icmp eq i32 {m.code}, 12`) # December rolls over to next January
|
||||||
let ny = emit_bind(`add i32 {y.code}, 1`)
|
let ny = emit_bind(`add i32 {y.code}, 1`)
|
||||||
let ny2 = emit_bind(`select i1 {dec}, i32 {ny}, i32 {y.code}`)
|
let ny2 = emit_bind(`select i1 {dec}, i32 {ny}, i32 {y.code}`)
|
||||||
let nm = emit_bind(`add i32 {m.code}, 1`)
|
let nm = emit_bind(`add i32 {m.code}, 1`)
|
||||||
let nm2 = emit_bind(`select i1 {dec}, i32 1, i32 {nm}`)
|
let nm2 = emit_bind(`select i1 {dec}, i32 1, i32 {nm}`)
|
||||||
let next = emit_bind(`call i32 @fn_days_from_civil(i32 {ny2}, i32 {nm2}, i32 1)`)
|
let next = emit_bind(`call i32 @lp_days_from_civil(i32 {ny2}, i32 {nm2}, i32 1)`)
|
||||||
return val(emit_bind(`sub i32 {next}, {this}`), "int")
|
return val(emit_bind(`sub i32 {next}, {this}`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "to_epoch") { # midnight UTC of the day, as a DateTime instant
|
if (meth == "to_epoch") { # midnight UTC of the day, as a DateTime instant
|
||||||
|
|
@ -132,21 +132,21 @@ function is_datetime_ns(meth: pointer) -> bool {
|
||||||
|
|
||||||
# concat two runtime string codes -> a fresh string code
|
# concat two runtime string codes -> a fresh string code
|
||||||
function dt_concat(a: pointer, b: pointer) -> pointer {
|
function dt_concat(a: pointer, b: pointer) -> pointer {
|
||||||
return emit_bind(`call ptr @fn_str_concat(ptr {a}, ptr {b})`)
|
return emit_bind(`call ptr @lp_str_concat(ptr {a}, ptr {b})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
# does the pattern have token `tok` (length tlen) starting at index i?
|
# does the pattern have token `tok` (length tlen) starting at index i?
|
||||||
function dt_tok_at(pat: pointer, n: int, i: int, tok: pointer, tlen: int) -> bool {
|
function dt_tok_at(pat: pointer, n: int, i: int, tok: pointer, tlen: int) -> bool {
|
||||||
if (i + tlen) > n { return false }
|
if (i + tlen) > n { return false }
|
||||||
var k = 0
|
var k = 0
|
||||||
while k < tlen { if pat[i + k] != tok[k] { return false }; k = k + 1 }
|
while k < tlen { if pat[i + k] != tok[k] { return false }; k += 1 }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
# DateTime.format(t, "pattern") -> string. The pattern MUST be a string literal;
|
# DateTime.format(t, "pattern") -> string. The pattern MUST be a string literal;
|
||||||
# the tokens YYYY / YY / MM / DD / HH / mm / ss expand to zero-padded fields and
|
# the tokens YYYY / YY / MM / DD / HH / mm / ss expand to zero-padded fields and
|
||||||
# every other character is copied through verbatim. Expanded at compile time into
|
# every other character is copied through verbatim. Expanded at compile time into
|
||||||
# a fold of @fn_str_concat over literal runs and @fn_dt_pad0 field conversions.
|
# a fold of @lp_str_concat over literal runs and @lp_dt_pad0 field conversions.
|
||||||
function emit_datetime_format(e: Node) -> Val {
|
function emit_datetime_format(e: Node) -> Val {
|
||||||
g_uses_datert = true
|
g_uses_datert = true
|
||||||
g_uses_str = true
|
g_uses_str = true
|
||||||
|
|
@ -157,7 +157,7 @@ function emit_datetime_format(e: Node) -> Val {
|
||||||
# compute all six components once
|
# compute all six components once
|
||||||
let ed = dt_epochday(t.code)
|
let ed = dt_epochday(t.code)
|
||||||
let yp = emit_alloca("i32"); let mp = emit_alloca("i32"); let dp = emit_alloca("i32")
|
let yp = emit_alloca("i32"); let mp = emit_alloca("i32"); let dp = emit_alloca("i32")
|
||||||
emit(` call void @fn_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
|
emit(` call void @lp_civil_from_days(i32 {ed}, ptr {yp}, ptr {mp}, ptr {dp})\n`)
|
||||||
let yv = emit_bind(`load i32, ptr {yp}`)
|
let yv = emit_bind(`load i32, ptr {yp}`)
|
||||||
let mv = emit_bind(`load i32, ptr {mp}`)
|
let mv = emit_bind(`load i32, ptr {mp}`)
|
||||||
let dv = emit_bind(`load i32, ptr {dp}`)
|
let dv = emit_bind(`load i32, ptr {dp}`)
|
||||||
|
|
@ -179,14 +179,14 @@ function emit_datetime_format(e: Node) -> Val {
|
||||||
else { if dt_tok_at(pat, n, i, "HH", 2) { field = hh; width = 2; tlen = 2 }
|
else { if dt_tok_at(pat, n, i, "HH", 2) { field = hh; width = 2; tlen = 2 }
|
||||||
else { if dt_tok_at(pat, n, i, "mm", 2) { field = mi; width = 2; tlen = 2 }
|
else { if dt_tok_at(pat, n, i, "mm", 2) { field = mi; width = 2; tlen = 2 }
|
||||||
else { if dt_tok_at(pat, n, i, "ss", 2) { field = ss; width = 2; tlen = 2 } } } } } } }
|
else { if dt_tok_at(pat, n, i, "ss", 2) { field = ss; width = 2; tlen = 2 } } } } } } }
|
||||||
if (field == null) { buf_putc(lit, pat[i]); i = i + 1 }
|
if (field == null) { buf_putc(lit, pat[i]); i += 1 }
|
||||||
else {
|
else {
|
||||||
let ls = buf_str(lit)
|
let ls = buf_str(lit)
|
||||||
if len(ls) > 0 { acc = dt_concat(acc, emit_str_const(ls)) }
|
if len(ls) > 0 { acc = dt_concat(acc, emit_str_const(ls)) }
|
||||||
let piece = emit_bind(`call ptr @fn_dt_pad0(i32 {field}, i32 {width})`)
|
let piece = emit_bind(`call ptr @lp_dt_pad0(i32 {field}, i32 {width})`)
|
||||||
acc = dt_concat(acc, piece)
|
acc = dt_concat(acc, piece)
|
||||||
lit.len = 0 # start a fresh literal run
|
lit.len = 0 # start a fresh literal run
|
||||||
i = i + tlen
|
i += tlen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tail = buf_str(lit)
|
let tail = buf_str(lit)
|
||||||
|
|
@ -223,12 +223,12 @@ function emit_datetime_parse(e: Node) -> Val {
|
||||||
else { if dt_tok_at(pat, n, i, "HH", 2) { slot = hp; width = 2; tlen = 2 }
|
else { if dt_tok_at(pat, n, i, "HH", 2) { slot = hp; width = 2; tlen = 2 }
|
||||||
else { if dt_tok_at(pat, n, i, "mm", 2) { slot = np; width = 2; tlen = 2 }
|
else { if dt_tok_at(pat, n, i, "mm", 2) { slot = np; width = 2; tlen = 2 }
|
||||||
else { if dt_tok_at(pat, n, i, "ss", 2) { slot = sp; width = 2; tlen = 2 } } } } } }
|
else { if dt_tok_at(pat, n, i, "ss", 2) { slot = sp; width = 2; tlen = 2 } } } } } }
|
||||||
if (slot == null) { off = off + 1; i = i + 1 }
|
if (slot == null) { off += 1; i += 1 }
|
||||||
else {
|
else {
|
||||||
let rd = emit_bind(`call i32 @fn_dt_rd(ptr {s.code}, i32 {itoa(off)}, i32 {width}, ptr {failp})`)
|
let rd = emit_bind(`call i32 @lp_dt_rd(ptr {s.code}, i32 {itoa(off)}, i32 {width}, ptr {failp})`)
|
||||||
store_at("i32", rd, slot)
|
store_at("i32", rd, slot)
|
||||||
off = off + width
|
off += width
|
||||||
i = i + tlen
|
i += tlen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let y = emit_bind(`load i32, ptr {yp}`)
|
let y = emit_bind(`load i32, ptr {yp}`)
|
||||||
|
|
@ -237,7 +237,7 @@ function emit_datetime_parse(e: Node) -> Val {
|
||||||
let h = emit_bind(`load i32, ptr {hp}`)
|
let h = emit_bind(`load i32, ptr {hp}`)
|
||||||
let mn = emit_bind(`load i32, ptr {np}`)
|
let mn = emit_bind(`load i32, ptr {np}`)
|
||||||
let sc = emit_bind(`load i32, ptr {sp}`)
|
let sc = emit_bind(`load i32, ptr {sp}`)
|
||||||
let ed = emit_bind(`call i32 @fn_days_from_civil(i32 {y}, i32 {mo}, i32 {d})`)
|
let ed = emit_bind(`call i32 @lp_days_from_civil(i32 {y}, i32 {mo}, i32 {d})`)
|
||||||
let days = emit_bind(`mul i32 {ed}, 86400`)
|
let days = emit_bind(`mul i32 {ed}, 86400`)
|
||||||
let hs = emit_bind(`mul i32 {h}, 3600`)
|
let hs = emit_bind(`mul i32 {h}, 3600`)
|
||||||
let ns = emit_bind(`mul i32 {mn}, 60`)
|
let ns = emit_bind(`mul i32 {mn}, 60`)
|
||||||
|
|
@ -256,7 +256,7 @@ function emit_datetime_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_datert = true
|
g_uses_datert = true
|
||||||
let y = emit_expr(e.kids[0]); let mo = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
let y = emit_expr(e.kids[0]); let mo = emit_expr(e.kids[1]); let d = emit_expr(e.kids[2])
|
||||||
let h = emit_expr(e.kids[3]); let mi = emit_expr(e.kids[4]); let s = emit_expr(e.kids[5])
|
let h = emit_expr(e.kids[3]); let mi = emit_expr(e.kids[4]); let s = emit_expr(e.kids[5])
|
||||||
let ed = emit_bind(`call i32 @fn_days_from_civil(i32 {y.code}, i32 {mo.code}, i32 {d.code})`)
|
let ed = emit_bind(`call i32 @lp_days_from_civil(i32 {y.code}, i32 {mo.code}, i32 {d.code})`)
|
||||||
let days = emit_bind(`mul i32 {ed}, 86400`)
|
let days = emit_bind(`mul i32 {ed}, 86400`)
|
||||||
let hh = emit_bind(`mul i32 {h.code}, 3600`)
|
let hh = emit_bind(`mul i32 {h.code}, 3600`)
|
||||||
let mm = emit_bind(`mul i32 {mi.code}, 60`)
|
let mm = emit_bind(`mul i32 {mi.code}, 60`)
|
||||||
|
|
@ -306,8 +306,8 @@ function emit_datetime_ns(meth: pointer, e: Node) -> Val {
|
||||||
# are deterministic and bit-identical everywhere. 719468 is the day count from
|
# are deterministic and bit-identical everywhere. 719468 is the day count from
|
||||||
# 0000-03-01 to 1970-01-01; 146097 is the days in a 400-year era.
|
# 0000-03-01 to 1970-01-01; 146097 is the days in a 400-year era.
|
||||||
function emit_datetime_prelude() -> void {
|
function emit_datetime_prelude() -> void {
|
||||||
# @fn_days_from_civil(y, m, d) -> days since 1970-01-01
|
# @lp_days_from_civil(y, m, d) -> days since 1970-01-01
|
||||||
emith("define i32 @fn_days_from_civil(i32 %y0, i32 %m, i32 %d) {\n")
|
emith("define i32 @lp_days_from_civil(i32 %y0, i32 %m, i32 %d) {\n")
|
||||||
emith(" %mle2 = icmp sle i32 %m, 2\n")
|
emith(" %mle2 = icmp sle i32 %m, 2\n")
|
||||||
emith(" %ysub = select i1 %mle2, i32 1, i32 0\n")
|
emith(" %ysub = select i1 %mle2, i32 1, i32 0\n")
|
||||||
emith(" %y = sub i32 %y0, %ysub\n")
|
emith(" %y = sub i32 %y0, %ysub\n")
|
||||||
|
|
@ -336,9 +336,9 @@ function emit_datetime_prelude() -> void {
|
||||||
emith(" %r = sub i32 %r0, 719468\n")
|
emith(" %r = sub i32 %r0, 719468\n")
|
||||||
emith(" ret i32 %r\n")
|
emith(" ret i32 %r\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
# @fn_civil_from_days(z0, yp, mp, dp): write the civil (year, month, day) of
|
# @lp_civil_from_days(z0, yp, mp, dp): write the civil (year, month, day) of
|
||||||
# the epoch-day z0 through the three out-pointers.
|
# the epoch-day z0 through the three out-pointers.
|
||||||
emith("define void @fn_civil_from_days(i32 %z0, ptr %yp, ptr %mp, ptr %dp) {\n")
|
emith("define void @lp_civil_from_days(i32 %z0, ptr %yp, ptr %mp, ptr %dp) {\n")
|
||||||
emith(" %z = add i32 %z0, 719468\n")
|
emith(" %z = add i32 %z0, 719468\n")
|
||||||
emith(" %zneg = icmp slt i32 %z, 0\n")
|
emith(" %zneg = icmp slt i32 %z, 0\n")
|
||||||
emith(" %zm = sub i32 %z, 146096\n")
|
emith(" %zm = sub i32 %z, 146096\n")
|
||||||
|
|
@ -381,9 +381,9 @@ function emit_datetime_prelude() -> void {
|
||||||
emith(" store i32 %dd, ptr %dp\n")
|
emith(" store i32 %dd, ptr %dp\n")
|
||||||
emith(" ret void\n")
|
emith(" ret void\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
# @fn_dt_pad0(v, w): a fresh, malloc'd, w-digit zero-padded decimal of v
|
# @lp_dt_pad0(v, w): a fresh, malloc'd, w-digit zero-padded decimal of v
|
||||||
# (v assumed non-negative). Backs DateTime.format's numeric fields.
|
# (v assumed non-negative). Backs DateTime.format's numeric fields.
|
||||||
emith("define ptr @fn_dt_pad0(i32 %v, i32 %w) {\n")
|
emith("define ptr @lp_dt_pad0(i32 %v, i32 %w) {\n")
|
||||||
emith(" %we = zext i32 %w to i64\n")
|
emith(" %we = zext i32 %w to i64\n")
|
||||||
emith(" %sz = add i64 %we, 1\n")
|
emith(" %sz = add i64 %we, 1\n")
|
||||||
emith(" %buf = call ptr @malloc(i64 %sz)\n")
|
emith(" %buf = call ptr @malloc(i64 %sz)\n")
|
||||||
|
|
@ -397,9 +397,9 @@ function emit_datetime_prelude() -> void {
|
||||||
emith(" %cp = getelementptr i8, ptr %buf, i32 %k\n store i8 %ch8, ptr %cp\n")
|
emith(" %cp = getelementptr i8, ptr %buf, i32 %k\n store i8 %ch8, ptr %cp\n")
|
||||||
emith(" %vn = sdiv i32 %vv, 10\n store i32 %vn, ptr %vp\n %kn = sub i32 %k, 1\n store i32 %kn, ptr %kp\n br label %loop\n")
|
emith(" %vn = sdiv i32 %vv, 10\n store i32 %vn, ptr %vp\n %kn = sub i32 %k, 1\n store i32 %kn, ptr %kp\n br label %loop\n")
|
||||||
emith("done:\n ret ptr %buf\n}\n")
|
emith("done:\n ret ptr %buf\n}\n")
|
||||||
# @fn_dt_rd(s, off, w, failp): read w decimal digits of s starting at off into an
|
# @lp_dt_rd(s, off, w, failp): read w decimal digits of s starting at off into an
|
||||||
# int; on any non-digit set *failp = 1. Backs DateTime.parse's fixed-width fields.
|
# int; on any non-digit set *failp = 1. Backs DateTime.parse's fixed-width fields.
|
||||||
emith("define i32 @fn_dt_rd(ptr %s, i32 %off, i32 %w, ptr %failp) {\n")
|
emith("define i32 @lp_dt_rd(ptr %s, i32 %off, i32 %w, ptr %failp) {\n")
|
||||||
emith(" %accp = alloca i32\n store i32 0, ptr %accp\n %kp = alloca i32\n store i32 0, ptr %kp\n")
|
emith(" %accp = alloca i32\n store i32 0, ptr %accp\n %kp = alloca i32\n store i32 0, ptr %kp\n")
|
||||||
emith(" br label %loop\n")
|
emith(" br label %loop\n")
|
||||||
emith("loop:\n %k = load i32, ptr %kp\n %kok = icmp slt i32 %k, %w\n br i1 %kok, label %body, label %done\n")
|
emith("loop:\n %k = load i32, ptr %kp\n %kok = icmp slt i32 %k, %w\n br i1 %kok, label %body, label %done\n")
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,10 @@ function ease_eval(mode: int, t: pointer) -> pointer {
|
||||||
g_uses_mathrt = true # 2^(-10t) * sin((10t - 0.75) * 2pi/3) + 1
|
g_uses_mathrt = true # 2^(-10t) * sin((10t - 0.75) * 2pi/3) + 1
|
||||||
let tt = emit_bind(`mul i32 {t}, 10`) # 10t
|
let tt = emit_bind(`mul i32 {t}, 10`) # 10t
|
||||||
let ntt = emit_bind(`sub i32 0, {tt}`) # -10t (exp2 exponent, Q16.16)
|
let ntt = emit_bind(`sub i32 0, {tt}`) # -10t (exp2 exponent, Q16.16)
|
||||||
let decay = emit_bind(`call i32 @fn_fx_exp2(i32 {ntt})`)
|
let decay = emit_bind(`call i32 @lp_fx_exp2(i32 {ntt})`)
|
||||||
let ph = emit_bind(`sub i32 {tt}, 49152`) # 10t - 0.75
|
let ph = emit_bind(`sub i32 {tt}, 49152`) # 10t - 0.75
|
||||||
let ang = fx_mul_code(ph, "137258") # * (2pi/3), 2pi/3 = 137258 fixed
|
let ang = fx_mul_code(ph, "137258") # * (2pi/3), 2pi/3 = 137258 fixed
|
||||||
let s = emit_bind(`call i32 @fn_fx_sin(i32 {ang})`)
|
let s = emit_bind(`call i32 @lp_fx_sin(i32 {ang})`)
|
||||||
let osc = fx_mul_code(decay, s)
|
let osc = fx_mul_code(decay, s)
|
||||||
return emit_bind(`add i32 {osc}, 65536`)
|
return emit_bind(`add i32 {osc}, 65536`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,38 +54,38 @@ function is_mime_ns(meth: pointer) -> bool {
|
||||||
function emit_fs_ns(meth: pointer, e: Node) -> Val {
|
function emit_fs_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_fsrt = true
|
g_uses_fsrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
if (meth == "exists") { return val(emit_bind(`call i32 @fn_fs_exists(ptr {a.code})`), "bool") }
|
if (meth == "exists") { return val(emit_bind(`call i32 @lp_fs_exists(ptr {a.code})`), "bool") }
|
||||||
if (meth == "is_dir") { return val(emit_bind(`call i32 @fn_fs_is_dir(ptr {a.code})`), "bool") }
|
if (meth == "is_dir") { return val(emit_bind(`call i32 @lp_fs_is_dir(ptr {a.code})`), "bool") }
|
||||||
if (meth == "read_text") { return val(emit_bind(`call ptr @fn_fs_read_text(ptr {a.code})`), "string") }
|
if (meth == "read_text") { return val(emit_bind(`call ptr @lp_fs_read_text(ptr {a.code})`), "string") }
|
||||||
if (meth == "remove") { return val(emit_bind(`call i32 @fn_fs_remove(ptr {a.code})`), "bool") }
|
if (meth == "remove") { return val(emit_bind(`call i32 @lp_fs_remove(ptr {a.code})`), "bool") }
|
||||||
if (meth == "size") { return val(emit_bind(`call i32 @fn_fs_size(ptr {a.code})`), "int") }
|
if (meth == "size") { return val(emit_bind(`call i32 @lp_fs_size(ptr {a.code})`), "int") }
|
||||||
if (meth == "mkdir") { return val(emit_bind(`call i32 @fn_fs_mkdir(ptr {a.code})`), "bool") }
|
if (meth == "mkdir") { return val(emit_bind(`call i32 @lp_fs_mkdir(ptr {a.code})`), "bool") }
|
||||||
if (meth == "list") { return val(emit_bind(`call ptr @fn_fs_list(ptr {a.code})`), "[]string") }
|
if (meth == "list") { return val(emit_bind(`call ptr @lp_fs_list(ptr {a.code})`), "[]string") }
|
||||||
let b = emit_expr(e.kids[1])
|
let b = emit_expr(e.kids[1])
|
||||||
if (meth == "write_text") { return val(emit_bind(`call i32 @fn_fs_write_text(ptr {a.code}, ptr {b.code})`), "bool") }
|
if (meth == "write_text") { return val(emit_bind(`call i32 @lp_fs_write_text(ptr {a.code}, ptr {b.code})`), "bool") }
|
||||||
if (meth == "append_text") { return val(emit_bind(`call i32 @fn_fs_append_text(ptr {a.code}, ptr {b.code})`), "bool") }
|
if (meth == "append_text") { return val(emit_bind(`call i32 @lp_fs_append_text(ptr {a.code}, ptr {b.code})`), "bool") }
|
||||||
# copy
|
# copy
|
||||||
return val(emit_bind(`call i32 @fn_fs_copy(ptr {a.code}, ptr {b.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_fs_copy(ptr {a.code}, ptr {b.code})`), "bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_path_ns(meth: pointer, e: Node) -> Val {
|
function emit_path_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_fsrt = true
|
g_uses_fsrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
if (meth == "dir") { return val(emit_bind(`call ptr @fn_path_dir(ptr {a.code})`), "string") }
|
if (meth == "dir") { return val(emit_bind(`call ptr @lp_path_dir(ptr {a.code})`), "string") }
|
||||||
if (meth == "base") { return val(emit_bind(`call ptr @fn_path_base(ptr {a.code})`), "string") }
|
if (meth == "base") { return val(emit_bind(`call ptr @lp_path_base(ptr {a.code})`), "string") }
|
||||||
if (meth == "ext") { return val(emit_bind(`call ptr @fn_path_ext(ptr {a.code})`), "string") }
|
if (meth == "ext") { return val(emit_bind(`call ptr @lp_path_ext(ptr {a.code})`), "string") }
|
||||||
if (meth == "stem") { return val(emit_bind(`call ptr @fn_path_stem(ptr {a.code})`), "string") }
|
if (meth == "stem") { return val(emit_bind(`call ptr @lp_path_stem(ptr {a.code})`), "string") }
|
||||||
if (meth == "normalize") { return val(emit_bind(`call ptr @fn_path_norm(ptr {a.code})`), "string") }
|
if (meth == "normalize") { return val(emit_bind(`call ptr @lp_path_norm(ptr {a.code})`), "string") }
|
||||||
# join
|
# join
|
||||||
let b = emit_expr(e.kids[1])
|
let b = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_path_join(ptr {a.code}, ptr {b.code})`), "string")
|
return val(emit_bind(`call ptr @lp_path_join(ptr {a.code}, ptr {b.code})`), "string")
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_mime_ns(meth: pointer, e: Node) -> Val {
|
function emit_mime_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_fsrt = true
|
g_uses_fsrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
if (meth == "sniff") { return val(emit_bind(`call ptr @fn_mime_sniff(ptr {a.code})`), "string") }
|
if (meth == "sniff") { return val(emit_bind(`call ptr @lp_mime_sniff(ptr {a.code})`), "string") }
|
||||||
return val(emit_bind(`call ptr @fn_mime_of(ptr {a.code})`), "string")
|
return val(emit_bind(`call ptr @lp_mime_of(ptr {a.code})`), "string")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_fs_prelude — the Fs/Path/Mime runtime, emitted once per program that uses
|
# emit_fs_prelude — the Fs/Path/Mime runtime, emitted once per program that uses
|
||||||
|
|
@ -108,15 +108,15 @@ function emit_fs_prelude() -> void {
|
||||||
# ---- shared helpers --------------------------------------------------------
|
# ---- shared helpers --------------------------------------------------------
|
||||||
function emit_fs_common() -> void {
|
function emit_fs_common() -> void {
|
||||||
# duplicate %n bytes of %s into a fresh NUL-terminated buffer
|
# duplicate %n bytes of %s into a fresh NUL-terminated buffer
|
||||||
emith("define ptr @fn_fs_dup(ptr %s, i32 %n) {\n")
|
emith("define ptr @lp_fs_dup(ptr %s, i32 %n) {\n")
|
||||||
emith("entry:\n %nz = zext i32 %n to i64\n %t = add i64 %nz, 1\n %m = call ptr @malloc(i64 %t)\n call ptr @memcpy(ptr %m, ptr %s, i64 %nz)\n")
|
emith("entry:\n %nz = zext i32 %n to i64\n %t = add i64 %nz, 1\n %m = call ptr @malloc(i64 %t)\n call ptr @memcpy(ptr %m, ptr %s, i64 %nz)\n")
|
||||||
emith(" %end = getelementptr i8, ptr %m, i32 %n\n store i8 0, ptr %end\n ret ptr %m\n}\n")
|
emith(" %end = getelementptr i8, ptr %m, i32 %n\n store i8 0, ptr %end\n ret ptr %m\n}\n")
|
||||||
|
|
||||||
emith("define ptr @fn_fs_strdup(ptr %s) {\n")
|
emith("define ptr @lp_fs_strdup(ptr %s) {\n")
|
||||||
emith("entry:\n %l = call i64 @strlen(ptr %s)\n %li = trunc i64 %l to i32\n %r = call ptr @fn_fs_dup(ptr %s, i32 %li)\n ret ptr %r\n}\n")
|
emith("entry:\n %l = call i64 @strlen(ptr %s)\n %li = trunc i64 %l to i32\n %r = call ptr @lp_fs_dup(ptr %s, i32 %li)\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
# index of the last '/' in %s, or -1 if none
|
# index of the last '/' in %s, or -1 if none
|
||||||
emith("define i32 @fn_fs_lastslash(ptr %s) {\n")
|
emith("define i32 @lp_fs_lastslash(ptr %s) {\n")
|
||||||
emith("entry:\n %ip = alloca i32\n %rp = alloca i32\n store i32 0, ptr %ip\n store i32 -1, ptr %rp\n br label %lp\n")
|
emith("entry:\n %ip = alloca i32\n %rp = alloca i32\n store i32 0, ptr %ip\n store i32 -1, ptr %rp\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||||
|
|
@ -130,14 +130,14 @@ function emit_fs_common() -> void {
|
||||||
function emit_fs_path() -> void {
|
function emit_fs_path() -> void {
|
||||||
# join(a, b): b absolute -> b; a empty -> b; b empty -> a; else a + "/" + b
|
# join(a, b): b absolute -> b; a empty -> b; b empty -> a; else a + "/" + b
|
||||||
# (avoiding a doubled separator when a already ends with one).
|
# (avoiding a doubled separator when a already ends with one).
|
||||||
emith("define ptr @fn_path_join(ptr %a, ptr %b) {\n")
|
emith("define ptr @lp_path_join(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n %la64 = call i64 @strlen(ptr %a)\n %la = trunc i64 %la64 to i32\n %lb64 = call i64 @strlen(ptr %b)\n %lb = trunc i64 %lb64 to i32\n")
|
emith("entry:\n %la64 = call i64 @strlen(ptr %a)\n %la = trunc i64 %la64 to i32\n %lb64 = call i64 @strlen(ptr %b)\n %lb = trunc i64 %lb64 to i32\n")
|
||||||
emith(" %b0 = load i8, ptr %b\n %b0i = zext i8 %b0 to i32\n %babs = icmp eq i32 %b0i, 47\n br i1 %babs, label %retb, label %ka\n")
|
emith(" %b0 = load i8, ptr %b\n %b0i = zext i8 %b0 to i32\n %babs = icmp eq i32 %b0i, 47\n br i1 %babs, label %retb, label %ka\n")
|
||||||
emith("retb:\n %rb = call ptr @fn_fs_strdup(ptr %b)\n ret ptr %rb\n")
|
emith("retb:\n %rb = call ptr @lp_fs_strdup(ptr %b)\n ret ptr %rb\n")
|
||||||
emith("ka:\n %aemp = icmp eq i32 %la, 0\n br i1 %aemp, label %retb2, label %kb\n")
|
emith("ka:\n %aemp = icmp eq i32 %la, 0\n br i1 %aemp, label %retb2, label %kb\n")
|
||||||
emith("retb2:\n %rb2 = call ptr @fn_fs_strdup(ptr %b)\n ret ptr %rb2\n")
|
emith("retb2:\n %rb2 = call ptr @lp_fs_strdup(ptr %b)\n ret ptr %rb2\n")
|
||||||
emith("kb:\n %bemp = icmp eq i32 %lb, 0\n br i1 %bemp, label %reta, label %chk\n")
|
emith("kb:\n %bemp = icmp eq i32 %lb, 0\n br i1 %bemp, label %reta, label %chk\n")
|
||||||
emith("reta:\n %ra = call ptr @fn_fs_strdup(ptr %a)\n ret ptr %ra\n")
|
emith("reta:\n %ra = call ptr @lp_fs_strdup(ptr %a)\n ret ptr %ra\n")
|
||||||
emith("chk:\n %lam1 = sub i32 %la, 1\n %pe = getelementptr i8, ptr %a, i32 %lam1\n %ae = load i8, ptr %pe\n %aei = zext i8 %ae to i32\n %ends = icmp eq i32 %aei, 47\n")
|
emith("chk:\n %lam1 = sub i32 %la, 1\n %pe = getelementptr i8, ptr %a, i32 %lam1\n %ae = load i8, ptr %pe\n %aei = zext i8 %ae to i32\n %ends = icmp eq i32 %aei, 47\n")
|
||||||
emith(" %sep = select i1 %ends, i32 0, i32 1\n %tot = add i32 %la, %lb\n %tot2 = add i32 %tot, %sep\n %tot3 = add i32 %tot2, 1\n %totz = zext i32 %tot3 to i64\n %m = call ptr @malloc(i64 %totz)\n")
|
emith(" %sep = select i1 %ends, i32 0, i32 1\n %tot = add i32 %la, %lb\n %tot2 = add i32 %tot, %sep\n %tot3 = add i32 %tot2, 1\n %totz = zext i32 %tot3 to i64\n %m = call ptr @malloc(i64 %totz)\n")
|
||||||
emith(" %laz = zext i32 %la to i64\n call ptr @memcpy(ptr %m, ptr %a, i64 %laz)\n")
|
emith(" %laz = zext i32 %la to i64\n call ptr @memcpy(ptr %m, ptr %a, i64 %laz)\n")
|
||||||
|
|
@ -149,39 +149,39 @@ function emit_fs_path() -> void {
|
||||||
emith(" %endoff = add i32 %boff, %lb\n %ep = getelementptr i8, ptr %m, i32 %endoff\n store i8 0, ptr %ep\n ret ptr %m\n}\n")
|
emith(" %endoff = add i32 %boff, %lb\n %ep = getelementptr i8, ptr %m, i32 %endoff\n store i8 0, ptr %ep\n ret ptr %m\n}\n")
|
||||||
|
|
||||||
# base(p): the component after the last '/', or p itself
|
# base(p): the component after the last '/', or p itself
|
||||||
emith("define ptr @fn_path_base(ptr %s) {\n")
|
emith("define ptr @lp_path_base(ptr %s) {\n")
|
||||||
emith("entry:\n %ls = call i32 @fn_fs_lastslash(ptr %s)\n %none = icmp eq i32 %ls, -1\n br i1 %none, label %whole, label %tail\n")
|
emith("entry:\n %ls = call i32 @lp_fs_lastslash(ptr %s)\n %none = icmp eq i32 %ls, -1\n br i1 %none, label %whole, label %tail\n")
|
||||||
emith("whole:\n %r = call ptr @fn_fs_strdup(ptr %s)\n ret ptr %r\n")
|
emith("whole:\n %r = call ptr @lp_fs_strdup(ptr %s)\n ret ptr %r\n")
|
||||||
emith("tail:\n %st = add i32 %ls, 1\n %p = getelementptr i8, ptr %s, i32 %st\n %r2 = call ptr @fn_fs_strdup(ptr %p)\n ret ptr %r2\n}\n")
|
emith("tail:\n %st = add i32 %ls, 1\n %p = getelementptr i8, ptr %s, i32 %st\n %r2 = call ptr @lp_fs_strdup(ptr %p)\n ret ptr %r2\n}\n")
|
||||||
|
|
||||||
# dir(p): everything before the last '/', or "." if none; "/" stays "/"
|
# dir(p): everything before the last '/', or "." if none; "/" stays "/"
|
||||||
emith("define ptr @fn_path_dir(ptr %s) {\n")
|
emith("define ptr @lp_path_dir(ptr %s) {\n")
|
||||||
emith("entry:\n %ls = call i32 @fn_fs_lastslash(ptr %s)\n %none = icmp eq i32 %ls, -1\n br i1 %none, label %dot, label %chk0\n")
|
emith("entry:\n %ls = call i32 @lp_fs_lastslash(ptr %s)\n %none = icmp eq i32 %ls, -1\n br i1 %none, label %dot, label %chk0\n")
|
||||||
emith("dot:\n %d = call ptr @fn_fs_strdup(ptr @fn_str_dot)\n ret ptr %d\n")
|
emith("dot:\n %d = call ptr @lp_fs_strdup(ptr @fn_str_dot)\n ret ptr %d\n")
|
||||||
emith("chk0:\n %isroot = icmp eq i32 %ls, 0\n br i1 %isroot, label %root, label %cut\n")
|
emith("chk0:\n %isroot = icmp eq i32 %ls, 0\n br i1 %isroot, label %root, label %cut\n")
|
||||||
emith("root:\n %r = call ptr @fn_fs_strdup(ptr @fn_str_slash)\n ret ptr %r\n")
|
emith("root:\n %r = call ptr @lp_fs_strdup(ptr @fn_str_slash)\n ret ptr %r\n")
|
||||||
emith("cut:\n %r2 = call ptr @fn_fs_dup(ptr %s, i32 %ls)\n ret ptr %r2\n}\n")
|
emith("cut:\n %r2 = call ptr @lp_fs_dup(ptr %s, i32 %ls)\n ret ptr %r2\n}\n")
|
||||||
|
|
||||||
# ext(p): from the last '.' in the base component to the end, incl. the dot;
|
# ext(p): from the last '.' in the base component to the end, incl. the dot;
|
||||||
# "" when the base has no '.' or begins with '.' (a dotfile has no extension)
|
# "" when the base has no '.' or begins with '.' (a dotfile has no extension)
|
||||||
emith("define ptr @fn_path_ext(ptr %s) {\n")
|
emith("define ptr @lp_path_ext(ptr %s) {\n")
|
||||||
emith("entry:\n %ls = call i32 @fn_fs_lastslash(ptr %s)\n %bstart = add i32 %ls, 1\n") # ls=-1 -> bstart=0
|
emith("entry:\n %ls = call i32 @lp_fs_lastslash(ptr %s)\n %bstart = add i32 %ls, 1\n") # ls=-1 -> bstart=0
|
||||||
emith(" %dp = alloca i32\n %ip = alloca i32\n store i32 -1, ptr %dp\n store i32 %bstart, ptr %ip\n br label %lp\n")
|
emith(" %dp = alloca i32\n %ip = alloca i32\n store i32 -1, ptr %dp\n store i32 %bstart, ptr %ip\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||||
emith("go:\n %dot = icmp eq i32 %c, 46\n br i1 %dot, label %sd, label %nx\n")
|
emith("go:\n %dot = icmp eq i32 %c, 46\n br i1 %dot, label %sd, label %nx\n")
|
||||||
emith("sd:\n store i32 %i, ptr %dp\n br label %nx\n")
|
emith("sd:\n store i32 %i, ptr %dp\n br label %nx\n")
|
||||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||||
emith("done:\n %d = load i32, ptr %dp\n %nod = icmp eq i32 %d, -1\n br i1 %nod, label %empty, label %chkpos\n")
|
emith("done:\n %d = load i32, ptr %dp\n %nod = icmp eq i32 %d, -1\n br i1 %nod, label %empty, label %chkpos\n")
|
||||||
emith("empty:\n %e = call ptr @fn_fs_strdup(ptr @fn_str_empty)\n ret ptr %e\n")
|
emith("empty:\n %e = call ptr @lp_fs_strdup(ptr @fn_str_empty)\n ret ptr %e\n")
|
||||||
emith("chkpos:\n %atstart = icmp eq i32 %d, %bstart\n br i1 %atstart, label %empty2, label %take\n")
|
emith("chkpos:\n %atstart = icmp eq i32 %d, %bstart\n br i1 %atstart, label %empty2, label %take\n")
|
||||||
emith("empty2:\n %e2 = call ptr @fn_fs_strdup(ptr @fn_str_empty)\n ret ptr %e2\n")
|
emith("empty2:\n %e2 = call ptr @lp_fs_strdup(ptr @fn_str_empty)\n ret ptr %e2\n")
|
||||||
emith("take:\n %pp = getelementptr i8, ptr %s, i32 %d\n %r = call ptr @fn_fs_strdup(ptr %pp)\n ret ptr %r\n}\n")
|
emith("take:\n %pp = getelementptr i8, ptr %s, i32 %d\n %r = call ptr @lp_fs_strdup(ptr %pp)\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
# stem(p): the base component without its extension
|
# stem(p): the base component without its extension
|
||||||
emith("define ptr @fn_path_stem(ptr %s) {\n")
|
emith("define ptr @lp_path_stem(ptr %s) {\n")
|
||||||
emith("entry:\n %base = call ptr @fn_path_base(ptr %s)\n %ext = call ptr @fn_path_ext(ptr %s)\n")
|
emith("entry:\n %base = call ptr @lp_path_base(ptr %s)\n %ext = call ptr @lp_path_ext(ptr %s)\n")
|
||||||
emith(" %bl64 = call i64 @strlen(ptr %base)\n %bl = trunc i64 %bl64 to i32\n %el64 = call i64 @strlen(ptr %ext)\n %el = trunc i64 %el64 to i32\n")
|
emith(" %bl64 = call i64 @strlen(ptr %base)\n %bl = trunc i64 %bl64 to i32\n %el64 = call i64 @strlen(ptr %ext)\n %el = trunc i64 %el64 to i32\n")
|
||||||
emith(" %keep = sub i32 %bl, %el\n %r = call ptr @fn_fs_dup(ptr %base, i32 %keep)\n ret ptr %r\n}\n")
|
emith(" %keep = sub i32 %bl, %el\n %r = call ptr @lp_fs_dup(ptr %base, i32 %keep)\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
emit_fs_normalize()
|
emit_fs_normalize()
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +190,7 @@ function emit_fs_path() -> void {
|
||||||
# popping the previous kept segment (never above an absolute root). Preserves a
|
# popping the previous kept segment (never above an absolute root). Preserves a
|
||||||
# leading '/'. An empty result becomes ".".
|
# leading '/'. An empty result becomes ".".
|
||||||
function emit_fs_normalize() -> void {
|
function emit_fs_normalize() -> void {
|
||||||
emith("define ptr @fn_path_norm(ptr %s) {\n")
|
emith("define ptr @lp_path_norm(ptr %s) {\n")
|
||||||
emith("entry:\n %len64 = call i64 @strlen(ptr %s)\n %len = trunc i64 %len64 to i32\n %cap = add i32 %len, 2\n %capz = zext i32 %cap to i64\n")
|
emith("entry:\n %len64 = call i64 @strlen(ptr %s)\n %len = trunc i64 %len64 to i32\n %cap = add i32 %len, 2\n %capz = zext i32 %cap to i64\n")
|
||||||
emith(" %out = call ptr @malloc(i64 %capz)\n")
|
emith(" %out = call ptr @malloc(i64 %capz)\n")
|
||||||
# segst holds output offsets (i32) of each kept segment's start; size cap ints
|
# segst holds output offsets (i32) of each kept segment's start; size cap ints
|
||||||
|
|
@ -240,17 +240,17 @@ function emit_fs_normalize() -> void {
|
||||||
|
|
||||||
# ---- Fs.* (libc) -----------------------------------------------------------
|
# ---- Fs.* (libc) -----------------------------------------------------------
|
||||||
function emit_fs_io() -> void {
|
function emit_fs_io() -> void {
|
||||||
emith("define i32 @fn_fs_exists(ptr %p) {\n")
|
emith("define i32 @lp_fs_exists(ptr %p) {\n")
|
||||||
emith("entry:\n %r = call i32 @access(ptr %p, i32 0)\n %ok = icmp eq i32 %r, 0\n %z = zext i1 %ok to i32\n ret i32 %z\n}\n")
|
emith("entry:\n %r = call i32 @access(ptr %p, i32 0)\n %ok = icmp eq i32 %r, 0\n %z = zext i1 %ok to i32\n ret i32 %z\n}\n")
|
||||||
|
|
||||||
# is_dir: opendir succeeds iff it is a directory
|
# is_dir: opendir succeeds iff it is a directory
|
||||||
emith("define i32 @fn_fs_is_dir(ptr %p) {\n")
|
emith("define i32 @lp_fs_is_dir(ptr %p) {\n")
|
||||||
emith("entry:\n %d = call ptr @opendir(ptr %p)\n %nz = icmp ne ptr %d, null\n br i1 %nz, label %yes, label %no\n")
|
emith("entry:\n %d = call ptr @opendir(ptr %p)\n %nz = icmp ne ptr %d, null\n br i1 %nz, label %yes, label %no\n")
|
||||||
emith("yes:\n call i32 @closedir(ptr %d)\n ret i32 1\n")
|
emith("yes:\n call i32 @closedir(ptr %d)\n ret i32 1\n")
|
||||||
emith("no:\n ret i32 0\n}\n")
|
emith("no:\n ret i32 0\n}\n")
|
||||||
|
|
||||||
# size: bytes via fseek/ftell, or -1 if it cannot be opened
|
# size: bytes via fseek/ftell, or -1 if it cannot be opened
|
||||||
emith("define i32 @fn_fs_size(ptr %p) {\n")
|
emith("define i32 @lp_fs_size(ptr %p) {\n")
|
||||||
emith("entry:\n %f = call ptr @fopen(ptr %p, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
emith("entry:\n %f = call ptr @fopen(ptr %p, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||||
emith("bad:\n ret i32 -1\n")
|
emith("bad:\n ret i32 -1\n")
|
||||||
emith("ok:\n call i32 @fseek(ptr %f, i64 0, i32 2)\n %n = call i64 @ftell(ptr %f)\n call i32 @fclose(ptr %f)\n %ni = trunc i64 %n to i32\n ret i32 %ni\n}\n")
|
emith("ok:\n call i32 @fseek(ptr %f, i64 0, i32 2)\n %n = call i64 @ftell(ptr %f)\n call i32 @fclose(ptr %f)\n %ni = trunc i64 %n to i32\n ret i32 %ni\n}\n")
|
||||||
|
|
@ -258,54 +258,54 @@ function emit_fs_io() -> void {
|
||||||
# readall: whole file into a fresh buffer; store byte length to %lenout; null on
|
# readall: whole file into a fresh buffer; store byte length to %lenout; null on
|
||||||
# failure. The buffer is NUL-terminated (one past the length) so text callers
|
# failure. The buffer is NUL-terminated (one past the length) so text callers
|
||||||
# can use it directly while binary callers use the length.
|
# can use it directly while binary callers use the length.
|
||||||
emith("define ptr @fn_fs_readall(ptr %p, ptr %lenout) {\n")
|
emith("define ptr @lp_fs_readall(ptr %p, ptr %lenout) {\n")
|
||||||
emith("entry:\n store i32 0, ptr %lenout\n %f = call ptr @fopen(ptr %p, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
emith("entry:\n store i32 0, ptr %lenout\n %f = call ptr @fopen(ptr %p, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||||
emith("bad:\n ret ptr null\n")
|
emith("bad:\n ret ptr null\n")
|
||||||
emith("ok:\n call i32 @fseek(ptr %f, i64 0, i32 2)\n %n64 = call i64 @ftell(ptr %f)\n call i32 @fseek(ptr %f, i64 0, i32 0)\n %n = trunc i64 %n64 to i32\n")
|
emith("ok:\n call i32 @fseek(ptr %f, i64 0, i32 2)\n %n64 = call i64 @ftell(ptr %f)\n call i32 @fseek(ptr %f, i64 0, i32 0)\n %n = trunc i64 %n64 to i32\n")
|
||||||
emith(" %cap = add i64 %n64, 1\n %m = call ptr @malloc(i64 %cap)\n %rd = call i64 @fread(ptr %m, i64 1, i64 %n64, ptr %f)\n call i32 @fclose(ptr %f)\n")
|
emith(" %cap = add i64 %n64, 1\n %m = call ptr @malloc(i64 %cap)\n %rd = call i64 @fread(ptr %m, i64 1, i64 %n64, ptr %f)\n call i32 @fclose(ptr %f)\n")
|
||||||
emith(" %rdi = trunc i64 %rd to i32\n %endp = getelementptr i8, ptr %m, i32 %rdi\n store i8 0, ptr %endp\n store i32 %rdi, ptr %lenout\n ret ptr %m\n}\n")
|
emith(" %rdi = trunc i64 %rd to i32\n %endp = getelementptr i8, ptr %m, i32 %rdi\n store i8 0, ptr %endp\n store i32 %rdi, ptr %lenout\n ret ptr %m\n}\n")
|
||||||
|
|
||||||
emith("define ptr @fn_fs_read_text(ptr %p) {\n")
|
emith("define ptr @lp_fs_read_text(ptr %p) {\n")
|
||||||
emith("entry:\n %lp = alloca i32\n %r = call ptr @fn_fs_readall(ptr %p, ptr %lp)\n ret ptr %r\n}\n")
|
emith("entry:\n %lp = alloca i32\n %r = call ptr @lp_fs_readall(ptr %p, ptr %lp)\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
# write_text: write to "<p>.tmp" then rename over %p, so a crash mid-write
|
# write_text: write to "<p>.tmp" then rename over %p, so a crash mid-write
|
||||||
# never truncates the previous file. Returns 1 on success.
|
# never truncates the previous file. Returns 1 on success.
|
||||||
emith("define i32 @fn_fs_write_text(ptr %p, ptr %s) {\n")
|
emith("define i32 @lp_fs_write_text(ptr %p, ptr %s) {\n")
|
||||||
emith("entry:\n %tmp = call ptr @fn_path_join_ext(ptr %p, ptr @fn_str_dottmp)\n %f = call ptr @fopen(ptr %tmp, ptr @fn_str_wb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
emith("entry:\n %tmp = call ptr @lp_path_join_ext(ptr %p, ptr @fn_str_dottmp)\n %f = call ptr @fopen(ptr %tmp, ptr @fn_str_wb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||||
emith("bad:\n ret i32 0\n")
|
emith("bad:\n ret i32 0\n")
|
||||||
emith("ok:\n %n = call i64 @strlen(ptr %s)\n %w = call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %f)\n call i32 @fclose(ptr %f)\n")
|
emith("ok:\n %n = call i64 @strlen(ptr %s)\n %w = call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %f)\n call i32 @fclose(ptr %f)\n")
|
||||||
emith(" %rr = call i32 @rename(ptr %tmp, ptr %p)\n %ok2 = icmp eq i32 %rr, 0\n %z = zext i1 %ok2 to i32\n ret i32 %z\n}\n")
|
emith(" %rr = call i32 @rename(ptr %tmp, ptr %p)\n %ok2 = icmp eq i32 %rr, 0\n %z = zext i1 %ok2 to i32\n ret i32 %z\n}\n")
|
||||||
|
|
||||||
# concat two strings (used to build "<p>.tmp"); local so write_text needs no
|
# concat two strings (used to build "<p>.tmp"); local so write_text needs no
|
||||||
# dependency on the Os prelude
|
# dependency on the Os prelude
|
||||||
emith("define ptr @fn_path_join_ext(ptr %a, ptr %b) {\n")
|
emith("define ptr @lp_path_join_ext(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %sum = add i64 %la, %lb\n %tot = add i64 %sum, 1\n %m = call ptr @malloc(i64 %tot)\n")
|
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %sum = add i64 %la, %lb\n %tot = add i64 %sum, 1\n %m = call ptr @malloc(i64 %tot)\n")
|
||||||
emith(" call ptr @memcpy(ptr %m, ptr %a, i64 %la)\n %m2 = getelementptr i8, ptr %m, i64 %la\n call ptr @memcpy(ptr %m2, ptr %b, i64 %lb)\n %ep = getelementptr i8, ptr %m, i64 %sum\n store i8 0, ptr %ep\n ret ptr %m\n}\n")
|
emith(" call ptr @memcpy(ptr %m, ptr %a, i64 %la)\n %m2 = getelementptr i8, ptr %m, i64 %la\n call ptr @memcpy(ptr %m2, ptr %b, i64 %lb)\n %ep = getelementptr i8, ptr %m, i64 %sum\n store i8 0, ptr %ep\n ret ptr %m\n}\n")
|
||||||
|
|
||||||
emith("define i32 @fn_fs_append_text(ptr %p, ptr %s) {\n")
|
emith("define i32 @lp_fs_append_text(ptr %p, ptr %s) {\n")
|
||||||
emith("entry:\n %f = call ptr @fopen(ptr %p, ptr @fn_str_ab)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
emith("entry:\n %f = call ptr @fopen(ptr %p, ptr @fn_str_ab)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %bad\n")
|
||||||
emith("bad:\n ret i32 0\n")
|
emith("bad:\n ret i32 0\n")
|
||||||
emith("ok:\n %n = call i64 @strlen(ptr %s)\n call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %f)\n call i32 @fclose(ptr %f)\n ret i32 1\n}\n")
|
emith("ok:\n %n = call i64 @strlen(ptr %s)\n call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %f)\n call i32 @fclose(ptr %f)\n ret i32 1\n}\n")
|
||||||
|
|
||||||
emith("define i32 @fn_fs_remove(ptr %p) {\n")
|
emith("define i32 @lp_fs_remove(ptr %p) {\n")
|
||||||
emith("entry:\n %r = call i32 @remove(ptr %p)\n %ok = icmp eq i32 %r, 0\n %z = zext i1 %ok to i32\n ret i32 %z\n}\n")
|
emith("entry:\n %r = call i32 @remove(ptr %p)\n %ok = icmp eq i32 %r, 0\n %z = zext i1 %ok to i32\n ret i32 %z\n}\n")
|
||||||
|
|
||||||
# copy: byte-for-byte via readall + a sized write. Returns 1 on success.
|
# copy: byte-for-byte via readall + a sized write. Returns 1 on success.
|
||||||
emith("define i32 @fn_fs_copy(ptr %src, ptr %dst) {\n")
|
emith("define i32 @lp_fs_copy(ptr %src, ptr %dst) {\n")
|
||||||
emith("entry:\n %lp = alloca i32\n %buf = call ptr @fn_fs_readall(ptr %src, ptr %lp)\n %nz = icmp ne ptr %buf, null\n br i1 %nz, label %ok, label %bad\n")
|
emith("entry:\n %lp = alloca i32\n %buf = call ptr @lp_fs_readall(ptr %src, ptr %lp)\n %nz = icmp ne ptr %buf, null\n br i1 %nz, label %ok, label %bad\n")
|
||||||
emith("bad:\n ret i32 0\n")
|
emith("bad:\n ret i32 0\n")
|
||||||
emith("ok:\n %tmp = call ptr @fn_path_join_ext(ptr %dst, ptr @fn_str_dottmp)\n %f = call ptr @fopen(ptr %tmp, ptr @fn_str_wb)\n %fnz = icmp ne ptr %f, null\n br i1 %fnz, label %w, label %bad\n")
|
emith("ok:\n %tmp = call ptr @lp_path_join_ext(ptr %dst, ptr @fn_str_dottmp)\n %f = call ptr @fopen(ptr %tmp, ptr @fn_str_wb)\n %fnz = icmp ne ptr %f, null\n br i1 %fnz, label %w, label %bad\n")
|
||||||
emith("w:\n %n = load i32, ptr %lp\n %nz64 = zext i32 %n to i64\n call i64 @fwrite(ptr %buf, i64 1, i64 %nz64, ptr %f)\n call i32 @fclose(ptr %f)\n %rr = call i32 @rename(ptr %tmp, ptr %dst)\n %ok2 = icmp eq i32 %rr, 0\n %z = zext i1 %ok2 to i32\n ret i32 %z\n}\n")
|
emith("w:\n %n = load i32, ptr %lp\n %nz64 = zext i32 %n to i64\n call i64 @fwrite(ptr %buf, i64 1, i64 %nz64, ptr %f)\n call i32 @fclose(ptr %f)\n %rr = call i32 @rename(ptr %tmp, ptr %dst)\n %ok2 = icmp eq i32 %rr, 0\n %z = zext i1 %ok2 to i32\n ret i32 %z\n}\n")
|
||||||
|
|
||||||
# mkdir: create %p and any missing parents (mkdir -p). Returns 1 if the
|
# mkdir: create %p and any missing parents (mkdir -p). Returns 1 if the
|
||||||
# directory exists afterwards. Intermediate EEXIST errors are ignored.
|
# directory exists afterwards. Intermediate EEXIST errors are ignored.
|
||||||
emith("define i32 @fn_fs_mkdir(ptr %p) {\n")
|
emith("define i32 @lp_fs_mkdir(ptr %p) {\n")
|
||||||
emith("entry:\n %dup = call ptr @fn_fs_strdup(ptr %p)\n %ip = alloca i32\n store i32 1, ptr %ip\n br label %lp\n")
|
emith("entry:\n %dup = call ptr @lp_fs_strdup(ptr %p)\n %ip = alloca i32\n store i32 1, ptr %ip\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %pp = getelementptr i8, ptr %dup, i32 %i\n %c = load i8, ptr %pp\n %ci = zext i8 %c to i32\n %z = icmp eq i32 %ci, 0\n br i1 %z, label %fin, label %go\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %pp = getelementptr i8, ptr %dup, i32 %i\n %c = load i8, ptr %pp\n %ci = zext i8 %c to i32\n %z = icmp eq i32 %ci, 0\n br i1 %z, label %fin, label %go\n")
|
||||||
emith("go:\n %sl = icmp eq i32 %ci, 47\n br i1 %sl, label %cut, label %nx\n")
|
emith("go:\n %sl = icmp eq i32 %ci, 47\n br i1 %sl, label %cut, label %nx\n")
|
||||||
emith("cut:\n store i8 0, ptr %pp\n call i32 @mkdir(ptr %dup, i32 493)\n store i8 47, ptr %pp\n br label %nx\n")
|
emith("cut:\n store i8 0, ptr %pp\n call i32 @mkdir(ptr %dup, i32 493)\n store i8 47, ptr %pp\n br label %nx\n")
|
||||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||||
emith("fin:\n call i32 @mkdir(ptr %dup, i32 493)\n %r = call i32 @fn_fs_is_dir(ptr %p)\n ret i32 %r\n}\n")
|
emith("fin:\n call i32 @mkdir(ptr %dup, i32 493)\n %r = call i32 @lp_fs_is_dir(ptr %p)\n ret i32 %r\n}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- Fs.list (+ sort) ------------------------------------------------------
|
# ---- Fs.list (+ sort) ------------------------------------------------------
|
||||||
|
|
@ -314,7 +314,7 @@ function emit_fs_dir() -> void {
|
||||||
# by byte value for a stable, reproducible order. Each name is copied out of
|
# by byte value for a stable, reproducible order. Each name is copied out of
|
||||||
# the readdir buffer before the next call. macOS/BSD dirent: d_name at offset
|
# the readdir buffer before the next call. macOS/BSD dirent: d_name at offset
|
||||||
# 21 (documented native layout).
|
# 21 (documented native layout).
|
||||||
emith("define ptr @fn_fs_list(ptr %path) {\n")
|
emith("define ptr @lp_fs_list(ptr %path) {\n")
|
||||||
emith("entry:\n %h = call ptr @malloc(i64 16)\n %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n %d2 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 2\n")
|
emith("entry:\n %h = call ptr @malloc(i64 16)\n %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n %d2 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 2\n")
|
||||||
emith(" %datap = alloca ptr\n %cntp = alloca i32\n %capp = alloca i32\n %init = call ptr @malloc(i64 128)\n store ptr %init, ptr %datap\n store i32 0, ptr %cntp\n store i32 16, ptr %capp\n")
|
emith(" %datap = alloca ptr\n %cntp = alloca i32\n %capp = alloca i32\n %init = call ptr @malloc(i64 128)\n store ptr %init, ptr %datap\n store i32 0, ptr %cntp\n store i32 16, ptr %capp\n")
|
||||||
emith(" %dir = call ptr @opendir(ptr %path)\n %dnz = icmp ne ptr %dir, null\n br i1 %dnz, label %rd, label %empty\n")
|
emith(" %dir = call ptr @opendir(ptr %path)\n %dnz = icmp ne ptr %dir, null\n br i1 %dnz, label %rd, label %empty\n")
|
||||||
|
|
@ -325,7 +325,7 @@ function emit_fs_dir() -> void {
|
||||||
emith("chkdots:\n %n1p = getelementptr i8, ptr %namep, i32 1\n %n1 = load i8, ptr %n1p\n %n1i = zext i8 %n1 to i32\n %n1z = icmp eq i32 %n1i, 0\n br i1 %n1z, label %rd, label %chkdd\n") # "." -> skip
|
emith("chkdots:\n %n1p = getelementptr i8, ptr %namep, i32 1\n %n1 = load i8, ptr %n1p\n %n1i = zext i8 %n1 to i32\n %n1z = icmp eq i32 %n1i, 0\n br i1 %n1z, label %rd, label %chkdd\n") # "." -> skip
|
||||||
emith("chkdd:\n %isdot1 = icmp eq i32 %n1i, 46\n br i1 %isdot1, label %chkdd2, label %keep\n")
|
emith("chkdd:\n %isdot1 = icmp eq i32 %n1i, 46\n br i1 %isdot1, label %chkdd2, label %keep\n")
|
||||||
emith("chkdd2:\n %n2p = getelementptr i8, ptr %namep, i32 2\n %n2 = load i8, ptr %n2p\n %n2i = zext i8 %n2 to i32\n %n2z = icmp eq i32 %n2i, 0\n br i1 %n2z, label %rd, label %keep\n") # ".." -> skip
|
emith("chkdd2:\n %n2p = getelementptr i8, ptr %namep, i32 2\n %n2 = load i8, ptr %n2p\n %n2i = zext i8 %n2 to i32\n %n2z = icmp eq i32 %n2i, 0\n br i1 %n2z, label %rd, label %keep\n") # ".." -> skip
|
||||||
emith("keep:\n %nm = call ptr @fn_fs_strdup(ptr %namep)\n")
|
emith("keep:\n %nm = call ptr @lp_fs_strdup(ptr %namep)\n")
|
||||||
# grow if full
|
# grow if full
|
||||||
emith(" %cnt = load i32, ptr %cntp\n %cap = load i32, ptr %capp\n %full = icmp sge i32 %cnt, %cap\n br i1 %full, label %grow, label %put\n")
|
emith(" %cnt = load i32, ptr %cntp\n %cap = load i32, ptr %capp\n %full = icmp sge i32 %cnt, %cap\n br i1 %full, label %grow, label %put\n")
|
||||||
emith("grow:\n %nc = mul i32 %cap, 2\n store i32 %nc, ptr %capp\n %ncz = zext i32 %nc to i64\n %nb = mul i64 %ncz, 8\n %old = load ptr, ptr %datap\n %new = call ptr @realloc(ptr %old, i64 %nb)\n store ptr %new, ptr %datap\n br label %put\n")
|
emith("grow:\n %nc = mul i32 %cap, 2\n store i32 %nc, ptr %capp\n %ncz = zext i32 %nc to i64\n %nb = mul i64 %ncz, 8\n %old = load ptr, ptr %datap\n %new = call ptr @realloc(ptr %old, i64 %nb)\n store ptr %new, ptr %datap\n br label %put\n")
|
||||||
|
|
@ -376,7 +376,7 @@ function emit_fs_mime() -> void {
|
||||||
# create the string constants FIRST (as module globals), then reference them
|
# create the string constants FIRST (as module globals), then reference them
|
||||||
let en = new []pointer; let tn = new []pointer
|
let en = new []pointer; let tn = new []pointer
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(exts) { push(en, emit_str_const(exts[i])); push(tn, emit_str_const(tys[i])); i = i + 1 }
|
while i < len(exts) { push(en, emit_str_const(exts[i])); push(tn, emit_str_const(tys[i])); i += 1 }
|
||||||
let k_octet = emit_str_const("application/octet-stream")
|
let k_octet = emit_str_const("application/octet-stream")
|
||||||
let n = len(exts)
|
let n = len(exts)
|
||||||
|
|
||||||
|
|
@ -386,21 +386,21 @@ function emit_fs_mime() -> void {
|
||||||
while i < len(exts) {
|
while i < len(exts) {
|
||||||
if i > 0 { emith(", ") }
|
if i > 0 { emith(", ") }
|
||||||
emith(`{{ ptr, ptr }} {{ ptr {en[i]}, ptr {tn[i]} }}`)
|
emith(`{{ ptr, ptr }} {{ ptr {en[i]}, ptr {tn[i]} }}`)
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emith("]\n")
|
emith("]\n")
|
||||||
|
|
||||||
# of(path): lowercased extension, then a linear scan of the table
|
# of(path): lowercased extension, then a linear scan of the table
|
||||||
emith("define ptr @fn_mime_of(ptr %path) {\n")
|
emith("define ptr @lp_mime_of(ptr %path) {\n")
|
||||||
emith("entry:\n %ext = call ptr @fn_path_ext(ptr %path)\n %lc = call ptr @fn_mime_lc(ptr %ext)\n %ip = alloca i32\n store i32 0, ptr %ip\n br label %lp\n")
|
emith("entry:\n %ext = call ptr @lp_path_ext(ptr %path)\n %lc = call ptr @lp_mime_lc(ptr %ext)\n %ip = alloca i32\n store i32 0, ptr %ip\n br label %lp\n")
|
||||||
emith(`lp:\n %i = load i32, ptr %ip\n %lt = icmp slt i32 %i, {itoa(n)}\n br i1 %lt, label %body, label %def\n`)
|
emith(`lp:\n %i = load i32, ptr %ip\n %lt = icmp slt i32 %i, {itoa(n)}\n br i1 %lt, label %body, label %def\n`)
|
||||||
emith(`body:\n %kp = getelementptr [{itoa(n)} x {{ ptr, ptr }}], ptr @mime_tbl, i32 0, i32 %i, i32 0\n %k = load ptr, ptr %kp\n %c = call i32 @strcmp(ptr %lc, ptr %k)\n %eq = icmp eq i32 %c, 0\n br i1 %eq, label %hit, label %nx\n`)
|
emith(`body:\n %kp = getelementptr [{itoa(n)} x {{ ptr, ptr }}], ptr @mime_tbl, i32 0, i32 %i, i32 0\n %k = load ptr, ptr %kp\n %c = call i32 @strcmp(ptr %lc, ptr %k)\n %eq = icmp eq i32 %c, 0\n br i1 %eq, label %hit, label %nx\n`)
|
||||||
emith(`hit:\n %vp = getelementptr [{itoa(n)} x {{ ptr, ptr }}], ptr @mime_tbl, i32 0, i32 %i, i32 1\n %v = load ptr, ptr %vp\n %r = call ptr @fn_fs_strdup(ptr %v)\n ret ptr %r\n`)
|
emith(`hit:\n %vp = getelementptr [{itoa(n)} x {{ ptr, ptr }}], ptr @mime_tbl, i32 0, i32 %i, i32 1\n %v = load ptr, ptr %vp\n %r = call ptr @lp_fs_strdup(ptr %v)\n ret ptr %r\n`)
|
||||||
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
emith("nx:\n %i1 = add i32 %i, 1\n store i32 %i1, ptr %ip\n br label %lp\n")
|
||||||
emith(`def:\n %d = call ptr @fn_fs_strdup(ptr {k_octet})\n ret ptr %d\n}}\n`)
|
emith(`def:\n %d = call ptr @lp_fs_strdup(ptr {k_octet})\n ret ptr %d\n}}\n`)
|
||||||
|
|
||||||
# lowercase an extension, dropping a leading '.' (ASCII only, for table lookup)
|
# lowercase an extension, dropping a leading '.' (ASCII only, for table lookup)
|
||||||
emith("define ptr @fn_mime_lc(ptr %s) {\n")
|
emith("define ptr @lp_mime_lc(ptr %s) {\n")
|
||||||
emith("entry:\n %l64 = call i64 @strlen(ptr %s)\n %l = trunc i64 %l64 to i32\n %cap = add i64 %l64, 1\n %out = call ptr @malloc(i64 %cap)\n")
|
emith("entry:\n %l64 = call i64 @strlen(ptr %s)\n %l = trunc i64 %l64 to i32\n %cap = add i64 %l64, 1\n %out = call ptr @malloc(i64 %cap)\n")
|
||||||
emith(" %b0 = load i8, ptr %s\n %b0i = zext i8 %b0 to i32\n %isdot = icmp eq i32 %b0i, 46\n %start = select i1 %isdot, i32 1, i32 0\n")
|
emith(" %b0 = load i8, ptr %s\n %b0i = zext i8 %b0 to i32\n %isdot = icmp eq i32 %b0i, 46\n %start = select i1 %isdot, i32 1, i32 0\n")
|
||||||
emith(" %ip = alloca i32\n %op = alloca i32\n store i32 %start, ptr %ip\n store i32 0, ptr %op\n br label %lp\n")
|
emith(" %ip = alloca i32\n %op = alloca i32\n store i32 %start, ptr %ip\n store i32 0, ptr %op\n br label %lp\n")
|
||||||
|
|
@ -414,23 +414,23 @@ function emit_fs_mime() -> void {
|
||||||
# sniff(path): read the first bytes and recognise a few well-known signatures,
|
# sniff(path): read the first bytes and recognise a few well-known signatures,
|
||||||
# otherwise fall back to the extension. Covers PNG/JPEG/GIF/PDF for now.
|
# otherwise fall back to the extension. Covers PNG/JPEG/GIF/PDF for now.
|
||||||
function emit_fs_sniff() -> void {
|
function emit_fs_sniff() -> void {
|
||||||
emith("define ptr @fn_mime_sniff(ptr %path) {\n")
|
emith("define ptr @lp_mime_sniff(ptr %path) {\n")
|
||||||
emith("entry:\n %f = call ptr @fopen(ptr %path, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %fallback\n")
|
emith("entry:\n %f = call ptr @fopen(ptr %path, ptr @fn_str_rb)\n %nz = icmp ne ptr %f, null\n br i1 %nz, label %ok, label %fallback\n")
|
||||||
emith("ok:\n %buf = call ptr @malloc(i64 16)\n %rd = call i64 @fread(ptr %buf, i64 1, i64 8, ptr %f)\n call i32 @fclose(ptr %f)\n %rdi = trunc i64 %rd to i32\n %has4 = icmp sge i32 %rdi, 4\n br i1 %has4, label %chk, label %fallback\n")
|
emith("ok:\n %buf = call ptr @malloc(i64 16)\n %rd = call i64 @fread(ptr %buf, i64 1, i64 8, ptr %f)\n call i32 @fclose(ptr %f)\n %rdi = trunc i64 %rd to i32\n %has4 = icmp sge i32 %rdi, 4\n br i1 %has4, label %chk, label %fallback\n")
|
||||||
emith("chk:\n %b0p = getelementptr i8, ptr %buf, i32 0\n %b0 = load i8, ptr %b0p\n %b0i = zext i8 %b0 to i32\n %b1p = getelementptr i8, ptr %buf, i32 1\n %b1 = load i8, ptr %b1p\n %b1i = zext i8 %b1 to i32\n %b2p = getelementptr i8, ptr %buf, i32 2\n %b2 = load i8, ptr %b2p\n %b2i = zext i8 %b2 to i32\n %b3p = getelementptr i8, ptr %buf, i32 3\n %b3 = load i8, ptr %b3p\n %b3i = zext i8 %b3 to i32\n")
|
emith("chk:\n %b0p = getelementptr i8, ptr %buf, i32 0\n %b0 = load i8, ptr %b0p\n %b0i = zext i8 %b0 to i32\n %b1p = getelementptr i8, ptr %buf, i32 1\n %b1 = load i8, ptr %b1p\n %b1i = zext i8 %b1 to i32\n %b2p = getelementptr i8, ptr %buf, i32 2\n %b2 = load i8, ptr %b2p\n %b2i = zext i8 %b2 to i32\n %b3p = getelementptr i8, ptr %buf, i32 3\n %b3 = load i8, ptr %b3p\n %b3i = zext i8 %b3 to i32\n")
|
||||||
# PNG: 89 50 4E 47
|
# PNG: 89 50 4E 47
|
||||||
emith(" %p0 = icmp eq i32 %b0i, 137\n %p1 = icmp eq i32 %b1i, 80\n %p2 = icmp eq i32 %b2i, 78\n %p3 = icmp eq i32 %b3i, 71\n %pa = and i1 %p0, %p1\n %pb = and i1 %pa, %p2\n %pc = and i1 %pb, %p3\n br i1 %pc, label %png, label %cj\n")
|
emith(" %p0 = icmp eq i32 %b0i, 137\n %p1 = icmp eq i32 %b1i, 80\n %p2 = icmp eq i32 %b2i, 78\n %p3 = icmp eq i32 %b3i, 71\n %pa = and i1 %p0, %p1\n %pb = and i1 %pa, %p2\n %pc = and i1 %pb, %p3\n br i1 %pc, label %png, label %cj\n")
|
||||||
emith("png:\n %rpng = call ptr @fn_fs_strdup(ptr @fn_sig_png)\n ret ptr %rpng\n")
|
emith("png:\n %rpng = call ptr @lp_fs_strdup(ptr @fn_sig_png)\n ret ptr %rpng\n")
|
||||||
# JPEG: FF D8 FF
|
# JPEG: FF D8 FF
|
||||||
emith("cj:\n %j0 = icmp eq i32 %b0i, 255\n %j1 = icmp eq i32 %b1i, 216\n %j2 = icmp eq i32 %b2i, 255\n %ja = and i1 %j0, %j1\n %jb = and i1 %ja, %j2\n br i1 %jb, label %jpg, label %cg\n")
|
emith("cj:\n %j0 = icmp eq i32 %b0i, 255\n %j1 = icmp eq i32 %b1i, 216\n %j2 = icmp eq i32 %b2i, 255\n %ja = and i1 %j0, %j1\n %jb = and i1 %ja, %j2\n br i1 %jb, label %jpg, label %cg\n")
|
||||||
emith("jpg:\n %rjpg = call ptr @fn_fs_strdup(ptr @fn_sig_jpg)\n ret ptr %rjpg\n")
|
emith("jpg:\n %rjpg = call ptr @lp_fs_strdup(ptr @fn_sig_jpg)\n ret ptr %rjpg\n")
|
||||||
# GIF: 47 49 46
|
# GIF: 47 49 46
|
||||||
emith("cg:\n %g0 = icmp eq i32 %b0i, 71\n %g1 = icmp eq i32 %b1i, 73\n %g2 = icmp eq i32 %b2i, 70\n %ga = and i1 %g0, %g1\n %gb = and i1 %ga, %g2\n br i1 %gb, label %gif, label %cp\n")
|
emith("cg:\n %g0 = icmp eq i32 %b0i, 71\n %g1 = icmp eq i32 %b1i, 73\n %g2 = icmp eq i32 %b2i, 70\n %ga = and i1 %g0, %g1\n %gb = and i1 %ga, %g2\n br i1 %gb, label %gif, label %cp\n")
|
||||||
emith("gif:\n %rgif = call ptr @fn_fs_strdup(ptr @fn_sig_gif)\n ret ptr %rgif\n")
|
emith("gif:\n %rgif = call ptr @lp_fs_strdup(ptr @fn_sig_gif)\n ret ptr %rgif\n")
|
||||||
# PDF: 25 50 44 46
|
# PDF: 25 50 44 46
|
||||||
emith("cp:\n %q0 = icmp eq i32 %b0i, 37\n %q1 = icmp eq i32 %b1i, 80\n %q2 = icmp eq i32 %b2i, 68\n %q3 = icmp eq i32 %b3i, 70\n %qa = and i1 %q0, %q1\n %qb = and i1 %qa, %q2\n %qc = and i1 %qb, %q3\n br i1 %qc, label %pdf, label %fallback\n")
|
emith("cp:\n %q0 = icmp eq i32 %b0i, 37\n %q1 = icmp eq i32 %b1i, 80\n %q2 = icmp eq i32 %b2i, 68\n %q3 = icmp eq i32 %b3i, 70\n %qa = and i1 %q0, %q1\n %qb = and i1 %qa, %q2\n %qc = and i1 %qb, %q3\n br i1 %qc, label %pdf, label %fallback\n")
|
||||||
emith("pdf:\n %rpdf = call ptr @fn_fs_strdup(ptr @fn_sig_pdf)\n ret ptr %rpdf\n")
|
emith("pdf:\n %rpdf = call ptr @lp_fs_strdup(ptr @fn_sig_pdf)\n ret ptr %rpdf\n")
|
||||||
emith("fallback:\n %r = call ptr @fn_mime_of(ptr %path)\n ret ptr %r\n}\n")
|
emith("fallback:\n %r = call ptr @lp_mime_of(ptr %path)\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
# the small string constants the Fs/Path/Mime runtime references
|
# the small string constants the Fs/Path/Mime runtime references
|
||||||
emith("@fn_str_rb = private unnamed_addr constant [3 x i8] c\"rb\\00\"\n")
|
emith("@fn_str_rb = private unnamed_addr constant [3 x i8] c\"rb\\00\"\n")
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,12 @@ function emit_hash_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "of") or (meth == "fnv1a") { # FNV-1a 32-bit over the bytes
|
if (meth == "of") or (meth == "fnv1a") { # FNV-1a 32-bit over the bytes
|
||||||
g_uses_hashrt = true
|
g_uses_hashrt = true
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_hash_fnv1a(ptr {s.code})`), "int")
|
return val(emit_bind(`call i32 @lp_hash_fnv1a(ptr {s.code})`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "of64") or (meth == "fnv1a_64") { # FNV-1a 64-bit -> a `long`
|
if (meth == "of64") or (meth == "fnv1a_64") { # FNV-1a 64-bit -> a `long`
|
||||||
g_uses_hashrt = true
|
g_uses_hashrt = true
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i64 @fn_hash_fnv1a_64(ptr {s.code})`), "long")
|
return val(emit_bind(`call i64 @lp_hash_fnv1a_64(ptr {s.code})`), "long")
|
||||||
}
|
}
|
||||||
if (meth == "mix64") { # fmix64 avalanche of one long
|
if (meth == "mix64") { # fmix64 avalanche of one long
|
||||||
let x = emit_expr(e.kids[0])
|
let x = emit_expr(e.kids[0])
|
||||||
|
|
@ -64,7 +64,7 @@ function emit_hash_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "crc32") { # CRC-32 (IEEE) checksum
|
if (meth == "crc32") { # CRC-32 (IEEE) checksum
|
||||||
g_uses_hashrt = true
|
g_uses_hashrt = true
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_hash_crc32(ptr {s.code})`), "int")
|
return val(emit_bind(`call i32 @lp_hash_crc32(ptr {s.code})`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "mix") { # fmix32 avalanche of one int
|
if (meth == "mix") { # fmix32 avalanche of one int
|
||||||
let x = emit_expr(e.kids[0])
|
let x = emit_expr(e.kids[0])
|
||||||
|
|
@ -84,7 +84,7 @@ function emit_hash_ns(meth: pointer, e: Node) -> Val {
|
||||||
let t1 = emit_bind(`add i32 {g}, {sl}`)
|
let t1 = emit_bind(`add i32 {g}, {sl}`)
|
||||||
let t2 = emit_bind(`add i32 {t1}, {sr}`)
|
let t2 = emit_bind(`add i32 {t1}, {sr}`)
|
||||||
seed = emit_bind(`xor i32 {seed}, {t2}`)
|
seed = emit_bind(`xor i32 {seed}, {t2}`)
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return val(seed, "int")
|
return val(seed, "int")
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ function emit_hash_ns(meth: pointer, e: Node) -> Val {
|
||||||
# prime, and a bitwise CRC-32 with the reflected poly 0xEDB88320. No libc, no
|
# prime, and a bitwise CRC-32 with the reflected poly 0xEDB88320. No libc, no
|
||||||
# allocation, bit-identical on every target.
|
# allocation, bit-identical on every target.
|
||||||
function emit_hash_prelude() -> void {
|
function emit_hash_prelude() -> void {
|
||||||
emith("define i32 @fn_hash_fnv1a(ptr %s) {\n")
|
emith("define i32 @lp_hash_fnv1a(ptr %s) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %hp = alloca i32\n")
|
emith(" %hp = alloca i32\n")
|
||||||
emith(" store i32 -2128831035, ptr %hp\n") # 0x811c9dc5 offset basis
|
emith(" store i32 -2128831035, ptr %hp\n") # 0x811c9dc5 offset basis
|
||||||
|
|
@ -121,7 +121,7 @@ function emit_hash_prelude() -> void {
|
||||||
emith(" %hr = load i32, ptr %hp\n")
|
emith(" %hr = load i32, ptr %hp\n")
|
||||||
emith(" ret i32 %hr\n")
|
emith(" ret i32 %hr\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define i64 @fn_hash_fnv1a_64(ptr %s) {\n") # 64-bit FNV-1a, same shape, i64
|
emith("define i64 @lp_hash_fnv1a_64(ptr %s) {\n") # 64-bit FNV-1a, same shape, i64
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %hp = alloca i64\n")
|
emith(" %hp = alloca i64\n")
|
||||||
emith(" store i64 -3750763034362895579, ptr %hp\n") # 0xcbf29ce484222325 offset basis
|
emith(" store i64 -3750763034362895579, ptr %hp\n") # 0xcbf29ce484222325 offset basis
|
||||||
|
|
@ -147,7 +147,7 @@ function emit_hash_prelude() -> void {
|
||||||
emith(" %hr = load i64, ptr %hp\n")
|
emith(" %hr = load i64, ptr %hp\n")
|
||||||
emith(" ret i64 %hr\n")
|
emith(" ret i64 %hr\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define i32 @fn_hash_crc32(ptr %s) {\n")
|
emith("define i32 @lp_hash_crc32(ptr %s) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %cp = alloca i32\n")
|
emith(" %cp = alloca i32\n")
|
||||||
emith(" store i32 -1, ptr %cp\n") # init 0xFFFFFFFF
|
emith(" store i32 -1, ptr %cp\n") # init 0xFFFFFFFF
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ function key_lookup(name: pointer) -> int {
|
||||||
# a single capital letter names that letter key: Key.A .. Key.Z
|
# a single capital letter names that letter key: Key.A .. Key.Z
|
||||||
if (name[1] == 0) {
|
if (name[1] == 0) {
|
||||||
let c = name[0]
|
let c = name[0]
|
||||||
if (c >= 65) and (c <= 90) { return c + 32 }
|
if (c >= 'A') and (c <= 'Z') { return c + 32 }
|
||||||
}
|
}
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ function is_log_ns(meth: pointer) -> bool {
|
||||||
# and an int are converted the same way the `string(...)` builtin does.
|
# and an int are converted the same way the `string(...)` builtin does.
|
||||||
function log_stringify(v: Val) -> pointer {
|
function log_stringify(v: Val) -> pointer {
|
||||||
if (llty(v.ty) == "ptr") { return v.code }
|
if (llty(v.ty) == "ptr") { return v.code }
|
||||||
if (llty(v.ty) == "i64") { g_uses_longstr = true; return emit_bind(`call ptr @fn_long_str(i64 {v.code})`) }
|
if (llty(v.ty) == "i64") { g_uses_longstr = true; return emit_bind(`call ptr @lp_long_str(i64 {v.code})`) }
|
||||||
g_uses_intstr = true
|
g_uses_intstr = true
|
||||||
return emit_bind(`call ptr @fn_int_str(i32 {v.code})`)
|
return emit_bind(`call ptr @lp_int_str(i32 {v.code})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_log_ns(meth: pointer, e: Node) -> Val {
|
function emit_log_ns(meth: pointer, e: Node) -> Val {
|
||||||
|
|
@ -67,19 +67,19 @@ function emit_log_ns(meth: pointer, e: Node) -> Val {
|
||||||
line = emit_str_op("+", line, val(log_stringify(k), "string"))
|
line = emit_str_op("+", line, val(log_stringify(k), "string"))
|
||||||
line = emit_str_op("+", line, val(emit_str_const("="), "string"))
|
line = emit_str_op("+", line, val(emit_str_const("="), "string"))
|
||||||
line = emit_str_op("+", line, val(log_stringify(v), "string"))
|
line = emit_str_op("+", line, val(log_stringify(v), "string"))
|
||||||
i = i + 2
|
i += 2
|
||||||
}
|
}
|
||||||
emit(` call void @fn_log_emit(i32 {lvl}, ptr {line.code})\n`)
|
emit(` call void @lp_log_emit(i32 {lvl}, ptr {line.code})\n`)
|
||||||
return val("0", "void")
|
return val("0", "void")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_log_prelude — the log level register and the console sink, emitted once per
|
# emit_log_prelude — the log level register and the console sink, emitted once per
|
||||||
# program that uses Log.* (g_uses_logrt). @fn_log_emit checks the threshold and,
|
# program that uses Log.* (g_uses_logrt). @lp_log_emit checks the threshold and,
|
||||||
# if the message is at or above it, writes the line + newline to stderr.
|
# if the message is at or above it, writes the line + newline to stderr.
|
||||||
function emit_log_prelude() -> void {
|
function emit_log_prelude() -> void {
|
||||||
emith("@L_log_level = global i32 0\n")
|
emith("@L_log_level = global i32 0\n")
|
||||||
emith("@.log_nl = private unnamed_addr constant [2 x i8] c\"\\0A\\00\"\n")
|
emith("@.log_nl = private unnamed_addr constant [2 x i8] c\"\\0A\\00\"\n")
|
||||||
emith("define void @fn_log_emit(i32 %lvl, ptr %s) {\n")
|
emith("define void @lp_log_emit(i32 %lvl, ptr %s) {\n")
|
||||||
emith("entry:\n %th = load i32, ptr @L_log_level\n %skip = icmp slt i32 %lvl, %th\n br i1 %skip, label %done, label %go\n")
|
emith("entry:\n %th = load i32, ptr @L_log_level\n %skip = icmp slt i32 %lvl, %th\n br i1 %skip, label %done, label %go\n")
|
||||||
emith("go:\n %e = load ptr, ptr @__stderrp\n %n = call i64 @strlen(ptr %s)\n")
|
emith("go:\n %e = load ptr, ptr @__stderrp\n %n = call i64 @strlen(ptr %s)\n")
|
||||||
emith(" %w = call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %e)\n %w2 = call i64 @fwrite(ptr @.log_nl, i64 1, i64 1, ptr %e)\n br label %done\n")
|
emith(" %w = call i64 @fwrite(ptr %s, i64 1, i64 %n, ptr %e)\n %w2 = call i64 @fwrite(ptr @.log_nl, i64 1, i64 1, ptr %e)\n br label %done\n")
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# emit_math.ludic — the Math.* namespace, all deterministic Q16.16 fixed-point.
|
# emit_math.ludic — the Math.* namespace, all deterministic Q16.16 fixed-point.
|
||||||
# min/max/abs/clamp lower to inline IR (and stay bare too); sign/floor/ceil/
|
# min/max/abs/clamp lower to inline IR (and stay bare too); sign/floor/ceil/
|
||||||
# round/lerp/inverse_lerp/remap and the geometry/interp helpers are inline; and
|
# round/lerp/inverse_lerp/remap and the geometry/interp helpers are inline; and
|
||||||
# sqrt/sin/cos/tan call the runtime prelude below (@fn_fx_sqrt is a bit-by-bit
|
# sqrt/sin/cos/tan call the runtime prelude below (@lp_fx_sqrt is a bit-by-bit
|
||||||
# integer root, @fn_fx_sin a 256-entry interpolated sine table). Everything is
|
# integer root, @lp_fx_sin a 256-entry interpolated sine table). Everything is
|
||||||
# plain integer IR, so it is bit-identical on every platform.
|
# plain integer IR, so it is bit-identical on every platform.
|
||||||
|
|
||||||
function is_math_builtin(name: pointer) -> bool {
|
function is_math_builtin(name: pointer) -> bool {
|
||||||
|
|
@ -115,54 +115,54 @@ function emit_math_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "sqrt") { # sqrt(fixed) -> fixed (deterministic isqrt)
|
if (meth == "sqrt") { # sqrt(fixed) -> fixed (deterministic isqrt)
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {a.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sqrt(i32 {a.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "sin") { # sin(radians: fixed) -> fixed
|
if (meth == "sin") { # sin(radians: fixed) -> fixed
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_fx_sin(i32 {a.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sin(i32 {a.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "cos") { # cos(x) = sin(x + pi/2), pi/2 = 102944 fixed
|
if (meth == "cos") { # cos(x) = sin(x + pi/2), pi/2 = 102944 fixed
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
let sh = emit_bind(`add i32 {a.code}, 102944`)
|
let sh = emit_bind(`add i32 {a.code}, 102944`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_sin(i32 {sh})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sin(i32 {sh})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "tan") { # tan(x) = sin(x) / cos(x)
|
if (meth == "tan") { # tan(x) = sin(x) / cos(x)
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
let s = emit_bind(`call i32 @fn_fx_sin(i32 {a.code})`)
|
let s = emit_bind(`call i32 @lp_fx_sin(i32 {a.code})`)
|
||||||
let sh = emit_bind(`add i32 {a.code}, 102944`)
|
let sh = emit_bind(`add i32 {a.code}, 102944`)
|
||||||
let c = emit_bind(`call i32 @fn_fx_sin(i32 {sh})`)
|
let c = emit_bind(`call i32 @lp_fx_sin(i32 {sh})`)
|
||||||
return val(fx_div_code(s, c), "fixed")
|
return val(fx_div_code(s, c), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "atan2") { # atan2(y, x) -> angle in radians
|
if (meth == "atan2") { # atan2(y, x) -> angle in radians
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let y = emit_expr(e.kids[0]); let x = emit_expr(e.kids[1])
|
let y = emit_expr(e.kids[0]); let x = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {y.code}, i32 {x.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_atan2(i32 {y.code}, i32 {x.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "asin") { # asin(x) = atan2(x, sqrt(1 - x^2))
|
if (meth == "asin") { # asin(x) = atan2(x, sqrt(1 - x^2))
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let x = emit_expr(e.kids[0])
|
let x = emit_expr(e.kids[0])
|
||||||
let xx = fx_mul_code(x.code, x.code)
|
let xx = fx_mul_code(x.code, x.code)
|
||||||
let om = emit_bind(`sub i32 65536, {xx}`)
|
let om = emit_bind(`sub i32 65536, {xx}`)
|
||||||
let root = emit_bind(`call i32 @fn_fx_sqrt(i32 {om})`)
|
let root = emit_bind(`call i32 @lp_fx_sqrt(i32 {om})`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {x.code}, i32 {root})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_atan2(i32 {x.code}, i32 {root})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "acos") { # acos(x) = atan2(sqrt(1 - x^2), x)
|
if (meth == "acos") { # acos(x) = atan2(sqrt(1 - x^2), x)
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let x = emit_expr(e.kids[0])
|
let x = emit_expr(e.kids[0])
|
||||||
let xx = fx_mul_code(x.code, x.code)
|
let xx = fx_mul_code(x.code, x.code)
|
||||||
let om = emit_bind(`sub i32 65536, {xx}`)
|
let om = emit_bind(`sub i32 65536, {xx}`)
|
||||||
let root = emit_bind(`call i32 @fn_fx_sqrt(i32 {om})`)
|
let root = emit_bind(`call i32 @lp_fx_sqrt(i32 {om})`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {root}, i32 {x.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_atan2(i32 {root}, i32 {x.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "hypot") { # hypot(x, y) = sqrt(x*x + y*y)
|
if (meth == "hypot") { # hypot(x, y) = sqrt(x*x + y*y)
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1])
|
||||||
let xx = fx_mul_code(x.code, x.code); let yy = fx_mul_code(y.code, y.code)
|
let xx = fx_mul_code(x.code, x.code); let yy = fx_mul_code(y.code, y.code)
|
||||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sqrt(i32 {s})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "dist2") { # dist2(x0,y0,x1,y1) = dx*dx + dy*dy
|
if (meth == "dist2") { # dist2(x0,y0,x1,y1) = dx*dx + dy*dy
|
||||||
let x0 = emit_expr(e.kids[0]); let y0 = emit_expr(e.kids[1])
|
let x0 = emit_expr(e.kids[0]); let y0 = emit_expr(e.kids[1])
|
||||||
|
|
@ -180,26 +180,26 @@ function emit_math_ns(meth: pointer, e: Node) -> Val {
|
||||||
let dy = emit_bind(`sub i32 {y1.code}, {y0.code}`)
|
let dy = emit_bind(`sub i32 {y1.code}, {y0.code}`)
|
||||||
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
||||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sqrt(i32 {s})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "exp") { # e^x = 2^(x * log2 e), log2 e = 94548 fixed
|
if (meth == "exp") { # e^x = 2^(x * log2 e), log2 e = 94548 fixed
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
let t = fx_mul_code(a.code, "94548")
|
let t = fx_mul_code(a.code, "94548")
|
||||||
return val(emit_bind(`call i32 @fn_fx_exp2(i32 {t})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_exp2(i32 {t})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "log") { # natural log: ln(x) = log2(x) * ln 2, ln 2 = 45426 fixed
|
if (meth == "log") { # natural log: ln(x) = log2(x) * ln 2, ln 2 = 45426 fixed
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
let l2 = emit_bind(`call i32 @fn_fx_log2(i32 {a.code})`)
|
let l2 = emit_bind(`call i32 @lp_fx_log2(i32 {a.code})`)
|
||||||
return val(fx_mul_code(l2, "45426"), "fixed")
|
return val(fx_mul_code(l2, "45426"), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "pow") { # a^b = 2^(b * log2 a); needs a > 0
|
if (meth == "pow") { # a^b = 2^(b * log2 a); needs a > 0
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
let l2 = emit_bind(`call i32 @fn_fx_log2(i32 {a.code})`)
|
let l2 = emit_bind(`call i32 @lp_fx_log2(i32 {a.code})`)
|
||||||
let t = fx_mul_code(b.code, l2)
|
let t = fx_mul_code(b.code, l2)
|
||||||
return val(emit_bind(`call i32 @fn_fx_exp2(i32 {t})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_exp2(i32 {t})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "deg_to_rad") { # d * (pi/180), pi/180 = 1144 fixed
|
if (meth == "deg_to_rad") { # d * (pi/180), pi/180 = 1144 fixed
|
||||||
let d = emit_expr(e.kids[0])
|
let d = emit_expr(e.kids[0])
|
||||||
|
|
@ -279,14 +279,14 @@ function emit_math_ns(meth: pointer, e: Node) -> Val {
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_math_prelude — the deterministic fixed-point math runtime, emitted once
|
# emit_math_prelude — the deterministic fixed-point math runtime, emitted once
|
||||||
# per program that uses Math.sqrt/sin/cos/tan/exp/log/pow. @fn_fx_sqrt is a
|
# per program that uses Math.sqrt/sin/cos/tan/exp/log/pow. @lp_fx_sqrt is a
|
||||||
# 64-bit integer square root (bit-by-bit); @fn_fx_sin reads a 256-entry Q16.16
|
# 64-bit integer square root (bit-by-bit); @lp_fx_sin reads a 256-entry Q16.16
|
||||||
# sine table with linear interpolation; @fn_fx_exp2/@fn_fx_log2 are range-reduced
|
# sine table with linear interpolation; @lp_fx_exp2/@lp_fx_log2 are range-reduced
|
||||||
# Q16.16 polynomials (base-2 exp and log) that back exp/log/pow. All are pure
|
# Q16.16 polynomials (base-2 exp and log) that back exp/log/pow. All are pure
|
||||||
# integer IR, so bit-identical on every platform.
|
# integer IR, so bit-identical on every platform.
|
||||||
function emit_math_prelude() -> void {
|
function emit_math_prelude() -> void {
|
||||||
emith("@L_sin_tab = private unnamed_addr constant [256 x i32] [i32 0, i32 1608, i32 3216, i32 4821, i32 6424, i32 8022, i32 9616, i32 11204, i32 12785, i32 14359, i32 15924, i32 17479, i32 19024, i32 20557, i32 22078, i32 23586, i32 25080, i32 26558, i32 28020, i32 29466, i32 30893, i32 32303, i32 33692, i32 35062, i32 36410, i32 37736, i32 39040, i32 40320, i32 41576, i32 42806, i32 44011, i32 45190, i32 46341, i32 47464, i32 48559, i32 49624, i32 50660, i32 51665, i32 52639, i32 53581, i32 54491, i32 55368, i32 56212, i32 57022, i32 57798, i32 58538, i32 59244, i32 59914, i32 60547, i32 61145, i32 61705, i32 62228, i32 62714, i32 63162, i32 63572, i32 63944, i32 64277, i32 64571, i32 64827, i32 65043, i32 65220, i32 65358, i32 65457, i32 65516, i32 65536, i32 65516, i32 65457, i32 65358, i32 65220, i32 65043, i32 64827, i32 64571, i32 64277, i32 63944, i32 63572, i32 63162, i32 62714, i32 62228, i32 61705, i32 61145, i32 60547, i32 59914, i32 59244, i32 58538, i32 57798, i32 57022, i32 56212, i32 55368, i32 54491, i32 53581, i32 52639, i32 51665, i32 50660, i32 49624, i32 48559, i32 47464, i32 46341, i32 45190, i32 44011, i32 42806, i32 41576, i32 40320, i32 39040, i32 37736, i32 36410, i32 35062, i32 33692, i32 32303, i32 30893, i32 29466, i32 28020, i32 26558, i32 25080, i32 23586, i32 22078, i32 20557, i32 19024, i32 17479, i32 15924, i32 14359, i32 12785, i32 11204, i32 9616, i32 8022, i32 6424, i32 4821, i32 3216, i32 1608, i32 0, i32 -1608, i32 -3216, i32 -4821, i32 -6424, i32 -8022, i32 -9616, i32 -11204, i32 -12785, i32 -14359, i32 -15924, i32 -17479, i32 -19024, i32 -20557, i32 -22078, i32 -23586, i32 -25080, i32 -26558, i32 -28020, i32 -29466, i32 -30893, i32 -32303, i32 -33692, i32 -35062, i32 -36410, i32 -37736, i32 -39040, i32 -40320, i32 -41576, i32 -42806, i32 -44011, i32 -45190, i32 -46341, i32 -47464, i32 -48559, i32 -49624, i32 -50660, i32 -51665, i32 -52639, i32 -53581, i32 -54491, i32 -55368, i32 -56212, i32 -57022, i32 -57798, i32 -58538, i32 -59244, i32 -59914, i32 -60547, i32 -61145, i32 -61705, i32 -62228, i32 -62714, i32 -63162, i32 -63572, i32 -63944, i32 -64277, i32 -64571, i32 -64827, i32 -65043, i32 -65220, i32 -65358, i32 -65457, i32 -65516, i32 -65536, i32 -65516, i32 -65457, i32 -65358, i32 -65220, i32 -65043, i32 -64827, i32 -64571, i32 -64277, i32 -63944, i32 -63572, i32 -63162, i32 -62714, i32 -62228, i32 -61705, i32 -61145, i32 -60547, i32 -59914, i32 -59244, i32 -58538, i32 -57798, i32 -57022, i32 -56212, i32 -55368, i32 -54491, i32 -53581, i32 -52639, i32 -51665, i32 -50660, i32 -49624, i32 -48559, i32 -47464, i32 -46341, i32 -45190, i32 -44011, i32 -42806, i32 -41576, i32 -40320, i32 -39040, i32 -37736, i32 -36410, i32 -35062, i32 -33692, i32 -32303, i32 -30893, i32 -29466, i32 -28020, i32 -26558, i32 -25080, i32 -23586, i32 -22078, i32 -20557, i32 -19024, i32 -17479, i32 -15924, i32 -14359, i32 -12785, i32 -11204, i32 -9616, i32 -8022, i32 -6424, i32 -4821, i32 -3216, i32 -1608]\n")
|
emith("@L_sin_tab = private unnamed_addr constant [256 x i32] [i32 0, i32 1608, i32 3216, i32 4821, i32 6424, i32 8022, i32 9616, i32 11204, i32 12785, i32 14359, i32 15924, i32 17479, i32 19024, i32 20557, i32 22078, i32 23586, i32 25080, i32 26558, i32 28020, i32 29466, i32 30893, i32 32303, i32 33692, i32 35062, i32 36410, i32 37736, i32 39040, i32 40320, i32 41576, i32 42806, i32 44011, i32 45190, i32 46341, i32 47464, i32 48559, i32 49624, i32 50660, i32 51665, i32 52639, i32 53581, i32 54491, i32 55368, i32 56212, i32 57022, i32 57798, i32 58538, i32 59244, i32 59914, i32 60547, i32 61145, i32 61705, i32 62228, i32 62714, i32 63162, i32 63572, i32 63944, i32 64277, i32 64571, i32 64827, i32 65043, i32 65220, i32 65358, i32 65457, i32 65516, i32 65536, i32 65516, i32 65457, i32 65358, i32 65220, i32 65043, i32 64827, i32 64571, i32 64277, i32 63944, i32 63572, i32 63162, i32 62714, i32 62228, i32 61705, i32 61145, i32 60547, i32 59914, i32 59244, i32 58538, i32 57798, i32 57022, i32 56212, i32 55368, i32 54491, i32 53581, i32 52639, i32 51665, i32 50660, i32 49624, i32 48559, i32 47464, i32 46341, i32 45190, i32 44011, i32 42806, i32 41576, i32 40320, i32 39040, i32 37736, i32 36410, i32 35062, i32 33692, i32 32303, i32 30893, i32 29466, i32 28020, i32 26558, i32 25080, i32 23586, i32 22078, i32 20557, i32 19024, i32 17479, i32 15924, i32 14359, i32 12785, i32 11204, i32 9616, i32 8022, i32 6424, i32 4821, i32 3216, i32 1608, i32 0, i32 -1608, i32 -3216, i32 -4821, i32 -6424, i32 -8022, i32 -9616, i32 -11204, i32 -12785, i32 -14359, i32 -15924, i32 -17479, i32 -19024, i32 -20557, i32 -22078, i32 -23586, i32 -25080, i32 -26558, i32 -28020, i32 -29466, i32 -30893, i32 -32303, i32 -33692, i32 -35062, i32 -36410, i32 -37736, i32 -39040, i32 -40320, i32 -41576, i32 -42806, i32 -44011, i32 -45190, i32 -46341, i32 -47464, i32 -48559, i32 -49624, i32 -50660, i32 -51665, i32 -52639, i32 -53581, i32 -54491, i32 -55368, i32 -56212, i32 -57022, i32 -57798, i32 -58538, i32 -59244, i32 -59914, i32 -60547, i32 -61145, i32 -61705, i32 -62228, i32 -62714, i32 -63162, i32 -63572, i32 -63944, i32 -64277, i32 -64571, i32 -64827, i32 -65043, i32 -65220, i32 -65358, i32 -65457, i32 -65516, i32 -65536, i32 -65516, i32 -65457, i32 -65358, i32 -65220, i32 -65043, i32 -64827, i32 -64571, i32 -64277, i32 -63944, i32 -63572, i32 -63162, i32 -62714, i32 -62228, i32 -61705, i32 -61145, i32 -60547, i32 -59914, i32 -59244, i32 -58538, i32 -57798, i32 -57022, i32 -56212, i32 -55368, i32 -54491, i32 -53581, i32 -52639, i32 -51665, i32 -50660, i32 -49624, i32 -48559, i32 -47464, i32 -46341, i32 -45190, i32 -44011, i32 -42806, i32 -41576, i32 -40320, i32 -39040, i32 -37736, i32 -36410, i32 -35062, i32 -33692, i32 -32303, i32 -30893, i32 -29466, i32 -28020, i32 -26558, i32 -25080, i32 -23586, i32 -22078, i32 -20557, i32 -19024, i32 -17479, i32 -15924, i32 -14359, i32 -12785, i32 -11204, i32 -9616, i32 -8022, i32 -6424, i32 -4821, i32 -3216, i32 -1608]\n")
|
||||||
emith("define i32 @fn_fx_sqrt(i32 %x) {\n")
|
emith("define i32 @lp_fx_sqrt(i32 %x) {\n")
|
||||||
emith("entry:\n %neg = icmp slt i32 %x, 0\n br i1 %neg, label %ret0, label %go\n")
|
emith("entry:\n %neg = icmp slt i32 %x, 0\n br i1 %neg, label %ret0, label %go\n")
|
||||||
emith("ret0:\n ret i32 0\n")
|
emith("ret0:\n ret i32 0\n")
|
||||||
emith("go:\n %x64 = sext i32 %x to i64\n %n0 = shl i64 %x64, 16\n")
|
emith("go:\n %x64 = sext i32 %x to i64\n %n0 = shl i64 %x64, 16\n")
|
||||||
|
|
@ -300,14 +300,14 @@ function emit_math_prelude() -> void {
|
||||||
emith("shift:\n %rsh2 = lshr i64 %r1, 1\n store i64 %rsh2, ptr %rp\n br label %next\n")
|
emith("shift:\n %rsh2 = lshr i64 %r1, 1\n store i64 %rsh2, ptr %rp\n br label %next\n")
|
||||||
emith("next:\n %b4 = lshr i64 %b3, 2\n store i64 %b4, ptr %bp\n br label %loop\n")
|
emith("next:\n %b4 = lshr i64 %b3, 2\n store i64 %b4, ptr %bp\n br label %loop\n")
|
||||||
emith("done:\n %rf = load i64, ptr %rp\n %r32 = trunc i64 %rf to i32\n ret i32 %r32\n}\n")
|
emith("done:\n %rf = load i64, ptr %rp\n %r32 = trunc i64 %rf to i32\n ret i32 %r32\n}\n")
|
||||||
emith("define i32 @fn_fx_sin(i32 %x) {\n")
|
emith("define i32 @lp_fx_sin(i32 %x) {\n")
|
||||||
emith(" %xe = sext i32 %x to i64\n %m = mul i64 %xe, 2670177\n %idxf = ashr i64 %m, 16\n")
|
emith(" %xe = sext i32 %x to i64\n %m = mul i64 %xe, 2670177\n %idxf = ashr i64 %m, 16\n")
|
||||||
emith(" %i0 = ashr i64 %idxf, 16\n %i0m = and i64 %i0, 255\n %frac = and i64 %idxf, 65535\n")
|
emith(" %i0 = ashr i64 %idxf, 16\n %i0m = and i64 %i0, 255\n %frac = and i64 %idxf, 65535\n")
|
||||||
emith(" %i1 = add i64 %i0m, 1\n %i1m = and i64 %i1, 255\n")
|
emith(" %i1 = add i64 %i0m, 1\n %i1m = and i64 %i1, 255\n")
|
||||||
emith(" %p0 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i0m\n %v0 = load i32, ptr %p0\n")
|
emith(" %p0 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i0m\n %v0 = load i32, ptr %p0\n")
|
||||||
emith(" %p1 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i1m\n %v1 = load i32, ptr %p1\n")
|
emith(" %p1 = getelementptr [256 x i32], ptr @L_sin_tab, i64 0, i64 %i1m\n %v1 = load i32, ptr %p1\n")
|
||||||
emith(" %d = sub i32 %v1, %v0\n %de = sext i32 %d to i64\n %dm = mul i64 %de, %frac\n %dsh = ashr i64 %dm, 16\n %dsh32 = trunc i64 %dsh to i32\n %res = add i32 %v0, %dsh32\n ret i32 %res\n}\n")
|
emith(" %d = sub i32 %v1, %v0\n %de = sext i32 %d to i64\n %dm = mul i64 %de, %frac\n %dsh = ashr i64 %dm, 16\n %dsh32 = trunc i64 %dsh to i32\n %res = add i32 %v0, %dsh32\n ret i32 %res\n}\n")
|
||||||
emith("define i32 @fn_fx_atan2(i32 %y, i32 %x) {\n")
|
emith("define i32 @lp_fx_atan2(i32 %y, i32 %x) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %xz = icmp eq i32 %x, 0\n")
|
emith(" %xz = icmp eq i32 %x, 0\n")
|
||||||
emith(" %yz = icmp eq i32 %y, 0\n")
|
emith(" %yz = icmp eq i32 %y, 0\n")
|
||||||
|
|
@ -363,11 +363,11 @@ function emit_math_prelude() -> void {
|
||||||
emith(" %res = select i1 %yneg, i32 %angneg, i32 %angle\n")
|
emith(" %res = select i1 %yneg, i32 %angneg, i32 %angle\n")
|
||||||
emith(" ret i32 %res\n")
|
emith(" ret i32 %res\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
# @fn_fx_exp2(x) = 2^x, Q16.16. Split x into integer part i and fraction f in
|
# @lp_fx_exp2(x) = 2^x, Q16.16. Split x into integer part i and fraction f in
|
||||||
# [0,1); 2^f is a 5th-order Taylor polynomial (Horner, coefficients (ln2)^k/k!
|
# [0,1); 2^f is a 5th-order Taylor polynomial (Horner, coefficients (ln2)^k/k!
|
||||||
# in Q16.16), then shift by i. Shift amounts are clamped to a safe [0,31] so a
|
# in Q16.16), then shift by i. Shift amounts are clamped to a safe [0,31] so a
|
||||||
# huge exponent saturates instead of hitting an undefined shift.
|
# huge exponent saturates instead of hitting an undefined shift.
|
||||||
emith("define i32 @fn_fx_exp2(i32 %x) {\n")
|
emith("define i32 @lp_fx_exp2(i32 %x) {\n")
|
||||||
emith(" %i = ashr i32 %x, 16\n %f = and i32 %x, 65535\n %fe = sext i32 %f to i64\n")
|
emith(" %i = ashr i32 %x, 16\n %f = and i32 %x, 65535\n %fe = sext i32 %f to i64\n")
|
||||||
emith(" %m5 = mul i64 %fe, 87\n %s5 = ashr i64 %m5, 16\n %p5 = add i64 %s5, 630\n")
|
emith(" %m5 = mul i64 %fe, 87\n %s5 = ashr i64 %m5, 16\n %p5 = add i64 %s5, 630\n")
|
||||||
emith(" %m4 = mul i64 %fe, %p5\n %s4 = ashr i64 %m4, 16\n %p4 = add i64 %s4, 3638\n")
|
emith(" %m4 = mul i64 %fe, %p5\n %s4 = ashr i64 %m4, 16\n %p4 = add i64 %s4, 3638\n")
|
||||||
|
|
@ -381,12 +381,12 @@ function emit_math_prelude() -> void {
|
||||||
emith(" %ni = sub i32 0, %i\n %ra0 = select i1 %ipos, i32 0, i32 %ni\n %rahi = icmp sgt i32 %ra0, 31\n %ra = select i1 %rahi, i32 31, i32 %ra0\n")
|
emith(" %ni = sub i32 0, %i\n %ra0 = select i1 %ipos, i32 0, i32 %ni\n %rahi = icmp sgt i32 %ra0, 31\n %ra = select i1 %rahi, i32 31, i32 %ra0\n")
|
||||||
emith(" %shr = ashr i32 %p, %ra\n")
|
emith(" %shr = ashr i32 %p, %ra\n")
|
||||||
emith(" %res = select i1 %ipos, i32 %shl, i32 %shr\n ret i32 %res\n}\n")
|
emith(" %res = select i1 %ipos, i32 %shl, i32 %shr\n ret i32 %res\n}\n")
|
||||||
# @fn_fx_log2(x) = log2(x), Q16.16, for x > 0 (x <= 0 saturates to the most
|
# @lp_fx_log2(x) = log2(x), Q16.16, for x > 0 (x <= 0 saturates to the most
|
||||||
# negative i32). ctlz finds the MSB, giving the integer part e and a mantissa
|
# negative i32). ctlz finds the MSB, giving the integer part e and a mantissa
|
||||||
# m in [1,2); ln(m) uses the fast-converging atanh series on r = (m-1)/(m+1),
|
# m in [1,2); ln(m) uses the fast-converging atanh series on r = (m-1)/(m+1),
|
||||||
# then log2(m) = ln(m)/ln2. Result is e + log2(m).
|
# then log2(m) = ln(m)/ln2. Result is e + log2(m).
|
||||||
emith("declare i32 @llvm.ctlz.i32(i32, i1)\n")
|
emith("declare i32 @llvm.ctlz.i32(i32, i1)\n")
|
||||||
emith("define i32 @fn_fx_log2(i32 %x) {\n")
|
emith("define i32 @lp_fx_log2(i32 %x) {\n")
|
||||||
emith("entry:\n %pos = icmp sgt i32 %x, 0\n br i1 %pos, label %go, label %neg\n")
|
emith("entry:\n %pos = icmp sgt i32 %x, 0\n br i1 %pos, label %go, label %neg\n")
|
||||||
emith("neg:\n ret i32 -2147483648\n")
|
emith("neg:\n ret i32 -2147483648\n")
|
||||||
emith("go:\n %lz = call i32 @llvm.ctlz.i32(i32 %x, i1 true)\n %pmsb = sub i32 31, %lz\n")
|
emith("go:\n %lz = call i32 @llvm.ctlz.i32(i32 %x, i1 true)\n %pmsb = sub i32 31, %lz\n")
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ function net_model_bytes(m: Node) -> int {
|
||||||
let c = find_comp(m.kids[ci].s)
|
let c = find_comp(m.kids[ci].s)
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
var fj = 0
|
var fj = 0
|
||||||
while fj < len(c.kids) { if c.kids[fj].ival == 1 { total = total + net_field_ibytes(c.kids[fj].ty) }; fj = fj + 1 }
|
while fj < len(c.kids) { if c.kids[fj].ival == 1 { total += net_field_ibytes(c.kids[fj].ty) }; fj += 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ci = ci + 1
|
ci += 1
|
||||||
}
|
}
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
@ -43,14 +43,14 @@ function net_model_syncs(m: Node) -> bool { return net_model_bytes(m) > 0 }
|
||||||
|
|
||||||
function net_has_sync() -> bool {
|
function net_has_sync() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_ARCH { if net_model_syncs(prog[i]) { return true } }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_ARCH { if net_model_syncs(prog[i]) { return true } }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- N3: ownership -----------------------------------------------------------
|
# ---- N3: ownership -----------------------------------------------------------
|
||||||
function net_has_owned() -> bool {
|
function net_has_owned() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_ARCH and (prog[i].ival == 1) { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_ARCH and (prog[i].ival == 1) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ function net_has_owned() -> bool {
|
||||||
# A handler tagged @Server (ival==1) or @Predicted (ival==2) has a network role.
|
# A handler tagged @Server (ival==1) or @Predicted (ival==2) has a network role.
|
||||||
function net_has_role() -> bool {
|
function net_has_role() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) { if prog[i].kind == N_SYS and (prog[i].ival != 0) { return true }; i = i + 1 }
|
while i < len(prog) { if prog[i].kind == N_SYS and (prog[i].ival != 0) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,15 +95,15 @@ function net_check() -> void {
|
||||||
any = true
|
any = true
|
||||||
if (llty(c.kids[fj].ty) == "ptr") { perr(`@Sync field {cn}.{c.kids[fj].s} is not a POD scalar (networked fields must be int/bool/fixed/byte)`) }
|
if (llty(c.kids[fj].ty) == "ptr") { perr(`@Sync field {cn}.{c.kids[fj].s} is not a POD scalar (networked fields must be int/bool/fixed/byte)`) }
|
||||||
}
|
}
|
||||||
fj = fj + 1
|
fj += 1
|
||||||
}
|
}
|
||||||
if not any { net_warn(`model {m.s} @Syncs {cn} but it has no @Sync fields — nothing replicates`) }
|
if not any { net_warn(`model {m.s} @Syncs {cn} but it has no @Sync fields — nothing replicates`) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ci = ci + 1
|
ci += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,13 +129,13 @@ function emit_net_serialize(m: Node) -> void {
|
||||||
let fa = nreg(); emit(" "); emit(fa); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(s); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n")
|
let fa = nreg(); emit(" "); emit(fa); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(s); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n")
|
||||||
let dst = nreg(); emit(" "); emit(dst); emit(" = getelementptr inbounds i8, ptr %buf, i32 "); emit(itoa(off)); emit("\n")
|
let dst = nreg(); emit(" "); emit(dst); emit(" = getelementptr inbounds i8, ptr %buf, i32 "); emit(itoa(off)); emit("\n")
|
||||||
emit(" call ptr @memcpy(ptr "); emit(dst); emit(", ptr "); emit(fa); emit(", i64 "); emit(bytes); emit(")\n")
|
emit(" call ptr @memcpy(ptr "); emit(dst); emit(", ptr "); emit(fa); emit(", i64 "); emit(bytes); emit(")\n")
|
||||||
off = off + net_field_ibytes(c.kids[fj].ty)
|
off += net_field_ibytes(c.kids[fj].ty)
|
||||||
}
|
}
|
||||||
fj = fj + 1
|
fj += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ci = ci + 1
|
ci += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 "); emit(itoa(off)); emit("\n}\n\n")
|
emit(" ret i32 "); emit(itoa(off)); emit("\n}\n\n")
|
||||||
}
|
}
|
||||||
|
|
@ -162,13 +162,13 @@ function emit_net_apply(m: Node) -> void {
|
||||||
let fa = nreg(); emit(" "); emit(fa); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(s); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n")
|
let fa = nreg(); emit(" "); emit(fa); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(s); emit(", i32 0, i32 "); emit(itoa(fj)); emit("\n")
|
||||||
let src = nreg(); emit(" "); emit(src); emit(" = getelementptr inbounds i8, ptr %buf, i32 "); emit(itoa(off)); emit("\n")
|
let src = nreg(); emit(" "); emit(src); emit(" = getelementptr inbounds i8, ptr %buf, i32 "); emit(itoa(off)); emit("\n")
|
||||||
emit(" call ptr @memcpy(ptr "); emit(fa); emit(", ptr "); emit(src); emit(", i64 "); emit(bytes); emit(")\n")
|
emit(" call ptr @memcpy(ptr "); emit(fa); emit(", ptr "); emit(src); emit(", i64 "); emit(bytes); emit(")\n")
|
||||||
off = off + net_field_ibytes(c.kids[fj].ty)
|
off += net_field_ibytes(c.kids[fj].ty)
|
||||||
}
|
}
|
||||||
fj = fj + 1
|
fj += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ci = ci + 1
|
ci += 1
|
||||||
}
|
}
|
||||||
emit(" ret void\n}\n\n")
|
emit(" ret void\n}\n\n")
|
||||||
}
|
}
|
||||||
|
|
@ -191,9 +191,9 @@ function emit_net_dispatch() -> void {
|
||||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||||
emit("h"); emit(sk); emit(":\n %r"); emit(sk); emit(" = call i32 @L_serialize_"); emit(mn); emit("(i32 %e, ptr %buf)\n ret i32 %r"); emit(sk); emit("\n")
|
emit("h"); emit(sk); emit(":\n %r"); emit(sk); emit(" = call i32 @L_serialize_"); emit(mn); emit("(i32 %e, ptr %buf)\n ret i32 %r"); emit(sk); emit("\n")
|
||||||
emit("n"); emit(sk); emit(":\n")
|
emit("n"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 0\n}\n\n")
|
emit(" ret i32 0\n}\n\n")
|
||||||
|
|
||||||
|
|
@ -207,9 +207,9 @@ function emit_net_dispatch() -> void {
|
||||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||||
emit("h"); emit(sk); emit(":\n call void @L_apply_"); emit(mn); emit("(i32 %e, ptr %buf, i32 %len)\n ret void\n")
|
emit("h"); emit(sk); emit(":\n call void @L_apply_"); emit(mn); emit("(i32 %e, ptr %buf, i32 %len)\n ret void\n")
|
||||||
emit("n"); emit(sk); emit(":\n")
|
emit("n"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret void\n}\n\n")
|
emit(" ret void\n}\n\n")
|
||||||
|
|
||||||
|
|
@ -225,9 +225,9 @@ function emit_net_dispatch() -> void {
|
||||||
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
emit(" br i1 %c"); emit(sk); emit(", label %h"); emit(sk); emit(", label %n"); emit(sk); emit("\n")
|
||||||
emit("h"); emit(sk); emit(":\n ret i32 "); emit(itoa(net_model_bytes(prog[i]))); emit("\n")
|
emit("h"); emit(sk); emit(":\n ret i32 "); emit(itoa(net_model_bytes(prog[i]))); emit("\n")
|
||||||
emit("n"); emit(sk); emit(":\n")
|
emit("n"); emit(sk); emit(":\n")
|
||||||
k = k + 1
|
k += 1
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit(" ret i32 0\n}\n\n")
|
emit(" ret i32 0\n}\n\n")
|
||||||
}
|
}
|
||||||
|
|
@ -296,14 +296,14 @@ function emit_loopback() -> void {
|
||||||
|
|
||||||
function net_has_remote() -> bool {
|
function net_has_remote() -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_events) { if (g_events[i].ty != null) { return true }; i = i + 1 }
|
while i < len(g_events) { if (g_events[i].ty != null) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
# stable wire id for an event = its index in g_events (same program both peers)
|
# stable wire id for an event = its index in g_events (same program both peers)
|
||||||
function net_event_id(name: pointer) -> int {
|
function net_event_id(name: pointer) -> int {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_events) { if (g_events[i].s == name) { return i }; i = i + 1 }
|
while i < len(g_events) { if (g_events[i].s == name) { return i }; i += 1 }
|
||||||
return 0 - 1
|
return -1
|
||||||
}
|
}
|
||||||
# the transport symbols: an `extern fn` override, else the built-in loopback.
|
# the transport symbols: an `extern fn` override, else the built-in loopback.
|
||||||
function net_send_sym() -> pointer { let x = find_extern("net_send"); if (x != null) { return x.a.s }; return "L_net_send" }
|
function net_send_sym() -> pointer { let x = find_extern("net_send"); if (x != null) { return x.a.s }; return "L_net_send" }
|
||||||
|
|
@ -338,14 +338,14 @@ function emit_net_pump() -> void {
|
||||||
emit(" %fv"); emit(fk); emit(" = load "); emit(ft); emit(", ptr %fa"); emit(fk); emit("\n")
|
emit(" %fv"); emit(fk); emit(" = load "); emit(ft); emit(", ptr %fa"); emit(fk); emit("\n")
|
||||||
if f > 0 { buf_puts(acc, ", ") }
|
if f > 0 { buf_puts(acc, ", ") }
|
||||||
buf_puts(acc, ft); buf_puts(acc, " %fv"); buf_puts(acc, fk)
|
buf_puts(acc, ft); buf_puts(acc, " %fv"); buf_puts(acc, fk)
|
||||||
off = off + net_field_ibytes(ev.kids[f].ty)
|
off += net_field_ibytes(ev.kids[f].ty)
|
||||||
f = f + 1
|
f += 1
|
||||||
}
|
}
|
||||||
emit(" call void @ev_"); emit(ev.s); emit("("); emit(buf_str(acc)); emit(")\n")
|
emit(" call void @ev_"); emit(ev.s); emit("("); emit(buf_str(acc)); emit(")\n")
|
||||||
emit(" br label %loop\n")
|
emit(" br label %loop\n")
|
||||||
emit("x"); emit(sk); emit(":\n")
|
emit("x"); emit(sk); emit(":\n")
|
||||||
}
|
}
|
||||||
e = e + 1
|
e += 1
|
||||||
}
|
}
|
||||||
emit(" br label %loop\n") # unknown id: skip, keep draining
|
emit(" br label %loop\n") # unknown id: skip, keep draining
|
||||||
emit("fin:\n ret void\n}\n\n")
|
emit("fin:\n ret void\n}\n\n")
|
||||||
|
|
@ -361,7 +361,7 @@ function emit_net() -> void {
|
||||||
emit_net_serialize(prog[i])
|
emit_net_serialize(prog[i])
|
||||||
emit_net_apply(prog[i])
|
emit_net_apply(prog[i])
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
emit_net_dispatch()
|
emit_net_dispatch()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,114 +36,114 @@ function emit_noise_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_noisert = true
|
g_uses_noisert = true
|
||||||
if (meth == "value2") {
|
if (meth == "value2") {
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call i32 @fn_noise_value2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_noise_value2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "perlin2") {
|
if (meth == "perlin2") {
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call i32 @fn_noise_perlin2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_noise_perlin2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "simplex2") {
|
if (meth == "simplex2") {
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call i32 @fn_noise_simplex2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_noise_simplex2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "fbm2") {
|
if (meth == "fbm2") {
|
||||||
g_uses_mathrt = true # simplex path is standalone; fbm needs fx_div only (local)
|
g_uses_mathrt = true # simplex path is standalone; fbm needs fx_div only (local)
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2]); let o = emit_expr(e.kids[3])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2]); let o = emit_expr(e.kids[3])
|
||||||
return val(emit_bind(`call i32 @fn_noise_fbm2(i32 {x.code}, i32 {y.code}, i32 {s.code}, i32 {o.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_noise_fbm2(i32 {x.code}, i32 {y.code}, i32 {s.code}, i32 {o.code})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "cellular2") {
|
if (meth == "cellular2") {
|
||||||
g_uses_mathrt = true # F1 distance needs @fn_fx_sqrt
|
g_uses_mathrt = true # F1 distance needs @lp_fx_sqrt
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call i32 @fn_noise_cellular2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
return val(emit_bind(`call i32 @lp_noise_cellular2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||||
}
|
}
|
||||||
# cellular2_id: the hash id of the nearest feature cell (stable per cell -> use
|
# cellular2_id: the hash id of the nearest feature cell (stable per cell -> use
|
||||||
# it to pick a biome/material). Distances come from cellular2.
|
# it to pick a biome/material). Distances come from cellular2.
|
||||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call i32 @fn_noise_cellular2_id(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "int")
|
return val(emit_bind(`call i32 @lp_noise_cellular2_id(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "int")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_noise_prelude — the noise runtime, emitted once per program that uses
|
# emit_noise_prelude — the noise runtime, emitted once per program that uses
|
||||||
# Noise.* (g_uses_noisert). Pure Q16.16 integer IR; cellular/fbm additionally use
|
# Noise.* (g_uses_noisert). Pure Q16.16 integer IR; cellular/fbm additionally use
|
||||||
# the math prelude (@fn_fx_sqrt), pulled in by setting g_uses_mathrt at the call.
|
# the math prelude (@lp_fx_sqrt), pulled in by setting g_uses_mathrt at the call.
|
||||||
function emit_noise_prelude() -> void {
|
function emit_noise_prelude() -> void {
|
||||||
# Q16.16 helpers (local to noise so the prelude is self-contained for the
|
# Q16.16 helpers (local to noise so the prelude is self-contained for the
|
||||||
# gradient/value paths). fx multiply, divide, lerp, and a [-1,1] clamp.
|
# gradient/value paths). fx multiply, divide, lerp, and a [-1,1] clamp.
|
||||||
emith("define i32 @fn_nfx_mul(i32 %a, i32 %b) {\n")
|
emith("define i32 @lp_nfx_mul(i32 %a, i32 %b) {\n")
|
||||||
emith(" %a64 = sext i32 %a to i64\n %b64 = sext i32 %b to i64\n %m = mul i64 %a64, %b64\n %s = ashr i64 %m, 16\n %r = trunc i64 %s to i32\n ret i32 %r\n}\n")
|
emith(" %a64 = sext i32 %a to i64\n %b64 = sext i32 %b to i64\n %m = mul i64 %a64, %b64\n %s = ashr i64 %m, 16\n %r = trunc i64 %s to i32\n ret i32 %r\n}\n")
|
||||||
emith("define i32 @fn_nfx_div(i32 %a, i32 %b) {\n")
|
emith("define i32 @lp_nfx_div(i32 %a, i32 %b) {\n")
|
||||||
emith(" %z = icmp eq i32 %b, 0\n br i1 %z, label %zero, label %go\n")
|
emith(" %z = icmp eq i32 %b, 0\n br i1 %z, label %zero, label %go\n")
|
||||||
emith("zero:\n ret i32 0\n")
|
emith("zero:\n ret i32 0\n")
|
||||||
emith("go:\n %a64 = sext i32 %a to i64\n %ash = shl i64 %a64, 16\n %b64 = sext i32 %b to i64\n %d = sdiv i64 %ash, %b64\n %r = trunc i64 %d to i32\n ret i32 %r\n}\n")
|
emith("go:\n %a64 = sext i32 %a to i64\n %ash = shl i64 %a64, 16\n %b64 = sext i32 %b to i64\n %d = sdiv i64 %ash, %b64\n %r = trunc i64 %d to i32\n ret i32 %r\n}\n")
|
||||||
emith("define i32 @fn_nfx_lerp(i32 %a, i32 %b, i32 %t) {\n")
|
emith("define i32 @lp_nfx_lerp(i32 %a, i32 %b, i32 %t) {\n")
|
||||||
emith(" %d = sub i32 %b, %a\n %dt = call i32 @fn_nfx_mul(i32 %d, i32 %t)\n %r = add i32 %a, %dt\n ret i32 %r\n}\n")
|
emith(" %d = sub i32 %b, %a\n %dt = call i32 @lp_nfx_mul(i32 %d, i32 %t)\n %r = add i32 %a, %dt\n ret i32 %r\n}\n")
|
||||||
emith("define i32 @fn_noise_clamp(i32 %v) {\n")
|
emith("define i32 @lp_noise_clamp(i32 %v) {\n")
|
||||||
emith(" %hi = icmp sgt i32 %v, 65536\n %v1 = select i1 %hi, i32 65536, i32 %v\n %lo = icmp slt i32 %v1, -65536\n %r = select i1 %lo, i32 -65536, i32 %v1\n ret i32 %r\n}\n")
|
emith(" %hi = icmp sgt i32 %v, 65536\n %v1 = select i1 %hi, i32 65536, i32 %v\n %lo = icmp slt i32 %v1, -65536\n %r = select i1 %lo, i32 -65536, i32 %v1\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# integer lattice hash: mix seed + cell coords with large odd constants, then a
|
# integer lattice hash: mix seed + cell coords with large odd constants, then a
|
||||||
# MurmurHash3-style fmix32 finalizer. Deterministic and well-distributed.
|
# MurmurHash3-style fmix32 finalizer. Deterministic and well-distributed.
|
||||||
emith("define i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi) {\n")
|
emith("define i32 @lp_noise_hash(i32 %seed, i32 %xi, i32 %yi) {\n")
|
||||||
emith(" %a = mul i32 %xi, 374761393\n %b = mul i32 %yi, -1028477387\n %c = add i32 %seed, %a\n %d0 = add i32 %c, %b\n")
|
emith(" %a = mul i32 %xi, 374761393\n %b = mul i32 %yi, -1028477387\n %c = add i32 %seed, %a\n %d0 = add i32 %c, %b\n")
|
||||||
emith(" %e = lshr i32 %d0, 16\n %f = xor i32 %d0, %e\n %g = mul i32 %f, -2048144789\n")
|
emith(" %e = lshr i32 %d0, 16\n %f = xor i32 %d0, %e\n %g = mul i32 %f, -2048144789\n")
|
||||||
emith(" %h = lshr i32 %g, 13\n %i = xor i32 %g, %h\n %j = mul i32 %i, -1028477387\n")
|
emith(" %h = lshr i32 %g, 13\n %i = xor i32 %g, %h\n %j = mul i32 %i, -1028477387\n")
|
||||||
emith(" %k = lshr i32 %j, 16\n %l = xor i32 %j, %k\n ret i32 %l\n}\n")
|
emith(" %k = lshr i32 %j, 16\n %l = xor i32 %j, %k\n ret i32 %l\n}\n")
|
||||||
|
|
||||||
# quintic fade 6t^5 - 15t^4 + 10t^3 (Q16.16); t in [0,1]
|
# quintic fade 6t^5 - 15t^4 + 10t^3 (Q16.16); t in [0,1]
|
||||||
emith("define i32 @fn_noise_fade(i32 %t) {\n")
|
emith("define i32 @lp_noise_fade(i32 %t) {\n")
|
||||||
emith(" %t2 = call i32 @fn_nfx_mul(i32 %t, i32 %t)\n %t3 = call i32 @fn_nfx_mul(i32 %t2, i32 %t)\n")
|
emith(" %t2 = call i32 @lp_nfx_mul(i32 %t, i32 %t)\n %t3 = call i32 @lp_nfx_mul(i32 %t2, i32 %t)\n")
|
||||||
emith(" %t4 = call i32 @fn_nfx_mul(i32 %t3, i32 %t)\n %t5 = call i32 @fn_nfx_mul(i32 %t4, i32 %t)\n")
|
emith(" %t4 = call i32 @lp_nfx_mul(i32 %t3, i32 %t)\n %t5 = call i32 @lp_nfx_mul(i32 %t4, i32 %t)\n")
|
||||||
emith(" %c6 = mul i32 %t5, 6\n %c15 = mul i32 %t4, 15\n %c10 = mul i32 %t3, 10\n")
|
emith(" %c6 = mul i32 %t5, 6\n %c15 = mul i32 %t4, 15\n %c10 = mul i32 %t3, 10\n")
|
||||||
emith(" %s1 = sub i32 %c6, %c15\n %r = add i32 %s1, %c10\n ret i32 %r\n}\n")
|
emith(" %s1 = sub i32 %c6, %c15\n %r = add i32 %s1, %c10\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# value noise: bilinear-interpolate the four corner random values (each mapped
|
# value noise: bilinear-interpolate the four corner random values (each mapped
|
||||||
# to [-1,1]) with the faded fractional coordinates.
|
# to [-1,1]) with the faded fractional coordinates.
|
||||||
emith("define i32 @fn_noise_value2(i32 %x, i32 %y, i32 %seed) {\n")
|
emith("define i32 @lp_noise_value2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||||
emith(" %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n %xf = and i32 %x, 65535\n %yf = and i32 %y, 65535\n")
|
emith(" %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n %xf = and i32 %x, 65535\n %yf = and i32 %y, 65535\n")
|
||||||
emith(" %u = call i32 @fn_noise_fade(i32 %xf)\n %v = call i32 @fn_noise_fade(i32 %yf)\n")
|
emith(" %u = call i32 @lp_noise_fade(i32 %xf)\n %v = call i32 @lp_noise_fade(i32 %yf)\n")
|
||||||
emith(" %xi1 = add i32 %xi, 1\n %yi1 = add i32 %yi, 1\n")
|
emith(" %xi1 = add i32 %xi, 1\n %yi1 = add i32 %yi, 1\n")
|
||||||
emith(" %h00 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi)\n %m00 = and i32 %h00, 131071\n %n00 = sub i32 %m00, 65536\n")
|
emith(" %h00 = call i32 @lp_noise_hash(i32 %seed, i32 %xi, i32 %yi)\n %m00 = and i32 %h00, 131071\n %n00 = sub i32 %m00, 65536\n")
|
||||||
emith(" %h10 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi)\n %m10 = and i32 %h10, 131071\n %n10 = sub i32 %m10, 65536\n")
|
emith(" %h10 = call i32 @lp_noise_hash(i32 %seed, i32 %xi1, i32 %yi)\n %m10 = and i32 %h10, 131071\n %n10 = sub i32 %m10, 65536\n")
|
||||||
emith(" %h01 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi1)\n %m01 = and i32 %h01, 131071\n %n01 = sub i32 %m01, 65536\n")
|
emith(" %h01 = call i32 @lp_noise_hash(i32 %seed, i32 %xi, i32 %yi1)\n %m01 = and i32 %h01, 131071\n %n01 = sub i32 %m01, 65536\n")
|
||||||
emith(" %h11 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi1)\n %m11 = and i32 %h11, 131071\n %n11 = sub i32 %m11, 65536\n")
|
emith(" %h11 = call i32 @lp_noise_hash(i32 %seed, i32 %xi1, i32 %yi1)\n %m11 = and i32 %h11, 131071\n %n11 = sub i32 %m11, 65536\n")
|
||||||
emith(" %a = call i32 @fn_nfx_lerp(i32 %n00, i32 %n10, i32 %u)\n %b = call i32 @fn_nfx_lerp(i32 %n01, i32 %n11, i32 %u)\n")
|
emith(" %a = call i32 @lp_nfx_lerp(i32 %n00, i32 %n10, i32 %u)\n %b = call i32 @lp_nfx_lerp(i32 %n01, i32 %n11, i32 %u)\n")
|
||||||
emith(" %n = call i32 @fn_nfx_lerp(i32 %a, i32 %b, i32 %v)\n %r = call i32 @fn_noise_clamp(i32 %n)\n ret i32 %r\n}\n")
|
emith(" %n = call i32 @lp_nfx_lerp(i32 %a, i32 %b, i32 %v)\n %r = call i32 @lp_noise_clamp(i32 %n)\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# 8 gradient directions (axis + diagonal, the diagonals scaled by 1/sqrt2), as
|
# 8 gradient directions (axis + diagonal, the diagonals scaled by 1/sqrt2), as
|
||||||
# packed (gx, gy) Q16.16 pairs; grad2 dots the selected gradient with (dx, dy).
|
# packed (gx, gy) Q16.16 pairs; grad2 dots the selected gradient with (dx, dy).
|
||||||
emith("@noise_grad2 = private unnamed_addr constant [16 x i32] [i32 65536, i32 0, i32 -65536, i32 0, i32 0, i32 65536, i32 0, i32 -65536, i32 46341, i32 46341, i32 -46341, i32 46341, i32 46341, i32 -46341, i32 -46341, i32 -46341]\n")
|
emith("@noise_grad2 = private unnamed_addr constant [16 x i32] [i32 65536, i32 0, i32 -65536, i32 0, i32 0, i32 65536, i32 0, i32 -65536, i32 46341, i32 46341, i32 -46341, i32 46341, i32 46341, i32 -46341, i32 -46341, i32 -46341]\n")
|
||||||
emith("define i32 @fn_noise_grad2(i32 %hash, i32 %dx, i32 %dy) {\n")
|
emith("define i32 @lp_noise_grad2(i32 %hash, i32 %dx, i32 %dy) {\n")
|
||||||
emith(" %h = and i32 %hash, 7\n %idx = shl i32 %h, 1\n %idx64 = sext i32 %idx to i64\n")
|
emith(" %h = and i32 %hash, 7\n %idx = shl i32 %h, 1\n %idx64 = sext i32 %idx to i64\n")
|
||||||
emith(" %gxp = getelementptr [16 x i32], ptr @noise_grad2, i64 0, i64 %idx64\n %gx = load i32, ptr %gxp\n")
|
emith(" %gxp = getelementptr [16 x i32], ptr @noise_grad2, i64 0, i64 %idx64\n %gx = load i32, ptr %gxp\n")
|
||||||
emith(" %idy = add i32 %idx, 1\n %idy64 = sext i32 %idy to i64\n %gyp = getelementptr [16 x i32], ptr @noise_grad2, i64 0, i64 %idy64\n %gy = load i32, ptr %gyp\n")
|
emith(" %idy = add i32 %idx, 1\n %idy64 = sext i32 %idy to i64\n %gyp = getelementptr [16 x i32], ptr @noise_grad2, i64 0, i64 %idy64\n %gy = load i32, ptr %gyp\n")
|
||||||
emith(" %px = call i32 @fn_nfx_mul(i32 %gx, i32 %dx)\n %py = call i32 @fn_nfx_mul(i32 %gy, i32 %dy)\n %r = add i32 %px, %py\n ret i32 %r\n}\n")
|
emith(" %px = call i32 @lp_nfx_mul(i32 %gx, i32 %dx)\n %py = call i32 @lp_nfx_mul(i32 %gy, i32 %dy)\n %r = add i32 %px, %py\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# Perlin gradient noise: interpolate the four corner gradient dots, then scale
|
# Perlin gradient noise: interpolate the four corner gradient dots, then scale
|
||||||
# the ~[-0.707,0.707] result by sqrt2 into [-1,1] (and clamp for safety).
|
# the ~[-0.707,0.707] result by sqrt2 into [-1,1] (and clamp for safety).
|
||||||
emith("define i32 @fn_noise_perlin2(i32 %x, i32 %y, i32 %seed) {\n")
|
emith("define i32 @lp_noise_perlin2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||||
emith(" %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n %xf = and i32 %x, 65535\n %yf = and i32 %y, 65535\n")
|
emith(" %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n %xf = and i32 %x, 65535\n %yf = and i32 %y, 65535\n")
|
||||||
emith(" %xf1 = sub i32 %xf, 65536\n %yf1 = sub i32 %yf, 65536\n")
|
emith(" %xf1 = sub i32 %xf, 65536\n %yf1 = sub i32 %yf, 65536\n")
|
||||||
emith(" %u = call i32 @fn_noise_fade(i32 %xf)\n %v = call i32 @fn_noise_fade(i32 %yf)\n")
|
emith(" %u = call i32 @lp_noise_fade(i32 %xf)\n %v = call i32 @lp_noise_fade(i32 %yf)\n")
|
||||||
emith(" %xi1 = add i32 %xi, 1\n %yi1 = add i32 %yi, 1\n")
|
emith(" %xi1 = add i32 %xi, 1\n %yi1 = add i32 %yi, 1\n")
|
||||||
emith(" %h00 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi)\n %h10 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi)\n")
|
emith(" %h00 = call i32 @lp_noise_hash(i32 %seed, i32 %xi, i32 %yi)\n %h10 = call i32 @lp_noise_hash(i32 %seed, i32 %xi1, i32 %yi)\n")
|
||||||
emith(" %h01 = call i32 @fn_noise_hash(i32 %seed, i32 %xi, i32 %yi1)\n %h11 = call i32 @fn_noise_hash(i32 %seed, i32 %xi1, i32 %yi1)\n")
|
emith(" %h01 = call i32 @lp_noise_hash(i32 %seed, i32 %xi, i32 %yi1)\n %h11 = call i32 @lp_noise_hash(i32 %seed, i32 %xi1, i32 %yi1)\n")
|
||||||
emith(" %g00 = call i32 @fn_noise_grad2(i32 %h00, i32 %xf, i32 %yf)\n %g10 = call i32 @fn_noise_grad2(i32 %h10, i32 %xf1, i32 %yf)\n")
|
emith(" %g00 = call i32 @lp_noise_grad2(i32 %h00, i32 %xf, i32 %yf)\n %g10 = call i32 @lp_noise_grad2(i32 %h10, i32 %xf1, i32 %yf)\n")
|
||||||
emith(" %g01 = call i32 @fn_noise_grad2(i32 %h01, i32 %xf, i32 %yf1)\n %g11 = call i32 @fn_noise_grad2(i32 %h11, i32 %xf1, i32 %yf1)\n")
|
emith(" %g01 = call i32 @lp_noise_grad2(i32 %h01, i32 %xf, i32 %yf1)\n %g11 = call i32 @lp_noise_grad2(i32 %h11, i32 %xf1, i32 %yf1)\n")
|
||||||
emith(" %a = call i32 @fn_nfx_lerp(i32 %g00, i32 %g10, i32 %u)\n %b = call i32 @fn_nfx_lerp(i32 %g01, i32 %g11, i32 %u)\n")
|
emith(" %a = call i32 @lp_nfx_lerp(i32 %g00, i32 %g10, i32 %u)\n %b = call i32 @lp_nfx_lerp(i32 %g01, i32 %g11, i32 %u)\n")
|
||||||
emith(" %n = call i32 @fn_nfx_lerp(i32 %a, i32 %b, i32 %v)\n %sc = call i32 @fn_nfx_mul(i32 %n, i32 92682)\n %r = call i32 @fn_noise_clamp(i32 %sc)\n ret i32 %r\n}\n")
|
emith(" %n = call i32 @lp_nfx_lerp(i32 %a, i32 %b, i32 %v)\n %sc = call i32 @lp_nfx_mul(i32 %n, i32 92682)\n %r = call i32 @lp_noise_clamp(i32 %sc)\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# one simplex corner contribution: t = 0.5 - x^2 - y^2; if t <= 0 -> 0, else
|
# one simplex corner contribution: t = 0.5 - x^2 - y^2; if t <= 0 -> 0, else
|
||||||
# t^4 * grad(hash, x, y). Kept as a helper so simplex2 reads as three corners.
|
# t^4 * grad(hash, x, y). Kept as a helper so simplex2 reads as three corners.
|
||||||
emith("define i32 @fn_noise_scorner(i32 %hash, i32 %dx, i32 %dy) {\n")
|
emith("define i32 @lp_noise_scorner(i32 %hash, i32 %dx, i32 %dy) {\n")
|
||||||
emith(" %xx = call i32 @fn_nfx_mul(i32 %dx, i32 %dx)\n %yy = call i32 @fn_nfx_mul(i32 %dy, i32 %dy)\n")
|
emith(" %xx = call i32 @lp_nfx_mul(i32 %dx, i32 %dx)\n %yy = call i32 @lp_nfx_mul(i32 %dy, i32 %dy)\n")
|
||||||
emith(" %s0 = sub i32 32768, %xx\n %t = sub i32 %s0, %yy\n %neg = icmp sle i32 %t, 0\n br i1 %neg, label %zero, label %go\n")
|
emith(" %s0 = sub i32 32768, %xx\n %t = sub i32 %s0, %yy\n %neg = icmp sle i32 %t, 0\n br i1 %neg, label %zero, label %go\n")
|
||||||
emith("zero:\n ret i32 0\n")
|
emith("zero:\n ret i32 0\n")
|
||||||
emith("go:\n %t2 = call i32 @fn_nfx_mul(i32 %t, i32 %t)\n %t4 = call i32 @fn_nfx_mul(i32 %t2, i32 %t2)\n")
|
emith("go:\n %t2 = call i32 @lp_nfx_mul(i32 %t, i32 %t)\n %t4 = call i32 @lp_nfx_mul(i32 %t2, i32 %t2)\n")
|
||||||
emith(" %g = call i32 @fn_noise_grad2(i32 %hash, i32 %dx, i32 %dy)\n %r = call i32 @fn_nfx_mul(i32 %t4, i32 %g)\n ret i32 %r\n}\n")
|
emith(" %g = call i32 @lp_noise_grad2(i32 %hash, i32 %dx, i32 %dy)\n %r = call i32 @lp_nfx_mul(i32 %t4, i32 %g)\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# 2D simplex noise (skewed triangular lattice). F2 = (sqrt3-1)/2 = 23994,
|
# 2D simplex noise (skewed triangular lattice). F2 = (sqrt3-1)/2 = 23994,
|
||||||
# G2 = (3-sqrt3)/6 = 13849 in Q16.16. Sum of three corner contributions, scaled
|
# G2 = (3-sqrt3)/6 = 13849 in Q16.16. Sum of three corner contributions, scaled
|
||||||
# into [-1,1] and clamped.
|
# into [-1,1] and clamped.
|
||||||
emith("define i32 @fn_noise_simplex2(i32 %x, i32 %y, i32 %seed) {\n")
|
emith("define i32 @lp_noise_simplex2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||||
emith(" %sum0 = add i32 %x, %y\n %skew = call i32 @fn_nfx_mul(i32 %sum0, i32 23994)\n")
|
emith(" %sum0 = add i32 %x, %y\n %skew = call i32 @lp_nfx_mul(i32 %sum0, i32 23994)\n")
|
||||||
emith(" %xs = add i32 %x, %skew\n %ys = add i32 %y, %skew\n %i = ashr i32 %xs, 16\n %j = ashr i32 %ys, 16\n")
|
emith(" %xs = add i32 %x, %skew\n %ys = add i32 %y, %skew\n %i = ashr i32 %xs, 16\n %j = ashr i32 %ys, 16\n")
|
||||||
emith(" %ij = add i32 %i, %j\n %tt = mul i32 %ij, 13849\n") # t = (i+j)*G2, fixed
|
emith(" %ij = add i32 %i, %j\n %tt = mul i32 %ij, 13849\n") # t = (i+j)*G2, fixed
|
||||||
emith(" %if0 = shl i32 %i, 16\n %jf0 = shl i32 %j, 16\n")
|
emith(" %if0 = shl i32 %i, 16\n %jf0 = shl i32 %j, 16\n")
|
||||||
|
|
@ -153,30 +153,30 @@ function emit_noise_prelude() -> void {
|
||||||
emith(" %x1a = sub i32 %x0, %i1f\n %x1 = add i32 %x1a, 13849\n %y1a = sub i32 %y0, %j1f\n %y1 = add i32 %y1a, 13849\n")
|
emith(" %x1a = sub i32 %x0, %i1f\n %x1 = add i32 %x1a, 13849\n %y1a = sub i32 %y0, %j1f\n %y1 = add i32 %y1a, 13849\n")
|
||||||
emith(" %x2a = sub i32 %x0, 65536\n %x2 = add i32 %x2a, 27698\n %y2a = sub i32 %y0, 65536\n %y2 = add i32 %y2a, 27698\n")
|
emith(" %x2a = sub i32 %x0, 65536\n %x2 = add i32 %x2a, 27698\n %y2a = sub i32 %y0, 65536\n %y2 = add i32 %y2a, 27698\n")
|
||||||
emith(" %i1p = add i32 %i, %i1\n %j1p = add i32 %j, %j1\n %i2 = add i32 %i, 1\n %j2 = add i32 %j, 1\n")
|
emith(" %i1p = add i32 %i, %i1\n %j1p = add i32 %j, %j1\n %i2 = add i32 %i, 1\n %j2 = add i32 %j, 1\n")
|
||||||
emith(" %gi0 = call i32 @fn_noise_hash(i32 %seed, i32 %i, i32 %j)\n %gi1 = call i32 @fn_noise_hash(i32 %seed, i32 %i1p, i32 %j1p)\n %gi2 = call i32 @fn_noise_hash(i32 %seed, i32 %i2, i32 %j2)\n")
|
emith(" %gi0 = call i32 @lp_noise_hash(i32 %seed, i32 %i, i32 %j)\n %gi1 = call i32 @lp_noise_hash(i32 %seed, i32 %i1p, i32 %j1p)\n %gi2 = call i32 @lp_noise_hash(i32 %seed, i32 %i2, i32 %j2)\n")
|
||||||
emith(" %n0 = call i32 @fn_noise_scorner(i32 %gi0, i32 %x0, i32 %y0)\n %n1 = call i32 @fn_noise_scorner(i32 %gi1, i32 %x1, i32 %y1)\n %n2 = call i32 @fn_noise_scorner(i32 %gi2, i32 %x2, i32 %y2)\n")
|
emith(" %n0 = call i32 @lp_noise_scorner(i32 %gi0, i32 %x0, i32 %y0)\n %n1 = call i32 @lp_noise_scorner(i32 %gi1, i32 %x1, i32 %y1)\n %n2 = call i32 @lp_noise_scorner(i32 %gi2, i32 %x2, i32 %y2)\n")
|
||||||
emith(" %sa = add i32 %n0, %n1\n %sb = add i32 %sa, %n2\n")
|
emith(" %sa = add i32 %n0, %n1\n %sb = add i32 %sa, %n2\n")
|
||||||
emith(" %sc = mul i32 %sb, 45\n %r = call i32 @fn_noise_clamp(i32 %sc)\n ret i32 %r\n}\n")
|
emith(" %sc = mul i32 %sb, 45\n %r = call i32 @lp_noise_clamp(i32 %sc)\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# fractal Brownian motion: sum `oct` octaves of simplex at rising frequency
|
# fractal Brownian motion: sum `oct` octaves of simplex at rising frequency
|
||||||
# (lacunarity 2.0) and falling amplitude (gain 0.5), normalised by total
|
# (lacunarity 2.0) and falling amplitude (gain 0.5), normalised by total
|
||||||
# amplitude so the result stays in [-1,1]. seed varies per octave.
|
# amplitude so the result stays in [-1,1]. seed varies per octave.
|
||||||
emith("define i32 @fn_noise_fbm2(i32 %x, i32 %y, i32 %seed, i32 %oct) {\n")
|
emith("define i32 @lp_noise_fbm2(i32 %x, i32 %y, i32 %seed, i32 %oct) {\n")
|
||||||
emith("entry:\n %sump = alloca i32\n %normp = alloca i32\n %ampp = alloca i32\n %freqp = alloca i32\n %op = alloca i32\n")
|
emith("entry:\n %sump = alloca i32\n %normp = alloca i32\n %ampp = alloca i32\n %freqp = alloca i32\n %op = alloca i32\n")
|
||||||
emith(" store i32 0, ptr %sump\n store i32 0, ptr %normp\n store i32 65536, ptr %ampp\n store i32 65536, ptr %freqp\n store i32 0, ptr %op\n br label %cond\n")
|
emith(" store i32 0, ptr %sump\n store i32 0, ptr %normp\n store i32 65536, ptr %ampp\n store i32 65536, ptr %freqp\n store i32 0, ptr %op\n br label %cond\n")
|
||||||
emith("cond:\n %o = load i32, ptr %op\n %lt = icmp slt i32 %o, %oct\n br i1 %lt, label %body, label %done\n")
|
emith("cond:\n %o = load i32, ptr %op\n %lt = icmp slt i32 %o, %oct\n br i1 %lt, label %body, label %done\n")
|
||||||
emith("body:\n %freq = load i32, ptr %freqp\n %amp = load i32, ptr %ampp\n")
|
emith("body:\n %freq = load i32, ptr %freqp\n %amp = load i32, ptr %ampp\n")
|
||||||
emith(" %fx = call i32 @fn_nfx_mul(i32 %x, i32 %freq)\n %fy = call i32 @fn_nfx_mul(i32 %y, i32 %freq)\n")
|
emith(" %fx = call i32 @lp_nfx_mul(i32 %x, i32 %freq)\n %fy = call i32 @lp_nfx_mul(i32 %y, i32 %freq)\n")
|
||||||
emith(" %so = add i32 %seed, %o\n %n = call i32 @fn_noise_simplex2(i32 %fx, i32 %fy, i32 %so)\n")
|
emith(" %so = add i32 %seed, %o\n %n = call i32 @lp_noise_simplex2(i32 %fx, i32 %fy, i32 %so)\n")
|
||||||
emith(" %na = call i32 @fn_nfx_mul(i32 %n, i32 %amp)\n %sum = load i32, ptr %sump\n %sum2 = add i32 %sum, %na\n store i32 %sum2, ptr %sump\n")
|
emith(" %na = call i32 @lp_nfx_mul(i32 %n, i32 %amp)\n %sum = load i32, ptr %sump\n %sum2 = add i32 %sum, %na\n store i32 %sum2, ptr %sump\n")
|
||||||
emith(" %norm = load i32, ptr %normp\n %norm2 = add i32 %norm, %amp\n store i32 %norm2, ptr %normp\n")
|
emith(" %norm = load i32, ptr %normp\n %norm2 = add i32 %norm, %amp\n store i32 %norm2, ptr %normp\n")
|
||||||
emith(" %amp2 = call i32 @fn_nfx_mul(i32 %amp, i32 32768)\n store i32 %amp2, ptr %ampp\n")
|
emith(" %amp2 = call i32 @lp_nfx_mul(i32 %amp, i32 32768)\n store i32 %amp2, ptr %ampp\n")
|
||||||
emith(" %freq2 = call i32 @fn_nfx_mul(i32 %freq, i32 131072)\n store i32 %freq2, ptr %freqp\n")
|
emith(" %freq2 = call i32 @lp_nfx_mul(i32 %freq, i32 131072)\n store i32 %freq2, ptr %freqp\n")
|
||||||
emith(" %o1 = add i32 %o, 1\n store i32 %o1, ptr %op\n br label %cond\n")
|
emith(" %o1 = add i32 %o, 1\n store i32 %o1, ptr %op\n br label %cond\n")
|
||||||
emith("done:\n %fsum = load i32, ptr %sump\n %fnorm = load i32, ptr %normp\n")
|
emith("done:\n %fsum = load i32, ptr %sump\n %fnorm = load i32, ptr %normp\n")
|
||||||
emith(" %nz = icmp eq i32 %fnorm, 0\n br i1 %nz, label %z, label %div\n")
|
emith(" %nz = icmp eq i32 %fnorm, 0\n br i1 %nz, label %z, label %div\n")
|
||||||
emith("z:\n ret i32 0\n")
|
emith("z:\n ret i32 0\n")
|
||||||
emith("div:\n %d = call i32 @fn_nfx_div(i32 %fsum, i32 %fnorm)\n %r = call i32 @fn_noise_clamp(i32 %d)\n ret i32 %r\n}\n")
|
emith("div:\n %d = call i32 @lp_nfx_div(i32 %fsum, i32 %fnorm)\n %r = call i32 @lp_noise_clamp(i32 %d)\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# Worley / cellular noise: scan the 3x3 neighbourhood of cells, each holding one
|
# Worley / cellular noise: scan the 3x3 neighbourhood of cells, each holding one
|
||||||
# feature point placed by its cell hash, and return the distance to (and id of)
|
# feature point placed by its cell hash, and return the distance to (and id of)
|
||||||
|
|
@ -188,7 +188,7 @@ function emit_noise_prelude() -> void {
|
||||||
function emit_noise_cellular() -> void {
|
function emit_noise_cellular() -> void {
|
||||||
# core scan -> writes the min squared distance to %d2out and the winning id to
|
# core scan -> writes the min squared distance to %d2out and the winning id to
|
||||||
# %idout (both caller-allocated), so both public entry points share one loop.
|
# %idout (both caller-allocated), so both public entry points share one loop.
|
||||||
emith("define void @fn_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2out, ptr %idout) {\n")
|
emith("define void @lp_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2out, ptr %idout) {\n")
|
||||||
emith("entry:\n %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n")
|
emith("entry:\n %xi = ashr i32 %x, 16\n %yi = ashr i32 %y, 16\n")
|
||||||
emith(" %bestp = alloca i32\n %idp = alloca i32\n %dyp = alloca i32\n %dxp = alloca i32\n")
|
emith(" %bestp = alloca i32\n %idp = alloca i32\n %dyp = alloca i32\n %dxp = alloca i32\n")
|
||||||
emith(" store i32 2147483647, ptr %bestp\n store i32 0, ptr %idp\n store i32 -1, ptr %dyp\n br label %yc\n")
|
emith(" store i32 2147483647, ptr %bestp\n store i32 0, ptr %idp\n store i32 -1, ptr %dyp\n br label %yc\n")
|
||||||
|
|
@ -196,22 +196,22 @@ function emit_noise_cellular() -> void {
|
||||||
emith("yb:\n store i32 -1, ptr %dxp\n br label %xc\n")
|
emith("yb:\n store i32 -1, ptr %dxp\n br label %xc\n")
|
||||||
emith("xc:\n %ox = load i32, ptr %dxp\n %xok = icmp sle i32 %ox, 1\n br i1 %xok, label %xb, label %xdone\n")
|
emith("xc:\n %ox = load i32, ptr %dxp\n %xok = icmp sle i32 %ox, 1\n br i1 %xok, label %xb, label %xdone\n")
|
||||||
emith("xb:\n %cx = add i32 %xi, %ox\n %cy = add i32 %yi, %oy\n")
|
emith("xb:\n %cx = add i32 %xi, %ox\n %cy = add i32 %yi, %oy\n")
|
||||||
emith(" %h = call i32 @fn_noise_hash(i32 %seed, i32 %cx, i32 %cy)\n")
|
emith(" %h = call i32 @lp_noise_hash(i32 %seed, i32 %cx, i32 %cy)\n")
|
||||||
emith(" %fxr = and i32 %h, 65535\n %hs = lshr i32 %h, 16\n %fyr = and i32 %hs, 65535\n")
|
emith(" %fxr = and i32 %h, 65535\n %hs = lshr i32 %h, 16\n %fyr = and i32 %hs, 65535\n")
|
||||||
emith(" %cxf = shl i32 %cx, 16\n %cyf = shl i32 %cy, 16\n %pxr = add i32 %cxf, %fxr\n %pyr = add i32 %cyf, %fyr\n")
|
emith(" %cxf = shl i32 %cx, 16\n %cyf = shl i32 %cy, 16\n %pxr = add i32 %cxf, %fxr\n %pyr = add i32 %cyf, %fyr\n")
|
||||||
emith(" %ddx = sub i32 %pxr, %x\n %ddy = sub i32 %pyr, %y\n")
|
emith(" %ddx = sub i32 %pxr, %x\n %ddy = sub i32 %pyr, %y\n")
|
||||||
emith(" %dxx = call i32 @fn_nfx_mul(i32 %ddx, i32 %ddx)\n %dyy = call i32 @fn_nfx_mul(i32 %ddy, i32 %ddy)\n %d2 = add i32 %dxx, %dyy\n")
|
emith(" %dxx = call i32 @lp_nfx_mul(i32 %ddx, i32 %ddx)\n %dyy = call i32 @lp_nfx_mul(i32 %ddy, i32 %ddy)\n %d2 = add i32 %dxx, %dyy\n")
|
||||||
emith(" %best = load i32, ptr %bestp\n %less = icmp slt i32 %d2, %best\n br i1 %less, label %upd, label %skip\n")
|
emith(" %best = load i32, ptr %bestp\n %less = icmp slt i32 %d2, %best\n br i1 %less, label %upd, label %skip\n")
|
||||||
emith("upd:\n store i32 %d2, ptr %bestp\n store i32 %h, ptr %idp\n br label %skip\n")
|
emith("upd:\n store i32 %d2, ptr %bestp\n store i32 %h, ptr %idp\n br label %skip\n")
|
||||||
emith("skip:\n %ox1 = add i32 %ox, 1\n store i32 %ox1, ptr %dxp\n br label %xc\n")
|
emith("skip:\n %ox1 = add i32 %ox, 1\n store i32 %ox1, ptr %dxp\n br label %xc\n")
|
||||||
emith("xdone:\n %oy1 = add i32 %oy, 1\n store i32 %oy1, ptr %dyp\n br label %yc\n")
|
emith("xdone:\n %oy1 = add i32 %oy, 1\n store i32 %oy1, ptr %dyp\n br label %yc\n")
|
||||||
emith("ydone:\n %fb = load i32, ptr %bestp\n store i32 %fb, ptr %d2out\n %fi = load i32, ptr %idp\n store i32 %fi, ptr %idout\n ret void\n}\n")
|
emith("ydone:\n %fb = load i32, ptr %bestp\n store i32 %fb, ptr %d2out\n %fi = load i32, ptr %idp\n store i32 %fi, ptr %idout\n ret void\n}\n")
|
||||||
|
|
||||||
emith("define i32 @fn_noise_cellular2(i32 %x, i32 %y, i32 %seed) {\n")
|
emith("define i32 @lp_noise_cellular2(i32 %x, i32 %y, i32 %seed) {\n")
|
||||||
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @fn_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @lp_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
||||||
emith(" %v = load i32, ptr %d2\n %r = call i32 @fn_fx_sqrt(i32 %v)\n ret i32 %r\n}\n")
|
emith(" %v = load i32, ptr %d2\n %r = call i32 @lp_fx_sqrt(i32 %v)\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
emith("define i32 @fn_noise_cellular2_id(i32 %x, i32 %y, i32 %seed) {\n")
|
emith("define i32 @lp_noise_cellular2_id(i32 %x, i32 %y, i32 %seed) {\n")
|
||||||
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @fn_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @lp_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
||||||
emith(" %r = load i32, ptr %id\n ret i32 %r\n}\n")
|
emith(" %r = load i32, ptr %id\n ret i32 %r\n}\n")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
# Os.set_env(name, val) -> bool set it (true on success)
|
# Os.set_env(name, val) -> bool set it (true on success)
|
||||||
# Os.unset_env(name) -> bool remove it (true on success)
|
# Os.unset_env(name) -> bool remove it (true on success)
|
||||||
# Os.exit(code) terminate the process with a status code
|
# Os.exit(code) terminate the process with a status code
|
||||||
|
# Os.pid() -> int this process's id (unique among live processes)
|
||||||
# Os.platform() -> string "macos" | "linux" | ...(raw uname sysname)
|
# Os.platform() -> string "macos" | "linux" | ...(raw uname sysname)
|
||||||
# Os.arch() -> string machine arch, e.g. "arm64" | "x86_64"
|
# Os.arch() -> string machine arch, e.g. "arm64" | "x86_64"
|
||||||
# Os.stdout_write(s) write a string to standard output
|
# Os.stdout_write(s) write a string to standard output
|
||||||
|
|
@ -35,15 +36,15 @@ function is_os_ns(meth: pointer) -> bool {
|
||||||
if (meth == "args") or (meth == "arg_count") or (meth == "arg") { return true }
|
if (meth == "args") or (meth == "arg_count") or (meth == "arg") { return true }
|
||||||
if (meth == "env") or (meth == "env_or") or (meth == "has_env") { return true }
|
if (meth == "env") or (meth == "env_or") or (meth == "has_env") { return true }
|
||||||
if (meth == "set_env") or (meth == "unset_env") { return true }
|
if (meth == "set_env") or (meth == "unset_env") { return true }
|
||||||
if (meth == "exit") or (meth == "platform") or (meth == "arch") { return true }
|
if (meth == "exit") or (meth == "pid") or (meth == "platform") or (meth == "arch") { return true }
|
||||||
if (meth == "stdout_write") or (meth == "stderr_write") { return true }
|
if (meth == "stdout_write") or (meth == "stderr_write") { return true }
|
||||||
if (meth == "save_dir") or (meth == "config_dir") or (meth == "cache_dir") or (meth == "temp_dir") { return true }
|
if (meth == "save_dir") or (meth == "config_dir") or (meth == "cache_dir") or (meth == "temp_dir") { return true }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function emit_os_ns(meth: pointer, e: Node) -> Val {
|
function emit_os_ns(meth: pointer, e: Node) -> Val {
|
||||||
# arg_count / arg / exit stay light — they mirror the bare intrinsics and need
|
# arg_count / arg / exit / pid stay light — they mirror the bare intrinsics and
|
||||||
# no Os runtime prelude, so a program using only these emits no extra IR.
|
# need no Os runtime prelude, so a program using only these emits no extra IR.
|
||||||
if (meth == "arg_count") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
|
if (meth == "arg_count") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
|
||||||
if (meth == "arg") {
|
if (meth == "arg") {
|
||||||
let i = emit_expr(e.kids[0])
|
let i = emit_expr(e.kids[0])
|
||||||
|
|
@ -51,6 +52,7 @@ function emit_os_ns(meth: pointer, e: Node) -> Val {
|
||||||
let q = emit_bind(`getelementptr ptr, ptr {v}, i32 {i.code}`)
|
let q = emit_bind(`getelementptr ptr, ptr {v}, i32 {i.code}`)
|
||||||
return val(emit_bind(`load ptr, ptr {q}`), "string")
|
return val(emit_bind(`load ptr, ptr {q}`), "string")
|
||||||
}
|
}
|
||||||
|
if (meth == "pid") { g_uses_pid = true; return val(emit_bind("call i32 @getpid()"), "int") }
|
||||||
if (meth == "exit") {
|
if (meth == "exit") {
|
||||||
let n = emit_expr(e.kids[0])
|
let n = emit_expr(e.kids[0])
|
||||||
emit(` call void @exit(i32 {n.code})\n`)
|
emit(` call void @exit(i32 {n.code})\n`)
|
||||||
|
|
@ -79,10 +81,10 @@ function emit_os_ns(meth: pointer, e: Node) -> Val {
|
||||||
}
|
}
|
||||||
# everything below is served by the Os runtime prelude
|
# everything below is served by the Os runtime prelude
|
||||||
g_uses_osrt = true
|
g_uses_osrt = true
|
||||||
if (meth == "args") { return val(emit_bind("call ptr @fn_os_args()"), "[]string") }
|
if (meth == "args") { return val(emit_bind("call ptr @lp_os_args()"), "[]string") }
|
||||||
if (meth == "env_or") {
|
if (meth == "env_or") {
|
||||||
let nm = emit_expr(e.kids[0]); let fb = emit_expr(e.kids[1])
|
let nm = emit_expr(e.kids[0]); let fb = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_os_getenv_or(ptr {nm.code}, ptr {fb.code})`), "string")
|
return val(emit_bind(`call ptr @lp_os_getenv_or(ptr {nm.code}, ptr {fb.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "set_env") {
|
if (meth == "set_env") {
|
||||||
let nm = emit_expr(e.kids[0]); let v = emit_expr(e.kids[1])
|
let nm = emit_expr(e.kids[0]); let v = emit_expr(e.kids[1])
|
||||||
|
|
@ -96,22 +98,22 @@ function emit_os_ns(meth: pointer, e: Node) -> Val {
|
||||||
let ok = emit_bind(`icmp eq i32 {r}, 0`)
|
let ok = emit_bind(`icmp eq i32 {r}, 0`)
|
||||||
return val(emit_bind(`zext i1 {ok} to i32`), "bool")
|
return val(emit_bind(`zext i1 {ok} to i32`), "bool")
|
||||||
}
|
}
|
||||||
if (meth == "platform") { return val(emit_bind("call ptr @fn_os_platform()"), "string") }
|
if (meth == "platform") { return val(emit_bind("call ptr @lp_os_platform()"), "string") }
|
||||||
if (meth == "arch") { return val(emit_bind("call ptr @fn_os_arch()"), "string") }
|
if (meth == "arch") { return val(emit_bind("call ptr @lp_os_arch()"), "string") }
|
||||||
if (meth == "save_dir") {
|
if (meth == "save_dir") {
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_os_save_dir(ptr {a.code})`), "string")
|
return val(emit_bind(`call ptr @lp_os_save_dir(ptr {a.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "config_dir") {
|
if (meth == "config_dir") {
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_os_config_dir(ptr {a.code})`), "string")
|
return val(emit_bind(`call ptr @lp_os_config_dir(ptr {a.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "cache_dir") {
|
if (meth == "cache_dir") {
|
||||||
let a = emit_expr(e.kids[0])
|
let a = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_os_cache_dir(ptr {a.code})`), "string")
|
return val(emit_bind(`call ptr @lp_os_cache_dir(ptr {a.code})`), "string")
|
||||||
}
|
}
|
||||||
# temp_dir
|
# temp_dir
|
||||||
return val(emit_bind("call ptr @fn_os_temp_dir()"), "string")
|
return val(emit_bind("call ptr @lp_os_temp_dir()"), "string")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_os_prelude — the Os runtime, emitted once per program that uses the
|
# emit_os_prelude — the Os runtime, emitted once per program that uses the
|
||||||
|
|
@ -135,34 +137,34 @@ function emit_os_prelude() -> void {
|
||||||
let k_linux = emit_str_const("linux")
|
let k_linux = emit_str_const("linux")
|
||||||
|
|
||||||
# getenv(name) or a fallback when it is unset
|
# getenv(name) or a fallback when it is unset
|
||||||
emith("define ptr @fn_os_getenv_or(ptr %name, ptr %fb) {\n")
|
emith("define ptr @lp_os_getenv_or(ptr %name, ptr %fb) {\n")
|
||||||
emith("entry:\n %r = call ptr @getenv(ptr %name)\n %z = icmp eq ptr %r, null\n br i1 %z, label %use, label %got\n")
|
emith("entry:\n %r = call ptr @getenv(ptr %name)\n %z = icmp eq ptr %r, null\n br i1 %z, label %use, label %got\n")
|
||||||
emith("use:\n ret ptr %fb\n")
|
emith("use:\n ret ptr %fb\n")
|
||||||
emith("got:\n ret ptr %r\n}\n")
|
emith("got:\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
# concatenate two NUL-terminated strings into a fresh malloc'd buffer
|
# concatenate two NUL-terminated strings into a fresh malloc'd buffer
|
||||||
emith("define ptr @fn_os_join2(ptr %a, ptr %b) {\n")
|
emith("define ptr @lp_os_join2(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n")
|
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n")
|
||||||
emith(" %sum = add i64 %la, %lb\n %tot = add i64 %sum, 1\n %m = call ptr @malloc(i64 %tot)\n")
|
emith(" %sum = add i64 %la, %lb\n %tot = add i64 %sum, 1\n %m = call ptr @malloc(i64 %tot)\n")
|
||||||
emith(" call ptr @memcpy(ptr %m, ptr %a, i64 %la)\n")
|
emith(" call ptr @memcpy(ptr %m, ptr %a, i64 %la)\n")
|
||||||
emith(" %m2 = getelementptr i8, ptr %m, i64 %la\n call ptr @memcpy(ptr %m2, ptr %b, i64 %lb)\n")
|
emith(" %m2 = getelementptr i8, ptr %m, i64 %la\n call ptr @memcpy(ptr %m2, ptr %b, i64 %lb)\n")
|
||||||
emith(" %end = getelementptr i8, ptr %m, i64 %sum\n store i8 0, ptr %end\n ret ptr %m\n}\n")
|
emith(" %end = getelementptr i8, ptr %m, i64 %sum\n store i8 0, ptr %end\n ret ptr %m\n}\n")
|
||||||
|
|
||||||
emith("define ptr @fn_os_join3(ptr %a, ptr %b, ptr %c) {\n")
|
emith("define ptr @lp_os_join3(ptr %a, ptr %b, ptr %c) {\n")
|
||||||
emith("entry:\n %ab = call ptr @fn_os_join2(ptr %a, ptr %b)\n %r = call ptr @fn_os_join2(ptr %ab, ptr %c)\n ret ptr %r\n}\n")
|
emith("entry:\n %ab = call ptr @lp_os_join2(ptr %a, ptr %b)\n %r = call ptr @lp_os_join2(ptr %ab, ptr %c)\n ret ptr %r\n}\n")
|
||||||
|
|
||||||
# the user's home directory, or "." when HOME is unset
|
# the user's home directory, or "." when HOME is unset
|
||||||
emith(`define ptr @fn_os_home() {{\n %r = call ptr @fn_os_getenv_or(ptr {k_home}, ptr {k_dot})\n ret ptr %r\n}}\n`)
|
emith(`define ptr @lp_os_home() {{\n %r = call ptr @lp_os_getenv_or(ptr {k_home}, ptr {k_dot})\n ret ptr %r\n}}\n`)
|
||||||
|
|
||||||
# per-user known folders (macOS/BSD layout)
|
# per-user known folders (macOS/BSD layout)
|
||||||
emith(`define ptr @fn_os_save_dir(ptr %app) {{\n %h = call ptr @fn_os_home()\n %r = call ptr @fn_os_join3(ptr %h, ptr {k_appsp}, ptr %app)\n ret ptr %r\n}}\n`)
|
emith(`define ptr @lp_os_save_dir(ptr %app) {{\n %h = call ptr @lp_os_home()\n %r = call ptr @lp_os_join3(ptr %h, ptr {k_appsp}, ptr %app)\n ret ptr %r\n}}\n`)
|
||||||
emith(`define ptr @fn_os_config_dir(ptr %app) {{\n %h = call ptr @fn_os_home()\n %r = call ptr @fn_os_join3(ptr %h, ptr {k_appsp}, ptr %app)\n ret ptr %r\n}}\n`)
|
emith(`define ptr @lp_os_config_dir(ptr %app) {{\n %h = call ptr @lp_os_home()\n %r = call ptr @lp_os_join3(ptr %h, ptr {k_appsp}, ptr %app)\n ret ptr %r\n}}\n`)
|
||||||
emith(`define ptr @fn_os_cache_dir(ptr %app) {{\n %h = call ptr @fn_os_home()\n %r = call ptr @fn_os_join3(ptr %h, ptr {k_cache}, ptr %app)\n ret ptr %r\n}}\n`)
|
emith(`define ptr @lp_os_cache_dir(ptr %app) {{\n %h = call ptr @lp_os_home()\n %r = call ptr @lp_os_join3(ptr %h, ptr {k_cache}, ptr %app)\n ret ptr %r\n}}\n`)
|
||||||
emith(`define ptr @fn_os_temp_dir() {{\n %r = call ptr @fn_os_getenv_or(ptr {k_tmpk}, ptr {k_tmp})\n ret ptr %r\n}}\n`)
|
emith(`define ptr @lp_os_temp_dir() {{\n %r = call ptr @lp_os_getenv_or(ptr {k_tmpk}, ptr {k_tmp})\n ret ptr %r\n}}\n`)
|
||||||
|
|
||||||
# Os.args() -> a %LSlice of the argv strings (data = argv, len = cap = argc), a
|
# Os.args() -> a %LSlice of the argv strings (data = argv, len = cap = argc), a
|
||||||
# snapshot the caller may iterate or index like any other []string.
|
# snapshot the caller may iterate or index like any other []string.
|
||||||
emith("define ptr @fn_os_args() {\n")
|
emith("define ptr @lp_os_args() {\n")
|
||||||
emith("entry:\n %c = load i32, ptr @L_argc\n %v = load ptr, ptr @L_argv\n")
|
emith("entry:\n %c = load i32, ptr @L_argc\n %v = load ptr, ptr @L_argv\n")
|
||||||
emith(" %h = call ptr @malloc(i64 16)\n")
|
emith(" %h = call ptr @malloc(i64 16)\n")
|
||||||
emith(" %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n store ptr %v, ptr %d0\n")
|
emith(" %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n store ptr %v, ptr %d0\n")
|
||||||
|
|
@ -171,7 +173,7 @@ function emit_os_prelude() -> void {
|
||||||
emith(" ret ptr %h\n}\n")
|
emith(" ret ptr %h\n}\n")
|
||||||
|
|
||||||
# platform(): uname sysname (field 0, portable) mapped to a short id
|
# platform(): uname sysname (field 0, portable) mapped to a short id
|
||||||
emith("define ptr @fn_os_platform() {\n")
|
emith("define ptr @lp_os_platform() {\n")
|
||||||
emith("entry:\n %buf = call ptr @malloc(i64 8192)\n call i32 @uname(ptr %buf)\n")
|
emith("entry:\n %buf = call ptr @malloc(i64 8192)\n call i32 @uname(ptr %buf)\n")
|
||||||
emith(` %cd = call i32 @strncmp(ptr %buf, ptr {k_darw}, i64 6)\n %isd = icmp eq i32 %cd, 0\n br i1 %isd, label %mac, label %chkl\n`)
|
emith(` %cd = call i32 @strncmp(ptr %buf, ptr {k_darw}, i64 6)\n %isd = icmp eq i32 %cd, 0\n br i1 %isd, label %mac, label %chkl\n`)
|
||||||
emith(`mac:\n ret ptr {k_macos}\n`)
|
emith(`mac:\n ret ptr {k_macos}\n`)
|
||||||
|
|
@ -182,7 +184,7 @@ function emit_os_prelude() -> void {
|
||||||
# arch(): the uname `machine` field. On macOS/BSD utsname each field is 256
|
# arch(): the uname `machine` field. On macOS/BSD utsname each field is 256
|
||||||
# bytes, so `machine` (index 4) sits at offset 1024. Documented BSD-layout
|
# bytes, so `machine` (index 4) sits at offset 1024. Documented BSD-layout
|
||||||
# assumption (see the header note); other layouts are a follow-up.
|
# assumption (see the header note); other layouts are a follow-up.
|
||||||
emith("define ptr @fn_os_arch() {\n")
|
emith("define ptr @lp_os_arch() {\n")
|
||||||
emith("entry:\n %buf = call ptr @malloc(i64 8192)\n call i32 @uname(ptr %buf)\n")
|
emith("entry:\n %buf = call ptr @malloc(i64 8192)\n call i32 @uname(ptr %buf)\n")
|
||||||
emith(" %m = getelementptr i8, ptr %buf, i64 1024\n ret ptr %m\n}\n")
|
emith(" %m = getelementptr i8, ptr %buf, i64 1024\n ret ptr %m\n}\n")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,22 @@ function emit_text_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "from_int") { # int -> string, same as string(n)
|
if (meth == "from_int") { # int -> string, same as string(n)
|
||||||
let n = emit_expr(e.kids[0])
|
let n = emit_expr(e.kids[0])
|
||||||
g_uses_intstr = true
|
g_uses_intstr = true
|
||||||
return val(emit_bind(`call ptr @fn_int_str(i32 {n.code})`), "string")
|
return val(emit_bind(`call ptr @lp_int_str(i32 {n.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "slice") { # s[a..b], same substring helper
|
if (meth == "slice") { # s[a..b], same substring helper
|
||||||
let s0 = emit_expr(e.kids[0]); let a = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2])
|
let s0 = emit_expr(e.kids[0]); let a = emit_expr(e.kids[1]); let b = emit_expr(e.kids[2])
|
||||||
g_uses_strslice = true
|
g_uses_strslice = true
|
||||||
return val(emit_bind(`call ptr @fn_str_slice(ptr {s0.code}, i32 {a.code}, i32 {b.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_slice(ptr {s0.code}, i32 {a.code}, i32 {b.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "equals") { # byte-wise equality, same as ==
|
if (meth == "equals") { # byte-wise equality, same as ==
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
g_uses_str = true
|
g_uses_str = true
|
||||||
return val(emit_bind(`call i32 @fn_str_eq(ptr {a.code}, ptr {b.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_str_eq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||||
}
|
}
|
||||||
if (meth == "concat") { # a + b, same as the + operator
|
if (meth == "concat") { # a + b, same as the + operator
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
g_uses_str = true
|
g_uses_str = true
|
||||||
return val(emit_bind(`call ptr @fn_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
|
|
@ -52,45 +52,45 @@ function emit_text_ns(meth: pointer, e: Node) -> Val {
|
||||||
}
|
}
|
||||||
if (meth == "upper") { # ASCII a-z -> A-Z, fresh string
|
if (meth == "upper") { # ASCII a-z -> A-Z, fresh string
|
||||||
g_uses_textrt = true
|
g_uses_textrt = true
|
||||||
return val(emit_bind(`call ptr @fn_str_upper(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_upper(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "lower") { # ASCII A-Z -> a-z, fresh string
|
if (meth == "lower") { # ASCII A-Z -> a-z, fresh string
|
||||||
g_uses_textrt = true
|
g_uses_textrt = true
|
||||||
return val(emit_bind(`call ptr @fn_str_lower(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_lower(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "trim") { # drop leading/trailing whitespace
|
if (meth == "trim") { # drop leading/trailing whitespace
|
||||||
g_uses_textrt = true
|
g_uses_textrt = true
|
||||||
return val(emit_bind(`call ptr @fn_str_trim(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_trim(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "repeat") { # s repeated n times
|
if (meth == "repeat") { # s repeated n times
|
||||||
g_uses_textrt = true
|
g_uses_textrt = true
|
||||||
let n = emit_expr(e.kids[1])
|
let n = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_str_repeat(ptr {s.code}, i32 {n.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_repeat(ptr {s.code}, i32 {n.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "pad_left") { # pad with spaces to width, on the left
|
if (meth == "pad_left") { # pad with spaces to width, on the left
|
||||||
g_uses_textrt = true
|
g_uses_textrt = true
|
||||||
let w = emit_expr(e.kids[1])
|
let w = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_str_pad(ptr {s.code}, i32 {w.code}, i1 1)`), "string")
|
return val(emit_bind(`call ptr @lp_str_pad(ptr {s.code}, i32 {w.code}, i1 1)`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "pad_right") { # pad with spaces to width, on the right
|
if (meth == "pad_right") { # pad with spaces to width, on the right
|
||||||
g_uses_textrt = true
|
g_uses_textrt = true
|
||||||
let w = emit_expr(e.kids[1])
|
let w = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_str_pad(ptr {s.code}, i32 {w.code}, i1 0)`), "string")
|
return val(emit_bind(`call ptr @lp_str_pad(ptr {s.code}, i32 {w.code}, i1 0)`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "replace") { # replace every `from` with `to`
|
if (meth == "replace") { # replace every `from` with `to`
|
||||||
g_uses_textrt2 = true
|
g_uses_textrt2 = true
|
||||||
let from = emit_expr(e.kids[1]); let to = emit_expr(e.kids[2])
|
let from = emit_expr(e.kids[1]); let to = emit_expr(e.kids[2])
|
||||||
return val(emit_bind(`call ptr @fn_str_replace(ptr {s.code}, ptr {from.code}, ptr {to.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_replace(ptr {s.code}, ptr {from.code}, ptr {to.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "split") { # split on a separator -> []string
|
if (meth == "split") { # split on a separator -> []string
|
||||||
g_uses_textrt2 = true
|
g_uses_textrt2 = true
|
||||||
let sep = emit_expr(e.kids[1])
|
let sep = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_str_split(ptr {s.code}, ptr {sep.code})`), "[]string")
|
return val(emit_bind(`call ptr @lp_str_split(ptr {s.code}, ptr {sep.code})`), "[]string")
|
||||||
}
|
}
|
||||||
if (meth == "join") { # join a []string with a separator (s is the slice)
|
if (meth == "join") { # join a []string with a separator (s is the slice)
|
||||||
g_uses_textrt2 = true
|
g_uses_textrt2 = true
|
||||||
let sep = emit_expr(e.kids[1])
|
let sep = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_str_join(ptr {s.code}, ptr {sep.code})`), "string")
|
return val(emit_bind(`call ptr @lp_str_join(ptr {s.code}, ptr {sep.code})`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "contains") or (meth == "index_of") { # substring search
|
if (meth == "contains") or (meth == "index_of") { # substring search
|
||||||
let sub = emit_expr(e.kids[1])
|
let sub = emit_expr(e.kids[1])
|
||||||
|
|
@ -134,4 +134,3 @@ function emit_text_ns(meth: pointer, e: Node) -> Val {
|
||||||
emit(en); emit(":\n")
|
emit(en); emit(":\n")
|
||||||
return val(emit_bind(`load i32, ptr {res}`), "bool")
|
return val(emit_bind(`load i32, ptr {res}`), "bool")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# repeat/pad. Emitted once per program that uses them (g_uses_textrt). Plain
|
# repeat/pad. Emitted once per program that uses them (g_uses_textrt). Plain
|
||||||
# libc (strlen/malloc/memcpy), deterministic, C-string in and out.
|
# libc (strlen/malloc/memcpy), deterministic, C-string in and out.
|
||||||
function emit_text_prelude() -> void {
|
function emit_text_prelude() -> void {
|
||||||
emith("define ptr @fn_str_upper(ptr %s) {\n")
|
emith("define ptr @lp_str_upper(ptr %s) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %n = call i64 @strlen(ptr %s)\n")
|
emith(" %n = call i64 @strlen(ptr %s)\n")
|
||||||
emith(" %sz = add i64 %n, 1\n")
|
emith(" %sz = add i64 %n, 1\n")
|
||||||
|
|
@ -33,7 +33,7 @@ function emit_text_prelude() -> void {
|
||||||
emith(" store i8 0, ptr %tp\n")
|
emith(" store i8 0, ptr %tp\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define ptr @fn_str_lower(ptr %s) {\n")
|
emith("define ptr @lp_str_lower(ptr %s) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %n = call i64 @strlen(ptr %s)\n")
|
emith(" %n = call i64 @strlen(ptr %s)\n")
|
||||||
emith(" %sz = add i64 %n, 1\n")
|
emith(" %sz = add i64 %n, 1\n")
|
||||||
|
|
@ -63,7 +63,7 @@ function emit_text_prelude() -> void {
|
||||||
emith(" store i8 0, ptr %tp\n")
|
emith(" store i8 0, ptr %tp\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define ptr @fn_str_trim(ptr %s) {\n")
|
emith("define ptr @lp_str_trim(ptr %s) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %n = call i64 @strlen(ptr %s)\n")
|
emith(" %n = call i64 @strlen(ptr %s)\n")
|
||||||
emith(" %sp = alloca i64\n")
|
emith(" %sp = alloca i64\n")
|
||||||
|
|
@ -79,7 +79,7 @@ function emit_text_prelude() -> void {
|
||||||
emith("lchk:\n")
|
emith("lchk:\n")
|
||||||
emith(" %p0 = getelementptr i8, ptr %s, i64 %a0\n")
|
emith(" %p0 = getelementptr i8, ptr %s, i64 %a0\n")
|
||||||
emith(" %c0 = load i8, ptr %p0\n")
|
emith(" %c0 = load i8, ptr %p0\n")
|
||||||
emith(" %ws0 = call i1 @fn_is_ws(i8 %c0)\n")
|
emith(" %ws0 = call i1 @lp_is_ws(i8 %c0)\n")
|
||||||
emith(" br i1 %ws0, label %linc, label %rcond\n")
|
emith(" br i1 %ws0, label %linc, label %rcond\n")
|
||||||
emith("linc:\n")
|
emith("linc:\n")
|
||||||
emith(" %a1 = add i64 %a0, 1\n")
|
emith(" %a1 = add i64 %a0, 1\n")
|
||||||
|
|
@ -94,7 +94,7 @@ function emit_text_prelude() -> void {
|
||||||
emith(" %em1 = sub i64 %e2, 1\n")
|
emith(" %em1 = sub i64 %e2, 1\n")
|
||||||
emith(" %p1 = getelementptr i8, ptr %s, i64 %em1\n")
|
emith(" %p1 = getelementptr i8, ptr %s, i64 %em1\n")
|
||||||
emith(" %c1 = load i8, ptr %p1\n")
|
emith(" %c1 = load i8, ptr %p1\n")
|
||||||
emith(" %ws1 = call i1 @fn_is_ws(i8 %c1)\n")
|
emith(" %ws1 = call i1 @lp_is_ws(i8 %c1)\n")
|
||||||
emith(" br i1 %ws1, label %rdec, label %build\n")
|
emith(" br i1 %ws1, label %rdec, label %build\n")
|
||||||
emith("rdec:\n")
|
emith("rdec:\n")
|
||||||
emith(" store i64 %em1, ptr %ep\n")
|
emith(" store i64 %em1, ptr %ep\n")
|
||||||
|
|
@ -111,7 +111,7 @@ function emit_text_prelude() -> void {
|
||||||
emith(" store i8 0, ptr %tp\n")
|
emith(" store i8 0, ptr %tp\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define i1 @fn_is_ws(i8 %c) {\n")
|
emith("define i1 @lp_is_ws(i8 %c) {\n")
|
||||||
emith(" %a = icmp eq i8 %c, 32\n")
|
emith(" %a = icmp eq i8 %c, 32\n")
|
||||||
emith(" %b = icmp eq i8 %c, 9\n")
|
emith(" %b = icmp eq i8 %c, 9\n")
|
||||||
emith(" %d = icmp eq i8 %c, 10\n")
|
emith(" %d = icmp eq i8 %c, 10\n")
|
||||||
|
|
@ -121,7 +121,7 @@ function emit_text_prelude() -> void {
|
||||||
emith(" %r = or i1 %ab, %de\n")
|
emith(" %r = or i1 %ab, %de\n")
|
||||||
emith(" ret i1 %r\n")
|
emith(" ret i1 %r\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define ptr @fn_str_repeat(ptr %s, i32 %n32) {\n")
|
emith("define ptr @lp_str_repeat(ptr %s, i32 %n32) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %nneg = icmp slt i32 %n32, 0\n")
|
emith(" %nneg = icmp slt i32 %n32, 0\n")
|
||||||
emith(" %nn = select i1 %nneg, i32 0, i32 %n32\n")
|
emith(" %nn = select i1 %nneg, i32 0, i32 %n32\n")
|
||||||
|
|
@ -149,7 +149,7 @@ function emit_text_prelude() -> void {
|
||||||
emith(" store i8 0, ptr %tp\n")
|
emith(" store i8 0, ptr %tp\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define ptr @fn_str_pad(ptr %s, i32 %w32, i1 %left) {\n")
|
emith("define ptr @lp_str_pad(ptr %s, i32 %w32, i1 %left) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %L = call i64 @strlen(ptr %s)\n")
|
emith(" %L = call i64 @strlen(ptr %s)\n")
|
||||||
emith(" %w0 = sext i32 %w32 to i64\n")
|
emith(" %w0 = sext i32 %w32 to i64\n")
|
||||||
|
|
@ -169,20 +169,20 @@ function emit_text_prelude() -> void {
|
||||||
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
emith(" %out = call ptr @malloc(i64 %sz)\n")
|
||||||
emith(" br i1 %left, label %padleft, label %padright\n")
|
emith(" br i1 %left, label %padleft, label %padright\n")
|
||||||
emith("padleft:\n")
|
emith("padleft:\n")
|
||||||
emith(" call void @fn_fill_sp(ptr %out, i64 0, i64 %pad)\n")
|
emith(" call void @lp_fill_sp(ptr %out, i64 0, i64 %pad)\n")
|
||||||
emith(" %dstL = getelementptr i8, ptr %out, i64 %pad\n")
|
emith(" %dstL = getelementptr i8, ptr %out, i64 %pad\n")
|
||||||
emith(" call ptr @memcpy(ptr %dstL, ptr %s, i64 %L)\n")
|
emith(" call ptr @memcpy(ptr %dstL, ptr %s, i64 %L)\n")
|
||||||
emith(" br label %term\n")
|
emith(" br label %term\n")
|
||||||
emith("padright:\n")
|
emith("padright:\n")
|
||||||
emith(" call ptr @memcpy(ptr %out, ptr %s, i64 %L)\n")
|
emith(" call ptr @memcpy(ptr %out, ptr %s, i64 %L)\n")
|
||||||
emith(" call void @fn_fill_sp(ptr %out, i64 %L, i64 %pad)\n")
|
emith(" call void @lp_fill_sp(ptr %out, i64 %L, i64 %pad)\n")
|
||||||
emith(" br label %term\n")
|
emith(" br label %term\n")
|
||||||
emith("term:\n")
|
emith("term:\n")
|
||||||
emith(" %tp = getelementptr i8, ptr %out, i64 %w0\n")
|
emith(" %tp = getelementptr i8, ptr %out, i64 %w0\n")
|
||||||
emith(" store i8 0, ptr %tp\n")
|
emith(" store i8 0, ptr %tp\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define void @fn_fill_sp(ptr %buf, i64 %start, i64 %count) {\n")
|
emith("define void @lp_fill_sp(ptr %buf, i64 %start, i64 %count) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %ip = alloca i64\n")
|
emith(" %ip = alloca i64\n")
|
||||||
emith(" store i64 0, ptr %ip\n")
|
emith(" store i64 0, ptr %ip\n")
|
||||||
|
|
@ -208,7 +208,7 @@ function emit_text_prelude() -> void {
|
||||||
# program that uses them (g_uses_textrt2). Slices are the {data,len,cap}
|
# program that uses them (g_uses_textrt2). Slices are the {data,len,cap}
|
||||||
# %LSlice with str (ptr) elements.
|
# %LSlice with str (ptr) elements.
|
||||||
function emit_text2_prelude() -> void {
|
function emit_text2_prelude() -> void {
|
||||||
emith("define ptr @fn_str_replace(ptr %s, ptr %from, ptr %to) {\n")
|
emith("define ptr @lp_str_replace(ptr %s, ptr %from, ptr %to) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %lf = call i64 @strlen(ptr %from)\n")
|
emith(" %lf = call i64 @strlen(ptr %from)\n")
|
||||||
emith(" %lz = icmp eq i64 %lf, 0\n")
|
emith(" %lz = icmp eq i64 %lf, 0\n")
|
||||||
|
|
@ -277,7 +277,7 @@ function emit_text2_prelude() -> void {
|
||||||
emith(" call ptr @memcpy(ptr %dst3, ptr %src3, i64 %remp1)\n")
|
emith(" call ptr @memcpy(ptr %dst3, ptr %src3, i64 %remp1)\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define ptr @fn_str_join(ptr %h, ptr %sep) {\n")
|
emith("define ptr @lp_str_join(ptr %h, ptr %sep) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %lp = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n")
|
emith(" %lp = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n")
|
||||||
emith(" %len32 = load i32, ptr %lp\n")
|
emith(" %len32 = load i32, ptr %lp\n")
|
||||||
|
|
@ -347,7 +347,7 @@ function emit_text2_prelude() -> void {
|
||||||
emith(" store i8 0, ptr %dsf\n")
|
emith(" store i8 0, ptr %dsf\n")
|
||||||
emith(" ret ptr %out\n")
|
emith(" ret ptr %out\n")
|
||||||
emith("}\n")
|
emith("}\n")
|
||||||
emith("define ptr @fn_str_split(ptr %s, ptr %sep) {\n")
|
emith("define ptr @lp_str_split(ptr %s, ptr %sep) {\n")
|
||||||
emith("entry:\n")
|
emith("entry:\n")
|
||||||
emith(" %lsep = call i64 @strlen(ptr %sep)\n")
|
emith(" %lsep = call i64 @strlen(ptr %sep)\n")
|
||||||
emith(" %cntp = alloca i64\n")
|
emith(" %cntp = alloca i64\n")
|
||||||
|
|
|
||||||
|
|
@ -43,35 +43,35 @@ function emit_unicode_ns(meth: pointer, e: Node) -> Val {
|
||||||
}
|
}
|
||||||
if (meth == "len") {
|
if (meth == "len") {
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_uni_len(ptr {s.code})`), "int")
|
return val(emit_bind(`call i32 @lp_uni_len(ptr {s.code})`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "is_valid_utf8") {
|
if (meth == "is_valid_utf8") {
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_uni_valid(ptr {s.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_uni_valid(ptr {s.code})`), "bool")
|
||||||
}
|
}
|
||||||
if (meth == "char_at") {
|
if (meth == "char_at") {
|
||||||
let s = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
let s = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call i32 @fn_uni_char_at(ptr {s.code}, i32 {i.code})`), "int")
|
return val(emit_bind(`call i32 @lp_uni_char_at(ptr {s.code}, i32 {i.code})`), "int")
|
||||||
}
|
}
|
||||||
if (meth == "chars") {
|
if (meth == "chars") {
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_uni_chars(ptr {s.code})`), "[]int")
|
return val(emit_bind(`call ptr @lp_uni_chars(ptr {s.code})`), "[]int")
|
||||||
}
|
}
|
||||||
if (meth == "upper") {
|
if (meth == "upper") {
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_uni_case(ptr {s.code}, i32 1)`), "string")
|
return val(emit_bind(`call ptr @lp_uni_case(ptr {s.code}, i32 1)`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "lower") {
|
if (meth == "lower") {
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_uni_case(ptr {s.code}, i32 0)`), "string")
|
return val(emit_bind(`call ptr @lp_uni_case(ptr {s.code}, i32 0)`), "string")
|
||||||
}
|
}
|
||||||
if (meth == "truncate") {
|
if (meth == "truncate") {
|
||||||
let s = emit_expr(e.kids[0]); let n = emit_expr(e.kids[1])
|
let s = emit_expr(e.kids[0]); let n = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call ptr @fn_uni_truncate(ptr {s.code}, i32 {n.code})`), "string")
|
return val(emit_bind(`call ptr @lp_uni_truncate(ptr {s.code}, i32 {n.code})`), "string")
|
||||||
}
|
}
|
||||||
# grapheme_len
|
# grapheme_len
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_uni_grapheme_len(ptr {s.code})`), "int")
|
return val(emit_bind(`call i32 @lp_uni_grapheme_len(ptr {s.code})`), "int")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_unicode_prelude — the UTF-8 runtime, emitted once per program that uses
|
# emit_unicode_prelude — the UTF-8 runtime, emitted once per program that uses
|
||||||
|
|
@ -87,7 +87,7 @@ function emit_uni_core() -> void {
|
||||||
# code-point count: every byte that is NOT a UTF-8 continuation byte
|
# code-point count: every byte that is NOT a UTF-8 continuation byte
|
||||||
# (0b10xxxxxx) begins a new code point. Walks byte-by-byte, so it is safe on
|
# (0b10xxxxxx) begins a new code point. Walks byte-by-byte, so it is safe on
|
||||||
# truncated/invalid input and stops exactly at the NUL.
|
# truncated/invalid input and stops exactly at the NUL.
|
||||||
emith("define i32 @fn_uni_len(ptr %s) {\n")
|
emith("define i32 @lp_uni_len(ptr %s) {\n")
|
||||||
emith("entry:\n %ip = alloca i32\n %np = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %np\n br label %lp\n")
|
emith("entry:\n %ip = alloca i32\n %np = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %np\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||||
|
|
@ -100,7 +100,7 @@ function emit_uni_core() -> void {
|
||||||
# offset just past it. Lenient and overrun-safe: a truncated multibyte sequence
|
# offset just past it. Lenient and overrun-safe: a truncated multibyte sequence
|
||||||
# (a continuation byte that is NUL) or an invalid lead byte decodes as a single
|
# (a continuation byte that is NUL) or an invalid lead byte decodes as a single
|
||||||
# byte, so the walk always makes progress and never reads past the terminator.
|
# byte, so the walk always makes progress and never reads past the terminator.
|
||||||
emith("define i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cpout) {\n")
|
emith("define i32 @lp_uni_decode(ptr %s, i32 %i, ptr %cpout) {\n")
|
||||||
emith("entry:\n %p0 = getelementptr i8, ptr %s, i32 %i\n %b0 = load i8, ptr %p0\n %c0 = zext i8 %b0 to i32\n")
|
emith("entry:\n %p0 = getelementptr i8, ptr %s, i32 %i\n %b0 = load i8, ptr %p0\n %c0 = zext i8 %b0 to i32\n")
|
||||||
emith(" %a1 = icmp ult i32 %c0, 128\n br i1 %a1, label %one, label %multi\n")
|
emith(" %a1 = icmp ult i32 %c0, 128\n br i1 %a1, label %one, label %multi\n")
|
||||||
emith("one:\n store i32 %c0, ptr %cpout\n %oi = add i32 %i, 1\n ret i32 %oi\n")
|
emith("one:\n store i32 %c0, ptr %cpout\n %oi = add i32 %i, 1\n ret i32 %oi\n")
|
||||||
|
|
@ -135,7 +135,7 @@ function emit_uni_core() -> void {
|
||||||
|
|
||||||
# strict UTF-8 validation: correct continuation bytes, no overlong encodings,
|
# strict UTF-8 validation: correct continuation bytes, no overlong encodings,
|
||||||
# no surrogates (U+D800..U+DFFF), and nothing above U+10FFFF. Returns 1/0.
|
# no surrogates (U+D800..U+DFFF), and nothing above U+10FFFF. Returns 1/0.
|
||||||
emith("define i32 @fn_uni_valid(ptr %s) {\n")
|
emith("define i32 @lp_uni_valid(ptr %s) {\n")
|
||||||
emith("entry:\n %ip = alloca i32\n store i32 0, ptr %ip\n br label %lp\n")
|
emith("entry:\n %ip = alloca i32\n store i32 0, ptr %ip\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %good, label %g\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %good, label %g\n")
|
||||||
|
|
@ -180,26 +180,26 @@ function emit_uni_core() -> void {
|
||||||
function emit_uni_case_fns() -> void {
|
function emit_uni_case_fns() -> void {
|
||||||
# uppercase one code point: ASCII a-z and Latin-1 a-with-diacritic .. thorn
|
# uppercase one code point: ASCII a-z and Latin-1 a-with-diacritic .. thorn
|
||||||
# (0xE0..0xFE except 0xF7). Other code points pass through unchanged (v1).
|
# (0xE0..0xFE except 0xF7). Other code points pass through unchanged (v1).
|
||||||
emith("define i32 @fn_uni_upcp(i32 %c) {\n")
|
emith("define i32 @lp_uni_upcp(i32 %c) {\n")
|
||||||
emith(" %la = icmp uge i32 %c, 97\n %lb = icmp ule i32 %c, 122\n %asc = and i1 %la, %lb\n")
|
emith(" %la = icmp uge i32 %c, 97\n %lb = icmp ule i32 %c, 122\n %asc = and i1 %la, %lb\n")
|
||||||
emith(" %da = icmp uge i32 %c, 224\n %db = icmp ule i32 %c, 254\n %dd = icmp ne i32 %c, 247\n %d1 = and i1 %da, %db\n %d2 = and i1 %d1, %dd\n")
|
emith(" %da = icmp uge i32 %c, 224\n %db = icmp ule i32 %c, 254\n %dd = icmp ne i32 %c, 247\n %d1 = and i1 %da, %db\n %d2 = and i1 %d1, %dd\n")
|
||||||
emith(" %map = or i1 %asc, %d2\n %up = sub i32 %c, 32\n %r = select i1 %map, i32 %up, i32 %c\n ret i32 %r\n}\n")
|
emith(" %map = or i1 %asc, %d2\n %up = sub i32 %c, 32\n %r = select i1 %map, i32 %up, i32 %c\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# lowercase one code point: ASCII A-Z and Latin-1 A-with-diacritic .. Thorn
|
# lowercase one code point: ASCII A-Z and Latin-1 A-with-diacritic .. Thorn
|
||||||
# (0xC0..0xDE except 0xD7). Other code points pass through unchanged (v1).
|
# (0xC0..0xDE except 0xD7). Other code points pass through unchanged (v1).
|
||||||
emith("define i32 @fn_uni_locp(i32 %c) {\n")
|
emith("define i32 @lp_uni_locp(i32 %c) {\n")
|
||||||
emith(" %ua = icmp uge i32 %c, 65\n %ub = icmp ule i32 %c, 90\n %asc = and i1 %ua, %ub\n")
|
emith(" %ua = icmp uge i32 %c, 65\n %ub = icmp ule i32 %c, 90\n %asc = and i1 %ua, %ub\n")
|
||||||
emith(" %da = icmp uge i32 %c, 192\n %db = icmp ule i32 %c, 222\n %dd = icmp ne i32 %c, 215\n %d1 = and i1 %da, %db\n %d2 = and i1 %d1, %dd\n")
|
emith(" %da = icmp uge i32 %c, 192\n %db = icmp ule i32 %c, 222\n %dd = icmp ne i32 %c, 215\n %d1 = and i1 %da, %db\n %d2 = and i1 %d1, %dd\n")
|
||||||
emith(" %map = or i1 %asc, %d2\n %lo = add i32 %c, 32\n %r = select i1 %map, i32 %lo, i32 %c\n ret i32 %r\n}\n")
|
emith(" %map = or i1 %asc, %d2\n %lo = add i32 %c, 32\n %r = select i1 %map, i32 %lo, i32 %c\n ret i32 %r\n}\n")
|
||||||
|
|
||||||
# UTF-8 byte width needed to encode a code point
|
# UTF-8 byte width needed to encode a code point
|
||||||
emith("define i32 @fn_uni_cpwidth(i32 %cp) {\n")
|
emith("define i32 @lp_uni_cpwidth(i32 %cp) {\n")
|
||||||
emith(" %a = icmp ult i32 %cp, 128\n %b = icmp ult i32 %cp, 2048\n %c = icmp ult i32 %cp, 65536\n")
|
emith(" %a = icmp ult i32 %cp, 128\n %b = icmp ult i32 %cp, 2048\n %c = icmp ult i32 %cp, 65536\n")
|
||||||
emith(" %w34 = select i1 %c, i32 3, i32 4\n %w234 = select i1 %b, i32 2, i32 %w34\n %w = select i1 %a, i32 1, i32 %w234\n ret i32 %w\n}\n")
|
emith(" %w34 = select i1 %c, i32 3, i32 4\n %w234 = select i1 %b, i32 2, i32 %w34\n %w = select i1 %a, i32 1, i32 %w234\n ret i32 %w\n}\n")
|
||||||
|
|
||||||
# encode a code point into %dst at byte offset %off; return the new offset
|
# encode a code point into %dst at byte offset %off; return the new offset
|
||||||
emith("define i32 @fn_uni_encode(ptr %dst, i32 %off, i32 %cp) {\n")
|
emith("define i32 @lp_uni_encode(ptr %dst, i32 %off, i32 %cp) {\n")
|
||||||
emith("entry:\n %w = call i32 @fn_uni_cpwidth(i32 %cp)\n %is1 = icmp eq i32 %w, 1\n br i1 %is1, label %e1, label %k2\n")
|
emith("entry:\n %w = call i32 @lp_uni_cpwidth(i32 %cp)\n %is1 = icmp eq i32 %w, 1\n br i1 %is1, label %e1, label %k2\n")
|
||||||
emith("e1:\n %d0 = getelementptr i8, ptr %dst, i32 %off\n %b0 = trunc i32 %cp to i8\n store i8 %b0, ptr %d0\n %o1 = add i32 %off, 1\n ret i32 %o1\n")
|
emith("e1:\n %d0 = getelementptr i8, ptr %dst, i32 %off\n %b0 = trunc i32 %cp to i8\n store i8 %b0, ptr %d0\n %o1 = add i32 %off, 1\n ret i32 %o1\n")
|
||||||
emith("k2:\n %is2 = icmp eq i32 %w, 2\n br i1 %is2, label %e2, label %k3\n")
|
emith("k2:\n %is2 = icmp eq i32 %w, 2\n br i1 %is2, label %e2, label %k3\n")
|
||||||
emith("e2:\n %hi2 = lshr i32 %cp, 6\n %by0 = or i32 %hi2, 192\n %lo2 = and i32 %cp, 63\n %by1 = or i32 %lo2, 128\n")
|
emith("e2:\n %hi2 = lshr i32 %cp, 6\n %by0 = or i32 %hi2, 192\n %lo2 = and i32 %cp, 63\n %by1 = or i32 %lo2, 128\n")
|
||||||
|
|
@ -220,35 +220,35 @@ function emit_uni_case_fns() -> void {
|
||||||
# is this code point a grapheme "extend" (a combining mark or variation
|
# is this code point a grapheme "extend" (a combining mark or variation
|
||||||
# selector that joins the preceding base)? An approximation of the common
|
# selector that joins the preceding base)? An approximation of the common
|
||||||
# ranges; ZWJ and regional indicators are handled by grapheme_len itself.
|
# ranges; ZWJ and regional indicators are handled by grapheme_len itself.
|
||||||
emith("define i32 @fn_uni_is_extend(i32 %c) {\n")
|
emith("define i32 @lp_uni_is_extend(i32 %c) {\n")
|
||||||
emith(" %r1 = call i32 @fn_uni_inrange(i32 %c, i32 768, i32 879)\n") # 0300-036F combining diacritics
|
emith(" %r1 = call i32 @lp_uni_inrange(i32 %c, i32 768, i32 879)\n") # 0300-036F combining diacritics
|
||||||
emith(" %r2 = call i32 @fn_uni_inrange(i32 %c, i32 6832, i32 6911)\n") # 1AB0-1AFF
|
emith(" %r2 = call i32 @lp_uni_inrange(i32 %c, i32 6832, i32 6911)\n") # 1AB0-1AFF
|
||||||
emith(" %r3 = call i32 @fn_uni_inrange(i32 %c, i32 7616, i32 7679)\n") # 1DC0-1DFF
|
emith(" %r3 = call i32 @lp_uni_inrange(i32 %c, i32 7616, i32 7679)\n") # 1DC0-1DFF
|
||||||
emith(" %r4 = call i32 @fn_uni_inrange(i32 %c, i32 8400, i32 8447)\n") # 20D0-20FF
|
emith(" %r4 = call i32 @lp_uni_inrange(i32 %c, i32 8400, i32 8447)\n") # 20D0-20FF
|
||||||
emith(" %r5 = call i32 @fn_uni_inrange(i32 %c, i32 65056, i32 65071)\n") # FE20-FE2F
|
emith(" %r5 = call i32 @lp_uni_inrange(i32 %c, i32 65056, i32 65071)\n") # FE20-FE2F
|
||||||
emith(" %r6 = call i32 @fn_uni_inrange(i32 %c, i32 65024, i32 65039)\n") # FE00-FE0F variation selectors
|
emith(" %r6 = call i32 @lp_uni_inrange(i32 %c, i32 65024, i32 65039)\n") # FE00-FE0F variation selectors
|
||||||
emith(" %r7 = call i32 @fn_uni_inrange(i32 %c, i32 917760, i32 917999)\n") # E0100-E01EF
|
emith(" %r7 = call i32 @lp_uni_inrange(i32 %c, i32 917760, i32 917999)\n") # E0100-E01EF
|
||||||
emith(" %o1 = or i32 %r1, %r2\n %o2 = or i32 %o1, %r3\n %o3 = or i32 %o2, %r4\n %o4 = or i32 %o3, %r5\n %o5 = or i32 %o4, %r6\n %o6 = or i32 %o5, %r7\n ret i32 %o6\n}\n")
|
emith(" %o1 = or i32 %r1, %r2\n %o2 = or i32 %o1, %r3\n %o3 = or i32 %o2, %r4\n %o4 = or i32 %o3, %r5\n %o5 = or i32 %o4, %r6\n %o6 = or i32 %o5, %r7\n ret i32 %o6\n}\n")
|
||||||
|
|
||||||
emith("define i32 @fn_uni_inrange(i32 %c, i32 %lo, i32 %hi) {\n")
|
emith("define i32 @lp_uni_inrange(i32 %c, i32 %lo, i32 %hi) {\n")
|
||||||
emith(" %a = icmp uge i32 %c, %lo\n %b = icmp ule i32 %c, %hi\n %x = and i1 %a, %b\n %r = zext i1 %x to i32\n ret i32 %r\n}\n")
|
emith(" %a = icmp uge i32 %c, %lo\n %b = icmp ule i32 %c, %hi\n %x = and i1 %a, %b\n %r = zext i1 %x to i32\n ret i32 %r\n}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- code-point access + string builders -----------------------------------
|
# ---- code-point access + string builders -----------------------------------
|
||||||
function emit_uni_str_fns() -> void {
|
function emit_uni_str_fns() -> void {
|
||||||
# the idx-th code point, or -1 when idx is past the end
|
# the idx-th code point, or -1 when idx is past the end
|
||||||
emith("define i32 @fn_uni_char_at(ptr %s, i32 %idx) {\n")
|
emith("define i32 @lp_uni_char_at(ptr %s, i32 %idx) {\n")
|
||||||
emith("entry:\n %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
emith("entry:\n %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %none, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %none, label %go\n")
|
||||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %k = load i32, ptr %kp\n %hit = icmp eq i32 %k, %idx\n br i1 %hit, label %found, label %next\n")
|
emith("go:\n %ni = call i32 @lp_uni_decode(ptr %s, i32 %i, ptr %cp)\n %k = load i32, ptr %kp\n %hit = icmp eq i32 %k, %idx\n br i1 %hit, label %found, label %next\n")
|
||||||
emith("found:\n %v = load i32, ptr %cp\n ret i32 %v\n")
|
emith("found:\n %v = load i32, ptr %cp\n ret i32 %v\n")
|
||||||
emith("next:\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
emith("next:\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||||
emith("none:\n ret i32 -1\n}\n")
|
emith("none:\n ret i32 -1\n}\n")
|
||||||
|
|
||||||
# chars(s) -> []int : a fresh %LSlice of every code point, in order
|
# chars(s) -> []int : a fresh %LSlice of every code point, in order
|
||||||
emith("define ptr @fn_uni_chars(ptr %s) {\n")
|
emith("define ptr @lp_uni_chars(ptr %s) {\n")
|
||||||
emith("entry:\n %n = call i32 @fn_uni_len(ptr %s)\n %h = call ptr @malloc(i64 16)\n")
|
emith("entry:\n %n = call i32 @lp_uni_len(ptr %s)\n %h = call ptr @malloc(i64 16)\n")
|
||||||
emith(" %nz = zext i32 %n to i64\n %bytes = mul i64 %nz, 4\n %data = call ptr @malloc(i64 %bytes)\n")
|
emith(" %nz = zext i32 %n to i64\n %bytes = mul i64 %nz, 4\n %data = call ptr @malloc(i64 %bytes)\n")
|
||||||
emith(" %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n store ptr %data, ptr %d0\n")
|
emith(" %d0 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 0\n store ptr %data, ptr %d0\n")
|
||||||
emith(" %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n store i32 %n, ptr %d1\n")
|
emith(" %d1 = getelementptr inbounds %LSlice, ptr %h, i32 0, i32 1\n store i32 %n, ptr %d1\n")
|
||||||
|
|
@ -256,31 +256,31 @@ function emit_uni_str_fns() -> void {
|
||||||
emith(" %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
emith(" %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n %k = load i32, ptr %kp\n")
|
emith("go:\n %ni = call i32 @lp_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n %k = load i32, ptr %kp\n")
|
||||||
emith(" %slot = getelementptr i32, ptr %data, i32 %k\n store i32 %v, ptr %slot\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
emith(" %slot = getelementptr i32, ptr %data, i32 %k\n store i32 %v, ptr %slot\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||||
emith("done:\n ret ptr %h\n}\n")
|
emith("done:\n ret ptr %h\n}\n")
|
||||||
|
|
||||||
# case map the whole string. %up != 0 -> uppercase, else lowercase. Decodes,
|
# case map the whole string. %up != 0 -> uppercase, else lowercase. Decodes,
|
||||||
# maps each code point, and re-encodes into a fresh buffer (worst case 4 bytes
|
# maps each code point, and re-encodes into a fresh buffer (worst case 4 bytes
|
||||||
# per code point, though ASCII/Latin-1 mapping preserves byte length).
|
# per code point, though ASCII/Latin-1 mapping preserves byte length).
|
||||||
emith("define ptr @fn_uni_case(ptr %s, i32 %up) {\n")
|
emith("define ptr @lp_uni_case(ptr %s, i32 %up) {\n")
|
||||||
emith("entry:\n %bl = call i64 @strlen(ptr %s)\n %cap0 = mul i64 %bl, 4\n %cap = add i64 %cap0, 4\n %out = call ptr @malloc(i64 %cap)\n")
|
emith("entry:\n %bl = call i64 @strlen(ptr %s)\n %cap0 = mul i64 %bl, 4\n %cap = add i64 %cap0, 4\n %out = call ptr @malloc(i64 %cap)\n")
|
||||||
emith(" %ip = alloca i32\n %op = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %op\n br label %lp\n")
|
emith(" %ip = alloca i32\n %op = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %op\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n")
|
emith("go:\n %ni = call i32 @lp_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n")
|
||||||
emith(" %mu = call i32 @fn_uni_upcp(i32 %v)\n %ml = call i32 @fn_uni_locp(i32 %v)\n %isup = icmp ne i32 %up, 0\n %m = select i1 %isup, i32 %mu, i32 %ml\n")
|
emith(" %mu = call i32 @lp_uni_upcp(i32 %v)\n %ml = call i32 @lp_uni_locp(i32 %v)\n %isup = icmp ne i32 %up, 0\n %m = select i1 %isup, i32 %mu, i32 %ml\n")
|
||||||
emith(" %o = load i32, ptr %op\n %no = call i32 @fn_uni_encode(ptr %out, i32 %o, i32 %m)\n store i32 %no, ptr %op\n store i32 %ni, ptr %ip\n br label %lp\n")
|
emith(" %o = load i32, ptr %op\n %no = call i32 @lp_uni_encode(ptr %out, i32 %o, i32 %m)\n store i32 %no, ptr %op\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||||
emith("done:\n %fo = load i32, ptr %op\n %endp = getelementptr i8, ptr %out, i32 %fo\n store i8 0, ptr %endp\n ret ptr %out\n}\n")
|
emith("done:\n %fo = load i32, ptr %op\n %endp = getelementptr i8, ptr %out, i32 %fo\n store i8 0, ptr %endp\n ret ptr %out\n}\n")
|
||||||
|
|
||||||
# truncate(s, n) -> the first n code points as a fresh string (never splits a
|
# truncate(s, n) -> the first n code points as a fresh string (never splits a
|
||||||
# multibyte character). n <= 0 yields the empty string.
|
# multibyte character). n <= 0 yields the empty string.
|
||||||
emith("define ptr @fn_uni_truncate(ptr %s, i32 %n) {\n")
|
emith("define ptr @lp_uni_truncate(ptr %s, i32 %n) {\n")
|
||||||
emith("entry:\n %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
emith("entry:\n %ip = alloca i32\n %kp = alloca i32\n %cp = alloca i32\n store i32 0, ptr %ip\n store i32 0, ptr %kp\n br label %lp\n")
|
||||||
emith("lp:\n %k = load i32, ptr %kp\n %enough = icmp sge i32 %k, %n\n br i1 %enough, label %cut, label %chk\n")
|
emith("lp:\n %k = load i32, ptr %kp\n %enough = icmp sge i32 %k, %n\n br i1 %enough, label %cut, label %chk\n")
|
||||||
emith("chk:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("chk:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %cut, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %cut, label %go\n")
|
||||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
emith("go:\n %ni = call i32 @lp_uni_decode(ptr %s, i32 %i, ptr %cp)\n %k1 = add i32 %k, 1\n store i32 %k1, ptr %kp\n store i32 %ni, ptr %ip\n br label %lp\n")
|
||||||
emith("cut:\n %len = load i32, ptr %ip\n %lz = zext i32 %len to i64\n %cap = add i64 %lz, 1\n %out = call ptr @malloc(i64 %cap)\n %lz2 = zext i32 %len to i64\n call ptr @memcpy(ptr %out, ptr %s, i64 %lz2)\n")
|
emith("cut:\n %len = load i32, ptr %ip\n %lz = zext i32 %len to i64\n %cap = add i64 %lz, 1\n %out = call ptr @malloc(i64 %cap)\n %lz2 = zext i32 %len to i64\n call ptr @memcpy(ptr %out, ptr %s, i64 %lz2)\n")
|
||||||
emith(" %endp = getelementptr i8, ptr %out, i32 %len\n store i8 0, ptr %endp\n ret ptr %out\n}\n")
|
emith(" %endp = getelementptr i8, ptr %out, i32 %len\n store i8 0, ptr %endp\n ret ptr %out\n}\n")
|
||||||
|
|
||||||
|
|
@ -292,26 +292,26 @@ function emit_uni_str_fns() -> void {
|
||||||
# ZWJ emoji sequences count as one); and the second regional indicator of a flag
|
# ZWJ emoji sequences count as one); and the second regional indicator of a flag
|
||||||
# pair. Simple state carried in allocas.
|
# pair. Simple state carried in allocas.
|
||||||
function emit_uni_grapheme() -> void {
|
function emit_uni_grapheme() -> void {
|
||||||
emith("define i32 @fn_uni_grapheme_len(ptr %s) {\n")
|
emith("define i32 @lp_uni_grapheme_len(ptr %s) {\n")
|
||||||
emith("entry:\n %ip = alloca i32\n %np = alloca i32\n %zp = alloca i32\n %rp = alloca i32\n %cp = alloca i32\n")
|
emith("entry:\n %ip = alloca i32\n %np = alloca i32\n %zp = alloca i32\n %rp = alloca i32\n %cp = alloca i32\n")
|
||||||
emith(" store i32 0, ptr %ip\n store i32 0, ptr %np\n store i32 0, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
emith(" store i32 0, ptr %ip\n store i32 0, ptr %np\n store i32 0, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
||||||
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
emith("lp:\n %i = load i32, ptr %ip\n %p = getelementptr i8, ptr %s, i32 %i\n %b = load i8, ptr %p\n %c = zext i8 %b to i32\n")
|
||||||
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
emith(" %z = icmp eq i32 %c, 0\n br i1 %z, label %done, label %go\n")
|
||||||
emith("go:\n %ni = call i32 @fn_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n store i32 %ni, ptr %ip\n")
|
emith("go:\n %ni = call i32 @lp_uni_decode(ptr %s, i32 %i, ptr %cp)\n %v = load i32, ptr %cp\n store i32 %ni, ptr %ip\n")
|
||||||
# ZWJ (U+200D): extends the cluster and arms the join for the next code point
|
# ZWJ (U+200D): extends the cluster and arms the join for the next code point
|
||||||
emith(" %iszwj = icmp eq i32 %v, 8205\n br i1 %iszwj, label %zwj, label %notzwj\n")
|
emith(" %iszwj = icmp eq i32 %v, 8205\n br i1 %iszwj, label %zwj, label %notzwj\n")
|
||||||
emith("zwj:\n %n0 = load i32, ptr %np\n %n0z = icmp eq i32 %n0, 0\n %n0b = zext i1 %n0z to i32\n %n0n = add i32 %n0, %n0b\n store i32 %n0n, ptr %np\n") # leading ZWJ still opens one cluster
|
emith("zwj:\n %n0 = load i32, ptr %np\n %n0z = icmp eq i32 %n0, 0\n %n0b = zext i1 %n0z to i32\n %n0n = add i32 %n0, %n0b\n store i32 %n0n, ptr %np\n") # leading ZWJ still opens one cluster
|
||||||
emith(" store i32 1, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
emith(" store i32 1, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
||||||
emith("notzwj:\n %n = load i32, ptr %np\n %first = icmp eq i32 %n, 0\n br i1 %first, label %open, label %cont\n")
|
emith("notzwj:\n %n = load i32, ptr %np\n %first = icmp eq i32 %n, 0\n br i1 %first, label %open, label %cont\n")
|
||||||
# first cluster
|
# first cluster
|
||||||
emith("open:\n store i32 1, ptr %np\n store i32 0, ptr %zp\n %ri0 = call i32 @fn_uni_inrange(i32 %v, i32 127462, i32 127487)\n store i32 %ri0, ptr %rp\n br label %lp\n")
|
emith("open:\n store i32 1, ptr %np\n store i32 0, ptr %zp\n %ri0 = call i32 @lp_uni_inrange(i32 %v, i32 127462, i32 127487)\n store i32 %ri0, ptr %rp\n br label %lp\n")
|
||||||
emith("cont:\n %zj = load i32, ptr %zp\n %afterz = icmp ne i32 %zj, 0\n br i1 %afterz, label %joinz, label %chkext\n")
|
emith("cont:\n %zj = load i32, ptr %zp\n %afterz = icmp ne i32 %zj, 0\n br i1 %afterz, label %joinz, label %chkext\n")
|
||||||
# code point right after a ZWJ joins the current cluster
|
# code point right after a ZWJ joins the current cluster
|
||||||
emith("joinz:\n store i32 0, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
emith("joinz:\n store i32 0, ptr %zp\n store i32 0, ptr %rp\n br label %lp\n")
|
||||||
emith("chkext:\n %ext = call i32 @fn_uni_is_extend(i32 %v)\n %isext = icmp ne i32 %ext, 0\n br i1 %isext, label %joinext, label %chkri\n")
|
emith("chkext:\n %ext = call i32 @lp_uni_is_extend(i32 %v)\n %isext = icmp ne i32 %ext, 0\n br i1 %isext, label %joinext, label %chkri\n")
|
||||||
emith("joinext:\n store i32 0, ptr %rp\n br label %lp\n")
|
emith("joinext:\n store i32 0, ptr %rp\n br label %lp\n")
|
||||||
# regional indicator: joins only as the second of a pair
|
# regional indicator: joins only as the second of a pair
|
||||||
emith("chkri:\n %ri = call i32 @fn_uni_inrange(i32 %v, i32 127462, i32 127487)\n %isri = icmp ne i32 %ri, 0\n %ropen = load i32, ptr %rp\n %ropenb = icmp ne i32 %ropen, 0\n %pair = and i1 %isri, %ropenb\n br i1 %pair, label %joinri, label %newcl\n")
|
emith("chkri:\n %ri = call i32 @lp_uni_inrange(i32 %v, i32 127462, i32 127487)\n %isri = icmp ne i32 %ri, 0\n %ropen = load i32, ptr %rp\n %ropenb = icmp ne i32 %ropen, 0\n %pair = and i1 %isri, %ropenb\n br i1 %pair, label %joinri, label %newcl\n")
|
||||||
emith("joinri:\n store i32 0, ptr %rp\n br label %lp\n")
|
emith("joinri:\n store i32 0, ptr %rp\n br label %lp\n")
|
||||||
emith("newcl:\n %nn = load i32, ptr %np\n %nn1 = add i32 %nn, 1\n store i32 %nn1, ptr %np\n store i32 0, ptr %zp\n %riset = select i1 %isri, i32 1, i32 0\n store i32 %riset, ptr %rp\n br label %lp\n")
|
emith("newcl:\n %nn = load i32, ptr %np\n %nn1 = add i32 %nn, 1\n store i32 %nn1, ptr %np\n store i32 0, ptr %zp\n %riset = select i1 %isri, i32 1, i32 0\n store i32 %riset, ptr %rp\n br label %lp\n")
|
||||||
emith("done:\n %r = load i32, ptr %np\n ret i32 %r\n}\n")
|
emith("done:\n %r = load i32, ptr %np\n ret i32 %r\n}\n")
|
||||||
|
|
|
||||||
|
|
@ -37,17 +37,17 @@ function emit_uuid_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_cryptort = true
|
g_uses_cryptort = true
|
||||||
g_uses_uuidrt = true
|
g_uses_uuidrt = true
|
||||||
if (meth == "new") or (meth == "v4") { # v4: 122 random bits
|
if (meth == "new") or (meth == "v4") { # v4: 122 random bits
|
||||||
return val(emit_bind("call ptr @fn_uuid_v4()"), "string")
|
return val(emit_bind("call ptr @lp_uuid_v4()"), "string")
|
||||||
}
|
}
|
||||||
if (meth == "new_v7") or (meth == "v7") { # v7: ms timestamp + random
|
if (meth == "new_v7") or (meth == "v7") { # v7: ms timestamp + random
|
||||||
return val(emit_bind("call ptr @fn_uuid_v7()"), "string")
|
return val(emit_bind("call ptr @lp_uuid_v7()"), "string")
|
||||||
}
|
}
|
||||||
if (meth == "nil") { # the all-zero UUID
|
if (meth == "nil") { # the all-zero UUID
|
||||||
return val(emit_bind("call ptr @fn_uuid_nil()"), "string")
|
return val(emit_bind("call ptr @lp_uuid_nil()"), "string")
|
||||||
}
|
}
|
||||||
if (meth == "is_valid") { # well-formed UUID? -> bool
|
if (meth == "is_valid") { # well-formed UUID? -> bool
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call i32 @fn_uuid_valid(ptr {s.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_uuid_valid(ptr {s.code})`), "bool")
|
||||||
}
|
}
|
||||||
if (meth == "to_text") { # already canonical text: identity
|
if (meth == "to_text") { # already canonical text: identity
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
|
|
@ -55,13 +55,13 @@ function emit_uuid_ns(meth: pointer, e: Node) -> Val {
|
||||||
}
|
}
|
||||||
if (meth == "equals") { # case-insensitive equality -> bool
|
if (meth == "equals") { # case-insensitive equality -> bool
|
||||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||||
return val(emit_bind(`call i32 @fn_uuid_eq(ptr {a.code}, ptr {b.code})`), "bool")
|
return val(emit_bind(`call i32 @lp_uuid_eq(ptr {a.code}, ptr {b.code})`), "bool")
|
||||||
}
|
}
|
||||||
# parse(s): normalise an untrusted string to a lowercase UUID, or the nil UUID
|
# parse(s): normalise an untrusted string to a lowercase UUID, or the nil UUID
|
||||||
# when it is not well-formed. Callers that must reject bad input should gate on
|
# when it is not well-formed. Callers that must reject bad input should gate on
|
||||||
# Uuid.is_valid(s) first; this never faults on garbage.
|
# Uuid.is_valid(s) first; this never faults on garbage.
|
||||||
let s = emit_expr(e.kids[0])
|
let s = emit_expr(e.kids[0])
|
||||||
return val(emit_bind(`call ptr @fn_uuid_parse(ptr {s.code})`), "string")
|
return val(emit_bind(`call ptr @lp_uuid_parse(ptr {s.code})`), "string")
|
||||||
}
|
}
|
||||||
|
|
||||||
# emit_uuid_prelude — the UUID runtime, emitted once per program that uses Uuid.*
|
# emit_uuid_prelude — the UUID runtime, emitted once per program that uses Uuid.*
|
||||||
|
|
@ -71,8 +71,8 @@ function emit_uuid_ns(meth: pointer, e: Node) -> Val {
|
||||||
function emit_uuid_prelude() -> void {
|
function emit_uuid_prelude() -> void {
|
||||||
# 16 raw bytes -> a fresh canonical 36-char string. hex-encode all 16 bytes,
|
# 16 raw bytes -> a fresh canonical 36-char string. hex-encode all 16 bytes,
|
||||||
# then splice the four hyphens between the 8/4/4/4/12 groups.
|
# then splice the four hyphens between the 8/4/4/4/12 groups.
|
||||||
emith("define ptr @fn_uuid_format(ptr %b16) {\n")
|
emith("define ptr @lp_uuid_format(ptr %b16) {\n")
|
||||||
emith("entry:\n %hex = call ptr @fn_hex_encode(ptr %b16, i64 16)\n %out = call ptr @malloc(i64 37)\n")
|
emith("entry:\n %hex = call ptr @lp_hex_encode(ptr %b16, i64 16)\n %out = call ptr @malloc(i64 37)\n")
|
||||||
emith(" call ptr @memcpy(ptr %out, ptr %hex, i64 8)\n")
|
emith(" call ptr @memcpy(ptr %out, ptr %hex, i64 8)\n")
|
||||||
emith(" %o8 = getelementptr i8, ptr %out, i64 8\n store i8 45, ptr %o8\n")
|
emith(" %o8 = getelementptr i8, ptr %out, i64 8\n store i8 45, ptr %o8\n")
|
||||||
emith(" %h8 = getelementptr i8, ptr %hex, i64 8\n %o9 = getelementptr i8, ptr %out, i64 9\n call ptr @memcpy(ptr %o9, ptr %h8, i64 4)\n")
|
emith(" %h8 = getelementptr i8, ptr %hex, i64 8\n %o9 = getelementptr i8, ptr %out, i64 9\n call ptr @memcpy(ptr %o9, ptr %h8, i64 4)\n")
|
||||||
|
|
@ -87,18 +87,18 @@ function emit_uuid_prelude() -> void {
|
||||||
|
|
||||||
# v4: 16 CSPRNG bytes, then set version (0x4x in byte 6) and variant (0b10xx in
|
# v4: 16 CSPRNG bytes, then set version (0x4x in byte 6) and variant (0b10xx in
|
||||||
# byte 8). 0x80 does not fit an i8 immediate, so it is written as -128.
|
# byte 8). 0x80 does not fit an i8 immediate, so it is written as -128.
|
||||||
emith("define ptr @fn_uuid_v4() {\n")
|
emith("define ptr @lp_uuid_v4() {\n")
|
||||||
emith("entry:\n %b = alloca [16 x i8]\n %bp = getelementptr [16 x i8], ptr %b, i64 0, i64 0\n call void @fn_secure_bytes(ptr %bp, i64 16)\n")
|
emith("entry:\n %b = alloca [16 x i8]\n %bp = getelementptr [16 x i8], ptr %b, i64 0, i64 0\n call void @lp_secure_bytes(ptr %bp, i64 16)\n")
|
||||||
emith(" %p6 = getelementptr i8, ptr %bp, i64 6\n %v6 = load i8, ptr %p6\n %v6a = and i8 %v6, 15\n %v6b = or i8 %v6a, 64\n store i8 %v6b, ptr %p6\n")
|
emith(" %p6 = getelementptr i8, ptr %bp, i64 6\n %v6 = load i8, ptr %p6\n %v6a = and i8 %v6, 15\n %v6b = or i8 %v6a, 64\n store i8 %v6b, ptr %p6\n")
|
||||||
emith(" %p8 = getelementptr i8, ptr %bp, i64 8\n %v8 = load i8, ptr %p8\n %v8a = and i8 %v8, 63\n %v8b = or i8 %v8a, -128\n store i8 %v8b, ptr %p8\n")
|
emith(" %p8 = getelementptr i8, ptr %bp, i64 8\n %v8 = load i8, ptr %p8\n %v8a = and i8 %v8, 63\n %v8b = or i8 %v8a, -128\n store i8 %v8b, ptr %p8\n")
|
||||||
emith(" %s = call ptr @fn_uuid_format(ptr %bp)\n ret ptr %s\n}\n")
|
emith(" %s = call ptr @lp_uuid_format(ptr %bp)\n ret ptr %s\n}\n")
|
||||||
|
|
||||||
# v7: random fill, then overwrite the first 6 bytes with a 48-bit big-endian
|
# v7: random fill, then overwrite the first 6 bytes with a 48-bit big-endian
|
||||||
# Unix-millisecond timestamp; set version 7 (0x7x) and the variant. Sub-second
|
# Unix-millisecond timestamp; set version 7 (0x7x) and the variant. Sub-second
|
||||||
# resolution is derived from time() seconds * 1000 — monotonic per second, with
|
# resolution is derived from time() seconds * 1000 — monotonic per second, with
|
||||||
# the random tail keeping same-millisecond IDs distinct.
|
# the random tail keeping same-millisecond IDs distinct.
|
||||||
emith("define ptr @fn_uuid_v7() {\n")
|
emith("define ptr @lp_uuid_v7() {\n")
|
||||||
emith("entry:\n %b = alloca [16 x i8]\n %bp = getelementptr [16 x i8], ptr %b, i64 0, i64 0\n call void @fn_secure_bytes(ptr %bp, i64 16)\n")
|
emith("entry:\n %b = alloca [16 x i8]\n %bp = getelementptr [16 x i8], ptr %b, i64 0, i64 0\n call void @lp_secure_bytes(ptr %bp, i64 16)\n")
|
||||||
emith(" %t = call i64 @time(ptr null)\n %ms = mul i64 %t, 1000\n")
|
emith(" %t = call i64 @time(ptr null)\n %ms = mul i64 %t, 1000\n")
|
||||||
emith(" %s40 = lshr i64 %ms, 40\n %t0 = trunc i64 %s40 to i8\n %q0 = getelementptr i8, ptr %bp, i64 0\n store i8 %t0, ptr %q0\n")
|
emith(" %s40 = lshr i64 %ms, 40\n %t0 = trunc i64 %s40 to i8\n %q0 = getelementptr i8, ptr %bp, i64 0\n store i8 %t0, ptr %q0\n")
|
||||||
emith(" %s32 = lshr i64 %ms, 32\n %t1 = trunc i64 %s32 to i8\n %q1 = getelementptr i8, ptr %bp, i64 1\n store i8 %t1, ptr %q1\n")
|
emith(" %s32 = lshr i64 %ms, 32\n %t1 = trunc i64 %s32 to i8\n %q1 = getelementptr i8, ptr %bp, i64 1\n store i8 %t1, ptr %q1\n")
|
||||||
|
|
@ -108,10 +108,10 @@ function emit_uuid_prelude() -> void {
|
||||||
emith(" %t5 = trunc i64 %ms to i8\n %q5 = getelementptr i8, ptr %bp, i64 5\n store i8 %t5, ptr %q5\n")
|
emith(" %t5 = trunc i64 %ms to i8\n %q5 = getelementptr i8, ptr %bp, i64 5\n store i8 %t5, ptr %q5\n")
|
||||||
emith(" %p6 = getelementptr i8, ptr %bp, i64 6\n %v6 = load i8, ptr %p6\n %v6a = and i8 %v6, 15\n %v6b = or i8 %v6a, 112\n store i8 %v6b, ptr %p6\n")
|
emith(" %p6 = getelementptr i8, ptr %bp, i64 6\n %v6 = load i8, ptr %p6\n %v6a = and i8 %v6, 15\n %v6b = or i8 %v6a, 112\n store i8 %v6b, ptr %p6\n")
|
||||||
emith(" %p8 = getelementptr i8, ptr %bp, i64 8\n %v8 = load i8, ptr %p8\n %v8a = and i8 %v8, 63\n %v8b = or i8 %v8a, -128\n store i8 %v8b, ptr %p8\n")
|
emith(" %p8 = getelementptr i8, ptr %bp, i64 8\n %v8 = load i8, ptr %p8\n %v8a = and i8 %v8, 63\n %v8b = or i8 %v8a, -128\n store i8 %v8b, ptr %p8\n")
|
||||||
emith(" %s = call ptr @fn_uuid_format(ptr %bp)\n ret ptr %s\n}\n")
|
emith(" %s = call ptr @lp_uuid_format(ptr %bp)\n ret ptr %s\n}\n")
|
||||||
|
|
||||||
# the nil UUID: 36 '0' with hyphens spliced in
|
# the nil UUID: 36 '0' with hyphens spliced in
|
||||||
emith("define ptr @fn_uuid_nil() {\n")
|
emith("define ptr @lp_uuid_nil() {\n")
|
||||||
emith("entry:\n %out = call ptr @malloc(i64 37)\n call ptr @memset(ptr %out, i32 48, i64 36)\n")
|
emith("entry:\n %out = call ptr @malloc(i64 37)\n call ptr @memset(ptr %out, i32 48, i64 36)\n")
|
||||||
emith(" %o8 = getelementptr i8, ptr %out, i64 8\n store i8 45, ptr %o8\n")
|
emith(" %o8 = getelementptr i8, ptr %out, i64 8\n store i8 45, ptr %o8\n")
|
||||||
emith(" %o13 = getelementptr i8, ptr %out, i64 13\n store i8 45, ptr %o13\n")
|
emith(" %o13 = getelementptr i8, ptr %out, i64 13\n store i8 45, ptr %o13\n")
|
||||||
|
|
@ -120,7 +120,7 @@ function emit_uuid_prelude() -> void {
|
||||||
emith(" %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n ret ptr %out\n}\n")
|
emith(" %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n ret ptr %out\n}\n")
|
||||||
|
|
||||||
# is %s a well-formed UUID? length 36, hyphens at 8/13/18/23, hex elsewhere.
|
# is %s a well-formed UUID? length 36, hyphens at 8/13/18/23, hex elsewhere.
|
||||||
emith("define i32 @fn_uuid_valid(ptr %s) {\n")
|
emith("define i32 @lp_uuid_valid(ptr %s) {\n")
|
||||||
emith("entry:\n %n = call i64 @strlen(ptr %s)\n %ne = icmp eq i64 %n, 36\n br i1 %ne, label %go, label %bad\n")
|
emith("entry:\n %n = call i64 @strlen(ptr %s)\n %ne = icmp eq i64 %n, 36\n br i1 %ne, label %go, label %bad\n")
|
||||||
emith("go:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
emith("go:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
||||||
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 36\n br i1 %lt, label %body, label %good\n")
|
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 36\n br i1 %lt, label %body, label %good\n")
|
||||||
|
|
@ -138,7 +138,7 @@ function emit_uuid_prelude() -> void {
|
||||||
emith("bad:\n ret i32 0\n}\n")
|
emith("bad:\n ret i32 0\n}\n")
|
||||||
|
|
||||||
# case-insensitive equality of two null-terminated strings -> i32 bool
|
# case-insensitive equality of two null-terminated strings -> i32 bool
|
||||||
emith("define i32 @fn_uuid_eq(ptr %a, ptr %b) {\n")
|
emith("define i32 @lp_uuid_eq(ptr %a, ptr %b) {\n")
|
||||||
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eq = icmp eq i64 %la, %lb\n br i1 %eq, label %go, label %ne\n")
|
emith("entry:\n %la = call i64 @strlen(ptr %a)\n %lb = call i64 @strlen(ptr %b)\n %eq = icmp eq i64 %la, %lb\n br i1 %eq, label %go, label %ne\n")
|
||||||
emith("go:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
emith("go:\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
||||||
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %la\n br i1 %lt, label %body, label %eqret\n")
|
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, %la\n br i1 %lt, label %body, label %eqret\n")
|
||||||
|
|
@ -151,8 +151,8 @@ function emit_uuid_prelude() -> void {
|
||||||
emith("ne:\n ret i32 0\n}\n")
|
emith("ne:\n ret i32 0\n}\n")
|
||||||
|
|
||||||
# parse: lowercase-normalise a valid UUID, else return the nil UUID.
|
# parse: lowercase-normalise a valid UUID, else return the nil UUID.
|
||||||
emith("define ptr @fn_uuid_parse(ptr %s) {\n")
|
emith("define ptr @lp_uuid_parse(ptr %s) {\n")
|
||||||
emith("entry:\n %ok = call i32 @fn_uuid_valid(ptr %s)\n %isok = icmp ne i32 %ok, 0\n br i1 %isok, label %dup, label %nilb\n")
|
emith("entry:\n %ok = call i32 @lp_uuid_valid(ptr %s)\n %isok = icmp ne i32 %ok, 0\n br i1 %isok, label %dup, label %nilb\n")
|
||||||
emith("dup:\n %out = call ptr @malloc(i64 37)\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
emith("dup:\n %out = call ptr @malloc(i64 37)\n %ip = alloca i64\n store i64 0, ptr %ip\n br label %cond\n")
|
||||||
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 36\n br i1 %lt, label %body, label %fin\n")
|
emith("cond:\n %i = load i64, ptr %ip\n %lt = icmp ult i64 %i, 36\n br i1 %lt, label %body, label %fin\n")
|
||||||
emith("body:\n %p = getelementptr i8, ptr %s, i64 %i\n %c = load i8, ptr %p\n")
|
emith("body:\n %p = getelementptr i8, ptr %s, i64 %i\n %c = load i8, ptr %p\n")
|
||||||
|
|
@ -160,5 +160,5 @@ function emit_uuid_prelude() -> void {
|
||||||
emith(" %op = getelementptr i8, ptr %out, i64 %i\n store i8 %cl, ptr %op\n")
|
emith(" %op = getelementptr i8, ptr %out, i64 %i\n store i8 %cl, ptr %op\n")
|
||||||
emith(" %i1 = add i64 %i, 1\n store i64 %i1, ptr %ip\n br label %cond\n")
|
emith(" %i1 = add i64 %i, 1\n store i64 %i1, ptr %ip\n br label %cond\n")
|
||||||
emith("fin:\n %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n ret ptr %out\n")
|
emith("fin:\n %o36 = getelementptr i8, ptr %out, i64 36\n store i8 0, ptr %o36\n ret ptr %out\n")
|
||||||
emith("nilb:\n %nn = call ptr @fn_uuid_nil()\n ret ptr %nn\n}\n")
|
emith("nilb:\n %nn = call ptr @lp_uuid_nil()\n ret ptr %nn\n}\n")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ function emit_vector_ns(meth: pointer, e: Node) -> Val {
|
||||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||||
let xx = fx_mul_code(vx, vx); let yy = fx_mul_code(vy, vy)
|
let xx = fx_mul_code(vx, vx); let yy = fx_mul_code(vy, vy)
|
||||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sqrt(i32 {s})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "distance") { # length(a - b) -> fixed
|
if (meth == "distance") { # length(a - b) -> fixed
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
|
|
@ -95,7 +95,7 @@ function emit_vector_ns(meth: pointer, e: Node) -> Val {
|
||||||
let dy = emit_bind(`sub i32 {ay}, {by}`)
|
let dy = emit_bind(`sub i32 {ay}, {by}`)
|
||||||
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
let xx = fx_mul_code(dx, dx); let yy = fx_mul_code(dy, dy)
|
||||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||||
return val(emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_sqrt(i32 {s})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "normalize") { # v / length(v); the zero vector maps to itself
|
if (meth == "normalize") { # v / length(v); the zero vector maps to itself
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
|
|
@ -103,7 +103,7 @@ function emit_vector_ns(meth: pointer, e: Node) -> Val {
|
||||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||||
let xx = fx_mul_code(vx, vx); let yy = fx_mul_code(vy, vy)
|
let xx = fx_mul_code(vx, vx); let yy = fx_mul_code(vy, vy)
|
||||||
let s = emit_bind(`add i32 {xx}, {yy}`)
|
let s = emit_bind(`add i32 {xx}, {yy}`)
|
||||||
let len = emit_bind(`call i32 @fn_fx_sqrt(i32 {s})`)
|
let len = emit_bind(`call i32 @lp_fx_sqrt(i32 {s})`)
|
||||||
let zero = emit_bind(`icmp eq i32 {len}, 0`)
|
let zero = emit_bind(`icmp eq i32 {len}, 0`)
|
||||||
let denom = emit_bind(`select i1 {zero}, i32 65536, i32 {len}`) # avoid divide-by-zero
|
let denom = emit_bind(`select i1 {zero}, i32 65536, i32 {len}`) # avoid divide-by-zero
|
||||||
let inv = fx_div_code("65536", denom)
|
let inv = fx_div_code("65536", denom)
|
||||||
|
|
@ -113,9 +113,9 @@ function emit_vector_ns(meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "rotate") { # rotate by angle (radians, fixed)
|
if (meth == "rotate") { # rotate by angle (radians, fixed)
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let v = emit_expr(e.kids[0]); let ang = emit_expr(e.kids[1])
|
let v = emit_expr(e.kids[0]); let ang = emit_expr(e.kids[1])
|
||||||
let sn = emit_bind(`call i32 @fn_fx_sin(i32 {ang.code})`)
|
let sn = emit_bind(`call i32 @lp_fx_sin(i32 {ang.code})`)
|
||||||
let ca = emit_bind(`add i32 {ang.code}, 102944`) # cos(a) = sin(a + pi/2)
|
let ca = emit_bind(`add i32 {ang.code}, 102944`) # cos(a) = sin(a + pi/2)
|
||||||
let cs = emit_bind(`call i32 @fn_fx_sin(i32 {ca})`)
|
let cs = emit_bind(`call i32 @lp_fx_sin(i32 {ca})`)
|
||||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||||
let xc = fx_mul_code(vx, cs); let ys = fx_mul_code(vy, sn)
|
let xc = fx_mul_code(vx, cs); let ys = fx_mul_code(vy, sn)
|
||||||
let xs = fx_mul_code(vx, sn); let yc = fx_mul_code(vy, cs)
|
let xs = fx_mul_code(vx, sn); let yc = fx_mul_code(vy, cs)
|
||||||
|
|
@ -127,14 +127,14 @@ function emit_vector_ns(meth: pointer, e: Node) -> Val {
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let v = emit_expr(e.kids[0])
|
let v = emit_expr(e.kids[0])
|
||||||
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
let vx = vec_x(v.code); let vy = vec_y(v.code)
|
||||||
return val(emit_bind(`call i32 @fn_fx_atan2(i32 {vy}, i32 {vx})`), "fixed")
|
return val(emit_bind(`call i32 @lp_fx_atan2(i32 {vy}, i32 {vx})`), "fixed")
|
||||||
}
|
}
|
||||||
if (meth == "from_angle") { # unit vector at angle a: (cos a, sin a)
|
if (meth == "from_angle") { # unit vector at angle a: (cos a, sin a)
|
||||||
g_uses_mathrt = true
|
g_uses_mathrt = true
|
||||||
let ang = emit_expr(e.kids[0])
|
let ang = emit_expr(e.kids[0])
|
||||||
let sn = emit_bind(`call i32 @fn_fx_sin(i32 {ang.code})`)
|
let sn = emit_bind(`call i32 @lp_fx_sin(i32 {ang.code})`)
|
||||||
let ca = emit_bind(`add i32 {ang.code}, 102944`)
|
let ca = emit_bind(`add i32 {ang.code}, 102944`)
|
||||||
let cs = emit_bind(`call i32 @fn_fx_sin(i32 {ca})`)
|
let cs = emit_bind(`call i32 @lp_fx_sin(i32 {ca})`)
|
||||||
return val(vec_pack(cs, sn), "Vector")
|
return val(vec_pack(cs, sn), "Vector")
|
||||||
}
|
}
|
||||||
# lerp(a, b, t: fixed) -> Vector — component-wise linear interpolation
|
# lerp(a, b, t: fixed) -> Vector — component-wise linear interpolation
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# ast.ludic — the node kinds and the single Node shape they share, plus the
|
# ast.ludic — the node kinds and the single Node shape they share, plus the
|
||||||
# declaration tables the emitter reads. Mirrors compiler/front/ast.h + sem/.
|
# declaration tables the emitter reads.
|
||||||
|
|
||||||
# declarations
|
# declarations
|
||||||
const N_STRUCT: int = 0
|
const N_STRUCT: int = 0
|
||||||
|
|
@ -36,6 +36,7 @@ const N_PREFAB: int = 53 # prefab Name: Model { Comp { field: v }, … }
|
||||||
const E_TRY: int = 52 # try EXPR else { ... } — recover a fallible result as a value (issue #46)
|
const E_TRY: int = 52 # try EXPR else { ... } — recover a fallible result as a value (issue #46)
|
||||||
# a=the fallible (result-typed) expression b=else block (its
|
# a=the fallible (result-typed) expression b=else block (its
|
||||||
# trailing expression is the fallback) line=source line
|
# trailing expression is the fallback) line=source line
|
||||||
|
const E_LIST: int = 54 # [a, b, c] — a slice literal; kids=the elements, all of one type
|
||||||
# statements
|
# statements
|
||||||
const S_LET: int = 10
|
const S_LET: int = 10
|
||||||
const S_ASSIGN: int = 11
|
const S_ASSIGN: int = 11
|
||||||
|
|
@ -81,11 +82,16 @@ property Node {
|
||||||
c: Node
|
c: Node
|
||||||
kids: []Node # variadic children
|
kids: []Node # variadic children
|
||||||
line: int = 0
|
line: int = 0
|
||||||
|
file: pointer = null # the source file the node was parsed from (for diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# every node remembers where it was parsed (file + the line of the token the
|
||||||
|
# parser is looking at), so any later diagnostic can point at real source
|
||||||
function node(kind: int) -> Node {
|
function node(kind: int) -> Node {
|
||||||
let n = new Node
|
let n = new Node
|
||||||
n.kind = kind
|
n.kind = kind
|
||||||
n.kids = new []Node
|
n.kids = new []Node
|
||||||
|
n.file = g_parse_file
|
||||||
|
if g_parsing and pi < len(toks) { n.line = toks[pi].line }
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# lex.ludic — source text -> a token slice. Mirrors compiler/front/lex.c.
|
# lex.ludic — source text -> a token slice. Tokens carry their kind, their
|
||||||
# Tokens carry their kind, their text (identifiers, strings, operators),
|
# text (identifiers, strings, operators), an integer value (numbers, char
|
||||||
# an integer value (numbers, char literals) and a line for diagnostics.
|
# literals) and a line for diagnostics.
|
||||||
|
|
||||||
const TK_ID: int = 0
|
const TK_ID: int = 0
|
||||||
const TK_INT: int = 1
|
const TK_INT: int = 1
|
||||||
|
|
@ -28,102 +28,125 @@ function two_at(src: pointer, i: int, a: int, b: int) -> bool {
|
||||||
|
|
||||||
function is_op1(c: int) -> bool {
|
function is_op1(c: int) -> bool {
|
||||||
# + - * / % < > = ( ) { } [ ] , : . ! @
|
# + - * / % < > = ( ) { } [ ] , : . ! @
|
||||||
if c == 43 or c == 45 or c == 42 or c == 47 or c == 37 { return true }
|
if c == '+' or c == '-' or c == '*' or c == '/' or c == '%' { return true }
|
||||||
if c == 60 or c == 62 or c == 61 { return true }
|
if c == '<' or c == '>' or c == '=' { return true }
|
||||||
if c == 40 or c == 41 or c == 123 or c == 125 { return true }
|
if c == '(' or c == ')' or c == '{' or c == '}' { return true }
|
||||||
if c == 91 or c == 93 or c == 44 or c == 58 or c == 46 { return true }
|
if c == '[' or c == ']' or c == ',' or c == ':' or c == '.' { return true }
|
||||||
if c == 33 or c == 64 { return true }
|
if c == '!' or c == '@' { return true }
|
||||||
if c == 38 or c == 124 or c == 94 or c == 126 { return true } # & | ^ ~
|
if c == '&' or c == '|' or c == '^' or c == '~' { return true } # & | ^ ~
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function lex(src: pointer) -> void {
|
# The two bytes that delimit escapes, spelled numerically on purpose: this file
|
||||||
|
# is what teaches the compiler to read `'\''` and `'\\'`, and the seed that
|
||||||
|
# bootstraps it must lex it without already knowing those spellings.
|
||||||
|
const CH_SQUOTE: int = 39 # '
|
||||||
|
const CH_BACKSLASH: int = 92 # \
|
||||||
|
|
||||||
|
# the byte an escape sequence `\e` stands for, shared by "strings" and 'chars':
|
||||||
|
# \n \r \t \0 are the named ones; anything else (\\ \' \" \`) is itself.
|
||||||
|
function unescape(e: int) -> int {
|
||||||
|
if e == 'n' { return 10 }
|
||||||
|
if e == 'r' { return 13 }
|
||||||
|
if e == 't' { return 9 }
|
||||||
|
if e == '0' { return 0 }
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
# a character the language has no use for is an error, never silently dropped:
|
||||||
|
# `x = 5 $ 3` must not compile as `x = 5 3`.
|
||||||
|
function lex_error(line: int, c: int) -> void {
|
||||||
|
let ch = bytes(2); ch[0] = c; ch[1] = 0
|
||||||
|
lex_fail(line, `unexpected character '{ch}' (byte {c})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
# report a lexical error in the standard `file:line: error: msg` shape and stop
|
||||||
|
function lex_fail(line: int, msg: pointer) -> void {
|
||||||
|
let m = `{g_parse_file}:{line}: error: {msg}\n`
|
||||||
|
file_write(file_stderr(), m, len(m))
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function lex(src: pointer) -> void { lex_at(src, 1) }
|
||||||
|
|
||||||
|
# lex `src` with its first line numbered `first_line` (an interpolation hole is
|
||||||
|
# re-lexed on its own, and keeps the line of the string it sits in)
|
||||||
|
function lex_at(src: pointer, first_line: int) -> void {
|
||||||
toks = new []Tok
|
toks = new []Tok
|
||||||
var i = 0
|
var i = 0
|
||||||
var line = 1
|
var line = first_line
|
||||||
let n = len(src)
|
let n = len(src)
|
||||||
while i < n {
|
while i < n {
|
||||||
let c = src[i]
|
let c = src[i]
|
||||||
if c == 10 { tok_push(TK_NL, null, 0, line); line = line + 1; i = i + 1; continue }
|
if c == '\n' { tok_push(TK_NL, null, 0, line); line += 1; i += 1; continue }
|
||||||
if c == 32 or c == 9 or c == 13 { i = i + 1; continue }
|
if c == ' ' or c == '\t' or c == '\r' { i += 1; continue }
|
||||||
if c == 35 { # '#' comment to end of line
|
if c == '#' { # '#' comment to end of line
|
||||||
while i < n and src[i] != 10 { i = i + 1 }
|
while i < n and src[i] != '\n' { i += 1 }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c == 34 { # "string"
|
if c == '"' { # "string"
|
||||||
i = i + 1
|
i += 1
|
||||||
let start = i
|
let start = i
|
||||||
let out = bytes(n)
|
let out = bytes(n)
|
||||||
var j = 0
|
var j = 0
|
||||||
while i < n and src[i] != 34 {
|
while i < n and src[i] != '"' {
|
||||||
if src[i] == 92 { # backslash escape
|
if src[i] == CH_BACKSLASH { # backslash escape
|
||||||
let e = src[i + 1]
|
out[j] = unescape(src[i + 1]); j += 1; i += 2
|
||||||
var r = e
|
} else { out[j] = src[i]; j += 1; i += 1 }
|
||||||
if e == 110 { r = 10 }
|
|
||||||
if e == 114 { r = 13 }
|
|
||||||
if e == 116 { r = 9 }
|
|
||||||
if e == 48 { r = 0 }
|
|
||||||
out[j] = r; j = j + 1; i = i + 2
|
|
||||||
} else { out[j] = src[i]; j = j + 1; i = i + 1 }
|
|
||||||
}
|
}
|
||||||
i = i + 1
|
i += 1
|
||||||
out[j] = 0
|
out[j] = 0
|
||||||
tok_push(TK_STR, out, 0, line)
|
tok_push(TK_STR, out, 0, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c == 96 { # `interpolated string` — captured raw
|
if c == '`' { # `interpolated string` — captured raw
|
||||||
i = i + 1
|
i += 1
|
||||||
let out = bytes(n)
|
let out = bytes(n)
|
||||||
var j = 0
|
var j = 0
|
||||||
while i < n and src[i] != 96 { out[j] = src[i]; j = j + 1; i = i + 1 }
|
while i < n and src[i] != '`' { out[j] = src[i]; j += 1; i += 1 }
|
||||||
i = i + 1
|
i += 1
|
||||||
out[j] = 0
|
out[j] = 0
|
||||||
tok_push(TK_INTERP, out, 0, line)
|
tok_push(TK_INTERP, out, 0, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c == 39 { # 'c' char literal -> int
|
if c == CH_SQUOTE { # 'c' char literal -> int
|
||||||
i = i + 1
|
i += 1
|
||||||
var v = 0
|
var v = 0
|
||||||
if src[i] == 92 {
|
if src[i] == CH_BACKSLASH { v = unescape(src[i + 1]); i += 2 }
|
||||||
let e = src[i + 1]
|
else { v = src[i]; i += 1 }
|
||||||
if e == 110 { v = 10 }
|
if src[i] != CH_SQUOTE { lex_fail(line, "unterminated character literal (expected closing ')") }
|
||||||
if e == 114 { v = 13 }
|
i += 1
|
||||||
if e == 116 { v = 9 }
|
|
||||||
if e == 48 { v = 0 }
|
|
||||||
i = i + 2
|
|
||||||
} else { v = src[i]; i = i + 1 }
|
|
||||||
if src[i] == 39 { i = i + 1 }
|
|
||||||
tok_push(TK_INT, null, v, line)
|
tok_push(TK_INT, null, v, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if char_is_digit(c) {
|
if char_is_digit(c) {
|
||||||
if c == 48 and src[i + 1] == 120 { # 0x hex
|
if c == '0' and src[i + 1] == 'x' { # 0x hex
|
||||||
var v = 0
|
var v = 0
|
||||||
i = i + 2
|
i += 2
|
||||||
while i < n {
|
while i < n {
|
||||||
let h = src[i]
|
let h = src[i]
|
||||||
var d = 0
|
var d = 0
|
||||||
if char_is_digit(h) { d = h - 48 }
|
if char_is_digit(h) { d = h - 48 }
|
||||||
else { if h >= 97 and h <= 102 { d = h - 87 }
|
else { if h >= 'a' and h <= 'f' { d = h - 87 }
|
||||||
else { if h >= 65 and h <= 70 { d = h - 55 } else { break } } }
|
else { if h >= 'A' and h <= 'F' { d = h - 55 } else { break } } }
|
||||||
v = v * 16 + d
|
v = v * 16 + d
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
tok_push(TK_INT, null, v, line)
|
tok_push(TK_INT, null, v, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var v = 0
|
var v = 0
|
||||||
while i < n and char_is_digit(src[i]) { v = v * 10 + (src[i] - 48); i = i + 1 }
|
while i < n and char_is_digit(src[i]) { v = v * 10 + (src[i] - 48); i += 1 }
|
||||||
# a fractional part makes it a Q16.16 fixed literal
|
# a fractional part makes it a Q16.16 fixed literal
|
||||||
if i < n and src[i] == 46 and char_is_digit(src[i + 1]) {
|
if i < n and src[i] == '.' and char_is_digit(src[i + 1]) {
|
||||||
i = i + 1
|
i += 1
|
||||||
# Accumulate only the first 4 fractional digits: `fnum << 16` must stay
|
# Accumulate only the first 4 fractional digits: `fnum << 16` must stay
|
||||||
# in i32 (5+ digits overflow), and Q16.16 resolves ~4-5 decimals anyway.
|
# in i32 (5+ digits overflow), and Q16.16 resolves ~4-5 decimals anyway.
|
||||||
# Extra digits are still consumed so they don't become a stray token.
|
# Extra digits are still consumed so they don't become a stray token.
|
||||||
var fnum = 0; var fden = 1
|
var fnum = 0; var fden = 1
|
||||||
while i < n and char_is_digit(src[i]) {
|
while i < n and char_is_digit(src[i]) {
|
||||||
if fden < 10000 { fnum = fnum * 10 + (src[i] - 48); fden = fden * 10 }
|
if fden < 10000 { fnum = fnum * 10 + (src[i] - 48); fden *= 10 }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
let bits = (v << 16) + ((fnum << 16) + (fden >> 1)) / fden
|
let bits = (v << 16) + ((fnum << 16) + (fden >> 1)) / fden
|
||||||
tok_push(TK_FLOAT, null, bits, line)
|
tok_push(TK_FLOAT, null, bits, line)
|
||||||
|
|
@ -134,27 +157,27 @@ function lex(src: pointer) -> void {
|
||||||
}
|
}
|
||||||
if char_is_alpha(c) {
|
if char_is_alpha(c) {
|
||||||
let start = i
|
let start = i
|
||||||
while i < n and char_is_alnum(src[i]) { i = i + 1 }
|
while i < n and char_is_alnum(src[i]) { i += 1 }
|
||||||
tok_push(TK_ID, src[start..i], 0, line)
|
tok_push(TK_ID, src[start..i], 0, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c == 59 { tok_push(TK_NL, null, 0, line); i = i + 1; continue } # ';'
|
if c == ';' { tok_push(TK_NL, null, 0, line); i += 1; continue } # ';'
|
||||||
# two-character operators
|
# two-character operators
|
||||||
if two_at(src, i, 45, 62) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # ->
|
if two_at(src, i, 45, 62) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # ->
|
||||||
if two_at(src, i, 61, 62) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # =>
|
if two_at(src, i, 61, 62) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # =>
|
||||||
if two_at(src, i, 61, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # ==
|
if two_at(src, i, 61, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # ==
|
||||||
if two_at(src, i, 33, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # !=
|
if two_at(src, i, 33, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # !=
|
||||||
if two_at(src, i, 60, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # <=
|
if two_at(src, i, 60, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # <=
|
||||||
if two_at(src, i, 62, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # >=
|
if two_at(src, i, 62, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # >=
|
||||||
if two_at(src, i, 43, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # +=
|
if two_at(src, i, 43, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # +=
|
||||||
if two_at(src, i, 45, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # -=
|
if two_at(src, i, 45, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # -=
|
||||||
if two_at(src, i, 42, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # *=
|
if two_at(src, i, 42, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # *=
|
||||||
if two_at(src, i, 47, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # /=
|
if two_at(src, i, 47, 61) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # /=
|
||||||
if two_at(src, i, 46, 46) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # ..
|
if two_at(src, i, 46, 46) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # ..
|
||||||
if two_at(src, i, 60, 60) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # <<
|
if two_at(src, i, 60, 60) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # <<
|
||||||
if two_at(src, i, 62, 62) { tok_push(TK_OP, src[i..i + 2], 0, line); i = i + 2; continue } # >>
|
if two_at(src, i, 62, 62) { tok_push(TK_OP, src[i..i + 2], 0, line); i += 2; continue } # >>
|
||||||
if is_op1(c) { tok_push(TK_OP, src[i..i + 1], 0, line); i = i + 1; continue }
|
if is_op1(c) { tok_push(TK_OP, src[i..i + 1], 0, line); i += 1; continue }
|
||||||
i = i + 1 # skip anything unrecognised
|
lex_error(line, c)
|
||||||
}
|
}
|
||||||
tok_push(TK_EOF, null, 0, line)
|
tok_push(TK_EOF, null, 0, line)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,16 @@
|
||||||
|
|
||||||
var pi: int = 0
|
var pi: int = 0
|
||||||
var prog: []Node # the top-level declarations
|
var prog: []Node # the top-level declarations
|
||||||
var g_game_name: pointer # the `game`/`module` name
|
var g_game_name: pointer # the `program` name
|
||||||
|
|
||||||
|
# ---- diagnostics -----------------------------------------------------------
|
||||||
|
# Every compiler error is `file:line: error: message` — the form editors and
|
||||||
|
# build tools parse. While parsing, the location is the current token's; while
|
||||||
|
# lowering, it is the statement being emitted (emit_stmt records it).
|
||||||
|
var g_parse_file: pointer = "" # the file whose tokens are being parsed
|
||||||
|
var g_parsing: bool = true # false once lowering starts
|
||||||
|
var g_err_file: pointer = "" # the statement being lowered
|
||||||
|
var g_err_line: int = 0
|
||||||
|
|
||||||
function cur() -> Tok { return toks[pi] }
|
function cur() -> Tok { return toks[pi] }
|
||||||
function pk(o: int) -> Tok { return toks[pi + o] }
|
function pk(o: int) -> Tok { return toks[pi + o] }
|
||||||
|
|
@ -13,32 +22,47 @@ function is_id(v: pointer) -> bool { let t = toks[pi]; return t.kind == TK_ID a
|
||||||
function is_kw(v: pointer) -> bool { return is_id(v) }
|
function is_kw(v: pointer) -> bool { return is_id(v) }
|
||||||
|
|
||||||
function perr(msg: pointer) -> void {
|
function perr(msg: pointer) -> void {
|
||||||
let e = file_stderr()
|
var file = g_err_file
|
||||||
file_write(e, "ludicc(self): parse error: ", 27)
|
var line = g_err_line
|
||||||
file_write(e, msg, len(msg))
|
if g_parsing {
|
||||||
file_write(e, "\n", 1)
|
file = g_parse_file
|
||||||
|
line = 0
|
||||||
|
if pi < len(toks) { line = toks[pi].line }
|
||||||
|
}
|
||||||
|
if file == null { file = "" }
|
||||||
|
let m = `{file}:{line}: error: {msg}\n`
|
||||||
|
file_write(file_stderr(), m, len(m))
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function eat_op(v: pointer) -> void { if not is_op(v) { perr(v) }; pi = pi + 1 }
|
# the current token, described for a diagnostic
|
||||||
|
function tok_desc() -> pointer {
|
||||||
|
let t = toks[pi]
|
||||||
|
if t.kind == TK_EOF { return "end of file" }
|
||||||
|
if t.kind == TK_NL { return "end of line" }
|
||||||
|
if t.kind == TK_INT or t.kind == TK_FLOAT { return `number {itoa(t.ival)}` }
|
||||||
|
if t.kind == TK_STR { return `string \"{t.text}\"` }
|
||||||
|
return `'{t.text}'`
|
||||||
|
}
|
||||||
|
function eat_op(v: pointer) -> void { if not is_op(v) { perr(`expected '{v}', got {tok_desc()}`) }; pi += 1 }
|
||||||
function eat_id() -> pointer {
|
function eat_id() -> pointer {
|
||||||
let t = toks[pi]
|
let t = toks[pi]
|
||||||
if t.kind != TK_ID { perr("expected identifier") }
|
if t.kind != TK_ID { perr(`expected an identifier, got {tok_desc()}`) }
|
||||||
pi = pi + 1
|
pi += 1
|
||||||
return t.text
|
return t.text
|
||||||
}
|
}
|
||||||
function skipnl() -> void { while toks[pi].kind == TK_NL { pi = pi + 1 } }
|
function skipnl() -> void { while toks[pi].kind == TK_NL { pi += 1 } }
|
||||||
|
|
||||||
# a type: `[]T` slice, or a plain name (int/ptr/str/bool/struct)
|
# a type: `[]T` slice, or a plain name (int/ptr/str/bool/struct)
|
||||||
function ptype() -> pointer {
|
function ptype() -> pointer {
|
||||||
if is_op("[") {
|
if is_op("[") {
|
||||||
pi = pi + 1
|
pi += 1
|
||||||
eat_op("]")
|
eat_op("]")
|
||||||
let el = ptype()
|
let el = ptype()
|
||||||
let out = bytes(len(el) + 3)
|
let out = bytes(len(el) + 3)
|
||||||
out[0] = 91; out[1] = 93 # "[]"
|
out[0] = '['; out[1] = ']' # "[]"
|
||||||
var i = 0
|
var i = 0
|
||||||
while el[i] != 0 { out[2 + i] = el[i]; i = i + 1 }
|
while el[i] != 0 { out[2 + i] = el[i]; i += 1 }
|
||||||
out[2 + i] = 0
|
out[2 + i] = 0
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +86,7 @@ function args_call(call: Node) -> void {
|
||||||
} else {
|
} else {
|
||||||
push(call.kids, expr())
|
push(call.kids, expr())
|
||||||
}
|
}
|
||||||
skipnl(); if is_op(",") { pi = pi + 1; skipnl() }
|
skipnl(); if is_op(",") { pi += 1; skipnl() }
|
||||||
}
|
}
|
||||||
eat_op(")")
|
eat_op(")")
|
||||||
}
|
}
|
||||||
|
|
@ -78,9 +102,10 @@ function interp_add(acc: Node, part: Node) -> Node {
|
||||||
function interp_str(e: Node) -> Node { # wrap a hole in string(...)
|
function interp_str(e: Node) -> Node { # wrap a hole in string(...)
|
||||||
let c = node(E_CALL); let id = node(E_ID); id.s = "string"; c.a = id; push(c.kids, e); return c
|
let c = node(E_CALL); let id = node(E_ID); id.s = "string"; c.a = id; push(c.kids, e); return c
|
||||||
}
|
}
|
||||||
|
var g_interp_line: int = 1 # the line the interpolated string sits on
|
||||||
function parse_hole(inner: pointer) -> Node { # re-lex+parse an embedded expression
|
function parse_hole(inner: pointer) -> Node { # re-lex+parse an embedded expression
|
||||||
let saved_toks = toks; let saved_pi = pi
|
let saved_toks = toks; let saved_pi = pi
|
||||||
lex(inner); pi = 0; skipnl()
|
lex_at(inner, g_interp_line); pi = 0; skipnl()
|
||||||
let e = expr()
|
let e = expr()
|
||||||
toks = saved_toks; pi = saved_pi
|
toks = saved_toks; pi = saved_pi
|
||||||
return e
|
return e
|
||||||
|
|
@ -93,29 +118,32 @@ function parse_interp(raw: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < n {
|
while i < n {
|
||||||
let c = raw[i]
|
let c = raw[i]
|
||||||
if c == 123 { # '{'
|
if c == '{' { # '{'
|
||||||
if raw[i + 1] == 123 { lit[lj] = 123; lj = lj + 1; i = i + 2; continue } # {{ -> {
|
if raw[i + 1] == '{' { lit[lj] = '{'; lj += 1; i += 2; continue } # {{ -> {
|
||||||
if lj > 0 { acc = interp_add(acc, interp_lit(lit, lj)); lj = 0 }
|
if lj > 0 { acc = interp_add(acc, interp_lit(lit, lj)); lj = 0 }
|
||||||
i = i + 1
|
i += 1
|
||||||
let hs = i
|
let hs = i
|
||||||
|
# find the hole's closing brace: braces nest, and a brace inside a
|
||||||
|
# "string" or 'char' literal in the hole is text, not structure
|
||||||
var depth = 1
|
var depth = 1
|
||||||
while i < n and depth > 0 {
|
while i < n and depth > 0 {
|
||||||
let d = raw[i]
|
let d = raw[i]
|
||||||
if d == 123 { depth = depth + 1 }
|
if d == '"' or d == CH_SQUOTE {
|
||||||
else { if d == 125 { depth = depth - 1; if depth == 0 { break } } }
|
i += 1
|
||||||
i = i + 1
|
while i < n and raw[i] != d { if raw[i] == CH_BACKSLASH { i += 1 }; i += 1 }
|
||||||
|
}
|
||||||
|
else if d == '{' { depth += 1 }
|
||||||
|
else if d == '}' { depth -= 1; if depth == 0 { break } }
|
||||||
|
i += 1
|
||||||
}
|
}
|
||||||
acc = interp_add(acc, interp_str(parse_hole(raw[hs..i])))
|
acc = interp_add(acc, interp_str(parse_hole(raw[hs..i])))
|
||||||
i = i + 1 # skip the closing '}'
|
i += 1 # skip the closing '}'
|
||||||
} else {
|
} else {
|
||||||
if c == 125 and raw[i + 1] == 125 { lit[lj] = 125; lj = lj + 1; i = i + 2; continue } # }} -> }
|
if c == '}' and raw[i + 1] == '}' { lit[lj] = '}'; lj += 1; i += 2; continue } # }} -> }
|
||||||
if c == 92 { # backslash escape in literal text
|
if c == CH_BACKSLASH { # backslash escape, same table as "strings"
|
||||||
let e = raw[i + 1]; var r = e
|
lit[lj] = unescape(raw[i + 1]); lj += 1; i += 2; continue
|
||||||
if e == 110 { r = 10 }
|
|
||||||
if e == 116 { r = 9 }
|
|
||||||
lit[lj] = r; lj = lj + 1; i = i + 2; continue
|
|
||||||
}
|
}
|
||||||
lit[lj] = c; lj = lj + 1; i = i + 1
|
lit[lj] = c; lj += 1; i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lj > 0 or (acc == null) { acc = interp_add(acc, interp_lit(lit, lj)) }
|
if lj > 0 or (acc == null) { acc = interp_add(acc, interp_lit(lit, lj)) }
|
||||||
|
|
@ -126,47 +154,60 @@ function parse_interp(raw: pointer) -> Node {
|
||||||
# As an expression it yields a cancellable event's cancelled flag (0/1); a
|
# As an expression it yields a cancellable event's cancelled flag (0/1); a
|
||||||
# non-cancellable event yields 0.
|
# non-cancellable event yields 0.
|
||||||
function parse_emit() -> Node {
|
function parse_emit() -> Node {
|
||||||
pi = pi + 1; let n = node(S_EMIT); n.s = eat_id()
|
pi += 1; let n = node(S_EMIT); n.s = eat_id()
|
||||||
let r = node(E_REC)
|
let r = node(E_REC)
|
||||||
eat_op("("); skipnl()
|
eat_op("("); skipnl()
|
||||||
while not is_op(")") {
|
while not is_op(")") {
|
||||||
let fi = node(E_FINIT); fi.s = eat_id(); eat_op(":"); fi.a = expr(); push(r.kids, fi)
|
let fi = node(E_FINIT); fi.s = eat_id(); eat_op(":"); fi.a = expr(); push(r.kids, fi)
|
||||||
skipnl(); if is_op(",") { pi = pi + 1; skipnl() }
|
skipnl(); if is_op(",") { pi += 1; skipnl() }
|
||||||
}
|
}
|
||||||
eat_op(")")
|
eat_op(")")
|
||||||
n.a = r
|
n.a = r
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# `[a, b, c]` — a slice literal (an empty `[]` needs a type: `new []T`)
|
||||||
|
function parse_list() -> Node {
|
||||||
|
let n = node(E_LIST); n.line = toks[pi].line
|
||||||
|
eat_op("["); skipnl()
|
||||||
|
while not is_op("]") {
|
||||||
|
push(n.kids, expr())
|
||||||
|
skipnl(); if is_op(",") { pi += 1; skipnl() }
|
||||||
|
}
|
||||||
|
eat_op("]")
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
function p_primary() -> Node {
|
function p_primary() -> Node {
|
||||||
let t = toks[pi]
|
let t = toks[pi]
|
||||||
if t.kind == TK_INTERP { pi = pi + 1; return parse_interp(t.text) }
|
if t.kind == TK_INTERP { pi += 1; g_interp_line = t.line; return parse_interp(t.text) }
|
||||||
|
if is_op("[") { return parse_list() }
|
||||||
if t.kind == TK_ID and (t.text == "emit") and (toks[pi + 1].kind == TK_ID) { return parse_emit() }
|
if t.kind == TK_ID and (t.text == "emit") and (toks[pi + 1].kind == TK_ID) { return parse_emit() }
|
||||||
if t.kind == TK_INT { let n = node(E_INT); n.ival = t.ival; pi = pi + 1; return n }
|
if t.kind == TK_INT { let n = node(E_INT); n.ival = t.ival; pi += 1; return n }
|
||||||
if t.kind == TK_FLOAT { let n = node(E_FLOAT); n.ival = t.ival; pi = pi + 1; return n }
|
if t.kind == TK_FLOAT { let n = node(E_FLOAT); n.ival = t.ival; pi += 1; return n }
|
||||||
if t.kind == TK_STR { let n = node(E_STR); n.s = t.text; pi = pi + 1; return n }
|
if t.kind == TK_STR { let n = node(E_STR); n.s = t.text; pi += 1; return n }
|
||||||
if t.kind == TK_ID {
|
if t.kind == TK_ID {
|
||||||
if (t.text == "true") { let n = node(E_BOOL); n.ival = 1; pi = pi + 1; return n }
|
if (t.text == "true") { let n = node(E_BOOL); n.ival = 1; pi += 1; return n }
|
||||||
if (t.text == "false") { let n = node(E_BOOL); n.ival = 0; pi = pi + 1; return n }
|
if (t.text == "false") { let n = node(E_BOOL); n.ival = 0; pi += 1; return n }
|
||||||
if (t.text == "null") { pi = pi + 1; return node(E_NULL) }
|
if (t.text == "null") { pi += 1; return node(E_NULL) }
|
||||||
if (t.text == "new") { pi = pi + 1; let n = node(E_NEW); n.s = ptype(); if is_op("{") { n.a = record() }; return n }
|
if (t.text == "new") { pi += 1; let n = node(E_NEW); n.s = ptype(); if is_op("{") { n.a = record() }; return n }
|
||||||
if (t.text == "spawn") { return parse_spawn() } # spawn as an expression: the new entity
|
if (t.text == "spawn") { return parse_spawn() } # spawn as an expression: the new entity
|
||||||
# try EXPR else { ... } — evaluate a fallible (result-typed) expression; on
|
# try EXPR else { ... } — evaluate a fallible (result-typed) expression; on
|
||||||
# `ok` the whole expression is its payload, on `err` the else block runs (with
|
# `ok` the whole expression is its payload, on `err` the else block runs (with
|
||||||
# the message bound to `error`) and its trailing expression is the fallback.
|
# the message bound to `error`) and its trailing expression is the fallback.
|
||||||
if (t.text == "try") {
|
if (t.text == "try") {
|
||||||
pi = pi + 1
|
pi += 1
|
||||||
let n = node(E_TRY); n.line = t.line
|
let n = node(E_TRY); n.line = t.line
|
||||||
n.a = expr()
|
n.a = expr()
|
||||||
skipnl()
|
skipnl()
|
||||||
if not is_id("else") { perr("try needs an `else { ... }` fallback") }
|
if not is_id("else") { perr("try needs an `else { ... }` fallback") }
|
||||||
pi = pi + 1; skipnl()
|
pi += 1; skipnl()
|
||||||
n.b = block()
|
n.b = block()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
let n = node(E_ID); n.s = t.text; pi = pi + 1; return n
|
let n = node(E_ID); n.s = t.text; pi += 1; return n
|
||||||
}
|
}
|
||||||
if is_op("(") { pi = pi + 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
if is_op("(") { pi += 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
||||||
perr("expected expression")
|
perr("expected expression")
|
||||||
return node(E_INT)
|
return node(E_INT)
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +215,7 @@ function p_primary() -> Node {
|
||||||
function p_postfix() -> Node {
|
function p_postfix() -> Node {
|
||||||
var e = p_primary()
|
var e = p_primary()
|
||||||
while true {
|
while true {
|
||||||
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
|
if is_op(".") { pi += 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
|
||||||
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
|
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
|
||||||
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
|
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
|
||||||
if e.a.kind == E_ID and (e.a.s == "Dict" or e.a.s == "Set") { g_uses_dict = true } # splice the hash-table runtime on demand
|
if e.a.kind == E_ID and (e.a.s == "Dict" or e.a.s == "Set") { g_uses_dict = true } # splice the hash-table runtime on demand
|
||||||
|
|
@ -213,20 +254,22 @@ function p_postfix() -> Node {
|
||||||
if e.a.kind == E_ID and e.a.s == "Tween" and (e.s == "to" or e.s == "chain" or e.s == "delay" or e.s == "value" or e.s == "stop" or e.s == "parallel") { g_uses_tween_rt = true }
|
if e.a.kind == E_ID and e.a.s == "Tween" and (e.s == "to" or e.s == "chain" or e.s == "delay" or e.s == "value" or e.s == "stop" or e.s == "parallel") { g_uses_tween_rt = true }
|
||||||
if e.a.kind == E_ID and e.a.s == "Fx" { g_uses_fx = true } # Fx.* -> splice fx.ludic + tick/draw it each frame
|
if e.a.kind == E_ID and e.a.s == "Fx" { g_uses_fx = true } # Fx.* -> splice fx.ludic + tick/draw it each frame
|
||||||
}
|
}
|
||||||
else { if is_op("[") { pi = pi + 1; let lo = expr()
|
else if is_op("[") { pi += 1; let lo = expr()
|
||||||
if is_op("..") { pi = pi + 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
|
if is_op("..") { pi += 1; let sl = node(E_SLICE); sl.a = e; sl.b = lo; sl.c = expr(); eat_op("]"); e = sl } # s[a..b] substring
|
||||||
else { let ix = node(E_INDEX); ix.a = e; ix.b = lo; eat_op("]"); e = ix } }
|
else { let ix = node(E_INDEX); ix.a = e; ix.b = lo; eat_op("]"); e = ix }
|
||||||
else { if is_op("(") { let c = node(E_CALL); c.a = e; c.line = toks[pi].line; args_call(c); e = c
|
}
|
||||||
|
else if is_op("(") { let c = node(E_CALL); c.a = e; c.line = toks[pi].line; args_call(c); e = c
|
||||||
if (c.a.kind == E_ID) and ((c.a.s == "draw_sprite") or (c.a.s == "draw_sprite_scaled")) { warn_draw_sprite(c.a.s) } # #85 deprecate the bare aliases
|
if (c.a.kind == E_ID) and ((c.a.s == "draw_sprite") or (c.a.s == "draw_sprite_scaled")) { warn_draw_sprite(c.a.s) } # #85 deprecate the bare aliases
|
||||||
} else { break } } }
|
}
|
||||||
|
else { break }
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
function p_unary() -> Node {
|
function p_unary() -> Node {
|
||||||
if is_op("-") { pi = pi + 1; let n = node(E_UN); n.s = "-"; n.a = p_unary(); return n }
|
if is_op("-") { pi += 1; let n = node(E_UN); n.s = "-"; n.a = p_unary(); return n }
|
||||||
if is_op("~") { pi = pi + 1; let n = node(E_UN); n.s = "~"; n.a = p_unary(); return n } # bitwise not
|
if is_op("~") { pi += 1; let n = node(E_UN); n.s = "~"; n.a = p_unary(); return n } # bitwise not
|
||||||
if is_id("not") { pi = pi + 1; let n = node(E_UN); n.s = "not"; n.a = p_unary(); return n }
|
if is_id("not") { pi += 1; let n = node(E_UN); n.s = "not"; n.a = p_unary(); return n }
|
||||||
return p_postfix()
|
return p_postfix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,30 +280,30 @@ function mkbin(op: pointer, l: Node, r: Node) -> Node { let b = node(E_BIN); b.s
|
||||||
function p_mul() -> Node {
|
function p_mul() -> Node {
|
||||||
var l = p_unary()
|
var l = p_unary()
|
||||||
while is_op("*") or is_op("/") or is_op("%") or is_op("<<") or is_op(">>") or is_op("&") {
|
while is_op("*") or is_op("/") or is_op("%") or is_op("<<") or is_op(">>") or is_op("&") {
|
||||||
let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_unary()) }
|
let op = toks[pi].text; pi += 1; l = mkbin(op, l, p_unary()) }
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
function p_add() -> Node {
|
function p_add() -> Node {
|
||||||
var l = p_mul()
|
var l = p_mul()
|
||||||
while is_op("+") or is_op("-") or is_op("|") or is_op("^") {
|
while is_op("+") or is_op("-") or is_op("|") or is_op("^") {
|
||||||
let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_mul()) }
|
let op = toks[pi].text; pi += 1; l = mkbin(op, l, p_mul()) }
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
function p_cmp() -> Node {
|
function p_cmp() -> Node {
|
||||||
var l = p_add()
|
var l = p_add()
|
||||||
while is_op("<") or is_op("<=") or is_op(">") or is_op(">=") or is_op("==") or is_op("!=") {
|
while is_op("<") or is_op("<=") or is_op(">") or is_op(">=") or is_op("==") or is_op("!=") {
|
||||||
let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_add())
|
let op = toks[pi].text; pi += 1; l = mkbin(op, l, p_add())
|
||||||
}
|
}
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
function p_and() -> Node {
|
function p_and() -> Node {
|
||||||
var l = p_cmp()
|
var l = p_cmp()
|
||||||
while is_id("and") { pi = pi + 1; l = mkbin("and", l, p_cmp()) }
|
while is_id("and") { pi += 1; l = mkbin("and", l, p_cmp()) }
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
function p_or() -> Node {
|
function p_or() -> Node {
|
||||||
var l = p_and()
|
var l = p_and()
|
||||||
while is_id("or") { pi = pi + 1; l = mkbin("or", l, p_and()) }
|
while is_id("or") { pi += 1; l = mkbin("or", l, p_and()) }
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +314,7 @@ function record() -> Node {
|
||||||
let r = node(E_REC)
|
let r = node(E_REC)
|
||||||
while true { skipnl(); if is_op("}") { break }
|
while true { skipnl(); if is_op("}") { break }
|
||||||
let fi = node(E_FINIT); fi.s = eat_id(); eat_op(":"); fi.a = expr(); push(r.kids, fi)
|
let fi = node(E_FINIT); fi.s = eat_id(); eat_op(":"); fi.a = expr(); push(r.kids, fi)
|
||||||
if is_op(",") { pi = pi + 1 } }
|
if is_op(",") { pi += 1 } }
|
||||||
eat_op("}"); return r
|
eat_op("}"); return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,27 +350,27 @@ function stmt_body() -> Node {
|
||||||
if t.kind == TK_ID {
|
if t.kind == TK_ID {
|
||||||
if (t.text == "let") or (t.text == "var") {
|
if (t.text == "let") or (t.text == "var") {
|
||||||
var mut = 0; if (t.text == "var") { mut = 1 } # let = immutable, var = mutable
|
var mut = 0; if (t.text == "var") { mut = 1 } # let = immutable, var = mutable
|
||||||
pi = pi + 1; let n = node(S_LET); n.ival = mut; n.line = toks[pi].line; n.s = eat_id()
|
pi += 1; let n = node(S_LET); n.ival = mut; n.line = toks[pi].line; n.s = eat_id()
|
||||||
if is_op(":") { pi = pi + 1; n.ty = ptype() }
|
if is_op(":") { pi += 1; n.ty = ptype() }
|
||||||
if is_op("=") { pi = pi + 1; n.a = expr() }
|
if is_op("=") { pi += 1; n.a = expr() }
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if (t.text == "return") {
|
if (t.text == "return") {
|
||||||
pi = pi + 1; let n = node(S_RETURN)
|
pi += 1; let n = node(S_RETURN)
|
||||||
if toks[pi].kind != TK_NL and not is_op("}") { n.a = expr() }
|
if toks[pi].kind != TK_NL and not is_op("}") { n.a = expr() }
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if (t.text == "if") {
|
if (t.text == "if") {
|
||||||
pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
|
pi += 1; let n = node(S_IF); n.a = expr(); n.b = block()
|
||||||
let save = pi; skipnl() # peek past newlines for a trailing `else`
|
let save = pi; skipnl() # peek past newlines for a trailing `else`
|
||||||
if is_id("else") { pi = pi + 1; skipnl()
|
if is_id("else") { pi += 1; skipnl()
|
||||||
if is_id("if") { n.c = stmt() } else { n.c = block() } }
|
if is_id("if") { n.c = stmt() } else { n.c = block() } }
|
||||||
else { pi = save } # no else: keep the separator for block()'s Rule-B check
|
else { pi = save } # no else: keep the separator for block()'s Rule-B check
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if (t.text == "while") { pi = pi + 1; let n = node(S_WHILE); n.a = expr(); n.b = block(); return n }
|
if (t.text == "while") { pi += 1; let n = node(S_WHILE); n.a = expr(); n.b = block(); return n }
|
||||||
if (t.text == "for") {
|
if (t.text == "for") {
|
||||||
pi = pi + 1
|
pi += 1
|
||||||
if is_op("(") { return parse_query_for() }
|
if is_op("(") { return parse_query_for() }
|
||||||
let n = node(S_FOR); n.s = eat_id()
|
let n = node(S_FOR); n.s = eat_id()
|
||||||
let inkw = eat_id() # 'in'
|
let inkw = eat_id() # 'in'
|
||||||
|
|
@ -336,54 +379,54 @@ function stmt_body() -> Node {
|
||||||
}
|
}
|
||||||
if (t.text == "spawn") { return parse_spawn() }
|
if (t.text == "spawn") { return parse_spawn() }
|
||||||
if (t.text == "machine") {
|
if (t.text == "machine") {
|
||||||
pi = pi + 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
|
pi += 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
|
||||||
var sidx = 0 # states auto-number by declaration order
|
var sidx = 0 # states auto-number by declaration order
|
||||||
while true { skipnl(); if is_op("}") { break }
|
while true { skipnl(); if is_op("}") { break }
|
||||||
let stkw = eat_id() # 'state'
|
let stkw = eat_id() # 'state'
|
||||||
let s = node(S_STATE); s.s = eat_id()
|
let s = node(S_STATE); s.s = eat_id()
|
||||||
if is_op("=") { pi = pi + 1; s.b = expr() } # explicit value (still allowed)
|
if is_op("=") { pi += 1; s.b = expr() } # explicit value (still allowed)
|
||||||
s.ival = sidx # else: the enum variant of the same name
|
s.ival = sidx # else: the enum variant of the same name
|
||||||
# when the store is enum-typed, or this ordinal
|
# when the store is enum-typed, or this ordinal
|
||||||
skipnl(); s.a = block()
|
skipnl(); s.a = block()
|
||||||
push(n.kids, s); sidx = sidx + 1 }
|
push(n.kids, s); sidx += 1 }
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
# `emit E(...)` fires an event, but a bare `emit(...)` is an ordinary call
|
# `emit E(...)` fires an event, but a bare `emit(...)` is an ordinary call
|
||||||
# (the compiler dogfoods a function named `emit`), so require an event name.
|
# (the compiler dogfoods a function named `emit`), so require an event name.
|
||||||
if (t.text == "emit") and (toks[pi + 1].kind == TK_ID) { return parse_emit() }
|
if (t.text == "emit") and (toks[pi + 1].kind == TK_ID) { return parse_emit() }
|
||||||
if (t.text == "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
|
if (t.text == "become") { pi += 1; let n = node(S_BECOME); n.s = eat_id(); return n }
|
||||||
if (t.text == "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
|
if (t.text == "despawn") { pi += 1; let n = node(S_DESPAWN); n.a = expr(); return n }
|
||||||
if (t.text == "enable") or (t.text == "disable") {
|
if (t.text == "enable") or (t.text == "disable") {
|
||||||
var en = 0; if (t.text == "enable") { en = 1 }
|
var en = 0; if (t.text == "enable") { en = 1 }
|
||||||
pi = pi + 1; let n = node(S_TOGGLE); n.ival = en
|
pi += 1; let n = node(S_TOGGLE); n.ival = en
|
||||||
if is_id("layer") { pi = pi + 1; n.ty = "layer"; n.s = eat_id(); note_toggled_layer(n.s); return n } # enable/disable layer L
|
if is_id("layer") { pi += 1; n.ty = "layer"; n.s = eat_id(); note_toggled_layer(n.s); return n } # enable/disable layer L
|
||||||
if is_id("system") { pi = pi + 1; n.ty = "system"; n.s = eat_id(); if en == 0 { push(g_disabled_sys, n.s) }; return n } # disable system <esys_fn> (lever 5, compile-time)
|
if is_id("system") { pi += 1; n.ty = "system"; n.s = eat_id(); if en == 0 { push(g_disabled_sys, n.s) }; return n } # disable system <esys_fn> (lever 5, compile-time)
|
||||||
n.s = eat_id() # `enable P on e` / `disable Model` / `disable Handler`
|
n.s = eat_id() # `enable P on e` / `disable Model` / `disable Handler`
|
||||||
if is_id("on") { pi = pi + 1; n.a = expr() } # property on an entity
|
if is_id("on") { pi += 1; n.a = expr() } # property on an entity
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if (t.text == "attach") { # attach P on e [{ field: val, ... }]
|
if (t.text == "attach") { # attach P on e [{ field: val, ... }]
|
||||||
pi = pi + 1; let n = node(S_ATTACH); n.s = eat_id()
|
pi += 1; let n = node(S_ATTACH); n.s = eat_id()
|
||||||
if not is_id("on") { perr("attach needs 'on <entity>'") }
|
if not is_id("on") { perr("attach needs 'on <entity>'") }
|
||||||
pi = pi + 1; n.a = expr()
|
pi += 1; n.a = expr()
|
||||||
if is_op("{") { n.b = record() } # optional field overrides (same-line)
|
if is_op("{") { n.b = record() } # optional field overrides (same-line)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if (t.text == "detach") { # detach P on e
|
if (t.text == "detach") { # detach P on e
|
||||||
pi = pi + 1; let n = node(S_DETACH); n.s = eat_id()
|
pi += 1; let n = node(S_DETACH); n.s = eat_id()
|
||||||
if not is_id("on") { perr("detach needs 'on <entity>'") }
|
if not is_id("on") { perr("detach needs 'on <entity>'") }
|
||||||
pi = pi + 1; n.a = expr()
|
pi += 1; n.a = expr()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if (t.text == "break") { pi = pi + 1; return node(S_BREAK) }
|
if (t.text == "break") { pi += 1; return node(S_BREAK) }
|
||||||
if (t.text == "continue") { pi = pi + 1; return node(S_CONTINUE) }
|
if (t.text == "continue") { pi += 1; return node(S_CONTINUE) }
|
||||||
if (t.text == "cancel") { pi = pi + 1; return node(S_CANCEL) } # veto a cancellable event
|
if (t.text == "cancel") { pi += 1; return node(S_CANCEL) } # veto a cancellable event
|
||||||
if (t.text == "match") {
|
if (t.text == "match") {
|
||||||
pi = pi + 1; let n = node(S_MATCH); n.a = expr(); skipnl(); eat_op("{")
|
pi += 1; let n = node(S_MATCH); n.a = expr(); skipnl(); eat_op("{")
|
||||||
while true {
|
while true {
|
||||||
skipnl(); if is_op("}") { break }
|
skipnl(); if is_op("}") { break }
|
||||||
let arm = node(S_MARM)
|
let arm = node(S_MARM)
|
||||||
while true { push(arm.kids, expr()); if is_op(",") { pi = pi + 1; skipnl(); continue }; break }
|
while true { push(arm.kids, expr()); if is_op(",") { pi += 1; skipnl(); continue }; break }
|
||||||
eat_op("=>"); skipnl()
|
eat_op("=>"); skipnl()
|
||||||
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK); push(b.kids, stmt()); arm.a = b }
|
if is_op("{") { arm.a = block() } else { let b = node(N_BLOCK); push(b.kids, stmt()); arm.a = b }
|
||||||
push(n.kids, arm)
|
push(n.kids, arm)
|
||||||
|
|
@ -393,19 +436,19 @@ function stmt_body() -> Node {
|
||||||
}
|
}
|
||||||
let e = expr()
|
let e = expr()
|
||||||
if is_op("=") or is_op("+=") or is_op("-=") or is_op("*=") or is_op("/=") {
|
if is_op("=") or is_op("+=") or is_op("-=") or is_op("*=") or is_op("/=") {
|
||||||
let n = node(S_ASSIGN); n.line = toks[pi].line; n.s = toks[pi].text; pi = pi + 1; n.a = e; n.b = expr(); return n
|
let n = node(S_ASSIGN); n.line = toks[pi].line; n.s = toks[pi].text; pi += 1; n.a = e; n.b = expr(); return n
|
||||||
}
|
}
|
||||||
let n = node(S_EXPR); n.a = e; return n
|
let n = node(S_EXPR); n.a = e; return n
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---- declarations ----------------------------------------------------------
|
# ---- declarations ----------------------------------------------------------
|
||||||
function parse_var() -> Node {
|
function parse_var() -> Node {
|
||||||
pi = pi + 1; let n = node(N_VAR); n.s = eat_id(); eat_op(":"); n.ty = ptype()
|
pi += 1; let n = node(N_VAR); n.s = eat_id(); eat_op(":"); n.ty = ptype()
|
||||||
if is_op("=") { pi = pi + 1; n.a = expr() }
|
if is_op("=") { pi += 1; n.a = expr() }
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
function parse_const() -> Node {
|
function parse_const() -> Node {
|
||||||
pi = pi + 1; let n = node(N_CONST); n.s = eat_id(); eat_op(":"); n.ty = ptype(); eat_op("="); n.a = expr()
|
pi += 1; let n = node(N_CONST); n.s = eat_id(); eat_op(":"); n.ty = ptype(); eat_op("="); n.a = expr()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
# the language's statement / declaration words cannot name a function (`new`,
|
# the language's statement / declaration words cannot name a function (`new`,
|
||||||
|
|
@ -424,19 +467,19 @@ function is_reserved_word(w: pointer) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_fn() -> Node {
|
function parse_fn() -> Node {
|
||||||
pi = pi + 1; let n = node(N_FN); n.s = eat_id(); eat_op("(")
|
pi += 1; let n = node(N_FN); n.s = eat_id(); eat_op("(")
|
||||||
if is_reserved_word(n.s) { perr(`'{n.s}' is a reserved word and cannot name a function`) }
|
if is_reserved_word(n.s) { perr(`'{n.s}' is a reserved word and cannot name a function`) }
|
||||||
while not is_op(")") {
|
while not is_op(")") {
|
||||||
let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
|
let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
|
||||||
if is_op(",") { pi = pi + 1 }
|
if is_op(",") { pi += 1 }
|
||||||
}
|
}
|
||||||
eat_op(")")
|
eat_op(")")
|
||||||
n.ty = "void"
|
n.ty = "void"
|
||||||
if is_op("->") { pi = pi + 1; n.ty = ptype() }
|
if is_op("->") { pi += 1; n.ty = ptype() }
|
||||||
n.a = block()
|
n.a = block()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
function parse_main() -> Node { pi = pi + 1; let n = node(N_MAIN); n.a = block(); return n }
|
function parse_main() -> Node { pi += 1; let n = node(N_MAIN); n.a = block(); return n }
|
||||||
|
|
||||||
# issue #76 — rewrite calls to a namespace-block sibling (a bare short-name call)
|
# issue #76 — rewrite calls to a namespace-block sibling (a bare short-name call)
|
||||||
# to the prefixed function name, so a body can call `pending()` where the function
|
# to the prefixed function name, so a body can call `pending()` where the function
|
||||||
|
|
@ -444,7 +487,7 @@ function parse_main() -> Node { pi = pi + 1; let n = node(N_MAIN); n.a = block()
|
||||||
# rewritten (Ludic has no function values, and Name.method calls are E_MEMBER).
|
# rewritten (Ludic has no function values, and Name.method calls are E_MEMBER).
|
||||||
function ns_short_in(names: []pointer, s: pointer) -> bool {
|
function ns_short_in(names: []pointer, s: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(names) { if (names[i] == s) { return true }; i = i + 1 }
|
while i < len(names) { if (names[i] == s) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
function ns_rewrite_calls(n: Node, shorts: []pointer, prefix: pointer) -> void {
|
function ns_rewrite_calls(n: Node, shorts: []pointer, prefix: pointer) -> void {
|
||||||
|
|
@ -456,7 +499,7 @@ function ns_rewrite_calls(n: Node, shorts: []pointer, prefix: pointer) -> void {
|
||||||
ns_rewrite_calls(n.b, shorts, prefix)
|
ns_rewrite_calls(n.b, shorts, prefix)
|
||||||
ns_rewrite_calls(n.c, shorts, prefix)
|
ns_rewrite_calls(n.c, shorts, prefix)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(n.kids) { ns_rewrite_calls(n.kids[i], shorts, prefix); i = i + 1 }
|
while i < len(n.kids) { ns_rewrite_calls(n.kids[i], shorts, prefix); i += 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
# issue #76 — `namespace Name { [export|internal] function short(…) … }`. Declares
|
# issue #76 — `namespace Name { [export|internal] function short(…) … }`. Declares
|
||||||
|
|
@ -467,7 +510,7 @@ function ns_rewrite_calls(n: Node, shorts: []pointer, prefix: pointer) -> void {
|
||||||
# name. `internal` keeps a function as a private helper: it is emitted but not part
|
# name. `internal` keeps a function as a private helper: it is emitted but not part
|
||||||
# of the Name.* surface.
|
# of the Name.* surface.
|
||||||
function parse_namespace() -> void {
|
function parse_namespace() -> void {
|
||||||
pi = pi + 1 # eat "namespace"
|
pi += 1 # eat "namespace"
|
||||||
let nsname = eat_id()
|
let nsname = eat_id()
|
||||||
eat_op("{"); skipnl()
|
eat_op("{"); skipnl()
|
||||||
let fns = new []Node
|
let fns = new []Node
|
||||||
|
|
@ -475,8 +518,8 @@ function parse_namespace() -> void {
|
||||||
let exps = new []int
|
let exps = new []int
|
||||||
while not is_op("}") {
|
while not is_op("}") {
|
||||||
var is_exp = 1 # default: exported (public)
|
var is_exp = 1 # default: exported (public)
|
||||||
if is_id("export") { pi = pi + 1 }
|
if is_id("export") { pi += 1 }
|
||||||
else { if is_id("internal") { pi = pi + 1; is_exp = 0 } }
|
else { if is_id("internal") { pi += 1; is_exp = 0 } }
|
||||||
if not is_id("function") { perr("a namespace body holds functions: expected `function`") }
|
if not is_id("function") { perr("a namespace body holds functions: expected `function`") }
|
||||||
let f = parse_fn()
|
let f = parse_fn()
|
||||||
push(fns, f); push(shorts, f.s); push(exps, is_exp)
|
push(fns, f); push(shorts, f.s); push(exps, is_exp)
|
||||||
|
|
@ -485,30 +528,30 @@ function parse_namespace() -> void {
|
||||||
eat_op("}")
|
eat_op("}")
|
||||||
let prefix = ns_lower(nsname) + ("_")
|
let prefix = ns_lower(nsname) + ("_")
|
||||||
var i = 0 # prefix every function name
|
var i = 0 # prefix every function name
|
||||||
while i < len(fns) { fns[i].s = prefix + shorts[i]; i = i + 1 }
|
while i < len(fns) { fns[i].s = prefix + shorts[i]; i += 1 }
|
||||||
i = 0 # rewrite sibling calls to the prefixed name
|
i = 0 # rewrite sibling calls to the prefixed name
|
||||||
while i < len(fns) { ns_rewrite_calls(fns[i].a, shorts, prefix); i = i + 1 }
|
while i < len(fns) { ns_rewrite_calls(fns[i].a, shorts, prefix); i += 1 }
|
||||||
register_namespace(nsname) # Name.method dispatch (generic path)
|
register_namespace(nsname) # Name.method dispatch (generic path)
|
||||||
push(g_ns_blocks, nsname)
|
push(g_ns_blocks, nsname)
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(fns) {
|
while i < len(fns) {
|
||||||
if (exps[i] == 1) { push(g_ns_exports, fns[i].s) } # record the public surface
|
if (exps[i] == 1) { push(g_ns_exports, fns[i].s) } # record the public surface
|
||||||
push(prog, fns[i])
|
push(prog, fns[i])
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# directory part of a path, including the trailing '/', or "" if none
|
# directory part of a path, including the trailing '/', or "" if none
|
||||||
function dir_of(path: pointer) -> pointer {
|
function dir_of(path: pointer) -> pointer {
|
||||||
var last = 0 - 1
|
var last = -1
|
||||||
var i = 0
|
var i = 0
|
||||||
while path[i] != 0 { if path[i] == 47 { last = i }; i = i + 1 }
|
while path[i] != 0 { if path[i] == '/' { last = i }; i += 1 }
|
||||||
if last < 0 { return "" }
|
if last < 0 { return "" }
|
||||||
return path[0..0 + (last + 1)]
|
return path[0..last + 1]
|
||||||
}
|
}
|
||||||
function join_path(dir: pointer, rel: pointer) -> pointer {
|
function join_path(dir: pointer, rel: pointer) -> pointer {
|
||||||
if rel[0] == 47 { return rel } # absolute
|
if rel[0] == '/' { return rel } # absolute
|
||||||
return (dir + rel)
|
return dir + rel
|
||||||
}
|
}
|
||||||
|
|
||||||
var loaded_paths: []pointer
|
var loaded_paths: []pointer
|
||||||
|
|
@ -529,8 +572,8 @@ var g_uses_atlas: bool = false # Sprite.sheet/cell/… or Assets.* -> splice
|
||||||
var g_uses_reflect_io: bool = false # Reflect.serialize/apply -> splice the reflection serializer
|
var g_uses_reflect_io: bool = false # Reflect.serialize/apply -> splice the reflection serializer
|
||||||
var g_uses_esys: bool = false # an engine-owned system component (SpriteAnim/Motion/Light2D) is declared -> splice systems.ludic + force the reflection ABI
|
var g_uses_esys: bool = false # an engine-owned system component (SpriteAnim/Motion/Light2D) is declared -> splice systems.ludic + force the reflection ABI
|
||||||
var g_uses_input: bool = false # a program used Input.bind/down/poll/… (action maps + record/replay) -> splice input.ludic
|
var g_uses_input: bool = false # a program used Input.bind/down/poll/… (action maps + record/replay) -> splice input.ludic
|
||||||
var g_has_clear_color: bool = false # @ClearColor(N) declared -> the Render phase auto-clears + auto-presents (#86)
|
var g_has_clear_color: bool = false # @ClearColor(colour) declared -> the Render phase auto-clears + auto-presents (#86)
|
||||||
var g_clear_color: int = 0 # the declared clear colour (0x00RRGGBB)
|
var g_clear_color: Node = null # the declared clear colour: a literal, a const name or a Color.Name
|
||||||
var g_warned_draw_sprite: bool = false # emit the bare-draw_sprite deprecation note once (#85)
|
var g_warned_draw_sprite: bool = false # emit the bare-draw_sprite deprecation note once (#85)
|
||||||
var g_uses_world_despawn: bool = false # World.despawn / esys_bounds kill -> emit @fn_world_despawn (#84)
|
var g_uses_world_despawn: bool = false # World.despawn / esys_bounds kill -> emit @fn_world_despawn (#84)
|
||||||
|
|
||||||
|
|
@ -572,12 +615,12 @@ var g_ns_blocks: []pointer
|
||||||
var g_ns_exports: []pointer
|
var g_ns_exports: []pointer
|
||||||
function is_ns_block(name: pointer) -> bool {
|
function is_ns_block(name: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_ns_blocks) { if (g_ns_blocks[i] == name) { return true }; i = i + 1 }
|
while i < len(g_ns_blocks) { if (g_ns_blocks[i] == name) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
function ns_export_has(prefixed: pointer) -> bool {
|
function ns_export_has(prefixed: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_ns_exports) { if (g_ns_exports[i] == prefixed) { return true }; i = i + 1 }
|
while i < len(g_ns_exports) { if (g_ns_exports[i] == prefixed) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -590,7 +633,7 @@ function ns_export_has(prefixed: pointer) -> bool {
|
||||||
var g_disabled_sys: []pointer
|
var g_disabled_sys: []pointer
|
||||||
function is_system_disabled(fn: pointer) -> bool {
|
function is_system_disabled(fn: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_disabled_sys) { if (g_disabled_sys[i] == fn) { return true }; i = i + 1 }
|
while i < len(g_disabled_sys) { if (g_disabled_sys[i] == fn) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -599,18 +642,18 @@ function is_system_disabled(fn: pointer) -> bool {
|
||||||
# bare function foo_method (lowercased namespace + "_" + method).
|
# bare function foo_method (lowercased namespace + "_" + method).
|
||||||
function register_namespace(name: pointer) -> void {
|
function register_namespace(name: pointer) -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_namespaces) { if (g_namespaces[i] == name) { return }; i = i + 1 }
|
while i < len(g_namespaces) { if (g_namespaces[i] == name) { return }; i += 1 }
|
||||||
push(g_namespaces, name)
|
push(g_namespaces, name)
|
||||||
}
|
}
|
||||||
function is_registered_namespace(name: pointer) -> bool {
|
function is_registered_namespace(name: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_namespaces) { if (g_namespaces[i] == name) { return true }; i = i + 1 }
|
while i < len(g_namespaces) { if (g_namespaces[i] == name) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function already_loaded(full: pointer) -> bool {
|
function already_loaded(full: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(loaded_paths) { if (loaded_paths[i] == full) { return true }; i = i + 1 }
|
while i < len(loaded_paths) { if (loaded_paths[i] == full) { return true }; i += 1 }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,41 +682,48 @@ function parse_one_decl() -> void {
|
||||||
var esys_phase: pointer = null
|
var esys_phase: pointer = null
|
||||||
var ns_name: pointer = null # @Namespace(Name) — a package-provided Foo.* namespace (#62)
|
var ns_name: pointer = null # @Namespace(Name) — a package-provided Foo.* namespace (#62)
|
||||||
while is_op("@") {
|
while is_op("@") {
|
||||||
pi = pi + 1; let a = eat_id() # collect a leading @annotation
|
pi += 1; let a = eat_id() # collect a leading @annotation
|
||||||
if (a == "export") { is_export = true }
|
if a == "export" { is_export = true }
|
||||||
else { if (a == "Public") { is_public = true } # @Public hook promotion
|
else if a == "Public" { is_public = true } # @Public hook promotion
|
||||||
else { if (a == "On") { eat_op("("); on_event = eat_id(); eat_op(")") } # @On(Event) listener
|
else if a == "On" { eat_op("("); on_event = eat_id(); eat_op(")") } # @On(Event) listener
|
||||||
else { if (a == "Queries") { qspec = parse_queries_anno() } # @Queries(these: [...], on: ...)
|
else if a == "Queries" { qspec = parse_queries_anno() } # @Queries(these: [...], on: ...)
|
||||||
else { if (a == "OnSpawn") { eat_op("("); onspawn_model = eat_id(); eat_op(")") }
|
else if a == "OnSpawn" { eat_op("("); onspawn_model = eat_id(); eat_op(")") }
|
||||||
else { if (a == "OnDespawn") { eat_op("("); ondespawn_model = eat_id()
|
else if a == "OnDespawn" {
|
||||||
if is_op(",") { pi = pi + 1; eat_id(); eat_op(":"); ondespawn_reason = eat_id() } # , reason: r
|
eat_op("("); ondespawn_model = eat_id()
|
||||||
eat_op(")") }
|
if is_op(",") { pi += 1; eat_id(); eat_op(":"); ondespawn_reason = eat_id() } # , reason: r
|
||||||
else { if (a == "OnAttach") { eat_op("("); onattach_prop = eat_id(); eat_op(")") }
|
eat_op(")")
|
||||||
else { if (a == "OnDetach") { eat_op("("); ondetach_prop = eat_id(); eat_op(")") }
|
}
|
||||||
else { if (a == "OnEnable") { eat_op("("); onenable_prop = eat_id(); eat_op(")") }
|
else if a == "OnAttach" { eat_op("("); onattach_prop = eat_id(); eat_op(")") }
|
||||||
else { if (a == "OnDisable") { eat_op("("); ondisable_prop = eat_id(); eat_op(")") }
|
else if a == "OnDetach" { eat_op("("); ondetach_prop = eat_id(); eat_op(")") }
|
||||||
else { if (a == "OnStart") { hook_phase = "Start" } # boot
|
else if a == "OnEnable" { eat_op("("); onenable_prop = eat_id(); eat_op(")") }
|
||||||
else { if (a == "OnQuit") { hook_phase = "OnQuit" } # shutdown
|
else if a == "OnDisable" { eat_op("("); ondisable_prop = eat_id(); eat_op(")") }
|
||||||
else { if (a == "Sync") { is_sync_prop = true } # @Sync property (N2)
|
else if a == "OnStart" { hook_phase = "Start" } # boot
|
||||||
else { if (a == "Owned") { is_owned = true } # @Owned model (N3)
|
else if a == "OnQuit" { hook_phase = "OnQuit" } # shutdown
|
||||||
else { if (a == "Server") { role = "server" } # @Server handler (N5)
|
else if a == "Sync" { is_sync_prop = true } # @Sync property (N2)
|
||||||
else { if (a == "Predicted") { role = "predicted" } # @Predicted handler (N5)
|
else if a == "Owned" { is_owned = true } # @Owned model (N3)
|
||||||
else { if (a == "ToServer") { remote_dir = "toserver" } # @ToServer event (N4)
|
else if a == "Server" { role = "server" } # @Server handler (N5)
|
||||||
else { if (a == "ToClients") { remote_dir = "toclients" } # @ToClients event (N4)
|
else if a == "Predicted" { role = "predicted" } # @Predicted handler (N5)
|
||||||
else { if (a == "System") { sys_phase = "Update"; if is_op("(") { pi = pi + 1; sys_phase = eat_id(); eat_op(")") } } # @System(Phase) binary-module system (#64)
|
else if a == "ToServer" { remote_dir = "toserver" } # @ToServer event (N4)
|
||||||
else { if (a == "EngineSystem") { eat_op("("); esys_comp = eat_id(); eat_op(","); esys_phase = eat_id(); eat_op(")") } # @EngineSystem(Comp, Phase) package engine system (#62)
|
else if a == "ToClients" { remote_dir = "toclients" } # @ToClients event (N4)
|
||||||
else { if (a == "Namespace") { eat_op("("); ns_name = eat_id(); eat_op(")") } # @Namespace(Name) package Foo.* namespace (#62)
|
else if a == "System" { sys_phase = "Update"; if is_op("(") { pi += 1; sys_phase = eat_id(); eat_op(")") } } # @System(Phase) binary-module system (#64)
|
||||||
else { if (a == "ClearColor") { eat_op("(") # @ClearColor(N) — Render auto-clear + auto-present (#86)
|
else if a == "EngineSystem" { eat_op("("); esys_comp = eat_id(); eat_op(","); esys_phase = eat_id(); eat_op(")") } # @EngineSystem(Comp, Phase) package engine system (#62)
|
||||||
if toks[pi].kind != TK_INT { perr("@ClearColor expects an integer colour, e.g. @ClearColor(0x101018)") }
|
else if a == "Namespace" { eat_op("("); ns_name = eat_id(); eat_op(")") } # @Namespace(Name) package Foo.* namespace (#62)
|
||||||
g_clear_color = toks[pi].ival; g_has_clear_color = true; pi = pi + 1; eat_op(")") }
|
else if a == "ClearColor" { # @ClearColor(colour) — Render auto-clear + auto-present (#86)
|
||||||
else { if is_op("(") { var d = 0 # any other @anno(args) — parsed and skipped
|
eat_op("(")
|
||||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } } } } } } } } } } } } } } } } } } } } } } }
|
g_clear_color = expr() # a literal, a `const`, or a Color.Name — resolved when emitted
|
||||||
|
g_has_clear_color = true
|
||||||
|
eat_op(")")
|
||||||
|
}
|
||||||
|
else if is_op("(") { # any other @anno(args) — parsed and skipped
|
||||||
|
var d = 0
|
||||||
|
while true { if is_op("(") { d += 1 }; if is_op(")") { d -= 1 }; pi += 1; if d == 0 { break } }
|
||||||
|
}
|
||||||
skipnl()
|
skipnl()
|
||||||
}
|
}
|
||||||
if is_id("import") { pi = pi + 1
|
if is_id("import") { pi += 1
|
||||||
let t = toks[pi]
|
let t = toks[pi]
|
||||||
if t.kind != TK_STR { perr("expected \"path\" after import") }
|
if t.kind != TK_STR { perr("expected \"path\" after import") }
|
||||||
let rel = t.text; pi = pi + 1
|
let rel = t.text; pi += 1
|
||||||
do_import(rel)
|
do_import(rel)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -685,7 +735,7 @@ function parse_one_decl() -> void {
|
||||||
}
|
}
|
||||||
if is_id("property") {
|
if is_id("property") {
|
||||||
let c = parse_component()
|
let c = parse_component()
|
||||||
if is_sync_prop { var fi = 0; while fi < len(c.kids) { c.kids[fi].ival = 1; fi = fi + 1 } } # N2: mark every field replicable
|
if is_sync_prop { var fi = 0; while fi < len(c.kids) { c.kids[fi].ival = 1; fi += 1 } } # N2: mark every field replicable
|
||||||
push(prog, c); return
|
push(prog, c); return
|
||||||
}
|
}
|
||||||
if is_id("model") {
|
if is_id("model") {
|
||||||
|
|
@ -741,7 +791,7 @@ function parse_one_decl() -> void {
|
||||||
if is_id("extern") { push(prog, parse_extern()); return }
|
if is_id("extern") { push(prog, parse_extern()); return }
|
||||||
if is_id("entry") { push(prog, parse_main()); return }
|
if is_id("entry") { push(prog, parse_main()); return }
|
||||||
if is_id("test") { push(g_tests, parse_test()); return }
|
if is_id("test") { push(g_tests, parse_test()); return }
|
||||||
perr("expected declaration")
|
perr(`expected a declaration (property, model, handler, function, …), got {tok_desc()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
# test "name" { ... } — a named test block, collected into g_tests. The block
|
# test "name" { ... } — a named test block, collected into g_tests. The block
|
||||||
|
|
@ -749,12 +799,12 @@ function parse_one_decl() -> void {
|
||||||
# inside it record failures. Kept out of `prog` so it never emits as a plain fn.
|
# inside it record failures. Kept out of `prog` so it never emits as a plain fn.
|
||||||
function parse_test() -> Node {
|
function parse_test() -> Node {
|
||||||
let ln = toks[pi].line
|
let ln = toks[pi].line
|
||||||
pi = pi + 1 # past `test`
|
pi += 1 # past `test`
|
||||||
if toks[pi].kind != TK_STR { perr("expected a \"name\" string after test") }
|
if toks[pi].kind != TK_STR { perr("expected a \"name\" string after test") }
|
||||||
let n = node(N_TEST)
|
let n = node(N_TEST)
|
||||||
n.s = toks[pi].text
|
n.s = toks[pi].text
|
||||||
n.line = ln
|
n.line = ln
|
||||||
pi = pi + 1 # past the name
|
pi += 1 # past the name
|
||||||
n.a = block()
|
n.a = block()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
@ -771,17 +821,7 @@ function parse_test() -> Node {
|
||||||
# import resolves exactly as before (the emitted IR is byte-identical).
|
# import resolves exactly as before (the emitted IR is byte-identical).
|
||||||
# true when `rel` is an engine-runtime splice path ("runtime/..."), as opposed to
|
# true when `rel` is an engine-runtime splice path ("runtime/..."), as opposed to
|
||||||
# a user or package import. Those ship with the toolchain, not the project (#75).
|
# a user or package import. Those ship with the toolchain, not the project (#75).
|
||||||
function is_runtime_path(rel: pointer) -> bool {
|
function is_runtime_path(rel: pointer) -> bool { return str_starts(rel, "runtime/") }
|
||||||
if rel[0] != 114 { return false } # r
|
|
||||||
if rel[1] != 117 { return false } # u
|
|
||||||
if rel[2] != 110 { return false } # n
|
|
||||||
if rel[3] != 116 { return false } # t
|
|
||||||
if rel[4] != 105 { return false } # i
|
|
||||||
if rel[5] != 109 { return false } # m
|
|
||||||
if rel[6] != 101 { return false } # e
|
|
||||||
if rel[7] != 47 { return false } # /
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
# byte-wise `a < b`, for a stable import order of a directory's files
|
# byte-wise `a < b`, for a stable import order of a directory's files
|
||||||
function str_less(a: pointer, b: pointer) -> bool {
|
function str_less(a: pointer, b: pointer) -> bool {
|
||||||
|
|
@ -790,7 +830,7 @@ function str_less(a: pointer, b: pointer) -> bool {
|
||||||
let ca = a[i]; let cb = b[i]
|
let ca = a[i]; let cb = b[i]
|
||||||
if ca != cb { return ca < cb }
|
if ca != cb { return ca < cb }
|
||||||
if ca == 0 { return false }
|
if ca == 0 { return false }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -804,16 +844,16 @@ function do_import_dir(rel: pointer) -> void {
|
||||||
while i < len(names) { # selection sort by name
|
while i < len(names) { # selection sort by name
|
||||||
var best = i
|
var best = i
|
||||||
var j = i + 1
|
var j = i + 1
|
||||||
while j < len(names) { if str_less(names[j], names[best]) { best = j }; j = j + 1 }
|
while j < len(names) { if str_less(names[j], names[best]) { best = j }; j += 1 }
|
||||||
let t = names[i]; names[i] = names[best]; names[best] = t
|
let t = names[i]; names[i] = names[best]; names[best] = t
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(names) {
|
while i < len(names) {
|
||||||
let nm = names[i]
|
let nm = names[i]
|
||||||
let n = len(nm)
|
let n = len(nm)
|
||||||
if (n > 6) and (nm[n - 6..n] == ".ludic") { do_import(dir + ("/") + nm) }
|
if (n > 6) and (nm[n - 6..n] == ".ludic") { do_import(dir + ("/") + nm) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -827,13 +867,13 @@ function do_import(rel: pointer) -> void {
|
||||||
# install root $LUDIC_HOME (default: the compiler binary's directory) — the same
|
# install root $LUDIC_HOME (default: the compiler binary's directory) — the same
|
||||||
# place main.ludic finds cocoa.ll / audio.ll. This is tried before the package
|
# place main.ludic finds cocoa.ll / audio.ll. This is tried before the package
|
||||||
# module root, so $LUDIC_MODULES / ludic_modules holds only third-party packages.
|
# module root, so $LUDIC_MODULES / ludic_modules holds only third-party packages.
|
||||||
if (src == null) and (rel[0] != 47) and is_runtime_path(rel) {
|
if (src == null) and (rel[0] != '/') and is_runtime_path(rel) {
|
||||||
let home = ensure_slash(getenv_or("LUDIC_HOME", dir_of(arg(0))))
|
let home = ensure_slash(getenv_or("LUDIC_HOME", dir_of(arg(0))))
|
||||||
let hc = home + rel
|
let hc = home + rel
|
||||||
let hs = read_file(hc)
|
let hs = read_file(hc)
|
||||||
if (hs != null) { full = hc; src = hs }
|
if (hs != null) { full = hc; src = hs }
|
||||||
}
|
}
|
||||||
if (src == null) and (rel[0] != 47) {
|
if (src == null) and (rel[0] != '/') {
|
||||||
let cand = ensure_slash(getenv_or("LUDIC_MODULES", "ludic_modules")) + rel
|
let cand = ensure_slash(getenv_or("LUDIC_MODULES", "ludic_modules")) + rel
|
||||||
let s2 = read_file(cand)
|
let s2 = read_file(cand)
|
||||||
if (s2 != null) { full = cand; src = s2 }
|
if (s2 != null) { full = cand; src = s2 }
|
||||||
|
|
@ -848,16 +888,19 @@ function do_import(rel: pointer) -> void {
|
||||||
let fl = len(full)
|
let fl = len(full)
|
||||||
if (fl >= len(atail)) and (full[fl - len(atail)..fl] == atail) { g_uses_audio = true }
|
if (fl >= len(atail)) and (full[fl - len(atail)..fl] == atail) { g_uses_audio = true }
|
||||||
let saved_toks = toks; let saved_pi = pi; let saved_dir = cur_dir
|
let saved_toks = toks; let saved_pi = pi; let saved_dir = cur_dir
|
||||||
|
let saved_file = g_parse_file; let saved_parsing = g_parsing
|
||||||
cur_dir = dir_of(full)
|
cur_dir = dir_of(full)
|
||||||
|
g_parse_file = full; g_parsing = true
|
||||||
lex(src) # resets the global token stream
|
lex(src) # resets the global token stream
|
||||||
pi = 0
|
pi = 0
|
||||||
skipnl()
|
skipnl()
|
||||||
while toks[pi].kind != TK_EOF { parse_one_decl(); skipnl() }
|
while toks[pi].kind != TK_EOF { parse_one_decl(); skipnl() }
|
||||||
toks = saved_toks; pi = saved_pi; cur_dir = saved_dir
|
toks = saved_toks; pi = saved_pi; cur_dir = saved_dir
|
||||||
|
g_parse_file = saved_file; g_parsing = saved_parsing
|
||||||
}
|
}
|
||||||
|
|
||||||
# a game (has systems/components) links the Ludic runtime; auto-splice it the
|
# a game (has handlers/components) links the Ludic runtime; auto-splice it.
|
||||||
# way the C compiler does. Tools (a `main` block, no ECS) get nothing.
|
# Tools (an `entry` block, no ECS) get nothing.
|
||||||
function maybe_splice_runtime() -> void {
|
function maybe_splice_runtime() -> void {
|
||||||
let saved = cur_dir
|
let saved = cur_dir
|
||||||
# a game (has systems/components) links the Ludic runtime.
|
# a game (has systems/components) links the Ludic runtime.
|
||||||
|
|
@ -1105,7 +1148,7 @@ function parse_program() -> void {
|
||||||
g_uses_esys = false
|
g_uses_esys = false
|
||||||
g_uses_input = false
|
g_uses_input = false
|
||||||
g_has_clear_color = false
|
g_has_clear_color = false
|
||||||
g_clear_color = 0
|
g_clear_color = null
|
||||||
g_warned_draw_sprite = false
|
g_warned_draw_sprite = false
|
||||||
g_uses_world_despawn = false
|
g_uses_world_despawn = false
|
||||||
g_uses_light = false
|
g_uses_light = false
|
||||||
|
|
@ -1138,15 +1181,15 @@ function parse_program() -> void {
|
||||||
skipnl()
|
skipnl()
|
||||||
g_game_name = "Ludic"
|
g_game_name = "Ludic"
|
||||||
# imports may precede the program block
|
# imports may precede the program block
|
||||||
while is_id("import") { pi = pi + 1; let t = toks[pi]; let rel = t.text; pi = pi + 1; do_import(rel); skipnl() }
|
while is_id("import") { pi += 1; let t = toks[pi]; let rel = t.text; pi += 1; do_import(rel); skipnl() }
|
||||||
# @annotations on the program itself (e.g. @Handles(Movement)) — parsed, skipped
|
# @annotations on the program itself (e.g. @Handles(Movement)) — parsed, skipped
|
||||||
while is_op("@") {
|
while is_op("@") {
|
||||||
pi = pi + 1; let a = eat_id()
|
pi += 1; let a = eat_id()
|
||||||
if is_op("(") { var d = 0
|
if is_op("(") { var d = 0
|
||||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
|
while true { if is_op("(") { d += 1 }; if is_op(")") { d -= 1 }; pi += 1; if d == 0 { break } } }
|
||||||
skipnl()
|
skipnl()
|
||||||
}
|
}
|
||||||
if is_id("program") { pi = pi + 1; g_game_name = eat_id(); skipnl(); eat_op("{") }
|
if is_id("program") { pi += 1; g_game_name = eat_id(); skipnl(); eat_op("{") }
|
||||||
while true {
|
while true {
|
||||||
skipnl()
|
skipnl()
|
||||||
if toks[pi].kind == TK_EOF { break }
|
if toks[pi].kind == TK_EOF { break }
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
# parse_game.ludic — the ECS front-end: component and system declarations,
|
# parse_game.ludic — the ECS front-end: property, model, handler, scene, ui and
|
||||||
# `for (vars) in query [terms] where cond`, spawn and despawn. Mirrors the
|
# enum declarations, `for (vars) in query [terms] where cond`, spawn and prefab.
|
||||||
# game-construct parsing in compiler/front/parse.c.
|
|
||||||
|
|
||||||
function parse_component() -> Node {
|
function parse_component() -> Node {
|
||||||
pi = pi + 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
|
pi += 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
|
||||||
while true { skipnl(); if is_op("}") { break }
|
while true { skipnl(); if is_op("}") { break }
|
||||||
var is_computed = false
|
var is_computed = false
|
||||||
var is_sync = false # @Sync — this field replicates (NETWORKING N2)
|
var is_sync = false # @Sync — this field replicates (NETWORKING N2)
|
||||||
if is_op("@") { pi = pi + 1; let ann = eat_id(); if (ann == "Computed") { is_computed = true } else { if (ann == "Sync") { is_sync = true } }; skipnl() }
|
if is_op("@") { pi += 1; let ann = eat_id(); if ann == "Computed" { is_computed = true } else if ann == "Sync" { is_sync = true }; skipnl() }
|
||||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
||||||
if is_op("=") { pi = pi + 1; f.a = expr() }
|
if is_op("=") { pi += 1; f.a = expr() }
|
||||||
if is_sync { f.ival = 1 } # mark the field replicable (read by emit_net)
|
if is_sync { f.ival = 1 } # mark the field replicable (read by emit_net)
|
||||||
if is_computed { register_computed(n.s, f.s, f.ty, f.a) } # derived: no storage
|
if is_computed { register_computed(n.s, f.s, f.ty, f.a) } # derived: no storage
|
||||||
else { push(n.kids, f) }
|
else { push(n.kids, f) }
|
||||||
if is_op(",") { pi = pi + 1 } }
|
if is_op(",") { pi += 1 } }
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,29 +20,33 @@ function parse_component() -> Node {
|
||||||
# field grammar as a `property`, but stored in g_events, not prog: an event is a
|
# field grammar as a `property`, but stored in g_events, not prog: an event is a
|
||||||
# signal shape, not per-entity storage. Zero fields is allowed (`event Ping {}`).
|
# signal shape, not per-entity storage. Zero fields is allowed (`event Ping {}`).
|
||||||
function parse_event() -> Node {
|
function parse_event() -> Node {
|
||||||
pi = pi + 1; let n = node(N_EVENT)
|
pi += 1; let n = node(N_EVENT)
|
||||||
if is_id("cancellable") { pi = pi + 1; n.ival = 1 } # a decision event: listeners may `cancel` it
|
if is_id("cancellable") { pi += 1; n.ival = 1 } # a decision event: listeners may `cancel` it
|
||||||
n.s = eat_id(); skipnl(); eat_op("{")
|
n.s = eat_id(); skipnl(); eat_op("{")
|
||||||
while true { skipnl(); if is_op("}") { break }
|
while true { skipnl(); if is_op("}") { break }
|
||||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
||||||
if is_op("=") { pi = pi + 1; f.a = expr() }
|
if is_op("=") { pi += 1; f.a = expr() }
|
||||||
push(n.kids, f)
|
push(n.kids, f)
|
||||||
if is_op(",") { pi = pi + 1 } }
|
if is_op(",") { pi += 1 } }
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_system() -> Node {
|
function parse_system() -> Node {
|
||||||
pi = pi + 1; let n = node(N_SYS); n.s = eat_id(); n.ty = "Update"
|
pi += 1; let n = node(N_SYS); n.s = eat_id(); n.ty = "Update"
|
||||||
# postfix clauses on `handler Name …`: @anno(...) (parsed and reserved, e.g.
|
# postfix clauses on `handler Name …`: @anno(...) (parsed and reserved, e.g.
|
||||||
# @deterministic / @Reads(...) / @Writes(...)) and `phase X`. The handler's
|
# @deterministic / @Reads(...) / @Writes(...)) and `phase X`. The handler's
|
||||||
# query lives in a prefix `@Queries(...)` annotation (see parse_one_decl), not
|
# query lives in a prefix `@Queries(...)` annotation (see parse_one_decl), not
|
||||||
# in a signature clause.
|
# in a signature clause.
|
||||||
while true {
|
while true {
|
||||||
skipnl() # clauses may span several lines
|
skipnl() # clauses may span several lines
|
||||||
if is_op("@") { pi = pi + 1; let a = eat_id(); if is_op("(") { var d = 0 # @anno, one per turn so a
|
if is_op("@") { pi += 1; let a = eat_id(); if is_op("(") { var d = 0 # @anno, one per turn so a
|
||||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
|
while true { if is_op("(") { d += 1 }; if is_op(")") { d -= 1 }; pi += 1; if d == 0 { break } } }
|
||||||
continue } # newline-separated @anno re-skips at the loop top
|
continue } # newline-separated @anno re-skips at the loop top
|
||||||
if is_id("phase") { pi = pi + 1; n.ty = eat_id(); continue }
|
if is_id("phase") {
|
||||||
|
pi += 1; n.ty = eat_id()
|
||||||
|
if not is_phase_name(n.ty) { perr(`unknown phase '{n.ty}' (expected Start, Input, FixedUpdate, Update, LateUpdate, Render or Overlay)`) }
|
||||||
|
continue
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
skipnl()
|
skipnl()
|
||||||
|
|
@ -57,12 +60,12 @@ function parse_query_tail() -> Node {
|
||||||
eat_op("[")
|
eat_op("[")
|
||||||
let q = node(N_BLOCK)
|
let q = node(N_BLOCK)
|
||||||
while not is_op("]") {
|
while not is_op("]") {
|
||||||
if is_op("{") { pi = pi + 1; let t = node(E_ID); t.s = eat_id(); t.ival = 1; push(q.kids, t); eat_op("}") }
|
if is_op("{") { pi += 1; let t = node(E_ID); t.s = eat_id(); t.ival = 1; push(q.kids, t); eat_op("}") }
|
||||||
else { let t = node(E_ID); t.s = eat_id(); t.ival = 0; push(q.kids, t) }
|
else { let t = node(E_ID); t.s = eat_id(); t.ival = 0; push(q.kids, t) }
|
||||||
if is_op(",") { pi = pi + 1 }
|
if is_op(",") { pi += 1 }
|
||||||
}
|
}
|
||||||
eat_op("]")
|
eat_op("]")
|
||||||
if is_id("where") { pi = pi + 1; q.a = expr() }
|
if is_id("where") { pi += 1; q.a = expr() }
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,11 +73,11 @@ function parse_query_tail() -> Node {
|
||||||
function parse_query_for() -> Node {
|
function parse_query_for() -> Node {
|
||||||
let n = node(S_QUERY)
|
let n = node(S_QUERY)
|
||||||
eat_op("(")
|
eat_op("(")
|
||||||
while not is_op(")") { let v = node(E_ID); v.s = eat_id(); push(n.kids, v); if is_op(",") { pi = pi + 1 } }
|
while not is_op(")") { let v = node(E_ID); v.s = eat_id(); push(n.kids, v); if is_op(",") { pi += 1 } }
|
||||||
eat_op(")")
|
eat_op(")")
|
||||||
let inkw = eat_id() # 'in'
|
let inkw = eat_id() # 'in'
|
||||||
if not is_id("query") { perr("expected 'query' in for-loop") }
|
if not is_id("query") { perr("expected 'query' in for-loop") }
|
||||||
pi = pi + 1 # 'query'
|
pi += 1 # 'query'
|
||||||
n.c = parse_query_tail()
|
n.c = parse_query_tail()
|
||||||
n.b = n.c.a # where
|
n.b = n.c.a # where
|
||||||
n.a = block()
|
n.a = block()
|
||||||
|
|
@ -127,16 +130,16 @@ function parse_queries_anno() -> Node {
|
||||||
let pname = eat_id()
|
let pname = eat_id()
|
||||||
let v = node(E_ID); v.s = pname; push(qn.kids, v) # binding var = property name
|
let v = node(E_ID); v.s = pname; push(qn.kids, v) # binding var = property name
|
||||||
let t = node(E_ID); t.s = pname; t.ival = 0; push(terms.kids, t)
|
let t = node(E_ID); t.s = pname; t.ival = 0; push(terms.kids, t)
|
||||||
if is_op("{") { pi = pi + 1; let ce = expr(); eat_op("}")
|
if is_op("{") { pi += 1; let ce = expr(); eat_op("}")
|
||||||
let cb = node(E_ID); cb.s = pname; wh = mk_and(wh, qualify_fields(ce, cb)) }
|
let cb = node(E_ID); cb.s = pname; wh = mk_and(wh, qualify_fields(ce, cb)) }
|
||||||
if is_op(",") { pi = pi + 1 }
|
if is_op(",") { pi += 1 }
|
||||||
skipnl()
|
skipnl()
|
||||||
}
|
}
|
||||||
eat_op("]")
|
eat_op("]")
|
||||||
} else { if (key == "on") {
|
} else { if (key == "on") {
|
||||||
let mname = eat_id(); let t = node(E_ID); t.s = mname; t.ival = 1; push(terms.kids, t) # {Model} tag
|
let mname = eat_id(); let t = node(E_ID); t.s = mname; t.ival = 1; push(terms.kids, t) # {Model} tag
|
||||||
} else { expr() } } # unknown key: skip its value
|
} else { expr() } } # unknown key: skip its value
|
||||||
if is_op(",") { pi = pi + 1 }
|
if is_op(",") { pi += 1 }
|
||||||
skipnl()
|
skipnl()
|
||||||
}
|
}
|
||||||
eat_op(")")
|
eat_op(")")
|
||||||
|
|
@ -145,7 +148,7 @@ function parse_queries_anno() -> Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_spawn() -> Node {
|
function parse_spawn() -> Node {
|
||||||
pi = pi + 1; let n = node(S_SPAWN); n.s = eat_id(); skipnl(); eat_op("{")
|
pi += 1; let n = node(S_SPAWN); n.s = eat_id(); skipnl(); eat_op("{")
|
||||||
parse_component_records(n)
|
parse_component_records(n)
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
|
|
@ -156,14 +159,14 @@ function parse_component_records(n: Node) -> void {
|
||||||
let ci = node(E_FINIT); ci.s = eat_id()
|
let ci = node(E_FINIT); ci.s = eat_id()
|
||||||
ci.a = record() # Comp { field: val, ... } — no `=` before the record
|
ci.a = record() # Comp { field: val, ... } — no `=` before the record
|
||||||
push(n.kids, ci)
|
push(n.kids, ci)
|
||||||
if is_op(",") { pi = pi + 1 }
|
if is_op(",") { pi += 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# prefab Name: Model { Comp { field: val }, ... } — a model with preset fields.
|
# prefab Name: Model { Comp { field: val }, ... } — a model with preset fields.
|
||||||
# `spawn Name { ... }` spawns the model with the prefab's fields, then the spawn's
|
# `spawn Name { ... }` spawns the model with the prefab's fields, then the spawn's
|
||||||
# own overrides on top; `Prefab.spawn(name: "Name")` does it by name at runtime.
|
# own overrides on top; `Prefab.spawn(name: "Name")` does it by name at runtime.
|
||||||
function parse_prefab() -> Node {
|
function parse_prefab() -> Node {
|
||||||
pi = pi + 1; let n = node(N_PREFAB); n.s = eat_id(); eat_op(":"); n.ty = eat_id(); skipnl(); eat_op("{")
|
pi += 1; let n = node(N_PREFAB); n.s = eat_id(); eat_op(":"); n.ty = eat_id(); skipnl(); eat_op("{")
|
||||||
parse_component_records(n)
|
parse_component_records(n)
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +178,7 @@ function parse_prefab() -> Node {
|
||||||
# ordinary N_SYS nodes, tagged with the owning scene in `.c`, so the whole
|
# ordinary N_SYS nodes, tagged with the owning scene in `.c`, so the whole
|
||||||
# system backend (functions, phases, enable/disable) is reused unchanged.
|
# system backend (functions, phases, enable/disable) is reused unchanged.
|
||||||
function parse_scene() -> void {
|
function parse_scene() -> void {
|
||||||
pi = pi + 1 # 'scene'
|
pi += 1 # 'scene'
|
||||||
let n = node(N_SCENE); n.s = eat_id()
|
let n = node(N_SCENE); n.s = eat_id()
|
||||||
n.ival = g_scene_count
|
n.ival = g_scene_count
|
||||||
# optional modifiers after the name, any order: `start` (the boot scene) and
|
# optional modifiers after the name, any order: `start` (the boot scene) and
|
||||||
|
|
@ -185,33 +188,34 @@ function parse_scene() -> void {
|
||||||
var then_scene: pointer = null
|
var then_scene: pointer = null
|
||||||
var loads_then: pointer = null # loads then Next: pump the Assets queue, draw a bar, go on when ready
|
var loads_then: pointer = null # loads then Next: pump the Assets queue, draw a bar, go on when ready
|
||||||
while is_id("start") or is_id("public") or is_id("shows") or is_id("lasts") or is_id("loads") {
|
while is_id("start") or is_id("public") or is_id("shows") or is_id("lasts") or is_id("loads") {
|
||||||
if is_id("start") { pi = pi + 1; g_start_scene = g_scene_count }
|
if is_id("start") { pi += 1; g_start_scene = g_scene_count }
|
||||||
else { if is_id("shows") { pi = pi + 1; n.ty = eat_id() } # shows Menu: the engine opens, renders and closes it
|
else if is_id("shows") { pi += 1; n.ty = eat_id() } # shows Menu: the engine opens, renders and closes it
|
||||||
else { if is_id("lasts") { pi = pi + 1; lasts = expr(); if not is_id("then") { perr("lasts N needs `then Scene`") }; pi = pi + 1; then_scene = eat_id() }
|
else if is_id("lasts") { pi += 1; lasts = expr(); if not is_id("then") { perr("lasts N needs `then Scene`") }; pi += 1; then_scene = eat_id() }
|
||||||
else { if is_id("loads") { pi = pi + 1; if not is_id("then") { perr("loads needs `then Scene`") }; pi = pi + 1; loads_then = eat_id(); g_uses_atlas = true }
|
else if is_id("loads") { pi += 1; if not is_id("then") { perr("loads needs `then Scene`") }; pi += 1; loads_then = eat_id(); g_uses_atlas = true }
|
||||||
else { pi = pi + 1; is_pub = true } } } }
|
else { pi += 1; is_pub = true }
|
||||||
}
|
}
|
||||||
g_scene_count = g_scene_count + 1
|
g_scene_count += 1
|
||||||
if is_pub { ensure_event_empty(`scene_{n.s}_enter`); ensure_event_empty(`scene_{n.s}_exit`) }
|
if is_pub { ensure_event_empty(`scene_{n.s}_enter`); ensure_event_empty(`scene_{n.s}_exit`) }
|
||||||
skipnl(); eat_op("{")
|
skipnl(); eat_op("{")
|
||||||
while true {
|
while true {
|
||||||
skipnl(); if is_op("}") { break }
|
skipnl(); if is_op("}") { break }
|
||||||
if is_id("on") { # on enter { .. } / on exit { .. }
|
if is_id("on") { # on enter { .. } / on exit { .. }
|
||||||
pi = pi + 1; let which = eat_id(); skipnl()
|
pi += 1; let which = eat_id(); skipnl()
|
||||||
if (which == "enter") { n.a = block() }
|
if which == "enter" { n.a = block() }
|
||||||
else { if (which == "exit") { n.b = block() } else { perr("expected 'enter' or 'exit' after 'on'") } }
|
else if which == "exit" { n.b = block() }
|
||||||
|
else { perr("expected 'enter' or 'exit' after 'on'") }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_id("layer") { # layer Name [public] { handler .. }
|
if is_id("layer") { # layer Name [public] { handler .. }
|
||||||
pi = pi + 1; let lname = eat_id()
|
pi += 1; let lname = eat_id()
|
||||||
if is_id("public") { pi = pi + 1; ensure_event_empty(`layer_{lname}_show`); ensure_event_empty(`layer_{lname}_hide`) }
|
if is_id("public") { pi += 1; ensure_event_empty(`layer_{lname}_show`); ensure_event_empty(`layer_{lname}_hide`) }
|
||||||
skipnl(); eat_op("{")
|
skipnl(); eat_op("{")
|
||||||
let ltag = node(E_ID); ltag.s = lname # the layer name, tagged onto each handler
|
let ltag = node(E_ID); ltag.s = lname # the layer name, tagged onto each handler
|
||||||
while true {
|
while true {
|
||||||
skipnl(); if is_op("}") { break }
|
skipnl(); if is_op("}") { break }
|
||||||
var qspec: Node = null # `@Queries(...)` makes the handler run once per matching entity
|
var qspec: Node = null # `@Queries(...)` makes the handler run once per matching entity
|
||||||
while is_op("@") {
|
while is_op("@") {
|
||||||
pi = pi + 1; let a = eat_id()
|
pi += 1; let a = eat_id()
|
||||||
if (a == "Queries") { qspec = parse_queries_anno() }
|
if (a == "Queries") { qspec = parse_queries_anno() }
|
||||||
else { perr(`a handler inside a layer may only carry @Queries (got @{a})`) }
|
else { perr(`a handler inside a layer may only carry @Queries (got @{a})`) }
|
||||||
skipnl()
|
skipnl()
|
||||||
|
|
@ -260,7 +264,7 @@ function scene_add_timer(sc: Node, frames: Node, next: pointer) -> void {
|
||||||
let set = node(S_ASSIGN); set.s = "="; let lhs = node(E_ID); lhs.s = counter; set.a = lhs; set.b = frames
|
let set = node(S_ASSIGN); set.s = "="; let lhs = node(E_ID); lhs.s = counter; set.a = lhs; set.b = frames
|
||||||
let ins = new []Node; push(ins, set)
|
let ins = new []Node; push(ins, set)
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(sc.a.kids) { push(ins, sc.a.kids[i]); i = i + 1 }
|
while i < len(sc.a.kids) { push(ins, sc.a.kids[i]); i += 1 }
|
||||||
sc.a.kids = ins
|
sc.a.kids = ins
|
||||||
let h = node(N_SYS); h.s = `{sc.s}_Timer`; h.ty = "Update"; h.c = sc
|
let h = node(N_SYS); h.s = `{sc.s}_Timer`; h.ty = "Update"; h.c = sc
|
||||||
let body = node(N_BLOCK)
|
let body = node(N_BLOCK)
|
||||||
|
|
@ -281,7 +285,7 @@ function ui_register_gotos() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
if prog[i].kind == N_UI and prog[i].ival == 1 { ui_gotos_in(prog[i].a) }
|
if prog[i].kind == N_UI and prog[i].ival == 1 { ui_gotos_in(prog[i].a) }
|
||||||
i = i + 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function ui_gotos_in(w: Node) -> void {
|
function ui_gotos_in(w: Node) -> void {
|
||||||
|
|
@ -292,7 +296,7 @@ function ui_gotos_in(w: Node) -> void {
|
||||||
let pr = w.b.kids[p]
|
let pr = w.b.kids[p]
|
||||||
if (pr.s == "id") and (pr.a.kind == E_ID) { idn = pr.a.s }
|
if (pr.s == "id") and (pr.a.kind == E_ID) { idn = pr.a.s }
|
||||||
if (pr.s == "goto") and (pr.a.kind == E_ID) { goto = pr.a.s }
|
if (pr.s == "goto") and (pr.a.kind == E_ID) { goto = pr.a.s }
|
||||||
p = p + 1
|
p += 1
|
||||||
}
|
}
|
||||||
if (idn != null) and (goto != null) {
|
if (idn != null) and (goto != null) {
|
||||||
let body = node(N_BLOCK)
|
let body = node(N_BLOCK)
|
||||||
|
|
@ -303,7 +307,7 @@ function ui_gotos_in(w: Node) -> void {
|
||||||
register_onlisten("UiClicked", body)
|
register_onlisten("UiClicked", body)
|
||||||
}
|
}
|
||||||
var k = 0
|
var k = 0
|
||||||
while k < len(w.kids) { ui_gotos_in(w.kids[k]); k = k + 1 }
|
while k < len(w.kids) { ui_gotos_in(w.kids[k]); k += 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
# enum Name { A, B, C } — named int constants; a variant's value is its index.
|
# enum Name { A, B, C } — named int constants; a variant's value is its index.
|
||||||
|
|
@ -318,17 +322,17 @@ function ui_gotos_in(w: Node) -> void {
|
||||||
# Each variant node is an E_ID (s=variant name); its payload types are stored as
|
# Each variant node is an E_ID (s=variant name); its payload types are stored as
|
||||||
# N_PARAM kids, one per slot, carrying only `.ty`.
|
# N_PARAM kids, one per slot, carrying only `.ty`.
|
||||||
function parse_enum() -> Node {
|
function parse_enum() -> Node {
|
||||||
pi = pi + 1; let n = node(N_ENUM); n.s = eat_id(); skipnl(); eat_op("{")
|
pi += 1; let n = node(N_ENUM); n.s = eat_id(); skipnl(); eat_op("{")
|
||||||
while true { skipnl(); if is_op("}") { break }
|
while true { skipnl(); if is_op("}") { break }
|
||||||
let v = node(E_ID); v.s = eat_id()
|
let v = node(E_ID); v.s = eat_id()
|
||||||
if is_op("(") { # variant payload: Door(int), Portal(int, int)
|
if is_op("(") { # variant payload: Door(int), Portal(int, int)
|
||||||
pi = pi + 1; skipnl()
|
pi += 1; skipnl()
|
||||||
while not is_op(")") {
|
while not is_op(")") {
|
||||||
let p = node(N_PARAM); p.ty = ptype(); push(v.kids, p)
|
let p = node(N_PARAM); p.ty = ptype(); push(v.kids, p)
|
||||||
if is_op(",") { pi = pi + 1 }; skipnl() }
|
if is_op(",") { pi += 1 }; skipnl() }
|
||||||
eat_op(")") }
|
eat_op(")") }
|
||||||
push(n.kids, v)
|
push(n.kids, v)
|
||||||
if is_op(",") { pi = pi + 1 }; skipnl() }
|
if is_op(",") { pi += 1 }; skipnl() }
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,28 +342,28 @@ function parse_enum() -> Node {
|
||||||
# per model use-site — the same property syncs in one model, not another. The
|
# per model use-site — the same property syncs in one model, not another. The
|
||||||
# per-member @Sync sets the member E_ID's ival=1 (read by emit_net).
|
# per-member @Sync sets the member E_ID's ival=1 (read by emit_net).
|
||||||
function parse_archetype() -> Node {
|
function parse_archetype() -> Node {
|
||||||
pi = pi + 1; let n = node(N_ARCH); n.s = eat_id(); skipnl(); eat_op("{")
|
pi += 1; let n = node(N_ARCH); n.s = eat_id(); skipnl(); eat_op("{")
|
||||||
while true { skipnl(); if is_op("}") { break }
|
while true { skipnl(); if is_op("}") { break }
|
||||||
let c = node(E_ID)
|
let c = node(E_ID)
|
||||||
if is_op("@") { pi = pi + 1; let a = eat_id(); if (a == "Sync") { c.ival = 1 }; skipnl() }
|
if is_op("@") { pi += 1; let a = eat_id(); if (a == "Sync") { c.ival = 1 }; skipnl() }
|
||||||
c.s = eat_id(); push(n.kids, c)
|
c.s = eat_id(); push(n.kids, c)
|
||||||
if is_op(",") { pi = pi + 1 }; skipnl() }
|
if is_op(",") { pi += 1 }; skipnl() }
|
||||||
eat_op("}"); return n
|
eat_op("}"); return n
|
||||||
}
|
}
|
||||||
|
|
||||||
# extern function name(params) -> T = "symbol"
|
# extern function name(params) -> T = "symbol"
|
||||||
function parse_extern() -> Node {
|
function parse_extern() -> Node {
|
||||||
pi = pi + 1 # 'extern'
|
pi += 1 # 'extern'
|
||||||
let fnkw = eat_id() # 'fn'
|
let fnkw = eat_id() # 'fn'
|
||||||
let n = node(N_EXTERN); n.s = eat_id(); eat_op("(")
|
let n = node(N_EXTERN); n.s = eat_id(); eat_op("(")
|
||||||
while not is_op(")") { let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
|
while not is_op(")") { let p = node(N_PARAM); p.s = eat_id(); eat_op(":"); p.ty = ptype(); push(n.kids, p)
|
||||||
if is_op(",") { pi = pi + 1 } }
|
if is_op(",") { pi += 1 } }
|
||||||
eat_op(")")
|
eat_op(")")
|
||||||
n.ty = "void"
|
n.ty = "void"
|
||||||
if is_op("->") { pi = pi + 1; n.ty = ptype() }
|
if is_op("->") { pi += 1; n.ty = ptype() }
|
||||||
eat_op("=")
|
eat_op("=")
|
||||||
let t = toks[pi] # "symbol"
|
let t = toks[pi] # "symbol"
|
||||||
n.a = node(E_STR); n.a.s = t.text; pi = pi + 1
|
n.a = node(E_STR); n.a.s = t.text; pi += 1
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,13 +375,13 @@ function parse_widget() -> Node {
|
||||||
let pr = node(E_FINIT); pr.s = eat_id(); eat_op(":"); pr.a = expr(); push(w.b.kids, pr) # widget prop `key: value` (Rule A)
|
let pr = node(E_FINIT); pr.s = eat_id(); eat_op(":"); pr.a = expr(); push(w.b.kids, pr) # widget prop `key: value` (Rule A)
|
||||||
}
|
}
|
||||||
skipnl()
|
skipnl()
|
||||||
if is_op("{") { pi = pi + 1
|
if is_op("{") { pi += 1
|
||||||
while true { skipnl(); if is_op("}") { break }; push(w.kids, parse_widget()) }
|
while true { skipnl(); if is_op("}") { break }; push(w.kids, parse_widget()) }
|
||||||
eat_op("}") }
|
eat_op("}") }
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
function parse_ui() -> Node {
|
function parse_ui() -> Node {
|
||||||
pi = pi + 1; let n = node(N_UI); n.s = eat_id(); n.ival = 1 # ival=1 marks the top ui block
|
pi += 1; let n = node(N_UI); n.s = eat_id(); n.ival = 1 # ival=1 marks the top ui block
|
||||||
skipnl(); eat_op("{"); skipnl()
|
skipnl(); eat_op("{"); skipnl()
|
||||||
n.a = parse_widget()
|
n.a = parse_widget()
|
||||||
skipnl(); eat_op("}")
|
skipnl(); eat_op("}")
|
||||||
|
|
|
||||||
56417
selfhost/ludicc.seed.ll
56417
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -11,14 +11,14 @@
|
||||||
# ludic app.ludic # compile to a temp binary and run it
|
# ludic app.ludic # compile to a temp binary and run it
|
||||||
#
|
#
|
||||||
# The multi-call twist: when argv[0] is "ludic" it defaults to compile-and-run.
|
# The multi-call twist: when argv[0] is "ludic" it defaults to compile-and-run.
|
||||||
# With no -o and no run, IR still goes to stdout — the contract build.sh and
|
# With no -o and no run, IR still goes to stdout — the contract `x reseed` and
|
||||||
# reseed.sh rely on, so the bootstrap is untouched.
|
# `x bootstrap` rely on, so the bootstrap is untouched.
|
||||||
|
|
||||||
# basename: the part of a path after the last '/'.
|
# basename: the part of a path after the last '/'.
|
||||||
function base_name(path: pointer) -> pointer {
|
function base_name(path: pointer) -> pointer {
|
||||||
var last = 0 - 1
|
var last = -1
|
||||||
var i = 0
|
var i = 0
|
||||||
while path[i] != 0 { if path[i] == 47 { last = i }; i = i + 1 }
|
while path[i] != 0 { if path[i] == '/' { last = i }; i += 1 }
|
||||||
return path[last + 1..i]
|
return path[last + 1..i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,8 +42,8 @@ function getenv_or(name: pointer, dflt: pointer) -> pointer {
|
||||||
function ensure_slash(d: pointer) -> pointer {
|
function ensure_slash(d: pointer) -> pointer {
|
||||||
let n = len(d)
|
let n = len(d)
|
||||||
if n == 0 { return d }
|
if n == 0 { return d }
|
||||||
if d[n - 1] == 47 { return d }
|
if d[n - 1] == '/' { return d }
|
||||||
return (d + ("/"))
|
return d + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
function die(msg: pointer) -> void {
|
function die(msg: pointer) -> void {
|
||||||
|
|
@ -54,7 +54,7 @@ function die(msg: pointer) -> void {
|
||||||
# strip trailing newline/carriage-return/space (the VERSION file's trailing \n)
|
# strip trailing newline/carriage-return/space (the VERSION file's trailing \n)
|
||||||
function chomp(s: pointer) -> pointer {
|
function chomp(s: pointer) -> pointer {
|
||||||
var n = len(s)
|
var n = len(s)
|
||||||
while (n > 0) and ((s[n - 1] == 10) or (s[n - 1] == 13) or (s[n - 1] == 32)) { n = n - 1 }
|
while (n > 0) and ((s[n - 1] == '\n') or (s[n - 1] == '\r') or (s[n - 1] == ' ')) { n -= 1 }
|
||||||
return s[0..n]
|
return s[0..n]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,25 +85,23 @@ entry {
|
||||||
var ai = 1
|
var ai = 1
|
||||||
while ai < arg_count() {
|
while ai < arg_count() {
|
||||||
let a = arg(ai)
|
let a = arg(ai)
|
||||||
if (a == ("--version")) { show_version() }
|
if a == "--version" { show_version() }
|
||||||
else { if (a == ("--windowed")) { want = 1 }
|
else if a == "--windowed" { want = 1 }
|
||||||
else { if (a == ("--headless")) { want = 2 }
|
else if a == "--headless" { want = 2 }
|
||||||
else { if (a == ("--emit-llvm")) { emit_ir = true }
|
else if a == "--emit-llvm" { emit_ir = true }
|
||||||
else { if (a == ("--emit-module")) { g_emit_module = true; emit_ir = true } # issue #64: prebuilt binary module IR
|
else if a == "--emit-module" { g_emit_module = true; emit_ir = true } # issue #64: prebuilt binary module IR
|
||||||
else { if (a == ("--fmt")) { fmt = true }
|
else if a == "--fmt" { fmt = true }
|
||||||
else { if (a == ("--save-temps")) { save = true }
|
else if a == "--save-temps" { save = true }
|
||||||
else { if (a == ("--run")) { run = true }
|
else if a == "--run" { run = true }
|
||||||
else { if (a == ("--coverage")) { g_coverage = true }
|
else if a == "--coverage" { g_coverage = true }
|
||||||
else { if (a == ("-o")) { ai = ai + 1; if ai < arg_count() { out = arg(ai) } }
|
else if a == "-o" { ai += 1; if ai < arg_count() { out = arg(ai) } }
|
||||||
else {
|
else if a[0] == '-' {
|
||||||
# an unknown flag is ignored (with a note) rather than mistaken for the
|
# an unknown flag is ignored (with a note) rather than mistaken for the input file
|
||||||
# input file — '-' is ASCII 45
|
|
||||||
if a[0] == 45 {
|
|
||||||
let m = `ludicc: ignoring unknown flag {a}\n`
|
let m = `ludicc: ignoring unknown flag {a}\n`
|
||||||
file_write(file_stderr(), m, len(m))
|
file_write(file_stderr(), m, len(m))
|
||||||
} else { path = a }
|
}
|
||||||
} } } } } } } } } }
|
else { path = a }
|
||||||
ai = ai + 1
|
ai += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
|
|
@ -114,6 +112,7 @@ entry {
|
||||||
|
|
||||||
cur_dir = dir_of(path)
|
cur_dir = dir_of(path)
|
||||||
g_src_name = base_name(path) # for panic/expect file:line messages
|
g_src_name = base_name(path) # for panic/expect file:line messages
|
||||||
|
g_parse_file = path # for the compiler's own file:line diagnostics
|
||||||
lex(src)
|
lex(src)
|
||||||
parse_program()
|
parse_program()
|
||||||
g_prog_user_end = len(prog) # decls from the user's source; runtime splice appends after
|
g_prog_user_end = len(prog) # decls from the user's source; runtime splice appends after
|
||||||
|
|
@ -128,12 +127,13 @@ entry {
|
||||||
|
|
||||||
# a game gets a window by default; a plain program stays headless. An explicit
|
# a game gets a window by default; a plain program stays headless. An explicit
|
||||||
# flag always wins. The stdout-IR path (no target) also stays headless, which
|
# flag always wins. The stdout-IR path (no target) also stays headless, which
|
||||||
# is what reseed.sh compiles the compiler itself with.
|
# is what `x reseed` compiles the compiler itself with.
|
||||||
let has_target = run or ((out != null))
|
let has_target = run or (out != null)
|
||||||
if want == 1 { g_windowed = true }
|
if want == 1 { g_windowed = true }
|
||||||
else { if want == 2 { g_windowed = false }
|
else if want == 2 { g_windowed = false }
|
||||||
else { g_windowed = has_target and has_systems() } }
|
else { g_windowed = has_target and has_systems() }
|
||||||
|
|
||||||
|
g_parsing = false # lowering errors now locate by statement, not token
|
||||||
emit_program()
|
emit_program()
|
||||||
|
|
||||||
# --emit-llvm, or no binary target: emit IR and stop (stdout when out is null).
|
# --emit-llvm, or no binary target: emit IR and stop (stdout when out is null).
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,17 @@ function buf_new() -> Buf {
|
||||||
}
|
}
|
||||||
function buf_ensure(b: Buf, extra: int) -> void {
|
function buf_ensure(b: Buf, extra: int) -> void {
|
||||||
if b.len + extra + 1 <= b.cap { return }
|
if b.len + extra + 1 <= b.cap { return }
|
||||||
while b.len + extra + 1 > b.cap { b.cap = b.cap * 2 }
|
while b.len + extra + 1 > b.cap { b.cap *= 2 }
|
||||||
b.data = resize(b.data, b.cap)
|
b.data = resize(b.data, b.cap)
|
||||||
}
|
}
|
||||||
function buf_putc(b: Buf, c: int) -> void {
|
function buf_putc(b: Buf, c: int) -> void {
|
||||||
buf_ensure(b, 1)
|
buf_ensure(b, 1)
|
||||||
b.data[b.len] = c
|
b.data[b.len] = c
|
||||||
b.len = b.len + 1
|
b.len += 1
|
||||||
}
|
}
|
||||||
function buf_puts(b: Buf, s: pointer) -> void {
|
function buf_puts(b: Buf, s: pointer) -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while s[i] != 0 { buf_putc(b, s[i]); i = i + 1 }
|
while s[i] != 0 { buf_putc(b, s[i]); i += 1 }
|
||||||
}
|
}
|
||||||
function buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) }
|
function buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) }
|
||||||
function buf_str(b: Buf) -> pointer { b.data[b.len] = 0; return b.data }
|
function buf_str(b: Buf) -> pointer { b.data[b.len] = 0; return b.data }
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@ function read_file(path: string) -> pointer {
|
||||||
# length of a NUL-terminated buffer
|
# length of a NUL-terminated buffer
|
||||||
function cstr_len(s: pointer) -> int {
|
function cstr_len(s: pointer) -> int {
|
||||||
var n = 0
|
var n = 0
|
||||||
while s[n] != 0 { n = n + 1 }
|
while s[n] != 0 { n += 1 }
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,37 @@
|
||||||
# slicing out substrings, and integer<->text. All over raw NUL-terminated
|
# slicing out substrings, and integer<->text. All over raw NUL-terminated
|
||||||
# byte buffers reached with peek8/poke8.
|
# byte buffers reached with peek8/poke8.
|
||||||
|
|
||||||
# a fresh NUL-terminated copy of src[start .. start+n]
|
function char_is_digit(c: int) -> bool { return c >= '0' and c <= '9' }
|
||||||
|
|
||||||
function char_is_digit(c: int) -> bool { return c >= 48 and c <= 57 }
|
|
||||||
function char_is_alpha(c: int) -> bool {
|
function char_is_alpha(c: int) -> bool {
|
||||||
if c >= 65 and c <= 90 { return true }
|
if c >= 'A' and c <= 'Z' { return true }
|
||||||
if c >= 97 and c <= 122 { return true }
|
if c >= 'a' and c <= 'z' { return true }
|
||||||
return c == 95
|
return c == '_'
|
||||||
}
|
}
|
||||||
function char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) }
|
function char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) }
|
||||||
|
|
||||||
|
# does `s` begin with `prefix`? (byte-wise; both NUL-terminated)
|
||||||
|
function str_starts(s: pointer, prefix: pointer) -> bool {
|
||||||
|
var i = 0
|
||||||
|
while prefix[i] != 0 { if s[i] != prefix[i] { return false }; i += 1 }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
# integer -> fresh decimal string
|
# integer -> fresh decimal string
|
||||||
function itoa(v: int) -> pointer {
|
function itoa(v: int) -> pointer {
|
||||||
if v == 0 { let z = bytes(2); z[0] = 48; z[1] = 0; return z }
|
if v == 0 { let z = bytes(2); z[0] = '0'; z[1] = 0; return z }
|
||||||
var neg = false
|
var neg = false
|
||||||
var x = v
|
var x = v
|
||||||
if x < 0 { neg = true; x = 0 - x }
|
if x < 0 { neg = true; x = -x }
|
||||||
let tmp = bytes(16)
|
let tmp = bytes(16)
|
||||||
var n = 0
|
var n = 0
|
||||||
while x > 0 { tmp[n] = 48 + x % 10; x = x / 10; n = n + 1 }
|
while x > 0 { tmp[n] = 48 + x % 10; x /= 10; n += 1 }
|
||||||
var total = n
|
var total = n
|
||||||
if neg { total = total + 1 }
|
if neg { total += 1 }
|
||||||
let out = bytes(total + 1)
|
let out = bytes(total + 1)
|
||||||
var k = 0
|
var k = 0
|
||||||
if neg { out[0] = 45; k = 1 }
|
if neg { out[0] = '-'; k = 1 }
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < n { out[k + i] = tmp[n - 1 - i]; i = i + 1 }
|
while i < n { out[k + i] = tmp[n - 1 - i]; i += 1 }
|
||||||
out[total] = 0
|
out[total] = 0
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ program T {
|
||||||
entry {
|
entry {
|
||||||
print(fib(10)) # 55
|
print(fib(10)) # 55
|
||||||
var s = 0
|
var s = 0
|
||||||
for i in 0 .. 10 { if i == 5 { break }; if i % 2 == 0 { continue }; s = s + i }
|
for i in 0 .. 10 { if i == 5 { break }; if i % 2 == 0 { continue }; s += i }
|
||||||
print(s) # 1+3 = 4
|
print(s) # 1+3 = 4
|
||||||
var i = 0; var t = 0
|
var i = 0; var t = 0
|
||||||
while true { i = i + 1; if i > 5 { break }; t = t + i }
|
while true { i += 1; if i > 5 { break }; t += i }
|
||||||
print(t) # 15
|
print(t) # 15
|
||||||
if false and (1 / 0 == 0) { print(999) } else { print(1) } # short-circuit: no div by zero
|
if false and (1 / 0 == 0) { print(999) } else { print(1) } # short-circuit: no div by zero
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ program T {
|
||||||
print(x > y) # 1
|
print(x > y) # 1
|
||||||
print(y < x) # 1
|
print(y < x) # 1
|
||||||
print(x == x) # 1
|
print(x == x) # 1
|
||||||
print(0 - sq) # -1000000000000 (unary negate stays 64-bit)
|
print(-sq) # -1000000000000 (unary negate stays 64-bit)
|
||||||
print(big) # 1000000
|
print(big) # 1000000
|
||||||
print(Text.length(string(sq))) # 13 — string(long) renders all 13 digits
|
print(Text.length(string(sq))) # 13 — string(long) renders all 13 digits
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ program T {
|
||||||
# min / max / abs / clamp, now reachable under the Math namespace
|
# min / max / abs / clamp, now reachable under the Math namespace
|
||||||
print(Math.min(3, 7)) # 3
|
print(Math.min(3, 7)) # 3
|
||||||
print(Math.max(3, 7)) # 7
|
print(Math.max(3, 7)) # 7
|
||||||
print(Math.abs(0 - 5)) # 5
|
print(Math.abs(-5)) # 5
|
||||||
print(Math.clamp(12, 0, 10)) # 10
|
print(Math.clamp(12, 0, 10)) # 10
|
||||||
|
|
||||||
# sign -> -1 / 0 / 1
|
# sign -> -1 / 0 / 1
|
||||||
print(Math.sign(0 - 4)) # -1
|
print(Math.sign(-4)) # -1
|
||||||
print(Math.sign(0)) # 0
|
print(Math.sign(0)) # 0
|
||||||
print(Math.sign(9)) # 1
|
print(Math.sign(9)) # 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ program T {
|
||||||
print(Math.floor(Math.dist2(0.0, 0.0, 3.0, 4.0))) # 25
|
print(Math.floor(Math.dist2(0.0, 0.0, 3.0, 4.0))) # 25
|
||||||
print(Math.floor(Math.deg_to_rad(180.0) * 1000.0)) # 3142
|
print(Math.floor(Math.deg_to_rad(180.0) * 1000.0)) # 3142
|
||||||
print(Math.floor(Math.rad_to_deg(3.1415926))) # 180
|
print(Math.floor(Math.rad_to_deg(3.1415926))) # 180
|
||||||
print(Math.posmod(0 - 1, 5)) # 4
|
print(Math.posmod(-1, 5)) # 4
|
||||||
print(Math.wrap(12, 0, 10)) # 2
|
print(Math.wrap(12, 0, 10)) # 2
|
||||||
print(Math.ping_pong(13, 10)) # 7
|
print(Math.ping_pong(13, 10)) # 7
|
||||||
print(Math.floor(Math.snapped(2.3, 0.5) * 10.0)) # 25
|
print(Math.floor(Math.snapped(2.3, 0.5) * 10.0)) # 25
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
program T {
|
program T {
|
||||||
property P { x: int = 0, y: int = 7, next: P }
|
property P { x: int = 0, y: int = 7, next: P }
|
||||||
function bump(p: P) -> void { p.x = p.x + 100 }
|
function bump(p: P) -> void { p.x += 100 }
|
||||||
entry {
|
entry {
|
||||||
let a = new P
|
let a = new P
|
||||||
print(a.y) # 7 default
|
print(a.y) # 7 default
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Create a Forgejo release and attach build artifacts.
|
|
||||||
|
|
||||||
forgejo_release.py <tag> <body-file> <assets-dir>
|
|
||||||
|
|
||||||
Reads the access token from $FORGEJO_TOKEN. `x release --publish` calls this
|
|
||||||
after it has pushed the tag; it is the one piece of the release flow that speaks
|
|
||||||
HTTP, which the Ludic task runner cannot do yet (a native Http client is issue
|
|
||||||
#6). Python stdlib only — no third-party deps — matching the docgen tooling.
|
|
||||||
|
|
||||||
Idempotent-ish: if the release for <tag> already exists it is reused, and each
|
|
||||||
asset in <assets-dir> is uploaded (a duplicate name is skipped, not fatal).
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
|
|
||||||
API = "https://git.workshopsoft.io/api/v1/repos/workshopsoft/ludic"
|
|
||||||
|
|
||||||
|
|
||||||
def token():
|
|
||||||
t = os.environ.get("FORGEJO_TOKEN", "").strip()
|
|
||||||
if not t:
|
|
||||||
sys.exit("forgejo_release: FORGEJO_TOKEN is not set")
|
|
||||||
return t
|
|
||||||
|
|
||||||
|
|
||||||
def api(method, path, tok, data=None, headers=None, raw=False):
|
|
||||||
url = API + path
|
|
||||||
h = {"Authorization": "token " + tok}
|
|
||||||
body = None
|
|
||||||
if data is not None and not raw:
|
|
||||||
h["Content-Type"] = "application/json"
|
|
||||||
body = json.dumps(data).encode()
|
|
||||||
elif raw:
|
|
||||||
body = data
|
|
||||||
h.update(headers or {})
|
|
||||||
req = urllib.request.Request(url, data=body, headers=h, method=method)
|
|
||||||
with urllib.request.urlopen(req) as r:
|
|
||||||
return r.getcode(), json.loads(r.read().decode() or "null")
|
|
||||||
|
|
||||||
|
|
||||||
def get_release_by_tag(tag, tok):
|
|
||||||
try:
|
|
||||||
_, rel = api("GET", "/releases/tags/" + tag, tok)
|
|
||||||
return rel
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
if e.code == 404:
|
|
||||||
return None
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
sys.exit(__doc__)
|
|
||||||
tag, body_file, assets_dir = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
||||||
tok = token()
|
|
||||||
|
|
||||||
body = ""
|
|
||||||
if os.path.exists(body_file):
|
|
||||||
with open(body_file, encoding="utf-8") as fh:
|
|
||||||
body = fh.read()
|
|
||||||
|
|
||||||
rel = get_release_by_tag(tag, tok)
|
|
||||||
if rel is None:
|
|
||||||
_, rel = api("POST", "/releases", tok, {
|
|
||||||
"tag_name": tag,
|
|
||||||
"name": tag,
|
|
||||||
"body": body,
|
|
||||||
"draft": False,
|
|
||||||
"prerelease": tag.startswith("v0."),
|
|
||||||
})
|
|
||||||
print("created release", tag, "id", rel["id"])
|
|
||||||
else:
|
|
||||||
print("release", tag, "already exists (id %s); uploading assets" % rel["id"])
|
|
||||||
|
|
||||||
rid = rel["id"]
|
|
||||||
existing = {a["name"] for a in (rel.get("assets") or [])}
|
|
||||||
if os.path.isdir(assets_dir):
|
|
||||||
for name in sorted(os.listdir(assets_dir)):
|
|
||||||
if name in existing:
|
|
||||||
print(" skip (exists):", name)
|
|
||||||
continue
|
|
||||||
path = os.path.join(assets_dir, name)
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
continue
|
|
||||||
with open(path, "rb") as fh:
|
|
||||||
blob = fh.read()
|
|
||||||
boundary = "----ludicrelease7f3b2a"
|
|
||||||
payload = (
|
|
||||||
("--%s\r\n" % boundary)
|
|
||||||
+ ('Content-Disposition: form-data; name="attachment"; filename="%s"\r\n' % name)
|
|
||||||
+ "Content-Type: application/octet-stream\r\n\r\n"
|
|
||||||
).encode() + blob + ("\r\n--%s--\r\n" % boundary).encode()
|
|
||||||
code, _ = api(
|
|
||||||
"POST", "/releases/%s/assets?name=%s" % (rid, name), tok,
|
|
||||||
data=payload, raw=True,
|
|
||||||
headers={"Content-Type": "multipart/form-data; boundary=" + boundary},
|
|
||||||
)
|
|
||||||
print(" uploaded:", name, "(HTTP %d)" % code)
|
|
||||||
print("done:", API + "/releases/tag/" + tag)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -217,6 +217,7 @@
|
||||||
"phase-fixedupdate",
|
"phase-fixedupdate",
|
||||||
"phase-input",
|
"phase-input",
|
||||||
"phase-lateupdate",
|
"phase-lateupdate",
|
||||||
|
"phase-overlay",
|
||||||
"phase-render",
|
"phase-render",
|
||||||
"phase-start",
|
"phase-start",
|
||||||
"phase-update"
|
"phase-update"
|
||||||
|
|
@ -601,6 +602,7 @@
|
||||||
"os-set_env",
|
"os-set_env",
|
||||||
"os-unset_env",
|
"os-unset_env",
|
||||||
"os-exit",
|
"os-exit",
|
||||||
|
"os-pid",
|
||||||
"os-platform",
|
"os-platform",
|
||||||
"os-arch",
|
"os-arch",
|
||||||
"os-stdout_write",
|
"os-stdout_write",
|
||||||
|
|
|
||||||
|
|
@ -1,301 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Drive build/ludic-lsp over real LSP traffic against the real source tree.
|
|
||||||
|
|
||||||
Every assertion here is about a feature an editor actually calls: if this
|
|
||||||
passes, VS Code, JetBrains, Neovim and the rest are exercising code paths that
|
|
||||||
have been checked, because they all speak to this one binary.
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
# the toolchain binaries live in bin/ (built by `x tools`); LUDIC_LSP overrides.
|
|
||||||
SERVER = os.environ.get("LUDIC_LSP", os.path.join(ROOT, "bin", "ludic-lsp"))
|
|
||||||
|
|
||||||
passed = 0
|
|
||||||
failed = 0
|
|
||||||
|
|
||||||
|
|
||||||
def check(name, got, want):
|
|
||||||
global passed, failed
|
|
||||||
if got == want:
|
|
||||||
passed += 1
|
|
||||||
print(f" ok {name}")
|
|
||||||
else:
|
|
||||||
failed += 1
|
|
||||||
print(f" FAIL {name}\n expected {want!r}, got {got!r}")
|
|
||||||
|
|
||||||
|
|
||||||
def check_true(name, cond, detail=""):
|
|
||||||
check(name, bool(cond), True) if cond else check(name + (f" ({detail})" if detail else ""), False, True)
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
def __init__(self, cwd):
|
|
||||||
self.p = subprocess.Popen([SERVER], stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE, cwd=cwd)
|
|
||||||
self.diagnostics = {}
|
|
||||||
|
|
||||||
def _send(self, obj):
|
|
||||||
body = json.dumps(obj).encode()
|
|
||||||
self.p.stdin.write(b"Content-Length: %d\r\n\r\n" % len(body) + body)
|
|
||||||
self.p.stdin.flush()
|
|
||||||
|
|
||||||
def _read(self):
|
|
||||||
header = b""
|
|
||||||
while not header.endswith(b"\r\n\r\n"):
|
|
||||||
c = self.p.stdout.read(1)
|
|
||||||
if not c:
|
|
||||||
return None
|
|
||||||
header += c
|
|
||||||
length = int([l for l in header.decode().split("\r\n")
|
|
||||||
if l.lower().startswith("content-length")][0].split(":")[1])
|
|
||||||
return json.loads(self.p.stdout.read(length))
|
|
||||||
|
|
||||||
def request(self, method, params, _id=[0]):
|
|
||||||
_id[0] += 1
|
|
||||||
rid = _id[0]
|
|
||||||
self._send({"jsonrpc": "2.0", "id": rid, "method": method, "params": params})
|
|
||||||
while True:
|
|
||||||
msg = self._read()
|
|
||||||
if msg is None:
|
|
||||||
raise RuntimeError(f"server closed during {method}")
|
|
||||||
if msg.get("id") == rid:
|
|
||||||
return msg.get("result")
|
|
||||||
if msg.get("method") == "textDocument/publishDiagnostics":
|
|
||||||
p = msg["params"]
|
|
||||||
self.diagnostics[os.path.basename(p["uri"])] = [d["message"] for d in p["diagnostics"]]
|
|
||||||
|
|
||||||
def notify(self, method, params):
|
|
||||||
self._send({"jsonrpc": "2.0", "method": method, "params": params})
|
|
||||||
|
|
||||||
def open(self, path, language_id="ludic"):
|
|
||||||
with open(path) as fh:
|
|
||||||
text = fh.read()
|
|
||||||
self.notify("textDocument/didOpen", {"textDocument": {
|
|
||||||
"uri": "file://" + path, "languageId": language_id, "version": 1, "text": text}})
|
|
||||||
return text
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
try:
|
|
||||||
self.request("shutdown", None)
|
|
||||||
self.notify("exit", None)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self.p.kill()
|
|
||||||
|
|
||||||
|
|
||||||
def position_of(text, needle, offset=0):
|
|
||||||
for i, line in enumerate(text.split("\n")):
|
|
||||||
if needle in line:
|
|
||||||
return {"line": i, "character": line.index(needle) + offset}
|
|
||||||
raise AssertionError(f"{needle!r} not found")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if not os.access(SERVER, os.X_OK):
|
|
||||||
print(" skip ludic-lsp missing")
|
|
||||||
return 2
|
|
||||||
|
|
||||||
c = Client(ROOT)
|
|
||||||
caps = c.request("initialize", {"rootUri": "file://" + ROOT, "capabilities": {}})["capabilities"]
|
|
||||||
c.notify("initialized", {})
|
|
||||||
|
|
||||||
for cap in ["completionProvider", "hoverProvider", "definitionProvider",
|
|
||||||
"referencesProvider", "renameProvider", "documentSymbolProvider",
|
|
||||||
"workspaceSymbolProvider", "documentFormattingProvider",
|
|
||||||
"semanticTokensProvider", "foldingRangeProvider",
|
|
||||||
"signatureHelpProvider", "inlayHintProvider", "documentLinkProvider"]:
|
|
||||||
check_true(f"advertises {cap}", cap in caps)
|
|
||||||
|
|
||||||
# ---- a fragment file: no `program` block, only reachable through an import --
|
|
||||||
frag = os.path.join(ROOT, "examples/games/chronorift/combat.ludic")
|
|
||||||
text = c.open(frag)
|
|
||||||
uri = {"uri": "file://" + frag}
|
|
||||||
|
|
||||||
syms = c.request("textDocument/documentSymbol", {"textDocument": uri})
|
|
||||||
check_true("document symbols in an imported fragment", len(syms) >= 3)
|
|
||||||
|
|
||||||
# a field reached through a query binding, declared in ANOTHER file
|
|
||||||
pos = position_of(text, "st.guard", 3)
|
|
||||||
hov = c.request("textDocument/hover", {"textDocument": uri, "position": pos})
|
|
||||||
check_true("hover resolves a field through a query binding",
|
|
||||||
hov and "Stats.guard: int" in hov["contents"]["value"])
|
|
||||||
|
|
||||||
d = c.request("textDocument/definition", {"textDocument": uri, "position": pos})
|
|
||||||
check_true("go-to-definition crosses files", d and d["uri"].endswith("world.ludic"))
|
|
||||||
|
|
||||||
# a binding is not yet inside its own scope at the point it is written, so
|
|
||||||
# resolving it has to go through the declaration table, not scope lookup
|
|
||||||
qpos = position_of(text, "for (e) in query", 5)
|
|
||||||
qhov = c.request("textDocument/hover", {"textDocument": uri, "position": qpos})
|
|
||||||
check_true("hover works on a query binding at its declaration site",
|
|
||||||
qhov and "query binding" in qhov["contents"]["value"],
|
|
||||||
json.dumps(qhov)[:80])
|
|
||||||
|
|
||||||
comp = c.request("textDocument/completion", {"textDocument": uri,
|
|
||||||
"position": position_of(text, "st.guard", 3)})
|
|
||||||
labels = [i["label"] for i in comp["items"]]
|
|
||||||
check_true("completion after '.' offers only that property's fields",
|
|
||||||
"guard" in labels and "hp" in labels and "clear" not in labels)
|
|
||||||
|
|
||||||
# a query's term list admits propertys and models and nothing else —
|
|
||||||
# not the loop variables, not builtins, not keywords
|
|
||||||
qsrc = ('program QT {\n property Pos { x: int = 0 }\n property Vel { dx: int = 0 }\n'
|
|
||||||
' handler S phase Update {\n for (a, b) in query [Pos, ] { }\n }\n}\n')
|
|
||||||
qfile = "/tmp/ludic_lsp_test_query.ludic"
|
|
||||||
with open(qfile, "w") as fh:
|
|
||||||
fh.write(qsrc)
|
|
||||||
c.open(qfile)
|
|
||||||
quri = {"uri": "file://" + qfile}
|
|
||||||
qcomp = c.request("textDocument/completion",
|
|
||||||
{"textDocument": quri, "position": position_of(qsrc, "[Pos, ]", 6)})
|
|
||||||
qlabels = sorted(i["label"] for i in qcomp["items"])
|
|
||||||
check("completion inside query terms lists propertys only", qlabels, ["Pos", "Vel"])
|
|
||||||
body = c.request("textDocument/completion",
|
|
||||||
{"textDocument": quri, "position": position_of(qsrc, "] { }", 3)})
|
|
||||||
blabels = [i["label"] for i in body["items"]]
|
|
||||||
check_true("completion in the loop body still has locals and builtins",
|
|
||||||
"a" in blabels and "clear" in blabels)
|
|
||||||
os.unlink(qfile)
|
|
||||||
|
|
||||||
refs = c.request("textDocument/references", {"textDocument": uri,
|
|
||||||
"position": position_of(text, "query [Stats", 7),
|
|
||||||
"context": {"includeDeclaration": True}})
|
|
||||||
files = {os.path.basename(r["uri"]) for r in refs}
|
|
||||||
check_true("find-usages spans the compilation unit", len(files) >= 3, str(sorted(files)))
|
|
||||||
|
|
||||||
hints = c.request("textDocument/inlayHint", {"textDocument": uri,
|
|
||||||
"range": {"start": {"line": 0, "character": 0},
|
|
||||||
"end": {"line": 9999, "character": 0}}})
|
|
||||||
check_true("inlay hints name query binding types", any(": Stats" == h["label"] for h in hints))
|
|
||||||
|
|
||||||
# a namespaced call resolves to its underlying bare-builtin signature
|
|
||||||
sig = c.request("textDocument/signatureHelp",
|
|
||||||
{"textDocument": uri, "position": position_of(text, "Random.chance(", 14)})
|
|
||||||
check_true("signature help for a namespaced builtin",
|
|
||||||
sig and sig["signatures"][0]["label"].startswith("rng_chance("))
|
|
||||||
|
|
||||||
toks = c.request("textDocument/semanticTokens/full", {"textDocument": uri})
|
|
||||||
check_true("semantic tokens produced", len(toks["data"]) // 5 > 100)
|
|
||||||
check_true("semantic token stream is well formed", len(toks["data"]) % 5 == 0)
|
|
||||||
|
|
||||||
folds = c.request("textDocument/foldingRange", {"textDocument": uri})
|
|
||||||
check_true("folding ranges produced", len(folds) > 5)
|
|
||||||
|
|
||||||
# ---- structural diagnostics arrive while typing --------------------------
|
|
||||||
c.notify("textDocument/didChange", {"textDocument": {"uri": uri["uri"], "version": 2},
|
|
||||||
"contentChanges": [{"text": text + "\n{ oops"}]})
|
|
||||||
c.request("textDocument/documentSymbol", {"textDocument": uri})
|
|
||||||
check_true("unbalanced brace reported while typing",
|
|
||||||
any("unclosed" in m for m in c.diagnostics.get("combat.ludic", [])))
|
|
||||||
|
|
||||||
# ---- the LSP analyses opened files on its own ----------------------------
|
|
||||||
# (Deep semantic diagnostics once came from `ludicc --fmt`; the compiler is
|
|
||||||
# now written in Ludic and the LSP relies on its own parser. It still opens
|
|
||||||
# any file and answers structural queries without a compiler present.)
|
|
||||||
bad = "/tmp/ludic_lsp_test_bad.ludic"
|
|
||||||
with open(bad, "w") as fh:
|
|
||||||
fh.write("program Bad {\n property P { x: nosuchtype = 0 }\n}\n")
|
|
||||||
c.open(bad)
|
|
||||||
syms = c.request("textDocument/documentSymbol", {"textDocument": {"uri": "file://" + bad}})
|
|
||||||
check_true("LSP opens and analyses a file with no compiler present",
|
|
||||||
any(s["name"] == "Bad" for s in syms))
|
|
||||||
os.unlink(bad)
|
|
||||||
|
|
||||||
# ---- markdown: ```ludic fences are real Ludic ---------------------------
|
|
||||||
md = "/tmp/ludic_lsp_test.md"
|
|
||||||
with open(md, "w") as fh:
|
|
||||||
fh.write("# Title\n\nprose\n\n```ludic\nprogram D { property Pt { x: int = 0 } }\n```\n\n"
|
|
||||||
"```python\nz = 1\n```\n")
|
|
||||||
c.open(md, "markdown")
|
|
||||||
mduri = {"uri": "file://" + md}
|
|
||||||
mdsyms = c.request("textDocument/documentSymbol", {"textDocument": mduri})
|
|
||||||
check_true("symbols found inside a markdown fence", [s["name"] for s in mdsyms] == ["D"])
|
|
||||||
mdtoks = c.request("textDocument/semanticTokens/full", {"textDocument": mduri})
|
|
||||||
check("markdown tokens start on the fence body line", mdtoks["data"][0], 5)
|
|
||||||
# A fence is a snippet: elisions and half-shown blocks are the documentation
|
|
||||||
# doing its job, so errors there would be noise. Highlighting still applies.
|
|
||||||
check("no diagnostics are reported for markdown", c.diagnostics.get("ludic_lsp_test.md"), [])
|
|
||||||
fmt = c.request("textDocument/formatting", {"textDocument": mduri,
|
|
||||||
"options": {"tabSize": 2, "insertSpaces": True}})
|
|
||||||
check_true("markdown formatting leaves prose and other fences alone",
|
|
||||||
"prose" in fmt[0]["newText"] and "z = 1" in fmt[0]["newText"])
|
|
||||||
os.unlink(md)
|
|
||||||
|
|
||||||
# the repository's own documentation must not light up with false errors
|
|
||||||
for doc in ("LANGUAGE.md", "README.md", "COMPILING.md"):
|
|
||||||
path = os.path.join(ROOT, doc)
|
|
||||||
if not os.path.exists(path):
|
|
||||||
continue
|
|
||||||
c.open(path, "markdown")
|
|
||||||
c.request("textDocument/documentSymbol", {"textDocument": {"uri": "file://" + path}})
|
|
||||||
check(f"{doc} opens with no diagnostics", c.diagnostics.get(doc, []), [])
|
|
||||||
lang = os.path.join(ROOT, "LANGUAGE.md")
|
|
||||||
if os.path.exists(lang):
|
|
||||||
t = c.request("textDocument/semanticTokens/full", {"textDocument": {"uri": "file://" + lang}})
|
|
||||||
check_true("LANGUAGE.md fences are highlighted", len(t["data"]) // 5 > 100)
|
|
||||||
|
|
||||||
# ---- rename has to be resolution-based, not textual ----------------------
|
|
||||||
# `x` here is a field of Pos, a field of Vel, a local, and a parameter.
|
|
||||||
# Renaming any one of them must leave the other three alone.
|
|
||||||
amb = "/tmp/ludic_lsp_test_rename.ludic"
|
|
||||||
with open(amb, "w") as fh:
|
|
||||||
fh.write("program R {\n"
|
|
||||||
" property Pos { x: int = 0, y: int = 0 }\n"
|
|
||||||
" property Vel { x: int = 0 }\n"
|
|
||||||
" function helper(x: int) -> int { return x + 1 }\n"
|
|
||||||
" handler A phase Update {\n"
|
|
||||||
" for (p) in query [Pos] {\n"
|
|
||||||
" let x = 5\n"
|
|
||||||
" p.x = p.x + x\n"
|
|
||||||
" }\n"
|
|
||||||
" }\n"
|
|
||||||
" handler B phase Update {\n"
|
|
||||||
" for (v) in query [Vel] { v.x = v.x + 1 }\n"
|
|
||||||
" }\n"
|
|
||||||
"}\n")
|
|
||||||
atext = c.open(amb)
|
|
||||||
auri = {"uri": "file://" + amb}
|
|
||||||
|
|
||||||
def rename_at(pos):
|
|
||||||
r = c.request("textDocument/rename",
|
|
||||||
{"textDocument": auri, "position": pos, "newName": "ZZZ"})
|
|
||||||
if not r or not r.get("changes"):
|
|
||||||
return []
|
|
||||||
return list(r["changes"].values())[0]
|
|
||||||
|
|
||||||
def lines_touched(edits):
|
|
||||||
return sorted({e["range"]["start"]["line"] for e in edits})
|
|
||||||
|
|
||||||
field = rename_at(position_of(atext, "property Pos { x", 15))
|
|
||||||
check("renaming Pos.x touches only its declaration and its uses",
|
|
||||||
lines_touched(field), [1, 7])
|
|
||||||
local = rename_at(position_of(atext, "let x = 5", 4))
|
|
||||||
check("renaming a local touches only that local", lines_touched(local), [6, 7])
|
|
||||||
param = rename_at(position_of(atext, "function helper(x", 16))
|
|
||||||
check("renaming a parameter touches only that function", lines_touched(param), [3])
|
|
||||||
check("renaming a parameter makes exactly two edits", len(param), 2)
|
|
||||||
|
|
||||||
# find-usages must be equally precise
|
|
||||||
refs_x = c.request("textDocument/references",
|
|
||||||
{"textDocument": auri,
|
|
||||||
"position": position_of(atext, "property Vel { x", 15),
|
|
||||||
"context": {"includeDeclaration": True}})
|
|
||||||
check("find-usages of Vel.x does not pick up Pos.x",
|
|
||||||
sorted({r["range"]["start"]["line"] for r in refs_x}), [2, 11])
|
|
||||||
os.unlink(amb)
|
|
||||||
|
|
||||||
# ---- workspace-wide symbol search ---------------------------------------
|
|
||||||
ws = c.request("workspace/symbol", {"query": "Stats"})
|
|
||||||
check_true("workspace symbol search finds a property", any(s["name"] == "Stats" for s in ws))
|
|
||||||
|
|
||||||
c.close()
|
|
||||||
print(f" {passed} passed, {failed} failed")
|
|
||||||
return 1 if failed else 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue