strconv: graduate to (T | invalid | overflow); add void expression
`type invalid = i32` (payload: byte index of first bad rune; mirrors
Hare's strconv::invalid = !size) and `type overflow = void` (Hare's
overflow = !void). stoi64/stou64 now return these instead of the
str-error placeholder. atoi64 dropped — lib/CLAUDE.md says graduate
in one go, don't keep both shapes around.
To produce the void variant payload, `void` is now a real
expression literal (TK_VOID kw, N_VOIDLIT). It evaluates to ty_void;
codegen emits MOVQ $0, AX. Both kinds are appended at the tail of
their enums to keep prior numeric values byte-stable for the
wwdump-diff fixtures.
check_file reorder: USE declarations are now installed in pass 1
alongside the type-decl placeholders so dotted type references
(`strconv.invalid` from a typedecl body) resolve. DEF/FN/LET silently
overwrite a USE-occupied slot — matches the old behavior where USE
silently no-op'd when a same-name fn/def existed (the conflict
manifested in selfhost main.combined.ww at `use parse;` colliding
with `export fn parse(a)`).
selfhost mirror: lib/ww/lex/tok.ww kwtab+name; lib/ww/ast.ww
N_VOIDLIT def+print; lib/ww/parse/{expr,parse}.ww TK_VOID handling;
selfhost/cmd/wcc/cgenexpr.ww N_VOIDLIT codegen.
This commit is contained in:
@@ -572,7 +572,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
case N_TRUE: cgexpr_int(c, 1); break;
|
||||
case N_FALSE:
|
||||
case N_NIL: cgexpr_int(c, 0); break;
|
||||
case N_NIL:
|
||||
case N_VOIDLIT: cgexpr_int(c, 0); break;
|
||||
case N_IDENT: {
|
||||
int off = localfind(locals, n->str);
|
||||
if (off != 0) {
|
||||
|
||||
@@ -83,6 +83,7 @@ nkname(Nkind k)
|
||||
case N_MASSIGN: return "massign";
|
||||
case N_TYPETEST: return "typetest";
|
||||
case N_TYPEASSERT: return "typeassert";
|
||||
case N_VOIDLIT: return "voidlit";
|
||||
case N_LAST: return "last";
|
||||
}
|
||||
return "?";
|
||||
|
||||
@@ -408,6 +408,7 @@ cexpr(Checker *c, Node *n)
|
||||
case N_TRUE:
|
||||
case N_FALSE: n->type = ty_untyped_bool; return n->type;
|
||||
case N_NIL: n->type = ty_untyped_nil; return n->type;
|
||||
case N_VOIDLIT: n->type = ty_void; return n->type;
|
||||
case N_IDENT: {
|
||||
if (n->str && n->str[0] == '\0')
|
||||
return n->type = err(c, n->pos,
|
||||
@@ -1201,8 +1202,14 @@ check_file(Checker *c, Node *file)
|
||||
* For self-referential types we install the named-type placeholder
|
||||
* BEFORE resolving its body; the body may legitimately mention
|
||||
* the type itself (`type stream = struct { read: fn(*stream)... }`).
|
||||
*/
|
||||
* USE declarations are installed in this same step so dotted type
|
||||
* references (`strconv.invalid`) resolve when typedecl bodies are
|
||||
* walked in the next pass. */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind == N_USE) {
|
||||
scope_define(c->cur, d->str, SK_USE, NULL, d);
|
||||
continue;
|
||||
}
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
Type *named = type_named(c->a, d->str, NULL);
|
||||
if (!scope_define(c->cur, d->str, SK_TYPE, named, d))
|
||||
@@ -1221,27 +1228,40 @@ check_file(Checker *c, Node *file)
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
switch (d->kind) {
|
||||
case N_USE:
|
||||
scope_define(c->cur, d->str, SK_USE, NULL, d);
|
||||
/* already installed in pass 1; no-op here so the
|
||||
* old fall-through doesn't re-define. */
|
||||
break;
|
||||
case N_DEF: {
|
||||
Type *t = resolve_type(c, d->lhs);
|
||||
d->type = t;
|
||||
if (!scope_define(c->cur, d->str, SK_DEF, t, d))
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev && prev->kind == SK_USE) {
|
||||
prev->kind = SK_DEF; prev->type = t; prev->decl = d;
|
||||
} else if (!scope_define(c->cur, d->str, SK_DEF, t, d))
|
||||
err(c, d->pos, "duplicate def %s", d->str);
|
||||
break;
|
||||
}
|
||||
case N_FNDECL: {
|
||||
Type *t = build_fn_type(c, d);
|
||||
d->type = t;
|
||||
if (!scope_define(c->cur, d->str, SK_FN, t, d))
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev && prev->kind == SK_USE) {
|
||||
prev->kind = SK_FN; prev->type = t; prev->decl = d;
|
||||
} else if (!scope_define(c->cur, d->str, SK_FN, t, d))
|
||||
err(c, d->pos, "duplicate fn %s", d->str);
|
||||
break;
|
||||
}
|
||||
case N_LET: {
|
||||
Type *t = d->lhs ? resolve_type(c, d->lhs) : NULL;
|
||||
d->type = t;
|
||||
if (d->str && d->str[0])
|
||||
scope_define(c->cur, d->str, SK_VAR, t, d);
|
||||
if (d->str && d->str[0]) {
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev && prev->kind == SK_USE) {
|
||||
prev->kind = SK_VAR; prev->type = t;
|
||||
prev->decl = d;
|
||||
} else
|
||||
scope_define(c->cur, d->str, SK_VAR, t, d);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
|
||||
@@ -222,6 +222,16 @@ parsetype(Parser *p)
|
||||
n->lhs = parsetype(p);
|
||||
return n;
|
||||
}
|
||||
case TK_VOID: {
|
||||
/* `void` keyword in type-expr context. Synthesise an
|
||||
* N_TNAME so type-resolution treats it like any other
|
||||
* primitive name. */
|
||||
Node *n = newnode(p->a, N_TNAME, pp);
|
||||
n->str = "void";
|
||||
n->strlen = 4;
|
||||
advance(p);
|
||||
return n;
|
||||
}
|
||||
case TK_IDENT: {
|
||||
Node *n = newnode(p->a, N_TNAME, pp);
|
||||
n->str = p->cur.text;
|
||||
@@ -455,6 +465,7 @@ parseprimary(Parser *p)
|
||||
case TK_TRUE: advance(p); return newnode(p->a, N_TRUE, pp);
|
||||
case TK_FALSE: advance(p); return newnode(p->a, N_FALSE, pp);
|
||||
case TK_NIL: advance(p); return newnode(p->a, N_NIL, pp);
|
||||
case TK_VOID: advance(p); return newnode(p->a, N_VOIDLIT, pp);
|
||||
case TK_UNDER: {
|
||||
/* Bare `_` — valid only as a discard lvalue. We yield an N_IDENT
|
||||
* with empty str; the checker rejects it outside assignment
|
||||
|
||||
@@ -40,7 +40,8 @@ static const struct kwent kwtab[] = {
|
||||
{ "switch", TK_SWITCH },
|
||||
{ "true", TK_TRUE },
|
||||
{ "type", TK_TYPE },
|
||||
{ "use", TK_USE }
|
||||
{ "use", TK_USE },
|
||||
{ "void", TK_VOID }
|
||||
};
|
||||
|
||||
Tkind
|
||||
@@ -91,6 +92,7 @@ tokname(Tkind k)
|
||||
case TK_FALSE: return "false";
|
||||
case TK_AS: return "as";
|
||||
case TK_IS: return "is";
|
||||
case TK_VOID: return "void";
|
||||
case TK_STATIC: return "static";
|
||||
case TK_MATCH: return "match";
|
||||
case TK_CONST: return "const";
|
||||
|
||||
@@ -176,6 +176,7 @@ typedef enum {
|
||||
* to keep the numeric value of every existing kind unchanged —
|
||||
* the selfhost wwdump-diff test (990) is byte-sensitive. */
|
||||
TK_IS, /* Hare-style type test: e is T */
|
||||
TK_VOID, /* `void` — both a type name and a zero-size value */
|
||||
|
||||
TK_LAST /* sentinel for tables */
|
||||
} Tkind;
|
||||
@@ -294,6 +295,7 @@ typedef enum {
|
||||
* byte-for-byte. lhs=value, rhs=variant type expr. */
|
||||
N_TYPETEST, /* lhs is T → bool */
|
||||
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
|
||||
N_VOIDLIT, /* `void` as expression — zero-size void value */
|
||||
|
||||
N_LAST
|
||||
} Nkind;
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
// strconv — number↔string conversions. Decimal i64 to/from a fixed
|
||||
// buffer. Two error idioms ship side by side:
|
||||
// - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the
|
||||
// tagged-union work; kept for callers that already use it.
|
||||
// - Hare style (stoi64/stou64): `(value | str)`. The error
|
||||
// variant carries a short, allocation-free message describing
|
||||
// why the parse failed. Prefer this for new code.
|
||||
// buffer. Error shapes mirror Hare's strconv types: (T | invalid |
|
||||
// overflow) where each error is a named alias over a payload type
|
||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position (Hare
|
||||
// strconv::invalid is `!size` carrying the same).
|
||||
export type invalid = i32;
|
||||
|
||||
// overflow — input was valid but doesn't fit the target type. No
|
||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
||||
export type overflow = void;
|
||||
|
||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||
@@ -64,41 +70,20 @@ export fn i64tos(buf: []u8, v: i64) i32 = {
|
||||
return out;
|
||||
};
|
||||
|
||||
export fn atoi64(s: str) (i64, bool) = {
|
||||
let v: i64 = 0;
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s.len > 0) {
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
};
|
||||
if (i >= s.len) { return 0, false; };
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return 0, false; };
|
||||
if (c > 57u8) { return 0, false; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
if (neg) { v = -v; };
|
||||
return v, true;
|
||||
};
|
||||
|
||||
// stoi64 — Hare-style fallible signed decimal parser. The value
|
||||
// variant is i64; the error variant is a short str (subset of Hare's
|
||||
// (invalid | overflow) tagged-union). No locale, no whitespace, no
|
||||
// underscores: a leading '-' is the only non-digit accepted, and only
|
||||
// at position 0.
|
||||
export fn stoi64(s: str) (i64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stoi64 — Hare-style fallible signed decimal parser. No locale, no
|
||||
// whitespace, no underscores: a leading '-' is the only non-digit
|
||||
// accepted, and only at position 0.
|
||||
export fn stoi64(s: str) (i64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
if (i >= s.len) { return "parse: lone sign"; };
|
||||
if (i >= s.len) { return i: invalid; };
|
||||
let v: i64 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
@@ -106,16 +91,15 @@ export fn stoi64(s: str) (i64 | str) = {
|
||||
return v;
|
||||
};
|
||||
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign. Mirrors
|
||||
// Hare's strconv::stou64.
|
||||
export fn stou64(s: str) (u64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign.
|
||||
export fn stou64(s: str) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let v: u64 = 0u64;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10u64 + ((c: u64) - 48u64);
|
||||
i += 1;
|
||||
};
|
||||
|
||||
@@ -88,8 +88,9 @@ def N_MASSIGN: i32 = 59;
|
||||
// 990_selfhost test diffs astprint against the C side byte-for-byte.
|
||||
def N_TYPETEST: i32 = 60;
|
||||
def N_TYPEASSERT: i32 = 61;
|
||||
def N_VOIDLIT: i32 = 62;
|
||||
|
||||
def N_LAST: i32 = 62;
|
||||
def N_LAST: i32 = 63;
|
||||
|
||||
// ---- Node -------------------------------------------------------------
|
||||
|
||||
@@ -190,6 +191,7 @@ fn nkname(k: i32) str = {
|
||||
if (k == N_MASSIGN) { return "massign"; };
|
||||
if (k == N_TYPETEST) { return "typetest"; };
|
||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||
if (k == N_LAST) { return "last"; };
|
||||
return "?";
|
||||
};
|
||||
|
||||
@@ -108,9 +108,10 @@ def TK_FATARROW: i32 = 81;
|
||||
// Appended at the tail (not grouped with the keyword block) so every
|
||||
// pre-existing TK_* value stays unchanged — the 990_selfhost test
|
||||
// diffs wwdump output against the C side, byte for byte.
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
|
||||
def TK_LAST: i32 = 83;
|
||||
def TK_LAST: i32 = 84;
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
//
|
||||
@@ -181,6 +182,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
||||
if (streqn(p, "true", n)) { return TK_TRUE; };
|
||||
if (streqn(p, "type", n)) { return TK_TYPE; };
|
||||
if (streqn(p, "use", n)) { return TK_USE; };
|
||||
if (streqn(p, "void", n)) { return TK_VOID; };
|
||||
return TK_NONE;
|
||||
};
|
||||
|
||||
@@ -222,6 +224,7 @@ export fn tokname(k: i32) str = {
|
||||
if (k == TK_FALSE) { return "false"; };
|
||||
if (k == TK_AS) { return "as"; };
|
||||
if (k == TK_IS) { return "is"; };
|
||||
if (k == TK_VOID) { return "void"; };
|
||||
if (k == TK_STATIC) { return "static"; };
|
||||
if (k == TK_MATCH) { return "match"; };
|
||||
if (k == TK_CONST) { return "const"; };
|
||||
|
||||
@@ -52,6 +52,10 @@ fn parseprimary(p: *parser) *node = {
|
||||
advance(p);
|
||||
return newnode(p.a, N_NIL, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_VOID) {
|
||||
advance(p);
|
||||
return newnode(p.a, N_VOIDLIT, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_UNDER) {
|
||||
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||
// with empty str; the checker rejects it outside lvalue
|
||||
|
||||
@@ -164,6 +164,15 @@ fn parsetype(p: *parser) *node = {
|
||||
return n;
|
||||
};
|
||||
|
||||
if (p.curkind == TK_VOID) {
|
||||
// `void` keyword in type-expr context — emit as N_TNAME so
|
||||
// resolution treats it like any other primitive name.
|
||||
let n: *node = newnode(p.a, N_TNAME, pf, pl, pc);
|
||||
n.str = "void";
|
||||
advance(p);
|
||||
return n;
|
||||
};
|
||||
|
||||
if (p.curkind == TK_IDENT) {
|
||||
let n: *node = newnode(p.a, N_TNAME, pf, pl, pc);
|
||||
n.str = p.curtext;
|
||||
|
||||
@@ -321,12 +321,18 @@ export fn freearena(a: *arena) void = {
|
||||
|
||||
// MODULE: strconv
|
||||
// strconv — number↔string conversions. Decimal i64 to/from a fixed
|
||||
// buffer. Two error idioms ship side by side:
|
||||
// - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the
|
||||
// tagged-union work; kept for callers that already use it.
|
||||
// - Hare style (stoi64/stou64): `(value | str)`. The error
|
||||
// variant carries a short, allocation-free message describing
|
||||
// why the parse failed. Prefer this for new code.
|
||||
// buffer. Error shapes mirror Hare's strconv types: (T | invalid |
|
||||
// overflow) where each error is a named alias over a payload type
|
||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position (Hare
|
||||
// strconv::invalid is `!size` carrying the same).
|
||||
export type invalid = i32;
|
||||
|
||||
// overflow — input was valid but doesn't fit the target type. No
|
||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
||||
export type overflow = void;
|
||||
|
||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||
@@ -386,41 +392,20 @@ export fn i64tos(buf: []u8, v: i64) i32 = {
|
||||
return out;
|
||||
};
|
||||
|
||||
export fn atoi64(s: str) (i64, bool) = {
|
||||
let v: i64 = 0;
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s.len > 0) {
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
};
|
||||
if (i >= s.len) { return 0, false; };
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return 0, false; };
|
||||
if (c > 57u8) { return 0, false; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
if (neg) { v = -v; };
|
||||
return v, true;
|
||||
};
|
||||
|
||||
// stoi64 — Hare-style fallible signed decimal parser. The value
|
||||
// variant is i64; the error variant is a short str (subset of Hare's
|
||||
// (invalid | overflow) tagged-union). No locale, no whitespace, no
|
||||
// underscores: a leading '-' is the only non-digit accepted, and only
|
||||
// at position 0.
|
||||
export fn stoi64(s: str) (i64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stoi64 — Hare-style fallible signed decimal parser. No locale, no
|
||||
// whitespace, no underscores: a leading '-' is the only non-digit
|
||||
// accepted, and only at position 0.
|
||||
export fn stoi64(s: str) (i64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
if (i >= s.len) { return "parse: lone sign"; };
|
||||
if (i >= s.len) { return i: invalid; };
|
||||
let v: i64 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
@@ -428,16 +413,15 @@ export fn stoi64(s: str) (i64 | str) = {
|
||||
return v;
|
||||
};
|
||||
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign. Mirrors
|
||||
// Hare's strconv::stou64.
|
||||
export fn stou64(s: str) (u64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign.
|
||||
export fn stou64(s: str) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let v: u64 = 0u64;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10u64 + ((c: u64) - 48u64);
|
||||
i += 1;
|
||||
};
|
||||
@@ -555,9 +539,10 @@ def TK_FATARROW: i32 = 81;
|
||||
// Appended at the tail (not grouped with the keyword block) so every
|
||||
// pre-existing TK_* value stays unchanged — the 990_selfhost test
|
||||
// diffs wwdump output against the C side, byte for byte.
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
|
||||
def TK_LAST: i32 = 83;
|
||||
def TK_LAST: i32 = 84;
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
//
|
||||
@@ -628,6 +613,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
||||
if (streqn(p, "true", n)) { return TK_TRUE; };
|
||||
if (streqn(p, "type", n)) { return TK_TYPE; };
|
||||
if (streqn(p, "use", n)) { return TK_USE; };
|
||||
if (streqn(p, "void", n)) { return TK_VOID; };
|
||||
return TK_NONE;
|
||||
};
|
||||
|
||||
@@ -669,6 +655,7 @@ export fn tokname(k: i32) str = {
|
||||
if (k == TK_FALSE) { return "false"; };
|
||||
if (k == TK_AS) { return "as"; };
|
||||
if (k == TK_IS) { return "is"; };
|
||||
if (k == TK_VOID) { return "void"; };
|
||||
if (k == TK_STATIC) { return "static"; };
|
||||
if (k == TK_MATCH) { return "match"; };
|
||||
if (k == TK_CONST) { return "const"; };
|
||||
@@ -1734,8 +1721,9 @@ def N_MASSIGN: i32 = 59;
|
||||
// 990_selfhost test diffs astprint against the C side byte-for-byte.
|
||||
def N_TYPETEST: i32 = 60;
|
||||
def N_TYPEASSERT: i32 = 61;
|
||||
def N_VOIDLIT: i32 = 62;
|
||||
|
||||
def N_LAST: i32 = 62;
|
||||
def N_LAST: i32 = 63;
|
||||
|
||||
// ---- Node -------------------------------------------------------------
|
||||
|
||||
@@ -1836,6 +1824,7 @@ fn nkname(k: i32) str = {
|
||||
if (k == N_MASSIGN) { return "massign"; };
|
||||
if (k == N_TYPETEST) { return "typetest"; };
|
||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||
if (k == N_LAST) { return "last"; };
|
||||
return "?";
|
||||
};
|
||||
@@ -2043,6 +2032,10 @@ fn parseprimary(p: *parser) *node = {
|
||||
advance(p);
|
||||
return newnode(p.a, N_NIL, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_VOID) {
|
||||
advance(p);
|
||||
return newnode(p.a, N_VOIDLIT, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_UNDER) {
|
||||
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||
// with empty str; the checker rejects it outside lvalue
|
||||
@@ -3011,6 +3004,15 @@ fn parsetype(p: *parser) *node = {
|
||||
return n;
|
||||
};
|
||||
|
||||
if (p.curkind == TK_VOID) {
|
||||
// `void` keyword in type-expr context — emit as N_TNAME so
|
||||
// resolution treats it like any other primitive name.
|
||||
let n: *node = newnode(p.a, N_TNAME, pf, pl, pc);
|
||||
n.str = "void";
|
||||
advance(p);
|
||||
return n;
|
||||
};
|
||||
|
||||
if (p.curkind == TK_IDENT) {
|
||||
let n: *node = newnode(p.a, N_TNAME, pf, pl, pc);
|
||||
n.str = p.curtext;
|
||||
@@ -4900,6 +4902,12 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (k == N_VOIDLIT) {
|
||||
// void value: zero-size, but the consumer's ABI expects a
|
||||
// deterministic AX. Emit 0 like nil/false do.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
return;
|
||||
};
|
||||
|
||||
if (k == N_IDENT) { cgident(c, n); return; };
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (k == N_VOIDLIT) {
|
||||
// void value: zero-size, but the consumer's ABI expects a
|
||||
// deterministic AX. Emit 0 like nil/false do.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
return;
|
||||
};
|
||||
|
||||
if (k == N_IDENT) { cgident(c, n); return; };
|
||||
|
||||
|
||||
@@ -321,12 +321,18 @@ export fn freearena(a: *arena) void = {
|
||||
|
||||
// MODULE: strconv
|
||||
// strconv — number↔string conversions. Decimal i64 to/from a fixed
|
||||
// buffer. Two error idioms ship side by side:
|
||||
// - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the
|
||||
// tagged-union work; kept for callers that already use it.
|
||||
// - Hare style (stoi64/stou64): `(value | str)`. The error
|
||||
// variant carries a short, allocation-free message describing
|
||||
// why the parse failed. Prefer this for new code.
|
||||
// buffer. Error shapes mirror Hare's strconv types: (T | invalid |
|
||||
// overflow) where each error is a named alias over a payload type
|
||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position (Hare
|
||||
// strconv::invalid is `!size` carrying the same).
|
||||
export type invalid = i32;
|
||||
|
||||
// overflow — input was valid but doesn't fit the target type. No
|
||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
||||
export type overflow = void;
|
||||
|
||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||
@@ -386,41 +392,20 @@ export fn i64tos(buf: []u8, v: i64) i32 = {
|
||||
return out;
|
||||
};
|
||||
|
||||
export fn atoi64(s: str) (i64, bool) = {
|
||||
let v: i64 = 0;
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s.len > 0) {
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
};
|
||||
if (i >= s.len) { return 0, false; };
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return 0, false; };
|
||||
if (c > 57u8) { return 0, false; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
if (neg) { v = -v; };
|
||||
return v, true;
|
||||
};
|
||||
|
||||
// stoi64 — Hare-style fallible signed decimal parser. The value
|
||||
// variant is i64; the error variant is a short str (subset of Hare's
|
||||
// (invalid | overflow) tagged-union). No locale, no whitespace, no
|
||||
// underscores: a leading '-' is the only non-digit accepted, and only
|
||||
// at position 0.
|
||||
export fn stoi64(s: str) (i64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stoi64 — Hare-style fallible signed decimal parser. No locale, no
|
||||
// whitespace, no underscores: a leading '-' is the only non-digit
|
||||
// accepted, and only at position 0.
|
||||
export fn stoi64(s: str) (i64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
if (i >= s.len) { return "parse: lone sign"; };
|
||||
if (i >= s.len) { return i: invalid; };
|
||||
let v: i64 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
@@ -428,16 +413,15 @@ export fn stoi64(s: str) (i64 | str) = {
|
||||
return v;
|
||||
};
|
||||
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign. Mirrors
|
||||
// Hare's strconv::stou64.
|
||||
export fn stou64(s: str) (u64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign.
|
||||
export fn stou64(s: str) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let v: u64 = 0u64;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10u64 + ((c: u64) - 48u64);
|
||||
i += 1;
|
||||
};
|
||||
@@ -555,9 +539,10 @@ def TK_FATARROW: i32 = 81;
|
||||
// Appended at the tail (not grouped with the keyword block) so every
|
||||
// pre-existing TK_* value stays unchanged — the 990_selfhost test
|
||||
// diffs wwdump output against the C side, byte for byte.
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_IS: i32 = 82;
|
||||
def TK_VOID: i32 = 83;
|
||||
|
||||
def TK_LAST: i32 = 83;
|
||||
def TK_LAST: i32 = 84;
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
//
|
||||
@@ -628,6 +613,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
|
||||
if (streqn(p, "true", n)) { return TK_TRUE; };
|
||||
if (streqn(p, "type", n)) { return TK_TYPE; };
|
||||
if (streqn(p, "use", n)) { return TK_USE; };
|
||||
if (streqn(p, "void", n)) { return TK_VOID; };
|
||||
return TK_NONE;
|
||||
};
|
||||
|
||||
@@ -669,6 +655,7 @@ export fn tokname(k: i32) str = {
|
||||
if (k == TK_FALSE) { return "false"; };
|
||||
if (k == TK_AS) { return "as"; };
|
||||
if (k == TK_IS) { return "is"; };
|
||||
if (k == TK_VOID) { return "void"; };
|
||||
if (k == TK_STATIC) { return "static"; };
|
||||
if (k == TK_MATCH) { return "match"; };
|
||||
if (k == TK_CONST) { return "const"; };
|
||||
@@ -1734,8 +1721,9 @@ def N_MASSIGN: i32 = 59;
|
||||
// 990_selfhost test diffs astprint against the C side byte-for-byte.
|
||||
def N_TYPETEST: i32 = 60;
|
||||
def N_TYPEASSERT: i32 = 61;
|
||||
def N_VOIDLIT: i32 = 62;
|
||||
|
||||
def N_LAST: i32 = 62;
|
||||
def N_LAST: i32 = 63;
|
||||
|
||||
// ---- Node -------------------------------------------------------------
|
||||
|
||||
@@ -1836,6 +1824,7 @@ fn nkname(k: i32) str = {
|
||||
if (k == N_MASSIGN) { return "massign"; };
|
||||
if (k == N_TYPETEST) { return "typetest"; };
|
||||
if (k == N_TYPEASSERT) { return "typeassert"; };
|
||||
if (k == N_VOIDLIT) { return "voidlit"; };
|
||||
if (k == N_LAST) { return "last"; };
|
||||
return "?";
|
||||
};
|
||||
@@ -2043,6 +2032,10 @@ fn parseprimary(p: *parser) *node = {
|
||||
advance(p);
|
||||
return newnode(p.a, N_NIL, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_VOID) {
|
||||
advance(p);
|
||||
return newnode(p.a, N_VOIDLIT, pf, pl, pc);
|
||||
};
|
||||
if (p.curkind == TK_UNDER) {
|
||||
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||
// with empty str; the checker rejects it outside lvalue
|
||||
@@ -3011,6 +3004,15 @@ fn parsetype(p: *parser) *node = {
|
||||
return n;
|
||||
};
|
||||
|
||||
if (p.curkind == TK_VOID) {
|
||||
// `void` keyword in type-expr context — emit as N_TNAME so
|
||||
// resolution treats it like any other primitive name.
|
||||
let n: *node = newnode(p.a, N_TNAME, pf, pl, pc);
|
||||
n.str = "void";
|
||||
advance(p);
|
||||
return n;
|
||||
};
|
||||
|
||||
if (p.curkind == TK_IDENT) {
|
||||
let n: *node = newnode(p.a, N_TNAME, pf, pl, pc);
|
||||
n.str = p.curtext;
|
||||
@@ -4900,6 +4902,12 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
return;
|
||||
};
|
||||
if (k == N_VOIDLIT) {
|
||||
// void value: zero-size, but the consumer's ABI expects a
|
||||
// deterministic AX. Emit 0 like nil/false do.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
return;
|
||||
};
|
||||
|
||||
if (k == N_IDENT) { cgident(c, n); return; };
|
||||
|
||||
|
||||
@@ -213,12 +213,18 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
|
||||
// MODULE: strconv
|
||||
// strconv — number↔string conversions. Decimal i64 to/from a fixed
|
||||
// buffer. Two error idioms ship side by side:
|
||||
// - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the
|
||||
// tagged-union work; kept for callers that already use it.
|
||||
// - Hare style (stoi64/stou64): `(value | str)`. The error
|
||||
// variant carries a short, allocation-free message describing
|
||||
// why the parse failed. Prefer this for new code.
|
||||
// buffer. Error shapes mirror Hare's strconv types: (T | invalid |
|
||||
// overflow) where each error is a named alias over a payload type
|
||||
// (Hare uses !size / !void; ww uses i32 / void without the `!` mark).
|
||||
|
||||
// invalid — input wasn't a valid number in the requested format.
|
||||
// Payload is the byte index of the first offending position (Hare
|
||||
// strconv::invalid is `!size` carrying the same).
|
||||
export type invalid = i32;
|
||||
|
||||
// overflow — input was valid but doesn't fit the target type. No
|
||||
// payload (a single yes/no signal). Mirrors Hare's `!void` shape.
|
||||
export type overflow = void;
|
||||
|
||||
// u64tos — write `v` in decimal into `buf` and return the byte count.
|
||||
// Hare name; the buffer-in shape is the sanctioned Plan 9 subset of
|
||||
@@ -278,41 +284,20 @@ export fn i64tos(buf: []u8, v: i64) i32 = {
|
||||
return out;
|
||||
};
|
||||
|
||||
export fn atoi64(s: str) (i64, bool) = {
|
||||
let v: i64 = 0;
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s.len > 0) {
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
};
|
||||
if (i >= s.len) { return 0, false; };
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return 0, false; };
|
||||
if (c > 57u8) { return 0, false; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
if (neg) { v = -v; };
|
||||
return v, true;
|
||||
};
|
||||
|
||||
// stoi64 — Hare-style fallible signed decimal parser. The value
|
||||
// variant is i64; the error variant is a short str (subset of Hare's
|
||||
// (invalid | overflow) tagged-union). No locale, no whitespace, no
|
||||
// underscores: a leading '-' is the only non-digit accepted, and only
|
||||
// at position 0.
|
||||
export fn stoi64(s: str) (i64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stoi64 — Hare-style fallible signed decimal parser. No locale, no
|
||||
// whitespace, no underscores: a leading '-' is the only non-digit
|
||||
// accepted, and only at position 0.
|
||||
export fn stoi64(s: str) (i64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let i: i32 = 0;
|
||||
let neg: bool = false;
|
||||
if (s[0] == 45u8) { neg = true; i = 1; };
|
||||
if (i >= s.len) { return "parse: lone sign"; };
|
||||
if (i >= s.len) { return i: invalid; };
|
||||
let v: i64 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10 + ((c: i64) - 48);
|
||||
i += 1;
|
||||
};
|
||||
@@ -320,16 +305,15 @@ export fn stoi64(s: str) (i64 | str) = {
|
||||
return v;
|
||||
};
|
||||
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign. Mirrors
|
||||
// Hare's strconv::stou64.
|
||||
export fn stou64(s: str) (u64 | str) = {
|
||||
if (s.len == 0) { return "parse: empty"; };
|
||||
// stou64 — fallible unsigned decimal parser. No leading sign.
|
||||
export fn stou64(s: str) (u64 | invalid | overflow) = {
|
||||
if (s.len == 0) { return 0: invalid; };
|
||||
let v: u64 = 0u64;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let c: u8 = s[i];
|
||||
if (c < 48u8) { return "parse: invalid digit"; };
|
||||
if (c > 57u8) { return "parse: invalid digit"; };
|
||||
if (c < 48u8) { return i: invalid; };
|
||||
if (c > 57u8) { return i: invalid; };
|
||||
v = v * 10u64 + ((c: u64) - 48u64);
|
||||
i += 1;
|
||||
};
|
||||
|
||||
@@ -744,46 +744,53 @@ static const struct row rows[] = {
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 13 }, /* 1 byte written to fd 1, plus len(\"write failed\")=12 */
|
||||
/* strconv.stoi64: fallible signed decimal. Two successful
|
||||
* parses contribute their values; one bad parse contributes
|
||||
* the error message length (20 = len(\"parse: invalid digit\")). */
|
||||
/* strconv.stoi64: fallible signed decimal, graduated to
|
||||
* (i64 | invalid | overflow). invalid carries the offending
|
||||
* index; overflow is the void variant. */
|
||||
{ "use strconv;\n"
|
||||
"type r_t = (i64 | strconv.invalid | strconv.overflow);\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r1: (i64 | str) = strconv.stoi64(\"42\");\n"
|
||||
" let r2: (i64 | str) = strconv.stoi64(\"-7\");\n"
|
||||
" let r3: (i64 | str) = strconv.stoi64(\"abc\");\n"
|
||||
" let r1: r_t = strconv.stoi64(\"42\");\n"
|
||||
" let r2: r_t = strconv.stoi64(\"-7\");\n"
|
||||
" let r3: r_t = strconv.stoi64(\"abc\");\n"
|
||||
" let acc: i32 = 0;\n"
|
||||
" match (r1) {\n"
|
||||
" case let v: i64 => acc += v: i32;\n"
|
||||
" case let e: str => acc += -100;\n"
|
||||
" case let e: strconv.invalid => acc += -100;\n"
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" };\n"
|
||||
" match (r2) {\n"
|
||||
" case let v: i64 => acc += v: i32;\n"
|
||||
" case let e: str => acc += -100;\n"
|
||||
" case let e: strconv.invalid => acc += -100;\n"
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" };\n"
|
||||
" match (r3) {\n"
|
||||
" case let v: i64 => acc += -100;\n"
|
||||
" case let e: str => acc += e.len: i32;\n"
|
||||
" case let e: strconv.invalid => acc += e: i32;\n"
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 55 }, /* 42 + (-7) + 20 */
|
||||
/* strconv.stou64: success path 123, error path captures
|
||||
* len(\"parse: invalid digit\") = 20 for the leading-sign reject. */
|
||||
"};", 35 }, /* 42 + (-7) + 0 (invalid at index 0 in \"abc\") */
|
||||
/* strconv.stou64: success path; leading-sign rejected with
|
||||
* invalid carrying the offending index. */
|
||||
{ "use strconv;\n"
|
||||
"type r_t = (u64 | strconv.invalid | strconv.overflow);\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r1: (u64 | str) = strconv.stou64(\"123\");\n"
|
||||
" let r2: (u64 | str) = strconv.stou64(\"-1\");\n"
|
||||
" let r1: r_t = strconv.stou64(\"123\");\n"
|
||||
" let r2: r_t = strconv.stou64(\"-1\");\n"
|
||||
" let acc: i32 = 0;\n"
|
||||
" match (r1) {\n"
|
||||
" case let v: u64 => acc += v: i32;\n"
|
||||
" case let e: str => acc += -100;\n"
|
||||
" case let e: strconv.invalid => acc += -100;\n"
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" };\n"
|
||||
" match (r2) {\n"
|
||||
" case let v: u64 => acc += -100;\n"
|
||||
" case let e: str => acc += e.len: i32;\n"
|
||||
" case let e: strconv.invalid => acc += e: i32;\n"
|
||||
" case let e: strconv.overflow => acc += -200;\n"
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 143 }, /* 123 + 20 */
|
||||
"};", 123 }, /* 123 + 0 (invalid at index 0 in \"-1\") */
|
||||
/* strings.byteindex and strings.index: now (i32 | void). */
|
||||
{ "use strings;\n"
|
||||
"fn pick(r: (i32 | void), miss: i32) i32 = {\n"
|
||||
|
||||
Reference in New Issue
Block a user