From 3e71e980c611ac9f00565564bd1e647194f74321 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:07:12 +0330 Subject: closures: allow 24 parameters, like functions (was 19) --- src/compiler/expr.zc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index 3d63e4b..c8cb11a 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); -- cgit v1.2.3 From 0b7a396cc5375afdeea1716126f8dd1a0efea870 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:08:50 +0330 Subject: unary: u*x, m*x, d*x are not recasts when the tag names a global variable either --- src/compiler/expr.zc | 7 ++++++- tests/conformance/1-source/ident_tags_global_glued_star.zc | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/conformance/1-source/ident_tags_global_glued_star.zc (limited to 'src') diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index c8cb11a..ffb90a7 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1507,12 +1507,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; 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 -- cgit v1.2.3 From a752292b54e22a8024b9d9162762edd2efa533b5 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:10:42 +0330 Subject: fndef: a second definition of a function in the same module is 'is defined twice' (appendix b.2.3) --- src/compiler/decl.zc | 4 ++++ tests/conformance/known.txt | 1 - tests/langerrors/fn_defined_twice.zc | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/langerrors/fn_defined_twice.zc (limited to 'src') diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 0870756..6794373 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -285,6 +285,10 @@ int fndef(int chk, int rty, int ret, int n) { if (rty || ntp) typed = 1; if (!dnm) dnm = n; s = lookup(n); + if (s >= ubase && skind[s] == K_FN && (sflag[s] & F_DEF) && !(sflag[s] & F_TPL) && !ginst && !ntp) { // appendix b.2.3: the same name in the same module + word qn = n; if (module_name) qn = module_symbol(n); + if (rt.eq_w(sname[s], qn)) serrn(n, "is defined twice"); + } if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) { s = addsym(n, K_FN, 0); sflag[s] = 0; diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index 13ef43c..ff67f6b 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -5,7 +5,6 @@ 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 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(); } -- cgit v1.2.3 From 19cdaa91b74da17a78809433ff7e6a2c084d8385 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:11:59 +0330 Subject: fndef: a prototype after a definition in the same module keeps the definition (appendix b.2.4) --- src/compiler/decl.zc | 9 +++++---- tests/conformance/known.txt | 1 - tests/langerrors/proto_after_def_mismatch.zc | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 tests/langerrors/proto_after_def_mismatch.zc (limited to 'src') diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 6794373..67aceb1 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -280,16 +280,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; o3nxt = 0; if (rty || ntp) typed = 1; if (!dnm) dnm = n; s = lookup(n); - if (s >= ubase && skind[s] == K_FN && (sflag[s] & F_DEF) && !(sflag[s] & F_TPL) && !ginst && !ntp) { // appendix b.2.3: the same name in the same module + 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); - if (rt.eq_w(sname[s], qn)) serrn(n, "is defined twice"); + redef = rt.eq_w(sname[s], qn); } - if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) { + if (!redef && (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF))) { s = addsym(n, K_FN, 0); sflag[s] = 0; } diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index ff67f6b..7b2082c 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -3,7 +3,6 @@ # 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/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" 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); } -- cgit v1.2.3 From 01125f4b3ced32230e19503ce72a2a9dac63c58d Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:13:27 +0330 Subject: globals: a float or f32 global takes a float literal initializer (appendix b.2.7) --- src/compiler/decl.zc | 3 +++ .../conformance/5-declarations/global_float_init_f32_and_list.zc | 8 ++++++++ tests/conformance/known.txt | 3 +-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 tests/conformance/5-declarations/global_float_init_f32_and_list.zc (limited to 'src') diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 67aceb1..2c8d13e 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -478,6 +478,9 @@ 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; if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; } 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/known.txt b/tests/conformance/known.txt index 7b2082c..044b862 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -3,8 +3,7 @@ # 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_unnamed_params_b13.zc zy a appendix b.2.13 confirmed (message now: doesn't match its earlier declaration) -5-declarations/global_float_init_b7.zc zy a appendix b.2.7 confirmed: float global initializer rejected +5-declarations/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed 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 -- cgit v1.2.3 From d93000c4b5ff7b3dda0e59a4d333b0463e745e04 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:14:02 +0330 Subject: include: written inside a function body is 'include is top level only' (appendix b.2.9) --- src/compiler/builtins.zc | 1 + tests/conformance/5-declarations/include_in_function_b9.zc | 2 +- tests/conformance/known.txt | 1 - tests/langerrors/include_in_function.zc | 2 ++ 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 tests/langerrors/include_in_function.zc (limited to 'src') diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc index d04c444..d6a58fb 100644 --- a/src/compiler/builtins.zc +++ b/src/compiler/builtins.zc @@ -312,6 +312,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) { 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/known.txt b/tests/conformance/known.txt index 044b862..8415b18 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 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_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed -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 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; } -- cgit v1.2.3 From b6f4d415c5fae9bfe9243ab0044135ce65492665 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:17:42 +0330 Subject: ternary: an int side and a float side convert the int on its own path only (appendix b.2.5) --- src/compiler/expr.zc | 10 +++++++--- tests/conformance/3-expressions/ternary_int_float_variables.zc | 8 ++++++++ tests/conformance/known.txt | 3 +-- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 tests/conformance/3-expressions/ternary_int_float_variables.zc (limited to 'src') diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index ffb90a7..95ce67b 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1727,7 +1727,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(); @@ -1735,6 +1735,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); @@ -1742,11 +1743,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; 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/known.txt b/tests/conformance/known.txt index 8415b18..5ff2ec0 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -2,9 +2,8 @@ # 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/enum_member_names_earlier_b2.zc zy c appendix b.2.2 confirmed, but spec 2.8.1 documents it (members are defined at the closing brace); not fixed, like b.2.8 the spec body wins until decided 5-declarations/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed -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 -- cgit v1.2.3 From 76fc2643d1df7da7c83f7b58fe7e8459f487b816 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:19:56 +0330 Subject: zc0: read integer literals like the compiler: decimal with leading zeros, 0x hex, wrapping (appendix b.1.11) --- src/tools/zc0.c | 14 ++++++++++++-- tests/conformance/known.txt | 11 ----------- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/tools/zc0.c b/src/tools/zc0.c index 5846e22..6c20e2a 100644 --- a/src/tools/zc0.c +++ b/src/tools/zc0.c @@ -133,6 +133,16 @@ static void docblock(void) while (dn && (dbuf[dn - 1] == ' ' || dbuf[dn - 1] == '\t' || dbuf[dn - 1] == '\r' || dbuf[dn - 1] == '\n')) dn--; dbuf[dn++] = '\n'; dlast = line; docpending = 1; } +// an integer literal the way the compiler reads it (lex1): decimal even with leading zeros, +// 0x hex (no digits is 0), both wrapping modulo 2^64 (appendix b.1.11; strtoll saturated and read octal) +static int64_t zc0_int(const char *s, char **end) { + uint64_t v = 0; const char *p = s; + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + for (;;) { int c = *p, d; if (c >= '0' && c <= '9') d = c - '0'; else if (c >= 'a' && c <= 'f') d = c - 'a' + 10; else if (c >= 'A' && c <= 'F') d = c - 'A' + 10; else break; v = v * 16 + (uint64_t)d; p++; } + } else while (*p >= '0' && *p <= '9') { v = v * 10 + (uint64_t)(*p - '0'); p++; } + *end = (char *)p; return (int64_t)v; +} static void next(void) { int ws = 0, i; @@ -153,7 +163,7 @@ static void next(void) if (isdigit((unsigned char)src[pos]) || (src[pos] == '.' && isdigit((unsigned char)src[pos + 1]))) { int p = pos, fl = 0; if (src[pos] == '0' && (src[pos + 1] == 'x' || src[pos + 1] == 'X')) { - char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; + char *e; tv = zc0_int(src + pos, &e); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } if (src[p] == '.') { int nch = (unsigned char)src[p + 1]; @@ -186,7 +196,7 @@ static void next(void) } b[n] = 0; memcpy(ts, b, (size_t)n + 1); tv = (int64_t)fop_parse(b); tk = T_FLT; return; } - { char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } + { char *e; tv = zc0_int(src + pos, &e); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } } if (isalpha((unsigned char)src[pos]) || src[pos] == '_') { i = 0; while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++]; diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index 6388f29..6232672 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -12,13 +12,6 @@ 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 1-source/ident_tags_as_variables_glued_star.zc zc0 b u*d with visible u and d is read as a recast (no code); zy follows 1.6.3 -1-source/lit_hex.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_hex_wide.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_hex_wraps.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_int_max.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_int_wraps.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_int_zero_and_leading_zeros.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_minus_folded.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) 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) @@ -45,7 +38,3 @@ 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) -9-appendix-b/b1_11_zc0_octal.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -9-appendix-b/b1_11_zc0_saturation.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) -1-source/lit_hex_no_digits_is_zero.zc zc0 b 0x with no digits is rejected (strtoll) -1-source/lit_int_octal_is_decimal.zc zc0 b 08 is rejected (strtoll base 0 octal) -- cgit v1.2.3 From 2f2597bd753fa04edc8cf51ffe9fd324965224d2 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:21:01 +0330 Subject: zc0: u*x, m*x, d*x are products when u, m or d is a visible local or global (spec 1.6.3) --- src/tools/zc0.c | 13 ++++++++----- tests/conformance/1-source/ident_tags_global_glued_star.zc | 1 - tests/conformance/known.txt | 1 - 3 files changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tools/zc0.c b/src/tools/zc0.c index 6c20e2a..e673eee 100644 --- a/src/tools/zc0.c +++ b/src/tools/zc0.c @@ -238,12 +238,8 @@ static int accept(const char *s) { if (is(s)) { next(); return 1; } return 0; } static void expect(const char *s) { if (!accept(s)) { char b[64]; sprintf(b, "expected '%s'", s); die(b); } } static char *ident(void) { static char b[8][256]; static int k; char *r = b[k++ & 7]; if (tk != T_ID) die("expected a name"); strcpy(r, ts); next(); return r; } /* u, m and d are ordinary names. u*x, m*x and d*x are recasts when the star touches - the letter, which the raw character after the name tells: u * x is a product, and + the letter and no variable of that name is visible, which the raw character after the name tells: u * x is a product, and so is u*=x. the current token is the name, so pos is just past it. */ -static int isrecast(void) -{ - return tk == T_ID && ts[1] == 0 && (ts[0] == 'u' || ts[0] == 'm' || ts[0] == 'd') && src[pos] == '*' && src[pos + 1] != '='; -} /* ---- symbols ---- */ typedef struct { char name[64]; int kind, sig, defd, str; int64_t val; } Sym; /* str: a global holding a byte string, or a function returning one */ @@ -284,6 +280,13 @@ static void fnref(const char *n) /* push a function address, fixing it up later typedef struct { char name[64]; int slot, arr, flt, str, dstr; } Loc; /* dstr: declared bstr, so always a byte string */ static Loc lo[1024]; static int nlo, nslot, infn; static Loc *findl(const char *n) { int i; for (i = nlo; i--;) if (!strcmp(lo[i].name, n)) return &lo[i]; return 0; } +static int isrecast(void) +{ + Sym *g; + if (!(tk == T_ID && ts[1] == 0 && (ts[0] == 'u' || ts[0] == 'm' || ts[0] == 'd') && src[pos] == '*' && src[pos + 1] != '=')) return 0; + if (findl(ts)) return 0; /* a visible variable named u, m or d: a product (spec 1.6.3) */ + g = find(ts); return !(g && (g->kind == K_GVAR || g->kind == K_GARR)); +} static void laddr(int k) { lit(ZFP); op(O_LD); if (k) { lit(k); op(O_ADD); } } /* ---- operator tables ---- */ diff --git a/tests/conformance/1-source/ident_tags_global_glued_star.zc b/tests/conformance/1-source/ident_tags_global_glued_star.zc index 516f9a3..92abad4 100644 --- a/tests/conformance/1-source/ident_tags_global_glued_star.zc +++ b/tests/conformance/1-source/ident_tags_global_glued_star.zc @@ -5,4 +5,3 @@ 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/known.txt b/tests/conformance/known.txt index 6232672..4d949fa 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -11,7 +11,6 @@ 9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc zy c appendix b.2.14 confirmed, but spec 3.13.5 leaves it unspecified 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 -1-source/ident_tags_as_variables_glued_star.zc zc0 b u*d with visible u and d is read as a recast (no code); zy follows 1.6.3 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) -- cgit v1.2.3 From f2ebb251efc5a528ce24c236e49b25fac225d881 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:21:48 +0330 Subject: rt.zdiv: INT_MIN / -1 is INT_MIN and % is 0 in software division too, so zc0 programs agree with spec 3.4.3 --- src/runtime/rt.zc | 1 + tests/conformance/known.txt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc index 4e52cb5..7018915 100644 --- a/src/runtime/rt.zc +++ b/src/runtime/rt.zc @@ -363,6 +363,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/known.txt b/tests/conformance/known.txt index 4d949fa..df4c94b 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -32,8 +32,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) -- cgit v1.2.3 From 87dba496160abf20557dbf102b7bc9ce4078c0d9 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:25:31 +0330 Subject: Revert "zc0: u*x, m*x, d*x are products when u, m or d is a visible local or global (spec 1.6.3)" This reverts commit 2f2597bd753fa04edc8cf51ffe9fd324965224d2. --- src/tools/zc0.c | 13 +++++-------- tests/conformance/1-source/ident_tags_global_glued_star.zc | 1 + tests/conformance/known.txt | 1 + 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/tools/zc0.c b/src/tools/zc0.c index 81561ce..2261661 100644 --- a/src/tools/zc0.c +++ b/src/tools/zc0.c @@ -238,8 +238,12 @@ static int accept(const char *s) { if (is(s)) { next(); return 1; } return 0; } static void expect(const char *s) { if (!accept(s)) { char b[64]; sprintf(b, "expected '%s'", s); die(b); } } static char *ident(void) { static char b[8][256]; static int k; char *r = b[k++ & 7]; if (tk != T_ID) die("expected a name"); strcpy(r, ts); next(); return r; } /* u, m and d are ordinary names. u*x, m*x and d*x are recasts when the star touches - the letter and no variable of that name is visible, which the raw character after the name tells: u * x is a product, and + the letter, which the raw character after the name tells: u * x is a product, and so is u*=x. the current token is the name, so pos is just past it. */ +static int isrecast(void) +{ + return tk == T_ID && ts[1] == 0 && (ts[0] == 'u' || ts[0] == 'm' || ts[0] == 'd') && src[pos] == '*' && src[pos + 1] != '='; +} /* ---- symbols ---- */ typedef struct { char name[64]; int kind, sig, defd, str; int64_t val; } Sym; /* str: a global holding a byte string, or a function returning one */ @@ -280,13 +284,6 @@ static void fnref(const char *n) /* push a function address, fixing it up later typedef struct { char name[64]; int slot, arr, flt, str, dstr; } Loc; /* dstr: declared bstr, so always a byte string */ static Loc lo[1024]; static int nlo, nslot, infn; static Loc *findl(const char *n) { int i; for (i = nlo; i--;) if (!strcmp(lo[i].name, n)) return &lo[i]; return 0; } -static int isrecast(void) -{ - Sym *g; - if (!(tk == T_ID && ts[1] == 0 && (ts[0] == 'u' || ts[0] == 'm' || ts[0] == 'd') && src[pos] == '*' && src[pos + 1] != '=')) return 0; - if (findl(ts)) return 0; /* a visible variable named u, m or d: a product (spec 1.6.3) */ - g = find(ts); return !(g && (g->kind == K_GVAR || g->kind == K_GARR)); -} static void laddr(int k) { lit(ZFP); op(O_LD); if (k) { lit(k); op(O_ADD); } } /* ---- operator tables ---- */ diff --git a/tests/conformance/1-source/ident_tags_global_glued_star.zc b/tests/conformance/1-source/ident_tags_global_glued_star.zc index 92abad4..516f9a3 100644 --- a/tests/conformance/1-source/ident_tags_global_glued_star.zc +++ b/tests/conformance/1-source/ident_tags_global_glued_star.zc @@ -5,3 +5,4 @@ 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/known.txt b/tests/conformance/known.txt index df4c94b..22a1e3d 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -11,6 +11,7 @@ 9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc zy c appendix b.2.14 confirmed, but spec 3.13.5 leaves it unspecified 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 +1-source/ident_tags_as_variables_glued_star.zc zc0 b u*d with visible u and d is read as a recast (no code); zy follows 1.6.3 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) -- cgit v1.2.3 From 8d68ed91b0537a5a3c274562f58f0b34933cf35d Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:25:31 +0330 Subject: Revert "zc0: read integer literals like the compiler: decimal with leading zeros, 0x hex, wrapping (appendix b.1.11)" This reverts commit 76fc2643d1df7da7c83f7b58fe7e8459f487b816. --- src/tools/zc0.c | 14 ++------------ tests/conformance/known.txt | 11 +++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/tools/zc0.c b/src/tools/zc0.c index 2261661..af85ff8 100644 --- a/src/tools/zc0.c +++ b/src/tools/zc0.c @@ -133,16 +133,6 @@ static void docblock(void) while (dn && (dbuf[dn - 1] == ' ' || dbuf[dn - 1] == '\t' || dbuf[dn - 1] == '\r' || dbuf[dn - 1] == '\n')) dn--; dbuf[dn++] = '\n'; dlast = line; docpending = 1; } -// an integer literal the way the compiler reads it (lex1): decimal even with leading zeros, -// 0x hex (no digits is 0), both wrapping modulo 2^64 (appendix b.1.11; strtoll saturated and read octal) -static int64_t zc0_int(const char *s, char **end) { - uint64_t v = 0; const char *p = s; - if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - p += 2; - for (;;) { int c = *p, d; if (c >= '0' && c <= '9') d = c - '0'; else if (c >= 'a' && c <= 'f') d = c - 'a' + 10; else if (c >= 'A' && c <= 'F') d = c - 'A' + 10; else break; v = v * 16 + (uint64_t)d; p++; } - } else while (*p >= '0' && *p <= '9') { v = v * 10 + (uint64_t)(*p - '0'); p++; } - *end = (char *)p; return (int64_t)v; -} static void next(void) { int ws = 0, i; @@ -163,7 +153,7 @@ static void next(void) if (isdigit((unsigned char)src[pos]) || (src[pos] == '.' && isdigit((unsigned char)src[pos + 1]))) { int p = pos, fl = 0; if (src[pos] == '0' && (src[pos + 1] == 'x' || src[pos + 1] == 'X')) { - char *e; tv = zc0_int(src + pos, &e); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; + char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } if (src[p] == '.') { int nch = (unsigned char)src[p + 1]; @@ -196,7 +186,7 @@ static void next(void) } b[n] = 0; memcpy(ts, b, (size_t)n + 1); tv = (int64_t)fop_parse(b); tk = T_FLT; return; } - { char *e; tv = zc0_int(src + pos, &e); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } + { char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } } if (isalpha((unsigned char)src[pos]) || src[pos] == '_') { i = 0; while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++]; diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index 22a1e3d..e67ec57 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -12,6 +12,13 @@ 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 1-source/ident_tags_as_variables_glued_star.zc zc0 b u*d with visible u and d is read as a recast (no code); zy follows 1.6.3 +1-source/lit_hex.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_hex_wide.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_hex_wraps.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_int_max.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_int_wraps.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_int_zero_and_leading_zeros.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_minus_folded.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) 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) @@ -36,3 +43,7 @@ 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) +9-appendix-b/b1_11_zc0_octal.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +9-appendix-b/b1_11_zc0_saturation.zc zc0 b literals read with strtoll base 0: octal and saturation (appendix b.1.11) +1-source/lit_hex_no_digits_is_zero.zc zc0 b 0x with no digits is rejected (strtoll) +1-source/lit_int_octal_is_decimal.zc zc0 b 08 is rejected (strtoll base 0 octal) -- cgit v1.2.3 From 4a96db860c95ed6ea8a03cb24c54ebfb9325ada8 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:47:08 +0330 Subject: globals: an integer constant initializing a float or f32 global converts to float (float g = 2; is 2.0) --- docs/SPEC.md | 2 +- src/compiler/decl.zc | 3 ++- tests/conformance/5-declarations/global_float_int_init.zc | 9 +++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 tests/conformance/5-declarations/global_float_int_init.zc (limited to 'src') diff --git a/docs/SPEC.md b/docs/SPEC.md index 3eda4f3..f53c469 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -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`, `f64` or `f32` global takes a float literal initializer (`float g = 1.5;`). 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. diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 371e8e8..e9500c4 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -484,7 +484,8 @@ int gdecl2(int t, int tg, int n) { 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/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 -- cgit v1.2.3 From 37faa46eff8210365b144ccb4758dc3967b68b48 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:48:18 +0330 Subject: enum: a member is defined as soon as it is declared, so later values can name it (appendix b.2.2) --- docs/SPEC.md | 4 ++-- src/compiler/builtins.zc | 8 ++++++-- src/compiler/expr.zc | 4 ++++ tests/conformance/2-types/enum_names_own_member.zc | 8 ++++++-- tests/conformance/known.txt | 1 - 5 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/docs/SPEC.md b/docs/SPEC.md index f53c469..5f84acc 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -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. @@ -1105,7 +1105,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the ### b.2 implementation bugs and limitations 1. fixed: `memdebug scrub;` is accepted and poisons freed payloads with 57005 (b.1.1). -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. +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. diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc index 214d493..6dc586a 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; diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index 230a2d3..e449508 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1959,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/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/known.txt b/tests/conformance/known.txt index 22a1e3d..723098a 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -2,7 +2,6 @@ # 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 c appendix b.2.2 confirmed, but spec 2.8.1 documents it (members are defined at the closing brace); not fixed, like b.2.8 the spec body wins until decided 5-declarations/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed 9-appendix-b/b2_06_bool_normalises.zc zy c appendix b.2.6 confirmed, but spec 2.1 documents bool as a number that is not normalised 9-appendix-b/b2_10_recovery_hides_errors.zc zy c appendix b.2.10 confirmed; by design per 8.2 -- cgit v1.2.3 From aadfdb87fdb42b42434358e643da8c98dd4647b2 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:49:25 +0330 Subject: bool: a value stored into a bool is normalised to 0 or 1 (appendix b.2.6) --- docs/SPEC.md | 4 ++-- src/compiler/lex.zc | 5 +++++ tests/conformance/2-types/bool_normalises.zc | 7 +++++++ tests/conformance/2-types/typecheck_number_kinds_convert.zc | 3 ++- tests/conformance/known.txt | 1 - 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 tests/conformance/2-types/bool_normalises.zc (limited to 'src') diff --git a/docs/SPEC.md b/docs/SPEC.md index 5f84acc..529b5bf 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -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 | @@ -1109,7 +1109,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the 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. `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. +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. diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc index 0348cae..d06b81b 100644 --- a/src/compiler/lex.zc +++ b/src/compiler/lex.zc @@ -298,6 +298,11 @@ 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); } + xty = tbool; return 0; + } if (!xfloat) { if (tbytes(dst) && !samesz(dst, xty)) tnorm(dst); return 0; 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/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/known.txt b/tests/conformance/known.txt index 723098a..8550c07 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -3,7 +3,6 @@ # 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/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed -9-appendix-b/b2_06_bool_normalises.zc zy c appendix b.2.6 confirmed, but spec 2.1 documents bool as a number that is not normalised 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 c appendix b.2.11 confirmed, but spec 1.2.2 says an unclosed block comment is not an error 9-appendix-b/b2_12_as_int_star.zc zy c appendix b.2.12 confirmed, but spec 3.2.3 documents it (parenthesise) -- cgit v1.2.3 From ccbc77c2d3fa348d0de6f32fc540468401e6880b Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:50:41 +0330 Subject: lexer: a block comment that is never closed is 'unterminated comment' (appendix b.2.11) --- docs/SPEC.md | 4 ++-- src/compiler/lex.zc | 2 ++ tests/conformance/1-source/comment_block_unclosed.zc | 3 ++- tests/conformance/9-appendix-b/b2_11_unterminated_comment.zc | 2 +- tests/conformance/known.txt | 1 - tests/langerrors/unterminated_comment_docs.zc | 4 ++++ 6 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 tests/langerrors/unterminated_comment_docs.zc (limited to 'src') diff --git a/docs/SPEC.md b/docs/SPEC.md index 529b5bf..cc94451 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. @@ -1114,7 +1114,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the 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. +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. `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. diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc index d06b81b..2763333 100644 --- a/src/compiler/lex.zc +++ b/src/compiler/lex.zc @@ -428,6 +428,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; @@ -489,6 +490,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/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/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/known.txt b/tests/conformance/known.txt index 8550c07..f666106 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 5-declarations/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed 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 c appendix b.2.11 confirmed, but spec 1.2.2 says an unclosed block comment is not an error 9-appendix-b/b2_12_as_int_star.zc zy c appendix b.2.12 confirmed, but spec 3.2.3 documents it (parenthesise) 9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc zy c appendix b.2.14 confirmed, but spec 3.13.5 leaves it unspecified 9-appendix-b/b2_15_duplicate_case.zc zy c appendix b.2.15 confirmed; 4.7.2 says duplicates are allowed 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 -- cgit v1.2.3 From 041c887fc081bbc9a40c3a7381866b9ff6902bc0 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:58:59 +0330 Subject: bool: normalising into a bool keeps an incompatible source type, so the return type check still fires (fixes typeerrors/ret.zc after b.2.6) --- src/compiler/lex.zc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc index 2763333..712eff8 100644 --- a/src/compiler/lex.zc +++ b/src/compiler/lex.zc @@ -301,7 +301,8 @@ int fcoerce(word dst) { 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); } - xty = tbool; return 0; + 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); -- cgit v1.2.3 From 77d2f8a44e2503796a7ef5c170e11811a4f7fcfd Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:58:59 +0330 Subject: prototypes: parameters may be unnamed, int f(int, int*); (appendix b.2.13) --- docs/SPEC.md | 4 ++-- src/compiler/decl.zc | 13 +++++++++++-- .../5-declarations/fn_prototype_unnamed_pointer_params.zc | 8 ++++++++ tests/conformance/known.txt | 1 - tests/langerrors/param_unnamed_with_body.zc | 3 +++ 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 tests/conformance/5-declarations/fn_prototype_unnamed_pointer_params.zc create mode 100644 tests/langerrors/param_unnamed_with_body.zc (limited to 'src') diff --git a/docs/SPEC.md b/docs/SPEC.md index 30d547d..128f961 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -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* 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). @@ -1116,7 +1116,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the 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. 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. prototypes need parameter names: `int f(int);` => `expected a name, found ')'`. +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. `len(s)` on a `char*` returns word 0 of the string when `typecheck` is off. 15. duplicate `case` labels are not diagnosed (4.7.2). 16. a local `const` cannot be used in a constant expression (b.1.7). diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index e9500c4..87281ad 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -282,7 +282,7 @@ 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, redef; + 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; @@ -327,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; @@ -338,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"); 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/known.txt b/tests/conformance/known.txt index 7cb703e..2fbd331 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -2,7 +2,6 @@ # 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/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed 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_14_len_char_ptr_typecheck_off.zc zy c appendix b.2.14 confirmed, but spec 3.13.5 leaves it unspecified 9-appendix-b/b2_15_duplicate_case.zc zy c appendix b.2.15 confirmed; 4.7.2 says duplicates are allowed 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; } -- cgit v1.2.3 From 6ef2efb750622819a3ba3ae9ee061e512c1266d6 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 17:00:26 +0330 Subject: len: len on a char* is an error with typecheck off too (appendix b.2.14) --- docs/SPEC.md | 4 ++-- src/compiler/builtins.zc | 3 ++- tests/conformance/9-appendix-b/b2_14_len_char_ptr_typecheck_off.zc | 4 ++-- tests/conformance/known.txt | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/docs/SPEC.md b/docs/SPEC.md index 128f961..dd04cde 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -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 @@ -1117,7 +1117,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the 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. `len(s)` on a `char*` returns word 0 of the string when `typecheck` is off. +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). diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc index 6dc586a..cbc380e 100644 --- a/src/compiler/builtins.zc +++ b/src/compiler/builtins.zc @@ -706,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/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 2fbd331..8e6ea97 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -3,7 +3,6 @@ # 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 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_14_len_char_ptr_typecheck_off.zc zy c appendix b.2.14 confirmed, but spec 3.13.5 leaves it unspecified 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 1-source/ident_tags_as_variables_glued_star.zc zc0 b u*d with visible u and d is read as a recast (no code); zy follows 1.6.3 -- cgit v1.2.3