aboutsummaryrefslogtreecommitdiff
path: root/PROPOSALS.md
diff options
context:
space:
mode:
Diffstat (limited to 'PROPOSALS.md')
-rw-r--r--PROPOSALS.md132
1 files changed, 132 insertions, 0 deletions
diff --git a/PROPOSALS.md b/PROPOSALS.md
new file mode 100644
index 0000000..a31e5c0
--- /dev/null
+++ b/PROPOSALS.md
@@ -0,0 +1,132 @@
+# proposals
+
+features i would like to see in zc, written after working through the compiler, the
+analyzer, the language server and the libraries. each one says what it is, why it
+would help, how it would sit on the machinery that already exists, and what it
+costs. they are ordered roughly by how much day-to-day pain they would remove. none
+of them is implemented.
+
+## 1. source positions at run time
+
+a program that reads far out of range today can exit with status 0 and no
+message. `int f(word p) { return p[100000000]; }` called with 0 does exactly that.
+when something does go wrong at run time there is no line number, no function
+name and no backtrace, so the only tools are print statements and `memdebug`.
+
+the compiler already knows the position of every token (`tpack`), and check mode
+already turns positions into diagnostics. the proposal is a line table emitted
+next to each function: pairs of (code offset, packed position), written into a
+side section of the image the way `docs on;` already writes documentation. a
+fault, a failed `rt.abort`, or a new `rt.trace()` would walk the return stack,
+map each return address through the table and print `file:line in function` for
+every frame. the interpreter, the microcore and the javascript vm share the image,
+so they would all get it from one change to the runtime. the jit and llvm
+backends would need their own map from native addresses back to vm code; i
+haven't checked how much of that they keep today.
+
+cost: image size (a few words per statement; it could be gated by a
+`lines on;` switch like `docs on;`) and a little work in the fault path. the
+compiler's own genome could ship without it.
+
+## 2. checked indexing in debug builds
+
+every heap block has a header that knows its payload size, and growable arrays
+know their length. a `bounds on;` switch, in the spirit of `memdebug on;`, could
+make `p[i]` on a pointer into the heap check `0 <= i < payload` and stop with the
+line from proposal 1. typed arrays (`T[n]`, `T[?]`) would check against their
+declared or current length. the analyzer already knows when an index is a
+constant inside a fixed array, so those checks can be dropped at compile time,
+and o3 regions would skip them as they skip other metadata.
+
+cost: one compare and branch per index when on, nothing when off. the
+enhancement system could make it a single opcode on the cores that matter.
+
+## 3. a `drop` method for resources that are not memory
+
+the ownership analyzer already decides exactly where an owned value dies and
+emits `rt.free` or `rt.recycle` there. file descriptors, actor processes and
+arena handles die at the same points but nothing closes them. a struct could
+declare `fn drop() { ... }`, and the compiler would call it wherever it would
+have freed an owned value of that type, before the memory goes back. `actor.zc`
+(which closes processes by hand) and `std.Host` would be the first users.
+
+this keeps the language free of a `defer` keyword: the place where cleanup runs
+is already computed, and the analyzer would refuse the same leaks it refuses for
+memory. the open question is moves into unchecked code, which would have to
+either forbid owned values with a `drop` or document that unchecked code takes
+responsibility.
+
+## 4. returning a borrow tied to an argument
+
+`find` in a string or `first` in a list wants to hand back a pointer into its
+argument. today that result is either a plain word (unchecked) or an owned copy.
+the lend rules added to the analyzer already track that a borrow came from a
+particular owner inside one function. extending that to signatures,
+
+```
+char*.from(s) skip_space(char*.m s);
+```
+
+would let the caller's analyzer treat the result as a borrow of whatever was
+passed as `s`, so freeing or moving `s` while the result is live becomes an
+error at the call site. the callee is checked to return only borrows of `s`.
+
+cost: a new tag form in the type grammar and one more case in `argcheck` and
+`retcheck`. no run-time cost.
+
+## 5. test blocks
+
+```
+test "itoa handles negatives" {
+ char[24] buf;
+ expect(std.Text.eq(std.Text.itoa(-5, buf), "-5"));
+}
+```
+
+`zy --test file.zc` would compile the file with its `test` blocks, run each one,
+and print a line per test with the position of the first failed `expect`.
+without `--test` the blocks are skipped at parse time and cost nothing. the
+actor library already shows how a library can add a keyword with
+`run.syntax` and `run.statement_hook`, so this could live in a `test` module
+rather than in the compiler. the test suite's many small programs with expected
+output strings in `tests/test.sh` could gradually move into the files they test.
+
+## 6. formatted output as a library syntax
+
+printing a value with a label today is a chain of `rt.put_s`, `rt.put_n` and
+`rt.put_c(10)`. a parse word in std could expand
+
+```
+std.say("x = {x}, name = {name}\n");
+```
+
+at compile time into that chain, using the type of each name to pick
+`put_n`, `put_s`, `put_bs` or the float printer. nothing happens at run time
+that the hand-written chain doesn't already do, and the compile-time code is
+the kind `lang.zc` already demonstrates.
+
+## 7. a parity mode between zc0 and the self-hosted compiler
+
+while testing the module scanner i found that the self-hosted compiler bound
+`||` tighter than `&&`, while zc0 and the readme follow c. the bootstrap
+fixpoint can't see this kind of bug: g1 and g2 are both compiled by the same
+wrong table, so they match. a `make parity` target could compile every program
+in the zc0 subset (the `tests/programs` that zc0 accepts, and the genome sources
+themselves) with both compilers and run both images, comparing output. a
+mismatch means the two front ends disagree about the language. the corpus is
+already there; only the runner is missing.
+
+## 8. untyped const at the top level and in structs
+
+`const k = 5;` now works at the top level and inside functions. struct-level
+constants (`struct Text { const MAX = 24; }`, reached as `std.Text.MAX`) would
+let libraries keep their limits next to the functions that use them, instead of
+in the module's global namespace.
+
+## 9. a typo pass over field and method names
+
+the missing-name diagnostics now suggest the closest function or module export.
+field access (`p.lenght`) and static calls (`std.Text.itao`) still only say that
+the name is wrong. the same edit-distance search over the struct's fields and
+methods would give "did you mean `length`?", and the language server's
+change-to quick fix would apply to it unchanged.