diff --git a/.trinity/seals/t27b_T27bTsEmit.json b/.trinity/seals/t27b_T27bTsEmit.json new file mode 100644 index 0000000000..08c6cb24eb --- /dev/null +++ b/.trinity/seals/t27b_T27bTsEmit.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+a1b584b29", + "closure": [ + { + "spec": "specs/tri/t27b/text_escape.t27", + "spec_hash": "sha256:c88981799b2e3f97cd0815471d288e48550f5a785c504412546ca58c0b19656a" + }, + { + "spec": "specs/tri/t27b/ts_plan.t27", + "spec_hash": "sha256:d046e9702ec1a0a9db5b9d8666e0bc8f7f38361f1bc320e5d9420931f7887e46" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:91d80cba62f45cc2dd2e052287b1336b88a00fec91e65f98653f7ae2f90bebc1", + "gen_hash_rust": "sha256:a902659f2f30c887876e487094668138d7fcd90f266c7ca4a56e859cc94bff34", + "gen_hash_verilog": "sha256:204115c5a4cbcac65f725db2cc393b9b909ce9faedf4132ad05feb2f5c0aa736", + "gen_hash_zig": "sha256:1eaa2c4f003d1912304766da67ddd8679b0dbc86edf6101344fbdb4f5dd68971", + "module": "T27bTsEmit", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-17T17:23:04Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:bc52c75db0f5923f9b3bdd4f9373e0d2b45ae62c7119eb135519c25d3d419ecb", + "spec_path": "specs/tri/t27b/ts_emit.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 4, + "total": 4, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27b_T27bTsPlan.json b/.trinity/seals/t27b_T27bTsPlan.json new file mode 100644 index 0000000000..56d1b99659 --- /dev/null +++ b/.trinity/seals/t27b_T27bTsPlan.json @@ -0,0 +1,33 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+a1b584b29", + "closure": [ + { + "spec": "specs/tri/t27b/verilog_plan.t27", + "spec_hash": "sha256:6178c276f6928f211b1cb65f470a93a5fcfbb11119bad7a26030072d777e8e70" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:c6741e3f49b460e9b22518adc6e429d46d7b4f42db3eef859fd570e191f1326d", + "gen_hash_rust": "sha256:228558265ac328ea4bd7696e216554b2bfd804ad2b363981ce3716d299a0c170", + "gen_hash_verilog": "sha256:dd6906ff28d579f1d59194cc69199f1abf934dea86141846680233d97976ad8d", + "gen_hash_zig": "sha256:d4255ad99d97654379ea3e017fbc087776752b51eadf3ec87b75d087073485c8", + "module": "T27bTsPlan", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-17T17:23:01Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:d046e9702ec1a0a9db5b9d8666e0bc8f7f38361f1bc320e5d9420931f7887e46", + "spec_path": "specs/tri/t27b/ts_plan.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 6, + "total": 6, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/cli/t27b/README.md b/cli/t27b/README.md index 1a2294adb6..948a0b587f 100644 --- a/cli/t27b/README.md +++ b/cli/t27b/README.md @@ -311,6 +311,7 @@ t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--bl t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] t27b asm [--overflow trap|wrap] t27b verilog +t27b ts t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list] [--json ] [--runner " [args]"] [--blockers [--reference [--reference-cache ] @@ -332,6 +333,29 @@ refused by name, exit 2. Every decision is in `specs/tri/t27b/verilog_plan.t27`: The writer is `specs/tri/t27b/verilog_emit.t27`. The fixtures are `specs/tri/t27b/conformance/verilog_*.t27` (#8796). +`t27b ts` writes the spec's consts, its pure fns over `u8`..`u32`, `i8`..`i32` +and `bool`, and its tests as one TypeScript module to stdout (#9994). Each +`test` is an exported self-check that returns how many asserts it ran, and +`t27_report()` prints the assert table of `t27c test-report`, line for line. +Each `invariant` is an exported self-check too. `t27_failures()` names each +check that throws. The integers keep Zig's meaning: `+ - *` throw on overflow, +`+% -% *%` wrap, and a narrowing cast truncates. An assert whose operands are +all compile-time constants is checked but not counted, the same as +test-report. Anything outside that subset is refused, exit 2, with +``t27b: ts: unsupported construct `` at line ``. + +The decisions are in `specs/tri/t27b/ts_plan.t27`: the types, the operator +forms, folding, reserved names, and the prelude and coda. The writer is +`specs/tri/t27b/ts_emit.t27`. Node 24 runs the output as `.mts`: + +``` +t27b ts specs/queen/key_failover.t27 > /tmp/kf.mts +node -e 'import("/tmp/kf.mts").then(m => console.log(m.t27_report()))' +``` + +A top-level declaration that is refused reports `at line 0`, because the t27c +parser records no line for it. + Options: * `--time`: prints per-phase milliseconds to stderr: read, parse, typecheck, diff --git a/cli/t27b/src/lower.rs b/cli/t27b/src/lower.rs index 1a01f26da7..691452e542 100644 --- a/cli/t27b/src/lower.rs +++ b/cli/t27b/src/lower.rs @@ -7393,12 +7393,11 @@ fn flat<'n>(ns: impl IntoIterator) -> (Vec, Vec) { (a, t) } const _: () = assert!(NodeKind::StmtExpr as u8 == aw::K_STMT_EXPR && NodeKind::ExprRange as u8 == aw::K_EXPR_RANGE); -#[path = "../../../gen/rust/tri/t27b/verilog_emit.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] -mod ve; // t27c gen-rust of specs/tri/t27b/verilog_emit.t27: `t27b verilog` (#8796) -#[path = "../../../gen/rust/tri/t27b/verilog_plan.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] -mod vp; // t27c gen-rust of specs/tri/t27b/verilog_plan.t27: hw_consts, the consts only the hardware reads -/// `t27b verilog`: `flat`'s bytes and per node four u32 (flags 1 var, 2 pub; line; its params' span in `p`). -pub fn verilog(ast: &Node) -> Result { +#[path = "../../../gen/rust/tri/t27b/verilog_emit.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] mod ve; // t27c gen-rust of specs/tri/t27b/verilog_emit.t27: `t27b verilog` (#8796) +#[path = "../../../gen/rust/tri/t27b/verilog_plan.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] mod vp; // t27c gen-rust of specs/tri/t27b/verilog_plan.t27: hw_consts, the consts only the hardware reads +#[path = "../../../gen/rust/tri/t27b/ts_emit.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] mod tse; // specs/tri/t27b/ts_emit.t27: `t27b ts` (#9994) +/// `t27b verilog` (`t27b ts` when `ts`): `flat`'s bytes and per node four u32 (flags 1 var, 2 pub; line; its params' span in `p`). +pub fn verilog(ast: &Node, ts: bool) -> Result { fn meta(n: &Node, m: &mut Vec, p: &mut Vec) { let lo = p.len() as u32; n.params.iter().for_each(|(a, b)| p.extend(format!("{a}:{b}\n").bytes())); for v in [n.extra_mutable as u32 | (n.extra_pub as u32) << 1, n.line, lo, p.len() as u32] { m.extend(v.to_le_bytes()) } @@ -7407,7 +7406,7 @@ pub fn verilog(ast: &Node) -> Result { let ((a, t), mut m, mut p) = (flat([ast]), Vec::new(), Vec::new()); meta(ast, &mut m, &mut p); let (mut w, mut out) = (vec![0u8; a.len() / aw::REC * 2 + 70], vec![0u8; 4 * (a.len() + t.len()) + (1 << 20)]); - let n = ve::emit(&a, &t, &m, &p, &mut w, &mut out); + let n = (if ts { tse::emit } else { ve::emit })(&a, &t, &m, &p, &mut w, &mut out); let s = String::from_utf8_lossy(&out[..n.unsigned_abs() as usize]).into_owned(); (n >= 0).then(|| s.clone()).ok_or(s) } diff --git a/cli/t27b/src/main.rs b/cli/t27b/src/main.rs index 53f8216258..148213e750 100644 --- a/cli/t27b/src/main.rs +++ b/cli/t27b/src/main.rs @@ -68,6 +68,7 @@ const USAGE: &str = "usage: t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] t27b asm [--overflow trap|wrap] t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH) + t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27) t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list] [--json ] [--runner \" [args]\"] [--blockers [--reference [--reference-cache ] @@ -212,7 +213,7 @@ fn main() -> ExitCode { Err(e) => return usage(&e), }; match o.cmd.as_str() { - "test" | "build" | "asm" | "verilog" => { + "test" | "build" | "asm" | "verilog" | "ts" => { let Some(input) = o.input.clone() else { return usage("missing input file"); }; @@ -267,8 +268,8 @@ fn compile_front(input: &str, o: &Opts) -> Result<(Program, Phases), u8> { } return Err(EXIT_FRONTEND); } - if o.cmd == "verilog" { - return Err(lower::verilog(&parsed.ast).map_or_else(|e| { errln!("{}", e); EXIT_UNSUPPORTED }, |v| { out(&v); 0 })); + if o.cmd == "verilog" || o.cmd == "ts" { + return Err(lower::verilog(&parsed.ast, o.cmd == "ts").map_or_else(|e| { errln!("{}", e); EXIT_UNSUPPORTED }, |v| { out(&v); 0 })); } if o.blockers { // Every unsupported construct, not just the first; a file with none diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index c041cbab56..f2795aa6e5 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -1329,6 +1329,8 @@ {"path": "specs/tri/t27b/stdmem_plan.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/steward.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/text_escape.t27", "t27b": "pass"}, + {"path": "specs/tri/t27b/ts_emit.t27", "t27b": "pass"}, + {"path": "specs/tri/t27b/ts_plan.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/tuple_local_plan.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/type_text.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/undefined_arg_plan.t27", "t27b": "pass"}, diff --git a/gen/rust/tri/t27b/ts_emit.rs b/gen/rust/tri/t27b/ts_emit.rs new file mode 100644 index 0000000000..dd867e023c --- /dev/null +++ b/gen/rust/tri/t27b/ts_emit.rs @@ -0,0 +1,2561 @@ +// Generated from .t27 spec +// DO NOT EDIT — generated by t27c +// NOT LOWERED BY THIS BACKEND: 4 test(s), 0 invariant(s), 0 function(s) with no body. +// This backend emits declarations only. The spec's checks live in +// the Zig and Verilog outputs; do not read this file as verified. + +pub const MSG_ROOM: usize = 400; + +pub const NAME_CAP: usize = 120; + +pub const SYM_UNIT: i64 = 1099511627776; + +pub const SYM_LOCAL: i64 = 1; + +pub const SYM_PARAM: i64 = 2; + +pub const SYM_TOP: i64 = 3; + +pub const SYM_FN: i64 = 4; + +pub const NO_SYM: i64 = -1; + +pub fn put_bytes(out: &mut [u8], at: usize, s: &[u8], a: usize, b: usize) -> usize { + let mut k: usize = a; + let mut q: usize = at; + while (k < b) { + out[(q) as usize] = s[(k) as usize]; + q = (q + 1); + k = (k + 1); + } + return q; +} + +pub fn dec_len(v: u64) -> usize { + let mut n: usize = 1; + let mut x: u64 = v; + while (x >= 10) { + x = (x / 10); + n = (n + 1); + } + return n; +} + +pub fn put_dec(out: &mut [u8], at: usize, v: u64) -> usize { + let n: usize = dec_len(v); + let mut x: u64 = v; + let mut k: usize = n; + while (k > 0) { + let db: u8 = (((x % 10) + 48) as _); + out[(((at + k) - 1)) as usize] = db; + x = (x / 10); + k = (k - 1); + } + return (at + n); +} + +pub fn put_ind(out: &mut [u8], at: usize, n: usize) -> usize { + let mut k: usize = 0; + while (k < n) { + out[((at + k)) as usize] = 32; + k = (k + 1); + } + return (at + n); +} + +pub fn put_i64(out: &mut [u8], at: usize, v: i64) -> usize { + if (v < 0) { + let mut q: usize = put_text(out, at, "(-"); + let m: u64 = ((0 - v) as u64); + q = put_dec(out, q, m); + return put_text(out, q, ")"); + } + let u: u64 = (v as u64); + return put_dec(out, at, u); +} + +pub fn put_lit(out: &mut [u8], at: usize, v: i64, t: i64) -> usize { + if (t == TS_BOOL) { + if (v == 0) { + return put_text(out, at, "false"); + } + return put_text(out, at, "true"); + } + return put_i64(out, at, v); +} + +pub fn put_sign(out: &mut [u8], at: usize, t: i64) -> usize { + if ts_signed(t) { + return put_text(out, at, ", true)"); + } + return put_text(out, at, ", false)"); +} + +pub fn line_near(nodes: &[u8], meta: &[u8], i: usize) -> usize { + let mut j: usize = i; + while (j < end(nodes, i)) { + if (line_of(meta, j) != 0) { + return line_of(meta, j); + } + j = (j + 1); + } + let mut k: usize = i; + while (k > 0) { + k = (k - 1); + if (line_of(meta, k) != 0) { + return line_of(meta, k); + } + } + return 0; +} + +pub fn fail(out: &mut [u8], nodes: &[u8], meta: &[u8], text: &[u8], i: usize, what: &'static str, a: usize, b: usize) -> i64 { + let base: usize = (out.len() - MSG_ROOM); + let mut q: usize = put_text(out, base, "t27b: ts: unsupported construct "); + q = put_text(out, q, what); + if (b > a) { + let mut nb: usize = b; + if ((nb - a) > NAME_CAP) { + nb = (a + NAME_CAP); + } + q = put_text(out, q, " `"); + q = put_bytes(out, q, text, a, nb); + q = put_text(out, q, "`"); + } + q = put_text(out, q, " at line "); + q = put_dec(out, q, (line_near(nodes, meta, i) as u64)); + let len: i64 = ((q - base) as _); + return (0 - len); +} + +pub fn to_front(out: &mut [u8], n: usize) -> i64 { + let base: usize = (out.len() - MSG_ROOM); + let mut k: usize = 0; + while (k < n) { + out[(k) as usize] = out[((base + k)) as usize]; + k = (k + 1); + } + let ni: i64 = (n as _); + return (0 - ni); +} + +pub fn fail_front(out: &mut [u8], what: &'static str) -> i64 { + let mut q: usize = put_text(out, 0, "t27b: ts: unsupported construct "); + q = put_text(out, q, what); + let l: i64 = (q as _); + return (0 - l); +} + +pub fn name_char(c: u8, first: bool) -> bool { + if (c == 95) { + return true; + } + if (((c >= 97) && (c <= 122)) || ((c >= 65) && (c <= 90))) { + return true; + } + return ((!(first) && (c >= 48)) && (c <= 57)); +} + +pub fn is_ident(s: &[u8], a: usize, b: usize) -> bool { + if (b <= a) { + return false; + } + if !(name_char(s[(a) as usize], true)) { + return false; + } + let mut j: usize = (a + 1); + while (j < b) { + if !(name_char(s[(j) as usize], false)) { + return false; + } + j = (j + 1); + } + return true; +} + +pub fn bad_name(s: &[u8], a: usize, b: usize) -> bool { + if !(is_ident(s, a, b)) { + return true; + } + return ts_reserved(&s[(a) as usize..(b) as usize]); +} + +pub fn child_holding(nodes: &[u8], c: usize, d: usize) -> usize { + let mut x: usize = (c + 1); + while (x < end(nodes, c)) { + if (d < end(nodes, x)) { + return x; + } + x = end(nodes, x); + } + return c; +} + +pub fn parent_of(nodes: &[u8], root: usize, d: usize) -> usize { + let mut cur: usize = root; + while true { + let nx: usize = child_holding(nodes, cur, d); + if ((nx == d) || (nx == cur)) { + return cur; + } + cur = nx; + } + return cur; +} + +pub fn in_scope(nodes: &[u8], item: usize, d: usize, u: usize) -> bool { + if ((d >= u) || (u < end(nodes, d))) { + return false; + } + let pb: usize = parent_of(nodes, item, d); + return (u < end(nodes, pb)); +} + +pub fn param_index(text: &[u8], meta: &[u8], ptext: &[u8], item: usize, a: usize, b: usize) -> i64 { + let pa: usize = params_lo(meta, item); + let pb: usize = params_hi(meta, item); + let mut k: usize = 0; + let mut s: usize = pa; + while (s < pb) { + let e: usize = line_stop(ptext, s, pb); + let c: usize = colon_at(ptext, s, e); + if (((c - s) == (b - a)) && has_at(ptext, s, c, &text[(a) as usize..(b) as usize])) { + let ki: i64 = (k as _); + return ki; + } + k = (k + 1); + s = (e + 1); + } + return -(1); +} + +pub fn param_name_lo(meta: &[u8], ptext: &[u8], item: usize, k: usize) -> usize { + return param_at(ptext, params_lo(meta, item), params_hi(meta, item), k); +} + +pub fn param_name_hi(meta: &[u8], ptext: &[u8], item: usize, k: usize) -> usize { + let s: usize = param_name_lo(meta, ptext, item, k); + return colon_at(ptext, s, line_stop(ptext, s, params_hi(meta, item))); +} + +pub fn param_type(meta: &[u8], ptext: &[u8], item: usize, k: usize) -> i64 { + let s: usize = param_name_lo(meta, ptext, item, k); + let e: usize = line_stop(ptext, s, params_hi(meta, item)); + let c: usize = colon_at(ptext, s, e); + if (c >= e) { + return TS_BAD; + } + return ts_type(ptext, (c + 1), e); +} + +pub fn resolve(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, u: usize, a: usize, b: usize) -> i64 { + if (item > 0) { + let mut j: usize = u; + while (j > (item + 1)) { + j = (j - 1); + if ((kind(nodes, j) == K_STMT_LOCAL) && field_is(nodes, text, j, F_NAME, &text[(a) as usize..(b) as usize])) { + if in_scope(nodes, item, j, u) { + let ji: i64 = (j as _); + return ((SYM_LOCAL * SYM_UNIT) + ji); + } + } + } + if (kind(nodes, item) == K_FN_DECL) { + let pk: i64 = param_index(text, meta, ptext, item, a, b); + if (pk >= 0) { + return ((SYM_PARAM * SYM_UNIT) + pk); + } + } + } + let d: i64 = find_decl(nodes, text, a, b); + if (d != NO_NODE) { + return ((SYM_TOP * SYM_UNIT) + d); + } + let f: i64 = find_fn(nodes, text, a, b); + if (f != NO_NODE) { + return ((SYM_FN * SYM_UNIT) + f); + } + return NO_SYM; +} + +pub fn sym_of(tag: i64, at: usize) -> i64 { + let ai: i64 = (at as _); + return ((tag * SYM_UNIT) + ai); +} + +pub fn sym_tag(s: i64) -> i64 { + let t: i64 = (s / SYM_UNIT); + return t; +} + +pub fn sym_at(s: i64) -> usize { + let r: i64 = (s % SYM_UNIT); + let ru: usize = (r as _); + return ru; +} + +pub fn sym_type(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, s: i64, depth: usize) -> i64 { + let tg: i64 = sym_tag(s); + let at: usize = sym_at(s); + if (tg == SYM_PARAM) { + return param_type(meta, ptext, item, at); + } + if ((tg == SYM_LOCAL) || (tg == SYM_TOP)) { + if ((kind(nodes, at) != K_CONST_DECL) && (kind(nodes, at) != K_STMT_LOCAL)) { + return TS_BAD; + } + if (hi(nodes, at, F_TYPE) > lo(nodes, at, F_TYPE)) { + return ts_type(text, lo(nodes, at, F_TYPE), hi(nodes, at, F_TYPE)); + } + if (((at + 1) >= end(nodes, at)) || (depth > 48)) { + return TS_BAD; + } + let mut owner: usize = item; + if (tg == SYM_TOP) { + owner = 0; + } + let it: i64 = ty_of(nodes, text, meta, ptext, owner, (at + 1), (depth + 1)); + if ((it == TS_COMPTIME) && is_mut(meta, at)) { + return TS_BAD; + } + return it; + } + return TS_BAD; +} + +pub fn fn_type(nodes: &[u8], text: &[u8], f: usize) -> i64 { + return ts_type(text, lo(nodes, f, F_RET), hi(nodes, f, F_RET)); +} + +pub fn ty_of(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, depth: usize) -> i64 { + if (depth > 64) { + return TS_BAD; + } + let k: u8 = kind(nodes, i); + if (k == K_EXPR_LITERAL) { + if (field_eq(nodes, text, i, F_VALUE, "true") || field_eq(nodes, text, i, F_VALUE, "false")) { + return TS_BOOL; + } + if field_eq(nodes, text, i, F_KIND, "string") { + return TS_BAD; + } + if lit_ok(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)) { + return TS_COMPTIME; + } + return TS_BAD; + } + if (k == K_EXPR_IDENTIFIER) { + let s: i64 = resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if ((s == NO_SYM) || (sym_tag(s) == SYM_FN)) { + return TS_BAD; + } + return sym_type(nodes, text, meta, ptext, item, s, (depth + 1)); + } + if (k == K_EXPR_BINARY) { + if (children(nodes, i) != 2) { + return TS_BAD; + } + let l: usize = (i + 1); + let r: usize = end(nodes, l); + let oc: u8 = op_class(&text[(lo(nodes, i, F_OP)) as usize..(hi(nodes, i, F_OP)) as usize]); + if ((oc == OP_CMP) || (oc == OP_LOGIC)) { + return TS_BOOL; + } + let lt: i64 = ty_of(nodes, text, meta, ptext, item, l, (depth + 1)); + if (oc == OP_SHIFT) { + return lt; + } + if (oc == OP_ARITH) { + let rt: i64 = ty_of(nodes, text, meta, ptext, item, r, (depth + 1)); + let pt: i64 = ts_peer(lt, rt); + if ((pt == TS_BOOL) && !(op_bitwise(&text[(lo(nodes, i, F_OP)) as usize..(hi(nodes, i, F_OP)) as usize]))) { + return TS_BAD; + } + return pt; + } + return TS_BAD; + } + if (k == K_EXPR_UNARY) { + if (children(nodes, i) != 1) { + return TS_BAD; + } + if field_eq(nodes, text, i, F_OP, "!") { + return TS_BOOL; + } + let ot: i64 = ty_of(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + if (field_eq(nodes, text, i, F_OP, "~") && ts_int(ot)) { + return ot; + } + if (field_eq(nodes, text, i, F_OP, "-") && (ts_signed(ot) || (ot == TS_COMPTIME))) { + return ot; + } + return TS_BAD; + } + if (k == K_EXPR_CAST) { + return ts_type(text, lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + if (k == K_EXPR_CALL) { + let f: i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (f == NO_NODE) { + return TS_BAD; + } + let fi: usize = (f as _); + return fn_type(nodes, text, fi); + } + if (k == K_EXPR_IF) { + if (children(nodes, i) != 3) { + return TS_BAD; + } + let a1: usize = end(nodes, (i + 1)); + let a2: usize = end(nodes, a1); + return ts_peer(ty_of(nodes, text, meta, ptext, item, a1, (depth + 1)), ty_of(nodes, text, meta, ptext, item, a2, (depth + 1))); + } + return TS_BAD; +} + +pub fn const_decl(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize) -> i64 { + let s: i64 = resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + let tg: i64 = sym_tag(s); + if ((s == NO_SYM) || ((tg != SYM_TOP) && (tg != SYM_LOCAL))) { + return NO_NODE; + } + let d: usize = sym_at(s); + if ((kind(nodes, d) != K_CONST_DECL) && (kind(nodes, d) != K_STMT_LOCAL)) { + return NO_NODE; + } + if (is_mut(meta, d) || ((d + 1) >= end(nodes, d))) { + return NO_NODE; + } + if ident_is(nodes, text, (d + 1), b"undefined") { + return NO_NODE; + } + let di: i64 = (d as _); + return di; +} + +pub fn fold_ok(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, depth: usize) -> bool { + if (depth > 48) { + return false; + } + let k: u8 = kind(nodes, i); + if (k == K_EXPR_LITERAL) { + if (field_eq(nodes, text, i, F_VALUE, "true") || field_eq(nodes, text, i, F_VALUE, "false")) { + return true; + } + if field_eq(nodes, text, i, F_KIND, "string") { + return false; + } + if !(lit_ok(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE))) { + return false; + } + return (lit_val(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)) <= 4503599627370496); + } + if (k == K_EXPR_IDENTIFIER) { + let d: i64 = const_decl(nodes, text, meta, ptext, item, i); + if (d == NO_NODE) { + return false; + } + let di: usize = (d as _); + let mut owner: usize = item; + if (sym_tag(resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME))) == SYM_TOP) { + owner = 0; + } + if !(fold_ok(nodes, text, meta, ptext, owner, (di + 1), (depth + 1))) { + return false; + } + if (hi(nodes, di, F_TYPE) > lo(nodes, di, F_TYPE)) { + let dt: i64 = ts_type(text, lo(nodes, di, F_TYPE), hi(nodes, di, F_TYPE)); + return ts_fits(fold_val(nodes, text, meta, ptext, owner, (di + 1), (depth + 1)), dt); + } + return true; + } + if (k == K_EXPR_CAST) { + if !(fold_ok(nodes, text, meta, ptext, item, (i + 1), (depth + 1))) { + return false; + } + let ct: i64 = ts_type(text, lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + return ts_fits(fold_val(nodes, text, meta, ptext, item, (i + 1), (depth + 1)), ct); + } + if ((k == K_EXPR_UNARY) && (children(nodes, i) == 1)) { + if (field_eq(nodes, text, i, F_OP, "-") || field_eq(nodes, text, i, F_OP, "!")) { + return fold_ok(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + } + return false; + } + if ((k == K_EXPR_BINARY) && (children(nodes, i) == 2)) { + let r: usize = end(nodes, (i + 1)); + if (!(fold_ok(nodes, text, meta, ptext, item, (i + 1), (depth + 1))) || !(fold_ok(nodes, text, meta, ptext, item, r, (depth + 1)))) { + return false; + } + let a: i64 = fold_val(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + let b: i64 = fold_val(nodes, text, meta, ptext, item, r, (depth + 1)); + return ts_fold_ok(&text[(lo(nodes, i, F_OP)) as usize..(hi(nodes, i, F_OP)) as usize], a, b); + } + return false; +} + +pub fn fold_val(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, depth: usize) -> i64 { + if (depth > 48) { + return 0; + } + let k: u8 = kind(nodes, i); + if (k == K_EXPR_LITERAL) { + if field_eq(nodes, text, i, F_VALUE, "true") { + return 1; + } + if field_eq(nodes, text, i, F_VALUE, "false") { + return 0; + } + let lv: u64 = lit_val(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)); + if (lv > 4503599627370496) { + return 0; + } + let li: i64 = (lv as _); + return li; + } + if (k == K_EXPR_IDENTIFIER) { + let d: i64 = const_decl(nodes, text, meta, ptext, item, i); + if (d == NO_NODE) { + return 0; + } + let di: usize = (d as _); + let mut owner: usize = item; + if (sym_tag(resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME))) == SYM_TOP) { + owner = 0; + } + return fold_val(nodes, text, meta, ptext, owner, (di + 1), (depth + 1)); + } + if (k == K_EXPR_CAST) { + return fold_val(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + } + if (k == K_EXPR_UNARY) { + let ov: i64 = fold_val(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + if field_eq(nodes, text, i, F_OP, "!") { + return (1 - ov); + } + return (0 - ov); + } + if ((k == K_EXPR_BINARY) && (children(nodes, i) == 2)) { + let a: i64 = fold_val(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + let b: i64 = fold_val(nodes, text, meta, ptext, item, end(nodes, (i + 1)), (depth + 1)); + return ts_fold_val(&text[(lo(nodes, i, F_OP)) as usize..(hi(nodes, i, F_OP)) as usize], a, b); + } + return 0; +} + +pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, want: i64) -> i64 { + let k: u8 = kind(nodes, i); + let own: i64 = ty_of(nodes, text, meta, ptext, item, i, 0); + if (own == TS_COMPTIME) { + if fold_ok(nodes, text, meta, ptext, item, i, 0) { + if !(ts_int(want)) { + return fail(out, nodes, meta, text, i, "untyped value with no integer type to take", 0, 0); + } + let v: i64 = fold_val(nodes, text, meta, ptext, item, i, 0); + if !(ts_fits(v, want)) { + return fail(out, nodes, meta, text, i, "literal that does not fit its type", 0, 0); + } + let q0: usize = put_lit(out, at, v, want); + let r0: i64 = (q0 as _); + return r0; + } + if (ts_int(want) && (((k == K_EXPR_BINARY) || (k == K_EXPR_UNARY)) || (k == K_EXPR_IF))) { + return ex_typed(out, at, nodes, text, meta, ptext, item, i, want); + } + return fail(out, nodes, meta, text, i, "untyped value that does not fold", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (own == TS_BAD) { + if (k == K_EXPR_CALL) { + let f: i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (f == NO_NODE) { + return fail(out, nodes, meta, text, i, "call of an undeclared fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + return fail(out, nodes, meta, text, i, "call of a fn whose result is no integer or bool", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (k == K_EXPR_IDENTIFIER) { + return fail(out, nodes, meta, text, i, "name with no integer or bool type", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if ((k == K_EXPR_BINARY) && (children(nodes, i) == 2)) { + let bl: usize = (i + 1); + let br: usize = end(nodes, bl); + if (ty_of(nodes, text, meta, ptext, item, bl, 0) == TS_BAD) { + return ex(out, at, nodes, text, meta, ptext, item, bl, TS_BAD); + } + if (ty_of(nodes, text, meta, ptext, item, br, 0) == TS_BAD) { + return ex(out, at, nodes, text, meta, ptext, item, br, TS_BAD); + } + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (k == K_EXPR_UNARY) { + if ((children(nodes, i) == 1) && (ty_of(nodes, text, meta, ptext, item, (i + 1), 0) == TS_BAD)) { + return ex(out, at, nodes, text, meta, ptext, item, (i + 1), TS_BAD); + } + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (k == K_EXPR_CAST) { + return fail(out, nodes, meta, text, i, "cast to type", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + if (k == K_EXPR_LITERAL) { + return fail(out, nodes, meta, text, i, "literal that is no integer or bool", lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)); + } + return fail(out, nodes, meta, text, i, kind_label(k), lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if !(ts_scalar(own)) { + return fail(out, nodes, meta, text, i, "value that is no integer or bool", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (ts_scalar(want) && !(ts_coerces(own, want))) { + return fail(out, nodes, meta, text, i, "value whose type does not coerce to the type of its place", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + return ex_typed(out, at, nodes, text, meta, ptext, item, i, own); +} + +pub fn ex_typed(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, own: i64) -> i64 { + let k: u8 = kind(nodes, i); + let mut q: usize = at; + if (k == K_EXPR_LITERAL) { + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, item, i, 0), own); + let rl: i64 = (q as _); + return rl; + } + if (k == K_EXPR_IDENTIFIER) { + if ((item == 0) && fold_ok(nodes, text, meta, ptext, 0, i, 0)) { + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, 0, i, 0), own); + } else { + q = put_bytes(out, q, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + let ri: i64 = (q as _); + return ri; + } + if (k == K_EXPR_BINARY) { + if (children(nodes, i) != 2) { + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + let l: usize = (i + 1); + let r: usize = end(nodes, l); + let oa: usize = lo(nodes, i, F_OP); + let ob: usize = hi(nodes, i, F_OP); + let oc: u8 = op_class(&text[(oa) as usize..(ob) as usize]); + let lt: i64 = ty_of(nodes, text, meta, ptext, item, l, 0); + let rt: i64 = ty_of(nodes, text, meta, ptext, item, r, 0); + if (lt == TS_BAD) { + return ex(out, q, nodes, text, meta, ptext, item, l, TS_BAD); + } + if (rt == TS_BAD) { + return ex(out, q, nodes, text, meta, ptext, item, r, TS_BAD); + } + if ((oc == OP_CMP) || (oc == OP_LOGIC)) { + let mut pt: i64 = ts_peer(lt, rt); + if (oc == OP_LOGIC) { + pt = TS_BOOL; + } + if (pt == TS_COMPTIME) { + if !(fold_ok(nodes, text, meta, ptext, item, i, 0)) { + return fail(out, nodes, meta, text, i, "comparison of untyped values that does not fold", oa, ob); + } + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, item, i, 0), TS_BOOL); + let rf: i64 = (q as _); + return rf; + } + if !(ts_scalar(pt)) { + return fail(out, nodes, meta, text, i, "comparison of values whose types do not meet", oa, ob); + } + if ((((pt == TS_BOOL) && (oc == OP_CMP)) && !(same(&text[(oa) as usize..(ob) as usize], "=="))) && !(same(&text[(oa) as usize..(ob) as usize], "!="))) { + return fail(out, nodes, meta, text, i, "ordering of bools", oa, ob); + } + q = put_text(out, q, "("); + let r1: i64 = ex(out, q, nodes, text, meta, ptext, item, l, pt); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, " "); + q = put_text(out, q, ts_cmp(&text[(oa) as usize..(ob) as usize])); + q = put_text(out, q, " "); + let r2: i64 = ex(out, q, nodes, text, meta, ptext, item, r, pt); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + q = put_text(out, q, ")"); + let rc: i64 = (q as _); + return rc; + } + if ((oc == OP_ARITH) || (oc == OP_SHIFT)) { + return ex_op(out, q, nodes, text, meta, ptext, item, l, r, oa, ob, own); + } + return fail(out, nodes, meta, text, i, "operator", oa, ob); + } + if (k == K_EXPR_UNARY) { + if (children(nodes, i) != 1) { + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if field_eq(nodes, text, i, F_OP, "!") { + q = put_text(out, q, "(!"); + let r3: i64 = ex(out, q, nodes, text, meta, ptext, item, (i + 1), TS_BOOL); + if (r3 < 0) { + return r3; + } + q = (r3 as _); + q = put_text(out, q, ")"); + let rn: i64 = (q as _); + return rn; + } + if (field_eq(nodes, text, i, F_OP, "~") && ts_int(own)) { + q = put_text(out, q, "(~"); + let r4: i64 = ex(out, q, nodes, text, meta, ptext, item, (i + 1), own); + if (r4 < 0) { + return r4; + } + q = (r4 as _); + if (own == 32) { + q = put_text(out, q, " >>> 0)"); + } else { + if ts_unsigned(own) { + q = put_text(out, q, " & "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + } else { + q = put_text(out, q, ")"); + } + } + let rt2: i64 = (q as _); + return rt2; + } + if (field_eq(nodes, text, i, F_OP, "-") && ts_signed(own)) { + q = put_text(out, q, "t27_chk(-("); + let r5: i64 = ex(out, q, nodes, text, meta, ptext, item, (i + 1), own); + if (r5 < 0) { + return r5; + } + q = (r5 as _); + q = put_text(out, q, "), "); + q = put_i64(out, q, ts_min(own)); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + let rm: i64 = (q as _); + return rm; + } + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (k == K_EXPR_CAST) { + return ex_cast(out, q, nodes, text, meta, ptext, item, i, own); + } + if (k == K_EXPR_CALL) { + return ex_call(out, q, nodes, text, meta, ptext, item, i); + } + if (k == K_EXPR_IF) { + if (children(nodes, i) != 3) { + return fail(out, nodes, meta, text, i, "if expression", 0, 0); + } + let c0: usize = (i + 1); + let a1: usize = end(nodes, c0); + let a2: usize = end(nodes, a1); + q = put_text(out, q, "("); + let r6: i64 = ex(out, q, nodes, text, meta, ptext, item, c0, TS_BOOL); + if (r6 < 0) { + return r6; + } + q = (r6 as _); + q = put_text(out, q, " ? "); + let r7: i64 = ex(out, q, nodes, text, meta, ptext, item, a1, own); + if (r7 < 0) { + return r7; + } + q = (r7 as _); + q = put_text(out, q, " : "); + let r8: i64 = ex(out, q, nodes, text, meta, ptext, item, a2, own); + if (r8 < 0) { + return r8; + } + q = (r8 as _); + q = put_text(out, q, ")"); + let rif: i64 = (q as _); + return rif; + } + return fail(out, nodes, meta, text, i, kind_label(k), lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); +} + +pub fn ex_op(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, l: usize, r: usize, oa: usize, ob: usize, own: i64) -> i64 { + let form: u8 = ts_arith(&text[(oa) as usize..(ob) as usize]); + if (form == AR_BAD) { + return fail(out, nodes, meta, text, l, "operator", oa, ob); + } + let mut q: usize = at; + if (form == AR_SHIFTS) { + if !(ts_int(own)) { + return fail(out, nodes, meta, text, l, "shift of a value that is no integer", oa, ob); + } + let mut rw: i64 = ty_of(nodes, text, meta, ptext, item, r, 0); + if (rw == TS_COMPTIME) { + rw = 32; + } + if (rw == TS_BAD) { + return ex(out, at, nodes, text, meta, ptext, item, r, TS_BAD); + } + if !(ts_unsigned(rw)) { + return fail(out, nodes, meta, text, l, "shift by an amount that is no unsigned integer", oa, ob); + } + q = put_text(out, q, ts_arith_op(&text[(oa) as usize..(ob) as usize])); + q = put_text(out, q, "("); + let s1: i64 = ex(out, q, nodes, text, meta, ptext, item, l, own); + if (s1 < 0) { + return s1; + } + q = (s1 as _); + q = put_text(out, q, ", "); + let s2: i64 = ex(out, q, nodes, text, meta, ptext, item, r, rw); + if (s2 < 0) { + return s2; + } + q = (s2 as _); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_width(own)); + q = put_sign(out, q, own); + let rs: i64 = (q as _); + return rs; + } + if (!(ts_scalar(own)) || ((own == TS_BOOL) && (form != AR_BITS))) { + return fail(out, nodes, meta, text, l, "operator", oa, ob); + } + if ((form == AR_DIVIDES) && !(ts_unsigned(own))) { + return fail(out, nodes, meta, text, l, "division of a value that is no unsigned integer (t27c asks for @divTrunc or @rem)", oa, ob); + } + let imul: bool = same(&text[(oa) as usize..(ob) as usize], "*%"); + if (form == AR_CHECKED) { + q = put_text(out, q, "t27_chk(("); + } + if (form == AR_WRAPS) { + q = put_text(out, q, "t27_wrap("); + if imul { + q = put_text(out, q, "Math.imul("); + } else { + q = put_text(out, q, "("); + } + } + if (form == AR_DIVIDES) { + q = put_text(out, q, ts_arith_op(&text[(oa) as usize..(ob) as usize])); + q = put_text(out, q, "("); + } + if (form == AR_BITS) { + if ((own == 32) || (own == TS_BOOL)) { + q = put_text(out, q, "(("); + } else { + q = put_text(out, q, "("); + } + } + let e1: i64 = ex(out, q, nodes, text, meta, ptext, item, l, own); + if (e1 < 0) { + return e1; + } + q = (e1 as _); + if ((form == AR_DIVIDES) || imul) { + q = put_text(out, q, ", "); + } else { + q = put_text(out, q, " "); + q = put_text(out, q, ts_arith_op(&text[(oa) as usize..(ob) as usize])); + q = put_text(out, q, " "); + } + let e2: i64 = ex(out, q, nodes, text, meta, ptext, item, r, own); + if (e2 < 0) { + return e2; + } + q = (e2 as _); + if (form == AR_CHECKED) { + q = put_text(out, q, "), "); + q = put_i64(out, q, ts_min(own)); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + } + if (form == AR_WRAPS) { + q = put_text(out, q, "), "); + q = put_i64(out, q, ts_width(own)); + q = put_sign(out, q, own); + } + if (form == AR_DIVIDES) { + q = put_text(out, q, ")"); + } + if (form == AR_BITS) { + if (own == 32) { + q = put_text(out, q, ") >>> 0)"); + } else { + if (own == TS_BOOL) { + q = put_text(out, q, ") !== 0)"); + } else { + q = put_text(out, q, ")"); + } + } + } + let ro: i64 = (q as _); + return ro; +} + +pub fn ex_cast(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, own: i64) -> i64 { + let src: i64 = ty_of(nodes, text, meta, ptext, item, (i + 1), 0); + if (src == TS_COMPTIME) { + return ex(out, at, nodes, text, meta, ptext, item, (i + 1), own); + } + if (src == TS_BAD) { + return ex(out, at, nodes, text, meta, ptext, item, (i + 1), TS_BAD); + } + if !(ts_scalar(src)) { + return fail(out, nodes, meta, text, i, "cast of a value that is no integer or bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + if ((src == TS_BOOL) || (own == TS_BOOL)) { + if (src != own) { + return fail(out, nodes, meta, text, i, "cast between bool and an integer", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + return ex(out, at, nodes, text, meta, ptext, item, (i + 1), src); + } + if ts_coerces(src, own) { + return ex(out, at, nodes, text, meta, ptext, item, (i + 1), src); + } + let narrows: bool = (ts_width(own) < ts_width(src)); + let mut q: usize = at; + if narrows { + q = put_text(out, q, "t27_wrap("); + } else { + q = put_text(out, q, "t27_chk("); + } + let r1: i64 = ex(out, q, nodes, text, meta, ptext, item, (i + 1), src); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, ", "); + if narrows { + q = put_i64(out, q, ts_width(own)); + q = put_sign(out, q, own); + } else { + q = put_i64(out, q, ts_min(own)); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + } + let rc: i64 = (q as _); + return rc; +} + +pub fn ex_call(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize) -> i64 { + let f: i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (f == NO_NODE) { + return fail(out, nodes, meta, text, i, "call of an undeclared fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + let fi: usize = (f as _); + if is_extern(nodes, fi) { + return fail(out, nodes, meta, text, i, "call of an extern fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + let np: usize = param_count(ptext, params_lo(meta, fi), params_hi(meta, fi)); + if (children(nodes, i) != np) { + return fail(out, nodes, meta, text, i, "call with the wrong number of arguments", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + let mut q: usize = put_bytes(out, at, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + q = put_text(out, q, "("); + let mut a: usize = (i + 1); + let mut kx: usize = 0; + while (a < end(nodes, i)) { + if (kx > 0) { + q = put_text(out, q, ", "); + } + let pt: i64 = param_type(meta, ptext, fi, kx); + if !(ts_scalar(pt)) { + return fail(out, nodes, meta, text, i, "call of a fn with a parameter that is no integer or bool", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + let r: i64 = ex(out, q, nodes, text, meta, ptext, item, a, pt); + if (r < 0) { + return r; + } + q = (r as _); + a = end(nodes, a); + kx = (kx + 1); + } + q = put_text(out, q, ")"); + let rr: i64 = (q as _); + return rr; +} + +pub fn stmts(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, b: usize, c: usize, n: usize, lp: usize) -> i64 { + let mut q: usize = at; + let mut s: usize = c; + while (s < end(nodes, b)) { + let r: i64 = stmt(out, q, nodes, text, meta, ptext, item, s, n, lp); + if (r < 0) { + return r; + } + q = (r as _); + s = end(nodes, s); + } + let re: i64 = (q as _); + return re; +} + +pub fn stmt(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, s: usize, n: usize, lp: usize) -> i64 { + let k: u8 = kind(nodes, s); + let mut q: usize = at; + if (k == K_STMT_LOCAL) { + if ((s + 1) >= end(nodes, s)) { + return fail(out, nodes, meta, text, s, "local with no value", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); + } + if ident_is(nodes, text, (s + 1), b"undefined") { + return fail(out, nodes, meta, text, s, "local left undefined", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); + } + let t: i64 = sym_type(nodes, text, meta, ptext, item, sym_of(SYM_LOCAL, s), 0); + if (t == TS_COMPTIME) { + let rc: i64 = (q as _); + return rc; + } + if !(ts_scalar(t)) { + return fail(out, nodes, meta, text, s, "local that is no integer or bool", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); + } + if bad_name(text, lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)) { + return fail(out, nodes, meta, text, s, "name TypeScript reserves or the prelude uses", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); + } + q = put_ind(out, q, n); + if is_mut(meta, s) { + q = put_text(out, q, "let "); + } else { + q = put_text(out, q, "const "); + } + q = put_bytes(out, q, text, lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); + q = put_text(out, q, ": "); + q = put_text(out, q, ts_annot(t)); + q = put_text(out, q, " = "); + let r1: i64 = ex(out, q, nodes, text, meta, ptext, item, (s + 1), t); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, ";\n"); + let rl: i64 = (q as _); + return rl; + } + if (k == K_STMT_ASSIGN) { + return assign(out, q, nodes, text, meta, ptext, item, s, n); + } + if (k == K_STMT_IF) { + return stmt_if(out, q, nodes, text, meta, ptext, item, s, n, lp); + } + if (k == K_STMT_WHILE) { + return stmt_while(out, q, nodes, text, meta, ptext, item, s, n); + } + if (k == K_EXPR_RETURN) { + if (kind(nodes, item) != K_FN_DECL) { + return fail(out, nodes, meta, text, s, "return in a test", 0, 0); + } + if ((s + 1) >= end(nodes, s)) { + return fail(out, nodes, meta, text, s, "return with no value from a fn", 0, 0); + } + q = put_ind(out, q, n); + q = put_text(out, q, "return "); + let r2: i64 = ex(out, q, nodes, text, meta, ptext, item, (s + 1), fn_type(nodes, text, item)); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + q = put_text(out, q, ";\n"); + let rr: i64 = (q as _); + return rr; + } + if ((k == K_STMT_BREAK) || (k == K_STMT_CONTINUE)) { + if (lp == 0) { + return fail(out, nodes, meta, text, s, kind_label(k), 0, 0); + } + q = put_ind(out, q, n); + if (k == K_STMT_BREAK) { + q = put_text(out, q, "break;\n"); + } else { + q = put_text(out, q, "continue;\n"); + } + let rb: i64 = (q as _); + return rb; + } + if (k == K_STMT_EXPR) { + if (((s + 1) < end(nodes, s)) && (kind(nodes, (s + 1)) == K_EXPR_CALL)) { + return call_stmt(out, q, nodes, text, meta, ptext, item, (s + 1), n); + } + return fail(out, nodes, meta, text, s, "expression statement", 0, 0); + } + return fail(out, nodes, meta, text, s, kind_label(k), lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); +} + +pub fn assign(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, s: usize, n: usize) -> i64 { + if (children(nodes, s) != 2) { + return fail(out, nodes, meta, text, s, "assignment", 0, 0); + } + let t: usize = (s + 1); + let e: usize = end(nodes, t); + let mut q: usize = at; + if ident_is(nodes, text, t, b"_") { + let et: i64 = ty_of(nodes, text, meta, ptext, item, e, 0); + if (et == TS_COMPTIME) { + let rd: i64 = (q as _); + return rd; + } + q = put_ind(out, q, n); + q = put_text(out, q, "void ("); + let r0: i64 = ex(out, q, nodes, text, meta, ptext, item, e, et); + if (r0 < 0) { + return r0; + } + q = (r0 as _); + q = put_text(out, q, ");\n"); + let rv: i64 = (q as _); + return rv; + } + if (kind(nodes, t) != K_EXPR_IDENTIFIER) { + return fail(out, nodes, meta, text, s, "assignment to", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + } + let sy: i64 = resolve(nodes, text, meta, ptext, item, t, lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + if (sy == NO_SYM) { + return fail(out, nodes, meta, text, s, "assignment that declares a name", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + } + if ((sym_tag(sy) != SYM_LOCAL) || !(is_mut(meta, sym_at(sy)))) { + return fail(out, nodes, meta, text, s, "assignment to a name that is no var local", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + } + let tt: i64 = sym_type(nodes, text, meta, ptext, item, sy, 0); + if !(ts_scalar(tt)) { + return fail(out, nodes, meta, text, s, "assignment to a name that is no integer or bool", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + } + q = put_ind(out, q, n); + q = put_bytes(out, q, text, lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + q = put_text(out, q, " = "); + let oa: usize = lo(nodes, s, F_OP); + let ob: usize = hi(nodes, s, F_OP); + if ((ob > oa) && !(field_eq(nodes, text, s, F_OP, "="))) { + let oc: u8 = op_class(&text[(oa) as usize..((ob - 1)) as usize]); + if ((oc != OP_ARITH) && (oc != OP_SHIFT)) { + return fail(out, nodes, meta, text, s, "compound assignment", oa, ob); + } + let r1: i64 = ex_op(out, q, nodes, text, meta, ptext, item, t, e, oa, (ob - 1), tt); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + } else { + let r2: i64 = ex(out, q, nodes, text, meta, ptext, item, e, tt); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + } + q = put_text(out, q, ";\n"); + let ra: i64 = (q as _); + return ra; +} + +pub fn stmt_if(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, s: usize, n: usize, lp: usize) -> i64 { + let c0: usize = (s + 1); + let th: usize = end(nodes, c0); + if ((th >= end(nodes, s)) || (kind(nodes, th) != K_MODULE)) { + return fail(out, nodes, meta, text, s, "if", 0, 0); + } + if (ty_of(nodes, text, meta, ptext, item, c0, 0) != TS_BOOL) { + return ex(out, at, nodes, text, meta, ptext, item, c0, TS_BOOL); + } + let mut q: usize = put_ind(out, at, n); + q = put_text(out, q, "if ("); + let r1: i64 = ex(out, q, nodes, text, meta, ptext, item, c0, TS_BOOL); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, ") {\n"); + let r2: i64 = stmts(out, q, nodes, text, meta, ptext, item, th, (th + 1), (n + 2), lp); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + q = put_ind(out, q, n); + q = put_text(out, q, "}"); + let el: usize = end(nodes, th); + if (el < end(nodes, s)) { + q = put_text(out, q, " else {\n"); + let r3: i64 = stmts(out, q, nodes, text, meta, ptext, item, el, (el + 1), (n + 2), lp); + if (r3 < 0) { + return r3; + } + q = (r3 as _); + q = put_ind(out, q, n); + q = put_text(out, q, "}"); + } + q = put_text(out, q, "\n"); + let ri: i64 = (q as _); + return ri; +} + +pub fn stmt_while(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, w: usize, n: usize) -> i64 { + let body: i64 = loop_block(nodes, text, w, "body"); + if (body == NO_NODE) { + return fail(out, nodes, meta, text, w, "while with no body", 0, 0); + } + let bi: usize = (body as _); + let ce: i64 = loop_block(nodes, text, w, "continue_expr"); + if ((ce != NO_NODE) && own_continue(nodes, (bi + 1), end(nodes, bi))) { + return fail(out, nodes, meta, text, w, "continue in a while with a continue expression", 0, 0); + } + let cnd: usize = (w + 1); + if (ty_of(nodes, text, meta, ptext, item, cnd, 0) != TS_BOOL) { + return ex(out, at, nodes, text, meta, ptext, item, cnd, TS_BOOL); + } + let mut q: usize = put_ind(out, at, n); + q = put_text(out, q, "while ("); + let r1: i64 = ex(out, q, nodes, text, meta, ptext, item, cnd, TS_BOOL); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, ") {\n"); + let r2: i64 = stmts(out, q, nodes, text, meta, ptext, item, bi, (bi + 1), (n + 2), w); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + if (ce != NO_NODE) { + let cei: usize = (ce as _); + let r3: i64 = stmts(out, q, nodes, text, meta, ptext, item, cei, (cei + 1), (n + 2), w); + if (r3 < 0) { + return r3; + } + q = (r3 as _); + } + q = put_ind(out, q, n); + q = put_text(out, q, "}\n"); + let rw: i64 = (q as _); + return rw; +} + +pub fn call_stmt(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, i: usize, n: usize) -> i64 { + let mut q: usize = at; + if (!(field_eq(nodes, text, i, F_NAME, "assert")) && !(field_eq(nodes, text, i, F_NAME, "assert_eq"))) { + return fail(out, nodes, meta, text, i, "call statement", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + let eq: bool = field_eq(nodes, text, i, F_NAME, "assert_eq"); + if ((eq && (children(nodes, i) != 2)) || (!(eq) && (children(nodes, i) != 1))) { + return fail(out, nodes, meta, text, i, "assert with a message", 0, 0); + } + let a: usize = (i + 1); + let mut pt: i64 = TS_BOOL; + if eq { + let at1: i64 = ty_of(nodes, text, meta, ptext, item, a, 0); + let at2: i64 = ty_of(nodes, text, meta, ptext, item, end(nodes, a), 0); + if (at1 == TS_BAD) { + return ex(out, q, nodes, text, meta, ptext, item, a, TS_BAD); + } + if (at2 == TS_BAD) { + return ex(out, q, nodes, text, meta, ptext, item, end(nodes, a), TS_BAD); + } + pt = ts_peer(at1, at2); + if !(ts_scalar(pt)) { + return fail(out, nodes, meta, text, i, "assert_eq of values whose types do not meet", 0, 0); + } + } + let mut folds: bool = fold_ok(nodes, text, meta, ptext, item, a, 0); + if eq { + folds = (folds && fold_ok(nodes, text, meta, ptext, item, end(nodes, a), 0)); + } + q = put_ind(out, q, n); + if folds { + q = put_text(out, q, "t27_check("); + } else { + q = put_text(out, q, "t27_assert("); + } + if eq { + q = put_text(out, q, "("); + } + let r1: i64 = ex(out, q, nodes, text, meta, ptext, item, a, pt); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + if eq { + q = put_text(out, q, " === "); + let r2: i64 = ex(out, q, nodes, text, meta, ptext, item, end(nodes, a), pt); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + q = put_text(out, q, ")"); + } + q = put_text(out, q, ", '"); + q = put_bytes(out, q, text, lo(nodes, item, F_NAME), hi(nodes, item, F_NAME)); + q = put_text(out, q, "', "); + q = put_dec(out, q, (line_near(nodes, meta, i) as u64)); + q = put_text(out, q, ");\n"); + let ra: i64 = (q as _); + return ra; +} + +pub fn const_text(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], d: usize) -> i64 { + let na: usize = lo(nodes, d, F_NAME); + let nb: usize = hi(nodes, d, F_NAME); + if is_mut(meta, d) { + return fail(out, nodes, meta, text, d, "module-level var", na, nb); + } + if ((hi(nodes, d, F_TYPE) > lo(nodes, d, F_TYPE)) && !(ts_scalar(ts_type(text, lo(nodes, d, F_TYPE), hi(nodes, d, F_TYPE))))) { + return fail(out, nodes, meta, text, d, "const that is no integer or bool", na, nb); + } + if ((d + 1) >= end(nodes, d)) { + return fail(out, nodes, meta, text, d, "const with no value", na, nb); + } + if ident_is(nodes, text, (d + 1), b"undefined") { + return fail(out, nodes, meta, text, d, "const left undefined", na, nb); + } + if bad_name(text, na, nb) { + return fail(out, nodes, meta, text, d, "name TypeScript reserves or the prelude uses", na, nb); + } + let mut t: i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, d), 0); + if (t == TS_COMPTIME) { + if !(fold_ok(nodes, text, meta, ptext, 0, (d + 1), 0)) { + return fail(out, nodes, meta, text, d, "untyped const that does not fold", na, nb); + } + t = (TS_SIGNED + TS_MAX_WIDTH); + if !(ts_fits(fold_val(nodes, text, meta, ptext, 0, (d + 1), 0), t)) { + t = TS_MAX_WIDTH; + } + } + if !(ts_scalar(t)) { + return fail(out, nodes, meta, text, d, "const that is no integer or bool", na, nb); + } + let mut q: usize = at; + if is_pub(meta, d) { + q = put_text(out, q, "export "); + } + q = put_text(out, q, "const "); + q = put_bytes(out, q, text, na, nb); + q = put_text(out, q, ": "); + q = put_text(out, q, ts_annot(t)); + q = put_text(out, q, " = "); + let r1: i64 = ex(out, q, nodes, text, meta, ptext, 0, (d + 1), t); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, ";\n"); + let rc: i64 = (q as _); + return rc; +} + +pub fn fn_text(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], f: usize) -> i64 { + let na: usize = lo(nodes, f, F_NAME); + let nb: usize = hi(nodes, f, F_NAME); + if is_extern(nodes, f) { + return fail(out, nodes, meta, text, f, "extern fn", na, nb); + } + let rt: i64 = fn_type(nodes, text, f); + if (rt == TS_VOID) { + return fail(out, nodes, meta, text, f, "fn with no result", na, nb); + } + if !(ts_scalar(rt)) { + return fail(out, nodes, meta, text, f, "fn whose result is no integer or bool", na, nb); + } + if bad_name(text, na, nb) { + return fail(out, nodes, meta, text, f, "name TypeScript reserves or the prelude uses", na, nb); + } + let mut q: usize = put_text(out, at, "\n"); + if is_pub(meta, f) { + q = put_text(out, q, "export "); + } + q = put_text(out, q, "function "); + q = put_bytes(out, q, text, na, nb); + q = put_text(out, q, "("); + let np: usize = param_count(ptext, params_lo(meta, f), params_hi(meta, f)); + let mut kx: usize = 0; + while (kx < np) { + let pa: usize = param_name_lo(meta, ptext, f, kx); + let pb: usize = param_name_hi(meta, ptext, f, kx); + if bad_name(ptext, pa, pb) { + return fail(out, nodes, meta, text, f, "parameter whose name TypeScript reserves or the prelude uses, of fn", na, nb); + } + let pt: i64 = param_type(meta, ptext, f, kx); + if !(ts_scalar(pt)) { + return fail(out, nodes, meta, text, f, "parameter that is no integer or bool, of fn", na, nb); + } + if (kx > 0) { + q = put_text(out, q, ", "); + } + q = put_bytes(out, q, ptext, pa, pb); + q = put_text(out, q, ": "); + q = put_text(out, q, ts_annot(pt)); + kx = (kx + 1); + } + q = put_text(out, q, "): "); + q = put_text(out, q, ts_annot(rt)); + q = put_text(out, q, " {\n"); + let r1: i64 = stmts(out, q, nodes, text, meta, ptext, f, f, (f + 1), 2, 0); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, "}\n"); + let rf: i64 = (q as _); + return rf; +} + +pub fn check_text(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], c: usize, prefix: &'static str) -> i64 { + let na: usize = lo(nodes, c, F_NAME); + let nb: usize = hi(nodes, c, F_NAME); + if !(is_ident(text, na, nb)) { + return fail(out, nodes, meta, text, c, "test whose name is no identifier", na, nb); + } + let mut q: usize = put_text(out, at, "\nexport function "); + q = put_text(out, q, prefix); + q = put_bytes(out, q, text, na, nb); + q = put_text(out, q, "(): number {\n t27_n = 0;\n"); + let r1: i64 = stmts(out, q, nodes, text, meta, ptext, c, c, (c + 1), 2, 0); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, " return t27_n;\n}\n"); + let rt: i64 = (q as _); + return rt; +} + +pub fn items(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8]) -> i64 { + let mut q: usize = at; + let mut c: usize = 1; + while (c < end(nodes, 0)) { + let k: u8 = kind(nodes, c); + let mut r: i64 = 0; + if (k == K_CONST_DECL) { + r = const_text(out, q, nodes, text, meta, ptext, c); + } else { + if (k == K_FN_DECL) { + r = fn_text(out, q, nodes, text, meta, ptext, c); + } else { + if (k == K_TEST_BLOCK) { + r = check_text(out, q, nodes, text, meta, ptext, c, "t27_test_"); + } else { + if (k == K_INVARIANT_BLOCK) { + r = check_text(out, q, nodes, text, meta, ptext, c, "t27_invariant_"); + } else { + return fail(out, nodes, meta, text, c, kind_label(k), lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + } + } + } + } + if (r < 0) { + return r; + } + q = (r as _); + c = end(nodes, c); + } + let ri: i64 = (q as _); + return ri; +} + +pub fn table(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], kw: u8, name: &'static str, prefix: &'static str) -> usize { + let mut q: usize = put_text(out, at, "\nexport const "); + q = put_text(out, q, name); + q = put_text(out, q, ": t27_case[] = [\n"); + let mut c: usize = 1; + while (c < end(nodes, 0)) { + if (kind(nodes, c) == kw) { + q = put_text(out, q, " ['"); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "', "); + q = put_text(out, q, prefix); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "],\n"); + } + c = end(nodes, c); + } + return put_text(out, q, "];\n"); +} + +pub fn emit(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: impl AsRef<[u8]>, out: &mut [u8]) -> i64 { + let work: &[u8] = work.as_ref(); + if ((count(nodes) == 0) || (kind(nodes, 0) != K_MODULE)) { + return fail_front(out, "file with no module node"); + } + let mut q: usize = put_text(out, 0, "// Generated by t27b ts from module "); + q = put_bytes(out, q, text, lo(nodes, 0, F_NAME), hi(nodes, 0, F_NAME)); + q = put_text(out, q, " -- DO NOT EDIT; the decisions are specs/tri/t27b/ts_plan.t27\n"); + q = put_text(out, q, "// phi^2 + 1/phi^2 = 3 | TRINITY\n\n"); + let mut j: usize = 0; + while (j < TS_PRELUDE_LINES) { + q = put_text(out, q, TS_PRELUDE[(j) as usize]); + q = put_text(out, q, "\n"); + j = (j + 1); + } + let r1: i64 = items(out, q, nodes, text, meta, ptext); + if (r1 < 0) { + let ml: usize = ((0 - r1) as _); + return to_front(out, ml); + } + q = (r1 as _); + q = table(out, q, nodes, text, K_TEST_BLOCK, "t27_tests", "t27_test_"); + q = table(out, q, nodes, text, K_INVARIANT_BLOCK, "t27_invariants", "t27_invariant_"); + j = 0; + while (j < TS_CODA_LINES) { + q = put_text(out, q, TS_CODA[(j) as usize]); + q = put_text(out, q, "\n"); + j = (j + 1); + } + if ((q + MSG_ROOM) > out.len()) { + return fail_front(out, "output larger than the buffer"); + } + let rq: i64 = (q as _); + return rq; +} + +pub fn tnode(buf: &mut [u8], i: usize, k: u8, e: usize, a: usize, b: usize) -> () { + buf[((i * REC)) as usize] = k; + vput32(buf, ((i * REC) + 4), e); + let mut f: usize = 0; + while (f < FIELDS) { + vput32(buf, (((i * REC) + 8) + (f * 8)), 0); + vput32(buf, (((i * REC) + 12) + (f * 8)), 0); + f = (f + 1); + } + vput32(buf, ((i * REC) + 8), a); + vput32(buf, ((i * REC) + 12), b); +} + +pub fn tmeta(m: &mut [u8], i: usize, flags: usize, line: usize) -> () { + vput32(m, (i * META), flags); + vput32(m, ((i * META) + 4), line); + vput32(m, ((i * META) + 8), 0); + vput32(m, ((i * META) + 12), 0); +} + +pub fn holds(out: &[u8], n: usize, w: &'static str) -> bool { + let mut i: usize = 0; + while ((i + w.len()) <= n) { + let mut k: usize = 0; + while ((k < w.len()) && (out[((i + k)) as usize] == w.as_bytes()[(k) as usize])) { + k = (k + 1); + } + if (k == w.len()) { + return true; + } + i = (i + 1); + } + return false; +} + +pub fn ttree(nb: &mut [u8], mb: &mut [u8], ca: usize, cb: usize) -> () { + tnode(nb, 0, K_MODULE, 13, 0, 1); + tnode(nb, 1, K_FN_DECL, 6, 1, 2); + vset(nb, 1, F_RET, 3, 5); + tnode(nb, 2, K_EXPR_RETURN, 6, 0, 0); + tnode(nb, 3, K_EXPR_BINARY, 6, 0, 0); + vset(nb, 3, F_OP, 5, 6); + tnode(nb, 4, K_EXPR_IDENTIFIER, 5, 2, 3); + tnode(nb, 5, K_EXPR_LITERAL, 6, 0, 0); + vset(nb, 5, F_VALUE, 6, 7); + tnode(nb, 6, K_TEST_BLOCK, 13, 8, 9); + tnode(nb, 7, K_STMT_EXPR, 13, 0, 0); + tnode(nb, 8, K_EXPR_CALL, 13, 9, 15); + tnode(nb, 9, K_EXPR_BINARY, 13, 0, 0); + vset(nb, 9, F_OP, 15, 17); + tnode(nb, 10, K_EXPR_CALL, 12, ca, cb); + tnode(nb, 11, K_EXPR_LITERAL, 12, 0, 0); + vset(nb, 11, F_VALUE, 6, 7); + tnode(nb, 12, K_EXPR_LITERAL, 13, 0, 0); + vset(nb, 12, F_VALUE, 7, 8); + let mut i: usize = 0; + while (i < 13) { + tmeta(mb, i, 0, 0); + i = (i + 1); + } + tmeta(mb, 1, FLAG_PUB, 1); + vput32(mb, ((1 * META) + 12), 5); + tmeta(mb, 6, 0, 2); + tmeta(mb, 8, 0, 3); +} + +pub const B_DOT: u8 = 46; + +pub const FIELDS: usize = 8; + +pub const F_KIND: usize = 5; + +pub const F_NAME: usize = 0; + +pub const F_OP: usize = 6; + +pub const F_RET: usize = 7; + +pub const F_TYPE: usize = 2; + +pub const F_VALUE: usize = 1; + +pub const K_CONST_DECL: u8 = 2; + +pub const K_EXPR_BINARY: u8 = 16; + +pub const K_EXPR_CALL: u8 = 13; + +pub const K_EXPR_CAST: u8 = 24; + +pub const K_EXPR_IDENTIFIER: u8 = 11; + +pub const K_EXPR_IF: u8 = 20; + +pub const K_EXPR_LITERAL: u8 = 10; + +pub const K_EXPR_RETURN: u8 = 18; + +pub const K_EXPR_UNARY: u8 = 17; + +pub const K_FN_DECL: u8 = 6; + +pub const K_INVARIANT_BLOCK: u8 = 7; + +pub const K_MODULE: u8 = 0; + +pub const K_STMT_ASSIGN: u8 = 27; + +pub const K_STMT_BREAK: u8 = 32; + +pub const K_STMT_CONTINUE: u8 = 33; + +pub const K_STMT_EXPR: u8 = 34; + +pub const K_STMT_FOR: u8 = 30; + +pub const K_STMT_FOR_RANGE: u8 = 31; + +pub const K_STMT_IF: u8 = 28; + +pub const K_STMT_LOCAL: u8 = 26; + +pub const K_STMT_WHILE: u8 = 29; + +pub const K_STRUCT_DECL: u8 = 5; + +pub const K_TEST_BLOCK: u8 = 8; + +pub const NO_NODE: i64 = -1; + +pub const REC: usize = 72; + +pub fn children(nodes: &[u8], i: usize) -> usize { + let mut n: usize = 0; + let mut c: usize = (i + 1); + while (c < end(nodes, i)) { + n = (n + 1); + c = end(nodes, c); + } + return n; +} + +pub fn count(nodes: &[u8]) -> usize { + return (nodes.len() / REC); +} + +pub fn end(nodes: &[u8], i: usize) -> usize { + return u32_at(nodes, ((i * REC) + 4)); +} + +pub fn field_eq(nodes: &[u8], text: &[u8], i: usize, f: usize, w: &'static str) -> bool { + return same(&text[(lo(nodes, i, f)) as usize..(hi(nodes, i, f)) as usize], w); +} + +pub fn field_is(nodes: &[u8], text: &[u8], i: usize, f: usize, w: &[u8]) -> bool { + let a: usize = lo(nodes, i, f); + let b: usize = hi(nodes, i, f); + return (((b - a) == w.len()) && has_at(text, a, b, w)); +} + +pub fn has_at(s: &[u8], lo: usize, hi: usize, w: &[u8]) -> bool { + if ((hi - lo) < w.len()) { + return false; + } + let mut k: usize = 0; + while (k < w.len()) { + if (s[((lo + k)) as usize] != w[(k) as usize]) { + return false; + } + k = (k + 1); + } + return true; +} + +pub fn hi(nodes: &[u8], i: usize, f: usize) -> usize { + return u32_at(nodes, (((i * REC) + 12) + (f * 8))); +} + +pub fn ident_is(nodes: &[u8], text: &[u8], i: usize, name: &[u8]) -> bool { + return ((kind(nodes, i) == K_EXPR_IDENTIFIER) && field_is(nodes, text, i, F_NAME, name)); +} + +pub fn kind(nodes: &[u8], i: usize) -> u8 { + return nodes[((i * REC)) as usize]; +} + +pub fn lo(nodes: &[u8], i: usize, f: usize) -> usize { + return u32_at(nodes, (((i * REC) + 8) + (f * 8))); +} + +pub fn names(nodes: &[u8], text: &[u8], i: usize, name: &[u8]) -> bool { + let a: usize = lo(nodes, i, F_NAME); + let b: usize = hi(nodes, i, F_NAME); + if !(has_at(text, a, b, name)) { + return false; + } + return (((b - a) == name.len()) || (text[((a + name.len())) as usize] == B_DOT)); +} + +pub fn node(nodes: &mut [u8], i: usize, k: u8, e: usize, a: usize, b: usize) -> () { + nodes[((i * REC)) as usize] = k; + put32(nodes, ((i * REC) + 4), e); + let mut f: usize = 0; + while (f < FIELDS) { + put32(nodes, (((i * REC) + 8) + (f * 8)), a); + put32(nodes, (((i * REC) + 12) + (f * 8)), a); + f = (f + 1); + } + put32(nodes, ((i * REC) + 12), b); +} + +pub fn put32(nodes: &mut [u8], at: usize, v: usize) -> () { + nodes[(at) as usize] = ((v % 256) as _); + nodes[((at + 1)) as usize] = (((v / 256) % 256) as _); + nodes[((at + 2)) as usize] = (((v / 65536) % 256) as _); + nodes[((at + 3)) as usize] = ((v / 16777216) as _); +} + +pub fn u32_at(nodes: &[u8], at: usize) -> usize { + let b0: usize = (nodes[(at) as usize] as usize); + let b1: usize = (nodes[((at + 1)) as usize] as usize); + let b2: usize = (nodes[((at + 2)) as usize] as usize); + let b3: usize = (nodes[((at + 3)) as usize] as usize); + return (((b0 + (b1 * 256)) + (b2 * 65536)) + (b3 * 16777216)); +} + +pub fn put_text(out: &mut [u8], at: usize, w: &'static str) -> usize { + let mut k: usize = 0; + while (k < w.len()) { + out[((at + k)) as usize] = w.as_bytes()[(k) as usize]; + k = (k + 1); + } + return (at + w.len()); +} + +pub const AR_BAD: u8 = 0; + +pub const AR_BITS: u8 = 4; + +pub const AR_CHECKED: u8 = 1; + +pub const AR_DIVIDES: u8 = 3; + +pub const AR_SHIFTS: u8 = 5; + +pub const AR_WRAPS: u8 = 2; + +pub const TS_BAD: i64 = -1; + +pub const TS_BOOL: i64 = 65; + +pub const TS_CODA: [&'static str; 20] = ["", "/// The assert table of `t27c test-report`, line for line.", "export function t27_report(): string {", " const lines: string[] = [' runtime asserts executed, per test (#6509; a pass with 0 is vacuous, T730):'];", " let passed = 0;", " let vacuous = 0;", " for (const [name, run] of t27_tests) {", " let n = -1;", " try { n = run(); } catch (e) { n = -1; }", " if (n < 0) { lines.push(' ' + '-'.padStart(6) + ' ' + name + ' (failed, not counted)'); continue; }", " passed = passed + 1;", " if (n === 0) { vacuous = vacuous + 1; lines.push(' ' + '0'.padStart(6) + ' ' + name + ' VACUOUS'); } else { lines.push(' ' + String(n).padStart(6) + ' ' + name); }", " }", " lines.push(' vacuous passes ' + String(vacuous) + ' of ' + String(passed) + ' (passed with 0 runtime asserts executed)');", " return lines.join(String.fromCharCode(10));", "}", "", "/// Every test and invariant that throws, with what it threw; empty when all hold.", "export function t27_failures(): string[] { const out: string[] = []; for (const [name, run] of t27_tests) { try { run(); } catch (e) { out.push('test ' + name + ': ' + String(e)); } } for (const [name, run] of t27_invariants) { try { run(); } catch (e) { out.push('invariant ' + name + ': ' + String(e)); } } return out; }", ""]; + +pub const TS_CODA_LINES: usize = 20; + +pub const TS_COMPTIME: i64 = 0; + +pub const TS_FOLD_MAX: i64 = 4503599627370496; + +pub const TS_KEYWORDS: [&'static str; 59] = ["break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield", "let", "static", "implements", "interface", "package", "private", "protected", "public", "await", "arguments", "eval", "undefined", "NaN", "Infinity", "Math", "String", "Error", "RangeError", "Array", "Number", "Boolean", "Object"]; + +pub const TS_MAX_WIDTH: i64 = 32; + +pub const TS_PRELUDE: [&'static str; 12] = ["let t27_n: number = 0;", "function t27_fail(m: string): never { throw new RangeError('t27: ' + m); }", "function t27_chk(v: number, lo: number, hi: number): number { if (v < lo || v > hi) { t27_fail('integer overflow'); } return v; }", "function t27_wrap(v: number, w: number, s: boolean): number { if (s) { return w === 32 ? v | 0 : (v << (32 - w)) >> (32 - w); } return w === 32 ? v >>> 0 : v & (2 ** w - 1); }", "function t27_div(a: number, b: number): number { if (b === 0) { t27_fail('division by zero'); } return Math.trunc(a / b); }", "function t27_rem(a: number, b: number): number { if (b === 0) { t27_fail('division by zero'); } return a % b; }", "function t27_shl(a: number, b: number, w: number, s: boolean): number { if (b >= w) { t27_fail('shift amount too large'); } return t27_wrap(a << b, w, s); }", "function t27_shr(a: number, b: number, w: number, s: boolean): number { if (b >= w) { t27_fail('shift amount too large'); } return s ? a >> b : a >>> b; }", "function t27_check(c: boolean, test: string, line: number): void { if (!c) { throw new Error('t27: assert failed in ' + test + ' at line ' + String(line)); } }", "function t27_assert(c: boolean, test: string, line: number): void { t27_n = t27_n + 1; t27_check(c, test, line); }", "type t27_case = [string, () => number];", ""]; + +pub const TS_PRELUDE_LINES: usize = 12; + +pub const TS_SIGNED: i64 = 100; + +pub const TS_VOID: i64 = 66; + +pub fn ts_abs(v: i64) -> i64 { + if (v < 0) { + return (0 - v); + } + return v; +} + +pub fn ts_annot(t: i64) -> &'static str { + if (t == TS_BOOL) { + return "boolean"; + } + return "number"; +} + +pub fn ts_arith(s: &[u8]) -> u8 { + if ((same(s, "+") || same(s, "-")) || same(s, "*")) { + return AR_CHECKED; + } + if ((same(s, "+%") || same(s, "-%")) || same(s, "*%")) { + return AR_WRAPS; + } + if (same(s, "/") || same(s, "%")) { + return AR_DIVIDES; + } + if op_bitwise(s) { + return AR_BITS; + } + if (same(s, "<<") || same(s, ">>")) { + return AR_SHIFTS; + } + return AR_BAD; +} + +pub fn ts_arith_op(s: &[u8]) -> &'static str { + if (same(s, "+") || same(s, "+%")) { + return "+"; + } + if (same(s, "-") || same(s, "-%")) { + return "-"; + } + if same(s, "*") { + return "*"; + } + if same(s, "*%") { + return "Math.imul"; + } + if same(s, "/") { + return "t27_div"; + } + if same(s, "%") { + return "t27_rem"; + } + if same(s, "&") { + return "&"; + } + if same(s, "|") { + return "|"; + } + if same(s, "^") { + return "^"; + } + if same(s, "<<") { + return "t27_shl"; + } + if same(s, ">>") { + return "t27_shr"; + } + return "?"; +} + +pub fn ts_cmp(s: &[u8]) -> &'static str { + if same(s, "==") { + return "==="; + } + if same(s, "!=") { + return "!=="; + } + if same(s, "<") { + return "<"; + } + if same(s, "<=") { + return "<="; + } + if same(s, ">") { + return ">"; + } + if same(s, ">=") { + return ">="; + } + if (same(s, "and") || same(s, "&&")) { + return "&&"; + } + if (same(s, "or") || same(s, "||")) { + return "||"; + } + return "?"; +} + +pub fn ts_coerces(own: i64, want: i64) -> bool { + if (own == want) { + return ts_scalar(own); + } + if (!(ts_int(own)) || !(ts_int(want))) { + return false; + } + return ((ts_min(want) <= ts_min(own)) && (ts_max(own) <= ts_max(want))); +} + +pub fn ts_fits(v: i64, t: i64) -> bool { + if !(ts_scalar(t)) { + return false; + } + return ((v >= ts_min(t)) && (v <= ts_max(t))); +} + +pub fn ts_fold_ok(s: &[u8], a: i64, b: i64) -> bool { + if (!(ts_in_fold(a)) || !(ts_in_fold(b))) { + return false; + } + if (((same(s, "+") || same(s, "+%")) || same(s, "-")) || same(s, "-%")) { + return ts_in_fold(ts_fold_val(s, a, b)); + } + if (same(s, "*") || same(s, "*%")) { + if ((a == 0) || (b == 0)) { + return true; + } + let mq: i64 = (TS_FOLD_MAX / ts_abs(b)); + return (ts_abs(a) <= mq); + } + if (same(s, "/") || same(s, "%")) { + return ((a >= 0) && (b > 0)); + } + if same(s, "<<") { + if (((a < 0) || (b < 0)) || (b >= 53)) { + return false; + } + let sq: i64 = (TS_FOLD_MAX / ts_pow2(b)); + return (a <= sq); + } + if same(s, ">>") { + return ((a >= 0) && (b >= 0)); + } + if op_bitwise(s) { + return ((a >= 0) && (b >= 0)); + } + if ((op_class(s) == OP_CMP) || (op_class(s) == OP_LOGIC)) { + return true; + } + return false; +} + +pub fn ts_fold_val(s: &[u8], a: i64, b: i64) -> i64 { + if (same(s, "+") || same(s, "+%")) { + return (a + b); + } + if (same(s, "-") || same(s, "-%")) { + return (a - b); + } + if (same(s, "*") || same(s, "*%")) { + return (a * b); + } + if same(s, "/") { + let dq: i64 = (a / b); + return dq; + } + if same(s, "%") { + let dr: i64 = (a % b); + return dr; + } + if same(s, "<<") { + return (a * ts_pow2(b)); + } + if same(s, ">>") { + if (b >= 53) { + return 0; + } + let ds: i64 = (a / ts_pow2(b)); + return ds; + } + if same(s, "&") { + return (a & b); + } + if same(s, "|") { + return (a | b); + } + if same(s, "^") { + return (a ^ b); + } + let mut t: bool = false; + if same(s, "==") { + t = (a == b); + } + if same(s, "!=") { + t = (a != b); + } + if same(s, "<") { + t = (a < b); + } + if same(s, "<=") { + t = (a <= b); + } + if same(s, ">") { + t = (a > b); + } + if same(s, ">=") { + t = (a >= b); + } + if (same(s, "and") || same(s, "&&")) { + t = ((a == 1) && (b == 1)); + } + if (same(s, "or") || same(s, "||")) { + t = ((a == 1) || (b == 1)); + } + if t { + return 1; + } + return 0; +} + +pub fn ts_in_fold(v: i64) -> bool { + return ((v >= (0 - TS_FOLD_MAX)) && (v <= TS_FOLD_MAX)); +} + +pub fn ts_int(t: i64) -> bool { + return (ts_unsigned(t) || ts_signed(t)); +} + +pub fn ts_max(t: i64) -> i64 { + if ts_signed(t) { + return (ts_pow2((ts_width(t) - 1)) - 1); + } + if (ts_unsigned(t) || (t == TS_BOOL)) { + return (ts_pow2(ts_width(t)) - 1); + } + return 0; +} + +pub fn ts_min(t: i64) -> i64 { + if ts_signed(t) { + return (0 - ts_pow2((ts_width(t) - 1))); + } + return 0; +} + +pub fn ts_peer(a: i64, b: i64) -> i64 { + if (a == TS_COMPTIME) { + if ((b == TS_COMPTIME) || ts_scalar(b)) { + return b; + } + return TS_BAD; + } + if (b == TS_COMPTIME) { + if ts_scalar(a) { + return a; + } + return TS_BAD; + } + if ((a == TS_BOOL) && (b == TS_BOOL)) { + return TS_BOOL; + } + if ts_coerces(a, b) { + return b; + } + if ts_coerces(b, a) { + return a; + } + return TS_BAD; +} + +pub fn ts_pow2(w: i64) -> i64 { + let mut m: i64 = 1; + let mut j: i64 = 0; + while ((j < w) && (j < 62)) { + m = (m * 2); + j = (j + 1); + } + return m; +} + +pub fn ts_reserved(s: &[u8]) -> bool { + if ((s.len() >= 4) && same(&s[(0) as usize..(4) as usize], "t27_")) { + return true; + } + let mut j: usize = 0; + while (j < 59) { + if same(s, TS_KEYWORDS[(j) as usize]) { + return true; + } + j = (j + 1); + } + return false; +} + +pub fn ts_scalar(t: i64) -> bool { + return (ts_int(t) || (t == TS_BOOL)); +} + +pub fn ts_signed(t: i64) -> bool { + return ((t > TS_SIGNED) && (t <= (TS_SIGNED + TS_MAX_WIDTH))); +} + +pub fn ts_type(s: &[u8], a: usize, b: usize) -> i64 { + let p: usize = trim_lo(s, a, b); + let q: usize = trim_hi(s, p, b); + if ((q == p) || same(&s[(p) as usize..(q) as usize], "void")) { + return TS_VOID; + } + if same(&s[(p) as usize..(q) as usize], "bool") { + return TS_BOOL; + } + if same(&s[(p) as usize..(q) as usize], "comptime_int") { + return TS_COMPTIME; + } + if ((((s[(p) as usize] != 117) && (s[(p) as usize] != 105)) || ((q - p) < 2)) || ((q - p) > 3)) { + return TS_BAD; + } + let p1: usize = (p + 1); + let mut w: i64 = 0; + let mut j: usize = p1; + while (j < q) { + if !(digit(s[(j) as usize])) { + return TS_BAD; + } + let dv: i64 = ((s[(j) as usize] - 48) as _); + w = ((w * 10) + dv); + j = (j + 1); + } + if (((s[(p1) as usize] == 48) || (w < 1)) || (w > TS_MAX_WIDTH)) { + return TS_BAD; + } + if (s[(p) as usize] == 105) { + return (TS_SIGNED + w); + } + return w; +} + +pub fn ts_unsigned(t: i64) -> bool { + return ((t >= 1) && (t <= TS_MAX_WIDTH)); +} + +pub fn ts_width(t: i64) -> i64 { + if ts_unsigned(t) { + return t; + } + if ts_signed(t) { + return (t - TS_SIGNED); + } + if (t == TS_BOOL) { + return 1; + } + return 0; +} + +pub const B_CR: u8 = 13; + +pub const B_LT: u8 = 60; + +pub const B_SPACE: u8 = 32; + +pub const B_TAB: u8 = 9; + +pub const B_UNDERSCORE: u8 = 95; + +pub const MAPPED_NAMES: [&'static str; 8] = ["str", "string", "float", "double", "int", "uint", "GF16", "gf16"]; + +pub const ZIG_NAMES: [&'static str; 21] = ["bool", "void", "type", "anyerror", "anyframe", "anyopaque", "noreturn", "usize", "isize", "comptime_int", "comptime_float", "c_char", "c_short", "c_ushort", "c_int", "c_uint", "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_longdouble"]; + +pub fn alpha(c: u8) -> bool { + return (((c >= 65) && (c <= 90)) || ((c >= 97) && (c <= 122))); +} + +pub fn digit(c: u8) -> bool { + return ((c >= 48) && (c <= 57)); +} + +pub fn ident(s: &[u8]) -> bool { + if (s.len() == 0) { + return false; + } + if (!(alpha(s[0])) && (s[0] != B_UNDERSCORE)) { + return false; + } + let mut i: usize = 1; + while (i < s.len()) { + if !(word(s[(i) as usize])) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn same(s: &[u8], w: &'static str) -> bool { + if (s.len() != w.len()) { + return false; + } + let mut k: usize = 0; + while (k < w.len()) { + if (s[(k) as usize] != w.as_bytes()[(k) as usize]) { + return false; + } + k = (k + 1); + } + return true; +} + +pub fn trim_hi(s: &[u8], lo: usize, hi: usize) -> usize { + let mut j: usize = hi; + while (j > lo) { + let n: usize = ws_before(s, lo, j); + if (n == 0) { + return j; + } + j = (j - n); + } + return j; +} + +pub fn trim_lo(s: &[u8], lo: usize, hi: usize) -> usize { + let mut i: usize = lo; + while (i < hi) { + let n: usize = ws_at(s, i, hi); + if (n == 0) { + return i; + } + i = (i + n); + } + return i; +} + +pub fn undeclared(base: &[u8], declared: bool) -> bool { + let mut i: usize = 0; + while (i < base.len()) { + if (base[(i) as usize] == B_LT) { + return true; + } + i = (i + 1); + } + if (declared || !(ident(base))) { + return false; + } + let mut k: usize = 0; + while (k < 8) { + if same(base, MAPPED_NAMES[(k) as usize]) { + return false; + } + k = (k + 1); + } + return !(zig_type_name(base)); +} + +pub fn word(c: u8) -> bool { + return ((alpha(c) || digit(c)) || (c == B_UNDERSCORE)); +} + +pub fn ws_at(s: &[u8], i: usize, hi: usize) -> usize { + let c: u8 = s[(i) as usize]; + if (((c >= B_TAB) && (c <= B_CR)) || (c == B_SPACE)) { + return 1; + } + if (c == 194) { + if (((i + 1) < hi) && ((s[((i + 1)) as usize] == 133) || (s[((i + 1)) as usize] == 160))) { + return 2; + } + return 0; + } + if ((i + 2) >= hi) { + return 0; + } + let b1: u8 = s[((i + 1)) as usize]; + let b2: u8 = s[((i + 2)) as usize]; + if (((c == 225) && (b1 == 154)) && (b2 == 128)) { + return 3; + } + if (((c == 226) && (b1 == 128)) && (((((b2 >= 128) && (b2 <= 138)) || (b2 == 168)) || (b2 == 169)) || (b2 == 175))) { + return 3; + } + if (((c == 226) && (b1 == 129)) && (b2 == 159)) { + return 3; + } + if (((c == 227) && (b1 == 128)) && (b2 == 128)) { + return 3; + } + return 0; +} + +pub fn ws_before(s: &[u8], lo: usize, end: usize) -> usize { + let c: u8 = s[((end - 1)) as usize]; + if (((c >= B_TAB) && (c <= B_CR)) || (c == B_SPACE)) { + return 1; + } + if (((end - lo) >= 2) && (ws_at(s, (end - 2), end) == 2)) { + return 2; + } + if (((end - lo) >= 3) && (ws_at(s, (end - 3), end) == 3)) { + return 3; + } + return 0; +} + +pub fn zig_type_name(s: &[u8]) -> bool { + let mut k: usize = 0; + while (k < 21) { + if same(s, ZIG_NAMES[(k) as usize]) { + return true; + } + k = (k + 1); + } + if (s.len() < 2) { + return false; + } + if (((s[0] != 117) && (s[0] != 105)) && (s[0] != 102)) { + return false; + } + let mut i: usize = 1; + while (i < s.len()) { + if !(digit(s[(i) as usize])) { + return false; + } + i = (i + 1); + } + return true; +} + +pub const B_COLON_V: u8 = 58; + +pub const B_NL: u8 = 10; + +pub const FLAG_MUT: usize = 1; + +pub const FLAG_PUB: usize = 2; + +pub const KIND_NAMES: [&'static str; 35] = ["Module", "UseDecl", "ConstDecl", "EnumDecl", "EnumVariant", "StructDecl", "FnDecl", "InvariantBlock", "TestBlock", "BenchBlock", "ExprLiteral", "ExprIdentifier", "ExprEnumValue", "ExprCall", "ExprFieldAccess", "ExprSwitch", "ExprBinary", "ExprUnary", "ExprReturn", "ExprIndex", "ExprIf", "ExprStructLit", "ExprArrayLiteral", "ExprTuple", "ExprCast", "ExprRange", "StmtLocal", "StmtAssign", "StmtIf", "StmtWhile", "StmtFor", "StmtForRange", "StmtBreak", "StmtContinue", "StmtExpr"]; + +pub const META: usize = 16; + +pub const OP_ARITH: u8 = 1; + +pub const OP_BAD: u8 = 0; + +pub const OP_CMP: u8 = 3; + +pub const OP_LOGIC: u8 = 4; + +pub const OP_SHIFT: u8 = 2; + +pub const U64_MAX: u64 = 18446744073709551615; + +pub fn colon_at(ptext: &[u8], s: usize, e: usize) -> usize { + let mut j: usize = s; + while ((j < e) && (ptext[(j) as usize] != B_COLON_V)) { + j = (j + 1); + } + return j; +} + +pub fn digit_of(c: u8) -> u64 { + if ((c >= 48) && (c <= 57)) { + return ((c - 48) as u64); + } + if ((c >= 97) && (c <= 102)) { + return ((c - 87) as u64); + } + if ((c >= 65) && (c <= 70)) { + return ((c - 55) as u64); + } + return 99; +} + +pub fn find_decl(nodes: &[u8], text: &[u8], a: usize, b: usize) -> i64 { + let mut c: usize = 1; + while (c < end(nodes, 0)) { + let k: u8 = kind(nodes, c); + if (((k == K_CONST_DECL) || (k == K_STRUCT_DECL)) && field_is(nodes, text, c, F_NAME, &text[(a) as usize..(b) as usize])) { + let ci: i64 = (c as _); + return ci; + } + c = end(nodes, c); + } + return NO_NODE; +} + +pub fn find_fn(nodes: &[u8], text: &[u8], a: usize, b: usize) -> i64 { + let mut c: usize = 1; + while (c < end(nodes, 0)) { + if ((kind(nodes, c) == K_FN_DECL) && field_is(nodes, text, c, F_NAME, &text[(a) as usize..(b) as usize])) { + let ci: i64 = (c as _); + return ci; + } + c = end(nodes, c); + } + return NO_NODE; +} + +pub fn is_extern(nodes: &[u8], i: usize) -> bool { + return ((kind(nodes, i) == K_FN_DECL) && (end(nodes, i) == (i + 1))); +} + +pub fn is_mut(meta: &[u8], i: usize) -> bool { + return ((meta_word(meta, i, 0) & FLAG_MUT) == FLAG_MUT); +} + +pub fn is_pub(meta: &[u8], i: usize) -> bool { + return ((meta_word(meta, i, 0) & FLAG_PUB) == FLAG_PUB); +} + +pub fn kind_label(k: u8) -> &'static str { + let ki: usize = (k as usize); + if (ki < 35) { + return KIND_NAMES[(ki) as usize]; + } + return "node"; +} + +pub fn line_of(meta: &[u8], i: usize) -> usize { + return meta_word(meta, i, 1); +} + +pub fn line_stop(ptext: &[u8], s: usize, b: usize) -> usize { + let mut j: usize = s; + while ((j < b) && (ptext[(j) as usize] != B_NL)) { + j = (j + 1); + } + return j; +} + +pub fn lit_ok(s: &[u8], a: usize, b: usize) -> bool { + return (lit_scan(s, a, b, true) == 1); +} + +pub fn lit_radix(s: &[u8], a: usize, b: usize) -> u64 { + if (((b - a) < 3) || (s[(a) as usize] != 48)) { + return 10; + } + let c1: u8 = s[((a + 1)) as usize]; + if ((c1 == 120) || (c1 == 88)) { + return 16; + } + if (c1 == 111) { + return 8; + } + if (c1 == 98) { + return 2; + } + return 10; +} + +pub fn lit_scan(s: &[u8], a: usize, b: usize, check: bool) -> u64 { + if (b <= a) { + return 0; + } + let base: u64 = lit_radix(s, a, b); + let mut j: usize = a; + if (base != 10) { + j = (a + 2); + } + let mut v: u64 = 0; + let mut digits: usize = 0; + while (j < b) { + let c: u8 = s[(j) as usize]; + if (c != 95) { + let d: u64 = digit_of(c); + if (d >= base) { + return 0; + } + if (v > ((U64_MAX - d) / base)) { + return 0; + } + v = ((v * base) + d); + digits = (digits + 1); + } + j = (j + 1); + } + if (digits == 0) { + return 0; + } + if check { + return 1; + } + return v; +} + +pub fn lit_val(s: &[u8], a: usize, b: usize) -> u64 { + return lit_scan(s, a, b, false); +} + +pub fn loop_block(nodes: &[u8], text: &[u8], w: usize, name: &'static str) -> i64 { + let mut c: usize = (w + 1); + while (c < end(nodes, w)) { + if ((kind(nodes, c) == K_MODULE) && field_eq(nodes, text, c, F_NAME, name)) { + let ci: i64 = (c as _); + return ci; + } + c = end(nodes, c); + } + return NO_NODE; +} + +pub fn meta_word(meta: &[u8], i: usize, w: usize) -> usize { + return u32_at(meta, ((i * META) + (w * 4))); +} + +pub fn op_bitwise(s: &[u8]) -> bool { + return ((same(s, "&") || same(s, "|")) || same(s, "^")); +} + +pub fn op_class(s: &[u8]) -> u8 { + if ((((same(s, "+") || same(s, "-")) || same(s, "*")) || same(s, "/")) || same(s, "%")) { + return OP_ARITH; + } + if ((same(s, "+%") || same(s, "-%")) || same(s, "*%")) { + return OP_ARITH; + } + if ((same(s, "&") || same(s, "|")) || same(s, "^")) { + return OP_ARITH; + } + if (same(s, "<<") || same(s, ">>")) { + return OP_SHIFT; + } + if (((((same(s, "==") || same(s, "!=")) || same(s, "<")) || same(s, "<=")) || same(s, ">")) || same(s, ">=")) { + return OP_CMP; + } + if (((same(s, "and") || same(s, "or")) || same(s, "&&")) || same(s, "||")) { + return OP_LOGIC; + } + return OP_BAD; +} + +pub fn own_continue(nodes: &[u8], a: usize, b: usize) -> bool { + let mut j: usize = a; + while (j < b) { + let k: u8 = kind(nodes, j); + if (((k == K_STMT_WHILE) || (k == K_STMT_FOR)) || (k == K_STMT_FOR_RANGE)) { + j = end(nodes, j); + } else { + if (k == K_STMT_CONTINUE) { + return true; + } + j = (j + 1); + } + } + return false; +} + +pub fn param_at(ptext: &[u8], a: usize, b: usize, k: usize) -> usize { + let mut seen: usize = 0; + let mut j: usize = a; + while ((j < b) && (seen < k)) { + if (ptext[(j) as usize] == B_NL) { + seen = (seen + 1); + } + j = (j + 1); + } + return j; +} + +pub fn param_count(ptext: &[u8], a: usize, b: usize) -> usize { + let mut n: usize = 0; + let mut j: usize = a; + while (j < b) { + if (ptext[(j) as usize] == B_NL) { + n = (n + 1); + } + j = (j + 1); + } + return n; +} + +pub fn params_hi(meta: &[u8], i: usize) -> usize { + return meta_word(meta, i, 3); +} + +pub fn params_lo(meta: &[u8], i: usize) -> usize { + return meta_word(meta, i, 2); +} + +pub fn vput32(buf: &mut [u8], at: usize, v: usize) -> () { + buf[(at) as usize] = ((v % 256) as _); + buf[((at + 1)) as usize] = (((v / 256) % 256) as _); + buf[((at + 2)) as usize] = (((v / 65536) % 256) as _); + buf[((at + 3)) as usize] = ((v / 16777216) as _); +} + +pub fn vset(buf: &mut [u8], i: usize, f: usize, a: usize, b: usize) -> () { + vput32(buf, (((i * REC) + 8) + (f * 8)), a); + vput32(buf, (((i * REC) + 12) + (f * 8)), b); +} + diff --git a/specs/tri/t27b/ts_emit.t27 b/specs/tri/t27b/ts_emit.t27 new file mode 100644 index 0000000000..e8501d6991 --- /dev/null +++ b/specs/tri/t27b/ts_emit.t27 @@ -0,0 +1,1413 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/ts_emit.t27 -- the writer of `t27b ts` (issue #9994, phase 1). It walks the t27c AST of one +; spec, as `flat` in cli/t27b/src/lower.rs writes it (see specs/tri/t27b/ast_walk.t27), and writes a +; TypeScript module: the spec's consts, its pure scalar fns with their real bodies, and each test and +; invariant as an exported self-check. Every decision it takes is in specs/tri/t27b/ts_plan.t27; this file +; only spells them. It reaches cli/t27b only through `t27c gen-rust` (gen/rust/tri/t27b/ts_emit.rs). +; -- +; The entry point is emit(nodes, text, meta, ptext, work, out), with the arguments of verilog_emit.t27's +; emit, so the CLI calls either the same way: the AST records and their text; per node a meta record of four +; u32 (flags, line, params span; verilog_plan.t27); the params text; a work area (unused here); the output +; buffer. It returns the length of the TypeScript it wrote at out[0 ..], or minus the length of the refusal +; it wrote there instead: "t27b: ts: unsupported construct `` at line ". A refusal writes no +; TypeScript at all: a module that lowers in part would be a module that lies about the spec. +; -- +; The output. A header naming the module, ts_plan.t27's prelude, then the items in the order of the spec: +; `[export ]const N: number = e;`, `[export ]function f(p: number): number { ... }`, and +; `export function t27_test_(): number`, which zeroes t27_n, runs the body and returns how many +; asserts ran. Then the tables t27_tests and t27_invariants and ts_plan.t27's coda (t27_report, +; t27_failures). Node 24 runs it as `.mts`, stripping the types. +; -- +; A module-level const is written by name inside a fn or a test, which run after the module is evaluated; +; inside another module-level const's value it is folded to its value when it folds, so the order of the +; consts in the spec does not matter. A const whose value does not fold and names a const declared below it +; is the one case left to TypeScript's temporal dead zone (it throws ReferenceError at load). +; ASCII only (L3) +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bTsEmit; + +use tri::t27b::ts_plan; +use tri::t27b::text_escape; + +/// The room at the end of `out` a refusal is first written to, so the text before it survives. +pub const MSG_ROOM : usize = 400; +/// The longest name a refusal quotes. +pub const NAME_CAP : usize = 120; + +/// Symbols: a resolved name is a tag times SYM_UNIT plus a node index (a parameter: its position). +pub const SYM_UNIT : i64 = 1099511627776; +pub const SYM_LOCAL : i64 = 1; +pub const SYM_PARAM : i64 = 2; +pub const SYM_TOP : i64 = 3; +pub const SYM_FN : i64 = 4; +pub const NO_SYM : i64 = -1; + +// ---------------------------------------------------------------------------------------------------------- +// Writing bytes. +// ---------------------------------------------------------------------------------------------------------- + +/// Writes s[a .. b] at out[at ..]. +pub fn put_bytes(out: []u8, at: usize, s: []const u8, a: usize, b: usize) -> usize { + var k : usize = a; + var q : usize = at; + while (k < b) { + out[q] = s[k]; + q = q + 1; + k = k + 1; + } + return q; +} + +/// How many decimal digits v has. +pub fn dec_len(v: u64) -> usize { + var n : usize = 1; + var x : u64 = v; + while (x >= 10) { + x = x / 10; + n = n + 1; + } + return n; +} + +/// Writes v in decimal. +pub fn put_dec(out: []u8, at: usize, v: u64) -> usize { + const n : usize = dec_len(v); + var x : u64 = v; + var k : usize = n; + while (k > 0) { + const db : u8 = @intCast(x % 10 + 48); + out[at + k - 1] = db; + x = x / 10; + k = k - 1; + } + return at + n; +} + +/// Writes n spaces. +pub fn put_ind(out: []u8, at: usize, n: usize) -> usize { + var k : usize = 0; + while (k < n) { + out[at + k] = 32; + k = k + 1; + } + return at + n; +} + +/// Writes v in decimal; a negative value in parentheses, `(-5)`, so no operator before it reads it +/// otherwise. +pub fn put_i64(out: []u8, at: usize, v: i64) -> usize { + if (v < 0) { + var q : usize = put_text(out, at, "(-"); + const m : u64 = (0 - v) as u64; + q = put_dec(out, q, m); + return put_text(out, q, ")"); + } + const u : u64 = v as u64; + return put_dec(out, at, u); +} + +/// Writes v as a value of the scalar type t: `true`/`false` for a bool, the number for an integer. +pub fn put_lit(out: []u8, at: usize, v: i64, t: i64) -> usize { + if (t == TS_BOOL) { + if (v == 0) { return put_text(out, at, "false"); } + return put_text(out, at, "true"); + } + return put_i64(out, at, v); +} + +/// Writes the signedness argument of t27_wrap, t27_shl and t27_shr and the closing parenthesis. +pub fn put_sign(out: []u8, at: usize, t: i64) -> usize { + if (ts_signed(t)) { return put_text(out, at, ", true)"); } + return put_text(out, at, ", false)"); +} + +/// The first non-zero source line at or after node i within its subtree, else the nearest one before it. +pub fn line_near(nodes: []const u8, meta: []const u8, i: usize) -> usize { + var j : usize = i; + while (j < end(nodes, i)) { + if (line_of(meta, j) != 0) { return line_of(meta, j); } + j = j + 1; + } + var k : usize = i; + while (k > 0) { + k = k - 1; + if (line_of(meta, k) != 0) { return line_of(meta, k); } + } + return 0; +} + +/// Writes a refusal at the end of out: "t27b: ts: unsupported construct `` at line ", the +/// name being text[a .. b] (none when a == b). Returns minus its length. +pub fn fail(out: []u8, nodes: []const u8, meta: []const u8, text: []const u8, i: usize, what: str, a: usize, b: usize) -> i64 { + const base : usize = out.len - MSG_ROOM; + var q : usize = put_text(out, base, "t27b: ts: unsupported construct "); + q = put_text(out, q, what); + if (b > a) { + var nb : usize = b; + if (nb - a > NAME_CAP) { nb = a + NAME_CAP; } + q = put_text(out, q, " `"); + q = put_bytes(out, q, text, a, nb); + q = put_text(out, q, "`"); + } + q = put_text(out, q, " at line "); + q = put_dec(out, q, line_near(nodes, meta, i) as u64); + const len : i64 = @intCast(q - base); + return 0 - len; +} + +/// Copies the refusal of length n from the end of out to its start; returns minus n. +pub fn to_front(out: []u8, n: usize) -> i64 { + const base : usize = out.len - MSG_ROOM; + var k : usize = 0; + while (k < n) { + out[k] = out[base + k]; + k = k + 1; + } + const ni : i64 = @intCast(n); + return 0 - ni; +} + +/// A refusal with no node, written at the front. +pub fn fail_front(out: []u8, what: str) -> i64 { + var q : usize = put_text(out, 0, "t27b: ts: unsupported construct "); + q = put_text(out, q, what); + const l : i64 = @intCast(q); + return 0 - l; +} + +// ---------------------------------------------------------------------------------------------------------- +// Names, scopes and types. +// ---------------------------------------------------------------------------------------------------------- + +/// Is c a byte of an identifier (a letter, `_`, or a digit when it is not the first)? +pub fn name_char(c: u8, first: bool) -> bool { + if (c == 95) { return true; } + if ((c >= 97 and c <= 122) or (c >= 65 and c <= 90)) { return true; } + return !first and c >= 48 and c <= 57; +} + +/// Is s[a .. b] an identifier? +pub fn is_ident(s: []const u8, a: usize, b: usize) -> bool { + if (b <= a) { return false; } + if (!name_char(s[a], true)) { return false; } + var j : usize = a + 1; + while (j < b) { + if (!name_char(s[j], false)) { return false; } + j = j + 1; + } + return true; +} + +/// Is s[a .. b] a name the TypeScript cannot declare as it is: no identifier, or one ts_plan.t27 reserves? +pub fn bad_name(s: []const u8, a: usize, b: usize) -> bool { + if (!is_ident(s, a, b)) { return true; } + return ts_reserved(s[a .. b]); +} + +/// The child of node c that holds node d in its subtree (c < d < end(c)). +pub fn child_holding(nodes: []const u8, c: usize, d: usize) -> usize { + var x : usize = c + 1; + while (x < end(nodes, c)) { + if (d < end(nodes, x)) { return x; } + x = end(nodes, x); + } + return c; +} + +/// The node whose direct child d is, searching down from `root` (root < d). +pub fn parent_of(nodes: []const u8, root: usize, d: usize) -> usize { + var cur : usize = root; + while (true) { + const nx : usize = child_holding(nodes, cur, d); + if (nx == d or nx == cur) { return cur; } + cur = nx; + } + return cur; +} + +/// Is the local declared at node d (under item) in scope at node u: d comes before u, u is not in d's own +/// initializer, and d's enclosing block holds u? +pub fn in_scope(nodes: []const u8, item: usize, d: usize, u: usize) -> bool { + if (d >= u or u < end(nodes, d)) { return false; } + const pb : usize = parent_of(nodes, item, d); + return u < end(nodes, pb); +} + +/// The position of the parameter named text[a .. b] in fn `item`'s parameter lines, or -1. +pub fn param_index(text: []const u8, meta: []const u8, ptext: []const u8, item: usize, a: usize, b: usize) -> i64 { + const pa : usize = params_lo(meta, item); + const pb : usize = params_hi(meta, item); + var k : usize = 0; + var s : usize = pa; + while (s < pb) { + const e : usize = line_stop(ptext, s, pb); + const c : usize = colon_at(ptext, s, e); + if (c - s == b - a and has_at(ptext, s, c, text[a .. b])) { + const ki : i64 = @intCast(k); + return ki; + } + k = k + 1; + s = e + 1; + } + return -1; +} + +/// Where parameter k's name starts in the params text of `item`. +pub fn param_name_lo(meta: []const u8, ptext: []const u8, item: usize, k: usize) -> usize { + return param_at(ptext, params_lo(meta, item), params_hi(meta, item), k); +} + +/// Where parameter k's name ends. +pub fn param_name_hi(meta: []const u8, ptext: []const u8, item: usize, k: usize) -> usize { + const s : usize = param_name_lo(meta, ptext, item, k); + return colon_at(ptext, s, line_stop(ptext, s, params_hi(meta, item))); +} + +/// The type code of parameter k of `item`. +pub fn param_type(meta: []const u8, ptext: []const u8, item: usize, k: usize) -> i64 { + const s : usize = param_name_lo(meta, ptext, item, k); + const e : usize = line_stop(ptext, s, params_hi(meta, item)); + const c : usize = colon_at(ptext, s, e); + if (c >= e) { return TS_BAD; } + return ts_type(ptext, c + 1, e); +} + +/// Resolves the name text[a .. b] used at node u inside `item` (a fn, a test or an invariant; 0 for the +/// module scope): a local in scope, a parameter of item, a top-level declaration, or a top-level fn. NO_SYM +/// when there is none. +pub fn resolve(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, u: usize, a: usize, b: usize) -> i64 { + if (item > 0) { + var j : usize = u; + while (j > item + 1) { + j = j - 1; + if (kind(nodes, j) == K_STMT_LOCAL and field_is(nodes, text, j, F_NAME, text[a .. b])) { + if (in_scope(nodes, item, j, u)) { + const ji : i64 = @intCast(j); + return SYM_LOCAL * SYM_UNIT + ji; + } + } + } + if (kind(nodes, item) == K_FN_DECL) { + const pk : i64 = param_index(text, meta, ptext, item, a, b); + if (pk >= 0) { return SYM_PARAM * SYM_UNIT + pk; } + } + } + const d : i64 = find_decl(nodes, text, a, b); + if (d != NO_NODE) { return SYM_TOP * SYM_UNIT + d; } + const f : i64 = find_fn(nodes, text, a, b); + if (f != NO_NODE) { return SYM_FN * SYM_UNIT + f; } + return NO_SYM; +} + +/// The symbol of tag `tag` at node (or parameter position) `at`. +pub fn sym_of(tag: i64, at: usize) -> i64 { + const ai : i64 = @intCast(at); + return tag * SYM_UNIT + ai; +} + +/// The tag of a symbol. +pub fn sym_tag(s: i64) -> i64 { + const t : i64 = @divTrunc(s, SYM_UNIT); + return t; +} + +/// The node index (or parameter position) of a symbol. +pub fn sym_at(s: i64) -> usize { + const r : i64 = @rem(s, SYM_UNIT); + const ru : usize = @intCast(r); + return ru; +} + +/// The type of a symbol (a local, a parameter, a top-level const). +pub fn sym_type(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, s: i64, depth: usize) -> i64 { + const tg : i64 = sym_tag(s); + const at : usize = sym_at(s); + if (tg == SYM_PARAM) { return param_type(meta, ptext, item, at); } + if (tg == SYM_LOCAL or tg == SYM_TOP) { + if (kind(nodes, at) != K_CONST_DECL and kind(nodes, at) != K_STMT_LOCAL) { return TS_BAD; } + if (hi(nodes, at, F_TYPE) > lo(nodes, at, F_TYPE)) { + return ts_type(text, lo(nodes, at, F_TYPE), hi(nodes, at, F_TYPE)); + } + if (at + 1 >= end(nodes, at) or depth > 48) { return TS_BAD; } + var owner : usize = item; + if (tg == SYM_TOP) { owner = 0; } + const it : i64 = ty_of(nodes, text, meta, ptext, owner, at + 1, depth + 1); + if (it == TS_COMPTIME and is_mut(meta, at)) { return TS_BAD; } + return it; + } + return TS_BAD; +} + +/// The result type of the fn declared at node f. +pub fn fn_type(nodes: []const u8, text: []const u8, f: usize) -> i64 { + return ts_type(text, lo(nodes, f, F_RET), hi(nodes, f, F_RET)); +} + +/// The type of expression node i, used inside `item`. TS_BAD for anything this path does not hold. +pub fn ty_of(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, depth: usize) -> i64 { + if (depth > 64) { return TS_BAD; } + const k : u8 = kind(nodes, i); + if (k == K_EXPR_LITERAL) { + if (field_eq(nodes, text, i, F_VALUE, "true") or field_eq(nodes, text, i, F_VALUE, "false")) { return TS_BOOL; } + if (field_eq(nodes, text, i, F_KIND, "string")) { return TS_BAD; } + if (lit_ok(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE))) { return TS_COMPTIME; } + return TS_BAD; + } + if (k == K_EXPR_IDENTIFIER) { + const s : i64 = resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (s == NO_SYM or sym_tag(s) == SYM_FN) { return TS_BAD; } + return sym_type(nodes, text, meta, ptext, item, s, depth + 1); + } + if (k == K_EXPR_BINARY) { + if (children(nodes, i) != 2) { return TS_BAD; } + const l : usize = i + 1; + const r : usize = end(nodes, l); + const oc : u8 = op_class(text[lo(nodes, i, F_OP) .. hi(nodes, i, F_OP)]); + if (oc == OP_CMP or oc == OP_LOGIC) { return TS_BOOL; } + const lt : i64 = ty_of(nodes, text, meta, ptext, item, l, depth + 1); + if (oc == OP_SHIFT) { return lt; } + if (oc == OP_ARITH) { + const rt : i64 = ty_of(nodes, text, meta, ptext, item, r, depth + 1); + const pt : i64 = ts_peer(lt, rt); + if (pt == TS_BOOL and !op_bitwise(text[lo(nodes, i, F_OP) .. hi(nodes, i, F_OP)])) { return TS_BAD; } + return pt; + } + return TS_BAD; + } + if (k == K_EXPR_UNARY) { + if (children(nodes, i) != 1) { return TS_BAD; } + if (field_eq(nodes, text, i, F_OP, "!")) { return TS_BOOL; } + const ot : i64 = ty_of(nodes, text, meta, ptext, item, i + 1, depth + 1); + if (field_eq(nodes, text, i, F_OP, "~") and ts_int(ot)) { return ot; } + if (field_eq(nodes, text, i, F_OP, "-") and (ts_signed(ot) or ot == TS_COMPTIME)) { return ot; } + return TS_BAD; + } + if (k == K_EXPR_CAST) { + return ts_type(text, lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + if (k == K_EXPR_CALL) { + const f : i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (f == NO_NODE) { return TS_BAD; } + const fi : usize = @intCast(f); + return fn_type(nodes, text, fi); + } + if (k == K_EXPR_IF) { + if (children(nodes, i) != 3) { return TS_BAD; } + const a1 : usize = end(nodes, i + 1); + const a2 : usize = end(nodes, a1); + return ts_peer(ty_of(nodes, text, meta, ptext, item, a1, depth + 1), ty_of(nodes, text, meta, ptext, item, a2, depth + 1)); + } + return TS_BAD; +} + +// ---------------------------------------------------------------------------------------------------------- +// Folding, over i64 and within ts_plan.t27's range. +// ---------------------------------------------------------------------------------------------------------- + +/// The declaration a name used at node i stands for when it is a constant one: a top-level `const` or a +/// `const` local, with a value; NO_NODE otherwise. +pub fn const_decl(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize) -> i64 { + const s : i64 = resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + const tg : i64 = sym_tag(s); + if (s == NO_SYM or (tg != SYM_TOP and tg != SYM_LOCAL)) { return NO_NODE; } + const d : usize = sym_at(s); + if (kind(nodes, d) != K_CONST_DECL and kind(nodes, d) != K_STMT_LOCAL) { return NO_NODE; } + if (is_mut(meta, d) or d + 1 >= end(nodes, d)) { return NO_NODE; } + if (ident_is(nodes, text, d + 1, "undefined")) { return NO_NODE; } + const di : i64 = @intCast(d); + return di; +} + +/// Does expression node i fold to a value: a literal within the folding range, `true`/`false`, a constant +/// that fits its declared type, a cast of one that fits, `-` and `!` of one, and a binary operator over two +/// of them that ts_plan.t27's ts_fold_ok folds? +pub fn fold_ok(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, depth: usize) -> bool { + if (depth > 48) { return false; } + const k : u8 = kind(nodes, i); + if (k == K_EXPR_LITERAL) { + if (field_eq(nodes, text, i, F_VALUE, "true") or field_eq(nodes, text, i, F_VALUE, "false")) { return true; } + if (field_eq(nodes, text, i, F_KIND, "string")) { return false; } + if (!lit_ok(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE))) { return false; } + return lit_val(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)) <= 4503599627370496; + } + if (k == K_EXPR_IDENTIFIER) { + const d : i64 = const_decl(nodes, text, meta, ptext, item, i); + if (d == NO_NODE) { return false; } + const di : usize = @intCast(d); + var owner : usize = item; + if (sym_tag(resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME))) == SYM_TOP) { owner = 0; } + if (!fold_ok(nodes, text, meta, ptext, owner, di + 1, depth + 1)) { return false; } + if (hi(nodes, di, F_TYPE) > lo(nodes, di, F_TYPE)) { + const dt : i64 = ts_type(text, lo(nodes, di, F_TYPE), hi(nodes, di, F_TYPE)); + return ts_fits(fold_val(nodes, text, meta, ptext, owner, di + 1, depth + 1), dt); + } + return true; + } + if (k == K_EXPR_CAST) { + if (!fold_ok(nodes, text, meta, ptext, item, i + 1, depth + 1)) { return false; } + const ct : i64 = ts_type(text, lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + return ts_fits(fold_val(nodes, text, meta, ptext, item, i + 1, depth + 1), ct); + } + if (k == K_EXPR_UNARY and children(nodes, i) == 1) { + if (field_eq(nodes, text, i, F_OP, "-") or field_eq(nodes, text, i, F_OP, "!")) { + return fold_ok(nodes, text, meta, ptext, item, i + 1, depth + 1); + } + return false; + } + if (k == K_EXPR_BINARY and children(nodes, i) == 2) { + const r : usize = end(nodes, i + 1); + if (!fold_ok(nodes, text, meta, ptext, item, i + 1, depth + 1) or !fold_ok(nodes, text, meta, ptext, item, r, depth + 1)) { return false; } + const a : i64 = fold_val(nodes, text, meta, ptext, item, i + 1, depth + 1); + const b : i64 = fold_val(nodes, text, meta, ptext, item, r, depth + 1); + return ts_fold_ok(text[lo(nodes, i, F_OP) .. hi(nodes, i, F_OP)], a, b); + } + return false; +} + +/// The value of expression node i, when fold_ok says it folds. +pub fn fold_val(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, depth: usize) -> i64 { + if (depth > 48) { return 0; } + const k : u8 = kind(nodes, i); + if (k == K_EXPR_LITERAL) { + if (field_eq(nodes, text, i, F_VALUE, "true")) { return 1; } + if (field_eq(nodes, text, i, F_VALUE, "false")) { return 0; } + const lv : u64 = lit_val(text, lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)); + if (lv > 4503599627370496) { return 0; } + const li : i64 = @intCast(lv); + return li; + } + if (k == K_EXPR_IDENTIFIER) { + const d : i64 = const_decl(nodes, text, meta, ptext, item, i); + if (d == NO_NODE) { return 0; } + const di : usize = @intCast(d); + var owner : usize = item; + if (sym_tag(resolve(nodes, text, meta, ptext, item, i, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME))) == SYM_TOP) { owner = 0; } + return fold_val(nodes, text, meta, ptext, owner, di + 1, depth + 1); + } + if (k == K_EXPR_CAST) { return fold_val(nodes, text, meta, ptext, item, i + 1, depth + 1); } + if (k == K_EXPR_UNARY) { + const ov : i64 = fold_val(nodes, text, meta, ptext, item, i + 1, depth + 1); + if (field_eq(nodes, text, i, F_OP, "!")) { return 1 - ov; } + return 0 - ov; + } + if (k == K_EXPR_BINARY and children(nodes, i) == 2) { + const a : i64 = fold_val(nodes, text, meta, ptext, item, i + 1, depth + 1); + const b : i64 = fold_val(nodes, text, meta, ptext, item, end(nodes, i + 1), depth + 1); + return ts_fold_val(text[lo(nodes, i, F_OP) .. hi(nodes, i, F_OP)], a, b); + } + return 0; +} + +// ---------------------------------------------------------------------------------------------------------- +// Expressions. ex writes node i where a value of type `want` is taken: an untyped value is folded and +// written at that type, a typed one is checked to coerce to it. ex_typed writes node i at its own type. +// Each returns the end, or a refusal. +// ---------------------------------------------------------------------------------------------------------- + +pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, want: i64) -> i64 { + const k : u8 = kind(nodes, i); + const own : i64 = ty_of(nodes, text, meta, ptext, item, i, 0); + if (own == TS_COMPTIME) { + if (fold_ok(nodes, text, meta, ptext, item, i, 0)) { + if (!ts_int(want)) { return fail(out, nodes, meta, text, i, "untyped value with no integer type to take", 0, 0); } + const v : i64 = fold_val(nodes, text, meta, ptext, item, i, 0); + if (!ts_fits(v, want)) { return fail(out, nodes, meta, text, i, "literal that does not fit its type", 0, 0); } + const q0 : usize = put_lit(out, at, v, want); + const r0 : i64 = @intCast(q0); + return r0; + } + // An untyped operator over a typed operand, like `1 << i`, takes the type of its place. + if (ts_int(want) and (k == K_EXPR_BINARY or k == K_EXPR_UNARY or k == K_EXPR_IF)) { + return ex_typed(out, at, nodes, text, meta, ptext, item, i, want); + } + return fail(out, nodes, meta, text, i, "untyped value that does not fold", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (own == TS_BAD) { + if (k == K_EXPR_CALL) { + const f : i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (f == NO_NODE) { return fail(out, nodes, meta, text, i, "call of an undeclared fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + return fail(out, nodes, meta, text, i, "call of a fn whose result is no integer or bool", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (k == K_EXPR_IDENTIFIER) { + return fail(out, nodes, meta, text, i, "name with no integer or bool type", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (k == K_EXPR_BINARY and children(nodes, i) == 2) { + // the operand that does not lower is the one to name + const bl : usize = i + 1; + const br : usize = end(nodes, bl); + if (ty_of(nodes, text, meta, ptext, item, bl, 0) == TS_BAD) { return ex(out, at, nodes, text, meta, ptext, item, bl, TS_BAD); } + if (ty_of(nodes, text, meta, ptext, item, br, 0) == TS_BAD) { return ex(out, at, nodes, text, meta, ptext, item, br, TS_BAD); } + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (k == K_EXPR_UNARY) { + if (children(nodes, i) == 1 and ty_of(nodes, text, meta, ptext, item, i + 1, 0) == TS_BAD) { return ex(out, at, nodes, text, meta, ptext, item, i + 1, TS_BAD); } + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (k == K_EXPR_CAST) { + return fail(out, nodes, meta, text, i, "cast to type", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + if (k == K_EXPR_LITERAL) { + return fail(out, nodes, meta, text, i, "literal that is no integer or bool", lo(nodes, i, F_VALUE), hi(nodes, i, F_VALUE)); + } + return fail(out, nodes, meta, text, i, kind_label(k), lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + if (!ts_scalar(own)) { return fail(out, nodes, meta, text, i, "value that is no integer or bool", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + if (ts_scalar(want) and !ts_coerces(own, want)) { return fail(out, nodes, meta, text, i, "value whose type does not coerce to the type of its place", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + return ex_typed(out, at, nodes, text, meta, ptext, item, i, own); +} + +pub fn ex_typed(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, own: i64) -> i64 { + const k : u8 = kind(nodes, i); + var q : usize = at; + if (k == K_EXPR_LITERAL) { + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, item, i, 0), own); + const rl : i64 = @intCast(q); + return rl; + } + if (k == K_EXPR_IDENTIFIER) { + // in a module-level const's value a const is folded: TypeScript has not declared it yet when it is + // declared below + if (item == 0 and fold_ok(nodes, text, meta, ptext, 0, i, 0)) { + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, 0, i, 0), own); + } else { + q = put_bytes(out, q, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } + const ri : i64 = @intCast(q); + return ri; + } + if (k == K_EXPR_BINARY) { + if (children(nodes, i) != 2) { return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); } + const l : usize = i + 1; + const r : usize = end(nodes, l); + const oa : usize = lo(nodes, i, F_OP); + const ob : usize = hi(nodes, i, F_OP); + const oc : u8 = op_class(text[oa .. ob]); + const lt : i64 = ty_of(nodes, text, meta, ptext, item, l, 0); + const rt : i64 = ty_of(nodes, text, meta, ptext, item, r, 0); + if (lt == TS_BAD) { return ex(out, q, nodes, text, meta, ptext, item, l, TS_BAD); } + if (rt == TS_BAD) { return ex(out, q, nodes, text, meta, ptext, item, r, TS_BAD); } + if (oc == OP_CMP or oc == OP_LOGIC) { + var pt : i64 = ts_peer(lt, rt); + if (oc == OP_LOGIC) { pt = TS_BOOL; } + if (pt == TS_COMPTIME) { + // two untyped operands: the comparison folds + if (!fold_ok(nodes, text, meta, ptext, item, i, 0)) { return fail(out, nodes, meta, text, i, "comparison of untyped values that does not fold", oa, ob); } + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, item, i, 0), TS_BOOL); + const rf : i64 = @intCast(q); + return rf; + } + if (!ts_scalar(pt)) { return fail(out, nodes, meta, text, i, "comparison of values whose types do not meet", oa, ob); } + if (pt == TS_BOOL and oc == OP_CMP and !same(text[oa .. ob], "==") and !same(text[oa .. ob], "!=")) { return fail(out, nodes, meta, text, i, "ordering of bools", oa, ob); } + q = put_text(out, q, "("); + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, item, l, pt); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, " "); + q = put_text(out, q, ts_cmp(text[oa .. ob])); + q = put_text(out, q, " "); + const r2 : i64 = ex(out, q, nodes, text, meta, ptext, item, r, pt); + if (r2 < 0) { return r2; } + q = @intCast(r2); + q = put_text(out, q, ")"); + const rc : i64 = @intCast(q); + return rc; + } + if (oc == OP_ARITH or oc == OP_SHIFT) { + return ex_op(out, q, nodes, text, meta, ptext, item, l, r, oa, ob, own); + } + return fail(out, nodes, meta, text, i, "operator", oa, ob); + } + if (k == K_EXPR_UNARY) { + if (children(nodes, i) != 1) { return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); } + if (field_eq(nodes, text, i, F_OP, "!")) { + q = put_text(out, q, "(!"); + const r3 : i64 = ex(out, q, nodes, text, meta, ptext, item, i + 1, TS_BOOL); + if (r3 < 0) { return r3; } + q = @intCast(r3); + q = put_text(out, q, ")"); + const rn : i64 = @intCast(q); + return rn; + } + if (field_eq(nodes, text, i, F_OP, "~") and ts_int(own)) { + q = put_text(out, q, "(~"); + const r4 : i64 = ex(out, q, nodes, text, meta, ptext, item, i + 1, own); + if (r4 < 0) { return r4; } + q = @intCast(r4); + if (own == 32) { + q = put_text(out, q, " >>> 0)"); + } else if (ts_unsigned(own)) { + q = put_text(out, q, " & "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + } else { + q = put_text(out, q, ")"); + } + const rt2 : i64 = @intCast(q); + return rt2; + } + if (field_eq(nodes, text, i, F_OP, "-") and ts_signed(own)) { + q = put_text(out, q, "t27_chk(-("); + const r5 : i64 = ex(out, q, nodes, text, meta, ptext, item, i + 1, own); + if (r5 < 0) { return r5; } + q = @intCast(r5); + q = put_text(out, q, "), "); + q = put_i64(out, q, ts_min(own)); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + const rm : i64 = @intCast(q); + return rm; + } + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (k == K_EXPR_CAST) { + return ex_cast(out, q, nodes, text, meta, ptext, item, i, own); + } + if (k == K_EXPR_CALL) { + return ex_call(out, q, nodes, text, meta, ptext, item, i); + } + if (k == K_EXPR_IF) { + if (children(nodes, i) != 3) { return fail(out, nodes, meta, text, i, "if expression", 0, 0); } + const c0 : usize = i + 1; + const a1 : usize = end(nodes, c0); + const a2 : usize = end(nodes, a1); + q = put_text(out, q, "("); + const r6 : i64 = ex(out, q, nodes, text, meta, ptext, item, c0, TS_BOOL); + if (r6 < 0) { return r6; } + q = @intCast(r6); + q = put_text(out, q, " ? "); + const r7 : i64 = ex(out, q, nodes, text, meta, ptext, item, a1, own); + if (r7 < 0) { return r7; } + q = @intCast(r7); + q = put_text(out, q, " : "); + const r8 : i64 = ex(out, q, nodes, text, meta, ptext, item, a2, own); + if (r8 < 0) { return r8; } + q = @intCast(r8); + q = put_text(out, q, ")"); + const rif : i64 = @intCast(q); + return rif; + } + return fail(out, nodes, meta, text, i, kind_label(k), lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); +} + +/// Writes the arithmetic or shift operator text[oa .. ob] over nodes l and r at type own, in its +/// ts_plan.t27 form: checked, wrapping, dividing, bitwise or shifting. +pub fn ex_op(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, l: usize, r: usize, oa: usize, ob: usize, own: i64) -> i64 { + const form : u8 = ts_arith(text[oa .. ob]); + if (form == AR_BAD) { return fail(out, nodes, meta, text, l, "operator", oa, ob); } + var q : usize = at; + if (form == AR_SHIFTS) { + if (!ts_int(own)) { return fail(out, nodes, meta, text, l, "shift of a value that is no integer", oa, ob); } + var rw : i64 = ty_of(nodes, text, meta, ptext, item, r, 0); + if (rw == TS_COMPTIME) { rw = 32; } + if (rw == TS_BAD) { return ex(out, at, nodes, text, meta, ptext, item, r, TS_BAD); } + if (!ts_unsigned(rw)) { return fail(out, nodes, meta, text, l, "shift by an amount that is no unsigned integer", oa, ob); } + q = put_text(out, q, ts_arith_op(text[oa .. ob])); + q = put_text(out, q, "("); + const s1 : i64 = ex(out, q, nodes, text, meta, ptext, item, l, own); + if (s1 < 0) { return s1; } + q = @intCast(s1); + q = put_text(out, q, ", "); + const s2 : i64 = ex(out, q, nodes, text, meta, ptext, item, r, rw); + if (s2 < 0) { return s2; } + q = @intCast(s2); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_width(own)); + q = put_sign(out, q, own); + const rs : i64 = @intCast(q); + return rs; + } + if (!ts_scalar(own) or (own == TS_BOOL and form != AR_BITS)) { return fail(out, nodes, meta, text, l, "operator", oa, ob); } + if (form == AR_DIVIDES and !ts_unsigned(own)) { return fail(out, nodes, meta, text, l, "division of a value that is no unsigned integer (t27c asks for @divTrunc or @rem)", oa, ob); } + const imul : bool = same(text[oa .. ob], "*%"); + if (form == AR_CHECKED) { q = put_text(out, q, "t27_chk(("); } + if (form == AR_WRAPS) { + q = put_text(out, q, "t27_wrap("); + if (imul) { + q = put_text(out, q, "Math.imul("); + } else { + q = put_text(out, q, "("); + } + } + if (form == AR_DIVIDES) { + q = put_text(out, q, ts_arith_op(text[oa .. ob])); + q = put_text(out, q, "("); + } + if (form == AR_BITS) { + if (own == 32 or own == TS_BOOL) { + q = put_text(out, q, "(("); + } else { + q = put_text(out, q, "("); + } + } + const e1 : i64 = ex(out, q, nodes, text, meta, ptext, item, l, own); + if (e1 < 0) { return e1; } + q = @intCast(e1); + if (form == AR_DIVIDES or imul) { + q = put_text(out, q, ", "); + } else { + q = put_text(out, q, " "); + q = put_text(out, q, ts_arith_op(text[oa .. ob])); + q = put_text(out, q, " "); + } + const e2 : i64 = ex(out, q, nodes, text, meta, ptext, item, r, own); + if (e2 < 0) { return e2; } + q = @intCast(e2); + if (form == AR_CHECKED) { + q = put_text(out, q, "), "); + q = put_i64(out, q, ts_min(own)); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + } + if (form == AR_WRAPS) { + q = put_text(out, q, "), "); + q = put_i64(out, q, ts_width(own)); + q = put_sign(out, q, own); + } + if (form == AR_DIVIDES) { q = put_text(out, q, ")"); } + if (form == AR_BITS) { + if (own == 32) { + q = put_text(out, q, ") >>> 0)"); + } else if (own == TS_BOOL) { + q = put_text(out, q, ") !== 0)"); + } else { + q = put_text(out, q, ")"); + } + } + const ro : i64 = @intCast(q); + return ro; +} + +/// Writes `x as T` (own is T): a narrower width wraps, a range that holds the source's is the value as it +/// is, any other cast is checked; a bool casts only to bool. +pub fn ex_cast(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, own: i64) -> i64 { + const src : i64 = ty_of(nodes, text, meta, ptext, item, i + 1, 0); + if (src == TS_COMPTIME) { return ex(out, at, nodes, text, meta, ptext, item, i + 1, own); } + if (src == TS_BAD) { return ex(out, at, nodes, text, meta, ptext, item, i + 1, TS_BAD); } + if (!ts_scalar(src)) { return fail(out, nodes, meta, text, i, "cast of a value that is no integer or bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } + if (src == TS_BOOL or own == TS_BOOL) { + if (src != own) { return fail(out, nodes, meta, text, i, "cast between bool and an integer", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } + return ex(out, at, nodes, text, meta, ptext, item, i + 1, src); + } + if (ts_coerces(src, own)) { return ex(out, at, nodes, text, meta, ptext, item, i + 1, src); } + const narrows : bool = ts_width(own) < ts_width(src); + var q : usize = at; + if (narrows) { + q = put_text(out, q, "t27_wrap("); + } else { + q = put_text(out, q, "t27_chk("); + } + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, item, i + 1, src); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, ", "); + if (narrows) { + q = put_i64(out, q, ts_width(own)); + q = put_sign(out, q, own); + } else { + q = put_i64(out, q, ts_min(own)); + q = put_text(out, q, ", "); + q = put_i64(out, q, ts_max(own)); + q = put_text(out, q, ")"); + } + const rc : i64 = @intCast(q); + return rc; +} + +/// Writes a call of a fn with a body: `f(a, b)`, each argument at its parameter's type. +pub fn ex_call(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize) -> i64 { + const f : i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (f == NO_NODE) { return fail(out, nodes, meta, text, i, "call of an undeclared fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + const fi : usize = @intCast(f); + if (is_extern(nodes, fi)) { return fail(out, nodes, meta, text, i, "call of an extern fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + const np : usize = param_count(ptext, params_lo(meta, fi), params_hi(meta, fi)); + if (children(nodes, i) != np) { return fail(out, nodes, meta, text, i, "call with the wrong number of arguments", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + var q : usize = put_bytes(out, at, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + q = put_text(out, q, "("); + var a : usize = i + 1; + var kx : usize = 0; + while (a < end(nodes, i)) { + if (kx > 0) { q = put_text(out, q, ", "); } + const pt : i64 = param_type(meta, ptext, fi, kx); + if (!ts_scalar(pt)) { return fail(out, nodes, meta, text, i, "call of a fn with a parameter that is no integer or bool", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + const r : i64 = ex(out, q, nodes, text, meta, ptext, item, a, pt); + if (r < 0) { return r; } + q = @intCast(r); + a = end(nodes, a); + kx = kx + 1; + } + q = put_text(out, q, ")"); + const rr : i64 = @intCast(q); + return rr; +} + +// ---------------------------------------------------------------------------------------------------------- +// Statements. TypeScript has t27's control flow, so each statement is written as it reads; `lp` is the +// innermost loop's node (0 outside a loop). +// ---------------------------------------------------------------------------------------------------------- + +/// Writes the statements of block node b, its children from c on, at indent n. +pub fn stmts(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, b: usize, c: usize, n: usize, lp: usize) -> i64 { + var q : usize = at; + var s : usize = c; + while (s < end(nodes, b)) { + const r : i64 = stmt(out, q, nodes, text, meta, ptext, item, s, n, lp); + if (r < 0) { return r; } + q = @intCast(r); + s = end(nodes, s); + } + const re : i64 = @intCast(q); + return re; +} + +/// Writes one statement at indent n. +pub fn stmt(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, s: usize, n: usize, lp: usize) -> i64 { + const k : u8 = kind(nodes, s); + var q : usize = at; + if (k == K_STMT_LOCAL) { + if (s + 1 >= end(nodes, s)) { return fail(out, nodes, meta, text, s, "local with no value", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); } + if (ident_is(nodes, text, s + 1, "undefined")) { return fail(out, nodes, meta, text, s, "local left undefined", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); } + const t : i64 = sym_type(nodes, text, meta, ptext, item, sym_of(SYM_LOCAL, s), 0); + if (t == TS_COMPTIME) { + // a constant local: every use is folded + const rc : i64 = @intCast(q); + return rc; + } + if (!ts_scalar(t)) { return fail(out, nodes, meta, text, s, "local that is no integer or bool", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); } + if (bad_name(text, lo(nodes, s, F_NAME), hi(nodes, s, F_NAME))) { return fail(out, nodes, meta, text, s, "name TypeScript reserves or the prelude uses", lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); } + q = put_ind(out, q, n); + if (is_mut(meta, s)) { + q = put_text(out, q, "let "); + } else { + q = put_text(out, q, "const "); + } + q = put_bytes(out, q, text, lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); + q = put_text(out, q, ": "); + q = put_text(out, q, ts_annot(t)); + q = put_text(out, q, " = "); + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, item, s + 1, t); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, ";\n"); + const rl : i64 = @intCast(q); + return rl; + } + if (k == K_STMT_ASSIGN) { + return assign(out, q, nodes, text, meta, ptext, item, s, n); + } + if (k == K_STMT_IF) { + return stmt_if(out, q, nodes, text, meta, ptext, item, s, n, lp); + } + if (k == K_STMT_WHILE) { + return stmt_while(out, q, nodes, text, meta, ptext, item, s, n); + } + if (k == K_EXPR_RETURN) { + if (kind(nodes, item) != K_FN_DECL) { return fail(out, nodes, meta, text, s, "return in a test", 0, 0); } + if (s + 1 >= end(nodes, s)) { return fail(out, nodes, meta, text, s, "return with no value from a fn", 0, 0); } + q = put_ind(out, q, n); + q = put_text(out, q, "return "); + const r2 : i64 = ex(out, q, nodes, text, meta, ptext, item, s + 1, fn_type(nodes, text, item)); + if (r2 < 0) { return r2; } + q = @intCast(r2); + q = put_text(out, q, ";\n"); + const rr : i64 = @intCast(q); + return rr; + } + if (k == K_STMT_BREAK or k == K_STMT_CONTINUE) { + if (lp == 0) { return fail(out, nodes, meta, text, s, kind_label(k), 0, 0); } + q = put_ind(out, q, n); + if (k == K_STMT_BREAK) { + q = put_text(out, q, "break;\n"); + } else { + q = put_text(out, q, "continue;\n"); + } + const rb : i64 = @intCast(q); + return rb; + } + if (k == K_STMT_EXPR) { + if (s + 1 < end(nodes, s) and kind(nodes, s + 1) == K_EXPR_CALL) { + return call_stmt(out, q, nodes, text, meta, ptext, item, s + 1, n); + } + return fail(out, nodes, meta, text, s, "expression statement", 0, 0); + } + return fail(out, nodes, meta, text, s, kind_label(k), lo(nodes, s, F_NAME), hi(nodes, s, F_NAME)); +} + +/// Writes an assignment to a `var` local: `x = e`; a compound operator `x op= e` as `x = ` in the +/// operator's form; `_ = e` evaluates e for what it throws, `void (e);`. +pub fn assign(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, s: usize, n: usize) -> i64 { + if (children(nodes, s) != 2) { return fail(out, nodes, meta, text, s, "assignment", 0, 0); } + const t : usize = s + 1; + const e : usize = end(nodes, t); + var q : usize = at; + if (ident_is(nodes, text, t, "_")) { + const et : i64 = ty_of(nodes, text, meta, ptext, item, e, 0); + if (et == TS_COMPTIME) { + const rd : i64 = @intCast(q); + return rd; + } + q = put_ind(out, q, n); + q = put_text(out, q, "void ("); + const r0 : i64 = ex(out, q, nodes, text, meta, ptext, item, e, et); + if (r0 < 0) { return r0; } + q = @intCast(r0); + q = put_text(out, q, ");\n"); + const rv : i64 = @intCast(q); + return rv; + } + if (kind(nodes, t) != K_EXPR_IDENTIFIER) { return fail(out, nodes, meta, text, s, "assignment to", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); } + const sy : i64 = resolve(nodes, text, meta, ptext, item, t, lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + if (sy == NO_SYM) { return fail(out, nodes, meta, text, s, "assignment that declares a name", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); } + if (sym_tag(sy) != SYM_LOCAL or !is_mut(meta, sym_at(sy))) { return fail(out, nodes, meta, text, s, "assignment to a name that is no var local", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); } + const tt : i64 = sym_type(nodes, text, meta, ptext, item, sy, 0); + if (!ts_scalar(tt)) { return fail(out, nodes, meta, text, s, "assignment to a name that is no integer or bool", lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); } + q = put_ind(out, q, n); + q = put_bytes(out, q, text, lo(nodes, t, F_NAME), hi(nodes, t, F_NAME)); + q = put_text(out, q, " = "); + const oa : usize = lo(nodes, s, F_OP); + const ob : usize = hi(nodes, s, F_OP); + if (ob > oa and !field_eq(nodes, text, s, F_OP, "=")) { + // `x op= e`: the operator is the text before `=` + const oc : u8 = op_class(text[oa .. ob - 1]); + if (oc != OP_ARITH and oc != OP_SHIFT) { return fail(out, nodes, meta, text, s, "compound assignment", oa, ob); } + const r1 : i64 = ex_op(out, q, nodes, text, meta, ptext, item, t, e, oa, ob - 1, tt); + if (r1 < 0) { return r1; } + q = @intCast(r1); + } else { + const r2 : i64 = ex(out, q, nodes, text, meta, ptext, item, e, tt); + if (r2 < 0) { return r2; } + q = @intCast(r2); + } + q = put_text(out, q, ";\n"); + const ra : i64 = @intCast(q); + return ra; +} + +/// Writes `if (c) { ... } else { ... }`; an `else if` chain nests. +pub fn stmt_if(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, s: usize, n: usize, lp: usize) -> i64 { + const c0 : usize = s + 1; + const th : usize = end(nodes, c0); + if (th >= end(nodes, s) or kind(nodes, th) != K_MODULE) { return fail(out, nodes, meta, text, s, "if", 0, 0); } + if (ty_of(nodes, text, meta, ptext, item, c0, 0) != TS_BOOL) { return ex(out, at, nodes, text, meta, ptext, item, c0, TS_BOOL); } + var q : usize = put_ind(out, at, n); + q = put_text(out, q, "if ("); + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, item, c0, TS_BOOL); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, ") {\n"); + const r2 : i64 = stmts(out, q, nodes, text, meta, ptext, item, th, th + 1, n + 2, lp); + if (r2 < 0) { return r2; } + q = @intCast(r2); + q = put_ind(out, q, n); + q = put_text(out, q, "}"); + const el : usize = end(nodes, th); + if (el < end(nodes, s)) { + q = put_text(out, q, " else {\n"); + const r3 : i64 = stmts(out, q, nodes, text, meta, ptext, item, el, el + 1, n + 2, lp); + if (r3 < 0) { return r3; } + q = @intCast(r3); + q = put_ind(out, q, n); + q = put_text(out, q, "}"); + } + q = put_text(out, q, "\n"); + const ri : i64 = @intCast(q); + return ri; +} + +/// Writes `while (c) { body; continue expression }`. A `continue` of a loop with a continue expression +/// would skip it in TypeScript, so that pair is refused. +pub fn stmt_while(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, w: usize, n: usize) -> i64 { + const body : i64 = loop_block(nodes, text, w, "body"); + if (body == NO_NODE) { return fail(out, nodes, meta, text, w, "while with no body", 0, 0); } + const bi : usize = @intCast(body); + const ce : i64 = loop_block(nodes, text, w, "continue_expr"); + if (ce != NO_NODE and own_continue(nodes, bi + 1, end(nodes, bi))) { return fail(out, nodes, meta, text, w, "continue in a while with a continue expression", 0, 0); } + const cnd : usize = w + 1; + if (ty_of(nodes, text, meta, ptext, item, cnd, 0) != TS_BOOL) { return ex(out, at, nodes, text, meta, ptext, item, cnd, TS_BOOL); } + var q : usize = put_ind(out, at, n); + q = put_text(out, q, "while ("); + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, item, cnd, TS_BOOL); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, ") {\n"); + const r2 : i64 = stmts(out, q, nodes, text, meta, ptext, item, bi, bi + 1, n + 2, w); + if (r2 < 0) { return r2; } + q = @intCast(r2); + if (ce != NO_NODE) { + const cei : usize = @intCast(ce); + const r3 : i64 = stmts(out, q, nodes, text, meta, ptext, item, cei, cei + 1, n + 2, w); + if (r3 < 0) { return r3; } + q = @intCast(r3); + } + q = put_ind(out, q, n); + q = put_text(out, q, "}\n"); + const rw : i64 = @intCast(q); + return rw; +} + +/// Writes `assert c` as `t27_assert(c, 'name', line);` and `assert_eq(a, b)` as the same over `(a === b)` +/// at the peer type, with t27_check when every operand folds; any other call statement is refused (a pure fn +/// has no effect but its value). +pub fn call_stmt(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, i: usize, n: usize) -> i64 { + var q : usize = at; + if (!field_eq(nodes, text, i, F_NAME, "assert") and !field_eq(nodes, text, i, F_NAME, "assert_eq")) { return fail(out, nodes, meta, text, i, "call statement", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + const eq : bool = field_eq(nodes, text, i, F_NAME, "assert_eq"); + if ((eq and children(nodes, i) != 2) or (!eq and children(nodes, i) != 1)) { return fail(out, nodes, meta, text, i, "assert with a message", 0, 0); } + const a : usize = i + 1; + var pt : i64 = TS_BOOL; + if (eq) { + const at1 : i64 = ty_of(nodes, text, meta, ptext, item, a, 0); + const at2 : i64 = ty_of(nodes, text, meta, ptext, item, end(nodes, a), 0); + if (at1 == TS_BAD) { return ex(out, q, nodes, text, meta, ptext, item, a, TS_BAD); } + if (at2 == TS_BAD) { return ex(out, q, nodes, text, meta, ptext, item, end(nodes, a), TS_BAD); } + pt = ts_peer(at1, at2); + if (!ts_scalar(pt)) { return fail(out, nodes, meta, text, i, "assert_eq of values whose types do not meet", 0, 0); } + } + // test-report does not count an assert whose operands are all compile-time constants (#6509) + var folds : bool = fold_ok(nodes, text, meta, ptext, item, a, 0); + if (eq) { folds = folds and fold_ok(nodes, text, meta, ptext, item, end(nodes, a), 0); } + q = put_ind(out, q, n); + if (folds) { q = put_text(out, q, "t27_check("); } else { q = put_text(out, q, "t27_assert("); } + if (eq) { q = put_text(out, q, "("); } + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, item, a, pt); + if (r1 < 0) { return r1; } + q = @intCast(r1); + if (eq) { + q = put_text(out, q, " === "); + const r2 : i64 = ex(out, q, nodes, text, meta, ptext, item, end(nodes, a), pt); + if (r2 < 0) { return r2; } + q = @intCast(r2); + q = put_text(out, q, ")"); + } + q = put_text(out, q, ", '"); + q = put_bytes(out, q, text, lo(nodes, item, F_NAME), hi(nodes, item, F_NAME)); + q = put_text(out, q, "', "); + q = put_dec(out, q, line_near(nodes, meta, i) as u64); + q = put_text(out, q, ");\n"); + const ra : i64 = @intCast(q); + return ra; +} + +// ---------------------------------------------------------------------------------------------------------- +// Items, tables and the module. +// ---------------------------------------------------------------------------------------------------------- + +/// Writes the module-level const at node d: `[export ]const N: number = e;`; an untyped one with its folded +/// value. +pub fn const_text(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, d: usize) -> i64 { + const na : usize = lo(nodes, d, F_NAME); + const nb : usize = hi(nodes, d, F_NAME); + if (is_mut(meta, d)) { return fail(out, nodes, meta, text, d, "module-level var", na, nb); } + if (hi(nodes, d, F_TYPE) > lo(nodes, d, F_TYPE) and !ts_scalar(ts_type(text, lo(nodes, d, F_TYPE), hi(nodes, d, F_TYPE)))) { + return fail(out, nodes, meta, text, d, "const that is no integer or bool", na, nb); + } + if (d + 1 >= end(nodes, d)) { return fail(out, nodes, meta, text, d, "const with no value", na, nb); } + if (ident_is(nodes, text, d + 1, "undefined")) { return fail(out, nodes, meta, text, d, "const left undefined", na, nb); } + if (bad_name(text, na, nb)) { return fail(out, nodes, meta, text, d, "name TypeScript reserves or the prelude uses", na, nb); } + var t : i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, d), 0); + if (t == TS_COMPTIME) { + if (!fold_ok(nodes, text, meta, ptext, 0, d + 1, 0)) { return fail(out, nodes, meta, text, d, "untyped const that does not fold", na, nb); } + t = TS_SIGNED + TS_MAX_WIDTH; + if (!ts_fits(fold_val(nodes, text, meta, ptext, 0, d + 1, 0), t)) { t = TS_MAX_WIDTH; } + } + if (!ts_scalar(t)) { return fail(out, nodes, meta, text, d, "const that is no integer or bool", na, nb); } + var q : usize = at; + if (is_pub(meta, d)) { q = put_text(out, q, "export "); } + q = put_text(out, q, "const "); + q = put_bytes(out, q, text, na, nb); + q = put_text(out, q, ": "); + q = put_text(out, q, ts_annot(t)); + q = put_text(out, q, " = "); + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, 0, d + 1, t); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, ";\n"); + const rc : i64 = @intCast(q); + return rc; +} + +/// Writes the fn at node f: `[export ]function f(p: number): number { ... }`. +pub fn fn_text(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, f: usize) -> i64 { + const na : usize = lo(nodes, f, F_NAME); + const nb : usize = hi(nodes, f, F_NAME); + if (is_extern(nodes, f)) { return fail(out, nodes, meta, text, f, "extern fn", na, nb); } + const rt : i64 = fn_type(nodes, text, f); + if (rt == TS_VOID) { return fail(out, nodes, meta, text, f, "fn with no result", na, nb); } + if (!ts_scalar(rt)) { return fail(out, nodes, meta, text, f, "fn whose result is no integer or bool", na, nb); } + if (bad_name(text, na, nb)) { return fail(out, nodes, meta, text, f, "name TypeScript reserves or the prelude uses", na, nb); } + var q : usize = put_text(out, at, "\n"); + if (is_pub(meta, f)) { q = put_text(out, q, "export "); } + q = put_text(out, q, "function "); + q = put_bytes(out, q, text, na, nb); + q = put_text(out, q, "("); + const np : usize = param_count(ptext, params_lo(meta, f), params_hi(meta, f)); + var kx : usize = 0; + while (kx < np) { + const pa : usize = param_name_lo(meta, ptext, f, kx); + const pb : usize = param_name_hi(meta, ptext, f, kx); + if (bad_name(ptext, pa, pb)) { return fail(out, nodes, meta, text, f, "parameter whose name TypeScript reserves or the prelude uses, of fn", na, nb); } + const pt : i64 = param_type(meta, ptext, f, kx); + if (!ts_scalar(pt)) { return fail(out, nodes, meta, text, f, "parameter that is no integer or bool, of fn", na, nb); } + if (kx > 0) { q = put_text(out, q, ", "); } + q = put_bytes(out, q, ptext, pa, pb); + q = put_text(out, q, ": "); + q = put_text(out, q, ts_annot(pt)); + kx = kx + 1; + } + q = put_text(out, q, "): "); + q = put_text(out, q, ts_annot(rt)); + q = put_text(out, q, " {\n"); + const r1 : i64 = stmts(out, q, nodes, text, meta, ptext, f, f, f + 1, 2, 0); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, "}\n"); + const rf : i64 = @intCast(q); + return rf; +} + +/// Writes the test or invariant at node c as `export function (): number`, which returns how +/// many asserts it ran. +pub fn check_text(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, c: usize, prefix: str) -> i64 { + const na : usize = lo(nodes, c, F_NAME); + const nb : usize = hi(nodes, c, F_NAME); + if (!is_ident(text, na, nb)) { return fail(out, nodes, meta, text, c, "test whose name is no identifier", na, nb); } + var q : usize = put_text(out, at, "\nexport function "); + q = put_text(out, q, prefix); + q = put_bytes(out, q, text, na, nb); + q = put_text(out, q, "(): number {\n t27_n = 0;\n"); + const r1 : i64 = stmts(out, q, nodes, text, meta, ptext, c, c, c + 1, 2, 0); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, " return t27_n;\n}\n"); + const rt : i64 = @intCast(q); + return rt; +} + +/// Writes the module's items in the order of the spec; anything that is no const, fn, test or invariant is +/// refused by its kind. +pub fn items(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8) -> i64 { + var q : usize = at; + var c : usize = 1; + while (c < end(nodes, 0)) { + const k : u8 = kind(nodes, c); + var r : i64 = 0; + if (k == K_CONST_DECL) { + r = const_text(out, q, nodes, text, meta, ptext, c); + } else if (k == K_FN_DECL) { + r = fn_text(out, q, nodes, text, meta, ptext, c); + } else if (k == K_TEST_BLOCK) { + r = check_text(out, q, nodes, text, meta, ptext, c, "t27_test_"); + } else if (k == K_INVARIANT_BLOCK) { + r = check_text(out, q, nodes, text, meta, ptext, c, "t27_invariant_"); + } else { + return fail(out, nodes, meta, text, c, kind_label(k), lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + } + if (r < 0) { return r; } + q = @intCast(r); + c = end(nodes, c); + } + const ri : i64 = @intCast(q); + return ri; +} + +/// Writes `export const : t27_case[] = [['a', a], ...];` over the items of kind kw. +pub fn table(out: []u8, at: usize, nodes: []const u8, text: []const u8, kw: u8, name: str, prefix: str) -> usize { + var q : usize = put_text(out, at, "\nexport const "); + q = put_text(out, q, name); + q = put_text(out, q, ": t27_case[] = [\n"); + var c : usize = 1; + while (c < end(nodes, 0)) { + if (kind(nodes, c) == kw) { + q = put_text(out, q, " ['"); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "', "); + q = put_text(out, q, prefix); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "],\n"); + } + c = end(nodes, c); + } + return put_text(out, q, "];\n"); +} + +/// The entry point (see the header). +pub fn emit(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, out: []u8) -> i64 { + if (count(nodes) == 0 or kind(nodes, 0) != K_MODULE) { return fail_front(out, "file with no module node"); } + var q : usize = put_text(out, 0, "// Generated by t27b ts from module "); + q = put_bytes(out, q, text, lo(nodes, 0, F_NAME), hi(nodes, 0, F_NAME)); + q = put_text(out, q, " -- DO NOT EDIT; the decisions are specs/tri/t27b/ts_plan.t27\n"); + q = put_text(out, q, "// phi^2 + 1/phi^2 = 3 | TRINITY\n\n"); + var j : usize = 0; + while (j < TS_PRELUDE_LINES) { + q = put_text(out, q, TS_PRELUDE[j]); + q = put_text(out, q, "\n"); + j = j + 1; + } + const r1 : i64 = items(out, q, nodes, text, meta, ptext); + if (r1 < 0) { + const ml : usize = @intCast(0 - r1); + return to_front(out, ml); + } + q = @intCast(r1); + q = table(out, q, nodes, text, K_TEST_BLOCK, "t27_tests", "t27_test_"); + q = table(out, q, nodes, text, K_INVARIANT_BLOCK, "t27_invariants", "t27_invariant_"); + j = 0; + while (j < TS_CODA_LINES) { + q = put_text(out, q, TS_CODA[j]); + q = put_text(out, q, "\n"); + j = j + 1; + } + if (q + MSG_ROOM > out.len) { return fail_front(out, "output larger than the buffer"); } + const rq : i64 = @intCast(q); + return rq; +} + +// ---------------------------------------------------------------------------------------------------------- +// Tests. A tree is written as ast_walk.t27 lays it out, with its meta records; its texts are slices of a +// constant. The expected TypeScript is what ts_plan.t27 decides for it. +// ---------------------------------------------------------------------------------------------------------- + +/// Node i: kind k, subtree end e, name text[a .. b], every other field empty. +fn tnode(buf: []u8, i: usize, k: u8, e: usize, a: usize, b: usize) -> void { + buf[i * REC] = k; + vput32(buf, i * REC + 4, e); + var f : usize = 0; + while (f < FIELDS) { + vput32(buf, i * REC + 8 + f * 8, 0); + vput32(buf, i * REC + 12 + f * 8, 0); + f = f + 1; + } + vput32(buf, i * REC + 8, a); + vput32(buf, i * REC + 12, b); +} + +/// Node i's meta record: flags, line, no parameters. +fn tmeta(m: []u8, i: usize, flags: usize, line: usize) -> void { + vput32(m, i * META, flags); + vput32(m, i * META + 4, line); + vput32(m, i * META + 8, 0); + vput32(m, i * META + 12, 0); +} + +/// Does out[0 .. n] hold w? +fn holds(out: []const u8, n: usize, w: str) -> bool { + var i : usize = 0; + while (i + w.len <= n) { + var k : usize = 0; + while (k < w.len and out[i + k] == w[k]) { k = k + 1; } + if (k == w.len) { return true; } + i = i + 1; + } + return false; +} + +/// The tree of `module M; pub fn f(x: u8) -> u8 { return x + 1; } test t { assert f(1) == 2; }`, with the +/// called fn's name at text[ca .. cb] (`f`, or an undeclared one). +/// text: M 0..1, f 1..2, x 2..3, u8 3..5, + 5..6, 1 6..7, 2 7..8, t 8..9, assert 9..15, == 15..17, g 17..18 +fn ttree(nb: []u8, mb: []u8, ca: usize, cb: usize) -> void { + tnode(nb, 0, K_MODULE, 13, 0, 1); + tnode(nb, 1, K_FN_DECL, 6, 1, 2); + vset(nb, 1, F_RET, 3, 5); + tnode(nb, 2, K_EXPR_RETURN, 6, 0, 0); + tnode(nb, 3, K_EXPR_BINARY, 6, 0, 0); + vset(nb, 3, F_OP, 5, 6); + tnode(nb, 4, K_EXPR_IDENTIFIER, 5, 2, 3); + tnode(nb, 5, K_EXPR_LITERAL, 6, 0, 0); + vset(nb, 5, F_VALUE, 6, 7); + tnode(nb, 6, K_TEST_BLOCK, 13, 8, 9); + tnode(nb, 7, K_STMT_EXPR, 13, 0, 0); + tnode(nb, 8, K_EXPR_CALL, 13, 9, 15); + tnode(nb, 9, K_EXPR_BINARY, 13, 0, 0); + vset(nb, 9, F_OP, 15, 17); + tnode(nb, 10, K_EXPR_CALL, 12, ca, cb); + tnode(nb, 11, K_EXPR_LITERAL, 12, 0, 0); + vset(nb, 11, F_VALUE, 6, 7); + tnode(nb, 12, K_EXPR_LITERAL, 13, 0, 0); + vset(nb, 12, F_VALUE, 7, 8); + var i : usize = 0; + while (i < 13) { + tmeta(mb, i, 0, 0); + i = i + 1; + } + // fn f is pub, at line 1, with the one parameter line `x:u8` of the params text + tmeta(mb, 1, FLAG_PUB, 1); + vput32(mb, 1 * META + 12, 5); + tmeta(mb, 6, 0, 2); + tmeta(mb, 8, 0, 3); +} + +test numbers_and_names_are_spelled { + var out : [64]u8 = undefined; + assert put_dec(out[0 .. 64], 0, 0) == 1 and out[0] == 48; + const neg : i64 = 0 - 128; + const n1 : usize = put_i64(out[0 .. 64], 0, neg); + assert n1 == 6 and holds(out[0 .. 64], 6, "(-128)"); + const n2 : usize = put_lit(out[0 .. 64], 0, 1, TS_BOOL); + assert n2 == 4 and holds(out[0 .. 64], 4, "true"); + const n3 : usize = put_lit(out[0 .. 64], 0, 4294967295, 32); + assert n3 == 10 and holds(out[0 .. 64], 10, "4294967295"); + const n4 : usize = put_sign(out[0 .. 64], 0, TS_SIGNED + 8); + assert n4 == 7 and holds(out[0 .. 64], 7, ", true)"); + assert is_ident("a_1", 0, 3) and !is_ident("1a", 0, 2) and !is_ident("a-b", 0, 3) and !is_ident("x", 0, 0); + assert bad_name("delete", 0, 6) and bad_name("t27_x", 0, 5) and !bad_name("wait", 0, 4); +} + +test a_fn_and_its_test_lower_to_checked_typescript { + const tx : str = "Mfxu8+12tassert==g"; + var nb : [936]u8 = undefined; + var mb : [208]u8 = undefined; + ttree(nb[0 .. 936], mb[0 .. 208], 1, 2); + var wk : [8]u8 = undefined; + var out : [8192]u8 = undefined; + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "x:u8\n", wk[0 .. 8], out[0 .. 8192]); + assert r > 0; + const n : usize = @intCast(r); + assert holds(out[0 .. 8192], n, "// Generated by t27b ts from module M -- DO NOT EDIT"); + assert holds(out[0 .. 8192], n, "function t27_chk(v: number, lo: number, hi: number): number"); + assert holds(out[0 .. 8192], n, "export function f(x: number): number {"); + assert holds(out[0 .. 8192], n, " return t27_chk((x + 1), 0, 255);"); + assert holds(out[0 .. 8192], n, "export function t27_test_t(): number {"); + assert holds(out[0 .. 8192], n, " t27_assert((f(1) === 2), 't', 3);"); + assert holds(out[0 .. 8192], n, " ['t', t27_test_t],"); + assert holds(out[0 .. 8192], n, "export function t27_report(): string {"); + // a fn that is not pub is not exported + tmeta(mb[0 .. 208], 1, 0, 1); + vput32(mb[0 .. 208], 1 * META + 12, 5); + const r2 : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "x:u8\n", wk[0 .. 8], out[0 .. 8192]); + assert r2 > 0; + const n2 : usize = @intCast(r2); + assert holds(out[0 .. 8192], n2, "\nfunction f(x: number): number {"); + assert !holds(out[0 .. 8192], n2, "export function f("); +} + +test a_call_of_an_undeclared_fn_is_refused_by_name_and_line { + const tx : str = "Mfxu8+12tassert==g"; + var nb : [936]u8 = undefined; + var mb : [208]u8 = undefined; + ttree(nb[0 .. 936], mb[0 .. 208], 17, 18); + var wk : [8]u8 = undefined; + var out : [8192]u8 = undefined; + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "x:u8\n", wk[0 .. 8], out[0 .. 8192]); + assert r < 0; + const n : usize = @intCast(0 - r); + assert holds(out[0 .. 8192], n, "t27b: ts: unsupported construct call of an undeclared fn `g` at line 3"); + // a refusal writes no TypeScript + assert !holds(out[0 .. 8192], n, "export function"); +} + +test a_value_that_does_not_fit_its_place_is_refused { + // the return of f is `x + 1` at u8; a literal 256 in its place does not fit + const tx : str = "Mfxu8+12tassert==g256"; + var nb : [936]u8 = undefined; + var mb : [208]u8 = undefined; + ttree(nb[0 .. 936], mb[0 .. 208], 1, 2); + vset(nb[0 .. 936], 5, F_VALUE, 18, 21); + var wk : [8]u8 = undefined; + var out : [8192]u8 = undefined; + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "x:u8\n", wk[0 .. 8], out[0 .. 8192]); + assert r < 0; + const n : usize = @intCast(0 - r); + assert holds(out[0 .. 8192], n, "unsupported construct literal that does not fit its type at line 1"); +} diff --git a/specs/tri/t27b/ts_plan.t27 b/specs/tri/t27b/ts_plan.t27 new file mode 100644 index 0000000000..7e396fbf44 --- /dev/null +++ b/specs/tri/t27b/ts_plan.t27 @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/ts_plan.t27 -- what t27b's TypeScript path lowers, and how (issue #9994, phase 1). +; `t27b ts ` writes a module's consts and its pure scalar fns as TypeScript with their real bodies, +; and each `test` as an exported self-check; specs/tri/t27b/ts_emit.t27 is the writer and this file holds +; every decision it takes. Both reach cli/t27b only through `t27c gen-rust`, as the Verilog path does; the +; readers of the flat AST, of the meta records and of literals are verilog_plan.t27's, reused as they are. +; -- +; The subset. Integers u1 to u32 and i1 to i32, bool, and an untyped (comptime) integer where it folds or +; takes a type from its place. A module-level `const` of such a value; a fn whose parameters and result are +; such values; the statements `const`/`var` locals, assignment (plain and compound), if/else, while (with a +; continue expression), break, continue, return, assert and assert_eq. Everything else -- u64 and usize, +; floats, str, arrays, structs, enums, switch, for, a module-level `var`, `use`, bench -- is refused by name +; and line, `t27b: ts: unsupported construct `` at line `, and nothing is written. +; -- +; Numbers. A TypeScript number is an f64, exact on every integer below 2^53, so each value of the subset is +; held exactly and each operator is written so that its result is the t27 result at the t27 type: +; - `+ - *` and unary `-` are checked: t27_chk(v, lo, hi) throws RangeError `t27: integer overflow` when the +; exact value leaves the type's range, where Zig's safe build panics. A product of two u32 that f64 cannot +; hold exactly is far above 2^32, so the check still fires. +; - `+% -% *%` wrap: t27_wrap(v, w, s) is `v >>> 0` for a u32, `v & (2^w - 1)` for a narrower unsigned type +; and `(v << (32 - w)) >> (32 - w)` for a signed one (`| 0` when w is 32); `*%` multiplies with +; Math.imul, whose low 32 bits are exact. +; - `/` and `%` are unsigned only (t27c refuses them on a signed type, which needs @divTrunc or @rem): +; t27_div and t27_rem throw RangeError `t27: division by zero` on a zero divisor. +; - `<<` and `>>` take the left operand's type; an amount at or above its width throws `t27: shift amount +; too large` (Zig panics there); `<<` drops the bits shifted out, `>>` is logical on an unsigned type and +; arithmetic on a signed one. +; - `& | ^ ~` are written so a u32 stays unsigned (`>>> 0`) and a narrower unsigned value within its mask. +; - `==` is `===`, `!=` is `!==`, `and` and `or` are `&&` and `||`. +; - A cast `x as T` that narrows the width wraps (Zig's @truncate); one whose range holds the source's range +; is the value as it is; any other is checked (Zig's checked @intCast). +; - A value goes where Zig coerces it: into a type whose range holds its own. A u32 into a u8 without a +; cast, or an i8 into a u8, is refused. +; -- +; Tests. `test name { ... }` is `export function t27_test_name(): number`, which runs the body and returns +; how many asserts it executed (an assert whose operands all fold is checked by t27_check and not counted, +; as test-report does not count an assert on compile-time constants, #6509); t27_tests lists them, and t27_report() writes the lines of the assert table +; of `t27c test-report`, so the two compare byte for byte. `invariant name { ... }` is +; t27_invariant_name() in t27_invariants: test-report does not count them (Zig checks them at compile +; time), so they are not in the table; t27_failures() runs both kinds and names each one that throws. +; ASCII only (L3) +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bTsPlan; + +use tri::t27b::verilog_plan; + +// ---------------------------------------------------------------------------------------------------------- +// Types. A type is one i64 code: 1 to 32 an unsigned integer of that width; TS_SIGNED + w a signed one of +// width w (1 to 32); TS_BOOL; TS_COMPTIME an untyped integer; TS_VOID no value; TS_BAD anything else, +// refused where it is used. A u64, a usize and every wider type are TS_BAD: f64 does not hold them. +// ---------------------------------------------------------------------------------------------------------- + +pub const TS_BAD : i64 = -1; +pub const TS_COMPTIME : i64 = 0; +pub const TS_BOOL : i64 = 65; +pub const TS_VOID : i64 = 66; +pub const TS_SIGNED : i64 = 100; +pub const TS_MAX_WIDTH : i64 = 32; + +/// The code of the type text s[a .. b] (trimmed). +pub fn ts_type(s: []const u8, a: usize, b: usize) -> i64 { + // The trimmed text is passed on as s[p .. q], never bound to a local (#8812). + const p : usize = trim_lo(s, a, b); + const q : usize = trim_hi(s, p, b); + if (q == p or same(s[p .. q], "void")) { return TS_VOID; } + if (same(s[p .. q], "bool")) { return TS_BOOL; } + if (same(s[p .. q], "comptime_int")) { return TS_COMPTIME; } + if ((s[p] != 117 and s[p] != 105) or q - p < 2 or q - p > 3) { return TS_BAD; } + // p1 is bound once: t27c's Zig backend hoists a repeated `p + 1` above the declaration of p (#2629). + const p1 : usize = p + 1; + var w : i64 = 0; + var j : usize = p1; + while (j < q) { + if (!digit(s[j])) { return TS_BAD; } + const dv : i64 = @intCast(s[j] - 48); + w = w * 10 + dv; + j = j + 1; + } + if (s[p1] == 48 or w < 1 or w > TS_MAX_WIDTH) { return TS_BAD; } + if (s[p] == 105) { return TS_SIGNED + w; } + return w; +} + +/// Is the type an unsigned integer this path holds? +pub fn ts_unsigned(t: i64) -> bool { + return t >= 1 and t <= TS_MAX_WIDTH; +} + +/// Is the type a signed integer this path holds? +pub fn ts_signed(t: i64) -> bool { + return t > TS_SIGNED and t <= TS_SIGNED + TS_MAX_WIDTH; +} + +/// Is the type an integer this path holds? +pub fn ts_int(t: i64) -> bool { + return ts_unsigned(t) or ts_signed(t); +} + +/// Is the type a value this path holds: an integer or a bool? +pub fn ts_scalar(t: i64) -> bool { + return ts_int(t) or t == TS_BOOL; +} + +/// The width in bits of a scalar type (a bool is one bit); 0 for anything else. +pub fn ts_width(t: i64) -> i64 { + if (ts_unsigned(t)) { return t; } + if (ts_signed(t)) { return t - TS_SIGNED; } + if (t == TS_BOOL) { return 1; } + return 0; +} + +/// 2^w, for w from 0 to 62. +pub fn ts_pow2(w: i64) -> i64 { + var m : i64 = 1; + var j : i64 = 0; + while (j < w and j < 62) { + m = m * 2; + j = j + 1; + } + return m; +} + +/// The smallest value of a scalar type. +pub fn ts_min(t: i64) -> i64 { + if (ts_signed(t)) { return 0 - ts_pow2(ts_width(t) - 1); } + return 0; +} + +/// The largest value of a scalar type (a bool holds 0 and 1); 0 for anything else. +pub fn ts_max(t: i64) -> i64 { + if (ts_signed(t)) { return ts_pow2(ts_width(t) - 1) - 1; } + if (ts_unsigned(t) or t == TS_BOOL) { return ts_pow2(ts_width(t)) - 1; } + return 0; +} + +/// Does v fit the scalar type t? +pub fn ts_fits(v: i64, t: i64) -> bool { + if (!ts_scalar(t)) { return false; } + return v >= ts_min(t) and v <= ts_max(t); +} + +/// Does a value of type own go, without a cast, where a value of type want is taken? The same scalar +/// type, or an integer type whose range holds own's range (Zig's coercion: u8 into u16 or i16, never into +/// i8; i8 never into an unsigned type). +pub fn ts_coerces(own: i64, want: i64) -> bool { + if (own == want) { return ts_scalar(own); } + if (!ts_int(own) or !ts_int(want)) { return false; } + return ts_min(want) <= ts_min(own) and ts_max(own) <= ts_max(want); +} + +/// The type both operands of an arithmetic or a comparison take (Zig's peer type): an untyped operand +/// takes the other's type, two bools bool, two integers the one the other coerces to. TS_BAD when they do +/// not meet (a bool and an integer, a u32 and an i32, anything not scalar). +pub fn ts_peer(a: i64, b: i64) -> i64 { + if (a == TS_COMPTIME) { + if (b == TS_COMPTIME or ts_scalar(b)) { return b; } + return TS_BAD; + } + if (b == TS_COMPTIME) { + if (ts_scalar(a)) { return a; } + return TS_BAD; + } + if (a == TS_BOOL and b == TS_BOOL) { return TS_BOOL; } + if (ts_coerces(a, b)) { return b; } + if (ts_coerces(b, a)) { return a; } + return TS_BAD; +} + +/// The TypeScript type a scalar is declared with. +pub fn ts_annot(t: i64) -> str { + if (t == TS_BOOL) { return "boolean"; } + return "number"; +} + +// ---------------------------------------------------------------------------------------------------------- +// Folding. An untyped expression is folded to its value, over i64, and the value is written where it is +// taken, checked to fit the type it takes there. Folding stays within 2^52 either way, so each value is +// exact both here and in TypeScript; an expression that leaves it, or divides by 0, does not fold and is +// refused. +// ---------------------------------------------------------------------------------------------------------- + +pub const TS_FOLD_MAX : i64 = 4503599627370496; + +/// Is v within the folding range? +pub fn ts_in_fold(v: i64) -> bool { + return v >= 0 - TS_FOLD_MAX and v <= TS_FOLD_MAX; +} + +/// |v|. +pub fn ts_abs(v: i64) -> i64 { + if (v < 0) { return 0 - v; } + return v; +} + +/// Does the binary operator s fold over a and b (both within the folding range) to a value within it? +pub fn ts_fold_ok(s: []const u8, a: i64, b: i64) -> bool { + if (!ts_in_fold(a) or !ts_in_fold(b)) { return false; } + if (same(s, "+") or same(s, "+%") or same(s, "-") or same(s, "-%")) { return ts_in_fold(ts_fold_val(s, a, b)); } + if (same(s, "*") or same(s, "*%")) { + if (a == 0 or b == 0) { return true; } + const mq : i64 = @divTrunc(TS_FOLD_MAX, ts_abs(b)); + return ts_abs(a) <= mq; + } + if (same(s, "/") or same(s, "%")) { return a >= 0 and b > 0; } + if (same(s, "<<")) { + if (a < 0 or b < 0 or b >= 53) { return false; } + const sq : i64 = @divTrunc(TS_FOLD_MAX, ts_pow2(b)); + return a <= sq; + } + if (same(s, ">>")) { return a >= 0 and b >= 0; } + if (op_bitwise(s)) { return a >= 0 and b >= 0; } + if (op_class(s) == OP_CMP or op_class(s) == OP_LOGIC) { return true; } + return false; +} + +/// The value of the binary operator s over a and b, when ts_fold_ok says it folds; a comparison or a +/// logical operator gives 1 or 0. +pub fn ts_fold_val(s: []const u8, a: i64, b: i64) -> i64 { + if (same(s, "+") or same(s, "+%")) { return a + b; } + if (same(s, "-") or same(s, "-%")) { return a - b; } + if (same(s, "*") or same(s, "*%")) { return a * b; } + if (same(s, "/")) { + const dq : i64 = @divTrunc(a, b); + return dq; + } + if (same(s, "%")) { + const dr : i64 = @rem(a, b); + return dr; + } + if (same(s, "<<")) { return a * ts_pow2(b); } + if (same(s, ">>")) { + if (b >= 53) { return 0; } + const ds : i64 = @divTrunc(a, ts_pow2(b)); + return ds; + } + if (same(s, "&")) { return a & b; } + if (same(s, "|")) { return a | b; } + if (same(s, "^")) { return a ^ b; } + var t : bool = false; + if (same(s, "==")) { t = a == b; } + if (same(s, "!=")) { t = a != b; } + if (same(s, "<")) { t = a < b; } + if (same(s, "<=")) { t = a <= b; } + if (same(s, ">")) { t = a > b; } + if (same(s, ">=")) { t = a >= b; } + if (same(s, "and") or same(s, "&&")) { t = a == 1 and b == 1; } + if (same(s, "or") or same(s, "||")) { t = a == 1 or b == 1; } + if (t) { return 1; } + return 0; +} + +// ---------------------------------------------------------------------------------------------------------- +// Operators. Each arithmetic operator is written in one of five forms (see the head of this file); a +// comparison or a logical operator is one TypeScript operator. +// ---------------------------------------------------------------------------------------------------------- + +pub const AR_BAD : u8 = 0; +/// `+ - *`: `t27_chk(a op b, lo, hi)`. +pub const AR_CHECKED : u8 = 1; +/// `+% -% *%`: `t27_wrap(a op b, w, s)`, `*%` with Math.imul. +pub const AR_WRAPS : u8 = 2; +/// `/ %`: `t27_div(a, b)`, `t27_rem(a, b)`; unsigned only. +pub const AR_DIVIDES : u8 = 3; +/// `& | ^`: `(a op b)`, and `((a op b) >>> 0)` at u32; integers only. +pub const AR_BITS : u8 = 4; +/// `<< >>`: `t27_shl(a, b, w, s)`, `t27_shr(a, b, w, s)`. +pub const AR_SHIFTS : u8 = 5; + +/// The form the arithmetic operator text s is written in; AR_BAD for any other operator. +pub fn ts_arith(s: []const u8) -> u8 { + if (same(s, "+") or same(s, "-") or same(s, "*")) { return AR_CHECKED; } + if (same(s, "+%") or same(s, "-%") or same(s, "*%")) { return AR_WRAPS; } + if (same(s, "/") or same(s, "%")) { return AR_DIVIDES; } + if (op_bitwise(s)) { return AR_BITS; } + if (same(s, "<<") or same(s, ">>")) { return AR_SHIFTS; } + return AR_BAD; +} + +/// The TypeScript operator, or helper, an arithmetic operator is written with. +pub fn ts_arith_op(s: []const u8) -> str { + if (same(s, "+") or same(s, "+%")) { return "+"; } + if (same(s, "-") or same(s, "-%")) { return "-"; } + if (same(s, "*")) { return "*"; } + if (same(s, "*%")) { return "Math.imul"; } + if (same(s, "/")) { return "t27_div"; } + if (same(s, "%")) { return "t27_rem"; } + if (same(s, "&")) { return "&"; } + if (same(s, "|")) { return "|"; } + if (same(s, "^")) { return "^"; } + if (same(s, "<<")) { return "t27_shl"; } + if (same(s, ">>")) { return "t27_shr"; } + return "?"; +} + +/// The TypeScript spelling of a comparison or a logical operator; `?` for any other. +pub fn ts_cmp(s: []const u8) -> str { + if (same(s, "==")) { return "==="; } + if (same(s, "!=")) { return "!=="; } + if (same(s, "<")) { return "<"; } + if (same(s, "<=")) { return "<="; } + if (same(s, ">")) { return ">"; } + if (same(s, ">=")) { return ">="; } + if (same(s, "and") or same(s, "&&")) { return "&&"; } + if (same(s, "or") or same(s, "||")) { return "||"; } + return "?"; +} + +// ---------------------------------------------------------------------------------------------------------- +// Names. A t27 name is written as it is; one TypeScript reserves, or one the prelude could collide with +// (every name that starts with `t27_`), is refused by name rather than renamed, so the TypeScript reads as +// the spec does. +// ---------------------------------------------------------------------------------------------------------- + +pub const TS_KEYWORDS : [59]str = ["break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield", "let", "static", "implements", "interface", "package", "private", "protected", "public", "await", "arguments", "eval", "undefined", "NaN", "Infinity", "Math", "String", "Error", "RangeError", "Array", "Number", "Boolean", "Object"]; + +/// Is s a name the TypeScript cannot carry as it is: a reserved word, a global the output uses, or a name +/// that starts with `t27_`? +pub fn ts_reserved(s: []const u8) -> bool { + if (s.len >= 4 and same(s[0 .. 4], "t27_")) { return true; } + var j : usize = 0; + while (j < 59) { + if (same(s, TS_KEYWORDS[j])) { return true; } + j = j + 1; + } + return false; +} + +// ---------------------------------------------------------------------------------------------------------- +// The prelude, written once at the head of every module, and the coda, after the tables of tests. Both are +// fixed text; their strings are single-quoted, so neither holds a byte the spec would have to escape. +// ---------------------------------------------------------------------------------------------------------- + +pub const TS_PRELUDE_LINES : usize = 12; + +pub const TS_PRELUDE : [12]str = [ + "let t27_n: number = 0;", + "function t27_fail(m: string): never { throw new RangeError('t27: ' + m); }", + "function t27_chk(v: number, lo: number, hi: number): number { if (v < lo || v > hi) { t27_fail('integer overflow'); } return v; }", + "function t27_wrap(v: number, w: number, s: boolean): number { if (s) { return w === 32 ? v | 0 : (v << (32 - w)) >> (32 - w); } return w === 32 ? v >>> 0 : v & (2 ** w - 1); }", + "function t27_div(a: number, b: number): number { if (b === 0) { t27_fail('division by zero'); } return Math.trunc(a / b); }", + "function t27_rem(a: number, b: number): number { if (b === 0) { t27_fail('division by zero'); } return a % b; }", + "function t27_shl(a: number, b: number, w: number, s: boolean): number { if (b >= w) { t27_fail('shift amount too large'); } return t27_wrap(a << b, w, s); }", + "function t27_shr(a: number, b: number, w: number, s: boolean): number { if (b >= w) { t27_fail('shift amount too large'); } return s ? a >> b : a >>> b; }", + "function t27_check(c: boolean, test: string, line: number): void { if (!c) { throw new Error('t27: assert failed in ' + test + ' at line ' + String(line)); } }", + "function t27_assert(c: boolean, test: string, line: number): void { t27_n = t27_n + 1; t27_check(c, test, line); }", + "type t27_case = [string, () => number];", + "" +]; + +pub const TS_CODA_LINES : usize = 20; + +pub const TS_CODA : [20]str = [ + "", + "/// The assert table of `t27c test-report`, line for line.", + "export function t27_report(): string {", + " const lines: string[] = [' runtime asserts executed, per test (#6509; a pass with 0 is vacuous, T730):'];", + " let passed = 0;", + " let vacuous = 0;", + " for (const [name, run] of t27_tests) {", + " let n = -1;", + " try { n = run(); } catch (e) { n = -1; }", + " if (n < 0) { lines.push(' ' + '-'.padStart(6) + ' ' + name + ' (failed, not counted)'); continue; }", + " passed = passed + 1;", + " if (n === 0) { vacuous = vacuous + 1; lines.push(' ' + '0'.padStart(6) + ' ' + name + ' VACUOUS'); } else { lines.push(' ' + String(n).padStart(6) + ' ' + name); }", + " }", + " lines.push(' vacuous passes ' + String(vacuous) + ' of ' + String(passed) + ' (passed with 0 runtime asserts executed)');", + " return lines.join(String.fromCharCode(10));", + "}", + "", + "/// Every test and invariant that throws, with what it threw; empty when all hold.", + "export function t27_failures(): string[] { const out: string[] = []; for (const [name, run] of t27_tests) { try { run(); } catch (e) { out.push('test ' + name + ': ' + String(e)); } } for (const [name, run] of t27_invariants) { try { run(); } catch (e) { out.push('invariant ' + name + ': ' + String(e)); } } return out; }", + "" +]; + +// ---------------------------------------------------------------------------------------------------------- +// Tests. +// ---------------------------------------------------------------------------------------------------------- + +/// How many bytes of s are a double quote or a backslash. +fn quotes_in(s: str) -> usize { + var n : usize = 0; + var k : usize = 0; + while (k < s.len) { + if (s[k] == 34 or s[k] == 92) { n = n + 1; } + k = k + 1; + } + return n; +} + +test ts_type_texts_read_as_codes { + assert ts_type("u8", 0, 2) == 8; + assert ts_type("u32", 0, 3) == 32; + assert ts_type(" u1 ", 0, 4) == 1; + assert ts_type("i32", 0, 3) == TS_SIGNED + 32; + assert ts_type("xi8", 1, 3) == TS_SIGNED + 8; + assert ts_type("u33", 0, 3) == TS_BAD; + assert ts_type("u64", 0, 3) == TS_BAD and ts_type("i64", 0, 3) == TS_BAD; + assert ts_type("usize", 0, 5) == TS_BAD; + assert ts_type("u0", 0, 2) == TS_BAD and ts_type("i08", 0, 3) == TS_BAD; + assert ts_type("bool", 0, 4) == TS_BOOL; + assert ts_type("", 0, 0) == TS_VOID and ts_type("void", 0, 4) == TS_VOID; + assert ts_type("comptime_int", 0, 12) == TS_COMPTIME; + assert ts_type("f64", 0, 3) == TS_BAD and ts_type("str", 0, 3) == TS_BAD and ts_type("[4]u8", 0, 5) == TS_BAD; +} + +test ranges_coercions_and_peers { + assert ts_width(8) == 8 and ts_width(TS_SIGNED + 16) == 16 and ts_width(TS_BOOL) == 1 and ts_width(TS_BAD) == 0; + assert ts_min(8) == 0 and ts_max(8) == 255 and ts_max(32) == 4294967295 and ts_max(1) == 1; + assert ts_min(TS_SIGNED + 8) == 0 - 128 and ts_max(TS_SIGNED + 8) == 127; + assert ts_min(TS_SIGNED + 32) == 0 - 2147483648 and ts_max(TS_SIGNED + 32) == 2147483647; + assert ts_min(TS_SIGNED + 1) == 0 - 1 and ts_max(TS_SIGNED + 1) == 0; + assert ts_fits(255, 8) and !ts_fits(256, 8) and !ts_fits(0 - 1, 32) and ts_fits(0 - 1, TS_SIGNED + 8); + assert ts_fits(1, TS_BOOL) and !ts_fits(2, TS_BOOL) and !ts_fits(0, TS_BAD); + assert ts_coerces(8, 16) and ts_coerces(8, TS_SIGNED + 16) and !ts_coerces(8, TS_SIGNED + 8); + assert !ts_coerces(TS_SIGNED + 8, 16) and !ts_coerces(32, 8) and ts_coerces(32, 32); + assert ts_coerces(TS_BOOL, TS_BOOL) and !ts_coerces(TS_BOOL, 8) and !ts_coerces(TS_BAD, TS_BAD); + assert ts_peer(8, 16) == 16 and ts_peer(32, 8) == 32 and ts_peer(8, TS_SIGNED + 16) == TS_SIGNED + 16; + assert ts_peer(TS_COMPTIME, 32) == 32 and ts_peer(32, TS_COMPTIME) == 32 and ts_peer(TS_COMPTIME, TS_COMPTIME) == TS_COMPTIME; + assert ts_peer(32, TS_SIGNED + 32) == TS_BAD and ts_peer(TS_BOOL, 8) == TS_BAD and ts_peer(TS_BOOL, TS_BOOL) == TS_BOOL; + assert same("boolean", ts_annot(TS_BOOL)) and same("number", ts_annot(TS_SIGNED + 8)); +} + +test untyped_values_fold_within_two_to_the_52 { + assert ts_fold_ok("+", 2, 3) and ts_fold_val("+", 2, 3) == 5; + assert ts_fold_ok("-", 2, 3) and ts_fold_val("-", 2, 3) == 0 - 1; + assert !ts_fold_ok("+", TS_FOLD_MAX, 1) and ts_fold_ok("+", TS_FOLD_MAX, 0); + assert ts_fold_val("*", 6, 7) == 42 and !ts_fold_ok("*", TS_FOLD_MAX, 2) and ts_fold_ok("*", 0 - 3, 5); + assert ts_fold_val("/", 7, 2) == 3 and !ts_fold_ok("/", 7, 0) and !ts_fold_ok("/", 0 - 7, 2); + assert ts_fold_val("%", 7, 4) == 3 and !ts_fold_ok("%", 7, 0); + assert ts_fold_ok("<<", 1, 52) and ts_fold_val("<<", 1, 31) == 2147483648; + assert !ts_fold_ok("<<", 2, 52) and !ts_fold_ok("<<", 1, 0 - 1); + assert ts_fold_val(">>", 4096, 3) == 512 and ts_fold_val(">>", 4096, 60) == 0; + assert ts_fold_val("&", 12, 10) == 8 and ts_fold_val("|", 12, 10) == 14 and ts_fold_val("^", 12, 10) == 6; + assert !ts_fold_ok("&", 0 - 1, 3); + assert ts_fold_val("<", 0 - 1, 2) == 1 and ts_fold_val("==", 5, 5) == 1 and ts_fold_val(">=", 1, 2) == 0; + assert ts_fold_val("and", 1, 0) == 0 and ts_fold_val("or", 1, 0) == 1; + assert !ts_fold_ok("**", 1, 1) and !ts_fold_ok("+", TS_FOLD_MAX + 1, 0); +} + +test operators_have_a_form_and_a_spelling { + assert ts_arith("+") == AR_CHECKED and ts_arith("-") == AR_CHECKED and ts_arith("*") == AR_CHECKED; + assert ts_arith("+%") == AR_WRAPS and ts_arith("*%") == AR_WRAPS; + assert ts_arith("/") == AR_DIVIDES and ts_arith("%") == AR_DIVIDES; + assert ts_arith("^") == AR_BITS and ts_arith("<<") == AR_SHIFTS and ts_arith(">>") == AR_SHIFTS; + assert ts_arith("==") == AR_BAD and ts_arith("and") == AR_BAD and ts_arith("**") == AR_BAD; + assert same("Math.imul", ts_arith_op("*%")) and same("+", ts_arith_op("+%")) and same("t27_rem", ts_arith_op("%")); + assert same("t27_shl", ts_arith_op("<<")) and same("?", ts_arith_op("==")); + assert same("===", ts_cmp("==")) and same("!==", ts_cmp("!=")) and same("<=", ts_cmp("<=")); + assert same("&&", ts_cmp("and")) and same("||", ts_cmp("or")) and same("?", ts_cmp("+")); +} + +test reserved_names_are_refused_not_renamed { + assert ts_reserved("class") and ts_reserved("NaN") and ts_reserved("arguments") and ts_reserved("Object"); + assert ts_reserved("t27_n") and ts_reserved("t27_"); + assert !ts_reserved("step") and !ts_reserved("t27") and !ts_reserved("bit") and !ts_reserved("classes"); +} + +test the_prelude_and_the_coda_need_no_escape { + var bad : usize = 0; + var j : usize = 0; + while (j < TS_PRELUDE_LINES) { + bad = bad + quotes_in(TS_PRELUDE[j]); + j = j + 1; + } + j = 0; + while (j < TS_CODA_LINES) { + bad = bad + quotes_in(TS_CODA[j]); + j = j + 1; + } + assert bad == 0; + assert same("let t27_n: number = 0;", TS_PRELUDE[0]); + assert same("type t27_case = [string, () => number];", TS_PRELUDE[10]); + assert same("export function t27_report(): string {", TS_CODA[2]); +} + +invariant the_codes_do_not_overlap { + assert TS_BOOL > TS_MAX_WIDTH and TS_VOID > TS_BOOL and TS_SIGNED > TS_VOID; + assert TS_FOLD_MAX == 4503599627370496; +}