|
1 | | -# Gengo (言語) |
| 1 | +# gengo |
2 | 2 |
|
3 | | -A small embeddable scripting language implemented in Zig. Single-pass Pratt compiler → bytecode VM. Runs natively or as a WASI module. |
| 3 | +Embeddable scripting language implemented in Zig. |
4 | 4 |
|
5 | | -**[Try it in the browser →](https://gengoscript.github.io/gengo/)** |
| 5 | +It compiles in one pass to bytecode and runs on a VM. |
6 | 6 |
|
7 | | -Project status: early-stage and intentionally evolving. |
| 7 | +Goal: a pragmatic language you can embed and run in a sandboxed environment, with WASM as a first-class target. |
8 | 8 |
|
9 | | -## Language at a Glance |
| 9 | +- bytecode VM |
| 10 | +- native CLI for development |
| 11 | +- WASI build for sandboxed execution |
| 12 | +- Zig embedding API |
| 13 | +- relative source-file modules |
10 | 14 |
|
11 | | -```gengo |
12 | | -std := import("std") |
| 15 | +**[Try it in the browser](https://gengoscript.github.io/gengo/)** |
13 | 16 |
|
14 | | -type Shape interface { |
15 | | - area() float |
16 | | -} |
| 17 | +Project status: early and still evolving. Breaking changes are expected while the language and runtime are being tightened. |
17 | 18 |
|
18 | | -type Rect struct { w float, h float } |
| 19 | +## Example |
19 | 20 |
|
20 | | -func (r Rect) area() float { |
21 | | - return r.w * r.h |
| 21 | +```gengo |
| 22 | +std := import("std") |
| 23 | +math := import("./math") |
| 24 | +
|
| 25 | +type Point struct { |
| 26 | + x int |
| 27 | + y int |
22 | 28 | } |
23 | 29 |
|
24 | | -func printArea(s Shape) { |
25 | | - std.io.printf("area: %f\n", s.area()) |
| 30 | +func (p Point) sum() int { |
| 31 | + return p.x + p.y |
26 | 32 | } |
27 | 33 |
|
28 | | -printArea(Rect{w: 4.0, h: 3.0}) |
| 34 | +p := Point{ x: 3, y: 4 } |
| 35 | +std.io.println(math.add(p.sum(), 10)) |
29 | 36 | ``` |
30 | 37 |
|
31 | 38 | ```gengo |
32 | | -std := import("std") |
33 | | -
|
34 | | -// Named types with range constraints |
35 | | -type Celsius float range -273.15..1000.0 |
36 | | -
|
37 | | -func toTemp(n float) { |
38 | | - if n < -273.15 { |
39 | | - return 0.0, std.core.error("below absolute zero") |
40 | | - } |
41 | | - return n, null |
| 39 | +// math.gengo |
| 40 | +pub func add(a int, b int) int { |
| 41 | + return a + b |
42 | 42 | } |
| 43 | +``` |
43 | 44 |
|
44 | | -// trap binding: null passes through, non-null panics into defer/recover |
45 | | -func parseTemp(s string) { |
46 | | - defer func() { |
47 | | - err := std.core.recover() |
48 | | - if err != null { std.io.println("bad input:", err) } |
49 | | - }() |
| 45 | +## What It Has |
50 | 46 |
|
51 | | - raw := std.conv.to_float(s) |
52 | | - temp, trap := toTemp(raw) |
53 | | - std.io.printf("%.1f C\n", float(Celsius(temp))) |
54 | | -} |
| 47 | +- structs and methods |
| 48 | +- interfaces |
| 49 | +- enums |
| 50 | +- variant types |
| 51 | +- named scalar types and subtypes with range constraints |
| 52 | +- arrays, maps, strings, runes, errors, `null`, `any` |
| 53 | +- closures |
| 54 | +- multi-return functions |
| 55 | +- `defer`, `recover`, `assert` |
| 56 | +- `for`, `for-in`, `switch` |
| 57 | +- `std` library namespaces such as `std.io`, `std.core`, `std.string`, `std.math`, `std.conv` |
| 58 | +- source-file modules via `import("./path")` |
55 | 59 |
|
56 | | -parseTemp("98.6") |
57 | | -parseTemp("-300.0") |
58 | | -``` |
| 60 | +## What It Is Aiming For |
| 61 | + |
| 62 | +- embeddable by default |
| 63 | +- predictable sandboxed execution |
| 64 | +- good WASM story |
| 65 | +- simple implementation that is still worth optimizing |
59 | 66 |
|
60 | | -## Features |
61 | | - |
62 | | -**Types** |
63 | | -- Structs with typed fields, nullable (`?T`), union (`A|B`), and method receivers |
64 | | -- Interfaces with structural satisfaction checking |
65 | | -- Enums with qualified member access (`Status.pending`) |
66 | | -- Named scalar types: `type UserId string`, `type Month int range 1..12` |
67 | | -- First-class `error` values; `any` as the empty interface |
68 | | - |
69 | | -**Functions** |
70 | | -- Typed parameters (mandatory); `any` to opt out |
71 | | -- Variadic: `func sum(...xs int) int` |
72 | | -- Multi-return: `return value, err` |
73 | | -- Named return variables: `func f() (result float, err ?error)` |
74 | | -- Closures with upvalue capture |
75 | | - |
76 | | -**Error Handling** |
77 | | -- `defer` — runs on function exit in LIFO order |
78 | | -- `std.core.recover()` — intercepts panics from inside a defer |
79 | | -- `assert condition` / `assert condition, "message"` — panic if false |
80 | | -- `x, trap := f()` — panic if the bound slot is non-null, pass through if null |
81 | | - |
82 | | -**Control Flow** |
83 | | -- `if / else if / else` with optional init statement |
84 | | -- `for cond`, `for init; cond; post`, `for v in seq`, `for i, v in seq` |
85 | | -- `switch expr { case v { } default { } }` |
86 | | -- `break`, `continue`, `return` |
87 | | - |
88 | | -**Declarations** |
89 | | -- `x := expr` — inferred mutable |
90 | | -- `x Type = expr` — explicit typed mutable |
91 | | -- `const x Type = expr` — immutable |
92 | | -- `_` discard in destructure: `val, _ := f()` |
93 | | - |
94 | | -## Standard Library |
95 | | - |
96 | | -| Namespace | Functions | |
97 | | -|---|---| |
98 | | -| `std.io` | `print`, `println`, `printf` | |
99 | | -| `std.core` | `len`, `bytelen`, `append`, `contains`, `remove`, `has`, `delete`, `keys`, `values`, `error`, `is_error`, `recover`, `gc`, `gc_live_objects`, `gc_stats` | |
100 | | -| `std.string` | `split`, `join`, `trim`, `upper`, `lower`, `starts_with`, `ends_with`, `index_of` | |
101 | | -| `std.math` | `abs`, `sqrt`, `floor`, `ceil`, `round`, `sin`, `cos`, `tan`, `log`, `log2`, `log10`, `pow`, `min`, `max`, `pi`, `e`, `inf` | |
102 | | -| `std.conv` | `to_int`, `to_float`, `to_bool`, `to_string` | |
| 67 | +This is not trying to preserve Tengo compatibility. The language is defined by the implementation in this repository. |
103 | 68 |
|
104 | 69 | ## Quick Start |
105 | 70 |
|
106 | | -**Native CLI** (recommended for development): |
| 71 | +Build the native CLI: |
107 | 72 |
|
108 | 73 | ```bash |
109 | 74 | zig build -Dpreset=dev cli |
110 | 75 | ./zig-out/bin/gengo script.gengo |
111 | 76 | ``` |
112 | 77 |
|
113 | | -**WASI runtime** (for browser/sandboxed embedding): |
| 78 | +Build the WASI runtime: |
114 | 79 |
|
115 | 80 | ```bash |
116 | 81 | zig build -Dpreset=dev wasi |
117 | | -wasmtime --dir . ./zig-out/lib/gengo-test.wasm -- script.gengo |
| 82 | +wasmtime --dir . ./build/gengo-test.wasm -- script.gengo |
118 | 83 | ``` |
119 | 84 |
|
120 | | -**Conformance tests:** |
| 85 | +Run tests: |
121 | 86 |
|
122 | 87 | ```bash |
123 | 88 | zig build -Dpreset=dev test |
124 | 89 | ``` |
125 | 90 |
|
126 | | -**Benchmarks:** |
| 91 | +Run benchmarks: |
127 | 92 |
|
128 | 93 | ```bash |
129 | 94 | zig build -Dpreset=dev bench |
130 | 95 | ``` |
131 | 96 |
|
132 | | -## Build Presets |
| 97 | +## Embedding |
133 | 98 |
|
134 | | -| Preset | Use | |
135 | | -|---|---| |
136 | | -| `dev` | Default — generous limits, debug-friendly | |
137 | | -| `tiny` | Minimal heap and stack for constrained embedding | |
138 | | -| `stress` | Tight limits to catch edge cases in tests | |
| 99 | +The embedding API lives in `src/runtime/api.zig`. |
139 | 100 |
|
140 | | -Pass with `-Dpreset=<name>` to any build step. |
| 101 | +It supports: |
141 | 102 |
|
142 | | -## Toolchain |
| 103 | +- running a script and calling exported globals |
| 104 | +- instruction budgets |
| 105 | +- relative imports with a root path |
| 106 | +- in-memory module tables |
| 107 | +- host-provided source callbacks |
143 | 108 |
|
144 | | -- Zig `0.16.0` |
145 | | -- wasmtime (any recent preview1-compatible release) — only needed for WASM target |
| 109 | +See [docs/embedding.md](docs/embedding.md). |
146 | 110 |
|
147 | | -## Repo Layout |
| 111 | +## Build Presets |
148 | 112 |
|
149 | | -``` |
150 | | -src/ |
151 | | - lang/ lexer, compiler, bytecode, VM |
152 | | - runtime/ heap, GC, host ABI, embedding API |
153 | | -examples/ |
154 | | - spec/ conformance pass-cases (+ .out expected output) |
155 | | - spec/fail/ conformance fail-cases (+ .err expected error) |
156 | | - bench/ benchmark scripts |
157 | | -docs/ language reference, embedding guide, changelog |
158 | | -playground/ browser playground (GitHub Pages) |
159 | | -``` |
| 113 | +- `dev`: default development preset |
| 114 | +- `tiny`: tighter limits for constrained embedding |
| 115 | +- `stress`: tighter limits for edge-case testing |
160 | 116 |
|
161 | | -## Embedding |
| 117 | +Use `-Dpreset=<name>` with build commands. |
| 118 | + |
| 119 | +## Repo Layout |
162 | 120 |
|
163 | | -Gengo is designed to be embedded in Zig hosts. See `docs/embedding.md` and `src/runtime/api.zig`. |
| 121 | +- `src/lang/`: lexer, compiler, bytecode, VM |
| 122 | +- `src/runtime/`: heap, GC, runtime, embedding API |
| 123 | +- `examples/spec/`: conformance cases |
| 124 | +- `examples/bench/`: benchmark programs |
| 125 | +- `docs/`: language, stdlib, embedding, changelog |
| 126 | +- `playground/`: browser playground |
164 | 127 |
|
165 | | -Runtime limits (heap, stack, call depth, etc.) are configured via preset files in `src/runtime/`. |
| 128 | +## Toolchain |
| 129 | + |
| 130 | +- Zig `0.16.0` |
| 131 | +- wasmtime for WASI testing and execution |
166 | 132 |
|
167 | | -## Notes |
| 133 | +## Docs |
168 | 134 |
|
169 | | -- File extension: `.gengo` |
170 | | -- Only `import("std")` is currently supported; file imports are planned |
171 | | -- A single managed heap block is capped at 32 KiB regardless of total heap size |
172 | | -- Language surface is still evolving; breaking changes are expected |
| 135 | +- [docs/language.md](docs/language.md) |
| 136 | +- [docs/stdlib.md](docs/stdlib.md) |
| 137 | +- [docs/embedding.md](docs/embedding.md) |
| 138 | +- [docs/changelog.md](docs/changelog.md) |
0 commit comments