aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--docs/SPEC.md15
-rw-r--r--src/compiler/base.zc3
-rw-r--r--src/compiler/expr.zc68
-rw-r--r--src/compiler/stmt.zc2
-rw-r--r--src/tools/zc0.c40
-rw-r--r--tests/conformance/3-expressions/const_expr_array_length.zc2
-rw-r--r--tests/conformance/3-expressions/const_expr_case_label.zc6
-rw-r--r--tests/conformance/3-expressions/const_expr_division.zc7
-rw-r--r--tests/conformance/3-expressions/const_expr_division_by_zero.zc4
-rw-r--r--tests/conformance/3-expressions/const_expr_division_rejected.zc4
-rw-r--r--tests/conformance/3-expressions/const_expr_local_const.zc11
-rw-r--r--tests/conformance/3-expressions/const_expr_local_const_rejected.zc7
-rw-r--r--tests/conformance/3-expressions/const_expr_modulo.zc6
-rw-r--r--tests/conformance/3-expressions/const_expr_modulo_rejected.zc4
-rw-r--r--tests/conformance/3-expressions/const_expr_neg_not_parens.zc2
-rw-r--r--tests/conformance/3-expressions/const_expr_precedence.zc (renamed from tests/conformance/3-expressions/const_expr_left_to_right.zc)5
-rw-r--r--tests/conformance/3-expressions/const_expr_xor.zc6
-rw-r--r--tests/conformance/3-expressions/const_expr_xor_rejected.zc4
-rw-r--r--tests/conformance/4-statements/switch_label_forms.zc4
-rw-r--r--tests/conformance/5-declarations/global_const_expr_init.zc4
-rw-r--r--tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc2
-rw-r--r--tests/conformance/known.txt1
23 files changed, 146 insertions, 63 deletions
diff --git a/README.md b/README.md
index 7f022fa..3d09d77 100644
--- a/README.md
+++ b/README.md
@@ -169,7 +169,7 @@ fn main() {
}
```
-declarations are C-style, type first: `int a = 1, b;`, `node* p = rt.new(node);`, `char[4] buf;`, `point origin;`. `let` declares without a type: it infers `char*` from a word-string literal and `bstr` from a packed byte-string literal, infers other pointers and function types from the initializer, uses a pointer alias for an in-place array or struct, and keeps numbers as plain words. `let a[10];` is an untyped array. `const k = 5;` inside a function is a local constant with no type: it takes the initializer's type, needs an initializer, and can't be assigned. statements are declarations, `if`/`else`, `while`, `do`/`while`, `for`, `switch`, `break`, `continue`, `return`, `defer`, `{ }` blocks, `asm { }` and expressions. `do` body `while (` cond `);` runs the body once before the test, and `continue` jumps to the test. `defer expr;` or `defer { ... }` registers code that runs when the enclosing block ends, on every way out of it: falling off the end, `return`, `break` and `continue`. several defers in a block run last registered first, the deferred code is compiled at each exit and so reads the variables as they are when it runs (a `return e;` computes `e` first, then runs the defers), a defer that was never reached registers nothing, and `return`, `break` and `continue` inside a deferred statement are errors. a deferred `rt.free(p);` is the transfer of `p` for the analyzer at the point it runs: `p` stays usable until the block ends, is not freed or reported a second time, and freeing an owner that was moved away meanwhile is the usual use-after-move error. defer also works in `unchecked` code. `enum color { RED, GREEN = 5, BLUE };` makes global constants, and `color` is a type of its own in typed code (`typecheck on;`): a member takes the enum, a plain int does not become a `color`, and a `color` still becomes an `int`. untyped code treats the members as plain ints. each member is also reached as `color.BLUE`, in an expression or a `case` label. a member can carry fields too: `enum shape { circle { int r; }, rect { int w; int h; }, dot };` makes the constructors `shape.circle(3)` and `shape.rect(4, 5)`, which return an owned `shape*`, and `shape.dot()`. the tag is the word at offset 0 of the object. a `switch` over a `shape*` matches a member with `case circle(r):`, which binds its fields in that arm only. a switch on a tagged enum that leaves out a member and has no `default` gets a warning on its line (not an error). `(T)e` is a cast with the same meaning as `e as T`: the bits stay, the type changes. it has to be a simple type the parser can see in a few tokens, `int` or `*node`, not a nested array. `include "path";` reads a file and compiles it at that point, signatures first, so a library can live in its own file. each file has its own namespace and imports; dependencies included by a file do not import into the caller. `u`, `m` and `d` are ordinary names. a tag is `.u`, `.m` or `.d`, and `u*x` with no space before the star is still a recast. `switch (e) { case 1, 2: ... default: ... }` takes constant labels, and its cases don't fall through: each ends where the next begins, and `break` leaves early. operators follow C precedence, including `c ? a : b`, plus `p.x` and `p->x` (the same thing: `.` goes through a pointer on its own), `e as T` to change a type without changing the bits, and the casts below. comparisons and `&& ||` give 0 or 1. `/`, `%` and `<<` compile to native enhancement opcodes. character literals, `0x` hex and the usual escapes are there.
+declarations are C-style, type first: `int a = 1, b;`, `node* p = rt.new(node);`, `char[4] buf;`, `point origin;`. `let` declares without a type: it infers `char*` from a word-string literal and `bstr` from a packed byte-string literal, infers other pointers and function types from the initializer, uses a pointer alias for an in-place array or struct, and keeps numbers as plain words. `let a[10];` is an untyped array. `const k = 5;` inside a function is a local constant with no type: it takes the initializer's type, needs an initializer, and can't be assigned; with a constant initializer it can be used in constant expressions (array lengths, `case` labels), which follow C precedence. statements are declarations, `if`/`else`, `while`, `do`/`while`, `for`, `switch`, `break`, `continue`, `return`, `defer`, `{ }` blocks, `asm { }` and expressions. `do` body `while (` cond `);` runs the body once before the test, and `continue` jumps to the test. `defer expr;` or `defer { ... }` registers code that runs when the enclosing block ends, on every way out of it: falling off the end, `return`, `break` and `continue`. several defers in a block run last registered first, the deferred code is compiled at each exit and so reads the variables as they are when it runs (a `return e;` computes `e` first, then runs the defers), a defer that was never reached registers nothing, and `return`, `break` and `continue` inside a deferred statement are errors. a deferred `rt.free(p);` is the transfer of `p` for the analyzer at the point it runs: `p` stays usable until the block ends, is not freed or reported a second time, and freeing an owner that was moved away meanwhile is the usual use-after-move error. defer also works in `unchecked` code. `enum color { RED, GREEN = 5, BLUE };` makes global constants, and `color` is a type of its own in typed code (`typecheck on;`): a member takes the enum, a plain int does not become a `color`, and a `color` still becomes an `int`. untyped code treats the members as plain ints. each member is also reached as `color.BLUE`, in an expression or a `case` label. a member can carry fields too: `enum shape { circle { int r; }, rect { int w; int h; }, dot };` makes the constructors `shape.circle(3)` and `shape.rect(4, 5)`, which return an owned `shape*`, and `shape.dot()`. the tag is the word at offset 0 of the object. a `switch` over a `shape*` matches a member with `case circle(r):`, which binds its fields in that arm only. a switch on a tagged enum that leaves out a member and has no `default` gets a warning on its line (not an error). `(T)e` is a cast with the same meaning as `e as T`: the bits stay, the type changes. it has to be a simple type the parser can see in a few tokens, `int` or `*node`, not a nested array. `include "path";` reads a file and compiles it at that point, signatures first, so a library can live in its own file. each file has its own namespace and imports; dependencies included by a file do not import into the caller. `u`, `m` and `d` are ordinary names. a tag is `.u`, `.m` or `.d`, and `u*x` with no space before the star is still a recast. `switch (e) { case 1, 2: ... default: ... }` takes constant labels, and its cases don't fall through: each ends where the next begins, and `break` leaves early. operators follow C precedence, including `c ? a : b`, plus `p.x` and `p->x` (the same thing: `.` goes through a pointer on its own), `e as T` to change a type without changing the bits, and the casts below. comparisons and `&& ||` give 0 or 1. `/`, `%` and `<<` compile to native enhancement opcodes. character literals, `0x` hex and the usual escapes are there.
`typedef T name;` gives a type another name, as in c, at the top level or inside a function (where it lasts to the end of its block, like a local): `typedef node* link;`, `typedef int[4] quad;`, `typedef int(int, int) binop;`, `typedef list<int> ints;`. `type name = T;` says the same thing. a typedef is only a new spelling: `link` and `node*` are the very same type record, so they mix freely in declarations, parameters, results, casts, `sizeof`, struct fields, generic arguments and `typecheck on;` code, and they print as the target type. `T` can be any type the parser accepts, but not a tag (`.u`, `.m` and `.d` belong to a declaration). a typedef can't name a type that isn't declared yet, so a struct comes before the typedefs of its pointers; using a name that is already defined is an error, and a typedef of `void` is `void` (a variable of it is the usual error). `string` is built in as a typedef of `char*`, so `string s = "hi";` and `string f(string a)` work with no declaration; a function, variable or parameter of the same name shadows it.
diff --git a/docs/SPEC.md b/docs/SPEC.md
index 1c65f42..9d860fe 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -498,16 +498,17 @@ a[2]; // zy: index 2 out of range (length 2)
### 3.14 constant expressions
-3.14.1 constant expressions are used for array lengths, enum values, `const` initializers, global initializers, `case` labels, `embed` array sizes and asm operands. the grammar is: number | character | named constant | `-` operand | `~` operand | `( constant )` | and the binary operators `+ - * | & << >>` only, evaluated left to right with no precedence.
+3.14.1 constant expressions are used for array lengths, enum values, `const` initializers, global initializers, `case` labels, `embed` array sizes and asm operands. the grammar is: number | character | named constant | `-` operand | `~` operand | `( constant )` | and the binary operators `* / % + - << >> & ^ |`, with the c precedences (3.3): `* / %` bind tightest, then `+ -`, `<< >>`, `&`, `^` and `|`; operators of one level group left to right.
```
-const X = 1 + 2 * 3; // 9, not 7
+const X = 1 + 2 * 3; // 7
const Y = 10 - 2 - 3; // 5
const V = 1 | 2 + 4; // 7
const W = ~0 & 255; // 255
+const Z = 7 - 6 / 2 % 2; // 6
```
-other operators (`/ % ^ < ?:` and so on) are not part of a constant expression: `const Z = 6 / 2;` is error: expected ';', found '/'.
+division truncates toward zero as at run time; a division or remainder by zero is error: division by zero in a constant expression. other operators (`< == && ?:` and so on) are not part of a constant expression: `const Z = 1 < 2;` is a syntax error.
-3.14.2 the names allowed are global constants (`const`, enum members, `Enum.MEMBER`, `Struct.CONST` inside that struct as the bare name), global variables and arrays (their value is their address), and defined functions (their address). a local `const` is not visible to a constant expression: `const n = 5; int[n] a;` inside a function is error: expected a constant.
+3.14.2 the names allowed are global constants (`const`, enum members, `Enum.MEMBER`, `Struct.CONST` inside that struct as the bare name), global variables and arrays (their value is their address), defined functions (their address), and a local `const` whose initializer is itself constant: `const n = 5; const m = n * 2; int[m] a;` inside a function gives `a` 10 elements. a local `const` with a run-time initializer is not a constant: `const n = f(); int[n] a;` is error: expected a constant.
### 3.15 closures
@@ -1145,11 +1146,11 @@ each item gives a minimal program, the expected behavior (per this spec) and the
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.
+3. fixed (3.14.1): constant expressions now follow c precedence and accept `/ % ^`. it was: 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.
5. README ("zc, the language"): "a unit is read whole and scanned for function signatures before it compiles, so calls can come before definitions, tags included, and prototypes are optional". prototypes that are written must name their parameters (`int f(int);` => `expected a name, found ')'`).
6. README/LEARN do not say that an embedded `\0` ends a string literal: `b"hi\n\0x"` has `len` 3 and `"ab\0cd"` is `ab`.
-7. README ("zc, the language") says `const k = 5;` in a function is a local constant. it is not a constant expression: `const n = 5; int[n] a;` in a function => `expected a constant`.
+7. fixed (3.14.2): README ("zc, the language") says `const k = 5;` in a function is a local constant. it is not a constant expression: `const n = 5; int[n] a;` in a function => `expected a constant`.
8. README (reflection) describes `run.tparamtag` as "the u/m/d tag of a parameter"; the code returns 0 untagged, 1 `.m`, 2 `.d` (there is no u at a parameter).
9. README/LEARN do not say that local variables and arrays are not zeroed (4.1.5).
10. zc0 vs compiler, the case that needed a referee: with `||` and `&&` now at c precedence, zc0 and compiler.zc agree on `1 || 0 && 0` => 1, `0 && 0 || 1` => 1, `1 | 2 && 0` => 0, `1 + 2 << 1` => 6, `7 & 3 ^ 1 | 8` => 10, `6 & 3 == 3` => 0 (checked on both).
@@ -1172,7 +1173,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the
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).
+16. fixed (3.14.2): a local `const` could not 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.
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
index a209f2b..3135b39 100644
--- a/src/compiler/base.zc
+++ b/src/compiler/base.zc
@@ -400,7 +400,7 @@ int nerr, panic;
const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000;
const int LWASREAD = 0x10000; // an earlier value was read: a fresh value assigned over it clears LREAD, so its drop can still be set (6.15.1)
const int NEWTYPED = 0x800000;
-word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos;
+word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lkval_initial; word lkval = lkval_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos;
word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address
// storage facts are shared by aliases. they describe one allocation, not a
// variable slot: [length, lifetime, kind, allocation place, end place, owner].
@@ -772,6 +772,7 @@ int reserve_locals(int need) {
lreadpos = chk_resize(lreadpos, nloc, cap);
lnsz = chk_resize(lnsz, nloc, cap);
lleaf = chk_resize(lleaf, nloc, cap);
+ lkval = chk_resize(lkval, nloc, cap);
lrsoff = chk_resize(lrsoff, nloc, cap);
lrskind = chk_resize(lrskind, nloc, cap);
lptr = chk_resize(lptr, nloc, cap);
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
index 2935a9b..e437d2f 100644
--- a/src/compiler/expr.zc
+++ b/src/compiler/expr.zc
@@ -1072,6 +1072,7 @@ int primary() {
if (isfl(ltype[i])) xfloat = 1;
xstr = getstr(i); if (xty == tbstr) xstr = 1;
lv = 1; lvk = LV_LOCAL; lvi = i;
+ if ((ltag[i] & LCONST) && treadonly(xty)) { xconst = 1; xcval = lkval[i]; } // a local const's value is known (spec 3.14.2)
if (isalias(i)) { touch(i); xbor = lroot(i); return C_VAR + i; }
if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i;
return C_PLAIN;
@@ -1762,17 +1763,31 @@ int unary() {
// level tighter and emit its code sequence from the operator table. && and ||
// short-circuit with jumps; floats get their own opcodes; a * or / by a
// constant that is a power of two gets a shift hint in check mode.
+// fold a binary operator on two known integers; *ok is set to 0 when it can't be folded
+int cfold(int o, int a, int b, word ok) {
+ if (o == bname[0]) return a * b;
+ if (o == bname[1] || o == bname[2]) { if (b == 0) { ok[0] = 0; return 0; } if (o == bname[1]) return a / b; return a % b; }
+ if (o == bname[3]) return a + b;
+ if (o == bname[4]) return a - b;
+ if (o == bname[5]) return a << b;
+ if (o == bname[6]) return a >> b;
+ if (o == bname[13]) return a & b;
+ if (o == bname[14]) return a ^ b;
+ if (o == bname[15]) return a | b;
+ ok[0] = 0; return 0;
+}
int if_comparison(int name) {
return name == s_eqeq || name == s_ne || name == s_lt || name == s_gt || name == s_le || name == s_ge;
}
int bin(int minp) {
- int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp, lu, ru, xu; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right;
+ int lk = 0, lkv = 0, c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp, lu, ru, xu; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right;
if (fortest == 1) { fortest = 2; top = 1; ft_nops = 0; }
while (tk == T_OP) {
b = binfind(tv);
if (b < 0) return c;
p = bprec[b];
if (p < minp) return c;
+ lk = 0;
lb = -1;
if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor;
borrowed = xaf; left_mask = effect_mask(c); lf = xfloat; ls = xstr; fresh = 0;
@@ -1807,6 +1822,7 @@ int bin(int minp) {
setlit(j, here);
xfloat = 0; xstr = 0;
} else {
+ lk = xconst && !lf && !ls; lkv = xcval;
c = bin(p + 1); infer_right = if_value; rightborrow = xaf; right_mask = effect_mask(c);
rb = -1;
if (c >= C_VAR && tracked(c - C_VAR)) rb = lroot(c - C_VAR); else rb = xbor;
@@ -1818,6 +1834,7 @@ int bin(int minp) {
shifthint(opp, xcval);
}
rf = xfloat; rs = xstr;
+ if (lk && xconst && !rf && !rs && !isfl(xty)) { lk = 2; lkv = cfold(bname[b], lkv, xcval, &lk); }
if (isfl(xty)) rf = 1;
if ((ls || rs) && (bname[b] == s_plus || bname[b] == s_eqeq || bname[b] == s_ne || bname[b] == s_lt || bname[b] == s_gt || bname[b] == s_le || bname[b] == s_ge)) {
if (!(ls && rs)) serr("a byte string needs a byte string on the other side");
@@ -1856,6 +1873,7 @@ int bin(int minp) {
if (xu > 0x40000000) xu = 0 - 1;
xub = xu; xubat = here;
lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0;
+ if (lk == 2 && blen[b] > 0) { xconst = 1; xcval = lkv; } // both sides constant: the value is known
effect_value = 0; if (isptr(xty) || xty == tword) effect_value = left_mask | right_mask;
if (fresh) c = C_FRESH;
if (borrowed && borrowed[2] == 5 && (isptr(xty) || xty == tword) && (bname[b] == s_plus || bname[b] == s_minus)) xaf = borrowed;
@@ -2032,7 +2050,7 @@ int blockexpr() {
// constant expressions, evaluated while compiling: numbers, named constants,
// addresses of globals and functions, unary minus and ~, parentheses, and the
-// operators + - * | & << >>. used for array lengths, enum values, const
+// operators * / % + - << >> & ^ | at c precedence. used for array lengths, enum values, const
// initializers and asm operands.
word cprim() {
int v, s, mc;
@@ -2040,6 +2058,10 @@ word cprim() {
if (accept(s_minus)) return 0 - cprim();
if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); }
if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; }
+ if (tk == T_ID && nloc > 0) { // a local const with a constant initializer (spec 3.14.2)
+ s = findloc(tv);
+ if (s >= 0 && (ltag[s] & LCONST) && !larr[s] && ltype[s] && treadonly(ltype[s])) { next(); return lkval[s]; }
+ }
if (tk == T_ID) {
s = lookup(tv);
if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; }
@@ -2065,20 +2087,38 @@ word cprim() {
die("expected a constant");
return 0;
}
-// compile an expression: a primary, then the operators that follow it.
-int cexpr() {
- int v = cprim(), o;
+// a constant expression with c precedence: * / % above + -, above << >>,
+// above &, above ^, above | (spec 3.14). binary operators by precedence
+// climbing, each level reading its right side one level tighter.
+int cprec(int o) {
+ if (o == bname[0] || o == bname[1] || o == bname[2]) return 10;
+ if (o == bname[3] || o == bname[4]) return 9;
+ if (o == bname[5] || o == bname[6]) return 8;
+ if (o == bname[13]) return 5;
+ if (o == bname[14]) return 4;
+ if (o == bname[15]) return 3;
+ return 0;
+}
+int cbin(int minp) {
+ int v = cprim(), o, p, w;
while (tk == T_OP) {
- o = tv;
- if (o == bname[3]) { next(); v = v + cprim(); }
- else if (o == bname[4]) { next(); v = v - cprim(); }
- else if (o == bname[0]) { next(); v = v * cprim(); }
- else if (o == bname[15]) { next(); v = v | cprim(); }
- else if (o == bname[13]) { next(); v = v & cprim(); }
- else if (o == bname[5]) { next(); v = v << cprim(); }
- else if (o == bname[6]) { next(); v = v >> cprim(); }
- else return v;
+ o = tv; p = cprec(o);
+ if (p == 0 || p < minp) return v;
+ next(); w = cbin(p + 1);
+ if (o == bname[0]) v = v * w;
+ else if (o == bname[1] || o == bname[2]) {
+ if (w == 0) { die("division by zero in a constant expression"); return 0; }
+ if (o == bname[1]) v = v / w; else v = v % w;
+ }
+ else if (o == bname[3]) v = v + w;
+ else if (o == bname[4]) v = v - w;
+ else if (o == bname[5]) v = v << w;
+ else if (o == bname[6]) v = v >> w;
+ else if (o == bname[13]) v = v & w;
+ else if (o == bname[14]) v = v ^ w;
+ else v = v | w;
}
return v;
}
+int cexpr() { return cbin(1); }
diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc
index 007306c..e98cb27 100644
--- a/src/compiler/stmt.zc
+++ b/src/compiler/stmt.zc
@@ -144,7 +144,7 @@ int localdecl(int typed) {
newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1;
if (t && t[0] == TY_DYN && is_op(s_lb)) c = array_dynamic_literal(t); else c = expr();
rval(); nomagic = 0; kc = xconst && !xfloat;
- if (kc) ltag[i] = ltag[i] | LCONST;
+ if (kc) { ltag[i] = ltag[i] | LCONST; lkval[i] = xcval; }
if (c == C_FRESH) { lnsz[i] = newsz; lleaf[i] = newleaf; if (drop_tail == here) local_dropset[i] = 1; }
if (t) fcoerce(t);
vaddr(i);
diff --git a/src/tools/zc0.c b/src/tools/zc0.c
index 6c62abf..229734c 100644
--- a/src/tools/zc0.c
+++ b/src/tools/zc0.c
@@ -396,16 +396,42 @@ static int64_t cprim(void)
if (tk == T_ID && (s = find(ts)) && (s->kind == K_CONST || (s->kind != K_GVAR && s->defd))) { next(); return s->val; }
die("constant expected"); return 0;
}
-static int64_t cexpr(void)
+/* c precedence (spec 3.14.1): * / % above + -, above << >>, above &, ^, | */
+static int cprec(void)
{
- int64_t v = cprim();
- for (;;) {
- if (accept("+")) v += cprim(); else if (accept("-")) v -= cprim();
- else if (accept("*")) v *= cprim(); else if (accept("|")) v |= cprim();
- else if (accept("&")) v &= cprim(); else if (accept("<<")) v <<= cprim();
- else if (accept(">>")) v >>= cprim(); else return v;
+ if (is("*") || is("/") || is("%")) return 10;
+ if (is("+") || is("-")) return 9;
+ if (is("<<") || is(">>")) return 8;
+ if (is("&")) return 5;
+ if (is("^")) return 4;
+ if (is("|")) return 3;
+ return 0;
+}
+static int64_t cbin(int minp)
+{
+ int64_t v = cprim(), w; int p; char o[3];
+ while ((p = cprec()) && p >= minp) {
+ o[0] = ts[0]; o[1] = ts[1]; o[2] = 0;
+ next(); w = cbin(p + 1);
+ switch (o[0]) {
+ case '*': v *= w; break;
+ case '/': case '%':
+ if (w == 0) { die("division by zero in a constant expression"); return 0; }
+ if (w == -1) v = o[0] == '/' ? (int64_t)(0 - (uint64_t)v) : 0;
+ else v = o[0] == '/' ? v / w : v % w;
+ break;
+ case '+': v += w; break;
+ case '-': v -= w; break;
+ case '<': v = (int64_t)((uint64_t)v << (w & 63)); break;
+ case '>': v >>= (w & 63); break;
+ case '&': v &= w; break;
+ case '^': v ^= w; break;
+ default: v |= w; break;
+ }
}
+ return v;
}
+static int64_t cexpr(void) { return cbin(1); }
/* ---- types ----
zc0 has no types: a type is read and thrown away. all it has to know is the
diff --git a/tests/conformance/3-expressions/const_expr_array_length.zc b/tests/conformance/3-expressions/const_expr_array_length.zc
index 07434a9..c6f11e3 100644
--- a/tests/conformance/3-expressions/const_expr_array_length.zc
+++ b/tests/conformance/3-expressions/const_expr_array_length.zc
@@ -1,4 +1,4 @@
-// expect: 10/
+// expect: 8/
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
fn main() {
diff --git a/tests/conformance/3-expressions/const_expr_case_label.zc b/tests/conformance/3-expressions/const_expr_case_label.zc
index 3f1e781..0c76d4b 100644
--- a/tests/conformance/3-expressions/const_expr_case_label.zc
+++ b/tests/conformance/3-expressions/const_expr_case_label.zc
@@ -1,9 +1,9 @@
-// expect: nine/
+// expect: seven/
include rt;
fn main() {
- int k = 9;
+ int k = 7;
switch (k) {
- case 1 + 2 * 3: rt.put_s("nine");
+ case 1 + 2 * 3: rt.put_s("seven");
default: rt.put_s("other");
}
rt.put_c(10);
diff --git a/tests/conformance/3-expressions/const_expr_division.zc b/tests/conformance/3-expressions/const_expr_division.zc
new file mode 100644
index 0000000..90db20c
--- /dev/null
+++ b/tests/conformance/3-expressions/const_expr_division.zc
@@ -0,0 +1,7 @@
+// expect: 3/-3/2/
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+const Z = 6 / 2;
+const N = -7 / 2;
+const P = 1 + 6 / 3 * 2 - 3;
+fn main() { pn(Z); pn(N + 0); pn(P); return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_division_by_zero.zc b/tests/conformance/3-expressions/const_expr_division_by_zero.zc
new file mode 100644
index 0000000..f1011ff
--- /dev/null
+++ b/tests/conformance/3-expressions/const_expr_division_by_zero.zc
@@ -0,0 +1,4 @@
+// expect error: division by zero in a constant expression
+include rt;
+const Z = 1 / (2 - 2);
+fn main() { return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_division_rejected.zc b/tests/conformance/3-expressions/const_expr_division_rejected.zc
deleted file mode 100644
index f095247..0000000
--- a/tests/conformance/3-expressions/const_expr_division_rejected.zc
+++ /dev/null
@@ -1,4 +0,0 @@
-// expect error: expected ';', found '/'
-include rt;
-const Z = 6 / 2;
-fn main() { return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_local_const.zc b/tests/conformance/3-expressions/const_expr_local_const.zc
new file mode 100644
index 0000000..46a8d55
--- /dev/null
+++ b/tests/conformance/3-expressions/const_expr_local_const.zc
@@ -0,0 +1,11 @@
+// expect: 5/11/7/
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+fn main() {
+ const n = 5;
+ const m = n * 2 + 1;
+ int[m] a;
+ const int k = 7;
+ pn(n); pn(len(a)); switch (7) { case k: pn(k); }
+ return 0;
+}
diff --git a/tests/conformance/3-expressions/const_expr_local_const_rejected.zc b/tests/conformance/3-expressions/const_expr_local_const_rejected.zc
deleted file mode 100644
index edab64e..0000000
--- a/tests/conformance/3-expressions/const_expr_local_const_rejected.zc
+++ /dev/null
@@ -1,7 +0,0 @@
-// expect error: expected a constant
-include rt;
-fn main() {
- const n = 5;
- int[n] a;
- return 0;
-}
diff --git a/tests/conformance/3-expressions/const_expr_modulo.zc b/tests/conformance/3-expressions/const_expr_modulo.zc
new file mode 100644
index 0000000..3f32f46
--- /dev/null
+++ b/tests/conformance/3-expressions/const_expr_modulo.zc
@@ -0,0 +1,6 @@
+// expect: 1/10/
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+const Z = 7 % 2;
+const Q = 1 + 20 % 7 * 3;
+fn main() { pn(Z); pn(Q - 9); return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_modulo_rejected.zc b/tests/conformance/3-expressions/const_expr_modulo_rejected.zc
deleted file mode 100644
index 4c54ffe..0000000
--- a/tests/conformance/3-expressions/const_expr_modulo_rejected.zc
+++ /dev/null
@@ -1,4 +0,0 @@
-// expect error: zc: line 3:
-include rt;
-const Z = 7 % 2;
-fn main() { return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_neg_not_parens.zc b/tests/conformance/3-expressions/const_expr_neg_not_parens.zc
index 6e96bdc..714990a 100644
--- a/tests/conformance/3-expressions/const_expr_neg_not_parens.zc
+++ b/tests/conformance/3-expressions/const_expr_neg_not_parens.zc
@@ -1,4 +1,4 @@
-// expect: -3/-6/-1/24/
+// expect: -3/-4/-1/24/
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
const A = -3;
diff --git a/tests/conformance/3-expressions/const_expr_left_to_right.zc b/tests/conformance/3-expressions/const_expr_precedence.zc
index 68e26ab..c173959 100644
--- a/tests/conformance/3-expressions/const_expr_left_to_right.zc
+++ b/tests/conformance/3-expressions/const_expr_precedence.zc
@@ -1,8 +1,9 @@
-// expect: 9/5/7/255/
+// expect: 7/5/7/255/16/
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
const X = 1 + 2 * 3;
const Y = 10 - 2 - 3;
const V = 1 | 2 + 4;
const W = ~0 & 255;
-fn main() { pn(X); pn(Y); pn(V); pn(W); return 0; }
+const S = 1 + 3 << 1 + 1 >> 1 - 1 | 6 & 2;
+fn main() { pn(X); pn(Y); pn(V); pn(W); pn(S - 2); return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_xor.zc b/tests/conformance/3-expressions/const_expr_xor.zc
new file mode 100644
index 0000000..30ca0a7
--- /dev/null
+++ b/tests/conformance/3-expressions/const_expr_xor.zc
@@ -0,0 +1,6 @@
+// expect: 5/7/
+include rt;
+void pn(int x) { rt.put_n(x); rt.put_c(10); }
+const Z = 7 ^ 2;
+const Q = 1 ^ 2 | 4 & 4;
+fn main() { pn(Z); pn(Q); return 0; }
diff --git a/tests/conformance/3-expressions/const_expr_xor_rejected.zc b/tests/conformance/3-expressions/const_expr_xor_rejected.zc
deleted file mode 100644
index eb1ad25..0000000
--- a/tests/conformance/3-expressions/const_expr_xor_rejected.zc
+++ /dev/null
@@ -1,4 +0,0 @@
-// expect error: zc: line 3:
-include rt;
-const Z = 7 ^ 2;
-fn main() { return 0; }
diff --git a/tests/conformance/4-statements/switch_label_forms.zc b/tests/conformance/4-statements/switch_label_forms.zc
index cfe2712..ef5e33b 100644
--- a/tests/conformance/4-statements/switch_label_forms.zc
+++ b/tests/conformance/4-statements/switch_label_forms.zc
@@ -1,5 +1,5 @@
// expect: neg/chr/sum/enum/
-// spec 4.7.1 and 3.14: labels are constant expressions (no precedence: 1 + 1 * 3 is 6)
+// spec 4.7.1 and 3.14: labels are constant expressions (c precedence: 1 + 1 * 3 is 4)
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
enum color { RED, GREEN };
@@ -12,5 +12,5 @@ void t(int k) {
}
rt.put_c(10);
}
-fn main() { t(-1); t('a'); t(6); t(1); return 0; }
+fn main() { t(-1); t('a'); t(4); t(1); return 0; }
// zc0: skip
diff --git a/tests/conformance/5-declarations/global_const_expr_init.zc b/tests/conformance/5-declarations/global_const_expr_init.zc
index 8a9d30b..64a4d06 100644
--- a/tests/conformance/5-declarations/global_const_expr_init.zc
+++ b/tests/conformance/5-declarations/global_const_expr_init.zc
@@ -1,5 +1,5 @@
-// expect: 4/
-// spec 5.3.2 and 3.14.1 (left to right: ((1+2)*2)-2)
+// expect: 3/
+// spec 5.3.2 and 3.14.1 (c precedence: 1+(2*2)-2)
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
int g = 1 + 2 * 2 - 2;
diff --git a/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc b/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc
index 70093c9..649321f 100644
--- a/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc
+++ b/tests/conformance/9-appendix-b/b2_16_local_const_in_const_expr.zc
@@ -1,5 +1,5 @@
// expect: 5/
-// spec appendix b.2.16 and 3.14.2: a local const is not a constant expression
+// spec 3.14.2 (was appendix b.2.16): a local const is a constant expression
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
fn main() { const n = 5; int[n] a; pn(5); return 0; }
diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt
index 8eeb0d3..25bc3b7 100644
--- a/tests/conformance/known.txt
+++ b/tests/conformance/known.txt
@@ -4,7 +4,6 @@
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
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_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)
2-types/conv_assignment_to_float.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)
2-types/conv_f32_rounds.zc zc0 b types are read and dropped: no conversion, no sized cut (appendix a)