aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 17:12:04 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 17:12:04 +0330
commitee6925c12b3275c05f5943ef7785fa1d958de499 (patch)
tree7f7a1f55e4f91ca0f30a85a868d1c20631e5f344
parentwip: bench wraps bench.sh (diff)
parentmerge work/spec: appendix b round, zc0umd rewrite (diff)
Merge branch 'main' into work/bench
-rw-r--r--docs/SPEC.md63
-rw-r--r--src/compiler/builtins.zc12
-rw-r--r--src/compiler/decl.zc26
-rw-r--r--src/compiler/expr.zc25
-rw-r--r--src/compiler/lex.zc8
-rw-r--r--src/runtime/rt.zc1
-rw-r--r--tests/conformance/1-source/comment_block_unclosed.zc3
-rw-r--r--tests/conformance/1-source/ident_tags_global_glued_star.zc8
-rw-r--r--tests/conformance/2-types/bool_normalises.zc7
-rw-r--r--tests/conformance/2-types/enum_names_own_member.zc8
-rw-r--r--tests/conformance/2-types/typecheck_number_kinds_convert.zc3
-rw-r--r--tests/conformance/3-expressions/cast_as_then_multiply_parenthesised.zc5
-rw-r--r--tests/conformance/3-expressions/closure_24_parameters.zc6
-rw-r--r--tests/conformance/3-expressions/ternary_int_float_variables.zc8
-rw-r--r--tests/conformance/5-declarations/fn_prototype_unnamed_pointer_params.zc8
-rw-r--r--tests/conformance/5-declarations/global_float_init_f32_and_list.zc8
-rw-r--r--tests/conformance/5-declarations/global_float_int_init.zc9
-rw-r--r--tests/conformance/5-declarations/include_in_function_b9.zc2
-rw-r--r--tests/conformance/9-appendix-b/b2_11_unterminated_comment.zc2
-rw-r--r--tests/conformance/9-appendix-b/b2_12_as_int_star.zc6
-rw-r--r--tests/conformance/9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc4
-rw-r--r--tests/conformance/known.txt13
-rw-r--r--tests/langerrors/closure_params_25.zc3
-rw-r--r--tests/langerrors/fn_defined_twice.zc4
-rw-r--r--tests/langerrors/include_in_function.zc2
-rw-r--r--tests/langerrors/param_unnamed_with_body.zc3
-rw-r--r--tests/langerrors/proto_after_def_mismatch.zc4
-rw-r--r--tests/langerrors/unterminated_comment_docs.zc4
-rw-r--r--tests/programs/zc0umd.zc19
29 files changed, 200 insertions, 74 deletions
diff --git a/docs/SPEC.md b/docs/SPEC.md
index 6df55f6..dd04cde 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -24,7 +24,7 @@ this is the reference for the zc language. it is written from `src/compiler/comp
1.2.1 `//` starts a comment that runs to the end of the line.
-1.2.2 `/* ... */` is a block comment. block comments do not nest. a block comment that is never closed runs to the end of the unit and is not an error.
+1.2.2 `/* ... */` is a block comment. block comments do not nest. a block comment that is never closed is error: unterminated comment.
1.2.3 comments are not tokens, but `docs on;` (5.14) and `hints` (4.12) read them: `//` lines directly above a declaration document it, and `//:` lines are implementation notes that document nothing. an inline block comment right before a numeric literal marks the literal as intentional for the magic-number hint. neither changes the generated code.
@@ -55,7 +55,7 @@ a name that looks like `x.y` where `x` is not a known module is the three tokens
| `break` `continue` `return` | S | | | 4.8 |
| `defer` | S | | | 4.14 |
| `enum` | S | T | | 5.8 |
-| `include` | S | T | | 5.12 (a statement only by accident, 5.12.7) |
+| `include` | S | T | | 5.12 (in a function body it is an error, 5.12.7) |
| `embed` | S | T | | 5.11 |
| `memdebug` | S | T | | 4.11 |
| `hints` | S | T | | 4.12 |
@@ -132,7 +132,7 @@ zc is untyped by default. an untyped name has type `word`, which is compatible w
| `word` | 1 | the untyped type, compatible with everything |
| `int` | 1 | 64-bit signed two's complement |
| `char` | 1 | a full word, treated as a number; `char[n]` takes n words |
-| `bool` | 1 | a number; values are not normalised to 0 or 1 on store (appendix b) |
+| `bool` | 1 | 0 or 1: a value stored into a bool (initializer, assignment, argument, return) becomes 1 when it is nonzero, a float when it is not 0.0 |
| `void` | 0 | function result only; a variable cannot be void |
| `float`, `f64` | 1 | ieee binary64 |
| `f32` | 1 | single precision: a variable holds the value already rounded to float; arrays and pointers of `f32` are packed two per word |
@@ -227,7 +227,7 @@ a struct cannot contain itself by value (error: a struct can't contain itself by
### 2.8 enums
-2.8.1 plain enum. `enum color { RED, GREEN = 5, BLUE };` declares the global constants `RED` 0, `GREEN` 5, `BLUE` 6, also reachable as `color.RED` in expressions and case labels. a member without a value is the previous value plus one, starting at 0. the name is optional (`enum { A, B };`). values are constant expressions (3.14). a member's value cannot name an earlier member of the same enum (the members are defined when the closing brace is reached; error: expected a constant). under `typecheck on;` the enum is a distinct type (2.9).
+2.8.1 plain enum. `enum color { RED, GREEN = 5, BLUE };` declares the global constants `RED` 0, `GREEN` 5, `BLUE` 6, also reachable as `color.RED` in expressions and case labels. a member without a value is the previous value plus one, starting at 0. the name is optional (`enum { A, B };`). values are constant expressions (3.14). a member is defined as soon as it is declared, so a later member's value can name it: `enum { HI = 16, LO = HI + 1 };` gives `LO` 17. under `typecheck on;` the enum is a distinct type (2.9).
2.8.2 tagged enum. when any member has a `{ fields }` block the enum is tagged: `enum shape { circle { int r; }, rect { int w; int h; }, dot };`. rules:
- it needs a name; payload fields are `int`, `float` or `let` (a word), at most 8 per member; at most 63 members; members take no explicit values.
@@ -361,7 +361,7 @@ a = b = 7 // assigns both, value 7
```
`&&` and `||` are lower than `|`. this matches c.
-3.2.3 `as` is a postfix operator: it applies to the operand to its left and binds tighter than every binary operator, so `a + b as T` is `a + (b as T)`. a type after `as` takes following stars: `x as int * y` parses `int*` then fails. parenthesise.
+3.2.3 `as` is a postfix operator: it applies to the operand to its left and binds tighter than every binary operator, so `a + b as T` is `a + (b as T)`. the type after `as` is read greedily, stars included (`x as char**`), in both compilers: `x as int * y` reads `int*` and then is a syntax error; write `(x as int) * y`.
3.2.4 the condition of `?:` is a `binary` expression (so `a = b ? c : d` is `a = (b ? c : d)`), the middle operand and the last operand are full `expr` (assignments allowed). `a ? b : c = 5` therefore parses as `a ? b : (c = 5)`: with `a` 1 the assignment does not run.
@@ -395,7 +395,7 @@ a = b = 7 // assigns both, value 7
3.5.4 `-x` of a float is computed as `0.0 - x`, so `-0.0` evaluates to `+0.0`. do not depend on the sign of zero.
-3.5.5 `?:` with one float branch and one integer branch is broken in the reference implementation (appendix b). write both branches as floats.
+3.5.5 `?:` with one float branch and one integer branch converts the integer branch to float: `1 ? 1 : 2.5` is 1.0, `1 ? 2.5 : 1` is 2.5.
### 3.6 evaluation order and short circuit
@@ -490,7 +490,7 @@ pn(pop(a)); pn(a.pop()); pn(a.len()); // 4 3 2
a[2]; // zy: index 2 out of range (length 2)
```
-3.13.5 the length of a non-array (`len("text")` on a `char*`) is unspecified when `typecheck` is off (it reads word 0) and error: len needs an array under `typecheck on;`. use `rt.len_w` for word strings and `len` for `bstr`.
+3.13.5 `len` on a `char*` is error: len needs an array, not a char* when `typecheck` is off, and error: len needs an array under `typecheck on;`. use `rt.len_w` for word strings and `len` for `bstr`.
### 3.14 constant expressions
@@ -507,7 +507,7 @@ other operators (`/ % ^ < ?:` and so on) are not part of a constant expression:
### 3.15 closures
-3.15.1 `fn(a, b) { ... }` in an expression is a closure. parameters may be typed (`fn(int a)`) and tagged. `fn:T(a) { ... }` declares the result type `T` and coerces and checks returns against it. a closure is an owned heap object (a fresh object, 6.1): bind it to a local, pass it to a `.d` parameter or to a borrower, or return it from a `.u` function.
+3.15.1 `fn(a, b) { ... }` in an expression is a closure. parameters (up to 24) may be typed (`fn(int a)`) and tagged. `fn:T(a) { ... }` declares the result type `T` and coerces and checks returns against it. a closure is an owned heap object (a fresh object, 6.1): bind it to a local, pass it to a `.d` parameter or to a borrower, or return it from a `.u` function.
3.15.2 a closure captures the locals and parameters of the enclosing function by reference (shared cells): later changes are visible both ways, and the cells outlive the creating function.
```
@@ -624,7 +624,7 @@ o3 int sum(fixed int[?] a) { int s = 0; for (int i = 0; i < len(a); i++) s += a[
### 4.11 memdebug
-`memdebug on;` turns on heap header checks and allocation-site tracking from this point in the file (top level) or from this point in the function (statement); the runtime enables the checks before `main` for the top-level form. `memdebug scrub;` additionally fills freed payloads with 57005; `memdebug off;` stops. with checks on, a use after free prints `zy: use after free from line N`, a double free prints `zy: double free from line N` and returns, a stray address prints `zy: bad heap pointer`. `rt.memreport()` prints a line `leak: line N W words` per live recorded object and returns the count. (`memdebug scrub;` is rejected by the reference implementation, appendix b.)
+`memdebug on;` turns on heap header checks and allocation-site tracking from this point in the file (top level) or from this point in the function (statement); the runtime enables the checks before `main` for the top-level form. `memdebug scrub;` additionally fills freed payloads with 57005; `memdebug off;` stops. with checks on, a use after free prints `zy: use after free from line N`, a double free prints `zy: double free from line N` and returns, a stray address prints `zy: bad heap pointer`. `rt.memreport()` prints a line `leak: line N W words` per live recorded object and returns the count.
### 4.12 hints
@@ -683,7 +683,7 @@ node*.u make() { ... } // owned result (.u)
fn f(a, b) { ... } // untyped; types are inferred
fn f(int a, b) { ... } // parameters may mix
T first<T>(T* p) { ... } // generic
-int proto(int n); // prototype (parameter names are required)
+int proto(int n); // prototype (int proto(int); also works: a prototype's parameter names are optional)
```
a bare `name(params) { ... }` with no `fn` and no type is not a function declaration (error: expected a declaration).
@@ -695,7 +695,7 @@ a bare `name(params) { ... }` with no `fn` and no type is not a function declara
5.2.5 `main`. after the unit compiles without errors, the runtime calls `main` with no arguments (declared parameters read unspecified values; `main(a, b)` prints 0 0 in the reference implementation). its return value is ignored and is not the exit status; `main` may be declared `int`, `void` or untyped, in a run-only image as in `zy`. 8.1.
-5.2.6 a function is defined once. redefining a name that already has a body is, per the rule, error: f: is defined twice; the reference implementation does not detect an ordinary redefinition (appendix b). a prototype must agree with the definition: error: f: doesn't match its earlier declaration.
+5.2.6 a function is defined once. redefining a name that already has a body in the same module is error: f: is defined twice. a prototype may come before or after the definition and must agree with it: error: f: doesn't match its earlier declaration. a prototype after the definition keeps the definition.
5.2.7 `immediate fn` defines a parse word (7.2). `unchecked fn` and `o3 fn` are 4.9 and 4.10.
@@ -703,7 +703,7 @@ a bare `name(params) { ... }` with no `fn` and no type is not a function declara
5.3.1 `T a = e, b, c[10];`, `let g = 1, buf[10];`, `int[10] buf;`, `point origin;`. storage is emitted in place, zero initialised.
-5.3.2 an initializer is: a constant expression (3.14), a word string, a byte string, or a brace list or compound literal for an array. anything else is an error: for `int a = f();` error: expected ';', found '('. a float literal initializer is rejected (error: expected a constant) in the reference implementation (appendix b); assign floats in code. a name of another global in the initializer evaluates to its address, not its value: `int a = 5; int b = a + 1;` gives `b` the value `&a + 1`.
+5.3.2 an initializer is: a constant expression (3.14), a word string, a byte string, or a brace list or compound literal for an array. anything else is an error: for `int a = f();` error: expected ';', found '('. a `float`, `f64` or `f32` global takes a float literal initializer (`float g = 1.5;`); an integer constant initializer converts to float (`float g = 2;` stores 2.0), as it does for a local. a name of another global in the initializer evaluates to its address, not its value: `int a = 5; int b = a + 1;` gives `b` the value `&a + 1`.
5.3.3 a global cannot be tagged (error: tags go on locals and parameters, not globals), cannot be `void`, and an array global needs a length or an initializer.
@@ -782,7 +782,7 @@ rt.put_n(util.twice(1)); // error: module access requires `include util;`
5.12.6 in a namespaced file, a declaration `f` is `ns.f`. an unqualified name is looked up first in the file's namespace, then globally, and `rt` is not searched for bare names (3.11.5).
-5.12.7 `include` is parsed at the top level; written inside a function body it mis-compiles (appendix b).
+5.12.7 `include` is parsed at the top level; written inside a function body it is error: include is top level only.
5.12.8 when a library is included more than once, it is compiled once.
@@ -1086,11 +1086,11 @@ not in the zc0 subset: `struct`, methods, `enum`, `type`, generics, `do`/`while`
## appendix b: disagreements and bugs
-each item gives a minimal program, the expected behavior (per this spec) and the actual behavior of the reference implementation on `build/bin/zy` (unless it names zc0). none are fixed here.
+each item gives a minimal program, the expected behavior (per this spec) and the actual behavior of the reference implementation on `build/bin/zy` (unless it names zc0). items marked fixed are kept for their numbers; the rest are open.
### b.1 disagreements between the prose docs and the code
-1. README ("memory debug"): "`memdebug scrub;` also fills a freed payload with 57005". the compiler compares the word after `memdebug` with the interned name `rt.scrub` instead of `scrub`, so the statement is rejected: `memdebug scrub;` => `zc: line N: memdebug on, memdebug scrub or memdebug off`. (also b.2.1)
+1. fixed: README ("memory debug") says `memdebug scrub;` also fills a freed payload with 57005; the compiler used to compare the word with `rt.scrub` and reject it. it is accepted now (also b.2.1).
2. README ("zc, the language") says a cast type "has to be a simple type ... `int` or `*node`". the prefix pointer spelling `*node` is not a type (README's own "pointer types" section says so, and `(*int)y` => `int: is a type, not a value`). the postfix form `(node*)x` is the one that works.
3. README says "operators follow C precedence". this holds for expressions. constant expressions (array lengths, enum values, `const`, `case` labels) have no precedence: `+ - * | & << >>` only, left to right (3.14). `const X = 1 + 2 * 3;` is 9, and `/ % ^` are rejected (`const Z = 6 / 2;` => `expected ';', found '/'`). README does not mention either fact.
4. README lists `switch` forms `case 1, 2:` and `default:`. the catch-all `case _:` exists in the compiler and is not documented.
@@ -1104,22 +1104,29 @@ each item gives a minimal program, the expected behavior (per this spec) and the
### b.2 implementation bugs and limitations
-1. `memdebug scrub;` is rejected (b.1.1). expected: poison freed payloads with 57005.
-2. enum members cannot name earlier members of the same enum: `enum { HI = 16, LO = HI + 1 };` => `expected a constant` (the members are defined after the closing brace). members of an earlier enum and constants work.
-3. a function can be silently redefined. `int f() { return 1; } int g() { return f() + 10; } int f() { return 2; }`, then `g()` gives 11 and a later call of `f()` gives 2. expected: error `f: is defined twice` (the message exists in the compiler and is unreachable for an ordinary second definition).
-4. a prototype after the definition hides it. `int f() { return 1; } int f();` then `f()` => `zc: line 0: f is used but never defined`. expected: the definition stays.
-5. `?:` with one float branch and one integer branch converts wrongly. `pf(1 ? 1 : 2.5)` prints `0.000000` (the integer 1's bits read as a float); `pf(1 ? 2.5 : 1)` prints `4612811918334230528.000000` (the taken float converted again as if an integer); `pf(0 ? 2.5 : 1)` prints 1.000000 and `pf(0 ? 1 : 2.5)` 2.500000 (these two are right). expected 1.0, 2.5, 1.0, 2.5.
-6. `bool` does not normalise: `bool b = 5; rt.put_n(b); rt.put_n(b == 1); rt.put_n(!b);` prints 5, 0, 0. expected 1, 1, 0 if `bool` is a 0/1 type; it behaves as a plain integer.
-7. a float literal as a global initializer: `float g = 1.5;` at the top level => `expected a constant`. expected: allowed (a float local initializer works).
-8. a global initializer that names another global uses the other global's address, not its value: `int a = 5; int b = a + 1;` gives `b` the value `&a + 1` (1 when `a` is at address 0). expected: the value of `a`, or an error.
-9. `include std;` written inside a function body: `zc: line N: return outside a function`. expected: an error saying include is top level only.
+1. fixed: `memdebug scrub;` is accepted and poisons freed payloads with 57005 (b.1.1).
+2. fixed (2.8.1): enum members could not name earlier members of the same enum: `enum { HI = 16, LO = HI + 1 };` => `expected a constant` (the members are defined after the closing brace). members of an earlier enum and constants work.
+3. fixed (now error: f: is defined twice): a function could be silently redefined. `int f() { return 1; } int g() { return f() + 10; } int f() { return 2; }`, then `g()` gives 11 and a later call of `f()` gives 2. expected: error `f: is defined twice` (the message exists in the compiler and is unreachable for an ordinary second definition).
+4. fixed (the definition stays): a prototype after the definition hid it. `int f() { return 1; } int f();` then `f()` => `zc: line 0: f is used but never defined`. expected: the definition stays.
+5. fixed (3.5.5): `?:` with one float branch and one integer branch converted wrongly. `pf(1 ? 1 : 2.5)` prints `0.000000` (the integer 1's bits read as a float); `pf(1 ? 2.5 : 1)` prints `4612811918334230528.000000` (the taken float converted again as if an integer); `pf(0 ? 2.5 : 1)` prints 1.000000 and `pf(0 ? 1 : 2.5)` 2.500000 (these two are right). expected 1.0, 2.5, 1.0, 2.5.
+6. fixed (2.1): `bool` did not normalise: `bool b = 5; rt.put_n(b); rt.put_n(b == 1); rt.put_n(!b);` prints 5, 0, 0. expected 1, 1, 0 if `bool` is a 0/1 type; it behaves as a plain integer.
+7. fixed (5.3.2): a float literal as a global initializer (`float g = 1.5;`) was rejected.
+8. not a bug: a global initializer that names another global uses the other global's address (5.3.2, 3.14.2): `int a = 5; int b = a + 1;` gives `b` the value `&a + 1`.
+9. fixed (5.12.7): `include std;` inside a function body gave `return outside a function`; it is now error: include is top level only.
10. a syntax error hides errors in the statements skipped by recovery: `int a = ; int b = 3 +;` reports only the first error (8.2). by design, but easy to miss.
-11. an unterminated `/* ...` comment is accepted silently (1.2.2); an unterminated string literal is only reported as a later syntax error.
-12. `x as int * y` parses `int*` as the type (3.2.3).
-13. prototypes need parameter names: `int f(int);` => `expected a name, found ')'`.
-14. `len(s)` on a `char*` returns word 0 of the string when `typecheck` is off.
+11. fixed (1.2.2): an unterminated `/* ...` comment was accepted silently; it is error: unterminated comment. (an unterminated string literal is still only reported as a later syntax error.)
+12. settled (3.2.3): `x as int * y` parses `int*` as the type. this is the rule (the type is read greedily, as zc0 does too), so the program is a syntax error.
+13. fixed (5.2.1): a prototype's parameters may be unnamed (`int f(int, int*);`). a lone type name counts as a type only in a prototype; with a body it is still the parameter's name, and an unnamed typed parameter there is `a parameter needs a name`.
+14. fixed (3.13.5): `len(s)` on a `char*` used to return word 0 of the string when `typecheck` was off; it is now a compile error.
15. duplicate `case` labels are not diagnosed (4.7.2).
16. a local `const` cannot be used in a constant expression (b.1.7).
+17. fixed: a bad float or character literal in a function's first statement was not reported (1.4).
+18. fixed: `u*d` with a visible variable `u` (local or global) was read as a recast; it is a product (1.6.3).
+19. fixed: a cast such as `(int*)s` of a `bstr` kept the byte-string flag, so `p[0]` read a byte.
+20. fixed: functions took 23 parameters and closures 19; both take 24 (5.2.2, 3.15.1).
+21. fixed: `return e;` in a `void` function with `typecheck` off returned `e`; the value is dropped and the function yields 0 (4.8.3).
+22. fixed: `case member():` on a tagged member with fields was rejected; it matches by tag (4.7.3).
+23. fixed: `INT_MIN / -1` gave 0 and `INT_MIN % -1` gave INT_MIN in programs compiled by zc0 (software division, `rt.zdiv`); they give INT_MIN and 0 (3.4.3).
## appendix c: how this spec was checked
diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc
index 9a89052..cbc380e 100644
--- a/src/compiler/builtins.zc
+++ b/src/compiler/builtins.zc
@@ -71,8 +71,10 @@ int ptagged(word t) {
// enum name { A, B = 5 } is a plain enum: each member is a constant, plain (A)
// and scoped (name.A). a member with a block of fields makes it a tagged enum
// (see enum_tagged): enum name { circle { int r; }, dot }.
+// the first member of the enum being read, so a member's value can name the members before it; -1 outside
+int enum_open = 0 - 1;
int kw_enum() {
- char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft; char* fname;
+ char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft, rv = 0; char* fname;
stmtonly();
next();
if (tk == T_ID) { en = tv; next(); }
@@ -97,7 +99,9 @@ int kw_enum() {
expect(s_semi);
}
expect(s_rb);
- } else if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; }
+ } else { enum_open = first; if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; } enum_open = 0 - 1; }
+ if (pm_exp[gi]) rv = pm_val[gi]; else if (!pm_nf[gi]) pm_val[gi] = rv; // the running value, read by cprim
+ rv++;
cnt++;
if (!accept(s_comma)) break;
if (is_op(s_rb)) break;
@@ -312,6 +316,7 @@ int inc_compile(char* buf, char* name, char*.d dir) {
int kw_include() {
int pp, sv; char* path; char* buf;
stmtonly();
+ if (infn) { die("include is top level only"); return 0; } // appendix b.2.9
next();
if (tk == T_ID) {
while (1) {
@@ -701,7 +706,8 @@ int array_len(word t) {
return 0;
}
if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); }
- else { tcneed(t[0] == TY_DYN || t == tword, "len needs an array", t); if (argloc >= 0) { op(O_DROP); vaddr(o3cache[argloc] + 1); op(O_LD); } else if (o3on && t[0] == TY_DYN) op(O_LD); else callname(s_dlen); }
+ else { if (!tc_on && t[0] == TY_PTR && t[4][0] == TY_CHAR) serr("len needs an array, not a char* (use rt.len_w, or a bstr)"); // b.2.14: it would read word 0
+ tcneed(t[0] == TY_DYN || t == tword, "len needs an array", t); if (argloc >= 0) { op(O_DROP); vaddr(o3cache[argloc] + 1); op(O_LD); } else if (o3on && t[0] == TY_DYN) op(O_LD); else callname(s_dlen); }
xty = tint;
xlenloc = -1; xstamp = 0;
if (a >= 0 && t[0] == TY_DYN && t[3]) xlenloc = a; // the whole expression is len of a fixed array
diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc
index 3a02b90..87281ad 100644
--- a/src/compiler/decl.zc
+++ b/src/compiler/decl.zc
@@ -282,12 +282,17 @@ int body() {
// into stype, checked against any earlier prototype or prescan result, and
// where the o3 region for an o3 function is handed to the core)
int fndef(int chk, int rty, int ret, int n) {
- int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk, lt, lend, lfl = tline;
+ int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk, lt, lend, lfl = tline, redef, amb = 0, nonm = 0;
o3nxt = 0;
if (rty || ntp) typed = 1;
if (!dnm) dnm = n;
s = lookup(n);
- if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) {
+ redef = 0; // a function this module already defined: a prototype after it keeps it (b.2.4), a second body is defined twice (b.2.3)
+ if (s >= ubase && skind[s] == K_FN && (sflag[s] & F_DEF) && !(sflag[s] & F_TPL) && !ginst && !ntp) {
+ word qn = n; if (module_name) qn = module_symbol(n);
+ redef = rt.eq_w(sname[s], qn);
+ }
+ if (!redef && (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF))) {
s = addsym(n, K_FN, 0);
sflag[s] = 0;
}
@@ -322,7 +327,9 @@ int fndef(int chk, int rty, int ret, int n) {
} else if (tv == s_d) t = 2; else die("a parameter tag is .m or .d");
next();
}
- pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name();
+ pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff);
+ if (!pk && !t && tk == T_ID && istypename(tv)) amb = amb | (1 << np); // int f(int): a name, or a type if this is a prototype
+ if (pk && tk == T_OP && (tv == s_comma || tv == s_rp)) { pn[np] = 0; nonm = 1; } else pn[np] = name(); // int f(int*, int): a prototype's parameters may be unnamed
if (o3f && pty[np] == tword) serr("o3 parameters need a type");
if (np >= 12 && t) die("only the first 12 parameters can be tagged");
pt[np] = t;
@@ -333,6 +340,13 @@ int fndef(int chk, int rty, int ret, int n) {
}
}
expect(s_rp);
+ if (is_op(s_semi)) { // a prototype: a lone type name is an unnamed parameter of that type (b.2.13)
+ i = 0;
+ while (i < np) {
+ if (((amb >> i) & 1) && pn[i]) { t = lookup(pn[i]); if (t >= 0 && skind[t] == K_TYPE) { pty[i] = sval[t]; typed = 1; } }
+ i++;
+ }
+ } else if (nonm) serr("a parameter needs a name");
sig = ret + np + np + tags * 64;
if ((sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) {
if (ssig[s] != sig) serrn(n, "doesn't match its earlier declaration");
@@ -475,8 +489,12 @@ int gdecl2(int t, int tg, int n) {
if (arraytype[0] != TY_ARR || !is_op(s_lb)) serr("a global compound literal needs an array initializer");
else { array_literal(arraytype); vv = xcval; *(a + hdelta) = vv; if (!t) stype[s] = decay(xty); }
}
+ else if (t && isfl(t) && tk == T_FLT) { // float g = 1.5; (appendix b.2.7)
+ vv = tv; next(); if (t == tf32) vv = rt.zs2f(rt.zf2s(vv)); *(a + hdelta) = vv; // an f32 scalar is a rounded double
+ }
else {
- vv = cnorm(t, cexpr()); *(a + hdelta) = vv;
+ vv = cnorm(t, cexpr()); if (t && isfl(t)) { vv = vf_i2f(vv); if (t == tf32) vv = rt.zs2f(rt.zf2s(vv)); } // float g = 2; is 2.0
+ *(a + hdelta) = vv;
if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; }
}
} else if (gok) gok = 0; // no initializer: nothing says what it is
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
index 4c77ceb..e449508 100644
--- a/src/compiler/expr.zc
+++ b/src/compiler/expr.zc
@@ -647,7 +647,7 @@ word closure_layout(int closure_index, int count) {
// variable in. ctx_push / ctx_pop save and restore the enclosing function's
// compiler state around the body.
int closure() {
- int np = 0; char*[20] pn; word[20] pty; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag, lt, lfl = tline; word[20] ptags;
+ int np = 0; char*[24] pn; word[24] pty; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag, lt, lfl = tline; word[24] ptags;
word expected = closure_expected; closure_expected = 0;
reserve_closures(nclo + 1);
if (o3on) serr("closures can't be made inside an o3 region");
@@ -668,8 +668,8 @@ int closure() {
if (expected && expected[0] == TY_FN && !tag && (isptr(pty[np]) || pty[np][0] == TY_DYN || pty[np][0] == TY_FN)) ptags[np] = 1;
pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name();
np++;
- if (np >= 20) { die("too many parameters"); break; }
if (!accept(s_comma)) break;
+ if (np >= 24) { die("too many parameters"); break; } // 24, like a function
}
}
expect(s_rp);
@@ -1561,12 +1561,17 @@ int postfix_tail(int c) {
}
return c;
}
+// a global variable or array named u, m or d: then u*x is a multiplication (spec 1.6.3)
+int tag_is_global(int n) {
+ int s = lookup(n);
+ return s >= 0 && (skind[s] == K_GVAR || skind[s] == K_GARR);
+}
// prefix operators: the u*x / d*x / m*x recasts (the star has to touch the
// tag), casts like (int)x and (node*)x, dereference, address-of, prefix ++ and
// --, and any unary operator a program added (- ! ~ built in).
int unary() {
int c, un, t, place, constant, value; word storage, dependency;
- if (tk == T_ID && (tv == s_u || tv == s_m || tv == s_d) && findloc(tv) < 0) { // u*x, d*x, m*x: recast only when * is glued and the tag is not a local's name
+ if (tk == T_ID && (tv == s_u || tv == s_m || tv == s_d) && findloc(tv) < 0 && !tag_is_global(tv)) { // u*x, d*x, m*x: recast only when * is glued and the tag is not a local's name
peek();
if (pk_tk == T_OP && pk_tv == s_star && pq_gl[0]) {
t = S_M; if (tv == s_u) t = S_U; if (tv == s_d) t = S_D;
@@ -1777,7 +1782,7 @@ int storekeep(int t) {
// restored for the second side) and then joined, so a variable can't be given
// away on one side only. both sides are fresh objects, or neither is.
int ternary(int c) {
- int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb; word temp_a, temp_b, mask_a, mask_b;
+ int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb, conv, skip, back; word temp_a, temp_b, mask_a, mask_b;
rval(); plainuse(c);
ftest();
next();
@@ -1785,6 +1790,7 @@ int ternary(int c) {
s0 = snap_push();
ca = expr(); rval(); ta = xty; ba = xbor; temp_a = xaf; mask_a = effect_mask(ca);
if (ca != C_FRESH) plainuse(ca);
+ conv = -1; if (isnum(ta) && !isfl(ta) && ta != tword) { conv = hole(); op(O_JMP); back = here; setlit(conv, back); } // a jump to the next word, redirected below if the other side is a float (appendix b.2.5)
livea = live; sa = snap_push();
b = hole(); op(O_JMP);
expect(s_colon);
@@ -1792,11 +1798,14 @@ int ternary(int c) {
snap_restore(s0); live = live0;
cb = expr(); rval(); bb = xbor; temp_b = xaf; mask_b = effect_mask(cb);
if (cb != C_FRESH) plainuse(cb);
+ if (isfl(ta) && !isfl(xty)) { op(OP_I2F); xfloat = 1; xty = tfloat; } // on the second side's path only
join2(sa, livea, b, live, n0, "ends the two sides of ?: in different states");
snap_pop(s0);
- if (isfl(ta) && !isfl(xty)) { op(OP_I2F); xfloat = 1; xty = tfloat; }
+ if (conv >= 0 && isfl(xty)) { // the first side was an integer: convert it out of line
+ skip = hole(); op(O_JMP); setlit(conv, here); op(OP_I2F); lit(back); op(O_JMP); setlit(skip, here);
+ }
if (!compat(ta, xty) && !compat(xty, ta)) tcerr("the two sides of ?:", ta, xty);
- if (ta != tword && !isfl(ta)) xty = ta;
+ if (ta != tword && !isfl(ta) && !(conv >= 0 && isfl(xty))) xty = ta;
else if (isfl(ta) || isfl(xty)) { xty = tfloat; xfloat = 1; }
if_value = 0;
effect_value = mask_a | mask_b;
@@ -1950,6 +1959,10 @@ word cprim() {
}
}
}
+ if (tk == T_ID && enum_open >= 0) { // an earlier member of the enum being read (spec 2.8.1)
+ mc = npm - 1;
+ while (mc > enum_open) { mc--; if (pm_name[mc] == tv && !pm_nf[mc]) { next(); return pm_val[mc]; } }
+ }
die("expected a constant");
return 0;
}
diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc
index 0348cae..712eff8 100644
--- a/src/compiler/lex.zc
+++ b/src/compiler/lex.zc
@@ -298,6 +298,12 @@ int fcoerce(word dst) {
if (dst == tf32 && xty != tf32 && !ispk(xty)) { xenh("zf2s"); xenh("zs2f"); xty = tf32; }
return 0;
}
+ if (dst[0] == TY_BOOL && xty[0] != TY_BOOL) { // bool holds 0 or 1: any nonzero value is 1 (spec 2.1)
+ if (xfloat) { op(OP_FLIT); emit(0); op(OP_FEQ); lit(1); op(18); xfloat = 0; }
+ else { lit(0); op(O_PICK); lit(0); op(O_SWAP); op(O_SUB); op(17); lit(63); op(19); lit(0); op(O_SWAP); op(O_SUB); }
+ if (compat(dst, xty)) xty = tbool; // (otherwise keep the type, so a type check after this still sees it)
+ return 0;
+ }
if (!xfloat) {
if (tbytes(dst) && !samesz(dst, xty)) tnorm(dst);
return 0;
@@ -423,6 +429,7 @@ int docblock(int nl) {
doc_char(c);
if (c == 10) bol = 1;
}
+ if (ch < 0) { tline = line; serr("unterminated comment"); }
while (dn && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 10 || dbuf[dn - 1] == 13)) dn--;
doc_char(10); dlast = cl; docpending = 1;
if (adj) dn = 0;
@@ -484,6 +491,7 @@ int lex1() {
if (docs_on) { docblock(nl); tglue = 0; continue; }
nextch();
while (ch >= 0) { if (ch == '*') { nextch(); if (ch == '/') break; } else nextch(); }
+ if (ch < 0) { tline = line; serr("unterminated comment"); } // a /* with no */ (spec 1.2.2)
nextch(); tglue = 0; continue;
}
tline = line; toff = cpos() - 1; tbuf[0] = '/'; oplex(1); return 0;
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index f1414e4..44f3570 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -377,6 +377,7 @@ int zudiv(int n, int dv) {
int zdiv(int a, int b) {
word s = 0; int q;
if (b == 0) { zrem = a; return 0; }
+ if (b == 0 - 1) { zrem = 0; return 0 - a; } // INT_MIN / -1 is INT_MIN and its remainder 0 (spec 3.4.3); zudiv can't take 2^63
if (a < 0) { a = 0 - a; s = 1; }
if (b < 0) { b = 0 - b; s = s ^ 1; }
q = zudiv(a, b);
diff --git a/tests/conformance/1-source/comment_block_unclosed.zc b/tests/conformance/1-source/comment_block_unclosed.zc
index 4ae34e7..cb410f9 100644
--- a/tests/conformance/1-source/comment_block_unclosed.zc
+++ b/tests/conformance/1-source/comment_block_unclosed.zc
@@ -1,4 +1,5 @@
-// expect: 4/
+// expect error: unterminated comment
+// spec 1.2.2: a block comment that is never closed is an error
include rt;
fn main() { rt.put_n(4); rt.put_c(10); return 0; }
/* never closed: rt.put_n(9);
diff --git a/tests/conformance/1-source/ident_tags_global_glued_star.zc b/tests/conformance/1-source/ident_tags_global_glued_star.zc
new file mode 100644
index 0000000..516f9a3
--- /dev/null
+++ b/tests/conformance/1-source/ident_tags_global_glued_star.zc
@@ -0,0 +1,8 @@
+// expect: 12/10/
+// spec 1.6.3: u*x and m*x are not recasts when u or m is a visible global variable
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+int u = 3;
+int m = 5;
+fn main() { int d = 4; int k = 2; pn(u*d); pn(m*k); return 0; }
+// zc0: skip
diff --git a/tests/conformance/2-types/bool_normalises.zc b/tests/conformance/2-types/bool_normalises.zc
new file mode 100644
index 0000000..c734fc5
--- /dev/null
+++ b/tests/conformance/2-types/bool_normalises.zc
@@ -0,0 +1,7 @@
+// expect: 1/1/0/1/0/1/1/
+// spec 2.1: a value stored in a bool is 0 or 1; any nonzero value becomes 1
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+bool id(bool x) { return x; }
+fn main() { bool b = 5; pn(b); pn(b == 1); pn(!b); b = -7; pn(b); b = 0; pn(b); pn(id(42)); bool f = 0.5; pn(f); return 0; }
+// zc0: skip
diff --git a/tests/conformance/2-types/enum_names_own_member.zc b/tests/conformance/2-types/enum_names_own_member.zc
index 26d4739..1a49106 100644
--- a/tests/conformance/2-types/enum_names_own_member.zc
+++ b/tests/conformance/2-types/enum_names_own_member.zc
@@ -1,4 +1,8 @@
-// expect error: expected a constant
+// expect: 17/3/6/
+// spec 2.8.1: a member is defined as soon as it is declared; a later value can name it
include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
enum e { HI = 16, LO = HI + 1 };
-fn main() { return 0; }
+enum f { A, B, C, D = C + 1, E = D * 2 };
+fn main() { pn(LO); pn(D); pn(E); return 0; }
+// zc0: skip
diff --git a/tests/conformance/2-types/typecheck_number_kinds_convert.zc b/tests/conformance/2-types/typecheck_number_kinds_convert.zc
index 9c3d76e..f41b42d 100644
--- a/tests/conformance/2-types/typecheck_number_kinds_convert.zc
+++ b/tests/conformance/2-types/typecheck_number_kinds_convert.zc
@@ -1,4 +1,5 @@
-// expect: 44/
+// expect: 1/
+// spec 2.1, 2.9: numbers convert freely; the bool normalises 44 to 1
include rt;
typecheck on;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
diff --git a/tests/conformance/3-expressions/cast_as_then_multiply_parenthesised.zc b/tests/conformance/3-expressions/cast_as_then_multiply_parenthesised.zc
new file mode 100644
index 0000000..ab11217
--- /dev/null
+++ b/tests/conformance/3-expressions/cast_as_then_multiply_parenthesised.zc
@@ -0,0 +1,5 @@
+// expect: 6/
+// spec 3.2.3: (x as int) * y multiplies
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() { int x = 2; int y = 3; pn((x as int) * y); return 0; }
diff --git a/tests/conformance/3-expressions/closure_24_parameters.zc b/tests/conformance/3-expressions/closure_24_parameters.zc
new file mode 100644
index 0000000..e12d258
--- /dev/null
+++ b/tests/conformance/3-expressions/closure_24_parameters.zc
@@ -0,0 +1,6 @@
+// expect: 300/
+// spec 3.15.1 and 5.2.2: a closure takes up to 24 parameters, like a function
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() { let f = fn(int a1,int a2,int a3,int a4,int a5,int a6,int a7,int a8,int a9,int a10,int a11,int a12,int a13,int a14,int a15,int a16,int a17,int a18,int a19,int a20,int a21,int a22,int a23,int a24) { return a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12+a13+a14+a15+a16+a17+a18+a19+a20+a21+a22+a23+a24; }; pn(f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)); return 0; }
+// zc0: skip
diff --git a/tests/conformance/3-expressions/ternary_int_float_variables.zc b/tests/conformance/3-expressions/ternary_int_float_variables.zc
new file mode 100644
index 0000000..4bd2d57
--- /dev/null
+++ b/tests/conformance/3-expressions/ternary_int_float_variables.zc
@@ -0,0 +1,8 @@
+// expect: 3.000000/0.500000/3.000000/0.500000/
+// spec 3.5 and appendix b.2.5: ?: with an int side and a float side gives a float on both paths, also in o3
+include rt;
+fn pf(float f) { asm { f @ #1220 } rt.put_c(10); return 0; }
+float pick(int k, int i, float f) { return k ? i : f; }
+o3 float pick3(int k, int i, float f) { return k ? i : f; }
+fn main() { pf(pick(1, 3, 0.5)); pf(pick(0, 3, 0.5)); pf(pick3(1, 3, 0.5)); pf(pick3(0, 3, 0.5)); return 0; }
+// zc0: skip
diff --git a/tests/conformance/5-declarations/fn_prototype_unnamed_pointer_params.zc b/tests/conformance/5-declarations/fn_prototype_unnamed_pointer_params.zc
new file mode 100644
index 0000000..7136bee
--- /dev/null
+++ b/tests/conformance/5-declarations/fn_prototype_unnamed_pointer_params.zc
@@ -0,0 +1,8 @@
+// expect: 7/
+// spec 5.2.1: a prototype's parameters may be unnamed, pointers included
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+int f(int, int*);
+fn main() { int[1] a; a[0] = 5; pn(f(2, a)); return 0; }
+int f(int k, int* p) { return k + p[0]; }
+// zc0: skip
diff --git a/tests/conformance/5-declarations/global_float_init_f32_and_list.zc b/tests/conformance/5-declarations/global_float_init_f32_and_list.zc
new file mode 100644
index 0000000..57df35b
--- /dev/null
+++ b/tests/conformance/5-declarations/global_float_init_f32_and_list.zc
@@ -0,0 +1,8 @@
+// expect: 1/1/
+// spec 5.3.2 and appendix b.2.7: float globals take a float literal initializer
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+float a = 0.25, b = 2.5;
+f32 c = 0.5;
+fn main() { pn(a + b == 2.75); pn(c * 4.0 == 2.0); return 0; }
+// zc0: skip
diff --git a/tests/conformance/5-declarations/global_float_int_init.zc b/tests/conformance/5-declarations/global_float_int_init.zc
new file mode 100644
index 0000000..ac0e187
--- /dev/null
+++ b/tests/conformance/5-declarations/global_float_int_init.zc
@@ -0,0 +1,9 @@
+// expect: 1/1/1/1/1/
+// spec 5.3.2: an integer constant initializing a float or f32 converts to float, for globals and locals
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+float g = 2;
+f32 h = 1 + 2;
+float n = -4;
+fn main() { float l = 2; f32 m = 3; pn(g == 2.0); pn(h == 3.0); pn(n == -4.0); pn(l == 2.0); pn(m == 3.0); return 0; }
+// zc0: skip
diff --git a/tests/conformance/5-declarations/include_in_function_b9.zc b/tests/conformance/5-declarations/include_in_function_b9.zc
index cccaea5..64f170c 100644
--- a/tests/conformance/5-declarations/include_in_function_b9.zc
+++ b/tests/conformance/5-declarations/include_in_function_b9.zc
@@ -1,4 +1,4 @@
-// expect error: include
+// expect error: include is top level only
// spec 5.12.7 and appendix b.2.9: expected an error saying include is top level only
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
diff --git a/tests/conformance/9-appendix-b/b2_11_unterminated_comment.zc b/tests/conformance/9-appendix-b/b2_11_unterminated_comment.zc
index e6e6bbf..ce4b236 100644
--- a/tests/conformance/9-appendix-b/b2_11_unterminated_comment.zc
+++ b/tests/conformance/9-appendix-b/b2_11_unterminated_comment.zc
@@ -1,4 +1,4 @@
-// expect error: unterminated
+// expect error: unterminated comment
// spec appendix b.2.11: an unterminated /* is accepted silently
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
diff --git a/tests/conformance/9-appendix-b/b2_12_as_int_star.zc b/tests/conformance/9-appendix-b/b2_12_as_int_star.zc
index fc12792..da54802 100644
--- a/tests/conformance/9-appendix-b/b2_12_as_int_star.zc
+++ b/tests/conformance/9-appendix-b/b2_12_as_int_star.zc
@@ -1,6 +1,6 @@
-// expect: 6/
-// spec appendix b.2.12: x as int * y should multiply
+// expect error: expected ')', found 'y'
+// spec 3.2.3 and appendix b.2.12: the type after as is read greedily, so x as int * y reads int* and then fails; write (x as int) * y
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
fn main() { int x = 2; int y = 3; pn(x as int * y); return 0; }
-// zc0: skip
+// zc0: error
diff --git a/tests/conformance/9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc b/tests/conformance/9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc
index b0d0d45..0953328 100644
--- a/tests/conformance/9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc
+++ b/tests/conformance/9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc
@@ -1,5 +1,5 @@
-// expect: 3/
-// spec appendix b.2.14: len(char*) with typecheck off returns word 0
+// expect error: len needs an array, not a char*
+// spec 3.13.5 and appendix b.2.14: len on a char* is an error with typecheck off too
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
typecheck off;
diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt
index 54e7141..8eeb0d3 100644
--- a/tests/conformance/known.txt
+++ b/tests/conformance/known.txt
@@ -2,18 +2,7 @@
# class a: self-hosted compiler bug; b: zc0 deviation (appendix a/b); c: spec issue to settle
# appendix b.2.8 (a global named in an initializer is its address) is documented behavior per 5.3.2 and 3.14.2: no entry
4-statements/o3_outside_function_ordinary_block.zc zy c top-level o3 { } is rejected; spec 4.10.2 says it is an ordinary block
-5-declarations/enum_member_names_earlier_b2.zc zy a appendix b.2.2 confirmed: enum members can't name earlier members
-5-declarations/fn_prototype_after_definition_b4.zc zy a appendix b.2.4 confirmed: a prototype after the definition hides it
-5-declarations/fn_prototype_unnamed_params_b13.zc zy a appendix b.2.13 confirmed (message now: doesn't match its earlier declaration)
-5-declarations/fn_redefined_b3.zc zy a appendix b.2.3 confirmed: a plain redefinition silently shadows the first
-5-declarations/global_float_init_b7.zc zy a appendix b.2.7 confirmed: float global initializer rejected
-5-declarations/include_in_function_b9.zc zy a appendix b.2.9 confirmed: include in a body gives "return outside a function"
-9-appendix-b/b2_05_ternary_int_float.zc zy a appendix b.2.5 confirmed
-9-appendix-b/b2_06_bool_normalises.zc zy a appendix b.2.6 confirmed
9-appendix-b/b2_10_recovery_hides_errors.zc zy c appendix b.2.10 confirmed; by design per 8.2
-9-appendix-b/b2_11_unterminated_comment.zc zy a appendix b.2.11 confirmed
-9-appendix-b/b2_12_as_int_star.zc zy a appendix b.2.12 confirmed
-9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc zy a appendix b.2.14 confirmed
9-appendix-b/b2_15_duplicate_case.zc zy c appendix b.2.15 confirmed; 4.7.2 says duplicates are allowed
9-appendix-b/b2_16_local_const_in_const_expr.zc zy c appendix b.2.16 confirmed; 3.14.2 documents it
2-types/conv_argument_and_return.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
@@ -37,8 +26,6 @@
3-expressions/float_division_int_stored.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
3-expressions/float_int_ops_on_bits.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
3-expressions/incr_on_float_changes_bits.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
-3-expressions/int_min_div_minus1.zc zc0 b INT_MIN / -1 gives 0 and % gives INT_MIN in zc0
-3-expressions/int_min_mod_minus1.zc zc0 b INT_MIN / -1 gives 0 and % gives INT_MIN in zc0
3-expressions/prec_as_binds_tight.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
4-statements/return_value_converted_float.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
4-statements/return_value_converted_int.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
diff --git a/tests/langerrors/closure_params_25.zc b/tests/langerrors/closure_params_25.zc
new file mode 100644
index 0000000..0444007
--- /dev/null
+++ b/tests/langerrors/closure_params_25.zc
@@ -0,0 +1,3 @@
+// expect: too many parameters
+include rt;
+fn main() { let f = fn(int a1,int a2,int a3,int a4,int a5,int a6,int a7,int a8,int a9,int a10,int a11,int a12,int a13,int a14,int a15,int a16,int a17,int a18,int a19,int a20,int a21,int a22,int a23,int a24, int a25) { return 0; }; return 0; }
diff --git a/tests/langerrors/fn_defined_twice.zc b/tests/langerrors/fn_defined_twice.zc
new file mode 100644
index 0000000..7af57e8
--- /dev/null
+++ b/tests/langerrors/fn_defined_twice.zc
@@ -0,0 +1,4 @@
+// expect: f: is defined twice
+int f() { return 1; }
+int f() { return 2; }
+fn main() { return f(); }
diff --git a/tests/langerrors/include_in_function.zc b/tests/langerrors/include_in_function.zc
new file mode 100644
index 0000000..06136f9
--- /dev/null
+++ b/tests/langerrors/include_in_function.zc
@@ -0,0 +1,2 @@
+// expect: include is top level only
+fn main() { include std; return 0; }
diff --git a/tests/langerrors/param_unnamed_with_body.zc b/tests/langerrors/param_unnamed_with_body.zc
new file mode 100644
index 0000000..bf96698
--- /dev/null
+++ b/tests/langerrors/param_unnamed_with_body.zc
@@ -0,0 +1,3 @@
+// expect: a parameter needs a name
+int f(int*) { return 1; }
+fn main() { return 0; }
diff --git a/tests/langerrors/proto_after_def_mismatch.zc b/tests/langerrors/proto_after_def_mismatch.zc
new file mode 100644
index 0000000..94a04e6
--- /dev/null
+++ b/tests/langerrors/proto_after_def_mismatch.zc
@@ -0,0 +1,4 @@
+// expect: f: doesn't match its earlier declaration
+int f(int a) { return a; }
+int f(int a, int b);
+fn main() { return f(1); }
diff --git a/tests/langerrors/unterminated_comment_docs.zc b/tests/langerrors/unterminated_comment_docs.zc
new file mode 100644
index 0000000..02f4335
--- /dev/null
+++ b/tests/langerrors/unterminated_comment_docs.zc
@@ -0,0 +1,4 @@
+// expect: unterminated comment
+docs on;
+fn main() { return 0; }
+/* never closed, with docs on
diff --git a/tests/programs/zc0umd.zc b/tests/programs/zc0umd.zc
index 9693111..b5a6d2a 100644
--- a/tests/programs/zc0umd.zc
+++ b/tests/programs/zc0umd.zc
@@ -2,12 +2,13 @@ namespace;
include rt;
// u, m and d are ordinary names, and a tag is written .u, .m or .d after a type.
-// u*x, m*x and d*x with no space before the star is a recast, and u * x with a
-// space is a product. the same program runs on the resident compiler and, built
+// u*x, m*x and d*x with no space before the star is a recast when no variable
+// named u, m or d is visible there (spec 1.6.3); otherwise, and with a space, it
+// is a product. the same program runs on the resident compiler and, built
// into a run-only image, on stage 0 (src/tools/zc0.c), which reads the tags and
// recasts and does nothing with them.
-int u = 2, m = 3; // globals
+int uu = 2, m = 3; // globals (no global u, so u*x recasts below; m*x with m visible is a product)
int[4] d4;
int d(int x) { return x + 1; } // a function
@@ -17,12 +18,12 @@ int tagged(int(int) f, int.m x) { return f(x); }
// owned objects through locals named for their tags
word*.u box(int v) { word* m = rt.new(2); m[0] = v; m[1] = v * 2; return u*m; }
-word*.u pair(int u, int m) {
+word*.u pair(int x, int m) {
word* d = rt.new(2);
- d[0] = u; d[1] = m;
+ d[0] = x; d[1] = m;
return u*d;
}
-.u single(int v) { let u = rt.new(1); u[0] = v; return u*u; }
+.u single(int v) { let d = rt.new(1); d[0] = v; return u*d; }
int sum(word*.m m) { return m[0] + m[1]; }
int eat(word*.d d) { return d[0] + d[1]; }
int hold(.m u, .d d) { return u[0] * 100 + d[0]; }
@@ -35,9 +36,9 @@ fn main() {
.m q = single(6);
word* r = pair(8, 9);
let c = box(10), e = pair(1, 2);
- rt.put_n(u * m); rt.put_c(32); // two globals multiplied
- rt.put_n(u*=m); rt.put_c(32); // u *= m is not a recast either
- rt.put_n(d(u) * d(m)); rt.put_c(32);
+ rt.put_n(uu * m); rt.put_c(32); // two globals multiplied
+ rt.put_n(uu*=m); rt.put_c(32); // *= is not a recast either
+ rt.put_n(d(uu) * d(m)); rt.put_c(32);
rt.put_n(mul3(2, 3, 4)); rt.put_c(32);
rt.put_n(mixed(7, 6, 5)); rt.put_c(32);
rt.put_n(tagged(d, n)); rt.put_c(10);