From 6d060fdfb23bdf3a7674d264f82362de725e76a9 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:25:20 +0700
Subject: [PATCH 01/63] widgets: catalogue all 82 widget folders and the
factory law -- 28 tools carry 22833 hand lines, 2775 of them pure logic
(Closes #10938)
specs/widgets/catalog.t27 pins the census measured at trinity origin/main.
specs/t27f/widget_factory.t27 states six stages, the t27c-seal build key and the port gate.
specs/t27f/frontend.t27 registers SF_WIDGET_FACTORY. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/t27f/frontend.t27 | 25 +++-
specs/t27f/widget_factory.t27 | 223 +++++++++++++++++++++++++++++++++
specs/widgets/catalog.t27 | 227 ++++++++++++++++++++++++++++++++++
3 files changed, 470 insertions(+), 5 deletions(-)
create mode 100644 specs/t27f/widget_factory.t27
create mode 100644 specs/widgets/catalog.t27
diff --git a/specs/t27f/frontend.t27 b/specs/t27f/frontend.t27
index 40fb2aa79c..d744a1ab65 100644
--- a/specs/t27f/frontend.t27
+++ b/specs/t27f/frontend.t27
@@ -55,10 +55,11 @@ pub const SF_PARTICLE_LIVE : u32 = 10; // T27F Live, particle_field.t27 + partic
pub const SF_UI_PLAN : u32 = 11; // trusted-widget UI plan, ui_plan.t27
pub const SF_DASHBOARD : u32 = 12; // corpus and session health, t27mcp/dashboard.t27, tool t27_dashboard
pub const SF_PETAL_MENU : u32 = 13; // T27 OS home: the 27-petal triangle menu, petal_menu.t27
-pub const SF_COUNT : u32 = 14;
+pub const SF_WIDGET_FACTORY : u32 = 14; // widgets as specs: logic, plan, page, data, card -- widget_factory.t27, catalogue widgets/catalog.t27
+pub const SF_COUNT : u32 = 15;
pub const SF_NONE : u32 = 255;
-pub const SURFACE_NAMES : str = "graph,outline,map_still,graph_page,hot,check,preview,particle_reel,still,map_video,particle_live,ui_plan,dashboard,petal_menu";
+pub const SURFACE_NAMES : str = "graph,outline,map_still,graph_page,hot,check,preview,particle_reel,still,map_video,particle_live,ui_plan,dashboard,petal_menu,widget_factory";
/// What draws a surface today.
pub const ST_LIVE : u8 = 0;
@@ -67,7 +68,7 @@ pub const ST_PARTIAL : u8 = 2;
pub const ST_BLOCKED : u8 = 3;
pub const ST_SPEC : u8 = 4;
-pub const SURFACE_STATUS : [14]u8 = [0, 0, 0, 0, 2, 3, 4, 1, 1, 4, 4, 4, 0, 4];
+pub const SURFACE_STATUS : [15]u8 = [0, 0, 0, 0, 2, 3, 4, 1, 1, 4, 4, 4, 0, 4, 4];
/// Where a surface is made: an MCP tool, a t27.ai page, the Railway lab, the TRI CLAW App, nowhere.
pub const MK_MCP : u8 = 0;
@@ -76,7 +77,7 @@ pub const MK_LAB : u8 = 2;
pub const MK_APP : u8 = 3;
pub const MK_NONE : u8 = 4;
-pub const SURFACE_MAKER : [14]u8 = [0, 0, 0, 1, 0, 3, 3, 2, 2, 4, 4, 4, 0, 4];
+pub const SURFACE_MAKER : [15]u8 = [0, 0, 0, 1, 0, 3, 3, 2, 2, 4, 4, 4, 0, 4, 4];
/// What a caller wants shown.
pub const I_SPEC_SHAPE : u32 = 0; // the structure of one spec
@@ -90,7 +91,8 @@ pub const I_HEALTH : u32 = 7; // the state of the corpus and the session
pub const I_LIVE_AGENT : u32 = 8; // an agent's work as a live particle field
pub const I_APP : u32 = 9; // an application screen built from trusted widgets
pub const I_OS_HOME : u32 = 10; // the T27 OS home screen: 27 petals, a petal opens its menu
-pub const I_COUNT : u32 = 11;
+pub const I_WIDGET : u32 = 11; // a shareable widget made from a spec, and the census of the ones that exist
+pub const I_COUNT : u32 = 12;
/// The drawing tools TRI CLAW's chat agent carries. service.t27 gives its coder persona CODER_TOOLS
/// for writing a spec; these are for showing one.
@@ -179,6 +181,9 @@ pub fn route(intent: u32, host: u8) -> u32 {
if (intent == I_OS_HOME) {
return SF_PETAL_MENU;
}
+ if (intent == I_WIDGET) {
+ return SF_WIDGET_FACTORY;
+ }
return SF_NONE;
}
@@ -322,3 +327,13 @@ test tri_claw_carries_every_drawing_tool {
assert claw_visual_tool(CLAW_VISUAL_COUNT) == TOOL_COUNT;
assert TOOLS[claw_visual_tool(0)].name == "t27f_graph";
}
+
+
+test the_widget_factory_is_a_spec_and_nothing_makes_it_yet {
+ assert route(I_WIDGET, HOST_CLAUDE_CODE_DESKTOP) == SF_WIDGET_FACTORY;
+ assert route(I_WIDGET, HOST_SITE) == SF_WIDGET_FACTORY;
+ assert SURFACE_STATUS[SF_WIDGET_FACTORY] == ST_SPEC;
+ assert pick(I_WIDGET, HOST_CLAUDE_CODE_DESKTOP) == SF_NONE;
+ assert claim_ok(SF_WIDGET_FACTORY, true) == false;
+ assert surface_tool(SF_WIDGET_FACTORY) == TOOL_COUNT;
+}
diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27
new file mode 100644
index 0000000000..f757c21c6f
--- /dev/null
+++ b/specs/t27f/widget_factory.t27
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/t27f/widget_factory.t27 -- the widget factory: how a widget goes from a spec to a page, its
+; numbers and its card with no hand-written line in between, and the gate that lets a hand-written
+; widget be retired. Owner, 2026-10-19: catalogue every widget, rewrite the hand-written ones in
+; .t27, make working with them easy, put the experience into an epic for tri CLI and TRI CLAW, and
+; build the factory natively in t27f, t27b and t27c seal. The census is specs/widgets/catalog.t27:
+; 28 tools, 22833 hand-written lines, of which 2775 are pure logic that ports as it stands.
+; --
+; The factory in six stages. Each is a function of the stage before and nothing else:
+; spec the widget's .t27 (words, rules, tests, data sources). The hand column of the catalog is
+; everything that is NOT this stage.
+; logic the pure functions of the tool (parse, grade, layout), written in .t27 and generated to
+; JavaScript by gen-ts. The spec's own test blocks are the fixed answers; a data builder no
+; longer re-imports a hand file "so the page cannot drift from the spec".
+; plan what is drawn, as a ui_plan.t27 plan: widget ids from an allowlist, no markup, no script.
+; The primitives below extend ui_plan's catalog; they are not a second catalog.
+; page one trusted renderer draws a plan. It replaces 28 tool.js files with one program.
+; data the tool's data builder, a spec compiled by t27b to a native binary (t27b is the
+; native path; it is the reason a 696-line test-touch.mjs does not need Node).
+; card the share picture, drawn by t27f as a still (SF_STILL) from the same spec, not by a
+; Python script beside it.
+; --
+; THE SEAL IS THE ACCELERATOR. t27c seal gives every spec a hash of its parse. A built artefact
+; records the seal of the spec it was built from. needs_build() is false when the seals match, so a
+; one-spec edit rebuilds one widget of 82 and skips the other 81. The renderer's own version is part
+; of the key: a renderer change rebuilds every page, never every card.
+; --
+; THE PORT GATE. A widget moves up one stage at a time, and only when (a) the new stage's output is
+; byte-identical to the hand-written output it replaces, (b) the spec's tests pass, and (c) the
+; hand-written lines do not grow. (c) is the same rule as t27b's "net <= 0" and FROZEN_HASH ports.
+; --
+; Not claimed: no stage past `spec` exists as a program today. gen-ts has not been run on a tool's
+; logic; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
+; PROPOSED from reading the 28 tools by name, not measured; wave 1 (catalog.t27, 11 tools of at
+; most 700 hand lines) measures them and may rename or merge them.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module t27f_widget_factory;
+
+use widgets::catalog::wave_of;
+use widgets::catalog::PORT_HAND;
+use widgets::catalog::PORT_WORDS;
+use widgets::catalog::PORT_LOGIC;
+use widgets::catalog::PORT_DRAW;
+use widgets::catalog::PORT_SEALED;
+use widgets::catalog::PORT_STAGES;
+use t27f::ui_plan::widget_id;
+use t27f::ui_plan::WIDGET_UNKNOWN;
+use t27mcp::service::home_of;
+use t27mcp::service::has_prefix;
+use t27mcp::service::H_T27;
+
+/// The stages, in order. A stage reads only the one before it.
+pub const ST_SPEC : u8 = 0;
+pub const ST_LOGIC : u8 = 1;
+pub const ST_PLAN : u8 = 2;
+pub const ST_PAGE : u8 = 3;
+pub const ST_DATA : u8 = 4;
+pub const ST_CARD : u8 = 5;
+pub const ST_COUNT : u8 = 6;
+pub const STAGE_NAMES : [6]str = ["spec", "logic", "plan", "page", "data", "card"];
+
+/// The tool of the toolchain that makes each stage. t27c is the compiler and the seal; gen-ts
+/// writes the logic; t27b compiles the data builder native; t27f draws plan, page and card.
+pub const MAKER_T27C : u8 = 0;
+pub const MAKER_GEN_TS : u8 = 1;
+pub const MAKER_T27F : u8 = 2;
+pub const MAKER_T27B : u8 = 3;
+pub const STAGE_MAKER : [6]u8 = [0, 1, 2, 2, 3, 2];
+
+/// Draw primitives the factory adds to ui_plan's catalog (which already has metrics_table and
+/// text_block). PROPOSED, see the header. None of them may be a forbidden name.
+pub const PRIMITIVE_COUNT : u32 = 8;
+pub const PRIMITIVES : [8]str = ["bars", "treemap", "timeline", "grid_map", "graph", "waves", "calc", "tabs"];
+pub const FORBIDDEN : [6]str = ["script", "eval", "html", "iframe", "raw_js", "function"];
+
+/// The verbs tri CLI and the tools TRI CLAW carries. `tri widget` exists today (trinity, the
+/// widget-forge skill); these are what it gains. Every tool is a t27f_ name so that service.t27
+/// mounts it into TRI CLAW's /mcp with the other t27 tools.
+pub const TRI_VERB_COUNT : u32 = 5;
+pub const TRI_VERBS : [5]str = ["widget list", "widget port", "widget make", "widget seal", "widget gate"];
+pub const CLAW_TOOL_COUNT : u32 = 3;
+pub const CLAW_TOOLS : [3]str = ["t27f_widgets", "t27f_widget_make", "t27f_widget_port_plan"];
+
+/// A stage is the next of a port stage only by one step.
+pub fn next_port(stage: u8) -> u8 {
+ if (stage + 1 >= PORT_STAGES) {
+ return PORT_STAGES;
+ }
+ return stage + 1;
+}
+
+/// The port gate. before and after are the widget's hand-written lines at the two ends of the move.
+pub fn port_gate(from: u8, to: u8, byte_identical: bool, tests_pass: bool, hand_before: u32, hand_after: u32) -> bool {
+ if (to >= PORT_STAGES) {
+ return false;
+ }
+ if (to != next_port(from)) {
+ return false;
+ }
+ if (byte_identical == false) {
+ return false;
+ }
+ if (tests_pass == false) {
+ return false;
+ }
+ return hand_after <= hand_before;
+}
+
+/// The key a built artefact is filed under: the spec's seal and the renderer version it was drawn by.
+pub fn build_key(spec_seal: u32, renderer: u32, uses_renderer: bool) -> u32 {
+ if (uses_renderer) {
+ return spec_seal * 31 + renderer;
+ }
+ return spec_seal * 31;
+}
+
+/// Build only when the key of what exists is not the key of what is wanted.
+pub fn needs_build(want: u32, have: u32) -> bool {
+ return want != have;
+}
+
+/// Of n widgets, how many are skipped when `changed` of them have a new key.
+pub fn skipped(n: u32, changed: u32) -> u32 {
+ if (changed >= n) {
+ return 0;
+ }
+ return n - changed;
+}
+
+/// A primitive name is allowed when it is not a forbidden word.
+pub fn primitive_ok(name: str) -> bool {
+ var f : u32 = 0;
+ while (f < 6) {
+ if (FORBIDDEN[f as usize] == name) { return false; }
+ f = f + 1;
+ }
+ return true;
+}
+
+/// How many hand-written lines a wave-1 tool of `hand` lines may keep after its logic is ported:
+/// none of the logic file, since gen-ts writes it. A pure bookkeeping rule the epic's issues quote.
+pub fn after_logic(hand: u32, logic: u32) -> u32 {
+ if (logic >= hand) {
+ return 0;
+ }
+ return hand - logic;
+}
+
+test the_stages_are_six_in_order_with_a_maker_each {
+ assert ST_COUNT == 6;
+ assert STAGE_NAMES[ST_SPEC] == "spec";
+ assert STAGE_NAMES[ST_CARD] == "card";
+ assert STAGE_MAKER[ST_LOGIC] == MAKER_GEN_TS;
+ assert STAGE_MAKER[ST_DATA] == MAKER_T27B;
+ assert STAGE_MAKER[ST_SPEC] == MAKER_T27C;
+ assert STAGE_MAKER[ST_PAGE] == MAKER_T27F;
+ assert STAGE_MAKER[ST_CARD] == MAKER_T27F;
+}
+
+test a_port_moves_one_stage_at_a_time {
+ assert next_port(PORT_WORDS) == PORT_LOGIC;
+ assert next_port(PORT_LOGIC) == PORT_DRAW;
+ assert next_port(PORT_SEALED) == PORT_STAGES;
+ assert port_gate(PORT_WORDS, PORT_LOGIC, true, true, 1000, 800);
+ assert port_gate(PORT_WORDS, PORT_DRAW, true, true, 1000, 800) == false;
+ assert port_gate(PORT_DRAW, PORT_LOGIC, true, true, 1000, 800) == false;
+ assert port_gate(PORT_SEALED, PORT_STAGES, true, true, 1000, 0) == false;
+}
+
+test a_port_needs_identical_output_green_tests_and_no_growth {
+ assert port_gate(PORT_HAND, PORT_WORDS, true, true, 500, 500);
+ assert port_gate(PORT_HAND, PORT_WORDS, false, true, 500, 400) == false;
+ assert port_gate(PORT_HAND, PORT_WORDS, true, false, 500, 400) == false;
+ assert port_gate(PORT_HAND, PORT_WORDS, true, true, 500, 501) == false;
+ assert port_gate(PORT_LOGIC, PORT_DRAW, true, true, 800, 0);
+}
+
+test the_seal_skips_what_did_not_change {
+ const k1 : u32 = build_key(1000, 7, true);
+ assert needs_build(k1, k1) == false;
+ assert needs_build(build_key(1001, 7, true), k1);
+ assert needs_build(build_key(1000, 8, true), k1);
+ assert needs_build(build_key(1000, 8, false), build_key(1000, 7, false)) == false;
+ assert skipped(82, 1) == 81;
+ assert skipped(82, 0) == 82;
+ assert skipped(82, 82) == 0;
+ assert skipped(5, 9) == 0;
+}
+
+test the_primitives_extend_ui_plan_and_hold_no_code {
+ var i : u32 = 0;
+ while (i < PRIMITIVE_COUNT) {
+ assert primitive_ok(PRIMITIVES[i as usize]);
+ assert widget_id(PRIMITIVES[i as usize]) == WIDGET_UNKNOWN;
+ i = i + 1;
+ }
+ assert widget_id("metrics_table") != WIDGET_UNKNOWN;
+ assert widget_id("text_block") != WIDGET_UNKNOWN;
+ assert widget_id("script") == WIDGET_UNKNOWN;
+ assert primitive_ok("script") == false;
+ assert primitive_ok("bars");
+}
+
+test every_claw_tool_is_a_t27_name_the_service_mounts {
+ var i : u32 = 0;
+ while (i < CLAW_TOOL_COUNT) {
+ assert has_prefix(CLAW_TOOLS[i as usize], "t27f_");
+ assert home_of(CLAW_TOOLS[i as usize]) == H_T27;
+ i = i + 1;
+ }
+ assert TRI_VERB_COUNT == 5;
+ assert TRI_VERBS[0] == "widget list";
+}
+
+test the_logic_stage_removes_exactly_the_logic_file {
+ assert after_logic(1000, 51) == 949;
+ assert after_logic(686, 0) == 686;
+ assert after_logic(10, 20) == 0;
+ assert wave_of(after_logic(1432, 370)) == 3;
+ assert wave_of(after_logic(1000, 51)) == 2;
+ assert port_gate(PORT_WORDS, PORT_LOGIC, true, true, 1432, after_logic(1432, 370));
+}
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
new file mode 100644
index 0000000000..35f526116f
--- /dev/null
+++ b/specs/widgets/catalog.t27
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/catalog.t27 -- the census of every widget in every t27 project, and how much of each
+; is still written by hand. Owner, 2026-10-19: find all widgets in all projects, catalogue them,
+; rewrite the hand-written ones in .t27, make them easy to work with, move the lesson into an epic
+; for tri CLI and TRI CLAW, and build a widget factory natively in t27f, t27b and t27c seal.
+; This file is the catalogue. The factory that retires the hand-written column is
+; specs/t27f/widget_factory.t27; the shipped gallery (what a reader may share) is
+; specs/widgets/gallery.t27 in gHashTag/trinity and is NOT copied here -- this spec counts, it does
+; not list titles.
+; --
+; MEASURED 2026-10-19 at gHashTag/trinity origin/main, wc -l of every file, nothing estimated:
+; apps/website/public/widgets// 82 widget folders, app.t27.ai/game/widgets previews them
+; apps/website/specs/widgets/.t27 the words and numbers of each (SAY_ and K_ constants)
+; apps/website/scripts/widget-data/ the data builders (.mjs) and the card renderers (.py)
+; apps/queen/QueenUI/Widgets/ 105 SwiftUI views, 42052 lines
+; apps/website/src/components/ 11 React *Widget*.tsx, 4902 lines
+; Of the 82 folders, 54 are LESSONS (27 basics-*, 27 golden-*): a 3-line tool.js and a 47-48 line
+; page, both generated from the spec. They are already native. The other 28 are TOOLS and carry
+; hand-written JavaScript, CSS, a Node data builder and a Python card renderer.
+; --
+; What the census found, which no one had written down:
+; 1. A tool is TWO layers. 12 of the 28 have a pure logic file beside tool.js (rule.js, bitparse.js,
+; fsmparse.js, lut.js, treemap.js, xdc.js, statparse.js, ...): 2775 lines of functions of
+; numbers and text, no DOM. They port to .t27 and gen-ts as they stand, and the spec's test
+; blocks replace the fixed answers the data builder re-checks today (gatle.mjs imports rule.js
+; "so the page cannot drift from the spec" -- one source would make that true by construction).
+; 2. The other 8920 lines are DOM drawing. Every tool.js opens with the same helpers (fill, el,
+; link, a stylesheet link) and then builds tables, bars, maps and timelines. That is a renderer,
+; not 28 programs: ui_plan.t27 already decides that a plan is data and a trusted renderer draws
+; it. The factory extends that catalog instead of adding a second one.
+; 3. 23 tools have a Node data builder (5572 lines) and 24 a Python card renderer (3415 lines).
+; The card is a picture of the page; t27f draws pictures from specs already (SF_STILL).
+; --
+; Not claimed: that any hand column has been rewritten. Every tool is at PORT_WORDS today: its
+; words live in its spec, its logic and drawing still live in JavaScript. A tool moves up only when
+; its output is byte-identical to the hand version, and the factory spec states that gate.
+; The Swift and React widgets are counted, not classified: they are another renderer's targets.
+; ASCII only (L3), English only (LANG-EN).
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_catalog;
+
+pub const KIND : str = "widgets-catalog";
+pub const ID : str = "widgets/catalog";
+pub const MEASURED : str = "2026-10-19";
+pub const MEASURED_AT : str = "gHashTag/trinity origin/main";
+
+; --- What the gallery serves, by kind (gallery.t27: WIDGET_COUNT) -------------------------------
+pub const GALLERY_CASTS : u32 = 104;
+pub const GALLERY_PLAYERS : u32 = 8;
+pub const GALLERY_TABS : u32 = 1;
+pub const GALLERY_TOOLS : u32 = 82;
+pub const GALLERY_COUNT : u32 = 195;
+
+; --- The 82 tool folders ----------------------------------------------------------------------
+pub const LESSON_BASICS : u32 = 27;
+pub const LESSON_GOLDEN : u32 = 27;
+pub const TOOL_COUNT : u32 = 28;
+
+; --- Other renderers' widgets, counted not classified ------------------------------------------
+pub const SWIFT_FILES : u32 = 105;
+pub const SWIFT_LINES : u32 = 42052;
+pub const REACT_FILES : u32 = 11;
+pub const REACT_LINES : u32 = 4902;
+
+; --- The 28 tools, in alphabetical order; every array below is TOOL_COUNT long -----------------
+pub const TOOL_IDS : [28]str = ["bit-autopsy", "bit-diff", "build-receipt", "cost-curves", "fasm-skyline", "fits-on", "fsm-sketch", "gatle", "gf16-calc", "idcode", "jtag-verdict", "lut-depth", "lut-explain", "lut-treemap", "pin-map", "placement-map", "race-the-bee", "resource-bars", "slack-waterfall", "stat-card", "synth-smells", "t27-cell", "t27-vs-nvfp4", "t27c-stages", "test-touch", "test-waves", "uart-bucket", "vcd-wrapped"];
+
+; Hand-written lines per tool. LOGIC is the pure parser/rule file beside tool.js (0 when the tool
+; has none), DRAW is tool.js. CSS is tool.css. DATA is scripts/widget-data/.mjs, CARD is
+; scripts/widget-data/-card.py (0 when the tool has neither). SPEC is specs/widgets/.t27.
+pub const LOGIC_LINES : [28]u32 = [289, 371, 0, 0, 122, 0, 379, 51, 0, 0, 0, 97, 370, 289, 207, 0, 0, 0, 0, 127, 192, 0, 0, 0, 0, 0, 0, 281];
+pub const DRAW_LINES : [28]u32 = [376, 274, 221, 273, 533, 232, 361, 309, 511, 316, 171, 241, 588, 322, 340, 379, 105, 172, 369, 318, 304, 187, 192, 364, 146, 726, 228, 362];
+pub const CSS_LINES : [28]u32 = [57, 50, 127, 65, 43, 51, 56, 98, 114, 96, 61, 89, 128, 85, 151, 32, 51, 33, 42, 48, 76, 78, 78, 169, 89, 34, 72, 78];
+pub const DATA_LINES : [28]u32 = [184, 124, 329, 395, 266, 209, 294, 411, 0, 153, 0, 187, 242, 252, 187, 0, 208, 0, 0, 163, 203, 144, 221, 109, 696, 144, 177, 274];
+pub const CARD_LINES : [28]u32 = [103, 72, 201, 131, 153, 126, 194, 131, 0, 121, 117, 150, 104, 252, 206, 0, 89, 0, 0, 147, 148, 85, 101, 163, 133, 189, 129, 170];
+pub const SPEC_LINES : [28]u32 = [183, 148, 139, 246, 196, 187, 197, 188, 193, 180, 159, 197, 304, 233, 189, 125, 110, 121, 135, 201, 201, 134, 168, 153, 183, 124, 129, 198];
+
+; Port stage of one tool. A tool moves up only through widget_factory.t27's port_gate.
+pub const PORT_HAND : u8 = 0; // words and rules in JavaScript
+pub const PORT_WORDS : u8 = 1; // words and numbers in the spec (every tool today)
+pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
+pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
+pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
+pub const PORT_STAGES : u8 = 5;
+pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+
+; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
+; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
+pub const WAVE_1_MAX : u32 = 700;
+pub const WAVE_2_MAX : u32 = 1000;
+
+pub fn gallery_sum() -> u32 {
+ return GALLERY_CASTS + GALLERY_PLAYERS + GALLERY_TABS + GALLERY_TOOLS;
+}
+
+pub fn folder_sum() -> u32 {
+ return LESSON_BASICS + LESSON_GOLDEN + TOOL_COUNT;
+}
+
+pub fn hand_lines(i: u32) -> u32 {
+ return LOGIC_LINES[i] + DRAW_LINES[i] + CSS_LINES[i] + DATA_LINES[i] + CARD_LINES[i];
+}
+
+pub fn wave_of(lines: u32) -> u32 {
+ if (lines <= WAVE_1_MAX) {
+ return 1;
+ }
+ if (lines <= WAVE_2_MAX) {
+ return 2;
+ }
+ return 3;
+}
+
+pub fn has_logic_file(i: u32) -> bool {
+ return LOGIC_LINES[i] > 0;
+}
+
+pub fn sum_lines(a: [28]u32) -> u32 {
+ var s : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ s = s + a[i];
+ i = i + 1;
+ }
+ return s;
+}
+
+pub fn total_hand() -> u32 {
+ return sum_lines(LOGIC_LINES) + sum_lines(DRAW_LINES) + sum_lines(CSS_LINES) + sum_lines(DATA_LINES) + sum_lines(CARD_LINES);
+}
+
+pub fn count_with(a: [28]u32) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ if (a[i] > 0) {
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+pub fn count_wave(w: u32) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ if (wave_of(hand_lines(i)) == w) {
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+pub fn count_port(stage: u8) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ if (TOOL_PORT[i] == stage) {
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+test the_gallery_kinds_add_up_to_its_count {
+ assert gallery_sum() == GALLERY_COUNT;
+ assert GALLERY_COUNT == 195;
+}
+
+test the_tool_folders_are_lessons_plus_tools {
+ assert folder_sum() == GALLERY_TOOLS;
+}
+
+test every_tool_has_an_id_and_a_spec {
+ var i : u32 = 0;
+ while (i < TOOL_COUNT) {
+ assert SPEC_LINES[i] > 100;
+ assert DRAW_LINES[i] > 0;
+ assert CSS_LINES[i] > 0;
+ i = i + 1;
+ }
+ assert TOOL_IDS[0] == "bit-autopsy";
+ assert TOOL_IDS[7] == "gatle";
+ assert TOOL_IDS[27] == "vcd-wrapped";
+}
+
+test the_hand_written_column_is_the_measured_total {
+ assert sum_lines(LOGIC_LINES) == 2775;
+ assert sum_lines(DRAW_LINES) == 8920;
+ assert sum_lines(CSS_LINES) == 2151;
+ assert sum_lines(DATA_LINES) == 5572;
+ assert sum_lines(CARD_LINES) == 3415;
+ assert sum_lines(SPEC_LINES) == 4921;
+ assert total_hand() == 22833;
+}
+
+test twelve_tools_split_logic_from_drawing {
+ assert count_with(LOGIC_LINES) == 12;
+ assert has_logic_file(7);
+ assert has_logic_file(2) == false;
+ assert LOGIC_LINES[7] == 51;
+ assert count_with(DATA_LINES) == 23;
+ assert count_with(CARD_LINES) == 24;
+}
+
+test the_waves_cover_every_tool_once {
+ assert count_wave(1) == 11;
+ assert count_wave(2) == 8;
+ assert count_wave(3) == 9;
+ assert count_wave(1) + count_wave(2) + count_wave(3) == TOOL_COUNT;
+ assert hand_lines(17) == 205;
+ assert hand_lines(7) == 1000;
+ assert wave_of(hand_lines(7)) == 2;
+ assert hand_lines(26) == 606;
+ assert hand_lines(12) == 1432;
+}
+
+test no_tool_has_left_the_words_stage_yet {
+ assert count_port(PORT_WORDS) == TOOL_COUNT;
+ assert count_port(PORT_LOGIC) == 0;
+ assert count_port(PORT_DRAW) == 0;
+ assert count_port(PORT_SEALED) == 0;
+}
From 9d714ffd965e31dfd526206d77f6d55cb1a35734 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:35:31 +0700
Subject: [PATCH 02/63] widgets: gatle rules ported to .t27 -- 3 tests, 19
asserts; gen-js lowers no fn bodies, so the logic stage waits (Closes #10938)
specs/widgets/gatle_rule.t27 replaces rule.js as the single source. widget_factory.t27
records that gen-js/gen-ts emit declarations only. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/t27f/widget_factory.t27 | 19 +++++-
specs/widgets/gatle_rule.t27 | 113 ++++++++++++++++++++++++++++++++++
2 files changed, 130 insertions(+), 2 deletions(-)
create mode 100644 specs/widgets/gatle_rule.t27
diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27
index f757c21c6f..934d3a159f 100644
--- a/specs/t27f/widget_factory.t27
+++ b/specs/t27f/widget_factory.t27
@@ -29,8 +29,8 @@
; byte-identical to the hand-written output it replaces, (b) the spec's tests pass, and (c) the
; hand-written lines do not grow. (c) is the same rule as t27b's "net <= 0" and FROZEN_HASH ports.
; --
-; Not claimed: no stage past `spec` exists as a program today. gen-ts has not been run on a tool's
-; logic; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
+; Not claimed: no stage past `spec` exists as a program today. gatle's rules are ported to
+; specs/widgets/gatle_rule.t27 and pass, but gen-js/gen-ts do not lower fn bodies, so no JS is generated; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
; PROPOSED from reading the 28 tools by name, not measured; wave 1 (catalog.t27, 11 tools of at
; most 700 hand lines) measures them and may rename or merge them.
; phi^2 + 1/phi^2 = 3 | TRINITY
@@ -82,6 +82,17 @@ pub const TRI_VERBS : [5]str = ["widget list", "widget port", "widget make", "wi
pub const CLAW_TOOL_COUNT : u32 = 3;
pub const CLAW_TOOLS : [3]str = ["t27f_widgets", "t27f_widget_make", "t27f_widget_port_plan"];
+/// MEASURED 2026-10-19 with t27c 0.5.2 on specs/widgets/gatle_rule.t27: gen-js and gen-ts emit
+/// declarations (consts, structs) and skip every fn body -- "this backend lowers declarations, not
+/// bodies". Until a body-lowering JS backend exists the logic stage writes a tested .t27 rule
+/// (the single source) but cannot generate the JavaScript the page runs. Flip this when it can.
+pub const GEN_JS_LOWERS_BODIES : bool = false;
+
+/// The logic stage can produce its artefact only when the backend lowers bodies.
+pub fn logic_stage_ready() -> bool {
+ return GEN_JS_LOWERS_BODIES;
+}
+
/// A stage is the next of a port stage only by one step.
pub fn next_port(stage: u8) -> u8 {
if (stage + 1 >= PORT_STAGES) {
@@ -213,6 +224,10 @@ test every_claw_tool_is_a_t27_name_the_service_mounts {
assert TRI_VERBS[0] == "widget list";
}
+test the_logic_stage_waits_for_a_body_lowering_backend {
+ assert logic_stage_ready() == false;
+}
+
test the_logic_stage_removes_exactly_the_logic_file {
assert after_logic(1000, 51) == 949;
assert after_logic(686, 0) == 686;
diff --git a/specs/widgets/gatle_rule.t27 b/specs/widgets/gatle_rule.t27
new file mode 100644
index 0000000000..c082dc8ccb
--- /dev/null
+++ b/specs/widgets/gatle_rule.t27
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/gatle_rule.t27 -- the rules of Gatle as pure functions: the first widget logic file
+; ported from JavaScript (trinity apps/website/public/widgets/gatle/rule.js, 51 lines) to .t27.
+; Epic gHashTag/t27#10937. Until now rule.js was hand-written and gatle.mjs re-imported it to check
+; it against the fixed answers in gatle.t27, "so the page cannot drift from the spec". Here the
+; rules and their fixed answers are one file, so there is nothing to drift.
+; Ported: dayNumber, dayIndex, puzzleNumber, grade (heat and dir), emoji_code. Not yet ported:
+; parseHash and formatHash (URL text handling) -- the next step, once gen-js of str ops is measured.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_gatle_rule;
+
+pub const DAY_MS : u64 = 86400000;
+
+pub const HEAT_CLOSE : u8 = 0;
+pub const HEAT_WARM : u8 = 1;
+pub const HEAT_COLD : u8 = 2;
+pub const DIR_HIT : u8 = 0;
+pub const DIR_UP : u8 = 1;
+pub const DIR_DOWN : u8 = 2;
+
+pub const EMOJI_HEAT : [3]u32 = [129001, 129000, 128997];
+pub const ARROWS : [2]u32 = [11014, 11015];
+
+/// Whole UTC days since 1970-01-01 for a time in milliseconds.
+pub fn day_number(ms: u64) -> u64 {
+ return ms / DAY_MS;
+}
+
+/// The puzzle a UTC day gets: days since the epoch, modulo the pool size (never negative).
+pub fn day_index(day: u64, epoch_day: u64, size: u64) -> u64 {
+ if (day >= epoch_day) {
+ return (day - epoch_day) % size;
+ }
+ return (size - ((epoch_day - day) % size)) % size;
+}
+
+/// The running number a day's puzzle is shared under: the epoch day is #1.
+pub fn puzzle_number(day: u64, epoch_day: u64) -> u64 {
+ return day - epoch_day + 1;
+}
+
+/// |guess - truth| * 100, integer arithmetic only.
+pub fn off100(guess: u64, truth: u64) -> u64 {
+ if (guess > truth) {
+ return (guess - truth) * 100;
+ }
+ return (truth - guess) * 100;
+}
+
+/// close within close_pct percent of the truth (boundary counts), warm within warm_pct, else cold.
+pub fn heat(guess: u64, truth: u64, close_pct: u64, warm_pct: u64) -> u8 {
+ const off : u64 = off100(guess, truth);
+ if (off <= truth * close_pct) {
+ return HEAT_CLOSE;
+ }
+ if (off <= truth * warm_pct) {
+ return HEAT_WARM;
+ }
+ return HEAT_COLD;
+}
+
+/// hit when close, else up (the truth is higher) or down.
+pub fn dir(guess: u64, truth: u64, close_pct: u64, warm_pct: u64) -> u8 {
+ if (heat(guess, truth, close_pct, warm_pct) == HEAT_CLOSE) {
+ return DIR_HIT;
+ }
+ if (truth > guess) {
+ return DIR_UP;
+ }
+ return DIR_DOWN;
+}
+
+/// The code point of the heat square of one graded guess.
+pub fn heat_code(h: u8) -> u32 {
+ return EMOJI_HEAT[h as usize];
+}
+
+/// The code point of the arrow of one graded guess; 0 for a hit (no arrow).
+pub fn arrow_code(d: u8) -> u32 {
+ if (d == DIR_HIT) {
+ return 0;
+ }
+ return ARROWS[(d - 1) as usize];
+}
+
+test day_index_on_two_fixed_dates {
+ assert day_number(1791072000000) == 20730;
+ assert day_index(20730, 20730, 37) == 0;
+ assert day_index(20819, 20730, 37) == 15;
+ assert puzzle_number(20730, 20730) == 1;
+ assert puzzle_number(20819, 20730) == 90;
+ assert day_index(20729, 20730, 37) == 36;
+}
+
+test close_is_within_ten_percent_boundary_included {
+ assert heat(180, 200, 10, 50) == HEAT_CLOSE;
+ assert heat(179, 200, 10, 50) == HEAT_WARM;
+ assert heat(220, 200, 10, 50) == HEAT_CLOSE;
+ assert heat(221, 200, 10, 50) == HEAT_WARM;
+ assert heat(1, 200, 10, 50) == HEAT_COLD;
+ assert dir(180, 200, 10, 50) == DIR_HIT;
+ assert dir(179, 200, 10, 50) == DIR_UP;
+ assert dir(221, 200, 10, 50) == DIR_DOWN;
+}
+
+test the_share_codes_follow_the_grade {
+ assert heat_code(HEAT_CLOSE) == 129001;
+ assert heat_code(HEAT_COLD) == 128997;
+ assert arrow_code(DIR_HIT) == 0;
+ assert arrow_code(DIR_UP) == 11014;
+ assert arrow_code(DIR_DOWN) == 11015;
+}
From 749e318e9404dac896e89c7f861fd59c0497308a Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:39:00 +0700
Subject: [PATCH 03/63] widgets: gallery rules as scalar .t27 -- 195 widgets,
refusal verdicts and embed sizes run native under t27b, 3 of 3 (Closes
#10938)
specs/widgets/gallery_rules.t27 holds the decisions of scripts/widgets-from-spec.mjs
(trinity); the gallery data stays in trinity gallery.t27. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/gallery_rules.t27 | 146 ++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
create mode 100644 specs/widgets/gallery_rules.t27
diff --git a/specs/widgets/gallery_rules.t27 b/specs/widgets/gallery_rules.t27
new file mode 100644
index 0000000000..1d31e5c1f6
--- /dev/null
+++ b/specs/widgets/gallery_rules.t27
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/gallery_rules.t27 -- the rules of the widget gallery (app.t27.ai/game/widgets) as
+; scalar functions, so t27b can run them native. Epic gHashTag/t27#10937, owner 2026-10-19: the
+; gallery is integrated natively into t27b and T27F.
+; The gallery is 195 widgets: 104 casts, 8 players, 1 tab, 82 tools; by category 95 fpga, 92
+; compiler, 8 game. Its data is gHashTag/trinity apps/website/specs/widgets/gallery.t27 and stays
+; there (this file does not copy 195 rows). Its generator, scripts/widgets-from-spec.mjs (269
+; lines), does two jobs: I/O (read each widget's page for og tags, read meta.json) and RULES (which
+; widget is refused, how tall its embed is, whether its preview carries ?embed=1). The rules are
+; here; the I/O stays a thin reader that hands this file booleans. verdict() is the generator's
+; problems list as one number, in the generator's order.
+; Not claimed: the generator still runs in node today; this file is what replaces its decisions.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_gallery_rules;
+
+pub const K_CAST : u8 = 0;
+pub const K_PLAYER : u8 = 1;
+pub const K_TAB : u8 = 2;
+pub const K_TOOL : u8 = 3;
+pub const K_KINDS : u8 = 4;
+
+pub const GALLERY_COUNT : u32 = 195;
+pub const KIND_COUNTS : [4]u32 = [104, 8, 1, 82];
+pub const CAT_COUNTS : [3]u32 = [95, 92, 8];
+
+pub const EMBED_WIDTH : u32 = 640;
+pub const PLAYER_EMBED_HEIGHT : u32 = 360;
+pub const CAST_EMBED_HEIGHT : u32 = 460;
+
+pub const V_OK : u8 = 0;
+pub const V_TAB_VIEW_MISSING : u8 = 1;
+pub const V_PAGE_MISSING : u8 = 2;
+pub const V_META_MISSING : u8 = 3;
+pub const V_IMAGE_NOT_LOCAL : u8 = 4;
+pub const V_CAST_META_MISSING : u8 = 5;
+pub const V_CAST_BAD_EXIT : u8 = 6;
+pub const V_TOOL_HOOK_MISSING : u8 = 7;
+pub const V_PLAYER_EMBED_MISSING : u8 = 8;
+pub const V_UNKNOWN_KIND : u8 = 9;
+
+/// Embed query a kind carries: 0 none, 1 "?embed=1", 2 "&embed=1" (a tab already has a query).
+pub fn embed_query(kind: u8) -> u8 {
+ if (kind == K_TOOL) {
+ return 1;
+ }
+ if (kind == K_TAB) {
+ return 2;
+ }
+ return 0;
+}
+
+pub fn embed_height(kind: u8) -> u32 {
+ if (kind == K_PLAYER) {
+ return PLAYER_EMBED_HEIGHT;
+ }
+ return CAST_EMBED_HEIGHT;
+}
+
+/// A tab has no page of its own to preview; every other kind previews its framed page.
+pub fn has_preview(kind: u8) -> bool {
+ return kind != K_TAB;
+}
+
+/// Why the generator would refuse one widget, or V_OK. The booleans are what its reader found.
+pub fn verdict(kind: u8, view_ok: bool, page_ok: bool, meta_ok: bool, image_local: bool, cast_meta: bool, bad_exit: bool, hook_ok: bool, player_ok: bool) -> u8 {
+ if (kind >= K_KINDS) {
+ return V_UNKNOWN_KIND;
+ }
+ if (kind == K_TAB) {
+ if (view_ok) {
+ return V_OK;
+ }
+ return V_TAB_VIEW_MISSING;
+ }
+ if (page_ok == false) {
+ return V_PAGE_MISSING;
+ }
+ if (meta_ok == false) {
+ return V_META_MISSING;
+ }
+ if (image_local == false) {
+ return V_IMAGE_NOT_LOCAL;
+ }
+ if (kind == K_CAST) {
+ if (cast_meta == false) {
+ return V_CAST_META_MISSING;
+ }
+ if (bad_exit) {
+ return V_CAST_BAD_EXIT;
+ }
+ return V_OK;
+ }
+ if (kind == K_TOOL) {
+ if (hook_ok == false) {
+ return V_TOOL_HOOK_MISSING;
+ }
+ return V_OK;
+ }
+ if (player_ok == false) {
+ return V_PLAYER_EMBED_MISSING;
+ }
+ return V_OK;
+}
+
+pub fn kinds_sum() -> u32 {
+ return KIND_COUNTS[0] + KIND_COUNTS[1] + KIND_COUNTS[2] + KIND_COUNTS[3];
+}
+
+pub fn cats_sum() -> u32 {
+ return CAT_COUNTS[0] + CAT_COUNTS[1] + CAT_COUNTS[2];
+}
+
+test the_gallery_counts_agree_by_kind_and_by_category {
+ assert kinds_sum() == GALLERY_COUNT;
+ assert cats_sum() == GALLERY_COUNT;
+}
+
+test embed_size_and_query_follow_the_kind {
+ assert embed_height(K_PLAYER) == 360;
+ assert embed_height(K_CAST) == 460;
+ assert embed_height(K_TOOL) == 460;
+ assert embed_height(K_TAB) == 460;
+ assert embed_query(K_TOOL) == 1;
+ assert embed_query(K_TAB) == 2;
+ assert embed_query(K_CAST) == 0;
+ assert embed_query(K_PLAYER) == 0;
+ assert has_preview(K_TAB) == false;
+ assert has_preview(K_TOOL);
+}
+
+test a_widget_is_refused_for_the_first_rule_it_breaks {
+ assert verdict(K_CAST, true, true, true, true, true, false, false, false) == V_OK;
+ assert verdict(K_CAST, true, false, true, true, true, false, false, false) == V_PAGE_MISSING;
+ assert verdict(K_CAST, true, true, false, true, true, false, false, false) == V_META_MISSING;
+ assert verdict(K_CAST, true, true, true, false, true, false, false, false) == V_IMAGE_NOT_LOCAL;
+ assert verdict(K_CAST, true, true, true, true, false, false, false, false) == V_CAST_META_MISSING;
+ assert verdict(K_CAST, true, true, true, true, true, true, false, false) == V_CAST_BAD_EXIT;
+ assert verdict(K_TOOL, true, true, true, true, false, false, false, false) == V_TOOL_HOOK_MISSING;
+ assert verdict(K_TOOL, true, true, true, true, false, false, true, false) == V_OK;
+ assert verdict(K_PLAYER, true, true, true, true, false, false, false, false) == V_PLAYER_EMBED_MISSING;
+ assert verdict(K_PLAYER, true, true, true, true, false, false, false, true) == V_OK;
+ assert verdict(K_TAB, false, false, false, false, false, false, false, false) == V_TAB_VIEW_MISSING;
+ assert verdict(K_TAB, true, false, false, false, false, false, false, false) == V_OK;
+ assert verdict(K_KINDS, true, true, true, true, true, false, true, true) == V_UNKNOWN_KIND;
+}
From ddcabdf0db17afaa6f67eddd7579d1528d87804c Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 14:58:14 +0700
Subject: [PATCH 04/63] widgets: the gallery reader as pure .t27 -- meta tags,
entities, localOf, exit codes and hud views over bytes; 8 of 8 under t27c and
t27b, byte-identical to metaOf on all 337 corpus files (Closes #10938)
specs/widgets/page_reader.t27 replaces the reading half of widgets-from-spec.mjs. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/page_reader.t27 | 430 ++++++++++++++++++++++++++++++++++
1 file changed, 430 insertions(+)
create mode 100644 specs/widgets/page_reader.t27
diff --git a/specs/widgets/page_reader.t27 b/specs/widgets/page_reader.t27
new file mode 100644
index 0000000000..60098ba81d
--- /dev/null
+++ b/specs/widgets/page_reader.t27
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/page_reader.t27 -- the gallery's reader as pure rules over bytes: what a widget's
+; page and recording say, without touching a disk. Epic gHashTag/t27#10937; owner 2026-10-19: the
+; gallery goes native into t27b and T27F. Replaces, rule for rule, the reading half of
+; apps/website/scripts/widgets-from-spec.mjs in gHashTag/trinity: metaOf (the og and twitter tags
+; of a share page), unescape, localOf (an absolute t27.ai address to its file), framed, the cast's
+; exit_codes test, and hudViews (which Queen board views exist, for the one tab).
+; --
+; The pattern is source_text.t27's (T27bSourceText): the glue passes a file's BYTES and gets back
+; offsets; nothing here reads a file. A sub-text is returned as its two byte offsets packed in one
+; i64 (start * 2^32 + end), NO_TEXT when there is none, so the whole module is scalar-and-slice and
+; t27b can run it. A text with HTML entities is read through text_len and text_byte, which unescape
+; on the fly: no buffer, no allocation.
+; --
+; Exact semantics kept from the generator, so the swap changes nothing a reader can see:
+; - a meta tag matches only as ` i64 {
+ return (lo as i64) * PACK + (hi as i64);
+}
+
+pub fn lo_of(p: i64) -> usize {
+ return (p / PACK) as usize;
+}
+
+pub fn hi_of(p: i64) -> usize {
+ return (p % PACK) as usize;
+}
+
+/// JavaScript's \s for the ASCII range: space, tab, line feed, vertical tab, form feed, carriage return.
+pub fn is_space(b: u8) -> bool {
+ return b == B_SPACE or (b >= B_TAB and b <= B_CR);
+}
+
+/// Do the bytes of w sit at s[p ..], inside s[.. hi]?
+pub fn match_in(s: []const u8, p: usize, hi: usize, w: []const u8) -> bool {
+ if (p + w.len > hi) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (s[p + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The first offset at or after `from` where w starts, or NO_TEXT.
+pub fn find_from(s: []const u8, from: usize, w: []const u8) -> i64 {
+ var p : usize = from;
+ while (p + w.len <= s.len) {
+ if (match_in(s, p, s.len, w)) { return p as i64; }
+ p = p + 1;
+ }
+ return NO_TEXT;
+}
+
+/// The offset of the next `"` at or after p, or NO_TEXT.
+pub fn next_quote(s: []const u8, p0: usize) -> i64 {
+ var p : usize = p0;
+ while (p < s.len) {
+ if (s[p] == B_QUOTE) { return p as i64; }
+ p = p + 1;
+ }
+ return NO_TEXT;
+}
+
+pub fn skip_space(s: []const u8, p0: usize) -> usize {
+ var p : usize = p0;
+ while (p < s.len and is_space(s[p])) { p = p + 1; }
+ return p;
+}
+
+/// One ` i64 {
+ var p : usize = skip_space(html, p0);
+ if (p == p0) { return NO_TEXT; }
+ if (match_in(html, p, html.len, "property=")) {
+ p = p + 9;
+ } else {
+ if (match_in(html, p, html.len, "name=")) {
+ p = p + 5;
+ } else {
+ return NO_TEXT;
+ }
+ }
+ if (p >= html.len or html[p] != B_QUOTE) { return NO_TEXT; }
+ p = p + 1;
+ if (!match_in(html, p, html.len, key)) { return NO_TEXT; }
+ p = p + key.len;
+ if (key.len == 0 or p >= html.len or html[p] != B_QUOTE) { return NO_TEXT; }
+ p = p + 1;
+ const q : usize = skip_space(html, p);
+ if (q == p) { return NO_TEXT; }
+ if (!match_in(html, q, html.len, "content=")) { return NO_TEXT; }
+ const v : usize = q + 8;
+ if (v >= html.len or html[v] != B_QUOTE) { return NO_TEXT; }
+ const e : i64 = next_quote(html, v + 1);
+ if (e < 0) { return NO_TEXT; }
+ return pack(v + 1, e as usize);
+}
+
+/// The packed content of the meta tag with this key; the last one wins, as in metaOf.
+pub fn meta_value(html: []const u8, key: []const u8) -> i64 {
+ var found : i64 = NO_TEXT;
+ var at : i64 = find_from(html, 0, "= 0) {
+ const after : usize = (at as usize) + 5;
+ const r : i64 = meta_at(html, after, key);
+ if (r >= 0) { found = r; }
+ at = find_from(html, after, " bool {
+ return meta_value(html, key) >= 0;
+}
+
+/// Which of the tags a gallery widget needs the page has.
+pub fn page_flags(html: []const u8) -> u32 {
+ var f : u32 = 0;
+ if (has_meta(html, "og:title")) { f = f + F_TITLE; }
+ if (has_meta(html, "og:description")) { f = f + F_LINE; }
+ if (has_meta(html, "og:image")) { f = f + F_IMAGE; }
+ if (has_meta(html, "t27:hook")) { f = f + F_HOOK; }
+ if (has_meta(html, "twitter:player")) { f = f + F_PLAYER; }
+ return f;
+}
+
+/// A share page has its title, line and image, and none is empty.
+pub fn words_ok(html: []const u8) -> bool {
+ var k : u32 = 0;
+ while (k < 3) {
+ var key : []const u8 = "og:title";
+ if (k == 1) { key = "og:description"; }
+ if (k == 2) { key = "og:image"; }
+ const v : i64 = meta_value(html, key);
+ if (v < 0) { return false; }
+ if (hi_of(v) == lo_of(v)) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The length of the HTML entity at s[p ..] inside s[.. hi], 0 when there is none of the six.
+pub fn entity_len(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi or s[p] != B_AMP) { return 0; }
+ if (match_in(s, p, hi, "&")) { return 5; }
+ if (match_in(s, p, hi, "<")) { return 4; }
+ if (match_in(s, p, hi, ">")) { return 4; }
+ if (match_in(s, p, hi, """)) { return 6; }
+ if (match_in(s, p, hi, "'")) { return 5; }
+ if (match_in(s, p, hi, "'")) { return 6; }
+ return 0;
+}
+
+pub fn entity_byte(s: []const u8, p: usize, hi: usize) -> u8 {
+ if (match_in(s, p, hi, "&")) { return B_AMP; }
+ if (match_in(s, p, hi, "<")) { return B_LT; }
+ if (match_in(s, p, hi, ">")) { return B_GT; }
+ if (match_in(s, p, hi, """)) { return B_QUOTE; }
+ return B_APOS;
+}
+
+/// The length of s[lo .. hi] once its entities are replaced, in one pass.
+pub fn text_len(s: []const u8, lo: usize, hi: usize) -> usize {
+ var p : usize = lo;
+ var n : usize = 0;
+ while (p < hi) {
+ const e : usize = entity_len(s, p, hi);
+ if (e > 0) { p = p + e; } else { p = p + 1; }
+ n = n + 1;
+ }
+ return n;
+}
+
+/// The k-th byte of s[lo .. hi] once its entities are replaced; 0 past the end.
+pub fn text_byte(s: []const u8, lo: usize, hi: usize, k: usize) -> u8 {
+ var p : usize = lo;
+ var n : usize = 0;
+ while (p < hi) {
+ const e : usize = entity_len(s, p, hi);
+ if (n == k) {
+ if (e > 0) { return entity_byte(s, p, hi); }
+ return s[p];
+ }
+ if (e > 0) { p = p + e; } else { p = p + 1; }
+ n = n + 1;
+ }
+ return 0;
+}
+
+/// localOf: the packed path of url after origin, up to the first ? or #; NO_TEXT when url is not
+/// under origin.
+pub fn local_of(url: []const u8, origin: []const u8) -> i64 {
+ if (!match_in(url, 0, url.len, origin)) { return NO_TEXT; }
+ var e : usize = origin.len;
+ while (e < url.len and url[e] != B_QUERY and url[e] != B_HASH) { e = e + 1; }
+ return pack(origin.len, e);
+}
+
+/// framed: a path that ends in a slash names a directory and gets index.html appended.
+pub fn is_dir(path: []const u8, lo: usize, hi: usize) -> bool {
+ return hi > lo and path[hi - 1] == B_SLASH;
+}
+
+/// The cast's exit codes: true when exit_codes holds anything but the number 0. A token is a run of
+/// bytes between spaces, commas and quotes; "exit_codes" absent or empty is clean.
+pub fn bad_exit(json: []const u8) -> bool {
+ const k : i64 = find_from(json, 0, "exit_codes");
+ if (k < 0) { return false; }
+ var p : usize = (k as usize) + 10;
+ while (p < json.len and json[p] != B_OPEN) { p = p + 1; }
+ if (p >= json.len) { return false; }
+ p = p + 1;
+ var len : usize = 0;
+ var zero : bool = true;
+ while (p < json.len and json[p] != B_CLOSE) {
+ const b : u8 = json[p];
+ if (is_space(b) or b == B_COMMA or b == B_QUOTE) {
+ if (len > 0 and (len != 1 or !zero)) { return true; }
+ len = 0;
+ zero = true;
+ } else {
+ if (b != B_ZERO) { zero = false; }
+ len = len + 1;
+ }
+ p = p + 1;
+ }
+ if (len > 0 and (len != 1 or !zero)) { return true; }
+ return false;
+}
+
+/// hudViews: is `view` one of the quoted names in `export const HUD_VIEWS ... = [ ... ] as const`?
+pub fn view_listed(src: []const u8, view: []const u8) -> bool {
+ const k : i64 = find_from(src, 0, "HUD_VIEWS");
+ if (k < 0) { return false; }
+ const eq : i64 = find_from(src, (k as usize) + 9, "=");
+ if (eq < 0) { return false; }
+ const open : i64 = find_from(src, (eq as usize) + 1, "[");
+ if (open < 0) { return false; }
+ var close : i64 = find_from(src, (open as usize) + 1, "]");
+ var ok : bool = false;
+ while (close >= 0 and !ok) {
+ const a : usize = skip_space(src, (close as usize) + 1);
+ if (match_in(src, a, src.len, "as const")) { ok = true; } else { close = find_from(src, (close as usize) + 1, "]"); }
+ }
+ if (!ok) { return false; }
+ var p : usize = (open as usize) + 1;
+ while (p < (close as usize)) {
+ if (src[p] == B_QUOTE) {
+ const e : i64 = next_quote(src, p + 1);
+ if (e < 0 or (e as usize) > (close as usize)) { return false; }
+ if (e as usize - p - 1 == view.len and match_in(src, p + 1, e as usize, view)) { return true; }
+ p = (e as usize) + 1;
+ } else {
+ p = p + 1;
+ }
+ }
+ return false;
+}
+
+/// The decision about one widget: the generator's problems list as gallery_rules' verdict. The glue
+/// answers what only a disk knows -- page_ok (the index.html exists), image_file_ok (the og:image is
+/// a file under public/), player_file_ok (the twitter:player is a page under public/), cast_meta
+/// (meta.json exists), view_ok (hudViews lists the tab's view) -- and passes the bytes.
+pub fn judge(kind: u8, html: []const u8, cast_json: []const u8, origin: []const u8, page_ok: bool, image_file_ok: bool, player_file_ok: bool, cast_meta: bool, view_ok: bool) -> u8 {
+ const img : i64 = meta_value(html, "og:image");
+ var image_local : bool = false;
+ if (img >= 0) {
+ image_local = local_of(html[lo_of(img) .. hi_of(img)], origin) >= 0 and image_file_ok;
+ }
+ const flags : u32 = page_flags(html);
+ const hook_ok : bool = (flags / F_HOOK) % 2 == 1;
+ return verdict(kind, view_ok, page_ok, words_ok(html), image_local, cast_meta, bad_exit(cast_json), hook_ok, player_file_ok);
+}
+
+test a_meta_value_is_the_content_of_the_last_matching_tag {
+ const h : []const u8 = "";
+ const v : i64 = meta_value(h, "og:title");
+ assert v >= 0;
+ assert hi_of(v) - lo_of(v) == 6;
+ assert h[lo_of(v)] == 83;
+ assert meta_value(h, "og:image") == NO_TEXT;
+ assert hi_of(meta_value(h, "description")) - lo_of(meta_value(h, "description")) == 1;
+}
+
+test a_meta_tag_must_be_spelled_the_way_the_generator_matches {
+ assert meta_value("", "og:title") >= 0;
+ assert meta_value("", "og:title") >= 0;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") == NO_TEXT;
+ assert meta_value("", "og:title") >= 0;
+ assert meta_value("", "og:title") == NO_TEXT;
+}
+
+test the_page_flags_say_which_tags_a_share_page_has {
+ const p : []const u8 = "";
+ assert page_flags(p) == F_TITLE + F_LINE + F_IMAGE + F_HOOK;
+ assert words_ok(p);
+ assert page_flags("") == F_PLAYER;
+ assert words_ok("") == false;
+ assert words_ok("") == false;
+}
+
+test entities_unescape_in_one_pass {
+ const s : []const u8 = "a & b <c> "d" it's 'e' & &bogus;";
+ assert text_len(s, 0, s.len) == 36;
+ assert text_byte(s, 0, s.len, 2) == 38;
+ assert text_byte(s, 0, s.len, 6) == 60;
+ assert text_byte(s, 0, s.len, 16) == 39;
+ assert text_byte(s, 0, s.len, 29) == 38;
+ assert text_byte(s, 0, s.len, 30) == 98;
+ assert text_byte(s, 0, s.len, 23) == 38;
+ assert text_byte(s, 0, s.len, 24) == 97;
+ assert text_byte(s, 0, s.len, 999) == 0;
+ assert text_len("plain", 0, 5) == 5;
+ assert text_len("&", 0, 4) == 4;
+ assert text_len("", 0, 0) == 0;
+}
+
+test an_address_is_local_when_it_sits_under_the_origin {
+ const u : []const u8 = "https://t27.ai/widgets/gatle/card.png?v=9e7908d83118";
+ const r : i64 = local_of(u, "https://t27.ai/");
+ assert r >= 0;
+ assert lo_of(r) == 15;
+ assert hi_of(r) == 37;
+ assert local_of("https://other.example/x.png", "https://t27.ai/") == NO_TEXT;
+ assert hi_of(local_of("https://t27.ai/term/a/#frag", "https://t27.ai/")) == 22;
+ assert lo_of(local_of("https://t27.ai/", "https://t27.ai/")) == 15;
+ assert hi_of(local_of("https://t27.ai/", "https://t27.ai/")) == 15;
+ assert is_dir("term/x7-board/", 0, 14);
+ assert is_dir("widgets/gatle/card.png", 0, 22) == false;
+ assert is_dir("", 0, 0) == false;
+}
+
+test a_cast_is_bad_when_any_exit_code_is_not_zero {
+ assert bad_exit("{ \"exit_codes\": [ 0, 0, 0 ], \"gif\": true }") == false;
+ assert bad_exit("{ \"exit_codes\": [0] }") == false;
+ assert bad_exit("{ \"exit_codes\": [0, 1] }");
+ assert bad_exit("{ \"exit_codes\": [ 2 ] }");
+ assert bad_exit("{ \"exit_codes\": [ 00 ] }");
+ assert bad_exit("{ \"exit_codes\": [ 10 ] }");
+ assert bad_exit("{ \"exit_codes\": [ \"0\", \"0\" ] }") == false;
+ assert bad_exit("{ \"exit_codes\": [] }") == false;
+ assert bad_exit("{ \"gif\": true }") == false;
+ assert bad_exit("") == false;
+}
+
+test the_tab_view_must_be_listed_in_the_hud {
+ const src : []const u8 = "export const HUD_VIEWS: readonly string[] = [ \"board\", \"widgets\", \"wars\" ] as const";
+ assert view_listed(src, "widgets");
+ assert view_listed(src, "board");
+ assert view_listed(src, "wars");
+ assert view_listed(src, "widget") == false;
+ assert view_listed(src, "nope") == false;
+ assert view_listed("const X = [ \"widgets\" ]", "widgets") == false;
+ assert view_listed("", "widgets") == false;
+}
+
+test judge_turns_what_was_read_into_gallery_rules_verdicts {
+ const page : []const u8 = "";
+ const origin : []const u8 = "https://t27.ai/";
+ assert judge(K_TOOL, page, "", origin, true, true, false, false, false) == V_OK;
+ assert judge(K_TOOL, page, "", origin, false, true, false, false, false) != V_OK;
+ assert judge(K_TOOL, page, "", origin, true, false, false, false, false) != V_OK;
+ assert judge(K_TOOL, "", "", origin, true, true, false, false, false) != V_OK;
+ assert judge(K_CAST, page, "{ \"exit_codes\": [0, 0] }", origin, true, true, false, true, false) == V_OK;
+ assert judge(K_CAST, page, "{ \"exit_codes\": [0, 1] }", origin, true, true, false, true, false) != V_OK;
+ assert judge(K_CAST, page, "{ \"exit_codes\": [0] }", origin, true, true, false, false, false) != V_OK;
+}
From def022a40db7a354a1900672a2be86934f789d9f Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 15:18:48 +0700
Subject: [PATCH 05/63] widgets: the logic stage runs through wasm --
gatle_rule built with gen-c and zig, 866178 comparisons against rule.js, 0
mismatches (Closes #10938)
gen-js lowers no fn bodies (#10943), but the path the Queen's app.wasm takes works: 490 bytes.
Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/t27f/widget_factory.t27 | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27
index 934d3a159f..6ffefd4b06 100644
--- a/specs/t27f/widget_factory.t27
+++ b/specs/t27f/widget_factory.t27
@@ -10,7 +10,7 @@
; spec the widget's .t27 (words, rules, tests, data sources). The hand column of the catalog is
; everything that is NOT this stage.
; logic the pure functions of the tool (parse, grade, layout), written in .t27 and generated to
-; JavaScript by gen-ts. The spec's own test blocks are the fixed answers; a data builder no
+; wasm (t27c gen-c, then zig cc --target=wasm32-wasi), not JavaScript: gen-js lowers no bodies. The spec's own test blocks are the fixed answers; a data builder no
; longer re-imports a hand file "so the page cannot drift from the spec".
; plan what is drawn, as a ui_plan.t27 plan: widget ids from an allowlist, no markup, no script.
; The primitives below extend ui_plan's catalog; they are not a second catalog.
@@ -30,7 +30,7 @@
; hand-written lines do not grow. (c) is the same rule as t27b's "net <= 0" and FROZEN_HASH ports.
; --
; Not claimed: no stage past `spec` exists as a program today. gatle's rules are ported to
-; specs/widgets/gatle_rule.t27 and pass, but gen-js/gen-ts do not lower fn bodies, so no JS is generated; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
+; specs/widgets/gatle_rule.t27 and built to wasm (0 mismatches over 866178 comparisons against rule.js), but the widget page still loads the hand-written rule.js: the trinity side is not changed; no plan is drawn by a renderer; t27b has not compiled a data builder. The primitives are
; PROPOSED from reading the 28 tools by name, not measured; wave 1 (catalog.t27, 11 tools of at
; most 700 hand lines) measures them and may rename or merge them.
; phi^2 + 1/phi^2 = 3 | TRINITY
@@ -83,14 +83,18 @@ pub const CLAW_TOOL_COUNT : u32 = 3;
pub const CLAW_TOOLS : [3]str = ["t27f_widgets", "t27f_widget_make", "t27f_widget_port_plan"];
/// MEASURED 2026-10-19 with t27c 0.5.2 on specs/widgets/gatle_rule.t27: gen-js and gen-ts emit
-/// declarations (consts, structs) and skip every fn body -- "this backend lowers declarations, not
-/// bodies". Until a body-lowering JS backend exists the logic stage writes a tested .t27 rule
-/// (the single source) but cannot generate the JavaScript the page runs. Flip this when it can.
+/// declarations only and skip every fn body (#10943), so the logic stage does not go through them.
+/// It goes through the path the Queen's app.wasm already takes: `t27c gen-c`, then
+/// `zig cc --target=wasm32-wasi -Oz -mexec-model=reactor` with one -Wl,--export per function.
+/// gatle_rule.t27 became a 490-byte wasm; 866178 comparisons against the hand-written rule.js
+/// (random and boundary inputs, every function) gave 0 mismatches. Byte slices and out buffers pass
+/// through the module's linear memory; module-level `var` arrays and `[]u8` out parameters work.
+pub const LOGIC_VIA_WASM : bool = true;
pub const GEN_JS_LOWERS_BODIES : bool = false;
-/// The logic stage can produce its artefact only when the backend lowers bodies.
+/// The logic stage can produce its artefact when some route lowers bodies.
pub fn logic_stage_ready() -> bool {
- return GEN_JS_LOWERS_BODIES;
+ return LOGIC_VIA_WASM or GEN_JS_LOWERS_BODIES;
}
/// A stage is the next of a port stage only by one step.
@@ -224,8 +228,10 @@ test every_claw_tool_is_a_t27_name_the_service_mounts {
assert TRI_VERBS[0] == "widget list";
}
-test the_logic_stage_waits_for_a_body_lowering_backend {
- assert logic_stage_ready() == false;
+test the_logic_stage_runs_through_wasm_while_gen_js_lowers_no_bodies {
+ assert logic_stage_ready();
+ assert LOGIC_VIA_WASM;
+ assert GEN_JS_LOWERS_BODIES == false;
}
test the_logic_stage_removes_exactly_the_logic_file {
From d4d8ccd60095f2f276aa44627d23261e530681cf Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 15:21:31 +0700
Subject: [PATCH 06/63] widgets: html_out -- the one HTML writer for native
widget views, bounded buffer, escaping, en-US grouped numbers; 4 of 4 under
t27c and t27b (Closes #10938)
Stage "page" of the widget factory: a view writes HTML into a fixed buffer and the host sets innerHTML.
Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/html_out.t27 | 243 +++++++++++++++++++++++++++++++++++++
1 file changed, 243 insertions(+)
create mode 100644 specs/widgets/html_out.t27
diff --git a/specs/widgets/html_out.t27 b/specs/widgets/html_out.t27
new file mode 100644
index 0000000000..d1a05d1c5d
--- /dev/null
+++ b/specs/widgets/html_out.t27
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/html_out.t27 -- the HTML writer every native widget view uses. Epic gHashTag/t27#10937,
+; stage "page" of specs/t27f/widget_factory.t27. A tool.js builds its page with el()/h() helpers and
+; DOM calls (28 files, 8920 lines); a native view writes HTML text into one module-level buffer and the
+; host sets it as innerHTML. This file is that writer, once: reset, put a byte, put text (raw or
+; HTML-escaped), put a number, open/close a tag, put an attribute. No allocation: the buffer is fixed
+; at OUT_CAP and a write past the end sets OVERFLOW and drops the byte, so a view can never write
+; outside it. The host reads out_len() bytes starting at out_at(0..).
+; ASCII only (L3), English only (LANG-EN).
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_html_out;
+
+pub const OUT_CAP : u32 = 65536;
+pub var OUT : [65536]u8 = [0; 65536];
+pub var OUT_LEN : u32 = 0;
+pub var OVERFLOW : u32 = 0;
+
+pub const B_LT : u8 = 60;
+pub const B_GT : u8 = 62;
+pub const B_AMP : u8 = 38;
+pub const B_QUOTE : u8 = 34;
+pub const B_APOS : u8 = 39;
+pub const B_SPACE : u8 = 32;
+pub const B_SLASH : u8 = 47;
+pub const B_EQ : u8 = 61;
+
+pub fn out_reset() -> u32 {
+ OUT_LEN = 0;
+ OVERFLOW = 0;
+ return 0;
+}
+
+pub fn out_len() -> u32 {
+ return OUT_LEN;
+}
+
+pub fn out_at(i: u32) -> u8 {
+ return OUT[i as usize];
+}
+
+pub fn out_overflowed() -> bool {
+ return OVERFLOW != 0;
+}
+
+pub fn put(b: u8) -> u32 {
+ if (OUT_LEN >= OUT_CAP) {
+ OVERFLOW = 1;
+ return OUT_LEN;
+ }
+ OUT[OUT_LEN as usize] = b;
+ OUT_LEN = OUT_LEN + 1;
+ return OUT_LEN;
+}
+
+/// Raw bytes, as the caller wrote them (markup the view itself owns).
+pub fn raw(s: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < s.len) {
+ put(s[i]);
+ i = i + 1;
+ }
+ return OUT_LEN;
+}
+
+/// Text from outside the view (data, words): & < > " ' are written as entities.
+pub fn text(s: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < s.len) {
+ const b : u8 = s[i];
+ if (b == B_AMP) {
+ raw("&");
+ } else {
+ if (b == B_LT) {
+ raw("<");
+ } else {
+ if (b == B_GT) {
+ raw(">");
+ } else {
+ if (b == B_QUOTE) {
+ raw(""");
+ } else {
+ if (b == B_APOS) {
+ raw("'");
+ } else {
+ put(b);
+ }
+ }
+ }
+ }
+ }
+ i = i + 1;
+ }
+ return OUT_LEN;
+}
+
+/// An unsigned decimal.
+pub fn num(n: u64) -> u32 {
+ if (n == 0) {
+ return put(48);
+ }
+ var digits : [20]u8 = [0; 20];
+ var k : usize = 0;
+ var v : u64 = n;
+ while (v > 0) {
+ digits[k] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ k = k + 1;
+ }
+ while (k > 0) {
+ k = k - 1;
+ put(digits[k]);
+ }
+ return OUT_LEN;
+}
+
+/// An unsigned decimal with thousands separated by commas, as toLocaleString('en-US') writes it.
+pub fn num_grouped(n: u64) -> u32 {
+ var digits : [20]u8 = [0; 20];
+ var k : usize = 0;
+ var v : u64 = n;
+ if (v == 0) {
+ return put(48);
+ }
+ while (v > 0) {
+ digits[k] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ k = k + 1;
+ }
+ while (k > 0) {
+ k = k - 1;
+ put(digits[k]);
+ if (k > 0 and k % 3 == 0) {
+ put(44);
+ }
+ }
+ return OUT_LEN;
+}
+
+/// u32 {
+ put(B_LT);
+ return raw(tag);
+}
+
+pub fn open_end() -> u32 {
+ return put(B_GT);
+}
+
+/// name="value" with the value escaped, preceded by a space.
+pub fn attr(name: []const u8, value: []const u8) -> u32 {
+ put(B_SPACE);
+ raw(name);
+ put(B_EQ);
+ put(B_QUOTE);
+ text(value);
+ return put(B_QUOTE);
+}
+
+pub fn close(tag: []const u8) -> u32 {
+ put(B_LT);
+ put(B_SLASH);
+ raw(tag);
+ return put(B_GT);
+}
+
+/// {0} {1} ... filled from up to three values, as fmt() does in the tool.js files. A placeholder with
+/// no value writes nothing. The template is the view's own words, written raw; each value is escaped.
+pub fn fill3(t: []const u8, a: []const u8, b: []const u8, c: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] <= 50) {
+ if (t[i + 1] == 48) { text(a); }
+ if (t[i + 1] == 49) { text(b); }
+ if (t[i + 1] == 50) { text(c); }
+ i = i + 3;
+ } else {
+ put(t[i]);
+ i = i + 1;
+ }
+ }
+ return OUT_LEN;
+}
+
+pub fn same_bytes(a: u32, b: []const u8) -> bool {
+ var i : usize = 0;
+ if (OUT_LEN != (b.len as u32) + a) { return false; }
+ while (i < b.len) {
+ if (OUT[(a as usize) + i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+test the_writer_escapes_text_and_leaves_markup_alone {
+ assert out_reset() == 0;
+ open("p");
+ attr("title", "a \"b\" & c");
+ open_end();
+ text("x < y & 'z'");
+ close("p");
+ assert same_bytes(0, "
x < y & 'z'
");
+ assert out_overflowed() == false;
+}
+
+test numbers_are_decimal_and_grouped_like_en_us {
+ out_reset();
+ num(0);
+ put(32);
+ num(1234567);
+ put(32);
+ num_grouped(0);
+ put(32);
+ num_grouped(999);
+ put(32);
+ num_grouped(1000);
+ put(32);
+ num_grouped(215360);
+ put(32);
+ num_grouped(1234567890);
+ assert same_bytes(0, "0 1234567 0 999 1,000 215,360 1,234,567,890");
+}
+
+test fill_puts_values_in_the_template_and_escapes_them {
+ out_reset();
+ fill3("{0} of {1}, {2}{3}", "Gatle", "<6>", "end");
+ assert same_bytes(0, "Gatle of <6>, end{3}");
+}
+
+test the_buffer_never_writes_past_its_end {
+ out_reset();
+ var i : u32 = 0;
+ while (i < OUT_CAP + 10) {
+ put(65);
+ i = i + 1;
+ }
+ assert out_len() == OUT_CAP;
+ assert out_overflowed();
+ assert out_at(OUT_CAP - 1) == 65;
+ out_reset();
+ assert out_len() == 0;
+ assert out_overflowed() == false;
+}
From 45af0f32afe9df39b54322f9ea38dfa998f47374 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 15:48:08 +0700
Subject: [PATCH 07/63] widgets: jtag-verdict is the first widget drawn
natively -- words, rules and page in one .t27, a 9269-byte wasm, 3015 DOM
comparisons against tool.js, 0 differences (Closes #10938)
specs/widgets/jtag_verdict.t27 holds the trinity spec's words and a native page (w_init, w_input,
w_click, w_hash, w_render, w_hash_out); html_out.t27 becomes a stateless writer (a pub var does not
cross a use). catalog.t27 records gatle at PORT_LOGIC and jtag-verdict at PORT_DRAW. Epic #10937.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/catalog.t27 | 22 +-
specs/widgets/html_out.t27 | 285 ++++++--------
specs/widgets/jtag_verdict.t27 | 689 +++++++++++++++++++++++++++++++++
3 files changed, 824 insertions(+), 172 deletions(-)
create mode 100644 specs/widgets/jtag_verdict.t27
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index 35f526116f..f74c118276 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -31,9 +31,13 @@
; 3. 23 tools have a Node data builder (5572 lines) and 24 a Python card renderer (3415 lines).
; The card is a picture of the page; t27f draws pictures from specs already (SF_STILL).
; --
-; Not claimed: that any hand column has been rewritten. Every tool is at PORT_WORDS today: its
-; words live in its spec, its logic and drawing still live in JavaScript. A tool moves up only when
-; its output is byte-identical to the hand version, and the factory spec states that gate.
+; Stages reached, each with a differential against the hand-written version on the trinity side:
+; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
+; and jtag-verdict at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM comparisons vs tool.js,
+; 0 mismatches). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
+; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
+; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
+; its output is identical to the hand version, and the factory spec states that gate.
; The Swift and React widgets are counted, not classified: they are another renderer's targets.
; ASCII only (L3), English only (LANG-EN).
; phi^2 + 1/phi^2 = 3 | TRINITY
@@ -83,7 +87,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+pub const TOOL_PORT : [28]u8 = [1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -219,9 +223,11 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test no_tool_has_left_the_words_stage_yet {
- assert count_port(PORT_WORDS) == TOOL_COUNT;
- assert count_port(PORT_LOGIC) == 0;
- assert count_port(PORT_DRAW) == 0;
+test two_tools_have_left_the_words_stage_with_a_differential {
+ assert count_port(PORT_WORDS) == TOOL_COUNT - 2;
+ assert count_port(PORT_LOGIC) == 1;
+ assert count_port(PORT_DRAW) == 1;
assert count_port(PORT_SEALED) == 0;
+ assert TOOL_PORT[7] == PORT_LOGIC;
+ assert TOOL_PORT[10] == PORT_DRAW;
}
diff --git a/specs/widgets/html_out.t27 b/specs/widgets/html_out.t27
index d1a05d1c5d..2aff837aa0 100644
--- a/specs/widgets/html_out.t27
+++ b/specs/widgets/html_out.t27
@@ -1,107 +1,82 @@
// SPDX-License-Identifier: Apache-2.0
; specs/widgets/html_out.t27 -- the HTML writer every native widget view uses. Epic gHashTag/t27#10937,
; stage "page" of specs/t27f/widget_factory.t27. A tool.js builds its page with el()/h() helpers and
-; DOM calls (28 files, 8920 lines); a native view writes HTML text into one module-level buffer and the
-; host sets it as innerHTML. This file is that writer, once: reset, put a byte, put text (raw or
-; HTML-escaped), put a number, open/close a tag, put an attribute. No allocation: the buffer is fixed
-; at OUT_CAP and a write past the end sets OVERFLOW and drops the byte, so a view can never write
-; outside it. The host reads out_len() bytes starting at out_at(0..).
+; DOM calls (28 files, 8920 lines); a native view writes HTML text into the buffer the host gave it
+; (a region of the wasm's linear memory) and the host sets it as innerHTML. This file is that writer,
+; once: put a byte, put text (raw or HTML-escaped), put a number, put an attribute, fill a template.
+; --
+; STATELESS ON PURPOSE. Every function takes the buffer o and the length written so far n and returns
+; the new length. There is no module-level state because t27c's splicer does not carry a `pub var`
+; across `use` (measured: a view that imported OUT and OUT_LEN got "undeclared identifier"), and
+; follows only the names a module itself mentions, not the names an imported function mentions: so a
+; view imports every function it calls, and these functions call only each other through the
+; ANCHOR in the view (see jtag_verdict.t27, html_out_anchor). A write past the end of o returns OVER,
+; and every function returns OVER at once when it is given OVER, so a view can never write outside o
+; and checks once, at the end, with ok(n).
; ASCII only (L3), English only (LANG-EN).
; phi^2 + 1/phi^2 = 3 | TRINITY
module widgets_html_out;
-pub const OUT_CAP : u32 = 65536;
-pub var OUT : [65536]u8 = [0; 65536];
-pub var OUT_LEN : u32 = 0;
-pub var OVERFLOW : u32 = 0;
-
-pub const B_LT : u8 = 60;
-pub const B_GT : u8 = 62;
-pub const B_AMP : u8 = 38;
-pub const B_QUOTE : u8 = 34;
-pub const B_APOS : u8 = 39;
-pub const B_SPACE : u8 = 32;
-pub const B_SLASH : u8 = 47;
-pub const B_EQ : u8 = 61;
-
-pub fn out_reset() -> u32 {
- OUT_LEN = 0;
- OVERFLOW = 0;
- return 0;
-}
-
-pub fn out_len() -> u32 {
- return OUT_LEN;
-}
+pub const OVER : u32 = 4294967295;
+pub const H_LT : u8 = 60;
+pub const H_GT : u8 = 62;
+pub const H_AMP : u8 = 38;
+pub const H_QUOTE : u8 = 34;
+pub const H_APOS : u8 = 39;
+pub const H_SPACE : u8 = 32;
+pub const H_SLASH : u8 = 47;
+pub const H_EQ : u8 = 61;
-pub fn out_at(i: u32) -> u8 {
- return OUT[i as usize];
+pub fn ok(n: u32) -> bool {
+ return n != OVER;
}
-pub fn out_overflowed() -> bool {
- return OVERFLOW != 0;
-}
-
-pub fn put(b: u8) -> u32 {
- if (OUT_LEN >= OUT_CAP) {
- OVERFLOW = 1;
- return OUT_LEN;
- }
- OUT[OUT_LEN as usize] = b;
- OUT_LEN = OUT_LEN + 1;
- return OUT_LEN;
+pub fn put(o: []u8, n: u32, b: u8) -> u32 {
+ if (n >= (o.len as u32)) { return OVER; }
+ o[n as usize] = b;
+ return n + 1;
}
/// Raw bytes, as the caller wrote them (markup the view itself owns).
-pub fn raw(s: []const u8) -> u32 {
+pub fn raw(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
var i : usize = 0;
while (i < s.len) {
- put(s[i]);
+ n = put(o, n, s[i]);
i = i + 1;
}
- return OUT_LEN;
+ return n;
}
-/// Text from outside the view (data, words): & < > " ' are written as entities.
-pub fn text(s: []const u8) -> u32 {
+/// One byte of text, escaped: & < > " ' become entities.
+pub fn put_escaped(o: []u8, n: u32, b: u8) -> u32 {
+ if (b == H_AMP) { return raw(o, n, "&"); }
+ if (b == H_LT) { return raw(o, n, "<"); }
+ if (b == H_GT) { return raw(o, n, ">"); }
+ if (b == H_QUOTE) { return raw(o, n, """); }
+ if (b == H_APOS) { return raw(o, n, "'"); }
+ return put(o, n, b);
+}
+
+/// Text from outside the view (data, words).
+pub fn text(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
var i : usize = 0;
while (i < s.len) {
- const b : u8 = s[i];
- if (b == B_AMP) {
- raw("&");
- } else {
- if (b == B_LT) {
- raw("<");
- } else {
- if (b == B_GT) {
- raw(">");
- } else {
- if (b == B_QUOTE) {
- raw(""");
- } else {
- if (b == B_APOS) {
- raw("'");
- } else {
- put(b);
- }
- }
- }
- }
- }
+ n = put_escaped(o, n, s[i]);
i = i + 1;
}
- return OUT_LEN;
+ return n;
}
/// An unsigned decimal.
-pub fn num(n: u64) -> u32 {
- if (n == 0) {
- return put(48);
- }
+pub fn num(o: []u8, n0: u32, v0: u64) -> u32 {
+ var n : u32 = n0;
+ if (v0 == 0) { return put(o, n, 48); }
var digits : [20]u8 = [0; 20];
var k : usize = 0;
- var v : u64 = n;
+ var v : u64 = v0;
while (v > 0) {
digits[k] = ((v % 10) as u8) + 48;
v = v / 10;
@@ -109,19 +84,18 @@ pub fn num(n: u64) -> u32 {
}
while (k > 0) {
k = k - 1;
- put(digits[k]);
+ n = put(o, n, digits[k]);
}
- return OUT_LEN;
+ return n;
}
/// An unsigned decimal with thousands separated by commas, as toLocaleString('en-US') writes it.
-pub fn num_grouped(n: u64) -> u32 {
+pub fn num_grouped(o: []u8, n0: u32, v0: u64) -> u32 {
+ var n : u32 = n0;
+ if (v0 == 0) { return put(o, n, 48); }
var digits : [20]u8 = [0; 20];
var k : usize = 0;
- var v : u64 = n;
- if (v == 0) {
- return put(48);
- }
+ var v : u64 = v0;
while (v > 0) {
digits[k] = ((v % 10) as u8) + 48;
v = v / 10;
@@ -129,115 +103,98 @@ pub fn num_grouped(n: u64) -> u32 {
}
while (k > 0) {
k = k - 1;
- put(digits[k]);
- if (k > 0 and k % 3 == 0) {
- put(44);
- }
+ n = put(o, n, digits[k]);
+ if (k > 0 and k % 3 == 0) { n = put(o, n, 44); }
}
- return OUT_LEN;
-}
-
-/// u32 {
- put(B_LT);
- return raw(tag);
-}
-
-pub fn open_end() -> u32 {
- return put(B_GT);
+ return n;
}
/// name="value" with the value escaped, preceded by a space.
-pub fn attr(name: []const u8, value: []const u8) -> u32 {
- put(B_SPACE);
- raw(name);
- put(B_EQ);
- put(B_QUOTE);
- text(value);
- return put(B_QUOTE);
-}
-
-pub fn close(tag: []const u8) -> u32 {
- put(B_LT);
- put(B_SLASH);
- raw(tag);
- return put(B_GT);
-}
-
-/// {0} {1} ... filled from up to three values, as fmt() does in the tool.js files. A placeholder with
-/// no value writes nothing. The template is the view's own words, written raw; each value is escaped.
-pub fn fill3(t: []const u8, a: []const u8, b: []const u8, c: []const u8) -> u32 {
+pub fn attr(o: []u8, n0: u32, name: []const u8, value: []const u8) -> u32 {
+ var n : u32 = put(o, n0, H_SPACE);
+ n = raw(o, n, name);
+ n = put(o, n, H_EQ);
+ n = put(o, n, H_QUOTE);
+ n = text(o, n, value);
+ return put(o, n, H_QUOTE);
+}
+
+/// {0} {1} {2} filled from k values (k up to 3), as fill() does in the tool.js files: a placeholder
+/// whose index is not below k stays as written. The template and each value are escaped as text.
+pub fn fill(o: []u8, n0: u32, t: []const u8, a: []const u8, b: []const u8, c: []const u8, k: u32) -> u32 {
+ var n : u32 = n0;
var i : usize = 0;
while (i < t.len) {
- if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] <= 50) {
- if (t[i + 1] == 48) { text(a); }
- if (t[i + 1] == 49) { text(b); }
- if (t[i + 1] == 50) { text(c); }
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] < 48 + (k as u8) and t[i + 1] <= 50) {
+ if (t[i + 1] == 48) { n = text(o, n, a); }
+ if (t[i + 1] == 49) { n = text(o, n, b); }
+ if (t[i + 1] == 50) { n = text(o, n, c); }
i = i + 3;
} else {
- put(t[i]);
+ n = put_escaped(o, n, t[i]);
i = i + 1;
}
}
- return OUT_LEN;
+ return n;
}
-pub fn same_bytes(a: u32, b: []const u8) -> bool {
+pub fn same_bytes(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (n != (want.len as u32)) { return false; }
var i : usize = 0;
- if (OUT_LEN != (b.len as u32) + a) { return false; }
- while (i < b.len) {
- if (OUT[(a as usize) + i] != b[i]) { return false; }
+ while (i < want.len) {
+ if (o[i] != want[i]) { return false; }
i = i + 1;
}
return true;
}
test the_writer_escapes_text_and_leaves_markup_alone {
- assert out_reset() == 0;
- open("p");
- attr("title", "a \"b\" & c");
- open_end();
- text("x < y & 'z'");
- close("p");
- assert same_bytes(0, "
x < y & 'z'
");
- assert out_overflowed() == false;
+ var o : [128]u8 = [0; 128];
+ var n : u32 = raw(o[0..], 0, "
");
+ n = text(o[0..], n, "x < y & 'z'");
+ n = raw(o[0..], n, "
");
+ assert same_bytes(o[0..], n, "
x < y & 'z'
");
+ assert ok(n);
}
test numbers_are_decimal_and_grouped_like_en_us {
- out_reset();
- num(0);
- put(32);
- num(1234567);
- put(32);
- num_grouped(0);
- put(32);
- num_grouped(999);
- put(32);
- num_grouped(1000);
- put(32);
- num_grouped(215360);
- put(32);
- num_grouped(1234567890);
- assert same_bytes(0, "0 1234567 0 999 1,000 215,360 1,234,567,890");
+ var o : [128]u8 = [0; 128];
+ var n : u32 = num(o[0..], 0, 0);
+ n = put(o[0..], n, 32);
+ n = num(o[0..], n, 1234567);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 0);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 999);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 1000);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 215360);
+ n = put(o[0..], n, 32);
+ n = num_grouped(o[0..], n, 1234567890);
+ assert same_bytes(o[0..], n, "0 1234567 0 999 1,000 215,360 1,234,567,890");
}
test fill_puts_values_in_the_template_and_escapes_them {
- out_reset();
- fill3("{0} of {1}, {2}{3}", "Gatle", "<6>", "end");
- assert same_bytes(0, "Gatle of <6>, end{3}");
+ var o : [128]u8 = [0; 128];
+ var n : u32 = fill(o[0..], 0, "{0} of {1}, {2}{3}", "Gatle", "<6>", "end", 3);
+ assert same_bytes(o[0..], n, "Gatle of <6>, end{3}");
+ n = fill(o[0..], 0, "{0}{1} it's", "x", "", "", 1);
+ assert same_bytes(o[0..], n, "x{1} it's");
}
test the_buffer_never_writes_past_its_end {
- out_reset();
- var i : u32 = 0;
- while (i < OUT_CAP + 10) {
- put(65);
- i = i + 1;
- }
- assert out_len() == OUT_CAP;
- assert out_overflowed();
- assert out_at(OUT_CAP - 1) == 65;
- out_reset();
- assert out_len() == 0;
- assert out_overflowed() == false;
+ var o : [8]u8 = [0; 8];
+ var n : u32 = raw(o[0..], 0, "12345678");
+ assert ok(n);
+ assert n == 8;
+ n = raw(o[0..], n, "9");
+ assert ok(n) == false;
+ n = text(o[0..], n, "more");
+ assert n == OVER;
+ assert o[7] == 56;
+ var p : [4]u8 = [0; 4];
+ assert text(p[0..], 0, "<<") == OVER;
}
diff --git a/specs/widgets/jtag_verdict.t27 b/specs/widgets/jtag_verdict.t27
new file mode 100644
index 0000000000..144177d227
--- /dev/null
+++ b/specs/widgets/jtag_verdict.t27
@@ -0,0 +1,689 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/jtag-verdict.t27 -- one IR capture and one DR word, and what they say about the link
+; Source of truth for public/widgets/jtag-verdict/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader types the raw IR capture and the raw DR word a JTAG probe shifted out
+; (or picks one of the seven vectors below). The page runs the same five checks, in the same order,
+; as t27 specs/port/tools/jtag/tdo_verdict.t27 and `tri fpga-jtag --decode HEX --ir HEX`: all ones,
+; all zeros, the IR capture rule, bit 0 of the DR word, and only then the IDCODE itself. It shows
+; which check decided, the bits it looked at, and the command that prints the same verdict.
+
+; THE RULE: IEEE 1149.1 Capture-IR loads binary 01 into the two least significant IR cells, so
+; the first two bits out of TDO after an IR scan are 1 then 0 on every compliant TAP. Above them
+; the device may put anything: Xilinx 7-series BSDL files give INSTRUCTION_CAPTURE as XXXX01, the
+; upper four being status. 0x35 (110101) is one value of that shape and is used here as the
+; example; it is not a bench reading. A DR word with bit 0 clear is BYPASS when the IR capture
+; holds 01, and noise when it does not: the same even word gets two different verdicts.
+
+; DATA PROVENANCE: the seven vectors are the test vectors of t27 specs/port/tools/jtag/
+; tdo_verdict.t27 at commit 60166a56194b5730dea355e704e98bc4f540cf14 (gHashTag/t27#8032, issue
+; #8030), and the tests below assert the same verdicts. 0x13636093 is the raw IDCODE read from our
+; XC7A200T on 2026-10-04 (openFPGALoader 1.1.1 --detect --verbose-level 2, see the idcode widget);
+; 0x03631093 is the XC7A100T entry of openFPGALoader v1.1.1 src/part.hpp.
+
+; WHAT IT DOES NOT CLAIM: that it talks to a cable. Nothing is read from hardware; the values are
+; typed. A clean verdict says the shift looks sane, not that the board is configured or healthy.
+; The device table has two entries; the idcode widget carries openFPGALoader's full list.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_jtag_verdict;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::page_reader::match_in;
+
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "jtag-verdict";
+pub const TITLE : str = "JTAG verdict: IR capture and DR word, BYPASS or a broken shift";
+pub const DESCRIPTION : str = "Type the IR capture and the DR word a JTAG probe shifted out. Five checks in a fixed order say stuck line, broken shift, BYPASS or a real IDCODE, and which bit decided.";
+pub const IMAGE_ALT : str = "JTAG verdict card: the same even DR word 0x13636092 read twice. With IR capture 0x35, whose low two bits are 01, the verdict is BYPASS: the TAP is alive. With IR capture 0x34, low bits 00, the verdict is broken shift: the DR word means nothing yet. Below, the five checks in order, from t27 spec tdo_verdict.t27 with 8 tests.";
+pub const HOOK : str = "Same DR word, two verdicts: IR 0x35 says BYPASS, IR 0x34 says your shift is broken. Type what your probe read and see which bit decides.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [4]str = ["t27 specs/port/tools/jtag/tdo_verdict.t27 at 60166a56194b5730dea355e704e98bc4f540cf14 (gHashTag/t27#8032, issue #8030): the order of the checks and the seven test vectors", "IEEE Std 1149.1 Capture-IR: binary 01 in the two least significant instruction register cells; Xilinx 7-series BSDL INSTRUCTION_CAPTURE XXXX01", "Bench 2026-10-04 UTC: openFPGALoader 1.1.1 --detect --verbose-level 2 on our XC7A200T: raw IDCODE 0x13636093 (the idcode widget's record)", "openFPGALoader v1.1.1 src/part.hpp: 0x03636093 xc7a200, 0x03631093 xc7a100"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+pub const K_CARD_WIDTH : u16 = 1200;
+pub const K_CARD_HEIGHT : u16 = 630;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+; Capture-IR: the low two bits must read 01.
+pub const K_IR_MASK : u32 = 3;
+pub const K_IR_LOW : u32 = 1;
+; 0xFFFFFFFF: TDO floating high, nothing drives it.
+pub const K_ALL_ONES : u32 = 4294967295;
+; Verdict codes, the same numbers as tdo_verdict.t27; they index SAY_VERDICT_NAMES from 1.
+pub const K_V_STUCK_HIGH : u8 = 1;
+pub const K_V_STUCK_LOW : u8 = 2;
+pub const K_V_BROKEN_SHIFT : u8 = 3;
+pub const K_V_BYPASS : u8 = 4;
+pub const K_V_IDCODE : u8 = 5;
+; IDCODE lookup ignores the version nibble: 0x0FFFFFFF.
+pub const K_VERSION_MASK : u32 = 268435455;
+pub const K_KNOWN_IDCODE : [2]u32 = [56844435, 56823955];
+; The seven vectors: IR capture, DR word, expected verdict.
+pub const K_CASE_IR : [7]u32 = [53, 53, 52, 54, 63, 0, 63];
+pub const K_CASE_DR : [7]u32 = [325279891, 325279890, 325279890, 325279891, 4294967295, 0, 325279891];
+pub const K_CASE_WANT : [7]u8 = [5, 4, 3, 3, 1, 2, 3];
+; The vector the page opens on: the even word behind a good IR capture.
+pub const K_START_CASE : u8 = 1;
+
+; --- Words on screen ------------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_LOADING : str = "Loading";
+pub const SAY_IR_LABEL : str = "IR capture (hex)";
+pub const SAY_DR_LABEL : str = "DR word (hex)";
+pub const SAY_IR_HINT : str = "The bits TDO shifted out during the IR scan, e.g. 0x35";
+pub const SAY_DR_HINT : str = "The 32 bits shifted out of the data register, e.g. 0x13636093";
+pub const SAY_CASES_LABEL : str = "The spec's test vectors";
+pub const SAY_CASE_NAMES : [7]str = ["real IDCODE", "BYPASS", "even word, IR 0x34", "odd word, IR 0x36", "all ones", "all zeros", "real-looking word, IR 0x3F"];
+pub const SAY_VERDICT_NAMES : [5]str = ["stuck high", "stuck low", "broken shift", "BYPASS", "IDCODE"];
+pub const SAY_VERDICT_WHY : [5]str = ["Every DR bit reads 1: nothing drives TDO. Check the cable's target voltage, board power, the ribbon and its pin 1, then the TDO wire.", "DR and IR both read 0: TDO is held low. Suspect the cable's own buffer before the board.", "IR capture {0} ends in {1}, not 01. Capture-IR always loads 01, so the shift itself is broken: TCK, the TMS order, or the TDO path. The DR word means nothing yet.", "IR capture {0} ends in 01, so the TAP shifts correctly, and DR bit 0 is 0: BYPASS is selected, not IDCODE. Load the IDCODE instruction, or this was read one bit late.", "IR capture {0} ends in 01 and DR bit 0 is 1: a real IDCODE."];
+pub const SAY_LADDER_TITLE : str = "The checks, in the order they run";
+pub const SAY_LADDER_STEPS : [5]str = ["DR is all ones", "DR is 0 and IR is 0", "IR does not end in 01", "DR bit 0 is 0", "otherwise"];
+pub const SAY_LADDER_DECIDED : str = "decided here";
+pub const SAY_LADDER_PASSED : str = "passed";
+pub const SAY_LADDER_SKIPPED : str = "not reached";
+pub const SAY_BITS_TITLE : str = "The bits that decide";
+pub const SAY_BITS_IR : str = "IR, low bit first";
+pub const SAY_BITS_DR : str = "DR bit 0";
+pub const SAY_BITS_IR_RULE : str = "must read 1 then 0";
+pub const SAY_DEVICE_KNOWN : str = "Masked IDCODE {0}: {1}";
+pub const SAY_DEVICE_NAMES : [2]str = ["XC7A200T", "XC7A100T"];
+pub const SAY_DEVICE_UNKNOWN : str = "Masked IDCODE {0}: not in this page's two-entry table.";
+pub const SAY_DECODER_LINK : str = "Take the word apart in the IDCODE decoder";
+pub const SAY_CLI_TITLE : str = "The same verdict on the command line";
+pub const SAY_CLI_LINE : str = "tri fpga-jtag --decode {0} --ir {1}";
+pub const SAY_CLI_EXIT : str = "exit {0}";
+pub const SAY_BAD_HEX : str = "Not a hex number that fits in 32 bits: {0}";
+pub const SAY_SELF_CHECK : str = "This page against the spec's {0} vectors: {1} agree.";
+pub const SAY_NOTE : str = "Nothing is read from a cable: the values are typed. 0x35 is an example of the XXXX01 shape, not a bench reading. A clean verdict says the shift looks sane, not that the board is configured.";
+pub const SAY_SPEC_LINK : str = "The spec: tdo_verdict.t27, 8 tests";
+pub const SAY_SPEC_HREF : str = "https://github.com/gHashTag/t27/pull/8032";
+pub const SAY_DECODER_HREF : str = "../idcode/#id={0}";
+; The page keeps the two words in its address: #ir=0x35&dr=0x13636092.
+pub const SAY_HASH_IR : str = "ir";
+pub const SAY_HASH_DR : str = "dr";
+pub const SAY_CARD_TITLE : str = "JTAG verdict";
+pub const SAY_CARD_BRAND : str = "Trinity S3AI";
+pub const SAY_CARD_HEAD : str = "Same DR word, two verdicts";
+pub const SAY_CARD_IR_LINE : str = "IR capture {0}, low bits {1}";
+pub const SAY_CARD_SPEC : str = "t27 spec tdo_verdict.t27, 8 tests";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/jtag-verdict/";
+
+test the_card_is_x_shaped {
+ assert K_CARD_WIDTH == 1200;
+ assert K_CARD_HEIGHT == 630;
+}
+
+test the_page_sends_nothing {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert CATEGORY == "fpga";
+}
+
+test the_vectors_cover_every_verdict {
+ assert K_CASE_WANT[0] == K_V_IDCODE;
+ assert K_CASE_WANT[1] == K_V_BYPASS;
+ assert K_CASE_WANT[2] == K_V_BROKEN_SHIFT;
+ assert K_CASE_WANT[4] == K_V_STUCK_HIGH;
+ assert K_CASE_WANT[5] == K_V_STUCK_LOW;
+ assert K_CASE_DR[4] == K_ALL_ONES;
+}
+
+test one_even_word_two_verdicts {
+ assert K_CASE_DR[1] == K_CASE_DR[2];
+ assert K_CASE_IR[1] % 4 == K_IR_LOW;
+ assert K_CASE_IR[2] % 4 != K_IR_LOW;
+ assert K_CASE_DR[1] % 2 == 0;
+ assert K_CASE_WANT[1] != K_CASE_WANT[2];
+}
+
+test the_ir_rule_decides_the_broken_shifts {
+ assert K_CASE_IR[3] % 4 != K_IR_LOW;
+ assert K_CASE_IR[6] % 4 != K_IR_LOW;
+ assert K_CASE_DR[6] % 2 == 1;
+ assert K_CASE_IR[0] % 4 == K_IR_LOW;
+ assert K_CASE_DR[0] % 2 == 1;
+}
+
+test the_bench_word_masks_to_the_200t {
+ assert K_CASE_DR[0] % (K_VERSION_MASK + 1) == K_KNOWN_IDCODE[0];
+ assert K_IR_MASK == 3;
+}
+
+test the_page_opens_on_bypass {
+ assert K_V_IDCODE == 5;
+ assert K_START_CASE < 7;
+ assert K_CASE_WANT[K_START_CASE] == K_V_BYPASS;
+}
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/jtag-verdict/tool.js (171 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The page is these functions, built to wasm (t27c gen-c,
+; then zig cc --target=wasm32-wasi) and driven by the one host glue every native widget shares: it
+; forwards input, click and hashchange events, then sets the bytes w_render() wrote as the page's
+; innerHTML, keeping the focused input. Host protocol, all exports of this module:
+; w_init(hash) the page opens; hash is the address's #fragment bytes (empty when none)
+; w_input(id, text) a field changed: id 0 is the IR field, 1 the DR field; its whole text
+; w_click(i) preset button i was pressed
+; w_hash(hash) the address changed
+; w_render(dst) write the page's HTML into dst, return its length (0 on overflow)
+; w_hash_out(dst) the address to show after the last event (length 0: leave it)
+; The state is the two field texts; the rest is derived on every render, as tool.js's update() did.
+; Not carried over from JavaScript: Unicode white space around a typed number (its \s) is not ASCII;
+; field text is cut at FIELD_CAP bytes; a malformed %-escape in the address is read as no value
+; (decodeURIComponent would throw).
+; MEASURED 2026-10-19: t27c gen, a 6-line Zig export wrapper and `zig build-exe -target
+; wasm32-freestanding -O ReleaseSmall` give a 9269-byte wasm. A browser harness loaded the original
+; page (tool.js) and this one side by side and compared their DOM (tag, attributes, text, the two
+; input values, the address) after 3015 actions: the initial page, the 7 preset buttons, 3000 typed
+; values (valid hex with and without 0x, spaces and underscores, 9-digit overflow, empty, quotes,
+; markup, random ASCII) and 7 hash changes, plus 7 page loads that carry an address: 0 differences.
+; Changing one text node of the native page makes the harness report a difference (control).
+; Not carried over from the page: nothing else; focus after a preset click is kept by the host.
+
+pub const FIELD_CAP : u32 = 255;
+pub var IR_TEXT : [255]u8 = [0; 255];
+pub var IR_LEN : u32 = 0;
+pub var DR_TEXT : [255]u8 = [0; 255];
+pub var DR_LEN : u32 = 0;
+pub var PRESSED : [7]u8 = [0; 7];
+pub var HASH_NEW : [64]u8 = [0; 64];
+pub var HASH_NEW_LEN : u32 = 0;
+pub var HEX_A : [16]u8 = [0; 16];
+pub var HEX_B : [16]u8 = [0; 16];
+pub var BIN2 : [2]u8 = [0; 2];
+pub var DECODED : [64]u8 = [0; 64];
+pub const NOT_HEX : i64 = -1;
+
+/// The five checks, in the order of specs/port/tools/jtag/tdo_verdict.t27 (written in an older dialect
+/// that cannot be imported; the seven vectors above are its test vectors and the tests below hold this to them).
+pub fn jtag_verdict(ir: u32, dr: u32) -> u8 {
+ if (dr == K_ALL_ONES) { return K_V_STUCK_HIGH; }
+ if (dr == 0 and ir == 0) { return K_V_STUCK_LOW; }
+ if ((ir & K_IR_MASK) != K_IR_LOW) { return K_V_BROKEN_SHIFT; }
+ if ((dr & 1) == 0) { return K_V_BYPASS; }
+ return K_V_IDCODE;
+}
+
+fn is_ws(b: u8) -> bool {
+ return b == 32 or (b >= 9 and b <= 13);
+}
+
+fn hex_val(b: u8) -> i64 {
+ if (b >= 48 and b <= 57) { return (b - 48) as i64; }
+ if (b >= 97 and b <= 102) { return (b - 97) as i64 + 10; }
+ if (b >= 65 and b <= 70) { return (b - 65) as i64 + 10; }
+ return -1;
+}
+
+/// parseHex: trim, drop white space and underscores, optional 0x, one to eight hex digits; else -1.
+pub fn parse_hex(s: []const u8) -> i64 {
+ var pos : u32 = 0;
+ var first : u8 = 0;
+ var digits : u32 = 0;
+ var val : i64 = 0;
+ var i : usize = 0;
+ while (i < s.len) {
+ const b : u8 = s[i];
+ i = i + 1;
+ if (is_ws(b) or b == 95) { continue; }
+ if (pos == 1 and first == 48 and (b == 120 or b == 88)) {
+ digits = 0;
+ val = 0;
+ pos = pos + 1;
+ continue;
+ }
+ const h : i64 = hex_val(b);
+ if (h < 0) { return NOT_HEX; }
+ if (pos == 0) { first = b; }
+ digits = digits + 1;
+ if (digits <= 8) { val = val * 16 + h; }
+ pos = pos + 1;
+ }
+ if (digits < 1 or digits > 8) { return NOT_HEX; }
+ return val;
+}
+
+/// hexN: "0x" and the lowercase hex digits of v, at least min_digits of them; written to dst, length returned.
+pub fn hex_to(dst: []u8, v: u32, min_digits: u32) -> u32 {
+ dst[0] = 48;
+ dst[1] = 120;
+ var count : u32 = 1;
+ var t : u32 = v / 16;
+ while (t > 0) {
+ count = count + 1;
+ t = t / 16;
+ }
+ var width : u32 = count;
+ if (min_digits > width) { width = min_digits; }
+ var k : u32 = width;
+ while (k > 0) {
+ k = k - 1;
+ var d : u32 = v;
+ var sh : u32 = 0;
+ while (sh < k) {
+ d = d / 16;
+ sh = sh + 1;
+ }
+ const nib : u32 = d % 16;
+ if (nib < 10) { dst[(2 + width - 1 - k) as usize] = (nib as u8) + 48; } else { dst[(2 + width - 1 - k) as usize] = (nib as u8) + 87; }
+ }
+ return 2 + width;
+}
+
+fn ir_value() -> i64 {
+ return parse_hex(IR_TEXT[0..(IR_LEN as usize)]);
+}
+
+fn dr_value() -> i64 {
+ return parse_hex(DR_TEXT[0..(DR_LEN as usize)]);
+}
+
+fn set_text(which: u32, s: []const u8) -> u32 {
+ var n : usize = s.len;
+ if (n > (FIELD_CAP as usize)) { n = FIELD_CAP as usize; }
+ var i : usize = 0;
+ while (i < n) {
+ if (which == 0) { IR_TEXT[i] = s[i]; } else { DR_TEXT[i] = s[i]; }
+ i = i + 1;
+ }
+ if (which == 0) { IR_LEN = n as u32; } else { DR_LEN = n as u32; }
+ return 0;
+}
+
+fn set_field_hex(which: u32, v: u32, digits: u32) -> u32 {
+ const n : u32 = hex_to(HEX_A[0..], v, digits);
+ return set_text(which, HEX_A[0..(n as usize)]);
+}
+
+/// The two fields are good when both parse; then the pressed preset and the new address follow.
+fn recompute(write_hash: bool) -> u32 {
+ HASH_NEW_LEN = 0;
+ const ir : i64 = ir_value();
+ const dr : i64 = dr_value();
+ if (ir < 0 or dr < 0) { return 0; }
+ var i : u32 = 0;
+ while (i < 7) {
+ PRESSED[i as usize] = 0;
+ if (K_CASE_IR[i as usize] == (ir as u32) and K_CASE_DR[i as usize] == (dr as u32)) { PRESSED[i as usize] = 1; }
+ i = i + 1;
+ }
+ if (write_hash) {
+ var n : u32 = 0;
+ HASH_NEW[0] = 35;
+ n = 1;
+ var k : usize = 0;
+ while (k < SAY_HASH_IR.len) { HASH_NEW[n as usize] = SAY_HASH_IR[k]; n = n + 1; k = k + 1; }
+ HASH_NEW[n as usize] = 61;
+ n = n + 1;
+ n = n + hex_to(HASH_NEW[(n as usize)..], ir as u32, 2);
+ HASH_NEW[n as usize] = 38;
+ n = n + 1;
+ k = 0;
+ while (k < SAY_HASH_DR.len) { HASH_NEW[n as usize] = SAY_HASH_DR[k]; n = n + 1; k = k + 1; }
+ HASH_NEW[n as usize] = 61;
+ n = n + 1;
+ n = n + hex_to(HASH_NEW[(n as usize)..], dr as u32, 8);
+ HASH_NEW_LEN = n;
+ }
+ return 0;
+}
+
+/// fromHash's get(): the hex value of #key=... or &key=... in the address, -1 when absent or unreadable.
+fn hash_get(h: []const u8, key: []const u8) -> i64 {
+ var p : usize = 0;
+ while (p + 1 + key.len < h.len) {
+ if ((h[p] == 35 or h[p] == 38) and match_in(h, p + 1, h.len, key) and h[p + 1 + key.len] == 61) {
+ var a : usize = p + 2 + key.len;
+ var e : usize = a;
+ while (e < h.len and h[e] != 38) { e = e + 1; }
+ if (e == a) { return NOT_HEX; }
+ var n : usize = 0;
+ var i : usize = a;
+ while (i < e) {
+ if (n >= 64) { return NOT_HEX; }
+ if (h[i] == 37) {
+ if (i + 2 >= e) { return NOT_HEX; }
+ const hi : i64 = hex_val(h[i + 1]);
+ const lo : i64 = hex_val(h[i + 2]);
+ if (hi < 0 or lo < 0) { return NOT_HEX; }
+ DECODED[n] = ((hi * 16 + lo) as u8);
+ i = i + 3;
+ } else {
+ DECODED[n] = h[i];
+ i = i + 1;
+ }
+ n = n + 1;
+ }
+ return parse_hex(DECODED[0..n]);
+ }
+ p = p + 1;
+ }
+ return NOT_HEX;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ const ir : i64 = hash_get(h, SAY_HASH_IR);
+ const dr : i64 = hash_get(h, SAY_HASH_DR);
+ var start : u32 = K_START_CASE as u32;
+ if (ir >= 0) { set_field_hex(0, ir as u32, 2); } else { set_field_hex(0, K_CASE_IR[start as usize], 2); }
+ if (dr >= 0) { set_field_hex(1, dr as u32, 8); } else { set_field_hex(1, K_CASE_DR[start as usize], 8); }
+ return recompute(false);
+}
+
+pub fn w_input(id: u32, s: []const u8) -> u32 {
+ set_text(id, s);
+ return recompute(true);
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (i >= 7) { return 0; }
+ set_field_hex(0, K_CASE_IR[i as usize], 2);
+ set_field_hex(1, K_CASE_DR[i as usize], 8);
+ return recompute(true);
+}
+
+pub fn w_hash(h: []const u8) -> u32 {
+ const ir : i64 = hash_get(h, SAY_HASH_IR);
+ const dr : i64 = hash_get(h, SAY_HASH_DR);
+ if (ir < 0 or dr < 0) { return recompute(false); }
+ set_field_hex(0, ir as u32, 2);
+ set_field_hex(1, dr as u32, 8);
+ return recompute(false);
+}
+
+pub fn w_hash_out(dst: []u8) -> u32 {
+ var i : u32 = 0;
+ while (i < HASH_NEW_LEN) {
+ dst[i as usize] = HASH_NEW[i as usize];
+ i = i + 1;
+ }
+ return HASH_NEW_LEN;
+}
+
+fn agree_count() -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < 7) {
+ if (jtag_verdict(K_CASE_IR[i as usize], K_CASE_DR[i as usize]) == K_CASE_WANT[i as usize]) { n = n + 1; }
+ i = i + 1;
+ }
+ return n;
+}
+
+fn bit_cells(dst: []u8, n0: u32, v: u32, count: u32) -> u32 {
+ var n : u32 = n0;
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < count) {
+ n = raw(dst, n, "");
+ n = put(dst, n, (((v >> i) % 2) as u8) + 48);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "");
+ return n;
+}
+
+fn field(dst: []u8, n0: u32, id: u32, label: []const u8, hint: []const u8) -> u32 {
+ var n : u32 = n0;
+ if (id == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ROUND_NOTE);
+ n = raw(dst, n, "
");
+ n = storage_html(dst, n, d, r);
+ n = raw(dst, n, "");
+ n = limits_html(dst, n, d, r);
+ n = raw(dst, n, "");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ return n;
+}
From 6a2fc5ad56d8bc0be17c46cae0ed133b0e183d4f Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 17:21:44 +0700
Subject: [PATCH 30/63] widgets: build-receipt drawn natively -- 32541-byte
wasm, 951 DOM comparisons, 0 differences (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/build_receipt.t27 | 1388 +++++++++++++++++++++++++++++++
1 file changed, 1388 insertions(+)
create mode 100644 specs/widgets/build_receipt.t27
diff --git a/specs/widgets/build_receipt.t27 b/specs/widgets/build_receipt.t27
new file mode 100644
index 0000000000..6da77f9198
--- /dev/null
+++ b/specs/widgets/build_receipt.t27
@@ -0,0 +1,1388 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/build-receipt.t27 -- one open-flow build, t27 spec to XC7A200T bitstream, as a receipt
+; Source of truth for public/widgets/build-receipt/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant or a field
+; of receipt.json written by the same script run.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: a shop-style receipt for one real build on one machine. Each stage of the open flow
+; is a line item with its wall time, its tool and version, the size of the file it wrote and that
+; file's sha256: the t27 compiler (WebAssembly, in node) turns specs/ternary/stream_ternary_mac.t27
+; into Verilog, yosys synthesises it, nextpnr-xilinx places and routes it on the xc7a200tfbg676-1
+; chipdb, prjxray's fasm2frames and xc7frames2bit write the bitstream, and the last line hashes it.
+; Under the items: the TOTAL, a line that no vendor tool and no licence was used, the exact commands,
+; and how to reproduce it. A barcode is drawn from the bitstream's sha256.
+
+; DATA PROVENANCE: scripts/widget-data/build-receipt.mjs ran every stage in /tmp/widget-receipt and
+; wrote receipt.json and the generated block below; it then re-checked that the stage times sum to
+; the total within rounding and that the stage count matches the data. The datapath is generated
+; from the t27 spec; the board wrapper (clock from STARTUPE2's CFGMCLK, two LFSRs as stimulus, three
+; LEDs) is hand-written Verilog, scripts/widget-data/build-receipt-top.v, and the receipt says so.
+; Outside the total, bitwalk (gHashTag/t27 specs/xilinx7, generated by the same compiler's rust
+; target) writes a second .bit from the same frames to check xc7frames2bit's payload.
+
+; WHAT IT DOES NOT CLAIM: that the bitstream was loaded into a board (it was not: no JTAG was run),
+; that the design does anything useful on one, or that the times hold on another machine. The run
+; shared the machine with other work; its load average at start is printed on the receipt. No Vivado
+; was run, so there is no Vivado time to compare against.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_build_receipt;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "build-receipt";
+pub const TITLE : str = "Build receipt: t27 spec to XC7A200T bitstream, no Vivado";
+pub const DESCRIPTION : str = "One real open-flow build itemised like a shop receipt: each stage from a t27 spec to an Artix-7 200T bitstream with its time, tool version, output size and sha256.";
+pub const IMAGE_ALT : str = "A white thermal-paper receipt on black. Header: Trinity S3AI open flow, build receipt. Six line items with times: t27 to Verilog 0.288 s, synthesis 29.608 s, place and route 57.238 s, FASM to frames 15.979 s, frames to .bit 0.635 s, sha256 0.016 s. TOTAL 103.764 s. Vivado licence 0. Below: a barcode drawn from the bitstream sha256 and the first hex digits of that sha.";
+pub const HOOK : str = "From a ternary spec to an Artix-7 200T bitstream with no Vivado: every stage timed, versioned and hashed.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [7]str = ["scripts/widget-data/build-receipt.mjs run, 2026-10-04 on an Apple M1 Pro: every stage time (process.hrtime), cpu and peak memory (/usr/bin/time -l), output size and sha256 (node crypto), written to receipt.json", "public/t27/t27_compiler.wasm in node, targets.verilog of specs/ternary/stream_ternary_mac.t27", "Yosys 0.67+post b8e7da6f40: yosys -V; synth_xilinx -flatten -abc9 -arch xc7", "nextpnr-xilinx 0.9.2-107-g7037c948 with ~/openxc7-src/chipdb/xc7a200tfbg676-1.bin: fmax and utilisation from its log, --freq 80 --seed 1", "prjxray c9f02d85 fasm2frames.py and xc7frames2bit with prjxray-db ab1fc60 artix7 xc7a200tfbg676-1 part.yaml", "bitwalk cross-check: gHashTag/t27 29b78e7fe specs/xilinx7, rust target of the same compiler, rustc 1.98.1", "LED pins R23, T23, J26 LVCMOS33: gHashTag/t27 fpga/vsa/gf16_heartbeat_top.xdc (QMTech Wukong V1)"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+pub const K_DATA_FILE : str = "./receipt.json";
+; The open flow has six stages: gen, synth, pnr, frames, bit, sha256.
+pub const K_STAGE_PLAN : u8 = 6;
+; Each stage time is rounded to the millisecond on its own and the total once, from the exact sum.
+; So the items may miss the total by at most half a millisecond each: K_STAGE_PLAN / 2, rounded up.
+pub const K_ROUND_TOL_MS : u8 = 3;
+
+; --- The run: written by the script, re-checked by it and by the tests below ---------------
+; --- BEGIN generated by scripts/widget-data/build-receipt.mjs run: do not edit by hand ---
+; Run of 2026-10-04T17:41:38Z on Apple M1 Pro, 8 cores, load 151.2 at start.
+pub const K_STAGE_COUNT : u8 = 6;
+pub const K_STAGES_OK : u8 = 6;
+pub const K_STAGE_MS : [6]u32 = [288, 29608, 57238, 15979, 635, 16];
+pub const K_TOTAL_MS : u32 = 103764;
+pub const K_BIT_BYTES : u32 = 9730757;
+pub const K_VENDOR_TOOLS_RUN : u8 = 0;
+; --- END generated ---
+
+pub const SAY_STAGE_NAMES : [6]str = ["t27 to Verilog", "synthesis", "place and route", "FASM to frames", "frames to .bit", "sha256 of the .bit"];
+
+; --- Words on the receipt -----------------------------------------------------------------
+; Templates fill {name} slots from receipt.json; tool.js adds no words of its own.
+pub const SAY_SHOP : str = "TRINITY S3AI OPEN FLOW";
+pub const SAY_RECEIPT : str = "build receipt no. {no}";
+pub const SAY_WHEN : str = "{when} UTC";
+pub const SAY_MACHINE : str = "{cpu}, {cores} cores, macOS {macos}";
+pub const SAY_LOAD : str = "load average {load} at start: the machine was shared";
+pub const SAY_LABEL_SPEC : str = "spec";
+pub const SAY_LABEL_PART : str = "part";
+pub const SAY_LABEL_BOARD : str = "board";
+pub const SAY_WRAPPER : str = "datapath {name} is generated from the spec; the board wrapper is {lines} lines of hand-written Verilog";
+pub const SAY_COL_ITEM : str = "ITEM";
+pub const SAY_COL_TIME : str = "TIME";
+pub const SAY_OUT : str = "wrote {file}, {size}";
+pub const SAY_SHA : str = "sha256 {sha}";
+pub const SAY_CPU : str = "cpu {cpu} s, peak memory {rss} MB";
+pub const SAY_FAILED : str = "FAILED, exit {exit}";
+pub const SAY_NOT_RUN : str = "not run";
+pub const SAY_ITEMS : str = "ITEMS {ok} of {count}";
+pub const SAY_TOTAL : str = "TOTAL";
+pub const SAY_NO_TOTAL : str = "NO TOTAL: a stage failed after {s} in all";
+pub const SAY_VIVADO : str = "Vivado licence";
+pub const SAY_VENDOR : str = "vendor tools run";
+pub const SAY_END_TO_END : str = "end to end, with the script's own work: {s}";
+pub const SAY_FMAX : str = "fmax {clock} {mhz} MHz, target {target} MHz";
+pub const SAY_PASS : str = "PASS";
+pub const SAY_FAIL : str = "FAIL";
+pub const SAY_UTIL_TITLE : str = "used on the 200T";
+pub const SAY_FILE_SHA : str = "sha256 of the .bit file";
+pub const SAY_FILE_SHA_NOTE : str = "changes every run: the file header holds a path, a date and a time";
+pub const SAY_PAYLOAD_SHA : str = "sha256 from the sync word on, {size}";
+pub const SAY_SAME_PAYLOAD : str = "same payload as the {n} earlier run(s) of the same inputs";
+pub const SAY_OTHER_PAYLOAD : str = "payload differs from an earlier run of the same inputs";
+pub const SAY_EARLIER : str = "earlier run {when}: total {s}, load {load}";
+pub const SAY_CROSS_OK : str = "cross-check: bitwalk, t27's own writer, wrote the same payload from the same frames in {ms} ms (not in the total)";
+pub const SAY_CROSS_BAD : str = "cross-check: bitwalk wrote a different payload from the same frames";
+pub const SAY_CROSS_SKIP : str = "cross-check: bitwalk was not run";
+pub const SAY_NOT_PROGRAMMED : str = "Not loaded into any board: no JTAG was run.";
+pub const SAY_BARCODE : str = "Barcode drawn from the sha256 of the .bit file";
+pub const SAY_FOOTER : str = "phi^2 + 1/phi^2 = 3 | keep the sha, not the receipt";
+pub const SAY_SHOW_CMDS : str = "show commands";
+pub const SAY_HIDE_CMDS : str = "hide commands";
+pub const SAY_VERIFY_TITLE : str = "Verify it yourself";
+pub const SAY_VERIFY_STEPS : [5]str = ["Install the openXC7 tools: yosys, nextpnr-xilinx with the xc7a200tfbg676-1 chipdb, and prjxray with its database.", "Clone gHashTag/trinity, then run: cd apps/website && node scripts/widget-data/build-receipt.mjs run", "Compare the payload sha256 (from the sync word on), not the file sha256: the file header holds a path and a time.", "Run node scripts/widget-data/build-receipt.mjs check: it re-adds the stage times and recounts the stages.", "Expect other times: these were taken once, on one shared laptop."];
+pub const SAY_SPEC_DIFF : str = "the spec and receipt.json disagree on {what}";
+pub const SAY_LOADING : str = "printing the receipt";
+pub const SAY_ERROR : str = "could not read the receipt data";
+pub const SAY_UNITS : [4]str = ["B", "kB", "MB", "GB"];
+pub const SAY_SECONDS : str = "{s} s";
+
+; --- Tests: the generator evaluates these before it writes the page ----------------------
+test the_items_add_up_to_the_total {
+ assert K_STAGE_MS[0] + K_STAGE_MS[1] + K_STAGE_MS[2] + K_STAGE_MS[3] + K_STAGE_MS[4] + K_STAGE_MS[5] <= K_TOTAL_MS + K_ROUND_TOL_MS;
+ assert K_STAGE_MS[0] + K_STAGE_MS[1] + K_STAGE_MS[2] + K_STAGE_MS[3] + K_STAGE_MS[4] + K_STAGE_MS[5] + K_ROUND_TOL_MS >= K_TOTAL_MS;
+ assert K_ROUND_TOL_MS == (K_STAGE_PLAN + 1) / 2;
+}
+
+test the_stage_count_matches_the_data {
+ assert K_STAGE_COUNT == K_STAGE_PLAN;
+ assert K_STAGES_OK <= K_STAGE_COUNT;
+ assert SAY_STAGE_NAMES[5] == "sha256 of the .bit";
+}
+
+test no_vendor_tool_was_run {
+ assert K_VENDOR_TOOLS_RUN == 0;
+ assert K_BIT_BYTES > 0;
+}
+
+test the_page_sends_nothing {
+ assert READS_LOCAL_FILES == false;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_DATA_FILE == "./receipt.json";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/build-receipt/tool.js (221 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's:
+; w_init(hash) the page opens: "printing the receipt"
+; w_data(i, bytes) file i of K_DATA_FILE has been fetched; w_data_failed(i) when it could not be
+; w_click(i) the "show commands" button (the only one) was pressed
+; w_render(dst) the page's HTML, length (0 on overflow)
+; The receipt is drawn from the bytes of receipt.json (json_scan.t27) and the values are written the way
+; tool.js's String() / toFixed(3) / Math.round / template literals write them (js_val.t27, js_num.t27).
+; Where tool.js throws while it draws (receipt.stages no array, a null stage, a machine that is a string
+; or a number, a payload digest that is no string, a utilisation row that is no array, earlier runs with a
+; null entry, commands that are no array) its catch shows SAY_ERROR; so does this page (data_ok).
+; The spec-versus-data check (specDiff) reads the K_ constants of this very file.
+; NOT carried over: String.prototype.toLowerCase / toUpperCase beyond ASCII and Latin-1 (the tool-name
+; test and the receipt number); Object.entries order for integer-like keys of `utilisation`, and duplicate keys; a
+; non-empty string as `utilisation` or `earlier_runs`, and a string as a row of `utilisation`;
+; the data file is cut at DATA_CAP bytes (the page then says it could not be read); a digest longer than
+; 293 hex digits is cut in the barcode and one longer than 511 bytes everywhere.
+; MEASURED 2026-10-19: t27c 0.5.2 gen + a 5-line Zig export wrapper + zig build-exe wasm32-freestanding
+; ReleaseSmall give a 32541-byte wasm. The original page (tool.js) and this one ran in two happy-dom
+; windows against the real 6673-byte receipt.json and 188 variants of it, 951 comparisons of tag,
+; attributes (the barcode svg included) and text, the button pressed up to 12 times in a row (569 presses)
+; and the page before the data arrives and after: no fmax / a failing one / an odd one, utilisation empty /
+; absent / an array / short rows / odd keys, stages not run / failed (with and without exit and error) /
+; with odd status words, outputs missing / partial / byte sizes at every unit boundary (0, 999, 1000,
+; 999999, 1e9, 1.5e12, negative, a string, 7e21), cpu and rss forms, ms of every form (a string makes the
+; sum a string, as in JavaScript), an extra / fewer / duplicate / one stage, every kind of spec-versus-data
+; disagreement (the warning line), a missing / null bit, digests of every shape (upper case, short, empty,
+; non-hex, 200 digits, a number, Latin-1 letters), cross-check ok / bad / skipped / missing, earlier runs
+; none / many / odd stamps, an incomplete receipt, vendor tools of every form, machine and design missing /
+; partial / numbers, commands empty / markup / nested, version and tool names with and without a match,
+; markup and unicode in every string, 15 data files the page cannot draw (a root that is an array or null,
+; stages missing / an object / with a null, a number and a string in it, a machine that is a string or a
+; number, a payload that is a number or an array, a utilisation row that is a number or null, an earlier
+; run that is null, commands that are a string or an object): all end on the error line in both; 70 files
+; with random numbers and strings in every field, 8 long sessions, a 404 and a body that is not JSON:
+; 0 differences. A negative control (one node of the native DOM changed) is caught.
+; Not compared: the stylesheet (happy-dom); the cases under "NOT carried over".
+
+pub const DATA_CAP : u32 = 32768;
+pub var DATA : [32768]u8 = [0; 32768];
+pub var DATA_LEN : u32 = 0;
+pub var DATA_STATE : u8 = 0;
+pub var CMDS_ON : bool = false;
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_STATE = 0;
+ DATA_LEN = 0;
+ CMDS_ON = false;
+ return 0;
+}
+
+fn nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+/// receipt.stages is an array, every stage is non-null, and the rest of render() would not throw.
+fn data_ok(d: []const u8, r: i64) -> bool {
+ if (kind(d, r) != J_OBJECT) { return false; }
+ const st : i64 = obj_get(d, r, "stages");
+ if (kind(d, st) != J_ARRAY) { return false; }
+ const cnt : u32 = arr_len(d, st);
+ var i : u32 = 0;
+ while (i < cnt) {
+ if (nullish(d, arr_at(d, st, i))) { return false; }
+ i = i + 1;
+ }
+ const m : i64 = obj_get(d, r, "machine");
+ if (jv_truthy(d, m) and kind(d, m) != J_OBJECT and kind(d, m) != J_ARRAY) { return false; }
+ const bit : i64 = obj_get(d, r, "bit");
+ var pay : i64 = obj_get(d, bit, "payload_sha256");
+ if (nullish(d, pay)) { pay = obj_get(d, bit, "sha256"); }
+ if (!nullish(d, pay) and kind(d, pay) != J_STRING) { return false; }
+ const ut : i64 = obj_get(d, r, "utilisation");
+ if (jv_truthy(d, ut)) {
+ const k : u8 = kind(d, ut);
+ if (k == J_STRING) { return false; }
+ if (k == J_OBJECT or k == J_ARRAY) {
+ if (!entries_ok(d, ut)) { return false; }
+ }
+ }
+ if (jv_truthy(d, bit)) {
+ const er : i64 = obj_get(d, r, "earlier_runs");
+ if (jv_truthy(d, er) and kind(d, er) == J_STRING) { return false; }
+ if (kind(d, er) == J_OBJECT and jv_truthy(d, obj_get(d, er, "length"))) { return false; }
+ if (kind(d, er) == J_ARRAY) {
+ const ec : u32 = arr_len(d, er);
+ var j : u32 = 0;
+ while (j < ec) {
+ if (nullish(d, arr_at(d, er, j))) { return false; }
+ j = j + 1;
+ }
+ }
+ }
+ const cm : i64 = obj_get(d, r, "commands");
+ if (jv_truthy(d, cm) and kind(d, cm) != J_ARRAY) { return false; }
+ return true;
+}
+
+/// Every entry of the object / array `ut` is an array (the `[used, total]` destructuring does not throw).
+fn entries_ok(d: []const u8, ut: i64) -> bool {
+ if (kind(d, ut) == J_ARRAY) {
+ const c : u32 = arr_len(d, ut);
+ var i : u32 = 0;
+ while (i < c) {
+ const v : i64 = arr_at(d, ut, i);
+ if (kind(d, v) != J_ARRAY) { return false; }
+ i = i + 1;
+ }
+ return true;
+ }
+ // an object: walk its members in file order
+ const hi : usize = span_hi(ut);
+ var p : usize = skip_ws(d, span_lo(ut) + 1);
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ const q : usize = skip_ws(d, ke);
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (kind(d, jspan(v, ve)) != J_ARRAY) { return false; }
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return true;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (s.len > (DATA_CAP as usize)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) {
+ DATA[k] = s[k];
+ k = k + 1;
+ }
+ DATA_LEN = s.len as u32;
+ DATA_STATE = 1;
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (!jv_valid(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ if (!data_ok(d, root(d))) { DATA_STATE = 2; }
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ DATA_STATE = 2;
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (i == 0 and DATA_STATE == 1) { CMDS_ON = !CMDS_ON; }
+ return 0;
+}
+
+// --- small writers ------------------------------------------------------------------------------
+
+///
0) {
+ n = put(dst, n, 32);
+ n = raw(dst, n, cls);
+ }
+ return raw(dst, n, "\">");
+}
+
+fn rule_html(dst: []u8, n0: u32) -> u32 {
+ return raw(dst, n0, "");
+}
+
+/// line(left, right, cls) up to the left text.
+fn line_open(dst: []u8, n0: u32, cls: []const u8) -> u32 {
+ var n : u32 = div_open(dst, n0, "br-line", cls);
+ return raw(dst, n, "");
+}
+
+fn line_mid(dst: []u8, n0: u32) -> u32 {
+ return raw(dst, n0, "");
+}
+
+fn line_close(dst: []u8, n0: u32) -> u32 {
+ return raw(dst, n0, "
");
+}
+
+fn put_all(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < l) {
+ n = put_escaped(dst, n, s[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+/// The text of a textContent / child assignment: null and undefined add nothing.
+fn tc_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+/// (ms / 1000).toFixed(3) in SAY_SECONDS.
+fn secs_text(dst: []u8, n0: u32, ms: f64) -> u32 {
+ var vb : [128]u8 = [0; 128];
+ const vn : u32 = jv_vput_fixed(vb[0..], 0, "s", ms / 1000.0, 3);
+ return jv_fill(dst, n0, SAY_SECONDS, vb[0..], vn, false);
+}
+
+/// `x ?? 0` then Number(): the coerced number of a field with a default of 0.
+fn num_or0(d: []const u8, sp: i64) -> f64 {
+ if (nullish(d, sp)) { return 0.0; }
+ return jv_num(d, sp);
+}
+
+/// Trim JavaScript white space off buf[0..l); the new start in lo and end in hi (written back through the arrays).
+fn trim_range(buf: []const u8, l: u32, out: []u32) -> u32 {
+ var lo : u32 = 0;
+ var hi : u32 = l;
+ var w : u32 = 1;
+ while (lo < hi and w > 0) {
+ w = jv_ws_at(buf, lo, hi);
+ lo = lo + w;
+ }
+ w = 1;
+ while (hi > lo and w > 0) {
+ w = jv_ws_before(buf, lo, hi);
+ hi = hi - w;
+ }
+ out[0] = lo;
+ out[1] = hi;
+ return 0;
+}
+
+/// size(bytes) as raw bytes into out, trimmed: `12.34 MB (12345678 B)` / `4492 B`; the length.
+fn size_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ var tmp : [512]u8 = [0; 512];
+ var t : u32 = 0;
+ var v : f64 = jv_num(d, sp);
+ var i : u32 = 0;
+ const last : u32 = (SAY_UNITS.len as u32) - 1;
+ while (v >= 1000.0 and i < last) {
+ v = v / 1000.0;
+ i = i + 1;
+ }
+ if (i == 0) {
+ t = jv_str(tmp[0..], t, d, sp);
+ } else {
+ t = js_to_fixed(tmp[0..], t, v, 2);
+ }
+ t = put(tmp[0..], t, 32);
+ t = jn_raw(tmp[0..], t, SAY_UNITS[i as usize]);
+ if (i != 0) {
+ t = jn_raw(tmp[0..], t, " (");
+ t = jv_str(tmp[0..], t, d, sp);
+ t = put(tmp[0..], t, 32);
+ t = jn_raw(tmp[0..], t, SAY_UNITS[0]);
+ t = put(tmp[0..], t, 41);
+ }
+ var rg : [2]u32 = [0; 2];
+ trim_range(tmp[0..], t, rg[0..]);
+ var n : u32 = 0;
+ var q : u32 = rg[0];
+ while (q < rg[1]) {
+ n = jn_put(out, n, tmp[q as usize]);
+ q = q + 1;
+ }
+ return n;
+}
+
+fn size_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ var tmp : [600]u8 = [0; 600];
+ const l : u32 = size_raw(tmp[0..], d, sp);
+ return put_all(dst, n0, tmp[0..], l);
+}
+
+/// toLowerCase for ASCII and Latin-1 (UTF-8 C3 80..9E except 97), in place; the length does not change.
+fn lower_in_place(buf: []u8, l: u32) -> u32 {
+ var i : u32 = 0;
+ while (i < l) {
+ const c : u8 = buf[i as usize];
+ if (c >= 65 and c <= 90) { buf[i as usize] = c + 32; }
+ if (c == 195 and i + 1 < l) {
+ const c2 : u8 = buf[(i + 1) as usize];
+ if (c2 >= 128 and c2 <= 158 and c2 != 151) { buf[(i + 1) as usize] = c2 + 32; }
+ i = i + 1;
+ }
+ i = i + 1;
+ }
+ return l;
+}
+
+/// toolLine(s): the version alone when it already names the tool, else `tool | version`.
+fn tool_line(dst: []u8, n0: u32, d: []const u8, s: i64) -> u32 {
+ var ver : [512]u8 = [0; 512];
+ var vl : u32 = 0;
+ const vv : i64 = obj_get(d, s, "version");
+ if (!nullish(d, vv)) { vl = jv_str(ver[0..], 0, d, vv); }
+ var tool : [512]u8 = [0; 512];
+ const tl : u32 = jv_str(tool[0..], 0, d, obj_get(d, s, "tool"));
+ var fl : u32 = 0;
+ while (fl < tl and tool[fl as usize] != 32) { fl = fl + 1; }
+ var lver : [512]u8 = [0; 512];
+ var ltool : [512]u8 = [0; 512];
+ var c : u32 = 0;
+ while (c < vl) {
+ lver[c as usize] = ver[c as usize];
+ c = c + 1;
+ }
+ c = 0;
+ while (c < fl) {
+ ltool[c as usize] = tool[c as usize];
+ c = c + 1;
+ }
+ lower_in_place(lver[0..], vl);
+ lower_in_place(ltool[0..], fl);
+ // does lower(ver) include lower(first)?
+ var found : bool = fl == 0;
+ var a : u32 = 0;
+ while (!found and a + fl <= vl) {
+ var m : bool = true;
+ var b : u32 = 0;
+ while (m and b < fl) {
+ if (lver[(a + b) as usize] != ltool[b as usize]) { m = false; }
+ b = b + 1;
+ }
+ if (m) { found = true; }
+ a = a + 1;
+ }
+ if (found) { return put_all(dst, n0, ver[0..], vl); }
+ var n : u32 = put_all(dst, n0, tool[0..], tl);
+ n = jn_raw(dst, n, " | ");
+ return put_all(dst, n, ver[0..], vl);
+}
+
+fn hexval(b: u8) -> u32 {
+ if (b >= 48 and b <= 57) { return (b - 48) as u32; }
+ if (b >= 97 and b <= 102) { return (b - 97) as u32 + 10; }
+ if (b >= 65 and b <= 70) { return (b - 65) as u32 + 10; }
+ return 99;
+}
+
+/// barcode(hex): one bar and one gap per hex digit, widths 1 to 4, with a guard at each end.
+fn barcode_html(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ var tmp : [512]u8 = [0; 512];
+ var l : u32 = 0;
+ if (jv_truthy(d, sp)) { l = jv_str(tmp[0..], 0, d, sp); }
+ var parts : [600]u32 = [0; 600];
+ var pc : u32 = 0;
+ parts[0] = 1; parts[1] = 1; parts[2] = 1; parts[3] = 1;
+ pc = 4;
+ var i : u32 = 0;
+ while (i < l and pc < 590) {
+ const hv : u32 = hexval(tmp[i as usize]);
+ if (hv < 16) {
+ parts[pc as usize] = hv / 4 + 1;
+ parts[(pc + 1) as usize] = hv % 4 + 1;
+ pc = pc + 2;
+ }
+ i = i + 1;
+ }
+ parts[pc as usize] = 1; parts[(pc + 1) as usize] = 1; parts[(pc + 2) as usize] = 1; parts[(pc + 3) as usize] = 1;
+ pc = pc + 4;
+ var width : u32 = 12;
+ i = 0;
+ while (i < pc) {
+ width = width + parts[i as usize];
+ i = i + 1;
+ }
+ var n : u32 = raw(dst, n0, "");
+ var x : u32 = 6;
+ i = 0;
+ while (i < pc) {
+ if (i % 2 == 0) {
+ n = raw(dst, n, "");
+ }
+ x = x + parts[i as usize];
+ i = i + 1;
+ }
+ return raw(dst, n, "");
+}
+
+// --- the stages -----------------------------------------------------------------------------------
+
+fn stage_item(dst: []u8, n0: u32, d: []const u8, s: i64, ix: u32) -> u32 {
+ const status : i64 = obj_get(d, s, "status");
+ var n : u32 = raw(dst, n0, "
");
+ // line(name, ...)
+ n = line_open(dst, n, "");
+ n = js_str_u64(dst, n, (ix + 1) as u64);
+ n = put(dst, n, 32);
+ if (ix < (SAY_STAGE_NAMES.len as u32)) { n = text(dst, n, SAY_STAGE_NAMES[ix as usize]); } else { n = jv_text(dst, n, d, obj_get(d, s, "id")); }
+ n = line_mid(dst, n);
+ if (jv_is(d, status, "not run")) {
+ n = text(dst, n, SAY_NOT_RUN);
+ n = line_close(dst, n);
+ n = div_open(dst, n, "br-sub", "");
+ n = tc_text(dst, n, d, obj_get(d, s, "tool"));
+ n = raw(dst, n, "
");
+ return n;
+ }
+ n = secs_text(dst, n, num_or0(d, obj_get(d, s, "ms")));
+ n = line_close(dst, n);
+ n = div_open(dst, n, "br-sub", "");
+ n = tool_line(dst, n, d, s);
+ n = raw(dst, n, "");
+ if (failed) {
+ n = div_open(dst, n, "br-sub", "br-bad");
+ var vb : [256]u8 = [0; 256];
+ var vn : u32 = 0;
+ const ex : i64 = obj_get(d, s, "exit");
+ if (nullish(d, ex)) { vn = jv_vput_str(vb[0..], 0, "exit", ""); } else { vn = jv_vput_val(vb[0..], 0, "exit", d, ex); }
+ n = jv_fill(dst, n, SAY_FAILED, vb[0..], vn, false);
+ n = raw(dst, n, "");
+ if (jv_truthy(d, obj_get(d, s, "error"))) {
+ n = raw(dst, n, "
");
+ n = jv_text(dst, n, d, obj_get(d, s, "error"));
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "");
+ }
+ const out : i64 = obj_get(d, s, "output");
+ if (jv_truthy(d, out)) {
+ n = div_open(dst, n, "br-sub", "br-detail");
+ var ob : [1024]u8 = [0; 1024];
+ var on : u32 = jv_vput_val(ob[0..], 0, "file", d, obj_get(d, out, "file"));
+ var szn : [600]u8 = [0; 600];
+ const tmpn : u32 = size_raw(szn[0..], d, obj_get(d, out, "bytes"));
+ on = jv_vput_str(ob[0..], on, "size", szn[0..(tmpn as usize)]);
+ n = jv_fill(dst, n, SAY_OUT, ob[0..], on, false);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-hex br-detail");
+ var hb : [512]u8 = [0; 512];
+ var hn : u32 = jv_vput_val(hb[0..], 0, "sha", d, obj_get(d, out, "sha256"));
+ n = jv_fill(dst, n, SAY_SHA, hb[0..], hn, false);
+ n = raw(dst, n, "");
+ }
+ const cpu : i64 = obj_get(d, s, "cpu_s");
+ if (!nullish(d, cpu)) {
+ n = div_open(dst, n, "br-sub", "br-faint br-detail");
+ var cb : [256]u8 = [0; 256];
+ var cn : u32 = jv_vput_val(cb[0..], 0, "cpu", d, cpu);
+ cn = jv_vput_f64(cb[0..], cn, "rss", js_round(num_or0(d, obj_get(d, s, "rss_mb"))));
+ n = jv_fill(dst, n, SAY_CPU, cb[0..], cn, false);
+ n = raw(dst, n, "");
+ }
+ return raw(dst, n, "");
+}
+
+/// The names specDiff() lists: where the generated constants of this spec and receipt.json disagree.
+fn spec_diff(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 {
+ const st : i64 = obj_get(d, r, "stages");
+ const cnt : u32 = arr_len(d, st);
+ var n : u32 = n0;
+ var any : bool = false;
+ const sc : i64 = obj_get(d, r, "stage_count");
+ if (!(kind(d, sc) == J_NUMBER and jv_num(d, sc) == (K_STAGE_COUNT as f64) and jv_num(d, sc) == (cnt as f64))) {
+ n = raw(dst, n, "K_STAGE_COUNT");
+ any = true;
+ }
+ const so : i64 = obj_get(d, r, "stages_ok");
+ if (!(kind(d, so) == J_NUMBER and jv_num(d, so) == (K_STAGES_OK as f64))) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_STAGES_OK");
+ any = true;
+ }
+ var msbad : bool = false;
+ if ((K_STAGE_MS.len as u32) != cnt) { msbad = true; }
+ var sum : f64 = 0.0;
+ // reduce((a, b) => a + b, 0): numbers add; once a string (or object / array) is met the rest is concatenated
+ var sbuf : [1100]u8 = [0; 1100];
+ var sl : u32 = 0;
+ var strmode : bool = false;
+ var i : u32 = 0;
+ while (i < cnt) {
+ const ms : i64 = obj_get(d, arr_at(d, st, i), "ms");
+ const mk : u8 = kind(d, ms);
+ var mv : f64 = 0.0;
+ var is_num : bool = true;
+ if (!nullish(d, ms)) {
+ mv = jv_num(d, ms);
+ is_num = mk == J_NUMBER;
+ }
+ if (i < (K_STAGE_MS.len as u32)) {
+ if (!is_num or mv != (K_STAGE_MS[i as usize] as f64)) { msbad = true; }
+ }
+ if (!strmode and (mk == J_STRING or mk == J_OBJECT or mk == J_ARRAY)) {
+ strmode = true;
+ sl = js_str_num(sbuf[0..], 0, sum);
+ }
+ if (strmode) {
+ if (nullish(d, ms)) { sl = jn_raw(sbuf[0..], sl, "0"); } else { sl = jv_str(sbuf[0..], sl, d, ms); }
+ } else {
+ sum = sum + mv;
+ }
+ i = i + 1;
+ }
+ if (strmode) { sum = jv_number_of_text(sbuf[0..], sl); }
+ if (msbad) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_STAGE_MS");
+ any = true;
+ }
+ const tm : i64 = obj_get(d, r, "total_ms");
+ if (!(kind(d, tm) == J_NUMBER and jv_num(d, tm) == (K_TOTAL_MS as f64))) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_TOTAL_MS");
+ any = true;
+ }
+ const bit : i64 = obj_get(d, r, "bit");
+ if (jv_truthy(d, bit)) {
+ const bb : i64 = obj_get(d, bit, "bytes");
+ if (!(kind(d, bb) == J_NUMBER and jv_num(d, bb) == (K_BIT_BYTES as f64))) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "K_BIT_BYTES");
+ any = true;
+ }
+ }
+ var gap : f64 = sum - jv_num(d, tm);
+ if (gap < 0.0) { gap = 0.0 - gap; }
+ if (gap > (K_ROUND_TOL_MS as f64)) {
+ if (any) { n = raw(dst, n, ", "); }
+ n = raw(dst, n, "total_ms");
+ }
+ return n;
+}
+
+fn member_value(d: []const u8, ut: i64, ix: u32, key_out: []u8, key_len: []u32) -> i64 {
+ const hi : usize = span_hi(ut);
+ var p : usize = skip_ws(d, span_lo(ut) + 1);
+ var at : u32 = 0;
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ const q : usize = skip_ws(d, ke);
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (at == ix) {
+ key_len[0] = jv_str_to(key_out, 0, d, jspan(p, ke));
+ return jspan(v, ve);
+ }
+ at = at + 1;
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return NO_SPAN;
+}
+
+fn member_count(d: []const u8, ut: i64) -> u32 {
+ const hi : usize = span_hi(ut);
+ var p : usize = skip_ws(d, span_lo(ut) + 1);
+ var at : u32 = 0;
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ const q : usize = skip_ws(d, ke);
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ at = at + 1;
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return at;
+}
+
+fn paper_html(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 {
+ const bit : i64 = obj_get(d, r, "bit");
+ const mach : i64 = obj_get(d, r, "machine");
+ const des : i64 = obj_get(d, r, "design");
+ const sha : i64 = obj_get(d, bit, "sha256");
+ var n : u32 = raw(dst, n0, "");
+ // the receipt number: the payload digest (else the file digest), first 8 characters, upper case
+ var pay : i64 = obj_get(d, bit, "payload_sha256");
+ if (nullish(d, pay)) { pay = sha; }
+ var nb : [64]u8 = [0; 64];
+ var nl : u32 = 0;
+ if (kind(d, pay) == J_STRING) {
+ var raw8 : [512]u8 = [0; 512];
+ const rl : u32 = jv_str(raw8[0..], 0, d, pay);
+ var units : u32 = 0;
+ var q : u32 = 0;
+ while (q < rl and units < 8 and nl < 56) {
+ const c : u8 = raw8[q as usize];
+ var w : u32 = 1;
+ var u : u32 = 1;
+ if (c >= 192) { w = 2; }
+ if (c >= 224) { w = 3; }
+ if (c >= 240) { w = 4; u = 2; }
+ if (units + u > 8) { break; }
+ if (c >= 97 and c <= 122) {
+ nb[nl as usize] = c - 32;
+ nl = nl + 1;
+ } else {
+ if (c == 195 and q + 1 < rl) {
+ // Latin-1: a..o / o..y with a diaeresis etc. go up by 32 (not the division sign); y with diaeresis and sharp s are special
+ const c2 : u8 = raw8[(q + 1) as usize];
+ if (c2 == 159) {
+ nb[nl as usize] = 83;
+ nb[(nl + 1) as usize] = 83;
+ nl = nl + 2;
+ } else {
+ if (c2 == 191) {
+ nb[nl as usize] = 197;
+ nb[(nl + 1) as usize] = 184;
+ nl = nl + 2;
+ } else {
+ nb[nl as usize] = c;
+ if (c2 >= 160 and c2 <= 190 and c2 != 183) { nb[(nl + 1) as usize] = c2 - 32; } else { nb[(nl + 1) as usize] = c2; }
+ nl = nl + 2;
+ }
+ }
+ } else {
+ if (c == 194 and q + 1 < rl and raw8[(q + 1) as usize] == 181) {
+ nb[nl as usize] = 206;
+ nb[(nl + 1) as usize] = 156;
+ nl = nl + 2;
+ } else {
+ var z : u32 = 0;
+ while (z < w and q + z < rl) {
+ nb[(nl + z) as usize] = raw8[(q + z) as usize];
+ z = z + 1;
+ }
+ nl = nl + z;
+ }
+ }
+ }
+ q = q + w;
+ units = units + u;
+ }
+ }
+ var vb : [512]u8 = [0; 512];
+ var vn : u32 = jv_vput_str(vb[0..], 0, "no", nb[0..(nl as usize)]);
+ n = div_open(dst, n, "br-center", "br-shop");
+ n = text(dst, n, SAY_SHOP);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-center", "");
+ n = jv_fill(dst, n, SAY_RECEIPT, vb[0..], vn, false);
+ n = raw(dst, n, "");
+ // when
+ n = div_open(dst, n, "br-center", "");
+ var wb : [1200]u8 = [0; 1200];
+ var wtmp : [1100]u8 = [0; 1100];
+ const wl : u32 = utc_raw(wtmp[0..], d, obj_get(d, r, "when_utc"));
+ var wn : u32 = jv_vput_str(wb[0..], 0, "when", wtmp[0..(wl as usize)]);
+ n = jv_fill(dst, n, SAY_WHEN, wb[0..], wn, false);
+ n = raw(dst, n, "");
+ // machine
+ n = div_open(dst, n, "br-center", "br-faint");
+ var mb : [1024]u8 = [0; 1024];
+ var mn : u32 = 0;
+ if (kind(d, mach) == J_OBJECT) {
+ mn = jv_vput_defined(mb[0..], mn, "cpu", d, obj_get(d, mach, "cpu"));
+ mn = jv_vput_defined(mb[0..], mn, "cores", d, obj_get(d, mach, "cores"));
+ mn = jv_vput_defined(mb[0..], mn, "macos", d, obj_get(d, mach, "macos"));
+ }
+ n = jv_fill(dst, n, SAY_MACHINE, mb[0..], mn, false);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-center", "br-faint");
+ var lb : [256]u8 = [0; 256];
+ var ln : u32 = 0;
+ const la : i64 = obj_get(d, mach, "loadavg_1m_at_start");
+ if (nullish(d, la)) { ln = jv_vput_str(lb[0..], 0, "load", ""); } else { ln = jv_vput_val(lb[0..], 0, "load", d, la); }
+ n = jv_fill(dst, n, SAY_LOAD, lb[0..], ln, false);
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ // design
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_LABEL_SPEC);
+ n = raw(dst, n, ": ");
+ n = nn_text(dst, n, d, obj_get(d, des, "spec"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_LABEL_PART);
+ n = raw(dst, n, ": ");
+ n = nn_text(dst, n, d, obj_get(d, des, "part"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-detail");
+ n = text(dst, n, SAY_LABEL_BOARD);
+ n = raw(dst, n, ": ");
+ n = nn_text(dst, n, d, obj_get(d, des, "board"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-faint br-detail");
+ var db : [1024]u8 = [0; 1024];
+ var dn : u32 = 0;
+ dn = jv_vput_nn(db[0..], dn, "name", d, obj_get(d, des, "datapath"));
+ dn = jv_vput_nn(db[0..], dn, "lines", d, obj_get(d, des, "wrapper_lines"));
+ n = jv_fill(dst, n, SAY_WRAPPER, db[0..], dn, false);
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ n = line_open(dst, n, "br-head");
+ n = text(dst, n, SAY_COL_ITEM);
+ n = line_mid(dst, n);
+ n = text(dst, n, SAY_COL_TIME);
+ n = line_close(dst, n);
+ // items
+ const st : i64 = obj_get(d, r, "stages");
+ const cnt : u32 = arr_len(d, st);
+ var i : u32 = 0;
+ while (i < cnt) {
+ n = stage_item(dst, n, d, arr_at(d, st, i), i);
+ i = i + 1;
+ }
+ n = rule_html(dst, n);
+ // totals
+ n = div_open(dst, n, "br-line", "");
+ var tb : [256]u8 = [0; 256];
+ var tn : u32 = jv_vput_val(tb[0..], 0, "ok", d, obj_get(d, r, "stages_ok"));
+ tn = jv_vput_val(tb[0..], tn, "count", d, obj_get(d, r, "stage_count"));
+ n = jv_fill(dst, n, SAY_ITEMS, tb[0..], tn, false);
+ n = raw(dst, n, "");
+ const total_ms : f64 = jv_num(d, obj_get(d, r, "total_ms"));
+ if (jv_truthy(d, obj_get(d, r, "complete"))) {
+ n = line_open(dst, n, "br-total");
+ n = text(dst, n, SAY_TOTAL);
+ n = line_mid(dst, n);
+ n = secs_text(dst, n, total_ms);
+ n = line_close(dst, n);
+ } else {
+ n = div_open(dst, n, "br-sub", "br-bad br-total-miss");
+ var sb : [256]u8 = [0; 256];
+ var stmp : [200]u8 = [0; 200];
+ const sl : u32 = secs_text_raw(stmp[0..], total_ms);
+ const sn : u32 = jv_vput_str(sb[0..], 0, "s", stmp[0..(sl as usize)]);
+ n = jv_fill(dst, n, SAY_NO_TOTAL, sb[0..], sn, false);
+ n = raw(dst, n, "");
+ }
+ var vt : [64]u8 = [0; 64];
+ var vtl : u32 = 0;
+ const vend : i64 = obj_get(d, r, "vendor_tools_run");
+ if (nullish(d, vend)) { vtl = jn_raw(vt[0..], 0, "0"); } else { vtl = jv_str(vt[0..], 0, d, vend); }
+ n = line_open(dst, n, "br-big");
+ n = text(dst, n, SAY_VIVADO);
+ n = line_mid(dst, n);
+ n = put_all(dst, n, vt[0..], vtl);
+ n = line_close(dst, n);
+ n = line_open(dst, n, "");
+ n = text(dst, n, SAY_VENDOR);
+ n = line_mid(dst, n);
+ n = put_all(dst, n, vt[0..], vtl);
+ n = line_close(dst, n);
+ n = div_open(dst, n, "br-sub", "br-faint");
+ var eb : [256]u8 = [0; 256];
+ var etmp : [200]u8 = [0; 200];
+ const el : u32 = secs_text_raw(etmp[0..], num_or0(d, obj_get(d, r, "wall_ms_end_to_end")));
+ const en : u32 = jv_vput_str(eb[0..], 0, "s", etmp[0..(el as usize)]);
+ n = jv_fill(dst, n, SAY_END_TO_END, eb[0..], en, false);
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ // the design that came out
+ const fm : i64 = obj_get(d, r, "fmax");
+ if (jv_truthy(d, fm)) {
+ const pass : bool = jv_truthy(d, obj_get(d, fm, "pass"));
+ if (pass) { n = line_open(dst, n, ""); } else { n = line_open(dst, n, "br-bad"); }
+ var fb : [512]u8 = [0; 512];
+ var fn_ : u32 = jv_vput_val(fb[0..], 0, "clock", d, obj_get(d, fm, "clock"));
+ fn_ = jv_vput_val(fb[0..], fn_, "mhz", d, obj_get(d, fm, "mhz"));
+ fn_ = jv_vput_val(fb[0..], fn_, "target", d, obj_get(d, fm, "target_mhz"));
+ n = jv_fill(dst, n, SAY_FMAX, fb[0..], fn_, false);
+ n = line_mid(dst, n);
+ if (pass) { n = text(dst, n, SAY_PASS); } else { n = text(dst, n, SAY_FAIL); }
+ n = line_close(dst, n);
+ }
+ const ut : i64 = obj_get(d, r, "utilisation");
+ var has_entries : bool = false;
+ if (jv_truthy(d, ut)) {
+ if (kind(d, ut) == J_ARRAY) { has_entries = arr_len(d, ut) > 0; }
+ if (kind(d, ut) == J_OBJECT) { has_entries = member_count(d, ut) > 0; }
+ }
+ if (has_entries) {
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_UTIL_TITLE);
+ n = raw(dst, n, "");
+ var ecount : u32 = 0;
+ if (kind(d, ut) == J_ARRAY) { ecount = arr_len(d, ut); } else { ecount = member_count(d, ut); }
+ var e : u32 = 0;
+ while (e < ecount) {
+ var kb : [512]u8 = [0; 512];
+ var kl : [1]u32 = [0; 1];
+ var val : i64 = NO_SPAN;
+ if (kind(d, ut) == J_ARRAY) {
+ val = arr_at(d, ut, e);
+ kl[0] = js_str_u64(kb[0..], 0, e as u64);
+ } else {
+ val = member_value(d, ut, e, kb[0..], kl[0..]);
+ }
+ n = line_open(dst, n, "br-small");
+ n = raw(dst, n, " ");
+ n = put_all(dst, n, kb[0..], kl[0]);
+ n = line_mid(dst, n);
+ n = jv_text(dst, n, d, arr_at(d, val, 0));
+ n = raw(dst, n, " / ");
+ n = jv_text(dst, n, d, arr_at(d, val, 1));
+ n = line_close(dst, n);
+ e = e + 1;
+ }
+ }
+ if (jv_truthy(d, fm) or jv_truthy(d, ut)) { n = rule_html(dst, n); }
+ // the bitstream
+ if (jv_truthy(d, bit)) {
+ n = div_open(dst, n, "br-sub", "");
+ n = text(dst, n, SAY_FILE_SHA);
+ n = raw(dst, n, ": ");
+ n = size_text(dst, n, d, obj_get(d, bit, "bytes"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-hex");
+ n = tc_text(dst, n, d, obj_get(d, bit, "sha256"));
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-faint");
+ n = text(dst, n, SAY_FILE_SHA_NOTE);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "");
+ var pb : [600]u8 = [0; 600];
+ var pstr : [600]u8 = [0; 600];
+ const pl : u32 = size_raw(pstr[0..], d, obj_get(d, bit, "payload_bytes"));
+ const pn : u32 = jv_vput_str(pb[0..], 0, "size", pstr[0..(pl as usize)]);
+ n = jv_fill(dst, n, SAY_PAYLOAD_SHA, pb[0..], pn, false);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-sub", "br-hex");
+ n = tc_text(dst, n, d, obj_get(d, bit, "payload_sha256"));
+ n = raw(dst, n, "");
+ const er : i64 = obj_get(d, r, "earlier_runs");
+ var ec : u32 = 0;
+ if (kind(d, er) == J_ARRAY) { ec = arr_len(d, er); }
+ if (ec > 0) {
+ const same : bool = jv_truthy(d, obj_get(d, r, "payload_same_as_earlier"));
+ if (same) { n = div_open(dst, n, "br-sub", ""); } else { n = div_open(dst, n, "br-sub", "br-bad"); }
+ if (same) {
+ var sb2 : [64]u8 = [0; 64];
+ const sn2 : u32 = jv_vput_f64(sb2[0..], 0, "n", ec as f64);
+ n = jv_fill(dst, n, SAY_SAME_PAYLOAD, sb2[0..], sn2, false);
+ } else {
+ n = text(dst, n, SAY_OTHER_PAYLOAD);
+ }
+ n = raw(dst, n, "");
+ var x : u32 = 0;
+ while (x < ec) {
+ const e2 : i64 = arr_at(d, er, x);
+ n = div_open(dst, n, "br-sub", "br-faint");
+ var xb : [1600]u8 = [0; 1600];
+ var xt : [1100]u8 = [0; 1100];
+ var xtl : u32 = utc_raw(xt[0..], d, obj_get(d, e2, "when_utc"));
+ var xn : u32 = jv_vput_str(xb[0..], 0, "when", xt[0..(xtl as usize)]);
+ var xs : [200]u8 = [0; 200];
+ const xsl : u32 = secs_text_raw(xs[0..], jv_num(d, obj_get(d, e2, "total_ms")));
+ xn = jv_vput_str(xb[0..], xn, "s", xs[0..(xsl as usize)]);
+ xn = jv_vput_val(xb[0..], xn, "load", d, obj_get(d, e2, "loadavg_1m_at_start"));
+ n = jv_fill(dst, n, SAY_EARLIER, xb[0..], xn, false);
+ n = raw(dst, n, "");
+ x = x + 1;
+ }
+ }
+ const cc : i64 = obj_get(d, r, "cross_check");
+ const cs : i64 = obj_get(d, cc, "status");
+ const cok : bool = jv_is(d, cs, "ok");
+ const ceq : bool = jv_truthy(d, obj_get(d, cc, "payload_equal"));
+ if (cok and !ceq) { n = div_open(dst, n, "br-sub", "br-bad"); } else { n = div_open(dst, n, "br-sub", ""); }
+ if (cok and ceq) {
+ var cb : [256]u8 = [0; 256];
+ const cn : u32 = jv_vput_val(cb[0..], 0, "ms", d, obj_get(d, cc, "ms"));
+ n = jv_fill(dst, n, SAY_CROSS_OK, cb[0..], cn, false);
+ } else {
+ if (cok) { n = text(dst, n, SAY_CROSS_BAD); } else { n = text(dst, n, SAY_CROSS_SKIP); }
+ }
+ n = raw(dst, n, "");
+ n = rule_html(dst, n);
+ n = barcode_html(dst, n, d, sha);
+ n = div_open(dst, n, "br-center", "br-hex br-tiny");
+ n = tc_text(dst, n, d, sha);
+ n = raw(dst, n, "");
+ }
+ n = div_open(dst, n, "br-center", "br-small");
+ n = text(dst, n, SAY_NOT_PROGRAMMED);
+ n = raw(dst, n, "");
+ n = div_open(dst, n, "br-center", "br-faint br-small");
+ n = text(dst, n, SAY_FOOTER);
+ n = raw(dst, n, "");
+ return raw(dst, n, "");
+}
+
+/// `x ?? ''` as a child / template-literal piece: the value's String(), nothing for null / missing.
+fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+/// A pool entry for `{key: x ?? ''}`.
+fn jv_vput_nn(vb: []u8, n0: u32, key: []const u8, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return jv_vput_str(vb, n0, key, ""); }
+ return jv_vput_val(vb, n0, key, d, sp);
+}
+
+/// utc(iso) as raw bytes (not escaped) into out; the length.
+fn utc_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ var l : u32 = 0;
+ if (!nullish(d, sp)) { l = jv_str(out, 0, d, sp); }
+ var k : u32 = 0;
+ var done : bool = false;
+ while (k < l and !done) {
+ if (out[k as usize] == 84) {
+ out[k as usize] = 32;
+ done = true;
+ }
+ k = k + 1;
+ }
+ if (l > 0 and out[(l - 1) as usize] == 90) {
+ var e : u32 = l - 1;
+ var j : u32 = e;
+ while (j > 0 and out[(j - 1) as usize] >= 48 and out[(j - 1) as usize] <= 57) { j = j - 1; }
+ if (j < e and j > 0 and out[(j - 1) as usize] == 46) { e = j - 1; }
+ l = e;
+ }
+ return l;
+}
+
+/// secs(ms) as raw bytes into out: SAY_SECONDS with (ms / 1000).toFixed(3).
+fn secs_text_raw(out: []u8, ms: f64) -> u32 {
+ var tmp : [128]u8 = [0; 128];
+ const l : u32 = js_to_fixed(tmp[0..], 0, ms / 1000.0, 3);
+ return fill_raw(out, SAY_SECONDS, tmp[0..(l as usize)]);
+}
+
+/// The template with {s} replaced by v, as raw (unescaped) bytes.
+fn fill_raw(out: []u8, t: []const u8, v: []const u8) -> u32 {
+ var n : u32 = 0;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 1] == 115 and t[i + 2] == 125) {
+ n = jn_raw(out, n, v);
+ i = i + 3;
+ } else {
+ n = jn_put(out, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if (DATA_STATE == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ if (DATA_STATE == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ERROR);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ n = raw(dst, n, "
");
+ var diff : [512]u8 = [0; 512];
+ const dl : u32 = spec_diff(diff[0..], 0, d, r);
+ if (dl > 0) {
+ n = raw(dst, n, "
");
+ var wb : [600]u8 = [0; 600];
+ const wn : u32 = jv_vput_str(wb[0..], 0, "what", diff[0..(dl as usize)]);
+ n = jv_fill(dst, n, SAY_SPEC_DIFF, wb[0..], wn, false);
+ n = raw(dst, n, "
");
+ }
+ n = paper_html(dst, n, d, r);
+ n = raw(dst, n, "
");
+ const cm : i64 = obj_get(d, r, "commands");
+ if (kind(d, cm) == J_ARRAY) {
+ const cc : u32 = arr_len(d, cm);
+ var c : u32 = 0;
+ while (c < cc) {
+ if (c > 0) { n = put(dst, n, 10); }
+ const el : i64 = arr_at(d, cm, c);
+ if (!nullish(d, el)) { n = jv_text(dst, n, d, el); }
+ c = c + 1;
+ }
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_VERIFY_TITLE);
+ n = raw(dst, n, "
");
+ var v : usize = 0;
+ while (v < SAY_VERIFY_STEPS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_VERIFY_STEPS[v]);
+ n = raw(dst, n, "
");
+ v = v + 1;
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ return n;
+}
From 9ec632829600595baccece4f5ab94f55694770e7 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 17:45:14 +0700
Subject: [PATCH 31/63] widgets: js_val gains jv_fill_raw, the filler without
escaping, for an SVG title (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/js_val.t27 | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/specs/widgets/js_val.t27 b/specs/widgets/js_val.t27
index 95d896102d..72d08c5782 100644
--- a/specs/widgets/js_val.t27
+++ b/specs/widgets/js_val.t27
@@ -578,6 +578,15 @@ pub fn jv_find(vb: []const u8, vn: u32, key: []const u8) -> u32 {
/// fill(template, pool): {key} becomes the pool's value (escaped), an unknown {key} stays as written.
pub fn jv_fill(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_only: bool) -> u32 {
+ return jv_fill_with(o, n0, t, vb, vn, digits_only, true);
+}
+
+/// jv_fill without the escaping, for a text that is a value itself (an SVG , a pool value).
+pub fn jv_fill_raw(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_only: bool) -> u32 {
+ return jv_fill_with(o, n0, t, vb, vn, digits_only, false);
+}
+
+pub fn jv_fill_with(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_only: bool, esc: bool) -> u32 {
var n : u32 = n0;
var i : usize = 0;
while (i < t.len) {
@@ -603,13 +612,13 @@ pub fn jv_fill(o: []u8, n0: u32, t: []const u8, vb: []const u8, vn: u32, digits_
const vl : u32 = (vb[(at - 2) as usize] as u32) * 256 + (vb[(at - 1) as usize] as u32);
var v : u32 = 0;
while (v < vl) {
- n = put_escaped(o, n, vb[(at + v) as usize]);
+ if (esc) { n = put_escaped(o, n, vb[(at + v) as usize]); } else { n = put(o, n, vb[(at + v) as usize]); }
v = v + 1;
}
}
i = closed + 1;
} else {
- n = put_escaped(o, n, t[i]);
+ if (esc) { n = put_escaped(o, n, t[i]); } else { n = put(o, n, t[i]); }
i = i + 1;
}
}
From 55741ca8415045665a69a53c41b05d3293195f90 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 17:45:14 +0700
Subject: [PATCH 32/63] widgets: uart-bucket drawn natively -- 32562-byte wasm,
3288 DOM comparisons, 0 differences (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/uart_bucket.t27 | 1141 +++++++++++++++++++++++++++++++++
1 file changed, 1141 insertions(+)
create mode 100644 specs/widgets/uart_bucket.t27
diff --git a/specs/widgets/uart_bucket.t27 b/specs/widgets/uart_bucket.t27
new file mode 100644
index 0000000000..bc169f5915
--- /dev/null
+++ b/specs/widgets/uart_bucket.t27
@@ -0,0 +1,1141 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/uart-bucket.t27 -- the bucket under the tap: answers waiting in a 512-byte UART buffer
+; Source of truth for public/widgets/uart-bucket/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, and the buffer rule is K_ constants.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: the Mac keeps a window of requests in flight to the AX7203 node; every answer the
+; node sends back is 19 bytes and passes through a CP2102N USB-UART bridge with a 512-byte receive
+; buffer. The node has no flow control, so while the Mac pauses reading, up to window x 19 bytes of
+; answers wait in that buffer. The reader drags the window and the bucket fills; past 512 bytes it
+; spills. Below it, the measured runs: two 2,016,000-job diagnostic arms (window 64 and window 24)
+; and two single 403,200-job runs at the edge (window 26 and window 30), then what the link carried.
+
+; WHERE THE NUMBERS COME FROM: scripts/widget-data/uart-bucket.mjs reads the record files of
+; gHashTag/trinity-fpga with git show at the commits named in DATA_SOURCES, parses every number,
+; checks the K_ constants below against the diagnostic's own spec, and writes data.json; the page
+; reads only that file. scripts/widget-data/uart-bucket-card.py draws card.png from data.json.
+
+; WHAT IT DOES NOT CLAIM: that the buffer is the cause. The diagnostic's own record says the pattern
+; fits the buffer and that nothing proves it. One USB path, one host, one evening. The model run
+; below used synthetic activation vectors; no text came out of it, and text generation ran on the Mac.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_uart_bucket;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "uart-bucket";
+pub const TITLE : str = "The bucket under the tap: a 512-byte UART buffer and 19-byte answers";
+pub const DESCRIPTION : str = "Drag the number of requests in flight and watch the answers fill a CP2102N's 512-byte receive buffer, then read the measured runs on the AX7203 board.";
+pub const IMAGE_ALT : str = "The bucket under the tap card: a CP2102N receive buffer holds 512 bytes and each answer is 19 bytes, so 26 answers fit. Window 64 (1216 bytes in flight) lost 574 of 2016000 answers in 424.3 s; window 24 (456 bytes) lost 0 in 427.5 s, on the AX7203 board.";
+pub const HOOK : str = "512 bytes of buffer, 19 bytes per answer: 26 fit. Window 64 lost 574 answers in 2,016,000 jobs; window 24 lost none and took the same time.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["gHashTag/trinity-fpga conformance/UART_LOSS_DIAG.md at commit 5a3111806: the window-64 and window-24 diagnostic arms (2,016,000 jobs each), the 512 / 19 rule, the wire time of the requests and the caveat", "gHashTag/trinity-fpga specs/trinet/uart_loss_diag_ax7203.t27 at commit df78b3249: REQ_LEN, RESP_LEN, CP2102N_RX_BYTES, ARM_WINDOWS and JOBS_PER_PASS, checked against the K_ constants of this spec", "gHashTag/trinity-fpga conformance/TERN_TC_LAYER_RECEIPTS.md at commit e1834ee6e: the --all runs at window 24 (rerun step 1), window 26 and window 30 (rerun step 3), harness tern_tc_layer_ax7203.py", "gHashTag/trinity-fpga conformance/TERN_TC_GENERATE.md at commit b2bd5a30c: the layer run used synthetic activation vectors and generation ran on the Mac", "scripts/widget-data/uart-bucket.mjs parses those files with git show at those commits into public/widgets/uart-bucket/data.json; scripts/widget-data/uart-bucket-card.py draws card.png from it"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The rule --------------------------------------------------------------------------------
+; The buffer and the frame sizes; uart-bucket.mjs fails if they drift from the diagnostic's spec.
+pub const K_RX_BYTES : u16 = 512;
+pub const K_ANSWER_BYTES : u16 = 19;
+pub const K_REQUEST_BYTES : u16 = 24;
+pub const K_LARGEST_FIT : u16 = 26;
+; The two diagnostic arms and the two single runs at the edge.
+pub const K_ARM_WINDOWS : [2]u16 = [64, 24];
+pub const K_EDGE_WINDOWS : [2]u16 = [26, 30];
+; The slider.
+pub const K_WINDOW_MIN : u16 = 1;
+pub const K_WINDOW_MAX : u16 = 64;
+pub const K_WINDOW_START : u16 = 64;
+; One fill-and-drain cycle of the animation, in milliseconds; none when the reader asks for less motion.
+pub const K_CYCLE_MS : u16 = 3600;
+pub const K_DATA_FILE : str = "./data.json";
+
+; --- Words -----------------------------------------------------------------------------------
+; {name} is filled by tool.js from data.json or from the K_ constants; nothing else is.
+pub const SAY_LOADING : str = "Reading the measured runs...";
+pub const SAY_LOAD_FAILED : str = "The data file could not be read.";
+pub const SAY_INTRO : str = "The Mac keeps a window of requests in flight to the {board} node. Every answer coming back is {answer} bytes. While the Mac pauses reading, answers wait in the USB-UART bridge's {rx}-byte receive buffer; the node has no flow control, so nothing tells it to wait.";
+pub const SAY_TAP : str = "node answers, {answer} B each";
+pub const SAY_BUCKET : str = "CP2102N receive buffer";
+pub const SAY_BRIM : str = "{rx} B";
+pub const SAY_DRAIN : str = "the Mac reads";
+pub const SAY_SPILL : str = "lost";
+pub const SAY_WINDOW_LABEL : str = "Requests in flight (window)";
+pub const SAY_FEWER : str = "One request fewer";
+pub const SAY_MORE : str = "One request more";
+pub const SAY_MINUS : str = "-";
+pub const SAY_PLUS : str = "+";
+pub const SAY_READOUT : str = "{w} x {answer} B = {bytes} B of answers can be waiting; the buffer holds {rx} B.";
+pub const SAY_FITS : str = "Fits, {spare} B to spare, however long the Mac pauses.";
+pub const SAY_OVER : str = "Over by {over} B: if the Mac pauses long enough, answer bytes are lost.";
+pub const SAY_RULE : str = "{rx} / {answer} = {ratio}, so {fit} is the largest window that fits.";
+pub const SAY_MOTION_OFF : str = "Animation off: your system asks for reduced motion. The bucket shows the full level.";
+pub const SAY_RUNS_TITLE : str = "Measured on the {board}: {jobs} jobs per arm, one arm each";
+pub const SAY_ARM_HEAD : str = "window {w}";
+pub const SAY_ARM_ROWS : [6]str = ["bytes in flight", "jobs sent", "answers accepted", "answers lost", "loss events", "elapsed"];
+pub const SAY_SECONDS : str = "s";
+pub const SAY_SHOW : str = "Put window {w} in the bucket";
+pub const SAY_SAME_TIME : str = "The smaller window cost no time: {t64} s against {t24} s. The requests are the slow direction: {req} B x 10 bit at {baud} baud x {jobs} jobs = {wire} s on the wire.";
+pub const SAY_EDGE_TITLE : str = "Two single runs at the edge, {planned} jobs planned each";
+pub const SAY_EDGE_PASS : str = "window {w} ({bytes} B): {ok} of {planned} receipts verified, no slip";
+pub const SAY_EDGE_FAIL : str = "window {w} ({bytes} B): stopped at the first slip after {sent} jobs sent; {ok} of {planned} receipts verified";
+pub const SAY_CAVEAT : str = "What this does not show: the diagnostic's own record says the pattern fits the buffer, but nothing proves the buffer is the cause. One USB path (a Genesys hub), one host, one evening; nothing about other hosts or a hub-free path.";
+pub const SAY_CARRIED_TITLE : str = "What the link then carried, at window {w}";
+pub const SAY_CARRIED_LINE : str = "The {board} board multiplied every ternary matrix of a small language model (igla-coder, tern_tc). Every answer carried a signed receipt, checked by the Mac.";
+pub const SAY_CARRIED_ROWS : [6]str = ["ternary matrices", "rows", "ternary weights", "receipts verified", "rows bit-exact", "elapsed"];
+pub const SAY_RATE : str = "{rate} answers/s";
+pub const SAY_CARRIED_PLAIN : str = "Plainly: the inputs were synthetic test vectors and no text came out of this run. Text generation ran on the Mac. Even in later generation runs, the Mac does everything except the ternary dot products.";
+pub const SAY_SOURCES : str = "Every number on this page is parsed from these files in gHashTag/trinity-fpga:";
+pub const SAY_AT : str = "at";
+
+; --- Tests -----------------------------------------------------------------------------------
+test twenty_six_answers_fit {
+ assert K_RX_BYTES / K_ANSWER_BYTES == K_LARGEST_FIT;
+ assert K_LARGEST_FIT * K_ANSWER_BYTES <= K_RX_BYTES;
+ assert K_ANSWER_BYTES * (K_LARGEST_FIT + 1) > K_RX_BYTES;
+ assert K_LARGEST_FIT * K_ANSWER_BYTES == 494;
+}
+
+test the_two_arms_sit_either_side_of_the_buffer {
+ assert K_ARM_WINDOWS[1] * K_ANSWER_BYTES <= K_RX_BYTES;
+ assert K_RX_BYTES < K_ARM_WINDOWS[0] * K_ANSWER_BYTES;
+ assert K_ARM_WINDOWS[0] * K_ANSWER_BYTES == 1216;
+ assert K_ARM_WINDOWS[1] * K_ANSWER_BYTES == 456;
+}
+
+test the_edge_runs_bracket_the_buffer {
+ assert K_EDGE_WINDOWS[0] == K_LARGEST_FIT;
+ assert K_EDGE_WINDOWS[0] * K_ANSWER_BYTES <= K_RX_BYTES;
+ assert K_EDGE_WINDOWS[1] * K_ANSWER_BYTES > K_RX_BYTES;
+ assert K_EDGE_WINDOWS[1] * K_ANSWER_BYTES == 570;
+}
+
+test the_slider_reaches_every_measured_window {
+ assert K_WINDOW_MIN == 1;
+ assert K_WINDOW_MAX >= K_ARM_WINDOWS[0];
+ assert K_WINDOW_MIN <= K_ARM_WINDOWS[1];
+ assert K_WINDOW_START <= K_WINDOW_MAX;
+ assert K_REQUEST_BYTES > K_ANSWER_BYTES;
+}
+
+test the_page_sends_nothing {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert K_DATA_FILE == "./data.json";
+ assert CATEGORY == "fpga";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/uart-bucket/tool.js (228 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's plus:
+; w_input(i, text) the slider (input 0) now holds text
+; w_click(i) button i in document order: 0 fewer, 1 more, 2 and 3 the two arms' "put window
+; in the bucket", then one per edge run
+; w_env(reduced, hidden) prefers-reduced-motion matches / document.hidden, before the data and whenever
+; either changes; w_resync() then re-runs set() as tool.js's two listeners do
+; w_now(ms) performance.now(), before every event that can start the animation
+; w_anim() 1 while the animation wants frames (host: keep requestAnimationFrame running)
+; w_frame(now, dst) one animation frame at time `now`: the DOM changes it makes, as patch lines in
+; dst, length. A patch line is `selector@index TAB kind TAB name TAB value LF`,
+; kind a = setAttribute, s = style[name], t = textContent, c = classList.toggle
+; (value 1 or 0); the host runs it with querySelectorAll(selector)[index]
+; w_render(dst) the page's HTML, length (0 on overflow). It holds the LAST DRAWN animation state,
+; so a re-render after an event shows what tool.js's nodes would still hold.
+; The bucket is drawn by the same rules as tool.js: the level, the spill and the three drops are numbers
+; from the cycle clock ((now - start) mod K_CYCLE_MS) written with toFixed(1); the readout, verdict and
+; the SVG follow the slider. Numbers are written the JavaScript way (js_num.t27: toLocaleString for
+; fmt(), toFixed, String(x) of the slider value), values read the way String() reads them (js_val.t27).
+; Where tool.js throws while it draws (arms without the two windows, no edges, a null wire / carried /
+; sources entry) its catch shows SAY_LOAD_FAILED and starts no animation; so does this page (data_ok).
+; NOT carried over: the data file is cut at DATA_CAP bytes (the page then says it could not be read);
+; classList.toggle on happy-dom normalises a class string even when nothing changes (a trailing space of
+; "ub-arm " goes at the first set()); this page writes the normalised string, a browser keeps the space.
+; MEASURED 2026-10-19: t27c 0.5.2 gen + an 11-function Zig export wrapper + zig build-exe wasm32-freestanding
+; ReleaseSmall give a 32562-byte wasm. The original page (tool.js) and this one ran in two happy-dom
+; windows against the real 2040-byte data.json and 95 variants of it, 3288 comparisons of tag, attributes
+; (the animated y / height / class / style / x) and text, with a VIRTUAL clock and requestAnimationFrame
+; on both sides (the same timestamps, so the cycle is compared at 1360 instants across the fill, hold and
+; drain phases, from 16 ms to 20 s apart), 561 slider values (in range, fractional, blank, not a number),
+; 409 presses of fewer / more, 364 presses of the measured-run buttons, 186 changes of prefers-reduced-motion
+; and 216 of document.hidden (the animation stops and a still frame is drawn, then restarts), runs that
+; start reduced / hidden / at a late or fractional clock, data with a markup / null / number board, lost
+; counts zero / string / null, in-flight at the 512 edge, huge / negative / null numbers, swapped / extra /
+; duplicate arms, a string window, three / one / all-slipped / none-slipped edges, odd wire and carried
+; fields, no generation line, empty / odd sources, 14 files the page cannot draw (an arm missing, no edges,
+; a null edge / wire / carried / source, a root that is null or an array): 0 differences. A negative
+; control (one attribute of the native DOM changed) is caught.
+; Not compared, and why: real time (requestAnimationFrame and performance.now are virtual: happy-dom has
+; no frame clock, so smoothness and the 60 Hz cadence are not measured); CSS (happy-dom loads none, so the
+; opacity / transform the stylesheet adds is not seen); focus (range.focus() after a "put window" press is
+; not a DOM difference); real pointer or media-query events.
+
+pub const DATA_CAP : u32 = 16384;
+pub var DATA : [16384]u8 = [0; 16384];
+pub var DATA_LEN : u32 = 0;
+pub var DATA_STATE : u8 = 0;
+pub var WIN : f64 = 64.0;
+pub var TARGET : f64 = 0.0;
+pub var START : f64 = 0.0;
+pub var RAF : bool = false;
+pub var NOW : f64 = 0.0;
+pub var REDUCED : bool = false;
+pub var HIDDEN : bool = false;
+pub var NOTE : bool = false;
+pub var DRAWN : bool = false;
+pub var D_WATER_Y : f64 = 268.0;
+pub var D_OVER : bool = false;
+pub var D_SPILL : u8 = 0;
+pub var D_LVL_TXT : [48]u8 = [0; 48];
+pub var D_LVL_LEN : u32 = 0;
+pub var D_LVL_X : f64 = 0.0;
+pub var D_LVL_Y : f64 = 0.0;
+pub var D_DROP_OP : [3]u8 = [0; 3];
+pub var D_DROP_Y : [3]f64 = [0.0; 3];
+pub var D_DROP_SET : [3]bool = [false; 3];
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_STATE = 0;
+ DATA_LEN = 0;
+ RAF = false;
+ DRAWN = false;
+ NOTE = false;
+ WIN = 64.0;
+ TARGET = 0.0;
+ D_SPILL = 0;
+ D_OVER = false;
+ D_WATER_Y = 268.0;
+ D_LVL_LEN = 0;
+ D_DROP_OP[0] = 0; D_DROP_OP[1] = 0; D_DROP_OP[2] = 0;
+ D_DROP_SET[0] = false; D_DROP_SET[1] = false; D_DROP_SET[2] = false;
+ return 0;
+}
+
+pub fn w_env(reduced: u32, hidden: u32) -> u32 {
+ REDUCED = reduced != 0;
+ HIDDEN = hidden != 0;
+ return 0;
+}
+
+pub fn w_now(ms: f64) -> u32 {
+ NOW = ms;
+ return 0;
+}
+
+pub fn w_anim() -> u32 {
+ if (RAF) { return 1; }
+ return 0;
+}
+
+fn nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+/// The arm whose window is `w` (a number equal to it), NO_SPAN when none.
+fn arm_of(d: []const u8, r: i64, w: u16) -> i64 {
+ const arms : i64 = obj_get(d, r, "arms");
+ const cnt : u32 = arr_len(d, arms);
+ var i : u32 = 0;
+ while (i < cnt) {
+ const a : i64 = arr_at(d, arms, i);
+ const v : i64 = obj_get(d, a, "window");
+ if (kind(d, v) == J_NUMBER and jv_num(d, v) == (w as f64)) { return a; }
+ i = i + 1;
+ }
+ return NO_SPAN;
+}
+
+/// Would draw() in tool.js get to its last line without a TypeError?
+fn data_ok(d: []const u8, r: i64) -> bool {
+ if (nullish(d, r)) { return false; }
+ if (nullish(d, arm_of(d, r, K_ARM_WINDOWS[0])) or nullish(d, arm_of(d, r, K_ARM_WINDOWS[1]))) { return false; }
+ const ed : i64 = obj_get(d, r, "edges");
+ if (kind(d, ed) != J_ARRAY) { return false; }
+ const ec : u32 = arr_len(d, ed);
+ if (ec == 0) { return false; }
+ var i : u32 = 0;
+ while (i < ec) {
+ if (nullish(d, arr_at(d, ed, i))) { return false; }
+ i = i + 1;
+ }
+ if (nullish(d, obj_get(d, r, "wire")) or nullish(d, obj_get(d, r, "carried"))) { return false; }
+ const so : i64 = obj_get(d, r, "sources");
+ if (kind(d, so) != J_ARRAY) { return false; }
+ const sc : u32 = arr_len(d, so);
+ i = 0;
+ while (i < sc) {
+ if (nullish(d, arr_at(d, so, i))) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+// --- the bucket's numbers -----------------------------------------------------------------------
+
+fn rxf() -> f64 {
+ return K_RX_BYTES as f64;
+}
+
+fn level_y(frac: f64) -> f64 {
+ return 268.0 - (268.0 - 104.0) * js_min(frac, 1.0);
+}
+
+/// xAt(y, +1): the right wall at height y.
+fn x_right(y: f64) -> f64 {
+ const t : f64 = (y - 104.0) / (268.0 - 104.0);
+ return 256.0 + (234.0 - 256.0) * t;
+}
+
+/// JavaScript's a % b for doubles: the sign of a, |a| mod |b| exactly.
+fn fmod(a: f64, b: f64) -> f64 {
+ if (a != a or b != b or b == 0.0 or (a - a) != 0.0) { return js_nan(0.0); }
+ var neg : bool = a < 0.0;
+ var x : f64 = a;
+ if (neg) { x = 0.0 - a; }
+ var m : f64 = b;
+ if (m < 0.0) { m = 0.0 - m; }
+ if (m > x) { return a; }
+ // subtract b * 2^k from the top down; every step is exact (x and the subtrahend are within a factor 2)
+ var scaled : f64 = m;
+ var steps : u32 = 0;
+ while (scaled * 2.0 <= x and steps < 1100) {
+ scaled = scaled * 2.0;
+ steps = steps + 1;
+ }
+ while (scaled >= m) {
+ if (x >= scaled) { x = x - scaled; }
+ scaled = scaled / 2.0;
+ }
+ if (neg) { return 0.0 - x; }
+ return x;
+}
+
+/// draw(bytes, overflowing, dropPhase) of tool.js's animator, into the last-drawn state D_*.
+fn draw_state(bytes: f64, over: bool, has_phase: bool, phase: f64) -> u32 {
+ const y : f64 = level_y(bytes / rxf());
+ DRAWN = true;
+ D_WATER_Y = y;
+ D_OVER = over;
+ if (over) { D_SPILL = 49; } else { D_SPILL = 48; }
+ var tmp : [48]u8 = [0; 48];
+ const l : u32 = js_locale_num(tmp[0..], 0, js_round(js_min(bytes, rxf())));
+ var q : u32 = 0;
+ while (q < l and q < 44) {
+ D_LVL_TXT[q as usize] = tmp[q as usize];
+ q = q + 1;
+ }
+ D_LVL_TXT[q as usize] = 32;
+ D_LVL_TXT[(q + 1) as usize] = 66;
+ D_LVL_LEN = q + 2;
+ D_LVL_X = x_right(y) + 8.0;
+ D_LVL_Y = y + 4.0;
+ var i : u32 = 0;
+ while (i < 3) {
+ if (!has_phase) {
+ D_DROP_OP[i as usize] = 48;
+ } else {
+ const p : f64 = fmod(phase + (i as f64) / 3.0, 1.0);
+ const bottom : f64 = js_max(level_y(bytes / rxf()) - 8.0, 56.0);
+ D_DROP_Y[i as usize] = 56.0 + (bottom - 56.0) * p;
+ D_DROP_SET[i as usize] = true;
+ D_DROP_OP[i as usize] = 49;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+fn still() -> u32 {
+ return draw_state(TARGET, TARGET > rxf(), false, 0.0);
+}
+
+/// animator.set(bytes)
+fn anim_set(bytes: f64) -> u32 {
+ TARGET = bytes;
+ if (REDUCED or HIDDEN) {
+ RAF = false;
+ still();
+ return 0;
+ }
+ if (!RAF) {
+ START = NOW;
+ RAF = true;
+ }
+ return 0;
+}
+
+/// set(w) of tool.js's page.
+fn set_win(w0: f64) -> u32 {
+ const lo : f64 = K_WINDOW_MIN as f64;
+ const hi : f64 = K_WINDOW_MAX as f64;
+ WIN = js_max(lo, js_min(hi, js_round(w0)));
+ anim_set(WIN * (K_ANSWER_BYTES as f64));
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (s.len > (DATA_CAP as usize)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) {
+ DATA[k] = s[k];
+ k = k + 1;
+ }
+ DATA_LEN = s.len as u32;
+ DATA_STATE = 1;
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (!jv_valid(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ if (!data_ok(d, root(d))) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ NOTE = REDUCED;
+ set_win(K_WINDOW_START as f64);
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ DATA_STATE = 2;
+ return 0;
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ if (i == 0) { set_win(jv_number_of_text(s, s.len as u32)); }
+ return 0;
+}
+
+pub fn w_resync() -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ set_win(WIN);
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ if (i == 0) { set_win(WIN - 1.0); }
+ if (i == 1) { set_win(WIN + 1.0); }
+ if (i >= 2) {
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ if (i == 2) { set_win(jv_num(d, obj_get(d, arm_of(d, r, K_ARM_WINDOWS[0]), "window"))); }
+ if (i == 3) { set_win(jv_num(d, obj_get(d, arm_of(d, r, K_ARM_WINDOWS[1]), "window"))); }
+ if (i >= 4) {
+ const ed : i64 = obj_get(d, r, "edges");
+ if (i - 4 < arr_len(d, ed)) { set_win(jv_num(d, obj_get(d, arr_at(d, ed, i - 4), "window"))); }
+ }
+ }
+ return 0;
+}
+
+// --- the frame ----------------------------------------------------------------------------------
+
+fn patch(dst: []u8, n0: u32, sel: []const u8, ix: u32, kind_c: u8, name: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, sel);
+ n = put(dst, n, 64);
+ n = js_str_u64(dst, n, ix as u64);
+ n = put(dst, n, 9);
+ n = put(dst, n, kind_c);
+ n = put(dst, n, 9);
+ n = raw(dst, n, name);
+ return put(dst, n, 9);
+}
+
+/// The patches that bring a page showing the previous state to the state D_*.
+fn emit_patches(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = patch(dst, n0, ".ub-water", 0, 97, "y");
+ n = js_to_fixed(dst, n, D_WATER_Y, 1);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-water", 0, 97, "height");
+ n = js_to_fixed(dst, n, 268.0 - D_WATER_Y, 1);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-water", 0, 99, "is-over");
+ if (D_OVER) { n = put(dst, n, 49); } else { n = put(dst, n, 48); }
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-spill", 0, 115, "opacity");
+ n = put(dst, n, D_SPILL);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-level", 0, 116, "");
+ var q : u32 = 0;
+ while (q < D_LVL_LEN) {
+ n = put(dst, n, D_LVL_TXT[q as usize]);
+ q = q + 1;
+ }
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-level", 0, 97, "x");
+ n = js_to_fixed(dst, n, D_LVL_X, 1);
+ n = put(dst, n, 10);
+ n = patch(dst, n, ".ub-level", 0, 97, "y");
+ n = js_to_fixed(dst, n, D_LVL_Y, 1);
+ n = put(dst, n, 10);
+ var i : u32 = 0;
+ while (i < 3) {
+ if (D_DROP_OP[i as usize] == 49) {
+ n = patch(dst, n, ".ub-drop", i, 97, "y");
+ n = js_to_fixed(dst, n, D_DROP_Y[i as usize], 1);
+ n = put(dst, n, 10);
+ }
+ n = patch(dst, n, ".ub-drop", i, 115, "opacity");
+ n = put(dst, n, D_DROP_OP[i as usize]);
+ n = put(dst, n, 10);
+ i = i + 1;
+ }
+ return n;
+}
+
+/// One animation frame at time `now`; the patch lines, their length (0 when no frame is wanted).
+pub fn w_frame(now: f64, dst: []u8) -> u32 {
+ if (!RAF or DATA_STATE != 1) { return 0; }
+ const cyc : f64 = K_CYCLE_MS as f64;
+ const t : f64 = fmod(now - START, cyc) / cyc;
+ var bytes : f64 = 0.0;
+ var has_phase : bool = false;
+ var phase : f64 = 0.0;
+ if (t < 0.55) {
+ bytes = TARGET * (t / 0.55);
+ has_phase = true;
+ phase = fmod((t / 0.55) * 4.0, 1.0);
+ } else {
+ if (t < 0.75) {
+ bytes = TARGET;
+ } else {
+ bytes = js_min(TARGET, rxf()) * (1.0 - (t - 0.75) / 0.25);
+ }
+ }
+ draw_state(bytes, bytes > rxf(), has_phase, phase);
+ return emit_patches(dst, 0);
+}
+
+// --- the page -----------------------------------------------------------------------------------
+
+/// fmt(n): toLocaleString for a number, String(n) for anything else.
+fn fmt_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) == J_NUMBER) { return js_locale_num(dst, n0, jv_num(d, sp)); }
+ return jv_text(dst, n0, d, sp);
+}
+
+/// fmt(x) as raw bytes (for a pool value).
+fn fmt_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) == J_NUMBER) { return js_locale_num(out, 0, jv_num(d, sp)); }
+ return jv_str(out, 0, d, sp);
+}
+
+fn fmt_f64_raw(out: []u8, v: f64) -> u32 {
+ return js_locale_num(out, 0, v);
+}
+
+fn bucket_html(dst: []u8, n0: u32, d: []const u8, r: i64, readout: []const u8, verdict: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = text(dst, n, readout);
+ n = put(dst, n, 32);
+ n = text(dst, n, verdict);
+ n = raw(dst, n, "");
+ n = nn_text(dst, n, d, obj_get(d, r, "board"));
+ n = raw(dst, n, "");
+ var vb : [128]u8 = [0; 128];
+ var vn : u32 = jv_vput_f64(vb[0..], 0, "answer", K_ANSWER_BYTES as f64);
+ n = jv_fill(dst, n, SAY_TAP, vb[0..], vn, false);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < 3) {
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_SPILL);
+ n = raw(dst, n, "");
+ var bb : [64]u8 = [0; 64];
+ const bn : u32 = jv_vput_f64(bb[0..], 0, "rx", rxf());
+ n = jv_fill(dst, n, SAY_BRIM, bb[0..], bn, false);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_DRAIN);
+ n = raw(dst, n, "");
+ if (DRAWN) {
+ var q : u32 = 0;
+ while (q < D_LVL_LEN) {
+ n = put_escaped(dst, n, D_LVL_TXT[q as usize]);
+ q = q + 1;
+ }
+ }
+ return raw(dst, n, "");
+}
+
+/// `x ?? ''` as a child.
+fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+fn arm_card(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ const inflight : i64 = obj_get(d, a, "inFlight");
+ const lost : i64 = obj_get(d, a, "lost");
+ const over : bool = jv_num(d, inflight) > rxf();
+ const w : i64 = obj_get(d, a, "window");
+ const shown : bool = kind(d, w) == J_NUMBER and jv_num(d, w) == WIN;
+ var n : u32 = raw(dst, n0, "
");
+ var vb : [256]u8 = [0; 256];
+ var vn : u32 = jv_vput_val(vb[0..], 0, "w", d, w);
+ n = jv_fill(dst, n, SAY_ARM_HEAD, vb[0..], vn, false);
+ n = raw(dst, n, "
");
+ var row : usize = 0;
+ while (row < SAY_ARM_ROWS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ARM_ROWS[row]);
+ n = raw(dst, n, "
0.0) { n = raw(dst, n, "ub-lost"); }
+ n = raw(dst, n, "\">");
+ if (row == 0) {
+ n = fmt_text(dst, n, d, inflight);
+ n = raw(dst, n, " B");
+ }
+ if (row == 1) { n = fmt_text(dst, n, d, obj_get(d, a, "jobsSent")); }
+ if (row == 2) { n = fmt_text(dst, n, d, obj_get(d, a, "accepted")); }
+ if (row == 3) { n = fmt_text(dst, n, d, lost); }
+ if (row == 4) { n = fmt_text(dst, n, d, obj_get(d, a, "events")); }
+ if (row == 5) {
+ n = jv_text(dst, n, d, obj_get(d, a, "elapsedS"));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_SECONDS);
+ }
+ n = raw(dst, n, "
");
+ row = row + 1;
+ }
+ n = raw(dst, n, "
");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if (DATA_STATE == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ if (DATA_STATE == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOAD_FAILED);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ // the readout and the verdict for the window
+ const bytes : f64 = WIN * (K_ANSWER_BYTES as f64);
+ const over : bool = bytes > rxf();
+ var rd : [256]u8 = [0; 256];
+ var rb : [256]u8 = [0; 256];
+ var tmp : [64]u8 = [0; 64];
+ var rv : u32 = jv_vput_f64(rb[0..], 0, "w", WIN);
+ rv = jv_vput_f64(rb[0..], rv, "answer", K_ANSWER_BYTES as f64);
+ var tl : u32 = fmt_f64_raw(tmp[0..], bytes);
+ rv = jv_vput_str(rb[0..], rv, "bytes", tmp[0..(tl as usize)]);
+ rv = jv_vput_f64(rb[0..], rv, "rx", rxf());
+ // the filled text, unescaped, for the : the fill escapes, so fill into a scratch and unescape
+ var ro : [512]u8 = [0; 512];
+ var rl : u32 = jv_fill_raw(ro[0..], 0, SAY_READOUT, rb[0..], rv, false);
+ var vd : [512]u8 = [0; 512];
+ var vb : [256]u8 = [0; 256];
+ var vl : u32 = 0;
+ if (over) {
+ tl = fmt_f64_raw(tmp[0..], bytes - rxf());
+ const vn : u32 = jv_vput_str(vb[0..], 0, "over", tmp[0..(tl as usize)]);
+ vl = jv_fill_raw(vd[0..], 0, SAY_OVER, vb[0..], vn, false);
+ } else {
+ tl = fmt_f64_raw(tmp[0..], rxf() - bytes);
+ const vn2 : u32 = jv_vput_str(vb[0..], 0, "spare", tmp[0..(tl as usize)]);
+ vl = jv_fill_raw(vd[0..], 0, SAY_FITS, vb[0..], vn2, false);
+ }
+ n = raw(dst, n, "
");
+ var ib : [512]u8 = [0; 512];
+ var in_ : u32 = jv_vput_val(ib[0..], 0, "board", d, obj_get(d, r, "board"));
+ in_ = jv_vput_f64(ib[0..], in_, "answer", K_ANSWER_BYTES as f64);
+ in_ = jv_vput_f64(ib[0..], in_, "rx", rxf());
+ n = jv_fill(dst, n, SAY_INTRO, ib[0..], in_, false);
+ n = raw(dst, n, "
");
+ n = bucket_html(dst, n, d, r, ro[0..(rl as usize)], vd[0..(vl as usize)]);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_BUCKET);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_WINDOW_LABEL);
+ n = raw(dst, n, "
");
+ n = put_all2(dst, n, ro[0..], rl);
+ n = raw(dst, n, "
");
+ n = put_all2(dst, n, vd[0..], vl);
+ n = raw(dst, n, "
");
+ var ub : [512]u8 = [0; 512];
+ var un : u32 = jv_vput_f64(ub[0..], 0, "rx", rxf());
+ un = jv_vput_f64(ub[0..], un, "answer", K_ANSWER_BYTES as f64);
+ un = jv_vput_val(ub[0..], un, "ratio", d, obj_get(d, r, "ratio"));
+ un = jv_vput_f64(ub[0..], un, "fit", K_LARGEST_FIT as f64);
+ n = jv_fill(dst, n, SAY_RULE, ub[0..], un, false);
+ n = raw(dst, n, "
");
+ if (NOTE) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_MOTION_OFF);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ var hb : [512]u8 = [0; 512];
+ var hn : u32 = jv_vput_val(hb[0..], 0, "board", d, obj_get(d, r, "board"));
+ var jt : [64]u8 = [0; 64];
+ const jl : u32 = fmt_raw(jt[0..], d, obj_get(d, r, "jobsPerArm"));
+ hn = jv_vput_str(hb[0..], hn, "jobs", jt[0..(jl as usize)]);
+ n = jv_fill(dst, n, SAY_RUNS_TITLE, hb[0..], hn, false);
+ n = raw(dst, n, "
");
+ const a64 : i64 = arm_of(d, r, K_ARM_WINDOWS[0]);
+ const a24 : i64 = arm_of(d, r, K_ARM_WINDOWS[1]);
+ n = arm_card(dst, n, d, a64);
+ n = arm_card(dst, n, d, a24);
+ n = raw(dst, n, "
");
+ const wire : i64 = obj_get(d, r, "wire");
+ var sb : [1024]u8 = [0; 1024];
+ var sn : u32 = jv_vput_val(sb[0..], 0, "t64", d, obj_get(d, a64, "elapsedS"));
+ sn = jv_vput_val(sb[0..], sn, "t24", d, obj_get(d, a24, "elapsedS"));
+ sn = jv_vput_val(sb[0..], sn, "req", d, obj_get(d, wire, "requestBytes"));
+ var bt : [64]u8 = [0; 64];
+ var btl : u32 = fmt_raw(bt[0..], d, obj_get(d, wire, "baud"));
+ sn = jv_vput_str(sb[0..], sn, "baud", bt[0..(btl as usize)]);
+ btl = fmt_raw(bt[0..], d, obj_get(d, wire, "jobs"));
+ sn = jv_vput_str(sb[0..], sn, "jobs", bt[0..(btl as usize)]);
+ sn = jv_vput_val(sb[0..], sn, "wire", d, obj_get(d, wire, "seconds"));
+ n = jv_fill(dst, n, SAY_SAME_TIME, sb[0..], sn, false);
+ n = raw(dst, n, "
");
+ const ed : i64 = obj_get(d, r, "edges");
+ var eb : [256]u8 = [0; 256];
+ var et : [64]u8 = [0; 64];
+ const el : u32 = fmt_raw(et[0..], d, obj_get(d, arr_at(d, ed, 0), "planned"));
+ const en : u32 = jv_vput_str(eb[0..], 0, "planned", et[0..(el as usize)]);
+ n = jv_fill(dst, n, SAY_EDGE_TITLE, eb[0..], en, false);
+ n = raw(dst, n, "
");
+ const ec : u32 = arr_len(d, ed);
+ var x : u32 = 0;
+ while (x < ec) {
+ const e : i64 = arr_at(d, ed, x);
+ const slipped : bool = jv_truthy(d, obj_get(d, e, "slipped"));
+ const shown : bool = kind(d, obj_get(d, e, "window")) == J_NUMBER and jv_num(d, obj_get(d, e, "window")) == WIN;
+ n = raw(dst, n, "
");
+ var xb : [1024]u8 = [0; 1024];
+ var xn : u32 = jv_vput_val(xb[0..], 0, "w", d, obj_get(d, e, "window"));
+ var xt : [64]u8 = [0; 64];
+ var xl : u32 = fmt_raw(xt[0..], d, obj_get(d, e, "inFlight"));
+ xn = jv_vput_str(xb[0..], xn, "bytes", xt[0..(xl as usize)]);
+ xl = fmt_raw(xt[0..], d, obj_get(d, e, "receipts"));
+ xn = jv_vput_str(xb[0..], xn, "ok", xt[0..(xl as usize)]);
+ xl = fmt_raw(xt[0..], d, obj_get(d, e, "receiptsOf"));
+ xn = jv_vput_str(xb[0..], xn, "planned", xt[0..(xl as usize)]);
+ xl = fmt_raw(xt[0..], d, obj_get(d, e, "jobsSent"));
+ xn = jv_vput_str(xb[0..], xn, "sent", xt[0..(xl as usize)]);
+ if (slipped) { n = jv_fill(dst, n, SAY_EDGE_FAIL, xb[0..], xn, false); } else { n = jv_fill(dst, n, SAY_EDGE_PASS, xb[0..], xn, false); }
+ n = raw(dst, n, "
");
+ x = x + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CAVEAT);
+ n = raw(dst, n, "
");
+ const c : i64 = obj_get(d, r, "carried");
+ var cb : [256]u8 = [0; 256];
+ var cn : u32 = jv_vput_val(cb[0..], 0, "w", d, obj_get(d, c, "window"));
+ n = jv_fill(dst, n, SAY_CARRIED_TITLE, cb[0..], cn, false);
+ n = raw(dst, n, "
");
+ cn = jv_vput_val(cb[0..], 0, "board", d, obj_get(d, r, "board"));
+ n = jv_fill(dst, n, SAY_CARRIED_LINE, cb[0..], cn, false);
+ n = raw(dst, n, "
");
+ var row : usize = 0;
+ while (row < SAY_CARRIED_ROWS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CARRIED_ROWS[row]);
+ n = raw(dst, n, "
");
+ if (row == 0) { n = fmt_text(dst, n, d, obj_get(d, c, "matrices")); }
+ if (row == 1) { n = fmt_text(dst, n, d, obj_get(d, c, "modelRows")); }
+ if (row == 2) { n = fmt_text(dst, n, d, obj_get(d, c, "weights")); }
+ if (row == 3) {
+ n = fmt_text(dst, n, d, obj_get(d, c, "receipts"));
+ n = raw(dst, n, " / ");
+ n = fmt_text(dst, n, d, obj_get(d, c, "receiptsOf"));
+ }
+ if (row == 4) {
+ n = fmt_text(dst, n, d, obj_get(d, c, "rows"));
+ n = raw(dst, n, " / ");
+ n = fmt_text(dst, n, d, obj_get(d, c, "rowsOf"));
+ }
+ if (row == 5) {
+ n = jv_text(dst, n, d, obj_get(d, c, "elapsedS"));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_SECONDS);
+ n = raw(dst, n, ", ");
+ var qb : [256]u8 = [0; 256];
+ var qt : [64]u8 = [0; 64];
+ const ql : u32 = fmt_raw(qt[0..], d, obj_get(d, c, "answersPerS"));
+ const qn : u32 = jv_vput_str(qb[0..], 0, "rate", qt[0..(ql as usize)]);
+ n = jv_fill(dst, n, SAY_RATE, qb[0..], qn, false);
+ }
+ n = raw(dst, n, "
");
+ row = row + 1;
+ }
+ n = raw(dst, n, "
");
+ if (jv_truthy(d, obj_get(d, r, "generationOnMac"))) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CARRIED_PLAIN);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_SOURCES);
+ n = raw(dst, n, "
");
+ const so : i64 = obj_get(d, r, "sources");
+ const sc : u32 = arr_len(d, so);
+ x = 0;
+ while (x < sc) {
+ const s : i64 = arr_at(d, so, x);
+ n = raw(dst, n, "
");
+ n = jv_text(dst, n, d, obj_get(d, r, "repo"));
+ n = put(dst, n, 32);
+ n = jv_text(dst, n, d, obj_get(d, s, "path"));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_AT);
+ n = put(dst, n, 32);
+ n = jv_text(dst, n, d, obj_get(d, s, "commit"));
+ n = raw(dst, n, "
");
+ x = x + 1;
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+fn put_all2(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < l) {
+ n = put_escaped(dst, n, s[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ return n;
+}
From 518a1827815aa94efa3b0b8d0605cfc82520c540 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:22:08 +0700
Subject: [PATCH 33/63] widgets: test-touch drawn natively -- 35775-byte wasm,
V8's TimSort ported, 786923-byte data in a 1 MiB buffer, 0 differences
(Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/test_touch.t27 | 1856 ++++++++++++++++++++++++++++++++++
1 file changed, 1856 insertions(+)
create mode 100644 specs/widgets/test_touch.t27
diff --git a/specs/widgets/test_touch.t27 b/specs/widgets/test_touch.t27
new file mode 100644
index 0000000000..df83e346e9
--- /dev/null
+++ b/specs/widgets/test_touch.t27
@@ -0,0 +1,1856 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/test-touch.t27 -- did the agent touch its own tests? Every merged pull request on gHashTag/t27
+; Source of truth for public/widgets/test-touch/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, and the rule is K_ constants. The data script
+; scripts/widget-data/test-touch.mjs reads the K_ rule from this file, runs the fixture diffs below
+; through its classifier, and exits non-zero when its counts disagree with K_FIX_* or K_SNAP_*.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: a dated snapshot. For every pull request merged on gHashTag/t27 in the window, the
+; script reads the patch that landed on master (git diff ^1 , the whole file as context
+; for .t27 files) and gives it one badge: tests untouched, tests added, tests changed, or assert
+; removed. Clicking a pull request shows the test lines it changed and links to it on GitHub. The
+; same is done for the human lane, so the bee lane has something to be compared with.
+
+; THE LANE RULE: a pull request is the bee's when its head branch is queen- (the race-the-bee
+; rule); every other one is the human lane, which means human-steered, not hand-typed.
+
+; WHAT IS A TEST LINE: a line inside a test, invariant or bench block of a .t27 file; any line of a
+; file under a tests/, test/ or conformance/ directory; any line of a *_test.* file (and test_*.py,
+; *.test.js, *.spec.ts). Not tests: .trinity/, .claude/, docs/ and .github/ (seals, notes and CI),
+; and JSON records under a test directory that carry a "generated_by" key (reports a tool wrote).
+
+; THE HEURISTIC: removed and added test lines are paired before they are judged. A line that only
+; moved is ignored. A test whose header was rewritten under the same name is "rewritten". A removed
+; check is paired with an added check on the same subject (the left side of the comparison) in the
+; same block first, then the same file. A pair is "expect" when the expected value changed, "loosened"
+; when the comparison got weaker (== to >=, a conjunct dropped, a disjunct added), "trivial" when the
+; new check cannot fail (true, x == x, len >= 0), "tightened" for the reverse. A removed check with no
+; partner, and a whole test block deleted, is "removed". The badge is "assert removed" when any check
+; or test block was removed or any check was loosened or made trivial; "tests changed" when other
+; test lines were removed; "tests added" when test lines were only added; else "tests untouched".
+
+; KNOWN FALSE POSITIVES: an assert merged, split or rewritten with a new subject reads as removed
+; (#5731 rewrote "invariant " checks as "then" checks); two checks in one file can be paired
+; across different tests; a quantifier added to an implication reads as an expected value change;
+; JSON test vectors reformatted (Infinity to "Infinity") or with a status renamed read as changed
+; expectations; a syntax-only rewrite of a test reads as "tests changed".
+; KNOWN FALSE NEGATIVES: a CI workflow or suite expectation file that stops running a test is not a
+; test line (the page flags those pull requests separately); asserts in code outside a test block or
+; test file are counted but not judged; a constant a test reads can change outside the test; an added
+; test that cannot fail is counted as "tests added", it is not judged.
+
+; WHAT IT DOES NOT CLAIM: intent. A removed test can be right: a test of a function that was deleted,
+; a placeholder test, a test that was false. The badge says the tests got smaller or weaker; the
+; reader decides whether that was gaming. It does not claim the lanes do the same work: most bee
+; merges are machine-filed port and test tasks. Numbers are from data.json, written by the script.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_test_touch;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "test-touch";
+pub const TITLE : str = "Did the agent touch its own tests? Every PR merged on t27, checked";
+pub const DESCRIPTION : str = "Every pull request merged on gHashTag/t27 in 14 days, bee lane and human lane: which added tests, changed them or removed asserts, with the exact test lines from git.";
+pub const IMAGE_ALT : str = "Test-touch card, snapshot 2026-10-04 of gHashTag/t27 over 14 days: the Queen's bees merged 367 pull requests, of which 11 removed tests or asserts, 32 changed tests, 238 added tests and 86 left them untouched. People merged 240, of which 17 removed tests or asserts. One bee PR titled Implement the 1 empty function body deleted all 26 tests in its file.";
+pub const HOOK : str = "Bees landed 367 PRs on t27 in 14 days. 11 deleted tests or asserts; one titled 'Implement the 1 empty function body' deleted all 26 tests in its file.";
+pub const CATEGORY : str = "compiler";
+pub const DATA_SOURCES : [3]str = ["gh 2.83.2, gh api graphql on gHashTag/t27: search is:pr is:merged, one day at a time, with head branch, author, merge commit and the file list GitHub reports", "git 2.52.0 on a shallow bare clone of t27 master: git diff -M ^1 , -U1000000 for .t27 files and -U0 for the rest", "node scripts/widget-data/test-touch.mjs: classifies every patch with the rule in this spec and writes public/widgets/test-touch/data.json with its UTC time"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The rule --------------------------------------------------------------------------------
+pub const K_REPO : str = "gHashTag/t27";
+pub const K_BEE_BRANCH_PREFIX : str = "queen-";
+pub const K_WINDOW_DAYS : u8 = 14;
+pub const K_DATA_FILE : str = "./data.json";
+pub const K_LANES : [2]str = ["bee", "human"];
+pub const K_TEST_DIRS : [3]str = ["tests", "test", "conformance"];
+pub const K_TEST_NAME_MARK : str = "_test.";
+pub const K_NOT_TEST_DIRS : [4]str = [".trinity", ".claude", "docs", ".github"];
+pub const K_GENERATED_KEY : str = "generated_by";
+pub const K_TEST_BLOCK_WORDS : [3]str = ["test", "invariant", "bench"];
+pub const K_TAGS : [8]str = ["removed", "trivial", "loosened", "expect", "rewritten", "tightened", "added", "line"];
+
+; --- Fixtures --------------------------------------------------------------------------------
+; Each WANT vector is [badge, class, checks added, checks removed, expect, loosened, test lines
+; added, test lines removed, code lines]. Badge: 0 untouched, 1 added, 2 changed, 3 assert removed.
+; Class: 0 code only, 1 tests only, 2 both, 3 neither.
+pub const K_FIX_COUNT : u8 = 3;
+pub const SAY_FIX_0_PATH : str = "specs/demo/clamp.t27";
+pub const SAY_FIX_0_DIFF : [7]str = ["@@ -1,3 +1,6 @@", " module clamp;", "-pub const K_MAX : u8 = 3;", "+pub const K_MAX : u8 = 4;", "+test max_is_four {", "+ assert K_MAX == 4;", "+}"];
+pub const K_FIX_0_WANT : [9]u16 = [1, 2, 1, 0, 0, 0, 3, 0, 2];
+pub const SAY_FIX_1_PATH : str = "specs/demo/width.t27";
+pub const SAY_FIX_1_DIFF : [5]str = ["@@ -1,3 +1,3 @@", " test width_ok {", "- assert width(27) == 27;", "+ assert width(27) >= 20;", " }"];
+pub const K_FIX_1_WANT : [9]u16 = [3, 1, 0, 0, 0, 1, 1, 1, 0];
+pub const SAY_FIX_2_PATH : str = "bootstrap/tests/fmt.rs";
+pub const SAY_FIX_2_DIFF : [7]str = ["@@ -3,4 +3,4 @@", " fn fmt_zero() {", "- assert_eq!(fmt(0), 0);", "- assert!(ok(1));", "+ assert!(ok(1));", "+ assert_eq!(fmt(0), 1);", " }"];
+pub const K_FIX_2_WANT : [9]u16 = [2, 1, 0, 0, 1, 0, 2, 2, 0];
+
+; --- The snapshot ----------------------------------------------------------------------------
+; [pull requests, untouched, added, changed, assert removed, code only, tests only, both, neither,
+; PRs with an expected value changed, PRs with a check loosened]. Pasted from the script's output;
+; the script exits non-zero when data.json disagrees.
+pub const K_SNAP_DATE : str = "2026-10-04";
+pub const K_SNAP_BEE : [11]u16 = [367, 86, 238, 32, 11, 77, 37, 244, 9, 10, 1];
+pub const K_SNAP_HUMAN : [11]u16 = [240, 95, 69, 59, 17, 64, 10, 135, 31, 39, 2];
+
+; --- Words -----------------------------------------------------------------------------------
+; {name} is filled from data.json by tool.js; nothing else is.
+pub const SAY_SNAPSHOT : str = "Snapshot, not live: taken {at} UTC from GitHub and git, covering every pull request merged on {repo} in the {days} days before it.";
+pub const SAY_LOADING : str = "Reading the snapshot...";
+pub const SAY_LOAD_FAILED : str = "The snapshot file could not be read.";
+pub const SAY_LANE_NAMES : [2]str = ["Bee", "Human"];
+pub const SAY_LANE_LINES : [2]str = ["head branch queen-, written by a bee the Queen dispatched", "any other branch: a person steered the session, often with an AI assistant"];
+pub const SAY_MERGED : str = "merged pull requests";
+pub const SAY_REMOVED_OF : str = "removed or weakened a test";
+pub const SAY_BADGES : [4]str = ["tests untouched", "tests added", "tests changed", "assert removed"];
+pub const SAY_BADGE_LINES : [4]str = ["no test line changed", "test lines only added", "test lines removed or rewritten, no check lost", "a check or test block removed, or a check made weaker"];
+pub const SAY_CLASSES : [4]str = ["code only", "tests only", "code and tests", "neither"];
+pub const SAY_STAT_NAMES : [5]str = ["checks added", "checks removed", "test blocks removed", "expected values changed", "checks loosened"];
+pub const SAY_VERDICT_YES : str = "Yes. {beeRemoved} of {beePrs} bee pull requests removed test blocks or asserts, or made a check weaker: {beeBlocks} test blocks deleted in all. People: {humanRemoved} of {humanPrs}.";
+pub const SAY_VERDICT_NO : str = "No bee pull request in this window removed a test block or an assert, or made a check weaker.";
+pub const SAY_VERDICT_LAST : str = "The last bee one merged {last} UTC; the {after} bee pull requests merged after it removed none.";
+pub const SAY_FILTER_LANE : str = "Lane";
+pub const SAY_FILTER_BADGE : str = "Badge";
+pub const SAY_ALL : str = "All";
+pub const SAY_SHOWING : str = "{n} of {total} pull requests";
+pub const SAY_SHOW_MORE : str = "Show more";
+pub const SAY_NO_TITLE : str = "(title not in English)";
+pub const SAY_TAGS : [8]str = ["removed", "now trivial", "loosened", "expected value", "rewritten", "tightened", "added", "test line"];
+pub const SAY_TEST_FILES : str = "{n} of {all} changed files hold test lines";
+pub const SAY_CHECKS : str = "checks +{added} / -{removed}; test blocks removed {blocks}";
+pub const SAY_NO_LINES : str = "No test line changed in this pull request.";
+pub const SAY_MORE_LINES : str = "+{n} more test lines in the pull request";
+pub const SAY_OPEN_PR : str = "Open the pull request on GitHub";
+pub const SAY_CI_GATE : str = "Also changed a CI workflow or a suite expectation file; those are not counted as test lines.";
+pub const SAY_API_ONLY : str = "GitHub lists {n} more file(s) than the merge diff holds; the badge reads the merge diff.";
+pub const SAY_EXCLUDED : str = "Left out: {notMaster} merged pull requests whose merge commit is not on master.";
+pub const SAY_RULE_TITLE : str = "How a pull request is judged, and where it is wrong";
+pub const SAY_RULE : [5]str = ["Lane: the bee's when the head branch is queen-; every other pull request is the human lane.", "Test lines: test, invariant and bench blocks in .t27 files; files under tests/, test/ or conformance/; *_test.* files. Not seals, docs, CI, or JSON reports a tool generated.", "Diff: git diff ^1 , the patch that landed on master. Lines that only moved are ignored.", "Pairing: a removed check is paired with an added check on the same subject. Then expected value changed, loosened (== to >=, a condition dropped), trivial (cannot fail), tightened, or rewritten. Unpaired: removed.", "Badge: assert removed when a check or test block was removed or a check got weaker; tests changed when other test lines went; tests added when lines were only added."];
+pub const SAY_FALSE_POS_TITLE : str = "Known false positives";
+pub const SAY_FALSE_POS : [4]str = ["An assert merged, split or rewritten with a new subject reads as removed (#5731 rewrote invariant checks as then checks).", "Two checks in one file can be paired across different tests.", "Reformatted JSON vectors (Infinity to a string) or a renamed status read as changed expected values.", "A syntax-only rewrite of a test reads as tests changed."];
+pub const SAY_FALSE_NEG_TITLE : str = "Known false negatives";
+pub const SAY_FALSE_NEG : [4]str = ["A CI workflow or suite expectation that stops running a test is not a test line; those pull requests carry a note.", "Asserts in code outside a test block are counted, not judged.", "A constant a test reads can change outside the test.", "An added test that cannot fail still counts as tests added."];
+pub const SAY_NOT_INTENT : str = "A badge is not a verdict on intent: a removed test can be right (its function was deleted, it was a placeholder, it was false). It says the tests got smaller or weaker; the lines are there to judge.";
+
+; --- Tests -----------------------------------------------------------------------------------
+test every_pull_request_gets_one_badge_and_one_class {
+ assert K_SNAP_BEE[1] + K_SNAP_BEE[2] + K_SNAP_BEE[3] + K_SNAP_BEE[4] == K_SNAP_BEE[0];
+ assert K_SNAP_BEE[5] + K_SNAP_BEE[6] + K_SNAP_BEE[7] + K_SNAP_BEE[8] == K_SNAP_BEE[0];
+ assert K_SNAP_HUMAN[1] + K_SNAP_HUMAN[2] + K_SNAP_HUMAN[3] + K_SNAP_HUMAN[4] == K_SNAP_HUMAN[0];
+ assert K_SNAP_HUMAN[5] + K_SNAP_HUMAN[6] + K_SNAP_HUMAN[7] + K_SNAP_HUMAN[8] == K_SNAP_HUMAN[0];
+ assert K_SNAP_BEE[10] <= K_SNAP_BEE[4];
+ assert K_SNAP_HUMAN[10] <= K_SNAP_HUMAN[4];
+}
+
+test the_hook_matches_the_snapshot {
+ assert K_SNAP_BEE[0] == 367;
+ assert K_SNAP_BEE[4] == 11;
+ assert K_SNAP_HUMAN[4] == 17;
+ assert K_WINDOW_DAYS == 14;
+ assert K_SNAP_DATE == "2026-10-04";
+}
+
+test the_fixtures_cover_add_loosen_and_flip {
+ assert K_FIX_COUNT == 3;
+ assert K_FIX_0_WANT[0] == 1;
+ assert K_FIX_0_WANT[2] == 1;
+ assert K_FIX_1_WANT[0] == 3;
+ assert K_FIX_1_WANT[5] == 1;
+ assert K_FIX_2_WANT[0] == 2;
+ assert K_FIX_2_WANT[4] == 1;
+ assert K_FIX_2_WANT[3] == 0;
+}
+
+test the_badges_and_tags_line_up {
+ assert SAY_BADGES[3] == "assert removed";
+ assert SAY_BADGES[0] == "tests untouched";
+ assert K_TAGS[0] == "removed";
+ assert K_TAGS[7] == "line";
+ assert K_LANES[0] == "bee";
+ assert K_BEE_BRANCH_PREFIX == "queen-";
+}
+
+test the_page_sends_nothing_and_reads_no_file {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert K_DATA_FILE == "./data.json";
+ assert KIND == "widget-tool";
+ assert ID == "test-touch";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/test-touch/tool.js (146 lines of hand-written JavaScript).
+; The words and numbers above are unchanged. The host protocol is race_the_bee.t27's plus:
+; w_click(i) button i in document order: 0..2 the lane filter (All, Bee, Human), 3..7 the badge
+; filter (All, then the four badges from the worst), 8 "Show more"
+; w_toggle(i, open) details i (document order: the pull request rows, then the rule block) was opened
+; or closed; 1 when the page changed (the host then re-renders), else 0. A row's
+; body is built the first time it opens, as tool.js's "toggle" listener does.
+; The host must not restore open by index for this widget (it does not
+; when the widget exports w_toggle): the open state is written as the `open` attribute.
+; The 607 pull requests of data.json are NOT copied into a tree: DATA holds the file's bytes (1 MiB
+; buffer for the 786923-byte file), PRS the byte span of each pull request. The list is sorted ONCE, as
+; tool.js does, by V8's own algorithm: Array.prototype.sort is TimSort and the comparator
+; (b.badge - a.badge || (a.merged < b.merged ? 1 : -1)) never returns 0, so pull requests with the same
+; badge and merge time (the real file has three, #5821 #5675 #5595) come out in the order the algorithm
+; happens to compare them. This file ports that algorithm (array-sort.tq: runs, binary insertion,
+; galloping merges, the same argument order in every Compare) and checks it against node's sort on tie-heavy
+; random lists. Numbers and strings are written as js_num.t27 / js_val.t27 write them.
+; Where tool.js throws while it draws (prs no array, a null pull request, lanes without bee / human or a
+; lane without a vector array) its catch shows SAY_LOAD_FAILED; so does this page (data_ok). A body
+; that tool.js cannot build (a merged stamp that is no string, a line text that is no string) throws inside
+; its toggle handler, which leaves the row open and empty; this page then draws the body as empty strings.
+; NOT carried over: more than PR_CAP (1024) pull requests; a `merged` stamp compared beyond its first 32
+; bytes; sort-key `merged` values that are objects or arrays; UTF-16 order of astral characters; the last
+; merge time when a bad pull request has a non-string stamp (the default .sort() of mixed values).
+; MEMORY: DATA 1 MiB (the file is 786923 bytes), the sort keys 100 KB, the page buffer is the host's: the
+; real file renders 17472 bytes at the start, 187106 bytes with all 607 rows listed and closed, and
+; 1157198 bytes with all 607 rows open (601 bodies), so the host's output buffer is 1 MiB for the first
+; two and must be 2 MiB or more for the last. The wasm module asks for 34 pages (2.2 MB) at start; with
+; the host's 17-page buffer area and the data copied in it holds 65 pages (4.3 MB). w_data takes 64 ms in
+; node (validate, 607 spans, TimSort, filter), a closed 607-row render 24-45 ms.
+; MEASURED 2026-10-19: t27c 0.5.2 gen + a 6-function Zig export wrapper + zig build-exe wasm32-freestanding
+; ReleaseSmall give a 35775-byte wasm. The original page (tool.js) and this one ran in two happy-dom
+; windows: (1) the real 786923-byte data.json, two sessions of 70 interactions (lane and badge filters,
+; show more, rows opened and closed, the rule block): 144 comparisons of tag, attributes and text; (2) 38
+; data variants cut to 45 pull requests (no bad bee PR; a bad one with a missing / null / odd stamp; lanes
+; and badges and classes out of range or as strings; titles empty / null / markup / numbers; line lists
+; empty / odd / many files; flags; counts / author / branch / url missing; merged stamps as numbers; vectors
+; short or with strings; excluded and snapshot fields missing; 1, 0, 40, 41 and 80 pull requests) and 8
+; data files the page cannot draw (prs missing or an object, a null pull request, lanes or a vector
+; missing, a root array): 400 comparisons; (3) the SORT: 156 lists of 2 to 200 pull requests with almost
+; all badges and stamps tied (2937 tied pairs; random, ascending, descending, sawtooth, blocks), every
+; row listed, all and bee only, the order of the rows compared; (4) the t27 TimSort alone against node's
+; Array.prototype.sort with the same comparator over 10560 lists of 2 to 1024 elements (1948600 tied
+; pairs): 0 differences everywhere. A negative control (one title in the native DOM changed) is caught;
+; a 404 and a body that is not JSON end on the same line in both.
+; Not compared, and why: happy-dom's 'toggle' event is the only way a row opening reaches either page (a
+; real browser fires it on the user's click as well); CSS; a body for a pull request the original cannot
+; build; runs of more than 200 tied pull requests in the DOM (the lists of up to 1024 are compared in
+; the sort test only); the host's 'toggle' forwarding is the new host code described in the report.
+
+pub const DATA_CAP : u32 = 1048576;
+pub const PR_CAP : u32 = 1024;
+pub const PAGE : u32 = 40;
+pub var DATA : [1048576]u8 = [0; 1048576];
+pub var DATA_LEN : u32 = 0;
+pub var DATA_STATE : u8 = 0;
+pub var NPR : u32 = 0;
+pub var PRS : [1024]i64 = [0; 1024];
+pub var KB : [1024]f64 = [0.0; 1024];
+pub var KM : [1024]f64 = [0.0; 1024];
+pub var KK : [1024]u8 = [0; 1024];
+pub var KS : [32768]u8 = [0; 32768];
+pub var KL : [1024]u32 = [0; 1024];
+pub var ORDER : [1024]u32 = [0; 1024];
+pub var WORK : [1024]u32 = [0; 1024];
+pub var TMP : [1024]u32 = [0; 1024];
+pub var RUN_BASE : [96]i32 = [0; 96];
+pub var RUN_LEN : [96]i32 = [0; 96];
+pub var RUNS : i32 = 0;
+pub var MIN_GALLOP : i32 = 7;
+pub var LANE : i32 = 0 - 1;
+pub var BADGE : i32 = 0 - 1;
+pub var SHOWN : u32 = 40;
+pub var ROW_OPEN : [1024]bool = [false; 1024];
+pub var ROW_BODY : [1024]bool = [false; 1024];
+pub var RULE_OPEN : bool = false;
+pub var ROWS : [1024]u32 = [0; 1024];
+pub var NROWS : u32 = 0;
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_STATE = 0;
+ DATA_LEN = 0;
+ NPR = 0;
+ LANE = 0 - 1;
+ BADGE = 0 - 1;
+ SHOWN = PAGE;
+ RULE_OPEN = false;
+ return 0;
+}
+
+fn nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+// --- V8's TimSort (third_party/v8/builtins/array-sort.tq), over indexes of the pull requests -------------
+
+/// Compare(a, b) of tool.js's comparator: (b.badge - a.badge) || (a.merged < b.merged ? 1 : -1), never 0.
+fn cmp(a: u32, b: u32) -> i32 {
+ const bd : f64 = KB[b as usize] - KB[a as usize];
+ if (bd == bd and bd != 0.0) {
+ if (bd < 0.0) { return 0 - 1; }
+ return 1;
+ }
+ var lt : bool = false;
+ if (KK[a as usize] == 1 and KK[b as usize] == 1) {
+ const la : u32 = KL[a as usize];
+ const lb : u32 = KL[b as usize];
+ var m : u32 = la;
+ if (lb < m) { m = lb; }
+ var i : u32 = 0;
+ var decided : bool = false;
+ while (i < m and !decided) {
+ const ca : u8 = KS[(a * 32 + i) as usize];
+ const cb : u8 = KS[(b * 32 + i) as usize];
+ if (ca != cb) {
+ lt = ca < cb;
+ decided = true;
+ }
+ i = i + 1;
+ }
+ if (!decided) { lt = la < lb; }
+ } else {
+ lt = KM[a as usize] < KM[b as usize];
+ }
+ if (lt) { return 1; }
+ return 0 - 1;
+}
+
+fn get(arr: u32, i: i32) -> u32 {
+ if (arr == 0) { return WORK[i as usize]; }
+ return TMP[i as usize];
+}
+
+fn copy_range(sa: u32, sp: i32, da: u32, dp: i32, len: i32) -> u32 {
+ if (len <= 0) { return 0; }
+ if (sa == da and sp == dp) { return 0; }
+ var k : i32 = 0;
+ if (sp < dp and sa == da) {
+ k = len - 1;
+ while (k >= 0) {
+ const v : u32 = get(sa, sp + k);
+ if (da == 0) { WORK[(dp + k) as usize] = v; } else { TMP[(dp + k) as usize] = v; }
+ k = k - 1;
+ }
+ } else {
+ while (k < len) {
+ const v2 : u32 = get(sa, sp + k);
+ if (da == 0) { WORK[(dp + k) as usize] = v2; } else { TMP[(dp + k) as usize] = v2; }
+ k = k + 1;
+ }
+ }
+ return 0;
+}
+
+fn put_w(i: i32, v: u32) -> u32 {
+ WORK[i as usize] = v;
+ return 0;
+}
+
+fn put_t(i: i32, v: u32) -> u32 {
+ TMP[i as usize] = v;
+ return 0;
+}
+
+fn gallop_left(arr: u32, key: u32, base: i32, length: i32, hint: i32) -> i32 {
+ var last_ofs : i32 = 0;
+ var offset : i32 = 1;
+ var order : i32 = cmp(get(arr, base + hint), key);
+ if (order < 0) {
+ const max_ofs : i32 = length - hint;
+ while (offset < max_ofs) {
+ order = cmp(get(arr, base + hint + offset), key);
+ if (order >= 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs) { offset = max_ofs; }
+ last_ofs = last_ofs + hint;
+ offset = offset + hint;
+ } else {
+ const max_ofs2 : i32 = hint + 1;
+ while (offset < max_ofs2) {
+ order = cmp(get(arr, base + hint - offset), key);
+ if (order < 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs2) { offset = max_ofs2; }
+ const tmp : i32 = last_ofs;
+ last_ofs = hint - offset;
+ offset = hint - tmp;
+ }
+ last_ofs = last_ofs + 1;
+ while (last_ofs < offset) {
+ const m : i32 = last_ofs + ((offset - last_ofs) / 2);
+ order = cmp(get(arr, base + m), key);
+ if (order < 0) { last_ofs = m + 1; } else { offset = m; }
+ }
+ return offset;
+}
+
+fn gallop_right(arr: u32, key: u32, base: i32, length: i32, hint: i32) -> i32 {
+ var last_ofs : i32 = 0;
+ var offset : i32 = 1;
+ var order : i32 = cmp(key, get(arr, base + hint));
+ if (order < 0) {
+ const max_ofs : i32 = hint + 1;
+ while (offset < max_ofs) {
+ order = cmp(key, get(arr, base + hint - offset));
+ if (order >= 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs) { offset = max_ofs; }
+ const tmp : i32 = last_ofs;
+ last_ofs = hint - offset;
+ offset = hint - tmp;
+ } else {
+ const max_ofs2 : i32 = length - hint;
+ while (offset < max_ofs2) {
+ order = cmp(key, get(arr, base + hint + offset));
+ if (order < 0) { break; }
+ last_ofs = offset;
+ offset = offset * 2 + 1;
+ }
+ if (offset > max_ofs2) { offset = max_ofs2; }
+ last_ofs = last_ofs + hint;
+ offset = offset + hint;
+ }
+ last_ofs = last_ofs + 1;
+ while (last_ofs < offset) {
+ const m : i32 = last_ofs + ((offset - last_ofs) / 2);
+ order = cmp(key, get(arr, base + m));
+ if (order < 0) { offset = m; } else { last_ofs = m + 1; }
+ }
+ return offset;
+}
+
+fn merge_low(base_a: i32, len_a0: i32, base_b: i32, len_b0: i32) -> u32 {
+ var len_a : i32 = len_a0;
+ var len_b : i32 = len_b0;
+ copy_range(0, base_a, 1, 0, len_a);
+ var dest : i32 = base_a;
+ var ct : i32 = 0;
+ var cb : i32 = base_b;
+ put_w(dest, WORK[cb as usize]);
+ dest = dest + 1;
+ cb = cb + 1;
+ var mode : u32 = 0;
+ len_b = len_b - 1;
+ if (len_b == 0) { mode = 1; } else { if (len_a == 1) { mode = 2; } }
+ var min_gallop : i32 = MIN_GALLOP;
+ while (mode == 0) {
+ var wins_a : i32 = 0;
+ var wins_b : i32 = 0;
+ var inner : bool = true;
+ while (inner and mode == 0) {
+ const order : i32 = cmp(WORK[cb as usize], TMP[ct as usize]);
+ if (order < 0) {
+ put_w(dest, WORK[cb as usize]);
+ dest = dest + 1;
+ cb = cb + 1;
+ wins_b = wins_b + 1;
+ len_b = len_b - 1;
+ wins_a = 0;
+ if (len_b == 0) { mode = 1; } else { if (wins_b >= min_gallop) { inner = false; } }
+ } else {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest + 1;
+ ct = ct + 1;
+ wins_a = wins_a + 1;
+ len_a = len_a - 1;
+ wins_b = 0;
+ if (len_a == 1) { mode = 2; } else { if (wins_a >= min_gallop) { inner = false; } }
+ }
+ }
+ if (mode != 0) { break; }
+ min_gallop = min_gallop + 1;
+ var first : bool = true;
+ while (mode == 0 and (wins_a >= 7 or wins_b >= 7 or first)) {
+ first = false;
+ min_gallop = min_gallop - 1;
+ if (min_gallop < 1) { min_gallop = 1; }
+ MIN_GALLOP = min_gallop;
+ wins_a = gallop_right(1, WORK[cb as usize], ct, len_a, 0);
+ if (wins_a > 0) {
+ copy_range(1, ct, 0, dest, wins_a);
+ dest = dest + wins_a;
+ ct = ct + wins_a;
+ len_a = len_a - wins_a;
+ if (len_a == 1) { mode = 2; }
+ if (len_a == 0) { mode = 1; }
+ }
+ if (mode == 0) {
+ put_w(dest, WORK[cb as usize]);
+ dest = dest + 1;
+ cb = cb + 1;
+ len_b = len_b - 1;
+ if (len_b == 0) { mode = 1; }
+ }
+ if (mode == 0) {
+ wins_b = gallop_left(0, TMP[ct as usize], cb, len_b, 0);
+ if (wins_b > 0) {
+ copy_range(0, cb, 0, dest, wins_b);
+ dest = dest + wins_b;
+ cb = cb + wins_b;
+ len_b = len_b - wins_b;
+ if (len_b == 0) { mode = 1; }
+ }
+ }
+ if (mode == 0) {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest + 1;
+ ct = ct + 1;
+ len_a = len_a - 1;
+ if (len_a == 1) { mode = 2; }
+ }
+ }
+ if (mode == 0) {
+ min_gallop = min_gallop + 1;
+ MIN_GALLOP = min_gallop;
+ }
+ }
+ if (mode == 1) {
+ if (len_a > 0) { copy_range(1, ct, 0, dest, len_a); }
+ }
+ if (mode == 2) {
+ copy_range(0, cb, 0, dest, len_b);
+ put_w(dest + len_b, TMP[ct as usize]);
+ }
+ return 0;
+}
+
+fn merge_high(base_a: i32, len_a0: i32, base_b: i32, len_b0: i32) -> u32 {
+ var len_a : i32 = len_a0;
+ var len_b : i32 = len_b0;
+ copy_range(0, base_b, 1, 0, len_b);
+ var dest : i32 = base_b + len_b - 1;
+ var ct : i32 = len_b - 1;
+ var ca : i32 = base_a + len_a - 1;
+ put_w(dest, WORK[ca as usize]);
+ dest = dest - 1;
+ ca = ca - 1;
+ var mode : u32 = 0;
+ len_a = len_a - 1;
+ if (len_a == 0) { mode = 1; } else { if (len_b == 1) { mode = 2; } }
+ var min_gallop : i32 = MIN_GALLOP;
+ while (mode == 0) {
+ var wins_a : i32 = 0;
+ var wins_b : i32 = 0;
+ var inner : bool = true;
+ while (inner and mode == 0) {
+ const order : i32 = cmp(TMP[ct as usize], WORK[ca as usize]);
+ if (order < 0) {
+ put_w(dest, WORK[ca as usize]);
+ dest = dest - 1;
+ ca = ca - 1;
+ wins_a = wins_a + 1;
+ len_a = len_a - 1;
+ wins_b = 0;
+ if (len_a == 0) { mode = 1; } else { if (wins_a >= min_gallop) { inner = false; } }
+ } else {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest - 1;
+ ct = ct - 1;
+ wins_b = wins_b + 1;
+ len_b = len_b - 1;
+ wins_a = 0;
+ if (len_b == 1) { mode = 2; } else { if (wins_b >= min_gallop) { inner = false; } }
+ }
+ }
+ if (mode != 0) { break; }
+ min_gallop = min_gallop + 1;
+ var first : bool = true;
+ while (mode == 0 and (wins_a >= 7 or wins_b >= 7 or first)) {
+ first = false;
+ min_gallop = min_gallop - 1;
+ if (min_gallop < 1) { min_gallop = 1; }
+ MIN_GALLOP = min_gallop;
+ var k : i32 = gallop_right(0, TMP[ct as usize], base_a, len_a, len_a - 1);
+ wins_a = len_a - k;
+ if (wins_a > 0) {
+ dest = dest - wins_a;
+ ca = ca - wins_a;
+ copy_range(0, ca + 1, 0, dest + 1, wins_a);
+ len_a = len_a - wins_a;
+ if (len_a == 0) { mode = 1; }
+ }
+ if (mode == 0) {
+ put_w(dest, TMP[ct as usize]);
+ dest = dest - 1;
+ ct = ct - 1;
+ len_b = len_b - 1;
+ if (len_b == 1) { mode = 2; }
+ }
+ if (mode == 0) {
+ k = gallop_left(1, WORK[ca as usize], 0, len_b, len_b - 1);
+ wins_b = len_b - k;
+ if (wins_b > 0) {
+ dest = dest - wins_b;
+ ct = ct - wins_b;
+ copy_range(1, ct + 1, 0, dest + 1, wins_b);
+ len_b = len_b - wins_b;
+ if (len_b == 1) { mode = 2; }
+ if (len_b == 0) { mode = 1; }
+ }
+ }
+ if (mode == 0) {
+ put_w(dest, WORK[ca as usize]);
+ dest = dest - 1;
+ ca = ca - 1;
+ len_a = len_a - 1;
+ if (len_a == 0) { mode = 1; }
+ }
+ }
+ if (mode == 0) {
+ min_gallop = min_gallop + 1;
+ MIN_GALLOP = min_gallop;
+ }
+ }
+ if (mode == 1) {
+ if (len_b > 0) { copy_range(1, 0, 0, dest - (len_b - 1), len_b); }
+ }
+ if (mode == 2) {
+ dest = dest - len_a;
+ ca = ca - len_a;
+ copy_range(0, ca + 1, 0, dest + 1, len_a);
+ put_w(dest, TMP[ct as usize]);
+ }
+ return 0;
+}
+
+fn merge_at(i: i32) -> u32 {
+ var base_a : i32 = RUN_BASE[i as usize];
+ var len_a : i32 = RUN_LEN[i as usize];
+ const base_b : i32 = RUN_BASE[(i + 1) as usize];
+ var len_b : i32 = RUN_LEN[(i + 1) as usize];
+ RUN_LEN[i as usize] = len_a + len_b;
+ if (i == RUNS - 3) {
+ RUN_BASE[(i + 1) as usize] = RUN_BASE[(i + 2) as usize];
+ RUN_LEN[(i + 1) as usize] = RUN_LEN[(i + 2) as usize];
+ }
+ RUNS = RUNS - 1;
+ const key_right : u32 = WORK[base_b as usize];
+ const k : i32 = gallop_right(0, key_right, base_a, len_a, 0);
+ base_a = base_a + k;
+ len_a = len_a - k;
+ if (len_a == 0) { return 0; }
+ const key_left : u32 = WORK[(base_a + len_a - 1) as usize];
+ len_b = gallop_left(0, key_left, base_b, len_b, len_b - 1);
+ if (len_b == 0) { return 0; }
+ if (len_a <= len_b) { merge_low(base_a, len_a, base_b, len_b); } else { merge_high(base_a, len_a, base_b, len_b); }
+ return 0;
+}
+
+fn run_ok(n: i32) -> bool {
+ if (n < 2) { return true; }
+ return RUN_LEN[(n - 2) as usize] > RUN_LEN[(n - 1) as usize] + RUN_LEN[n as usize];
+}
+
+fn merge_collapse() -> u32 {
+ var go : bool = true;
+ while (RUNS > 1 and go) {
+ var n : i32 = RUNS - 2;
+ if (!run_ok(n + 1) or !run_ok(n)) {
+ if (n > 0 and RUN_LEN[(n - 1) as usize] < RUN_LEN[(n + 1) as usize]) { n = n - 1; }
+ merge_at(n);
+ } else {
+ if (RUN_LEN[n as usize] <= RUN_LEN[(n + 1) as usize]) { merge_at(n); } else { go = false; }
+ }
+ }
+ return 0;
+}
+
+fn merge_force_collapse() -> u32 {
+ while (RUNS > 1) {
+ var n : i32 = RUNS - 2;
+ if (n > 0 and RUN_LEN[(n - 1) as usize] < RUN_LEN[(n + 1) as usize]) { n = n - 1; }
+ merge_at(n);
+ }
+ return 0;
+}
+
+fn count_and_make_run(low_arg: i32, high: i32) -> i32 {
+ const low : i32 = low_arg + 1;
+ if (low == high) { return 1; }
+ var run_length : i32 = 2;
+ const element_low : u32 = WORK[low as usize];
+ const element_high : u32 = WORK[(low - 1) as usize];
+ var order : i32 = cmp(element_low, element_high);
+ const desc : bool = order < 0;
+ var prev : u32 = element_low;
+ var idx : i32 = low + 1;
+ var stop : bool = false;
+ while (idx < high and !stop) {
+ const cur : u32 = WORK[idx as usize];
+ order = cmp(cur, prev);
+ if (desc) {
+ if (order >= 0) { stop = true; }
+ } else {
+ if (order < 0) { stop = true; }
+ }
+ if (!stop) {
+ prev = cur;
+ run_length = run_length + 1;
+ idx = idx + 1;
+ }
+ }
+ if (desc) {
+ var lo : i32 = low_arg;
+ var hi : i32 = low_arg + run_length - 1;
+ while (lo < hi) {
+ const t : u32 = WORK[lo as usize];
+ WORK[lo as usize] = WORK[hi as usize];
+ WORK[hi as usize] = t;
+ lo = lo + 1;
+ hi = hi - 1;
+ }
+ }
+ return run_length;
+}
+
+fn binary_insertion_sort(low: i32, start_arg: i32, high: i32) -> u32 {
+ var start : i32 = start_arg;
+ if (low == start_arg) { start = start_arg + 1; }
+ while (start < high) {
+ var left : i32 = low;
+ var right : i32 = start;
+ const pivot : u32 = WORK[right as usize];
+ while (left < right) {
+ const mid : i32 = left + ((right - left) / 2);
+ const order : i32 = cmp(pivot, WORK[mid as usize]);
+ if (order < 0) { right = mid; } else { left = mid + 1; }
+ }
+ var p : i32 = start;
+ while (p > left) {
+ WORK[p as usize] = WORK[(p - 1) as usize];
+ p = p - 1;
+ }
+ WORK[left as usize] = pivot;
+ start = start + 1;
+ }
+ return 0;
+}
+
+fn min_run_length(n0: i32) -> i32 {
+ var n : i32 = n0;
+ var r : i32 = 0;
+ while (n >= 64) {
+ r = r | (n & 1);
+ n = n / 2;
+ }
+ return n + r;
+}
+
+/// Array.prototype.sort of ORDER[0..n) with cmp, as V8 runs it.
+fn timsort(n: i32) -> u32 {
+ var i : i32 = 0;
+ while (i < n) {
+ WORK[i as usize] = i as u32;
+ i = i + 1;
+ }
+ RUNS = 0;
+ MIN_GALLOP = 7;
+ if (n >= 2) {
+ var low : i32 = 0;
+ var remaining : i32 = n;
+ const min_run : i32 = min_run_length(remaining);
+ while (remaining != 0) {
+ var cur : i32 = count_and_make_run(low, low + remaining);
+ if (cur < min_run) {
+ var forced : i32 = min_run;
+ if (remaining < forced) { forced = remaining; }
+ binary_insertion_sort(low, low + cur, low + forced);
+ cur = forced;
+ }
+ RUN_BASE[RUNS as usize] = low;
+ RUN_LEN[RUNS as usize] = cur;
+ RUNS = RUNS + 1;
+ merge_collapse();
+ low = low + cur;
+ remaining = remaining - cur;
+ }
+ merge_force_collapse();
+ }
+ i = 0;
+ while (i < n) {
+ ORDER[i as usize] = WORK[i as usize];
+ i = i + 1;
+ }
+ return 0;
+}
+
+// --- the data -------------------------------------------------------------------------------------
+
+/// d.lanes[lane].vector is an array of numbers (tool.js reads v[0..4]).
+fn lane_vec(d: []const u8, r: i64, lane: []const u8) -> i64 {
+ return obj_get(d, obj_get(d, obj_get(d, r, "lanes"), lane), "vector");
+}
+
+fn data_ok(d: []const u8, r: i64) -> bool {
+ if (kind(d, r) != J_OBJECT) { return false; }
+ if (kind(d, obj_get(d, r, "prs")) != J_ARRAY) { return false; }
+ if (kind(d, lane_vec(d, r, "bee")) != J_ARRAY or kind(d, lane_vec(d, r, "human")) != J_ARRAY) { return false; }
+ if (nullish(d, obj_get(d, obj_get(d, r, "lanes"), "bee"))) { return false; }
+ return true;
+}
+
+/// Does verdict(d) throw? stamp(last) calls .replace on the greatest stamp of the removed-assert bee pull
+/// requests (by String()); that throws when it is a truthy value that is no string.
+fn verdict_throws(d: []const u8) -> bool {
+ var have : bool = false;
+ var last : [96]u8 = [0; 96];
+ var ll : u32 = 0;
+ var bad_kind : bool = false;
+ var missing : bool = false;
+ var i : u32 = 0;
+ while (i < NPR) {
+ const sp : i64 = PRS[i as usize];
+ const bv : i64 = obj_get(d, sp, "badge");
+ if (jv_is(d, obj_get(d, sp, "lane"), "bee") and kind(d, bv) == J_NUMBER and jv_num(d, bv) == 3.0) {
+ const mv : i64 = obj_get(d, sp, "merged");
+ if (kind(d, mv) == J_NONE) {
+ missing = true;
+ } else {
+ var cand : [96]u8 = [0; 96];
+ const cl : u32 = jv_str(cand[0..], 0, d, mv);
+ if (!have or str_gt(cand[0..], cl, last[0..], ll)) {
+ have = true;
+ bad_kind = jv_truthy(d, mv) and kind(d, mv) != J_STRING;
+ var z : u32 = 0;
+ while (z < cl and z < 96) {
+ last[z as usize] = cand[z as usize];
+ z = z + 1;
+ }
+ ll = z;
+ }
+ }
+ }
+ i = i + 1;
+ }
+ if (missing) { return false; }
+ return have and bad_kind;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (s.len > (DATA_CAP as usize)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) {
+ DATA[k] = s[k];
+ k = k + 1;
+ }
+ DATA_LEN = s.len as u32;
+ DATA_STATE = 1;
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (!jv_valid(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ const r : i64 = root(d);
+ if (!data_ok(d, r)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ // the span of every pull request, once (arr_at from the start each time would be quadratic)
+ const arr : i64 = obj_get(d, r, "prs");
+ const hi : usize = span_hi(arr);
+ var p : usize = skip_ws(d, span_lo(arr) + 1);
+ var n : u32 = 0;
+ while (p < hi - 1 and n < PR_CAP) {
+ const e : usize = value_end(d, p);
+ if (e > hi) { break; }
+ const sp : i64 = jspan(p, e);
+ if (nullish(d, sp)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ PRS[n as usize] = sp;
+ KB[n as usize] = jv_num(d, obj_get(d, sp, "badge"));
+ const mv : i64 = obj_get(d, sp, "merged");
+ KM[n as usize] = jv_num(d, mv);
+ KK[n as usize] = 0;
+ KL[n as usize] = 0;
+ if (kind(d, mv) == J_STRING) {
+ KK[n as usize] = 1;
+ var tmp : [64]u8 = [0; 64];
+ var l : u32 = jv_str_to(tmp[0..], 0, d, mv);
+ if (l > 32) { l = 32; }
+ var q : u32 = 0;
+ while (q < l) {
+ KS[(n * 32 + q) as usize] = tmp[q as usize];
+ q = q + 1;
+ }
+ KL[n as usize] = l;
+ }
+ n = n + 1;
+ p = skip_ws(d, e);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ NPR = n;
+ if (verdict_throws(d)) {
+ DATA_STATE = 2;
+ return 0;
+ }
+ timsort(n as i32);
+ refilter(d);
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ DATA_STATE = 2;
+ return 0;
+}
+
+/// render(): the rows after the filters; every row closed and without a body again.
+fn refilter(d: []const u8) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < NPR) {
+ const pi : u32 = ORDER[i as usize];
+ const sp : i64 = PRS[pi as usize];
+ var ok_l : bool = true;
+ var ok_b : bool = true;
+ if (LANE >= 0) { ok_l = jv_is(d, obj_get(d, sp, "lane"), K_LANES[LANE as usize]); }
+ if (BADGE >= 0) {
+ const bv : i64 = obj_get(d, sp, "badge");
+ ok_b = kind(d, bv) == J_NUMBER and jv_num(d, bv) == (BADGE as f64);
+ }
+ if (ok_l and ok_b) {
+ ROWS[n as usize] = pi;
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ NROWS = n;
+ var j : u32 = 0;
+ while (j < PR_CAP) {
+ ROW_OPEN[j as usize] = false;
+ ROW_BODY[j as usize] = false;
+ j = j + 1;
+ }
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (i < 3) {
+ LANE = (i as i32) - 1;
+ SHOWN = PAGE;
+ refilter(d);
+ }
+ if (i >= 3 and i < 8) {
+ if (i == 3) { BADGE = 0 - 1; } else { BADGE = 7 - (i as i32); }
+ SHOWN = PAGE;
+ refilter(d);
+ }
+ if (i == 8) {
+ SHOWN = SHOWN + PAGE;
+ refilter(d);
+ }
+ return 0;
+}
+
+/// Would prBody(p) in tool.js get to its end? (Else its toggle handler throws and the row stays open and empty.)
+fn body_ok(d: []const u8, p: i64) -> bool {
+ const mv : i64 = obj_get(d, p, "merged");
+ if (jv_truthy(d, mv) and kind(d, mv) != J_STRING) { return false; }
+ if (nullish(d, obj_get(d, p, "counts"))) { return false; }
+ const lines : i64 = obj_get(d, p, "lines");
+ if (kind(d, lines) != J_ARRAY) { return false; }
+ const lc : u32 = arr_len(d, lines);
+ var i : u32 = 0;
+ while (i < lc) {
+ const l : i64 = arr_at(d, lines, i);
+ if (nullish(d, l)) { return false; }
+ if (kind(d, obj_get(d, l, "t")) != J_STRING) { return false; }
+ i = i + 1;
+ }
+ if (lc > 0 and nullish(d, obj_get(d, p, "files"))) { return false; }
+ return true;
+}
+
+pub fn w_toggle(i: u32, open: u32) -> u32 {
+ if (DATA_STATE != 1) { return 0; }
+ var shown_rows : u32 = NROWS;
+ if (SHOWN < shown_rows) { shown_rows = SHOWN; }
+ const o : bool = open != 0;
+ if (i < shown_rows) {
+ var changed : bool = ROW_OPEN[i as usize] != o;
+ ROW_OPEN[i as usize] = o;
+ if (o and !ROW_BODY[i as usize]) {
+ const dd : []const u8 = DATA[0..(DATA_LEN as usize)];
+ if (body_ok(dd, PRS[ROWS[i as usize] as usize])) {
+ ROW_BODY[i as usize] = true;
+ changed = true;
+ }
+ }
+ if (changed) { return 1; }
+ return 0;
+ }
+ if (i == shown_rows) {
+ if (RULE_OPEN != o) {
+ RULE_OPEN = o;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+// --- the page -------------------------------------------------------------------------------------
+
+/// stamp(iso): '' for a falsy value, else the first T as a space and a trailing :dd Z cut, as raw bytes.
+fn stamp_raw(out: []u8, d: []const u8, sp: i64) -> u32 {
+ if (!jv_truthy(d, sp)) { return 0; }
+ var l : u32 = jv_str(out, 0, d, sp);
+ var k : u32 = 0;
+ var done : bool = false;
+ while (k < l and !done) {
+ if (out[k as usize] == 84) {
+ out[k as usize] = 32;
+ done = true;
+ }
+ k = k + 1;
+ }
+ if (l >= 4 and out[(l - 1) as usize] == 90 and out[(l - 4) as usize] == 58 and out[(l - 3) as usize] >= 48 and out[(l - 3) as usize] <= 57 and out[(l - 2) as usize] >= 48 and out[(l - 2) as usize] <= 57) {
+ l = l - 4;
+ }
+ return l;
+}
+
+fn put_esc(dst: []u8, n0: u32, s: []const u8, l: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < l) {
+ n = put_escaped(dst, n, s[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+fn lane_index(d: []const u8, sp: i64) -> i32 {
+ var i : usize = 0;
+ while (i < K_LANES.len) {
+ if (jv_is(d, sp, K_LANES[i])) { return i as i32; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+/// laneName(lane): the word for a known lane, else the data's own text.
+fn lane_name(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ const ix : i32 = lane_index(d, sp);
+ if (ix >= 0) { return text(dst, n0, SAY_LANE_NAMES[ix as usize]); }
+ return jv_kid(dst, n0, d, sp);
+}
+
+/// The array index a value names as a property key (arr[v]): an integer number, or a canonical decimal string; -1 else.
+fn key_index(d: []const u8, sp: i64) -> i64 {
+ const k : u8 = kind(d, sp);
+ if (k == J_NUMBER) {
+ const v : f64 = jv_num(d, sp);
+ if (v >= 0.0 and v < 4294967295.0 and v == js_floor(v)) { return v as i64; }
+ return 0 - 1;
+ }
+ if (k == J_STRING) {
+ const l : u32 = str_len(d, sp);
+ if (l == 0 or l > 9) { return 0 - 1; }
+ var v2 : i64 = 0;
+ var i : u32 = 0;
+ while (i < l) {
+ const c : u8 = str_byte(d, sp, i);
+ if (c < 48 or c > 57) { return 0 - 1; }
+ if (i == 0 and c == 48 and l > 1) { return 0 - 1; }
+ v2 = v2 * 10 + ((c - 48) as i64);
+ i = i + 1;
+ }
+ return v2;
+ }
+ return 0 - 1;
+}
+
+fn badge_span(dst: []u8, n0: u32, d: []const u8, b: i64) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ const bi : i64 = key_index(d, b);
+ if (bi >= 0 and bi < (SAY_BADGES.len as i64)) { n = text(dst, n, SAY_BADGES[bi as usize]); }
+ return raw(dst, n, "");
+}
+
+fn chip_span(dst: []u8, n0: u32, d: []const u8, lane: i64) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = lane_name(dst, n, d, lane);
+ return raw(dst, n, "");
+}
+
+fn vnum(d: []const u8, v: i64, ix: u32) -> i64 {
+ return arr_at(d, v, ix);
+}
+
+fn lane_card(dst: []u8, n0: u32, d: []const u8, r: i64, li: u32) -> u32 {
+ const L : i64 = obj_get(d, obj_get(d, r, "lanes"), K_LANES[li as usize]);
+ const v : i64 = obj_get(d, L, "vector");
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_LANE_NAMES[li as usize]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LANE_LINES[li as usize]);
+ n = raw(dst, n, "
");
+ n = jv_text(dst, n, d, vnum(d, v, 4));
+ n = raw(dst, n, " / ");
+ n = jv_text(dst, n, d, vnum(d, v, 0));
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_REMOVED_OF);
+ n = raw(dst, n, "; ");
+ n = jv_text(dst, n, d, vnum(d, v, 0));
+ n = put(dst, n, 32);
+ n = text(dst, n, SAY_MERGED);
+ n = raw(dst, n, "
1) { n = raw(dst, n, ", "); }
+ n = text(dst, n, SAY_BADGES[(q - 1) as usize]);
+ n = put(dst, n, 32);
+ n = jv_text(dst, n, d, vnum(d, v, q));
+ q = q + 1;
+ }
+ n = raw(dst, n, "\">");
+ q = 4;
+ while (q >= 1) {
+ n = raw(dst, n, "");
+ q = q - 1;
+ }
+ n = raw(dst, n, "
");
+ q = 4;
+ while (q >= 1) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BADGES[(q - 1) as usize]);
+ n = raw(dst, n, "");
+ n = jv_text(dst, n, d, vnum(d, v, q));
+ n = raw(dst, n, "
");
+ q = q - 1;
+ }
+ n = raw(dst, n, "
");
+ var s : usize = 0;
+ while (s < SAY_STAT_NAMES.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAT_NAMES[s]);
+ n = raw(dst, n, "
");
+ if (s == 0) { n = jv_text(dst, n, d, obj_get(d, L, "checksAdded")); }
+ if (s == 1) { n = jv_text(dst, n, d, obj_get(d, L, "checksRemoved")); }
+ if (s == 2) { n = jv_text(dst, n, d, obj_get(d, L, "testsRemoved")); }
+ if (s == 3) { n = jv_text(dst, n, d, obj_get(d, L, "expect")); }
+ if (s == 4) { n = jv_text(dst, n, d, obj_get(d, L, "loosened")); }
+ n = raw(dst, n, "
");
+ s = s + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn verdict_html(dst: []u8, n0: u32, d: []const u8, r: i64) -> u32 {
+ // bad = bee pull requests with badge === 3 ; last = the greatest of their stamps, as .sort().pop() gives it
+ var bad : u32 = 0;
+ var have_last : bool = false;
+ var last_missing : bool = false;
+ var last_null : bool = false;
+ var last : [64]u8 = [0; 64];
+ var ll : u32 = 0;
+ var i : u32 = 0;
+ while (i < NPR) {
+ const sp : i64 = PRS[i as usize];
+ const bv : i64 = obj_get(d, sp, "badge");
+ if (jv_is(d, obj_get(d, sp, "lane"), "bee") and kind(d, bv) == J_NUMBER and jv_num(d, bv) == 3.0) {
+ bad = bad + 1;
+ const mv : i64 = obj_get(d, sp, "merged");
+ if (kind(d, mv) == J_NONE) {
+ last_missing = true;
+ } else {
+ var cand : [96]u8 = [0; 96];
+ const cl : u32 = jv_str(cand[0..], 0, d, mv);
+ if (!have_last or str_gt(cand[0..], cl, last[0..], ll)) {
+ have_last = true;
+ last_null = kind(d, mv) == J_NULL;
+ var z : u32 = 0;
+ while (z < cl and z < 64) {
+ last[z as usize] = cand[z as usize];
+ z = z + 1;
+ }
+ ll = z;
+ }
+ }
+ }
+ i = i + 1;
+ }
+ var n : u32 = n0;
+ if (bad == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_VERDICT_NO);
+ return raw(dst, n, "
");
+ // after = bee pull requests whose stamp is a string greater than last
+ var after : u32 = 0;
+ if (have_last and !last_missing and !last_null) {
+ i = 0;
+ while (i < NPR) {
+ const sp2 : i64 = PRS[i as usize];
+ const mv2 : i64 = obj_get(d, sp2, "merged");
+ if (jv_is(d, obj_get(d, sp2, "lane"), "bee") and kind(d, mv2) == J_STRING) {
+ var c2 : [96]u8 = [0; 96];
+ const c2l : u32 = jv_str(c2[0..], 0, d, mv2);
+ if (str_gt(c2[0..], c2l, last[0..], ll)) { after = after + 1; }
+ }
+ i = i + 1;
+ }
+ }
+ var lb : [256]u8 = [0; 256];
+ var lt : [200]u8 = [0; 200];
+ var ltl : u32 = 0;
+ if (!last_missing and !last_null) {
+ // stamp(last): last is a string here
+ var tmp : [96]u8 = [0; 96];
+ var z2 : u32 = 0;
+ while (z2 < ll) {
+ tmp[z2 as usize] = last[z2 as usize];
+ z2 = z2 + 1;
+ }
+ var done : bool = false;
+ var k : u32 = 0;
+ while (k < ll and !done) {
+ if (tmp[k as usize] == 84) {
+ tmp[k as usize] = 32;
+ done = true;
+ }
+ k = k + 1;
+ }
+ var l2 : u32 = ll;
+ if (l2 >= 4 and tmp[(l2 - 1) as usize] == 90 and tmp[(l2 - 4) as usize] == 58 and tmp[(l2 - 3) as usize] >= 48 and tmp[(l2 - 3) as usize] <= 57 and tmp[(l2 - 2) as usize] >= 48 and tmp[(l2 - 2) as usize] <= 57) { l2 = l2 - 4; }
+ var y : u32 = 0;
+ while (y < l2) {
+ lt[y as usize] = tmp[y as usize];
+ y = y + 1;
+ }
+ ltl = l2;
+ }
+ var ln : u32 = jv_vput_str(lb[0..], 0, "last", lt[0..(ltl as usize)]);
+ ln = jv_vput_f64(lb[0..], ln, "after", after as f64);
+ n = jv_fill(dst, n, SAY_VERDICT_LAST, lb[0..], ln, false);
+ return raw(dst, n, "
");
+}
+
+/// a > b as strings (UTF-16 order for BMP text, the bytes here).
+fn str_gt(a: []const u8, al: u32, b: []const u8, bl: u32) -> bool {
+ var m : u32 = al;
+ if (bl < m) { m = bl; }
+ var i : u32 = 0;
+ while (i < m) {
+ if (a[i as usize] != b[i as usize]) { return a[i as usize] > b[i as usize]; }
+ i = i + 1;
+ }
+ return al > bl;
+}
+
+fn same_key(d: []const u8, a: i64, b: i64) -> bool {
+ const ka : u8 = kind(d, a);
+ if (ka != kind(d, b)) { return false; }
+ if (ka == J_NUMBER) {
+ const x : f64 = jv_num(d, a);
+ const y : f64 = jv_num(d, b);
+ return x == y or (x != x and y != y);
+ }
+ if (ka == J_STRING) {
+ const l : u32 = str_len(d, a);
+ if (l != str_len(d, b)) { return false; }
+ var k : u32 = 0;
+ while (k < l) {
+ if (str_byte(d, a, k) != str_byte(d, b, k)) { return false; }
+ k = k + 1;
+ }
+ return true;
+ }
+ return ka != J_OBJECT and ka != J_ARRAY and ka != J_NONE;
+}
+
+/// replace(/^\s+/, '') then the rest of the string t as escaped text.
+fn trimmed_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) != J_STRING) { return n0; }
+ var tmp : [4096]u8 = [0; 4096];
+ const l : u32 = jv_str_to(tmp[0..], 0, d, sp);
+ var lo : u32 = 0;
+ var w : u32 = 1;
+ while (lo < l and w > 0) {
+ w = jv_ws_at(tmp[0..], lo, l);
+ lo = lo + w;
+ }
+ var n : u32 = n0;
+ var q : u32 = lo;
+ while (q < l) {
+ n = put_escaped(dst, n, tmp[q as usize]);
+ q = q + 1;
+ }
+ return n;
+}
+
+fn tag_word(dst: []u8, n0: u32, d: []const u8, g: i64) -> u32 {
+ var i : usize = 0;
+ while (i < K_TAGS.len) {
+ if (jv_is(d, g, K_TAGS[i])) { return text(dst, n0, SAY_TAGS[i]); }
+ i = i + 1;
+ }
+ return jv_kid(dst, n0, d, g);
+}
+
+fn body_html(dst: []u8, n0: u32, d: []const u8, p: i64) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var sb : [1100]u8 = [0; 1100];
+ const sl : u32 = stamp_raw(sb[0..], d, obj_get(d, p, "merged"));
+ n = put_esc(dst, n, sb[0..], sl);
+ n = raw(dst, n, ", ");
+ n = jv_text(dst, n, d, obj_get(d, p, "author"));
+ n = raw(dst, n, ", ");
+ n = jv_text(dst, n, d, obj_get(d, p, "branch"));
+ n = raw(dst, n, ", ");
+ const cls : i64 = obj_get(d, p, "cls");
+ const ci : i64 = key_index(d, cls);
+ if (ci >= 0 and ci < (SAY_CLASSES.len as i64)) {
+ n = text(dst, n, SAY_CLASSES[ci as usize]);
+ } else {
+ n = raw(dst, n, "undefined");
+ }
+ n = raw(dst, n, "
");
+ var vb : [512]u8 = [0; 512];
+ var vn : u32 = jv_vput_val(vb[0..], 0, "n", d, obj_get(d, p, "testFiles"));
+ vn = jv_vput_val(vb[0..], vn, "all", d, obj_get(d, p, "allFiles"));
+ n = jv_fill(dst, n, SAY_TEST_FILES, vb[0..], vn, false);
+ n = raw(dst, n, "; ");
+ const c : i64 = obj_get(d, p, "counts");
+ vn = jv_vput_nn0(vb[0..], 0, "added", d, obj_get(d, c, "checksAdded"));
+ vn = jv_vput_nn0(vb[0..], vn, "removed", d, obj_get(d, c, "checksRemoved"));
+ vn = jv_vput_nn0(vb[0..], vn, "blocks", d, obj_get(d, c, "testsRemoved"));
+ n = jv_fill(dst, n, SAY_CHECKS, vb[0..], vn, false);
+ n = raw(dst, n, "
");
+ const lines : i64 = obj_get(d, p, "lines");
+ const lc : u32 = arr_len(d, lines);
+ if (lc > 0) {
+ // group by file in first-seen order
+ var done : [128]bool = [false; 128];
+ var a : u32 = 0;
+ while (a < lc and a < 128) {
+ if (!done[a as usize]) {
+ const la : i64 = arr_at(d, lines, a);
+ const fa : i64 = obj_get(d, la, "f");
+ n = raw(dst, n, "
");
+ const fidx : i64 = key_index(d, fa);
+ if (fidx >= 0) {
+ const fe : i64 = arr_at(d, obj_get(d, p, "files"), fidx as u32);
+ n = nn_text(dst, n, d, obj_get(d, fe, "p"));
+ }
+ n = raw(dst, n, "
");
+ var b : u32 = a;
+ while (b < lc and b < 128) {
+ if (!done[b as usize]) {
+ const lb : i64 = arr_at(d, lines, b);
+ if (same_key(d, fa, obj_get(d, lb, "f"))) {
+ done[b as usize] = true;
+ const sgn : i64 = obj_get(d, lb, "s");
+ n = raw(dst, n, "
");
+ n = tag_word(dst, n, d, obj_get(d, lb, "g"));
+ n = raw(dst, n, "");
+ n = jv_text(dst, n, d, sgn);
+ n = put(dst, n, 32);
+ n = trimmed_text(dst, n, d, obj_get(d, lb, "t"));
+ n = raw(dst, n, "");
+ if (jv_truthy(d, obj_get(d, lb, "b"))) {
+ n = raw(dst, n, "");
+ n = jv_text(dst, n, d, obj_get(d, lb, "b"));
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "
");
+ }
+ }
+ b = b + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ a = a + 1;
+ }
+ } else {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NO_LINES);
+ n = raw(dst, n, "
");
+ }
+ if (jv_truthy(d, obj_get(d, p, "more"))) {
+ n = raw(dst, n, "
");
+ var mb : [128]u8 = [0; 128];
+ const mn : u32 = jv_vput_val(mb[0..], 0, "n", d, obj_get(d, p, "more"));
+ n = jv_fill(dst, n, SAY_MORE_LINES, mb[0..], mn, false);
+ n = raw(dst, n, "
");
+ }
+ if (jv_truthy(d, obj_get(d, p, "ciGate"))) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CI_GATE);
+ n = raw(dst, n, "
");
+ }
+ if (jv_truthy(d, obj_get(d, p, "apiOnly"))) {
+ n = raw(dst, n, "
");
+ var ab : [128]u8 = [0; 128];
+ const an : u32 = jv_vput_val(ab[0..], 0, "n", d, obj_get(d, p, "apiOnly"));
+ n = jv_fill(dst, n, SAY_API_ONLY, ab[0..], an, false);
+ n = raw(dst, n, "
");
+}
+
+/// A pool entry for `{key: x ?? 0}`.
+fn jv_vput_nn0(vb: []u8, n0: u32, key: []const u8, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return jv_vput_str(vb, n0, key, "0"); }
+ return jv_vput_val(vb, n0, key, d, sp);
+}
+
+fn nn_text(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (nullish(d, sp)) { return n0; }
+ return jv_text(dst, n0, d, sp);
+}
+
+fn row_html(dst: []u8, n0: u32, d: []const u8, ri: u32) -> u32 {
+ const p : i64 = PRS[ROWS[ri as usize] as usize];
+ var n : u32 = raw(dst, n0, "");
+ n = badge_span(dst, n, d, obj_get(d, p, "badge"));
+ n = put(dst, n, 32);
+ n = chip_span(dst, n, d, obj_get(d, p, "lane"));
+ n = raw(dst, n, " #");
+ n = jv_text(dst, n, d, obj_get(d, p, "n"));
+ n = raw(dst, n, "");
+ const tt : i64 = obj_get(d, p, "title");
+ if (jv_truthy(d, tt)) { n = jv_text(dst, n, d, tt); } else { n = text(dst, n, SAY_NO_TITLE); }
+ n = raw(dst, n, "");
+ if (ROW_BODY[ri as usize]) { n = body_html(dst, n, d, p); }
+ return raw(dst, n, "");
+}
+
+fn filter_buttons(dst: []u8, n0: u32, which: u32) -> u32 {
+ var n : u32 = n0;
+ if (which == 0) {
+ var i : i32 = 0 - 1;
+ while (i < 2) {
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ } else {
+ var j : i32 = 0 - 1;
+ while (j < 4) {
+ var b : i32 = j;
+ if (j >= 0) { b = 3 - j; }
+ n = raw(dst, n, "");
+ j = j + 1;
+ }
+ }
+ return n;
+}
+
+fn bullets_rule(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var i : usize = 0;
+ while (i < SAY_RULE.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_RULE[i]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn bullets_pos(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var i : usize = 0;
+ while (i < SAY_FALSE_POS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_POS[i]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn bullets_neg(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var i : usize = 0;
+ while (i < SAY_FALSE_NEG.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_NEG[i]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if (DATA_STATE == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ if (DATA_STATE == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOAD_FAILED);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+ }
+ const d : []const u8 = DATA[0..(DATA_LEN as usize)];
+ const r : i64 = root(d);
+ n = raw(dst, n, "
");
+ var vb : [1200]u8 = [0; 1200];
+ var st : [1100]u8 = [0; 1100];
+ const stl : u32 = stamp_raw(st[0..], d, obj_get(d, r, "snapshotAt"));
+ var vn : u32 = jv_vput_str(vb[0..], 0, "at", st[0..(stl as usize)]);
+ vn = jv_vput_val(vb[0..], vn, "days", d, obj_get(d, r, "days"));
+ vn = jv_vput_val(vb[0..], vn, "repo", d, obj_get(d, r, "repo"));
+ n = jv_fill(dst, n, SAY_SNAPSHOT, vb[0..], vn, false);
+ n = raw(dst, n, "
");
+ n = verdict_html(dst, n, d, r);
+ n = raw(dst, n, "
");
+ n = lane_card(dst, n, d, r, 0);
+ n = lane_card(dst, n, d, r, 1);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FILTER_LANE);
+ n = raw(dst, n, "");
+ n = filter_buttons(dst, n, 0);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FILTER_BADGE);
+ n = raw(dst, n, "");
+ n = filter_buttons(dst, n, 1);
+ n = raw(dst, n, "
");
+ var cb : [128]u8 = [0; 128];
+ var cn : u32 = jv_vput_f64(cb[0..], 0, "n", NROWS as f64);
+ cn = jv_vput_f64(cb[0..], cn, "total", NPR as f64);
+ n = jv_fill(dst, n, SAY_SHOWING, cb[0..], cn, false);
+ n = raw(dst, n, "
");
+ var shown : u32 = NROWS;
+ if (SHOWN < shown) { shown = SHOWN; }
+ var i : u32 = 0;
+ while (i < shown) {
+ n = row_html(dst, n, d, i);
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_RULE_TITLE);
+ n = raw(dst, n, "");
+ n = bullets_rule(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_POS_TITLE);
+ n = raw(dst, n, "
");
+ n = bullets_pos(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FALSE_NEG_TITLE);
+ n = raw(dst, n, "
");
+ n = bullets_neg(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NOT_INTENT);
+ n = raw(dst, n, "
");
+ var eb : [128]u8 = [0; 128];
+ const ex : i64 = obj_get(d, obj_get(d, r, "excluded"), "notMaster");
+ var en : u32 = 0;
+ if (nullish(d, ex)) { en = jv_vput_str(eb[0..], 0, "notMaster", "0"); } else { en = jv_vput_val(eb[0..], 0, "notMaster", d, ex); }
+ n = jv_fill(dst, n, SAY_EXCLUDED, eb[0..], en, false);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader and the number rules call.
+/// Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ return n;
+}
From cb66747f66e237fc3ed6d66622378ad167bbbef1 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:43:49 +0700
Subject: [PATCH 34/63] widgets: native-only constants of the seven pages are
W_-prefixed, the name trinity's generator accepts beside SAY_ and K_ (Closes
#11016)
---
specs/widgets/build_receipt.t27 | 6 ++---
specs/widgets/jtag_verdict.t27 | 40 ++++++++++++++++-----------------
specs/widgets/race_the_bee.t27 | 6 ++---
specs/widgets/t27_cell.t27 | 12 +++++-----
specs/widgets/t27_vs_nvfp4.t27 | 6 ++---
specs/widgets/test_touch.t27 | 22 +++++++++---------
specs/widgets/uart_bucket.t27 | 6 ++---
7 files changed, 49 insertions(+), 49 deletions(-)
diff --git a/specs/widgets/build_receipt.t27 b/specs/widgets/build_receipt.t27
index 6da77f9198..fc368134b5 100644
--- a/specs/widgets/build_receipt.t27
+++ b/specs/widgets/build_receipt.t27
@@ -269,7 +269,7 @@ test the_page_sends_nothing {
; NOT carried over: String.prototype.toLowerCase / toUpperCase beyond ASCII and Latin-1 (the tool-name
; test and the receipt number); Object.entries order for integer-like keys of `utilisation`, and duplicate keys; a
; non-empty string as `utilisation` or `earlier_runs`, and a string as a row of `utilisation`;
-; the data file is cut at DATA_CAP bytes (the page then says it could not be read); a digest longer than
+; the data file is cut at W_DATA_CAP bytes (the page then says it could not be read); a digest longer than
; 293 hex digits is cut in the barcode and one longer than 511 bytes everywhere.
; MEASURED 2026-10-19: t27c 0.5.2 gen + a 5-line Zig export wrapper + zig build-exe wasm32-freestanding
; ReleaseSmall give a 32541-byte wasm. The original page (tool.js) and this one ran in two happy-dom
@@ -292,7 +292,7 @@ test the_page_sends_nothing {
; 0 differences. A negative control (one node of the native DOM changed) is caught.
; Not compared: the stylesheet (happy-dom); the cases under "NOT carried over".
-pub const DATA_CAP : u32 = 32768;
+pub const W_DATA_CAP : u32 = 32768;
pub var DATA : [32768]u8 = [0; 32768];
pub var DATA_LEN : u32 = 0;
pub var DATA_STATE : u8 = 0;
@@ -381,7 +381,7 @@ fn entries_ok(d: []const u8, ut: i64) -> bool {
}
pub fn w_data(i: u32, s: []const u8) -> u32 {
- if (s.len > (DATA_CAP as usize)) {
+ if (s.len > (W_DATA_CAP as usize)) {
DATA_STATE = 2;
return 0;
}
diff --git a/specs/widgets/jtag_verdict.t27 b/specs/widgets/jtag_verdict.t27
index 144177d227..fa2c0061e3 100644
--- a/specs/widgets/jtag_verdict.t27
+++ b/specs/widgets/jtag_verdict.t27
@@ -193,7 +193,7 @@ test the_page_opens_on_bypass {
; w_hash_out(dst) the address to show after the last event (length 0: leave it)
; The state is the two field texts; the rest is derived on every render, as tool.js's update() did.
; Not carried over from JavaScript: Unicode white space around a typed number (its \s) is not ASCII;
-; field text is cut at FIELD_CAP bytes; a malformed %-escape in the address is read as no value
+; field text is cut at W_FIELD_CAP bytes; a malformed %-escape in the address is read as no value
; (decodeURIComponent would throw).
; MEASURED 2026-10-19: t27c gen, a 6-line Zig export wrapper and `zig build-exe -target
; wasm32-freestanding -O ReleaseSmall` give a 9269-byte wasm. A browser harness loaded the original
@@ -204,7 +204,7 @@ test the_page_opens_on_bypass {
; Changing one text node of the native page makes the harness report a difference (control).
; Not carried over from the page: nothing else; focus after a preset click is kept by the host.
-pub const FIELD_CAP : u32 = 255;
+pub const W_FIELD_CAP : u32 = 255;
pub var IR_TEXT : [255]u8 = [0; 255];
pub var IR_LEN : u32 = 0;
pub var DR_TEXT : [255]u8 = [0; 255];
@@ -216,7 +216,7 @@ pub var HEX_A : [16]u8 = [0; 16];
pub var HEX_B : [16]u8 = [0; 16];
pub var BIN2 : [2]u8 = [0; 2];
pub var DECODED : [64]u8 = [0; 64];
-pub const NOT_HEX : i64 = -1;
+pub const W_NOT_HEX : i64 = -1;
/// The five checks, in the order of specs/port/tools/jtag/tdo_verdict.t27 (written in an older dialect
/// that cannot be imported; the seven vectors above are its test vectors and the tests below hold this to them).
@@ -257,13 +257,13 @@ pub fn parse_hex(s: []const u8) -> i64 {
continue;
}
const h : i64 = hex_val(b);
- if (h < 0) { return NOT_HEX; }
+ if (h < 0) { return W_NOT_HEX; }
if (pos == 0) { first = b; }
digits = digits + 1;
if (digits <= 8) { val = val * 16 + h; }
pos = pos + 1;
}
- if (digits < 1 or digits > 8) { return NOT_HEX; }
+ if (digits < 1 or digits > 8) { return W_NOT_HEX; }
return val;
}
@@ -304,7 +304,7 @@ fn dr_value() -> i64 {
fn set_text(which: u32, s: []const u8) -> u32 {
var n : usize = s.len;
- if (n > (FIELD_CAP as usize)) { n = FIELD_CAP as usize; }
+ if (n > (W_FIELD_CAP as usize)) { n = W_FIELD_CAP as usize; }
var i : usize = 0;
while (i < n) {
if (which == 0) { IR_TEXT[i] = s[i]; } else { DR_TEXT[i] = s[i]; }
@@ -360,16 +360,16 @@ fn hash_get(h: []const u8, key: []const u8) -> i64 {
var a : usize = p + 2 + key.len;
var e : usize = a;
while (e < h.len and h[e] != 38) { e = e + 1; }
- if (e == a) { return NOT_HEX; }
+ if (e == a) { return W_NOT_HEX; }
var n : usize = 0;
var i : usize = a;
while (i < e) {
- if (n >= 64) { return NOT_HEX; }
+ if (n >= 64) { return W_NOT_HEX; }
if (h[i] == 37) {
- if (i + 2 >= e) { return NOT_HEX; }
+ if (i + 2 >= e) { return W_NOT_HEX; }
const hi : i64 = hex_val(h[i + 1]);
const lo : i64 = hex_val(h[i + 2]);
- if (hi < 0 or lo < 0) { return NOT_HEX; }
+ if (hi < 0 or lo < 0) { return W_NOT_HEX; }
DECODED[n] = ((hi * 16 + lo) as u8);
i = i + 3;
} else {
@@ -382,7 +382,7 @@ fn hash_get(h: []const u8, key: []const u8) -> i64 {
}
p = p + 1;
}
- return NOT_HEX;
+ return W_NOT_HEX;
}
pub fn w_init(h: []const u8) -> u32 {
@@ -631,11 +631,11 @@ test hex_is_parsed_the_way_parseHex_does {
assert parse_hex("0x35") == 53;
assert parse_hex(" 0X13_63 6093 ") == 325279891;
assert parse_hex("35") == 53;
- assert parse_hex("0x") == NOT_HEX;
- assert parse_hex("") == NOT_HEX;
- assert parse_hex("0x123456789") == NOT_HEX;
- assert parse_hex("0xg") == NOT_HEX;
- assert parse_hex("00x1") == NOT_HEX;
+ assert parse_hex("0x") == W_NOT_HEX;
+ assert parse_hex("") == W_NOT_HEX;
+ assert parse_hex("0x123456789") == W_NOT_HEX;
+ assert parse_hex("0xg") == W_NOT_HEX;
+ assert parse_hex("00x1") == W_NOT_HEX;
assert parse_hex("ffffffff") == 4294967295;
}
@@ -654,9 +654,9 @@ test the_address_is_read_and_written_both_ways {
assert hash_get("#ir=0x35&dr=0x13636092", "ir") == 53;
assert hash_get("#ir=0x35&dr=0x13636092", "dr") == 325279890;
assert hash_get("#dr=1&ir=%30x35", "ir") == 53;
- assert hash_get("#ir=&dr=1", "ir") == NOT_HEX;
- assert hash_get("#xir=1", "ir") == NOT_HEX;
- assert hash_get("", "ir") == NOT_HEX;
+ assert hash_get("#ir=&dr=1", "ir") == W_NOT_HEX;
+ assert hash_get("#xir=1", "ir") == W_NOT_HEX;
+ assert hash_get("", "ir") == W_NOT_HEX;
assert w_init("#ir=0x35&dr=0x13636092") == 0;
assert IR_LEN == 4 and DR_LEN == 10;
assert PRESSED[1] == 1;
@@ -666,7 +666,7 @@ test the_address_is_read_and_written_both_ways {
w_init("");
assert PRESSED[1] == 1;
assert PRESSED[0] == 0;
- assert hash_get("#ir=%3", "ir") == NOT_HEX;
+ assert hash_get("#ir=%3", "ir") == W_NOT_HEX;
}
test events_move_the_state_and_the_render_follows {
diff --git a/specs/widgets/race_the_bee.t27 b/specs/widgets/race_the_bee.t27
index a0792560e9..e0d5619456 100644
--- a/specs/widgets/race_the_bee.t27
+++ b/specs/widgets/race_the_bee.t27
@@ -174,10 +174,10 @@ test it_is_a_game_widget {
; contested issues, nobody merged, open and odd PR states, null hours, titles with markup, quotes,
; backslash, newline, accents and an emoji, 3 or no landed rows, an unknown lane, odd timestamps, big
; numbers, no open issues) each with its three tabs: 59 comparisons of tag, attributes and text, 0
-; differences. Not carried over: the data file is cut at DATA_CAP bytes (the page then says it could not be read); a timestamp
+; differences. Not carried over: the data file is cut at W_DATA_CAP bytes (the page then says it could not be read); a timestamp
; longer than 63 bytes is cut.
-pub const DATA_CAP : u32 = 131072;
+pub const W_DATA_CAP : u32 = 131072;
pub var DATA : [131072]u8 = [0; 131072];
pub var DATA_LEN : u32 = 0;
pub var DATA_STATE : u8 = 0;
@@ -192,7 +192,7 @@ pub fn w_init(h: []const u8) -> u32 {
}
pub fn w_data(i: u32, s: []const u8) -> u32 {
- if (s.len > (DATA_CAP as usize)) {
+ if (s.len > (W_DATA_CAP as usize)) {
DATA_STATE = 2;
return 0;
}
diff --git a/specs/widgets/t27_cell.t27 b/specs/widgets/t27_cell.t27
index cbfae6dec2..c2cf6b4b75 100644
--- a/specs/widgets/t27_cell.t27
+++ b/specs/widgets/t27_cell.t27
@@ -264,7 +264,7 @@ test the_page_opens_inside_the_line_and_sends_nothing {
; Number(text) of js_val.t27. Where tool.js throws while it draws (a missing t27 / e2m1 / cell / quality
; / sources object, levels that are no array, a storage row without T27, NVFP4 or E2M2, a ppl that is
; no number) its catch shows SAY_ERR_DATA; so does this page, for the same list of causes (data_ok).
-; NOT carried over: levels beyond LEVEL_CAP entries; text typed into the number box beyond 255 bytes;
+; NOT carried over: levels beyond W_LEVEL_CAP entries; text typed into the number box beyond 255 bytes;
; the pointer events are the host's (host.js grows 'change' and pointer forwarding, see the report).
; MEASURED 2026-10-19: t27c 0.5.2 gen + an 8-function Zig export wrapper + zig build-exe wasm32-freestanding
; ReleaseSmall give a 31401-byte wasm. The original page (tool.js) and this one ran in two happy-dom
@@ -284,8 +284,8 @@ test the_page_opens_inside_the_line_and_sends_nothing {
; Not compared: the real getBoundingClientRect, real pointer capture and a real blur / Enter for change
; (stubbed or synthesised identically on both sides); the stylesheet (happy-dom).
-pub const DATA_CAP : u32 = 16384;
-pub const LEVEL_CAP : u32 = 128;
+pub const W_DATA_CAP : u32 = 16384;
+pub const W_LEVEL_CAP : u32 = 128;
pub var DATA : [16384]u8 = [0; 16384];
pub var DATA_LEN : u32 = 0;
pub var DATA_STATE : u8 = 0;
@@ -395,7 +395,7 @@ fn set_x(v: f64, from: u32) -> u32 {
}
pub fn w_data(i: u32, s: []const u8) -> u32 {
- if (s.len > (DATA_CAP as usize)) {
+ if (s.len > (W_DATA_CAP as usize)) {
DATA_STATE = 2;
return 0;
}
@@ -419,7 +419,7 @@ pub fn w_data(i: u32, s: []const u8) -> u32 {
const t : i64 = obj_get(d, r, "t27");
const e : i64 = obj_get(d, r, "e2m1");
TL_N = arr_len(d, obj_get(d, t, "levels"));
- if (TL_N > LEVEL_CAP) { TL_N = LEVEL_CAP; }
+ if (TL_N > W_LEVEL_CAP) { TL_N = W_LEVEL_CAP; }
var j : u32 = 0;
while (j < TL_N) {
TL[j as usize] = jv_num(d, arr_at(d, obj_get(d, t, "levels"), j));
@@ -427,7 +427,7 @@ pub fn w_data(i: u32, s: []const u8) -> u32 {
}
TL_TOP = jv_num(d, obj_get(d, t, "top"));
EL_N = arr_len(d, obj_get(d, e, "levels"));
- if (EL_N > LEVEL_CAP) { EL_N = LEVEL_CAP; }
+ if (EL_N > W_LEVEL_CAP) { EL_N = W_LEVEL_CAP; }
j = 0;
while (j < EL_N) {
EL[j as usize] = jv_num(d, arr_at(d, obj_get(d, e, "levels"), j));
diff --git a/specs/widgets/t27_vs_nvfp4.t27 b/specs/widgets/t27_vs_nvfp4.t27
index 6676031eec..19e5389baf 100644
--- a/specs/widgets/t27_vs_nvfp4.t27
+++ b/specs/widgets/t27_vs_nvfp4.t27
@@ -292,7 +292,7 @@ test the_page_reads_nothing_of_yours_and_sends_nothing {
; tool.js prints. NOT carried over: where tool.js throws a TypeError (an arm with id T27_STAR, NVFP4,
; BASE or E2M2 missing, d.ratios.test without NVFP4, a missing protocol or run object) it leaves a
; half-drawn page; this page draws the same frame with the missing values as undefined and compares
-; equal only where tool.js does not throw. The data file is cut at DATA_CAP bytes (the page then says
+; equal only where tool.js does not throw. The data file is cut at W_DATA_CAP bytes (the page then says
; it could not be read).
; MEASURED 2026-10-19: t27c 0.5.2 gen + a 5-line Zig export wrapper + zig build-exe wasm32-freestanding
; ReleaseSmall give a 28660-byte wasm. The original page (tool.js) and this one ran in two happy-dom
@@ -308,7 +308,7 @@ test the_page_reads_nothing_of_yours_and_sends_nothing {
; not JSON: 0 differences. A negative control (one node of the native DOM changed) is reported.
; Not compared: a data file where tool.js throws (see above); the stylesheet is not loaded (happy-dom).
-pub const DATA_CAP : u32 = 32768;
+pub const W_DATA_CAP : u32 = 32768;
pub var DATA : [32768]u8 = [0; 32768];
pub var DATA_LEN : u32 = 0;
pub var DATA_STATE : u8 = 0;
@@ -324,7 +324,7 @@ pub fn w_init(h: []const u8) -> u32 {
}
pub fn w_data(i: u32, s: []const u8) -> u32 {
- if (s.len > (DATA_CAP as usize)) {
+ if (s.len > (W_DATA_CAP as usize)) {
DATA_STATE = 2;
return 0;
}
diff --git a/specs/widgets/test_touch.t27 b/specs/widgets/test_touch.t27
index df83e346e9..f1e9ad0240 100644
--- a/specs/widgets/test_touch.t27
+++ b/specs/widgets/test_touch.t27
@@ -321,7 +321,7 @@ test the_page_sends_nothing_and_reads_no_file {
; lane without a vector array) its catch shows SAY_LOAD_FAILED; so does this page (data_ok). A body
; that tool.js cannot build (a merged stamp that is no string, a line text that is no string) throws inside
; its toggle handler, which leaves the row open and empty; this page then draws the body as empty strings.
-; NOT carried over: more than PR_CAP (1024) pull requests; a `merged` stamp compared beyond its first 32
+; NOT carried over: more than W_PR_CAP (1024) pull requests; a `merged` stamp compared beyond its first 32
; bytes; sort-key `merged` values that are objects or arrays; UTF-16 order of astral characters; the last
; merge time when a bad pull request has a non-string stamp (the default .sort() of mixed values).
; MEMORY: DATA 1 MiB (the file is 786923 bytes), the sort keys 100 KB, the page buffer is the host's: the
@@ -350,9 +350,9 @@ test the_page_sends_nothing_and_reads_no_file {
; build; runs of more than 200 tied pull requests in the DOM (the lists of up to 1024 are compared in
; the sort test only); the host's 'toggle' forwarding is the new host code described in the report.
-pub const DATA_CAP : u32 = 1048576;
-pub const PR_CAP : u32 = 1024;
-pub const PAGE : u32 = 40;
+pub const W_DATA_CAP : u32 = 1048576;
+pub const W_PR_CAP : u32 = 1024;
+pub const W_PAGE : u32 = 40;
pub var DATA : [1048576]u8 = [0; 1048576];
pub var DATA_LEN : u32 = 0;
pub var DATA_STATE : u8 = 0;
@@ -385,7 +385,7 @@ pub fn w_init(h: []const u8) -> u32 {
NPR = 0;
LANE = 0 - 1;
BADGE = 0 - 1;
- SHOWN = PAGE;
+ SHOWN = W_PAGE;
RULE_OPEN = false;
return 0;
}
@@ -947,7 +947,7 @@ fn verdict_throws(d: []const u8) -> bool {
}
pub fn w_data(i: u32, s: []const u8) -> u32 {
- if (s.len > (DATA_CAP as usize)) {
+ if (s.len > (W_DATA_CAP as usize)) {
DATA_STATE = 2;
return 0;
}
@@ -973,7 +973,7 @@ pub fn w_data(i: u32, s: []const u8) -> u32 {
const hi : usize = span_hi(arr);
var p : usize = skip_ws(d, span_lo(arr) + 1);
var n : u32 = 0;
- while (p < hi - 1 and n < PR_CAP) {
+ while (p < hi - 1 and n < W_PR_CAP) {
const e : usize = value_end(d, p);
if (e > hi) { break; }
const sp : i64 = jspan(p, e);
@@ -1040,7 +1040,7 @@ fn refilter(d: []const u8) -> u32 {
}
NROWS = n;
var j : u32 = 0;
- while (j < PR_CAP) {
+ while (j < W_PR_CAP) {
ROW_OPEN[j as usize] = false;
ROW_BODY[j as usize] = false;
j = j + 1;
@@ -1053,16 +1053,16 @@ pub fn w_click(i: u32) -> u32 {
const d : []const u8 = DATA[0..(DATA_LEN as usize)];
if (i < 3) {
LANE = (i as i32) - 1;
- SHOWN = PAGE;
+ SHOWN = W_PAGE;
refilter(d);
}
if (i >= 3 and i < 8) {
if (i == 3) { BADGE = 0 - 1; } else { BADGE = 7 - (i as i32); }
- SHOWN = PAGE;
+ SHOWN = W_PAGE;
refilter(d);
}
if (i == 8) {
- SHOWN = SHOWN + PAGE;
+ SHOWN = SHOWN + W_PAGE;
refilter(d);
}
return 0;
diff --git a/specs/widgets/uart_bucket.t27 b/specs/widgets/uart_bucket.t27
index bc169f5915..9c6e6d2996 100644
--- a/specs/widgets/uart_bucket.t27
+++ b/specs/widgets/uart_bucket.t27
@@ -267,7 +267,7 @@ test the_page_sends_nothing {
; fmt(), toFixed, String(x) of the slider value), values read the way String() reads them (js_val.t27).
; Where tool.js throws while it draws (arms without the two windows, no edges, a null wire / carried /
; sources entry) its catch shows SAY_LOAD_FAILED and starts no animation; so does this page (data_ok).
-; NOT carried over: the data file is cut at DATA_CAP bytes (the page then says it could not be read);
+; NOT carried over: the data file is cut at W_DATA_CAP bytes (the page then says it could not be read);
; classList.toggle on happy-dom normalises a class string even when nothing changes (a trailing space of
; "ub-arm " goes at the first set()); this page writes the normalised string, a browser keeps the space.
; MEASURED 2026-10-19: t27c 0.5.2 gen + an 11-function Zig export wrapper + zig build-exe wasm32-freestanding
@@ -289,7 +289,7 @@ test the_page_sends_nothing {
; opacity / transform the stylesheet adds is not seen); focus (range.focus() after a "put window" press is
; not a DOM difference); real pointer or media-query events.
-pub const DATA_CAP : u32 = 16384;
+pub const W_DATA_CAP : u32 = 16384;
pub var DATA : [16384]u8 = [0; 16384];
pub var DATA_LEN : u32 = 0;
pub var DATA_STATE : u8 = 0;
@@ -494,7 +494,7 @@ fn set_win(w0: f64) -> u32 {
}
pub fn w_data(i: u32, s: []const u8) -> u32 {
- if (s.len > (DATA_CAP as usize)) {
+ if (s.len > (W_DATA_CAP as usize)) {
DATA_STATE = 2;
return 0;
}
From afbad2c90b7b46e667ca69f0b02b080c1f6dbbee Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:44:34 +0700
Subject: [PATCH 35/63] widgets: pin-map drawn natively -- 41095-byte wasm,
2582 DOM comparisons, 0 differences (Closes #10938)
The trinity spec pin-map.t27 verbatim (two u8*u8 test products cast to u16 so zig
can evaluate them) plus the native page: board SVG, search, bank and colour
filters, demos, XDC paste/file/drop lint. Reader and lint imported from
pin_map_logic.t27. t27c test-report 15/15, t27b 15/15. Native-only constants
are named W_...; K_DATA_FILES is the one K_ name added, read by the host.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/pin_map.t27 | 2372 +++++++++++++++++++++++++++++++++++++
1 file changed, 2372 insertions(+)
create mode 100644 specs/widgets/pin_map.t27
diff --git a/specs/widgets/pin_map.t27 b/specs/widgets/pin_map.t27
new file mode 100644
index 0000000000..317884d1d5
--- /dev/null
+++ b/specs/widgets/pin_map.t27
@@ -0,0 +1,2372 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/pin-map.t27 -- the XC7A200T FGG676 package, ball by ball, with an XDC pin lint
+; Source of truth for public/widgets/pin-map/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant or comes
+; from pins.json, which scripts/widget-data/pin-map.mjs writes and checks against this file.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT SHOWS: the 26 by 26 ball grid of the XC7A200T in its 676-ball package, seen from above
+; with ball A1 at the top left. The row letters are read from the data, not assumed: prjxray-db
+; uses A to Y without I, O, Q, S and X, then AA to AF. Each ball the database lists is coloured by
+; bank or by pin type; on top sit the ports of one of the project's XDC files, or the reader's own,
+; pasted or dropped, read in the browser and sent nowhere. The lint flags a port placed on a ball
+; that does not exist, on a ball with no user IO, on a transceiver or XADC pin, two ports on one
+; ball, a port with no IOSTANDARD, and a bank whose standards need different VCCO.
+
+; DATA PROVENANCE: the package is prjxray-db ab1fc60 xc7a200tfbg676-1 package_pins.csv. The board
+; carries the FGG676 part; fpga/HARDWARE_SSOT.md in gHashTag/t27 (2026-07-05 update) records that
+; the FGG and FBG 676 parts share the die and the BGA-676 pinout, and AMD publishes one pinout file
+; for both. The overlays are the project's own XDC files at the commits named in DATA_SOURCES; the
+; data script reads each with git show at that commit and lints it with the page's own xdc.js, so
+; the counts below are the counts the page shows.
+
+; WHAT IT DOES NOT CLAIM: prjxray-db lists only the balls the fabric reaches (user IO, transceiver
+; pads, the XADC pair). The other 234 balls are power, ground or dedicated pins such as JTAG, DONE
+; and CCLK, and the database does not say which is which; the page draws them grey and says so
+; rather than guessing. The VCCO check covers single-ended standards only and reads AMD UG471, not
+; a board schematic. A clean lint does not prove a board works: it proves the balls exist and are
+; user IO.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_pin_map;
+
+use widgets::html_out::OVER;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::ok;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::same_bytes;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::json_scan::is_true;
+use widgets::json_scan::is_false;
+use widgets::json_scan::is_null;
+use widgets::json_scan::num_f64;
+use widgets::json_scan::num_i64;
+use widgets::pin_map_logic::NONE;
+use widgets::pin_map_logic::NO_TEXT;
+use widgets::pin_map_logic::PACK;
+use widgets::pin_map_logic::B_TAB;
+use widgets::pin_map_logic::B_CR;
+use widgets::pin_map_logic::B_LF;
+use widgets::pin_map_logic::B_SPACE;
+use widgets::pin_map_logic::B_QUOTE;
+use widgets::pin_map_logic::B_HASH;
+use widgets::pin_map_logic::B_COMMA;
+use widgets::pin_map_logic::B_DASH;
+use widgets::pin_map_logic::B_ZERO;
+use widgets::pin_map_logic::B_SEMI;
+use widgets::pin_map_logic::B_STAR;
+use widgets::pin_map_logic::B_QUERY;
+use widgets::pin_map_logic::B_OPEN_B;
+use widgets::pin_map_logic::B_BSLASH;
+use widgets::pin_map_logic::B_CLOSE_B;
+use widgets::pin_map_logic::B_UNDER;
+use widgets::pin_map_logic::B_OPEN_C;
+use widgets::pin_map_logic::B_CLOSE_C;
+use widgets::pin_map_logic::T_IO;
+use widgets::pin_map_logic::T_CLOCK;
+use widgets::pin_map_logic::T_CONFIG;
+use widgets::pin_map_logic::T_ANALOG;
+use widgets::pin_map_logic::T_VREF;
+use widgets::pin_map_logic::T_MGT;
+use widgets::pin_map_logic::T_XADC;
+use widgets::pin_map_logic::ID_NOBALL;
+use widgets::pin_map_logic::ID_NOTIO;
+use widgets::pin_map_logic::ID_MGT;
+use widgets::pin_map_logic::ID_XADC;
+use widgets::pin_map_logic::ID_TWOPORTS;
+use widgets::pin_map_logic::ID_MOVED;
+use widgets::pin_map_logic::ID_NOSTD;
+use widgets::pin_map_logic::ID_VCCO;
+use widgets::pin_map_logic::ID_DUAL;
+use widgets::pin_map_logic::SEV_ERROR;
+use widgets::pin_map_logic::SEV_WARN;
+use widgets::pin_map_logic::SEV_INFO;
+use widgets::pin_map_logic::KIND_NONE;
+use widgets::pin_map_logic::KIND_PIN;
+use widgets::pin_map_logic::KIND_STD;
+use widgets::pin_map_logic::M_PINS;
+use widgets::pin_map_logic::M_STDS;
+use widgets::pin_map_logic::M_CMDS;
+use widgets::pin_map_logic::M_PLACED;
+use widgets::pin_map_logic::M_SKIPPED;
+use widgets::pin_map_logic::M_NORM;
+use widgets::pin_map_logic::LM_PORTS;
+use widgets::pin_map_logic::LM_FINDS;
+use widgets::pin_map_logic::LM_POOL;
+use widgets::pin_map_logic::LM_STATUS;
+use widgets::pin_map_logic::COLL_RANK;
+use widgets::pin_map_logic::pack;
+use widgets::pin_map_logic::lo_of;
+use widgets::pin_map_logic::hi_of;
+use widgets::pin_map_logic::is_ws;
+use widgets::pin_map_logic::is_digit;
+use widgets::pin_map_logic::up_b;
+use widgets::pin_map_logic::span_is;
+use widgets::pin_map_logic::span_eq;
+use widgets::pin_map_logic::trim_span;
+use widgets::pin_map_logic::unwrap_span;
+use widgets::pin_map_logic::next_word;
+use widgets::pin_map_logic::is_config_token;
+use widgets::pin_map_logic::is_analog_token;
+use widgets::pin_map_logic::any_token;
+use widgets::pin_map_logic::is_dual_config;
+use widgets::pin_map_logic::pin_type;
+use widgets::pin_map_logic::glob_match;
+use widgets::pin_map_logic::covers;
+use widgets::pin_map_logic::ports_head;
+use widgets::pin_map_logic::put_pin;
+use widgets::pin_map_logic::emit_ports;
+use widgets::pin_map_logic::key_kind;
+use widgets::pin_map_logic::process_command;
+use widgets::pin_map_logic::process_line;
+use widgets::pin_map_logic::xdc_parse;
+use widgets::pin_map_logic::pool_put;
+use widgets::pin_map_logic::dec_len;
+use widgets::pin_map_logic::pool_put_dec;
+use widgets::pin_map_logic::put_find;
+use widgets::pin_map_logic::on_grid;
+use widgets::pin_map_logic::field_of;
+use widgets::pin_map_logic::table_find;
+use widgets::pin_map_logic::parse_dec;
+use widgets::pin_map_logic::coll_cmp;
+use widgets::pin_map_logic::find_cmp;
+use widgets::pin_map_logic::sort_findings;
+use widgets::pin_map_logic::xdc_lint;
+use widgets::pin_map_logic::xdc_summarize;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "pin-map";
+pub const TITLE : str = "Pin map: the XC7A200T FGG676 package ball by ball, with an XDC lint";
+pub const DESCRIPTION : str = "All 676 balls of the XC7A200T FGG676 by bank and type, with our board's pins on top. Paste or drop an XDC: it is linted in your browser and sent nowhere.";
+pub const IMAGE_ALT : str = "Pin map card: the XC7A200T FGG676 package drawn as chip art, 676 balls in a 26 by 26 grid tinted by bank, grey where prjxray-db lists no pin. The project's Wukong XDC places 49 ports: 36 balls glow green on user IO, 13 glow red because that package has no user IO there.";
+pub const HOOK : str = "Our own Wukong XDC puts 13 of its 49 ports on balls with no user IO. Paste yours and see where every pin lands on the XC7A200T.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [8]str = ["prjxray-db ab1fc60 artix7 xc7a200tfbg676-1/package_pins.csv, 442 pins, sha256 3bfc5cc78ac975cc69e677c69d8c7bdc89ab8bd629ea2cef88625002a99858f7; the -2, -2L, -3 and fbv676 files checked byte-identical", "gHashTag/t27 fpga/HARDWARE_SSOT.md at 8fcafe894, 2026-07-05 update: xc7a200tfbg676-1 and xc7a200tfgg676-1 share the die and the BGA-676 pinout", "gHashTag/t27 4832ec6ab specs/fpga/constraints/qmtech_a100t.xdc: the Wukong board XDC, 49 ports", "gHashTag/t27 ea15cd54c fpga/verilog/ternary_mac_demo_top.xdc (the flashed LED demo) and fpga/bscan_spi_qmtech/bscan_spi_qmtech.xdc", "gHashTag/trinity-fpga e1dc687b7 fpga/openxc7-synth/power_modes.xdc: the QMTECH core board pins", "prjxray-db ab1fc60 xc7a100tfgg676-1/package_pins.csv: all 342 balls carry the same pin name on the 200T", "AMD UG471 7 Series FPGAs SelectIO Resources: the VCCO each single-ended IOSTANDARD needs", "scripts/widget-data/pin-map.mjs: derives every K_ count with public/widgets/pin-map/xdc.js and exits 1 when this spec disagrees"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The package, as prjxray-db lists it (pin-map.mjs checks every one) -----------------------
+pub const K_GRID_ROWS : u8 = 26;
+pub const K_GRID_COLS : u8 = 26;
+pub const K_BALLS : u16 = 676;
+; Single-letter rows A..Y without I O Q S X; double-letter rows AA..AF.
+pub const K_ROWS_SINGLE : u8 = 20;
+pub const K_ROWS_DOUBLE : u8 = 6;
+pub const K_SKIPPED_LETTERS : u8 = 6;
+pub const K_LISTED_PINS : u16 = 442;
+pub const K_UNLISTED_BALLS : u16 = 234;
+pub const K_IO_BANKS : u8 = 8;
+pub const K_IO_PER_BANK : u8 = 50;
+pub const K_USER_IO : u16 = 400;
+pub const K_MGT_BANKS : u8 = 2;
+pub const K_MGT_PINS_PER_BANK : u8 = 20;
+pub const K_MGT_PINS : u8 = 40;
+; Bank 0 as the database lists it: only VP_0 and VN_0, the XADC pair.
+pub const K_DEDICATED_LISTED : u8 = 2;
+pub const K_BANKS : u8 = 11;
+; User IO banks, then transceiver banks, then bank 0.
+pub const K_BANK_IDS : [11]u16 = [12, 13, 14, 15, 16, 33, 34, 35, 213, 216, 0];
+; Balls per pin type, in the order of SAY_TYPE_IDS.
+pub const K_TYPE_COUNTS : [8]u16 = [243, 64, 48, 32, 13, 40, 2, 234];
+; The XC7A100T in the same package: balls whose pin name matches the 200T's.
+pub const K_A100T_PINS : u16 = 342;
+
+; --- The demo XDCs, linted by xdc.js in node (order of SAY_DEMO_IDS) ---------------------------
+pub const K_DEMO_PORTS : [4]u8 = [49, 2, 8, 3];
+pub const K_DEMO_OK : [4]u8 = [36, 2, 8, 2];
+pub const K_DEMO_FLAGGED : [4]u8 = [13, 0, 0, 1];
+
+; --- VCCO per single-ended IOSTANDARD (AMD UG471), millivolts --------------------------------
+pub const SAY_VCCO_STD : [16]str = ["LVCMOS33", "LVTTL", "LVCMOS25", "LVCMOS18", "LVCMOS15", "LVCMOS12", "SSTL15", "SSTL135", "SSTL18_I", "SSTL18_II", "HSTL_I", "HSTL_II", "HSTL_I_18", "HSTL_II_18", "HSUL_12", "MOBILE_DDR"];
+pub const K_VCCO_MV : [16]u16 = [3300, 3300, 2500, 1800, 1500, 1200, 1500, 1350, 1800, 1800, 1500, 1500, 1800, 1800, 1200, 1800];
+
+; --- Page limits ------------------------------------------------------------------------------
+pub const K_MAX_XDC_BYTES : u32 = 2000000;
+pub const K_MAX_LINT_ROWS : u16 = 200;
+
+; --- Words on screen ------------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_PART_LINE : str = "XC7A200T, 676-ball package: {0} rows by {1} columns, {2} pins listed, {3} banks";
+pub const SAY_GRID_ALT : str = "Ball grid of the XC7A200T 676-ball package, seen from above, A1 at the top left";
+pub const SAY_SKIPPED_LETTERS : [6]str = ["I", "O", "Q", "S", "X", "Z"];
+pub const SAY_ROWS_NOTE : str = "Rows run A to AF; the letters {0} are not used.";
+pub const SAY_SEARCH_LABEL : str = "Find";
+pub const SAY_SEARCH_HINT : str = "ball, signal or pin name: E3, led, MRCC";
+pub const SAY_MATCHES : str = "Matching balls: {0}";
+pub const SAY_NO_MATCH : str = "Nothing matches.";
+pub const SAY_COLOUR_LABEL : str = "Colour by";
+pub const SAY_COLOUR_MODES : [2]str = ["bank", "pin type"];
+pub const SAY_BANK_LABEL : str = "Bank";
+pub const SAY_ALL : str = "all";
+pub const SAY_BANK_NAME : str = "bank {0}";
+pub const SAY_BANK_KINDS : [3]str = ["user IO", "transceiver", "XADC pair"];
+pub const SAY_BANK_LEGEND : str = "{0}: {1}, {2} pins";
+pub const SAY_TYPE_IDS : [8]str = ["io", "clock", "config", "analog", "vref", "mgt", "xadc", "unlisted"];
+pub const SAY_TYPE_NAMES : [8]str = ["user IO", "clock capable (MRCC, SRCC)", "IO shared with configuration", "IO with an XADC input", "IO or VREF", "transceiver (MGT)", "XADC VP or VN", "not in prjxray-db"];
+pub const SAY_TYPE_LEGEND : str = "{0}: {1}";
+pub const SAY_DETAIL_HINT : str = "Hover or tap a ball.";
+pub const SAY_DETAIL_FIELDS : [9]str = ["ball", "pin name", "bank", "type", "site", "tile", "signal", "IOSTANDARD", "XDC line"];
+pub const SAY_NONE : str = "none";
+pub const SAY_UNLISTED_LINE : str = "prjxray-db lists no pin here: power, ground or a dedicated pin such as JTAG, DONE or CCLK. The database does not say which.";
+pub const SAY_OVERLAY_LABEL : str = "Pins from";
+pub const SAY_DEMO_IDS : [4]str = ["wukong", "flashed", "core-board", "spi-proxy"];
+pub const SAY_DEMO_NAMES : [4]str = ["Wukong board", "Flashed LED demo", "Core board", "SPI flash proxy"];
+pub const SAY_DEMO_LINES : [4]str = ["The project's Wukong board XDC. Its header names the XC7A200T-FGG676 core board and the Wukong expansion.", "The two LEDs of ternary_mac_demo_top, the design fpga/HARDWARE_SSOT.md programs onto the board.", "The QMTECH core board pins used by power_modes: clock, LEDs, UART, two switches and a button.", "The JTAG-to-SPI flash proxy. Its comment calls C8 FCS_B; on this package C8 is a transceiver pin."];
+pub const SAY_DEMO_SOURCE : str = "{0} at {1}: {2}";
+pub const SAY_YOURS : str = "Your XDC";
+pub const SAY_PASTE_HINT : str = "Paste an XDC here, or drop a file on the box below.";
+pub const SAY_DROP : str = "Drop or pick an .xdc file";
+pub const SAY_READ : str = "Lint it";
+pub const SAY_CLEAR : str = "Clear";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab; nothing is uploaded or stored.";
+pub const SAY_TOO_BIG : str = "{0} is {1} bytes; this page reads up to {2}.";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_DATA : str = "The package data did not load.";
+pub const SAY_EMPTY_XDC : str = "No PACKAGE_PIN or LOC on get_ports found.";
+pub const SAY_LINT_TITLE : str = "Lint";
+pub const SAY_LINT_SUMMARY : str = "Ports placed: {0}. Pass: {1}. Balls flagged as errors: {2}.";
+pub const SAY_PARSE_LINE : str = "{0} commands read, {1} place ports or set their IOSTANDARD, {2} are about something else.";
+pub const SAY_LINT_CLEAN : str = "Every placed port sits on a user IO ball.";
+pub const SAY_LINT_MORE : str = "{0} more not shown.";
+pub const SAY_SEV : [3]str = ["error", "warning", "note"];
+pub const SAY_LINT_IDS : [9]str = ["noball", "notio", "mgt", "xadc", "twoports", "moved", "nostd", "vcco", "dual"];
+pub const SAY_LINT_TEXT : [9]str = ["{0} ({1}): no such ball on this package.", "{0} ({1}): not a user IO. prjxray-db lists no pin here, so it is power, ground or a dedicated pin.", "{0} ({1}): {2} is a transceiver pin, not a user IO.", "{0} ({1}): {2} is the dedicated XADC input, not a user IO.", "{0}: given to more than one port: {2}.", "{0} ({1}): placed again; line {3} had it on {2}.", "{0} ({1}): no IOSTANDARD.", "Bank {2} mixes standards that need different VCCO: {3}.", "{0} ({1}): {2} doubles as a configuration pin; it is user IO once the FPGA is configured."];
+pub const SAY_OVERLAY_LEGEND : [3]str = ["port on user IO", "flagged", "matches the search"];
+pub const SAY_NOTE_FGG : str = "The board carries the FGG676 part; prjxray-db lists the FBG676. fpga/HARDWARE_SSOT.md (2026-07-05) records the same die and the same BGA-676 pinout.";
+pub const SAY_NOTE_UNLISTED : str = "{0} of {1} balls are not in prjxray-db: power, ground and dedicated pins. They are drawn grey, not guessed.";
+pub const SAY_NOTE_100T : str = "Many project XDCs name the XC7A100T: its {0} listed balls carry the same pin names on the 200T.";
+
+; --- Tests ------------------------------------------------------------------------------------
+test the_grid_is_26_by_26 {
+ assert K_GRID_ROWS == 26;
+ assert K_GRID_COLS == 26;
+ assert K_BALLS == (K_GRID_ROWS as u16) * (K_GRID_COLS as u16);
+ assert K_GRID_ROWS == K_ROWS_SINGLE + K_ROWS_DOUBLE;
+ assert K_ROWS_SINGLE + K_SKIPPED_LETTERS == 26;
+ assert SAY_SKIPPED_LETTERS[0] == "I";
+ assert SAY_SKIPPED_LETTERS[5] == "Z";
+}
+
+test every_ball_is_listed_or_counted_as_unlisted {
+ assert K_LISTED_PINS + K_UNLISTED_BALLS == K_BALLS;
+ assert K_LISTED_PINS == K_USER_IO + K_MGT_PINS + K_DEDICATED_LISTED;
+ assert K_TYPE_COUNTS[0] + K_TYPE_COUNTS[1] + K_TYPE_COUNTS[2] + K_TYPE_COUNTS[3] + K_TYPE_COUNTS[4] + K_TYPE_COUNTS[5] + K_TYPE_COUNTS[6] + K_TYPE_COUNTS[7] == K_BALLS;
+ assert K_TYPE_COUNTS[7] == K_UNLISTED_BALLS;
+ assert K_TYPE_COUNTS[5] == K_MGT_PINS;
+ assert K_TYPE_COUNTS[6] == K_DEDICATED_LISTED;
+}
+
+test the_bank_count_matches_the_data {
+ assert K_BANKS == 11;
+ assert K_BANKS == K_IO_BANKS + K_MGT_BANKS + 1;
+ assert K_USER_IO == (K_IO_BANKS as u16) * (K_IO_PER_BANK as u16);
+ assert K_MGT_PINS == K_MGT_BANKS * K_MGT_PINS_PER_BANK;
+ assert K_BANK_IDS[0] == 12;
+ assert K_BANK_IDS[7] == 35;
+ assert K_BANK_IDS[8] == 213;
+ assert K_BANK_IDS[9] == 216;
+ assert K_BANK_IDS[10] == 0;
+}
+
+test the_100t_fits_inside_the_200t_package {
+ assert K_A100T_PINS < K_USER_IO;
+ assert K_A100T_PINS > 0;
+}
+
+test the_demo_counts_add_up {
+ assert K_DEMO_OK[0] + K_DEMO_FLAGGED[0] == K_DEMO_PORTS[0];
+ assert K_DEMO_OK[1] + K_DEMO_FLAGGED[1] == K_DEMO_PORTS[1];
+ assert K_DEMO_OK[2] + K_DEMO_FLAGGED[2] == K_DEMO_PORTS[2];
+ assert K_DEMO_OK[3] + K_DEMO_FLAGGED[3] == K_DEMO_PORTS[3];
+ assert SAY_DEMO_IDS[0] == "wukong";
+ assert K_DEMO_FLAGGED[0] == 13;
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_VCCO_MV[0] == 3300;
+ assert SAY_VCCO_STD[0] == "LVCMOS33";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/pin-map/tool.js (340 lines of hand-written JavaScript). The
+; XDC reader and the lint are NOT rewritten here: they are imported from pin_map_logic.t27 (the port of
+; xdc.js, differential-tested). The words above are unchanged and are the only words the view prints.
+; The host protocol is jtag_verdict.t27's, plus (all optional, the host calls what the wasm exports):
+; w_init(hash) the page opens: state "loading"
+; w_data(i, bytes) file i of K_DATA_FILES has been fetched (0 pins.json, 1 demos.json)
+; w_data_failed(i) it could not be fetched
+; w_input(i, bytes) control i changed; i counts the input and textarea elements in page order:
+; 0 the search field, 1 the XDC text area, 2 the file input (ignored)
+; w_click(i) button i was pressed (index among the page's buttons); an SVG click goes
+; through w_point
+; w_point(kind, x, y) the pointer over the board, in viewBox units: kind 0 moved (mouse only),
+; 1 left the board, 2 clicked
+; w_key(bytes) -> 0 | 1 a key went down on the board; 1 means it was used (preventDefault)
+; w_drag(kind) 0 dragover, 1 dragleave, 2 drop on the file box
+; w_file(name, size, bytes, st) a file was picked or dropped: its name, size in bytes, its text as
+; UTF-8 bytes (empty when it was not read), st 0 read / 1 read failed
+; w_value(i, dst) -> n the text control i must now hold (the wasm pushes it: Clear, a file), or
+; 4294967295 for "leave what the reader typed"
+; w_effect(dst) -> n a CSS selector the host must scroll into view (a finding was pressed), or 0
+; w_render(dst) -> len the page's HTML, 0 on overflow
+; The board is drawn as SVG text inside w_render; the host sets the HTML as innerHTML.
+; --
+; What is NOT carried over from tool.js (also reported in the commit message):
+; * the 120 ms and 350 ms debounce of the search and the XDC text area: the native page lints on every
+; keystroke (the result after the last keystroke is the same);
+; * XDC texts above 2097152 bytes are cut there (the page's own limit is K_MAX_XDC_BYTES = 2000000);
+; more than 1024 ports or 2048 findings in one XDC make the lint give no ports and no findings; a
+; package with more than 40 rows, 40 columns, 2048 pins or 32 banks, or data files above 262144
+; bytes, show the "data did not load" line;
+; * the search is read as ASCII (toUpperCase/toLowerCase and trim() of non-ASCII letters and spaces are
+; not mapped); a selected ball name is cut at 255 bytes, a search at 1023;
+; * a pins.json or demos.json that is JSON but lacks rows, cols, banks or pins (tool.js then throws and
+; leaves the loading page) is shown as the loading page too, without any promise of equal details.
+; --
+
+pub const K_DATA_FILES : [2]str = ["./pins.json", "./demos.json"];
+
+pub const W_CELL : u32 = 20;
+pub const W_OX : u32 = 26;
+pub const W_OY : u32 = 22;
+pub const W_MAX_ROWS : u32 = 40;
+pub const W_MAX_COLS : u32 = 40;
+pub const W_MAX_CELLS : u32 = 1600;
+pub const W_MAX_PINS : u32 = 2048;
+pub const W_MAX_BANKS : u32 = 32;
+pub const W_CAPX : u32 = 2097152;
+pub const W_NO : u32 = 4294967295;
+pub const W_CAP_DATA : u32 = 262144;
+
+pub var PINS_RAW : [262144]u8 = [0; 262144];
+pub var PINS_LEN : u32 = 0;
+pub var DEMOS_RAW : [262144]u8 = [0; 262144];
+pub var DEMOS_LEN : u32 = 0;
+pub var D_STATE : [2]u8 = [0; 2];
+pub var LOADED : bool = false;
+pub var FAILED : bool = false;
+
+pub var DP : [262144]u8 = [0; 262144];
+pub var DP_LEN : u32 = 0;
+pub var PB_O : [2048]u32 = [0; 2048];
+pub var PB_L : [2048]u32 = [0; 2048];
+pub var PS_O : [2048]u32 = [0; 2048];
+pub var PS_L : [2048]u32 = [0; 2048];
+pub var PT_O : [2048]u32 = [0; 2048];
+pub var PT_L : [2048]u32 = [0; 2048];
+pub var PF_O : [2048]u32 = [0; 2048];
+pub var PF_L : [2048]u32 = [0; 2048];
+pub var PY_O : [2048]u32 = [0; 2048];
+pub var PY_L : [2048]u32 = [0; 2048];
+pub var PY_C : [2048]u32 = [0; 2048];
+pub var PB_BANK : [2048]i64 = [0; 2048];
+pub var PIN_LAST : [2048]u8 = [0; 2048];
+pub var N_PINS : u32 = 0;
+pub var ROW_O : [40]u32 = [0; 40];
+pub var ROW_L : [40]u32 = [0; 40];
+pub var N_ROWS : u32 = 0;
+pub var COLS : u32 = 0;
+pub var BK_ID : [32]i64 = [0; 32];
+pub var BK_PINS : [32]i64 = [0; 32];
+pub var BK_KIND : [32]u32 = [0; 32];
+pub var N_BANKS : u32 = 0;
+pub var DEMO_EL : [8]i64 = [0; 8];
+pub var N_DEMO : u32 = 0;
+pub var CELLPIN : [1600]i32 = [0; 1600];
+
+pub var PKG_TXT : [131072]u8 = [0; 131072];
+pub var PKG_LEN : u32 = 0;
+pub var ROWS_TXT : [512]u8 = [0; 512];
+pub var ROWS_LEN : u32 = 0;
+pub var VCCO_TXT : [1024]u8 = [0; 1024];
+pub var VCCO_LEN : u32 = 0;
+
+pub var PASTE : [2097152]u8 = [0; 2097152];
+pub var PASTE_LEN : u32 = 0;
+pub var NORM : [2097152]u8 = [0; 2097152];
+pub var UPN : [2097152]u8 = [0; 2097152];
+pub var XT : [262144]u8 = [0; 262144];
+
+pub var LP_PINS : [5120]u32 = [0; 5120];
+pub var LP_STDS : [5120]u32 = [0; 5120];
+pub var LP_META : [8]u32 = [0; 8];
+pub var LP_WORK : [5120]u32 = [0; 5120];
+pub var LP_PORTS : [12288]u32 = [0; 12288];
+pub var LP_FINDS : [22528]u32 = [0; 22528];
+pub var LP_ORDER : [2048]u32 = [0; 2048];
+pub var LP_POOL : [1048576]u8 = [0; 1048576];
+pub var LP_LM : [4]u32 = [0; 4];
+pub var LP_SUM : [6]u32 = [0; 6];
+pub var N_PORTS : u32 = 0;
+pub var N_FINDS : u32 = 0;
+pub var PORT_CELL : [1024]i32 = [0; 1024];
+pub var CELL_BAD : [1600]u8 = [0; 1600];
+pub var CELL_USED : [1600]u8 = [0; 1600];
+pub var LINT_APPLIED : bool = false;
+
+pub var QUERY : [1024]u8 = [0; 1024];
+pub var QUERY_LEN : u32 = 0;
+pub var QU : [1024]u8 = [0; 1024];
+pub var QL : [1024]u8 = [0; 1024];
+pub var Q_LEN : u32 = 0;
+pub var PIN_M : [2048]u8 = [0; 2048];
+pub var PORT_M : [1024]u8 = [0; 1024];
+pub var CELL_M : [1600]u8 = [0; 1600];
+pub var M_ACTIVE : bool = false;
+pub var M_COUNT : u32 = 0;
+
+pub var CLS_SEQ : [6400]u8 = [0; 6400];
+pub var CLS_N : [1600]u8 = [0; 1600];
+pub var PAINTED : bool = false;
+
+pub var COLOUR : u32 = 0;
+pub var BANK_SEL : i64 = -1;
+pub var HOVER : i32 = -1;
+pub var SEL : [256]u8 = [0; 256];
+pub var SEL_LEN : u32 = 0;
+pub var RING : i32 = -1;
+pub var DETAIL_SHOWN : bool = false;
+pub var DL_KIND : u32 = 0;
+pub var DL_IDX : u32 = 0;
+pub var DS_KIND : u32 = 0;
+pub var DS_IDX : u32 = 0;
+pub var DS_NAME : [512]u8 = [0; 512];
+pub var DS_NAME_LEN : u32 = 0;
+pub var DEMO_ON : i32 = -1;
+pub var ERR_KIND : u32 = 0;
+pub var ERR_NAME : [512]u8 = [0; 512];
+pub var ERR_NAME_LEN : u32 = 0;
+pub var ERR_SIZE : u64 = 0;
+pub var DROP_OVER : bool = false;
+pub var PASTE_PUSH : bool = false;
+pub var EFFECT : bool = false;
+pub var FILE_NAME : [512]u8 = [0; 512];
+
+pub var TMPB : [96]u8 = [0; 96];
+pub var VA : [96]u8 = [0; 96];
+pub var VB : [96]u8 = [0; 96];
+pub var VC : [96]u8 = [0; 96];
+pub var VD : [96]u8 = [0; 96];
+pub var IDBUF : [256]u8 = [0; 256];
+
+// ------------------------------------------------------------------------------------------------
+// small helpers
+// ------------------------------------------------------------------------------------------------
+
+pub var OVF : bool = false;
+
+/// The JSON string at sp, decoded to UTF-8, written to dst from `at`; the new length. OVF when it does not fit.
+fn dec_into(d: []const u8, sp: i64, dst: []u8, at: u32) -> u32 {
+ if (kind(d, sp) != J_STRING) { return at; }
+ const hi : usize = span_hi(sp) - 1;
+ var p : usize = span_lo(sp) + 1;
+ var n : u32 = at;
+ while (p < hi) {
+ var cp : u32 = d[p] as u32;
+ var adv : usize = 1;
+ if (d[p] == 92 and p + 1 < hi) {
+ const e : u32 = escape_at(d, p + 1, hi);
+ cp = e / 16;
+ adv = 1 + ((e % 16) as usize);
+ }
+ if (adv == 1 and d[p] != 92 and d[p] >= 128) {
+ if ((n as usize) >= dst.len) { OVF = true; return n; }
+ dst[n as usize] = d[p];
+ n = n + 1;
+ p = p + 1;
+ continue;
+ }
+ const w : u32 = cp_utf8_len(cp);
+ var j : u32 = 0;
+ while (j < w) {
+ if ((n as usize) >= dst.len) { OVF = true; return n; }
+ dst[n as usize] = cp_utf8_byte(cp, j);
+ n = n + 1;
+ j = j + 1;
+ }
+ p = p + adv;
+ }
+ return n;
+}
+
+/// Is the JSON value at sp the string lit?
+fn jstr_is(d: []const u8, sp: i64, lit: []const u8) -> bool {
+ if (kind(d, sp) != J_STRING) { return false; }
+ if (str_len(d, sp) != (lit.len as u32)) { return false; }
+ var k : usize = 0;
+ while (k < lit.len) {
+ if (str_byte(d, sp, k as u32) != lit[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// Does hay[ho .. ho + hl] contain nd[0 .. nl]?
+fn contains(hay: []const u8, ho: u32, hl: u32, nd: []const u8, nl: u32) -> bool {
+ if (nl == 0) { return true; }
+ if (hl < nl) { return false; }
+ var i : u32 = 0;
+ while (i + nl <= hl) {
+ var k : u32 = 0;
+ while (k < nl and hay[(ho + i + k) as usize] == nd[k as usize]) { k = k + 1; }
+ if (k == nl) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+/// contains() on hay with every ASCII capital turned to its small letter (toLowerCase().includes()).
+fn contains_low(hay: []const u8, ho: u32, hl: u32, nd: []const u8, nl: u32) -> bool {
+ if (nl == 0) { return true; }
+ if (hl < nl) { return false; }
+ var i : u32 = 0;
+ while (i + nl <= hl) {
+ var k : u32 = 0;
+ while (k < nl and low_b(hay[(ho + i + k) as usize]) == nd[k as usize]) { k = k + 1; }
+ if (k == nl) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn low_b(b: u8) -> u8 {
+ if (b >= 65 and b <= 90) { return b + 32; }
+ return b;
+}
+
+fn copy_to(dst: []u8, src: []const u8, cap: u32) -> u32 {
+ var n : u32 = src.len as u32;
+ if (n > cap) { n = cap; }
+ var k : u32 = 0;
+ while (k < n) {
+ dst[k as usize] = src[k as usize];
+ k = k + 1;
+ }
+ return n;
+}
+
+/// A signed whole number as text in a value buffer; its length.
+fn sdec_to(dst: []u8, v: i64) -> u32 {
+ if (v < 0) {
+ dst[0] = 45;
+ return 1 + dec_to(dst[1..], (0 - v) as u64);
+ }
+ return dec_to(dst, v as u64);
+}
+
+fn floor_i(x: f64) -> i64 {
+ var t : i64 = x as i64;
+ if ((t as f64) > x) { t = t - 1; }
+ return t;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the board: cells, ball names
+// ------------------------------------------------------------------------------------------------
+
+/// The ball name of cell ci (the row text then the column number), written to buf; its length.
+fn cell_id(ci: u32, buf: []u8) -> u32 {
+ const ri : u32 = ci / COLS;
+ const c : u32 = ci % COLS + 1;
+ var n : u32 = 0;
+ var k : u32 = 0;
+ while (k < ROW_L[ri as usize]) {
+ buf[n as usize] = DP[(ROW_O[ri as usize] + k) as usize];
+ n = n + 1;
+ k = k + 1;
+ }
+ n = n + dec_to(buf[(n as usize)..], c as u64);
+ return n;
+}
+
+/// The cell whose ball name is exactly s (-1 for none): the row text, then a column number 1..COLS
+/// without leading zeros.
+fn cell_of(s: []const u8) -> i32 {
+ if (COLS == 0) { return -1; }
+ var ri : u32 = 0;
+ while (ri < N_ROWS) {
+ const rl : usize = ROW_L[ri as usize] as usize;
+ if (s.len > rl and span_is(DP[0..(DP_LEN as usize)], ROW_O[ri as usize] as usize, (ROW_O[ri as usize] as usize) + rl, s[0..rl])) {
+ var v : u64 = 0;
+ var ok_digits : bool = s[rl] != 48;
+ var q : usize = rl;
+ while (q < s.len and ok_digits) {
+ if (is_digit(s[q]) and q - rl < 9) { v = v * 10 + ((s[q] - 48) as u64); } else { ok_digits = false; }
+ q = q + 1;
+ }
+ if (ok_digits and v >= 1 and v <= (COLS as u64)) { return ((ri * COLS) as i32) + (v as i32) - 1; }
+ }
+ ri = ri + 1;
+ }
+ return -1;
+}
+
+/// The pin whose ball is exactly s (the last such pin, as a Map keeps); -1 for none.
+fn pin_of(s: []const u8) -> i32 {
+ var j : i32 = (N_PINS as i32) - 1;
+ while (j >= 0) {
+ const o : usize = PB_O[j as usize] as usize;
+ if (span_is(DP[0..(DP_LEN as usize)], o, o + (PB_L[j as usize] as usize), s)) { return j; }
+ j = j - 1;
+ }
+ return -1;
+}
+
+/// JavaScript's slice(0, 9) of a UTF-8 string: the bytes that make up its first 9 UTF-16 units.
+fn utf16_prefix_len(s: []const u8, units: u32) -> u32 {
+ var n : u32 = 0;
+ var u : u32 = 0;
+ while ((n as usize) < s.len) {
+ const b : u8 = s[n as usize];
+ var w : u32 = 1;
+ var uu : u32 = 1;
+ if (b >= 192 and b < 224) { w = 2; }
+ if (b >= 224 and b < 240) { w = 3; }
+ if (b >= 240) { w = 4; uu = 2; }
+ if (u + uu > units) { return n; }
+ u = u + uu;
+ n = n + w;
+ }
+ if ((n as usize) > s.len) { return s.len as u32; }
+ return n;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the data: pins.json and demos.json
+// ------------------------------------------------------------------------------------------------
+
+fn type_code(o: u32, l: u32) -> u32 {
+ var k : u32 = 0;
+ while ((k as usize) < SAY_TYPE_IDS.len) {
+ if (span_is(DP[0..(DP_LEN as usize)], o as usize, (o + l) as usize, SAY_TYPE_IDS[k as usize])) { return k; }
+ k = k + 1;
+ }
+ return 99;
+}
+
+fn append_pkg(b: []const u8, o: u32, l: u32) {
+ var k : u32 = 0;
+ while (k < l) {
+ if (PKG_LEN < 131072) { PKG_TXT[PKG_LEN as usize] = b[(o + k) as usize]; PKG_LEN = PKG_LEN + 1; } else { OVF = true; }
+ k = k + 1;
+ }
+}
+
+fn pkg_byte(b: u8) {
+ if (PKG_LEN < 131072) { PKG_TXT[PKG_LEN as usize] = b; PKG_LEN = PKG_LEN + 1; } else { OVF = true; }
+}
+
+/// Is d one JSON value and nothing else? Only the outer shape is checked: the first byte, the literals,
+/// the end of the value and what follows it; a fault inside an object or array is not looked for.
+fn json_ok(d: []const u8) -> bool {
+ const r : i64 = root(d);
+ if (r < 0) { return false; }
+ const lo : usize = span_lo(r);
+ const hi : usize = span_hi(r);
+ if (skip_ws(d, hi) < d.len) { return false; }
+ const c : u8 = d[lo];
+ if (c == 123 or c == 91 or c == 34) { return true; }
+ if (c == 116) { return span_is(d, lo, hi, "true"); }
+ if (c == 102) { return span_is(d, lo, hi, "false"); }
+ if (c == 110) { return span_is(d, lo, hi, "null"); }
+ if (c == 45 or is_digit(c)) { return true; }
+ return false;
+}
+
+/// pins.json: 0 read, 1 not JSON, 2 not the shape tool.js reads, 3 over a capacity of this page.
+fn load_pins() -> u32 {
+ const d : []const u8 = PINS_RAW[0..(PINS_LEN as usize)];
+ if (!json_ok(d)) { return 1; }
+ const r : i64 = root(d);
+ if (kind(d, r) != J_OBJECT) { return 2; }
+ const rows : i64 = obj_get(d, r, "rows");
+ const cols : i64 = obj_get(d, r, "cols");
+ const banks : i64 = obj_get(d, r, "banks");
+ const pins : i64 = obj_get(d, r, "pins");
+ if (kind(d, rows) != J_ARRAY or kind(d, cols) != J_NUMBER or kind(d, banks) != J_ARRAY or kind(d, pins) != J_ARRAY) { return 2; }
+ OVF = false;
+ DP_LEN = 0;
+ N_ROWS = arr_len(d, rows);
+ N_BANKS = arr_len(d, banks);
+ N_PINS = arr_len(d, pins);
+ const c64 : i64 = num_i64(d, cols);
+ if (N_ROWS > W_MAX_ROWS or N_BANKS > W_MAX_BANKS or N_PINS > W_MAX_PINS or c64 < 0 or c64 > (W_MAX_COLS as i64)) { return 3; }
+ COLS = c64 as u32;
+ if (N_ROWS * COLS > W_MAX_CELLS) { return 3; }
+ var i : u32 = 0;
+ ROWS_LEN = 0;
+ while (i < N_ROWS) {
+ const e : i64 = arr_at(d, rows, i);
+ ROW_O[i as usize] = DP_LEN;
+ DP_LEN = dec_into(d, e, DP[0..262144], DP_LEN);
+ ROW_L[i as usize] = DP_LEN - ROW_O[i as usize];
+ if (i > 0 and ROWS_LEN < 512) { ROWS_TXT[ROWS_LEN as usize] = 44; ROWS_LEN = ROWS_LEN + 1; }
+ var k : u32 = 0;
+ while (k < ROW_L[i as usize]) {
+ if (ROWS_LEN < 512) { ROWS_TXT[ROWS_LEN as usize] = DP[(ROW_O[i as usize] + k) as usize]; ROWS_LEN = ROWS_LEN + 1; }
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ i = 0;
+ while (i < N_BANKS) {
+ const e : i64 = arr_at(d, banks, i);
+ BK_ID[i as usize] = num_i64(d, obj_get(d, e, "id"));
+ BK_PINS[i as usize] = num_i64(d, obj_get(d, e, "pins"));
+ const kd : i64 = obj_get(d, e, "kind");
+ BK_KIND[i as usize] = 99;
+ if (jstr_is(d, kd, "io")) { BK_KIND[i as usize] = 0; }
+ if (jstr_is(d, kd, "mgt")) { BK_KIND[i as usize] = 1; }
+ if (jstr_is(d, kd, "dedicated")) { BK_KIND[i as usize] = 2; }
+ i = i + 1;
+ }
+ PKG_LEN = 0;
+ i = 0;
+ while (i < N_PINS) {
+ const e : i64 = arr_at(d, pins, i);
+ PB_O[i as usize] = DP_LEN;
+ DP_LEN = dec_into(d, arr_at(d, e, 0), DP[0..262144], DP_LEN);
+ PB_L[i as usize] = DP_LEN - PB_O[i as usize];
+ PB_BANK[i as usize] = num_i64(d, arr_at(d, e, 1));
+ PS_O[i as usize] = DP_LEN;
+ DP_LEN = dec_into(d, arr_at(d, e, 2), DP[0..262144], DP_LEN);
+ PS_L[i as usize] = DP_LEN - PS_O[i as usize];
+ PT_O[i as usize] = DP_LEN;
+ DP_LEN = dec_into(d, arr_at(d, e, 3), DP[0..262144], DP_LEN);
+ PT_L[i as usize] = DP_LEN - PT_O[i as usize];
+ PF_O[i as usize] = DP_LEN;
+ DP_LEN = dec_into(d, arr_at(d, e, 4), DP[0..262144], DP_LEN);
+ PF_L[i as usize] = DP_LEN - PF_O[i as usize];
+ PY_O[i as usize] = DP_LEN;
+ DP_LEN = dec_into(d, arr_at(d, e, 5), DP[0..262144], DP_LEN);
+ PY_L[i as usize] = DP_LEN - PY_O[i as usize];
+ PY_C[i as usize] = type_code(PY_O[i as usize], PY_L[i as usize]);
+ append_pkg(DP[0..262144], PB_O[i as usize], PB_L[i as usize]);
+ pkg_byte(32);
+ VA[0] = 0;
+ const nb : u32 = sdec_to(VA[0..], PB_BANK[i as usize]);
+ append_pkg(VA[0..], 0, nb);
+ pkg_byte(32);
+ append_pkg(DP[0..262144], PY_O[i as usize], PY_L[i as usize]);
+ pkg_byte(32);
+ append_pkg(DP[0..262144], PF_O[i as usize], PF_L[i as usize]);
+ pkg_byte(10);
+ i = i + 1;
+ }
+ VCCO_LEN = 0;
+ i = 0;
+ while ((i as usize) < SAY_VCCO_STD.len) {
+ const sd : []const u8 = SAY_VCCO_STD[i as usize];
+ var k : usize = 0;
+ while (k < sd.len) { VCCO_TXT[VCCO_LEN as usize] = sd[k]; VCCO_LEN = VCCO_LEN + 1; k = k + 1; }
+ VCCO_TXT[VCCO_LEN as usize] = 32;
+ VCCO_LEN = VCCO_LEN + 1;
+ VCCO_LEN = VCCO_LEN + dec_to(VCCO_TXT[(VCCO_LEN as usize)..], K_VCCO_MV[i as usize] as u64);
+ VCCO_TXT[VCCO_LEN as usize] = 10;
+ VCCO_LEN = VCCO_LEN + 1;
+ i = i + 1;
+ }
+ if (OVF) { return 3; }
+ // a Map keeps one entry per ball, the last pin that names it
+ i = 0;
+ while (i < N_PINS) {
+ PIN_LAST[i as usize] = 1;
+ var q : u32 = i + 1;
+ while (q < N_PINS) {
+ if (span_eq(DP[0..262144], PB_O[i as usize] as usize, (PB_O[i as usize] + PB_L[i as usize]) as usize, DP[0..262144], PB_O[q as usize] as usize, (PB_O[q as usize] + PB_L[q as usize]) as usize)) { PIN_LAST[i as usize] = 0; }
+ q = q + 1;
+ }
+ i = i + 1;
+ }
+ var ci : u32 = 0;
+ while (ci < N_ROWS * COLS) {
+ const n : u32 = cell_id(ci, IDBUF[0..]);
+ CELLPIN[ci as usize] = pin_of(IDBUF[0..(n as usize)]);
+ ci = ci + 1;
+ }
+ return 0;
+}
+
+/// demos.json: the demos named by SAY_DEMO_IDS, in that order, those that exist.
+fn load_demos() -> u32 {
+ const d : []const u8 = DEMOS_RAW[0..(DEMOS_LEN as usize)];
+ if (!json_ok(d)) { return 1; }
+ const r : i64 = root(d);
+ if (kind(d, r) != J_ARRAY) { return 2; }
+ N_DEMO = 0;
+ const n : u32 = arr_len(d, r);
+ var k : u32 = 0;
+ while ((k as usize) < SAY_DEMO_IDS.len) {
+ var found : bool = false;
+ var i : u32 = 0;
+ while (i < n and !found) {
+ const e : i64 = arr_at(d, r, i);
+ if (jstr_is(d, obj_get(d, e, "id"), SAY_DEMO_IDS[k as usize])) {
+ found = true;
+ if (N_DEMO < 8) { DEMO_EL[N_DEMO as usize] = e; N_DEMO = N_DEMO + 1; }
+ }
+ i = i + 1;
+ }
+ k = k + 1;
+ }
+ return 0;
+}
+
+fn start() {
+ LOADED = true;
+ if (N_DEMO > 0) { show_demo(0); }
+}
+
+fn try_start() {
+ if (FAILED or LOADED) { return; }
+ if (D_STATE[0] == 1 and D_STATE[1] == 1) {
+ const a : u32 = load_pins();
+ if (a == 1 or a == 3) { FAILED = true; return; }
+ if (a == 2) { return; }
+ const b : u32 = load_demos();
+ if (b == 1) { FAILED = true; return; }
+ if (b == 2) { return; }
+ start();
+ }
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (i > 1 or FAILED) { return 0; }
+ if (s.len > (W_CAP_DATA as usize)) { FAILED = true; return 0; }
+ if (i == 0) {
+ PINS_LEN = copy_to(PINS_RAW[0..], s, W_CAP_DATA);
+ } else {
+ DEMOS_LEN = copy_to(DEMOS_RAW[0..], s, W_CAP_DATA);
+ }
+ D_STATE[i as usize] = 1;
+ try_start();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ FAILED = true;
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the XDC: parse, lint, and what the board and the lists keep of it
+// ------------------------------------------------------------------------------------------------
+
+fn pool_span(off: u32, len: u32) -> []const u8 {
+ return LP_POOL[(off as usize)..((off + len) as usize)];
+}
+
+fn port_name(i: u32) -> []const u8 {
+ return pool_span(LP_PORTS[(i * 12) as usize], LP_PORTS[(i * 12 + 1) as usize]);
+}
+
+fn port_ball(i: u32) -> []const u8 {
+ return pool_span(LP_PORTS[(i * 12 + 2) as usize], LP_PORTS[(i * 12 + 3) as usize]);
+}
+
+fn find_ball(f: u32) -> []const u8 {
+ return pool_span(LP_FINDS[(f * 11 + 2) as usize], LP_FINDS[(f * 11 + 3) as usize]);
+}
+
+fn same_span(a: []const u8, b: []const u8) -> bool {
+ return span_eq(a, 0, a.len, b, 0, b.len);
+}
+
+/// Parse and lint `text`; keep the ports and findings the lists and the board read. A lint that cannot
+/// run (a table full) keeps no ports and no findings.
+fn apply_xdc(text: []const u8) -> u32 {
+ if (!LOADED) { return 0; }
+ var st : u32 = xdc_parse(text, NORM[0..2097152], UPN[0..2097152], LP_PINS[0..5120], LP_STDS[0..5120], LP_META[0..8]);
+ N_PORTS = 0;
+ N_FINDS = 0;
+ var z : usize = 0;
+ while (z < 6) { LP_SUM[z] = 0; z = z + 1; }
+ if (st == 0) {
+ st = xdc_lint(NORM[0..2097152], UPN[0..2097152], LP_PINS[0..5120], LP_META[0] as usize, LP_STDS[0..5120], LP_META[1] as usize, ROWS_TXT[0..(ROWS_LEN as usize)], COLS, PKG_TXT[0..(PKG_LEN as usize)], VCCO_TXT[0..(VCCO_LEN as usize)], LP_WORK[0..5120], LP_PORTS[0..12288], LP_FINDS[0..22528], LP_ORDER[0..2048], LP_POOL[0..1048576], LP_LM[0..4]);
+ if (st == 0) {
+ N_PORTS = LP_LM[0];
+ N_FINDS = LP_LM[1];
+ xdc_summarize(LP_PORTS[0..12288], N_PORTS as usize, LP_FINDS[0..22528], N_FINDS as usize, LP_POOL[0..1048576], LP_SUM[0..6]);
+ }
+ }
+ var c : u32 = 0;
+ while (c < W_MAX_CELLS) {
+ CELL_BAD[c as usize] = 0;
+ CELL_USED[c as usize] = 0;
+ c = c + 1;
+ }
+ var i : u32 = 0;
+ while (i < N_PORTS) {
+ const ci : i32 = cell_of(port_ball(i));
+ PORT_CELL[i as usize] = ci;
+ if (ci >= 0) { CELL_USED[ci as usize] = 1; }
+ i = i + 1;
+ }
+ i = 0;
+ while (i < N_FINDS) {
+ if (LP_FINDS[(i * 11 + 1) as usize] == SEV_ERROR) {
+ const cf : i32 = cell_of(find_ball(i));
+ if (cf >= 0) { CELL_BAD[cf as usize] = 1; }
+ }
+ i = i + 1;
+ }
+ LINT_APPLIED = true;
+ paint();
+ show_detail();
+ return 0;
+}
+
+fn is_blank(s: []const u8) -> bool {
+ var k : usize = 0;
+ while (k < s.len) {
+ if (!is_ws(s[k])) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn show_demo(i: u32) {
+ if (i >= N_DEMO) { return; }
+ DEMO_ON = i as i32;
+ DL_KIND = 1;
+ DL_IDX = i;
+ DS_KIND = 1;
+ DS_IDX = i;
+ const d : []const u8 = DEMOS_RAW[0..(DEMOS_LEN as usize)];
+ OVF = false;
+ const n : u32 = dec_into(d, obj_get(d, DEMO_EL[i as usize], "text"), XT[0..262144], 0);
+ apply_xdc(XT[0..(n as usize)]);
+}
+
+fn lint_yours(text: []const u8, name: []const u8) {
+ ERR_KIND = 0;
+ if (is_blank(text)) { return; }
+ DEMO_ON = -1;
+ DL_KIND = 2;
+ DS_KIND = 2;
+ DS_NAME_LEN = copy_to(DS_NAME[0..], name, 512);
+ apply_xdc(text);
+}
+
+fn clear_yours() {
+ PASTE_LEN = 0;
+ PASTE_PUSH = true;
+ ERR_KIND = 0;
+ show_demo(0);
+}
+
+// ------------------------------------------------------------------------------------------------
+// painting: the search, the classes of the balls
+// ------------------------------------------------------------------------------------------------
+
+pub var SOLE_SRC : u32 = 0;
+pub var SOLE_O : u32 = 0;
+pub var SOLE_L : u32 = 0;
+
+fn compute_matches() {
+ M_ACTIVE = false;
+ M_COUNT = 0;
+ SOLE_SRC = 0;
+ var z : u32 = 0;
+ while (z < W_MAX_CELLS) { CELL_M[z as usize] = 0; z = z + 1; }
+ z = 0;
+ while (z < W_MAX_PINS) { PIN_M[z as usize] = 0; z = z + 1; }
+ z = 0;
+ while (z < 1024) { PORT_M[z as usize] = 0; z = z + 1; }
+ const tr : i64 = trim_span(QUERY[0..(QUERY_LEN as usize)], 0, QUERY_LEN as usize);
+ const lo : usize = lo_of(tr);
+ const hi : usize = hi_of(tr);
+ if (lo == hi) { return; }
+ M_ACTIVE = true;
+ Q_LEN = (hi - lo) as u32;
+ var k : u32 = 0;
+ while (k < Q_LEN) {
+ QU[k as usize] = up_b(QUERY[lo + (k as usize)]);
+ QL[k as usize] = low_b(QUERY[lo + (k as usize)]);
+ k = k + 1;
+ }
+ const qc : i32 = cell_of(QU[0..(Q_LEN as usize)]);
+ if (qc >= 0) {
+ CELL_M[qc as usize] = 1;
+ M_COUNT = 1;
+ SOLE_SRC = 3;
+ SOLE_O = qc as u32;
+ return;
+ }
+ var a : u32 = 0;
+ var j : u32 = 0;
+ while (j < N_PINS) {
+ if (PIN_LAST[j as usize] == 1 and (contains(DP[0..(DP_LEN as usize)], PF_O[j as usize], PF_L[j as usize], QU[0..], Q_LEN) or contains(DP[0..(DP_LEN as usize)], PS_O[j as usize], PS_L[j as usize], QU[0..], Q_LEN))) {
+ PIN_M[j as usize] = 1;
+ if (a == 0) { SOLE_SRC = 1; SOLE_O = PB_O[j as usize]; SOLE_L = PB_L[j as usize]; }
+ a = a + 1;
+ const pc : i32 = cell_of(DP[(PB_O[j as usize] as usize)..((PB_O[j as usize] + PB_L[j as usize]) as usize)]);
+ if (pc >= 0) { CELL_M[pc as usize] = 1; }
+ }
+ j = j + 1;
+ }
+ var i : u32 = 0;
+ while (i < N_PORTS) {
+ PORT_M[i as usize] = 0;
+ if (contains_low(LP_POOL[0..1048576], LP_PORTS[(i * 12) as usize], LP_PORTS[(i * 12 + 1) as usize], QL[0..], Q_LEN)) { PORT_M[i as usize] = 1; }
+ i = i + 1;
+ }
+ var b : u32 = 0;
+ var firstb : u32 = 0;
+ i = 0;
+ while (i < N_PORTS) {
+ var leader : bool = true;
+ var any : bool = false;
+ var g : u32 = 0;
+ while (g < N_PORTS) {
+ if (same_span(port_ball(g), port_ball(i))) {
+ if (g < i) { leader = false; }
+ if (PORT_M[g as usize] == 1) { any = true; }
+ }
+ g = g + 1;
+ }
+ if (leader and any) {
+ if (PORT_CELL[i as usize] >= 0) { CELL_M[PORT_CELL[i as usize] as usize] = 1; }
+ var inpins : bool = false;
+ var q : u32 = 0;
+ while (q < N_PINS) {
+ if (PIN_M[q as usize] == 1 and same_span(DP[(PB_O[q as usize] as usize)..((PB_O[q as usize] + PB_L[q as usize]) as usize)], port_ball(i))) { inpins = true; }
+ q = q + 1;
+ }
+ if (!inpins) {
+ if (a == 0 and b == 0) { SOLE_SRC = 2; SOLE_O = LP_PORTS[(i * 12 + 2) as usize]; SOLE_L = LP_PORTS[(i * 12 + 3) as usize]; }
+ b = b + 1;
+ }
+ }
+ i = i + 1;
+ }
+ M_COUNT = a + b;
+}
+
+/// classList.toggle(flag, want) on the ball: the classes stay in the order they were first set.
+fn toggle(ci: u32, flag: u8, want: bool) {
+ const base : usize = (ci as usize) * 4;
+ const n : usize = CLS_N[ci as usize] as usize;
+ var at : usize = 4;
+ var k : usize = 0;
+ while (k < n) {
+ if (CLS_SEQ[base + k] == flag) { at = k; }
+ k = k + 1;
+ }
+ if (want and at == 4) {
+ CLS_SEQ[base + n] = flag;
+ CLS_N[ci as usize] = (n + 1) as u8;
+ }
+ if (!want and at < 4) {
+ var m : usize = at;
+ while (m + 1 < n) {
+ CLS_SEQ[base + m] = CLS_SEQ[base + m + 1];
+ m = m + 1;
+ }
+ CLS_N[ci as usize] = (n - 1) as u8;
+ }
+}
+
+fn select_bytes(s: []const u8) {
+ SEL_LEN = copy_to(SEL[0..], s, 255);
+ const c : i32 = cell_of(s);
+ if (c >= 0) { RING = c; }
+ show_detail();
+}
+
+fn show_detail() {
+ DETAIL_SHOWN = true;
+}
+
+fn paint() {
+ if (!LOADED) { return; }
+ compute_matches();
+ var ci : u32 = 0;
+ while (ci < N_ROWS * COLS) {
+ const p : i32 = CELLPIN[ci as usize];
+ const bad : bool = CELL_BAD[ci as usize] == 1;
+ const used : bool = CELL_USED[ci as usize] == 1 and !bad;
+ const mt : bool = M_ACTIVE and CELL_M[ci as usize] == 1;
+ var in_bank : bool = BANK_SEL < 0;
+ if (!in_bank and p >= 0 and PB_BANK[p as usize] == BANK_SEL) { in_bank = true; }
+ toggle(ci, 1, bad);
+ toggle(ci, 2, used);
+ toggle(ci, 3, mt);
+ toggle(ci, 4, !in_bank);
+ ci = ci + 1;
+ }
+ PAINTED = true;
+ if (M_ACTIVE and M_COUNT == 1) {
+ if (SOLE_SRC == 1) { select_bytes(DP[(SOLE_O as usize)..((SOLE_O + SOLE_L) as usize)]); }
+ if (SOLE_SRC == 2) { select_bytes(LP_POOL[(SOLE_O as usize)..((SOLE_O + SOLE_L) as usize)]); }
+ if (SOLE_SRC == 3) {
+ const n : u32 = cell_id(SOLE_O, IDBUF[0..]);
+ select_bytes(IDBUF[0..(n as usize)]);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// events
+// ------------------------------------------------------------------------------------------------
+
+pub fn w_init(h: []const u8) -> u32 {
+ D_STATE[0] = 0;
+ D_STATE[1] = 0;
+ LOADED = false;
+ FAILED = false;
+ PINS_LEN = 0;
+ DEMOS_LEN = 0;
+ DP_LEN = 0;
+ N_PINS = 0;
+ N_ROWS = 0;
+ COLS = 0;
+ N_BANKS = 0;
+ N_DEMO = 0;
+ N_PORTS = 0;
+ N_FINDS = 0;
+ LINT_APPLIED = false;
+ QUERY_LEN = 0;
+ M_ACTIVE = false;
+ M_COUNT = 0;
+ PAINTED = false;
+ COLOUR = 0;
+ BANK_SEL = -1;
+ HOVER = -1;
+ SEL_LEN = 0;
+ RING = -1;
+ DETAIL_SHOWN = false;
+ DL_KIND = 0;
+ DS_KIND = 0;
+ DEMO_ON = -1;
+ ERR_KIND = 0;
+ DROP_OVER = false;
+ PASTE_PUSH = false;
+ PASTE_LEN = 0;
+ EFFECT = false;
+ var c : u32 = 0;
+ while (c < W_MAX_CELLS) {
+ CLS_N[c as usize] = 0;
+ CELL_BAD[c as usize] = 0;
+ CELL_USED[c as usize] = 0;
+ c = c + 1;
+ }
+ return 0;
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ if (i == 0) {
+ QUERY_LEN = copy_to(QUERY[0..], s, 1023);
+ paint();
+ }
+ if (i == 1) {
+ PASTE_LEN = copy_to(PASTE[0..], s, W_CAPX);
+ PASTE_PUSH = false;
+ if (PASTE_LEN <= K_MAX_XDC_BYTES) { lint_yours(PASTE[0..(PASTE_LEN as usize)], SAY_YOURS); }
+ }
+ return 0;
+}
+
+pub fn w_file(name: []const u8, size: f64, data: []const u8, st: u32) -> u32 {
+ ERR_NAME_LEN = copy_to(ERR_NAME[0..], name, 512);
+ if (size > (K_MAX_XDC_BYTES as f64)) {
+ ERR_KIND = 1;
+ ERR_SIZE = size as u64;
+ return 0;
+ }
+ if (st != 0) {
+ ERR_KIND = 2;
+ return 0;
+ }
+ PASTE_LEN = copy_to(PASTE[0..], data, W_CAPX);
+ PASTE_PUSH = true;
+ var nm : u32 = copy_to(FILE_NAME[0..], name, 512);
+ lint_yours(PASTE[0..(PASTE_LEN as usize)], FILE_NAME[0..(nm as usize)]);
+ // before the data is there, applyXdc throws inside readOwn's try: the reader is told the file could not be read
+ if (!LOADED and !is_blank(PASTE[0..(PASTE_LEN as usize)])) { ERR_KIND = 2; }
+ return 0;
+}
+
+pub fn w_value(i: u32, dst: []u8) -> u32 {
+ if (i == 1 and PASTE_PUSH) {
+ PASTE_PUSH = false;
+ return copy_to(dst, PASTE[0..(PASTE_LEN as usize)], dst.len as u32);
+ }
+ return W_NO;
+}
+
+pub fn w_effect(dst: []u8) -> u32 {
+ if (EFFECT) {
+ EFFECT = false;
+ return copy_to(dst, ".pm-board", 9);
+ }
+ return 0;
+}
+
+/// 1 when the box changed (the host then draws again).
+pub fn w_drag(kind_of: u32) -> u32 {
+ const was : bool = DROP_OVER;
+ if (kind_of == 0) { DROP_OVER = true; } else { DROP_OVER = false; }
+ if (was != DROP_OVER) { return 1; }
+ return 0;
+}
+
+fn ball_at(x: f64, y: f64) -> i32 {
+ const c : i64 = floor_i((x - (W_OX as f64)) / (W_CELL as f64)) + 1;
+ const r : i64 = floor_i((y - (W_OY as f64)) / (W_CELL as f64));
+ if (c < 1 or c > (COLS as i64) or r < 0 or r >= (N_ROWS as i64)) { return -1; }
+ return ((r as i32) * (COLS as i32)) + (c as i32) - 1;
+}
+
+pub fn w_point(kind_of: u32, x: f64, y: f64) -> u32 {
+ if (kind_of == 1) {
+ HOVER = -1;
+ show_detail();
+ return 0;
+ }
+ if (!LOADED) { return 0; }
+ const b : i32 = ball_at(x, y);
+ if (kind_of == 0) {
+ if (b != HOVER) {
+ HOVER = b;
+ show_detail();
+ }
+ }
+ if (kind_of == 2 and b >= 0) {
+ const n : u32 = cell_id(b as u32, IDBUF[0..]);
+ select_bytes(IDBUF[0..(n as usize)]);
+ }
+ return 0;
+}
+
+/// 1 when `key` was a key the board uses.
+pub fn w_key(key: []const u8) -> u32 {
+ if (!LOADED) { return 0; }
+ var dr : i32 = 0;
+ var dc : i32 = 0;
+ var known : bool = false;
+ if (span_is(key, 0, key.len, "ArrowUp")) { dr = -1; known = true; }
+ if (span_is(key, 0, key.len, "ArrowDown")) { dr = 1; known = true; }
+ if (span_is(key, 0, key.len, "ArrowLeft")) { dc = -1; known = true; }
+ if (span_is(key, 0, key.len, "ArrowRight")) { dc = 1; known = true; }
+ if (!known) { return 0; }
+ if (N_ROWS == 0 or COLS == 0) { return 1; }
+ // /^([A-Z]+)(\d+)$/ on the selected ball
+ var i : usize = 0;
+ while (i < (SEL_LEN as usize) and SEL[i] >= 65 and SEL[i] <= 90) { i = i + 1; }
+ var j : usize = i;
+ while (j < (SEL_LEN as usize) and is_digit(SEL[j])) { j = j + 1; }
+ const matched : bool = i > 0 and j > i and j == (SEL_LEN as usize);
+ var ri : i64 = 0;
+ var ci : i64 = 1;
+ if (matched) {
+ ri = -1;
+ var r : u32 = 0;
+ while (r < N_ROWS) {
+ if (span_is(SEL[0..(SEL_LEN as usize)], 0, i, DP[(ROW_O[r as usize] as usize)..((ROW_O[r as usize] + ROW_L[r as usize]) as usize)])) {
+ ri = r as i64;
+ break;
+ }
+ r = r + 1;
+ }
+ var v : i64 = 0;
+ var q : usize = i;
+ while (q < j) {
+ if (v < 1000000000) { v = v * 10 + ((SEL[q] - 48) as i64); }
+ q = q + 1;
+ }
+ ci = v;
+ ri = ri + (dr as i64);
+ if (ri < 0) { ri = 0; }
+ if (ri > (N_ROWS as i64) - 1) { ri = (N_ROWS as i64) - 1; }
+ ci = ci + (dc as i64);
+ if (ci < 1) { ci = 1; }
+ if (ci > (COLS as i64)) { ci = COLS as i64; }
+ }
+ const cell : u32 = ((ri as u32) * COLS) + (ci as u32) - 1;
+ const n : u32 = cell_id(cell, IDBUF[0..]);
+ select_bytes(IDBUF[0..(n as usize)]);
+ return 1;
+}
+
+/// Button i, counted in page order: the demos, the colours, the banks (all first), the findings shown,
+/// then Lint it and Clear.
+pub fn w_click(i: u32) -> u32 {
+ var at : u32 = i;
+ var nd : u32 = 0;
+ var nb : u32 = 0;
+ var nf : u32 = 0;
+ if (LOADED) { nd = N_DEMO; nb = N_BANKS + 1; }
+ if (LINT_APPLIED) { nf = shown_findings(); }
+ if (at < nd) {
+ show_demo(at);
+ return 0;
+ }
+ at = at - nd;
+ if (at < (SAY_COLOUR_MODES.len as u32)) {
+ COLOUR = at;
+ paint();
+ return 0;
+ }
+ at = at - (SAY_COLOUR_MODES.len as u32);
+ if (at < nb) {
+ if (at == 0) {
+ BANK_SEL = -1;
+ } else {
+ const id : i64 = BK_ID[(at - 1) as usize];
+ if (BANK_SEL == id) { BANK_SEL = -1; } else { BANK_SEL = id; }
+ }
+ paint();
+ return 0;
+ }
+ at = at - nb;
+ if (at < nf) {
+ const f : u32 = LP_ORDER[at as usize];
+ select_bytes(find_ball(f));
+ EFFECT = true;
+ return 0;
+ }
+ at = at - nf;
+ if (at == 0) {
+ lint_yours(PASTE[0..(PASTE_LEN as usize)], SAY_YOURS);
+ return 0;
+ }
+ if (at == 1) { clear_yours(); }
+ return 0;
+}
+
+fn shown_findings() -> u32 {
+ if (N_FINDS > K_MAX_LINT_ROWS as u32) { return K_MAX_LINT_ROWS as u32; }
+ return N_FINDS;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the view
+// ------------------------------------------------------------------------------------------------
+
+/// tool.js's fmt(): each {N} is replaced by the N-th value, '' when there is none; values are escaped as text.
+fn fmt4(o: []u8, n0: u32, t: []const u8, va: []const u8, vb: []const u8, vc: []const u8, vd: []const u8, k: u32) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ var j : usize = i + 1;
+ if (t[i] == 123) {
+ while (j < t.len and is_digit(t[j])) { j = j + 1; }
+ }
+ if (t[i] == 123 and j > i + 1 and j < t.len and t[j] == 125) {
+ var idx : u32 = 0;
+ var q : usize = i + 1;
+ while (q < j) {
+ if (idx < 1000) { idx = idx * 10 + ((t[q] - 48) as u32); }
+ q = q + 1;
+ }
+ if (idx < k) {
+ if (idx == 0) { n = text(o, n, va); }
+ if (idx == 1) { n = text(o, n, vb); }
+ if (idx == 2) { n = text(o, n, vc); }
+ if (idx == 3) { n = text(o, n, vd); }
+ }
+ i = j + 1;
+ } else {
+ n = put_escaped(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+/// The decimal text of v in VA.. (which: 0 VA, 1 VB, 2 VC, 3 VD); its length.
+fn dec_a(v: i64) -> u32 { return sdec_to(VA[0..], v); }
+fn dec_b(v: i64) -> u32 { return sdec_to(VB[0..], v); }
+fn dec_c(v: i64) -> u32 { return sdec_to(VC[0..], v); }
+
+fn type_idx(lit: []const u8) -> u32 {
+ var k : u32 = 0;
+ while ((k as usize) < SAY_TYPE_IDS.len) {
+ if (span_is(SAY_TYPE_IDS[k as usize], 0, SAY_TYPE_IDS[k as usize].len, lit)) { return k; }
+ k = k + 1;
+ }
+ return 99;
+}
+
+fn sev_class(o: []u8, n0: u32, sev: u32) -> u32 {
+ var n : u32 = raw(o, n0, "");
+ if (sev < (SAY_SEV.len as u32)) { n = text(o, n, SAY_SEV[sev as usize]); }
+ return raw(o, n, "");
+}
+
+/// lintText(f) for the finding record f.
+fn lint_text(o: []u8, n0: u32, f: u32) -> u32 {
+ const id : u32 = LP_FINDS[(f * 11) as usize];
+ if (id >= (SAY_LINT_TEXT.len as u32)) { return n0; }
+ const na : u32 = LP_FINDS[(f * 11 + 6) as usize];
+ const a0 : []const u8 = pool_span(LP_FINDS[(f * 11 + 7) as usize], LP_FINDS[(f * 11 + 8) as usize]);
+ const a1 : []const u8 = pool_span(LP_FINDS[(f * 11 + 9) as usize], LP_FINDS[(f * 11 + 10) as usize]);
+ var k : u32 = 2 + na;
+ if (k > 4) { k = 4; }
+ return fmt4(o, n0, SAY_LINT_TEXT[id as usize], find_ball(f), pool_span(LP_FINDS[(f * 11 + 4) as usize], LP_FINDS[(f * 11 + 5) as usize]), a0, a1, k);
+}
+
+fn cls_text(o: []u8, n0: u32, ci: u32) -> u32 {
+ var n : u32 = raw(o, n0, "pm-ball");
+ var k : u32 = 0;
+ while (k < (CLS_N[ci as usize] as u32)) {
+ const fl : u8 = CLS_SEQ[((ci as usize) * 4) + (k as usize)];
+ if (fl == 1) { n = raw(o, n, " is-bad"); }
+ if (fl == 2) { n = raw(o, n, " is-used"); }
+ if (fl == 3) { n = raw(o, n, " is-match"); }
+ if (fl == 4) { n = raw(o, n, " is-dim"); }
+ k = k + 1;
+ }
+ return n;
+}
+
+fn board(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ n = raw(o, n, "");
+ n = raw(o, n, "");
+ n = raw(o, n, "");
+ var c : u32 = 1;
+ while (c <= COLS) {
+ n = raw(o, n, "");
+ n = num(o, n, c as u64);
+ n = raw(o, n, "");
+ c = c + 1;
+ }
+ var r : u32 = 0;
+ while (r < N_ROWS) {
+ n = raw(o, n, "");
+ n = text(o, n, DP[(ROW_O[r as usize] as usize)..((ROW_O[r as usize] + ROW_L[r as usize]) as usize)]);
+ n = raw(o, n, "");
+ r = r + 1;
+ }
+ var ci : u32 = 0;
+ while (ci < N_ROWS * COLS) {
+ const rr : u32 = ci / COLS;
+ const cc : u32 = ci % COLS + 1;
+ n = raw(o, n, "");
+ ci = ci + 1;
+ }
+ n = raw(o, n, "");
+ return n;
+}
+
+///
+fn sw_open(o: []u8, n0: u32, cls: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "
0) {
+ n = raw(o, n, " ");
+ n = raw(o, n, cls);
+ }
+ return raw(o, n, "\"");
+}
+
+fn legend(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (COLOUR == 0) {
+ var b : u32 = 0;
+ while (b < N_BANKS) {
+ n = sw_open(o, n, "");
+ n = raw(o, n, " style=\"background:var(--pm-bank-");
+ const nid : u32 = dec_a(BK_ID[b as usize]);
+ n = raw(o, n, VA[0..(nid as usize)]);
+ n = raw(o, n, ")\">");
+ const nm : u32 = fmt4(TMPB[0..], 0, SAY_BANK_NAME, VA[0..(nid as usize)], "", "", "", 1);
+ const np : u32 = dec_c(BK_PINS[b as usize]);
+ var kn : []const u8 = "";
+ if (BK_KIND[b as usize] < (SAY_BANK_KINDS.len as u32)) { kn = SAY_BANK_KINDS[BK_KIND[b as usize] as usize]; }
+ n = fmt4(o, n, SAY_BANK_LEGEND, TMPB[0..(nm as usize)], kn, VC[0..(np as usize)], "", 3);
+ n = raw(o, n, "
");
+ b = b + 1;
+ }
+ const ui : u32 = type_idx("unlisted");
+ n = sw_open(o, n, "");
+ n = raw(o, n, " style=\"background:var(--pm-type-unlisted)\">");
+ const nc : u32 = dec_a(K_TYPE_COUNTS[ui as usize] as i64);
+ n = fmt4(o, n, SAY_TYPE_LEGEND, SAY_TYPE_NAMES[ui as usize], VA[0..(nc as usize)], "", "", 2);
+ n = raw(o, n, "");
+ } else {
+ var t : u32 = 0;
+ while ((t as usize) < SAY_TYPE_IDS.len) {
+ n = sw_open(o, n, "");
+ n = raw(o, n, " style=\"background:var(--pm-type-");
+ n = text(o, n, SAY_TYPE_IDS[t as usize]);
+ n = raw(o, n, ")\">");
+ const nc : u32 = dec_a(K_TYPE_COUNTS[t as usize] as i64);
+ n = fmt4(o, n, SAY_TYPE_LEGEND, SAY_TYPE_NAMES[t as usize], VA[0..(nc as usize)], "", "", 2);
+ n = raw(o, n, "");
+ t = t + 1;
+ }
+ }
+ n = sw_open(o, n, "is-used");
+ n = raw(o, n, ">");
+ n = text(o, n, SAY_OVERLAY_LEGEND[0]);
+ n = raw(o, n, "");
+ n = sw_open(o, n, "is-bad");
+ n = raw(o, n, ">");
+ n = text(o, n, SAY_OVERLAY_LEGEND[1]);
+ n = raw(o, n, "");
+ n = sw_open(o, n, "is-match");
+ n = raw(o, n, ">");
+ n = text(o, n, SAY_OVERLAY_LEGEND[2]);
+ n = raw(o, n, "");
+ return n;
+}
+
+fn dd(o: []u8, n0: u32, label: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, label);
+ return raw(o, n, "
");
+}
+
+/// The ports placed on the ball b (all of them, in lint order): the names, standards or lines, joined by ", ".
+fn ports_join(o: []u8, n0: u32, b: []const u8, what: u32) -> u32 {
+ var n : u32 = n0;
+ var first : bool = true;
+ var i : u32 = 0;
+ while (i < N_PORTS) {
+ if (same_span(port_ball(i), b)) {
+ if (!first) { n = raw(o, n, ", "); }
+ first = false;
+ if (what == 0) { n = text(o, n, port_name(i)); }
+ if (what == 1) {
+ if (LP_PORTS[(i * 12 + 4) as usize] == NONE or LP_PORTS[(i * 12 + 5) as usize] == 0) {
+ n = text(o, n, SAY_NONE);
+ } else {
+ n = text(o, n, pool_span(LP_PORTS[(i * 12 + 4) as usize], LP_PORTS[(i * 12 + 5) as usize]));
+ }
+ }
+ if (what == 2) { n = num(o, n, LP_PORTS[(i * 12 + 6) as usize] as u64); }
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+fn has_ports(b: []const u8) -> bool {
+ var i : u32 = 0;
+ while (i < N_PORTS) {
+ if (same_span(port_ball(i), b)) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn detail(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ var bl : u32 = 0;
+ var none_sel : bool = false;
+ if (HOVER >= 0) {
+ bl = cell_id(HOVER as u32, IDBUF[0..]);
+ } else {
+ if (SEL_LEN == 0) { none_sel = true; }
+ bl = copy_to(IDBUF[0..], SEL[0..(SEL_LEN as usize)], 255);
+ }
+ if (none_sel or !LOADED) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_DETAIL_HINT);
+ return raw(o, n, "
");
+ }
+ const b : []const u8 = IDBUF[0..(bl as usize)];
+ const pj : i32 = pin_of(b);
+ n = raw(o, n, "
");
+ n = dd(o, n, SAY_DETAIL_FIELDS[0]);
+ n = text(o, n, b);
+ n = raw(o, n, "
");
+ n = dd(o, n, SAY_DETAIL_FIELDS[1]);
+ if (pj >= 0) { n = text(o, n, DP[(PF_O[pj as usize] as usize)..((PF_O[pj as usize] + PF_L[pj as usize]) as usize)]); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ n = dd(o, n, SAY_DETAIL_FIELDS[2]);
+ if (pj >= 0) {
+ const nb : u32 = sdec_to(TMPB[0..], PB_BANK[pj as usize]);
+ n = raw(o, n, TMPB[0..(nb as usize)]);
+ } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ n = dd(o, n, SAY_DETAIL_FIELDS[3]);
+ if (pj >= 0) {
+ if (PY_C[pj as usize] < (SAY_TYPE_NAMES.len as u32)) { n = text(o, n, SAY_TYPE_NAMES[PY_C[pj as usize] as usize]); }
+ } else {
+ n = text(o, n, SAY_TYPE_NAMES[type_idx("unlisted") as usize]);
+ }
+ n = raw(o, n, "");
+ n = dd(o, n, SAY_DETAIL_FIELDS[4]);
+ if (pj >= 0) { n = text(o, n, DP[(PS_O[pj as usize] as usize)..((PS_O[pj as usize] + PS_L[pj as usize]) as usize)]); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ n = dd(o, n, SAY_DETAIL_FIELDS[5]);
+ if (pj >= 0) { n = text(o, n, DP[(PT_O[pj as usize] as usize)..((PT_O[pj as usize] + PT_L[pj as usize]) as usize)]); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ const hp : bool = has_ports(b);
+ n = dd(o, n, SAY_DETAIL_FIELDS[6]);
+ if (hp) { n = ports_join(o, n, b, 0); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ n = dd(o, n, SAY_DETAIL_FIELDS[7]);
+ if (hp) { n = ports_join(o, n, b, 1); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ n = dd(o, n, SAY_DETAIL_FIELDS[8]);
+ if (hp) { n = ports_join(o, n, b, 2); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "");
+ if (pj < 0) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_UNLISTED_LINE);
+ n = raw(o, n, "
");
+ }
+ var any_f : bool = false;
+ var k : u32 = 0;
+ while (k < N_FINDS) {
+ if (same_span(find_ball(LP_ORDER[k as usize]), b)) {
+ if (!any_f) { n = raw(o, n, "
"); }
+ any_f = true;
+ n = raw(o, n, "
");
+ n = sev_class(o, n, LP_FINDS[(LP_ORDER[k as usize] * 11 + 1) as usize]);
+ n = raw(o, n, " ");
+ n = lint_text(o, n, LP_ORDER[k as usize]);
+ n = raw(o, n, "
");
+ }
+ k = k + 1;
+ }
+ if (any_f) { n = raw(o, n, "
");
+ w_init("");
+ w_data(0, "{\"rows\":[");
+ w_data(1, t_demos());
+ assert FAILED;
+}
From a3d78db5919acae9d925c8be08ff9ff05cf4520b Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:45:13 +0700
Subject: [PATCH 36/63] widgets: lut-depth drawn natively -- 19862-byte wasm,
3603 DOM comparisons, 0 differences (Closes #10938)
specs/widgets/lut_depth.t27: the trinity spec verbatim, the reader of lut_depth_logic.t27 folded in
(a pub var cannot cross a use), and the page: sample, paste box, file input and drop, path-block
buttons, staircase, legend, table. 16 tests (12 new), t27b 16/16.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/lut_depth.t27 | 1832 +++++++++++++++++++++++++++++++++++
1 file changed, 1832 insertions(+)
create mode 100644 specs/widgets/lut_depth.t27
diff --git a/specs/widgets/lut_depth.t27 b/specs/widgets/lut_depth.t27
new file mode 100644
index 0000000000..4f3b912081
--- /dev/null
+++ b/specs/widgets/lut_depth.t27
@@ -0,0 +1,1832 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/lut-depth.t27 -- the longest path yosys `ltp` printed, drawn one step per cell
+; Source of truth for public/widgets/lut-depth/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its
+; own and reads every word below from window.T27_WIDGET.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader pastes or drops what yosys `ltp` printed after LUT mapping. The page
+; reads it in the browser with public/widgets/lut-depth/ltpparse.js and draws each path block as a
+; staircase, one step per cell, with one big number: the length yosys printed. ltp prints a cell
+; NAME per step, never a type. When the same text also holds an RTLIL `dump` (yosys
+; `dump t:*`), the page looks every cell name up there and labels the step LUT6, CARRY4 and so
+; on; without a dump it says "type not given" and guesses nothing from the name. The file never
+; leaves the page; nothing is sent.
+
+; THE -noff TRAP, measured on the sample: `ltp -noff` leaves out only yosys' own flip-flop types
+; ($dff, $_DFF_P_ and kin). synth_xilinx writes 7-series flip-flops (FDRE, FDCE, FDSE, FDPE),
+; which ltp walks through like any other cell, from D and C to Q. On the sample, plain
+; `ltp -noff` printed length=27, and steps 24 to 27 are FDCE, LUT2, FDCE, OBUF: the path runs
+; through two registers to an output pin. `ltp -noff t:FD* %n` (every object except FD* cells)
+; printed length=23, the same first 23 steps, ending at a register's D input. The page flags any
+; path that crosses a cell in K_CLOCKED_TYPES. With generic `synth -lut 6 -flatten` (cells
+; $lut and $dff) plain `ltp -noff` does skip the flip-flops: on the same design it printed
+; length=14, all $lut. That run was made once to check the parser; it is not shipped.
+
+; THE SAMPLE: one real run of Yosys 0.67+post (git sha1 b8e7da6f) on this machine, made by
+; scripts/widget-data/lut-depth.mjs: fpga/openxc7-synth/gf16_add.v (gHashTag/trinity), the GF16
+; (Golden Float 16) adder, `synth_xilinx -flatten -top gf16_add`, then `echo on` and three
+; commands teed into one file: `ltp -noff`, `ltp -noff t:FD* %n` and `dump t:*`. That file is
+; shipped byte for byte as public/widgets/lut-depth/sample.ltp.txt; the command, the yosys -V
+; line, the exit code, the warnings and the sha256 of the input and output are in sample.json.
+; Every K_SAMPLE_ fact below is re-read from sample.ltp.txt (K_SAMPLE_YOSYS from the -V line) by
+; `node scripts/widget-data/lut-depth.mjs --check`, which also runs this spec's tests on the
+; values it read and a negative control that must fail them. yosys printed three warnings for
+; this design (two about in_ready, one out-of-range select at gf16_add.v:165); the path is the
+; netlist yosys built from it, warnings and all.
+
+; WHAT IT DOES NOT CLAIM: levels are not nanoseconds. ltp counts cells on a path of the netlist
+; before place and route: no routing delay, no cell delay, a CARRY4 counts the same as a LUT6, and
+; the path with the most cells need not be the slowest one or the one with the most LUTs. No Fmax
+; or time is computed from it anywhere on the page or the card. ltp works per module, so the
+; design must be flattened for it to see a whole path.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_lut_depth;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "lut-depth";
+pub const TITLE : str = "LUT depth: your longest yosys path, drawn as a staircase";
+pub const DESCRIPTION : str = "Paste yosys ltp -noff output: the longest path after LUT mapping, one step per cell, typed when a dump is in the text. A cell count, not nanoseconds. Read in your browser.";
+pub const IMAGE_ALT : str = "A staircase of 23 steps on black: the longest path yosys 0.67 ltp found in gf16_add, a GF16 adder, after synth_xilinx: 1 IBUF, 11 LUTs, 9 CARRY4 and 2 wide muxes. Plain ltp -noff said 27, because it walked through two FDCE flip-flops. Marked: cells, not nanoseconds.";
+pub const HOOK : str = "Our GF16 adder's longest path: 23 cells, 11 of them LUTs. Plain ltp -noff said 27: it walked through two 7-series flip-flops it does not skip.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159), run once by scripts/widget-data/lut-depth.mjs: nice -n 10 yosys -q -p \"read_verilog fpga/openxc7-synth/gf16_add.v; synth_xilinx -flatten -top gf16_add; echo on; tee -q -o ltp.txt ltp -noff; tee -q -a ltp.txt ltp -noff t:FD* %n; tee -q -a ltp.txt dump t:*\"", "gHashTag/trinity fpga/openxc7-synth/gf16_add.v (sha256 in sample.json), copied to a scratch directory under the same relative path", "public/widgets/lut-depth/sample.ltp.txt: the teed yosys output, byte for byte; sample.json: command, yosys -V line, exit code, warnings, seconds, sha256 of input and output, and the parsed path", "public/widgets/lut-depth/ltpparse.js: the one parser, run in node for the sample and in the browser for your text; card.png is drawn from sample.json by scripts/widget-data/lut-depth-card.py", "node scripts/widget-data/lut-depth.mjs --check: re-reads sample.ltp.txt and fails if any K_SAMPLE_ fact in this spec disagrees"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Which cell type is which (7-series names as synth_xilinx writes them; $lut from generic synth)
+pub const K_LUT_TYPES : [7]str = ["LUT1", "LUT2", "LUT3", "LUT4", "LUT5", "LUT6", "$lut"];
+pub const K_CARRY_TYPES : [1]str = ["CARRY4"];
+pub const K_MUX_TYPES : [2]str = ["MUXF7", "MUXF8"];
+pub const K_IO_TYPES : [7]str = ["IBUF", "OBUF", "OBUFT", "IOBUF", "IBUFG", "BUFG", "BUFGCTRL"];
+; Cells with a clock: ltp walks through them from input to output like logic, so a path that
+; holds one is no longer a path between registers.
+pub const K_CLOCKED_TYPES : [11]str = ["FDRE", "FDSE", "FDCE", "FDPE", "LDCE", "LDPE", "SRL16E", "SRLC32E", "RAMB18E1", "RAMB36E1", "DSP48E1"];
+
+; --- The sample (scripts/widget-data/lut-depth.mjs re-reads every K_SAMPLE_ fact) ---------------
+pub const K_SAMPLE_JSON : str = "sample.json";
+pub const K_SAMPLE_LOG : str = "sample.ltp.txt";
+pub const K_SAMPLE_DESIGN : str = "fpga/openxc7-synth/gf16_add.v";
+pub const K_SAMPLE_TOP : str = "gf16_add";
+pub const K_SAMPLE_YOSYS : str = "0.67+post";
+; The two ltp commands of the sample, in the order they ran, and the length each printed.
+pub const K_SAMPLE_COMMANDS : [2]str = ["ltp -noff", "ltp -noff t:FD* %n"];
+pub const K_SAMPLE_LENGTHS : [2]u16 = [27, 23];
+; The cell type of every step of the second path (steps 1 to 23), from the dump in the same file.
+pub const K_SAMPLE_PATH : [23]str = ["IBUF", "LUT2", "CARRY4", "CARRY4", "LUT4", "LUT3", "LUT2", "LUT6", "LUT3", "CARRY4", "CARRY4", "CARRY4", "MUXF7", "MUXF8", "LUT2", "LUT4", "CARRY4", "CARRY4", "LUT6", "CARRY4", "CARRY4", "LUT3", "LUT6"];
+; Steps 24 to 27 of the first path, the ones the second leaves out.
+pub const K_SAMPLE_PLAIN_TAIL : [4]str = ["FDCE", "LUT2", "FDCE", "OBUF"];
+pub const K_SAMPLE_LUT : u16 = 11;
+pub const K_SAMPLE_CARRY : u16 = 9;
+pub const K_SAMPLE_MUX : u16 = 2;
+pub const K_SAMPLE_IO : u16 = 1;
+pub const K_SAMPLE_CLOCKED : u16 = 0;
+pub const K_SAMPLE_PLAIN_CLOCKED : u16 = 2;
+; RTLIL `cell` lines in the dump: every cell of the design.
+pub const K_SAMPLE_CELLS : u16 = 305;
+
+; --- How it reads and draws ---------------------------------------------------------------------
+pub const K_CARD_W : u16 = 1200;
+pub const K_CARD_H : u16 = 630;
+pub const K_MIN_TARGET_PX : u8 = 32;
+; A pasted text or a file over this many characters is not read.
+pub const K_MAX_CHARS : u32 = 16000000;
+
+; --- Every word on the screen -------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_SAMPLE_LABEL : str = "Real sample";
+pub const SAY_SAMPLE_NAME : str = "gf16_add (yosys 0.67)";
+pub const SAY_SAMPLE_LINE : str = "The GF16 (Golden Float 16) adder from gHashTag/trinity, fpga/openxc7-synth/gf16_add.v, synthesised on our machine with synth_xilinx -flatten. The log holds two ltp runs, plain -noff and with the flip-flops left out, and a dump that names each cell's type.";
+pub const SAY_OPEN_SAMPLE : str = "Open the yosys output";
+pub const SAY_OWN_LABEL : str = "Your ltp output";
+pub const SAY_HOW_HEAD : str = "In yosys, after synthesis (the design flattened):";
+pub const SAY_HOW : [3]str = ["synth_xilinx -flatten -top ", "tee -o ltp.txt ltp -noff t:FD* %n", "tee -q -a ltp.txt dump t:*"];
+pub const SAY_HOW_NOTE : str = "The dump line is optional: without it the page has cell names, not types. With generic synth -lut 6 -flatten, plain ltp -noff does skip the flip-flops, and LUTs read as $lut.";
+pub const SAY_DROP : str = "Drop ltp.txt or a yosys log here, or pick a file";
+pub const SAY_PASTE_HINT : str = "...or paste what ltp printed";
+pub const SAY_PASTE_LABEL : str = "yosys ltp output";
+pub const SAY_READ : str = "Draw the path";
+pub const SAY_CLEAR : str = "Clear";
+pub const SAY_PICKED : str = "{0}: {1} bytes";
+pub const SAY_PASTED : str = "pasted text";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab; nothing is sent.";
+pub const SAY_TOO_BIG : str = "{0} characters is more than this page reads ({1}).";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_LOAD : str = "The sample did not load.";
+; One per parse error code of ltpparse.js, in this order.
+pub const SAY_ERROR_IDS : [3]str = ["empty", "noltp", "loop"];
+pub const SAY_ERROR_TEXT : [3]str = ["Nothing to read yet.", "No \"Longest topological path in ... (length=N):\" line in this text. Is it the output of yosys ltp?", "yosys found a combinational loop and printed no path; a loop has no longest path."];
+
+; The path blocks of one text.
+pub const SAY_BLOCKS_HEAD : str = "Paths in this text";
+pub const SAY_BLOCK_NAME : str = "{0}: {1}, length {2}";
+
+; The headline.
+pub const SAY_LENGTH_LABEL : str = "cells on the longest path, as yosys counts them";
+pub const SAY_LUT_LABEL : str = "of them LUTs";
+pub const SAY_LUT_UNKNOWN : str = "LUTs: give the dump to count them";
+pub const SAY_MODULE : str = "module {0}, printed by: {1}";
+pub const SAY_NO_COMMAND : str = "the command is not in the text";
+pub const SAY_CLASS_IDS : [8]str = ["lut", "carry", "mux", "io", "clocked", "other", "unknown", "start"];
+pub const SAY_CLASS_NAMES : [8]str = ["LUT", "carry", "wide mux", "I/O buffer", "clocked", "other", "type not given", "start"];
+
+; Warnings, each shown only when it applies.
+pub const SAY_CLOCKED_WARN : str = "This path runs through {0} clocked cell(s): {1}. ltp -noff leaves out only yosys' own flip-flop types ($dff and kin), not the FDRE, FDCE, FDSE and FDPE that synth_xilinx writes, so the path crosses registers. Run ltp -noff t:FD* %n to leave them out.";
+pub const SAY_UNTYPED : str = "No RTLIL dump in this text, so each step is named by its cell, not its type. Add tee -q -a ltp.txt dump t:* after ltp to see LUT, CARRY4 and the rest.";
+pub const SAY_MISSING_TYPES : str = "{0} cell(s) on this path are not in the dump: was the dump taken from the same run?";
+pub const SAY_INCOMPLETE : str = "This block lists {0} of its {1} steps: part of it is missing from the text.";
+pub const SAY_LOOPS : str = "yosys also reported {0} combinational loop(s), first at {1}.";
+pub const SAY_LOOP_AT : str = "The first is at {0} in module {1}.";
+
+; The staircase.
+pub const SAY_STAIRS_HEAD : str = "The path, one step per cell";
+pub const SAY_STAIRS_LABEL : str = "Staircase of the path: one step per cell, from where the path starts to where it ends.";
+pub const SAY_START : str = "starts at {0}";
+pub const SAY_END : str = "ends at {0}";
+pub const SAY_TABLE_SUMMARY : str = "Every step: cell type, cell name and the net it drives";
+pub const SAY_TABLE_HEAD : [4]str = ["step", "cell type", "cell", "net it drives"];
+
+pub const SAY_NOT_NS : str = "Levels are not nanoseconds. ltp counts cells on a path of the netlist before place and route: no routing delay, no cell delay, and a CARRY4 counts the same as a LUT6. No Fmax can be read from it; only a timing report after routing gives one.";
+pub const SAY_HONEST : str = "ltp finds the path with the most cells, not the slowest path or the one with the most LUTs, and it works per module: flatten the design first.";
+
+; The card (drawn by scripts/widget-data/lut-depth-card.py from sample.json).
+pub const SAY_CARD_BRAND : str = "TRINITY S3AI / LUT DEPTH";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/lut-depth";
+pub const SAY_CARD_KIND : str = "cells, not nanoseconds: before place and route";
+pub const SAY_CARD_VERSION : str = "yosys {0}";
+pub const SAY_CARD_PLAIN : str = "plain ltp -noff: {0}, through {1} FDCE";
+
+; --- Tests --------------------------------------------------------------------------------------
+test the_lists_name_7_series_cells {
+ assert K_LUT_TYPES[0] == "LUT1";
+ assert K_LUT_TYPES[5] == "LUT6";
+ assert K_CARRY_TYPES[0] == "CARRY4";
+ assert K_MUX_TYPES[1] == "MUXF8";
+ assert K_CLOCKED_TYPES[0] == "FDRE";
+ assert K_CLOCKED_TYPES[2] == "FDCE";
+}
+
+test the_sample_path_is_its_classes_summed {
+ assert K_SAMPLE_COMMANDS[1] == "ltp -noff t:FD* %n";
+ assert K_SAMPLE_LENGTHS[1] == 23;
+ assert K_SAMPLE_LUT + K_SAMPLE_CARRY + K_SAMPLE_MUX + K_SAMPLE_IO + K_SAMPLE_CLOCKED == K_SAMPLE_LENGTHS[1];
+ assert K_SAMPLE_PATH[0] == "IBUF";
+ assert K_SAMPLE_PATH[2] == K_CARRY_TYPES[0];
+ assert K_SAMPLE_PATH[12] == K_MUX_TYPES[0];
+ assert K_SAMPLE_PATH[22] == "LUT6";
+ assert K_SAMPLE_LUT == 11;
+ assert K_SAMPLE_CLOCKED == 0;
+}
+
+test plain_noff_walks_through_two_flip_flops {
+ assert K_SAMPLE_COMMANDS[0] == "ltp -noff";
+ assert K_SAMPLE_LENGTHS[0] == K_SAMPLE_LENGTHS[1] + 4;
+ assert K_SAMPLE_PLAIN_TAIL[0] == K_CLOCKED_TYPES[2];
+ assert K_SAMPLE_PLAIN_TAIL[2] == K_CLOCKED_TYPES[2];
+ assert K_SAMPLE_PLAIN_TAIL[3] == "OBUF";
+ assert K_SAMPLE_PLAIN_CLOCKED == 2;
+}
+
+test the_card_and_the_page {
+ assert K_CARD_W == 1200;
+ assert K_CARD_H == 630;
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/lut-depth/tool.js (241 lines of hand-written JavaScript)
+; and, as the page's reader, ltpparse.js. The words and numbers above are unchanged: the view prints
+; SAY_ constants and uses K_ lists, it writes no English of its own.
+; --
+; THE READER IS FOLDED IN, NOT IMPORTED. specs/widgets/lut_depth_logic.t27 is the differential-tested
+; port of ltpparse.js. Its functions keep their tables in module-level `pub var`s (BLK, STP, CEL ...)
+; and t27c's `use` does not carry a `pub var` across modules (measured on 0.5.2: "undeclared
+; identifier"), so ltp_parse cannot be imported. The block between the two "FOLDED" markers is that
+; module's text, lines 55-565, byte for byte except that B_CR (defined here by page_reader) is not
+; declared again; the build script below diffs the region against the logic module. Change the
+; reader there, then regenerate this region.
+; --
+; Host protocol (host.js; all exports of this module):
+; w_base(url) the page's directory address, for the sample link (the original used new URL(..., import.meta.url))
+; w_init(hash) the page opens
+; w_data(i, bytes) the sample file (W_DATA_FILE) was fetched; w_data_failed(i) when it was not
+; w_click(i) button i: 0 the sample, 1 "Draw the path", 2 "Clear", 3.. the path-block buttons
+; w_input(i, bytes) control i (among input and textarea, in page order) changed: i 1 is the paste box
+; w_file(z, name, size) -> 1 | 0 a file was picked or dropped on drop zone z: 1 = send its bytes
+; w_file_data(z, bytes) / w_file_failed(z) the bytes, or "could not be read"
+; w_drag(z, over) -> 1 | 0 the drag entered (1) or left/dropped (0) zone z; 1 = the page changed
+; w_effect(k, dst) -> len effect k after the last event: bytes 'v', control index = set that control's value to ''
+; w_render(dst) the page's HTML, length (0 on overflow)
+; Buffers (all module-level, static): the sample SAMP 2 MiB, the paste box PASTE 2 MiB, the two
+; shown/candidate texts TA and TB 2 MiB each, the status STATUS 64 KiB, a file name FNAME 1 KiB.
+; Not carried over from JavaScript: a text over W_TEXT_CAP (2,097,152 bytes) is refused with the
+; page's own "too big" sentence and W_TEXT_CAP in place of K_MAX_CHARS (16,000,000); the reader's
+; tables hold 1024 blocks, 16384 steps, 1024 loops and 16384 dump cells (a text over a table limit
+; says "could not read"); a page whose HTML passes the host's 1 MiB render buffer is not drawn;
+; a file's bytes are not decoded: invalid UTF-8 reaches the page as the browser decodes the HTML;
+; the Clear button shows the cached sample, the original fetched it again.
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::page_reader::match_in;
+use widgets::page_reader::is_space;
+use widgets::page_reader::pack;
+use widgets::page_reader::lo_of;
+use widgets::page_reader::hi_of;
+use widgets::page_reader::NO_TEXT;
+use widgets::page_reader::PACK;
+use widgets::page_reader::B_SPACE;
+use widgets::page_reader::B_TAB;
+use widgets::page_reader::B_CR;
+
+pub const W_DATA_FILE : str = "./sample.ltp.txt";
+pub const W_TEXT_CAP : u32 = 2097152;
+pub const W_STATUS_CAP : u32 = 65536;
+
+; FOLDED-BEGIN lut_depth_logic.t27 55-565
+pub const W_B_NL : u8 = 10;
+pub const W_B_RPAREN : u8 = 41;
+pub const W_B_BACKSLASH : u8 = 92;
+pub const W_B_BACKTICK : u8 = 96;
+
+pub const W_CLS_OTHER : u32 = 5;
+pub const W_CLS_UNKNOWN : u32 = 6;
+pub const W_CLS_START : u32 = 7;
+
+pub const W_MAX_BLOCKS : usize = 1024;
+pub const W_MAX_STEPS : usize = 16384;
+pub const W_MAX_LOOPS : usize = 1024;
+pub const W_MAX_CELLS : usize = 16384;
+
+pub var BLK : [16384]u64 = [0; 16384];
+pub var STP : [147456]u32 = [0; 147456];
+pub var LOP : [4096]u32 = [0; 4096];
+pub var CEL : [65536]u32 = [0; 65536];
+pub var SC : [8]u64 = [0; 8];
+pub var INFO : [8]u64 = [0; 8];
+
+pub fn is_digit(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+/// JavaScript's \w for the ASCII range.
+pub fn is_word(b: u8) -> bool {
+ return is_digit(b) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 95;
+}
+
+pub fn skip_ws(s: []const u8, p0: usize, hi: usize) -> usize {
+ var p : usize = p0;
+ while (p < hi and is_space(s[p])) { p = p + 1; }
+ return p;
+}
+
+/// The end of the run of non-space bytes that starts at p0.
+pub fn tok_end(s: []const u8, p0: usize, hi: usize) -> usize {
+ var p : usize = p0;
+ while (p < hi and !is_space(s[p])) { p = p + 1; }
+ return p;
+}
+
+pub fn digits_end(s: []const u8, p0: usize, hi: usize) -> usize {
+ var p : usize = p0;
+ while (p < hi and is_digit(s[p])) { p = p + 1; }
+ return p;
+}
+
+/// The value of the digits s[a..b), held at a ceiling so a long run cannot wrap.
+pub fn digits_val(s: []const u8, a: usize, b: usize) -> u64 {
+ var v : u64 = 0;
+ var k : usize = a;
+ while (k < b) {
+ if (v < 100000000000000000) { v = v * 10 + ((s[k] - 48) as u64); }
+ k = k + 1;
+ }
+ return v;
+}
+
+/// The end of s[lo..hi) with its trailing space removed (JS trimEnd for ASCII).
+pub fn rtrim_end(s: []const u8, lo: usize, hi: usize) -> usize {
+ var e : usize = hi;
+ while (e > lo and is_space(s[e - 1])) { e = e - 1; }
+ return e;
+}
+
+pub fn has_cr(s: []const u8, a: usize, b: usize) -> bool {
+ var k : usize = a;
+ while (k < b) {
+ if (s[k] == B_CR) { return true; }
+ k = k + 1;
+ }
+ return false;
+}
+
+/// The first offset in [from, hi) where w starts, wholly inside the range, or NO_TEXT.
+pub fn find_in(s: []const u8, from: usize, hi: usize, w: []const u8) -> i64 {
+ var p : usize = from;
+ while (p + w.len <= hi) {
+ if (match_in(s, p, hi, w)) { return p as i64; }
+ p = p + 1;
+ }
+ return NO_TEXT;
+}
+
+/// The last offset in [lo, hi) where w starts, wholly inside the range, or NO_TEXT.
+pub fn rfind_in(s: []const u8, lo: usize, hi: usize, w: []const u8) -> i64 {
+ if (hi < lo + w.len) { return NO_TEXT; }
+ var p : usize = hi - w.len + 1;
+ while (p > lo) {
+ p = p - 1;
+ if (match_in(s, p, hi, w)) { return p as i64; }
+ }
+ return NO_TEXT;
+}
+
+pub fn next_nl(s: []const u8, from: usize) -> usize {
+ var p : usize = from;
+ while (p < s.len and s[p] != W_B_NL) { p = p + 1; }
+ return p;
+}
+
+/// bare(): a public yosys name loses its leading backslash; the start offset of the result.
+pub fn bare_lo(s: []const u8, lo: usize, hi: usize) -> usize {
+ if (lo < hi and s[lo] == W_B_BACKSLASH) { return lo + 1; }
+ return lo;
+}
+
+/// Are s[a..b) and t[c..d) the same bytes?
+pub fn same_range(s: []const u8, a: usize, b: usize, t: []const u8, c: usize, d: usize) -> bool {
+ if (b - a != d - c) { return false; }
+ var k : usize = 0;
+ while (a + k < b) {
+ if (s[a + k] != t[c + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// classOf for a non-empty type: the first of lut, carry, mux, io, clocked whose list holds it, else other.
+pub fn class_of(s: []const u8, tl: usize, th: usize, lists: []const u8) -> u32 {
+ var best : u32 = W_CLS_OTHER;
+ var a : usize = 0;
+ while (a < lists.len) {
+ const e : usize = next_nl(lists, a);
+ if (e > a + 1 and lists[a] >= 48 and lists[a] <= 52) {
+ if (same_range(s, tl, th, lists, a + 1, e)) {
+ const c : u32 = (lists[a] - 48) as u32;
+ if (c < best) { best = c; }
+ }
+ }
+ a = e + 1;
+ }
+ return best;
+}
+
+/// The tail of HEAD_RE from q: " (length=):" and then only space to the end of the line.
+pub fn head_tail(s: []const u8, q: usize, hi: usize) -> bool {
+ if (!match_in(s, q, hi, " (length=")) { return false; }
+ const a : usize = q + 9;
+ const d : usize = digits_end(s, a, hi);
+ if (d == a) { return false; }
+ if (!match_in(s, d, hi, "):")) { return false; }
+ if (skip_ws(s, d + 2, hi) != hi) { return false; }
+ SC[0] = digits_val(s, a, d);
+ return true;
+}
+
+/// HEAD_RE on the line [lo, hi): the packed range of the module name (lazy, so the first name that
+/// lets the tail match), the length in SC[0]; NO_TEXT when the line is no header.
+pub fn head_match(s: []const u8, lo: usize, hi: usize) -> i64 {
+ var f : i64 = find_in(s, lo, hi, "Longest topological path in ");
+ while (f >= 0) {
+ const ns : usize = (f as usize) + 28;
+ var q : usize = ns + 1;
+ var dead : bool = false;
+ while (q <= hi and !dead) {
+ if (s[q - 1] == B_CR) {
+ dead = true;
+ } else {
+ if (head_tail(s, q, hi)) { return pack(ns, q); }
+ q = q + 1;
+ }
+ }
+ f = find_in(s, (f as usize) + 1, hi, "Longest topological path in ");
+ }
+ return NO_TEXT;
+}
+
+/// LOOP_RE on the line: the packed range of the net bit, the module in SC[1]..SC[2]; NO_TEXT when none.
+pub fn loop_match(s: []const u8, lo: usize, hi: usize) -> i64 {
+ const te : usize = rtrim_end(s, lo, hi);
+ var ts : usize = te;
+ while (ts > lo and !is_space(s[ts - 1])) { ts = ts - 1; }
+ if (ts == te) { return NO_TEXT; }
+ if (ts < lo + 4) { return NO_TEXT; }
+ if (!match_in(s, ts - 4, hi, " in ")) { return NO_TEXT; }
+ const bhi : usize = ts - 4;
+ var f : i64 = find_in(s, lo, hi, "Detected loop at ");
+ while (f >= 0) {
+ const bl : usize = (f as usize) + 17;
+ if (bl + 1 <= bhi and !has_cr(s, bl, bhi)) {
+ SC[1] = ts as u64;
+ SC[2] = te as u64;
+ return pack(bl, bhi);
+ }
+ f = find_in(s, (f as usize) + 1, hi, "Detected loop at ");
+ }
+ return NO_TEXT;
+}
+
+/// CELL_RE on the line: type token in SC[0]..SC[1], name token in SC[2]..SC[3].
+pub fn cell_match(s: []const u8, lo: usize, hi: usize) -> bool {
+ var p : usize = skip_ws(s, lo, hi);
+ if (!match_in(s, p, hi, "cell")) { return false; }
+ p = p + 4;
+ var q : usize = skip_ws(s, p, hi);
+ if (q == p) { return false; }
+ p = q;
+ q = tok_end(s, p, hi);
+ if (q == p) { return false; }
+ SC[0] = p as u64;
+ SC[1] = q as u64;
+ p = skip_ws(s, q, hi);
+ if (p == q) { return false; }
+ q = tok_end(s, p, hi);
+ if (q == p) { return false; }
+ SC[2] = p as u64;
+ SC[3] = q as u64;
+ if (skip_ws(s, q, hi) != hi) { return false; }
+ return true;
+}
+
+/// STEP_RE on the line: the step number in SC[0] (held at a ceiling), the start of the rest in SC[1].
+pub fn step_match(s: []const u8, lo: usize, hi: usize) -> bool {
+ const p : usize = skip_ws(s, lo, hi);
+ const d : usize = digits_end(s, p, hi);
+ if (d == p) { return false; }
+ if (!match_in(s, d, hi, ": ")) { return false; }
+ if (has_cr(s, d + 2, hi)) { return false; }
+ SC[0] = digits_val(s, p, d);
+ SC[1] = (d + 2) as u64;
+ return true;
+}
+
+/// \b after a word character: the end of the line or a non-word byte.
+pub fn boundary_after(s: []const u8, p: usize, hi: usize) -> bool {
+ return p >= hi or !is_word(s[p]);
+}
+
+/// The first backtick in [from, hi), else hi.
+pub fn backtick_or_end(s: []const u8, from: usize, hi: usize) -> usize {
+ var p : usize = from;
+ while (p < hi and s[p] != W_B_BACKTICK) { p = p + 1; }
+ return p;
+}
+
+/// CMD_RES[0]: yosys> ltp ... (also yosys [module]> ltp ...)
+pub fn cmd_prompt(s: []const u8, lo: usize, hi: usize) -> i64 {
+ var p : usize = skip_ws(s, lo, hi);
+ if (!match_in(s, p, hi, "yosys")) { return NO_TEXT; }
+ p = p + 5;
+ if (match_in(s, p, hi, " [")) {
+ const c : i64 = find_in(s, p + 2, hi, "]");
+ if (c >= 0) { p = (c as usize) + 1; }
+ }
+ if (!match_in(s, p, hi, "> ")) { return NO_TEXT; }
+ p = p + 2;
+ if (!match_in(s, p, hi, "ltp")) { return NO_TEXT; }
+ if (!boundary_after(s, p + 3, hi)) { return NO_TEXT; }
+ const e : usize = rtrim_end(s, p + 3, hi);
+ if (has_cr(s, p + 3, e)) { return NO_TEXT; }
+ return pack(p, e);
+}
+
+/// CMD_RES[1]: -- Running command `ltp ...' --
+pub fn cmd_running_ltp(s: []const u8, lo: usize, hi: usize) -> i64 {
+ if (!match_in(s, lo, hi, "-- Running command `ltp")) { return NO_TEXT; }
+ const t : usize = lo + 23;
+ if (!boundary_after(s, t, hi)) { return NO_TEXT; }
+ const r : usize = backtick_or_end(s, t, hi);
+ const e : i64 = rfind_in(s, t, r, "' --");
+ if (e < 0) { return NO_TEXT; }
+ return pack(lo + 20, e as usize);
+}
+
+/// CMD_RES[2]: -- Running command `... ltp ...' -- with the last whole word ltp that lets the rest fit.
+pub fn cmd_running_any(s: []const u8, lo: usize, hi: usize) -> i64 {
+ if (!match_in(s, lo, hi, "-- Running command `")) { return NO_TEXT; }
+ const a : usize = lo + 20;
+ if (hi < a + 3) { return NO_TEXT; }
+ var t : usize = hi - 2;
+ while (t > a) {
+ t = t - 1;
+ if (match_in(s, t, hi, "ltp") and !has_cr(s, a, t)) {
+ if (t == a or !is_word(s[t - 1])) {
+ if (boundary_after(s, t + 3, hi)) {
+ const r : usize = backtick_or_end(s, t + 3, hi);
+ const e : i64 = rfind_in(s, t + 3, r, "' --");
+ if (e >= 0) { return pack(a, e as usize); }
+ }
+ }
+ }
+ }
+ return NO_TEXT;
+}
+
+pub fn cmd_match(s: []const u8, lo: usize, hi: usize) -> i64 {
+ var c : i64 = cmd_prompt(s, lo, hi);
+ if (c < 0) { c = cmd_running_ltp(s, lo, hi); }
+ if (c < 0) { c = cmd_running_any(s, lo, hi); }
+ return c;
+}
+
+/// The end of the line that starts at ls: the offset of its newline (or the end), minus a CR just before the newline.
+pub fn line_hi(s: []const u8, ls: usize, le: usize) -> usize {
+ if (le < s.len and le > ls and s[le - 1] == B_CR) { return le - 1; }
+ return le;
+}
+
+/// Is every byte of the text a space? (JS: no line has anything left after trim.)
+pub fn all_space(s: []const u8) -> bool {
+ return skip_ws(s, 0, s.len) == s.len;
+}
+
+/// Pass 1: every RTLIL `cell ` line, in order, into CEL as name_lo, name_hi, type_lo, type_hi.
+/// Returns false when there are more than W_MAX_CELLS.
+pub fn read_cells(s: []const u8) -> bool {
+ var ls : usize = 0;
+ var n : usize = 0;
+ var distinct : u64 = 0;
+ while (ls <= s.len) {
+ const le : usize = next_nl(s, ls);
+ const hi : usize = line_hi(s, ls, le);
+ if (cell_match(s, ls, hi)) {
+ if (n >= W_MAX_CELLS) { return false; }
+ const tl : usize = bare_lo(s, SC[0] as usize, SC[1] as usize);
+ const th : usize = SC[1] as usize;
+ const nl : usize = bare_lo(s, SC[2] as usize, SC[3] as usize);
+ const nh : usize = SC[3] as usize;
+ var seen : bool = false;
+ var k : usize = 0;
+ while (k < n and !seen) {
+ if (same_range(s, CEL[k * 4], CEL[k * 4 + 1], s, nl, nh)) { seen = true; }
+ k = k + 1;
+ }
+ if (!seen) { distinct = distinct + 1; }
+ CEL[n * 4] = nl as u32;
+ CEL[n * 4 + 1] = nh as u32;
+ CEL[n * 4 + 2] = tl as u32;
+ CEL[n * 4 + 3] = th as u32;
+ n = n + 1;
+ }
+ ls = le + 1;
+ }
+ INFO[3] = distinct;
+ INFO[5] = n as u64;
+ return true;
+}
+
+/// Pass 3, one step: its type from the dump (the last cell line with its via as name), and its class.
+pub fn type_step(s: []const u8, i: usize, lists: []const u8) -> u32 {
+ const vf : u32 = STP[i * 9 + 2];
+ const vl : usize = STP[i * 9 + 3] as usize;
+ const vh : usize = STP[i * 9 + 4] as usize;
+ STP[i * 9 + 5] = 0;
+ STP[i * 9 + 6] = 0;
+ STP[i * 9 + 7] = 0;
+ if (vf == 0 or vh <= vl) {
+ STP[i * 9 + 8] = W_CLS_START;
+ return W_CLS_START;
+ }
+ var k : usize = INFO[5] as usize;
+ var cls : u32 = W_CLS_UNKNOWN;
+ var found : bool = false;
+ while (k > 0 and !found) {
+ k = k - 1;
+ if (same_range(s, CEL[k * 4], CEL[k * 4 + 1], s, vl, vh)) {
+ found = true;
+ STP[i * 9 + 5] = 1;
+ STP[i * 9 + 6] = CEL[k * 4 + 2];
+ STP[i * 9 + 7] = CEL[k * 4 + 3];
+ if (CEL[k * 4 + 3] > CEL[k * 4 + 2]) {
+ cls = class_of(s, CEL[k * 4 + 2] as usize, CEL[k * 4 + 3] as usize, lists);
+ }
+ }
+ }
+ STP[i * 9 + 8] = cls;
+ return cls;
+}
+
+/// parseLtp. Returns 0 ok, 1 empty, 2 noltp, 3 loop, -1 over a table limit.
+pub fn ltp_parse(text: []const u8, lists: []const u8) -> i32 {
+ INFO[0] = 0;
+ INFO[1] = 0;
+ INFO[2] = 0;
+ INFO[3] = 0;
+ INFO[4] = 0;
+ INFO[5] = 0;
+ if (all_space(text)) { return 1; }
+ if (!read_cells(text)) { return -1; }
+ var nb : usize = 0;
+ var ns : usize = 0;
+ var nl : usize = 0;
+ var cmd_set : u64 = 0;
+ var cmd_lo : u64 = 0;
+ var cmd_hi : u64 = 0;
+ var cur : i64 = -1;
+ var ls : usize = 0;
+ while (ls <= text.len) {
+ const le : usize = next_nl(text, ls);
+ const hi : usize = line_hi(text, ls, le);
+ const c : i64 = cmd_match(text, ls, hi);
+ if (c >= 0) {
+ cmd_set = 1;
+ cmd_lo = lo_of(c) as u64;
+ cmd_hi = hi_of(c) as u64;
+ }
+ var handled : bool = false;
+ const lm : i64 = loop_match(text, ls, hi);
+ if (lm >= 0) {
+ if (nl >= W_MAX_LOOPS) { return -1; }
+ LOP[nl * 4] = lo_of(lm) as u32;
+ LOP[nl * 4 + 1] = hi_of(lm) as u32;
+ LOP[nl * 4 + 2] = SC[1] as u32;
+ LOP[nl * 4 + 3] = SC[2] as u32;
+ nl = nl + 1;
+ handled = true;
+ }
+ if (!handled) {
+ const hm : i64 = head_match(text, ls, hi);
+ if (hm >= 0) {
+ if (nb >= W_MAX_BLOCKS) { return -1; }
+ const len : u64 = SC[0];
+ const ml : usize = bare_lo(text, lo_of(hm), hi_of(hm));
+ const b : usize = nb * 16;
+ BLK[b] = ml as u64;
+ BLK[b + 1] = hi_of(hm) as u64;
+ BLK[b + 2] = len;
+ BLK[b + 3] = cmd_set;
+ BLK[b + 4] = cmd_lo;
+ BLK[b + 5] = cmd_hi;
+ BLK[b + 6] = 0;
+ BLK[b + 7] = ns as u64;
+ var z : usize = 8;
+ while (z < 15) {
+ BLK[b + z] = 0;
+ z = z + 1;
+ }
+ cur = nb as i64;
+ nb = nb + 1;
+ handled = true;
+ }
+ }
+ if (!handled and cur >= 0) {
+ const cb : usize = (cur as usize) * 16;
+ var took : bool = false;
+ if (step_match(text, ls, hi)) {
+ if (SC[0] == BLK[cb + 6]) {
+ if (ns >= W_MAX_STEPS) { return -1; }
+ var bl : usize = SC[1] as usize;
+ var bh : usize = hi;
+ var vf : u32 = 0;
+ var vl : usize = 0;
+ var vh : usize = 0;
+ if (bh > bl and text[bh - 1] == W_B_RPAREN) {
+ const at : i64 = rfind_in(text, bl, bh, " (via ");
+ if (at >= 0) {
+ vl = bare_lo(text, (at as usize) + 6, bh - 1);
+ vh = bh - 1;
+ vf = 1;
+ bh = at as usize;
+ }
+ }
+ STP[ns * 9] = bl as u32;
+ STP[ns * 9 + 1] = bh as u32;
+ STP[ns * 9 + 2] = vf;
+ STP[ns * 9 + 3] = vl as u32;
+ STP[ns * 9 + 4] = vh as u32;
+ ns = ns + 1;
+ BLK[cb + 6] = BLK[cb + 6] + 1;
+ took = true;
+ }
+ }
+ if (!took and BLK[cb + 6] > 0) { cur = -1; }
+ }
+ ls = le + 1;
+ }
+ INFO[0] = nb as u64;
+ INFO[1] = nl as u64;
+ INFO[4] = ns as u64;
+ if (nb == 0) {
+ if (nl > 0) { return 3; }
+ return 2;
+ }
+ INFO[2] = 0;
+ if (INFO[3] > 0) { INFO[2] = 1; }
+ var bi : usize = 0;
+ while (bi < nb) {
+ const first : usize = BLK[bi * 16 + 7] as usize;
+ const cnt : usize = BLK[bi * 16 + 6] as usize;
+ var k : usize = 0;
+ while (k < cnt) {
+ const cls : u32 = type_step(text, first + k, lists);
+ if (cls < W_CLS_START) { BLK[bi * 16 + 8 + (cls as usize)] = BLK[bi * 16 + 8 + (cls as usize)] + 1; }
+ k = k + 1;
+ }
+ bi = bi + 1;
+ }
+ return 0;
+}
+
+pub fn ltp_info(f: u32) -> u64 {
+ return INFO[f as usize];
+}
+
+pub fn ltp_blk(b: u32, f: u32) -> u64 {
+ return BLK[(b as usize) * 16 + (f as usize)];
+}
+
+pub fn ltp_step(i: u32, f: u32) -> u32 {
+ return STP[(i as usize) * 9 + (f as usize)];
+}
+
+pub fn ltp_loop(l: u32, f: u32) -> u32 {
+ return LOP[(l as usize) * 4 + (f as usize)];
+}
+
+// ---------------------------------------------------------------------------------------------
+; FOLDED-END
+
+pub var LISTS : [1024]u8 = [0; 1024];
+pub var LISTS_N : usize = 0;
+pub var SAMP : [2097152]u8 = [0; 2097152];
+pub var SAMP_LEN : u32 = 0;
+pub var SAMP_STATE : u32 = 0;
+pub var PASTE : [2097152]u8 = [0; 2097152];
+pub var PASTE_LEN : u32 = 0;
+pub var PASTE_CHARS : u32 = 0;
+pub var PASTE_FIT : bool = true;
+pub var TA : [2097152]u8 = [0; 2097152];
+pub var TA_LEN : u32 = 0;
+pub var TB : [2097152]u8 = [0; 2097152];
+pub var TB_LEN : u32 = 0;
+pub var SRC : u32 = 0;
+pub var PICK : u32 = 0;
+pub var RESULT_HIDDEN : bool = true;
+pub var SAMPLE_ON : bool = false;
+pub var DRAGGING : bool = false;
+pub var EFF_CLEAR : bool = false;
+pub var STATUS : [65536]u8 = [0; 65536];
+pub var STATUS_LEN : u32 = 0;
+pub var FNAME : [1024]u8 = [0; 1024];
+pub var FNAME_LEN : u32 = 0;
+pub var FSIZE : u32 = 0;
+pub var BASE : [1024]u8 = [0; 1024];
+pub var BASE_LEN : u32 = 0;
+pub var G1 : [32]u8 = [0; 32];
+pub var G2 : [32]u8 = [0; 32];
+pub var G3 : [32]u8 = [0; 32];
+pub var WARN_TYPES : [1024]u8 = [0; 1024];
+
+fn lists_add(c: u8, s: []const u8) -> usize {
+ LISTS[LISTS_N] = c;
+ LISTS_N = LISTS_N + 1;
+ var k : usize = 0;
+ while (k < s.len) {
+ LISTS[LISTS_N] = s[k];
+ LISTS_N = LISTS_N + 1;
+ k = k + 1;
+ }
+ LISTS[LISTS_N] = W_B_NL;
+ LISTS_N = LISTS_N + 1;
+ return LISTS_N;
+}
+
+/// listsOf(W): the cell-type lists, one class digit then the type per line, in the reader's layout.
+fn lists_build() -> usize {
+ LISTS_N = 0;
+ var i : usize = 0;
+ while (i < 7) { lists_add(48, K_LUT_TYPES[i]); i = i + 1; }
+ i = 0;
+ while (i < 1) { lists_add(49, K_CARRY_TYPES[i]); i = i + 1; }
+ i = 0;
+ while (i < 2) { lists_add(50, K_MUX_TYPES[i]); i = i + 1; }
+ i = 0;
+ while (i < 7) { lists_add(51, K_IO_TYPES[i]); i = i + 1; }
+ i = 0;
+ while (i < 11) { lists_add(52, K_CLOCKED_TYPES[i]); i = i + 1; }
+ return LISTS_N;
+}
+
+/// The bytes of text number cs: 1 the sample, 2 TA, 3 TB (what the page shows is one of them).
+fn buf(cs: u32) -> []const u8 {
+ if (cs == 1) { return SAMP[0..(SAMP_LEN as usize)]; }
+ if (cs == 2) { return TA[0..(TA_LEN as usize)]; }
+ return TB[0..(TB_LEN as usize)];
+}
+
+/// The other of TA and TB than the one the page shows.
+fn cand_src() -> u32 {
+ if (SRC == 2) { return 3; }
+ return 2;
+}
+
+/// Copy s into the candidate buffer; its number.
+fn load_cand(s: []const u8) -> u32 {
+ const cs : u32 = cand_src();
+ var k : usize = 0;
+ if (cs == 2) {
+ while (k < s.len) { TA[k] = s[k]; k = k + 1; }
+ TA_LEN = s.len as u32;
+ } else {
+ while (k < s.len) { TB[k] = s[k]; k = k + 1; }
+ TB_LEN = s.len as u32;
+ }
+ return cs;
+}
+
+/// JavaScript's String length of UTF-8 bytes: a lead byte counts one, or two from 240 up (a surrogate pair).
+fn utf16_len(s: []const u8) -> u32 {
+ var n : u32 = 0;
+ var k : usize = 0;
+ while (k < s.len) {
+ const b : u8 = s[k];
+ if (b < 128 or b >= 192) {
+ n = n + 1;
+ if (b >= 240) { n = n + 1; }
+ }
+ k = k + 1;
+ }
+ return n;
+}
+
+/// The offset after a leading byte order mark, which Blob.text() and Response.text() drop.
+fn bom_skip(s: []const u8) -> usize {
+ if (s.len >= 3 and s[0] == 239 and s[1] == 187 and s[2] == 191) { return 3; }
+ return 0;
+}
+
+fn fin(n: u32) -> u32 {
+ if (n == OVER) { return 0; }
+ return n;
+}
+
+fn status_note() -> u32 {
+ STATUS_LEN = fin(text(STATUS[0..], 0, SAY_LOCAL_NOTE));
+ return 0;
+}
+
+fn status_err_load() -> u32 {
+ STATUS_LEN = fin(text(STATUS[0..], 0, SAY_ERR_LOAD));
+ return 0;
+}
+
+fn picked_into(n0: u32) -> u32 {
+ const gl : u32 = num_grouped(G1[0..], 0, FSIZE as u64);
+ return fill(STATUS[0..], n0, SAY_PICKED, FNAME[0..(FNAME_LEN as usize)], G1[0..(gl as usize)], "", 2);
+}
+
+fn status_picked() -> u32 {
+ STATUS_LEN = fin(picked_into(0));
+ return 0;
+}
+
+/// readText's success status: ". ", the name being "pasted text" or the picked-file line.
+fn status_read(kind: u32) -> u32 {
+ var n : u32 = 0;
+ if (kind == 0) { n = text(STATUS[0..], 0, SAY_PASTED); } else { n = picked_into(0); }
+ n = raw(STATUS[0..], n, ". ");
+ n = text(STATUS[0..], n, SAY_LOCAL_NOTE);
+ STATUS_LEN = fin(n);
+ return 0;
+}
+
+fn status_too_big(chars: u64) -> u32 {
+ const a : u32 = num_grouped(G1[0..], 0, chars);
+ const b : u32 = num_grouped(G2[0..], 0, W_TEXT_CAP as u64);
+ STATUS_LEN = fin(fill(STATUS[0..], 0, SAY_TOO_BIG, G1[0..(a as usize)], G2[0..(b as usize)], "", 2));
+ return 0;
+}
+
+fn status_err_read(kind: u32) -> u32 {
+ if (kind == 0) {
+ STATUS_LEN = fin(fill(STATUS[0..], 0, SAY_ERR_READ, SAY_PASTED, "", "", 1));
+ } else {
+ STATUS_LEN = fin(fill(STATUS[0..], 0, SAY_ERR_READ, FNAME[0..(FNAME_LEN as usize)], "", "", 1));
+ }
+ return 0;
+}
+
+/// errorText(code) for the parse failure code, the loop one with its place: read from the failed text.
+fn status_parse_error(code: i32, t: []const u8) -> u32 {
+ if (code == 1) { STATUS_LEN = fin(text(STATUS[0..], 0, SAY_ERROR_TEXT[0])); }
+ if (code == 2) { STATUS_LEN = fin(text(STATUS[0..], 0, SAY_ERROR_TEXT[1])); }
+ if (code == 3) {
+ var n : u32 = text(STATUS[0..], 0, SAY_ERROR_TEXT[2]);
+ n = raw(STATUS[0..], n, " ");
+ const bl : usize = ltp_loop(0, 0) as usize;
+ const bh : usize = ltp_loop(0, 1) as usize;
+ const ml : usize = ltp_loop(0, 2) as usize;
+ const mh : usize = ltp_loop(0, 3) as usize;
+ n = fill(STATUS[0..], n, SAY_LOOP_AT, t[bl..bh], t[ml..mh], "", 2);
+ STATUS_LEN = fin(n);
+ }
+ return 0;
+}
+
+/// After a failed parse the tables hold the failed text: read the shown text again.
+fn restore() -> u32 {
+ if (SRC != 0) {
+ const code : i32 = ltp_parse(buf(SRC), LISTS[0..LISTS_N]);
+ return code as u32;
+ }
+ return 0;
+}
+
+fn refuse() -> u32 {
+ RESULT_HIDDEN = true;
+ SAMPLE_ON = false;
+ return 0;
+}
+
+fn show() -> u32 {
+ PICK = (ltp_info(0) as u32) - 1;
+ RESULT_HIDDEN = false;
+ return 0;
+}
+
+/// readText: parse the candidate text cs; kind 0 pasted, 1 a picked file.
+fn read_text(cs: u32, kind: u32) -> u32 {
+ const code : i32 = ltp_parse(buf(cs), LISTS[0..LISTS_N]);
+ if (code != 0) {
+ if (code < 0) { status_err_read(kind); } else { status_parse_error(code, buf(cs)); }
+ restore();
+ refuse();
+ return 0;
+ }
+ SRC = cs;
+ SAMPLE_ON = false;
+ status_read(kind);
+ show();
+ return 0;
+}
+
+/// showSample: the cached sample; its failures say the sample did not load.
+fn show_sample() -> u32 {
+ if (SAMP_STATE != 1) {
+ status_err_load();
+ return 0;
+ }
+ const code : i32 = ltp_parse(buf(1), LISTS[0..LISTS_N]);
+ if (code != 0) {
+ status_err_load();
+ restore();
+ return 0;
+ }
+ SRC = 1;
+ SAMPLE_ON = true;
+ status_note();
+ show();
+ return 0;
+}
+
+pub fn w_base(u: []const u8) -> u32 {
+ var n : usize = u.len;
+ if (n > 1000) { n = 1000; }
+ var k : usize = 0;
+ while (k < n) { BASE[k] = u[k]; k = k + 1; }
+ BASE_LEN = n as u32;
+ return 0;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ lists_build();
+ SAMP_LEN = 0;
+ SAMP_STATE = 0;
+ PASTE_LEN = 0;
+ PASTE_CHARS = 0;
+ PASTE_FIT = true;
+ TA_LEN = 0;
+ TB_LEN = 0;
+ SRC = 0;
+ PICK = 0;
+ RESULT_HIDDEN = true;
+ SAMPLE_ON = false;
+ DRAGGING = false;
+ EFF_CLEAR = false;
+ FNAME_LEN = 0;
+ FSIZE = 0;
+ status_note();
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ const from : usize = bom_skip(s);
+ if (s.len - from > (W_TEXT_CAP as usize)) {
+ SAMP_STATE = 2;
+ } else {
+ var k : usize = from;
+ while (k < s.len) { SAMP[k - from] = s[k]; k = k + 1; }
+ SAMP_LEN = (s.len - from) as u32;
+ SAMP_STATE = 1;
+ }
+ show_sample();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ SAMP_STATE = 2;
+ show_sample();
+ return 0;
+}
+
+/// A control's text changed. Control 1 is the paste box (0 is the file input, which carries no text).
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ if (i != 1) { return 0; }
+ PASTE_CHARS = utf16_len(s);
+ if (s.len > (W_TEXT_CAP as usize)) {
+ PASTE_FIT = false;
+ PASTE_LEN = 0;
+ return 0;
+ }
+ PASTE_FIT = true;
+ var k : usize = 0;
+ while (k < s.len) { PASTE[k] = s[k]; k = k + 1; }
+ PASTE_LEN = s.len as u32;
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ if (i == 0) { show_sample(); }
+ if (i == 1) {
+ if (!PASTE_FIT) {
+ status_too_big(PASTE_CHARS as u64);
+ refuse();
+ return 0;
+ }
+ const cs : u32 = load_cand(PASTE[0..(PASTE_LEN as usize)]);
+ read_text(cs, 0);
+ }
+ if (i == 2) {
+ PASTE_LEN = 0;
+ PASTE_CHARS = 0;
+ PASTE_FIT = true;
+ EFF_CLEAR = true;
+ status_note();
+ show_sample();
+ }
+ if (i >= 3) {
+ const j : u32 = i - 3;
+ if (SRC != 0 and ltp_info(0) >= 2 and (j as u64) < ltp_info(0)) {
+ PICK = j;
+ RESULT_HIDDEN = false;
+ }
+ }
+ return 0;
+}
+
+/// readFile, first half: remember the name and size, say what was picked, refuse a file over the cap.
+pub fn w_file(z: u32, name: []const u8, size: u32) -> u32 {
+ EFF_CLEAR = false;
+ var n : usize = name.len;
+ if (n > 1000) { n = 1000; }
+ var k : usize = 0;
+ while (k < n) { FNAME[k] = name[k]; k = k + 1; }
+ FNAME_LEN = n as u32;
+ FSIZE = size;
+ status_picked();
+ if (size > W_TEXT_CAP) {
+ status_too_big(size as u64);
+ refuse();
+ return 0;
+ }
+ return 1;
+}
+
+/// readFile, second half: the bytes arrived.
+pub fn w_file_data(z: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ const from : usize = bom_skip(s);
+ const cs : u32 = load_cand(s[from..s.len]);
+ read_text(cs, 1);
+ return 0;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ EFF_CLEAR = false;
+ status_err_read(1);
+ refuse();
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ EFF_CLEAR = false;
+ const want : bool = over != 0;
+ if (want == DRAGGING) { return 0; }
+ DRAGGING = want;
+ return 1;
+}
+
+/// Effects after the last event: after "Clear" the paste box and the file input are emptied.
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (EFF_CLEAR and k < 2) {
+ dst[0] = 118;
+ dst[1] = k as u8;
+ return 2;
+ }
+ return 0;
+}
+
+; --- drawing ---------------------------------------------------------------------------------------
+
+/// One path block's buttons (when there are two or more): fmt(SAY_BLOCK_NAME, i + 1, command ?? module, num(length)).
+fn render_blocks(dst: []u8, n0: u32, t: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_BLOCKS_HEAD);
+ n = raw(dst, n, "
");
+ const nb : u32 = ltp_info(0) as u32;
+ var i : u32 = 0;
+ while (i < nb) {
+ n = raw(dst, n, "");
+ const a : u32 = dec_small(G1[0..], (i + 1) as u64);
+ const c : u32 = num_grouped(G2[0..], 0, ltp_blk(i, 2));
+ if (ltp_blk(i, 3) == 1) {
+ const cl : usize = ltp_blk(i, 4) as usize;
+ const ch : usize = ltp_blk(i, 5) as usize;
+ n = fill(dst, n, SAY_BLOCK_NAME, G1[0..(a as usize)], t[cl..ch], G2[0..(c as usize)], 3);
+ } else {
+ const ml : usize = ltp_blk(i, 0) as usize;
+ const mh : usize = ltp_blk(i, 1) as usize;
+ n = fill(dst, n, SAY_BLOCK_NAME, G1[0..(a as usize)], t[ml..mh], G2[0..(c as usize)], 3);
+ }
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+/// String(v): plain decimal.
+fn dec_small(dst: []u8, v0: u64) -> u32 {
+ if (v0 == 0) { dst[0] = 48; return 1; }
+ var digits : [20]u8 = [0; 20];
+ var k : usize = 0;
+ var v : u64 = v0;
+ while (v > 0) {
+ digits[k] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ k = k + 1;
+ }
+ var n : u32 = 0;
+ while (k > 0) {
+ k = k - 1;
+ dst[n as usize] = digits[k];
+ n = n + 1;
+ }
+ return n;
+}
+
+fn render_head(dst: []u8, n0: u32, t: []const u8, b: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ n = num_grouped(dst, n, ltp_blk(b, 2));
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_LENGTH_LABEL);
+ n = raw(dst, n, "
");
+ if (ltp_info(2) == 1) {
+ n = raw(dst, n, "");
+ n = num_grouped(dst, n, ltp_blk(b, 8));
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_LUT_LABEL);
+ n = raw(dst, n, "");
+ } else {
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_LUT_UNKNOWN);
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "
");
+ const ml : usize = ltp_blk(b, 0) as usize;
+ const mh : usize = ltp_blk(b, 1) as usize;
+ if (ltp_blk(b, 3) == 1) {
+ const cl : usize = ltp_blk(b, 4) as usize;
+ const ch : usize = ltp_blk(b, 5) as usize;
+ n = fill(dst, n, SAY_MODULE, t[ml..mh], t[cl..ch], "", 2);
+ } else {
+ n = fill(dst, n, SAY_MODULE, t[ml..mh], SAY_NO_COMMAND, "", 2);
+ }
+ return raw(dst, n, "
");
+}
+
+/// The clocked-cell types on the block, each once, in the order first met, joined by ", " into WARN_TYPES.
+fn clocked_types(t: []const u8, b: u32) -> u32 {
+ var w : u32 = 0;
+ const first : u32 = ltp_blk(b, 7) as u32;
+ const cnt : u32 = ltp_blk(b, 6) as u32;
+ var k : u32 = 0;
+ while (k < cnt) {
+ const si : u32 = first + k;
+ if (ltp_step(si, 8) == 4) {
+ const tl : usize = ltp_step(si, 6) as usize;
+ const th : usize = ltp_step(si, 7) as usize;
+ var seen : bool = false;
+ var q : u32 = 0;
+ while (q < k and !seen) {
+ const sj : u32 = first + q;
+ if (ltp_step(sj, 8) == 4) {
+ if (same_range(t, ltp_step(sj, 6) as usize, ltp_step(sj, 7) as usize, t, tl, th)) { seen = true; }
+ }
+ q = q + 1;
+ }
+ if (!seen) {
+ if (w > 0) {
+ WARN_TYPES[w as usize] = 44;
+ WARN_TYPES[(w + 1) as usize] = 32;
+ w = w + 2;
+ }
+ var c : usize = tl;
+ while (c < th) {
+ WARN_TYPES[w as usize] = t[c];
+ w = w + 1;
+ c = c + 1;
+ }
+ }
+ }
+ k = k + 1;
+ }
+ return w;
+}
+
+fn render_warns(dst: []u8, n0: u32, t: []const u8, b: u32) -> u32 {
+ var n : u32 = n0;
+ if (ltp_blk(b, 12) > 0) {
+ const w : u32 = clocked_types(t, b);
+ const a : u32 = num_grouped(G1[0..], 0, ltp_blk(b, 12));
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_CLOCKED_WARN, G1[0..(a as usize)], WARN_TYPES[0..(w as usize)], "", 2);
+ n = raw(dst, n, "
");
+ }
+ if (ltp_info(2) == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_UNTYPED);
+ n = raw(dst, n, "
");
+ } else {
+ if (ltp_blk(b, 14) > 0) {
+ const a : u32 = num_grouped(G1[0..], 0, ltp_blk(b, 14));
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_MISSING_TYPES, G1[0..(a as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ }
+ }
+ if (ltp_blk(b, 6) != ltp_blk(b, 2) + 1) {
+ const a : u32 = num_grouped(G1[0..], 0, ltp_blk(b, 6));
+ const c : u32 = num_grouped(G2[0..], 0, ltp_blk(b, 2) + 1);
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_INCOMPLETE, G1[0..(a as usize)], G2[0..(c as usize)], "", 2);
+ n = raw(dst, n, "
");
+ }
+ if (ltp_info(1) > 0) {
+ const a : u32 = num_grouped(G1[0..], 0, ltp_info(1));
+ const bl : usize = ltp_loop(0, 0) as usize;
+ const bh : usize = ltp_loop(0, 1) as usize;
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_LOOPS, G1[0..(a as usize)], t[bl..bh], "", 2);
+ n = raw(dst, n, "
");
+ }
+ return n;
+}
+
+fn legend_item(dst: []u8, n0: u32, b: u32, c: u32) -> u32 {
+ var n : u32 = n0;
+ const cnt : u64 = ltp_blk(b, 8 + c);
+ if (cnt > 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_CLASS_NAMES[c as usize]);
+ n = raw(dst, n, " ");
+ n = num_grouped(dst, n, cnt);
+ n = raw(dst, n, "
");
+ }
+ return n;
+}
+
+/// A step's via has bytes: JavaScript's `s.via` is truthy.
+fn has_via(si: u32) -> bool {
+ return ltp_step(si, 2) == 1 and ltp_step(si, 4) > ltp_step(si, 3);
+}
+
+fn render_stairs(dst: []u8, n0: u32, t: []const u8, b: u32) -> u32 {
+ var n : u32 = n0;
+ const first : u32 = ltp_blk(b, 7) as u32;
+ const cnt : u32 = ltp_blk(b, 6) as u32;
+ var cells : u32 = 0;
+ var k : u32 = 0;
+ while (k < cnt) {
+ if (has_via(first + k)) { cells = cells + 1; }
+ k = k + 1;
+ }
+ var width : u32 = cells;
+ if (width < 1) { width = 1; }
+ n = raw(dst, n, "
");
+ var m : u32 = 0;
+ k = 0;
+ while (k < cnt) {
+ const si : u32 = first + k;
+ if (has_via(si)) {
+ n = raw(dst, n, "
");
+ n = num(dst, n, k as u64);
+ n = raw(dst, n, "= width) { n = raw(dst, n, " is-left"); }
+ n = raw(dst, n, "\"");
+ const vl : usize = ltp_step(si, 3) as usize;
+ const vh : usize = ltp_step(si, 4) as usize;
+ n = attr(dst, n, "title", t[vl..vh]);
+ n = raw(dst, n, ">");
+ if (ltp_step(si, 5) == 1) {
+ n = text(dst, n, t[(ltp_step(si, 6) as usize)..(ltp_step(si, 7) as usize)]);
+ } else {
+ n = text(dst, n, t[vl..vh]);
+ }
+ n = raw(dst, n, "
");
+ m = m + 1;
+ }
+ k = k + 1;
+ }
+ n = raw(dst, n, "
");
+ if (cnt > 0) {
+ const bl : usize = ltp_step(first, 0) as usize;
+ const bh : usize = ltp_step(first, 1) as usize;
+ n = raw(dst, n, "");
+ n = fill(dst, n, SAY_START, t[bl..bh], "", "", 1);
+ n = raw(dst, n, "");
+ }
+ if (cnt > 1) {
+ const el : usize = ltp_step(first + cnt - 1, 0) as usize;
+ const eh : usize = ltp_step(first + cnt - 1, 1) as usize;
+ n = raw(dst, n, "");
+ n = fill(dst, n, SAY_END, t[el..eh], "", "", 1);
+ n = raw(dst, n, "");
+ }
+ return raw(dst, n, "
");
+}
+
+fn render_table(dst: []u8, n0: u32, t: []const u8, b: u32) -> u32 {
+ var n : u32 = n0;
+ const first : u32 = ltp_blk(b, 7) as u32;
+ const cnt : u32 = ltp_blk(b, 6) as u32;
+ var k : u32 = 0;
+ while (k < cnt) {
+ const si : u32 = first + k;
+ n = raw(dst, n, "
");
+ n = num(dst, n, k as u64);
+ n = raw(dst, n, "
");
+ if (has_via(si)) {
+ if (ltp_step(si, 5) == 1) {
+ n = text(dst, n, t[(ltp_step(si, 6) as usize)..(ltp_step(si, 7) as usize)]);
+ } else {
+ n = text(dst, n, SAY_CLASS_NAMES[6]);
+ }
+ } else {
+ n = text(dst, n, SAY_CLASS_NAMES[7]);
+ }
+ n = raw(dst, n, "
");
+ if (ltp_step(si, 2) == 1) {
+ n = text(dst, n, t[(ltp_step(si, 3) as usize)..(ltp_step(si, 4) as usize)]);
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, t[(ltp_step(si, 0) as usize)..(ltp_step(si, 1) as usize)]);
+ n = raw(dst, n, "
");
+ k = k + 1;
+ }
+ return n;
+}
+
+fn render_result(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ } else {
+ n = raw(dst, n, ">");
+ n = render_blocks(dst, n, t);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ if (have) { n = render_head(dst, n, t, PICK); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NOT_NS);
+ n = raw(dst, n, "
");
+ if (have) { n = render_warns(dst, n, t, PICK); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAIRS_HEAD);
+ n = raw(dst, n, "
");
+ if (have) {
+ n = legend_item(dst, n, PICK, 0);
+ n = legend_item(dst, n, PICK, 1);
+ n = legend_item(dst, n, PICK, 2);
+ n = legend_item(dst, n, PICK, 3);
+ n = legend_item(dst, n, PICK, 5);
+ n = legend_item(dst, n, PICK, 6);
+ n = legend_item(dst, n, PICK, 4);
+ }
+ n = raw(dst, n, "
");
+ if (have) {
+ n = render_stairs(dst, n, t, PICK);
+ } else {
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TABLE_SUMMARY);
+ n = raw(dst, n, "
");
+ var h : usize = 0;
+ while (h < 4) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TABLE_HEAD[h]);
+ n = raw(dst, n, "
");
+ h = h + 1;
+ }
+ n = raw(dst, n, "
");
+ if (have) { n = render_table(dst, n, t, PICK); }
+ return raw(dst, n, "
");
+}
+
+/// The whole page, as tool.js's root.replaceChildren(...) and the render calls left it. 0 on overflow.
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_SAMPLE_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SAMPLE_NAME);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW_HEAD);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW[0]);
+ n = put(dst, n, 10);
+ n = text(dst, n, SAY_HOW[1]);
+ n = put(dst, n, 10);
+ n = text(dst, n, SAY_HOW[2]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW_NOTE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_READ);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_CLEAR);
+ n = raw(dst, n, "
");
+ var q : usize = 0;
+ while (q < (STATUS_LEN as usize)) {
+ n = put(dst, n, STATUS[q]);
+ q = q + 1;
+ }
+ n = raw(dst, n, "
");
+ n = render_result(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HONEST);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names its imported functions
+/// mention. This anchor mentions every helper the writer and the line reader call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = put(o, 0, 0);
+ n = put_escaped(o, n, 0);
+ n = n + OVER + ((H_LT + H_GT + H_AMP + H_QUOTE + H_APOS + H_SPACE + H_SLASH + H_EQ) as u32);
+ n = n + (is_space(0) as u32) + (B_SPACE as u32) + (B_TAB as u32) + (B_CR as u32) + (PACK as u32);
+ n = n + (pack(0, 0) as u32) + (lo_of(0) as u32) + (hi_of(0) as u32);
+ return n;
+}
+
+; --- Test support (lut_depth_logic.t27 567-568, 572-587 and 624-645): a text built from lines ---
+pub var TXT : [8192]u8 = [0; 8192];
+pub var TXN : usize = 0;
+pub fn tx_reset() -> usize {
+ TXN = 0;
+ return 0;
+}
+
+pub fn tx_line(s: []const u8) -> usize {
+ var k : usize = 0;
+ while (k < s.len) {
+ TXT[TXN] = s[k];
+ TXN = TXN + 1;
+ k = k + 1;
+ }
+ TXT[TXN] = W_B_NL;
+ TXN = TXN + 1;
+ return TXN;
+}
+pub fn is_text(s: []const u8, lo: u64, hi: u64, w: []const u8) -> bool {
+ return (hi - lo) == (w.len as u64) and match_in(s, lo as usize, hi as usize, w);
+}
+
+/// The sample's first path, cut to its first four steps, with the real names, and a dump of the cells it names.
+pub fn short_block() -> usize {
+ tx_reset();
+ tx_line("yosys> ltp -noff t:FD* %n");
+ tx_line("");
+ tx_line("Longest topological path in gf16_add (length=3):");
+ tx_line(" 0: \\in_b [12]");
+ tx_line(" 1: \\exp_b [3] (via $iopadmap$gf16_add.in_b_12)");
+ tx_line(" 2: $auto$alumacc.cc:512:replace_alu$1455.X [3] (via $abc$2850$auto$blifparse.cc:557:parse_blif$2977)");
+ tx_line(" 3: $auto$alumacc.cc:512:replace_alu$1455.CO [3] (via $auto$alumacc.cc:512:replace_alu$1455.genblk1.slice[0].genblk1.carry4)");
+ tx_line("");
+ tx_line("yosys> dump t:*");
+ tx_line(" cell \\IBUF $iopadmap$gf16_add.in_b_12");
+ tx_line(" cell \\LUT2 $abc$2850$auto$blifparse.cc:557:parse_blif$2977");
+ tx_line(" cell \\CARRY4 $auto$alumacc.cc:512:replace_alu$1455.genblk1.slice[0].genblk1.carry4");
+ tx_line(" cell \\FDCE $auto$ff.cc:337:slice$1884");
+ return TXN;
+}
+
+; --- Tests of the native page ----------------------------------------------------------------------
+; The reader's own tests (7, in lut_depth_logic.t27) run on the same text; these hold the page.
+
+pub var OUT : [262144]u8 = [0; 262144];
+
+/// Does the rendered page hold w?
+fn has(n: u32, w: []const u8) -> bool {
+ return find_in(OUT[0..(n as usize)], 0, n as usize, w) >= 0;
+}
+
+/// The test sample: the short block, as the host hands it over.
+fn load_sample() -> u32 {
+ short_block();
+ w_init("");
+ w_base("http://h/w/");
+ w_data(0, TXT[0..TXN]);
+ return 0;
+}
+
+test the_lists_are_built_from_the_specs_constants {
+ w_init("");
+ assert LISTS[0] == 48;
+ assert LISTS[1] == 76;
+ assert LISTS_N == 195;
+ assert class_of("FDCE", 0, 4, LISTS[0..LISTS_N]) == 4;
+ assert class_of("CARRY4", 0, 6, LISTS[0..LISTS_N]) == 1;
+ assert class_of("MUXF8", 0, 5, LISTS[0..LISTS_N]) == 2;
+ assert class_of("IBUF", 0, 4, LISTS[0..LISTS_N]) == 3;
+ assert class_of("$lut", 0, 4, LISTS[0..LISTS_N]) == 0;
+ assert class_of("MY_CELL", 0, 7, LISTS[0..LISTS_N]) == 5;
+ assert W_DATA_FILE.len == K_SAMPLE_LOG.len + 2;
+}
+
+test the_page_opens_with_the_result_hidden_and_the_note_in_the_status {
+ w_init("");
+ w_base("http://h/w/");
+ const n : u32 = w_render(OUT[0..]);
+ assert n > 2000;
+ assert has(n, "");
+ assert has(n, "");
+ assert has(n, "
This path runs through 2 clocked cell(s): FDCE. ");
+ assert has(n, "
This block lists 4 of its 5 steps: part of it is missing from the text.
");
+ assert has(n, "
clocked 2
");
+ assert has(n, "1");
+}
+
+test two_blocks_give_buttons_and_a_click_moves_the_pick {
+ load_sample();
+ tx_reset();
+ tx_line("yosys> ltp -noff");
+ tx_line("Longest topological path in one (length=1):");
+ tx_line(" 0: a");
+ tx_line(" 1: b (via u1)");
+ tx_line("yosys> ltp");
+ tx_line("Longest topological path in two (length=2):");
+ tx_line(" 0: a");
+ tx_line(" 1: b (via u1)");
+ tx_line(" 2: c (via u1)");
+ tx_line(" cell \\LUT6 u1");
+ w_input(1, TXT[0..TXN]);
+ w_click(1);
+ assert PICK == 1;
+ var n : u32 = w_render(OUT[0..]);
+ assert has(n, "
Paths in this text
");
+ assert has(n, "aria-pressed=\"false\">1: ltp -noff, length 12: ltp, length 2");
+ w_click(3);
+ assert PICK == 0;
+ n = w_render(OUT[0..]);
+ assert has(n, "aria-pressed=\"true\">1: ltp -noff, length 1");
+ w_click(9);
+ assert PICK == 0;
+}
+
+test a_refused_text_hides_the_result_and_keeps_what_was_drawn {
+ load_sample();
+ w_input(1, "this is not ltp output");
+ w_click(1);
+ var n : u32 = w_render(OUT[0..]);
+ assert RESULT_HIDDEN;
+ assert SAMPLE_ON == false;
+ assert has(n, "No "Longest topological path in ... (length=N):" line in this text. Is it the output of yosys ltp?");
+ assert has(n, "");
+ assert has(n, "3");
+ w_input(1, " ");
+ w_click(1);
+ n = w_render(OUT[0..]);
+ assert has(n, "role=\"status\">Nothing to read yet.");
+ w_click(0);
+ n = w_render(OUT[0..]);
+ assert RESULT_HIDDEN == false;
+ assert has(n, "role=\"status\">Read in this tab; nothing is sent.");
+}
+
+test a_loop_is_told_with_its_place {
+ load_sample();
+ tx_reset();
+ tx_line("Detected loop at \\n [3] in top");
+ w_input(1, TXT[0..TXN]);
+ w_click(1);
+ const n : u32 = w_render(OUT[0..]);
+ assert has(n, "found a combinational loop and printed no path; a loop has no longest path. The first is at \\n [3] in module top.");
+ assert RESULT_HIDDEN;
+}
+
+test a_file_is_announced_then_read_and_a_big_or_unreadable_one_is_refused {
+ load_sample();
+ short_block();
+ assert w_file(0, "my.txt", 1234) == 1;
+ var n : u32 = w_render(OUT[0..]);
+ assert has(n, "role=\"status\">my<ltp>.txt: 1,234 bytes");
+ w_file_data(0, TXT[0..TXN]);
+ n = w_render(OUT[0..]);
+ assert has(n, "role=\"status\">my<ltp>.txt: 1,234 bytes. Read in this tab; nothing is sent.");
+ assert RESULT_HIDDEN == false;
+ assert w_file(0, "big.log", 3000000) == 0;
+ n = w_render(OUT[0..]);
+ assert has(n, "role=\"status\">3,000,000 characters is more than this page reads (2,097,152).");
+ assert RESULT_HIDDEN;
+ w_file_failed(0);
+ n = w_render(OUT[0..]);
+ assert has(n, "role=\"status\">Could not read big.log.");
+}
+
+test a_byte_order_mark_is_dropped_and_lengths_count_utf16 {
+ assert bom_skip("abc") == 0;
+ var b : [4]u8 = [239, 187, 191, 65];
+ assert bom_skip(b[0..4]) == 3;
+ var u : [6]u8 = [97, 195, 169, 226, 130, 172];
+ assert utf16_len(u[0..6]) == 3;
+ var e : [5]u8 = [97, 240, 159, 152, 128];
+ assert utf16_len(e[0..5]) == 3;
+}
+
+test dragging_marks_the_drop_zone_and_clear_empties_the_two_inputs {
+ load_sample();
+ assert w_drag(0, 1) == 1;
+ assert w_drag(0, 1) == 0;
+ var n : u32 = w_render(OUT[0..]);
+ assert has(n, "");
+ assert w_drag(0, 0) == 1;
+ n = w_render(OUT[0..]);
+ assert has(n, "");
+ w_input(1, "something");
+ assert PASTE_LEN == 9;
+ var e : [4]u8 = [0; 4];
+ w_click(2);
+ assert PASTE_LEN == 0;
+ assert w_effect(0, e[0..]) == 2;
+ assert e[0] == 118 and e[1] == 0;
+ assert w_effect(1, e[0..]) == 2;
+ assert e[1] == 1;
+ assert w_effect(2, e[0..]) == 0;
+ w_click(0);
+ assert w_effect(0, e[0..]) == 0;
+}
+
+test a_sample_that_did_not_load_says_so_and_draws_nothing {
+ w_init("");
+ w_data_failed(0);
+ const n : u32 = w_render(OUT[0..]);
+ assert has(n, "role=\"status\">The sample did not load.");
+ assert RESULT_HIDDEN;
+ w_data(0, "no path in here");
+ assert SAMP_STATE == 1;
+ const m : u32 = w_render(OUT[0..]);
+ assert has(m, "role=\"status\">The sample did not load.");
+ assert RESULT_HIDDEN;
+}
From 178d7d617665c5bdce53bef2925f1e826545098d Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:45:13 +0700
Subject: [PATCH 37/63] widgets: lut-explain drawn natively -- 64924-byte wasm,
3055 DOM comparisons, 0 differences (Closes #10938)
specs/widgets/lut_explain.t27: the trinity spec verbatim, the decoder of lut_explain_logic.t27 folded in
(a pub var cannot cross a use), and the page: decode with truth table and flips, FASM lines against the db,
file read, the guessing game, the design histogram. 19 tests (14 new), t27b 19/19. 38 data variants.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/lut_explain.t27 | 4931 +++++++++++++++++++++++++++++++++
1 file changed, 4931 insertions(+)
create mode 100644 specs/widgets/lut_explain.t27
diff --git a/specs/widgets/lut_explain.t27 b/specs/widgets/lut_explain.t27
new file mode 100644
index 0000000000..73a2603eba
--- /dev/null
+++ b/specs/widgets/lut_explain.t27
@@ -0,0 +1,4931 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/lut-explain.t27 -- a Xilinx 7-series LUT6 INIT read as logic, and the FASM line that sets it
+; Source of truth for public/widgets/lut-explain/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, and every number is a K_ constant or a field of the two
+; data files the script below wrote. ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: three views. Decode: the reader pastes a 64-bit INIT or FASM lines (or drops a
+; .fasm file, read in the tab and sent nowhere); the page shows the 64-row truth table (tap a row
+; to flip that bit and the INIT follows), the inputs that matter, a minimal sum of products from a
+; small Quine-McCluskey in lut.js, the function's name when it has one, the two LUT5 halves, and
+; each FASM line split into tile, site, BEL and feature with what prjxray-db says it sets. Guess
+; the gate: an INIT from our own routed design and four names to pick from. Our design: the
+; histogram of what all 4738 LUTs of that design are.
+
+; THE BIT CONVENTION (UG474; prjxray-db segbits name the bits INIT[00]..INIT[63]): INIT[i] is the
+; LUT output when the six inputs, read as a binary number, equal i, with input 0 as the least
+; significant bit. In FASM the inputs are the pins A1..A6, so A1 = I0 and A6 = I5, and a 64'b value
+; lists INIT[63] first. The tests below hold the INITs as four 16-bit words, word 0 = INIT[15:0],
+; because the test evaluator has no 64-bit integers.
+
+; DATA PROVENANCE: design.json and prjxray.json are written by scripts/widget-data/lut-explain.mjs,
+; which runs the page's own decoder (public/widgets/lut-explain/lut.js) in node over node.fasm and
+; over prjxray-db; its --selftest compares every K_DESIGN_ number and every K_EXAMPLE_ line here
+; with that output and with the real file. node.fasm is the TRI-NET node v2 for the AX7203 board
+; (xc7a200tfbg484-2), synthesised and routed by the tools and commands named in DATA_SOURCES.
+
+; WHAT IT DOES NOT CLAIM: that the names are complete. A LUT with no short name here may still be
+; a familiar gate of more inputs; the decoder knows the families listed in SAY_CLASS_ and no more.
+; It does not claim the design came from t27: the node is hand-written Verilog. It does not claim
+; the O5/O6 split was used: no LUT in this design routes O5, and the page says so. It does not ship
+; the interconnect tiles' bits, only the CLBLL and CLBLM part of prjxray-db.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_lut_explain;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "lut-explain";
+pub const TITLE : str = "LUT explain: a 7-series LUT6 INIT and its FASM line, read as logic";
+pub const DESCRIPTION : str = "Paste a 64-bit LUT6 INIT or FASM lines: truth table, minimal expression, the gate's name, what each feature sets. Then guess the gate on 4738 LUTs from a routed design.";
+pub const IMAGE_ALT : str = "LUT explain card: INIT 64'h6996966996696996 is XOR6, the parity of six inputs. Beside it, the functions of 4738 LUTs routed by nextpnr-xilinx: XOR2 520, XNOR2 457, 2:1 mux 189, and 2251 with no short name.";
+pub const HOOK : str = "One routed design, 4738 LUTs: 1305 are XOR or XNOR, 2251 have no short name. Guess the gate from its 64 INIT bits.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [6]str = ["node.fasm (sha256 e413fb51f92e88cb...), TRI-NET node v2 for the AX7203 board: part xc7a200tfbg484-2, top trinet_node_v2_ax7203, built in /tmp/x7board/node0 and recorded in its manifest.json", "Yosys 0.67+post (git b8e7da6f): synth_xilinx -flatten -abc9 -nocarry -nodsp -arch xc7. nextpnr-xilinx 0.9.2-107-g7037c948: --freq 50 --seed 1 --placer heap --router router1 --timing-allow-fail", "prjxray-db ab1fc60, artix7: segbits, ppips and tile_type JSON of CLBLL_L, CLBLL_R, CLBLM_L and CLBLM_R, extracted to prjxray.json", "prjxray c9f02d85, CLB fuzzer READMEs 011 to 019 (ffconfig, n5ffmux, ncy0, nffmux, noutmux, precyinit, ram, ndi1mux): what each CLB feature sets", "Xilinx UG474, 7 Series FPGAs CLB User Guide: a LUT6 is two LUT5s on shared inputs with outputs O5 and O6", "scripts/widget-data/lut-explain.mjs: runs this page's decoder (lut.js) over node.fasm, writes design.json and prjxray.json, and --selftest checks this spec's K_ numbers against them"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers ---------------------------------------------------------------------------------
+pub const K_N_INPUTS : u8 = 6;
+pub const K_N_BITS : u8 = 64;
+pub const K_DATA_PRJXRAY : str = "./prjxray.json";
+pub const K_DATA_DESIGN : str = "./design.json";
+pub const K_DESIGN_SHA256 : str = "e413fb51f92e88cbee0cdd82d588aaaed8313d5ddbc3b5c25177c465329995f4";
+pub const K_DESIGN_LUTS : u16 = 4738;
+pub const K_DESIGN_DISTINCT : u16 = 2109;
+pub const K_DESIGN_NAMED : u16 = 2487;
+pub const K_DESIGN_OTHER : u16 = 2251;
+pub const K_DESIGN_XOR_FAMILY : u16 = 1305;
+pub const K_DESIGN_PINS_AGREE : u16 = 4738;
+pub const K_DESIGN_O5_USED : u16 = 0;
+pub const K_DESIGN_LUT_RAM_SRL : u16 = 0;
+pub const K_DESIGN_INEXACT : u16 = 1;
+pub const K_DESIGN_MAX_TERMS : u16 = 22;
+pub const K_DESIGN_TOP_KEY : str = "xor/2/0";
+pub const K_DESIGN_TOP_COUNT : u16 = 520;
+pub const K_DESIGN_ROUNDS : u16 = 197;
+pub const K_ROUNDS_PER_CLASS : u8 = 4;
+pub const K_GAME_ROUNDS : u8 = 10;
+pub const K_GAME_CHOICES : u8 = 4;
+pub const K_HIST_SHOWN : u8 = 14;
+pub const K_DEFAULT_LOC : str = "CLBLM_L_X10Y248.SLICEM_X0.C";
+
+; The four INITs of the tests, as 16-bit words, word 0 = INIT[15:0].
+pub const K_XOR6_WORDS : [4]u16 = [27030, 38505, 38505, 27030];
+pub const K_AND6_WORDS : [4]u16 = [0, 0, 0, 32768];
+pub const K_A1_WORDS : [4]u16 = [43690, 43690, 43690, 43690];
+pub const K_A6_WORDS : [4]u16 = [0, 0, 65535, 65535];
+
+; Example buttons. The class keys are what lut.js says (checked by --selftest); the two FASM
+; examples are lines of node.fasm, byte for byte (checked too).
+pub const K_EXAMPLE_INITS : [6]str = ["6996966996696996", "8000000000000000", "AAAAAAAAAAAAAAAA", "FFFFFFFF00000000", "E8E8E8E8E8E8E8E8", "CACACACACACACACA"];
+pub const K_EXAMPLE_CLASSES : [6]str = ["xor/6/0", "and/6/0", "buf/1/0", "buf/1/0", "maj/3/0", "mux2/3/0"];
+pub const K_EXAMPLE_N : [6]u8 = [6, 6, 1, 1, 3, 3];
+pub const K_EXAMPLE_FASM_CARRY : [7]str = ["CLBLM_L_X10Y248.SLICEM_X0.CLUT.INIT[63:0] = 64'b1111111111101000111010000000000011111111111010001110100000000000", "CLBLM_L_X10Y248.CLBLM_M_C1.CLBLM_IMUX32", "CLBLM_L_X10Y248.CLBLM_M_C2.CLBLM_IMUX29", "CLBLM_L_X10Y248.CLBLM_M_C3.CLBLM_IMUX22", "CLBLM_L_X10Y248.CLBLM_M_C4.CLBLM_IMUX28", "CLBLM_L_X10Y248.CLBLM_M_C5.CLBLM_IMUX31", "CLBLM_L_X10Y248.CLBLM_LOGIC_OUTS14.CLBLM_M_C"];
+pub const K_EXAMPLE_FASM_FF : [12]str = ["CLBLM_L_X10Y248.SLICEM_X0.ALUT.INIT[63:0] = 64'b1111101001010000111110100101000011111010010100001111101001010000", "CLBLM_L_X10Y248.SLICEM_X0.AOUTMUX.O6", "CLBLM_L_X10Y248.SLICEM_X0.AFFMUX.O6", "CLBLM_L_X10Y248.SLICEM_X0.AFF.ZINI", "CLBLM_L_X10Y248.SLICEM_X0.AFF.ZRST", "CLBLM_L_X10Y248.SLICEM_X0.NOCLKINV", "CLBLM_L_X10Y248.SLICEM_X0.SRUSEDMUX", "CLBLM_L_X10Y248.CLBLM_M_A1.CLBLM_IMUX7", "CLBLM_L_X10Y248.CLBLM_M_A3.CLBLM_IMUX1", "CLBLM_L_X10Y248.CLBLM_M_A4.CLBLM_IMUX11", "CLBLM_L_X10Y248.CLBLM_LOGIC_OUTS4.CLBLM_M_AQ", "CLBLM_L_X10Y248.CLBLM_M_CLK.CLBLM_CLK1"];
+
+; --- Words: the frame ------------------------------------------------------------------------
+; {name} is filled by tool.js from the data files or from the reader's paste; nothing else is.
+pub const SAY_TABS : [3]str = ["Decode", "Guess the gate", "Our design"];
+pub const SAY_LOADING : str = "Reading the data files...";
+pub const SAY_LOAD_FAILED : str = "A data file could not be read: {file}";
+pub const SAY_LIMITS : str = "The names are this page's own: a LUT with no short name may still be a familiar gate with more inputs. The design is hand-written Verilog, not generated from t27.";
+
+; --- Words: decode ---------------------------------------------------------------------------
+pub const SAY_INPUT_LABEL : str = "Paste a LUT6 INIT (64'h..., 64'b..., 0x..., or 16 hex digits) or FASM lines";
+pub const SAY_INPUT_PLACEHOLDER : str = "64'h6996966996696996";
+pub const SAY_DROP : str = "or drop a .fasm file here: it is read in this tab and sent nowhere";
+pub const SAY_EXAMPLES : str = "Examples";
+pub const SAY_EXAMPLE_NAMES : [8]str = ["XOR6", "AND6", "A1 only", "A6 only", "majority", "2:1 mux", "carry LUT, our design", "LUT and flip-flop, our design"];
+pub const SAY_NOTHING : str = "Nothing to read yet: paste an INIT or a FASM LUT line.";
+pub const SAY_FILE_LUTS : str = "{name}: {n} LUT INIT lines. This is LUT {i} of {n}, {loc}.";
+pub const SAY_PREV : str = "Previous LUT";
+pub const SAY_NEXT : str = "Next LUT";
+pub const SAY_NAME_LABEL : str = "Recognised as";
+pub const SAY_INPUTS_LABEL : str = "Inputs that matter";
+pub const SAY_DONT_CARE : str = "don't-care";
+pub const SAY_NONE : str = "none";
+pub const SAY_EXPR_LABEL : str = "Minimal sum of products: {terms} terms, from the Quine-McCluskey in this page";
+pub const SAY_EXPR_INEXACT : str = "The search ran out of its node budget: this cover is correct but may not be the smallest.";
+pub const SAY_EXPR_CHECK : str = "Checked: the expression gives the same 64 outputs as the INIT.";
+pub const SAY_OP_AND : str = "&";
+pub const SAY_OP_OR : str = " | ";
+pub const SAY_OP_NOT : str = "~";
+pub const SAY_PIN : str = "A{n}";
+pub const SAY_CONST_0 : str = "0";
+pub const SAY_CONST_1 : str = "1";
+pub const SAY_INIT_LABEL : str = "INIT";
+pub const SAY_FASM_REBUILT : str = "The FASM line for this INIT";
+pub const SAY_LOC_ASSUMED : str = "The paste names no LUT, so the line uses {loc}, a real SLICEM LUT of our design.";
+pub const SAY_TABLE_LABEL : str = "Truth table: row i is input vector i, A1 the lowest bit. Tap a row to flip its output. Faded columns are don't-care.";
+pub const SAY_TABLE_HEAD : [3]str = ["i", "A6 A5 A4 A3 A2 A1", "O"];
+pub const SAY_FLIPPED : str = "INIT[{i}] is now {v}. prjxray-db puts it at frame offset {frame}, bit {bit} of a {tt} tile, {site} {lut}LUT.";
+pub const SAY_RESET : str = "Undo my flips";
+
+; --- Words: the two LUT5 halves ---------------------------------------------------------------
+pub const SAY_HALVES_TITLE : str = "LUT5 halves: O5 and O6";
+pub const SAY_HALVES_BODY : str = "A LUT6 is two LUT5s on A1-A5 and a 2:1 mux on A6 (UG474). INIT[31:0] is the A6 = 0 half and is also the O5 output. INIT[63:32] is the A6 = 1 half. With A6 tied high and O5 routed, one LUT gives two functions of the same five inputs.";
+pub const SAY_HALF_LOW : str = "INIT[31:0], A6 = 0, the O5 output";
+pub const SAY_HALF_HIGH : str = "INIT[63:32], A6 = 1";
+pub const SAY_HALVES_SAME : str = "Both halves are the same function: A6 is don't-care and this LUT needs only five inputs.";
+pub const SAY_HALVES_DIFF : str = "The halves differ: A6 picks between them.";
+pub const SAY_HALVES_DESIGN : str = "In our design {o5} of {luts} LUTs route O5 (node.fasm has no OUTMUX.O5, FFMUX.O5, 5FFMUX.IN_A or CARRY4 CY0 feature): every LUT there is used as one LUT6.";
+
+; --- Words: FASM lines -------------------------------------------------------------------------
+pub const SAY_FASM_TITLE : str = "FASM lines, read against prjxray-db";
+pub const SAY_FASM_NONE : str = "No FASM lines in the paste.";
+pub const SAY_PART_NAMES : [5]str = ["tile", "site", "BEL", "feature", "value"];
+pub const SAY_BITS : str = "config bits, as frame offset _ bit in the tile";
+pub const SAY_BITS_INIT : str = "64 bits: INIT[00] at {first}, INIT[63] at {last}";
+pub const SAY_BIT_NEG : str = "a bit written !x must be 0";
+pub const SAY_TILE_CLBLL : str = "CLBLL: a logic tile with two SLICEL sites (LUTs, flip-flops, carry chain).";
+pub const SAY_TILE_CLBLM : str = "CLBLM: a SLICEM site, whose LUTs can also be RAM or shift registers, and a SLICEL site.";
+pub const SAY_TILE_SIDE : str = "In this db the {a} and {b} tiles have the same feature names and the same bits.";
+pub const SAY_TILE_OTHER : str = "{tt} is not a CLB tile: this page ships only the CLBLL and CLBLM part of prjxray-db.";
+pub const SAY_TILE_NO_FEATURE : str = "{tt} has no feature {feature} in prjxray-db ab1fc60.";
+pub const SAY_F_LUT_INIT : str = "{L}LUT truth table: INIT[i] is the output for input vector i, A1 the lowest bit.";
+pub const SAY_F_LUT_RAM : str = "{L}LUT is distributed RAM (a RAM32, RAM64, RAM128 or RAM256 primitive), not logic.";
+pub const SAY_F_LUT_SMALL : str = "{L}LUT holds a smaller primitive (SRL16E or a RAM32 one), per the prjxray ram fuzzer.";
+pub const SAY_F_LUT_SRL : str = "{L}LUT is a shift register (SRL16E or SRLC32E).";
+pub const SAY_F_DI1MUX : str = "{L}LUT RAM data input DI1 comes from {src}.";
+pub const SAY_F_ZINI : str = "{ff} starts at 0 after configuration (GSR). Without this bit a flip-flop starts at 1.";
+pub const SAY_F_ZRST : str = "{ff}: for FDRE and FDCE this bit makes reset store 0.";
+pub const SAY_F_5FFMUX_IN_A : str = "{ff} D input comes from the {L}LUT O5 output.";
+pub const SAY_F_5FFMUX_IN_B : str = "{ff} D input comes from the {L}X bypass pin.";
+pub const SAY_F_FFMUX : str = "{ff} D input comes from {src}.";
+pub const SAY_F_OUTMUX : str = "The {L}MUX site output carries {src}.";
+pub const SAY_F_CY0 : str = "CARRY4 stage {L} takes its DI from the {L}LUT O5. Without this bit it takes {L}X.";
+pub const SAY_F_PRECYINIT : str = "The first carry input of the CARRY4 is {src}.";
+pub const SAY_F_CEUSEDMUX : str = "Clock enable is a routed signal. Without this bit CE is always 1. Whole slice.";
+pub const SAY_F_SRUSEDMUX : str = "Set/reset is a routed signal. Without this bit the flip-flops are never reset. Whole slice.";
+pub const SAY_F_CLKINV : str = "The clock into the slice is inverted. Whole slice.";
+pub const SAY_F_NOCLKINV : str = "The clock into the slice is not inverted: NOCLKINV is the inverse of CLKINV, one bit that must be 0. Whole slice.";
+pub const SAY_F_FFSYNC : str = "Set/reset is synchronous (FDRE, FDSE). Without this bit it is asynchronous. Whole slice.";
+pub const SAY_F_LATCH : str = "The LUT6 storage elements are latches and the LUT5 ones cannot be used. Whole slice.";
+pub const SAY_F_WA7USED : str = "RAM address bit WA7 comes from the CX pin.";
+pub const SAY_F_WA8USED : str = "RAM address bit WA8 comes from the BX pin.";
+pub const SAY_F_WEMUX_CE : str = "RAM write enable comes from the CE pin instead of WE.";
+pub const SAY_F_UNKNOWN : str = "A {tt} feature this page has no words for.";
+pub const SAY_SRC_KEYS : [13]str = ["O6", "O5", "XOR", "CY", "F7", "F8", "NX", "N5Q", "MC31", "C0", "C1", "CIN", "NI"];
+pub const SAY_SRC_WORDS : [13]str = ["the LUT6 output O6", "the LUT5 output O5", "the carry chain sum (CARRY4 XOR)", "the carry chain carry (CARRY4 CO)", "the F7 wide mux", "the F8 wide mux", "the {L}X bypass pin", "the {L}5FF output", "the shift-register cascade output MC31", "constant 0", "constant 1", "the carry in from the CLB below", "the {L}I data pin"];
+pub const SAY_PIP_LUTPIN : str = "Input A{n} of the {site} {L}LUT comes from {src}.";
+pub const SAY_PIP_ALWAYS : str = "Always connected inside the tile (prjxray-db ppip 'always'): no config bits.";
+pub const SAY_PIP_HINT : str = "A path through the LUT from an input to its output (prjxray-db ppip 'hint'): no config bits.";
+pub const SAY_PIP_DEFAULT : str = "Connected when nothing else drives this wire (prjxray-db ppip 'default').";
+pub const SAY_PIP_CLB : str = "Inside the CLB tile: wire {dst} from {src}.";
+pub const SAY_PIP_OTHER : str = "Routing pip: wire {dst} driven from {src}. Its bits are in the segbits of {tt}, which this page does not ship.";
+pub const SAY_BAD_LINE : str = "Not read: {line}";
+
+; --- Words: function names ---------------------------------------------------------------------
+; {n} inputs matter, {k} of them inverted (or, for the XOR-with forms, {k} inputs enter only through
+; the XOR), {inner} is the name of the rest. Keys in design.json are id/n/k.
+pub const SAY_CLASS_ZERO : str = "constant 0";
+pub const SAY_CLASS_ONE : str = "constant 1";
+pub const SAY_CLASS_BUF : str = "buffer: copies one input";
+pub const SAY_CLASS_NOT : str = "inverter: NOT of one input";
+pub const SAY_CLASS_AND : str = "AND{n}";
+pub const SAY_CLASS_NAND : str = "NAND{n}";
+pub const SAY_CLASS_OR : str = "OR{n}";
+pub const SAY_CLASS_NOR : str = "NOR{n}";
+pub const SAY_CLASS_ANDX : str = "AND{n} with {k} inverted";
+pub const SAY_CLASS_ORX : str = "OR{n} with {k} inverted";
+pub const SAY_CLASS_XOR : str = "XOR{n}: parity of {n} inputs";
+pub const SAY_CLASS_XNOR : str = "XNOR{n}: inverted parity of {n}";
+pub const SAY_CLASS_MAJ : str = "majority of 3";
+pub const SAY_CLASS_MAJX : str = "majority of 3, {k} inverted";
+pub const SAY_CLASS_CARRY2 : str = "2-bit carry out: MAJ(a, b, MAJ(c, d, e))";
+pub const SAY_CLASS_CARRY2X : str = "2-bit carry out, {k} inverted";
+pub const SAY_CLASS_MUX2 : str = "2:1 mux";
+pub const SAY_CLASS_MUX4 : str = "4:1 mux";
+pub const SAY_CLASS_XORWITH : str = "XOR of {k} inputs with ({inner})";
+pub const SAY_CLASS_XNORWITH : str = "XNOR of {k} inputs with ({inner})";
+pub const SAY_CLASS_OTHER : str = "no short name, {n} inputs";
+pub const SAY_DETAIL_BUF : str = "The output is {pin}.";
+pub const SAY_DETAIL_NOT : str = "The output is NOT {pin}.";
+pub const SAY_DETAIL_MUX2 : str = "Select {s}: {d0} when {s} = 0, {d1} when {s} = 1.";
+pub const SAY_DETAIL_MUX4 : str = "Selects {s1} {s0}: data {d} for select values 0, 1, 2, 3.";
+pub const SAY_DETAIL_CARRY2 : str = "MAJ({a}, {b}, MAJ({c}, {d}, {e})): the carry out of a two-bit add.";
+pub const SAY_DETAIL_LINEAR : str = "{list} enter only through the XOR.";
+
+; --- Words: the game ---------------------------------------------------------------------------
+pub const SAY_GAME_INTRO : str = "A real LUT from our routed design. Which function is it? The answer comes from this page's decoder.";
+pub const SAY_GAME_LOC : str = "{loc} in node.fasm";
+pub const SAY_GAME_SCORE : str = "Round {round} of {rounds}. Score {right}.";
+pub const SAY_GAME_RIGHT : str = "Right.";
+pub const SAY_GAME_WRONG : str = "No: it is {answer}.";
+pub const SAY_GAME_NEXT : str = "Next LUT";
+pub const SAY_GAME_OPEN : str = "Open it in the decoder";
+pub const SAY_GAME_DONE : str = "{right} of {rounds} right.";
+pub const SAY_GAME_AGAIN : str = "Play again";
+pub const SAY_GAME_SAMPLE : str = "Rounds come from {pool} INITs: at most {per} distinct ones per named function, so rare functions turn up as often as XOR2. The {other} LUTs with no short name are left out.";
+
+; --- Words: our design -------------------------------------------------------------------------
+pub const SAY_HIST_TITLE : str = "What the LUTs of one routed design are";
+pub const SAY_HIST_INTRO : str = "{luts} LUT6 INIT lines in node.fasm ({distinct} distinct INITs), the TRI-NET node v2 for the AX7203 board ({part}). {named} have a short name in this decoder, {other} do not.";
+pub const SAY_HIST_REST : str = "{kinds} more named kinds";
+pub const SAY_HIST_OURS : str = "our design";
+pub const SAY_HIST_YOURS : str = "your file";
+pub const SAY_FACT_PINS : str = "{agree} of {luts}: for every LUT the inputs its INIT depends on are exactly the A1-A6 pins the router connected. That is the A1 = bit 0 convention, checked on real data.";
+pub const SAY_FACT_O5 : str = "{o5} LUTs route O5 and {ram} are RAM or shift registers: every LUT here is plain logic used as one LUT6.";
+pub const SAY_FACT_QM : str = "Quine-McCluskey finds the proven minimum for all but {inexact} of them. The longest cover has {terms} terms.";
+pub const SAY_FACT_TOOLS : str = "Built with {yosys}: {ycmd}. Placed and routed with {nextpnr}. Read with prjxray {prjxray} and {db}.";
+pub const SAY_YOUR_FILE : str = "Your file {name}: {luts} LUTs, {named} with a short name. Read in this tab only.";
+
+; --- Tests -------------------------------------------------------------------------------------
+; INIT[i] is the output for input vector i, I0 = A1 the least significant bit. If the output is A1,
+; every odd row is 1 and every even row 0 (0xAAAA in each word). If the output is A6, rows 0..31
+; are 0 and rows 32..63 are 1: INIT[31] is bit 15 of word 1, INIT[32] bit 0 of word 2.
+test bit_index_convention_i0_is_the_lsb {
+ assert K_A1_WORDS[0] % 2 == 0;
+ assert 1 == (K_A1_WORDS[0] / 2) % 2;
+ assert 1 == (K_A1_WORDS[3] / 32768) % 2;
+ assert K_A1_WORDS[0] == 43690;
+ assert K_A1_WORDS[1] == K_A1_WORDS[0];
+ assert K_A1_WORDS[3] == K_A1_WORDS[0];
+ assert 0 == (K_A6_WORDS[1] / 32768) % 2;
+ assert K_A6_WORDS[2] % 2 == 1;
+ assert K_A6_WORDS[0] + K_A6_WORDS[1] == 0;
+ assert K_A6_WORDS[2] == 65535;
+ assert K_A6_WORDS[3] == 65535;
+ assert 31 / 16 == 1;
+ assert 32 / 16 == 2;
+}
+
+; 64'h6996966996696996 is XOR6. Nibble 0 of word 0 is rows 0..3 (A1, A2 vary): 6 = 0110, the parity
+; of A1 and A2. A3 = 1 (nibble 1) and A4 = 1 (nibble 2) invert it, both together (nibble 3) do not.
+; A5 = 1 (word 1) and A6 = 1 (word 2) invert the whole word, both together (word 3) do not.
+test init_6996966996696996_is_xor6_the_parity_of_six_inputs {
+ assert K_XOR6_WORDS[0] % 16 == 6;
+ assert 6 % 2 == 0;
+ assert 1 == (6 / 2) % 2;
+ assert 1 == (6 / 4) % 2;
+ assert 0 == (6 / 8) % 2;
+ assert 15 - 6 == (K_XOR6_WORDS[0] / 16) % 16;
+ assert 15 - 6 == (K_XOR6_WORDS[0] / 256) % 16;
+ assert K_XOR6_WORDS[0] / 4096 == 6;
+ assert K_XOR6_WORDS[1] == 65535 - K_XOR6_WORDS[0];
+ assert K_XOR6_WORDS[2] == 65535 - K_XOR6_WORDS[0];
+ assert K_XOR6_WORDS[3] == K_XOR6_WORDS[0];
+ assert K_EXAMPLE_INITS[0] == "6996966996696996";
+ assert K_EXAMPLE_CLASSES[0] == "xor/6/0";
+ assert K_EXAMPLE_N[0] == K_N_INPUTS;
+}
+
+; 64'h8000000000000000 is AND6: one 1, at bit 15 of word 3, which is INIT[63], the row where all
+; six inputs are 1.
+test init_8000000000000000_is_and6 {
+ assert K_AND6_WORDS[0] + K_AND6_WORDS[1] + K_AND6_WORDS[2] == 0;
+ assert K_AND6_WORDS[3] == 32768;
+ assert 3 * 16 + 15 == K_N_BITS - 1;
+ assert K_N_BITS - 1 == 32 + 16 + 8 + 4 + 2 + 1;
+ assert K_EXAMPLE_INITS[1] == "8000000000000000";
+ assert K_EXAMPLE_CLASSES[1] == "and/6/0";
+ assert K_EXAMPLE_N[1] == 6;
+}
+
+test the_design_numbers_add_up {
+ assert K_DESIGN_NAMED + K_DESIGN_OTHER == K_DESIGN_LUTS;
+ assert K_DESIGN_PINS_AGREE == K_DESIGN_LUTS;
+ assert K_DESIGN_XOR_FAMILY <= K_DESIGN_NAMED;
+ assert K_DESIGN_TOP_COUNT <= K_DESIGN_XOR_FAMILY;
+ assert K_DESIGN_DISTINCT <= K_DESIGN_LUTS;
+ assert K_DESIGN_O5_USED == 0;
+ assert K_DESIGN_ROUNDS >= K_GAME_ROUNDS;
+ assert K_GAME_CHOICES == 4;
+}
+
+test the_page_sends_nothing {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == true;
+ assert K_DATA_DESIGN == "./design.json";
+ assert K_DATA_PRJXRAY == "./prjxray.json";
+ assert CATEGORY == "fpga";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/lut-explain/tool.js (588 lines of hand-written JavaScript)
+; and, as its decoder, lut.js. The words and numbers above are unchanged: the view prints SAY_
+; constants and K_ numbers, it writes no English of its own.
+; --
+; THE DECODER IS FOLDED IN, NOT IMPORTED. specs/widgets/lut_explain_logic.t27 is the differential-
+; tested port of lut.js. Its functions keep their tables in module-level `pub var`s and t27c's `use`
+; does not carry a `pub var` across modules (measured on 0.5.2), so they cannot be imported. The
+; block between the two FOLDED markers is that module's text, from its first constant to class_key(),
+; byte for byte; the build diffs the region against the logic module. Change the decoder there,
+; then regenerate this region.
+; --
+; Host protocol (host.js; all exports of this module):
+; w_init(hash) the page opens
+; w_data(i, bytes) file i of W_DATA_FILES (0 prjxray.json, 1 design.json) was fetched; w_data_failed(i) when not
+; w_press(i) button number i in page order (the page maps it to its action; buttons carry no data-i)
+; w_input(i, bytes) control i (among input and textarea, in page order) changed: 0 is the paste box
+; w_file(z, name, size) -> 1 | 0, w_file_data(z, bytes), w_file_failed(z) a picked or dropped .fasm file
+; w_drag(z, over) -> 1 | 0 the drag entered (1) or left/dropped (0) zone z
+; w_seed(n) the seed of the game's random numbers (xorshift32)
+; w_effect(k, dst) -> len effect k after the last event: 'v', control index, text = set that control's value
+; w_render(dst) the page's HTML, length (0 on overflow)
+; The state is what tool.js kept: tab, the paste text and what was read from it, the flipped bits, the
+; file, the game, and the design box. The design box is a cached HTML region because tool.js draws it
+; only when its tab opens (or a file is read on that tab); everything else is derived on every render.
+; Not carried over from JavaScript: the paste text is cut at W_IN_CAP (524,288 bytes) and a text with more
+; than 16,384 non-empty lines reads as nothing; a dropped file over W_FILE_CAP (8 MiB) is ignored, and
+; one with more than 8,192 LUTs or 24 lines for one LUT is cut; a data file over 512 KiB, or that is
+; not JSON, counts as not read (the message names the file, JavaScript names the parse error); numbers
+; in the data files are printed as integers; the game's random numbers are an xorshift32 seeded by the
+; host (Math.random in the original); a width of 100*n/max is rounded by exact integer arithmetic on
+; the double (as toFixed(1) does).
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::dec_to;
+use widgets::html_out::fill_named;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::json_scan::num_f64;
+
+pub const W_DATA_FILES : [2]str = ["./prjxray.json", "./design.json"];
+pub const W_IN_CAP : u32 = 524288;
+pub const W_DATA_CAP : u32 = 524288;
+pub const W_FILE_CAP : u32 = 8388608;
+pub const W_MAX_LUTS : usize = 8192;
+pub const W_LUT_LINES : usize = 24;
+pub const W_MAX_PINS : usize = 131072;
+pub const W_HIST_MAX : usize = 256;
+pub const W_NAN_V : i64 = -9000000000000000000;
+
+; FOLDED-BEGIN lut_explain_logic.t27 73-1708
+pub const W_N_IN : u32 = 6;
+pub const W_N_BITS : u32 = 64;
+
+pub const W_FK_OTHER : u32 = 0;
+pub const W_FK_BAD_VALUE : u32 = 1;
+pub const W_FK_SITE : u32 = 2;
+pub const W_FK_LUT_INIT : u32 = 3;
+pub const W_FK_PIP : u32 = 4;
+pub const W_FK_BAD_RANGE : u32 = 5;
+
+pub const W_C_ZERO : u32 = 0;
+pub const W_C_ONE : u32 = 1;
+pub const W_C_BUF : u32 = 2;
+pub const W_C_NOT : u32 = 3;
+pub const W_C_AND : u32 = 4;
+pub const W_C_NAND : u32 = 5;
+pub const W_C_OR : u32 = 6;
+pub const W_C_NOR : u32 = 7;
+pub const W_C_ANDX : u32 = 8;
+pub const W_C_ORX : u32 = 9;
+pub const W_C_XOR : u32 = 10;
+pub const W_C_XNOR : u32 = 11;
+pub const W_C_MAJ : u32 = 12;
+pub const W_C_MAJX : u32 = 13;
+pub const W_C_CARRY2 : u32 = 14;
+pub const W_C_CARRY2X : u32 = 15;
+pub const W_C_MUX2 : u32 = 16;
+pub const W_C_MUX4 : u32 = 17;
+pub const W_C_XORWITH : u32 = 18;
+pub const W_C_XNORWITH : u32 = 19;
+pub const W_C_OTHER : u32 = 20;
+
+pub var OUT : [512]u8 = [0; 512];
+pub var OUT_N : usize = 0;
+
+pub var LB : [65536]u8 = [0; 65536];
+pub var LIT_VAL : u64 = 0;
+pub var LIT_W : u32 = 0;
+pub var LV_OVER : bool = false;
+
+pub var FL_KIND : u32 = 0;
+pub var FL_TEXT_LO : usize = 0;
+pub var FL_TEXT_HI : usize = 0;
+pub var FL_TILE_LO : usize = 0;
+pub var FL_TILE_HI : usize = 0;
+pub var FL_TYPE_HI : usize = 0;
+pub var FL_PATH_LO : usize = 0;
+pub var FL_PATH_HI : usize = 0;
+pub var FL_HAS_HI : bool = false;
+pub var FL_HI : f64 = 0.0;
+pub var FL_HAS_LO : bool = false;
+pub var FL_LO : f64 = 0.0;
+pub var FL_HAS_VTEXT : bool = false;
+pub var FL_VTEXT_LO : usize = 0;
+pub var FL_VTEXT_HI : usize = 0;
+pub var FL_HAS_LIT : bool = false;
+pub var FL_LIT : u64 = 0;
+pub var FL_LIT_W : u32 = 0;
+pub var FL_HAS_SITE : bool = false;
+pub var FL_SITE_LO : usize = 0;
+pub var FL_SITE_HI : usize = 0;
+pub var FL_FEAT_LO : usize = 0;
+pub var FL_FEAT_HI : usize = 0;
+pub var FL_LUT : u8 = 0;
+pub var FL_NPARTS : usize = 0;
+pub var FL_PART_LO : [1024]usize = [0; 1024];
+pub var FL_PART_HI : [1024]usize = [0; 1024];
+
+pub var PI_HAS_BITS : bool = false;
+pub var PI_INIT_KIND : u32 = 0;
+pub var PI_LIT_INIT : u64 = 0;
+pub var PI_LBITS : u64 = 0;
+pub var PI_LUT_SET : bool = false;
+pub var PI_TILE_LO : usize = 0;
+pub var PI_TILE_HI : usize = 0;
+pub var PI_TYPE_HI : usize = 0;
+pub var PI_SITE_LO : usize = 0;
+pub var PI_SITE_HI : usize = 0;
+pub var PI_LUT : u8 = 0;
+pub var PI_N : usize = 0;
+pub var PI_LO : [16384]usize = [0; 16384];
+pub var PI_HI : [16384]usize = [0; 16384];
+pub var PI_KIND : [16384]u32 = [0; 16384];
+pub var PI_BAD_N : usize = 0;
+pub var PI_BAD_LO : [16384]usize = [0; 16384];
+pub var PI_BAD_HI : [16384]usize = [0; 16384];
+
+pub var CL_ID : [4]u32 = [0; 4];
+pub var CL_N : [4]u32 = [0; 4];
+pub var CL_K : [4]u32 = [0; 4];
+pub var CL_INPUTS : [4]u32 = [0; 4];
+pub var CL_SEL_N : [4]u32 = [0; 4];
+pub var CL_SEL : [8]u32 = [0; 8];
+pub var CL_DATA_N : [4]u32 = [0; 4];
+pub var CL_DATA : [24]u32 = [0; 24];
+pub var CL_INNER : bool = false;
+
+pub var MN_PV : [1024]u32 = [0; 1024];
+pub var MN_PD : [1024]u32 = [0; 1024];
+pub var MN_PN : usize = 0;
+pub var MN_COV : [1024]u64 = [0; 1024];
+pub var MN_LIT : [1024]u32 = [0; 1024];
+pub var LV_V : [1024]u32 = [0; 1024];
+pub var LV_D : [1024]u32 = [0; 1024];
+pub var LV_USED : [1024]u8 = [0; 1024];
+pub var NX_V : [1024]u32 = [0; 1024];
+pub var NX_D : [1024]u32 = [0; 1024];
+pub var SV_BEST : [64]u32 = [0; 64];
+pub var SV_BEST_N : usize = 0;
+pub var SV_HAS_BEST : bool = false;
+pub var SV_BEST_LITS : u32 = 0;
+pub var SV_NODES : u32 = 0;
+pub var SV_BUDGET : u32 = 0;
+pub var SV_EXACT : bool = true;
+pub var SV_CHOSEN : [64]u32 = [0; 64];
+pub var SV_CHOSEN_N : usize = 0;
+pub var OPT_STACK : [65536]u32 = [0; 65536];
+pub var OPT_TOP : usize = 0;
+pub var MN_EXACT : bool = true;
+pub var MN_PRIMES : usize = 0;
+pub var MN_V : [1024]u32 = [0; 1024];
+pub var MN_D : [1024]u32 = [0; 1024];
+pub var MN_N : usize = 0;
+pub var TL_N : usize = 0;
+pub var TL_INPUT : [6]u32 = [0; 6];
+pub var TL_INV : [6]u32 = [0; 6];
+
+// --- Constants and bit helpers -----------------------------------------------------------------
+pub fn n_in() -> u32 { return W_N_IN; }
+pub fn n_bits() -> u32 { return W_N_BITS; }
+pub fn out_byte(i: usize) -> u8 { return OUT[i]; }
+pub fn out_n() -> usize { return OUT_N; }
+
+pub fn bit_of(v: u64, i: u32) -> u32 {
+ return ((v >> i) & 1) as u32;
+}
+
+pub fn pop64(x0: u64) -> u32 {
+ var x : u64 = x0;
+ var n : u32 = 0;
+ while (x != 0) {
+ x = x & (x - 1);
+ n = n + 1;
+ }
+ return n;
+}
+
+pub fn is_digit(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+pub fn is_hex(b: u8) -> bool {
+ return (b >= 48 and b <= 57) or (b >= 97 and b <= 102) or (b >= 65 and b <= 70);
+}
+
+pub fn hexv(b: u8) -> u32 {
+ if (b >= 48 and b <= 57) { return (b as u32) - 48; }
+ if (b >= 97 and b <= 102) { return (b as u32) - 87; }
+ return (b as u32) - 55;
+}
+
+pub fn is_class(b: u8) -> bool {
+ return (b >= 48 and b <= 57) or (b >= 97 and b <= 122) or (b >= 65 and b <= 90) or b == 95;
+}
+
+pub fn out_reset() { OUT_N = 0; }
+
+pub fn out_push(b: u8) {
+ if (OUT_N < 512) {
+ OUT[OUT_N] = b;
+ OUT_N = OUT_N + 1;
+ }
+}
+
+pub fn out_str(w: []const u8) {
+ var k : usize = 0;
+ while (k < w.len) {
+ out_push(w[k]);
+ k = k + 1;
+ }
+}
+
+/// bigFromBits: bits[i] & 1 over i = 63 down to 0 (a missing entry reads as 0).
+pub fn big_from_bits(b: []const u8) -> u64 {
+ var v : u64 = 0;
+ var i : u32 = 0;
+ while (i < 64) {
+ if ((i as usize) < b.len) {
+ v = v | (((b[(i as usize)] & 1) as u64) << i);
+ }
+ i = i + 1;
+ }
+ return v;
+}
+
+/// initHex: 16 upper-case hex digits in OUT.
+pub fn init_hex(bits: u64) -> usize {
+ out_reset();
+ var k : u32 = 0;
+ while (k < 16) {
+ const d : u32 = ((bits >> ((15 - k) * 4)) & 15) as u32;
+ if (d < 10) { out_push((d + 48) as u8); } else { out_push((d + 55) as u8); }
+ k = k + 1;
+ }
+ return OUT_N;
+}
+
+/// initBin: 64 characters in OUT, INIT[63] first.
+pub fn init_bin(bits: u64) -> usize {
+ out_reset();
+ var k : u32 = 0;
+ while (k < 64) {
+ out_push((bit_of(bits, 63 - k) + 48) as u8);
+ k = k + 1;
+ }
+ return OUT_N;
+}
+
+// --- JavaScript whitespace (trim and \s) -------------------------------------------------------
+pub fn ws_fwd(b0: u8, b1: u8, b2: u8, avail: usize) -> usize {
+ if (avail == 0) { return 0; }
+ if (b0 == 32 or (b0 >= 9 and b0 <= 13)) { return 1; }
+ if (b0 == 194) {
+ if (avail >= 2 and b1 == 160) { return 2; }
+ return 0;
+ }
+ if (avail < 3) { return 0; }
+ if (b0 == 225) {
+ if (b1 == 154 and b2 == 128) { return 3; }
+ return 0;
+ }
+ if (b0 == 226) {
+ if (b1 == 128) {
+ if (b2 >= 128 and b2 <= 138) { return 3; }
+ if (b2 == 168 or b2 == 169 or b2 == 175) { return 3; }
+ }
+ if (b1 == 129 and b2 == 159) { return 3; }
+ return 0;
+ }
+ if (b0 == 227) {
+ if (b1 == 128 and b2 == 128) { return 3; }
+ return 0;
+ }
+ if (b0 == 239) {
+ if (b1 == 187 and b2 == 191) { return 3; }
+ return 0;
+ }
+ return 0;
+}
+
+pub fn ws_back(a: u8, m: u8, c: u8, avail: usize) -> usize {
+ if (avail == 0) { return 0; }
+ if (c == 32 or (c >= 9 and c <= 13)) { return 1; }
+ if (avail >= 2 and m == 194 and c == 160) { return 2; }
+ if (avail < 3) { return 0; }
+ if (a == 225 and m == 154 and c == 128) { return 3; }
+ if (a == 226 and m == 128) {
+ if (c >= 128 and c <= 138) { return 3; }
+ if (c == 168 or c == 169 or c == 175) { return 3; }
+ }
+ if (a == 226 and m == 129 and c == 159) { return 3; }
+ if (a == 227 and m == 128 and c == 128) { return 3; }
+ if (a == 239 and m == 187 and c == 191) { return 3; }
+ return 0;
+}
+
+pub fn ws_at(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi) { return 0; }
+ var b1 : u8 = 0;
+ var b2 : u8 = 0;
+ var avail : usize = 1;
+ if (p + 1 < hi) { b1 = s[p + 1]; avail = 2; }
+ if (p + 2 < hi) { b2 = s[p + 2]; avail = 3; }
+ return ws_fwd(s[p], b1, b2, avail);
+}
+
+pub fn ws_before(s: []const u8, lo: usize, e: usize) -> usize {
+ if (e <= lo) { return 0; }
+ var a : u8 = 0;
+ var m : u8 = 0;
+ var avail : usize = 1;
+ if (e >= lo + 2) { m = s[e - 2]; avail = 2; }
+ if (e >= lo + 3) { a = s[e - 3]; avail = 3; }
+ return ws_back(a, m, s[e - 1], avail);
+}
+
+/// s.trim() as [lo, hi) packed lo * 4294967296 + hi.
+pub fn trim_pack(s: []const u8, lo0: usize, hi0: usize) -> u64 {
+ var lo : usize = lo0;
+ var hi : usize = hi0;
+ while (lo < hi) {
+ const w : usize = ws_at(s, lo, hi);
+ if (w == 0) { break; }
+ lo = lo + w;
+ }
+ while (hi > lo) {
+ const w2 : usize = ws_before(s, lo, hi);
+ if (w2 == 0) { break; }
+ hi = hi - w2;
+ }
+ return (lo as u64) * 4294967296 + (hi as u64);
+}
+
+pub fn pk_lo(p: u64) -> usize { return (p / 4294967296) as usize; }
+pub fn pk_hi(p: u64) -> usize { return (p % 4294967296) as usize; }
+
+// --- parseLiteral ------------------------------------------------------------------------------
+pub fn lit_value() -> u64 { return LIT_VAL; }
+pub fn lit_width() -> u32 { return LIT_W; }
+
+/// Value of digits LB[a .. b) in base 2, 10 or 16; LV_OVER is set when it does not fit a u64.
+pub fn lb_value(a: usize, b: usize, base: u64) -> u64 {
+ var v : u64 = 0;
+ LV_OVER = false;
+ var i : usize = a;
+ while (i < b) {
+ const d : u64 = hexv(LB[i]) as u64;
+ if (v > (18446744073709551615 - d) / base) {
+ LV_OVER = true;
+ return 0;
+ }
+ v = v * base + d;
+ i = i + 1;
+ }
+ return v;
+}
+
+/// The matching on LB[0 .. n): the four forms of lut.js.
+pub fn lit_match(n: usize) -> bool {
+ LIT_VAL = 0;
+ LIT_W = 0;
+ var p : usize = 0;
+ while (p < n and is_digit(LB[p])) { p = p + 1; }
+ if (p > 0 and p + 2 < n and LB[p] == 39) {
+ const bl : u8 = LB[p + 1];
+ if (bl == 104 or bl == 72 or bl == 98 or bl == 66 or bl == 100 or bl == 68) {
+ var all_hex : bool = true;
+ var q : usize = p + 2;
+ while (q < n) {
+ if (!is_hex(LB[q])) { all_hex = false; }
+ q = q + 1;
+ }
+ if (all_hex) {
+ // the Verilog form: it answers for itself, a failure does not fall through
+ var width : f64 = 0.0;
+ var i : usize = 0;
+ while (i < p) {
+ width = width * 10.0 + (((LB[i] - 48) as u32) as f64);
+ i = i + 1;
+ }
+ if (width < 1.0 or width > 64.0) { return false; }
+ var base : u64 = 16;
+ if (bl == 98 or bl == 66) {
+ base = 2;
+ var j : usize = p + 2;
+ while (j < n) {
+ if (LB[j] != 48 and LB[j] != 49) { return false; }
+ j = j + 1;
+ }
+ }
+ if (bl == 100 or bl == 68) {
+ base = 10;
+ var j2 : usize = p + 2;
+ while (j2 < n) {
+ if (!is_digit(LB[j2])) { return false; }
+ j2 = j2 + 1;
+ }
+ }
+ const v : u64 = lb_value(p + 2, n, base);
+ if (LV_OVER) { return false; }
+ const w : u32 = width as u32;
+ if (w < 64) {
+ if ((v >> w) != 0) { return false; }
+ }
+ LIT_VAL = v;
+ LIT_W = w;
+ return true;
+ }
+ }
+ }
+ // 0x + 1..16 hex digits
+ if (n >= 3 and n <= 18 and LB[0] == 48 and (LB[1] == 120 or LB[1] == 88)) {
+ var ok : bool = true;
+ var k : usize = 2;
+ while (k < n) {
+ if (!is_hex(LB[k])) { ok = false; }
+ k = k + 1;
+ }
+ if (ok) {
+ LIT_VAL = lb_value(2, n, 16);
+ LIT_W = 64;
+ return true;
+ }
+ }
+ // 64 binary digits
+ if (n == 64) {
+ var okb : bool = true;
+ var m : usize = 0;
+ while (m < n) {
+ if (LB[m] != 48 and LB[m] != 49) { okb = false; }
+ m = m + 1;
+ }
+ if (okb) {
+ LIT_VAL = lb_value(0, n, 2);
+ LIT_W = 64;
+ return true;
+ }
+ }
+ // bare hex, 1..16 digits
+ if (n >= 1 and n <= 16) {
+ var okh : bool = true;
+ var r : usize = 0;
+ while (r < n) {
+ if (!is_hex(LB[r])) { okh = false; }
+ r = r + 1;
+ }
+ if (okh) {
+ LIT_VAL = lb_value(0, n, 16);
+ LIT_W = 64;
+ return true;
+ }
+ }
+ return false;
+}
+
+/// parseLiteral(text): String(text).trim() with underscores removed, then the four forms.
+pub fn parse_literal(text: []const u8) -> bool {
+ const t : u64 = trim_pack(text, 0, text.len);
+ var n : usize = 0;
+ var i : usize = pk_lo(t);
+ const e : usize = pk_hi(t);
+ while (i < e) {
+ if (text[i] != 95) {
+ if (n >= 65536) { return false; }
+ LB[n] = text[i];
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return lit_match(n);
+}
+
+// --- parseFasmLine -----------------------------------------------------------------------------
+pub fn fl_kind() -> u32 { return FL_KIND; }
+pub fn fl_text_lo() -> usize { return FL_TEXT_LO; }
+pub fn fl_text_hi() -> usize { return FL_TEXT_HI; }
+pub fn fl_tile_lo() -> usize { return FL_TILE_LO; }
+pub fn fl_tile_hi() -> usize { return FL_TILE_HI; }
+pub fn fl_type_hi() -> usize { return FL_TYPE_HI; }
+pub fn fl_path_lo() -> usize { return FL_PATH_LO; }
+pub fn fl_path_hi() -> usize { return FL_PATH_HI; }
+pub fn fl_has_hi() -> bool { return FL_HAS_HI; }
+pub fn fl_hi() -> f64 { return FL_HI; }
+pub fn fl_has_lo() -> bool { return FL_HAS_LO; }
+pub fn fl_lo() -> f64 { return FL_LO; }
+pub fn fl_has_vtext() -> bool { return FL_HAS_VTEXT; }
+pub fn fl_vtext_lo() -> usize { return FL_VTEXT_LO; }
+pub fn fl_vtext_hi() -> usize { return FL_VTEXT_HI; }
+pub fn fl_has_lit() -> bool { return FL_HAS_LIT; }
+pub fn fl_lit_value() -> u64 { return FL_LIT; }
+pub fn fl_lit_width() -> u32 { return FL_LIT_W; }
+pub fn fl_has_site() -> bool { return FL_HAS_SITE; }
+pub fn fl_site_lo() -> usize { return FL_SITE_LO; }
+pub fn fl_site_hi() -> usize { return FL_SITE_HI; }
+pub fn fl_feat_lo() -> usize { return FL_FEAT_LO; }
+pub fn fl_feat_hi() -> usize { return FL_FEAT_HI; }
+pub fn fl_lut() -> u8 { return FL_LUT; }
+pub fn fl_nparts() -> usize { return FL_NPARTS; }
+
+pub fn fl_part_lo(i: usize) -> usize {
+ if (i >= 1024) { return 0; }
+ return FL_PART_LO[i];
+}
+
+pub fn fl_part_hi(i: usize) -> usize {
+ if (i >= 1024) { return 0; }
+ return FL_PART_HI[i];
+}
+
+/// The decimal digits s[a .. b) as a number (exact up to 2^53).
+pub fn dec_f64(s: []const u8, a: usize, b: usize) -> f64 {
+ var v : f64 = 0.0;
+ var i : usize = a;
+ while (i < b) {
+ v = v * 10.0 + (((s[i] - 48) as u32) as f64);
+ i = i + 1;
+ }
+ return v;
+}
+
+/// /_X\d+Y\d+$/ at the end of s[lo .. hi): the offset where the suffix starts, or hi.
+pub fn type_end(s: []const u8, lo: usize, hi: usize) -> usize {
+ var p : usize = lo;
+ while (p < hi) {
+ if (s[p] == 95 and p + 1 < hi and s[p + 1] == 88) {
+ var q : usize = p + 2;
+ const d0 : usize = q;
+ while (q < hi and is_digit(s[q])) { q = q + 1; }
+ if (q > d0 and q < hi and s[q] == 89) {
+ q = q + 1;
+ const d1 : usize = q;
+ while (q < hi and is_digit(s[q])) { q = q + 1; }
+ if (q > d1 and q == hi) { return p; }
+ }
+ }
+ p = p + 1;
+ }
+ return hi;
+}
+
+/// /^SLICE[LM]_X[01]$/ on s[lo .. hi).
+pub fn is_slice_site(s: []const u8, lo: usize, hi: usize) -> bool {
+ if (hi - lo != 9) { return false; }
+ if (!(s[lo] == 83 and s[lo + 1] == 76 and s[lo + 2] == 73 and s[lo + 3] == 67 and s[lo + 4] == 69)) { return false; }
+ if (s[lo + 5] != 76 and s[lo + 5] != 77) { return false; }
+ if (s[lo + 6] != 95 or s[lo + 7] != 88) { return false; }
+ return s[lo + 8] == 48 or s[lo + 8] == 49;
+}
+
+pub fn fl_clear() {
+ FL_KIND = 0;
+ FL_HAS_HI = false;
+ FL_HI = 0.0;
+ FL_HAS_LO = false;
+ FL_LO = 0.0;
+ FL_HAS_VTEXT = false;
+ FL_VTEXT_LO = 0;
+ FL_VTEXT_HI = 0;
+ FL_HAS_LIT = false;
+ FL_LIT = 0;
+ FL_LIT_W = 0;
+ FL_HAS_SITE = false;
+ FL_SITE_LO = 0;
+ FL_SITE_HI = 0;
+ FL_FEAT_LO = 0;
+ FL_FEAT_HI = 0;
+ FL_LUT = 0;
+ FL_NPARTS = 0;
+}
+
+/// parseFasmLine(line): false when the JavaScript returns null.
+pub fn parse_fasm_line(s: []const u8) -> bool {
+ fl_clear();
+ const n : usize = s.len;
+ // line terminators: the comment regex #.*$ needs none after its '#'
+ var lt_end : usize = 0;
+ var i : usize = 0;
+ while (i < n) {
+ const b : u8 = s[i];
+ if (b == 10 or b == 13) { lt_end = i + 1; }
+ if (b == 226 and i + 2 < n and s[i + 1] == 128 and (s[i + 2] == 168 or s[i + 2] == 169)) { lt_end = i + 3; }
+ i = i + 1;
+ }
+ var cut : usize = n;
+ var c : usize = lt_end;
+ while (c < n) {
+ if (s[c] == 35) {
+ cut = c;
+ break;
+ }
+ c = c + 1;
+ }
+ const tp : u64 = trim_pack(s, 0, cut);
+ const lo : usize = pk_lo(tp);
+ const hi : usize = pk_hi(tp);
+ if (hi == lo) { return false; }
+ FL_TEXT_LO = lo;
+ FL_TEXT_HI = hi;
+ var p : usize = lo;
+ while (p < hi and is_class(s[p])) { p = p + 1; }
+ if (p == lo or p >= hi or s[p] != 46) { return false; }
+ FL_TILE_LO = lo;
+ FL_TILE_HI = p;
+ FL_TYPE_HI = type_end(s, lo, p);
+ const ps : usize = p + 1;
+ var q : usize = ps;
+ while (q < hi and (is_class(s[q]) or s[q] == 46)) { q = q + 1; }
+ if (q == ps) { return false; }
+ FL_PATH_LO = ps;
+ FL_PATH_HI = q;
+ var r : usize = q;
+ var has_hi : bool = false;
+ var has_lo_s : bool = false;
+ var hi_a : usize = 0;
+ var hi_b : usize = 0;
+ var lo_a : usize = 0;
+ var lo_b : usize = 0;
+ if (r < hi and s[r] == 91) {
+ r = r + 1;
+ hi_a = r;
+ while (r < hi and is_digit(s[r])) { r = r + 1; }
+ if (r == hi_a) { return false; }
+ hi_b = r;
+ has_hi = true;
+ if (r < hi and s[r] == 58) {
+ r = r + 1;
+ lo_a = r;
+ while (r < hi and is_digit(s[r])) { r = r + 1; }
+ if (r == lo_a) { return false; }
+ lo_b = r;
+ has_lo_s = true;
+ }
+ if (r >= hi or s[r] != 93) { return false; }
+ r = r + 1;
+ }
+ var has_v : bool = false;
+ var v0 : usize = 0;
+ var v1 : usize = 0;
+ var w : usize = r;
+ while (w < hi) {
+ const k : usize = ws_at(s, w, hi);
+ if (k == 0) { break; }
+ w = w + k;
+ }
+ if (w < hi and s[w] == 61) {
+ w = w + 1;
+ while (w < hi) {
+ const k2 : usize = ws_at(s, w, hi);
+ if (k2 == 0) { break; }
+ w = w + k2;
+ }
+ v0 = w;
+ while (w < hi) {
+ const k3 : usize = ws_at(s, w, hi);
+ if (k3 > 0) { break; }
+ w = w + 1;
+ }
+ if (w == hi and w > v0) {
+ has_v = true;
+ v1 = w;
+ }
+ }
+ if (!has_v and r != hi) { return false; }
+ if (has_hi) {
+ FL_HAS_HI = true;
+ FL_HI = dec_f64(s, hi_a, hi_b);
+ FL_HAS_LO = true;
+ if (has_lo_s) { FL_LO = dec_f64(s, lo_a, lo_b); } else { FL_LO = FL_HI; }
+ if (FL_LO > FL_HI) { return false; }
+ }
+ if (has_v) {
+ FL_HAS_VTEXT = true;
+ FL_VTEXT_LO = v0;
+ FL_VTEXT_HI = v1;
+ }
+ // parts of the path
+ var pi : usize = ps;
+ var pstart : usize = ps;
+ FL_NPARTS = 0;
+ while (pi <= q) {
+ if (pi == q or s[pi] == 46) {
+ if (FL_NPARTS < 1024) {
+ FL_PART_LO[FL_NPARTS] = pstart;
+ FL_PART_HI[FL_NPARTS] = pi;
+ }
+ FL_NPARTS = FL_NPARTS + 1;
+ pstart = pi + 1;
+ }
+ pi = pi + 1;
+ }
+ if (has_v) {
+ if (!parse_literal(s[v0 .. v1])) {
+ FL_KIND = W_FK_BAD_VALUE;
+ return true;
+ }
+ FL_HAS_LIT = true;
+ FL_LIT = LIT_VAL;
+ FL_LIT_W = LIT_W;
+ }
+ FL_KIND = W_FK_OTHER;
+ if (is_slice_site(s, FL_PART_LO[0], FL_PART_HI[0]) and FL_NPARTS >= 2) {
+ FL_HAS_SITE = true;
+ FL_SITE_LO = FL_PART_LO[0];
+ FL_SITE_HI = FL_PART_HI[0];
+ FL_FEAT_LO = FL_PART_HI[0] + 1;
+ FL_FEAT_HI = q;
+ FL_KIND = W_FK_SITE;
+ // /^([ABCD])LUT\.INIT$/ on the feature
+ if (q - FL_FEAT_LO == 9) {
+ const f0 : u8 = s[FL_FEAT_LO];
+ if (f0 >= 65 and f0 <= 68 and lit_is(s, FL_FEAT_LO + 1, "LUT.INIT")) {
+ FL_LUT = f0;
+ FL_KIND = W_FK_LUT_INIT;
+ }
+ }
+ return true;
+ }
+ if (FL_NPARTS == 2) { FL_KIND = W_FK_PIP; }
+ return true;
+}
+
+pub fn lit_is(s: []const u8, p: usize, w: []const u8) -> bool {
+ if (p + w.len > s.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (s[p + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+// --- parseInput --------------------------------------------------------------------------------
+pub fn pi_has_bits() -> bool { return PI_HAS_BITS; }
+
+pub fn pi_bits() -> u64 {
+ if (PI_INIT_KIND == 2) { return PI_LBITS; }
+ return PI_LIT_INIT;
+}
+
+pub fn pi_has_lut() -> bool { return PI_LUT_SET; }
+pub fn pi_tile_lo() -> usize { return PI_TILE_LO; }
+pub fn pi_tile_hi() -> usize { return PI_TILE_HI; }
+pub fn pi_type_hi() -> usize { return PI_TYPE_HI; }
+pub fn pi_site_lo() -> usize { return PI_SITE_LO; }
+pub fn pi_site_hi() -> usize { return PI_SITE_HI; }
+pub fn pi_lut_letter() -> u8 { return PI_LUT; }
+pub fn pi_n() -> usize { return PI_N; }
+pub fn pi_line_lo(k: usize) -> usize { return PI_LO[k]; }
+pub fn pi_line_hi(k: usize) -> usize { return PI_HI[k]; }
+pub fn pi_kind(k: usize) -> u32 { return PI_KIND[k]; }
+pub fn pi_bad_n() -> usize { return PI_BAD_N; }
+pub fn pi_bad_lo(k: usize) -> usize { return PI_BAD_LO[k]; }
+pub fn pi_bad_hi(k: usize) -> usize { return PI_BAD_HI[k]; }
+
+pub fn pi_push(lo: usize, hi: usize, kind: u32) -> bool {
+ if (PI_N >= 16384) { return false; }
+ PI_LO[PI_N] = lo;
+ PI_HI[PI_N] = hi;
+ PI_KIND[PI_N] = kind;
+ PI_N = PI_N + 1;
+ return true;
+}
+
+pub fn pi_bad(lo: usize, hi: usize) -> bool {
+ if (PI_BAD_N >= 16384) { return false; }
+ PI_BAD_LO[PI_BAD_N] = lo;
+ PI_BAD_HI[PI_BAD_N] = hi;
+ PI_BAD_N = PI_BAD_N + 1;
+ return true;
+}
+
+pub fn same_range(s: []const u8, a: usize, b: usize, len: usize) -> bool {
+ var k : usize = 0;
+ while (k < len) {
+ if (s[a + k] != s[b + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The first match of /(\d+'[hHbBdD][0-9a-fA-F_]+|0[xX][0-9a-fA-F_]+)/ in s[lo .. hi): packed, or hi * 2^32 + hi when none (empty).
+pub fn find_inside(s: []const u8, lo: usize, hi: usize) -> u64 {
+ var i : usize = lo;
+ while (i < hi) {
+ var p : usize = i;
+ while (p < hi and is_digit(s[p])) { p = p + 1; }
+ if (p > i and p + 2 < hi and s[p] == 39) {
+ const bl : u8 = s[p + 1];
+ if (bl == 104 or bl == 72 or bl == 98 or bl == 66 or bl == 100 or bl == 68) {
+ var q : usize = p + 2;
+ while (q < hi and (is_hex(s[q]) or s[q] == 95)) { q = q + 1; }
+ if (q > p + 2) { return (i as u64) * 4294967296 + (q as u64); }
+ }
+ }
+ if (s[i] == 48 and i + 1 < hi and (s[i + 1] == 120 or s[i + 1] == 88)) {
+ var q2 : usize = i + 2;
+ while (q2 < hi and (is_hex(s[q2]) or s[q2] == 95)) { q2 = q2 + 1; }
+ if (q2 > i + 2) { return (i as u64) * 4294967296 + (q2 as u64); }
+ }
+ i = i + 1;
+ }
+ return (hi as u64) * 4294967296 + (hi as u64);
+}
+
+/// One trimmed non-empty line s[lo .. hi) of parseInput. False on a capacity hit.
+pub fn pi_line(s: []const u8, lo: usize, hi: usize) -> bool {
+ const line : []const u8 = s[lo .. hi];
+ const fok : bool = parse_fasm_line(line);
+ if (fok and FL_KIND == W_FK_LUT_INIT) {
+ var same : bool = true;
+ if (PI_LUT_SET) {
+ same = (PI_TILE_HI - PI_TILE_LO) == (FL_TILE_HI - FL_TILE_LO) and (PI_SITE_HI - PI_SITE_LO) == (FL_SITE_HI - FL_SITE_LO) and PI_LUT == FL_LUT;
+ if (same) {
+ same = same_range(s, PI_TILE_LO, lo + FL_TILE_LO, PI_TILE_HI - PI_TILE_LO) and same_range(s, PI_SITE_LO, lo + FL_SITE_LO, PI_SITE_HI - PI_SITE_LO);
+ }
+ }
+ if (same) {
+ if (!PI_LUT_SET) {
+ PI_LUT_SET = true;
+ PI_TILE_LO = lo + FL_TILE_LO;
+ PI_TILE_HI = lo + FL_TILE_HI;
+ PI_TYPE_HI = lo + FL_TYPE_HI;
+ PI_SITE_LO = lo + FL_SITE_LO;
+ PI_SITE_HI = lo + FL_SITE_HI;
+ PI_LUT = FL_LUT;
+ PI_LBITS = 0;
+ }
+ var lo_n : f64 = 0.0;
+ if (FL_HAS_LO) { lo_n = FL_LO; }
+ var hi_n : f64 = lo_n;
+ if (FL_HAS_HI) {
+ hi_n = FL_HI;
+ } else {
+ if (FL_HAS_LIT) { hi_n = lo_n + (FL_LIT_W as f64) - 1.0; }
+ }
+ if (hi_n >= 64.0) {
+ if (!pi_bad(lo, hi)) { return false; }
+ return pi_push(lo, hi, W_FK_BAD_RANGE);
+ }
+ var v : u64 = 1;
+ if (FL_HAS_LIT) { v = FL_LIT; }
+ const a : u32 = lo_n as u32;
+ const z : u32 = hi_n as u32;
+ var bi : u32 = a;
+ while (bi <= z) {
+ const m : u64 = (1 as u64) << bi;
+ if (((v >> (bi - a)) & 1) == 1) {
+ PI_LBITS = PI_LBITS | m;
+ } else {
+ PI_LBITS = PI_LBITS & ~m;
+ }
+ bi = bi + 1;
+ }
+ PI_INIT_KIND = 2;
+ PI_HAS_BITS = true;
+ }
+ return pi_push(lo, hi, FL_KIND);
+ }
+ if (fok and FL_KIND != W_FK_BAD_VALUE) { return pi_push(lo, hi, FL_KIND); }
+ const was_bad : bool = fok;
+ var lit_ok : bool = parse_literal(line);
+ var lit_v : u64 = LIT_VAL;
+ if (!lit_ok) {
+ const ins : u64 = find_inside(s, lo, hi);
+ if (pk_hi(ins) > pk_lo(ins)) {
+ lit_ok = parse_literal(s[pk_lo(ins) .. pk_hi(ins)]);
+ lit_v = LIT_VAL;
+ }
+ }
+ if (lit_ok and !PI_HAS_BITS) {
+ PI_LIT_INIT = lit_v;
+ PI_INIT_KIND = 1;
+ PI_HAS_BITS = true;
+ return true;
+ }
+ if (!lit_ok) {
+ if (!pi_bad(lo, hi)) { return false; }
+ }
+ if (was_bad) { return pi_push(lo, hi, W_FK_BAD_VALUE); }
+ return true;
+}
+
+/// parseInput(text): lines split at \r?\n, trimmed, empty ones dropped. False: a capacity was hit.
+pub fn parse_input(s: []const u8) -> bool {
+ PI_HAS_BITS = false;
+ PI_INIT_KIND = 0;
+ PI_LIT_INIT = 0;
+ PI_LBITS = 0;
+ PI_LUT_SET = false;
+ PI_N = 0;
+ PI_BAD_N = 0;
+ var start : usize = 0;
+ var i : usize = 0;
+ while (i <= s.len) {
+ if (i == s.len or s[i] == 10) {
+ var e : usize = i;
+ if (i < s.len and e > start and s[e - 1] == 13) { e = e - 1; }
+ const t : u64 = trim_pack(s, start, e);
+ if (pk_hi(t) > pk_lo(t)) {
+ if (!pi_line(s, pk_lo(t), pk_hi(t))) { return false; }
+ }
+ start = i + 1;
+ }
+ i = i + 1;
+ }
+ return true;
+}
+
+// --- support and the bit-level helpers ---------------------------------------------------------
+/// The inputs the output depends on, as a mask (bit k = input k).
+pub fn support_mask(b: u64) -> u32 {
+ var m : u32 = 0;
+ var k : u32 = 0;
+ while (k < 6) {
+ var x : u32 = 0;
+ while (x < 64) {
+ if (bit_of(b, x) != bit_of(b, x ^ (1 << k))) {
+ m = m | (1 << k);
+ break;
+ }
+ x = x + 1;
+ }
+ k = k + 1;
+ }
+ return m;
+}
+
+pub fn halves_o5(b: u64) -> u64 {
+ const lo : u64 = b & 4294967295;
+ return lo | (lo << 32);
+}
+
+pub fn halves_o6(b: u64) -> u64 {
+ const hi : u64 = b >> 32;
+ return hi | (hi << 32);
+}
+
+pub fn halves_same(b: u64) -> bool {
+ return halves_o5(b) == halves_o6(b);
+}
+
+pub fn row_level(i: u32, q: u32) -> u32 {
+ return (i >> (5 - q)) & 1;
+}
+
+// --- Quine-McCluskey -----------------------------------------------------------------------------
+pub fn min_value(i: usize) -> u32 { return MN_V[i]; }
+pub fn min_dc(i: usize) -> u32 { return MN_D[i]; }
+pub fn min_exact() -> bool { return MN_EXACT; }
+pub fn min_primes() -> usize { return MN_PRIMES; }
+
+pub fn term_set(i: usize, value: u32, dc: u32) {
+ MN_V[i] = value;
+ MN_D[i] = dc;
+}
+
+/// coverBits(terms) over the terms term_set / minimize left in MN_V, MN_D: the u64 of the points covered.
+pub fn cover_bits(n: usize) -> u64 {
+ var b : u64 = 0;
+ var x : u32 = 0;
+ while (x < 64) {
+ var hit : bool = false;
+ var t : usize = 0;
+ while (t < n) {
+ if ((x & ~MN_D[t]) == MN_V[t]) { hit = true; }
+ t = t + 1;
+ }
+ if (hit) { b = b | ((1 as u64) << x); }
+ x = x + 1;
+ }
+ return b;
+}
+
+/// termLiterals(t): the literals of one implicant in input order; the count.
+pub fn term_literals(value: u32, dc: u32) -> usize {
+ TL_N = 0;
+ var k : u32 = 0;
+ while (k < 6) {
+ if (((dc >> k) & 1) == 0) {
+ TL_INPUT[TL_N] = k;
+ if (((value >> k) & 1) == 0) { TL_INV[TL_N] = 1; } else { TL_INV[TL_N] = 0; }
+ TL_N = TL_N + 1;
+ }
+ k = k + 1;
+ }
+ return TL_N;
+}
+
+pub fn tl_input(i: usize) -> u32 { return TL_INPUT[i]; }
+pub fn tl_inverted(i: usize) -> u32 { return TL_INV[i]; }
+
+/// The solver's covers with the point m: opts pushed on OPT_STACK, the count.
+pub fn cov_count(left: u64, i: usize) -> u32 {
+ return pop64(MN_COV[i] & left);
+}
+
+/// opts[a] goes before opts[b] when this is negative: more of `left` first, then fewer literals.
+pub fn opt_cmp(left: u64, i: u32, j: u32) -> i32 {
+ const ci : i32 = cov_count(left, i as usize) as i32;
+ const cj : i32 = cov_count(left, j as usize) as i32;
+ if (cj - ci != 0) { return cj - ci; }
+ return (MN_LIT[(i as usize)] as i32) - (MN_LIT[(j as usize)] as i32);
+}
+
+pub fn solve(left: u64, lit_sum: u32) {
+ SV_NODES = SV_NODES + 1;
+ if (SV_NODES > SV_BUDGET) {
+ SV_EXACT = false;
+ return;
+ }
+ if (left == 0) {
+ if (!SV_HAS_BEST or SV_CHOSEN_N < SV_BEST_N or (SV_CHOSEN_N == SV_BEST_N and lit_sum < SV_BEST_LITS)) {
+ var k : usize = 0;
+ while (k < SV_CHOSEN_N) {
+ SV_BEST[k] = SV_CHOSEN[k];
+ k = k + 1;
+ }
+ SV_BEST_N = SV_CHOSEN_N;
+ SV_HAS_BEST = true;
+ SV_BEST_LITS = lit_sum;
+ }
+ return;
+ }
+ if (SV_HAS_BEST and SV_CHOSEN_N + 1 > SV_BEST_N) { return; }
+ // the uncovered point with the fewest primes able to cover it decides the branch
+ var pick : i32 = -1;
+ var best_cnt : usize = 0;
+ var m : u32 = 0;
+ while (m < 64) {
+ if (((left >> m) & 1) == 1) {
+ var cnt : usize = 0;
+ var i : usize = 0;
+ while (i < MN_PN) {
+ if (((MN_COV[i] >> m) & 1) == 1) { cnt = cnt + 1; }
+ i = i + 1;
+ }
+ if (pick < 0 or cnt < best_cnt) {
+ pick = m as i32;
+ best_cnt = cnt;
+ if (cnt == 1) { break; }
+ }
+ }
+ m = m + 1;
+ }
+ if (pick < 0) { return; }
+ const base : usize = OPT_TOP;
+ if (base + best_cnt > 65536 or SV_CHOSEN_N >= 63) {
+ SV_EXACT = false;
+ return;
+ }
+ var nopt : usize = 0;
+ var ii : usize = 0;
+ while (ii < MN_PN) {
+ if (((MN_COV[ii] >> (pick as u32)) & 1) == 1) {
+ // stable insertion into opts by opt_cmp
+ const x : u32 = ii as u32;
+ var j : usize = base + nopt;
+ while (j > base) {
+ if (opt_cmp(left, OPT_STACK[j - 1], x) > 0) {
+ OPT_STACK[j] = OPT_STACK[j - 1];
+ j = j - 1;
+ } else {
+ break;
+ }
+ }
+ OPT_STACK[j] = x;
+ nopt = nopt + 1;
+ }
+ ii = ii + 1;
+ }
+ OPT_TOP = base + nopt;
+ var q : usize = 0;
+ while (q < nopt) {
+ const pi : u32 = OPT_STACK[base + q];
+ SV_CHOSEN[SV_CHOSEN_N] = pi;
+ SV_CHOSEN_N = SV_CHOSEN_N + 1;
+ solve(left & ~MN_COV[(pi as usize)], lit_sum + MN_LIT[(pi as usize)]);
+ SV_CHOSEN_N = SV_CHOSEN_N - 1;
+ if (!SV_EXACT) {
+ OPT_TOP = base;
+ return;
+ }
+ q = q + 1;
+ }
+ OPT_TOP = base;
+}
+
+/// minimize(bits, budget): the term count; the terms are in MN_V / MN_D (min_value / min_dc).
+pub fn minimize(b: u64, budget: u32) -> usize {
+ MN_EXACT = true;
+ MN_PRIMES = 0;
+ MN_N = 0;
+ const ones : u32 = pop64(b);
+ if (ones == 0) { return 0; }
+ if (ones == 64) {
+ MN_V[0] = 0;
+ MN_D[0] = 63;
+ MN_N = 1;
+ MN_PRIMES = 1;
+ return 1;
+ }
+ var ln : usize = 0;
+ var mm : u32 = 0;
+ while (mm < 64) {
+ if (((b >> mm) & 1) == 1) {
+ LV_V[ln] = mm;
+ LV_D[ln] = 0;
+ ln = ln + 1;
+ }
+ mm = mm + 1;
+ }
+ MN_PN = 0;
+ while (ln > 0) {
+ var i : usize = 0;
+ while (i < ln) {
+ LV_USED[i] = 0;
+ i = i + 1;
+ }
+ var nn : usize = 0;
+ var a : usize = 0;
+ while (a < ln) {
+ var bb : usize = a + 1;
+ while (bb < ln) {
+ if (LV_D[a] == LV_D[bb]) {
+ const d : u32 = LV_V[a] ^ LV_V[bb];
+ if ((d & (d - 1)) == 0) {
+ LV_USED[a] = 1;
+ LV_USED[bb] = 1;
+ const tv : u32 = LV_V[a] & ~d;
+ const td : u32 = LV_D[a] | d;
+ var found : bool = false;
+ var f : usize = 0;
+ while (f < nn) {
+ if (NX_V[f] == tv and NX_D[f] == td) {
+ found = true;
+ break;
+ }
+ f = f + 1;
+ }
+ if (!found) {
+ NX_V[nn] = tv;
+ NX_D[nn] = td;
+ nn = nn + 1;
+ }
+ }
+ }
+ bb = bb + 1;
+ }
+ a = a + 1;
+ }
+ i = 0;
+ while (i < ln) {
+ if (LV_USED[i] == 0) {
+ MN_PV[MN_PN] = LV_V[i];
+ MN_PD[MN_PN] = LV_D[i];
+ MN_PN = MN_PN + 1;
+ }
+ i = i + 1;
+ }
+ i = 0;
+ while (i < nn) {
+ LV_V[i] = NX_V[i];
+ LV_D[i] = NX_D[i];
+ i = i + 1;
+ }
+ ln = nn;
+ }
+ MN_PRIMES = MN_PN;
+ // covers
+ var p : usize = 0;
+ while (p < MN_PN) {
+ var cv : u64 = 0;
+ var m2 : u32 = 0;
+ while (m2 < 64) {
+ if (((b >> m2) & 1) == 1) {
+ if ((m2 & ~MN_PD[p]) == MN_PV[p]) { cv = cv | ((1 as u64) << m2); }
+ }
+ m2 = m2 + 1;
+ }
+ MN_COV[p] = cv;
+ MN_LIT[p] = 6 - pop64(MN_PD[p] as u64);
+ p = p + 1;
+ }
+ SV_HAS_BEST = false;
+ SV_BEST_N = 0;
+ SV_BEST_LITS = 0;
+ SV_NODES = 0;
+ SV_BUDGET = budget;
+ SV_EXACT = true;
+ SV_CHOSEN_N = 0;
+ OPT_TOP = 0;
+ solve(b, 0);
+ MN_EXACT = SV_EXACT;
+ if (!SV_HAS_BEST) {
+ // budget gone before any cover: the greedy cover
+ SV_BEST_N = 0;
+ var left : u64 = b;
+ while (left != 0) {
+ var bi : usize = 0;
+ var q : usize = 1;
+ while (q < MN_PN) {
+ if (cov_count(left, q) > cov_count(left, bi)) { bi = q; }
+ q = q + 1;
+ }
+ SV_BEST[SV_BEST_N] = bi as u32;
+ SV_BEST_N = SV_BEST_N + 1;
+ left = left & ~MN_COV[bi];
+ }
+ }
+ // terms: best.map(primes).sort(fewer literals, then smaller value), stable
+ var t : usize = 0;
+ while (t < SV_BEST_N) {
+ const pv : u32 = MN_PV[(SV_BEST[t] as usize)];
+ const pd : u32 = MN_PD[(SV_BEST[t] as usize)];
+ var j : usize = t;
+ while (j > 0) {
+ const qv : u32 = MN_V[j - 1];
+ const qd : u32 = MN_D[j - 1];
+ const lq : u32 = 6 - pop64(qd as u64);
+ const lp : u32 = 6 - pop64(pd as u64);
+ var after : bool = false;
+ if (lq > lp) { after = true; }
+ if (lq == lp and qv > pv) { after = true; }
+ if (after) {
+ MN_V[j] = qv;
+ MN_D[j] = qd;
+ j = j - 1;
+ } else {
+ break;
+ }
+ }
+ MN_V[j] = pv;
+ MN_D[j] = pd;
+ t = t + 1;
+ }
+ MN_N = SV_BEST_N;
+ return MN_N;
+}
+
+// --- classify ----------------------------------------------------------------------------------
+pub fn cls_id() -> u32 { return CL_ID[0]; }
+pub fn cls_n() -> u32 { return CL_N[0]; }
+pub fn cls_k() -> u32 { return CL_K[0]; }
+pub fn cls_inputs_mask() -> u32 { return CL_INPUTS[0]; }
+pub fn cls_sel_n() -> u32 { return CL_SEL_N[0]; }
+pub fn cls_sel(i: usize) -> u32 { return CL_SEL[i]; }
+pub fn cls_data_n() -> u32 { return CL_DATA_N[0]; }
+pub fn cls_data(i: usize) -> u32 { return CL_DATA[i]; }
+pub fn cls_has_inner() -> bool { return CL_INNER; }
+pub fn cls_inner_id() -> u32 { return CL_ID[1]; }
+pub fn cls_inner_n() -> u32 { return CL_N[1]; }
+pub fn cls_inner_k() -> u32 { return CL_K[1]; }
+pub fn cls_inner_inputs_mask() -> u32 { return CL_INPUTS[1]; }
+pub fn cls_inner_sel_n() -> u32 { return CL_SEL_N[1]; }
+pub fn cls_inner_sel(i: usize) -> u32 { return CL_SEL[2 + i]; }
+pub fn cls_inner_data_n() -> u32 { return CL_DATA_N[1]; }
+pub fn cls_inner_data(i: usize) -> u32 { return CL_DATA[6 + i]; }
+
+/// The k-th input (ascending) of a support mask.
+pub fn input_at(mask: u32, k: u32) -> u32 {
+ var seen : u32 = 0;
+ var q : u32 = 0;
+ while (q < 6) {
+ if (((mask >> q) & 1) == 1) {
+ if (seen == k) { return q; }
+ seen = seen + 1;
+ }
+ q = q + 1;
+ }
+ return 0;
+}
+
+pub fn cl_start(slot: usize, id: u32, n: u32, k: u32, mask: u32) {
+ CL_ID[slot] = id;
+ CL_N[slot] = n;
+ CL_K[slot] = k;
+ CL_INPUTS[slot] = mask;
+ CL_SEL_N[slot] = 0;
+ CL_DATA_N[slot] = 0;
+}
+
+pub fn cl_sel(slot: usize, v: u32) {
+ CL_SEL[slot * 2 + (CL_SEL_N[slot] as usize)] = v;
+ CL_SEL_N[slot] = CL_SEL_N[slot] + 1;
+}
+
+pub fn cl_data(slot: usize, v: u32) {
+ CL_DATA[slot * 6 + (CL_DATA_N[slot] as usize)] = v;
+ CL_DATA_N[slot] = CL_DATA_N[slot] + 1;
+}
+
+/// score(c) of the xorwith search: other 99, an 'x' family 10, plus k.
+pub fn cl_score(slot: usize) -> u32 {
+ const id : u32 = CL_ID[slot];
+ if (id == W_C_OTHER) { return 99; }
+ var s : u32 = 0;
+ if (id == W_C_ANDX or id == W_C_ORX or id == W_C_MAJX or id == W_C_CARRY2X) { s = 10; }
+ return s + CL_K[slot];
+}
+
+pub fn cl_copy(from: usize, to: usize) {
+ CL_ID[to] = CL_ID[from];
+ CL_N[to] = CL_N[from];
+ CL_K[to] = CL_K[from];
+ CL_INPUTS[to] = CL_INPUTS[from];
+ CL_SEL_N[to] = CL_SEL_N[from];
+ CL_DATA_N[to] = CL_DATA_N[from];
+ var i : usize = 0;
+ while (i < 2) {
+ CL_SEL[to * 2 + i] = CL_SEL[from * 2 + i];
+ i = i + 1;
+ }
+ i = 0;
+ while (i < 6) {
+ CL_DATA[to * 6 + i] = CL_DATA[from * 6 + i];
+ i = i + 1;
+ }
+}
+
+/// g[j] (the function on its own n inputs): bit j of the result.
+pub fn compress(b: u64, mask: u32, n: u32) -> u64 {
+ var g : u64 = 0;
+ const size : u32 = 1 << n;
+ var j : u32 = 0;
+ while (j < size) {
+ var x : u32 = 0;
+ var q : u32 = 0;
+ while (q < n) {
+ if (((j >> q) & 1) == 1) { x = x | (1 << input_at(mask, q)); }
+ q = q + 1;
+ }
+ if (bit_of(b, x) == 1) { g = g | ((1 as u64) << j); }
+ j = j + 1;
+ }
+ return g;
+}
+
+pub fn lowest_bit(g: u64, want: u32) -> u32 {
+ var i : u32 = 0;
+ while (i < 64) {
+ if (bit_of(g, i) == want) { return i; }
+ i = i + 1;
+ }
+ return 64;
+}
+
+/// classify(bits) into slot (0 = the answer, 1 and 2 = candidates for the inner function).
+pub fn classify_into(b: u64, slot: usize) {
+ const mask : u32 = support_mask(b);
+ const n : u32 = pop64(mask as u64);
+ if (n == 0) {
+ if (bit_of(b, 0) == 1) { cl_start(slot, W_C_ONE, 0, 0, mask); } else { cl_start(slot, W_C_ZERO, 0, 0, mask); }
+ return;
+ }
+ const size : u32 = 1 << n;
+ const g : u64 = compress(b, mask, n);
+ const ones : u32 = pop64(g);
+ if (n == 1) {
+ if (bit_of(g, 1) == 1) { cl_start(slot, W_C_BUF, n, 0, mask); } else { cl_start(slot, W_C_NOT, n, 0, mask); }
+ return;
+ }
+ // xor, xnor
+ var is_xor : bool = true;
+ var is_xnor : bool = true;
+ var j : u32 = 0;
+ while (j < size) {
+ const par : u32 = pop64(j as u64) & 1;
+ if (bit_of(g, j) != par) { is_xor = false; }
+ if (bit_of(g, j) != 1 - par) { is_xnor = false; }
+ j = j + 1;
+ }
+ if (is_xor) {
+ cl_start(slot, W_C_XOR, n, 0, mask);
+ return;
+ }
+ if (is_xnor) {
+ cl_start(slot, W_C_XNOR, n, 0, mask);
+ return;
+ }
+ if (ones == 1) {
+ const m1 : u32 = lowest_bit(g, 1);
+ const k1 : u32 = n - pop64(m1 as u64);
+ var id1 : u32 = W_C_ANDX;
+ if (k1 == 0) { id1 = W_C_AND; }
+ if (k1 == n) { id1 = W_C_NOR; }
+ cl_start(slot, id1, n, k1, mask);
+ return;
+ }
+ if (ones == size - 1) {
+ const m0 : u32 = lowest_bit(g, 0);
+ const k0 : u32 = pop64(m0 as u64);
+ var id0 : u32 = W_C_ORX;
+ if (k0 == 0) { id0 = W_C_OR; }
+ if (k0 == n) { id0 = W_C_NAND; }
+ cl_start(slot, id0, n, k0, mask);
+ return;
+ }
+ if (n == 3) {
+ var msk : u32 = 0;
+ while (msk < 8) {
+ var ok : bool = true;
+ var jj : u32 = 0;
+ while (jj < 8) {
+ var want : u32 = 0;
+ if (pop64((jj ^ msk) as u64) >= 2) { want = 1; }
+ if (bit_of(g, jj) != want) { ok = false; }
+ jj = jj + 1;
+ }
+ if (ok) {
+ if (msk != 0) { cl_start(slot, W_C_MAJX, n, pop64(msk as u64), mask); } else { cl_start(slot, W_C_MAJ, n, 0, mask); }
+ return;
+ }
+ msk = msk + 1;
+ }
+ var s : u32 = 0;
+ while (s < 3) {
+ var a3 : u32 = 0;
+ var b3 : u32 = 0;
+ var seen : u32 = 0;
+ var qq : u32 = 0;
+ while (qq < 3) {
+ if (qq != s) {
+ if (seen == 0) { a3 = qq; } else { b3 = qq; }
+ seen = seen + 1;
+ }
+ qq = qq + 1;
+ }
+ var pair : u32 = 0;
+ while (pair < 2) {
+ var hi3 : u32 = a3;
+ var lo3 : u32 = b3;
+ if (pair == 1) {
+ hi3 = b3;
+ lo3 = a3;
+ }
+ var okm : bool = true;
+ var jm : u32 = 0;
+ while (jm < 8) {
+ var wm : u32 = (jm >> lo3) & 1;
+ if (((jm >> s) & 1) == 1) { wm = (jm >> hi3) & 1; }
+ if (bit_of(g, jm) != wm) { okm = false; }
+ jm = jm + 1;
+ }
+ if (okm) {
+ cl_start(slot, W_C_MUX2, n, 0, mask);
+ cl_sel(slot, input_at(mask, s));
+ cl_data(slot, input_at(mask, lo3));
+ cl_data(slot, input_at(mask, hi3));
+ return;
+ }
+ pair = pair + 1;
+ }
+ s = s + 1;
+ }
+ }
+ if (n == 5) {
+ var a : u32 = 0;
+ while (a < 5) {
+ var bq : u32 = a + 1;
+ while (bq < 5) {
+ var rest : [3]u32 = [0; 3];
+ var rn : usize = 0;
+ var qr : u32 = 0;
+ while (qr < 5) {
+ if (qr != a and qr != bq) {
+ rest[rn] = qr;
+ rn = rn + 1;
+ }
+ qr = qr + 1;
+ }
+ var cm : u32 = 0;
+ while (cm < 32) {
+ var okc : bool = true;
+ var jc : u32 = 0;
+ while (jc < 32) {
+ const y : u32 = jc ^ cm;
+ const low : u32 = ((y >> rest[0]) & 1) + ((y >> rest[1]) & 1) + ((y >> rest[2]) & 1);
+ var lowb : u32 = 0;
+ if (low >= 2) { lowb = 1; }
+ var wc : u32 = 0;
+ if (((y >> a) & 1) + ((y >> bq) & 1) + lowb >= 2) { wc = 1; }
+ if (bit_of(g, jc) != wc) { okc = false; }
+ jc = jc + 1;
+ }
+ if (okc) {
+ const kc : u32 = pop64(cm as u64);
+ if (kc != 0) { cl_start(slot, W_C_CARRY2X, n, kc, mask); } else { cl_start(slot, W_C_CARRY2, n, 0, mask); }
+ cl_data(slot, input_at(mask, a));
+ cl_data(slot, input_at(mask, bq));
+ cl_data(slot, input_at(mask, rest[0]));
+ cl_data(slot, input_at(mask, rest[1]));
+ cl_data(slot, input_at(mask, rest[2]));
+ return;
+ }
+ cm = cm + 1;
+ }
+ bq = bq + 1;
+ }
+ a = a + 1;
+ }
+ }
+ if (n == 6) {
+ var s1 : u32 = 0;
+ while (s1 < 6) {
+ var s2 : u32 = s1 + 1;
+ while (s2 < 6) {
+ var r4 : [4]u32 = [0; 4];
+ var r4n : usize = 0;
+ var q6 : u32 = 0;
+ while (q6 < 6) {
+ if (q6 != s1 and q6 != s2) {
+ r4[r4n] = q6;
+ r4n = r4n + 1;
+ }
+ q6 = q6 + 1;
+ }
+ var p0 : usize = 0;
+ while (p0 < 4) {
+ var p1 : usize = 0;
+ while (p1 < 4) {
+ if (p1 != p0) {
+ var p2 : usize = 0;
+ while (p2 < 4) {
+ if (p2 != p0 and p2 != p1) {
+ const p3 : usize = 6 - p0 - p1 - p2;
+ var pm : [4]u32 = [0; 4];
+ pm[0] = r4[p0];
+ pm[1] = r4[p1];
+ pm[2] = r4[p2];
+ pm[3] = r4[p3];
+ var ok4 : bool = true;
+ var j4 : u32 = 0;
+ while (j4 < 64) {
+ const sel : usize = (((j4 >> s1) & 1) | (((j4 >> s2) & 1) << 1)) as usize;
+ if (bit_of(g, j4) != ((j4 >> pm[sel]) & 1)) { ok4 = false; }
+ j4 = j4 + 1;
+ }
+ if (ok4) {
+ cl_start(slot, W_C_MUX4, n, 0, mask);
+ cl_sel(slot, input_at(mask, s1));
+ cl_sel(slot, input_at(mask, s2));
+ cl_data(slot, input_at(mask, pm[0]));
+ cl_data(slot, input_at(mask, pm[1]));
+ cl_data(slot, input_at(mask, pm[2]));
+ cl_data(slot, input_at(mask, pm[3]));
+ return;
+ }
+ }
+ p2 = p2 + 1;
+ }
+ }
+ p1 = p1 + 1;
+ }
+ p0 = p0 + 1;
+ }
+ s2 = s2 + 1;
+ }
+ s1 = s1 + 1;
+ }
+ }
+ // f = (XOR of some inputs) ^ h(the others): an input is linear when flipping it flips every row
+ var lin : u32 = 0;
+ var ln : u32 = 0;
+ var qi : u32 = 0;
+ while (qi < 6) {
+ if (((mask >> qi) & 1) == 1) {
+ var all_flip : bool = true;
+ var xx : u32 = 0;
+ while (xx < 64) {
+ if (bit_of(b, xx) == bit_of(b, xx ^ (1 << qi))) { all_flip = false; }
+ xx = xx + 1;
+ }
+ if (all_flip) {
+ lin = lin | (1 << qi);
+ ln = ln + 1;
+ }
+ }
+ qi = qi + 1;
+ }
+ if (ln != 0 and n - ln >= 2 and slot < 1) {
+ var h : u64 = 0;
+ var x2 : u32 = 0;
+ while (x2 < 64) {
+ if (bit_of(b, x2 & ~lin) == 1) { h = h | ((1 as u64) << x2); }
+ x2 = x2 + 1;
+ }
+ classify_into(h, 1);
+ classify_into(~h, 2);
+ var use_flipped : bool = false;
+ if (cl_score(2) < cl_score(1)) { use_flipped = true; }
+ var use_slot : usize = 1;
+ if (use_flipped) { use_slot = 2; }
+ if (CL_ID[use_slot] != W_C_OTHER) {
+ if (use_flipped) { cl_copy(2, 1); }
+ if (use_flipped) { cl_start(slot, W_C_XNORWITH, n, ln, mask); } else { cl_start(slot, W_C_XORWITH, n, ln, mask); }
+ var lq : u32 = 0;
+ while (lq < 6) {
+ if (((lin >> lq) & 1) == 1) { cl_data(slot, lq); }
+ lq = lq + 1;
+ }
+ CL_INNER = true;
+ return;
+ }
+ }
+ cl_start(slot, W_C_OTHER, n, 0, mask);
+}
+
+pub fn classify(b: u64) {
+ CL_INNER = false;
+ classify_into(b, 0);
+}
+
+pub fn put_id(id: u32) {
+ if (id == W_C_ZERO) { out_str("zero"); }
+ if (id == W_C_ONE) { out_str("one"); }
+ if (id == W_C_BUF) { out_str("buf"); }
+ if (id == W_C_NOT) { out_str("not"); }
+ if (id == W_C_AND) { out_str("and"); }
+ if (id == W_C_NAND) { out_str("nand"); }
+ if (id == W_C_OR) { out_str("or"); }
+ if (id == W_C_NOR) { out_str("nor"); }
+ if (id == W_C_ANDX) { out_str("andx"); }
+ if (id == W_C_ORX) { out_str("orx"); }
+ if (id == W_C_XOR) { out_str("xor"); }
+ if (id == W_C_XNOR) { out_str("xnor"); }
+ if (id == W_C_MAJ) { out_str("maj"); }
+ if (id == W_C_MAJX) { out_str("majx"); }
+ if (id == W_C_CARRY2) { out_str("carry2"); }
+ if (id == W_C_CARRY2X) { out_str("carry2x"); }
+ if (id == W_C_MUX2) { out_str("mux2"); }
+ if (id == W_C_MUX4) { out_str("mux4"); }
+ if (id == W_C_XORWITH) { out_str("xorwith"); }
+ if (id == W_C_XNORWITH) { out_str("xnorwith"); }
+ if (id == W_C_OTHER) { out_str("other"); }
+}
+
+/// CLASS_IDS[i] in OUT; the length.
+pub fn class_id(i: u32) -> usize {
+ out_reset();
+ put_id(i);
+ return OUT_N;
+}
+
+pub fn put_num(v: u32) {
+ out_push((v + 48) as u8);
+}
+
+/// classKey of the last classify(): "id/n/k" or "id/n/k/inner-id/n/k" in OUT; the length.
+pub fn class_key() -> usize {
+ out_reset();
+ put_id(CL_ID[0]);
+ out_push(47);
+ put_num(CL_N[0]);
+ out_push(47);
+ put_num(CL_K[0]);
+ if (CL_INNER and (CL_ID[0] == W_C_XORWITH or CL_ID[0] == W_C_XNORWITH)) {
+ out_push(47);
+ put_id(CL_ID[1]);
+ out_push(47);
+ put_num(CL_N[1]);
+ out_push(47);
+ put_num(CL_K[1]);
+ }
+ return OUT_N;
+}
+; FOLDED-END
+
+; --- state ----------------------------------------------------------------------------------------
+pub var INPUT : [524288]u8 = [0; 524288];
+pub var INPUT_N : u32 = 0;
+pub var PXD : [524288]u8 = [0; 524288];
+pub var PX_N : u32 = 0;
+pub var DSD : [524288]u8 = [0; 524288];
+pub var DS_N : u32 = 0;
+pub var PX_ST : u32 = 0;
+pub var DS_ST : u32 = 0;
+pub var FAIL_FILE : u32 = 0;
+pub var DATA_ST : u32 = 0;
+pub var TAB : u32 = 0;
+pub var EX_ON : i32 = -1;
+pub var DRAGGING : bool = false;
+pub var EFF_INPUT : bool = false;
+pub var HAS_BITS : bool = false;
+pub var BITS : u64 = 0;
+pub var ORIG : u64 = 0;
+pub var FLIPPED : i32 = -1;
+pub var ASSUMED : bool = false;
+pub var LOC_TILE : [160]u8 = [0; 160];
+pub var LOC_TILE_N : usize = 0;
+pub var LOC_TYPE_N : usize = 0;
+pub var LOC_SITE : [16]u8 = [0; 16];
+pub var LOC_SITE_N : usize = 0;
+pub var LOC_LUT : u8 = 0;
+pub var LOC_OK : bool = false;
+pub var BTN_ACT : [512]u32 = [0; 512];
+pub var BTN_N : u32 = 0;
+pub var RNG : u64 = 1;
+pub var DESIGN_HTML : [131072]u8 = [0; 131072];
+pub var DESIGN_N : u32 = 0;
+pub var FILE_ON : bool = false;
+pub var FILE_NAME : [1024]u8 = [0; 1024];
+pub var FILE_NAME_N : usize = 0;
+pub var FILE_BUF : [8388608]u8 = [0; 8388608];
+pub var FILE_N : u32 = 0;
+pub var FILE_IDX : u32 = 0;
+pub var FILE_NAMED : u32 = 0;
+pub var LUT_N : usize = 0;
+pub var LUT_LOC_LO : [8192]u32 = [0; 8192];
+pub var LUT_LOC_N : [8192]u32 = [0; 8192];
+pub var LUT_NL : [8192]u32 = [0; 8192];
+pub var LUT_LO : [196608]u32 = [0; 196608];
+pub var LUT_HI : [196608]u32 = [0; 196608];
+pub var LUT_HT : [16384]u32 = [0; 16384];
+pub var PIN_LO : [131072]u32 = [0; 131072];
+pub var PIN_HI : [131072]u32 = [0; 131072];
+pub var HK : [10240]u8 = [0; 10240];
+pub var HK_OFF : [256]u32 = [0; 256];
+pub var HK_LEN : [256]u32 = [0; 256];
+pub var HK_CNT : [256]u32 = [0; 256];
+pub var HK_ORD : [256]u32 = [0; 256];
+pub var HK_N : usize = 0;
+pub var HK_BYTES : usize = 0;
+pub var LNB : [65536]u8 = [0; 65536];
+pub var KB : [1024]u8 = [0; 1024];
+pub var KB2 : [1024]u8 = [0; 1024];
+pub var W1 : [4096]u8 = [0; 4096];
+pub var W2 : [4096]u8 = [0; 4096];
+pub var W3 : [4096]u8 = [0; 4096];
+pub var W4 : [4096]u8 = [0; 4096];
+pub var W5 : [4096]u8 = [0; 4096];
+pub var W6 : [4096]u8 = [0; 4096];
+pub var W7 : [4096]u8 = [0; 4096];
+pub var W8 : [4096]u8 = [0; 4096];
+pub var GAME_ON : bool = false;
+pub var G_N : u32 = 0;
+pub var G_AT : u32 = 0;
+pub var G_RIGHT : u32 = 0;
+pub var G_PICK : i32 = -1;
+pub var G_IDX : [16]u32 = [0; 16];
+pub var G_BITS : [16]u64 = [0; 16];
+pub var G_NL : [16]u32 = [0; 16];
+pub var G_ANS : [16]u32 = [0; 16];
+pub var G_LEN : [80]u32 = [0; 80];
+pub var G_ORD : [80]u32 = [0; 80];
+pub var G_TXT : [20480]u8 = [0; 20480];
+pub var NM_N : usize = 0;
+pub var NM_ID : [128]u32 = [0; 128];
+pub var NM_SAME : [128]u32 = [0; 128];
+pub var NM_NUM : [128]i64 = [0; 128];
+pub var NM_OFF : [128]u32 = [0; 128];
+pub var NM_LEN : [128]u32 = [0; 128];
+pub var NM_TXT : [16384]u8 = [0; 16384];
+pub var CAND : [128]u32 = [0; 128];
+pub var GOUT : [8]u32 = [0; 8];
+pub var GOUT_N : usize = 0;
+pub var UQ_LO : [512]u32 = [0; 512];
+pub var UQ_HI : [512]u32 = [0; 512];
+
+; --- plain text helpers ---------------------------------------------------------------------------
+pub fn eq_s(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var k : usize = 0;
+ while (k < a.len) {
+ if (a[k] != b[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn starts_s(a: []const u8, p: []const u8) -> bool {
+ if (a.len < p.len) { return false; }
+ var k : usize = 0;
+ while (k < p.len) {
+ if (a[k] != p[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn ends_s(a: []const u8, p: []const u8) -> bool {
+ if (a.len < p.len) { return false; }
+ var k : usize = 0;
+ while (k < p.len) {
+ if (a[a.len - p.len + k] != p[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn up_b(c: u8) -> u8 {
+ if (c >= 97 and c <= 122) { return c - 32; }
+ return c;
+}
+
+/// Is s, upper-cased, the upper-case word w?
+fn eq_up(s: []const u8, w: []const u8) -> bool {
+ if (s.len != w.len) { return false; }
+ var k : usize = 0;
+ while (k < s.len) {
+ if (up_b(s[k]) != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn is_abcd(c: u8) -> bool {
+ return c >= 65 and c <= 68;
+}
+
+/// Are all bytes of s[lo..hi) word characters (\w), and is there at least one?
+fn all_word(s: []const u8, lo: usize, hi: usize) -> bool {
+ if (hi <= lo) { return false; }
+ var k : usize = lo;
+ while (k < hi) {
+ if (!is_word_byte(s[k])) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn copy_to(dst: []u8, n0: u32, s: []const u8) -> u32 {
+ return raw(dst, n0, s);
+}
+
+fn wr(o: []u8, n: u32, esc: bool, s: []const u8) -> u32 {
+ if (esc) { return text(o, n, s); }
+ return raw(o, n, s);
+}
+
+fn wb(o: []u8, n: u32, esc: bool, b: u8) -> u32 {
+ if (esc) { return put_escaped(o, n, b); }
+ return put(o, n, b);
+}
+
+/// Fill {name} placeholders from up to eight pairs. esc = false writes plain text (for a name that is
+/// built first and written later); esc = true escapes the template and the values as text. A placeholder
+/// with no pair stays as written.
+fn pf8(o: []u8, n0: u32, esc: bool, t: []const u8, k0: []const u8, v0: []const u8, k1: []const u8, v1: []const u8, k2: []const u8, v2: []const u8, k3: []const u8, v3: []const u8, k4: []const u8, v4: []const u8, k5: []const u8, v5: []const u8, k6: []const u8, v6: []const u8, k7: []const u8, v7: []const u8) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ var closed : usize = 0;
+ if (t[i] == 123) {
+ var j : usize = i + 1;
+ while (j < t.len and is_word_byte(t[j])) { j = j + 1; }
+ if (j > i + 1 and j < t.len and t[j] == 125) { closed = j; }
+ }
+ if (closed > 0) {
+ var hit : bool = false;
+ if (!hit and name_is(t, i + 1, closed, k0)) { n = wr(o, n, esc, v0); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k1)) { n = wr(o, n, esc, v1); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k2)) { n = wr(o, n, esc, v2); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k3)) { n = wr(o, n, esc, v3); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k4)) { n = wr(o, n, esc, v4); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k5)) { n = wr(o, n, esc, v5); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k6)) { n = wr(o, n, esc, v6); hit = true; }
+ if (!hit and name_is(t, i + 1, closed, k7)) { n = wr(o, n, esc, v7); hit = true; }
+ if (!hit) {
+ var q : usize = i;
+ while (q <= closed) { n = put(o, n, t[q]); q = q + 1; }
+ }
+ i = closed + 1;
+ } else {
+ n = wb(o, n, esc, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+fn pf1(o: []u8, n0: u32, esc: bool, t: []const u8, k0: []const u8, v0: []const u8) -> u32 {
+ return pf8(o, n0, esc, t, k0, v0, "", "", "", "", "", "", "", "", "", "", "", "", "", "");
+}
+
+fn pf2(o: []u8, n0: u32, esc: bool, t: []const u8, k0: []const u8, v0: []const u8, k1: []const u8, v1: []const u8) -> u32 {
+ return pf8(o, n0, esc, t, k0, v0, k1, v1, "", "", "", "", "", "", "", "", "", "", "", "");
+}
+
+fn pf3(o: []u8, n0: u32, esc: bool, t: []const u8, k0: []const u8, v0: []const u8, k1: []const u8, v1: []const u8, k2: []const u8, v2: []const u8) -> u32 {
+ return pf8(o, n0, esc, t, k0, v0, k1, v1, k2, v2, "", "", "", "", "", "", "", "", "", "");
+}
+
+/// A signed integer as toLocaleString('en-US') writes it (grouped); an integer-valued double.
+fn fnum(o: []u8, n0: u32, v: f64) -> u32 {
+ var n : u32 = n0;
+ var x : f64 = v;
+ if (x < 0.0) {
+ n = put(o, n, 45);
+ x = 0.0 - x;
+ }
+ return num_grouped(o, n, x as u64);
+}
+
+/// String(n) for a small integer: plain decimal.
+fn dnum(o: []u8, n0: u32, v: u64) -> u32 {
+ var tmp : [24]u8 = [0; 24];
+ const m : u32 = dec_to(tmp[0..], v);
+ return raw(o, n0, tmp[0..(m as usize)]);
+}
+
+fn pin_to(o: []u8, n0: u32, q: u32) -> u32 {
+ var d : [4]u8 = [0; 4];
+ const m : u32 = dec_to(d[0..], (q + 1) as u64);
+ return pf1(o, n0, false, SAY_PIN, "n", d[0..(m as usize)]);
+}
+
+/// pins(mask): A1, A3, ... for the set bits of a mask, joined by ", ".
+fn pins_mask(o: []u8, n0: u32, mask: u32) -> u32 {
+ var n : u32 = n0;
+ var first : bool = true;
+ var q : u32 = 0;
+ while (q < 6) {
+ if (((mask >> q) & 1) == 1) {
+ if (!first) { n = raw(o, n, ", "); }
+ n = pin_to(o, n, q);
+ first = false;
+ }
+ q = q + 1;
+ }
+ return n;
+}
+
+fn lowest_set(mask: u32) -> u32 {
+ var q : u32 = 0;
+ while (q < 6) {
+ if (((mask >> q) & 1) == 1) { return q; }
+ q = q + 1;
+ }
+ return 0;
+}
+
+; --- JSON helpers -----------------------------------------------------------------------------------
+/// state.px and state.design are set only when both files have been read.
+fn pxs() -> []const u8 {
+ if (DATA_ST != 1) { return PXD[0..0]; }
+ return PXD[0..(PX_N as usize)];
+}
+
+fn dss() -> []const u8 {
+ if (DATA_ST != 1) { return DSD[0..0]; }
+ return DSD[0..(DS_N as usize)];
+}
+
+fn jstr_copy(dst: []u8, d: []const u8, sp: i64) -> u32 {
+ var len : u32 = str_len(d, sp);
+ if (len > (dst.len as u32)) { len = dst.len as u32; }
+ var k : u32 = 0;
+ while (k < len) {
+ dst[k as usize] = str_byte(d, sp, k);
+ k = k + 1;
+ }
+ return len;
+}
+
+fn jstr_is(d: []const u8, sp: i64, w: []const u8) -> bool {
+ if (kind(d, sp) != J_STRING) { return false; }
+ if (str_len(d, sp) != (w.len as u32)) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (str_byte(d, sp, k as u32) != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// num(n) of a JSON value: '-' for a missing or null one, else the grouped integer.
+fn jnum_to(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (sp < 0 or kind(d, sp) == J_NULL) { return put(o, n0, 45); }
+ return fnum(o, n0, num_f64(d, sp));
+}
+
+fn jfield_to(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ var n : u32 = n0;
+ const len : u32 = str_len(d, sp);
+ var k : u32 = 0;
+ while (k < len) {
+ n = put(o, n, str_byte(d, sp, k));
+ k = k + 1;
+ }
+ return n;
+}
+
+; --- prjxray-db ----------------------------------------------------------------------------------------
+fn tiles_sp() -> i64 {
+ const d : []const u8 = pxs();
+ return obj_get(d, root(d), "tiles");
+}
+
+/// tileOf(tt): the tile object, following one sameAs; NO_SPAN when there is none.
+fn tile_of(tt: []const u8) -> i64 {
+ const d : []const u8 = pxs();
+ const tiles : i64 = tiles_sp();
+ var t : i64 = obj_get(d, tiles, tt);
+ if (t < 0) { return NO_SPAN; }
+ const sa : i64 = obj_get(d, t, "sameAs");
+ if (sa >= 0 and kind(d, sa) == J_STRING and str_len(d, sa) > 0) {
+ const m : u32 = jstr_copy(KB[0..], d, sa);
+ t = obj_get(d, tiles, KB[0..(m as usize)]);
+ if (t < 0) { return NO_SPAN; }
+ }
+ return t;
+}
+
+/// twinOf(tt) into KB2; its length, or 0 when there is none.
+fn twin_of(tt: []const u8) -> u32 {
+ const d : []const u8 = pxs();
+ const tiles : i64 = tiles_sp();
+ const own : i64 = obj_get(d, tiles, tt);
+ if (own < 0) { return 0; }
+ const sa : i64 = obj_get(d, own, "sameAs");
+ if (sa >= 0 and kind(d, sa) == J_STRING and str_len(d, sa) > 0) {
+ return jstr_copy(KB2[0..], d, sa);
+ }
+ if (kind(d, tiles) != J_OBJECT) { return 0; }
+ const hi : usize = span_hi(tiles);
+ var p : usize = skip_ws(d, span_lo(tiles) + 1);
+ while (p < hi and d[p] == 34) {
+ const ke : usize = string_end(d, p);
+ if (ke > hi) { return 0; }
+ const q : usize = skip_ws(d, ke);
+ if (q >= hi or d[q] != 58) { return 0; }
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (ve > hi) { return 0; }
+ const sa2 : i64 = obj_get(d, jspan(v, ve), "sameAs");
+ if (sa2 >= 0 and jstr_is(d, sa2, tt)) {
+ const ks : i64 = jspan(p, ke);
+ return jstr_copy(KB2[0..], d, ks);
+ }
+ p = skip_ws(d, ve);
+ if (p < hi and d[p] == 44) { p = skip_ws(d, p + 1); }
+ }
+ return 0;
+}
+
+/// parseLoc's test: TILE.SITE.L with TILE ending _XY, SITE SLICE[LM]_X[01], L one of ABCD.
+fn loc_valid(s: []const u8) -> bool {
+ var p : usize = 0;
+ while (p < s.len and is_class(s[p])) { p = p + 1; }
+ if (p == 0 or p >= s.len or s[p] != 46 or p > 160) { return false; }
+ const te : usize = type_end(s, 0, p);
+ if (te == p or te == 0) { return false; }
+ if (s.len != p + 1 + 9 + 1 + 1) { return false; }
+ if (!is_slice_site(s, p + 1, p + 10)) { return false; }
+ if (s[p + 10] != 46) { return false; }
+ if (!is_abcd(s[p + 11])) { return false; }
+ return true;
+}
+
+fn parse_loc(s: []const u8) -> bool {
+ if (!loc_valid(s)) { return false; }
+ var p : usize = 0;
+ while (p < s.len and s[p] != 46) { p = p + 1; }
+ const te : usize = type_end(s, 0, p);
+ var k : usize = 0;
+ while (k < p) {
+ LOC_TILE[k] = s[k];
+ k = k + 1;
+ }
+ LOC_TILE_N = p;
+ LOC_TYPE_N = te;
+ k = 0;
+ while (k < 9) {
+ LOC_SITE[k] = s[p + 1 + k];
+ k = k + 1;
+ }
+ LOC_SITE_N = 9;
+ LOC_LUT = s[p + 11];
+ LOC_OK = true;
+ return true;
+}
+
+fn loc_type() -> []const u8 {
+ return LOC_TILE[0..LOC_TYPE_N];
+}
+
+fn loc_site() -> []const u8 {
+ return LOC_SITE[0..LOC_SITE_N];
+}
+
+/// The key "SITE.X" in KB for a site name and a trailing part; its length.
+fn dotted(site: []const u8, tail: []const u8) -> u32 {
+ var n : u32 = raw(KB[0..], 0, site);
+ n = put(KB[0..], n, 46);
+ n = raw(KB[0..], n, tail);
+ return n;
+}
+
+/// The span of init["SITE.L"] of the tile type tt, or NO_SPAN.
+fn init_list(tt: []const u8, site: []const u8, lut: u8) -> i64 {
+ const t : i64 = tile_of(tt);
+ if (t < 0) { return NO_SPAN; }
+ const d : []const u8 = pxs();
+ var one : [2]u8 = [0; 2];
+ one[0] = lut;
+ const m : u32 = dotted(site, one[0..1]);
+ return obj_get(d, obj_get(d, t, "init"), KB[0..(m as usize)]);
+}
+
+; --- class names -------------------------------------------------------------------------------------
+fn id_name(i: u32) -> []const u8 {
+ if (i == 0) { return "ZERO"; }
+ if (i == 1) { return "ONE"; }
+ if (i == 2) { return "BUF"; }
+ if (i == 3) { return "NOT"; }
+ if (i == 4) { return "AND"; }
+ if (i == 5) { return "NAND"; }
+ if (i == 6) { return "OR"; }
+ if (i == 7) { return "NOR"; }
+ if (i == 8) { return "ANDX"; }
+ if (i == 9) { return "ORX"; }
+ if (i == 10) { return "XOR"; }
+ if (i == 11) { return "XNOR"; }
+ if (i == 12) { return "MAJ"; }
+ if (i == 13) { return "MAJX"; }
+ if (i == 14) { return "CARRY2"; }
+ if (i == 15) { return "CARRY2X"; }
+ if (i == 16) { return "MUX2"; }
+ if (i == 17) { return "MUX4"; }
+ if (i == 18) { return "XORWITH"; }
+ if (i == 19) { return "XNORWITH"; }
+ return "OTHER";
+}
+
+fn class_tpl(i: u32) -> []const u8 {
+ if (i == 0) { return SAY_CLASS_ZERO; }
+ if (i == 1) { return SAY_CLASS_ONE; }
+ if (i == 2) { return SAY_CLASS_BUF; }
+ if (i == 3) { return SAY_CLASS_NOT; }
+ if (i == 4) { return SAY_CLASS_AND; }
+ if (i == 5) { return SAY_CLASS_NAND; }
+ if (i == 6) { return SAY_CLASS_OR; }
+ if (i == 7) { return SAY_CLASS_NOR; }
+ if (i == 8) { return SAY_CLASS_ANDX; }
+ if (i == 9) { return SAY_CLASS_ORX; }
+ if (i == 10) { return SAY_CLASS_XOR; }
+ if (i == 11) { return SAY_CLASS_XNOR; }
+ if (i == 12) { return SAY_CLASS_MAJ; }
+ if (i == 13) { return SAY_CLASS_MAJX; }
+ if (i == 14) { return SAY_CLASS_CARRY2; }
+ if (i == 15) { return SAY_CLASS_CARRY2X; }
+ if (i == 16) { return SAY_CLASS_MUX2; }
+ if (i == 17) { return SAY_CLASS_MUX4; }
+ if (i == 18) { return SAY_CLASS_XORWITH; }
+ if (i == 19) { return SAY_CLASS_XNORWITH; }
+ return SAY_CLASS_OTHER;
+}
+
+/// The class a text names, upper-cased: 0..20, or 255.
+fn id_of_ci(s: []const u8) -> u32 {
+ var i : u32 = 0;
+ while (i < 21) {
+ if (eq_up(s, id_name(i))) { return i; }
+ i = i + 1;
+ }
+ return 255;
+}
+
+/// Number(part) for a class key's n and k: a missing part is NaN, an empty or blank one 0, a sign and digits
+/// (with white space around) their value, anything else NaN (W_NAN_V).
+fn num_part(s: []const u8, lo0: usize, hi0: usize, has: bool) -> i64 {
+ if (!has) { return W_NAN_V; }
+ var lo : usize = lo0;
+ var hi : usize = hi0;
+ while (lo < hi and (s[lo] == 32 or (s[lo] >= 9 and s[lo] <= 13))) { lo = lo + 1; }
+ while (hi > lo and (s[hi - 1] == 32 or (s[hi - 1] >= 9 and s[hi - 1] <= 13))) { hi = hi - 1; }
+ if (hi <= lo) { return 0; }
+ var neg : bool = false;
+ if (s[lo] == 43 or s[lo] == 45) {
+ neg = s[lo] == 45;
+ lo = lo + 1;
+ }
+ if (hi <= lo) { return W_NAN_V; }
+ var v : i64 = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ if (!is_digit(s[k])) { return W_NAN_V; }
+ if (v < 100000000000000) { v = v * 10 + ((s[k] - 48) as i64); }
+ k = k + 1;
+ }
+ if (neg) { return 0 - v; }
+ return v;
+}
+
+fn num_plain(o: []u8, n0: u32, v: i64) -> u32 {
+ if (v == W_NAN_V) { return raw(o, n0, "NaN"); }
+ if (v < 0) {
+ const n : u32 = put(o, n0, 45);
+ return dnum(o, n, (0 - v) as u64);
+ }
+ return dnum(o, n0, v as u64);
+}
+
+/// className(classFromKey(key)): plain text of the name a key "id/n/k[/inner]" stands for.
+fn class_name_key(o: []u8, n0: u32, key: []const u8) -> u32 {
+ var s0 : usize = key.len;
+ var s1 : usize = key.len;
+ var s2 : usize = key.len;
+ var has1 : bool = false;
+ var has2 : bool = false;
+ var has3 : bool = false;
+ var p : usize = 0;
+ while (p < key.len and key[p] != 47) { p = p + 1; }
+ s0 = p;
+ var a : usize = p + 1;
+ if (p < key.len) {
+ has1 = true;
+ var q : usize = a;
+ while (q < key.len and key[q] != 47) { q = q + 1; }
+ s1 = q;
+ if (q < key.len) {
+ has2 = true;
+ var r : usize = q + 1;
+ while (r < key.len and key[r] != 47) { r = r + 1; }
+ s2 = r;
+ if (r < key.len) { has3 = true; }
+ }
+ }
+ const id : u32 = id_of_ci(key[0..s0]);
+ var tpl : []const u8 = key[0..s0];
+ if (id < 21) { tpl = class_tpl(id); }
+ const nv : i64 = num_part(key, a, s1, has1);
+ var kv : i64 = W_NAN_V;
+ var b : usize = s1 + 1;
+ if (has2) { kv = num_part(key, b, s2, true); }
+ var nb : [32]u8 = [0; 32];
+ var kb : [32]u8 = [0; 32];
+ const nl : u32 = num_plain(nb[0..], 0, nv);
+ const kl : u32 = num_plain(kb[0..], 0, kv);
+ var inner : [1024]u8 = [0; 1024];
+ var il : u32 = 0;
+ if (has3) { il = class_name_key(inner[0..], 0, key[(s2 + 1)..key.len]); }
+ return pf3(o, n0, false, tpl, "n", nb[0..(nl as usize)], "k", kb[0..(kl as usize)], "inner", inner[0..(il as usize)]);
+}
+
+/// className(c) of the last classify(): through its key.
+fn class_name_cls(o: []u8, n0: u32) -> u32 {
+ const kl : usize = class_key();
+ var kb : [64]u8 = [0; 64];
+ var k : usize = 0;
+ while (k < kl) {
+ kb[k] = OUT[k];
+ k = k + 1;
+ }
+ return class_name_key(o, n0, kb[0..kl]);
+}
+
+; --- what prjxray-db says (words) -----------------------------------------------------------------------
+fn letter_buf(b: u8) -> u32 {
+ W8[0] = b;
+ return 1;
+}
+
+/// srcWord(key, lut) as plain text in o; the length.
+fn src_word(o: []u8, n0: u32, key: []const u8, lut: u8) -> u32 {
+ var kk : []const u8 = key;
+ if (key.len == 2 and is_abcd(key[0]) and key[1] == 88) {
+ kk = "NX";
+ } else {
+ if (key.len == 3 and is_abcd(key[0]) and key[1] == 53 and key[2] == 81) {
+ kk = "N5Q";
+ } else {
+ if (key.len == 2 and is_abcd(key[0]) and key[1] == 73) {
+ kk = "NI";
+ } else {
+ if (ends_s(key, "MC31")) { kk = "MC31"; }
+ }
+ }
+ }
+ var l0 : u8 = lut;
+ if (key.len > 0 and is_abcd(key[0]) and !eq_s(kk, key)) { l0 = key[0]; }
+ var tpl : []const u8 = key;
+ var i : usize = 0;
+ while (i < 13) {
+ if (eq_s(kk, SAY_SRC_KEYS[i])) { tpl = SAY_SRC_WORDS[i]; }
+ i = i + 1;
+ }
+ W8[0] = l0;
+ return pf1(o, n0, false, tpl, "L", W8[0..1]);
+}
+
+fn flat_word(f: []const u8) -> []const u8 {
+ if (eq_s(f, "CEUSEDMUX")) { return SAY_F_CEUSEDMUX; }
+ if (eq_s(f, "SRUSEDMUX")) { return SAY_F_SRUSEDMUX; }
+ if (eq_s(f, "CLKINV")) { return SAY_F_CLKINV; }
+ if (eq_s(f, "NOCLKINV")) { return SAY_F_NOCLKINV; }
+ if (eq_s(f, "FFSYNC")) { return SAY_F_FFSYNC; }
+ if (eq_s(f, "LATCH")) { return SAY_F_LATCH; }
+ if (eq_s(f, "WA7USED")) { return SAY_F_WA7USED; }
+ if (eq_s(f, "WA8USED")) { return SAY_F_WA8USED; }
+ if (eq_s(f, "WEMUX.CE")) { return SAY_F_WEMUX_CE; }
+ return "";
+}
+
+/// featureWords(f) for the feature text f of a site line in tile type tt, written as escaped text to o.
+fn feature_words(o: []u8, n0: u32, f: []const u8, tt: []const u8) -> u32 {
+ const len : usize = f.len;
+ if (len == 9 and is_abcd(f[0]) and eq_s(f[1..9], "LUT.INIT")) {
+ return pf1(o, n0, true, SAY_F_LUT_INIT, "L", f[0..1]);
+ }
+ if (len >= 5 and is_abcd(f[0]) and eq_s(f[1..5], "LUT.")) {
+ if (eq_s(f[5..len], "RAM")) { return pf1(o, n0, true, SAY_F_LUT_RAM, "L", f[0..1]); }
+ if (eq_s(f[5..len], "SMALL")) { return pf1(o, n0, true, SAY_F_LUT_SMALL, "L", f[0..1]); }
+ if (eq_s(f[5..len], "SRL")) { return pf1(o, n0, true, SAY_F_LUT_SRL, "L", f[0..1]); }
+ }
+ if (len > 12 and is_abcd(f[0]) and eq_s(f[1..12], "LUT.DI1MUX.") and all_word(f, 12, len)) {
+ const m : u32 = src_word(W1[0..], 0, f[12..len], f[0]);
+ return pf2(o, n0, true, SAY_F_DI1MUX, "L", f[0..1], "src", W1[0..(m as usize)]);
+ }
+ if (len >= 1 and is_abcd(f[0])) {
+ var ix : usize = 1;
+ if (len > 1 and f[1] == 53) { ix = 2; }
+ if (eq_s(f[ix..len], "FF.ZINI")) { return pf1(o, n0, true, SAY_F_ZINI, "ff", f[0..(ix + 2)]); }
+ if (eq_s(f[ix..len], "FF.ZRST")) { return pf1(o, n0, true, SAY_F_ZRST, "ff", f[0..(ix + 2)]); }
+ }
+ if (len == 12 and is_abcd(f[0]) and eq_s(f[1..12], "5FFMUX.IN_A")) {
+ return pf2(o, n0, true, SAY_F_5FFMUX_IN_A, "ff", f[0..4], "L", f[0..1]);
+ }
+ if (len == 12 and is_abcd(f[0]) and eq_s(f[1..12], "5FFMUX.IN_B")) {
+ return pf2(o, n0, true, SAY_F_5FFMUX_IN_B, "ff", f[0..4], "L", f[0..1]);
+ }
+ if (len > 7 and is_abcd(f[0]) and eq_s(f[1..7], "FFMUX.") and all_word(f, 7, len)) {
+ const m2 : u32 = src_word(W1[0..], 0, f[7..len], f[0]);
+ return pf2(o, n0, true, SAY_F_FFMUX, "ff", f[0..3], "src", W1[0..(m2 as usize)]);
+ }
+ if (len > 8 and is_abcd(f[0]) and eq_s(f[1..8], "OUTMUX.") and all_word(f, 8, len)) {
+ const m3 : u32 = src_word(W1[0..], 0, f[8..len], f[0]);
+ return pf2(o, n0, true, SAY_F_OUTMUX, "L", f[0..1], "src", W1[0..(m3 as usize)]);
+ }
+ if (len == 11 and eq_s(f[0..7], "CARRY4.") and is_abcd(f[7]) and eq_s(f[8..11], "CY0")) {
+ return pf1(o, n0, true, SAY_F_CY0, "L", f[7..8]);
+ }
+ if (len > 10 and eq_s(f[0..10], "PRECYINIT.") and all_word(f, 10, len)) {
+ const m4 : u32 = src_word(W1[0..], 0, f[10..len], 65);
+ return pf1(o, n0, true, SAY_F_PRECYINIT, "src", W1[0..(m4 as usize)]);
+ }
+ const fw : []const u8 = flat_word(f);
+ if (fw.len > 0) { return text(o, n0, fw); }
+ return pf1(o, n0, true, SAY_F_UNKNOWN, "tt", tt);
+}
+
+/// pipWords(f): dst and src are the two parts of the path; the words, each in a lx-w paragraph.
+fn pip_words(o: []u8, n0: u32, dst: []const u8, src: []const u8, tt: []const u8) -> u32 {
+ var n : u32 = n0;
+ const t : i64 = tile_of(tt);
+ if (t < 0) {
+ n = raw(o, n, "
");
+ n = pf3(o, n, true, SAY_PIP_OTHER, "tt", tt, "dst", dst, "src", src);
+ return raw(o, n, "
");
+ }
+ const d : []const u8 = pxs();
+ var plen : usize = 0;
+ // /^(CLBL[LM]_(?:LL|L|M)_)([ABCD])([1-6])$/
+ if (dst.len >= 8 and eq_s(dst[0..4], "CLBL") and (dst[4] == 76 or dst[4] == 77) and dst[5] == 95) {
+ if (dst.len >= 9 and dst[6] == 76 and dst[7] == 76 and dst[8] == 95) { plen = 9; }
+ if (plen == 0 and dst[6] == 76 and dst[7] == 95) { plen = 8; }
+ if (plen == 0 and dst[6] == 77 and dst[7] == 95) { plen = 8; }
+ }
+ if (plen > 0 and (dst.len != plen + 2 or !is_abcd(dst[plen]) or dst[plen + 1] < 49 or dst[plen + 1] > 54)) { plen = 0; }
+ var found : bool = false;
+ var site_name : i64 = NO_SPAN;
+ if (plen > 0) {
+ const sites : i64 = obj_get(d, t, "sites");
+ const sn : u32 = arr_len(d, sites);
+ var i : u32 = 0;
+ while (i < sn and !found) {
+ const sobj : i64 = arr_at(d, sites, i);
+ if (jstr_is(d, obj_get(d, sobj, "prefix"), dst[0..plen])) {
+ found = true;
+ site_name = obj_get(d, sobj, "name");
+ }
+ i = i + 1;
+ }
+ }
+ n = raw(o, n, "
");
+ if (found) {
+ const nm : u32 = jstr_copy(W2[0..], d, site_name);
+ n = pf8(o, n, true, SAY_PIP_LUTPIN, "n", dst[(plen + 1)..(plen + 2)], "site", W2[0..(nm as usize)], "L", dst[plen..(plen + 1)], "src", src, "", "", "", "", "", "", "", "");
+ } else {
+ n = pf2(o, n, true, SAY_PIP_CLB, "dst", dst, "src", src);
+ }
+ n = raw(o, n, "
");
+ var kl : u32 = raw(KB[0..], 0, dst);
+ kl = put(KB[0..], kl, 46);
+ kl = raw(KB[0..], kl, src);
+ if (kl != OVER) {
+ const kind_sp : i64 = obj_get(d, obj_get(d, t, "ppips"), KB[0..(kl as usize)]);
+ if (jstr_is(d, kind_sp, "always")) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_PIP_ALWAYS);
+ n = raw(o, n, "
");
+ }
+ if (jstr_is(d, kind_sp, "hint")) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_PIP_HINT);
+ n = raw(o, n, "
");
+ }
+ if (jstr_is(d, kind_sp, "default")) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_PIP_DEFAULT);
+ n = raw(o, n, "
");
+ }
+ }
+ return n;
+}
+
+/// tileWords(tt), each in a lx-note paragraph.
+fn tile_words(o: []u8, n0: u32, tt: []const u8) -> u32 {
+ var n : u32 = n0;
+ if (starts_s(tt, "CLBLL_")) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_TILE_CLBLL);
+ n = raw(o, n, "
");
+ } else {
+ if (starts_s(tt, "CLBLM_")) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_TILE_CLBLM);
+ n = raw(o, n, "
");
+ } else {
+ n = raw(o, n, "
");
+ n = pf1(o, n, true, SAY_TILE_OTHER, "tt", tt);
+ return raw(o, n, "
");
+ }
+ }
+ const tw : u32 = twin_of(tt);
+ if (tw > 0) {
+ n = raw(o, n, "
");
+ n = pf2(o, n, true, SAY_TILE_SIDE, "a", tt, "b", KB2[0..(tw as usize)]);
+ n = raw(o, n, "
");
+ }
+ return n;
+}
+
+fn chip(o: []u8, n0: u32, k: usize, v: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "");
+ n = text(o, n, SAY_PART_NAMES[k]);
+ n = raw(o, n, " ");
+ n = text(o, n, v);
+ return raw(o, n, "");
+}
+
+; --- the FASM lines of the paste ---------------------------------------------------------------------------
+/// One list item for the line f (already parsed into FL_*); `line` is the line's bytes. kind is pi_kind.
+fn emit_item(o: []u8, n0: u32, line: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, line[FL_TEXT_LO..FL_TEXT_HI]);
+ n = raw(o, n, "
");
+ const tile : []const u8 = line[FL_TILE_LO..FL_TILE_HI];
+ const tt : []const u8 = line[FL_TILE_LO..FL_TYPE_HI];
+ n = chip(o, n, 0, tile);
+ const kd : u32 = FL_KIND;
+ if (kd == W_FK_SITE or kd == W_FK_LUT_INIT) {
+ const site : []const u8 = line[FL_SITE_LO..FL_SITE_HI];
+ const feat : []const u8 = line[FL_FEAT_LO..FL_FEAT_HI];
+ const lutc : u8 = FL_LUT;
+ const has_v : bool = FL_HAS_VTEXT;
+ const vtext : []const u8 = line[FL_VTEXT_LO..FL_VTEXT_HI];
+ n = chip(o, n, 1, site);
+ var dot : usize = feat.len;
+ var q : usize = 0;
+ while (q < feat.len) {
+ if (feat[q] == 46) { dot = q; break; }
+ q = q + 1;
+ }
+ if (dot < feat.len) {
+ n = chip(o, n, 2, feat[0..dot]);
+ n = chip(o, n, 3, feat[(dot + 1)..feat.len]);
+ } else {
+ n = chip(o, n, 3, feat);
+ }
+ if (has_v) {
+ if (vtext.len > 24) {
+ var vb : [32]u8 = [0; 32];
+ var z : usize = 0;
+ while (z < 22) { vb[z] = vtext[z]; z = z + 1; }
+ vb[22] = 46;
+ vb[23] = 46;
+ n = chip(o, n, 4, vb[0..24]);
+ } else {
+ n = chip(o, n, 4, vtext);
+ }
+ }
+ n = raw(o, n, "
");
+ n = raw(o, n, "
");
+ n = feature_words(o, n, feat, tt);
+ n = raw(o, n, "
");
+ const t : i64 = tile_of(tt);
+ if (t >= 0) {
+ const d : []const u8 = pxs();
+ if (kd == W_FK_LUT_INIT) {
+ const list : i64 = init_list(tt, site, lutc);
+ if (list >= 0) {
+ n = raw(o, n, "
");
+ const first : i64 = arr_at(d, list, 0);
+ const last : i64 = arr_at(d, list, 63);
+ const m1 : u32 = jstr_copy(W3[0..], d, first);
+ const m2 : u32 = jstr_copy(W4[0..], d, last);
+ n = pf2(o, n, true, SAY_BITS_INIT, "first", W3[0..(m1 as usize)], "last", W4[0..(m2 as usize)]);
+ n = raw(o, n, "
");
+ }
+ } else {
+ const kl : u32 = dotted(site, feat);
+ var list2 : i64 = NO_SPAN;
+ if (kl != OVER) { list2 = obj_get(d, obj_get(d, t, "features"), KB[0..(kl as usize)]); }
+ if (list2 >= 0) {
+ const cnt : u32 = arr_len(d, list2);
+ var neg : bool = false;
+ var b : u32 = 0;
+ while (b < cnt) {
+ const bs : i64 = arr_at(d, list2, b);
+ if (kind(d, bs) == J_STRING and str_len(d, bs) > 0 and str_byte(d, bs, 0) == 33) { neg = true; }
+ b = b + 1;
+ }
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_BITS);
+ n = raw(o, n, ": ");
+ b = 0;
+ while (b < cnt) {
+ const bs2 : i64 = arr_at(d, list2, b);
+ if (kind(d, bs2) == J_STRING and str_len(d, bs2) > 0 and str_byte(d, bs2, 0) == 33) {
+ n = raw(o, n, "");
+ } else {
+ n = raw(o, n, "");
+ }
+ n = jtext_to(o, n, d, bs2);
+ n = raw(o, n, "");
+ b = b + 1;
+ }
+ n = raw(o, n, "");
+ if (neg) {
+ n = raw(o, n, " (");
+ n = text(o, n, SAY_BIT_NEG);
+ n = raw(o, n, ")");
+ }
+ n = raw(o, n, "
");
+ } else {
+ n = raw(o, n, "
");
+ const kk : u32 = dotted(site, feat);
+ n = pf2(o, n, true, SAY_TILE_NO_FEATURE, "tt", tt, "feature", KB[0..(kk as usize)]);
+ n = raw(o, n, "
");
+ n = pf1(o, n, true, SAY_F_UNKNOWN, "tt", tt);
+ n = raw(o, n, "
");
+ }
+ }
+ return raw(o, n, "");
+}
+
+/// A JSON string as escaped text.
+fn jtext_to(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ var n : u32 = n0;
+ const len : u32 = str_len(d, sp);
+ var k : u32 = 0;
+ while (k < len) {
+ n = put_escaped(o, n, str_byte(d, sp, k));
+ k = k + 1;
+ }
+ return n;
+}
+
+/// parseInput over the paste again (the tables are shared with the file reader); false: a capacity was hit.
+fn reparse() -> bool {
+ if (!parse_input(INPUT[0..(INPUT_N as usize)])) {
+ PI_N = 0;
+ PI_BAD_N = 0;
+ PI_HAS_BITS = false;
+ PI_LUT_SET = false;
+ return false;
+ }
+ return true;
+}
+
+fn emit_fasm(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, SAY_FASM_TITLE);
+ n = raw(o, n, "
");
+ const inp : []const u8 = INPUT[0..(INPUT_N as usize)];
+ const cnt : usize = PI_N;
+ // the tile types of the lines, each once, in order
+ var uq : usize = 0;
+ var k : usize = 0;
+ while (k < cnt) {
+ const lo : usize = PI_LO[k];
+ const hi : usize = PI_HI[k];
+ if (parse_fasm_line(inp[lo..hi])) {
+ const a : usize = lo + FL_TILE_LO;
+ const b : usize = lo + FL_TYPE_HI;
+ var seen : bool = false;
+ var j : usize = 0;
+ while (j < uq and !seen) {
+ if (eq_s(inp[(UQ_LO[j] as usize)..(UQ_HI[j] as usize)], inp[a..b])) { seen = true; }
+ j = j + 1;
+ }
+ if (!seen and uq < 512) {
+ UQ_LO[uq] = a as u32;
+ UQ_HI[uq] = b as u32;
+ uq = uq + 1;
+ }
+ }
+ k = k + 1;
+ }
+ var j2 : usize = 0;
+ while (j2 < uq) {
+ n = tile_words(o, n, inp[(UQ_LO[j2] as usize)..(UQ_HI[j2] as usize)]);
+ j2 = j2 + 1;
+ }
+ var items : usize = 0;
+ k = 0;
+ while (k < cnt) {
+ if (PI_KIND[k] != W_FK_BAD_RANGE) { items = items + 1; }
+ k = k + 1;
+ }
+ items = items + PI_BAD_N;
+ if (items > 0) {
+ n = raw(o, n, "
");
+ k = 0;
+ while (k < cnt) {
+ if (PI_KIND[k] != W_FK_BAD_RANGE) {
+ const lo2 : usize = PI_LO[k];
+ const hi2 : usize = PI_HI[k];
+ if (parse_fasm_line(inp[lo2..hi2])) {
+ n = emit_item(o, n, inp[lo2..hi2]);
+ }
+ }
+ k = k + 1;
+ }
+ var bk : usize = 0;
+ while (bk < PI_BAD_N) {
+ n = raw(o, n, "
");
+ n = fill_named(o, n, SAY_BAD_LINE, "line", inp[PI_BAD_LO[bk]..PI_BAD_HI[bk]], "", "", "", "", "", "", "", "", "", "");
+ n = raw(o, n, "
");
+ bk = bk + 1;
+ }
+ n = raw(o, n, "
");
+ } else {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_FASM_NONE);
+ n = raw(o, n, "
");
+ }
+ return raw(o, n, "
");
+}
+
+; --- the decode panel --------------------------------------------------------------------------------------
+fn btn_mark(a: u32) -> u32 {
+ if (BTN_N < 512) { BTN_ACT[BTN_N as usize] = a; }
+ BTN_N = BTN_N + 1;
+ return 0;
+}
+
+/// className(c) of the function the last classify() found, as escaped text.
+fn emit_class_name(o: []u8, n0: u32) -> u32 {
+ const m : u32 = class_name_cls(W1[0..], 0);
+ return text(o, n0, W1[0..(m as usize)]);
+}
+
+/// classDetail(c) for the last classify(): the sentence in a dd, or nothing.
+fn emit_detail(o: []u8, n0: u32) -> u32 {
+ const id : u32 = cls_id();
+ var n : u32 = n0;
+ var has : bool = false;
+ if (id == W_C_BUF or id == W_C_NOT) {
+ const m : u32 = pin_to(W2[0..], 0, lowest_set(cls_inputs_mask()));
+ n = raw(o, n, "
");
+ if (id == W_C_BUF) { n = pf1(o, n, true, SAY_DETAIL_BUF, "pin", W2[0..(m as usize)]); } else { n = pf1(o, n, true, SAY_DETAIL_NOT, "pin", W2[0..(m as usize)]); }
+ has = true;
+ }
+ if (id == W_C_MUX2) {
+ const a : u32 = pin_to(W2[0..], 0, cls_sel(0));
+ const b : u32 = pin_to(W3[0..], 0, cls_data(0));
+ const c : u32 = pin_to(W4[0..], 0, cls_data(1));
+ n = raw(o, n, "
");
+ n = pf3(o, n, true, SAY_DETAIL_MUX2, "s", W2[0..(a as usize)], "d0", W3[0..(b as usize)], "d1", W4[0..(c as usize)]);
+ has = true;
+ }
+ if (id == W_C_MUX4) {
+ const a2 : u32 = pin_to(W2[0..], 0, cls_sel(1));
+ const b2 : u32 = pin_to(W3[0..], 0, cls_sel(0));
+ var dn : u32 = 0;
+ var i : usize = 0;
+ while (i < (cls_data_n() as usize)) {
+ if (i > 0) { dn = raw(W4[0..], dn, ", "); }
+ dn = pin_to(W4[0..], dn, cls_data(i));
+ i = i + 1;
+ }
+ n = raw(o, n, "
");
+ n = pf3(o, n, true, SAY_DETAIL_MUX4, "s1", W2[0..(a2 as usize)], "s0", W3[0..(b2 as usize)], "d", W4[0..(dn as usize)]);
+ has = true;
+ }
+ if (id == W_C_CARRY2 or id == W_C_CARRY2X) {
+ const pa : u32 = pin_to(W1[0..], 0, cls_data(0));
+ const pb : u32 = pin_to(W2[0..], 0, cls_data(1));
+ const pc : u32 = pin_to(W3[0..], 0, cls_data(2));
+ const pd : u32 = pin_to(W4[0..], 0, cls_data(3));
+ const pe : u32 = pin_to(W5[0..], 0, cls_data(4));
+ n = raw(o, n, "
");
+ n = pf8(o, n, true, SAY_DETAIL_CARRY2, "a", W1[0..(pa as usize)], "b", W2[0..(pb as usize)], "c", W3[0..(pc as usize)], "d", W4[0..(pd as usize)], "e", W5[0..(pe as usize)], "", "", "", "", "", "");
+ has = true;
+ }
+ if (id == W_C_XORWITH or id == W_C_XNORWITH) {
+ var ln : u32 = 0;
+ var j : usize = 0;
+ while (j < (cls_data_n() as usize)) {
+ if (j > 0) { ln = raw(W2[0..], ln, ", "); }
+ ln = pin_to(W2[0..], ln, cls_data(j));
+ j = j + 1;
+ }
+ n = raw(o, n, "
");
+ n = pf1(o, n, true, SAY_DETAIL_LINEAR, "list", W2[0..(ln as usize)]);
+ has = true;
+ }
+ if (has) { n = raw(o, n, "
"); }
+ return n;
+}
+
+/// expression(terms) over the terms minimize() left, as escaped text.
+fn emit_expression(o: []u8, n0: u32, cnt: usize) -> u32 {
+ var n : u32 = n0;
+ if (cnt == 0) { return text(o, n, SAY_CONST_0); }
+ var t : usize = 0;
+ while (t < cnt) {
+ if (t > 0) { n = text(o, n, SAY_OP_OR); }
+ const nl : usize = term_literals(min_value(t), min_dc(t));
+ if (nl == 0) {
+ n = text(o, n, SAY_CONST_1);
+ } else {
+ var l : usize = 0;
+ while (l < nl) {
+ if (l > 0) { n = text(o, n, SAY_OP_AND); }
+ if (tl_inverted(l) == 1) { n = text(o, n, SAY_OP_NOT); }
+ const m : u32 = pin_to(W6[0..], 0, tl_input(l));
+ n = text(o, n, W6[0..(m as usize)]);
+ l = l + 1;
+ }
+ }
+ t = t + 1;
+ }
+ return n;
+}
+
+fn fasm_line_to(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, LOC_TILE[0..LOC_TILE_N]);
+ n = put(o, n, 46);
+ n = raw(o, n, LOC_SITE[0..LOC_SITE_N]);
+ n = put(o, n, 46);
+ n = put(o, n, LOC_LUT);
+ n = raw(o, n, "LUT.INIT[63:0] = 64'b");
+ init_bin(BITS);
+ var k : usize = 0;
+ while (k < OUT_N) {
+ n = put(o, n, OUT[k]);
+ k = k + 1;
+ }
+ return n;
+}
+
+fn emit_truth(o: []u8, n0: u32, dcmask: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ var b : u32 = 0;
+ while (b < 4) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_TABLE_HEAD[0]);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_TABLE_HEAD[1]);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_TABLE_HEAD[2]);
+ n = raw(o, n, "
");
+ var i : u32 = b * 16;
+ while (i < b * 16 + 16) {
+ btn_mark(100 + i);
+ n = raw(o, n, "");
+ n = dnum(o, n, i as u64);
+ n = raw(o, n, "");
+ var q : u32 = 0;
+ while (q < 6) {
+ if (((dcmask >> (5 - q)) & 1) == 1) { n = raw(o, n, ""); } else { n = raw(o, n, ""); }
+ n = dnum(o, n, row_level(i, q) as u64);
+ n = raw(o, n, "");
+ q = q + 1;
+ }
+ n = raw(o, n, "");
+ n = dnum(o, n, v as u64);
+ n = raw(o, n, "");
+ i = i + 1;
+ }
+ n = raw(o, n, "
");
+ b = b + 1;
+ }
+ return raw(o, n, "
");
+}
+
+/// decodeBits(bits): its four blocks.
+fn emit_bits(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ classify(BITS);
+ const mask : u32 = cls_inputs_mask();
+ const dcmask : u32 = 63 - mask;
+ const cid : u32 = cls_id();
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_NAME_LABEL);
+ n = raw(o, n, "
");
+ n = emit_class_name(o, n);
+ n = raw(o, n, "
");
+ n = emit_detail(o, n);
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_INPUTS_LABEL);
+ n = raw(o, n, "
");
+ if (mask != 0) {
+ const m : u32 = pins_mask(W2[0..], 0, mask);
+ n = text(o, n, W2[0..(m as usize)]);
+ } else {
+ n = text(o, n, SAY_NONE);
+ }
+ n = raw(o, n, " ");
+ n = text(o, n, SAY_DONT_CARE);
+ n = raw(o, n, ": ");
+ if (dcmask != 0) {
+ const m2 : u32 = pins_mask(W2[0..], 0, dcmask);
+ n = text(o, n, W2[0..(m2 as usize)]);
+ } else {
+ n = text(o, n, SAY_NONE);
+ }
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_INIT_LABEL);
+ n = raw(o, n, "
");
+ n = text(o, n, "64'h");
+ init_hex(BITS);
+ var k : usize = 0;
+ while (k < OUT_N) {
+ n = put(o, n, OUT[k]);
+ k = k + 1;
+ }
+ n = raw(o, n, "
");
+ const tc : usize = minimize(BITS, 40000);
+ const exact : bool = min_exact();
+ const good : bool = cover_bits(tc) == BITS;
+ var tn : [24]u8 = [0; 24];
+ const tl : u32 = dec_to(tn[0..], tc as u64);
+ n = raw(o, n, "
");
+ n = pf1(o, n, true, SAY_EXPR_LABEL, "terms", tn[0..(tl as usize)]);
+ n = raw(o, n, "
");
+ n = emit_expression(o, n, tc);
+ n = raw(o, n, "
");
+ if (!exact) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_EXPR_INEXACT);
+ n = raw(o, n, "
");
+ }
+ if (good) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_EXPR_CHECK);
+ n = raw(o, n, "
");
+ }
+ n = raw(o, n, "
");
+ // the truth table, the flip note, the undo button
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_TABLE_LABEL);
+ n = raw(o, n, "
");
+ if (FLIPPED >= 0 and LOC_OK) {
+ const list : i64 = init_list(loc_type(), loc_site(), LOC_LUT);
+ if (list >= 0) {
+ const d : []const u8 = pxs();
+ const item : i64 = arr_at(d, list, FLIPPED as u32);
+ if (item >= 0) {
+ const m3 : u32 = jstr_copy(W2[0..], d, item);
+ var us : usize = 0;
+ while (us < (m3 as usize) and W2[us] != 95) { us = us + 1; }
+ const frame : u64 = dec_val(W2[0..us]);
+ var bitv : u64 = 0;
+ if (us < (m3 as usize)) { bitv = dec_val(W2[(us + 1)..(m3 as usize)]); }
+ var ib : [24]u8 = [0; 24];
+ var vb : [24]u8 = [0; 24];
+ var fb : [24]u8 = [0; 24];
+ var bb : [24]u8 = [0; 24];
+ const il : u32 = dec_to(ib[0..], FLIPPED as u64);
+ const vl : u32 = dec_to(vb[0..], bit_of(BITS, FLIPPED as u32) as u64);
+ const fl : u32 = dec_to(fb[0..], frame);
+ const bl : u32 = dec_to(bb[0..], bitv);
+ var lb : [2]u8 = [0; 2];
+ lb[0] = LOC_LUT;
+ n = raw(o, n, "
");
+ n = pf8(o, n, true, SAY_FLIPPED, "i", ib[0..(il as usize)], "v", vb[0..(vl as usize)], "frame", fb[0..(fl as usize)], "bit", bb[0..(bl as usize)], "tt", loc_type(), "site", loc_site(), "lut", lb[0..1], "", "");
+ n = raw(o, n, "
");
+ }
+ }
+ }
+ if (BITS != ORIG) {
+ btn_mark(30);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_RESET);
+ n = raw(o, n, "");
+ }
+ n = emit_truth(o, n, dcmask);
+ n = raw(o, n, "
");
+ // the rebuilt FASM line
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_FASM_REBUILT);
+ n = raw(o, n, "
");
+ const fl2 : u32 = fasm_line_to(W1[0..], 0);
+ n = text(o, n, W1[0..(fl2 as usize)]);
+ n = raw(o, n, "
");
+ if (ASSUMED) {
+ n = raw(o, n, "
");
+ n = pf1(o, n, true, SAY_LOC_ASSUMED, "loc", K_DEFAULT_LOC);
+ n = raw(o, n, "
");
+ }
+ n = raw(o, n, "
");
+ // the two LUT5 halves
+ var lo_name : [512]u8 = [0; 512];
+ var hi_name : [512]u8 = [0; 512];
+ classify(halves_o5(BITS));
+ const lol : u32 = class_name_cls(lo_name[0..], 0);
+ classify(halves_o6(BITS));
+ const hil : u32 = class_name_cls(hi_name[0..], 0);
+ init_hex(BITS);
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HALVES_TITLE);
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HALVES_BODY);
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HALF_LOW);
+ n = raw(o, n, "
32'h");
+ k = 8;
+ while (k < 16) {
+ n = put(o, n, OUT[k]);
+ k = k + 1;
+ }
+ n = raw(o, n, " ");
+ n = text(o, n, lo_name[0..(lol as usize)]);
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HALF_HIGH);
+ n = raw(o, n, "
32'h");
+ k = 0;
+ while (k < 8) {
+ n = put(o, n, OUT[k]);
+ k = k + 1;
+ }
+ n = raw(o, n, " ");
+ n = text(o, n, hi_name[0..(hil as usize)]);
+ n = raw(o, n, "
");
+ if (halves_same(BITS)) { n = text(o, n, SAY_HALVES_SAME); } else { n = text(o, n, SAY_HALVES_DIFF); }
+ n = raw(o, n, "
");
+ const ds : []const u8 = dss();
+ const dr : i64 = root(ds);
+ const a1 : u32 = jnum_to(W2[0..], 0, ds, obj_get(ds, dr, "o5Used"));
+ const a2 : u32 = jnum_to(W3[0..], 0, ds, obj_get(ds, dr, "luts"));
+ n = pf2(o, n, true, SAY_HALVES_DESIGN, "o5", W2[0..(a1 as usize)], "luts", W3[0..(a2 as usize)]);
+ return raw(o, n, "
");
+}
+
+/// Number(digits) of a digit string.
+fn dec_val(s: []const u8) -> u64 {
+ var v : u64 = 0;
+ var k : usize = 0;
+ while (k < s.len) {
+ if (s[k] >= 48 and s[k] <= 57 and v < 100000000000000) { v = v * 10 + ((s[k] - 48) as u64); }
+ k = k + 1;
+ }
+ return v;
+}
+
+fn emit_out(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ if (DATA_ST != 1) { return raw(o, n, "
"); }
+ reparse();
+ if (!HAS_BITS and PI_N == 0) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_NOTHING);
+ n = raw(o, n, "
");
+ }
+ if (HAS_BITS) { n = emit_bits(o, n); }
+ n = emit_fasm(o, n);
+ return raw(o, n, "");
+}
+
+; --- readInput, examples, setInput -------------------------------------------------------------------------
+fn example_text(i: u32) -> u32 {
+ // exampleText(i) into INPUT: the 64'h form of an INIT, or the joined FASM lines.
+ var n : u32 = 0;
+ if (i < 6) {
+ n = raw(INPUT[0..], n, "64'h");
+ n = raw(INPUT[0..], n, K_EXAMPLE_INITS[i as usize]);
+ return n;
+ }
+ if (i == 6) {
+ var j : usize = 0;
+ while (j < 7) {
+ if (j > 0) { n = put(INPUT[0..], n, 10); }
+ n = raw(INPUT[0..], n, K_EXAMPLE_FASM_CARRY[j]);
+ j = j + 1;
+ }
+ return n;
+ }
+ var j2 : usize = 0;
+ while (j2 < 12) {
+ if (j2 > 0) { n = put(INPUT[0..], n, 10); }
+ n = raw(INPUT[0..], n, K_EXAMPLE_FASM_FF[j2]);
+ j2 = j2 + 1;
+ }
+ return n;
+}
+
+/// readInput(): parse the paste, keep the bits and the place.
+fn read_input() -> u32 {
+ FLIPPED = -1;
+ const okp : bool = reparse();
+ if (okp and PI_HAS_BITS) {
+ HAS_BITS = true;
+ BITS = pi_bits();
+ ORIG = BITS;
+ ASSUMED = !PI_LUT_SET;
+ if (PI_LUT_SET) {
+ var k : usize = 0;
+ const tl : usize = PI_TILE_HI - PI_TILE_LO;
+ while (k < tl and k < 160) {
+ LOC_TILE[k] = INPUT[PI_TILE_LO + k];
+ k = k + 1;
+ }
+ LOC_TILE_N = tl;
+ LOC_TYPE_N = PI_TYPE_HI - PI_TILE_LO;
+ k = 0;
+ while (k < 9 and k < PI_SITE_HI - PI_SITE_LO) {
+ LOC_SITE[k] = INPUT[PI_SITE_LO + k];
+ k = k + 1;
+ }
+ LOC_SITE_N = PI_SITE_HI - PI_SITE_LO;
+ LOC_LUT = PI_LUT;
+ LOC_OK = true;
+ } else {
+ parse_loc(K_DEFAULT_LOC);
+ }
+ } else {
+ HAS_BITS = false;
+ }
+ return 0;
+}
+
+/// setInput(text) when the text is already in INPUT: the examples are all unpressed.
+fn set_input(len: u32) -> u32 {
+ INPUT_N = len;
+ EFF_INPUT = true;
+ EX_ON = -1;
+ read_input();
+ return 0;
+}
+
+; --- the reader's own file ---------------------------------------------------------------------------------
+fn hash_key(s: []const u8) -> u64 {
+ var h : u64 = 7;
+ var k : usize = 0;
+ while (k < s.len) {
+ h = (h * 31 + (s[k] as u64)) % 1000000007;
+ k = k + 1;
+ }
+ return h;
+}
+
+/// The LUT whose location text is key: its index, or 0xFFFFFFFF.
+fn lut_find(key: []const u8) -> u32 {
+ var slot : usize = (hash_key(key) % 16384) as usize;
+ var probes : usize = 0;
+ while (probes < 16384) {
+ const e : u32 = LUT_HT[slot];
+ if (e == 0) { return 4294967295; }
+ const li : usize = (e - 1) as usize;
+ const lo : usize = LUT_LOC_LO[li] as usize;
+ if (eq_s(FILE_BUF[lo..(lo + (LUT_LOC_N[li] as usize))], key)) { return li as u32; }
+ slot = (slot + 1) % 16384;
+ probes = probes + 1;
+ }
+ return 4294967295;
+}
+
+fn lut_insert(key_lo: usize, key_n: usize) -> u32 {
+ if (LUT_N >= W_MAX_LUTS) { return 4294967295; }
+ const key : []const u8 = FILE_BUF[key_lo..(key_lo + key_n)];
+ const f : u32 = lut_find(key);
+ if (f != 4294967295) { return f; }
+ const li : usize = LUT_N;
+ LUT_LOC_LO[li] = key_lo as u32;
+ LUT_LOC_N[li] = key_n as u32;
+ LUT_NL[li] = 0;
+ LUT_N = LUT_N + 1;
+ var slot : usize = (hash_key(key) % 16384) as usize;
+ while (LUT_HT[slot] != 0) { slot = (slot + 1) % 16384; }
+ LUT_HT[slot] = (li + 1) as u32;
+ return li as u32;
+}
+
+fn lut_add_line(li: u32, lo: usize, hi: usize) -> u32 {
+ const c : usize = LUT_NL[li as usize] as usize;
+ if (c >= W_LUT_LINES) { return 0; }
+ LUT_LO[(li as usize) * W_LUT_LINES + c] = lo as u32;
+ LUT_HI[(li as usize) * W_LUT_LINES + c] = hi as u32;
+ LUT_NL[li as usize] = (c + 1) as u32;
+ return 0;
+}
+
+/// A tile name: word characters ending _XY, at least one character before it; the end of it from lo.
+fn tile_end(s: []const u8, lo: usize, hi: usize) -> usize {
+ var p : usize = lo;
+ while (p < hi and is_class(s[p])) { p = p + 1; }
+ if (p == lo) { return 0; }
+ const te : usize = type_end(s, lo, p);
+ if (te == p or te == lo) { return 0; }
+ return p;
+}
+
+/// /^TILE\.SLICE[LM]_X[01]\.[ABCD]LUT\.INIT\[/ on s[lo..hi): the end of "TILE.SITE.L", or 0.
+fn init_line_loc(s: []const u8, lo: usize, hi: usize) -> usize {
+ const te : usize = tile_end(s, lo, hi);
+ if (te == 0 or te >= hi or s[te] != 46) { return 0; }
+ if (te + 1 + 9 > hi or !is_slice_site(s, te + 1, te + 10)) { return 0; }
+ if (te + 10 >= hi or s[te + 10] != 46) { return 0; }
+ if (te + 11 >= hi or !is_abcd(s[te + 11])) { return 0; }
+ if (!lit_is(s, te + 12, "LUT.INIT[")) { return 0; }
+ return te + 12;
+}
+
+/// /^TILE\.CLBL[LM]_(?:LL|L|M)_[ABCD][1-6]\./ on s[lo..hi): the end of the prefix "CLBLx_yy_", or 0 (the tile end in te).
+fn pin_line_prefix(s: []const u8, lo: usize, hi: usize) -> usize {
+ const te : usize = tile_end(s, lo, hi);
+ if (te == 0 or te >= hi or s[te] != 46) { return 0; }
+ const a : usize = te + 1;
+ if (a + 8 > hi or !lit_is(s, a, "CLBL")) { return 0; }
+ if (s[a + 4] != 76 and s[a + 4] != 77) { return 0; }
+ if (s[a + 5] != 95) { return 0; }
+ var pl : usize = 0;
+ if (a + 9 <= hi and s[a + 6] == 76 and s[a + 7] == 76 and s[a + 8] == 95) { pl = 9; }
+ if (pl == 0 and s[a + 6] == 76 and s[a + 7] == 95) { pl = 8; }
+ if (pl == 0 and s[a + 6] == 77 and s[a + 7] == 95) { pl = 8; }
+ if (pl == 0) { return 0; }
+ if (a + pl + 3 > hi) { return 0; }
+ if (!is_abcd(s[a + pl])) { return 0; }
+ if (s[a + pl + 1] < 49 or s[a + pl + 1] > 54) { return 0; }
+ if (s[a + pl + 2] != 46) { return 0; }
+ return a + pl;
+}
+
+/// The tile type of tile s[lo..te): the name without its _XY tail.
+fn tile_type_len(s: []const u8, lo: usize, te: usize) -> usize {
+ return type_end(s, lo, te) - lo;
+}
+
+fn hist_add(key: []const u8) -> u32 {
+ var i : usize = 0;
+ while (i < HK_N) {
+ if (eq_s(HK[(HK_OFF[i] as usize)..((HK_OFF[i] + HK_LEN[i]) as usize)], key)) {
+ HK_CNT[i] = HK_CNT[i] + 1;
+ return 0;
+ }
+ i = i + 1;
+ }
+ if (HK_N >= W_HIST_MAX or HK_BYTES + key.len > 10240) { return 0; }
+ HK_OFF[HK_N] = HK_BYTES as u32;
+ HK_LEN[HK_N] = key.len as u32;
+ HK_CNT[HK_N] = 1;
+ var k : usize = 0;
+ while (k < key.len) {
+ HK[HK_BYTES + k] = key[k];
+ k = k + 1;
+ }
+ HK_BYTES = HK_BYTES + key.len;
+ HK_N = HK_N + 1;
+ return 0;
+}
+
+/// Does s[a..b) hold the text INIT[ ?
+fn has_init_br(s: []const u8, a: usize, b: usize) -> bool {
+ var k : usize = a;
+ while (k + 5 <= b) {
+ if (s[k] == 73 and s[k + 1] == 78 and s[k + 2] == 73 and s[k + 3] == 84 and s[k + 4] == 91) { return true; }
+ k = k + 1;
+ }
+ return false;
+}
+
+/// takeFile: read the whole file for its LUTs.
+fn take_file(len: u32) -> u32 {
+ const s : []const u8 = FILE_BUF[0..(len as usize)];
+ FILE_ON = true;
+ FILE_IDX = 0;
+ FILE_NAMED = 0;
+ LUT_N = 0;
+ HK_N = 0;
+ HK_BYTES = 0;
+ var i : usize = 0;
+ while (i < 16384) { LUT_HT[i] = 0; i = i + 1; }
+ var npin : usize = 0;
+ var ls : usize = 0;
+ while (ls <= s.len) {
+ var le : usize = ls;
+ while (le < s.len and s[le] != 10) { le = le + 1; }
+ var he : usize = le;
+ if (le < s.len and he > ls and s[he - 1] == 13) { he = he - 1; }
+ const tp : u64 = trim_pack(s, ls, he);
+ const lo : usize = pk_lo(tp);
+ const hi : usize = pk_hi(tp);
+ if (hi > lo and s[lo] != 35) {
+ const ie : usize = init_line_loc(s, lo, hi);
+ if (ie > 0) {
+ const li : u32 = lut_insert(lo, ie - lo);
+ if (li != 4294967295) { lut_add_line(li, lo, hi); }
+ } else {
+ if (pin_line_prefix(s, lo, hi) > 0 and npin < W_MAX_PINS) {
+ PIN_LO[npin] = lo as u32;
+ PIN_HI[npin] = hi as u32;
+ npin = npin + 1;
+ }
+ }
+ }
+ ls = le + 1;
+ }
+ // the pin lines, each given to its LUT
+ var p : usize = 0;
+ while (p < npin) {
+ const lo2 : usize = PIN_LO[p] as usize;
+ const hi2 : usize = PIN_HI[p] as usize;
+ const te : usize = tile_end(s, lo2, hi2);
+ const pe : usize = pin_line_prefix(s, lo2, hi2);
+ const tt : []const u8 = s[lo2..(lo2 + tile_type_len(s, lo2, te))];
+ const t : i64 = tile_of(tt);
+ if (t >= 0) {
+ const d : []const u8 = pxs();
+ const sites : i64 = obj_get(d, t, "sites");
+ const sn : u32 = arr_len(d, sites);
+ var found : bool = false;
+ var j : u32 = 0;
+ while (j < sn and !found) {
+ const sobj : i64 = arr_at(d, sites, j);
+ if (jstr_is(d, obj_get(d, sobj, "prefix"), s[(te + 1)..pe])) {
+ found = true;
+ var kn : u32 = raw(KB[0..], 0, s[lo2..te]);
+ kn = put(KB[0..], kn, 46);
+ kn = jfield_to(KB[0..], kn, d, obj_get(d, sobj, "name"));
+ kn = put(KB[0..], kn, 46);
+ kn = put(KB[0..], kn, s[pe]);
+ if (kn != OVER) {
+ const li2 : u32 = lut_find(KB[0..(kn as usize)]);
+ if (li2 != 4294967295) { lut_add_line(li2, lo2, hi2); }
+ }
+ }
+ j = j + 1;
+ }
+ }
+ p = p + 1;
+ }
+ // the histogram of what the LUTs are
+ var li3 : usize = 0;
+ while (li3 < LUT_N) {
+ var tn : u32 = 0;
+ var c : usize = 0;
+ while (c < (LUT_NL[li3] as usize)) {
+ const a : usize = LUT_LO[li3 * W_LUT_LINES + c] as usize;
+ const b : usize = LUT_HI[li3 * W_LUT_LINES + c] as usize;
+ if (has_init_br(s, a, b)) {
+ if (tn > 0) { tn = put(LNB[0..], tn, 10); }
+ tn = raw(LNB[0..], tn, s[a..b]);
+ }
+ c = c + 1;
+ }
+ if (tn != OVER) {
+ if (parse_input(LNB[0..(tn as usize)]) and PI_HAS_BITS) {
+ classify(pi_bits());
+ const kl : usize = class_key();
+ var kb : [64]u8 = [0; 64];
+ var q : usize = 0;
+ while (q < kl) { kb[q] = OUT[q]; q = q + 1; }
+ if (!starts_s(kb[0..kl], "other/")) { FILE_NAMED = FILE_NAMED + 1; }
+ hist_add(kb[0..kl]);
+ }
+ }
+ li3 = li3 + 1;
+ }
+ // sorted by count, largest first, ties in the order first met
+ var z : usize = 0;
+ while (z < HK_N) { HK_ORD[z] = z as u32; z = z + 1; }
+ var a2 : usize = 1;
+ while (a2 < HK_N) {
+ const cur : u32 = HK_ORD[a2];
+ var b2 : usize = a2;
+ while (b2 > 0 and HK_CNT[HK_ORD[b2 - 1] as usize] < HK_CNT[cur as usize]) {
+ HK_ORD[b2] = HK_ORD[b2 - 1];
+ b2 = b2 - 1;
+ }
+ HK_ORD[b2] = cur;
+ a2 = a2 + 1;
+ }
+ return 0;
+}
+
+/// showFileLut(i): the LUT number i (wrapping), its lines into the paste box.
+fn show_file_lut(i: i64) -> u32 {
+ if (LUT_N == 0) { return 0; }
+ var idx : i64 = i;
+ const total : i64 = LUT_N as i64;
+ idx = ((idx % total) + total) % total;
+ FILE_IDX = idx as u32;
+ const li : usize = idx as usize;
+ var n : u32 = 0;
+ var c : usize = 0;
+ while (c < (LUT_NL[li] as usize)) {
+ if (c > 0) { n = put(INPUT[0..], n, 10); }
+ n = raw(INPUT[0..], n, FILE_BUF[(LUT_LO[li * W_LUT_LINES + c] as usize)..(LUT_HI[li * W_LUT_LINES + c] as usize)]);
+ c = c + 1;
+ }
+ if (n == OVER) { n = 0; }
+ set_input(n);
+ return 0;
+}
+
+; --- the game ------------------------------------------------------------------------------------------------
+fn rnd_next() -> f64 {
+ var x : u64 = RNG;
+ x = x ^ ((x << 13) & 4294967295);
+ x = x ^ (x >> 17);
+ x = x ^ ((x << 5) & 4294967295);
+ RNG = x;
+ return (x as f64) / 4294967296.0;
+}
+
+/// Math.floor(Math.random() * m)
+fn rnd_below(m: u32) -> u32 {
+ return (rnd_next() * (m as f64)) as u32;
+}
+
+pub var POOL : [512]u32 = [0; 512];
+
+/// The sort of shuffle(): Fisher-Yates from the end, over POOL[0..n).
+fn shuffle_pool(n: usize) -> u32 {
+ var i : usize = n;
+ while (i > 1) {
+ i = i - 1;
+ const j : usize = rnd_below((i + 1) as u32) as usize;
+ const t : u32 = POOL[i];
+ POOL[i] = POOL[j];
+ POOL[j] = t;
+ }
+ return 0;
+}
+
+fn eq_low(s: []const u8, w: []const u8) -> bool {
+ if (s.len != w.len) { return false; }
+ var k : usize = 0;
+ while (k < s.len) {
+ var want : u8 = w[k];
+ if (want >= 65 and want <= 90) { want = want + 32; }
+ if (s[k] != want) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The named classes of the design's histogram (keys that do not start "other/"), in order.
+fn build_named() -> u32 {
+ NM_N = 0;
+ const d : []const u8 = dss();
+ const hist : i64 = obj_get(d, root(d), "hist");
+ const hn : u32 = arr_len(d, hist);
+ var i : u32 = 0;
+ var off : u32 = 0;
+ while (i < hn and NM_N < 128) {
+ const e : i64 = arr_at(d, hist, i);
+ const ks : i64 = arr_at(d, e, 0);
+ const kl : u32 = str_len(d, ks);
+ if (kind(d, ks) == J_STRING and off + kl <= 16384) {
+ var k : u32 = 0;
+ while (k < kl) {
+ NM_TXT[(off + k) as usize] = str_byte(d, ks, k);
+ k = k + 1;
+ }
+ const key : []const u8 = NM_TXT[(off as usize)..((off + kl) as usize)];
+ if (!starts_s(key, "other/")) {
+ var p : usize = 0;
+ while (p < key.len and key[p] != 47) { p = p + 1; }
+ var idv : u32 = 255;
+ var w : u32 = 0;
+ while (w < 21) {
+ if (eq_low(key[0..p], id_name(w))) { idv = w; }
+ w = w + 1;
+ }
+ NM_ID[NM_N] = idv;
+ var nv : i64 = W_NAN_V;
+ if (p < key.len) {
+ var q : usize = p + 1;
+ while (q < key.len and key[q] != 47) { q = q + 1; }
+ nv = num_part(key, p + 1, q, true);
+ }
+ NM_NUM[NM_N] = nv;
+ NM_OFF[NM_N] = off;
+ NM_LEN[NM_N] = kl;
+ NM_N = NM_N + 1;
+ off = off + kl;
+ }
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+/// Is the label in the text w (length wl) already among the first cnt labels of round r?
+fn label_seen(r: usize, cnt: usize, w: []const u8) -> bool {
+ var j : usize = 0;
+ while (j < cnt) {
+ const base : usize = (r * 5 + j) * 256;
+ const ln : usize = G_LEN[r * 5 + j] as usize;
+ if (eq_s(G_TXT[base..(base + ln)], w)) { return true; }
+ j = j + 1;
+ }
+ return false;
+}
+
+fn label_set(r: usize, j: usize, w: []const u8) -> u32 {
+ const base : usize = (r * 5 + j) * 256;
+ var k : usize = 0;
+ while (k < w.len and k < 256) {
+ G_TXT[base + k] = w[k];
+ k = k + 1;
+ }
+ G_LEN[r * 5 + j] = k as u32;
+ return 0;
+}
+
+/// tryAdd(cands, upTo) for round r with the candidates CAND[0..m): shuffle a copy, then add the new names.
+fn try_add(r: usize, m: usize, up_to: usize) -> u32 {
+ // the shuffle consumes the same random numbers whatever happens next
+ var i : usize = m;
+ while (i > 1) {
+ i = i - 1;
+ const j : usize = rnd_below((i + 1) as u32) as usize;
+ const t : u32 = CAND[i];
+ CAND[i] = CAND[j];
+ CAND[j] = t;
+ }
+ var x : usize = 0;
+ while (x < m) {
+ if ((G_NL[r] as usize) >= up_to) { return 0; }
+ const ci : usize = CAND[x] as usize;
+ const off : usize = NM_OFF[ci] as usize;
+ const nm : u32 = class_name_key(W7[0..], 0, NM_TXT[off..(off + (NM_LEN[ci] as usize))]);
+ if (!label_seen(r, G_NL[r] as usize, W7[0..(nm as usize)])) {
+ if (G_NL[r] < 5) {
+ label_set(r, G_NL[r] as usize, W7[0..(nm as usize)]);
+ G_NL[r] = G_NL[r] + 1;
+ }
+ }
+ x = x + 1;
+ }
+ return 0;
+}
+
+/// newGame()
+fn new_game() -> u32 {
+ const d : []const u8 = dss();
+ const rounds : i64 = obj_get(d, root(d), "rounds");
+ var rn : usize = arr_len(d, rounds) as usize;
+ if (rn > 512) { rn = 512; }
+ var i : usize = 0;
+ while (i < rn) { POOL[i] = i as u32; i = i + 1; }
+ shuffle_pool(rn);
+ var pick : usize = K_GAME_ROUNDS as usize;
+ if (pick > rn) { pick = rn; }
+ build_named();
+ var r : usize = 0;
+ while (r < pick) {
+ G_IDX[r] = POOL[r];
+ const item : i64 = arr_at(d, rounds, POOL[r]);
+ const init : i64 = obj_get(d, item, "init");
+ const il : u32 = jstr_copy(W1[0..], d, init);
+ // BigInt('0x' + init) as 64 bits: the last 16 hex digits
+ var bits : u64 = 0;
+ var from : usize = 0;
+ if (il > 16) { from = (il as usize) - 16; }
+ var h : usize = from;
+ while (h < (il as usize)) {
+ bits = bits * 16 + (hexv(W1[h]) as u64);
+ h = h + 1;
+ }
+ G_BITS[r] = bits;
+ classify(bits);
+ const cid : u32 = cls_id();
+ const cn : u32 = cls_n();
+ const an : u32 = class_name_cls(W2[0..], 0);
+ G_NL[r] = 0;
+ label_set(r, 0, W2[0..(an as usize)]);
+ G_NL[r] = 1;
+ // one near miss in the same family, one with the same number of inputs, the rest from anywhere
+ var m : usize = 0;
+ var q : usize = 0;
+ while (q < NM_N) {
+ if (NM_ID[q] == cid) { CAND[m] = q as u32; m = m + 1; }
+ q = q + 1;
+ }
+ try_add(r, m, 2);
+ m = 0;
+ q = 0;
+ while (q < NM_N) {
+ if (NM_NUM[q] == (cn as i64) and NM_ID[q] != cid) { CAND[m] = q as u32; m = m + 1; }
+ q = q + 1;
+ }
+ try_add(r, m, 3);
+ m = 0;
+ q = 0;
+ while (q < NM_N) { CAND[m] = q as u32; m = m + 1; q = q + 1; }
+ try_add(r, m, K_GAME_CHOICES as usize);
+ // labels: shuffle(out)
+ var o : usize = 0;
+ while (o < 5) { G_ORD[r * 5 + o] = o as u32; o = o + 1; }
+ var z : usize = G_NL[r] as usize;
+ while (z > 1) {
+ z = z - 1;
+ const j : usize = rnd_below((z + 1) as u32) as usize;
+ const t : u32 = G_ORD[r * 5 + z];
+ G_ORD[r * 5 + z] = G_ORD[r * 5 + j];
+ G_ORD[r * 5 + j] = t;
+ }
+ r = r + 1;
+ }
+ G_N = pick as u32;
+ G_AT = 0;
+ G_RIGHT = 0;
+ G_PICK = -1;
+ GAME_ON = true;
+ return 0;
+}
+
+fn emit_game_sample(o: []u8, n0: u32) -> u32 {
+ const d : []const u8 = dss();
+ const dr : i64 = root(d);
+ var n : u32 = raw(o, n0, "
");
+ const rounds : i64 = obj_get(d, dr, "rounds");
+ const a : u32 = fnum(W1[0..], 0, arr_len(d, rounds) as f64);
+ var per : [8]u8 = [0; 8];
+ const pl : u32 = dec_to(per[0..], K_ROUNDS_PER_CLASS as u64);
+ const ot : u32 = fnum(W3[0..], 0, num_f64(d, obj_get(d, dr, "luts")) - num_f64(d, obj_get(d, dr, "named")));
+ n = pf3(o, n, true, SAY_GAME_SAMPLE, "pool", W1[0..(a as usize)], "per", per[0..(pl as usize)], "other", W3[0..(ot as usize)]);
+ return raw(o, n, "
");
+}
+
+fn emit_game(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ if (!GAME_ON) { return raw(o, n, "
"); }
+ var sc : [24]u8 = [0; 24];
+ var s2 : [24]u8 = [0; 24];
+ var s3 : [24]u8 = [0; 24];
+ if (G_AT >= G_N) {
+ const l1 : u32 = dec_to(sc[0..], G_RIGHT as u64);
+ const l2 : u32 = dec_to(s2[0..], G_N as u64);
+ n = raw(o, n, "
");
+ n = pf2(o, n, true, SAY_GAME_DONE, "right", sc[0..(l1 as usize)], "rounds", s2[0..(l2 as usize)]);
+ n = raw(o, n, "
");
+ btn_mark(302);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_GAME_AGAIN);
+ n = raw(o, n, "");
+ n = emit_game_sample(o, n);
+ return raw(o, n, "");
+ }
+ const r : usize = G_AT as usize;
+ const answered : bool = G_PICK >= 0;
+ const l1 : u32 = dec_to(sc[0..], (G_AT + 1) as u64);
+ const l2 : u32 = dec_to(s2[0..], G_N as u64);
+ const l3 : u32 = dec_to(s3[0..], G_RIGHT as u64);
+ n = raw(o, n, "
");
+ n = pf3(o, n, true, SAY_GAME_SCORE, "round", sc[0..(l1 as usize)], "rounds", s2[0..(l2 as usize)], "right", s3[0..(l3 as usize)]);
+ n = raw(o, n, "
64'h");
+ const d : []const u8 = dss();
+ const item : i64 = arr_at(d, obj_get(d, root(d), "rounds"), G_IDX[r]);
+ n = jtext_to(o, n, d, obj_get(d, item, "init"));
+ n = raw(o, n, "
");
+ const lc : u32 = jstr_copy(W4[0..], d, obj_get(d, item, "loc"));
+ n = pf1(o, n, true, SAY_GAME_LOC, "loc", W4[0..(lc as usize)]);
+ n = raw(o, n, "
");
+ var b : u32 = 64;
+ while (b > 0) {
+ b = b - 1;
+ if (bit_of(G_BITS[r], b) == 1) { n = raw(o, n, ""); } else { n = raw(o, n, ""); }
+ }
+ n = raw(o, n, "
");
+ var k : usize = 0;
+ while (k < (G_NL[r] as usize)) {
+ const slot : usize = G_ORD[r * 5 + k] as usize;
+ btn_mark(200 + (k as u32));
+ n = raw(o, n, "");
+ const base : usize = (r * 5 + slot) * 256;
+ n = text(o, n, G_TXT[base..(base + (G_LEN[r * 5 + slot] as usize))]);
+ n = raw(o, n, "");
+ k = k + 1;
+ }
+ n = raw(o, n, "
");
+ if (answered) {
+ const pslot : usize = G_ORD[r * 5 + (G_PICK as usize)] as usize;
+ if (pslot == 0) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_GAME_RIGHT);
+ } else {
+ n = raw(o, n, "
");
+ const base0 : usize = (r * 5) * 256;
+ n = pf1(o, n, true, SAY_GAME_WRONG, "answer", G_TXT[base0..(base0 + (G_LEN[r * 5] as usize))]);
+ }
+ n = raw(o, n, "
");
+ btn_mark(300);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_GAME_NEXT);
+ n = raw(o, n, "");
+ btn_mark(301);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_GAME_OPEN);
+ n = raw(o, n, "
");
+ }
+ n = emit_game_sample(o, n);
+ return raw(o, n, "");
+}
+
+/// openInDecoder(r): the round's LUT as a FASM line when its place parses, else as 64'h...
+fn open_in_decoder() -> u32 {
+ const r : usize = G_AT as usize;
+ const d : []const u8 = dss();
+ const item : i64 = arr_at(d, obj_get(d, root(d), "rounds"), G_IDX[r]);
+ const lc : u32 = jstr_copy(W4[0..], d, obj_get(d, item, "loc"));
+ var n : u32 = 0;
+ if (loc_valid(W4[0..(lc as usize)])) {
+ n = raw(INPUT[0..], n, W4[0..(lc as usize)]);
+ n = raw(INPUT[0..], n, "LUT.INIT[63:0] = 64'b");
+ init_bin(G_BITS[r]);
+ var k : usize = 0;
+ while (k < OUT_N) {
+ n = put(INPUT[0..], n, OUT[k]);
+ k = k + 1;
+ }
+ } else {
+ n = raw(INPUT[0..], n, "64'h");
+ n = jfield_to(INPUT[0..], n, d, obj_get(d, item, "init"));
+ }
+ if (n == OVER) { n = 0; }
+ set_input(n);
+ TAB = 0;
+ return 0;
+}
+
+; --- our design ----------------------------------------------------------------------------------------------
+/// (100 * n / max).toFixed(1): the digits of round-half-up of ten times the double's exact value.
+fn fixed1(o: []u8, n0: u32, x: f64) -> u32 {
+ if (x != x) { return raw(o, n0, "NaN"); }
+ if (x > 1.0e15) { return raw(o, n0, "Infinity"); }
+ var m : f64 = x;
+ var k : u32 = 0;
+ while (m != ((m as u64) as f64) and k < 80) {
+ m = m * 2.0;
+ k = k + 1;
+ }
+ var v : u64 = 0;
+ if (k <= 62) {
+ const t : u64 = (m as u64) * 10;
+ if (k == 0) { v = t; } else { v = (t + ((1 as u64) << (k - 1))) >> k; }
+ }
+ var n : u32 = dnum(o, n0, v / 10);
+ n = put(o, n, 46);
+ return dnum(o, n, v % 10);
+}
+
+fn hist_len(mode: u32) -> u32 {
+ if (mode == 0) {
+ const d : []const u8 = dss();
+ return arr_len(d, obj_get(d, root(d), "hist"));
+ }
+ return HK_N as u32;
+}
+
+/// Entry i of the histogram: its key as plain text into KB; the length. Its count in HCOUNT.
+pub var HCOUNT : f64 = 0.0;
+
+fn hist_entry(mode: u32, i: u32) -> u32 {
+ if (mode == 0) {
+ const d : []const u8 = dss();
+ const e : i64 = arr_at(d, obj_get(d, root(d), "hist"), i);
+ HCOUNT = num_f64(d, arr_at(d, e, 1));
+ return jstr_copy(KB[0..], d, arr_at(d, e, 0));
+ }
+ const ix : usize = HK_ORD[i as usize] as usize;
+ HCOUNT = HK_CNT[ix] as f64;
+ return raw(KB[0..], 0, HK[(HK_OFF[ix] as usize)..((HK_OFF[ix] + HK_LEN[ix]) as usize)]);
+}
+
+fn bar_row(o: []u8, n0: u32, label: []const u8, cnt: f64, cls: []const u8, maxv: f64, total: f64) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, label);
+ n = raw(o, n, "");
+ n = fnum(o, n, cnt);
+ n = raw(o, n, " ");
+ n = fixed1(o, n, (100.0 * cnt) / total);
+ return raw(o, n, "%
");
+ n = text(o, n, SAY_HIST_OURS);
+ n = raw(o, n, "
");
+ }
+ n = emit_bars(o, n, 0, luts);
+ if (FILE_ON) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HIST_YOURS);
+ n = raw(o, n, "
");
+ const b1 : u32 = fnum(W1[0..], 0, LUT_N as f64);
+ const b2 : u32 = fnum(W2[0..], 0, FILE_NAMED as f64);
+ n = pf3(o, n, true, SAY_YOUR_FILE, "name", FILE_NAME[0..FILE_NAME_N], "luts", W1[0..(b1 as usize)], "named", W2[0..(b2 as usize)]);
+ n = raw(o, n, "
");
+ var total : f64 = LUT_N as f64;
+ if (total < 1.0) { total = 1.0; }
+ n = emit_bars(o, n, 1, total);
+ }
+ n = raw(o, n, "
");
+ const c1 : u32 = jnum_to(W1[0..], 0, d, obj_get(d, dr, "pinsAgree"));
+ const c2 : u32 = jnum_to(W2[0..], 0, d, obj_get(d, dr, "luts"));
+ n = pf2(o, n, true, SAY_FACT_PINS, "agree", W1[0..(c1 as usize)], "luts", W2[0..(c2 as usize)]);
+ n = raw(o, n, "
");
+ const e1 : u32 = jnum_to(W1[0..], 0, d, obj_get(d, dr, "o5Used"));
+ const e2 : u32 = jnum_to(W2[0..], 0, d, obj_get(d, dr, "ramSrl"));
+ n = pf2(o, n, true, SAY_FACT_O5, "o5", W1[0..(e1 as usize)], "ram", W2[0..(e2 as usize)]);
+ n = raw(o, n, "
");
+ const g1 : u32 = jnum_to(W1[0..], 0, d, obj_get(d, dr, "inexact"));
+ const g2 : u32 = jnum_to(W2[0..], 0, d, obj_get(d, dr, "maxTerms"));
+ n = pf2(o, n, true, SAY_FACT_QM, "inexact", W1[0..(g1 as usize)], "terms", W2[0..(g2 as usize)]);
+ n = raw(o, n, "
");
+ // yosys: the text before " (" ; prjxray: the first word
+ const y1 : u32 = jstr_copy(W1[0..], d, obj_get(d, tools, "yosys"));
+ var ycut : usize = 0;
+ while (ycut < (y1 as usize) and W1[ycut] != 40) { ycut = ycut + 1; }
+ var ye : usize = ycut;
+ while (ye > 0 and (W1[ye - 1] == 32 or (W1[ye - 1] >= 9 and W1[ye - 1] <= 13))) { ye = ye - 1; }
+ const y2 : u32 = jstr_copy(W2[0..], d, obj_get(d, src, "yosys"));
+ const y3 : u32 = jstr_copy(W3[0..], d, obj_get(d, tools, "nextpnr"));
+ const y4 : u32 = jstr_copy(W4[0..], d, obj_get(d, tools, "prjxray"));
+ var y4e : usize = 0;
+ while (y4e < (y4 as usize) and W4[y4e] != 32) { y4e = y4e + 1; }
+ const y5 : u32 = jstr_copy(W5[0..], d, obj_get(d, tools, "db"));
+ n = pf8(o, n, true, SAY_FACT_TOOLS, "yosys", W1[0..ye], "ycmd", W2[0..(y2 as usize)], "nextpnr", W3[0..(y3 as usize)], "prjxray", W4[0..y4e], "db", W5[0..(y5 as usize)], "", "", "", "", "", "");
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_LIMITS);
+ n = raw(o, n, "
");
+ if (n == OVER) { n = 0; }
+ DESIGN_N = n;
+ return 0;
+}
+
+; --- events -----------------------------------------------------------------------------------------------------
+fn show_tab(i: u32) -> u32 {
+ TAB = i;
+ if (i == 1 and !GAME_ON and DATA_ST == 1) { new_game(); }
+ if (i == 2) { render_design(); }
+ return 0;
+}
+
+/// start(): both data files are in.
+fn start_page() -> u32 {
+ DATA_ST = 1;
+ EX_ON = 6;
+ const n : u32 = example_text(6);
+ INPUT_N = n;
+ EFF_INPUT = true;
+ read_input();
+ return 0;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ PX_N = 0;
+ DS_N = 0;
+ PX_ST = 0;
+ DS_ST = 0;
+ DATA_ST = 0;
+ FAIL_FILE = 0;
+ TAB = 0;
+ EX_ON = -1;
+ DRAGGING = false;
+ EFF_INPUT = false;
+ HAS_BITS = false;
+ BITS = 0;
+ ORIG = 0;
+ FLIPPED = -1;
+ ASSUMED = false;
+ INPUT_N = 0;
+ LOC_OK = false;
+ GAME_ON = false;
+ G_N = 0;
+ G_AT = 0;
+ G_RIGHT = 0;
+ G_PICK = -1;
+ FILE_ON = false;
+ FILE_NAME_N = 0;
+ FILE_N = 0;
+ LUT_N = 0;
+ HK_N = 0;
+ DESIGN_N = 0;
+ PI_N = 0;
+ PI_BAD_N = 0;
+ PI_HAS_BITS = false;
+ PI_LUT_SET = false;
+ parse_loc(K_DEFAULT_LOC);
+ return 0;
+}
+
+pub fn w_seed(s: u32) -> u32 {
+ RNG = s as u64;
+ if (RNG == 0) { RNG = 1; }
+ return 0;
+}
+
+fn data_ready() -> u32 {
+ if (PX_ST == 1 and DS_ST == 1) { start_page(); }
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ EFF_INPUT = false;
+ if (DATA_ST != 0) { return 0; }
+ var ok_data : bool = s.len <= (W_DATA_CAP as usize);
+ if (ok_data) {
+ if (i == 0) {
+ var k : usize = 0;
+ while (k < s.len) { PXD[k] = s[k]; k = k + 1; }
+ PX_N = s.len as u32;
+ ok_data = kind(PXD[0..(PX_N as usize)], root(PXD[0..(PX_N as usize)])) == J_OBJECT;
+ } else {
+ var k2 : usize = 0;
+ while (k2 < s.len) { DSD[k2] = s[k2]; k2 = k2 + 1; }
+ DS_N = s.len as u32;
+ ok_data = kind(DSD[0..(DS_N as usize)], root(DSD[0..(DS_N as usize)])) == J_OBJECT;
+ }
+ }
+ if (!ok_data) { return w_data_failed(i); }
+ if (i == 0) { PX_ST = 1; } else { DS_ST = 1; }
+ data_ready();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ EFF_INPUT = false;
+ if (DATA_ST != 0) { return 0; }
+ DATA_ST = 2;
+ FAIL_FILE = i;
+ return 0;
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ EFF_INPUT = false;
+ if (i != 0) { return 0; }
+ var n : usize = s.len;
+ if (n > (W_IN_CAP as usize)) { n = W_IN_CAP as usize; }
+ var k : usize = 0;
+ while (k < n) { INPUT[k] = s[k]; k = k + 1; }
+ INPUT_N = n as u32;
+ read_input();
+ return 0;
+}
+
+pub fn w_press(i: u32) -> u32 {
+ EFF_INPUT = false;
+ if (i >= BTN_N or i >= 512) { return 0; }
+ const a : u32 = BTN_ACT[i as usize];
+ if (a < 3) { show_tab(a); }
+ if (a >= 10 and a < 18) {
+ const n : u32 = example_text(a - 10);
+ set_input(n);
+ EX_ON = (a - 10) as i32;
+ }
+ if (a == 20) { show_file_lut((FILE_IDX as i64) - 1); }
+ if (a == 21) { show_file_lut((FILE_IDX as i64) + 1); }
+ if (a == 30) {
+ BITS = ORIG;
+ FLIPPED = -1;
+ }
+ if (a >= 100 and a < 164) {
+ BITS = BITS ^ ((1 as u64) << (a - 100));
+ FLIPPED = (a - 100) as i32;
+ }
+ if (a >= 200 and a < 205) {
+ if (G_PICK < 0 and GAME_ON) {
+ G_PICK = (a - 200) as i32;
+ if (G_ORD[(G_AT as usize) * 5 + ((a - 200) as usize)] == 0) { G_RIGHT = G_RIGHT + 1; }
+ }
+ }
+ if (a == 300) {
+ G_AT = G_AT + 1;
+ G_PICK = -1;
+ }
+ if (a == 301) { open_in_decoder(); }
+ if (a == 302) { new_game(); }
+ return 0;
+}
+
+pub fn w_file(z: u32, name: []const u8, size: u32) -> u32 {
+ EFF_INPUT = false;
+ if (size > W_FILE_CAP) { return 0; }
+ var n : usize = name.len;
+ if (n > 1000) { n = 1000; }
+ var k : usize = 0;
+ while (k < n) { FILE_NAME[k] = name[k]; k = k + 1; }
+ FILE_NAME_N = n;
+ return 1;
+}
+
+pub fn w_file_data(z: u32, s: []const u8) -> u32 {
+ EFF_INPUT = false;
+ const from : usize = bom_skip(s);
+ if (s.len - from > (W_FILE_CAP as usize)) { return 0; }
+ var k : usize = from;
+ while (k < s.len) { FILE_BUF[k - from] = s[k]; k = k + 1; }
+ FILE_N = (s.len - from) as u32;
+ take_file(FILE_N);
+ show_file_lut(0);
+ if (TAB == 2) { render_design(); }
+ return 0;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ EFF_INPUT = false;
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ EFF_INPUT = false;
+ const want : bool = over != 0;
+ if (want == DRAGGING) { return 0; }
+ DRAGGING = want;
+ return 1;
+}
+
+/// The offset after a leading byte order mark, which FileReader.readAsText drops.
+fn bom_skip(s: []const u8) -> usize {
+ if (s.len >= 3 and s[0] == 239 and s[1] == 187 and s[2] == 191) { return 3; }
+ return 0;
+}
+
+/// Effects after the last event: the paste box takes the text the page put in INPUT.
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (EFF_INPUT and k == 0) {
+ if (((INPUT_N + 2) as usize) > dst.len) { return 0; }
+ dst[0] = 118;
+ dst[1] = 0;
+ var i : usize = 0;
+ while (i < (INPUT_N as usize)) {
+ dst[2 + i] = INPUT[i];
+ i = i + 1;
+ }
+ return INPUT_N + 2;
+ }
+ return 0;
+}
+
+; --- the page ---------------------------------------------------------------------------------------------------------
+fn emit_fileline(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, ""); }
+ n = raw(o, n, ">
");
+ var b1 : [24]u8 = [0; 24];
+ var b2 : [24]u8 = [0; 24];
+ if (LUT_N == 0) {
+ n = pf8(o, n, true, SAY_FILE_LUTS, "name", FILE_NAME[0..FILE_NAME_N], "n", "0", "i", "0", "loc", "", "", "", "", "", "", "", "", "");
+ return raw(o, n, "
");
+ }
+ const l1 : u32 = fnum(b1[0..], 0, LUT_N as f64);
+ const l2 : u32 = fnum(b2[0..], 0, (FILE_IDX + 1) as f64);
+ const li : usize = FILE_IDX as usize;
+ const loc : []const u8 = FILE_BUF[(LUT_LOC_LO[li] as usize)..((LUT_LOC_LO[li] + LUT_LOC_N[li]) as usize)];
+ n = pf8(o, n, true, SAY_FILE_LUTS, "name", FILE_NAME[0..FILE_NAME_N], "n", b1[0..(l1 as usize)], "i", b2[0..(l2 as usize)], "loc", loc, "", "", "", "", "", "", "", "");
+ n = raw(o, n, "
");
+ btn_mark(20);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_PREV);
+ n = raw(o, n, "");
+ btn_mark(21);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_NEXT);
+ n = raw(o, n, "
");
+ return n;
+}
+
+fn panel_open(o: []u8, n0: u32, ix: u32) -> u32 {
+ var n : u32 = raw(o, n0, "");
+}
+
+/// The whole page, as tool.js's root.replaceChildren(...) and the render calls left it. 0 on overflow.
+pub fn w_render(dst: []u8) -> u32 {
+ BTN_N = 0;
+ var n : u32 = raw(dst, 0, "
");
+ } else {
+ n = raw(dst, n, ">");
+ if (DATA_ST == 0) {
+ n = text(dst, n, SAY_LOADING);
+ } else {
+ if (FAIL_FILE == 0) { n = pf1(dst, n, true, SAY_LOAD_FAILED, "file", K_DATA_PRJXRAY); } else { n = pf1(dst, n, true, SAY_LOAD_FAILED, "file", K_DATA_DESIGN); }
+ }
+ n = raw(dst, n, "");
+ }
+ n = panel_open(dst, n, 0);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_INPUT_LABEL);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_EXAMPLES);
+ n = raw(dst, n, "");
+ i = 0;
+ while (i < 8) {
+ btn_mark(10 + i);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_EXAMPLE_NAMES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = emit_fileline(dst, n);
+ n = emit_out(dst, n);
+ n = raw(dst, n, "");
+ n = panel_open(dst, n, 1);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_GAME_INTRO);
+ n = raw(dst, n, "
");
+ n = emit_game(dst, n);
+ n = raw(dst, n, "");
+ n = panel_open(dst, n, 2);
+ n = raw(dst, n, "
");
+ var q : usize = 0;
+ while (q < (DESIGN_N as usize)) {
+ n = put(dst, n, DESIGN_HTML[q]);
+ q = q + 1;
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names its imported functions
+/// mention. This anchor mentions every helper the writer and the JSON reader call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = put(o, 0, 0);
+ n = put_escaped(o, n, 0);
+ n = n + OVER + ((H_LT + H_GT + H_AMP + H_QUOTE + H_APOS + H_SPACE + H_SLASH + H_EQ) as u32);
+ n = n + (name_is(s, 0, 0, s) as u32) + (is_word_byte(0) as u32);
+ n = n + (skip_ws(s, 0) as u32) + (string_end(s, 0) as u32) + (value_end(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32) + hex4(s, 0) + escape_at(s, 0, 0) + cp_utf8_len(0) + (cp_utf8_byte(0, 0) as u32);
+ n = n + (SPAN as u32) + (J_NONE as u32) + (J_OBJECT as u32) + (J_ARRAY as u32) + (J_STRING as u32) + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32) + (J_FALSE as u32) + (J_NULL as u32);
+ n = n + (NO_SPAN as u32) + (jspan(0, 0) as u32);
+ return n;
+}
+
+; --- Tests of the native page ------------------------------------------------------------------------------
+; The decoder's own tests (in lut_explain_logic.t27) run on the same code; these hold the page. The data
+; files here are small stand-ins written by this test: one CLB tile type with a twin, five histogram rows,
+; three game rounds.
+
+pub var T_OUT : [262144]u8 = [0; 262144];
+pub var T_TXT : [8192]u8 = [0; 8192];
+pub var T_N : usize = 0;
+
+fn contains(s: []const u8, w: []const u8) -> bool {
+ if (w.len > s.len) { return false; }
+ var p : usize = 0;
+ while (p + w.len <= s.len) {
+ if (starts_s(s[p..s.len], w)) { return true; }
+ p = p + 1;
+ }
+ return false;
+}
+
+fn render() -> u32 {
+ return w_render(T_OUT[0..]);
+}
+
+fn has(n: u32, w: []const u8) -> bool {
+ return contains(T_OUT[0..(n as usize)], w);
+}
+
+fn t_reset() -> usize {
+ T_N = 0;
+ return 0;
+}
+
+fn t_line(s: []const u8) -> usize {
+ var k : usize = 0;
+ while (k < s.len) {
+ T_TXT[T_N] = s[k];
+ T_N = T_N + 1;
+ k = k + 1;
+ }
+ T_TXT[T_N] = 10;
+ T_N = T_N + 1;
+ return T_N;
+}
+
+/// Press the button whose action is a, as the page numbered it in its last render.
+fn press_act(a: u32) -> u32 {
+ render();
+ var i : u32 = 0;
+ while (i < BTN_N) {
+ if (BTN_ACT[i as usize] == a) {
+ w_press(i);
+ return 1;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+fn load_ok() -> u32 {
+ w_init("");
+ w_seed(1);
+ w_data(0, W_PX_T);
+ w_data(1, W_DS_T);
+ return 0;
+}
+
+pub const W_PX_T : str = "{\"schema\":1,\"tiles\":{\"CLBLM_L\":{\"sites\":[{\"name\":\"SLICEM_X0\",\"type\":\"SLICEM\",\"prefix\":\"CLBLM_M_\"},{\"name\":\"SLICEL_X1\",\"type\":\"SLICEL\",\"prefix\":\"CLBLM_L_\"}],\"features\":{\"SLICEM_X0.NOCLKINV\":[\"01_02\",\"!03_04\"],\"SLICEM_X0.AFF.ZINI\":[\"31_06\"]},\"init\":{\"SLICEM_X0.C\":[\"20_0\",\"21_1\",\"22_2\",\"23_3\",\"24_4\",\"25_5\",\"26_6\",\"27_7\",\"28_8\",\"29_9\",\"30_10\",\"31_11\",\"32_12\",\"33_13\",\"34_14\",\"35_15\",\"36_0\",\"37_1\",\"38_2\",\"39_3\",\"40_4\",\"41_5\",\"42_6\",\"43_7\",\"44_8\",\"45_9\",\"46_10\",\"47_11\",\"48_12\",\"49_13\",\"50_14\",\"51_15\",\"52_0\",\"53_1\",\"54_2\",\"55_3\",\"56_4\",\"57_5\",\"58_6\",\"59_7\",\"60_8\",\"61_9\",\"62_10\",\"63_11\",\"64_12\",\"65_13\",\"66_14\",\"67_15\",\"68_0\",\"69_1\",\"70_2\",\"71_3\",\"72_4\",\"73_5\",\"74_6\",\"75_7\",\"76_8\",\"77_9\",\"78_10\",\"79_11\",\"80_12\",\"81_13\",\"82_14\",\"83_15\"],\"SLICEL_X1.A\":[\"20_0\",\"21_1\",\"22_2\",\"23_3\",\"24_4\",\"25_5\",\"26_6\",\"27_7\",\"28_8\",\"29_9\",\"30_10\",\"31_11\",\"32_12\",\"33_13\",\"34_14\",\"35_15\",\"36_0\",\"37_1\",\"38_2\",\"39_3\",\"40_4\",\"41_5\",\"42_6\",\"43_7\",\"44_8\",\"45_9\",\"46_10\",\"47_11\",\"48_12\",\"49_13\",\"50_14\",\"51_15\",\"52_0\",\"53_1\",\"54_2\",\"55_3\",\"56_4\",\"57_5\",\"58_6\",\"59_7\",\"60_8\",\"61_9\",\"62_10\",\"63_11\",\"64_12\",\"65_13\",\"66_14\",\"67_15\",\"68_0\",\"69_1\",\"70_2\",\"71_3\",\"72_4\",\"73_5\",\"74_6\",\"75_7\",\"76_8\",\"77_9\",\"78_10\",\"79_11\",\"80_12\",\"81_13\",\"82_14\",\"83_15\"]},\"ppips\":{\"CLBLM_M_C.CLBLM_M_C1\":\"hint\",\"CLBLM_L_B.CLBLM_L_B2\":\"always\"}},\"CLBLM_R\":{\"sameAs\":\"CLBLM_L\"}}}";
+pub const W_DS_T : str = "{\"luts\":12,\"distinct\":9,\"named\":8,\"pinsAgree\":12,\"inexact\":0,\"maxTerms\":4,\"o5Used\":0,\"ramSrl\":0,\"hist\":[[\"xor/2/0\",4],[\"and/3/0\",2],[\"mux2/3/0\",1],[\"xorwith/5/2/and/3/0\",1],[\"other/5/0\",4]],\"rounds\":[{\"loc\":\"CLBLM_L_X1Y2.SLICEM_X0.C\",\"init\":\"6996966996696996\"},{\"loc\":\"CLBLM_L_X1Y2.SLICEM_X0.C\",\"init\":\"8000000000000000\"},{\"loc\":\"CLBLM_R_X3Y4.SLICEL_X1.A\",\"init\":\"CACACACACACACACA\"}],\"source\":{\"part\":\"xc7\",\"tools\":{\"yosys\":\"Yosys 1.2 (git abc)\",\"nextpnr\":\"np 9\",\"prjxray\":\"c9f 2025 x\",\"db\":\"db1\"},\"yosys\":\"synth_x\"}}";
+
+test the_page_opens_loading_with_the_tabs_the_examples_and_two_hidden_panels {
+ w_init("");
+ const n : u32 = render();
+ assert n > 1500;
+ assert has(n, "
CLBLM_L_X10Y248.SLICEM_X0.CLUT.INIT[63:0] = 64'b0110100110010110");
+ assert has(n, "The paste names no LUT, so the line uses CLBLM_L_X10Y248.SLICEM_X0.C, a real SLICEM LUT of our design.");
+ assert has(n, "32'h96696996 XOR5: parity of 5 inputs");
+ assert has(n, "Both halves are the same function") == false;
+ assert has(n, "The halves differ: A6 picks between them.");
+ assert has(n, "In our design 0 of 12 LUTs route O5");
+ w_input(0, "");
+ assert HAS_BITS == false;
+ n = render();
+ assert has(n, "
Nothing to read yet: paste an INIT or a FASM LUT line.
");
+ assert has(n, "Rounds come from 3 INITs: at most 4 distinct ones per named function, so rare functions turn up as often as XOR2. The 4 LUTs with no short name are left out.");
+ assert G_NL[0] >= 2;
+ var round : u32 = 0;
+ while (round < 3) {
+ // the right answer is always label slot 0; find where it was dealt
+ var k : u32 = 0;
+ while (k < G_NL[G_AT as usize]) {
+ if (G_ORD[(G_AT as usize) * 5 + (k as usize)] == 0) { press_act(200 + k); }
+ k = k + 1;
+ }
+ n = render();
+ assert has(n, "
Play again");
+ assert press_act(302) == 1;
+ assert G_AT == 0 and G_RIGHT == 0;
+ // a wrong pick
+ var j : u32 = 0;
+ while (j < G_NL[0]) {
+ if (G_ORD[j as usize] != 0) { press_act(200 + j); break; }
+ j = j + 1;
+ }
+ n = render();
+ assert has(n, "
No: it is ");
+ assert has(n, " is-wrong");
+ // open it in the decoder
+ assert press_act(301) == 1;
+ assert TAB == 0;
+ assert HAS_BITS;
+ assert EX_ON == -1;
+ assert EFF_INPUT;
+}
+
+test the_random_numbers_are_the_xorshift_the_original_was_given {
+ w_seed(1);
+ const r1 : u64 = (rnd_next() * 4294967296.0) as u64;
+ const r2 : u64 = (rnd_next() * 4294967296.0) as u64;
+ const r3 : u64 = (rnd_next() * 4294967296.0) as u64;
+ assert r1 == 270369;
+ assert r2 == 67634689;
+ assert r3 == 2647435461;
+ w_seed(0);
+ assert RNG == 1;
+}
+
+test widths_are_rounded_like_toFixed_of_the_exact_double {
+ var b : [32]u8 = [0; 32];
+ var n : u32 = fixed1(b[0..], 0, 12.25);
+ assert eq_s(b[0..(n as usize)], "12.3");
+ n = fixed1(b[0..], 0, 0.35);
+ assert eq_s(b[0..(n as usize)], "0.3");
+ n = fixed1(b[0..], 0, 100.0);
+ assert eq_s(b[0..(n as usize)], "100.0");
+ n = fixed1(b[0..], 0, 0.05);
+ assert eq_s(b[0..(n as usize)], "0.1");
+ n = fixed1(b[0..], 0, 0.15);
+ assert eq_s(b[0..(n as usize)], "0.1");
+ n = fixed1(b[0..], 0, 0.25);
+ assert eq_s(b[0..(n as usize)], "0.3");
+ n = fixed1(b[0..], 0, 1.45);
+ assert eq_s(b[0..(n as usize)], "1.4");
+ n = fixed1(b[0..], 0, 99.95);
+ assert eq_s(b[0..(n as usize)], "100.0");
+ n = fixed1(b[0..], 0, 0.049999999999999996);
+ assert eq_s(b[0..(n as usize)], "0.0");
+ n = fixed1(b[0..], 0, (52000.0) / 4738.0);
+ assert eq_s(b[0..(n as usize)], "11.0");
+ n = fixed1(b[0..], 0, 33.333333333333336);
+ assert eq_s(b[0..(n as usize)], "33.3");
+ n = fixed1(b[0..], 0, 0.0);
+ assert eq_s(b[0..(n as usize)], "0.0");
+}
+
+test class_keys_become_the_names_the_original_gives {
+ var b : [256]u8 = [0; 256];
+ var n : u32 = class_name_key(b[0..], 0, "xorwith/5/2/and/3/0");
+ assert eq_s(b[0..(n as usize)], "XOR of 2 inputs with (AND3)");
+ n = class_name_key(b[0..], 0, "foo/2/0");
+ assert eq_s(b[0..(n as usize)], "foo");
+ n = class_name_key(b[0..], 0, "xor");
+ assert eq_s(b[0..(n as usize)], "XORNaN: parity of NaN inputs");
+ n = class_name_key(b[0..], 0, "XOR/ 2/+3");
+ assert eq_s(b[0..(n as usize)], "XOR2: parity of 2 inputs");
+ n = class_name_key(b[0..], 0, "other/4/0");
+ assert eq_s(b[0..(n as usize)], "no short name, 4 inputs");
+ n = class_name_key(b[0..], 0, "mux2/3/0");
+ assert eq_s(b[0..(n as usize)], "2:1 mux");
+ n = class_name_key(b[0..], 0, "andx/3/-1");
+ assert eq_s(b[0..(n as usize)], "AND3 with -1 inverted");
+ assert num_part("12", 0, 2, true) == 12;
+ assert num_part(" 7 ", 0, 3, true) == 7;
+ assert num_part("", 0, 0, true) == 0;
+ assert num_part("1x", 0, 2, true) == W_NAN_V;
+ assert num_part("1", 0, 1, false) == W_NAN_V;
+}
+
+test placeholders_fill_from_pairs_and_unknown_ones_stay_and_escaping_is_a_choice {
+ var b : [128]u8 = [0; 128];
+ var n : u32 = pf8(b[0..], 0, false, "a {x} <{y}> {z} {x}", "x", "1&2", "y", "B", "", "", "", "", "", "", "", "", "", "", "", "");
+ assert eq_s(b[0..(n as usize)], "a 1&2 {z} 1&2");
+ n = pf8(b[0..], 0, true, "a {x} <{y}> {z}", "x", "1&2", "y", "B", "", "", "", "", "", "", "", "", "", "", "", "");
+ assert eq_s(b[0..(n as usize)], "a 1&2 <B> {z}");
+}
+
+test dragging_marks_the_drop_zone {
+ load_ok();
+ assert w_drag(0, 1) == 1;
+ assert w_drag(0, 1) == 0;
+ var n : u32 = render();
+ assert has(n, "");
+ assert w_drag(0, 0) == 1;
+ n = render();
+ assert has(n, "Or drop") == false;
+ assert has(n, "");
+}
From 88830418dffffa04c8cdd2054ddfe5f87a23e4ca Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 18:45:13 +0700
Subject: [PATCH 38/63] widgets: gf16-calc drawn natively -- 30292-byte wasm,
4544 DOM comparisons, 0 differences (Closes #10938)
specs/widgets/gf16_calc.t27: the trinity spec verbatim, a small bignum (16 registers of 640 limbs) for the
exact fractions tool.js kept in BigInt, the layout reader, the rounding, ternary and error digits, and the
page with its address and copy-link effects. 17 tests (11 new), t27b 17/17. 29 layout/address variants.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/gf16_calc.t27 | 2693 +++++++++++++++++++++++++++++++++++
1 file changed, 2693 insertions(+)
create mode 100644 specs/widgets/gf16_calc.t27
diff --git a/specs/widgets/gf16_calc.t27 b/specs/widgets/gf16_calc.t27
new file mode 100644
index 0000000000..d99077f25e
--- /dev/null
+++ b/specs/widgets/gf16_calc.t27
@@ -0,0 +1,2693 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/gf16-calc.t27 -- the GF16 calculator: one number, its 16 GoldenFloat bits, and the
+; same number in balanced ternary, trit by trit.
+; Source of truth for public/widgets/gf16-calc/ (gHashTag/trinity): every word on that page is a
+; SAY_* constant below and every number its logic needs is a K_* constant. tool.js holds neither.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader types a decimal number (or 16 raw bits as hex). The page rounds the
+; decimal exactly -- as a fraction of big integers, never through a binary64 first -- to the
+; nearest GF16 value, draws the sign, exponent and mantissa fields bit by bit, prints the decoded
+; value in full (every GF16 value is a finite binary fraction, so it has a finite decimal), and
+; the error of the rounding. Beside it the same number in balanced ternary: digits -1, 0, +1 and
+; K_TERNARY_FRAC_TRITS trits after the point. Then the round trip: the decoded value encoded again
+; gives the same 16 bits, and the trits read back give the same trits.
+
+; WHERE THE FORMAT COMES FROM (law L6): conformance/FORMAT-SPEC-001.json, formats.GF16, in
+; gHashTag/t27 -- bits 16, sign 1, exp 6, mant 9, bias 31, exp_max 63, read on 2026-10-05 -- and
+; specs/numeric/gf16.t27. At run time the page reads the layout again from the vendored copy of
+; gf16.t27 that t27.ai already serves (K_LAYOUT_FILE: SIGN_SHIFT, EXP_SHIFT, EXP_MAX, BIAS,
+; MANT_MASK, GF16_NAN), uses those values, and says so on screen; if that file cannot be read, or
+; disagrees with K_* below, the page says that too. The tests below hold K_* to what
+; FORMAT-SPEC-001.json says, so a change to the format fails this spec before it reaches a reader.
+; Subnormals, the largest normal and overflow to infinity follow the bit-exact pack
+; conformance/vectors/gf16_conformance_v0.json, whose finite and infinite vectors the page runs on
+; itself at load (K_CHECK_INPUTS, K_CHECK_BITS) and reports.
+
+; WHAT IT DOES NOT CLAIM: that GF16 is better than any other 16-bit format; the page compares GF16
+; with nothing. The SSOT names two tie rules -- gf16.t27 says round to nearest, ties to even;
+; FORMAT-SPEC-001.json records that the frozen TT4913 Gamma multiplier rounds ties toward zero --
+; so the page offers both, says which source each comes from, and starts on ties to even. The two
+; tie vectors (K_TIE_*) are this widget's own arithmetic, not vectors from the SSOT. gf16.t27's
+; encoder comment says subnormals are flushed to zero; the conformance pack encodes them, and the
+; page follows the pack. Balanced ternary here is positional notation, not a GoldenFloat format.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_gf16_calc;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "gf16-calc";
+pub const TITLE : str = "GF16 calculator: a number as 16 GoldenFloat bits and as ternary";
+pub const DESCRIPTION : str = "Type a number and see its GF16 sign, exponent and mantissa bit by bit, the exact value it rounds to, the error, and the same number in balanced ternary.";
+pub const IMAGE_ALT : str = "The GF16 calculator on phi = 1.6180339887: sixteen bits in three coloured fields, sign 0, exponent 31, mantissa 316, hex 0x3F3C, decoded exactly as 1.6171875 with a relative error of 5.2e-4, and below it phi in balanced ternary as trits T, 0 and 1.";
+pub const HOOK : str = "Where does phi land in 16 bits? Type any number and watch it rounded to GF16, field by field, with the error printed exactly.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [3]str = ["https://github.com/gHashTag/t27/blob/master/conformance/FORMAT-SPEC-001.json (formats.GF16, frozen_silicon_anchor)", "https://t27.ai/t27/files/specs/numeric/gf16.t27 (layout constants, read by the page at run time)", "https://github.com/gHashTag/t27/blob/master/conformance/vectors/gf16_conformance_v0.json (vectors the page checks itself against)"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The format, as FORMAT-SPEC-001.json states it (formats.GF16) ------------------------------
+pub const K_FORMAT_NAME : str = "GF16";
+pub const K_BITS : u8 = 16;
+pub const K_SIGN_BITS : u8 = 1;
+pub const K_EXP_BITS : u8 = 6;
+pub const K_MANT_BITS : u8 = 9;
+pub const K_BIAS : u8 = 31;
+pub const K_EXP_MAX : u8 = 63;
+; 2^K_MANT_BITS: the implicit leading one of a normal number, in mantissa units.
+pub const K_MANT_ONE : u16 = 512;
+; gf16.t27 GF16_NAN (0xFE01): the code the page writes for a NaN input.
+pub const K_NAN_BITS : u16 = 65025;
+
+; --- Where the page reads the layout at run time ------------------------------------------------
+; Relative to tool.js, so it resolves on t27.ai and under any path the site is served from.
+pub const K_LAYOUT_FILE : str = "../../t27/files/specs/numeric/gf16.t27";
+pub const K_LAYOUT_NAMES : [6]str = ["SIGN_SHIFT", "EXP_SHIFT", "EXP_MAX", "BIAS", "MANT_MASK", "GF16_NAN"];
+
+; --- Rounding ----------------------------------------------------------------------------------
+pub const K_ROUNDING_IDS : [2]str = ["even", "zero"];
+pub const K_DEFAULT_ROUNDING : str = "even";
+
+; --- Balanced ternary ----------------------------------------------------------------------------
+pub const K_TERNARY_FRAC_TRITS : u8 = 9;
+pub const K_TERNARY_MAX_TRITS : u8 = 48;
+; Two notations for the digits -1, 0, +1: the first three, then the last three.
+pub const K_TRIT_GLYPHS : [6]str = ["T", "0", "1", "-", "0", "+"];
+
+; --- Input ---------------------------------------------------------------------------------------
+pub const K_DEFAULT_INPUT : str = "1.618033988749895";
+pub const K_EXAMPLES : [7]str = ["1.618033988749895", "0.1", "-2.5", "3.141592653589793", "1.0029296875", "1e-11", "0x7E00"];
+; Decimal exponents beyond this are refused rather than turned into a huge integer.
+pub const K_DECIMAL_EXP_LIMIT : u16 = 999;
+; Significant digits for errors and approximate values.
+pub const K_SIG_DIGITS : u8 = 4;
+
+; --- Self-check at load: gf16_conformance_v0.json, every finite and infinite vector -------------
+; Inputs exactly as the pack prints input_f64; bits are its gf16_bits_int.
+pub const K_CHECK_COUNT : u8 = 20;
+pub const K_CHECK_NAMES : [20]str = ["pos_zero", "neg_zero", "pos_one", "neg_one", "pos_two", "pos_half", "phi", "inv_phi", "phi_squared", "inv_phi_sq", "three", "smallest_subnormal", "largest_subnormal", "smallest_normal", "largest_normal", "overflow_to_inf", "pos_inf", "neg_inf", "e", "pi"];
+pub const K_CHECK_INPUTS : [20]str = ["0.0", "-0.0", "1.0", "-1.0", "2.0", "0.5", "1.618033988749895", "0.6180339887498948", "2.618033988749895", "0.3819660112501051", "3.0", "1.8189894035458565e-12", "9.295035852119327e-10", "9.313225746154785e-10", "4290772992.0", "1e+40", "inf", "-inf", "2.718281828459045", "3.141592653589793"];
+pub const K_CHECK_BITS : [20]u16 = [0, 32768, 15872, 48640, 16384, 15360, 16188, 15481, 16542, 15118, 16640, 1, 511, 512, 32255, 32256, 32256, 65024, 16568, 16676];
+
+; --- Tie vectors: this widget's arithmetic, not the SSOT's ------------------------------------
+; 1 + 2^-10 lies halfway between mantissa 0 and 1; 1 + 3 * 2^-10 halfway between 1 and 2.
+pub const K_TIE_INPUTS : [2]str = ["1.0009765625", "1.0029296875"];
+pub const K_TIE_BITS_EVEN : [2]u16 = [15872, 15874];
+pub const K_TIE_BITS_ZERO : [2]u16 = [15872, 15873];
+
+; --- Every word on the page ---------------------------------------------------------------------
+pub const SAY_INPUT_LABEL : str = "A decimal number, or 16 raw bits as hex";
+pub const SAY_INPUT_PLACEHOLDER : str = "1.618033988749895 or 0x3F3C";
+pub const SAY_EXAMPLES_LABEL : str = "Try";
+pub const SAY_ROUNDING_LABEL : str = "Ties";
+pub const SAY_ROUNDING_NAMES : [2]str = ["to even", "toward zero"];
+pub const SAY_ROUNDING_NOTES : [2]str = ["Round to nearest, ties to even: the rule gf16.t27 states for its encoder.", "Round to nearest, ties toward zero: the frozen TT4913 Gamma multiplier, per FORMAT-SPEC-001.json."];
+pub const SAY_NOTATION_LABEL : str = "Trits";
+pub const SAY_NOTATION_NAMES : [2]str = ["T 0 1", "- 0 +"];
+pub const SAY_COPY_LINK : str = "Copy link";
+pub const SAY_COPIED : str = "Copied";
+pub const SAY_BAD_INPUT : str = "Not a number this page reads. Use digits with an optional point and exponent (1e-3), inf, nan, or 0x and up to 4 hex digits.";
+pub const SAY_GF16_HEADING : str = "GF16";
+pub const SAY_FIELD_NAMES : [3]str = ["sign", "exponent", "mantissa"];
+pub const SAY_BIT_TITLE : str = "bit {i}";
+pub const SAY_SIGN_DETAIL : [2]str = ["0: positive", "1: negative"];
+pub const SAY_EXP_DETAIL : str = "{e} - bias {b} = {u}";
+pub const SAY_EXP_SUBNORMAL : str = "0: subnormal, scale 2^{u}";
+pub const SAY_EXP_SPECIAL : str = "{e}: all ones, infinity or NaN";
+pub const SAY_MANT_DETAIL : str = "{m} / {one}";
+pub const SAY_CLASS_LABEL : str = "class";
+pub const SAY_CLASS_NAMES : [5]str = ["zero", "subnormal", "normal", "infinity", "NaN"];
+pub const SAY_HEX_LABEL : str = "hex";
+pub const SAY_INPUT_ECHO : str = "you typed";
+pub const SAY_RAW_ECHO : str = "raw bits, decoded";
+pub const SAY_DECODED : str = "decoded, exactly";
+pub const SAY_ERROR : str = "error";
+pub const SAY_REL_ERROR : str = "relative error";
+pub const SAY_ROUNDED_LABEL : str = "rounding";
+pub const SAY_ROUNDED_NAMES : [3]str = ["exact: no rounding", "rounded down in magnitude", "rounded up in magnitude"];
+pub const SAY_TIE : str = "a tie, broken {rule}";
+pub const SAY_OVERFLOW : str = "beyond the largest finite GF16 value: infinity";
+pub const SAY_UNDERFLOW : str = "below half the smallest subnormal: zero";
+pub const SAY_TERNARY_HEADING : str = "Balanced ternary";
+pub const SAY_TERNARY_NOTE : str = "Base 3 with digits -1, 0, +1 and {f} trits after the point: a notation, not a GoldenFloat format.";
+pub const SAY_TERNARY_VALUE : str = "these trits are";
+pub const SAY_TRIT_COUNT : str = "{n} trits";
+pub const SAY_TRIT_TITLE : str = "{d} x 3^{p}";
+pub const SAY_TERNARY_TOO_WIDE : str = "More than {n} trits before the point; not drawn.";
+pub const SAY_TERNARY_NONE : str = "Infinity and NaN have no ternary digits.";
+pub const SAY_APPROX : str = "about";
+pub const SAY_ROUNDTRIP_HEADING : str = "Round trip";
+pub const SAY_RT_GF16 : str = "decoded value, encoded again: {hex}";
+pub const SAY_RT_TERNARY : str = "trits read back, written again";
+pub const SAY_SAME : str = "same";
+pub const SAY_DIFFERENT : str = "different";
+pub const SAY_CONFORMANCE : str = "{ok} of {n} vectors from gf16_conformance_v0.json encode bit-exact on this page; tie vectors {tok} of {tn}.";
+pub const SAY_CONFORMANCE_FAIL : str = "Failed here: {names}.";
+pub const SAY_LAYOUT_READ : str = "Layout read from {file}: S{s} E{e} M{m}, bias {b}.";
+pub const SAY_LAYOUT_FALLBACK : str = "Could not read {file}; using this widget's spec: S{s} E{e} M{m}, bias {b}.";
+pub const SAY_LAYOUT_MISMATCH : str = "{file} and this widget's spec disagree ({names}); the page uses {file}.";
+
+test the_fields_fill_sixteen_bits {
+ assert K_SIGN_BITS + K_EXP_BITS + K_MANT_BITS == K_BITS;
+ assert K_BITS == 16;
+ assert K_MANT_ONE == 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
+}
+
+test the_layout_is_format_spec_001_gf16 {
+ assert K_EXP_BITS == 6;
+ assert K_MANT_BITS == 9;
+ assert K_BIAS == 31;
+ assert K_EXP_MAX == 63;
+ assert K_FORMAT_NAME == "GF16";
+}
+
+test the_bias_follows_the_closed_form_rule {
+ assert K_EXP_MAX + 1 == 2 * 2 * 2 * 2 * 2 * 2;
+ assert K_BIAS == (K_EXP_MAX + 1) / 2 - 1;
+}
+
+test the_conformance_bits_follow_from_the_layout {
+ assert K_CHECK_NAMES[2] == "pos_one";
+ assert K_CHECK_BITS[2] == K_BIAS * K_MANT_ONE;
+ assert K_CHECK_BITS[3] == K_CHECK_BITS[2] + 32768;
+ assert K_CHECK_NAMES[14] == "largest_normal";
+ assert K_CHECK_BITS[14] == K_EXP_MAX * K_MANT_ONE - 1;
+ assert K_CHECK_NAMES[15] == "overflow_to_inf";
+ assert K_CHECK_BITS[15] == K_EXP_MAX * K_MANT_ONE;
+ assert K_CHECK_BITS[16] == K_CHECK_BITS[15];
+ assert K_CHECK_BITS[13] == K_MANT_ONE;
+ assert K_CHECK_BITS[12] == K_MANT_ONE - 1;
+ assert K_NAN_BITS == 32768 + K_EXP_MAX * K_MANT_ONE + 1;
+}
+
+test the_two_tie_rules_differ_only_on_an_odd_neighbour {
+ assert K_TIE_BITS_EVEN[0] == K_TIE_BITS_ZERO[0];
+ assert K_TIE_BITS_EVEN[1] == K_TIE_BITS_ZERO[1] + 1;
+ assert K_ROUNDING_IDS[0] == K_DEFAULT_ROUNDING;
+}
+
+test the_widget_sends_nothing {
+ assert SENDS_NOTHING == true;
+ assert READS_LOCAL_FILES == false;
+ assert K_TERNARY_FRAC_TRITS < K_TERNARY_MAX_TRITS;
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/gf16-calc/tool.js (511 lines of hand-written JavaScript:
+; the BigInt arithmetic and the page). The words and numbers above are unchanged: the view prints SAY_
+; constants and K_ numbers, it writes no English of its own.
+; --
+; ARITHMETIC. tool.js rounds on exact fractions of BigInts. There is no BigInt in .t27, so this module
+; has the little it needs: sixteen registers of 640 32-bit limbs (20480 bits; the widest value is a
+; 999-digit decimal times 10^999, about 6700 bits) with compare, add, subtract, multiply, shift, divide
+; by a small number, and a binary long division that costs one shift and one compare per bit of the
+; QUOTIENT (every quotient in this page is small: a 12-bit mantissa, a 34-bit rounded digit string, a
+; 95-bit ternary count). Nothing here is a float: a decimal the reader types is read digit by digit into
+; one of these registers.
+; --
+; Host protocol (host.js; all exports of this module):
+; w_base(url) unused (the page link is built by the host)
+; w_init(hash) the page opens, with the address's #fragment
+; w_hash(hash) the address's fragment changed
+; w_data(i, bytes) the layout file (W_DATA_FILE) was fetched; w_data_failed(i) when it was not
+; w_input(i, bytes) control i (among input and textarea) changed: 0 is the number field
+; w_press(i) button number i in page order (the page maps it to its action)
+; w_copied(ok) the host's answer to a copy effect
+; w_timer(id) a timer effect ran out
+; w_hash_out(dst) the fragment to show after the last event (length 0: leave it)
+; w_effect(k, dst) effect k after the last event: 'v' + control index + text = set that control's value;
+; 'c' = copy the page link (without ?embed) to the clipboard, then call w_copied(ok);
+; 's' + control index = select that control's text; 't' + id + decimal ms = call w_timer(id) after ms
+; w_render(dst) the page's HTML, length (0 until the layout file has been read or has failed)
+; Not carried over from JavaScript: the number field is cut at W_X_CAP (8192) bytes; a layout wider than 40 bits,
+; with a mantissa wider than 30 bits or an exponent wider than 20, reads as unreadable (the spec's layout is used);
+; the value attribute of the number field follows the text (tool.js leaves the initial text there, the property is the same);
+; Unicode lower-casing is ASCII only; a malformed %-escape in the address stays as written.
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::attr;
+use widgets::html_out::dec_to;
+use widgets::html_out::fill_named;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+
+pub const W_DATA_FILE : str = "../../t27/files/specs/numeric/gf16.t27";
+pub const W_X_CAP : usize = 8192;
+pub const W_BNL : usize = 640;
+pub const W_NAN_ID : u32 = 0;
+
+pub var BN : [10240]u32 = [0; 10240];
+pub var BLEN : [16]usize = [0; 16];
+
+; --- the registers --------------------------------------------------------------------------------------
+; 0 W_R_N and 1 W_R_D hold the parsed fraction; 2..9 are scratch for the function that names them; 14 and 15
+; belong to bn_divmod and bn_shl.
+pub const W_R_N : usize = 0;
+pub const W_R_D : usize = 1;
+pub const W_R_T : usize = 15;
+pub const W_R_S : usize = 14;
+
+fn bn_norm(r: usize) -> u32 {
+ var n : usize = BLEN[r];
+ while (n > 0 and BN[r * 640 + n - 1] == 0) { n = n - 1; }
+ BLEN[r] = n;
+ return 0;
+}
+
+fn bn_zero(r: usize) -> u32 {
+ BLEN[r] = 0;
+ return 0;
+}
+
+fn bn_from_u64(r: usize, v: u64) -> u32 {
+ BN[r * 640] = (v & 4294967295) as u32;
+ BN[r * 640 + 1] = (v >> 32) as u32;
+ BLEN[r] = 2;
+ bn_norm(r);
+ return 0;
+}
+
+fn bn_copy(d: usize, s: usize) -> u32 {
+ const n : usize = BLEN[s];
+ var i : usize = 0;
+ while (i < n) {
+ BN[d * 640 + i] = BN[s * 640 + i];
+ i = i + 1;
+ }
+ BLEN[d] = n;
+ return 0;
+}
+
+fn bn_cmp(a: usize, b: usize) -> i32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ if (la < lb) { return -1; }
+ if (la > lb) { return 1; }
+ var i : usize = la;
+ while (i > 0) {
+ i = i - 1;
+ const x : u32 = BN[a * 640 + i];
+ const y : u32 = BN[b * 640 + i];
+ if (x < y) { return -1; }
+ if (x > y) { return 1; }
+ }
+ return 0;
+}
+
+fn bn_add(d: usize, a: usize, b: usize) -> u32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ var n : usize = la;
+ if (lb > n) { n = lb; }
+ var carry : u64 = 0;
+ var i : usize = 0;
+ while (i < n) {
+ var s : u64 = carry;
+ if (i < la) { s = s + (BN[a * 640 + i] as u64); }
+ if (i < lb) { s = s + (BN[b * 640 + i] as u64); }
+ BN[d * 640 + i] = (s & 4294967295) as u32;
+ carry = s >> 32;
+ i = i + 1;
+ }
+ if (carry > 0) {
+ BN[d * 640 + n] = carry as u32;
+ n = n + 1;
+ }
+ BLEN[d] = n;
+ return 0;
+}
+
+/// d = a - b, for a >= b.
+fn bn_sub(d: usize, a: usize, b: usize) -> u32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ var borrow : u64 = 0;
+ var i : usize = 0;
+ while (i < la) {
+ var y : u64 = borrow;
+ if (i < lb) { y = y + (BN[b * 640 + i] as u64); }
+ const x : u64 = (BN[a * 640 + i] as u64) + 4294967296;
+ const t : u64 = x - y;
+ BN[d * 640 + i] = (t & 4294967295) as u32;
+ borrow = 1 - (t >> 32);
+ i = i + 1;
+ }
+ BLEN[d] = la;
+ bn_norm(d);
+ return 0;
+}
+
+fn bn_mul_small(d: usize, a: usize, m: u64) -> u32 {
+ const la : usize = BLEN[a];
+ var carry : u64 = 0;
+ var i : usize = 0;
+ while (i < la) {
+ const t : u64 = (BN[a * 640 + i] as u64) * m + carry;
+ BN[d * 640 + i] = (t & 4294967295) as u32;
+ carry = t >> 32;
+ i = i + 1;
+ }
+ var n : usize = la;
+ if (carry > 0) {
+ BN[d * 640 + n] = carry as u32;
+ n = n + 1;
+ }
+ BLEN[d] = n;
+ bn_norm(d);
+ return 0;
+}
+
+fn bn_add_small(d: usize, a: usize, v: u64) -> u32 {
+ const la : usize = BLEN[a];
+ var carry : u64 = v;
+ var i : usize = 0;
+ while (i < la) {
+ const t : u64 = (BN[a * 640 + i] as u64) + carry;
+ BN[d * 640 + i] = (t & 4294967295) as u32;
+ carry = t >> 32;
+ i = i + 1;
+ }
+ var n : usize = la;
+ while (carry > 0) {
+ BN[d * 640 + n] = (carry & 4294967295) as u32;
+ carry = carry >> 32;
+ n = n + 1;
+ }
+ BLEN[d] = n;
+ return 0;
+}
+
+/// d = a / m, the remainder returned.
+fn bn_divmod_small(d: usize, a: usize, m: u64) -> u64 {
+ const la : usize = BLEN[a];
+ var rem : u64 = 0;
+ var i : usize = la;
+ while (i > 0) {
+ i = i - 1;
+ const cur : u64 = (rem << 32) + (BN[a * 640 + i] as u64);
+ BN[d * 640 + i] = (cur / m) as u32;
+ rem = cur % m;
+ }
+ BLEN[d] = la;
+ bn_norm(d);
+ return rem;
+}
+
+fn bitlen32(x: u32) -> u32 {
+ var n : u32 = 0;
+ var v : u32 = x;
+ while (v > 0) {
+ n = n + 1;
+ v = v >> 1;
+ }
+ return n;
+}
+
+fn bn_bitlen(r: usize) -> u32 {
+ const n : usize = BLEN[r];
+ if (n == 0) { return 0; }
+ return ((n - 1) as u32) * 32 + bitlen32(BN[r * 640 + n - 1]);
+}
+
+/// d = a << sh (alias-safe: it works from the top limb down).
+fn bn_shl(d: usize, a: usize, sh: u32) -> u32 {
+ const la : usize = BLEN[a];
+ if (la == 0) {
+ BLEN[d] = 0;
+ return 0;
+ }
+ const ls : usize = (sh / 32) as usize;
+ const bs : u32 = sh % 32;
+ const nl : usize = la + ls + 1;
+ var i : usize = nl;
+ while (i > 0) {
+ i = i - 1;
+ var v : u64 = 0;
+ if (i >= ls and i - ls < la) {
+ v = (BN[a * 640 + i - ls] as u64) << bs;
+ }
+ if (bs > 0 and i >= ls + 1 and i - ls - 1 < la) {
+ v = v | ((BN[a * 640 + i - ls - 1] as u64) >> (32 - bs));
+ }
+ if (i < ls) { v = 0; }
+ BN[d * 640 + i] = (v & 4294967295) as u32;
+ }
+ BLEN[d] = nl;
+ bn_norm(d);
+ return 0;
+}
+
+/// d = a * b (d must not be a or b).
+fn bn_mul(d: usize, a: usize, b: usize) -> u32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ if (la == 0 or lb == 0) {
+ BLEN[d] = 0;
+ return 0;
+ }
+ var i : usize = 0;
+ while (i < la + lb) {
+ BN[d * 640 + i] = 0;
+ i = i + 1;
+ }
+ i = 0;
+ while (i < la) {
+ var carry : u64 = 0;
+ const x : u64 = BN[a * 640 + i] as u64;
+ var j : usize = 0;
+ while (j < lb) {
+ const t : u64 = x * (BN[b * 640 + j] as u64) + (BN[d * 640 + i + j] as u64) + carry;
+ BN[d * 640 + i + j] = (t & 4294967295) as u32;
+ carry = t >> 32;
+ j = j + 1;
+ }
+ BN[d * 640 + i + lb] = carry as u32;
+ i = i + 1;
+ }
+ BLEN[d] = la + lb;
+ bn_norm(d);
+ return 0;
+}
+
+fn pow10_small(k: u32) -> u64 {
+ var v : u64 = 1;
+ var i : u32 = 0;
+ while (i < k) {
+ v = v * 10;
+ i = i + 1;
+ }
+ return v;
+}
+
+/// d = a * 10^p
+fn bn_mul_pow10(d: usize, a: usize, p: u32) -> u32 {
+ if (d != a) { bn_copy(d, a); }
+ var left : u32 = p;
+ while (left >= 9) {
+ bn_mul_small(d, d, 1000000000);
+ left = left - 9;
+ }
+ if (left > 0) { bn_mul_small(d, d, pow10_small(left)); }
+ return 0;
+}
+
+/// d = base^e for a small base.
+fn bn_set_pow(d: usize, base: u64, e: u32) -> u32 {
+ bn_from_u64(d, 1);
+ var i : u32 = 0;
+ while (i < e) {
+ bn_mul_small(d, d, base);
+ i = i + 1;
+ }
+ return 0;
+}
+
+/// q = a / b and rem = a mod b by binary long division: one shift and one compare per quotient bit.
+/// q, rem, a, b are four different registers; W_R_T is scratch.
+fn bn_divmod(q: usize, rem: usize, a: usize, b: usize) -> u32 {
+ bn_copy(rem, a);
+ const ka : u32 = bn_bitlen(a);
+ const kb : u32 = bn_bitlen(b);
+ if (ka < kb) {
+ BLEN[q] = 0;
+ return 0;
+ }
+ const k : u32 = ka - kb;
+ const nl : usize = ((k / 32) + 1) as usize;
+ var z : usize = 0;
+ while (z < nl) {
+ BN[q * 640 + z] = 0;
+ z = z + 1;
+ }
+ BLEN[q] = nl;
+ var i : u32 = k + 1;
+ while (i > 0) {
+ i = i - 1;
+ bn_shl(W_R_T, b, i);
+ if (bn_cmp(W_R_T, rem) <= 0) {
+ bn_sub(rem, rem, W_R_T);
+ const li : usize = (i / 32) as usize;
+ BN[q * 640 + li] = BN[q * 640 + li] | ((1 as u32) << (i % 32));
+ }
+ }
+ bn_norm(q);
+ return 0;
+}
+
+/// The value as a u64 (the register holds at most two limbs).
+fn bn_u64(r: usize) -> u64 {
+ var v : u64 = 0;
+ if (BLEN[r] > 0) { v = BN[r * 640] as u64; }
+ if (BLEN[r] > 1) { v = v | ((BN[r * 640 + 1] as u64) << 32); }
+ return v;
+}
+
+/// The decimal digits of a register, most significant first, into o at n0; the new length ("0" for zero).
+fn bn_dec(o: []u8, n0: u32, r: usize) -> u32 {
+ var n : u32 = n0;
+ if (BLEN[r] == 0) { return put(o, n, 48); }
+ // peel nine digits at a time off a copy in register 13
+ bn_copy(13, r);
+ var chunks : [100]u32 = [0; 100];
+ var nc : usize = 0;
+ while (BLEN[13] > 0 and nc < 100) {
+ chunks[nc] = bn_divmod_small(13, 13, 1000000000) as u32;
+ nc = nc + 1;
+ }
+ var first : bool = true;
+ while (nc > 0) {
+ nc = nc - 1;
+ var digits : [9]u8 = [48; 9];
+ var v : u32 = chunks[nc];
+ var j : usize = 9;
+ while (j > 0) {
+ j = j - 1;
+ digits[j] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ }
+ var from : usize = 0;
+ if (first) {
+ while (from < 8 and digits[from] == 48) { from = from + 1; }
+ first = false;
+ }
+ while (from < 9) {
+ n = put(o, n, digits[from]);
+ from = from + 1;
+ }
+ }
+ return n;
+}
+
+; --- the layout ----------------------------------------------------------------------------------------------
+pub var LB : u64 = 16;
+pub var LE : u64 = 6;
+pub var LM : u64 = 9;
+pub var LBIAS : u64 = 31;
+pub var LEXPMAX : u64 = 63;
+pub var LNAN : u64 = 65025;
+pub var WIDE_N : u32 = 79;
+
+fn set_spec_layout() -> u32 {
+ LB = K_BITS as u64;
+ LE = K_EXP_BITS as u64;
+ LM = K_MANT_BITS as u64;
+ LBIAS = K_BIAS as u64;
+ LEXPMAX = K_EXP_MAX as u64;
+ LNAN = K_NAN_BITS as u64;
+ return 0;
+}
+
+fn pow2(k: u64) -> u64 {
+ return (1 as u64) << k;
+}
+
+; --- the number field: trim, lower-case, no underscores -----------------------------------------------------
+pub var PS : [8256]u8 = [0; 8256];
+pub var PS_N : usize = 0;
+pub var P_KIND : u32 = 0;
+pub var P_NEG : bool = false;
+pub var P_HEX : u64 = 0;
+
+/// The length of the JavaScript white space that starts at s[p] (0 when none).
+fn ws_fwd(s: []const u8, p: usize) -> usize {
+ const b : u8 = s[p];
+ if (b == 32 or (b >= 9 and b <= 13)) { return 1; }
+ const left : usize = s.len - p;
+ if (b == 194 and left >= 2 and s[p + 1] == 160) { return 2; }
+ if (left < 3) { return 0; }
+ const c : u8 = s[p + 1];
+ const d : u8 = s[p + 2];
+ if (b == 225 and c == 154 and d == 128) { return 3; }
+ if (b == 226 and c == 128 and ((d >= 128 and d <= 138) or d == 168 or d == 169 or d == 175)) { return 3; }
+ if (b == 226 and c == 129 and d == 159) { return 3; }
+ if (b == 227 and c == 128 and d == 128) { return 3; }
+ if (b == 239 and c == 187 and d == 191) { return 3; }
+ return 0;
+}
+
+/// The length of the white space that ends at s[e - 1] (0 when none).
+fn ws_back(s: []const u8, e: usize) -> usize {
+ const c : u8 = s[e - 1];
+ if (c == 32 or (c >= 9 and c <= 13)) { return 1; }
+ if (e >= 2 and s[e - 2] == 194 and c == 160) { return 2; }
+ if (e < 3) { return 0; }
+ const a : u8 = s[e - 3];
+ const m : u8 = s[e - 2];
+ if (a == 225 and m == 154 and c == 128) { return 3; }
+ if (a == 226 and m == 128 and ((c >= 128 and c <= 138) or c == 168 or c == 169 or c == 175)) { return 3; }
+ if (a == 226 and m == 129 and c == 159) { return 3; }
+ if (a == 227 and m == 128 and c == 128) { return 3; }
+ if (a == 239 and m == 187 and c == 191) { return 3; }
+ return 0;
+}
+
+/// String(raw).trim().toLowerCase().replace(/_/g, '') into PS.
+fn clean(raw_s: []const u8) -> u32 {
+ var lo : usize = 0;
+ var hi : usize = raw_s.len;
+ while (lo < hi) {
+ const w : usize = ws_fwd(raw_s, lo);
+ if (w == 0) { break; }
+ lo = lo + w;
+ }
+ while (hi > lo) {
+ const w2 : usize = ws_back(raw_s[0..hi], hi);
+ if (w2 == 0) { break; }
+ hi = hi - w2;
+ }
+ var n : usize = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ var b : u8 = raw_s[k];
+ if (b >= 65 and b <= 90) { b = b + 32; }
+ if (b != 95) {
+ PS[n] = b;
+ n = n + 1;
+ }
+ k = k + 1;
+ }
+ PS_N = n;
+ return 0;
+}
+
+fn is_dig(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+fn hex_digit(b: u8) -> i32 {
+ if (b >= 48 and b <= 57) { return (b as i32) - 48; }
+ if (b >= 97 and b <= 102) { return (b as i32) - 87; }
+ return -1;
+}
+
+fn ps_is(p: usize, w: []const u8) -> bool {
+ if (p + w.len != PS_N) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (PS[p + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// parseInput(raw): P_KIND 0 none, 1 hex, 2 inf, 3 nan, 4 num (then W_R_N / W_R_D hold the fraction).
+fn parse_number(raw_s: []const u8) -> u32 {
+ P_KIND = 0;
+ P_NEG = false;
+ clean(raw_s);
+ const n : usize = PS_N;
+ if (n == 0) { return 0; }
+ const hexn : usize = ((LB + 3) / 4) as usize;
+ if (n >= 3 and PS[0] == 48 and PS[1] == 120) {
+ var all : bool = true;
+ var v : u64 = 0;
+ var k : usize = 2;
+ while (k < n) {
+ const h : i32 = hex_digit(PS[k]);
+ if (h < 0) { all = false; break; }
+ if (k - 2 < 16) { v = v * 16 + (h as u64); }
+ k = k + 1;
+ }
+ if (all) {
+ if (n - 2 > hexn) { return 0; }
+ if (v < pow2(LB)) {
+ P_KIND = 1;
+ P_HEX = v;
+ }
+ return 0;
+ }
+ }
+ var p : usize = 0;
+ var neg : bool = false;
+ if (PS[0] == 43 or PS[0] == 45) {
+ neg = PS[0] == 45;
+ p = 1;
+ }
+ if (ps_is(p, "inf") or ps_is(p, "infinity")) {
+ P_KIND = 2;
+ P_NEG = PS[0] == 45;
+ return 0;
+ }
+ if (ps_is(p, "nan")) {
+ P_KIND = 3;
+ return 0;
+ }
+ const ilo : usize = p;
+ while (p < n and is_dig(PS[p])) { p = p + 1; }
+ const ihi : usize = p;
+ var flo : usize = p;
+ var fhi : usize = p;
+ if (p < n and PS[p] == 46) {
+ p = p + 1;
+ flo = p;
+ while (p < n and is_dig(PS[p])) { p = p + 1; }
+ fhi = p;
+ }
+ var exp : i64 = 0;
+ if (p < n and PS[p] == 101) {
+ p = p + 1;
+ var eneg : bool = false;
+ if (p < n and (PS[p] == 43 or PS[p] == 45)) {
+ eneg = PS[p] == 45;
+ p = p + 1;
+ }
+ const elo : usize = p;
+ while (p < n and is_dig(PS[p])) { p = p + 1; }
+ if (p == elo) { return 0; }
+ var z : usize = elo;
+ while (z + 1 < p and PS[z] == 48) { z = z + 1; }
+ if (p - z > 3) { return 0; }
+ var ev : i64 = 0;
+ while (z < p) {
+ ev = ev * 10 + ((PS[z] - 48) as i64);
+ z = z + 1;
+ }
+ if (eneg) { ev = 0 - ev; }
+ exp = ev;
+ }
+ if (p != n) { return 0; }
+ if (ihi == ilo and fhi == flo) { return 0; }
+ if (exp > 999 or exp < -999) { return 0; }
+ if ((ihi - ilo) + (fhi - flo) > 999) { return 0; }
+ bn_zero(W_R_N);
+ var i : usize = ilo;
+ while (i < ihi) {
+ bn_mul_small(W_R_N, W_R_N, 10);
+ bn_add_small(W_R_N, W_R_N, (PS[i] - 48) as u64);
+ i = i + 1;
+ }
+ i = flo;
+ while (i < fhi) {
+ bn_mul_small(W_R_N, W_R_N, 10);
+ bn_add_small(W_R_N, W_R_N, (PS[i] - 48) as u64);
+ i = i + 1;
+ }
+ const scale : i64 = exp - ((fhi - flo) as i64);
+ if (scale >= 0) {
+ bn_mul_pow10(W_R_N, W_R_N, scale as u32);
+ bn_from_u64(W_R_D, 1);
+ } else {
+ bn_from_u64(W_R_D, 1);
+ bn_mul_pow10(W_R_D, W_R_D, (0 - scale) as u32);
+ }
+ P_KIND = 4;
+ P_NEG = neg;
+ return 0;
+}
+
+; --- rounding to the layout ----------------------------------------------------------------------------------------
+pub var E_BITS : u64 = 0;
+pub var E_DIR : i32 = 0;
+pub var E_TIE : bool = false;
+pub var E_OVER : bool = false;
+pub var E_UNDER : bool = false;
+
+fn enc_set(bits: u64, dir: i32, tie: bool, over: bool, under: bool) -> u32 {
+ E_BITS = bits;
+ E_DIR = dir;
+ E_TIE = tie;
+ E_OVER = over;
+ E_UNDER = under;
+ return 0;
+}
+
+/// encodeExact(neg, n, d, L, mode) with n, d in W_R_N, W_R_D.
+fn encode_exact(neg: bool, even: bool) -> u32 {
+ var sign_bit : u64 = 0;
+ if (neg) { sign_bit = pow2(LB - 1); }
+ const inf_bits : u64 = sign_bit + (LEXPMAX << LM);
+ if (BLEN[W_R_N] == 0) { return enc_set(sign_bit, 0, false, false, false); }
+ var e : i64 = (bn_bitlen(W_R_N) as i64) - (bn_bitlen(W_R_D) as i64);
+ // cmpPow2(n, d, e) < 0 ?
+ if (e >= 0) {
+ bn_copy(2, W_R_N);
+ bn_shl(3, W_R_D, e as u32);
+ } else {
+ bn_shl(2, W_R_N, (0 - e) as u32);
+ bn_copy(3, W_R_D);
+ }
+ if (bn_cmp(2, 3) < 0) { e = e - 1; }
+ const emin : i64 = 1 - (LBIAS as i64);
+ if (e > (LEXPMAX as i64) - (LBIAS as i64)) { return enc_set(inf_bits, 1, false, true, false); }
+ var eff : i64 = e;
+ if (emin > eff) { eff = emin; }
+ const sh : i64 = (LM as i64) - eff;
+ if (sh >= 0) {
+ bn_shl(2, W_R_N, sh as u32);
+ bn_copy(3, W_R_D);
+ } else {
+ bn_copy(2, W_R_N);
+ bn_shl(3, W_R_D, (0 - sh) as u32);
+ }
+ bn_divmod(4, 5, 2, 3);
+ var q : u64 = bn_u64(4);
+ bn_shl(6, 5, 1);
+ const c : i32 = bn_cmp(6, 3);
+ const rzero : bool = BLEN[5] == 0;
+ const tie : bool = !rzero and c == 0;
+ const up : bool = !rzero and (c > 0 or (tie and even and (q & 1) == 1));
+ if (up) { q = q + 1; }
+ var dir : i32 = 0;
+ if (!rzero) {
+ if (up) { dir = 1; } else { dir = -1; }
+ }
+ var ee : i64 = eff;
+ const one_m : u64 = pow2(LM);
+ if (q == one_m * 2) {
+ q = q / 2;
+ ee = ee + 1;
+ }
+ var biased : i64 = 0;
+ if (q >= one_m) { biased = ee + (LBIAS as i64); }
+ if (biased >= (LEXPMAX as i64)) { return enc_set(inf_bits, 1, tie, true, false); }
+ var mant : u64 = q;
+ if (biased != 0) { mant = q - one_m; }
+ if (biased == 0 and mant == 0) { return enc_set(sign_bit, -1, tie, false, true); }
+ return enc_set(sign_bit + ((biased as u64) << LM) + mant, dir, tie, false, false);
+}
+
+/// encodeParsed(p, L, mode)
+fn encode_parsed(even: bool) -> u32 {
+ if (P_KIND == 1) { return enc_set(P_HEX, 0, false, false, false); }
+ if (P_KIND == 3) { return enc_set(LNAN, 0, false, false, false); }
+ if (P_KIND == 2) {
+ var sb : u64 = 0;
+ if (P_NEG) { sb = pow2(LB - 1); }
+ return enc_set(sb + (LEXPMAX << LM), 0, false, false, false);
+ }
+ return encode_exact(P_NEG, even);
+}
+
+pub var D_SIGN : u64 = 0;
+pub var D_EXP : u64 = 0;
+pub var D_MANT : u64 = 0;
+pub var D_CLS : u32 = 0;
+pub var D_HASQ : bool = false;
+pub var D_Q : u64 = 0;
+pub var D_K : i64 = 0;
+
+/// decodeBits(bits, L)
+fn decode_bits(bits: u64) -> u32 {
+ D_SIGN = (bits >> (LB - 1)) & 1;
+ D_EXP = (bits >> LM) & LEXPMAX;
+ D_MANT = bits & (pow2(LM) - 1);
+ D_HASQ = true;
+ D_Q = 0;
+ D_K = 0;
+ if (D_EXP == LEXPMAX) {
+ D_HASQ = false;
+ if (D_MANT == 0) { D_CLS = 3; } else { D_CLS = 4; }
+ return 0;
+ }
+ if (D_EXP == 0) {
+ if (D_MANT == 0) { D_CLS = 0; } else { D_CLS = 1; }
+ D_Q = D_MANT;
+ D_K = 1 - (LBIAS as i64) - (LM as i64);
+ return 0;
+ }
+ D_CLS = 2;
+ D_Q = pow2(LM) + D_MANT;
+ D_K = (D_EXP as i64) - (LBIAS as i64) - (LM as i64);
+ return 0;
+}
+
+/// hexOf(bits, L): 0x and upper-case digits, padded to ceil(bits / 4).
+fn hex_to(o: []u8, n0: u32, bits: u64) -> u32 {
+ var n : u32 = raw(o, n0, "0x");
+ const digits : u64 = (LB + 3) / 4;
+ var i : u64 = digits;
+ while (i > 0) {
+ i = i - 1;
+ const d : u64 = (bits >> (i * 4)) & 15;
+ if (d < 10) { n = put(o, n, (d as u8) + 48); } else { n = put(o, n, (d as u8) + 55); }
+ }
+ return n;
+}
+
+; --- decimal text of exact values ----------------------------------------------------------------------------------
+/// exactDecimal(q, k, neg)
+fn exact_decimal(o: []u8, n0: u32, q: u64, k: i64, neg: bool) -> u32 {
+ var n : u32 = n0;
+ if (neg) { n = put(o, n, 45); }
+ if (k >= 0) {
+ bn_from_u64(4, q);
+ bn_shl(4, 4, k as u32);
+ return bn_dec(o, n, 4);
+ }
+ const kk : u32 = (0 - k) as u32;
+ bn_from_u64(4, q);
+ var i : u32 = 0;
+ while (i < kk) {
+ bn_mul_small(4, 4, 5);
+ i = i + 1;
+ }
+ var tmp : [512]u8 = [0; 512];
+ var tl : u32 = bn_dec(tmp[0..], 0, 4);
+ // padStart(-k + 1, '0')
+ var digits : [640]u8 = [0; 640];
+ var dl : u32 = 0;
+ var pad : u32 = 0;
+ if (tl < kk + 1) { pad = kk + 1 - tl; }
+ var z : u32 = 0;
+ while (z < pad) { digits[dl as usize] = 48; dl = dl + 1; z = z + 1; }
+ z = 0;
+ while (z < tl) { digits[dl as usize] = tmp[z as usize]; dl = dl + 1; z = z + 1; }
+ const cut : u32 = dl - kk;
+ // the integer part, a point, the fraction without its trailing zeros
+ var fe : u32 = dl;
+ while (fe > cut and digits[(fe - 1) as usize] == 48) { fe = fe - 1; }
+ var j : u32 = 0;
+ while (j < cut) { n = put(o, n, digits[j as usize]); j = j + 1; }
+ if (fe > cut) {
+ n = put(o, n, 46);
+ j = cut;
+ while (j < fe) { n = put(o, n, digits[j as usize]); j = j + 1; }
+ }
+ return n;
+}
+
+fn ge10(pp: i32) -> bool {
+ // R6 >= R7 * 10^pp (pp >= 0), or R6 * 10^-pp >= R7
+ if (pp >= 0) {
+ bn_mul_pow10(8, 7, pp as u32);
+ return bn_cmp(6, 8) >= 0;
+ }
+ bn_mul_pow10(8, 6, (0 - pp) as u32);
+ return bn_cmp(8, 7) >= 0;
+}
+
+/// fmtSig(num, den, sig) for the magnitude in R6 over R7 (and a sign): significant digits, plain or e-notation.
+fn fmt_sig(o: []u8, n0: u32, neg: bool, sig: u32) -> u32 {
+ var n : u32 = n0;
+ if (BLEN[6] == 0) { return put(o, n, 48); }
+ const diff : i64 = (bn_bitlen(6) as i64) - (bn_bitlen(7) as i64);
+ var x : i64 = diff * 30103;
+ var rem : i64 = x % 100000;
+ if (rem < 0) { rem = rem + 100000; }
+ var p : i32 = ((x - rem) / 100000) as i32;
+ while (!ge10(p)) { p = p - 1; }
+ while (ge10(p + 1)) { p = p + 1; }
+ const shift : i32 = (sig as i32) - 1 - p;
+ if (shift >= 0) {
+ bn_mul_pow10(9, 6, shift as u32);
+ bn_copy(10, 7);
+ } else {
+ bn_copy(9, 6);
+ bn_mul_pow10(10, 7, (0 - shift) as u32);
+ }
+ // m = (2 * sn + sd) / (2 * sd)
+ bn_shl(11, 9, 1);
+ bn_add(11, 11, 10);
+ bn_shl(12, 10, 1);
+ bn_divmod(13, 8, 11, 12);
+ var m : u64 = bn_u64(13);
+ if (m >= pow10_small(sig)) {
+ m = m / 10;
+ p = p + 1;
+ }
+ var digits : [24]u8 = [0; 24];
+ const dl : u32 = dec_to(digits[0..], m);
+ if (neg) { n = put(o, n, 45); }
+ if (p >= -4 and p < (sig as i32) + 2) {
+ var body : [64]u8 = [0; 64];
+ var bl : u32 = 0;
+ if (p >= 0) {
+ const int_len : u32 = (p + 1) as u32;
+ var j : u32 = 0;
+ while (j < int_len) {
+ if (j < dl) { body[bl as usize] = digits[j as usize]; } else { body[bl as usize] = 48; }
+ bl = bl + 1;
+ j = j + 1;
+ }
+ if (dl > int_len) {
+ body[bl as usize] = 46;
+ bl = bl + 1;
+ j = int_len;
+ while (j < dl) { body[bl as usize] = digits[j as usize]; bl = bl + 1; j = j + 1; }
+ }
+ } else {
+ body[0] = 48;
+ body[1] = 46;
+ bl = 2;
+ var z : i32 = 0;
+ while (z < 0 - p - 1) { body[bl as usize] = 48; bl = bl + 1; z = z + 1; }
+ var j2 : u32 = 0;
+ while (j2 < dl) { body[bl as usize] = digits[j2 as usize]; bl = bl + 1; j2 = j2 + 1; }
+ }
+ var has_dot : bool = false;
+ var t : u32 = 0;
+ while (t < bl) { if (body[t as usize] == 46) { has_dot = true; } t = t + 1; }
+ if (has_dot) {
+ while (bl > 0 and body[(bl - 1) as usize] == 48) { bl = bl - 1; }
+ if (bl > 0 and body[(bl - 1) as usize] == 46) { bl = bl - 1; }
+ }
+ var w : u32 = 0;
+ while (w < bl) { n = put(o, n, body[w as usize]); w = w + 1; }
+ return n;
+ }
+ n = put(o, n, digits[0]);
+ var re : u32 = dl;
+ while (re > 1 and digits[(re - 1) as usize] == 48) { re = re - 1; }
+ if (re > 1) {
+ n = put(o, n, 46);
+ var j3 : u32 = 1;
+ while (j3 < re) { n = put(o, n, digits[j3 as usize]); j3 = j3 + 1; }
+ }
+ n = put(o, n, 101);
+ if (p < 0) {
+ n = put(o, n, 45);
+ return dnum(o, n, (0 - p) as u64);
+ }
+ return dnum(o, n, p as u64);
+}
+
+fn dnum(o: []u8, n0: u32, v: u64) -> u32 {
+ var tmp : [24]u8 = [0; 24];
+ const m : u32 = dec_to(tmp[0..], v);
+ return raw(o, n0, tmp[0..(m as usize)]);
+}
+
+; --- balanced ternary ------------------------------------------------------------------------------------------------
+pub var TR : [200]i32 = [0; 200];
+pub var TR1 : [200]i32 = [0; 200];
+pub var TR_N : usize = 0;
+pub var TR_INT : usize = 0;
+pub var TR_WIDE : bool = false;
+pub var TR_NEG : bool = false;
+
+/// ternaryOf(neg, n, d, frac, maxInt) for n, d in R2, R3. On success the trits are in TR (most significant
+/// first), the scaled integer's magnitude in R5.
+fn ternary_of(neg: bool, frac: u32, max_int: u32) -> u32 {
+ TR_WIDE = false;
+ TR_N = 0;
+ TR_NEG = neg;
+ if (bn_bitlen(2) > bn_bitlen(3) and bn_bitlen(2) - bn_bitlen(3) > WIDE_N) {
+ TR_WIDE = true;
+ return 0;
+ }
+ bn_copy(4, 2);
+ var i : u32 = 0;
+ while (i < frac) { bn_mul_small(4, 4, 3); i = i + 1; }
+ // T = (2 * scaled + d) / (2 * d)
+ bn_shl(8, 4, 1);
+ bn_add(8, 8, 3);
+ bn_shl(9, 3, 1);
+ bn_divmod(5, 10, 8, 9);
+ bn_copy(12, 5);
+ var digits : [200]i32 = [0; 200];
+ var dn : usize = 0;
+ while (BLEN[12] > 0 and dn < 190) {
+ const r : u64 = bn_divmod_small(12, 12, 3);
+ if (r == 0) { digits[dn] = 0; }
+ if (r == 1) { digits[dn] = 1; }
+ if (r == 2) {
+ digits[dn] = -1;
+ bn_add_small(12, 12, 1);
+ }
+ dn = dn + 1;
+ }
+ while (dn < (frac as usize) + 1) { digits[dn] = 0; dn = dn + 1; }
+ const int_trits : usize = dn - (frac as usize);
+ if (int_trits > (max_int as usize)) {
+ TR_WIDE = true;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < dn) {
+ var t : i32 = digits[dn - 1 - k];
+ if (neg) { t = 0 - t; }
+ TR[k] = t;
+ k = k + 1;
+ }
+ TR_N = dn;
+ TR_INT = int_trits;
+ return 0;
+}
+
+/// The signed value the trits spell, as a magnitude in R2 and its sign in the result.
+fn trits_value(src_arr: u32) -> bool {
+ bn_zero(8);
+ bn_zero(9);
+ var k : usize = 0;
+ while (k < TR_N) {
+ var t : i32 = TR[k];
+ if (src_arr == 1) { t = TR1[k]; }
+ bn_mul_small(8, 8, 3);
+ bn_mul_small(9, 9, 3);
+ if (t == 1) { bn_add_small(8, 8, 1); }
+ if (t == -1) { bn_add_small(9, 9, 1); }
+ k = k + 1;
+ }
+ if (bn_cmp(8, 9) >= 0) {
+ bn_sub(2, 8, 9);
+ return false;
+ }
+ bn_sub(2, 9, 8);
+ return true;
+}
+
+; --- state --------------------------------------------------------------------------------------------------------------
+pub var X : [8256]u8 = [0; 8256];
+pub var X_N : usize = 0;
+pub var ST_R : u32 = 0;
+pub var ST_T : u32 = 0;
+pub var HASH_RAW : [8256]u8 = [0; 8256];
+pub var HASH_N : usize = 0;
+pub var BUILT : bool = false;
+pub var LAYOUT_KIND : u32 = 2;
+pub var LAYOUT_DIFF : [96]u8 = [0; 96];
+pub var LAYOUT_DIFF_N : u32 = 0;
+pub var CK_OKV : u32 = 0;
+pub var CK_OKT : u32 = 0;
+pub var CK_FAIL : [1024]u8 = [0; 1024];
+pub var CK_FAIL_N : u32 = 0;
+pub var CUR_OK : bool = false;
+pub var CUR_BITS : u64 = 0;
+pub var CARD_EMPTY : bool = true;
+pub var BAD_HIDDEN : bool = true;
+pub var HAVE_RES : bool = false;
+pub var COPIED : bool = false;
+pub var HASH_OUT : [8400]u8 = [0; 8400];
+pub var HASH_OUT_N : u32 = 0;
+pub var EFF_VAL : bool = false;
+pub var EFF_COPY : bool = false;
+pub var EFF_SEL : bool = false;
+pub var EFF_TIMER : bool = false;
+pub var BTN_ACT : [64]u32 = [0; 64];
+pub var BTN_N : u32 = 0;
+pub var H_X_SET : bool = false;
+pub var H_X : [8256]u8 = [0; 8256];
+pub var H_X_N : usize = 0;
+pub var H_R : i32 = -1;
+pub var H_T : i32 = -1;
+
+; the last good analysis
+pub var RES_BITS : u64 = 0;
+pub var RES_KIND : u32 = 0;
+pub var RES_DIR : i32 = 0;
+pub var RES_TIE : bool = false;
+pub var RES_OVER : bool = false;
+pub var RES_UNDER : bool = false;
+pub var RES_SIGN : u64 = 0;
+pub var RES_EXP : u64 = 0;
+pub var RES_MANT : u64 = 0;
+pub var RES_CLS : u32 = 0;
+pub var RES_HASQ : bool = false;
+pub var RES_R : u32 = 0;
+pub var RES_NOTE : u32 = 0;
+pub var RES_HEX : [48]u8 = [0; 48];
+pub var RES_HEX_N : u32 = 0;
+pub var RES_DEC : [256]u8 = [0; 256];
+pub var RES_DEC_N : u32 = 0;
+pub var RES_HAS_ERR : bool = false;
+pub var RES_ERR : [160]u8 = [0; 160];
+pub var RES_ERR_N : u32 = 0;
+pub var RES_HAS_REL : bool = false;
+pub var RES_REL : [160]u8 = [0; 160];
+pub var RES_REL_N : u32 = 0;
+pub var RES_ECHO : [8256]u8 = [0; 8256];
+pub var RES_ECHO_N : usize = 0;
+pub var RES_TSTATE : u32 = 0;
+pub var RES_TR : [200]i32 = [0; 200];
+pub var RES_TR_N : usize = 0;
+pub var RES_TR_INT : usize = 0;
+pub var RES_APPROX : [160]u8 = [0; 160];
+pub var RES_APPROX_N : u32 = 0;
+pub var RES_TERR : [160]u8 = [0; 160];
+pub var RES_TERR_N : u32 = 0;
+pub var RES_TRT : bool = false;
+pub var RES_RTBITS : u64 = 0;
+
+fn btn_mark(a: u32) -> u32 {
+ if (BTN_N < 64) { BTN_ACT[BTN_N as usize] = a; }
+ BTN_N = BTN_N + 1;
+ return 0;
+}
+
+; --- the address -----------------------------------------------------------------------------------------------------------
+fn hex_val(b: u8) -> i32 {
+ if (b >= 48 and b <= 57) { return (b as i32) - 48; }
+ if (b >= 97 and b <= 102) { return (b as i32) - 87; }
+ if (b >= 65 and b <= 70) { return (b as i32) - 55; }
+ return -1;
+}
+
+/// A form-urlencoded component s[lo..hi) decoded (plus is a space, %XX a byte) into dst; its length.
+fn url_decode(dst: []u8, s: []const u8, lo: usize, hi: usize) -> usize {
+ var n : usize = 0;
+ var k : usize = lo;
+ while (k < hi and n < dst.len) {
+ const b : u8 = s[k];
+ if (b == 43) {
+ dst[n] = 32;
+ n = n + 1;
+ k = k + 1;
+ } else {
+ if (b == 37 and k + 2 < hi and hex_val(s[k + 1]) >= 0 and hex_val(s[k + 2]) >= 0) {
+ dst[n] = ((hex_val(s[k + 1]) * 16 + hex_val(s[k + 2])) as u8);
+ n = n + 1;
+ k = k + 3;
+ } else {
+ dst[n] = b;
+ n = n + 1;
+ k = k + 1;
+ }
+ }
+ }
+ return n;
+}
+
+/// readHash(): x, r and t of the address's fragment (a leading # dropped); the first of each name counts.
+fn read_hash() -> u32 {
+ H_X_SET = false;
+ H_X_N = 0;
+ H_R = -1;
+ H_T = -1;
+ const h : []const u8 = HASH_RAW[0..HASH_N];
+ var start : usize = 0;
+ if (h.len > 0 and h[0] == 35) { start = 1; }
+ var seen_x : bool = false;
+ var seen_r : bool = false;
+ var seen_t : bool = false;
+ var p : usize = start;
+ while (p <= h.len) {
+ var e : usize = p;
+ while (e < h.len and h[e] != 38) { e = e + 1; }
+ if (e > p) {
+ var eq : usize = p;
+ while (eq < e and h[eq] != 61) { eq = eq + 1; }
+ var name : [64]u8 = [0; 64];
+ const nl : usize = url_decode(name[0..], h, p, eq);
+ var vlo : usize = eq + 1;
+ if (eq >= e) { vlo = e; }
+ if (nl == 1 and name[0] == 120 and !seen_x) {
+ seen_x = true;
+ H_X_SET = true;
+ H_X_N = url_decode(H_X[0..], h, vlo, e);
+ }
+ if (nl == 1 and name[0] == 114 and !seen_r) {
+ seen_r = true;
+ var v : [64]u8 = [0; 64];
+ const vl : usize = url_decode(v[0..], h, vlo, e);
+ H_R = -2;
+ if (eq_s(v[0..vl], K_ROUNDING_IDS[0])) { H_R = 0; }
+ if (eq_s(v[0..vl], K_ROUNDING_IDS[1])) { H_R = 1; }
+ }
+ if (nl == 1 and name[0] == 116 and !seen_t) {
+ seen_t = true;
+ var v2 : [64]u8 = [0; 64];
+ const vl2 : usize = url_decode(v2[0..], h, vlo, e);
+ H_T = 0;
+ if (vl2 == 1 and v2[0] == 49) { H_T = 1; }
+ }
+ }
+ p = e + 1;
+ }
+ return 0;
+}
+
+fn eq_s(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var k : usize = 0;
+ while (k < a.len) {
+ if (a[k] != b[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// URLSearchParams.toString for x, r, t, with a leading #.
+fn build_hash_out() -> u32 {
+ var n : u32 = raw(HASH_OUT[0..], 0, "#x=");
+ var k : usize = 0;
+ while (k < X_N) {
+ const b : u8 = X[k];
+ if ((b >= 48 and b <= 57) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 42 or b == 45 or b == 46 or b == 95) {
+ n = put(HASH_OUT[0..], n, b);
+ } else {
+ if (b == 32) {
+ n = put(HASH_OUT[0..], n, 43);
+ } else {
+ n = put(HASH_OUT[0..], n, 37);
+ const hi : u8 = b / 16;
+ const lo : u8 = b % 16;
+ if (hi < 10) { n = put(HASH_OUT[0..], n, hi + 48); } else { n = put(HASH_OUT[0..], n, hi + 55); }
+ if (lo < 10) { n = put(HASH_OUT[0..], n, lo + 48); } else { n = put(HASH_OUT[0..], n, lo + 55); }
+ }
+ }
+ k = k + 1;
+ }
+ n = raw(HASH_OUT[0..], n, "&r=");
+ n = raw(HASH_OUT[0..], n, K_ROUNDING_IDS[ST_R as usize]);
+ n = raw(HASH_OUT[0..], n, "&t=");
+ if (ST_T == 1) { n = put(HASH_OUT[0..], n, 49); } else { n = put(HASH_OUT[0..], n, 48); }
+ HASH_OUT_N = n;
+ return 0;
+}
+
+; --- the analysis ---------------------------------------------------------------------------------------------------------
+fn x_trim(lo_out: u32) -> usize {
+ // state.x.trim(): the range of X that is not white space, copied to RES_ECHO; its length
+ var lo : usize = 0;
+ var hi : usize = X_N;
+ while (lo < hi) {
+ const w : usize = ws_fwd(X[0..X_N], lo);
+ if (w == 0) { break; }
+ lo = lo + w;
+ }
+ while (hi > lo) {
+ const w2 : usize = ws_back(X[0..hi], hi);
+ if (w2 == 0) { break; }
+ hi = hi - w2;
+ }
+ var n : usize = 0;
+ while (lo < hi) {
+ RES_ECHO[n] = X[lo];
+ n = n + 1;
+ lo = lo + 1;
+ }
+ return n;
+}
+
+fn same_trits(a_n: usize) -> bool {
+ var k : usize = 0;
+ while (k < a_n) {
+ if (TR[k] != TR1[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// analyse(raw, c, L, mode): fills RES_* and returns true, or returns false when the text is not read.
+fn analyse(even: bool) -> bool {
+ parse_number(X[0..X_N]);
+ if (P_KIND == 0) { return false; }
+ const kind : u32 = P_KIND;
+ const pneg : bool = P_NEG;
+ encode_parsed(even);
+ RES_BITS = E_BITS;
+ RES_DIR = E_DIR;
+ RES_TIE = E_TIE;
+ RES_OVER = E_OVER;
+ RES_UNDER = E_UNDER;
+ RES_KIND = kind;
+ decode_bits(E_BITS);
+ RES_SIGN = D_SIGN;
+ RES_EXP = D_EXP;
+ RES_MANT = D_MANT;
+ RES_CLS = D_CLS;
+ RES_HASQ = D_HASQ;
+ const dq : u64 = D_Q;
+ const dk : i64 = D_K;
+ const dneg : bool = D_SIGN == 1;
+ RES_R = ST_R;
+ RES_NOTE = ST_T;
+ RES_HEX_N = hex_to(RES_HEX[0..], 0, E_BITS);
+ if (D_HASQ) { RES_DEC_N = exact_decimal(RES_DEC[0..], 0, dq, dk, dneg); } else { RES_DEC_N = 0; }
+ RES_ECHO_N = x_trim(0);
+ // the error of the rounding
+ RES_HAS_ERR = false;
+ RES_HAS_REL = false;
+ if (kind == 4 and RES_HASQ) {
+ var shift : u32 = 0;
+ bn_from_u64(5, dq);
+ if (dk >= 0) {
+ bn_shl(5, 5, dk as u32);
+ } else {
+ shift = (0 - dk) as u32;
+ }
+ bn_mul(4, 5, W_R_D);
+ if (shift > 0) { bn_shl(5, W_R_N, shift); } else { bn_copy(5, W_R_N); }
+ var diff_neg : bool = false;
+ if (bn_cmp(4, 5) >= 0) {
+ bn_sub(6, 4, 5);
+ } else {
+ bn_sub(6, 5, 4);
+ diff_neg = true;
+ }
+ if (shift > 0) { bn_shl(7, W_R_D, shift); } else { bn_copy(7, W_R_D); }
+ RES_ERR_N = fmt_sig(RES_ERR[0..], 0, diff_neg != pneg, K_SIG_DIGITS as u32);
+ RES_HAS_ERR = true;
+ if (BLEN[W_R_N] > 0) {
+ if (shift > 0) { bn_shl(7, W_R_N, shift); } else { bn_copy(7, W_R_N); }
+ RES_REL_N = fmt_sig(RES_REL[0..], 0, diff_neg, K_SIG_DIGITS as u32);
+ RES_HAS_REL = true;
+ }
+ }
+ // the ternary row: the number the input names, or the raw bits decoded
+ var has_src : bool = false;
+ var sneg : bool = false;
+ if (kind == 4) {
+ has_src = true;
+ sneg = pneg;
+ bn_copy(2, W_R_N);
+ bn_copy(3, W_R_D);
+ }
+ if (kind == 1 and RES_HASQ) {
+ has_src = true;
+ sneg = dneg;
+ if (dk >= 0) {
+ bn_from_u64(2, dq);
+ bn_shl(2, 2, dk as u32);
+ bn_from_u64(3, 1);
+ } else {
+ bn_from_u64(2, dq);
+ bn_from_u64(3, 1);
+ bn_shl(3, 3, (0 - dk) as u32);
+ }
+ }
+ RES_TSTATE = 0;
+ RES_TRT = false;
+ if (has_src) {
+ const frac : u32 = K_TERNARY_FRAC_TRITS as u32;
+ ternary_of(sneg, frac, K_TERNARY_MAX_TRITS as u32);
+ if (TR_WIDE) {
+ RES_TSTATE = 1;
+ } else {
+ RES_TSTATE = 2;
+ RES_TR_N = TR_N;
+ RES_TR_INT = TR_INT;
+ var q : usize = 0;
+ while (q < TR_N) { RES_TR[q] = TR[q]; TR1[q] = TR[q]; q = q + 1; }
+ // t.approx
+ bn_copy(6, 5);
+ bn_set_pow(7, 3, frac);
+ RES_APPROX_N = fmt_sig(RES_APPROX[0..], 0, sneg, 10);
+ // t.err = T * d - s * n * 3^frac over 3^frac * d
+ bn_mul(4, 5, 3);
+ bn_copy(8, 2);
+ var i : u32 = 0;
+ while (i < frac) { bn_mul_small(8, 8, 3); i = i + 1; }
+ var tneg : bool = false;
+ if (bn_cmp(4, 8) >= 0) {
+ bn_sub(6, 4, 8);
+ } else {
+ bn_sub(6, 8, 4);
+ tneg = true;
+ }
+ bn_copy(7, 3);
+ i = 0;
+ while (i < frac) { bn_mul_small(7, 7, 3); i = i + 1; }
+ RES_TERR_N = fmt_sig(RES_TERR[0..], 0, tneg != sneg, K_SIG_DIGITS as u32);
+ // read the trits back and write them again
+ const back_neg : bool = trits_value(0);
+ bn_set_pow(3, 3, frac);
+ ternary_of(back_neg, frac, K_TERNARY_MAX_TRITS as u32);
+ RES_TRT = !TR_WIDE and TR_N == RES_TR_N and same_trits(RES_TR_N);
+ }
+ }
+ // the decoded value, encoded again
+ if (RES_HASQ) {
+ if (dk >= 0) {
+ bn_from_u64(W_R_N, dq);
+ bn_shl(W_R_N, W_R_N, dk as u32);
+ bn_from_u64(W_R_D, 1);
+ } else {
+ bn_from_u64(W_R_N, dq);
+ bn_from_u64(W_R_D, 1);
+ bn_shl(W_R_D, W_R_D, (0 - dk) as u32);
+ }
+ encode_exact(dneg, even);
+ RES_RTBITS = E_BITS;
+ } else {
+ var sb : u64 = 0;
+ if (dneg) { sb = pow2(LB - 1); }
+ if (RES_CLS == 3) { RES_RTBITS = sb + (LEXPMAX << LM); } else { RES_RTBITS = LNAN; }
+ }
+ return true;
+}
+
+fn is_blank() -> bool {
+ var lo : usize = 0;
+ while (lo < X_N) {
+ const w : usize = ws_fwd(X[0..X_N], lo);
+ if (w == 0) { return false; }
+ lo = lo + w;
+ }
+ return true;
+}
+
+/// update(writeHash)
+fn update_page(write_hash: bool) -> u32 {
+ if (write_hash) { build_hash_out(); }
+ const a : bool = analyse(ST_R == 0);
+ CUR_OK = a;
+ if (a) { CUR_BITS = RES_BITS; HAVE_RES = true; }
+ BAD_HIDDEN = a or is_blank();
+ CARD_EMPTY = !a;
+ return 0;
+}
+
+fn set_x_from(s: []const u8) -> u32 {
+ var n : usize = s.len;
+ if (n > W_X_CAP) { n = W_X_CAP; }
+ var k : usize = 0;
+ while (k < n) { X[k] = s[k]; k = k + 1; }
+ X_N = n;
+ return 0;
+}
+
+; --- the layout file and the self-check -----------------------------------------------------------------------------------
+pub var RL : [8]u64 = [0; 8];
+
+fn skip_space(t: []const u8, p0: usize) -> usize {
+ var p : usize = p0;
+ while (p < t.len) {
+ const w : usize = ws_fwd(t, p);
+ if (w == 0) { break; }
+ p = p + w;
+ }
+ return p;
+}
+
+fn word_at(t: []const u8, p: usize, w: []const u8) -> bool {
+ if (p + w.len > t.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (t[p + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The value of pub const NAME : type = value ; on its own line, first such line; -1 when none.
+fn find_const(t: []const u8, name: []const u8) -> i64 {
+ var ls : usize = 0;
+ while (ls <= t.len) {
+ var p : usize = skip_space(t, ls);
+ var good : bool = true;
+ if (!word_at(t, p, "pub")) { good = false; }
+ if (good) {
+ p = p + 3;
+ const a : usize = skip_space(t, p);
+ if (a == p) { good = false; }
+ p = a;
+ }
+ if (good and !word_at(t, p, "const")) { good = false; }
+ if (good) {
+ p = p + 5;
+ const a2 : usize = skip_space(t, p);
+ if (a2 == p) { good = false; }
+ p = a2;
+ }
+ if (good and !word_at(t, p, name)) { good = false; }
+ if (good) {
+ p = skip_space(t, p + name.len);
+ if (p >= t.len or t[p] != 58) { good = false; }
+ }
+ if (good) {
+ p = skip_space(t, p + 1);
+ const tl : usize = p;
+ while (p < t.len and ((t[p] >= 97 and t[p] <= 122) or (t[p] >= 48 and t[p] <= 57))) { p = p + 1; }
+ if (p == tl) { good = false; }
+ }
+ if (good) {
+ p = skip_space(t, p);
+ if (p >= t.len or t[p] != 61) { good = false; }
+ }
+ if (good) {
+ p = skip_space(t, p + 1);
+ var v : u64 = 0;
+ var digits : usize = 0;
+ if (p + 2 < t.len + 1 and word_at(t, p, "0x") and p + 2 < t.len and hex_val(t[p + 2]) >= 0) {
+ p = p + 2;
+ while (p < t.len and hex_val(t[p]) >= 0) {
+ if (v < 1152921504606846976) { v = v * 16 + (hex_val(t[p]) as u64); }
+ p = p + 1;
+ digits = digits + 1;
+ }
+ } else {
+ while (p < t.len and t[p] >= 48 and t[p] <= 57) {
+ if (v < 1844674407370955161) { v = v * 10 + ((t[p] - 48) as u64); }
+ p = p + 1;
+ digits = digits + 1;
+ }
+ }
+ if (digits == 0) { good = false; }
+ if (good) {
+ p = skip_space(t, p);
+ if (p >= t.len or t[p] != 59) { good = false; }
+ }
+ if (good) { return v as i64; }
+ }
+ // the next line start
+ var q : usize = ls;
+ while (q < t.len and t[q] != 10 and t[q] != 13) { q = q + 1; }
+ ls = q + 1;
+ }
+ return -1;
+}
+
+/// layoutFromSpecText: true and RL = bits, e, m, bias, expMax, nan when the file reads and is consistent.
+fn read_layout(t: []const u8) -> bool {
+ const sign_shift : i64 = find_const(t, "SIGN_SHIFT");
+ const exp_shift : i64 = find_const(t, "EXP_SHIFT");
+ const exp_max : i64 = find_const(t, "EXP_MAX");
+ const bias : i64 = find_const(t, "BIAS");
+ const mant_mask : i64 = find_const(t, "MANT_MASK");
+ const nan : i64 = find_const(t, "GF16_NAN");
+ if (sign_shift < 0 or exp_shift < 0 or exp_max < 0 or bias < 0 or mant_mask < 0 or nan < 0) { return false; }
+ const mant : i64 = exp_shift;
+ const exp : i64 = sign_shift - exp_shift;
+ if (!(mant > 0 and exp > 0)) { return false; }
+ if (mant > 30 or exp > 8 or sign_shift + 1 > 40) { return false; }
+ if (mant_mask != (1 << mant) - 1 or exp_max != (1 << exp) - 1) { return false; }
+ RL[0] = (sign_shift + 1) as u64;
+ RL[1] = exp as u64;
+ RL[2] = mant as u64;
+ RL[3] = bias as u64;
+ RL[4] = exp_max as u64;
+ RL[5] = nan as u64;
+ return true;
+}
+
+fn push_name(n0: u32, w: []const u8) -> u32 {
+ var n : u32 = n0;
+ if (n > 0) { n = raw(LAYOUT_DIFF[0..], n, ", "); }
+ return raw(LAYOUT_DIFF[0..], n, w);
+}
+
+fn load_layout(t: []const u8, have: bool) -> u32 {
+ set_spec_layout();
+ LAYOUT_KIND = 2;
+ LAYOUT_DIFF_N = 0;
+ if (have and read_layout(t)) {
+ var n : u32 = 0;
+ if (RL[0] != (K_BITS as u64)) { n = push_name(n, "bits"); }
+ if (1 != (K_SIGN_BITS as u64)) { n = push_name(n, "s"); }
+ if (RL[1] != (K_EXP_BITS as u64)) { n = push_name(n, "e"); }
+ if (RL[2] != (K_MANT_BITS as u64)) { n = push_name(n, "m"); }
+ if (RL[3] != (K_BIAS as u64)) { n = push_name(n, "bias"); }
+ if (RL[4] != (K_EXP_MAX as u64)) { n = push_name(n, "expMax"); }
+ if (RL[5] != (K_NAN_BITS as u64)) { n = push_name(n, "nan"); }
+ LAYOUT_DIFF_N = n;
+ LB = RL[0];
+ LE = RL[1];
+ LM = RL[2];
+ LBIAS = RL[3];
+ LEXPMAX = RL[4];
+ LNAN = RL[5];
+ if (n > 0) { LAYOUT_KIND = 1; } else { LAYOUT_KIND = 0; }
+ }
+ return 0;
+}
+
+fn check_name(n0: u32, w: []const u8) -> u32 {
+ var n : u32 = n0;
+ if (n > 0) { n = raw(CK_FAIL[0..], n, ", "); }
+ return raw(CK_FAIL[0..], n, w);
+}
+
+/// runChecks(c, L): the 20 conformance vectors and the four tie vectors.
+fn run_checks() -> u32 {
+ CK_OKV = 0;
+ CK_OKT = 0;
+ CK_FAIL_N = 0;
+ var i : usize = 0;
+ var failed : u32 = 0;
+ while (i < 20) {
+ parse_number(K_CHECK_INPUTS[i]);
+ var good : bool = false;
+ if (P_KIND != 0) {
+ encode_parsed(true);
+ good = E_BITS == (K_CHECK_BITS[i] as u64);
+ }
+ if (good) { CK_OKV = CK_OKV + 1; } else { failed = check_name(failed, K_CHECK_NAMES[i]); }
+ i = i + 1;
+ }
+ i = 0;
+ while (i < 2) {
+ var m : u32 = 0;
+ while (m < 2) {
+ parse_number(K_TIE_INPUTS[i]);
+ encode_parsed(m == 0);
+ var want : u64 = K_TIE_BITS_EVEN[i] as u64;
+ if (m == 1) { want = K_TIE_BITS_ZERO[i] as u64; }
+ if (E_BITS == want) {
+ CK_OKT = CK_OKT + 1;
+ } else {
+ failed = check_name(failed, K_TIE_INPUTS[i]);
+ failed = raw(CK_FAIL[0..], failed, "/");
+ failed = raw(CK_FAIL[0..], failed, K_ROUNDING_IDS[m as usize]);
+ }
+ m = m + 1;
+ }
+ i = i + 1;
+ }
+ CK_FAIL_N = failed;
+ return 0;
+}
+
+/// The page is built once the layout file has been read or has failed.
+fn build_page(t: []const u8, have: bool) -> u32 {
+ load_layout(t, have);
+ // 3^K_TERNARY_MAX_TRITS - 1 has as many bits as ceil(log2(3^max))
+ bn_set_pow(4, 3, K_TERNARY_MAX_TRITS as u32);
+ bn_from_u64(5, 1);
+ bn_sub(4, 4, 5);
+ WIDE_N = bn_bitlen(4) + 2;
+ run_checks();
+ read_hash();
+ if (H_X_SET) { set_x_from(H_X[0..H_X_N]); } else { set_x_from(K_DEFAULT_INPUT); }
+ ST_R = 0;
+ if (H_R >= 0) { ST_R = H_R as u32; }
+ ST_T = 0;
+ if (H_T == 1) { ST_T = 1; }
+ BUILT = true;
+ COPIED = false;
+ HAVE_RES = false;
+ update_page(false);
+ return 0;
+}
+
+; --- drawing ------------------------------------------------------------------------------------------------------------------
+fn sdec(o: []u8, v: i64) -> u32 {
+ var n : u32 = 0;
+ var x : i64 = v;
+ if (v < 0) {
+ o[0] = 45;
+ n = 1;
+ x = 0 - v;
+ }
+ var tmp : [24]u8 = [0; 24];
+ const m : u32 = dec_to(tmp[0..], x as u64);
+ return raw(o, n, tmp[0..(m as usize)]);
+}
+
+fn udec(o: []u8, v: u64) -> u32 {
+ return dec_to(o, v);
+}
+
+fn dd_open(o: []u8, n0: u32, term: []const u8, cls: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, term);
+ n = raw(o, n, "
0) {
+ n = raw(o, n, " class=\"");
+ n = raw(o, n, cls);
+ n = raw(o, n, "\"");
+ }
+ return raw(o, n, ">");
+}
+
+fn row_text(o: []u8, n0: u32, term: []const u8, cls: []const u8, val: []const u8) -> u32 {
+ var n : u32 = dd_open(o, n0, term, cls);
+ n = text(o, n, val);
+ return raw(o, n, "
");
+}
+
+fn emit_gf16_facts(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (!HAVE_RES) { return n; }
+ var a : [32]u8 = [0; 32];
+ var b : [32]u8 = [0; 32];
+ var c : [32]u8 = [0; 32];
+ n = row_text(o, n, SAY_FIELD_NAMES[0], "g16-f0", SAY_SIGN_DETAIL[RES_SIGN as usize]);
+ n = dd_open(o, n, SAY_FIELD_NAMES[1], "g16-f1");
+ const al : u32 = udec(a[0..], RES_EXP);
+ if (RES_EXP == LEXPMAX) {
+ n = fill_named(o, n, SAY_EXP_SPECIAL, "e", a[0..(al as usize)], "", "", "", "", "", "", "", "", "", "");
+ } else {
+ if (RES_EXP == 0) {
+ const ul : u32 = sdec(b[0..], 1 - (LBIAS as i64));
+ n = fill_named(o, n, SAY_EXP_SUBNORMAL, "u", b[0..(ul as usize)], "", "", "", "", "", "", "", "", "", "");
+ } else {
+ const bl : u32 = udec(b[0..], LBIAS);
+ const ul2 : u32 = sdec(c[0..], (RES_EXP as i64) - (LBIAS as i64));
+ n = fill_named(o, n, SAY_EXP_DETAIL, "e", a[0..(al as usize)], "b", b[0..(bl as usize)], "u", c[0..(ul2 as usize)], "", "", "", "", "", "");
+ }
+ }
+ n = raw(o, n, "");
+ n = dd_open(o, n, SAY_FIELD_NAMES[2], "g16-f2");
+ const ml : u32 = udec(a[0..], RES_MANT);
+ const ol : u32 = udec(b[0..], pow2(LM));
+ n = fill_named(o, n, SAY_MANT_DETAIL, "m", a[0..(ml as usize)], "one", b[0..(ol as usize)], "", "", "", "", "", "", "", "");
+ n = raw(o, n, "");
+ n = row_text(o, n, SAY_CLASS_LABEL, "", SAY_CLASS_NAMES[RES_CLS as usize]);
+ if (RES_KIND == 1) {
+ n = row_text(o, n, SAY_RAW_ECHO, "", RES_HEX[0..(RES_HEX_N as usize)]);
+ } else {
+ n = row_text(o, n, SAY_INPUT_ECHO, "", RES_ECHO[0..RES_ECHO_N]);
+ }
+ if (RES_HASQ) { n = row_text(o, n, SAY_DECODED, "g16-big", RES_DEC[0..(RES_DEC_N as usize)]); }
+ if (RES_HAS_ERR) {
+ n = row_text(o, n, SAY_ERROR, "", RES_ERR[0..(RES_ERR_N as usize)]);
+ if (RES_HAS_REL) { n = row_text(o, n, SAY_REL_ERROR, "", RES_REL[0..(RES_REL_N as usize)]); }
+ }
+ if (RES_KIND == 4) {
+ n = dd_open(o, n, SAY_ROUNDED_LABEL, "");
+ if (RES_OVER) {
+ n = text(o, n, SAY_OVERFLOW);
+ } else {
+ if (RES_UNDER) {
+ n = text(o, n, SAY_UNDERFLOW);
+ } else {
+ if (RES_DIR == 0) { n = text(o, n, SAY_ROUNDED_NAMES[0]); }
+ if (RES_DIR < 0) { n = text(o, n, SAY_ROUNDED_NAMES[1]); }
+ if (RES_DIR > 0) { n = text(o, n, SAY_ROUNDED_NAMES[2]); }
+ }
+ }
+ if (RES_TIE) {
+ n = raw(o, n, "; ");
+ n = fill_named(o, n, SAY_TIE, "rule", SAY_ROUNDING_NAMES[RES_R as usize], "", "", "", "", "", "", "", "", "", "");
+ }
+ n = raw(o, n, "");
+ }
+ return n;
+}
+
+fn emit_ternary(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (!HAVE_RES) { return n; }
+ if (RES_TSTATE == 0) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_TERNARY_NONE);
+ return raw(o, n, "
");
+ }
+ if (RES_TSTATE == 1) {
+ var m : [24]u8 = [0; 24];
+ const ml : u32 = udec(m[0..], K_TERNARY_MAX_TRITS as u64);
+ n = raw(o, n, "
");
+ n = fill_named(o, n, SAY_TERNARY_TOO_WIDE, "n", m[0..(ml as usize)], "", "", "", "", "", "", "", "", "", "");
+ return raw(o, n, "
");
+ }
+ var idx : usize = 0;
+ while (idx < RES_TR_N) {
+ const d : i32 = RES_TR[idx];
+ if (idx == RES_TR_INT) { n = raw(o, n, "."); }
+ const p : i64 = (RES_TR_INT as i64) - 1 - (idx as i64);
+ var db : [24]u8 = [0; 24];
+ var pb : [24]u8 = [0; 24];
+ const dl : u32 = sdec(db[0..], d as i64);
+ const pl : u32 = sdec(pb[0..], p);
+ n = raw(o, n, "");
+ n = text(o, n, K_TRIT_GLYPHS[(RES_NOTE as usize) * 3 + ((d + 1) as usize)]);
+ n = raw(o, n, "");
+ idx = idx + 1;
+ }
+ return n;
+}
+
+fn emit_ternary_facts(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (!HAVE_RES or RES_TSTATE != 2) { return n; }
+ var cb : [24]u8 = [0; 24];
+ const cl : u32 = udec(cb[0..], RES_TR_N as u64);
+ var tmp : [160]u8 = [0; 160];
+ var tl : u32 = raw(tmp[0..], 0, SAY_APPROX);
+ tl = raw(tmp[0..], tl, " ");
+ tl = raw(tmp[0..], tl, RES_APPROX[0..(RES_APPROX_N as usize)]);
+ tl = raw(tmp[0..], tl, " (");
+ tl = fill_named(tmp[0..], tl, SAY_TRIT_COUNT, "n", cb[0..(cl as usize)], "", "", "", "", "", "", "", "", "", "");
+ tl = raw(tmp[0..], tl, ")");
+ n = row_text(o, n, SAY_TERNARY_VALUE, "", tmp[0..(tl as usize)]);
+ return row_text(o, n, SAY_ERROR, "", RES_TERR[0..(RES_TERR_N as usize)]);
+}
+
+fn same_dd(o: []u8, n0: u32, good: bool) -> u32 {
+ var n : u32 = n0;
+ if (good) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_SAME);
+ } else {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_DIFFERENT);
+ }
+ return raw(o, n, "
");
+}
+
+fn emit_roundtrip(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (!HAVE_RES) { return n; }
+ var hb : [48]u8 = [0; 48];
+ const hl : u32 = hex_to(hb[0..], 0, RES_RTBITS);
+ n = raw(o, n, "
");
+ n = fill_named(o, n, SAY_RT_GF16, "hex", hb[0..(hl as usize)], "", "", "", "", "", "", "", "", "", "");
+ n = raw(o, n, "
");
+ n = same_dd(o, n, RES_RTBITS == RES_BITS);
+ if (RES_TSTATE == 2) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_RT_TERNARY);
+ n = raw(o, n, "
");
+ n = same_dd(o, n, RES_TRT);
+ }
+ return n;
+}
+
+fn card_open(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "");
+}
+
+fn emit_layout_line(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ var file : usize = 0;
+ // fileName = K_LAYOUT_FILE without its leading ../ pairs
+ while (file + 3 <= K_LAYOUT_FILE.len and K_LAYOUT_FILE[file] == 46 and K_LAYOUT_FILE[file + 1] == 46 and K_LAYOUT_FILE[file + 2] == 47) { file = file + 3; }
+ const fname : []const u8 = K_LAYOUT_FILE[file..K_LAYOUT_FILE.len];
+ var sb : [24]u8 = [0; 24];
+ var eb : [24]u8 = [0; 24];
+ var mb : [24]u8 = [0; 24];
+ var bb : [24]u8 = [0; 24];
+ var sv : u64 = 1;
+ if (LAYOUT_KIND == 2) { sv = K_SIGN_BITS as u64; }
+ const sl : u32 = udec(sb[0..], sv);
+ const el : u32 = udec(eb[0..], LE);
+ const ml : u32 = udec(mb[0..], LM);
+ const bl : u32 = udec(bb[0..], LBIAS);
+ if (LAYOUT_KIND == 1) {
+ return fill_named(o, n, SAY_LAYOUT_MISMATCH, "file", fname, "names", LAYOUT_DIFF[0..(LAYOUT_DIFF_N as usize)], "", "", "", "", "", "", "", "");
+ }
+ if (LAYOUT_KIND == 0) {
+ return fill_named(o, n, SAY_LAYOUT_READ, "file", fname, "s", sb[0..(sl as usize)], "e", eb[0..(el as usize)], "m", mb[0..(ml as usize)], "b", bb[0..(bl as usize)], "", "");
+ }
+ return fill_named(o, n, SAY_LAYOUT_FALLBACK, "file", fname, "s", sb[0..(sl as usize)], "e", eb[0..(el as usize)], "m", mb[0..(ml as usize)], "b", bb[0..(bl as usize)], "", "");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ if (!BUILT) { return 0; }
+ BTN_N = 0;
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_INPUT_LABEL);
+ n = raw(dst, n, "
");
+ btn_mark(0);
+ n = raw(dst, n, "");
+ if (COPIED) { n = text(dst, n, SAY_COPIED); } else { n = text(dst, n, SAY_COPY_LINK); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_EXAMPLES_LABEL);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < 7) {
+ btn_mark(10 + i);
+ n = raw(dst, n, "");
+ n = text(dst, n, K_EXAMPLES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ROUNDING_LABEL);
+ n = raw(dst, n, "");
+ i = 0;
+ while (i < 2) {
+ btn_mark(20 + i);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_ROUNDING_NAMES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_NOTATION_LABEL);
+ n = raw(dst, n, "");
+ i = 0;
+ while (i < 2) {
+ btn_mark(30 + i);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_NOTATION_NAMES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ROUNDING_NOTES[ST_R as usize]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BAD_INPUT);
+ n = raw(dst, n, "
");
+ // the GF16 card
+ n = card_open(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_GF16_HEADING);
+ n = raw(dst, n, "");
+ if (HAVE_RES) { n = text(dst, n, RES_HEX[0..(RES_HEX_N as usize)]); }
+ var wb : [24]u8 = [0; 24];
+ const nl : u32 = udec(wb[0..], LB);
+ n = raw(dst, n, "
");
+ var j : u64 = 0;
+ while (j < LB) {
+ const bi : u64 = LB - 1 - j;
+ var field : u32 = 2;
+ if (bi == LB - 1) { field = 0; } else { if (bi >= LM) { field = 1; } }
+ btn_mark(100 + (bi as u32));
+ var tb : [24]u8 = [0; 24];
+ var ttl : [64]u8 = [0; 64];
+ const bl : u32 = udec(tb[0..], bi);
+ const tl : u32 = fill_named(ttl[0..], 0, SAY_BIT_TITLE, "i", tb[0..(bl as usize)], "", "", "", "", "", "", "", "", "", "");
+ n = raw(dst, n, "> bi) & 1) == 1; }
+ if (on) { n = raw(dst, n, " is-one"); }
+ n = raw(dst, n, "\" aria-label=\"");
+ n = raw(dst, n, ttl[0..(tl as usize)]);
+ n = raw(dst, n, "\" title=\"");
+ n = raw(dst, n, ttl[0..(tl as usize)]);
+ n = raw(dst, n, "\">");
+ if (HAVE_RES) { if (on) { n = raw(dst, n, "1"); } else { n = raw(dst, n, "0"); } }
+ n = raw(dst, n, "");
+ j = j + 1;
+ }
+ n = raw(dst, n, "
");
+ var f : u32 = 0;
+ while (f < 3) {
+ var w : u64 = 1;
+ if (f == 1) { w = LE; }
+ if (f == 2) { w = LM; }
+ var fb : [24]u8 = [0; 24];
+ const fl : u32 = udec(fb[0..], w);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_FIELD_NAMES[f as usize]);
+ n = raw(dst, n, "");
+ f = f + 1;
+ }
+ n = raw(dst, n, "
");
+ n = emit_gf16_facts(dst, n);
+ n = raw(dst, n, "
");
+ // the ternary card
+ n = card_open(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TERNARY_HEADING);
+ n = raw(dst, n, "
");
+ var fr : [24]u8 = [0; 24];
+ const frl : u32 = udec(fr[0..], K_TERNARY_FRAC_TRITS as u64);
+ n = fill_named(dst, n, SAY_TERNARY_NOTE, "f", fr[0..(frl as usize)], "", "", "", "", "", "", "", "", "", "");
+ n = raw(dst, n, "
");
+ n = emit_ternary(dst, n);
+ n = raw(dst, n, "
");
+ n = emit_ternary_facts(dst, n);
+ n = raw(dst, n, "
");
+ // the round trip
+ n = card_open(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ROUNDTRIP_HEADING);
+ n = raw(dst, n, "
");
+ n = emit_roundtrip(dst, n);
+ n = raw(dst, n, "
");
+ n = emit_layout_line(dst, n);
+ n = raw(dst, n, "
0) { n = raw(dst, n, "g16-warn"); }
+ n = raw(dst, n, "\">");
+ var ob : [24]u8 = [0; 24];
+ var nb : [24]u8 = [0; 24];
+ var tb2 : [24]u8 = [0; 24];
+ var tnb : [24]u8 = [0; 24];
+ const o1 : u32 = udec(ob[0..], CK_OKV as u64);
+ const o2 : u32 = udec(nb[0..], 20);
+ const o3 : u32 = udec(tb2[0..], CK_OKT as u64);
+ const o4 : u32 = udec(tnb[0..], 4);
+ n = fill_named(dst, n, SAY_CONFORMANCE, "ok", ob[0..(o1 as usize)], "n", nb[0..(o2 as usize)], "tok", tb2[0..(o3 as usize)], "tn", tnb[0..(o4 as usize)], "", "", "", "");
+ if (CK_FAIL_N > 0) {
+ n = raw(dst, n, " ");
+ n = fill_named(dst, n, SAY_CONFORMANCE_FAIL, "names", CK_FAIL[0..(CK_FAIL_N as usize)], "", "", "", "", "", "", "", "", "", "");
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+; --- events -----------------------------------------------------------------------------------------------------------------------
+fn clear_effects() -> u32 {
+ EFF_VAL = false;
+ EFF_COPY = false;
+ EFF_SEL = false;
+ EFF_TIMER = false;
+ HASH_OUT_N = 0;
+ return 0;
+}
+
+pub fn w_base(u: []const u8) -> u32 {
+ return 0;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ clear_effects();
+ var n : usize = h.len;
+ if (n > 8192) { n = 8192; }
+ var k : usize = 0;
+ while (k < n) { HASH_RAW[k] = h[k]; k = k + 1; }
+ HASH_N = n;
+ set_spec_layout();
+ BUILT = false;
+ HAVE_RES = false;
+ CUR_OK = false;
+ COPIED = false;
+ X_N = 0;
+ ST_R = 0;
+ ST_T = 0;
+ CARD_EMPTY = true;
+ BAD_HIDDEN = true;
+ return 0;
+}
+
+pub fn w_hash(h: []const u8) -> u32 {
+ clear_effects();
+ var n : usize = h.len;
+ if (n > 8192) { n = 8192; }
+ var k : usize = 0;
+ while (k < n) { HASH_RAW[k] = h[k]; k = k + 1; }
+ HASH_N = n;
+ if (!BUILT) { return 0; }
+ read_hash();
+ if (H_X_SET and !eq_s(H_X[0..H_X_N], X[0..X_N])) {
+ set_x_from(H_X[0..H_X_N]);
+ EFF_VAL = true;
+ }
+ if (H_R >= 0) { ST_R = H_R as u32; }
+ if (H_T >= 0) { ST_T = H_T as u32; }
+ update_page(false);
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ clear_effects();
+ build_page(s, true);
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ clear_effects();
+ build_page("", false);
+ return 0;
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ clear_effects();
+ if (i != 0 or !BUILT) { return 0; }
+ set_x_from(s);
+ update_page(true);
+ return 0;
+}
+
+pub fn w_press(i: u32) -> u32 {
+ clear_effects();
+ if (!BUILT or i >= BTN_N or i >= 64) { return 0; }
+ const a : u32 = BTN_ACT[i as usize];
+ if (a == 0) { EFF_COPY = true; }
+ if (a >= 10 and a < 17) {
+ set_x_from(K_EXAMPLES[(a - 10) as usize]);
+ EFF_VAL = true;
+ update_page(true);
+ }
+ if (a >= 20 and a < 22) {
+ ST_R = a - 20;
+ update_page(true);
+ }
+ if (a >= 30 and a < 32) {
+ ST_T = a - 30;
+ update_page(true);
+ }
+ if (a >= 100 and CUR_OK) {
+ const bits : u64 = CUR_BITS ^ pow2((a - 100) as u64);
+ var hb : [48]u8 = [0; 48];
+ const hl : u32 = hex_to(hb[0..], 0, bits);
+ set_x_from(hb[0..(hl as usize)]);
+ EFF_VAL = true;
+ update_page(true);
+ }
+ return 0;
+}
+
+pub fn w_copied(good: u32) -> u32 {
+ clear_effects();
+ if (good != 0) { COPIED = true; } else { EFF_SEL = true; }
+ EFF_TIMER = true;
+ return 0;
+}
+
+pub fn w_timer(id: u32) -> u32 {
+ clear_effects();
+ COPIED = false;
+ return 0;
+}
+
+pub fn w_hash_out(dst: []u8) -> u32 {
+ if (HASH_OUT_N == 0 or (HASH_OUT_N as usize) > dst.len) { return 0; }
+ var k : usize = 0;
+ while (k < (HASH_OUT_N as usize)) { dst[k] = HASH_OUT[k]; k = k + 1; }
+ return HASH_OUT_N;
+}
+
+/// The effects after the last event, in a fixed order: set the field, copy the link, select the field, start the timer.
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ var at : u32 = 0;
+ if (EFF_VAL) {
+ if (at == k) {
+ if ((X_N + 2) > dst.len) { return 0; }
+ dst[0] = 118;
+ dst[1] = 0;
+ var q : usize = 0;
+ while (q < X_N) { dst[2 + q] = X[q]; q = q + 1; }
+ return (X_N + 2) as u32;
+ }
+ at = at + 1;
+ }
+ if (EFF_COPY) {
+ if (at == k) {
+ dst[0] = 99;
+ return 1;
+ }
+ at = at + 1;
+ }
+ if (EFF_SEL) {
+ if (at == k) {
+ dst[0] = 115;
+ dst[1] = 0;
+ return 2;
+ }
+ at = at + 1;
+ }
+ if (EFF_TIMER) {
+ if (at == k) {
+ dst[0] = 116;
+ dst[1] = 0;
+ dst[2] = 49;
+ dst[3] = 53;
+ dst[4] = 48;
+ dst[5] = 48;
+ return 6;
+ }
+ }
+ return 0;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names its imported functions
+/// mention. This anchor mentions every helper the writer calls. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = put(o, 0, 0);
+ n = put_escaped(o, n, 0);
+ n = n + OVER + ((H_LT + H_GT + H_AMP + H_QUOTE + H_APOS + H_SPACE + H_SLASH + H_EQ) as u32);
+ n = n + (name_is(s, 0, 0, s) as u32) + (is_word_byte(0) as u32);
+ return n;
+}
+
+; --- Tests of the native page --------------------------------------------------------------------------------------
+; The numbers are the page's own vectors (the spec's K_CHECK_ list, the two ties) and values worked out by hand.
+
+pub var T_OUT : [262144]u8 = [0; 262144];
+pub const W_LAY_T : str = "pub const SIGN_SHIFT : u8 = 15;
+pub const EXP_SHIFT : u8 = 9;
+pub const EXP_MAX : u8 = 0x3F;
+pub const BIAS : i8 = 31;
+pub const MANT_MASK : u16 = 0x1FF;
+pub const GF16_NAN : u16 = 0xFE01;
+";
+pub const W_LAY_B30 : str = "pub const SIGN_SHIFT : u8 = 15;
+pub const EXP_SHIFT : u8 = 9;
+pub const EXP_MAX : u8 = 0x3F;
+pub const BIAS : i8 = 30;
+pub const MANT_MASK : u16 = 0x1FF;
+pub const GF16_NAN : u16 = 0xFE01;
+";
+pub const W_LAY_HALF : str = "pub const SIGN_SHIFT : u8 = 15;
+pub const EXP_SHIFT : u8 = 10;
+pub const EXP_MAX : u8 = 31;
+pub const BIAS : i8 = 15;
+pub const MANT_MASK : u16 = 1023;
+pub const GF16_NAN : u16 = 0xFE01;
+";
+
+fn contains(s: []const u8, w: []const u8) -> bool {
+ if (w.len > s.len) { return false; }
+ var p : usize = 0;
+ while (p + w.len <= s.len) {
+ if (word_at(s, p, w)) { return true; }
+ p = p + 1;
+ }
+ return false;
+}
+
+fn render() -> u32 {
+ return w_render(T_OUT[0..]);
+}
+
+fn has(n: u32, w: []const u8) -> bool {
+ return contains(T_OUT[0..(n as usize)], w);
+}
+
+fn press_act(a: u32) -> u32 {
+ render();
+ var i : u32 = 0;
+ while (i < BTN_N) {
+ if (BTN_ACT[i as usize] == a) {
+ w_press(i);
+ return 1;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+fn start(h: []const u8) -> u32 {
+ w_init(h);
+ w_data(0, W_LAY_T);
+ return 0;
+}
+
+fn dec_of(r: usize) -> u64 {
+ var b : [64]u8 = [0; 64];
+ const n : u32 = bn_dec(b[0..], 0, r);
+ var v : u64 = 0;
+ var k : usize = 0;
+ while (k < (n as usize)) { v = v * 10 + ((b[k] - 48) as u64); k = k + 1; }
+ return v;
+}
+
+test the_registers_add_multiply_shift_and_divide_exactly {
+ var b : [128]u8 = [0; 128];
+ bn_set_pow(2, 10, 30);
+ var n : u32 = bn_dec(b[0..], 0, 2);
+ assert eq_s(b[0..(n as usize)], "1000000000000000000000000000000");
+ bn_set_pow(3, 3, 48);
+ n = bn_dec(b[0..], 0, 3);
+ assert eq_s(b[0..(n as usize)], "79766443076872509863361");
+ assert bn_bitlen(3) == 77;
+ bn_mul(4, 2, 3);
+ n = bn_dec(b[0..], 0, 4);
+ assert eq_s(b[0..(n as usize)], "79766443076872509863361000000000000000000000000000000");
+ bn_set_pow(5, 10, 40);
+ bn_add_small(5, 5, 7);
+ bn_set_pow(6, 10, 20);
+ bn_divmod(7, 8, 5, 6);
+ n = bn_dec(b[0..], 0, 7);
+ assert eq_s(b[0..(n as usize)], "100000000000000000000");
+ assert dec_of(8) == 7;
+ bn_from_u64(9, 1);
+ bn_shl(9, 9, 100);
+ n = bn_dec(b[0..], 0, 9);
+ assert eq_s(b[0..(n as usize)], "1267650600228229401496703205376");
+ bn_sub(9, 9, 9);
+ assert BLEN[9] == 0;
+ assert bn_cmp(5, 6) > 0;
+ assert bn_cmp(6, 5) < 0;
+ assert bn_cmp(6, 6) == 0;
+}
+
+test the_number_field_is_read_the_way_parseInput_reads_it {
+ set_spec_layout();
+ parse_number("1.5");
+ assert P_KIND == 4 and !P_NEG;
+ parse_number(" -0x3f3c ");
+ assert P_KIND == 0;
+ parse_number("0X3f3C");
+ assert P_KIND == 1 and P_HEX == 16188;
+ parse_number("0x3F3C0");
+ assert P_KIND == 0;
+ parse_number("0x");
+ assert P_KIND == 0;
+ parse_number("-Infinity");
+ assert P_KIND == 2 and P_NEG;
+ parse_number("+nan");
+ assert P_KIND == 3;
+ parse_number("1_0.5_0e1_0");
+ assert P_KIND == 4;
+ parse_number("");
+ assert P_KIND == 0;
+ parse_number("1e999");
+ assert P_KIND == 4;
+ parse_number("1e1000");
+ assert P_KIND == 0;
+ parse_number("1e-");
+ assert P_KIND == 0;
+ parse_number(".e5");
+ assert P_KIND == 0;
+ parse_number(".5");
+ assert P_KIND == 4;
+ parse_number("5.");
+ assert P_KIND == 4;
+ parse_number("1 2");
+ assert P_KIND == 0;
+ parse_number("0.25");
+ assert dec_of(W_R_N) == 25 and dec_of(W_R_D) == 100;
+}
+
+test the_rounding_matches_the_conformance_pack_and_the_two_tie_rules {
+ set_spec_layout();
+ parse_number("1.618033988749895");
+ encode_parsed(true);
+ assert E_BITS == 16188;
+ assert E_DIR == -1;
+ parse_number("1.0009765625");
+ encode_parsed(true);
+ assert E_BITS == 15872 and E_TIE;
+ encode_parsed(false);
+ assert E_BITS == 15872;
+ parse_number("1.0029296875");
+ encode_parsed(true);
+ assert E_BITS == 15874;
+ encode_parsed(false);
+ assert E_BITS == 15873;
+ parse_number("1e40");
+ encode_parsed(true);
+ assert E_BITS == 32256 and E_OVER and E_DIR == 1;
+ parse_number("1e-13");
+ encode_parsed(true);
+ assert E_BITS == 0 and E_UNDER;
+ parse_number("-1.8189894035458565e-12");
+ encode_parsed(true);
+ assert E_BITS == 32769;
+ parse_number("-0.0");
+ encode_parsed(true);
+ assert E_BITS == 32768 and E_DIR == 0;
+ parse_number("4290772992.0");
+ encode_parsed(true);
+ assert E_BITS == 32255;
+ run_checks();
+ assert CK_OKV == 20 and CK_OKT == 4 and CK_FAIL_N == 0;
+}
+
+test the_code_fields_and_their_exact_decimals {
+ set_spec_layout();
+ decode_bits(16188);
+ assert D_SIGN == 0 and D_EXP == 31 and D_MANT == 316 and D_CLS == 2;
+ var b : [160]u8 = [0; 160];
+ var n : u32 = exact_decimal(b[0..], 0, D_Q, D_K, false);
+ assert eq_s(b[0..(n as usize)], "1.6171875");
+ decode_bits(1);
+ assert D_CLS == 1 and D_Q == 1 and D_K == -39;
+ n = exact_decimal(b[0..], 0, D_Q, D_K, true);
+ assert eq_s(b[0..(n as usize)], "-0.000000000001818989403545856475830078125");
+ decode_bits(32255);
+ n = exact_decimal(b[0..], 0, D_Q, D_K, false);
+ assert eq_s(b[0..(n as usize)], "4290772992");
+ decode_bits(32256);
+ assert D_CLS == 3 and !D_HASQ;
+ decode_bits(65025);
+ assert D_CLS == 4;
+ n = hex_to(b[0..], 0, 15);
+ assert eq_s(b[0..(n as usize)], "0x000F");
+}
+
+test significant_digits_are_rounded_like_fmtSig {
+ var b : [64]u8 = [0; 64];
+ bn_from_u64(6, 1);
+ bn_from_u64(7, 3);
+ var n : u32 = fmt_sig(b[0..], 0, false, 4);
+ assert eq_s(b[0..(n as usize)], "0.3333");
+ bn_from_u64(6, 2);
+ n = fmt_sig(b[0..], 0, true, 4);
+ assert eq_s(b[0..(n as usize)], "-0.6667");
+ bn_from_u64(6, 1);
+ bn_from_u64(7, 1000000000);
+ n = fmt_sig(b[0..], 0, false, 4);
+ assert eq_s(b[0..(n as usize)], "1e-9");
+ bn_from_u64(6, 123456);
+ bn_from_u64(7, 1);
+ n = fmt_sig(b[0..], 0, false, 4);
+ assert eq_s(b[0..(n as usize)], "123500");
+ bn_from_u64(6, 10000000);
+ n = fmt_sig(b[0..], 0, false, 4);
+ assert eq_s(b[0..(n as usize)], "1e7");
+ bn_from_u64(6, 5);
+ bn_from_u64(7, 1000);
+ n = fmt_sig(b[0..], 0, false, 4);
+ assert eq_s(b[0..(n as usize)], "0.005");
+ bn_from_u64(6, 9999);
+ bn_from_u64(7, 10);
+ n = fmt_sig(b[0..], 0, false, 3);
+ assert eq_s(b[0..(n as usize)], "1000");
+ bn_from_u64(6, 0);
+ n = fmt_sig(b[0..], 0, false, 4);
+ assert eq_s(b[0..(n as usize)], "0");
+ bn_from_u64(6, 12345678);
+ bn_from_u64(7, 1);
+ n = fmt_sig(b[0..], 0, false, 10);
+ assert eq_s(b[0..(n as usize)], "12345678");
+}
+
+test the_page_opens_on_the_default_number_with_its_cards {
+ start("");
+ assert BUILT;
+ assert X_N == 17;
+ assert LAYOUT_KIND == 0;
+ const n : u32 = render();
+ assert n > 5000;
+ assert has(n, "");
+ assert has(n, "0x3F3C");
+ assert has(n, "
");
+ w_input(0, "a b&c");
+ assert HASH_OUT_N > 0;
+ var h : [64]u8 = [0; 64];
+ const hl : u32 = w_hash_out(h[0..]);
+ assert eq_s(h[0..(hl as usize)], "#x=a+b%26c&r=zero&t=1");
+ n = render();
+ // the text is not a number: the cards keep what they showed, marked empty, and the message shows
+ assert has(n, "");
+ assert has(n, "
");
+}
From 629fd1152f4e2626fb5fa7049a7c8ed6c19640f5 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 19:01:18 +0700
Subject: [PATCH 39/63] widgets: catalog records pin-map, lut-depth,
lut-explain, gf16-calc at PORT_DRAW; lut-depth tests compare texts with
same_text (Closes #11016)
---
specs/widgets/catalog.t27 | 23 ++++++++++-------------
specs/widgets/lut_depth.t27 | 19 +++++++++++++++----
2 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index 1803efd18f..c2b9427694 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -37,7 +37,7 @@
; 14667449, lut-explain 1395124, bit-diff 275620, vcd-wrapped 76076, pin-map 180587, synth-smells 87094, fsm-sketch 91657 (its Math.sin/cos/atan2 stay with the host: V8 differs from libm in the last bit), bit-autopsy 96007.
; With these, all 12 pure logic files of the census (2775 lines of JavaScript) exist as .t27.
; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
-; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket and test-touch at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
+; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain and gf16-calc at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
; comparisons vs tool.js, 0 mismatches; specs/widgets/race_the_bee.t27: 59, with 13 data variants, 0). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
@@ -91,7 +91,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 1, 2, 2, 1, 1, 3, 2, 2, 2, 2, 1, 3, 1, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
+pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 1, 2, 2, 3, 1, 3, 3, 3, 2, 3, 1, 3, 1, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -227,18 +227,15 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test seven_pages_are_native_and_the_twelve_logic_files_are_ported {
- assert count_port(PORT_DRAW) == 7;
- assert count_port(PORT_WORDS) == 9;
- assert count_port(PORT_LOGIC) == 12;
+test eleven_pages_are_native_and_the_remaining_logic_files_are_ported {
+ assert count_port(PORT_DRAW) == 11;
+ assert count_port(PORT_LOGIC) == 9;
+ assert count_port(PORT_WORDS) == 8;
assert count_port(PORT_SEALED) == 0;
- assert count_port(PORT_LOGIC) + count_port(PORT_DRAW) == 19;
+ assert TOOL_PORT[14] == PORT_DRAW;
+ assert TOOL_PORT[11] == PORT_DRAW;
+ assert TOOL_PORT[12] == PORT_DRAW;
+ assert TOOL_PORT[8] == PORT_DRAW;
assert TOOL_PORT[10] == PORT_DRAW;
- assert TOOL_PORT[16] == PORT_DRAW;
- assert TOOL_PORT[2] == PORT_DRAW;
- assert TOOL_PORT[21] == PORT_DRAW;
- assert TOOL_PORT[22] == PORT_DRAW;
- assert TOOL_PORT[24] == PORT_DRAW;
- assert TOOL_PORT[26] == PORT_DRAW;
assert TOOL_PORT[7] == PORT_LOGIC;
}
diff --git a/specs/widgets/lut_depth.t27 b/specs/widgets/lut_depth.t27
index 4f3b912081..b2a126a043 100644
--- a/specs/widgets/lut_depth.t27
+++ b/specs/widgets/lut_depth.t27
@@ -172,8 +172,8 @@ test the_sample_path_is_its_classes_summed {
assert K_SAMPLE_LENGTHS[1] == 23;
assert K_SAMPLE_LUT + K_SAMPLE_CARRY + K_SAMPLE_MUX + K_SAMPLE_IO + K_SAMPLE_CLOCKED == K_SAMPLE_LENGTHS[1];
assert K_SAMPLE_PATH[0] == "IBUF";
- assert K_SAMPLE_PATH[2] == K_CARRY_TYPES[0];
- assert K_SAMPLE_PATH[12] == K_MUX_TYPES[0];
+ assert same_text(K_SAMPLE_PATH[2], K_CARRY_TYPES[0]);
+ assert same_text(K_SAMPLE_PATH[12], K_MUX_TYPES[0]);
assert K_SAMPLE_PATH[22] == "LUT6";
assert K_SAMPLE_LUT == 11;
assert K_SAMPLE_CLOCKED == 0;
@@ -182,8 +182,8 @@ test the_sample_path_is_its_classes_summed {
test plain_noff_walks_through_two_flip_flops {
assert K_SAMPLE_COMMANDS[0] == "ltp -noff";
assert K_SAMPLE_LENGTHS[0] == K_SAMPLE_LENGTHS[1] + 4;
- assert K_SAMPLE_PLAIN_TAIL[0] == K_CLOCKED_TYPES[2];
- assert K_SAMPLE_PLAIN_TAIL[2] == K_CLOCKED_TYPES[2];
+ assert same_text(K_SAMPLE_PLAIN_TAIL[0], K_CLOCKED_TYPES[2]);
+ assert same_text(K_SAMPLE_PLAIN_TAIL[2], K_CLOCKED_TYPES[2]);
assert K_SAMPLE_PLAIN_TAIL[3] == "OBUF";
assert K_SAMPLE_PLAIN_CLOCKED == 2;
}
@@ -1830,3 +1830,14 @@ test a_sample_that_did_not_load_says_so_and_draws_nothing {
assert has(m, "role=\"status\">The sample did not load.");
assert RESULT_HIDDEN;
}
+
+/// Two texts equal byte for byte (t27c's Zig target does not compare two str array elements with ==).
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
From ef0433d3e94eb43842e72c9afb09af2300186a6b Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 19:00:59 +0700
Subject: [PATCH 40/63] widgets: resource-bars drawn natively -- 21381-byte
wasm, 1956 DOM comparisons, 0 differences (Closes #10938)
specs/widgets/resource_bars.t27: the trinity spec verbatim, the routed-JSON counter (json_scan, hash sets,
a strict JSON check), JavaScript number printing for the bar widths and percentages (shortest round-trip
digits, exact toFixed/toPrecision, on a small big-number block), and the page with file input, drop and
page-wide drop. File cap 32 MiB (the original 512 MB). 12 tests (8 new), t27b 12/12. 33 data variants.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/resource_bars.t27 | 1867 +++++++++++++++++++++++++++++++
1 file changed, 1867 insertions(+)
create mode 100644 specs/widgets/resource_bars.t27
diff --git a/specs/widgets/resource_bars.t27 b/specs/widgets/resource_bars.t27
new file mode 100644
index 0000000000..54cc1e19f7
--- /dev/null
+++ b/specs/widgets/resource_bars.t27
@@ -0,0 +1,1867 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/resource-bars.t27 -- what a placed design uses of the XC7A200T, as bars
+; Source of truth for public/widgets/resource-bars/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file and hands every constant below to
+; tool.js as window.T27_WIDGET. tool.js holds no English of its own: every word on screen is a
+; SAY_ constant here, every number the tool relies on is a K_ constant.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: one bar per resource -- six-input LUTs, flip-flops, CARRY4, slices, BRAM36,
+; BRAM18, DSP48, bonded IOB, BUFGCTRL -- the used count against the part's total, with the
+; percentage. Drop a routed nextpnr-xilinx JSON (the file --write writes) and it counts that
+; design instead, in the browser with FileReader; nothing is sent anywhere. The IOB total depends
+; on the package, which a routed JSON does not record, so the reader picks it.
+
+; HOW IT COUNTS: LUT is the number of occupied LUT6 positions (a slice letter A-D whose 6LUT or
+; 5LUT is used), because that is the unit AMD counts in DS180; nextpnr's own line counts LUT BELs
+; (6LUT and 5LUT separately) and is shown beside it. FF counts SLICE_FFX cells, CARRY4 and the
+; rest count their cells, slices count distinct SLICE sites, IOB counts PAD cells.
+
+; DATA PROVENANCE: resources.json is written by scripts/widget-data/fpga-pnr.py from one real run
+; made on 2026-10-04 (trinity_v3_jtaguart.v from gHashTag/trinity-fpga de4ebda367, yosys
+; 0.67+post b8e7da6f40, nextpnr-xilinx 0.9.2-107-g7037c948 on the xc7a200tfbg676-1 chipdb, --freq
+; 50 --seed 1); the exact commands ship in the file. That design uses 155 LUT6 positions (239 LUT
+; BELs in nextpnr's count), 98 FF, 23 CARRY4, 60 slices, 5 IOB, 1 BUFGCTRL, no BRAM and no DSP.
+; Totals are counted, not typed: the chipdb's BEL counts (nextpnr --report "available":
+; SLICE_LUTX 269200, SLICE_FFX 269200, CARRY4 33650, RAMB36E1 365, RAMB18E1 730, DSP48E1 740,
+; BUFGCTRL 32) and prjxray-db 517d66a383 (tilegrid.json site counts; package_pins.csv rows whose
+; site is an IOB: 400 for fbg676, 285 for fbg484). fpga-pnr.py stops if the two sources disagree.
+; All of them match AMD DS180 (7 Series Product Selection Guide) for the XC7A200T: 33,650
+; slices, 134,600 LUTs, 269,200 flip-flops, 365 36Kb block RAMs, 740 DSP slices, 400 and 285
+; user IO in the FBG676 and FBG484 packages.
+
+; WHAT IT DOES NOT CLAIM: that a design which fits also meets timing or works; this counts
+; placed cells only. Totals are the XC7A200T's: a dropped file from another part is counted
+; against the 200T. MMCM, PLL, IDELAY and the other primitives are not drawn.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_resource_bars;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "resource-bars";
+pub const TITLE : str = "Resource bars: LUT, FF, BRAM and DSP used on the XC7A200T";
+pub const DESCRIPTION : str = "A real nextpnr-xilinx run counted against the XC7A200T's own totals, read from the chipdb and prjxray-db. Drop your routed JSON and it counts yours.";
+pub const IMAGE_ALT : str = "Eight bars on black, each a used count against the XC7A200T total: LUT 155 of 134,600, FF 98 of 269,200, CARRY4 23 of 33,650, slices 60 of 33,650, BRAM36 0 of 365, DSP48 0 of 740, IOB 5 of 400, BUFG 1 of 32. Headline: 155 of 134,600 LUTs, 0.12%.";
+pub const HOOK : str = "How much of a 200T does a UART-command blinker take? 0.12% of the LUTs. Drop your routed JSON and see your own bars.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["gHashTag/trinity-fpga de4ebda367: fpga/openxc7-synth/trinity_v3_jtaguart.v and jtaguart.xdc", "nextpnr-xilinx 0.9.2-107-g7037c948 --chipdb xc7a200tfbg676-1.bin --report report.json: used and available BEL counts", "prjxray-db 517d66a383 artix7: xc7a200t/tilegrid.json site counts, xc7a200tfbg676-1 and xc7a200tfbg484-1 package_pins.csv", "AMD DS180 7 Series FPGAs Product Selection Guide: XC7A200T totals, cross-checked", "A routed nextpnr-xilinx JSON the reader drops or picks: read in the browser with FileReader, never uploaded"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+pub const K_DATA_FILE : str = "resources.json";
+; Rows in order: the key in resources.json "used" and "totals", and the XC7A200T total.
+pub const K_ROWS : [9]str = ["LUT", "FF", "CARRY4", "SLICE", "BRAM36", "BRAM18", "DSP48", "IOB", "BUFG"];
+pub const K_TOTALS : [9]u32 = [134600, 269200, 33650, 33650, 365, 730, 740, 400, 32];
+; Index of IOB in K_ROWS: its total follows the package the reader picks.
+pub const K_IOB_ROW : u8 = 7;
+pub const K_PACKAGES : [2]str = ["fbg676", "fbg484"];
+pub const K_PACKAGE_IOB : [2]u32 = [400, 285];
+; Chipdb BEL counts behind the totals.
+pub const K_SLICES : u32 = 33650;
+pub const K_LUT_BELS : u32 = 269200;
+pub const K_FF_BELS : u32 = 269200;
+pub const K_RAMB36 : u32 = 365;
+pub const K_RAMB18 : u32 = 730;
+; Cell type prefixes per row, for a dropped file (the counting rule is in the header).
+pub const K_BRAM36_PREFIX : [3]str = ["RAMB36", "RAMBFIFO36", "FIFO36"];
+pub const K_BRAM18_PREFIX : [2]str = ["RAMB18", "FIFO18"];
+pub const K_DSP_PREFIX : str = "DSP48";
+pub const K_BUFG_TYPES : [2]str = ["BUFGCTRL", "BUFG"];
+; A used count above zero gets at least this many pixels of bar, so 1 of 269200 still shows.
+pub const K_MIN_BAR_PX : u8 = 3;
+pub const K_MAX_FILE_MB : u16 = 512;
+
+; --- Words on screen ------------------------------------------------------------------------
+pub const SAY_ROW_NAMES : [9]str = ["LUT6", "Flip-flops", "CARRY4", "Slices", "BRAM36", "BRAM18", "DSP48", "Bonded IOB", "BUFGCTRL"];
+pub const SAY_ROW_NOTES : [9]str = ["occupied LUT6 positions (A-D per slice)", "SLICE_FFX", "carry chain blocks", "distinct SLICE sites", "36Kb block RAM", "18Kb block RAM halves", "DSP48E1", "PAD cells, against the package", "global clock buffers"];
+pub const SAY_USED_OF : str = "{0} / {1}";
+pub const SAY_PCT : str = "{0}%";
+pub const SAY_PACKAGE : str = "Package";
+pub const SAY_DEMO : str = "Demo design";
+pub const SAY_SUMMARY : str = "{0}: {1} cells, {2} of the LUTs";
+pub const SAY_NEXTPNR_LINE : str = "nextpnr's own count: {0}";
+pub const SAY_SOURCE_DEMO : str = "{0} on {1}, placed by {2}";
+pub const SAY_SOURCE_FILE : str = "{0} ({1} bytes), {2}";
+pub const SAY_TOTALS_NOTE : str = "Totals: chipdb BEL counts and prjxray-db, equal to AMD DS180 for the XC7A200T.";
+pub const SAY_DROP : str = "Drop a routed nextpnr-xilinx JSON here, or tap to pick one. It is read in this browser and sent nowhere.";
+pub const SAY_LOADING : str = "Loading the counts...";
+pub const SAY_READING : str = "Reading {0}...";
+pub const SAY_ERR_JSON : str = "{0} is not JSON.";
+pub const SAY_ERR_NO_BELS : str = "{0} has no placed cells: no NEXTPNR_BEL attributes. Use the JSON nextpnr writes with --write after routing.";
+pub const SAY_ERR_SIZE : str = "{0} is larger than {1} MB.";
+pub const SAY_ERR_DEMO : str = "The demo counts did not load.";
+pub const SAY_COMMANDS : str = "How this was made";
+
+; --- Tests ----------------------------------------------------------------------------------
+test the_totals_follow_from_the_chipdb_counts {
+ assert K_TOTALS[0] == K_LUT_BELS / 2;
+ assert K_TOTALS[0] == 4 * K_SLICES;
+ assert K_TOTALS[1] == K_FF_BELS;
+ assert K_TOTALS[2] == K_SLICES;
+ assert K_TOTALS[3] == K_SLICES;
+}
+
+test the_block_ram_halves_are_two_per_36kb {
+ assert K_TOTALS[4] == K_RAMB36;
+ assert K_TOTALS[5] == K_RAMB18;
+ assert K_RAMB18 == 2 * K_RAMB36;
+}
+
+test the_iob_row_starts_on_the_first_package {
+ assert K_ROWS[K_IOB_ROW] == "IOB";
+ assert K_TOTALS[K_IOB_ROW] == K_PACKAGE_IOB[0];
+ assert K_PACKAGE_IOB[1] < K_PACKAGE_IOB[0];
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/resource-bars/tool.js (172 lines of hand-written JavaScript).
+; The words and numbers above are unchanged: the view prints SAY_ constants and uses K_ numbers.
+; --
+; NUMBERS AS JAVASCRIPT PRINTS THEM. The bar's width is `${Math.min(100, frac * 100)}%`, which is the
+; shortest decimal that reads back as the same binary64, and the percentage is toFixed(1), toFixed(2)
+; or toPrecision(2) of 100 * used / total. There is no float printing in .t27, so this module has the
+; little big-number arithmetic it takes to do both exactly (the register block below is the one of
+; gf16_calc.t27, copied): the double is split into M * 2^-k with M below 2^53, and every candidate
+; digit string is compared with the rounding interval of the double in whole integers.
+; --
+; THE FILE. The original reads a routed nextpnr JSON of up to 512 MB through FileReader. This page
+; keeps the file in a static buffer of W_FILE_CAP bytes (32 MiB) and reads it with json_scan.t27; a
+; larger file gets the page's own "larger than N MB" sentence with N = 32. The cells are counted as
+; the original counts them; the two sets (occupied LUT6 positions, distinct slice sites) are hash
+; sets of W_SET_CAP entries each (a 200T has 134,600 and 33,650).
+; --
+; Host protocol (host.js; all exports of this module):
+; w_init(hash) the page opens
+; w_data(i, bytes) resources.json (K_DATA_FILE) was fetched; w_data_failed(i) when not
+; w_click(i) button number i in page order: 0 and 1 the packages, 2 the demo design
+; w_file(z, name, size) -> 1 | 0, w_file_data(z, bytes), w_file_failed(z) a picked or dropped JSON
+; w_drag(z, over) -> 1 | 0 the drag entered (1) or left/dropped (0) zone z
+; w_drop_root() -> 1 a file dropped anywhere on the page is read too (tool.js listens on the root)
+; w_effect(k, dst) -> len effect after the last event: 'v', control index, text = set that control's value
+; w_render(dst) the page's HTML, length (0 on overflow)
+; Not carried over from JavaScript: counts, totals and file sizes are printed as integers; a NEXTPNR_BEL
+; that is not a string, or a cell type that is not a string, reads as absent / "undefined"; keys with
+; repeated names in the JSON take the first value; the style attribute of a bar is written the way the
+; original's `style.width = ...` leaves it in a browser that accepts max() (happy-dom does not, so
+; the measurement compares the width text from a JavaScript reference instead).
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::dec_to;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::json_scan::num_f64;
+
+pub const W_FILE_CAP : u32 = 33554432;
+pub const W_DEMO_CAP : u32 = 262144;
+pub const W_SET_CAP : u32 = 163840;
+pub const W_POOL_CAP : u32 = 4194304;
+
+; BIGNUM-BEGIN
+pub var BN : [10240]u32 = [0; 10240];
+pub var BLEN : [16]usize = [0; 16];
+
+; --- the registers --------------------------------------------------------------------------------------
+; 0 W_R_N and 1 W_R_D hold the parsed fraction; 2..9 are scratch for the function that names them; 14 and 15
+; belong to bn_divmod and bn_shl.
+pub const W_R_N : usize = 0;
+pub const W_R_D : usize = 1;
+pub const W_R_T : usize = 15;
+pub const W_R_S : usize = 14;
+
+fn bn_norm(r: usize) -> u32 {
+ var n : usize = BLEN[r];
+ while (n > 0 and BN[r * 640 + n - 1] == 0) { n = n - 1; }
+ BLEN[r] = n;
+ return 0;
+}
+
+fn bn_zero(r: usize) -> u32 {
+ BLEN[r] = 0;
+ return 0;
+}
+
+fn bn_from_u64(r: usize, v: u64) -> u32 {
+ BN[r * 640] = (v & 4294967295) as u32;
+ BN[r * 640 + 1] = (v >> 32) as u32;
+ BLEN[r] = 2;
+ bn_norm(r);
+ return 0;
+}
+
+fn bn_copy(d: usize, s: usize) -> u32 {
+ const n : usize = BLEN[s];
+ var i : usize = 0;
+ while (i < n) {
+ BN[d * 640 + i] = BN[s * 640 + i];
+ i = i + 1;
+ }
+ BLEN[d] = n;
+ return 0;
+}
+
+fn bn_cmp(a: usize, b: usize) -> i32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ if (la < lb) { return -1; }
+ if (la > lb) { return 1; }
+ var i : usize = la;
+ while (i > 0) {
+ i = i - 1;
+ const x : u32 = BN[a * 640 + i];
+ const y : u32 = BN[b * 640 + i];
+ if (x < y) { return -1; }
+ if (x > y) { return 1; }
+ }
+ return 0;
+}
+
+fn bn_add(d: usize, a: usize, b: usize) -> u32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ var n : usize = la;
+ if (lb > n) { n = lb; }
+ var carry : u64 = 0;
+ var i : usize = 0;
+ while (i < n) {
+ var s : u64 = carry;
+ if (i < la) { s = s + (BN[a * 640 + i] as u64); }
+ if (i < lb) { s = s + (BN[b * 640 + i] as u64); }
+ BN[d * 640 + i] = (s & 4294967295) as u32;
+ carry = s >> 32;
+ i = i + 1;
+ }
+ if (carry > 0) {
+ BN[d * 640 + n] = carry as u32;
+ n = n + 1;
+ }
+ BLEN[d] = n;
+ return 0;
+}
+
+/// d = a - b, for a >= b.
+fn bn_sub(d: usize, a: usize, b: usize) -> u32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ var borrow : u64 = 0;
+ var i : usize = 0;
+ while (i < la) {
+ var y : u64 = borrow;
+ if (i < lb) { y = y + (BN[b * 640 + i] as u64); }
+ const x : u64 = (BN[a * 640 + i] as u64) + 4294967296;
+ const t : u64 = x - y;
+ BN[d * 640 + i] = (t & 4294967295) as u32;
+ borrow = 1 - (t >> 32);
+ i = i + 1;
+ }
+ BLEN[d] = la;
+ bn_norm(d);
+ return 0;
+}
+
+fn bn_mul_small(d: usize, a: usize, m: u64) -> u32 {
+ const la : usize = BLEN[a];
+ var carry : u64 = 0;
+ var i : usize = 0;
+ while (i < la) {
+ const t : u64 = (BN[a * 640 + i] as u64) * m + carry;
+ BN[d * 640 + i] = (t & 4294967295) as u32;
+ carry = t >> 32;
+ i = i + 1;
+ }
+ var n : usize = la;
+ if (carry > 0) {
+ BN[d * 640 + n] = carry as u32;
+ n = n + 1;
+ }
+ BLEN[d] = n;
+ bn_norm(d);
+ return 0;
+}
+
+fn bn_add_small(d: usize, a: usize, v: u64) -> u32 {
+ const la : usize = BLEN[a];
+ var carry : u64 = v;
+ var i : usize = 0;
+ while (i < la) {
+ const t : u64 = (BN[a * 640 + i] as u64) + carry;
+ BN[d * 640 + i] = (t & 4294967295) as u32;
+ carry = t >> 32;
+ i = i + 1;
+ }
+ var n : usize = la;
+ while (carry > 0) {
+ BN[d * 640 + n] = (carry & 4294967295) as u32;
+ carry = carry >> 32;
+ n = n + 1;
+ }
+ BLEN[d] = n;
+ return 0;
+}
+
+/// d = a / m, the remainder returned.
+fn bn_divmod_small(d: usize, a: usize, m: u64) -> u64 {
+ const la : usize = BLEN[a];
+ var rem : u64 = 0;
+ var i : usize = la;
+ while (i > 0) {
+ i = i - 1;
+ const cur : u64 = (rem << 32) + (BN[a * 640 + i] as u64);
+ BN[d * 640 + i] = (cur / m) as u32;
+ rem = cur % m;
+ }
+ BLEN[d] = la;
+ bn_norm(d);
+ return rem;
+}
+
+fn bitlen32(x: u32) -> u32 {
+ var n : u32 = 0;
+ var v : u32 = x;
+ while (v > 0) {
+ n = n + 1;
+ v = v >> 1;
+ }
+ return n;
+}
+
+fn bn_bitlen(r: usize) -> u32 {
+ const n : usize = BLEN[r];
+ if (n == 0) { return 0; }
+ return ((n - 1) as u32) * 32 + bitlen32(BN[r * 640 + n - 1]);
+}
+
+/// d = a << sh (alias-safe: it works from the top limb down).
+fn bn_shl(d: usize, a: usize, sh: u32) -> u32 {
+ const la : usize = BLEN[a];
+ if (la == 0) {
+ BLEN[d] = 0;
+ return 0;
+ }
+ const ls : usize = (sh / 32) as usize;
+ const bs : u32 = sh % 32;
+ const nl : usize = la + ls + 1;
+ var i : usize = nl;
+ while (i > 0) {
+ i = i - 1;
+ var v : u64 = 0;
+ if (i >= ls and i - ls < la) {
+ v = (BN[a * 640 + i - ls] as u64) << bs;
+ }
+ if (bs > 0 and i >= ls + 1 and i - ls - 1 < la) {
+ v = v | ((BN[a * 640 + i - ls - 1] as u64) >> (32 - bs));
+ }
+ if (i < ls) { v = 0; }
+ BN[d * 640 + i] = (v & 4294967295) as u32;
+ }
+ BLEN[d] = nl;
+ bn_norm(d);
+ return 0;
+}
+
+/// d = a * b (d must not be a or b).
+fn bn_mul(d: usize, a: usize, b: usize) -> u32 {
+ const la : usize = BLEN[a];
+ const lb : usize = BLEN[b];
+ if (la == 0 or lb == 0) {
+ BLEN[d] = 0;
+ return 0;
+ }
+ var i : usize = 0;
+ while (i < la + lb) {
+ BN[d * 640 + i] = 0;
+ i = i + 1;
+ }
+ i = 0;
+ while (i < la) {
+ var carry : u64 = 0;
+ const x : u64 = BN[a * 640 + i] as u64;
+ var j : usize = 0;
+ while (j < lb) {
+ const t : u64 = x * (BN[b * 640 + j] as u64) + (BN[d * 640 + i + j] as u64) + carry;
+ BN[d * 640 + i + j] = (t & 4294967295) as u32;
+ carry = t >> 32;
+ j = j + 1;
+ }
+ BN[d * 640 + i + lb] = carry as u32;
+ i = i + 1;
+ }
+ BLEN[d] = la + lb;
+ bn_norm(d);
+ return 0;
+}
+
+fn pow10_small(k: u32) -> u64 {
+ var v : u64 = 1;
+ var i : u32 = 0;
+ while (i < k) {
+ v = v * 10;
+ i = i + 1;
+ }
+ return v;
+}
+
+/// d = a * 10^p
+fn bn_mul_pow10(d: usize, a: usize, p: u32) -> u32 {
+ if (d != a) { bn_copy(d, a); }
+ var left : u32 = p;
+ while (left >= 9) {
+ bn_mul_small(d, d, 1000000000);
+ left = left - 9;
+ }
+ if (left > 0) { bn_mul_small(d, d, pow10_small(left)); }
+ return 0;
+}
+
+/// d = base^e for a small base.
+fn bn_set_pow(d: usize, base: u64, e: u32) -> u32 {
+ bn_from_u64(d, 1);
+ var i : u32 = 0;
+ while (i < e) {
+ bn_mul_small(d, d, base);
+ i = i + 1;
+ }
+ return 0;
+}
+
+/// q = a / b and rem = a mod b by binary long division: one shift and one compare per quotient bit.
+/// q, rem, a, b are four different registers; W_R_T is scratch.
+fn bn_divmod(q: usize, rem: usize, a: usize, b: usize) -> u32 {
+ bn_copy(rem, a);
+ const ka : u32 = bn_bitlen(a);
+ const kb : u32 = bn_bitlen(b);
+ if (ka < kb) {
+ BLEN[q] = 0;
+ return 0;
+ }
+ const k : u32 = ka - kb;
+ const nl : usize = ((k / 32) + 1) as usize;
+ var z : usize = 0;
+ while (z < nl) {
+ BN[q * 640 + z] = 0;
+ z = z + 1;
+ }
+ BLEN[q] = nl;
+ var i : u32 = k + 1;
+ while (i > 0) {
+ i = i - 1;
+ bn_shl(W_R_T, b, i);
+ if (bn_cmp(W_R_T, rem) <= 0) {
+ bn_sub(rem, rem, W_R_T);
+ const li : usize = (i / 32) as usize;
+ BN[q * 640 + li] = BN[q * 640 + li] | ((1 as u32) << (i % 32));
+ }
+ }
+ bn_norm(q);
+ return 0;
+}
+
+/// The value as a u64 (the register holds at most two limbs).
+fn bn_u64(r: usize) -> u64 {
+ var v : u64 = 0;
+ if (BLEN[r] > 0) { v = BN[r * 640] as u64; }
+ if (BLEN[r] > 1) { v = v | ((BN[r * 640 + 1] as u64) << 32); }
+ return v;
+}
+
+/// The decimal digits of a register, most significant first, into o at n0; the new length ("0" for zero).
+fn bn_dec(o: []u8, n0: u32, r: usize) -> u32 {
+ var n : u32 = n0;
+ if (BLEN[r] == 0) { return put(o, n, 48); }
+ // peel nine digits at a time off a copy in register 13
+ bn_copy(13, r);
+ var chunks : [100]u32 = [0; 100];
+ var nc : usize = 0;
+ while (BLEN[13] > 0 and nc < 100) {
+ chunks[nc] = bn_divmod_small(13, 13, 1000000000) as u32;
+ nc = nc + 1;
+ }
+ var first : bool = true;
+ while (nc > 0) {
+ nc = nc - 1;
+ var digits : [9]u8 = [48; 9];
+ var v : u32 = chunks[nc];
+ var j : usize = 9;
+ while (j > 0) {
+ j = j - 1;
+ digits[j] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ }
+ var from : usize = 0;
+ if (first) {
+ while (from < 8 and digits[from] == 48) { from = from + 1; }
+ first = false;
+ }
+ while (from < 9) {
+ n = put(o, n, digits[from]);
+ from = from + 1;
+ }
+ }
+ return n;
+}
+
+; BIGNUM-END
+
+pub var FILE : [33554432]u8 = [0; 33554432];
+pub var FILE_N : u32 = 0;
+pub var DEMO : [262144]u8 = [0; 262144];
+pub var DEMO_N : u32 = 0;
+pub var DEMO_OK : bool = false;
+pub var PKG : u32 = 0;
+pub var HAS_USED : bool = false;
+pub var USED : [9]u64 = [0; 9];
+pub var LABEL : [1024]u8 = [0; 1024];
+pub var LABEL_N : u32 = 0;
+pub var CELLS : u64 = 0;
+pub var NEXTPNR : [1024]u8 = [0; 1024];
+pub var NEXTPNR_N : u32 = 0;
+pub var SRCLINE : [4096]u8 = [0; 4096];
+pub var SRCLINE_N : u32 = 0;
+pub var CMDS : [8192]u8 = [0; 8192];
+pub var CMDS_N : u32 = 0;
+pub var CMD_COUNT : u32 = 0;
+pub var CMD_OFF : [64]u32 = [0; 64];
+pub var CMD_LEN : [64]u32 = [0; 64];
+pub var DEMO_HIDDEN : bool = true;
+pub var STATUS : [4096]u8 = [0; 4096];
+pub var STATUS_N : u32 = 0;
+pub var DRAGGING : bool = false;
+pub var EFF_CLEAR : bool = false;
+pub var FNAME : [1024]u8 = [0; 1024];
+pub var FNAME_N : usize = 0;
+pub var FSIZE : u32 = 0;
+pub var T1 : [64]u8 = [0; 64];
+pub var T2 : [64]u8 = [0; 64];
+pub var T3 : [64]u8 = [0; 64];
+
+; the two sets of the counting rule
+pub var POOL : [4194304]u8 = [0; 4194304];
+pub var POOL_N : u32 = 0;
+pub var HT_L : [262144]u32 = [0; 262144];
+pub var HT_S : [262144]u32 = [0; 262144];
+pub var OFF_L : [163840]u32 = [0; 163840];
+pub var LEN_L : [163840]u32 = [0; 163840];
+pub var OFF_S : [163840]u32 = [0; 163840];
+pub var LEN_S : [163840]u32 = [0; 163840];
+pub var NL : u32 = 0;
+pub var NS : u32 = 0;
+pub var BELB : [512]u8 = [0; 512];
+pub var TYPB : [256]u8 = [0; 256];
+pub var KEYB : [1024]u8 = [0; 1024];
+
+fn eq_s(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var k : usize = 0;
+ while (k < a.len) {
+ if (a[k] != b[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn starts_s(a: []const u8, p: []const u8) -> bool {
+ if (a.len < p.len) { return false; }
+ var k : usize = 0;
+ while (k < p.len) {
+ if (a[k] != p[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn fin(n: u32) -> u32 {
+ if (n == OVER) { return 0; }
+ return n;
+}
+
+fn jstr_copy(dst: []u8, d: []const u8, sp: i64) -> u32 {
+ var len : u32 = str_len(d, sp);
+ if (len > (dst.len as u32)) { len = dst.len as u32; }
+ var k : u32 = 0;
+ while (k < len) {
+ dst[k as usize] = str_byte(d, sp, k);
+ k = k + 1;
+ }
+ return len;
+}
+
+fn dnum(o: []u8, n0: u32, v: u64) -> u32 {
+ var tmp : [24]u8 = [0; 24];
+ const m : u32 = dec_to(tmp[0..], v);
+ return raw(o, n0, tmp[0..(m as usize)]);
+}
+
+; --- printing numbers the way JavaScript does ------------------------------------------------------------------------
+pub var G_M : u64 = 0;
+pub var G_K : u32 = 0;
+pub var DS : [24]u8 = [0; 24];
+pub var DS_N : usize = 0;
+pub var DS_E : i32 = 0;
+
+fn bitlen64(v: u64) -> u32 {
+ var n : u32 = 0;
+ var x : u64 = v;
+ while (x > 0) {
+ n = n + 1;
+ x = x >> 1;
+ }
+ return n;
+}
+
+/// x = G_M * 2^-G_K with G_M an integer below 2^53 (x is a positive double below 2^53).
+fn decompose(x: f64) -> u32 {
+ var m : f64 = x;
+ var k : u32 = 0;
+ while (m != ((m as u64) as f64) and k < 1100) {
+ m = m * 2.0;
+ k = k + 1;
+ }
+ G_M = m as u64;
+ G_K = k;
+ return 0;
+}
+
+/// Is x >= 10^e ? (x = G_M * 2^-G_K)
+fn x_ge_pow10(e: i32) -> bool {
+ if (e >= 0) {
+ bn_set_pow(2, 10, e as u32);
+ bn_shl(2, 2, G_K);
+ bn_from_u64(3, G_M);
+ return bn_cmp(3, 2) >= 0;
+ }
+ bn_from_u64(2, G_M);
+ bn_mul_pow10(2, 2, (0 - e) as u32);
+ bn_from_u64(3, 1);
+ bn_shl(3, 3, G_K);
+ return bn_cmp(2, 3) >= 0;
+}
+
+/// floor(log10 x)
+fn floor_log10() -> i32 {
+ const q : i64 = (bitlen64(G_M) as i64) - 1 - (G_K as i64);
+ var x : i64 = q * 30103;
+ var rem : i64 = x % 100000;
+ if (rem < 0) { rem = rem + 100000; }
+ var n : i32 = ((x - rem) / 100000) as i32;
+ while (!x_ge_pow10(n)) { n = n - 1; }
+ while (x_ge_pow10(n + 1)) { n = n + 1; }
+ return n;
+}
+
+/// D = round half up of x / 10^s, for x = G_M * 2^-G_K.
+fn round_at(s: i32) -> u64 {
+ if (s <= 0) {
+ bn_from_u64(4, G_M);
+ bn_mul_pow10(4, 4, (0 - s) as u32);
+ bn_from_u64(5, 1);
+ bn_shl(5, 5, G_K);
+ } else {
+ bn_from_u64(4, G_M);
+ bn_from_u64(5, 1);
+ bn_shl(5, 5, G_K);
+ bn_mul_pow10(5, 5, s as u32);
+ }
+ bn_shl(6, 4, 1);
+ bn_add(6, 6, 5);
+ bn_shl(7, 5, 1);
+ bn_divmod(8, 9, 6, 7);
+ return bn_u64(8);
+}
+
+/// The shortest digit string that reads back as x: DS[0..DS_N), and DS_E so that x = 0.DS * 10^DS_E.
+fn shortest(x: f64) -> u32 {
+ decompose(x);
+ const mb : u32 = bitlen64(G_M);
+ const q : i64 = (mb as i64) - 1 - (G_K as i64);
+ const is_pow2 : bool = (G_M & (G_M - 1)) == 0;
+ const u2 : i64 = q - 52;
+ const shift : i64 = 0 - (G_K as i64) - u2;
+ const odd_f : bool = shift == 0 and (G_M & 1) == 1;
+ const w : u32 = (2 - u2) as u32;
+ const n10 : i32 = floor_log10();
+ var p : i32 = 1;
+ var found : bool = false;
+ var best_d : u64 = 0;
+ var best_s : i32 = 0;
+ while (p <= 17 and !found) {
+ const s : i32 = n10 - p + 1;
+ const d : u64 = round_at(s);
+ // C * F and x * F with F = 2^k (s >= 0) or 2^k * 10^-s (s < 0)
+ if (s >= 0) {
+ bn_from_u64(6, d);
+ bn_mul_pow10(6, 6, s as u32);
+ bn_shl(6, 6, G_K);
+ bn_from_u64(7, G_M);
+ bn_from_u64(10, 1);
+ bn_shl(10, 10, G_K);
+ } else {
+ bn_from_u64(6, d);
+ bn_shl(6, 6, G_K);
+ bn_from_u64(7, G_M);
+ bn_mul_pow10(7, 7, (0 - s) as u32);
+ bn_from_u64(10, 1);
+ bn_shl(10, 10, G_K);
+ bn_mul_pow10(10, 10, (0 - s) as u32);
+ }
+ var c_gt : bool = false;
+ if (bn_cmp(6, 7) > 0) {
+ c_gt = true;
+ bn_sub(11, 6, 7);
+ } else {
+ bn_sub(11, 7, 6);
+ }
+ bn_shl(11, 11, w);
+ bn_shl(12, 10, 1);
+ var good : bool = false;
+ var c : i32 = 0;
+ if (c_gt or !is_pow2) {
+ c = bn_cmp(11, 12);
+ } else {
+ c = bn_cmp(11, 10);
+ }
+ if (c < 0 or (c == 0 and !odd_f)) { good = true; }
+ if (good) {
+ found = true;
+ best_d = d;
+ best_s = s;
+ }
+ p = p + 1;
+ }
+ // the digits of best_d, trailing zeros dropped; x = 0.digits * 10^(len + s)
+ var tmp : [24]u8 = [0; 24];
+ const len : u32 = dec_to(tmp[0..], best_d);
+ var e : usize = len as usize;
+ while (e > 1 and tmp[e - 1] == 48) { e = e - 1; }
+ var k : usize = 0;
+ while (k < e) { DS[k] = tmp[k]; k = k + 1; }
+ DS_N = e;
+ DS_E = (len as i32) + best_s;
+ return 0;
+}
+
+/// String(x) for a positive finite double (Number::toString).
+fn js_num(o: []u8, n0: u32, x: f64) -> u32 {
+ var n : u32 = n0;
+ if (x == 0.0) { return put(o, n, 48); }
+ shortest(x);
+ const k : i32 = DS_N as i32;
+ const e : i32 = DS_E;
+ if (k <= e and e <= 21) {
+ var i : usize = 0;
+ while (i < DS_N) { n = put(o, n, DS[i]); i = i + 1; }
+ var z : i32 = e - k;
+ while (z > 0) { n = put(o, n, 48); z = z - 1; }
+ return n;
+ }
+ if (0 < e and e <= 21) {
+ var i2 : usize = 0;
+ while (i2 < DS_N) {
+ if ((i2 as i32) == e) { n = put(o, n, 46); }
+ n = put(o, n, DS[i2]);
+ i2 = i2 + 1;
+ }
+ return n;
+ }
+ if (-6 < e and e <= 0) {
+ n = raw(o, n, "0.");
+ var z2 : i32 = 0 - e;
+ while (z2 > 0) { n = put(o, n, 48); z2 = z2 - 1; }
+ var i3 : usize = 0;
+ while (i3 < DS_N) { n = put(o, n, DS[i3]); i3 = i3 + 1; }
+ return n;
+ }
+ n = put(o, n, DS[0]);
+ if (DS_N > 1) {
+ n = put(o, n, 46);
+ var i4 : usize = 1;
+ while (i4 < DS_N) { n = put(o, n, DS[i4]); i4 = i4 + 1; }
+ }
+ n = put(o, n, 101);
+ var ex : i32 = e - 1;
+ if (ex < 0) {
+ n = put(o, n, 45);
+ ex = 0 - ex;
+ } else {
+ n = put(o, n, 43);
+ }
+ return dnum(o, n, ex as u64);
+}
+
+/// x.toFixed(d) for 0 < x < 1e21 and d of 1 or 2.
+fn to_fixed(o: []u8, n0: u32, x: f64, d: u32) -> u32 {
+ decompose(x);
+ bn_from_u64(4, G_M);
+ bn_mul_pow10(4, 4, d);
+ bn_from_u64(5, 1);
+ bn_shl(5, 5, G_K);
+ bn_shl(6, 4, 1);
+ bn_add(6, 6, 5);
+ bn_shl(7, 5, 1);
+ bn_divmod(8, 9, 6, 7);
+ var v : u64 = bn_u64(8);
+ const scale : u64 = pow10_small(d);
+ var n : u32 = dnum(o, n0, v / scale);
+ n = put(o, n, 46);
+ var f : u64 = v % scale;
+ var div : u64 = scale / 10;
+ while (div > 0) {
+ n = put(o, n, ((f / div) as u8) + 48);
+ f = f % div;
+ div = div / 10;
+ }
+ return n;
+}
+
+/// x.toPrecision(2) for 1e-6 <= x < 0.01 (the only range the page reaches): 0.00ab.
+fn to_precision2(o: []u8, n0: u32, x: f64) -> u32 {
+ decompose(x);
+ var e10 : i32 = floor_log10();
+ var d : u64 = round_at(e10 - 1);
+ if (d >= 100) {
+ d = d / 10;
+ e10 = e10 + 1;
+ }
+ var n : u32 = raw(o, n0, "0.");
+ var z : i32 = 0 - e10 - 1;
+ while (z > 0) { n = put(o, n, 48); z = z - 1; }
+ n = put(o, n, ((d / 10) as u8) + 48);
+ return put(o, n, ((d % 10) as u8) + 48);
+}
+
+
+; --- JSON.parse's yes or no -----------------------------------------------------------------------------------------------
+pub const W_BAD : usize = 4294967295;
+
+fn jv_ws(d: []const u8, p0: usize) -> usize {
+ var p : usize = p0;
+ while (p < d.len and (d[p] == 32 or d[p] == 9 or d[p] == 10 or d[p] == 13)) { p = p + 1; }
+ return p;
+}
+
+fn jv_digits(d: []const u8, p0: usize) -> usize {
+ var p : usize = p0;
+ while (p < d.len and d[p] >= 48 and d[p] <= 57) { p = p + 1; }
+ return p;
+}
+
+fn jv_str(d: []const u8, p0: usize) -> usize {
+ var p : usize = p0 + 1;
+ while (p < d.len) {
+ const c : u8 = d[p];
+ if (c == 34) { return p + 1; }
+ if (c < 32) { return W_BAD; }
+ if (c == 92) {
+ if (p + 1 >= d.len) { return W_BAD; }
+ const e : u8 = d[p + 1];
+ if (e == 117) {
+ if (p + 5 >= d.len) { return W_BAD; }
+ var k : usize = 2;
+ while (k < 6) {
+ const h : u8 = d[p + k];
+ if (!((h >= 48 and h <= 57) or (h >= 97 and h <= 102) or (h >= 65 and h <= 70))) { return W_BAD; }
+ k = k + 1;
+ }
+ p = p + 6;
+ } else {
+ if (!(e == 34 or e == 92 or e == 47 or e == 98 or e == 102 or e == 110 or e == 114 or e == 116)) { return W_BAD; }
+ p = p + 2;
+ }
+ } else {
+ p = p + 1;
+ }
+ }
+ return W_BAD;
+}
+
+fn jv_num(d: []const u8, p0: usize) -> usize {
+ var p : usize = p0;
+ if (p < d.len and d[p] == 45) { p = p + 1; }
+ if (p >= d.len) { return W_BAD; }
+ if (d[p] == 48) {
+ p = p + 1;
+ } else {
+ if (d[p] < 49 or d[p] > 57) { return W_BAD; }
+ p = jv_digits(d, p);
+ }
+ if (p < d.len and d[p] == 46) {
+ const q : usize = jv_digits(d, p + 1);
+ if (q == p + 1) { return W_BAD; }
+ p = q;
+ }
+ if (p < d.len and (d[p] == 101 or d[p] == 69)) {
+ var q2 : usize = p + 1;
+ if (q2 < d.len and (d[q2] == 43 or d[q2] == 45)) { q2 = q2 + 1; }
+ const q3 : usize = jv_digits(d, q2);
+ if (q3 == q2) { return W_BAD; }
+ p = q3;
+ }
+ return p;
+}
+
+fn jv_lit(d: []const u8, p: usize, w: []const u8) -> usize {
+ if (p + w.len > d.len) { return W_BAD; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (d[p + k] != w[k]) { return W_BAD; }
+ k = k + 1;
+ }
+ return p + w.len;
+}
+
+fn jv_val(d: []const u8, p0: usize, depth: u32) -> usize {
+ if (depth > 400) { return W_BAD; }
+ const p : usize = jv_ws(d, p0);
+ if (p >= d.len) { return W_BAD; }
+ const c : u8 = d[p];
+ if (c == 34) { return jv_str(d, p); }
+ if (c == 116) { return jv_lit(d, p, "true"); }
+ if (c == 102) { return jv_lit(d, p, "false"); }
+ if (c == 110) { return jv_lit(d, p, "null"); }
+ if (c == 123) {
+ var q : usize = jv_ws(d, p + 1);
+ if (q < d.len and d[q] == 125) { return q + 1; }
+ while (true) {
+ q = jv_ws(d, q);
+ if (q >= d.len or d[q] != 34) { return W_BAD; }
+ q = jv_str(d, q);
+ if (q == W_BAD) { return W_BAD; }
+ q = jv_ws(d, q);
+ if (q >= d.len or d[q] != 58) { return W_BAD; }
+ q = jv_val(d, q + 1, depth + 1);
+ if (q == W_BAD) { return W_BAD; }
+ q = jv_ws(d, q);
+ if (q >= d.len) { return W_BAD; }
+ if (d[q] == 125) { return q + 1; }
+ if (d[q] != 44) { return W_BAD; }
+ q = q + 1;
+ }
+ }
+ if (c == 91) {
+ var q : usize = jv_ws(d, p + 1);
+ if (q < d.len and d[q] == 93) { return q + 1; }
+ while (true) {
+ q = jv_val(d, q, depth + 1);
+ if (q == W_BAD) { return W_BAD; }
+ q = jv_ws(d, q);
+ if (q >= d.len) { return W_BAD; }
+ if (d[q] == 93) { return q + 1; }
+ if (d[q] != 44) { return W_BAD; }
+ q = q + 1;
+ }
+ }
+ return jv_num(d, p);
+}
+
+fn json_ok(d: []const u8) -> bool {
+ const e : usize = jv_val(d, 0, 0);
+ if (e == W_BAD) { return false; }
+ return jv_ws(d, e) == d.len;
+}
+
+; --- the counting rule ------------------------------------------------------------------------------------------------
+fn hash_key(s: []const u8) -> u64 {
+ var h : u64 = 7;
+ var k : usize = 0;
+ while (k < s.len) {
+ h = (h * 31 + (s[k] as u64)) % 1000000007;
+ k = k + 1;
+ }
+ return h;
+}
+
+/// Add key to set 0 (LUT6 positions) or 1 (slice sites); true when it was new.
+fn set_add(which: u32, key: []const u8) -> bool {
+ var slot : usize = (hash_key(key) % 262144) as usize;
+ var probes : usize = 0;
+ while (probes < 262144) {
+ var e : u32 = HT_S[slot];
+ if (which == 0) { e = HT_L[slot]; }
+ if (e == 0) {
+ var cnt : u32 = NS;
+ if (which == 0) { cnt = NL; }
+ if (cnt >= W_SET_CAP or POOL_N + (key.len as u32) > W_POOL_CAP) { return false; }
+ var k : usize = 0;
+ while (k < key.len) { POOL[(POOL_N as usize) + k] = key[k]; k = k + 1; }
+ if (which == 0) {
+ OFF_L[cnt as usize] = POOL_N;
+ LEN_L[cnt as usize] = key.len as u32;
+ HT_L[slot] = cnt + 1;
+ NL = NL + 1;
+ } else {
+ OFF_S[cnt as usize] = POOL_N;
+ LEN_S[cnt as usize] = key.len as u32;
+ HT_S[slot] = cnt + 1;
+ NS = NS + 1;
+ }
+ POOL_N = POOL_N + (key.len as u32);
+ return true;
+ }
+ var off : u32 = 0;
+ var len : u32 = 0;
+ if (which == 0) {
+ off = OFF_L[(e - 1) as usize];
+ len = LEN_L[(e - 1) as usize];
+ } else {
+ off = OFF_S[(e - 1) as usize];
+ len = LEN_S[(e - 1) as usize];
+ }
+ if (eq_s(POOL[(off as usize)..((off + len) as usize)], key)) { return false; }
+ slot = (slot + 1) % 262144;
+ probes = probes + 1;
+ }
+ return false;
+}
+
+fn clear_sets() -> u32 {
+ var i : usize = 0;
+ while (i < 262144) {
+ HT_L[i] = 0;
+ HT_S[i] = 0;
+ i = i + 1;
+ }
+ NL = 0;
+ NS = 0;
+ POOL_N = 0;
+ return 0;
+}
+
+pub var DRAWN_PKG : u32 = 0;
+pub var M_KEY : i64 = 0;
+pub var M_VAL : i64 = 0;
+
+/// The member of the object ending at hi that starts at p: M_KEY and M_VAL, and the position after it plus one; 0 at the end.
+fn member(d: []const u8, hi: usize, p: usize) -> usize {
+ if (p >= hi or d[p] != 34) { return 0; }
+ const ke : usize = string_end(d, p);
+ if (ke > hi) { return 0; }
+ const q : usize = skip_ws(d, ke);
+ if (q >= hi or d[q] != 58) { return 0; }
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (ve > hi) { return 0; }
+ M_KEY = jspan(p, ke);
+ M_VAL = jspan(v, ve);
+ var np : usize = skip_ws(d, ve);
+ if (np < hi and d[np] == 44) { np = skip_ws(d, np + 1); }
+ return np + 1;
+}
+
+fn starts_any3(t: []const u8, a: []const u8, b: []const u8, c: []const u8) -> bool {
+ return starts_s(t, a) or starts_s(t, b) or starts_s(t, c);
+}
+
+pub var N_LUTX : u64 = 0;
+pub var N_FFX : u64 = 0;
+pub var N_C4 : u64 = 0;
+
+/// One placed cell: type t, bel text.
+fn count_cell(t: []const u8, bel: []const u8) -> u32 {
+ // [site, belName = ''] = bel.split('/')
+ var sl : usize = 0;
+ while (sl < bel.len and bel[sl] != 47) { sl = sl + 1; }
+ const site : []const u8 = bel[0..sl];
+ var bs : usize = sl + 1;
+ var be : usize = bs;
+ if (sl < bel.len) {
+ while (be < bel.len and bel[be] != 47) { be = be + 1; }
+ } else {
+ bs = bel.len;
+ be = bel.len;
+ }
+ const is_lut : bool = eq_s(t, "SLICE_LUTX");
+ const is_ff : bool = eq_s(t, "SLICE_FFX");
+ const is_c4 : bool = eq_s(t, "CARRY4");
+ if (is_lut) { N_LUTX = N_LUTX + 1; }
+ if (is_ff) { N_FFX = N_FFX + 1; }
+ if (is_c4) { N_C4 = N_C4 + 1; }
+ if (is_lut or is_ff or is_c4) { set_add(1, site); }
+ if (is_lut) {
+ // site + '/' + belName[0] (the text "undefined" when belName is empty)
+ var kn : u32 = raw(KEYB[0..], 0, site);
+ kn = put(KEYB[0..], kn, 47);
+ if (be > bs) {
+ var cl : usize = 1;
+ const c0 : u8 = bel[bs];
+ if (c0 >= 240) { cl = 4; } else { if (c0 >= 224) { cl = 3; } else { if (c0 >= 192) { cl = 2; } } }
+ var q : usize = 0;
+ while (q < cl and bs + q < be) { kn = put(KEYB[0..], kn, bel[bs + q]); q = q + 1; }
+ } else {
+ kn = raw(KEYB[0..], kn, "undefined");
+ }
+ if (kn != OVER) { set_add(0, KEYB[0..(kn as usize)]); }
+ }
+ if (is_ff) { USED[1] = USED[1] + 1; return 0; }
+ if (is_c4) { USED[2] = USED[2] + 1; return 0; }
+ if (starts_any3(t, K_BRAM36_PREFIX[0], K_BRAM36_PREFIX[1], K_BRAM36_PREFIX[2])) { USED[4] = USED[4] + 1; return 0; }
+ if (starts_s(t, K_BRAM18_PREFIX[0]) or starts_s(t, K_BRAM18_PREFIX[1])) { USED[5] = USED[5] + 1; return 0; }
+ if (starts_s(t, K_DSP_PREFIX)) { USED[6] = USED[6] + 1; return 0; }
+ if (eq_s(t, "PAD")) { USED[7] = USED[7] + 1; return 0; }
+ if (eq_s(t, K_BUFG_TYPES[0]) or eq_s(t, K_BUFG_TYPES[1])) { USED[8] = USED[8] + 1; }
+ return 0;
+}
+
+/// The cells of a routed JSON with a NEXTPNR_BEL, counted. Returns how many there were.
+fn count_file(d: []const u8) -> u64 {
+ clear_sets();
+ var i : usize = 0;
+ while (i < 9) { USED[i] = 0; i = i + 1; }
+ N_LUTX = 0;
+ N_FFX = 0;
+ N_C4 = 0;
+ var cells : u64 = 0;
+ const r : i64 = root(d);
+ const mods : i64 = obj_get(d, r, "modules");
+ if (kind(d, mods) != J_OBJECT) { return 0; }
+ const mhi : usize = span_hi(mods);
+ var mp : usize = skip_ws(d, span_lo(mods) + 1);
+ while (mp > 0) {
+ const nm : usize = member(d, mhi, mp);
+ if (nm == 0) { break; }
+ mp = nm - 1;
+ const module : i64 = M_VAL;
+ const cs : i64 = obj_get(d, module, "cells");
+ if (kind(d, cs) == J_OBJECT) {
+ const chi : usize = span_hi(cs);
+ var cp : usize = skip_ws(d, span_lo(cs) + 1);
+ while (true) {
+ const nc : usize = member(d, chi, cp);
+ if (nc == 0) { break; }
+ cp = nc - 1;
+ const c : i64 = M_VAL;
+ const bel : i64 = obj_get(d, obj_get(d, c, "attributes"), "NEXTPNR_BEL");
+ if (kind(d, bel) == J_STRING and str_len(d, bel) > 0) {
+ cells = cells + 1;
+ const bl : u32 = jstr_copy(BELB[0..], d, bel);
+ const ty : i64 = obj_get(d, c, "type");
+ var tl : u32 = 0;
+ if (kind(d, ty) == J_STRING) { tl = jstr_copy(TYPB[0..], d, ty); } else { tl = raw(TYPB[0..], 0, "undefined"); }
+ count_cell(TYPB[0..(tl as usize)], BELB[0..(bl as usize)]);
+ }
+ }
+ }
+ if (nm - 1 >= mhi) { break; }
+ }
+ USED[0] = NL as u64;
+ USED[3] = NS as u64;
+ return cells;
+}
+
+; --- state of the page -----------------------------------------------------------------------------------------------
+fn status_loading() -> u32 {
+ STATUS_N = fin(text(STATUS[0..], 0, SAY_LOADING));
+ return 0;
+}
+
+fn status_text(t: []const u8) -> u32 {
+ STATUS_N = fin(text(STATUS[0..], 0, t));
+ return 0;
+}
+
+fn status_name(t: []const u8) -> u32 {
+ STATUS_N = fin(fill(STATUS[0..], 0, t, FNAME[0..FNAME_N], "", "", 1));
+ return 0;
+}
+
+fn total_of(i: u32) -> u64 {
+ if (i == (K_IOB_ROW as u32)) { return K_PACKAGE_IOB[DRAWN_PKG as usize] as u64; }
+ return K_TOTALS[i as usize] as u64;
+}
+
+/// pctText(u, t): '-' without a total, else the percentage in the page's words.
+fn pct_text(o: []u8, n0: u32, u: u64, t: u64) -> u32 {
+ if (t == 0) { return put(o, n0, 45); }
+ if (u == 0) { return fill(o, n0, SAY_PCT, "0", "", "", 1); }
+ const p : f64 = (100.0 * (u as f64)) / (t as f64);
+ var v : [64]u8 = [0; 64];
+ var vl : u32 = 0;
+ if (p >= 10.0) {
+ vl = to_fixed(v[0..], 0, p, 1);
+ } else {
+ if (p >= 0.01) { vl = to_fixed(v[0..], 0, p, 2); } else { vl = to_precision2(v[0..], 0, p); }
+ }
+ return fill(o, n0, SAY_PCT, v[0..(vl as usize)], "", "", 1);
+}
+
+/// The status line of render(): label, cells, the LUT share.
+fn status_summary() -> u32 {
+ var c : [32]u8 = [0; 32];
+ var p : [64]u8 = [0; 64];
+ const cl : u32 = num_grouped(c[0..], 0, CELLS);
+ const pl : u32 = pct_text(p[0..], 0, USED[0], total_of(0));
+ STATUS_N = fin(fill(STATUS[0..], 0, SAY_SUMMARY, LABEL[0..(LABEL_N as usize)], c[0..(cl as usize)], p[0..(pl as usize)], 3));
+ return 0;
+}
+
+fn do_render() -> u32 {
+ if (!HAS_USED) { return 0; }
+ DRAWN_PKG = PKG;
+ status_summary();
+ return 0;
+}
+
+fn demo_str(dst: []u8, d: []const u8, obj: i64, key: []const u8) -> u32 {
+ const v : i64 = obj_get(d, obj, key);
+ if (kind(d, v) == J_STRING) { return jstr_copy(dst, d, v); }
+ return 0;
+}
+
+/// showDemo()
+fn show_demo() -> u32 {
+ if (!DEMO_OK) { return 0; }
+ const d : []const u8 = DEMO[0..(DEMO_N as usize)];
+ const r : i64 = root(d);
+ const src : i64 = obj_get(d, r, "source");
+ const used : i64 = obj_get(d, r, "used");
+ // state.used is whatever the file holds under "used"; falsy (absent, null, false, 0, "") draws nothing
+ var truthy : bool = false;
+ const uk : u8 = kind(d, used);
+ if (uk == J_OBJECT or uk == J_ARRAY or uk == J_TRUE) { truthy = true; }
+ if (uk == J_STRING and str_len(d, used) > 0) { truthy = true; }
+ if (uk == J_NUMBER and num_f64(d, used) != 0.0) { truthy = true; }
+ var pb : [256]u8 = [0; 256];
+ const pbl : u32 = demo_str(pb[0..], d, src, "part");
+ PKG = 0;
+ var pi : usize = 0;
+ var found : bool = false;
+ while (pi < 2 and !found) {
+ const pk : []const u8 = K_PACKAGES[pi];
+ var s : usize = 0;
+ while (s + pk.len <= (pbl as usize)) {
+ if (eq_s(pb[s..(s + pk.len)], pk)) { found = true; PKG = pi as u32; break; }
+ s = s + 1;
+ }
+ pi = pi + 1;
+ }
+ DEMO_HIDDEN = true;
+ if (!truthy) { return 0; }
+ HAS_USED = true;
+ var i : usize = 0;
+ while (i < 9 and truthy) {
+ USED[i] = 0;
+ const v : i64 = obj_get(d, used, K_ROWS[i]);
+ if (kind(d, v) == J_NUMBER) {
+ const f : f64 = num_f64(d, v);
+ if (f > 0.0) { USED[i] = f as u64; }
+ }
+ i = i + 1;
+ }
+ LABEL_N = demo_str(LABEL[0..], d, src, "top");
+ // cells ?? the sum of nextpnr_util[*][0]
+ const cv : i64 = obj_get(d, r, "cells");
+ const util : i64 = obj_get(d, r, "nextpnr_util");
+ CELLS = 0;
+ if (kind(d, cv) == J_NUMBER) {
+ const cf : f64 = num_f64(d, cv);
+ if (cf > 0.0) { CELLS = cf as u64; }
+ } else {
+ if (kind(d, util) == J_OBJECT) {
+ const uhi : usize = span_hi(util);
+ var up : usize = skip_ws(d, span_lo(util) + 1);
+ while (true) {
+ const nu : usize = member(d, uhi, up);
+ if (nu == 0) { break; }
+ up = nu - 1;
+ const f0 : f64 = num_f64(d, arr_at(d, M_VAL, 0));
+ if (f0 > 0.0) { CELLS = CELLS + (f0 as u64); }
+ }
+ }
+ }
+ // nextpnr's own count for the three BEL kinds
+ var nn : u32 = 0;
+ if (kind(d, util) == J_OBJECT) {
+ const uhi2 : usize = span_hi(util);
+ var up2 : usize = skip_ws(d, span_lo(util) + 1);
+ while (true) {
+ const nu2 : usize = member(d, uhi2, up2);
+ if (nu2 == 0) { break; }
+ up2 = nu2 - 1;
+ const kn : u32 = jstr_copy(KEYB[0..], d, M_KEY);
+ const key : []const u8 = KEYB[0..(kn as usize)];
+ if (eq_s(key, "SLICE_LUTX") or eq_s(key, "SLICE_FFX") or eq_s(key, "CARRY4")) {
+ if (nn > 0) { nn = raw(NEXTPNR[0..], nn, ", "); }
+ nn = raw(NEXTPNR[0..], nn, key);
+ nn = put(NEXTPNR[0..], nn, 32);
+ nn = num_grouped(NEXTPNR[0..], nn, num_f64(d, arr_at(d, M_VAL, 0)) as u64);
+ nn = put(NEXTPNR[0..], nn, 47);
+ nn = num_grouped(NEXTPNR[0..], nn, num_f64(d, arr_at(d, M_VAL, 1)) as u64);
+ }
+ }
+ }
+ NEXTPNR_N = fin(nn);
+ // SOURCE_DEMO: design, part, nextpnr
+ var a : [512]u8 = [0; 512];
+ var c : [256]u8 = [0; 256];
+ const al : u32 = demo_str(a[0..], d, src, "design");
+ const cl : u32 = demo_str(c[0..], d, src, "nextpnr");
+ SRCLINE_N = fin(fill(SRCLINE[0..], 0, SAY_SOURCE_DEMO, a[0..(al as usize)], pb[0..(pbl as usize)], c[0..(cl as usize)], 3));
+ // commands
+ CMD_COUNT = 0;
+ CMDS_N = 0;
+ const cmds : i64 = obj_get(d, src, "commands");
+ const cn : u32 = arr_len(d, cmds);
+ var j : u32 = 0;
+ while (j < cn and CMD_COUNT < 64) {
+ const e : i64 = arr_at(d, cmds, j);
+ if (kind(d, e) == J_STRING) {
+ const room : usize = CMDS.len - (CMDS_N as usize);
+ var len : u32 = str_len(d, e);
+ if ((len as usize) > room) { len = room as u32; }
+ var k : u32 = 0;
+ while (k < len) {
+ CMDS[(CMDS_N + k) as usize] = str_byte(d, e, k);
+ k = k + 1;
+ }
+ CMD_OFF[CMD_COUNT as usize] = CMDS_N;
+ CMD_LEN[CMD_COUNT as usize] = len;
+ CMDS_N = CMDS_N + len;
+ CMD_COUNT = CMD_COUNT + 1;
+ }
+ j = j + 1;
+ }
+ do_render();
+ return 0;
+}
+
+; --- drawing and events ---------------------------------------------------------------------------------------------------
+fn emit_rows(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ var i : u32 = 0;
+ while (i < 9) {
+ const u : u64 = USED[i as usize];
+ const t : u64 = total_of(i);
+ var frac : f64 = 0.0;
+ if (t > 0) { frac = (u as f64) / (t as f64); }
+ var ul : [32]u8 = [0; 32];
+ var tl : [32]u8 = [0; 32];
+ const a : u32 = num_grouped(ul[0..], 0, u);
+ const b : u32 = num_grouped(tl[0..], 0, t);
+ var used_of : [96]u8 = [0; 96];
+ const uo : u32 = fin(fill(used_of[0..], 0, SAY_USED_OF, ul[0..(a as usize)], tl[0..(b as usize)], "", 2));
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_ROW_NAMES[i as usize]);
+ n = raw(o, n, "");
+ n = text(o, n, SAY_ROW_NOTES[i as usize]);
+ n = raw(o, n, " 1.0) {
+ n = raw(o, n, " is-over");
+ } else {
+ if (frac >= 0.02) { n = raw(o, n, " is-big"); }
+ }
+ n = raw(o, n, "\" style=\"width: ");
+ if (u > 0) {
+ var x : f64 = frac * 100.0;
+ if (x > 100.0) { x = 100.0; }
+ n = raw(o, n, "max(");
+ n = dnum(o, n, K_MIN_BAR_PX as u64);
+ n = raw(o, n, "px, ");
+ n = js_num(o, n, x);
+ n = raw(o, n, "%);");
+ } else {
+ n = raw(o, n, "0px;");
+ }
+ n = raw(o, n, "\">");
+ n = text(o, n, used_of[0..(uo as usize)]);
+ n = raw(o, n, "");
+ var pt : [64]u8 = [0; 64];
+ const pl : u32 = pct_text(pt[0..], 0, u, t);
+ n = text(o, n, pt[0..(pl as usize)]);
+ n = raw(o, n, "
");
+ i = i + 1;
+ }
+ return n;
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_PACKAGE);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < 2) {
+ n = raw(dst, n, "");
+ n = text(dst, n, K_PACKAGES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_DEMO);
+ n = raw(dst, n, "
");
+ var q : u32 = 0;
+ while (q < STATUS_N) { n = put(dst, n, STATUS[q as usize]); q = q + 1; }
+ n = raw(dst, n, "
");
+ if (HAS_USED) { n = emit_rows(dst, n); }
+ n = raw(dst, n, "
");
+ if (HAS_USED and NEXTPNR_N > 0) { n = fill(dst, n, SAY_NEXTPNR_LINE, NEXTPNR[0..(NEXTPNR_N as usize)], "", "", 1); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TOTALS_NOTE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "");
+ if (HAS_USED) {
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_COMMANDS);
+ n = raw(dst, n, "
");
+ var sq : u32 = 0;
+ while (sq < SRCLINE_N) { n = put(dst, n, SRCLINE[sq as usize]); sq = sq + 1; }
+ n = raw(dst, n, "
");
+ var c : u32 = 0;
+ while (c < CMD_COUNT) {
+ n = raw(dst, n, "
");
+ const off : usize = CMD_OFF[c as usize] as usize;
+ n = text(dst, n, CMDS[off..(off + (CMD_LEN[c as usize] as usize))]);
+ n = raw(dst, n, "
");
+ c = c + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ DEMO_OK = false;
+ DEMO_N = 0;
+ PKG = 0;
+ DRAWN_PKG = 0;
+ HAS_USED = false;
+ LABEL_N = 0;
+ CELLS = 0;
+ NEXTPNR_N = 0;
+ SRCLINE_N = 0;
+ CMD_COUNT = 0;
+ CMDS_N = 0;
+ DEMO_HIDDEN = true;
+ DRAGGING = false;
+ EFF_CLEAR = false;
+ FNAME_N = 0;
+ status_loading();
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ if (s.len > (W_DEMO_CAP as usize) or !json_ok(s)) {
+ status_text(SAY_ERR_DEMO);
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) { DEMO[k] = s[k]; k = k + 1; }
+ DEMO_N = s.len as u32;
+ DEMO_OK = true;
+ show_demo();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ status_text(SAY_ERR_DEMO);
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ if (i < 2) {
+ PKG = i;
+ do_render();
+ }
+ if (i == 2) { show_demo(); }
+ return 0;
+}
+
+/// takeFile, first half: the name, the size check, "Reading ...".
+pub fn w_file(z: u32, name: []const u8, size: u32) -> u32 {
+ EFF_CLEAR = true;
+ var n : usize = name.len;
+ if (n > 1000) { n = 1000; }
+ var k : usize = 0;
+ while (k < n) { FNAME[k] = name[k]; k = k + 1; }
+ FNAME_N = n;
+ FSIZE = size;
+ if (size > W_FILE_CAP) {
+ var mb : [16]u8 = [0; 16];
+ const ml : u32 = dec_to(mb[0..], (W_FILE_CAP / 1048576) as u64);
+ STATUS_N = fin(fill(STATUS[0..], 0, SAY_ERR_SIZE, FNAME[0..FNAME_N], mb[0..(ml as usize)], "", 2));
+ return 0;
+ }
+ status_name(SAY_READING);
+ return 1;
+}
+
+/// takeFile, second half: the bytes arrived.
+pub fn w_file_data(z: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ var k : usize = 0;
+ var from : usize = 0;
+ if (s.len >= 3 and s[0] == 239 and s[1] == 187 and s[2] == 191) { from = 3; }
+ if (s.len - from > (W_FILE_CAP as usize)) {
+ status_name(SAY_ERR_JSON);
+ return 0;
+ }
+ k = from;
+ while (k < s.len) { FILE[k - from] = s[k]; k = k + 1; }
+ FILE_N = (s.len - from) as u32;
+ const d : []const u8 = FILE[0..(FILE_N as usize)];
+ if (!json_ok(d)) {
+ status_name(SAY_ERR_JSON);
+ return 0;
+ }
+ var keep : [9]u64 = [0; 9];
+ var ki : usize = 0;
+ while (ki < 9) { keep[ki] = USED[ki]; ki = ki + 1; }
+ const cells : u64 = count_file(d);
+ if (cells == 0) {
+ ki = 0;
+ while (ki < 9) { USED[ki] = keep[ki]; ki = ki + 1; }
+ status_name(SAY_ERR_NO_BELS);
+ return 0;
+ }
+ HAS_USED = true;
+ CELLS = cells;
+ var ln : u32 = 0;
+ while (ln < (FNAME_N as u32) and ln < 1000) { LABEL[ln as usize] = FNAME[ln as usize]; ln = ln + 1; }
+ LABEL_N = ln;
+ var nn : u32 = 0;
+ if (N_LUTX > 0) {
+ nn = raw(NEXTPNR[0..], nn, "SLICE_LUTX ");
+ nn = num_grouped(NEXTPNR[0..], nn, N_LUTX);
+ }
+ if (N_FFX > 0) {
+ if (nn > 0) { nn = raw(NEXTPNR[0..], nn, ", "); }
+ nn = raw(NEXTPNR[0..], nn, "SLICE_FFX ");
+ nn = num_grouped(NEXTPNR[0..], nn, N_FFX);
+ }
+ if (N_C4 > 0) {
+ if (nn > 0) { nn = raw(NEXTPNR[0..], nn, ", "); }
+ nn = raw(NEXTPNR[0..], nn, "CARRY4 ");
+ nn = num_grouped(NEXTPNR[0..], nn, N_C4);
+ }
+ NEXTPNR_N = fin(nn);
+ // SOURCE_FILE: name, size, creator
+ var cr : [512]u8 = [0; 512];
+ var crl : u32 = 0;
+ const cv : i64 = obj_get(d, root(d), "creator");
+ if (kind(d, cv) == J_STRING) { crl = jstr_copy(cr[0..], d, cv); }
+ var sz : [32]u8 = [0; 32];
+ const szl : u32 = num_grouped(sz[0..], 0, FSIZE as u64);
+ SRCLINE_N = fin(fill(SRCLINE[0..], 0, SAY_SOURCE_FILE, FNAME[0..FNAME_N], sz[0..(szl as usize)], cr[0..(crl as usize)], 3));
+ CMD_COUNT = 0;
+ CMDS_N = 0;
+ DEMO_HIDDEN = false;
+ do_render();
+ return 0;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ EFF_CLEAR = false;
+ status_name(SAY_ERR_JSON);
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ EFF_CLEAR = false;
+ const want : bool = over != 0;
+ if (want == DRAGGING) { return 0; }
+ DRAGGING = want;
+ return 1;
+}
+
+pub fn w_drop_root() -> u32 {
+ return 1;
+}
+
+/// The picked file is forgotten at once: the file input is emptied after a pick.
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (EFF_CLEAR and k == 0) {
+ dst[0] = 118;
+ dst[1] = 0;
+ return 2;
+ }
+ return 0;
+}
+
+/// The splicer follows only the names a module itself mentions. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = put(o, 0, 0);
+ n = put_escaped(o, n, 0);
+ n = n + OVER + ((H_LT + H_GT + H_AMP + H_QUOTE + H_APOS + H_SPACE + H_SLASH + H_EQ) as u32);
+ n = n + (name_is(s, 0, 0, s) as u32) + (is_word_byte(0) as u32);
+ n = n + (hex4(s, 0)) + escape_at(s, 0, 0) + cp_utf8_len(0) + (cp_utf8_byte(0, 0) as u32);
+ n = n + (SPAN as u32) + (J_FALSE as u32) + (J_TRUE as u32) + (J_ARRAY as u32);
+ n = n + (key_is(s, 0, 0, s) as u32) + (NO_SPAN as u32);
+ return n;
+}
+
+; --- Tests of the native page ---------------------------------------------------------------------------------------
+; The expected texts are what node prints for the same doubles (String(x), toFixed, toPrecision).
+
+pub var T_OUT : [262144]u8 = [0; 262144];
+pub const W_RES_T : str = "{\"t27\":\"resource-bars/1\",\"source\":{\"design\":\"gHashTag/trinity-fpga fpga/openxc7-synth/trinity_v3_jtaguart.v @ de4ebda367\",\"xdc\":\"fpga/openxc7-synth/jtaguart.xdc\",\"top\":\"trinity_v3_jtaguart\",\"part\":\"xc7a200tfbg676-1\",\"chipdb\":\"xc7a200tfbg676-1.bin\",\"nextpnr\":\"nextpnr-xilinx 0.9.2-107-g7037c948\",\"yosys\":\"yosys 0.67+post b8e7da6f40\",\"commands\":[\"yosys -q -l yosys.log -p \\\"synth_xilinx -flatten -abc9 -arch xc7 -top trinity_v3_jtaguart; write_json synth.json\\\" trinity_v3_jtaguart.v\",\"nextpnr-xilinx --chipdb ~/openxc7-src/chipdb/xc7a200tfbg676-1.bin --xdc jtaguart.xdc --json synth.json --freq 50 --seed 1 --write routed.json --report report.json --sdf routed.sdf --fasm routed.fasm --log nextpnr.log\"],\"when\":\"2026-10-04 17:16 UTC\",\"prjxray_db\":\"517d66a383\"},\"cells\":373,\"used\":{\"LUT\":155,\"FF\":98,\"CARRY4\":23,\"SLICE\":60,\"BRAM36\":0,\"BRAM18\":0,\"DSP48\":0,\"IOB\":5,\"BUFG\":1,\"LUT_CELLS\":239},\"totals\":{\"LUT\":134600,\"FF\":269200,\"CARRY4\":33650,\"SLICE\":33650,\"BRAM36\":365,\"BRAM18\":730,\"DSP48\":740,\"IOB\":400,\"BUFG\":32},\"checks\":{\"slices_chipdb_carry4\":33650,\"slices_prjxray\":33650,\"lutx_bels_chipdb\":269200,\"ffx_bels_chipdb\":269200,\"ramb36_chipdb\":365,\"ramb36_prjxray\":365,\"ramb18_chipdb\":730,\"ramb18_prjxray\":730,\"dsp48_chipdb\":740,\"dsp48_prjxray\":740,\"bufgctrl_chipdb\":32,\"bufgctrl_prjxray\":32,\"pad_chipdb\":1062,\"iob_die_prjxray\":500,\"iob_bonded_package\":400},\"nextpnr_util\":{\"SLICE_LUTX\":[239,269200],\"SLICE_FFX\":[98,269200],\"CARRY4\":[23,33650],\"PSEUDO_GND\":[1,69165],\"PSEUDO_VCC\":[1,69165],\"PAD\":[5,1062],\"IOB33_OUTBUF\":[3,500],\"IOB33_INBUF_EN\":[2,500],\"BUFGCTRL\":[1,32]}}";
+pub const W_SMALL_T : str = "{\"modules\":{\"top\":{\"cells\":{\"a\":{\"type\":\"SLICE_LUTX\",\"attributes\":{\"NEXTPNR_BEL\":\"SLICE_X1Y2/A6LUT\"}},\"b\":{\"type\":\"SLICE_LUTX\",\"attributes\":{\"NEXTPNR_BEL\":\"SLICE_X1Y2/A5LUT\"}},\"c\":{\"type\":\"SLICE_LUTX\",\"attributes\":{\"NEXTPNR_BEL\":\"SLICE_X1Y2/B6LUT\"}},\"d\":{\"type\":\"SLICE_FFX\",\"attributes\":{\"NEXTPNR_BEL\":\"SLICE_X1Y3/AFF\"}},\"e\":{\"type\":\"CARRY4\",\"attributes\":{\"NEXTPNR_BEL\":\"SLICE_X2Y2/CARRY4\"}},\"f\":{\"type\":\"PAD\",\"attributes\":{\"NEXTPNR_BEL\":\"IOB_X0Y1/PAD\"}},\"g\":{\"type\":\"BUFGCTRL\",\"attributes\":{\"NEXTPNR_BEL\":\"BUFGCTRL_X0Y0/BUFGCTRL\"}},\"h\":{\"type\":\"RAMB36E1\",\"attributes\":{\"NEXTPNR_BEL\":\"RAMB36_X0Y0/RAMB36E1\"}},\"i\":{\"type\":\"FIFO18E1\",\"attributes\":{\"NEXTPNR_BEL\":\"RAMB18_X0Y0/RAMB18E1\"}},\"j\":{\"type\":\"DSP48E1\",\"attributes\":{\"NEXTPNR_BEL\":\"DSP48_X0Y0/DSP48E1\"}},\"k\":{\"type\":\"X\",\"attributes\":{}}}}},\"creator\":\"cr<>\"}";
+
+fn contains(s: []const u8, w: []const u8) -> bool {
+ if (w.len > s.len) { return false; }
+ var p : usize = 0;
+ while (p + w.len <= s.len) {
+ if (starts_s(s[p..s.len], w)) { return true; }
+ p = p + 1;
+ }
+ return false;
+}
+
+fn render() -> u32 {
+ return w_render(T_OUT[0..]);
+}
+
+fn has(n: u32, w: []const u8) -> bool {
+ return contains(T_OUT[0..(n as usize)], w);
+}
+
+fn js(x: f64, want: []const u8) -> bool {
+ var b : [64]u8 = [0; 64];
+ const n : u32 = js_num(b[0..], 0, x);
+ return eq_s(b[0..(n as usize)], want);
+}
+
+fn start() -> u32 {
+ w_init("");
+ w_data(0, W_RES_T);
+ return 0;
+}
+
+test shortest_digits_are_what_javascript_prints {
+ assert js(0.0007429420505200594, "0.0007429420505200594");
+ assert js(0.1, "0.1");
+ assert js(0.30000000000000004, "0.30000000000000004");
+ assert js(100.0, "100");
+ assert js(5.0, "5");
+ assert js(0.5, "0.5");
+ assert js(123456789.123, "123456789.123");
+ assert js(1.5e-7, "1.5e-7");
+ assert js(0.3333333333333333, "0.3333333333333333");
+ assert js(0.11515353115727003, "0.11515353115727003");
+ assert js(99.99999999999999, "99.99999999999999");
+ assert js(4.35, "4.35");
+ assert js(0.000001, "0.000001");
+ assert js(0.036404160475482915, "0.036404160475482915");
+ assert js(1.25, "1.25");
+ assert js(3.125, "3.125");
+ assert js(0.06835066864784546, "0.06835066864784546");
+ assert js(0.17830609212481427, "0.17830609212481427");
+}
+
+test percentages_are_rounded_like_toFixed_and_toPrecision_of_the_exact_double {
+ var b : [64]u8 = [0; 64];
+ var n : u32 = to_fixed(b[0..], 0, 12.25, 1);
+ assert eq_s(b[0..(n as usize)], "12.3");
+ n = to_fixed(b[0..], 0, 0.35, 2);
+ assert eq_s(b[0..(n as usize)], "0.35");
+ n = to_fixed(b[0..], 0, 9.995, 2);
+ assert eq_s(b[0..(n as usize)], "9.99");
+ n = to_fixed(b[0..], 0, 99.95, 1);
+ assert eq_s(b[0..(n as usize)], "100.0");
+ n = to_fixed(b[0..], 0, 0.125, 2);
+ assert eq_s(b[0..(n as usize)], "0.13");
+ n = to_fixed(b[0..], 0, 0.375, 2);
+ assert eq_s(b[0..(n as usize)], "0.38");
+ n = to_precision2(b[0..], 0, 0.005);
+ assert eq_s(b[0..(n as usize)], "0.0050");
+ n = to_precision2(b[0..], 0, 0.0052);
+ assert eq_s(b[0..(n as usize)], "0.0052");
+ n = to_precision2(b[0..], 0, 0.00995);
+ assert eq_s(b[0..(n as usize)], "0.010");
+ n = to_precision2(b[0..], 0, 0.00037147102526002974);
+ assert eq_s(b[0..(n as usize)], "0.00037");
+ PKG = 0;
+ n = pct_text(b[0..], 0, 155, 134600);
+ assert eq_s(b[0..(n as usize)], "0.12%");
+ n = pct_text(b[0..], 0, 0, 5);
+ assert eq_s(b[0..(n as usize)], "0%");
+ n = pct_text(b[0..], 0, 5, 0);
+ assert eq_s(b[0..(n as usize)], "-");
+ n = pct_text(b[0..], 0, 1, 269200);
+ assert eq_s(b[0..(n as usize)], "0.00037%");
+ n = pct_text(b[0..], 0, 5, 32);
+ assert eq_s(b[0..(n as usize)], "15.6%");
+}
+
+test json_is_accepted_or_refused_like_JSON_parse {
+ assert json_ok("{}");
+ assert json_ok(" [1, 2.5e-3, -0, \"a\\u00e9\\n\", null, true, false, {\"k\": []}] ");
+ assert json_ok("\"s\"");
+ assert json_ok("0");
+ assert json_ok("") == false;
+ assert json_ok("{") == false;
+ assert json_ok("not json") == false;
+ assert json_ok("[1,]") == false;
+ assert json_ok("{\"a\":1,}") == false;
+ assert json_ok("{a:1}") == false;
+ assert json_ok("01") == false;
+ assert json_ok("1.") == false;
+ assert json_ok("\"\\x\"") == false;
+ assert json_ok("[1] 2") == false;
+ assert json_ok("'a'") == false;
+ assert json_ok("tru") == false;
+}
+
+test the_demo_counts_draw_nine_bars_with_the_totals_and_the_lut_share {
+ start();
+ assert HAS_USED;
+ assert DEMO_HIDDEN;
+ const n : u32 = render();
+ assert has(n, "
");
+ }
+ if (copies == 1) {
+ n = text(dst, n, SAY_RESULT_ONE);
+ } else {
+ vn = pool_u(vb[0..], 0, "0", copies);
+ n = fmt_to(dst, n, SAY_RESULT_MANY, vb[0..], vn);
+ }
+ n = raw(dst, n, "
");
+ // bindNames: the names of every binding row, joined with " + "
+ var names : [256]u8 = [0; 256];
+ var nl : u32 = 0;
+ var j : u32 = 0;
+ while (j < 4) {
+ if (is_binding(j, b)) {
+ if (nl > 0) { nl = jn_raw(names[0..], nl, " + "); }
+ nl = jn_raw(names[0..], nl, SAY_RES_NAMES[j as usize]);
+ }
+ j = j + 1;
+ }
+ vn = jv_vput_str(vb[0..], 0, "0", names[0..(nl as usize)]);
+ vn = pool_num_unit(vb[0..], vn, "1", per_copy(b), b);
+ vn = pool_u(vb[0..], vn, "2", cap_of(b));
+ n = fmt_to(dst, n, SAY_BINDING, vb[0..], vn);
+ n = raw(dst, n, "
");
+ vn = pool_unit(vb[0..], 0, "0", b);
+ n = fmt_to(dst, n, SAY_UPPER_BOUND, vb[0..], vn);
+ return raw(dst, n, "
");
+}
+
+fn canvas_html(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+}
+
+/// The grid line under the canvas.
+fn grid_line_html(dst: []u8, n0: u32, b: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ if (!RENDERED or CV_HIDDEN or b == 4) { return raw(dst, n, "
"); }
+ var vb : [512]u8 = [0; 512];
+ var vn : u32 = 0;
+ const copies : u64 = copies_of(b);
+ const left : u64 = cap_of(b) - copies * per_copy(b);
+ var scale : u64 = (copies + (K_GRID_MAX_CELLS as u64) - 1) / (K_GRID_MAX_CELLS as u64);
+ if (scale < 1) { scale = 1; }
+ if (scale == 1) {
+ vn = pool_num_unit(vb[0..], 0, "0", cap_of(b), b);
+ vn = pool_num_unit(vb[0..], vn, "1", left, b);
+ n = fmt_to(dst, n, SAY_GRID_LINE, vb[0..], vn);
+ } else {
+ vn = pool_u(vb[0..], 0, "0", scale);
+ vn = pool_num_unit(vb[0..], vn, "1", cap_of(b), b);
+ vn = pool_num_unit(vb[0..], vn, "2", left, b);
+ n = fmt_to(dst, n, SAY_GRID_SCALED, vb[0..], vn);
+ }
+ return raw(dst, n, "");
+}
+
+/// The whole page, as tool.js's render() and load left it.
+pub fn w_render(dst: []u8) -> u32 {
+ const b : u32 = binding_first();
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_PRESET_LABEL);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < W_NPRESETS) {
+ n = btn(dst, n, PRESET == (i as i32), SAY_PRESET_NAMES[i as usize]);
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ if (RENDERED) { n = preset_line(dst, n, DATA[0..(DATA_LEN as usize)]); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DIVIDE_LABEL);
+ n = raw(dst, n, "");
+ i = 0;
+ while (i < 2) {
+ n = btn(dst, n, MODE == i, SAY_DIVIDE_NAMES[i as usize]);
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ if (LOAD >= 2) { n = text(dst, n, SAY_ERR_DATA); } else { n = text(dst, n, SAY_LOADING); }
+ n = raw(dst, n, "
");
+ n = result_html(dst, n, b);
+ n = canvas_html(dst, n);
+ n = grid_line_html(dst, n, b);
+ n = table_html(dst, n, b);
+ if (LOAD == 1) {
+ n = notes_html(dst, n, DATA[0..(DATA_LEN as usize)]);
+ n = sources_html(dst, n, DATA[0..(DATA_LEN as usize)]);
+ } else {
+ if (LOAD == 3 and !ex0_is_null(DATA[0..(DATA_LEN as usize)])) {
+ n = notes_html(dst, n, DATA[0..(DATA_LEN as usize)]);
+ } else {
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "");
+ }
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+// --- Tests of the native page ---------------------------------------------------------------------
+
+pub const W_TEST_DATA : str = "{\"totals\":{\"SLICE_LUTX\":269200,\"SLICE_FFX\":269200,\"DSP48E1\":740,\"RAMB18E1\":730,\"RAMB36E1\":365},\"examples\":[{\"top\":\"trinet\",\"board\":\"ALINX\",\"part\":\"xc7\",\"nextpnr_version\":\"0.9\",\"nextpnr_log\":{\"path\":\"/a\",\"sha256\":\"ab\",\"bytes\":1234},\"used\":{\"SLICE_LUTX\":4738},\"yosys\":{\"LUT\":4732}},{\"top\":\"tnf16\",\"board\":\"B\",\"part\":\"p\",\"nextpnr_log\":{\"path\":\"/b\",\"sha256\":\"cd\",\"bytes\":20},\"used\":{\"SLICE_LUTX\":838},\"yosys\":{\"LUT\":836}}]}";
+
+/// Does the first n bytes of o contain the text want?
+fn has(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (want.len > (n as usize)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn count_of(o: []const u8, n: u32, want: []const u8) -> u32 {
+ var c : u32 = 0;
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { c = c + 1; }
+ i = i + 1;
+ }
+ return c;
+}
+
+test the_page_opens_loading_with_an_empty_table_and_a_visible_canvas {
+ w_init("");
+ var o : [24576]u8 = [0; 24576];
+ const n : u32 = w_render(o[0..]);
+ assert n > 0;
+ assert has(o[0..], n, "
Reading the device totals...
");
+ assert has(o[0..], n, "");
+ assert has(o[0..], n, "");
+ assert has(o[0..], n, "aria-pressed=\"true\">LUT6 positions");
+ assert w_draw(o[0..]) == 0;
+}
+
+test node0_loads_fits_28_times_and_luts_bind {
+ w_init("");
+ w_view(600.0, 1.0, 0);
+ w_data(0, W_TEST_DATA);
+ assert LOAD == 1;
+ assert COUNTS[0] == 4738 and COUNTS[1] == 2378 and COUNTS[3] == 1;
+ assert binding_first() == 0;
+ assert copies_of(0) == K_EX_NODE0_COPIES as u64;
+ var o : [24576]u8 = [0; 24576];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
28 copies fit
");
+ assert has(o[0..], n, "LUTs run out first: 4,738 LUT6 positions per copy, 134,600 on the device.");
+ assert has(o[0..], n, "trinet on the ALINX (xc7): nextpnr placed 4,738 SLICE_LUTX; yosys had counted 4,732 LUT1-LUT6 cells.");
+ assert has(o[0..], n, "width=\"600\" height=\"264\" style=\"width: 600px; height: 264px;\" aria-label=\"28 copies drawn over 134,600 LUT6 positions\"");
+ assert has(o[0..], n, "
");
+ assert has(o[0..], n, "113");
+}
+
+test the_draw_list_is_28_green_cells_then_the_gold_one_with_its_tint {
+ w_init("");
+ w_view(600.0, 1.0, 0);
+ w_data(0, W_TEST_DATA);
+ var o : [16384]u8 = [0; 16384];
+ const n : u32 = w_draw(o[0..]);
+ assert n > 0;
+ assert has(o[0..], n, "[\"c\",0],[\"m\",\"setTransform\",1,0,0,1,0,0],[\"m\",\"clearRect\",0,0,600,264],[\"s\",\"fillStyle\",{\"v\":\"--w-green\",\"f\":\"#00ff88\"}],[\"m\",\"fillRect\",0,0,59,59],[\"s\",\"fillStyle\",{\"v\":\"--w-green\",\"f\":\"#00ff88\"}],[\"m\",\"fillRect\",66,0,59,59],");
+ assert count_of(o[0..], n, "\"fillRect\"") == 29;
+ assert count_of(o[0..], n, "\"strokeRect\"") == 1;
+ assert has(o[0..], n, "[\"s\",\"globalAlpha\",0.35]");
+ assert has(o[0..], n, "[\"s\",\"strokeStyle\",{\"v\":\"--w-gold\",\"f\":\"#ffd700\"}],[\"s\",\"lineWidth\",2]");
+}
+
+test dividing_by_lut_bels_doubles_the_copies {
+ w_init("");
+ w_view(600.0, 1.0, 0);
+ w_data(0, W_TEST_DATA);
+ w_click(3);
+ assert MODE == 1;
+ assert copies_of(binding_first()) == K_EX_NODE0_COPIES_BELS as u64;
+ var o : [24576]u8 = [0; 24576];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "56 copies fit");
+ assert has(o[0..], n, "LUT BELs per copy, 269,200 on the device.");
+ w_click(1);
+ assert PRESET == 1;
+ assert copies_of(binding_first()) == K_EX_TNF16_COPIES_BELS as u64;
+}
+
+test a_typed_text_is_read_like_number_floor_and_cut_at_the_ceiling {
+ w_init("");
+ w_input(0, " 12.9 ");
+ assert COUNTS[0] == 12;
+ w_input(0, "abc");
+ assert COUNTS[0] == 0;
+ w_input(0, "1e9");
+ assert COUNTS[0] == K_INPUT_MAX as u64;
+ w_input(0, "0x10");
+ assert COUNTS[0] == 16;
+ w_input(0, "-5");
+ assert COUNTS[0] == 0;
+ w_input(0, "Infinity");
+ assert COUNTS[0] == 0;
+ assert PRESET == 0 - 1;
+}
+
+test a_design_too_big_does_not_fit_and_hides_the_canvas {
+ w_init("");
+ w_view(600.0, 1.0, 0);
+ w_input(0, "200000");
+ var o : [24576]u8 = [0; 24576];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
Does not fit
");
+ assert has(o[0..], n, "One copy needs 200,000 LUT6 positions; the device has 134,600.");
+ assert has(o[0..], n, "");
+ assert w_draw(o[0..]) == 0;
+ w_input(0, "");
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "
Type a LUT count, or pick a real design.
");
+}
+
+test a_small_design_draws_one_cell_per_several_copies_and_a_hollow_cell {
+ w_init("");
+ w_view(600.0, 1.0, 0);
+ w_input(0, "10");
+ assert G_SCALE == 23;
+ assert G_FULL == 585 and G_REST == 5;
+ var o : [49152]u8 = [0; 49152];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "13,460 copies fit");
+ assert has(o[0..], n, "One cell per 23 copies over the device's 134,600 LUT6 positions; the last, hollow cell is what is left: 0 LUT6 positions.");
+ const d : u32 = w_draw(o[0..]);
+ assert has(o[0..], d, "[\"s\",\"strokeStyle\",{\"v\":\"--w-line\",\"f\":\"#1a4\"}]");
+ assert count_of(o[0..], d, "\"fillRect\"") == 586;
+}
+
+test the_window_sets_the_canvas_size_and_a_resize_recomputes_it {
+ w_init("");
+ w_view(1000.0, 2.0, 0);
+ w_input(0, "4738");
+ assert CV_CSS_W == 1000 and CV_PX_W == 2000;
+ w_view(320.0, 1.5, 1);
+ assert CV_CSS_W == 1000;
+ w_resize();
+ assert CV_CSS_W == 320 and CV_PX_W == 480;
+ w_view(0.0, 0.0, 0);
+ w_resize();
+ assert CV_CSS_W == 600 and CV_PX_W == 600;
+}
+
+test data_that_does_not_agree_with_the_totals_says_so_and_stays_visible {
+ w_init("");
+ w_data(0, "{\"totals\":{\"SLICE_LUTX\":\"269200\"},\"examples\":[{}]}");
+ assert LOAD == 2;
+ var o : [24576]u8 = [0; 24576];
+ var n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
The device totals did not load.
");
+ w_init("");
+ w_data(0, "{\"totals\":{\"SLICE_LUTX\":269200,\"SLICE_FFX\":269200,\"DSP48E1\":740,\"RAMB18E1\":730,\"RAMB36E1\":365},\"examples\":[]}");
+ assert LOAD == 2;
+ w_init("");
+ w_data(0, "{\"totals\":");
+ assert LOAD == 2;
+ w_init("");
+ w_data_failed(0);
+ n = w_render(o[0..]);
+ assert has(o[0..], n, "The device totals did not load.");
+ assert w_draw(o[0..]) == 0;
+}
+
+test the_numbers_in_the_words_are_grouped_like_en_us_and_the_notes_read_the_data {
+ w_init("");
+ w_data(0, W_TEST_DATA);
+ var o : [24576]u8 = [0; 24576];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "its SLICE_LUTX total 269,200 is 33,650 slices x 8, a LUT6 and a LUT5 BEL");
+ assert has(o[0..], n, "Both logs are from the ALINX (xc7), not the QMTech Wukong (fgg676). Their PAD total, 1,062,");
+ assert has(o[0..], n, "
/a: sha256 ab, 1,234 bytes, top trinet
");
+ assert has(o[0..], n, "names nextpnr-xilinx 0.9. Both logs print the same total for all 5 BEL types.");
+ assert obj_len(W_TEST_DATA, obj_get(W_TEST_DATA, root(W_TEST_DATA), "totals")) == 5;
+}
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader, the number rules and the
+/// canvas writers call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ n = n + num_grouped(o, 0, 0);
+ n = n + CV_OVER;
+ n = n + (cv_ok(0) as u32);
+ n = n + cv_put(o, 0, 0);
+ n = n + cv_raw(o, 0, s);
+ n = n + (cv_fin(0.0) as u32);
+ n = n + cv_num(o, 0, 0.0);
+ n = n + cv_str(o, 0, s);
+ n = n + (cv_hex(0) as u32);
+ n = n + cv_open(o, 0, 0, s);
+ n = n + cv_close(o, 0);
+ n = n + cv_arg_f(o, 0, 0.0);
+ n = n + cv_arg_s(o, 0, s);
+ n = n + cv_arg_v(o, 0, s, s);
+ n = n + cv_arg_b(o, 0, false);
+ n = n + cv_arg_list(o, 0, 0.0, 0.0, 0);
+ n = n + cv_canvas(o, 0, 0);
+ n = n + cv_set_num(o, 0, s, 0.0);
+ n = n + cv_set_str(o, 0, s, s);
+ n = n + cv_set_var(o, 0, s, s, s);
+ n = n + cv_call0(o, 0, s);
+ n = n + cv_call2(o, 0, s, 0.0, 0.0);
+ n = n + cv_call4(o, 0, s, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_call6(o, 0, s, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_arc(o, 0, 0.0, 0.0, 0.0, 0.0, 0.0, false);
+ n = n + cv_text(o, 0, s, s, 0.0, 0.0);
+ n = n + cv_dash(o, 0, 0.0, 0.0, 0);
+ return n;
+}
From b04c7cac427f76ab73582dfc158eb983ca65705a Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 19:23:24 +0700
Subject: [PATCH 44/63] widgets: catalog records fits-on, the first canvas
page, at PORT_DRAW (Closes #11016)
---
specs/widgets/catalog.t27 | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index 30a05962dd..6762b5ed8d 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -37,7 +37,7 @@
; 14667449, lut-explain 1395124, bit-diff 275620, vcd-wrapped 76076, pin-map 180587, synth-smells 87094, fsm-sketch 91657 (its Math.sin/cos/atan2 stay with the host: V8 differs from libm in the last bit), bit-autopsy 96007.
; With these, all 12 pure logic files of the census (2775 lines of JavaScript) exist as .t27.
; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
-; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc and resource-bars at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
+; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
; comparisons vs tool.js, 0 mismatches; specs/widgets/race_the_bee.t27: 59, with 13 data variants, 0). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
@@ -91,7 +91,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 1, 2, 2, 3, 1, 3, 3, 3, 2, 3, 1, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
+pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 3, 2, 2, 3, 1, 3, 3, 3, 2, 3, 1, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -227,13 +227,12 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test twelve_pages_are_native_and_the_remaining_logic_files_are_ported {
- assert count_port(PORT_DRAW) == 12;
+test thirteen_pages_are_native_and_the_remaining_logic_files_are_ported {
+ assert count_port(PORT_DRAW) == 13;
assert count_port(PORT_LOGIC) == 9;
- assert count_port(PORT_WORDS) == 7;
+ assert count_port(PORT_WORDS) == 6;
assert count_port(PORT_SEALED) == 0;
+ assert TOOL_PORT[5] == PORT_DRAW;
assert TOOL_PORT[17] == PORT_DRAW;
- assert TOOL_PORT[14] == PORT_DRAW;
- assert TOOL_PORT[8] == PORT_DRAW;
assert TOOL_PORT[7] == PORT_LOGIC;
}
From 39d49f4dd0a62b7b718f3bb0bbad980e3e56b5ea Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 19:11:25 +0700
Subject: [PATCH 45/63] widgets: fsm-sketch drawn natively -- 62941-byte wasm,
2612 DOM comparisons, 0 differences (Closes #10938)
The trinity spec fsm-sketch.t27 verbatim (one string-vs-string test assert split in
two so zig can compile it) plus the native page: sample picker, KISS2 / yosys log
reading and the state diagram (SVG) from fsm_sketch_logic.t27, table, key, SVG save.
sin/cos/atan2 come from the host through a queue/answer service (w_libm_need /
w_libm_give). t27c test-report 12/12; t27b: unsupported (`as` between float and int).
Native-only constants are named W_...; K_DATA_FILES is the one K_ name added.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/fsm_sketch.t27 | 2834 ++++++++++++++++++++++++++++++++++
1 file changed, 2834 insertions(+)
create mode 100644 specs/widgets/fsm_sketch.t27
diff --git a/specs/widgets/fsm_sketch.t27 b/specs/widgets/fsm_sketch.t27
new file mode 100644
index 0000000000..da80f160bc
--- /dev/null
+++ b/specs/widgets/fsm_sketch.t27
@@ -0,0 +1,2834 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/fsm-sketch.t27 -- the state machine yosys extracted from your RTL, drawn
+; Source of truth for public/widgets/fsm-sketch/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its
+; own and reads every word below from window.T27_WIDGET.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader drops the KISS2 file yosys `fsm_export` wrote, or drops or pastes the
+; log of a yosys run that went through fsm_detect and fsm_info (any synth log does: its fsm pass
+; runs both). public/widgets/fsm-sketch/fsmparse.js reads it in the browser: the registers
+; fsm_detect took, the ones it named and refused with its reason, and for each FSM fsm_info
+; dumped its inputs, state codes and transition table. The page says plainly what yosys
+; extracted, counts states, transitions and inputs, and draws the graph as SVG: states on an
+; ellipse in breadth-first order from the reset state, one curve per state pair labelled with the
+; input patterns of its rows. No layout library; the file never leaves the page.
+
+; THE SAMPLES: four real runs of Yosys 0.67+post (git sha1 b8e7da6f) on this machine, made by
+; scripts/widget-data/fsm-sketch.mjs on three Verilog files of gHashTag/trinity, each copied to a
+; scratch directory first. Each run is
+; yosys -q -l .yosys.txt -p "read_verilog ; hierarchy -top ; proc; ;
+; fsm_detect; fsm_extract; fsm_info; fsm_export -o .kiss2"
+; with = "opt -nodffe -nosdff" (as yosys synth runs it) or, for the fourth, plain "opt".
+; The logs and KISS2 files are shipped byte for byte under public/widgets/fsm-sketch/samples/;
+; sample.json holds the commands, the yosys -V line, the sha256 of every input and output, and
+; the layout card.png is drawn from. Every K_SAMPLE_ fact below is re-read from the shipped logs
+; by `node scripts/widget-data/fsm-sketch.mjs --check`, which also runs this spec's tests on the
+; values it read and a negative control that must fail them, and checks that each KISS2 file
+; holds the same rows as the fsm_info table of its log.
+
+; WHAT IT DOES NOT CLAIM: that a design with no extracted FSM has no state machine. fsm_detect
+; takes only registers it recognises, and names only some of the ones it refuses: on fsm_simple.v,
+; a three-state traffic light, it refuses next_state and says nothing about state. yosys calls the states s0 to sN; on the samples
+; this page matches each code to the source's localparam values in its own script, and says so.
+; The codes are the source's own until fsm_recode re-encodes them (one-hot, in a synth run);
+; re-encoding changes the flip-flops, not the graph.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_fsm_sketch;
+
+use widgets::html_out::OVER;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::ok;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::same_bytes;
+use widgets::fsm_sketch_logic::NONE;
+use widgets::fsm_sketch_logic::NO_TEXT;
+use widgets::fsm_sketch_logic::PACK;
+use widgets::fsm_sketch_logic::PI;
+use widgets::fsm_sketch_logic::pack;
+use widgets::fsm_sketch_logic::lo_of;
+use widgets::fsm_sketch_logic::hi_of;
+use widgets::fsm_sketch_logic::inf;
+use widgets::fsm_sketch_logic::nan;
+use widgets::fsm_sketch_logic::neg_zero;
+use widgets::fsm_sketch_logic::truthy;
+use widgets::fsm_sketch_logic::jsmax;
+use widgets::fsm_sketch_logic::jsmin;
+use widgets::fsm_sketch_logic::jsround;
+use widgets::fsm_sketch_logic::hypot2;
+use widgets::fsm_sketch_logic::unit_to;
+use widgets::fsm_sketch_logic::rot_to;
+use widgets::fsm_sketch_logic::node_angle;
+use widgets::fsm_sketch_logic::box_exit;
+use widgets::fsm_sketch_logic::code_value;
+use widgets::fsm_sketch_logic::condition_of;
+use widgets::fsm_sketch_logic::counts_of;
+use widgets::fsm_sketch_logic::walk_order;
+use widgets::fsm_sketch_logic::label_box;
+use widgets::fsm_sketch_logic::overlap;
+use widgets::fsm_sketch_logic::digits_of;
+use widgets::fsm_sketch_logic::anchor_of;
+use widgets::fsm_sketch_logic::valign_of;
+use widgets::fsm_sketch_logic::layout_fsm;
+use widgets::fsm_sketch_logic::place;
+use widgets::fsm_sketch_logic::place_labels;
+use widgets::fsm_sketch_logic::EV;
+use widgets::fsm_sketch_logic::EV_FOUND;
+use widgets::fsm_sketch_logic::EV_REJ;
+use widgets::fsm_sketch_logic::EV_REASON;
+use widgets::fsm_sketch_logic::EV_RECODE;
+use widgets::fsm_sketch_logic::EV_FSM;
+use widgets::fsm_sketch_logic::EV_NUM;
+use widgets::fsm_sketch_logic::EV_IN;
+use widgets::fsm_sketch_logic::EV_OUT;
+use widgets::fsm_sketch_logic::EV_STATE;
+use widgets::fsm_sketch_logic::EV_ROW;
+use widgets::fsm_sketch_logic::EV_RESET;
+use widgets::fsm_sketch_logic::EV_NAME;
+use widgets::fsm_sketch_logic::EV_ROW2;
+use widgets::fsm_sketch_logic::EV_ILB;
+use widgets::fsm_sketch_logic::EV_OB;
+use widgets::fsm_sketch_logic::N_KISS2_S;
+use widgets::fsm_sketch_logic::N_KISS2_P;
+use widgets::fsm_sketch_logic::N_NO_RESET_NAMED;
+use widgets::fsm_sketch_logic::N_NO_RESET;
+use widgets::fsm_sketch_logic::N_WIDTH_IN;
+use widgets::fsm_sketch_logic::N_RECODED;
+use widgets::fsm_sketch_logic::N_NO_INFO;
+use widgets::fsm_sketch_logic::E_UNKNOWN;
+use widgets::fsm_sketch_logic::E_KISS2ROW;
+use widgets::fsm_sketch_logic::E_NOFSMPASS;
+use widgets::fsm_sketch_logic::is_ws;
+use widgets::fsm_sketch_logic::is_digit;
+use widgets::fsm_sketch_logic::match_in;
+use widgets::fsm_sketch_logic::eat;
+use widgets::fsm_sketch_logic::span_eq;
+use widgets::fsm_sketch_logic::skip_ws;
+use widgets::fsm_sketch_logic::skip_digits;
+use widgets::fsm_sketch_logic::tok_end;
+use widgets::fsm_sketch_logic::run_noquote;
+use widgets::fsm_sketch_logic::run_code;
+use widgets::fsm_sketch_logic::rstrip;
+use widgets::fsm_sketch_logic::no_cr;
+use widgets::fsm_sketch_logic::has_sub;
+use widgets::fsm_sketch_logic::dig_val;
+use widgets::fsm_sketch_logic::split_nl;
+use widgets::fsm_sketch_logic::hex_val;
+use widgets::fsm_sketch_logic::js_number;
+use widgets::fsm_sketch_logic::state_num;
+use widgets::fsm_sketch_logic::is_nl;
+use widgets::fsm_sketch_logic::tail_ok;
+use widgets::fsm_sketch_logic::sniff;
+use widgets::fsm_sketch_logic::ow;
+use widgets::fsm_sketch_logic::ev;
+use widgets::fsm_sketch_logic::clear_res;
+use widgets::fsm_sketch_logic::pat_ok;
+use widgets::fsm_sketch_logic::name_index;
+use widgets::fsm_sketch_logic::parse_kiss2;
+use widgets::fsm_sketch_logic::name_event;
+use widgets::fsm_sketch_logic::state_num_of;
+use widgets::fsm_sketch_logic::m_found;
+use widgets::fsm_sketch_logic::m_not_marking;
+use widgets::fsm_sketch_logic::m_recoding;
+use widgets::fsm_sketch_logic::m_mapping;
+use widgets::fsm_sketch_logic::m_info;
+use widgets::fsm_sketch_logic::m_number_line;
+use widgets::fsm_sketch_logic::is_header;
+use widgets::fsm_sketch_logic::is_rule;
+use widgets::fsm_sketch_logic::is_indented;
+use widgets::fsm_sketch_logic::m_index_line;
+use widgets::fsm_sketch_logic::m_encoding_line;
+use widgets::fsm_sketch_logic::m_row_line;
+use widgets::fsm_sketch_logic::is_fsm_module_line;
+use widgets::fsm_sketch_logic::m_module_name;
+use widgets::fsm_sketch_logic::unslash;
+use widgets::fsm_sketch_logic::m_yosys_version;
+use widgets::fsm_sketch_logic::find_slot;
+use widgets::fsm_sketch_logic::count_sets;
+use widgets::fsm_sketch_logic::parse_log;
+use widgets::fsm_sketch_logic::pend_lo_set;
+use widgets::fsm_sketch_logic::ev_last_index;
+use widgets::fsm_sketch_logic::slot_has_rows;
+use widgets::fsm_sketch_logic::last_with;
+use widgets::fsm_sketch_logic::emit_log;
+use widgets::fsm_sketch_logic::last_set_event;
+use widgets::fsm_sketch_logic::recode_of;
+use widgets::fsm_sketch_logic::emit_fsm;
+use widgets::fsm_sketch_logic::fsm_parse;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "fsm-sketch";
+pub const TITLE : str = "FSM sketch: the state machine yosys found in your RTL";
+pub const DESCRIPTION : str = "Drop a yosys fsm_export KISS2 file or paste the fsm_info log: the state diagram yosys extracted, its states, transitions and inputs, and the registers it refused.";
+pub const IMAGE_ALT : str = "FSM sketch card: the state diagram yosys 0.67 extracted from uart_rx.v, four states IDLE, START, DATA and STOP with 14 transitions on 4 inputs; beside it fsm_simple.v, a traffic light yosys refused because a register has an initial value, and uart_rx.v again with plain opt, where yosys found nothing and said nothing.";
+pub const HOOK : str = "Did yosys see your state machine? Drop its fsm_export file or log and see the graph it extracted, or the reason it refused.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159), run four times by scripts/widget-data/fsm-sketch.mjs: nice -n 10 yosys -q -l .yosys.txt -p \"read_verilog ; hierarchy -top ; proc; opt -nodffe -nosdff; fsm_detect; fsm_extract; fsm_info; fsm_export -o .kiss2\" (the fourth run: plain opt)", "gHashTag/trinity fpga/rtl/uart_rx.v, fpga/openxc7-synth/spi_flash_master.v and fpga/openxc7-synth/fsm_simple.v, copied to a scratch directory at their repo-relative paths; sha256 of each in sample.json", "public/widgets/fsm-sketch/samples/: the four yosys logs and the two KISS2 files, byte for byte; sample.json: commands, yosys -V line, exit codes, sha256 of inputs and outputs, localparam matches", "public/widgets/fsm-sketch/fsmparse.js: the one parser and layout, run in node for the samples and in the browser for your file; card.png is drawn from sample.json by scripts/widget-data/fsm-sketch-card.py", "node scripts/widget-data/fsm-sketch.mjs --check: re-reads the shipped logs and fails if any K_SAMPLE_ fact in this spec disagrees or a KISS2 file and its log differ"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The samples: what was run -------------------------------------------------------------------
+pub const K_SAMPLE_JSON : str = "sample.json";
+pub const K_SAMPLE_DIR : str = "samples";
+pub const K_SAMPLE_IDS : [4]str = ["uart-rx", "spi-flash", "fsm-simple", "uart-rx-plain-opt"];
+pub const K_SAMPLE_DESIGNS : [4]str = ["fpga/rtl/uart_rx.v", "fpga/openxc7-synth/spi_flash_master.v", "fpga/openxc7-synth/fsm_simple.v", "fpga/rtl/uart_rx.v"];
+pub const K_SAMPLE_TOPS : [4]str = ["uart_rx", "spi_flash_master", "fsm_simple", "uart_rx"];
+pub const K_SAMPLE_OPTS : [4]str = ["opt -nodffe -nosdff", "opt -nodffe -nosdff", "opt -nodffe -nosdff", "opt"];
+; The sample the card shows.
+pub const K_CARD_SAMPLE : u8 = 0;
+
+; --- The samples: what yosys said (re-read from the logs by fsm-sketch.mjs --check) ---------------
+pub const K_SAMPLE_YOSYS : str = "0.67+post";
+pub const K_SAMPLE_FOUND : [4]u16 = [1, 1, 0, 0];
+pub const K_SAMPLE_REJECTED : [4]u16 = [0, 0, 1, 0];
+pub const K_SAMPLE_STATES : [4]u16 = [4, 7, 0, 0];
+pub const K_SAMPLE_TRANSITIONS : [4]u16 = [14, 23, 0, 0];
+pub const K_SAMPLE_INPUTS : [4]u16 = [4, 7, 0, 0];
+pub const K_SAMPLE_REASON : str = "Register has an initialization value.";
+
+; --- How it reads and draws ------------------------------------------------------------------------
+; A file or pasted text over this many characters is not read.
+pub const K_MAX_CHARS : u32 = 4000000;
+; Above this many states the page lists the table and draws no graph.
+pub const K_MAX_DRAW_STATES : u8 = 40;
+; Input patterns written on one edge before the rest fold into "+N more".
+pub const K_LABEL_ROWS : u8 = 3;
+pub const K_FONT_PX : u8 = 13;
+; The diagram is laid out between these widths: scaled down on a narrow screen, centred on a wide one.
+pub const K_DIAGRAM_MIN_W : u16 = 460;
+pub const K_DIAGRAM_MAX_W : u16 = 780;
+pub const K_DIAGRAM_MAX_H : u16 = 600;
+; Transition rows listed before "Show all".
+pub const K_TABLE_ROWS : u8 = 24;
+pub const K_MIN_TARGET_PX : u8 = 32;
+pub const K_CARD_W : u16 = 1200;
+pub const K_CARD_H : u16 = 630;
+; The graph's box on the card, and its font.
+pub const K_CARD_GRAPH_W : u16 = 680;
+pub const K_CARD_GRAPH_H : u16 = 470;
+pub const K_CARD_FONT_PX : u8 = 19;
+
+; --- Every word on the screen ----------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_SAMPLE_LABEL : str = "Real yosys runs";
+pub const SAY_SAMPLE_NAMES : [4]str = ["uart_rx: 4 states", "spi_flash_master: 7 states", "fsm_simple: refused", "uart_rx, plain opt: silent"];
+pub const SAY_SAMPLE_LINES : [4]str = ["A UART receiver from gHashTag/trinity, fpga/rtl/uart_rx.v. yosys 0.67 took its state register and extracted all four states.", "The SPI flash reader in fpga/openxc7-synth/spi_flash_master.v: seven states, from chip select low to done.", "A three-state traffic light, fpga/openxc7-synth/fsm_simple.v, whose registers are declared with an initial value. yosys refused next_state for that reason and said nothing at all about state.", "The same uart_rx.v with plain opt where synth runs opt -nodffe -nosdff. opt folded the reset into the register first; fsm_detect then found nothing and printed nothing."];
+pub const SAY_OPEN_LOG : str = "yosys log";
+pub const SAY_OPEN_KISS2 : str = "KISS2 file";
+pub const SAY_NAMES_NOTE : str = "Names after the state are this page's match of each code to a localparam in the source file, made by its script. yosys itself only says s0 to s{0}.";
+pub const SAY_OWN_LABEL : str = "Your design";
+pub const SAY_HOW : str = "yosys -l fsm.txt -p \"read_verilog top.v; proc; opt -nodffe -nosdff; fsm_detect; fsm_extract; fsm_info; fsm_export -o fsm.kiss2\"";
+pub const SAY_HOW_LINE : str = "Drop fsm.txt (names, codes and refusals) or fsm.kiss2 (the graph only). The log of a synth or synth_xilinx run works too: its fsm pass prints the same lines, after re-encoding.";
+pub const SAY_DROP : str = "Drop a yosys log or a .kiss2 file here, or pick one";
+pub const SAY_PASTE_HINT : str = "...or paste the yosys log or the KISS2 text";
+pub const SAY_PASTE_LABEL : str = "yosys log or KISS2 text";
+pub const SAY_READ : str = "Draw it";
+pub const SAY_CLEAR : str = "Clear";
+pub const SAY_PICKED : str = "{0}: {1} bytes";
+pub const SAY_PASTED : str = "pasted text";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab; nothing is sent.";
+pub const SAY_TOO_BIG : str = "{0} characters is more than this page reads ({1}).";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_LOAD : str = "The sample did not load.";
+; One per parse error code of fsmparse.js, in this order.
+pub const SAY_ERROR_IDS : [3]str = ["unknown", "kiss2row", "nofsmpass"];
+pub const SAY_ERROR_TEXT : [3]str = ["Neither a KISS2 file nor a yosys log with fsm_detect or fsm_info in it.", "A KISS2 row that is not : {0}", "This log never ran fsm_detect or fsm_info. Add them, or run synth: its fsm pass runs both."];
+
+; What yosys extracted, said first.
+pub const SAY_VERDICT_NONE : str = "yosys extracted no FSM";
+pub const SAY_VERDICT_ONE : str = "yosys extracted 1 FSM";
+pub const SAY_VERDICT_MANY : str = "yosys extracted {0} FSMs";
+pub const SAY_VERDICT_KISS2 : str = "One FSM, as fsm_export wrote it";
+pub const SAY_FSM_LINE : str = "{0}: {1} states, {2} transitions, {3} inputs";
+pub const SAY_REFUSED : str = "Refused by fsm_detect: {0}";
+pub const SAY_SILENT : str = "fsm_detect ran and named no register: it found no candidate, and it does not list what it skipped. In the uart_rx sample, plain opt had already merged the reset and enable into the state register ($sdffe) and fsm_detect passed it over; opt -nodffe -nosdff, as synth runs it, keeps it a plain $dff.";
+pub const SAY_NO_RESET : str = "no reset state";
+pub const SAY_ENCODING : str = "re-encoded by fsm_recode: {0}";
+pub const SAY_VERSION : str = "yosys {0}";
+pub const SAY_KISS2_NOTE : str = "A KISS2 file names no signal and no code: inputs read in[N] down to in[0], highest first as yosys prints them. The log carries the names.";
+pub const SAY_NOTE_IDS : [7]str = ["recoded", "no_info", "kiss2_s", "kiss2_p", "no_reset_named", "no_reset", "width_in"];
+pub const SAY_NOTE_TEXT : [7]str = ["fsm_recode changed the encoding before fsm_info printed it: the codes shown are the new ones, not your localparams.", "fsm_detect took {0} register(s); this log has fsm_info for {1} of them.", "The header says .s {0}; the rows name {1} states.", "The header says .p {0}; the file has {1} rows.", "The reset state {0} is in no row: no reset state is drawn.", "No .r line: the file names no reset state.", "Some rows are not {0} inputs wide."];
+
+; The FSM in view.
+pub const SAY_PICK_FSM : str = "FSM";
+pub const SAY_STAT_NAMES : [4]str = ["states", "transitions", "inputs", "outputs"];
+pub const SAY_DIAGRAM_HEAD : str = "State diagram";
+pub const SAY_DIAGRAM_ALT : str = "State diagram of {0}: {1} states, {2} transitions.";
+pub const SAY_LEGEND : str = "Gold double outline: the reset state. Each edge lists the input pattern of its rows, highest input first; - is a don't-care.";
+pub const SAY_EDGE_MORE : str = "+{0} more";
+pub const SAY_TOO_MANY : str = "{0} states is more than this page draws ({1}); the table lists every transition.";
+pub const SAY_INPUT_KEY : str = "Input pattern, left to right";
+pub const SAY_TABLE_HEAD : [5]str = ["from", "inputs", "to", "outputs", "reads as"];
+pub const SAY_TABLE_SHOW : str = "Show all {0} transitions";
+pub const SAY_ANY : str = "any input";
+pub const SAY_SVG_BUTTON : str = "Save diagram SVG";
+pub const SAY_SVG_FILE : str = "fsm-{0}.svg";
+pub const SAY_HONEST : str = "This is what yosys extracted, not what your hardware does: a register yosys did not take is still a state machine on the board. yosys calls the states s0 to sN and keeps your codes until fsm_recode re-encodes them; re-encoding changes the flip-flops, not this graph.";
+
+; The card.
+pub const SAY_CARD_BRAND : str = "TRINITY S3AI / FSM SKETCH";
+pub const SAY_CARD_TITLE : str = "The FSM yosys {0} extracted";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/fsm-sketch";
+pub const SAY_CARD_REFUSED : str = "{0}: refused";
+pub const SAY_CARD_SILENT : str = "{0} with plain opt: nothing found, nothing said";
+
+; --- Tests -----------------------------------------------------------------------------------------
+test the_uart_rx_sample_is_four_states_and_fourteen_rows {
+ assert K_SAMPLE_IDS[0] == "uart-rx";
+ assert K_SAMPLE_FOUND[0] == 1;
+ assert K_SAMPLE_STATES[0] == 4;
+ assert K_SAMPLE_TRANSITIONS[0] == 14;
+ assert K_SAMPLE_INPUTS[0] == 4;
+ assert K_CARD_SAMPLE == 0;
+}
+
+test plain_opt_on_the_same_file_finds_nothing_and_names_nothing {
+ assert K_SAMPLE_DESIGNS[0] == "fpga/rtl/uart_rx.v";
+ assert K_SAMPLE_DESIGNS[3] == "fpga/rtl/uart_rx.v";
+ assert K_SAMPLE_OPTS[0] == "opt -nodffe -nosdff";
+ assert K_SAMPLE_OPTS[3] == "opt";
+ assert K_SAMPLE_FOUND[3] == 0;
+ assert K_SAMPLE_REJECTED[3] == 0;
+ assert K_SAMPLE_STATES[3] == 0;
+}
+
+test the_traffic_light_is_named_and_refused {
+ assert K_SAMPLE_TOPS[2] == "fsm_simple";
+ assert K_SAMPLE_FOUND[2] == 0;
+ assert K_SAMPLE_REJECTED[2] == 1;
+ assert K_SAMPLE_REASON == "Register has an initialization value.";
+}
+
+test two_of_four_runs_extract_an_fsm {
+ assert K_SAMPLE_FOUND[0] + K_SAMPLE_FOUND[1] + K_SAMPLE_FOUND[2] + K_SAMPLE_FOUND[3] == 2;
+ assert K_SAMPLE_STATES[1] == 7;
+ assert K_SAMPLE_TRANSITIONS[1] == 23;
+ assert K_SAMPLE_TRANSITIONS[1] > K_SAMPLE_STATES[1];
+}
+
+test the_card_and_the_page {
+ assert K_CARD_W == 1200;
+ assert K_CARD_H == 630;
+ assert K_CARD_GRAPH_W < K_CARD_W;
+ assert K_MIN_TARGET_PX == 32;
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/fsm-sketch/tool.js (361 lines of hand-written JavaScript). The
+; yosys log and KISS2 readers and the layout of the state diagram are NOT rewritten here: they are
+; imported from fsm_sketch_logic.t27 (the port of fsmparse.js, differential-tested). The words above are
+; unchanged and are the only words the view prints.
+; Host protocol (jtag_verdict.t27's plus, all optional, the host calls what the wasm exports):
+; w_init(hash) the page opens
+; w_base(bytes) the URL of the folder the page is served from, with a closing slash: the links
+; to the sample files are written absolute, as tool.js's new URL(..) writes them
+; w_theme(bytes) the nine colours of the shared frame, --w-bg --w-panel --w-line --w-green --w-gold
+; --w-red --w-text --w-muted --w-subtle, one per line, as getComputedStyle gives
+; them (#888 where a variable is empty): they are written into the SVG so that a
+; saved copy stands alone
+; w_size(px) the width of the diagram box, 0 when it is not laid out; the host calls it after
+; every draw and whenever the box is resized (tool.js's ResizeObserver)
+; w_data(i, bytes) file i of K_DATA_FILES has been fetched: 0 sample.json, 1 to 4 the yosys logs of
+; the samples, in the order of K_SAMPLE_IDS; w_data_failed(i) when it could not be
+; w_input(i, bytes) control i changed (the input and textarea elements in page order: 0 the file
+; input, 1 the text area); only the text area is read, when "Draw it" is pressed
+; w_click(i) button i was pressed (index among the page's buttons)
+; w_drag(kind) 0 dragover, 1 dragleave, 2 drop on the file box; 1 when the box changed
+; w_file(name, size, bytes, st) a file was picked or dropped; st 0 read, 1 read failed
+; w_value(i, dst) -> n the text control i must now hold (Clear), or 4294967295 for "leave it"
+; w_effect(dst) -> n an action for the host: byte 1 then a CSS selector, a newline and a file name means
+; "save the SVG that selector finds, serialised, under that name"; else a CSS
+; selector to scroll into view; 0 for none
+; w_render(dst) -> len the page's HTML (0 on overflow); the host draws again while
+; w_libm_need(dst) -> n says n libm calls were missing in that pass (the wasm has no libm): n records of
+; three f64, op (0 sin 1 cos 2 atan2 3 log2 4 log10 5 pow), a, b; the host computes
+; them with Math.* and answers
+; w_libm_give(src, n) with the n results, in order. The pass is repeated until none is missing.
+; Why the libm service: Math.sin, Math.cos and Math.atan2 of V8 differ in the last bit from every portable
+; libm (fsm_sketch_logic.t27, "Floating point"); the page has to print what the browser's own functions give.
+; --
+; The four module variables of the logic (V, M, NUMFLAG, PEND) are declared here: t27c does not splice a
+; `pub var` across a `use` (measured), and the imported functions name them.
+; --
+; What is NOT carried over from tool.js (also reported in the commit message):
+; * texts over 262144 bytes are not read (the page's own limit is K_MAX_CHARS = 4000000); a text between the
+; two shows the "too big" line with 262144 as the limit; a log with more than 60000 events, 16384
+; transition rows, 256 FSMs or 1024 states is not read either (the same line);
+; * non-ASCII is not mapped by trim or by \\s in the readers (see fsm_sketch_logic.t27, "Domain");
+; * the width of the diagram is the host's last measure: the first draw of a result uses K_DIAGRAM_MIN_W and
+; the host's size message then draws it again at the measured width (tool.js measures first);
+; * a sample that is still on its way when its button is pressed is shown when it arrives (tool.js fetches
+; on the press); the last press wins.
+; --
+
+pub const K_DATA_FILES : [5]str = ["./sample.json", "./samples/uart-rx.yosys.txt", "./samples/spi-flash.yosys.txt", "./samples/fsm-simple.yosys.txt", "./samples/uart-rx-plain-opt.yosys.txt"];
+
+pub var V : [8]f64 = [0.0; 8];
+pub var M : [8]usize = [0; 8];
+pub var NUMFLAG : u32 = 0;
+pub var PEND : [2]usize = [0; 2];
+
+pub const W_NO : u32 = 4294967295;
+pub const W_TEXT_CAP : u32 = 262144;
+pub const W_MAX_ROWS : u32 = 16384;
+pub const W_MAX_STATES : u32 = 1024;
+pub const W_MAX_FSMS : u32 = 256;
+
+// raw data
+pub var SJ : [65536]u8 = [0; 65536];
+pub var SJ_LEN : u32 = 0;
+pub var SLOG : [524288]u8 = [0; 524288];
+pub var SL_LEN : [4]u32 = [0; 4];
+pub var D_STATE : [5]u8 = [0; 5];
+pub var PEND_SAMPLE : i32 = -1;
+pub var PASTE : [262145]u8 = [0; 262145];
+pub var PASTE_LEN : u32 = 0;
+pub var PASTE_PUSH : bool = false;
+pub var BASE : [512]u8 = [0; 512];
+pub var BASE_LEN : u32 = 0;
+pub var THEME : [288]u8 = [0; 288];
+pub var THEME_L : [9]u32 = [0; 9];
+pub var WPX : f64 = 0.0;
+
+// the reader's work arrays and the two copies of a result: the one being parsed, and the one on show
+pub var PTEXT : [262144]u8 = [0; 262144];
+pub var PTEXT_LEN : u32 = 0;
+pub var LS : [524290]u32 = [0; 524290];
+pub var WORK : [600000]u32 = [0; 600000];
+pub var TMP : [262144]u32 = [0; 262144];
+pub var POUT : [262144]u32 = [0; 262144];
+pub var PNUMS : [1024]f64 = [0.0; 1024];
+pub var PRES : [32]u32 = [0; 32];
+pub var LTEXT : [262144]u8 = [0; 262144];
+pub var LTEXT_LEN : u32 = 0;
+pub var LOUT : [262144]u32 = [0; 262144];
+pub var LNUMS : [1024]f64 = [0.0; 1024];
+pub var LRES : [32]u32 = [0; 32];
+pub var LIVE : bool = false;
+
+// what the page shows besides the result
+pub var PICK : u32 = 0;
+pub var SHOW_ALL : bool = false;
+pub var LABEL : [512]u8 = [0; 512];
+pub var LABEL_LEN : u32 = 0;
+pub var SHOWN : i32 = -1;
+pub var NAMES_ON : bool = false;
+pub var NM : [16384]u8 = [0; 16384];
+pub var NM_O : [256]u32 = [0; 256];
+pub var NM_L : [256]u32 = [0; 256];
+pub var NM_N : u32 = 0;
+pub var SHOW_KISS2 : bool = false;
+pub var LINK_I : i32 = -1;
+pub var LINK_KISS2 : bool = false;
+pub var LINK_NAMES : bool = false;
+pub var LINK_NAMES_N : u32 = 0;
+pub var ST_KIND : u32 = 0;
+pub var ST_LABEL : [512]u8 = [0; 512];
+pub var ST_LABEL_LEN : u32 = 0;
+pub var ST_A : u64 = 0;
+pub var ST_B : u64 = 0;
+pub var ST_LINE : [512]u8 = [0; 512];
+pub var ST_LINE_LEN : u32 = 0;
+pub var FSMV_HIDDEN : bool = true;
+pub var PICKER_HIDDEN : bool = false;
+pub var SVGBTN_HIDDEN : bool = false;
+pub var LEGEND_HIDDEN : bool = false;
+pub var STATE_W : f64 = 0.0;
+pub var DROP_OVER : bool = false;
+pub var EFFECT_SAVE : bool = false;
+pub var EFFECT_NAME : [256]u8 = [0; 256];
+pub var EFFECT_NAME_LEN : u32 = 0;
+
+// the parts of the result view that outlive a refusal (tool.js hides the section, it does not empty it)
+pub var C_PICKER : [65536]u8 = [0; 65536];
+pub var C_PICKER_LEN : u32 = 0;
+pub var C_STATS : [8192]u8 = [0; 8192];
+pub var C_STATS_LEN : u32 = 0;
+pub var C_DIAGRAM : [524288]u8 = [0; 524288];
+pub var C_DIAGRAM_LEN : u32 = 0;
+pub var C_KEY : [65536]u8 = [0; 65536];
+pub var C_KEY_LEN : u32 = 0;
+pub var C_TABLE : [786432]u8 = [0; 786432];
+pub var C_TABLE_LEN : u32 = 0;
+
+// layout arrays (one diagram: at most 40 states)
+pub var RF : [16384]u32 = [0; 16384];
+pub var RT : [16384]u32 = [0; 16384];
+pub var RIL : [16384]u32 = [0; 16384];
+pub var LN : [64]u32 = [0; 64];
+pub var LM : [64]u32 = [0; 64];
+pub var NODES : [256]f64 = [0.0; 256];
+pub var EF : [24000]f64 = [0.0; 24000];
+pub var EU : [18480]u32 = [0; 18480];
+pub var ORDER : [64]u32 = [0; 64];
+pub var LWORK : [100000]u32 = [0; 100000];
+pub var FW : [140000]f64 = [0.0; 140000];
+pub var TRIG : [128]f64 = [0.0; 128];
+
+// the libm service
+pub var LB_OP : [4096]u8 = [0; 4096];
+pub var LB_A : [4096]f64 = [0.0; 4096];
+pub var LB_B : [4096]f64 = [0.0; 4096];
+pub var LB_R : [4096]f64 = [0.0; 4096];
+pub var LB_N : u32 = 0;
+pub var RQ_OP : [4096]u8 = [0; 4096];
+pub var RQ_A : [4096]f64 = [0.0; 4096];
+pub var RQ_B : [4096]f64 = [0.0; 4096];
+pub var RQ_N : u32 = 0;
+pub var RQ_SENT : u32 = 0;
+pub var MISSING : bool = false;
+
+pub var VA : [128]u8 = [0; 128];
+pub var VB : [128]u8 = [0; 128];
+pub var VC : [128]u8 = [0; 128];
+pub var VD : [128]u8 = [0; 128];
+pub var TMPB : [1024]u8 = [0; 1024];
+
+// ------------------------------------------------------------------------------------------------
+// numbers, the libm service
+// ------------------------------------------------------------------------------------------------
+
+pub var SM : u64 = 0;
+pub var SK : u32 = 0;
+
+fn pow10f(k: u32) -> f64 {
+ var p : f64 = 1.0;
+ var i : u32 = 0;
+ while (i < k) {
+ p = p * 10.0;
+ i = i + 1;
+ }
+ return p;
+}
+
+/// The shortest decimal that reads back as v (v >= 0, finite, below 2^53 and not a whole number): SM digits,
+/// SK of them after the point. A decimal m / 10^k with m below 2^53 and k up to 22 is one correctly rounded
+/// division, so m / 10^k == v says the decimal parses back to v. False when no such decimal is found.
+fn shortest_dec(v: f64) -> bool {
+ var k : u32 = 0;
+ while (k <= 20) {
+ const p : f64 = pow10f(k);
+ const m : f64 = jsround(v * p);
+ if (m >= 9007199254740992.0) { return false; }
+ if (m / p == v) {
+ SM = m as u64;
+ SK = k;
+ return true;
+ }
+ k = k + 1;
+ }
+ return false;
+}
+
+fn is_neg(v: f64) -> bool {
+ return v < 0.0 or neg_zero(v);
+}
+
+/// String(v) for the numbers a log or a header gives: NaN, Infinity, whole numbers, and decimals with at
+/// most 15 significant digits. Beyond that (1e21 and up, below 1e-6) it writes the digits it has.
+fn js_string_num(o: []u8, n0: u32, v0: f64) -> u32 {
+ var n : u32 = n0;
+ if (v0 != v0) { return raw(o, n, "NaN"); }
+ if (v0 == inf()) { return raw(o, n, "Infinity"); }
+ if (v0 == 0.0 - inf()) { return raw(o, n, "-Infinity"); }
+ var v : f64 = v0;
+ if (v < 0.0) {
+ n = put(o, n, 45);
+ v = 0.0 - v0;
+ }
+ if (v == @floor(v) and v < 9007199254740992.0) { return num(o, n, v as u64); }
+ if (shortest_dec(v)) {
+ var tmp : [24]u8 = [0; 24];
+ const len : u32 = dec_to(tmp[0..], SM);
+ var digits : u32 = len;
+ var lead : u32 = 0;
+ if (digits <= SK) { lead = SK - digits + 1; }
+ // digits of m, padded on the left to SK + 1 digits
+ var total : u32 = digits + lead;
+ var i : u32 = 0;
+ while (i < total) {
+ if (i == total - SK) { n = put(o, n, 46); }
+ if (i < lead) { n = put(o, n, 48); } else { n = put(o, n, tmp[(i - lead) as usize]); }
+ i = i + 1;
+ }
+ return n;
+ }
+ return num(o, n, v as u64);
+}
+
+/// Number(v).toLocaleString('en-US'): groups of three, at most three decimals (rounded half away from zero on
+/// the shortest decimal), "-0" for a negative zero, "NaN" and the infinity sign (U+221E).
+fn js_locale_num(o: []u8, n0: u32, v0: f64) -> u32 {
+ var n : u32 = n0;
+ if (v0 != v0) { return raw(o, n, "NaN"); }
+ var v : f64 = v0;
+ if (is_neg(v0)) {
+ n = put(o, n, 45);
+ v = 0.0 - v0;
+ if (v0 == 0.0) { v = 0.0; }
+ }
+ if (v == inf()) {
+ n = put(o, n, 226);
+ n = put(o, n, 136);
+ return put(o, n, 158);
+ }
+ if (v == @floor(v) and v < 9007199254740992.0) { return num_grouped(o, n, v as u64); }
+ var m : u64 = 0;
+ var k : u32 = 0;
+ if (v < 9007199254740992.0 and shortest_dec(v)) {
+ m = SM;
+ k = SK;
+ } else {
+ return num_grouped(o, n, v as u64);
+ }
+ while (k > 3) {
+ const q : u64 = m / 10;
+ const r : u64 = m % 10;
+ m = q;
+ if (k == 4 and r >= 5) { m = m + 1; }
+ k = k - 1;
+ }
+ while (k > 0 and m % 10 == 0) {
+ m = m / 10;
+ k = k - 1;
+ }
+ var p : u64 = 1;
+ var i : u32 = 0;
+ while (i < k) {
+ p = p * 10;
+ i = i + 1;
+ }
+ n = num_grouped(o, n, m / p);
+ if (k > 0) {
+ n = put(o, n, 46);
+ var f : u64 = m % p;
+ var d : u32 = 0;
+ var div : u64 = p / 10;
+ while (d < k) {
+ n = put(o, n, ((f / div) as u8) + 48);
+ f = f % div;
+ if (div > 1) { div = div / 10; }
+ d = d + 1;
+ }
+ }
+ return n;
+}
+
+/// Math.round(v * 10) / 10 written as String() writes it.
+fn put_r1(o: []u8, n0: u32, v: f64) -> u32 {
+ var n : u32 = n0;
+ if (v != v) { return raw(o, n, "NaN"); }
+ if (v == inf()) { return raw(o, n, "Infinity"); }
+ if (v == 0.0 - inf()) { return raw(o, n, "-Infinity"); }
+ const k : f64 = jsround(v * 10.0);
+ if (k == 0.0) { return put(o, n, 48); }
+ var a : f64 = k;
+ if (k < 0.0) {
+ n = put(o, n, 45);
+ a = 0.0 - k;
+ }
+ if (a >= 9007199254740992.0) { return js_string_num(o, n, k / 10.0); }
+ const ai : u64 = a as u64;
+ n = num(o, n, ai / 10);
+ if (ai % 10 != 0) {
+ n = put(o, n, 46);
+ n = put(o, n, ((ai % 10) as u8) + 48);
+ }
+ return n;
+}
+
+fn same_f64(a: f64, b: f64) -> bool {
+ if (a != a) { return b != b; }
+ if (a == b) {
+ if (a == 0.0) { return neg_zero(a) == neg_zero(b); }
+ return true;
+ }
+ return false;
+}
+
+/// A libm call: the answer when the host has given it, else 0.0 and the call is queued for the host.
+fn libm(op: u32, a: f64, b: f64) -> f64 {
+ var i : u32 = 0;
+ while (i < LB_N) {
+ if ((LB_OP[i as usize] as u32) == op and same_f64(LB_A[i as usize], a) and same_f64(LB_B[i as usize], b)) { return LB_R[i as usize]; }
+ i = i + 1;
+ }
+ MISSING = true;
+ var j : u32 = 0;
+ while (j < RQ_N) {
+ if ((RQ_OP[j as usize] as u32) == op and same_f64(RQ_A[j as usize], a) and same_f64(RQ_B[j as usize], b)) { return 0.0; }
+ j = j + 1;
+ }
+ if (RQ_N < 4096) {
+ RQ_OP[RQ_N as usize] = op as u8;
+ RQ_A[RQ_N as usize] = a;
+ RQ_B[RQ_N as usize] = b;
+ RQ_N = RQ_N + 1;
+ }
+ return 0.0;
+}
+
+pub fn w_libm_need(dst: []f64) -> u32 {
+ if (RQ_N == 0) { return 0; }
+ var k : u32 = 0;
+ while (k < RQ_N and ((k * 3 + 2) as usize) < dst.len) {
+ dst[(k * 3) as usize] = RQ_OP[k as usize] as f64;
+ dst[(k * 3 + 1) as usize] = RQ_A[k as usize];
+ dst[(k * 3 + 2) as usize] = RQ_B[k as usize];
+ k = k + 1;
+ }
+ RQ_SENT = k;
+ return k;
+}
+
+pub fn w_libm_give(src: []const f64, n: u32) -> u32 {
+ var k : u32 = 0;
+ while (k < n and k < RQ_SENT and (k as usize) < src.len) {
+ if (LB_N >= 4096) { LB_N = 0; }
+ LB_OP[LB_N as usize] = RQ_OP[k as usize];
+ LB_A[LB_N as usize] = RQ_A[k as usize];
+ LB_B[LB_N as usize] = RQ_B[k as usize];
+ LB_R[LB_N as usize] = src[k as usize];
+ LB_N = LB_N + 1;
+ k = k + 1;
+ }
+ RQ_N = 0;
+ RQ_SENT = 0;
+ MISSING = false;
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// small helpers
+// ------------------------------------------------------------------------------------------------
+
+fn copy_to(dst: []u8, src: []const u8, cap: u32) -> u32 {
+ var n : u32 = src.len as u32;
+ if (n > cap) { n = cap; }
+ var k : u32 = 0;
+ while (k < n) {
+ dst[k as usize] = src[k as usize];
+ k = k + 1;
+ }
+ return n;
+}
+
+/// fmt(): each {N} is replaced by the N-th value, '' when there is none; values are escaped as text.
+fn fmt4(o: []u8, n0: u32, t: []const u8, va: []const u8, vb: []const u8, vc: []const u8, vd: []const u8, k: u32) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ var j : usize = i + 1;
+ if (t[i] == 123) {
+ while (j < t.len and is_digit(t[j])) { j = j + 1; }
+ }
+ if (t[i] == 123 and j > i + 1 and j < t.len and t[j] == 125) {
+ var idx : u32 = 0;
+ var q : usize = i + 1;
+ while (q < j) {
+ if (idx < 1000) { idx = idx * 10 + ((t[q] - 48) as u32); }
+ q = q + 1;
+ }
+ if (idx < k) {
+ if (idx == 0) { n = text(o, n, va); }
+ if (idx == 1) { n = text(o, n, vb); }
+ if (idx == 2) { n = text(o, n, vc); }
+ if (idx == 3) { n = text(o, n, vd); }
+ }
+ i = j + 1;
+ } else {
+ n = put_escaped(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+/// The length of a UTF-8 string in UTF-16 units.
+fn utf16_len(s: []const u8) -> u32 {
+ var n : u32 = 0;
+ var i : usize = 0;
+ while (i < s.len) {
+ const b : u8 = s[i];
+ if (b >= 240) { n = n + 2; } else { if (b < 128 or b >= 192) { n = n + 1; } }
+ i = i + 1;
+ }
+ return n;
+}
+
+fn low_b(b: u8) -> u8 {
+ if (b >= 65 and b <= 90) { return b + 32; }
+ return b;
+}
+
+fn same_span(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var k : usize = 0;
+ while (k < a.len) {
+ if (a[k] != b[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn text_is(t: []const u8, lo: u32, len: u32, w: []const u8) -> bool {
+ if (len != (w.len as u32)) { return false; }
+ var k : u32 = 0;
+ while (k < len) {
+ if (t[(lo + k) as usize] != w[k as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// sample.json: just what the page reads (samples[].id, .kiss2, .names). json_scan.t27 has skip_ws, which
+// fsm_sketch_logic.t27 has too (different arguments): one module cannot import both, so this is a small
+// reader of its own, over the bytes of SJ.
+// ------------------------------------------------------------------------------------------------
+
+fn sj_ws(p0: usize) -> usize {
+ var p : usize = p0;
+ while (p < (SJ_LEN as usize) and (SJ[p] == 32 or SJ[p] == 9 or SJ[p] == 10 or SJ[p] == 13)) { p = p + 1; }
+ return p;
+}
+
+fn sj_str_end(p: usize) -> usize {
+ var q : usize = p + 1;
+ while (q < (SJ_LEN as usize)) {
+ if (SJ[q] == 92) { q = q + 2; continue; }
+ if (SJ[q] == 34) { return q + 1; }
+ q = q + 1;
+ }
+ return (SJ_LEN as usize) + 1;
+}
+
+fn sj_val_end(p: usize) -> usize {
+ if (p >= (SJ_LEN as usize)) { return (SJ_LEN as usize) + 1; }
+ const c : u8 = SJ[p];
+ if (c == 34) { return sj_str_end(p); }
+ if (c == 123 or c == 91) {
+ var depth : u32 = 0;
+ var q : usize = p;
+ while (q < (SJ_LEN as usize)) {
+ const b : u8 = SJ[q];
+ if (b == 34) {
+ q = sj_str_end(q);
+ continue;
+ }
+ if (b == 123 or b == 91) { depth = depth + 1; }
+ if (b == 125 or b == 93) {
+ depth = depth - 1;
+ if (depth == 0) { return q + 1; }
+ }
+ q = q + 1;
+ }
+ return (SJ_LEN as usize) + 1;
+ }
+ var q : usize = p;
+ while (q < (SJ_LEN as usize) and SJ[q] != 44 and SJ[q] != 125 and SJ[q] != 93 and SJ[q] != 32 and SJ[q] != 9 and SJ[q] != 10 and SJ[q] != 13) { q = q + 1; }
+ return q;
+}
+
+/// The one value of the document, or -1; the document must be nothing else and start like JSON.
+fn sj_root() -> i64 {
+ const a : usize = sj_ws(0);
+ if (a >= (SJ_LEN as usize)) { return -1; }
+ const e : usize = sj_val_end(a);
+ if (e > (SJ_LEN as usize)) { return -1; }
+ if (sj_ws(e) < (SJ_LEN as usize)) { return -1; }
+ const c : u8 = SJ[a];
+ if (c == 123 or c == 91 or c == 34 or c == 45 or is_digit(c)) { return pack(a, e); }
+ if (c == 116 and span_is_sj(a, e, "true")) { return pack(a, e); }
+ if (c == 102 and span_is_sj(a, e, "false")) { return pack(a, e); }
+ if (c == 110 and span_is_sj(a, e, "null")) { return pack(a, e); }
+ return -1;
+}
+
+fn span_is_sj(lo: usize, hi: usize, w: []const u8) -> bool {
+ if (hi - lo != w.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (SJ[lo + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// 0 none, 1 object, 2 array, 3 string, 4 other.
+fn sj_kind(sp: i64) -> u32 {
+ if (sp < 0) { return 0; }
+ const c : u8 = SJ[lo_of(sp)];
+ if (c == 123) { return 1; }
+ if (c == 91) { return 2; }
+ if (c == 34) { return 3; }
+ return 4;
+}
+
+fn sj_get(sp: i64, key: []const u8) -> i64 {
+ if (sj_kind(sp) != 1) { return -1; }
+ const hi : usize = hi_of(sp);
+ var p : usize = sj_ws(lo_of(sp) + 1);
+ while (p < hi and SJ[p] == 34) {
+ const ke : usize = sj_str_end(p);
+ if (ke > hi) { return -1; }
+ const q : usize = sj_ws(ke);
+ if (q >= hi or SJ[q] != 58) { return -1; }
+ const v : usize = sj_ws(q + 1);
+ const ve : usize = sj_val_end(v);
+ if (ve > hi) { return -1; }
+ if (ke - p == key.len + 2 and span_is_sj(p + 1, ke - 1, key)) { return pack(v, ve); }
+ p = sj_ws(ve);
+ if (p < hi and SJ[p] == 44) { p = sj_ws(p + 1); }
+ }
+ return -1;
+}
+
+fn sj_len(sp: i64) -> u32 {
+ if (sj_kind(sp) != 2) { return 0; }
+ const hi : usize = hi_of(sp);
+ var p : usize = sj_ws(lo_of(sp) + 1);
+ var n : u32 = 0;
+ while (p + 1 < hi) {
+ const e : usize = sj_val_end(p);
+ if (e > hi) { return n; }
+ n = n + 1;
+ p = sj_ws(e);
+ if (p < hi and SJ[p] == 44) { p = sj_ws(p + 1); }
+ }
+ return n;
+}
+
+fn sj_at(sp: i64, i: u32) -> i64 {
+ if (sj_kind(sp) != 2) { return -1; }
+ const hi : usize = hi_of(sp);
+ var p : usize = sj_ws(lo_of(sp) + 1);
+ var n : u32 = 0;
+ while (p + 1 < hi) {
+ const e : usize = sj_val_end(p);
+ if (e > hi) { return -1; }
+ if (n == i) { return pack(p, e); }
+ n = n + 1;
+ p = sj_ws(e);
+ if (p < hi and SJ[p] == 44) { p = sj_ws(p + 1); }
+ }
+ return -1;
+}
+
+/// The JSON string at sp, decoded to UTF-8 and appended to dst at `at`; the new length.
+fn sj_dec(sp: i64, dst: []u8, at: u32) -> u32 {
+ if (sj_kind(sp) != 3) { return at; }
+ const hi : usize = hi_of(sp) - 1;
+ var p : usize = lo_of(sp) + 1;
+ var n : u32 = at;
+ while (p < hi) {
+ var cp : u32 = SJ[p] as u32;
+ var adv : usize = 1;
+ if (SJ[p] == 92 and p + 1 < hi) {
+ const e : u32 = sj_escape_at(SJ[0..(SJ_LEN as usize)], p + 1, hi);
+ cp = e / 16;
+ adv = 1 + ((e % 16) as usize);
+ }
+ if (adv == 1 and SJ[p] != 92 and SJ[p] >= 128) {
+ if ((n as usize) < dst.len) { dst[n as usize] = SJ[p]; n = n + 1; }
+ p = p + 1;
+ continue;
+ }
+ const w : u32 = sj_cp_len(cp);
+ var j : u32 = 0;
+ while (j < w) {
+ if ((n as usize) < dst.len) { dst[n as usize] = sj_cp_byte(cp, j); n = n + 1; }
+ j = j + 1;
+ }
+ p = p + adv;
+ }
+ return n;
+}
+
+fn sj_str_is(sp: i64, w: []const u8) -> bool {
+ if (sj_kind(sp) != 3) { return false; }
+ var tmp : [128]u8 = [0; 128];
+ const n : u32 = sj_dec(sp, tmp[0..], 0);
+ return text_is(tmp[0..], 0, n, w);
+}
+
+/// (copies of json_scan.t27: hex4, escape_at, cp_utf8_len, cp_utf8_byte; that module cannot be used, see above)
+fn sj_hex4(s: []const u8, p: usize) -> u32 {
+ var v : u32 = 0;
+ var i : usize = 0;
+ while (i < 4) {
+ const b : u8 = s[p + i];
+ var d : u32 = 0;
+ if (b >= 48 and b <= 57) { d = (b - 48) as u32; }
+ if (b >= 97 and b <= 102) { d = (b - 97) as u32 + 10; }
+ if (b >= 65 and b <= 70) { d = (b - 65) as u32 + 10; }
+ v = v * 16 + d;
+ i = i + 1;
+ }
+ return v;
+}
+
+/// The code point (and its source length) at s[p ..], p just after a backslash: returns cp * 16 + srclen.
+fn sj_escape_at(s: []const u8, p: usize, hi: usize) -> u32 {
+ const c : u8 = s[p];
+ if (c == 110) { return 10 * 16 + 1; }
+ if (c == 116) { return 9 * 16 + 1; }
+ if (c == 114) { return 13 * 16 + 1; }
+ if (c == 98) { return 8 * 16 + 1; }
+ if (c == 102) { return 12 * 16 + 1; }
+ if (c == 117 and p + 4 < hi) {
+ const u : u32 = sj_hex4(s, p + 1);
+ if (u >= 55296 and u <= 56319 and p + 10 < hi and s[p + 5] == 92 and s[p + 6] == 117) {
+ const lo : u32 = sj_hex4(s, p + 7);
+ if (lo >= 56320 and lo <= 57343) {
+ const cp : u32 = 65536 + (u - 55296) * 1024 + (lo - 56320);
+ return cp * 16 + 11;
+ }
+ }
+ return u * 16 + 5;
+ }
+ return (c as u32) * 16 + 1;
+}
+
+fn sj_cp_len(cp: u32) -> u32 {
+ if (cp < 128) { return 1; }
+ if (cp < 2048) { return 2; }
+ if (cp < 65536) { return 3; }
+ return 4;
+}
+
+fn sj_cp_byte(cp: u32, j: u32) -> u8 {
+ if (cp < 128) { return cp as u8; }
+ if (cp < 2048) {
+ if (j == 0) { return (192 + cp / 64) as u8; }
+ return (128 + cp % 64) as u8;
+ }
+ if (cp < 65536) {
+ if (j == 0) { return (224 + cp / 4096) as u8; }
+ if (j == 1) { return (128 + (cp / 64) % 64) as u8; }
+ return (128 + cp % 64) as u8;
+ }
+ if (j == 0) { return (240 + cp / 262144) as u8; }
+ if (j == 1) { return (128 + (cp / 4096) % 64) as u8; }
+ if (j == 2) { return (128 + (cp / 64) % 64) as u8; }
+ return (128 + cp % 64) as u8;
+}
+
+/// The length in bytes of the string at sp once its escapes are decoded to UTF-8.
+
+/// Is the JSON value at sp truthy the way JavaScript says it: not null, false, 0, "" or absent.
+fn sj_truthy(sp: i64) -> bool {
+ if (sp < 0) { return false; }
+ const lo : usize = lo_of(sp);
+ const hi : usize = hi_of(sp);
+ const c : u8 = SJ[lo];
+ if (c == 110 or c == 102) { return false; }
+ if (c == 34) { return hi - lo > 2; }
+ if (c == 123 or c == 91 or c == 116) { return true; }
+ return !(hi - lo == 1 and SJ[lo] == 48);
+}
+
+// ------------------------------------------------------------------------------------------------
+// the result of a read: where the FSMs sit in the result stream (see fsm_sketch_logic.t27, "the result
+// stream"). Offsets in the stream are offsets in LTEXT.
+// ------------------------------------------------------------------------------------------------
+
+fn lspan(off: u32, len: u32) -> []const u8 {
+ return LTEXT[(off as usize)..((off + len) as usize)];
+}
+
+/// Where the header of FSM k starts in LOUT.
+fn fsm_base(k: u32) -> u32 {
+ var p : u32 = LRES[10] * 2;
+ var i : u32 = 0;
+ while (i < LRES[11]) {
+ p = p + 3 + LOUT[(p + 2) as usize] * 2;
+ i = i + 1;
+ }
+ p = p + LRES[12] * 5;
+ i = 0;
+ while (i < k) {
+ p = p + 16 + LOUT[(p + 9) as usize] * 4 + LOUT[(p + 10) as usize] * 6 + LOUT[(p + 12) as usize] * 3 + LOUT[(p + 14) as usize] * 3;
+ i = i + 1;
+ }
+ return p;
+}
+
+pub var CF_P : u32 = 0;
+pub var CF_NS : u32 = 0;
+pub var CF_NR : u32 = 0;
+pub var CF_NIN : f64 = 0.0;
+pub var CF_NOUT : f64 = 0.0;
+pub var FNAME : [1024]u8 = [0; 1024];
+pub var FNAME_LEN : u32 = 0;
+pub var SLB : [65536]u8 = [0; 65536];
+pub var SL_O : [128]u32 = [0; 128];
+pub var SL_L : [128]u32 = [0; 128];
+
+fn select_fsm(k: u32) {
+ CF_P = fsm_base(k);
+ CF_NS = LOUT[(CF_P + 9) as usize];
+ CF_NR = LOUT[(CF_P + 10) as usize];
+ CF_NIN = LNUMS[(1 + 3 * k) as usize];
+ CF_NOUT = LNUMS[(2 + 3 * k) as usize];
+}
+
+fn app_raw(dst: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
+ var k : usize = 0;
+ while (k < s.len) {
+ if ((n as usize) < dst.len) { dst[n as usize] = s[k]; n = n + 1; }
+ k = k + 1;
+ }
+ return n;
+}
+
+/// fsmName(f) as raw bytes into FNAME.
+fn fsm_name_raw(k: u32) {
+ const p : u32 = fsm_base(k);
+ var n : u32 = 0;
+ if (LOUT[p as usize] == W_NO) {
+ n = app_raw(FNAME[0..], 0, LABEL[0..(LABEL_LEN as usize)]);
+ FNAME_LEN = n;
+ return;
+ }
+ if (LOUT[(p + 2) as usize] != W_NO and LOUT[(p + 3) as usize] > 0) {
+ n = app_raw(FNAME[0..], n, lspan(LOUT[(p + 2) as usize], LOUT[(p + 3) as usize]));
+ n = app_raw(FNAME[0..], n, ".");
+ if (LOUT[(p + 4) as usize] != W_NO) { n = app_raw(FNAME[0..], n, lspan(LOUT[(p + 4) as usize], LOUT[(p + 5) as usize])); }
+ FNAME_LEN = n;
+ return;
+ }
+ if (LOUT[(p + 4) as usize] != W_NO) {
+ n = app_raw(FNAME[0..], n, lspan(LOUT[(p + 4) as usize], LOUT[(p + 5) as usize]));
+ } else {
+ n = app_raw(FNAME[0..], n, lspan(LOUT[p as usize], LOUT[(p + 1) as usize]));
+ }
+ FNAME_LEN = n;
+}
+
+/// The name of state i of the current FSM, as raw bytes appended to dst at n.
+fn state_name_raw(dst: []u8, n0: u32, i: u32) -> u32 {
+ const e : u32 = CF_P + 16 + i * 4;
+ var n : u32 = n0;
+ if (LOUT[e as usize] == W_NO) {
+ n = app_raw(dst, n, "s");
+ var t : [16]u8 = [0; 16];
+ const l : u32 = dec_to(t[0..], i as u64);
+ return app_raw(dst, n, t[0..(l as usize)]);
+ }
+ return app_raw(dst, n, lspan(LOUT[e as usize], LOUT[(e + 1) as usize]));
+}
+
+fn state_has_code(i: u32) -> bool {
+ const e : u32 = CF_P + 16 + i * 4;
+ return LOUT[(e + 2) as usize] != W_NO and LOUT[(e + 3) as usize] > 0;
+}
+
+fn state_code(i: u32) -> []const u8 {
+ const e : u32 = CF_P + 16 + i * 4;
+ return lspan(LOUT[(e + 2) as usize], LOUT[(e + 3) as usize]);
+}
+
+fn row_at(r: u32) -> u32 {
+ return CF_P + 16 + CF_NS * 4 + r * 6;
+}
+
+fn row_in(r: u32) -> []const u8 {
+ const e : u32 = row_at(r);
+ return lspan(LOUT[(e + 2) as usize], LOUT[(e + 3) as usize]);
+}
+
+fn row_out(r: u32) -> []const u8 {
+ const e : u32 = row_at(r);
+ return lspan(LOUT[(e + 4) as usize], LOUT[(e + 5) as usize]);
+}
+
+/// inputs?.[i]: 0 when the name is absent (no array, past it, or a hole), else 1 with the text in IN_O, IN_L.
+pub var IN_O : u32 = 0;
+pub var IN_L : u32 = 0;
+
+fn input_name(i: u32) -> bool {
+ const p : u32 = CF_P;
+ if (LOUT[(p + 11) as usize] == W_NO) { return false; }
+ if (i >= LOUT[(p + 11) as usize]) { return false; }
+ const base : u32 = p + 16 + CF_NS * 4 + CF_NR * 6;
+ var e : u32 = 0;
+ while (e < LOUT[(p + 12) as usize]) {
+ if (LOUT[(base + e * 3) as usize] == i) {
+ IN_O = LOUT[(base + e * 3 + 1) as usize];
+ IN_L = LOUT[(base + e * 3 + 2) as usize];
+ return true;
+ }
+ e = e + 1;
+ }
+ return false;
+}
+
+/// The rejected and found counts, the notes, and so on come straight from LRES.
+fn lres_ok() -> bool {
+ return LIVE and LRES[1] == 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+// reading a text
+// ------------------------------------------------------------------------------------------------
+
+pub const W_R_OK : u32 = 0;
+pub const W_R_FAIL : u32 = 1;
+pub const W_R_THROW : u32 = 2;
+pub const W_R_CAP : u32 = 3;
+
+pub var LAST_ERR : u32 = 0;
+pub var PASTE_TRUE : u32 = 0;
+
+/// Parse the text src into the scratch result; on success make it the result on show.
+fn parse_text(src: []const u8, commit: bool) -> u32 {
+ PTEXT_LEN = copy_to(PTEXT[0..], src, W_TEXT_CAP);
+ const st : u32 = fsm_parse(PTEXT[0..(PTEXT_LEN as usize)], 0, LS[0..524290], WORK[0..600000], TMP[0..262144], POUT[0..262144], PNUMS[0..1024], PRES[0..32]);
+ if (st == 6) { return W_R_THROW; }
+ if (st != 0) { return W_R_CAP; }
+ if (PRES[1] == 0) {
+ LAST_ERR = PRES[3];
+ ST_LINE_LEN = 0;
+ if (PRES[3] == E_KISS2ROW) {
+ ST_LINE_LEN = copy_to(ST_LINE[0..], PTEXT[(PRES[4] as usize)..((PRES[4] + PRES[5]) as usize)], 512);
+ }
+ return W_R_FAIL;
+ }
+ if (PRES[9] > W_MAX_FSMS) { return W_R_CAP; }
+ // sizes the page keeps arrays for
+ var k : u32 = 0;
+ var p : u32 = PRES[10] * 2;
+ var i : u32 = 0;
+ while (i < PRES[11]) {
+ p = p + 3 + POUT[(p + 2) as usize] * 2;
+ i = i + 1;
+ }
+ p = p + PRES[12] * 5;
+ while (k < PRES[9]) {
+ if (POUT[(p + 9) as usize] > W_MAX_STATES or POUT[(p + 10) as usize] > W_MAX_ROWS) { return W_R_CAP; }
+ p = p + 16 + POUT[(p + 9) as usize] * 4 + POUT[(p + 10) as usize] * 6 + POUT[(p + 12) as usize] * 3 + POUT[(p + 14) as usize] * 3;
+ k = k + 1;
+ }
+ if (commit) { commit_parse(); }
+ return W_R_OK;
+}
+
+fn commit_parse() {
+ LTEXT_LEN = copy_to(LTEXT[0..], PTEXT[0..(PTEXT_LEN as usize)], W_TEXT_CAP);
+ const words : u32 = PRES[13];
+ var w : u32 = 0;
+ while (w < words and w < 262144) {
+ LOUT[w as usize] = POUT[w as usize];
+ w = w + 1;
+ }
+ var q : u32 = 0;
+ while (q < 1 + 3 * PRES[9] and q < 1024) {
+ LNUMS[q as usize] = PNUMS[q as usize];
+ q = q + 1;
+ }
+ q = 0;
+ while (q < 32) {
+ LRES[q as usize] = PRES[q as usize];
+ q = q + 1;
+ }
+}
+
+pub var DIRTY_ALL : bool = false;
+pub var DIRTY_TABLE : bool = false;
+pub var DIRTY_DIAGRAM : bool = false;
+
+fn refuse_kind(kind_of: u32) {
+ ST_KIND = kind_of;
+ LIVE = false;
+ FSMV_HIDDEN = true;
+ SHOWN = -1;
+ LINK_I = -1;
+}
+
+/// show(r, label, names): the result on show is the one just parsed.
+fn show_result() {
+ LIVE = true;
+ PICK = 0;
+ SHOW_ALL = false;
+ render_fsm();
+}
+
+fn render_fsm() {
+ if (!LIVE or PICK >= LRES[9]) {
+ FSMV_HIDDEN = true;
+ return;
+ }
+ FSMV_HIDDEN = false;
+ DIRTY_ALL = true;
+}
+
+/// readText(text, name) where `name` is already in ST_LABEL; 1 when it threw (tool.js lets the throw out).
+fn read_text(src: []const u8, label: []const u8, len: u32) -> u32 {
+ if (len > K_MAX_CHARS) {
+ refuse_kind(2);
+ ST_A = len as u64;
+ ST_B = K_MAX_CHARS as u64;
+ return 0;
+ }
+ if (len > W_TEXT_CAP) {
+ refuse_kind(2);
+ ST_A = len as u64;
+ ST_B = W_TEXT_CAP as u64;
+ return 0;
+ }
+ const r : u32 = parse_text(src, true);
+ if (r == W_R_THROW) { return 1; }
+ if (r == W_R_CAP) {
+ refuse_kind(2);
+ ST_A = len as u64;
+ ST_B = W_TEXT_CAP as u64;
+ return 0;
+ }
+ if (r == W_R_FAIL) {
+ refuse_kind(5);
+ return 0;
+ }
+ SHOWN = -1;
+ LINK_I = -1;
+ NAMES_ON = false;
+ NM_N = 0;
+ ST_KIND = 1;
+ ST_LABEL_LEN = copy_to(ST_LABEL[0..], label, 512);
+ LABEL_LEN = copy_to(LABEL[0..], label, 512);
+ show_result();
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the samples
+// ------------------------------------------------------------------------------------------------
+
+pub var META : i64 = -1;
+
+fn sample_id_is(sp: i64, k: u32) -> bool {
+ return sj_str_is(sj_get(sp, "id"), K_SAMPLE_IDS[k as usize]);
+}
+
+/// showSample(i): needs sample.json (file 0) and the log (file 1 + i). 0 done, 1 still waiting.
+fn try_sample(i: u32) -> u32 {
+ if (D_STATE[0] == 0) { return 1; }
+ if (D_STATE[0] == 2) { ST_KIND = 4; return 0; }
+ const root : i64 = sj_root();
+ if (root < 0) { ST_KIND = 4; return 0; }
+ const samples : i64 = sj_get(root, "samples");
+ var meta : i64 = -1;
+ if (samples >= 0) {
+ if (sj_kind(samples) != 2) { ST_KIND = 4; return 0; }
+ var j : u32 = 0;
+ const n : u32 = sj_len(samples);
+ while (j < n and meta < 0) {
+ const e : i64 = sj_at(samples, j);
+ if (sample_id_is(e, i)) { meta = e; }
+ j = j + 1;
+ }
+ }
+ if (D_STATE[1 + i] == 0) { return 1; }
+ if (D_STATE[1 + i] == 2) { ST_KIND = 4; return 0; }
+ const base : usize = (i as usize) * 131072;
+ const r : u32 = parse_text(SLOG[base..(base + (SL_LEN[i as usize] as usize))], false);
+ if (r != W_R_OK) { ST_KIND = 4; return 0; }
+ // pressSample(i) and the sample line come before the names are read
+ SHOWN = i as i32;
+ var names_on : bool = false;
+ var kiss2 : bool = false;
+ var cnt : u32 = 0;
+ NM_N = 0;
+ if (meta >= 0) {
+ kiss2 = sj_truthy(sj_get(meta, "kiss2"));
+ const nm : i64 = sj_get(meta, "names");
+ if (nm >= 0) {
+ if (sj_kind(nm) != 2) {
+ // names is there and is no array: .some is not a function, tool.js throws into its catch
+ if (!(sj_kind(nm) == 4 and span_is_sj(lo_of(nm), hi_of(nm), "null"))) {
+ ST_KIND = 4;
+ return 0;
+ }
+ } else {
+ cnt = sj_len(nm);
+ var o : u32 = 0;
+ var q : u32 = 0;
+ while (q < cnt and q < 256) {
+ const el : i64 = sj_at(nm, q);
+ NM_O[q as usize] = o;
+ const e2 : u32 = sj_dec(el, NM[0..16384], o);
+ NM_L[q as usize] = e2 - o;
+ o = e2;
+ if (sj_truthy(el)) { names_on = true; }
+ q = q + 1;
+ }
+ NM_N = cnt;
+ if (cnt > 256) { NM_N = 256; }
+ }
+ }
+ }
+ commit_parse();
+ NAMES_ON = names_on;
+ SHOW_KISS2 = kiss2;
+ LINK_I = i as i32;
+ LINK_KISS2 = kiss2;
+ LINK_NAMES = names_on;
+ LINK_NAMES_N = cnt;
+ ST_KIND = 0;
+ LABEL_LEN = copy_to(LABEL[0..], K_SAMPLE_IDS[i as usize], 512);
+ show_result();
+ return 0;
+}
+
+fn want_sample(i: u32) {
+ PEND_SAMPLE = i as i32;
+ if (try_sample(i) == 0) { PEND_SAMPLE = -1; }
+}
+
+fn after_data() {
+ if (PEND_SAMPLE >= 0) {
+ if (try_sample(PEND_SAMPLE as u32) == 0) { PEND_SAMPLE = -1; }
+ }
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ var k : u32 = 0;
+ while (k < 5) { D_STATE[k as usize] = 0; k = k + 1; }
+ LIVE = false;
+ PICK = 0;
+ SHOW_ALL = false;
+ SHOWN = -1;
+ LINK_I = -1;
+ NAMES_ON = false;
+ ST_KIND = 0;
+ FSMV_HIDDEN = true;
+ PICKER_HIDDEN = false;
+ SVGBTN_HIDDEN = false;
+ LEGEND_HIDDEN = false;
+ C_PICKER_LEN = 0;
+ C_STATS_LEN = 0;
+ C_DIAGRAM_LEN = 0;
+ C_KEY_LEN = 0;
+ C_TABLE_LEN = 0;
+ PICKER_N = 0;
+ TABLE_MORE = false;
+ DIRTY_ALL = false;
+ DIRTY_TABLE = false;
+ DIRTY_DIAGRAM = false;
+ DROP_OVER = false;
+ PASTE_PUSH = false;
+ PASTE_LEN = 0;
+ EFFECT_SAVE = false;
+ STATE_W = 0.0;
+ LB_N = 0;
+ RQ_N = 0;
+ MISSING = false;
+ want_sample(0);
+ return 0;
+}
+
+pub fn w_base(s: []const u8) -> u32 {
+ BASE_LEN = copy_to(BASE[0..], s, 512);
+ return 0;
+}
+
+pub fn w_theme(s: []const u8) -> u32 {
+ var i : usize = 0;
+ var c : u32 = 0;
+ var l : u32 = 0;
+ while (i < s.len and c < 9) {
+ if (s[i] == 10) {
+ THEME_L[c as usize] = l;
+ c = c + 1;
+ l = 0;
+ } else {
+ if (l < 32) { THEME[(c * 32 + l) as usize] = s[i]; l = l + 1; }
+ }
+ i = i + 1;
+ }
+ if (c < 9) { THEME_L[c as usize] = l; }
+ return 0;
+}
+
+/// The diagram box is now px wide (0: not laid out). 1 when the diagram has to be drawn again.
+pub fn w_size(px: f64) -> u32 {
+ WPX = px;
+ if (LIVE and !FSMV_HIDDEN and !SVGBTN_HIDDEN) {
+ if (@abs(px - STATE_W) > 8.0) {
+ DIRTY_DIAGRAM = true;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (i > 4) { return 0; }
+ if (i == 0) {
+ if (s.len > 65536) { D_STATE[0] = 2; } else {
+ SJ_LEN = copy_to(SJ[0..], s, 65536);
+ D_STATE[0] = 1;
+ }
+ } else {
+ if (s.len > 131072) { D_STATE[i as usize] = 2; } else {
+ const base : usize = ((i - 1) as usize) * 131072;
+ var k : usize = 0;
+ while (k < s.len) { SLOG[base + k] = s[k]; k = k + 1; }
+ SL_LEN[(i - 1) as usize] = s.len as u32;
+ D_STATE[i as usize] = 1;
+ }
+ }
+ after_data();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ if (i <= 4) { D_STATE[i as usize] = 2; }
+ after_data();
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// events
+// ------------------------------------------------------------------------------------------------
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ if (i == 1) {
+ PASTE_LEN = copy_to(PASTE[0..], s, W_TEXT_CAP + 1);
+ PASTE_TRUE = s.len as u32;
+ PASTE_PUSH = false;
+ }
+ return 0;
+}
+
+pub fn w_drag(kind_of: u32) -> u32 {
+ const was : bool = DROP_OVER;
+ if (kind_of == 0) { DROP_OVER = true; } else { DROP_OVER = false; }
+ if (was != DROP_OVER) { return 1; }
+ return 0;
+}
+
+pub fn w_value(i: u32, dst: []u8) -> u32 {
+ if (i == 1 and PASTE_PUSH) {
+ PASTE_PUSH = false;
+ return copy_to(dst, PASTE[0..(PASTE_LEN as usize)], dst.len as u32);
+ }
+ return W_NO;
+}
+
+pub fn w_effect(dst: []u8) -> u32 {
+ if (EFFECT_SAVE) {
+ EFFECT_SAVE = false;
+ var n : u32 = 0;
+ dst[0] = 1;
+ n = 1;
+ n = n + copy_to(dst[1..], ".fk-diagram svg", 64);
+ dst[n as usize] = 10;
+ n = n + 1;
+ n = n + copy_to(dst[(n as usize)..], EFFECT_NAME[0..(EFFECT_NAME_LEN as usize)], 256);
+ return n;
+ }
+ return 0;
+}
+
+/// A file picked or dropped: readFile(file).
+pub fn w_file(name: []const u8, size: f64, data: []const u8, st: u32) -> u32 {
+ var pk : [1200]u8 = [0; 1200];
+ var szb : [64]u8 = [0; 64];
+ const sl : u32 = js_locale_num(szb[0..], 0, size);
+ // picked = fmt(SAY_PICKED, file.name, num(file.size)), raw
+ var n : u32 = 0;
+ const t : []const u8 = SAY_PICKED;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] == 48) {
+ n = app_raw(pk[0..], n, name);
+ i = i + 3;
+ } else {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] == 49) {
+ n = app_raw(pk[0..], n, szb[0..(sl as usize)]);
+ i = i + 3;
+ } else {
+ n = app_raw(pk[0..], n, t[i..(i + 1)]);
+ i = i + 1;
+ }
+ }
+ }
+ ST_LABEL_LEN = copy_to(ST_LABEL[0..], name, 512);
+ var fail : bool = false;
+ if (size > (K_MAX_CHARS as f64)) {
+ refuse_kind(2);
+ ST_A = size as u64;
+ ST_B = K_MAX_CHARS as u64;
+ return 0;
+ }
+ if (st != 0) { fail = true; }
+ if (!fail and data.len == 0 and size > (W_TEXT_CAP as f64)) {
+ refuse_kind(2);
+ ST_A = size as u64;
+ ST_B = W_TEXT_CAP as u64;
+ return 0;
+ }
+ if (!fail) {
+ const th : u32 = read_text(data, pk[0..(n as usize)], data.len as u32);
+ if (th == 1) { fail = true; }
+ }
+ if (fail) {
+ refuse_kind(3);
+ ST_LABEL_LEN = copy_to(ST_LABEL[0..], name, 512);
+ }
+ return 0;
+}
+
+fn clear_own() {
+ PASTE_LEN = 0;
+ PASTE_TRUE = 0;
+ PASTE_PUSH = true;
+ ST_KIND = 0;
+ want_sample(0);
+}
+
+pub var PICKER_N : u32 = 0;
+pub var TABLE_MORE : bool = false;
+
+pub fn w_click(i: u32) -> u32 {
+ const ns : u32 = K_SAMPLE_IDS.len as u32;
+ var at : u32 = i;
+ if (at < ns) {
+ want_sample(at);
+ return 0;
+ }
+ at = at - ns;
+ if (at == 0) {
+ // readText(paste.value, SAY_PASTED): a throw leaves everything as it was
+ read_text(PASTE[0..(PASTE_LEN as usize)], SAY_PASTED, PASTE_TRUE);
+ return 0;
+ }
+ if (at == 1) {
+ clear_own();
+ return 0;
+ }
+ at = at - 2;
+ if (at < PICKER_N) {
+ if (LIVE) {
+ PICK = at;
+ SHOW_ALL = false;
+ render_fsm();
+ }
+ return 0;
+ }
+ at = at - PICKER_N;
+ if (at == 0) {
+ if (LIVE and !SVGBTN_HIDDEN and !FSMV_HIDDEN) {
+ fsm_name_raw(PICK);
+ EFFECT_NAME_LEN = save_name(EFFECT_NAME[0..]);
+ EFFECT_SAVE = true;
+ }
+ return 0;
+ }
+ at = at - 1;
+ if (at == 0 and TABLE_MORE and LIVE and !FSMV_HIDDEN) {
+ SHOW_ALL = true;
+ DIRTY_TABLE = true;
+ }
+ return 0;
+}
+
+fn slug_char_ok(b: u8) -> bool {
+ return (b >= 48 and b <= 57) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 95 or b == 46 or b == 45;
+}
+
+/// fmt(SAY_SVG_FILE, slug(fsmName(f))) into dst, raw.
+fn save_name(dst: []u8) -> u32 {
+ var sl : [128]u8 = [0; 128];
+ var n : u32 = 0;
+ var i : u32 = 0;
+ var inrun : bool = false;
+ while (i < FNAME_LEN) {
+ const b : u8 = FNAME[i as usize];
+ var ok_b : bool = slug_char_ok(b);
+ if (b >= 128 and b < 192) { i = i + 1; continue; }
+ if (ok_b) {
+ if (n < 128) { sl[n as usize] = b; n = n + 1; }
+ inrun = false;
+ } else {
+ if (!inrun and n < 128) { sl[n as usize] = 95; n = n + 1; }
+ inrun = true;
+ }
+ i = i + 1;
+ }
+ var lo : u32 = 0;
+ var hi : u32 = n;
+ while (lo < hi and sl[lo as usize] == 95) { lo = lo + 1; }
+ while (hi > lo and sl[(hi - 1) as usize] == 95) { hi = hi - 1; }
+ if (hi - lo > 60) { hi = lo + 60; }
+ var out : u32 = 0;
+ const t : []const u8 = SAY_SVG_FILE;
+ var k : usize = 0;
+ while (k < t.len) {
+ if (t[k] == 123 and k + 2 < t.len and t[k + 2] == 125 and t[k + 1] == 48) {
+ if (hi > lo) {
+ out = app_raw(dst, out, sl[(lo as usize)..(hi as usize)]);
+ } else {
+ out = app_raw(dst, out, ID);
+ }
+ k = k + 3;
+ } else {
+ out = app_raw(dst, out, t[k..(k + 1)]);
+ k = k + 1;
+ }
+ }
+ return out;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the view
+// ------------------------------------------------------------------------------------------------
+
+fn theme_col(o: []u8, n0: u32, i: u32) -> u32 {
+ if (THEME_L[i as usize] == 0) { return raw(o, n0, "#888"); }
+ return text(o, n0, THEME[((i * 32) as usize)..((i * 32 + THEME_L[i as usize]) as usize)]);
+}
+
+fn put_u(o: []u8, n0: u32, v: f64) -> u32 {
+ return js_string_num(o, n0, v);
+}
+
+/// r1(v) as an attribute: ` name="..."`.
+fn a_r1(o: []u8, n0: u32, name: []const u8, v: f64) -> u32 {
+ var n : u32 = raw(o, n0, " ");
+ n = raw(o, n, name);
+ n = raw(o, n, "=\"");
+ n = put_r1(o, n, v);
+ return raw(o, n, "\"");
+}
+
+fn a_col(o: []u8, n0: u32, name: []const u8, col: u32) -> u32 {
+ var n : u32 = raw(o, n0, " ");
+ n = raw(o, n, name);
+ n = raw(o, n, "=\"");
+ n = theme_col(o, n, col);
+ return raw(o, n, "\"");
+}
+
+/// The text of the state box lines: for state i the lines are written to SLB; SL_O/SL_L[2i], [2i+1]; LN/LM.
+fn make_labels(ns: u32) {
+ var at : u32 = 0;
+ var i : u32 = 0;
+ while (i < ns) {
+ var local : bool = false;
+ if (NAMES_ON and i < NM_N and NM_L[i as usize] > 0) { local = true; }
+ var count : u32 = 1;
+ SL_O[(i * 2) as usize] = at;
+ if (local) {
+ at = app_raw(SLB[0..], at, NM[(NM_O[i as usize] as usize)..((NM_O[i as usize] + NM_L[i as usize]) as usize)]);
+ SL_L[(i * 2) as usize] = at - SL_O[(i * 2) as usize];
+ SL_O[(i * 2 + 1) as usize] = at;
+ var tmp : [1100]u8 = [0; 1100];
+ var tn : u32 = state_name_raw(tmp[0..], 0, i);
+ if (state_has_code(i)) {
+ tn = app_raw(tmp[0..], tn, " ");
+ tn = app_raw(tmp[0..], tn, state_code(i));
+ }
+ var tlo : u32 = 0;
+ var thi : u32 = tn;
+ while (tlo < thi and is_ws(tmp[tlo as usize])) { tlo = tlo + 1; }
+ while (thi > tlo and is_ws(tmp[(thi - 1) as usize])) { thi = thi - 1; }
+ at = app_raw(SLB[0..], at, tmp[(tlo as usize)..(thi as usize)]);
+ SL_L[(i * 2 + 1) as usize] = at - SL_O[(i * 2 + 1) as usize];
+ count = 2;
+ } else {
+ at = state_name_raw(SLB[0..], at, i);
+ SL_L[(i * 2) as usize] = at - SL_O[(i * 2) as usize];
+ if (state_has_code(i)) {
+ SL_O[(i * 2 + 1) as usize] = at;
+ at = app_raw(SLB[0..], at, state_code(i));
+ SL_L[(i * 2 + 1) as usize] = at - SL_O[(i * 2 + 1) as usize];
+ count = 2;
+ }
+ }
+ LN[i as usize] = count;
+ var mx : u32 = 0;
+ var c : u32 = 0;
+ while (c < count) {
+ const l : u32 = utf16_len(SLB[(SL_O[(i * 2 + c) as usize] as usize)..((SL_O[(i * 2 + c) as usize] + SL_L[(i * 2 + c) as usize]) as usize)]);
+ if (l > mx) { mx = l; }
+ c = c + 1;
+ }
+ LM[i as usize] = mx;
+ i = i + 1;
+ }
+}
+
+fn slb(i: u32, c: u32) -> []const u8 {
+ return SLB[(SL_O[(i * 2 + c) as usize] as usize)..((SL_O[(i * 2 + c) as usize] + SL_L[(i * 2 + c) as usize]) as usize)];
+}
+
+fn atan2_of(y: f64, x: f64) -> f64 {
+ return libm(2, y, x);
+}
+
+/// svgOf(f, w)
+fn svg_of(o: []u8, n0: u32, k: u32, w: f64) -> u32 {
+ var n : u32 = n0;
+ const ns : u32 = CF_NS;
+ const nr : u32 = CF_NR;
+ const font : f64 = K_FONT_PX as f64;
+ const l : f64 = jsmax(jsmin(w, K_DIAGRAM_MAX_W as f64), K_DIAGRAM_MIN_W as f64);
+ var ratio : f64 = 0.68;
+ if (ns > 5) { ratio = 0.82; }
+ const h : f64 = jsmin(K_DIAGRAM_MAX_H as f64, jsmax(300.0, jsround(l * ratio)));
+ make_labels(ns);
+ var i : u32 = 0;
+ while (i < nr) {
+ const e : u32 = row_at(i);
+ RF[i as usize] = LOUT[e as usize];
+ RT[i as usize] = LOUT[(e + 1) as usize];
+ RIL[i as usize] = utf16_len(row_in(i));
+ i = i + 1;
+ }
+ i = 0;
+ while (i < ns) {
+ const a : f64 = node_angle(i as usize, ns as usize);
+ TRIG[(i * 2) as usize] = libm(1, a, 0.0);
+ TRIG[(i * 2 + 1) as usize] = libm(0, a, 0.0);
+ i = i + 1;
+ }
+ var fx : u32 = 0;
+ while (fx < 4) {
+ var a : f64 = 0.0 - 0.45;
+ if (fx == 1) { a = 0.45; }
+ if (fx == 2) { a = 0.0 - 0.55; }
+ if (fx == 3) { a = 0.55; }
+ TRIG[(ns * 2 + fx * 2) as usize] = libm(1, a, 0.0);
+ TRIG[(ns * 2 + fx * 2 + 1) as usize] = libm(0, a, 0.0);
+ fx = fx + 1;
+ }
+ const reset : i64 = if_reset(CF_P);
+ const edge_font : f64 = font - 2.0;
+ const ne : usize = layout_fsm(ns as usize, reset, nr as usize, RF[0..16384], RT[0..16384], RIL[0..16384], LN[0..64], LM[0..64], l, h, font, font * 0.6, edge_font, K_LABEL_ROWS as i64, NODES[0..256], EF[0..24000], EU[0..18480], ORDER[0..64], LWORK[0..100000], FW[0..140000], TRIG[0..128]);
+ const line_h : f64 = jsround(font * 1.25);
+ // the svg element
+ n = raw(o, n, "");
+ n = raw(o, n, "");
+ // edges
+ n = raw(o, n, "");
+ var e : usize = 0;
+ while (e < ne) {
+ const q : usize = e * 13;
+ n = raw(o, n, "");
+ var first : bool = true;
+ var r : u32 = 0;
+ while (r < nr) {
+ if (RF[r as usize] == EU[e * 10] and RT[r as usize] == EU[e * 10 + 1]) {
+ if (!first) { n = put(o, n, 10); }
+ first = false;
+ var tmp : [1100]u8 = [0; 1100];
+ var tn : u32 = state_name_raw(tmp[0..], 0, RF[r as usize]);
+ tn = app_raw(tmp[0..], tn, " -> ");
+ tn = state_name_raw(tmp[0..], tn, RT[r as usize]);
+ tn = app_raw(tmp[0..], tn, " ");
+ n = text(o, n, tmp[0..(tn as usize)]);
+ n = text(o, n, row_in(r));
+ }
+ r = r + 1;
+ }
+ n = raw(o, n, "");
+ e = e + 1;
+ }
+ n = raw(o, n, "");
+ e = 0;
+ while (e < ne) {
+ const q : usize = e * 13;
+ const x : f64 = EF[q + 8];
+ const y : f64 = EF[q + 9];
+ var angle : f64 = 0.0;
+ if (EU[e * 10 + 2] == 1) {
+ angle = atan2_of(EF[q + 7] - EF[q + 5], EF[q + 6] - EF[q + 4]);
+ } else {
+ angle = atan2_of(EF[q + 7] - EF[q + 3], EF[q + 6] - EF[q + 2]);
+ }
+ const cs : f64 = libm(1, angle, 0.0);
+ const sn : f64 = libm(0, angle, 0.0);
+ const bx : f64 = x - 9.0 * cs;
+ const by : f64 = y - 9.0 * sn;
+ const nx : f64 = (0.0 - sn) * 4.5;
+ const ny : f64 = cs * 4.5;
+ n = raw(o, n, "");
+ e = e + 1;
+ }
+ n = raw(o, n, "");
+ i = 0;
+ while (i < ns) {
+ const cx : f64 = NODES[(i * 4) as usize];
+ const cy : f64 = NODES[(i * 4 + 1) as usize];
+ const nw : f64 = NODES[(i * 4 + 2) as usize];
+ const nh : f64 = NODES[(i * 4 + 3) as usize];
+ const is_reset : bool = (i as i64) == reset;
+ n = raw(o, n, "");
+ if (is_reset) {
+ n = raw(o, n, "");
+ }
+ n = raw(o, n, "");
+ const top : f64 = cy - ((LN[i as usize] as f64) * line_h) / 2.0 + line_h * 0.78;
+ var c : u32 = 0;
+ while (c < LN[i as usize]) {
+ n = raw(o, n, " 0) { n = put_u(o, n, font - 1.0); } else { n = put_u(o, n, font); }
+ n = raw(o, n, "\" font-weight=\"");
+ if (c > 0) { n = raw(o, n, "normal"); } else { n = raw(o, n, "bold"); }
+ n = raw(o, n, "\"");
+ if (c > 0) { n = a_col(o, n, "fill", 8); } else { if (is_reset) { n = a_col(o, n, "fill", 4); } else { n = a_col(o, n, "fill", 6); } }
+ n = raw(o, n, ">");
+ n = text(o, n, slb(i, c));
+ n = raw(o, n, "");
+ c = c + 1;
+ }
+ n = raw(o, n, "");
+ i = i + 1;
+ }
+ n = raw(o, n, "");
+ e = 0;
+ while (e < ne) {
+ const q : usize = e * 13;
+ const shown : u32 = EU[e * 10 + 4];
+ const more : u32 = EU[e * 10 + 5];
+ var total_lines : u32 = shown;
+ if (more != 0) { total_lines = total_lines + 1; }
+ const lh : f64 = edge_font * 1.15;
+ const total : f64 = (total_lines as f64) * lh;
+ const ly : f64 = EF[q + 12];
+ var y0 : f64 = ly - total / 2.0 + lh * 0.8;
+ if (EU[e * 10 + 7] == 1) { y0 = ly - total + lh * 0.8; }
+ if (EU[e * 10 + 7] == 2) { y0 = ly + lh * 0.8; }
+ n = raw(o, n, ""); }
+ if (EU[e * 10 + 6] == 2) { n = raw(o, n, " text-anchor=\"end\">"); }
+ if (EU[e * 10 + 6] == 0) { n = raw(o, n, " text-anchor=\"middle\">"); }
+ var seen : u32 = 0;
+ var r : u32 = 0;
+ var tl : u32 = 0;
+ while (tl < total_lines) {
+ n = raw(o, n, " 0) { n = put_r1(o, n, lh); } else { n = raw(o, n, "0"); }
+ n = raw(o, n, "\"");
+ if (tl >= shown) { n = a_col(o, n, "fill", 8); } else { n = a_col(o, n, "fill", 4); }
+ n = raw(o, n, ">");
+ if (tl < shown) {
+ // the tl-th row of the pair
+ var found : bool = false;
+ while (r < nr and !found) {
+ if (RF[r as usize] == EU[e * 10] and RT[r as usize] == EU[e * 10 + 1]) { found = true; }
+ r = r + 1;
+ }
+ const pat : []const u8 = row_in(r - 1);
+ if (pat.len == 0) { n = text(o, n, SAY_ANY); } else { n = text(o, n, pat); }
+ } else {
+ const dm : u32 = dec_a(more as i64);
+ n = fmt4(o, n, SAY_EDGE_MORE, VA[0..(dm as usize)], "", "", "", 1);
+ }
+ n = raw(o, n, "");
+ tl = tl + 1;
+ }
+ n = raw(o, n, "");
+ e = e + 1;
+ }
+ n = raw(o, n, "");
+ STATE_W = w;
+ return n;
+}
+
+fn if_reset(p: u32) -> i64 {
+ if (LOUT[(p + 6) as usize] == W_NO) { return -1; }
+ return LOUT[(p + 6) as usize] as i64;
+}
+
+/// Number(v).toLocaleString('en-US') of a count into VA.. buffers helpers
+fn loc_a(v: f64) -> u32 { return js_locale_num(VA[0..], 0, v); }
+fn loc_b(v: f64) -> u32 { return js_locale_num(VB[0..], 0, v); }
+fn str_a(v: f64) -> u32 { return js_string_num(VA[0..], 0, v); }
+fn str_b(v: f64) -> u32 { return js_string_num(VB[0..], 0, v); }
+fn str_c(v: f64) -> u32 { return js_string_num(VC[0..], 0, v); }
+fn dec_a(v: i64) -> u32 { return js_string_num(VA[0..], 0, v as f64); }
+fn dec_b(v: i64) -> u32 { return js_string_num(VB[0..], 0, v as f64); }
+
+// ------------------------------------------------------------------------------------------------
+// the regions of the result view
+// ------------------------------------------------------------------------------------------------
+
+fn region_picker(o: []u8) -> u32 {
+ var n : u32 = 0;
+ PICKER_N = 0;
+ if (LRES[9] < 2) {
+ PICKER_HIDDEN = true;
+ return 0;
+ }
+ PICKER_HIDDEN = false;
+ var i : u32 = 0;
+ while (i < LRES[9]) {
+ n = raw(o, n, ""); } else { n = raw(o, n, " aria-pressed=\"false\">"); }
+ n = text(o, n, SAY_PICK_FSM);
+ n = raw(o, n, " ");
+ n = num(o, n, (i + 1) as u64);
+ n = raw(o, n, ": ");
+ fsm_name_raw(i);
+ n = text(o, n, FNAME[0..(FNAME_LEN as usize)]);
+ n = raw(o, n, "");
+ i = i + 1;
+ }
+ PICKER_N = LRES[9];
+ return n;
+}
+
+fn region_stats(o: []u8) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ while (i < 4) {
+ var v : f64 = CF_NS as f64;
+ if (i == 1) { v = CF_NR as f64; }
+ if (i == 2) { v = CF_NIN; }
+ if (i == 3) { v = CF_NOUT; }
+ n = raw(o, n, "
");
+ n = js_locale_num(o, n, v);
+ n = raw(o, n, "");
+ if ((i as usize) < SAY_STAT_NAMES.len) { n = text(o, n, SAY_STAT_NAMES[i as usize]); }
+ n = raw(o, n, "
");
+ i = i + 1;
+ }
+ const p : u32 = CF_P;
+ if (LOUT[(p + 7) as usize] != W_NO) {
+ const enc : []const u8 = lspan(LOUT[(p + 7) as usize], LOUT[(p + 8) as usize]);
+ if (!(enc.len == 4 and enc[0] == 97 and enc[1] == 117 and enc[2] == 116 and enc[3] == 111) and enc.len > 0) {
+ n = raw(o, n, "
");
+ n = fmt4(o, n, SAY_ENCODING, enc, "", "", "", 1);
+ n = raw(o, n, "
");
+ }
+ }
+ return n;
+}
+
+fn region_diagram(o: []u8) -> u32 {
+ var n : u32 = 0;
+ if (CF_NS > K_MAX_DRAW_STATES as u32 or CF_NS == 0) {
+ const a : u32 = loc_a(CF_NS as f64);
+ const b : u32 = loc_b(K_MAX_DRAW_STATES as f64);
+ n = raw(o, n, "
");
+ n = fmt4(o, n, SAY_TOO_MANY, VA[0..(a as usize)], VB[0..(b as usize)], "", "", 2);
+ n = raw(o, n, "
");
+ SVGBTN_HIDDEN = true;
+ LEGEND_HIDDEN = true;
+ return n;
+ }
+ SVGBTN_HIDDEN = false;
+ LEGEND_HIDDEN = false;
+ var w : f64 = jsround(WPX);
+ if (!truthy(w)) { w = K_DIAGRAM_MIN_W as f64; }
+ return svg_of(o, 0, PICK, w);
+}
+
+fn region_key(o: []u8) -> u32 {
+ var n : u32 = 0;
+ if (!truthy(CF_NIN)) { return 0; }
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_INPUT_KEY);
+ n = raw(o, n, "
");
+ var i : f64 = CF_NIN - 1.0;
+ var guard : u32 = 0;
+ while (i >= 0.0 and guard < 20000) {
+ n = raw(o, n, "
");
+ var named : bool = false;
+ if (i == @floor(i) and i < 4294967295.0) { named = input_name(i as u32); }
+ if (named) {
+ n = text(o, n, lspan(IN_O, IN_L));
+ } else {
+ n = raw(o, n, "in[");
+ n = js_string_num(o, n, i);
+ n = raw(o, n, "]");
+ }
+ n = raw(o, n, "
");
+ i = i - 1.0;
+ guard = guard + 1;
+ }
+ n = raw(o, n, "");
+ return n;
+}
+
+fn nm_td(o: []u8, n0: u32, i: u32) -> u32 {
+ var n : u32 = n0;
+ var tmp : [1100]u8 = [0; 1100];
+ var tn : u32 = state_name_raw(tmp[0..], 0, i);
+ if (NAMES_ON and i < NM_N and NM_L[i as usize] > 0) {
+ tn = app_raw(tmp[0..], tn, " ");
+ tn = app_raw(tmp[0..], tn, NM[(NM_O[i as usize] as usize)..((NM_O[i as usize] + NM_L[i as usize]) as usize)]);
+ }
+ return text(o, n, tmp[0..(tn as usize)]);
+}
+
+fn region_table(o: []u8) -> u32 {
+ var n : u32 = 0;
+ var shown : u32 = CF_NR;
+ if (!SHOW_ALL and shown > K_TABLE_ROWS as u32) { shown = K_TABLE_ROWS as u32; }
+ n = raw(o, n, "
");
+ var h : u32 = 0;
+ while ((h as usize) < SAY_TABLE_HEAD.len) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_TABLE_HEAD[h as usize]);
+ n = raw(o, n, "
");
+ h = h + 1;
+ }
+ n = raw(o, n, "
");
+ var r : u32 = 0;
+ while (r < shown) {
+ const e : u32 = row_at(r);
+ n = raw(o, n, "
");
+ n = nm_td(o, n, LOUT[e as usize]);
+ n = raw(o, n, "
");
+ const pin : []const u8 = row_in(r);
+ if (pin.len == 0) { n = raw(o, n, "-"); } else { n = text(o, n, pin); }
+ n = raw(o, n, "
");
+ n = nm_td(o, n, LOUT[(e + 1) as usize]);
+ n = raw(o, n, "
");
+ const pout : []const u8 = row_out(r);
+ if (pout.len == 0) { n = raw(o, n, "-"); } else { n = text(o, n, pout); }
+ n = raw(o, n, "
");
+ // conditionOf(r.in, f.inputs) joined by " & "
+ var idx : [256]u32 = [0; 256];
+ var bit : [256]u8 = [0; 256];
+ var cnt : usize = 0;
+ cnt = condition_of(pin, idx[0..256], bit[0..256]);
+ if (cnt > 256) { cnt = 256; }
+ if (cnt == 0) {
+ n = text(o, n, SAY_ANY);
+ } else {
+ var c : usize = 0;
+ while (c < cnt) {
+ if (c > 0) { n = raw(o, n, " & "); }
+ if (input_name(idx[c])) {
+ n = text(o, n, lspan(IN_O, IN_L));
+ } else {
+ n = raw(o, n, "in[");
+ n = num(o, n, idx[c] as u64);
+ n = raw(o, n, "]");
+ }
+ n = raw(o, n, "=");
+ n = put_escaped(o, n, bit[c]);
+ c = c + 1;
+ }
+ }
+ n = raw(o, n, "
");
+ r = r + 1;
+ }
+ n = raw(o, n, "
");
+ TABLE_MORE = false;
+ if (CF_NR > shown) {
+ TABLE_MORE = true;
+ n = raw(o, n, "
");
+ const a : u32 = loc_a(CF_NR as f64);
+ n = fmt4(o, n, SAY_TABLE_SHOW, VA[0..(a as usize)], "", "", "", 1);
+ n = raw(o, n, "
");
+ }
+ return n;
+}
+
+/// Regenerate what is dirty: the result view keeps its last text when it is not on show.
+fn refresh_regions() {
+ if (!LIVE or FSMV_HIDDEN) { return; }
+ if (DIRTY_ALL) {
+ select_fsm(PICK);
+ C_PICKER_LEN = region_picker(C_PICKER[0..65536]);
+ C_STATS_LEN = region_stats(C_STATS[0..8192]);
+ C_DIAGRAM_LEN = region_diagram(C_DIAGRAM[0..524288]);
+ C_KEY_LEN = region_key(C_KEY[0..65536]);
+ C_TABLE_LEN = region_table(C_TABLE[0..786432]);
+ if (!MISSING) { DIRTY_ALL = false; DIRTY_TABLE = false; DIRTY_DIAGRAM = false; }
+ return;
+ }
+ if (DIRTY_DIAGRAM) {
+ select_fsm(PICK);
+ C_DIAGRAM_LEN = region_diagram(C_DIAGRAM[0..524288]);
+ if (!MISSING) { DIRTY_DIAGRAM = false; }
+ }
+ if (DIRTY_TABLE) {
+ select_fsm(PICK);
+ C_TABLE_LEN = region_table(C_TABLE[0..786432]);
+ DIRTY_TABLE = false;
+ }
+}
+
+fn status_text(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (ST_KIND == 0) { return text(o, n, SAY_LOCAL_NOTE); }
+ if (ST_KIND == 1) {
+ n = text(o, n, ST_LABEL[0..(ST_LABEL_LEN as usize)]);
+ n = raw(o, n, ". ");
+ return text(o, n, SAY_LOCAL_NOTE);
+ }
+ if (ST_KIND == 2) {
+ const a : u32 = js_locale_num(VA[0..], 0, ST_A as f64);
+ const b : u32 = js_locale_num(VB[0..], 0, ST_B as f64);
+ return fmt4(o, n, SAY_TOO_BIG, VA[0..(a as usize)], VB[0..(b as usize)], "", "", 2);
+ }
+ if (ST_KIND == 3) { return fmt4(o, n, SAY_ERR_READ, ST_LABEL[0..(ST_LABEL_LEN as usize)], "", "", "", 1); }
+ if (ST_KIND == 4) { return text(o, n, SAY_ERR_LOAD); }
+ if (LAST_ERR >= 1 and (LAST_ERR as usize) <= SAY_ERROR_TEXT.len) {
+ return fmt4(o, n, SAY_ERROR_TEXT[(LAST_ERR - 1) as usize], ST_LINE[0..(ST_LINE_LEN as usize)], "", "", "", 1);
+ }
+ return n;
+}
+
+fn note_id(code: u32) -> u32 {
+ var lit : []const u8 = "";
+ if (code == N_KISS2_S) { lit = "kiss2_s"; }
+ if (code == N_KISS2_P) { lit = "kiss2_p"; }
+ if (code == N_NO_RESET_NAMED) { lit = "no_reset_named"; }
+ if (code == N_NO_RESET) { lit = "no_reset"; }
+ if (code == N_WIDTH_IN) { lit = "width_in"; }
+ if (code == N_RECODED) { lit = "recoded"; }
+ if (code == N_NO_INFO) { lit = "no_info"; }
+ var k : u32 = 0;
+ while ((k as usize) < SAY_NOTE_IDS.len) {
+ if (same_span(SAY_NOTE_IDS[k as usize], lit)) { return k; }
+ k = k + 1;
+ }
+ return 99;
+}
+
+fn verdict(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ if (!LIVE) { return n; }
+ const nf : u32 = LRES[9];
+ if (LRES[2] == 2) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_VERDICT_KISS2);
+ } else {
+ if (nf > 0) { n = raw(o, n, "
"); } else { n = raw(o, n, "
"); }
+ if (nf == 0) { n = text(o, n, SAY_VERDICT_NONE); }
+ if (nf == 1) { n = text(o, n, SAY_VERDICT_ONE); }
+ if (nf > 1) {
+ const a : u32 = dec_a(nf as i64);
+ n = fmt4(o, n, SAY_VERDICT_MANY, VA[0..(a as usize)], "", "", "", 1);
+ }
+ }
+ n = raw(o, n, "
");
+ const nrej : u32 = LRES[11];
+ if (nf + nrej > 0) {
+ n = raw(o, n, "
");
+ var k : u32 = 0;
+ while (k < nf) {
+ select_fsm(k);
+ fsm_name_raw(k);
+ const a : u32 = str_a(CF_NS as f64);
+ const b : u32 = str_b(CF_NR as f64);
+ const c : u32 = str_c(CF_NIN);
+ n = raw(o, n, "
");
+ n = fmt4(o, n, SAY_FSM_LINE, FNAME[0..(FNAME_LEN as usize)], VA[0..(a as usize)], VB[0..(b as usize)], VC[0..(c as usize)], 4);
+ if (LOUT[(CF_P + 6) as usize] == W_NO) {
+ n = raw(o, n, " (");
+ n = text(o, n, SAY_NO_RESET);
+ n = raw(o, n, ")");
+ }
+ n = raw(o, n, "
");
+ k = k + 1;
+ }
+ // rejected: found words first (2 each), then 3 + 2 per reason
+ var p : u32 = LRES[10] * 2;
+ k = 0;
+ while (k < nrej) {
+ const ro : u32 = LOUT[p as usize];
+ const rl : u32 = LOUT[(p + 1) as usize];
+ const nre : u32 = LOUT[(p + 2) as usize];
+ n = raw(o, n, "
");
+ n = fmt4(o, n, SAY_REFUSED, lspan(ro, rl), "", "", "", 1);
+ if (nre > 0) {
+ n = raw(o, n, " ");
+ var q : u32 = 0;
+ while (q < nre) {
+ if (q > 0) { n = raw(o, n, " "); }
+ n = text(o, n, lspan(LOUT[(p + 3 + q * 2) as usize], LOUT[(p + 4 + q * 2) as usize]));
+ q = q + 1;
+ }
+ n = raw(o, n, "");
+ }
+ n = raw(o, n, "
");
+ p = p + 3 + nre * 2;
+ k = k + 1;
+ }
+ n = raw(o, n, "
");
+ }
+ if (LRES[2] == 1 and nf == 0 and nrej == 0 and LRES[10] == 0) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_SILENT);
+ n = raw(o, n, "
");
+ }
+ if (LRES[2] == 2) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_KISS2_NOTE);
+ n = raw(o, n, "
");
+ }
+ // notes
+ var nb : u32 = LRES[10] * 2;
+ var rk : u32 = 0;
+ while (rk < nrej) {
+ nb = nb + 3 + LOUT[(nb + 2) as usize] * 2;
+ rk = rk + 1;
+ }
+ var j : u32 = 0;
+ while (j < LRES[12]) {
+ const q : u32 = nb + j * 5;
+ const code : u32 = LOUT[q as usize];
+ const ix : u32 = note_id(code);
+ n = raw(o, n, "
");
+ if (ix < (SAY_NOTE_TEXT.len as u32)) {
+ if (code == N_KISS2_S or code == N_KISS2_P) {
+ const c2 : u32 = dec_b(LOUT[(q + 3) as usize] as i64);
+ n = fmt4(o, n, SAY_NOTE_TEXT[ix as usize], lspan(LOUT[(q + 1) as usize], LOUT[(q + 2) as usize]), VB[0..(c2 as usize)], "", "", 2);
+ }
+ if (code == N_NO_RESET_NAMED) { n = fmt4(o, n, SAY_NOTE_TEXT[ix as usize], lspan(LOUT[(q + 1) as usize], LOUT[(q + 2) as usize]), "", "", "", 1); }
+ if (code == N_NO_RESET or code == N_RECODED) { n = fmt4(o, n, SAY_NOTE_TEXT[ix as usize], "", "", "", "", 0); }
+ if (code == N_WIDTH_IN) {
+ const w0 : u32 = str_a(LNUMS[0]);
+ n = fmt4(o, n, SAY_NOTE_TEXT[ix as usize], VA[0..(w0 as usize)], "", "", "", 1);
+ }
+ if (code == N_NO_INFO) {
+ const f0 : u32 = dec_a(LOUT[(q + 1) as usize] as i64);
+ const f1 : u32 = dec_b(LOUT[(q + 2) as usize] as i64);
+ n = fmt4(o, n, SAY_NOTE_TEXT[ix as usize], VA[0..(f0 as usize)], VB[0..(f1 as usize)], "", "", 2);
+ }
+ }
+ n = raw(o, n, "
");
+ j = j + 1;
+ }
+ if (LRES[6] != W_NO) {
+ n = raw(o, n, "
");
+ n = fmt4(o, n, SAY_VERSION, lspan(LRES[6], LRES[7]), "", "", "", 1);
+ n = raw(o, n, "
");
+ }
+ return n;
+}
+
+fn sample_url(o: []u8, n0: u32, k: u32, ext: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, " href=\"");
+ n = text(o, n, BASE[0..(BASE_LEN as usize)]);
+ n = text(o, n, K_SAMPLE_DIR);
+ n = raw(o, n, "/");
+ n = text(o, n, K_SAMPLE_IDS[k as usize]);
+ n = raw(o, n, ".");
+ n = text(o, n, ext);
+ return raw(o, n, "\"");
+}
+
+/// The whole page, as tool.js leaves it. 0 on overflow. The host draws again while w_libm_need says so.
+pub fn w_render(dst: []u8) -> u32 {
+ RQ_N = 0;
+ MISSING = false;
+ if (LB_N > 3500) { LB_N = 0; }
+ refresh_regions();
+ var n : u32 = 0;
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SAMPLE_LABEL);
+ n = raw(dst, n, "
");
+ var k : u32 = 0;
+ while ((k as usize) < K_SAMPLE_IDS.len) {
+ n = raw(dst, n, ""); } else { n = raw(dst, n, " aria-pressed=\"false\">"); }
+ if ((k as usize) < SAY_SAMPLE_NAMES.len) { n = text(dst, n, SAY_SAMPLE_NAMES[k as usize]); } else { n = text(dst, n, K_SAMPLE_IDS[k as usize]); }
+ n = raw(dst, n, "");
+ k = k + 1;
+ }
+ n = raw(dst, n, "
");
+ if (SHOWN >= 0 and (SHOWN as usize) < SAY_SAMPLE_LINES.len) { n = text(dst, n, SAY_SAMPLE_LINES[SHOWN as usize]); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW_LINE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_READ);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_CLEAR);
+ n = raw(dst, n, "
");
+ n = status_text(dst, n);
+ n = raw(dst, n, "
");
+ n = verdict(dst, n);
+ n = raw(dst, n, "
");
+ n = raw(dst, n, C_PICKER[0..(C_PICKER_LEN as usize)]);
+ n = raw(dst, n, "
");
+ n = raw(dst, n, C_STATS[0..(C_STATS_LEN as usize)]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DIAGRAM_HEAD);
+ n = raw(dst, n, "
");
+ n = raw(dst, n, C_DIAGRAM[0..(C_DIAGRAM_LEN as usize)]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SVG_BUTTON);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LEGEND);
+ n = raw(dst, n, "
");
+ n = raw(dst, n, C_KEY[0..(C_KEY_LEN as usize)]);
+ n = raw(dst, n, "
");
+ n = raw(dst, n, C_TABLE[0..(C_TABLE_LEN as usize)]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HONEST);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every name of the three libraries, so that they arrive whole. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8, w: []u32, d: []f64) -> u32 {
+ var n : u32 = 0;
+ n = n + ((OVER) as u32);
+ n = n + ((H_LT) as u32);
+ n = n + ((H_GT) as u32);
+ n = n + ((H_AMP) as u32);
+ n = n + ((H_QUOTE) as u32);
+ n = n + ((H_APOS) as u32);
+ n = n + ((H_SPACE) as u32);
+ n = n + ((H_SLASH) as u32);
+ n = n + ((H_EQ) as u32);
+ n = n + ((ok(0)) as u32);
+ n = n + ((put(o, 0, 0)) as u32);
+ n = n + ((raw(o, 0, s)) as u32);
+ n = n + ((put_escaped(o, 0, 0)) as u32);
+ n = n + ((text(o, 0, s)) as u32);
+ n = n + ((num(o, 0, 0)) as u32);
+ n = n + ((num_grouped(o, 0, 0)) as u32);
+ n = n + ((attr(o, 0, s, s)) as u32);
+ n = n + ((fill(o, 0, s, s, s, s, 0)) as u32);
+ n = n + ((name_is(s, 0, 0, s)) as u32);
+ n = n + ((is_word_byte(0)) as u32);
+ n = n + ((fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s)) as u32);
+ n = n + ((dec_to(o, 0)) as u32);
+ n = n + ((same_bytes(s, 0, s)) as u32);
+ n = n + ((NONE) as u32);
+ n = n + ((NO_TEXT) as u32);
+ n = n + ((PACK) as u32);
+ n = n + ((PI) as u32);
+ n = n + ((pack(0, 0)) as u32);
+ n = n + ((lo_of(0)) as u32);
+ n = n + ((hi_of(0)) as u32);
+ n = n + ((inf()) as u32);
+ n = n + ((nan()) as u32);
+ n = n + ((neg_zero(0.0)) as u32);
+ n = n + ((truthy(0.0)) as u32);
+ n = n + ((jsmax(0.0, 0.0)) as u32);
+ n = n + ((jsmin(0.0, 0.0)) as u32);
+ n = n + ((jsround(0.0)) as u32);
+ n = n + ((hypot2(0.0, 0.0)) as u32);
+ unit_to(0.0, 0.0);
+ rot_to(0.0, 0.0, 0.0, 0.0);
+ n = n + ((node_angle(0, 0)) as u32);
+ box_exit(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ n = n + ((code_value(s)) as u32);
+ n = n + ((condition_of(s, w, o)) as u32);
+ counts_of(0, 0, 0.0, 0.0, 0.0, d);
+ n = n + ((walk_order(0, 0, 0, w, w, w, w)) as u32);
+ label_box(0.0, 0.0, 0, 0, 0, 0, 0, 0.0, 0.0);
+ n = n + ((overlap(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) as u32);
+ n = n + ((digits_of(0)) as u32);
+ n = n + ((anchor_of(0.0)) as u32);
+ n = n + ((valign_of(0.0)) as u32);
+ n = n + ((layout_fsm(0, 0, 0, w, w, w, w, w, 0.0, 0.0, 0.0, 0.0, 0.0, 0, d, d, w, w, w, d, d)) as u32);
+ place(0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, d, d, w, w, d, d);
+ place_labels(0, 0, 0.0, 0.0, 0.0, 0.0, d, d, w, d);
+ n = n + ((EV) as u32);
+ n = n + ((EV_FOUND) as u32);
+ n = n + ((EV_REJ) as u32);
+ n = n + ((EV_REASON) as u32);
+ n = n + ((EV_RECODE) as u32);
+ n = n + ((EV_FSM) as u32);
+ n = n + ((EV_NUM) as u32);
+ n = n + ((EV_IN) as u32);
+ n = n + ((EV_OUT) as u32);
+ n = n + ((EV_STATE) as u32);
+ n = n + ((EV_ROW) as u32);
+ n = n + ((EV_RESET) as u32);
+ n = n + ((EV_NAME) as u32);
+ n = n + ((EV_ROW2) as u32);
+ n = n + ((EV_ILB) as u32);
+ n = n + ((EV_OB) as u32);
+ n = n + ((N_KISS2_S) as u32);
+ n = n + ((N_KISS2_P) as u32);
+ n = n + ((N_NO_RESET_NAMED) as u32);
+ n = n + ((N_NO_RESET) as u32);
+ n = n + ((N_WIDTH_IN) as u32);
+ n = n + ((N_RECODED) as u32);
+ n = n + ((N_NO_INFO) as u32);
+ n = n + ((E_UNKNOWN) as u32);
+ n = n + ((E_KISS2ROW) as u32);
+ n = n + ((E_NOFSMPASS) as u32);
+ n = n + ((is_ws(0)) as u32);
+ n = n + ((is_digit(0)) as u32);
+ n = n + ((match_in(s, 0, 0, s)) as u32);
+ n = n + ((eat(s, 0, 0, s)) as u32);
+ n = n + ((span_eq(s, 0, 0, 0, 0)) as u32);
+ n = n + ((skip_ws(s, 0, 0)) as u32);
+ n = n + ((skip_digits(s, 0, 0)) as u32);
+ n = n + ((tok_end(s, 0, 0)) as u32);
+ n = n + ((run_noquote(s, 0, 0)) as u32);
+ n = n + ((run_code(s, 0, 0)) as u32);
+ n = n + ((rstrip(s, 0, 0)) as u32);
+ n = n + ((no_cr(s, 0, 0)) as u32);
+ n = n + ((has_sub(s, s)) as u32);
+ n = n + ((dig_val(s, 0, 0)) as u32);
+ n = n + ((split_nl(s, w)) as u32);
+ n = n + ((hex_val(0)) as u32);
+ n = n + ((js_number(s, 0, 0)) as u32);
+ n = n + ((state_num(s, 0, 0)) as u32);
+ n = n + ((is_nl(0)) as u32);
+ n = n + ((tail_ok(s, 0)) as u32);
+ n = n + ((sniff(s)) as u32);
+ ow(w, w, 0);
+ n = n + ((ev(w, w, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) as u32);
+ clear_res(w, d);
+ n = n + ((pat_ok(s, 0, 0)) as u32);
+ n = n + ((name_index(s, w, w, 0, 0)) as u32);
+ parse_kiss2(s, w, 0, w, w, w, d, w);
+ n = n + ((name_event(w, 0, 0)) as u32);
+ n = n + ((state_num_of(s, w, 0, 0)) as u32);
+ n = n + ((m_found(s, 0, 0)) as u32);
+ n = n + ((m_not_marking(s, 0, 0)) as u32);
+ n = n + ((m_recoding(s, 0, 0)) as u32);
+ n = n + ((m_mapping(s, 0, 0)) as u32);
+ n = n + ((m_info(s, 0, 0)) as u32);
+ n = n + ((m_number_line(s, 0, 0, s)) as u32);
+ n = n + ((is_header(s, 0, 0, s)) as u32);
+ n = n + ((is_rule(s, 0, 0)) as u32);
+ n = n + ((is_indented(s, 0, 0)) as u32);
+ n = n + ((m_index_line(s, 0, 0)) as u32);
+ n = n + ((m_encoding_line(s, 0, 0)) as u32);
+ n = n + ((m_row_line(s, 0, 0)) as u32);
+ n = n + ((is_fsm_module_line(s, 0, 0)) as u32);
+ n = n + ((m_module_name(s, 0, 0)) as u32);
+ n = n + ((unslash(s, 0, 0)) as u32);
+ n = n + ((m_yosys_version(s)) as u32);
+ n = n + ((find_slot(s, w, 0, 0, 0)) as u32);
+ n = n + ((count_sets(w, 0, 0)) as u32);
+ parse_log(s, w, 0, w, w, d, w);
+ pend_lo_set(0, 0);
+ n = n + ((ev_last_index(w)) as u32);
+ n = n + ((slot_has_rows(w, 0, 0, 0)) as u32);
+ n = n + ((last_with(w, 0, 0, 0, 0, 0)) as u32);
+ emit_log(s, w, w, d, w);
+ n = n + ((last_set_event(w, 0, 0)) as u32);
+ n = n + ((recode_of(s, w, 0, 0, 0)) as u32);
+ emit_fsm(s, w, 0, 0, w, d, w);
+ n = n + ((fsm_parse(s, 0, w, w, w, w, d, w)) as u32);
+ return n;
+}
+
+// ------------------------------------------------------------------------------------------------
+// tests
+// ------------------------------------------------------------------------------------------------
+
+pub var T_BUF : [900000]u8 = [0; 900000];
+pub var T_F : [12288]f64 = [0.0; 12288];
+
+fn t_has(n: u32, lit: []const u8) -> bool {
+ var i : u32 = 0;
+ const l : u32 = lit.len as u32;
+ if (n < l) { return false; }
+ while (i + l <= n) {
+ var k : u32 = 0;
+ while (k < l and T_BUF[(i + k) as usize] == lit[k as usize]) { k = k + 1; }
+ if (k == l) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn t_sample_json() -> []const u8 {
+ return "{\"samples\":[{\"id\":\"uart-rx\",\"kiss2\":true,\"names\":[\"IDLE\",\"\",\"X\"]},{\"id\":\"spi-flash\"}]}";
+}
+
+fn t_log() -> []const u8 {
+ return "5. Executing FSM_DETECT pass.\nFound FSM state register m.s.\nFSM `$fsm$\\s$1' from module `\\m':\n Information on FSM $fsm$\\s$1 (\\s):\n Number of input signals: 2\n Number of output signals: 1\n Number of state bits: 1\n Input signals:\n 0: \\a\n Output signals:\n 0: \\y\n State encoding:\n 0: 1'0 \n 1: 1'1\n Transition Table (state_in, ctrl_in, state_out, ctrl_out):\n 0: 0 2'-1 -> 1 1'1\n 1: 1 2'0- -> 0 1'0\n 2: 1 2'1- -> 1 1'0\n\n";
+}
+
+fn t_load() {
+ w_init("");
+ w_base("http://x/y/");
+ w_data(0, t_sample_json());
+ w_data(1, t_log());
+ w_data_failed(2);
+}
+
+test numbers_are_written_like_javascript {
+ var o : [64]u8 = [0; 64];
+ var n : u32 = js_string_num(o[0..], 0, 2.5);
+ assert same_bytes(o[0..], n, "2.5");
+ n = js_string_num(o[0..], 0, 0.1);
+ assert same_bytes(o[0..], n, "0.1");
+ n = js_string_num(o[0..], 0, 0.0 - 12.25);
+ assert same_bytes(o[0..], n, "-12.25");
+ n = js_string_num(o[0..], 0, 100.0);
+ assert same_bytes(o[0..], n, "100");
+ n = js_string_num(o[0..], 0, nan());
+ assert same_bytes(o[0..], n, "NaN");
+ n = js_locale_num(o[0..], 0, 1234567.0);
+ assert same_bytes(o[0..], n, "1,234,567");
+ n = js_locale_num(o[0..], 0, 2.0005);
+ assert same_bytes(o[0..], n, "2.001");
+ n = js_locale_num(o[0..], 0, 0.5);
+ assert same_bytes(o[0..], n, "0.5");
+ n = js_locale_num(o[0..], 0, 0.0 - 3.0);
+ assert same_bytes(o[0..], n, "-3");
+ n = put_r1(o[0..], 0, 12.34);
+ assert same_bytes(o[0..], n, "12.3");
+ n = put_r1(o[0..], 0, 0.0 - 0.04);
+ assert same_bytes(o[0..], n, "0");
+ n = put_r1(o[0..], 0, 7.96);
+ assert same_bytes(o[0..], n, "8");
+ n = put_r1(o[0..], 0, 0.0 - 2.55);
+ assert same_bytes(o[0..], n, "-2.5");
+}
+
+test the_templates_fill_like_fmt {
+ var o : [64]u8 = [0; 64];
+ const n : u32 = fmt4(o[0..], 0, "{0} of {1}{2}{3}{9}{x}", "a<", "b", "c", "d", 3);
+ assert same_bytes(o[0..], n, "a< of bc{x}");
+}
+
+test libm_calls_are_queued_and_answered {
+ w_init("");
+ LB_N = 0;
+ RQ_N = 0;
+ const a : f64 = libm(1, 0.5, 0.0);
+ assert a == 0.0;
+ assert MISSING;
+ assert RQ_N == 1;
+ const b : f64 = libm(1, 0.5, 0.0);
+ assert RQ_N == 1;
+ assert w_libm_need(T_F[0..12288]) == 1;
+ assert T_F[0] == 1.0;
+ assert T_F[1] == 0.5;
+ T_F[3] = 0.875;
+ w_libm_give(T_F[3..12288], 1);
+ assert libm(1, 0.5, 0.0) == 0.875;
+ assert MISSING == false;
+ assert libm(0, 0.5, 0.0) == 0.0;
+ assert MISSING;
+}
+
+test the_sample_is_shown_when_its_files_are_there {
+ w_init("");
+ var n : u32 = w_render(T_BUF[0..900000]);
+ assert n > 0;
+ assert t_has(n, "");
+ assert t_has(n, "
");
+ assert t_has(n, "One FSM, as fsm_export wrote it");
+ w_input(1, "nothing here");
+ w_click(4);
+ assert LIVE == false;
+ assert FSMV_HIDDEN;
+ n = w_render(T_BUF[0..900000]);
+ assert t_has(n, "Neither a KISS2 file nor a yosys log");
+ assert t_has(n, "");
+ assert t_has(n, "
01
");
+}
+
+test a_file_is_read_and_a_big_or_unreadable_one_is_refused {
+ t_load();
+ var e : [4]u8 = [0; 4];
+ w_file("a.txt", 5000000.0, e[0..0], 0);
+ assert ST_KIND == 2;
+ var n : u32 = w_render(T_BUF[0..900000]);
+ assert t_has(n, "5,000,000 characters is more than this page reads (4,000,000).");
+ w_file("b.kiss2", 10.0, e[0..0], 1);
+ n = w_render(T_BUF[0..900000]);
+ assert t_has(n, "Could not read b.kiss2.");
+ w_file("c.kiss2", 40.0, ".i 1\n.o 1\n0 s0 s0 1\n1 s0 s1 0\n", 0);
+ assert LIVE;
+ n = w_render(T_BUF[0..900000]);
+ assert t_has(n, "c.kiss2: 40 bytes. Read in this tab; nothing is sent.");
+ w_drag(0);
+ assert DROP_OVER;
+ assert w_drag(1) == 1;
+ w_click(5);
+ assert PASTE_PUSH;
+ var d : [8]u8 = [0; 8];
+ assert w_value(1, d[0..8]) == 0;
+ assert w_value(1, d[0..8]) == W_NO;
+}
+
+test the_save_name_is_a_slug {
+ t_load();
+ fsm_name_raw(0);
+ const k : u32 = save_name(EFFECT_NAME[0..]);
+ assert same_bytes(EFFECT_NAME[0..], k, "fsm-m.s.svg");
+}
From a05e8227b5e5f98fe770703f046c6743aab47052 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 19:24:32 +0700
Subject: [PATCH 46/63] widgets: catalog records fsm-sketch at PORT_DRAW
(Closes #11016)
---
specs/widgets/catalog.t27 | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index 6762b5ed8d..f996aaaf15 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -37,7 +37,7 @@
; 14667449, lut-explain 1395124, bit-diff 275620, vcd-wrapped 76076, pin-map 180587, synth-smells 87094, fsm-sketch 91657 (its Math.sin/cos/atan2 stay with the host: V8 differs from libm in the last bit), bit-autopsy 96007.
; With these, all 12 pure logic files of the census (2775 lines of JavaScript) exist as .t27.
; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
-; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
+; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) and fsm-sketch at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
; comparisons vs tool.js, 0 mismatches; specs/widgets/race_the_bee.t27: 59, with 13 data variants, 0). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
@@ -91,7 +91,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 3, 2, 2, 3, 1, 3, 3, 3, 2, 3, 1, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
+pub const TOOL_PORT : [28]u8 = [2, 2, 3, 1, 2, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 1, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -227,12 +227,12 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test thirteen_pages_are_native_and_the_remaining_logic_files_are_ported {
- assert count_port(PORT_DRAW) == 13;
- assert count_port(PORT_LOGIC) == 9;
+test fourteen_pages_are_native_and_the_remaining_logic_files_are_ported {
+ assert count_port(PORT_DRAW) == 14;
+ assert count_port(PORT_LOGIC) == 8;
assert count_port(PORT_WORDS) == 6;
assert count_port(PORT_SEALED) == 0;
+ assert TOOL_PORT[6] == PORT_DRAW;
assert TOOL_PORT[5] == PORT_DRAW;
- assert TOOL_PORT[17] == PORT_DRAW;
assert TOOL_PORT[7] == PORT_LOGIC;
}
From ad79175506858333531cb608758d82d74ef80ad5 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 20:34:26 +0700
Subject: [PATCH 47/63] widgets: cost-curves drawn natively -- 36644-byte wasm,
2576 DOM comparisons, 0 differences (Closes #10938)
The trinity spec cost-curves.t27 (three u16 products cast to u32 so zig can evaluate
them) plus the native page: controls, log/linear chart, table, tooltip and halo,
notes, findings, provenance. Every chart coordinate is printed as JavaScript's
String(double) through the host's libm service (w_libm_need / w_libm_give: log2,
log10, pow, shortest digits, and Number() of numbers with more than 15 digits).
t27c test-report 11/11 (7 original constant-only tests are vacuous); t27b
unsupported (reference CSE hoist in w_tipsize). Native-only constants are W_...;
K_DATA_FILES is the one K_ name added.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/cost_curves.t27 | 2467 +++++++++++++++++++++++++++++++++
1 file changed, 2467 insertions(+)
create mode 100644 specs/widgets/cost_curves.t27
diff --git a/specs/widgets/cost_curves.t27 b/specs/widgets/cost_curves.t27
new file mode 100644
index 0000000000..67365ccb56
--- /dev/null
+++ b/specs/widgets/cost_curves.t27
@@ -0,0 +1,2467 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/cost-curves.t27 -- cost curves: what an adder, multiplier, MAC or comparator costs on a 7-series
+; Source of truth for public/widgets/cost-curves/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its
+; own and reads every word below from window.T27_WIDGET.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DRAWS: cost against width for four binary operations (add, multiply, multiply-
+; accumulate, compare) at the widths in K_WIDTHS, each synthesised by yosys
+; `synth_xilinx -family xc7 -flatten` twice: with DSP48E1 blocks allowed and with -nodsp. The
+; metric is chosen by the reader: LUTs (LUT1..LUT6 summed), flip-flops (FDRE/FDCE/FDSE/FDPE),
+; CARRY4 or DSP48E1. A dashed line is the whole XC7A200T, read from prjxray-db's tilegrid.json
+; and cross-checked against nextpnr-xilinx's bel counts on the xc7a200tfbg676-1 chipdb.
+
+; THE TERNARY SIDE, SAID PLAINLY: n trits carry n*log2(3) bits of information, so a 15-trit
+; adder is drawn at x = 23.8 bits beside the 24-bit binary adder. Every ternary design here is
+; binary-encoded, two wires per trit (N=00, Z=01, P=10), and synthesised onto binary LUTs. That
+; is NOT ternary hardware and says nothing about what a native ternary circuit would cost; it
+; says what carrying trits through a binary FPGA costs. The n-trit adder sweep is a reference
+; ripple adder this widget's data script writes (checked exhaustively at 3 trits under Icarus
+; Verilog), because no spec in the corpus is an n-trit adder. The t27 points are real specs
+; from the corpus, compiled to Verilog by public/t27/t27_compiler.wasm. No spec in the corpus
+; is an n-trit multiplier, so there is no ternary multiplier curve; comb_ternary_dot and
+; stream_ternary_mac are 27 one-trit products summed, drawn at their operand information
+; (27 trits = 42.8 bits) beside one 43-bit-wide binary product -- the same information, a
+; different operation, and the page says so beside the point.
+
+; PROVENANCE: scripts/widget-data/cost-curves.mjs writes every module, runs every command and
+; writes data.json: the yosys, iverilog and nextpnr-xilinx versions, every command, the seconds
+; each run took, the full cell map of each run, the spec and compiler sha256, and the findings.
+; A module yosys refuses, or one that synthesises to nothing, is a finding on the page, never a
+; silent gap. The K_ facts below are re-checked against data.json by the same script
+; (`--check`); a spec that disagrees with the data fails it.
+
+; WHAT IT DOES NOT CLAIM: timing, power or routability. These are post-synthesis cell counts,
+; before place and route; the capacity percentage assumes one LUT per LUT6 site, and the packer
+; can put two small LUTs in one site. A different yosys, or Vivado, gives different numbers.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_cost_curves;
+
+use widgets::html_out::OVER;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::ok;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::same_bytes;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::json_scan::is_true;
+use widgets::json_scan::is_false;
+use widgets::json_scan::is_null;
+use widgets::json_scan::num_f64;
+use widgets::json_scan::num_i64;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "cost-curves";
+pub const TITLE : str = "Cost curves: adders, multipliers and MACs on a Xilinx 7-series";
+pub const DESCRIPTION : str = "Real yosys synth_xilinx counts for add, multiply, MAC and compare from 4 to 64 bits, with and without DSPs, against a whole XC7A200T, and t27 ternary specs beside them.";
+pub const IMAGE_ALT : str = "A log-scale chart on black under the headline 9,772 LUTs = 7.26% of an XC7A200T: LUTs against operand width from 4 to 64 bits, with -nodsp. The binary multiplier climbs to 9,772 LUTs, the binary-encoded ternary adder reaches 480 at 40 trits, the binary adder 64, all under a dashed line at the 134,600 LUT6 sites of an XC7A200T.";
+pub const HOOK : str = "A 64x64 multiplier with -nodsp costs 9,772 LUTs, 7.26% of an XC7A200T. Let it use DSPs and it is 16 DSP48E1 and 243 LUTs.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["yosys synth_xilinx -family xc7 -flatten, with and without -nodsp, on Verilog written by scripts/widget-data/cost-curves.mjs; versions and every command in data.json", "specs/ternary/ternary_full_adder.t27, ternary_ripple_adder.t27, comb_ternary_dot.t27, stream_ternary_mac.t27 and specs/base/ternary_add.t27, compiled by public/t27/t27_compiler.wasm", "prjxray-db artix7/xc7a200t/tilegrid.json: SLICEL, SLICEM and DSP48E1 site counts", "nextpnr-xilinx --pack-only on the xc7a200tfbg676-1 chipdb: SLICE_LUTX, SLICE_FFX, CARRY4 and DSP48E1 bel counts", "Icarus Verilog: the reference n-trit adder checked on all 729 input pairs at 3 trits"];
+pub const READS_LOCAL_FILES : bool = false;
+pub const SENDS_NOTHING : bool = true;
+
+; --- What is measured (the data script reads these) ---------------------------------------------
+pub const K_DATA_JSON : str = "data.json";
+pub const K_OPS : [4]str = ["add", "mul", "mac", "cmp"];
+pub const K_WIDTHS : [8]u16 = [4, 8, 12, 16, 24, 32, 48, 64];
+; Trit counts chosen so n*log2(3) lands near each binary width: 3 -> 4.8, ..., 40 -> 63.4 bits.
+pub const K_TRIT_COUNTS : [8]u16 = [3, 5, 8, 10, 15, 20, 30, 40];
+; log2(3) in thousandths, rounded: 1.58496...
+pub const K_LOG2_3_MILLI : u16 = 1585;
+; The t27 specs synthesised, under public/t27/files/specs/, with the chart each lands on, its
+; operand information in thousandths of a bit, and the binary twin written beside it ("none" when
+; the twin is the binary curve itself).
+pub const K_T27_SPECS : [5]str = ["ternary/ternary_full_adder.t27", "ternary/ternary_ripple_adder.t27", "base/ternary_add.t27", "ternary/comb_ternary_dot.t27", "ternary/stream_ternary_mac.t27"];
+pub const K_T27_OPS : [5]str = ["add", "add", "add", "mul", "mac"];
+pub const K_T27_INFO_MILLIBITS : [5]u32 = [1000, 2000, 1585, 42794, 42794];
+; Trits per operand where the spec's operands are trits (0 where they are bits carried as trits).
+pub const K_T27_TRITS : [5]u16 = [0, 0, 1, 27, 27];
+pub const K_T27_TWINS : [5]str = ["fa1", "add2", "none", "none", "none"];
+; The synthesis budget: wall-clock seconds for every yosys run together (about 15 minutes).
+pub const K_SYNTH_BUDGET_S : u16 = 900;
+
+; --- How it draws -------------------------------------------------------------------------------
+pub const K_MIN_TARGET_PX : u8 = 32;
+; The page opens on this entry of K_OPS (multiply), with -nodsp and LUTs: the chart the card shows.
+pub const K_START_OP : u8 = 1;
+pub const K_CHART_H_PX : u16 = 340;
+
+; --- Facts from the run (scripts/widget-data/cost-curves.mjs re-checks every one against data.json) ---
+; LUTs (LUT1..LUT6 summed) and CARRY4 of the binary adder at each K_WIDTHS, with -nodsp.
+pub const K_ADD_LUTS : [8]u16 = [4, 8, 12, 16, 24, 32, 48, 64];
+pub const K_ADD_CARRY4 : [8]u16 = [2, 3, 4, 5, 7, 9, 13, 17];
+; LUTs of the reference balanced-ternary adder at each K_TRIT_COUNTS, with -nodsp.
+pub const K_TADD_LUTS : [8]u16 = [36, 60, 96, 120, 180, 240, 360, 480];
+; LUTs of the binary multiplier with -nodsp, and DSP48E1 blocks of the multiplier and the MAC
+; with DSPs allowed, at each K_WIDTHS.
+pub const K_MUL_NODSP_LUTS : [8]u16 = [24, 130, 278, 528, 1312, 2322, 5642, 9772];
+pub const K_MUL_DSP48 : [8]u16 = [0, 1, 1, 1, 2, 4, 9, 16];
+pub const K_MAC_DSP48 : [8]u16 = [0, 1, 1, 1, 2, 4, 9, 16];
+; The hook: the 64x64 multiplier with -nodsp, and its share of the device's LUT6 sites in
+; hundredths of a percent, truncated.
+pub const K_HOOK_LUTS : u16 = 9772;
+pub const K_HOOK_BASIS_POINTS : u16 = 726;
+; The t27 specs, with -nodsp, and the binary twin of each adder.
+pub const K_T27_FA_LUTS : u16 = 52;
+pub const K_TWIN_FA1_LUTS : u16 = 2;
+pub const K_T27_RIPPLE_LUTS : u16 = 101;
+pub const K_TWIN_ADD2_LUTS : u16 = 2;
+pub const K_COMB_DOT_LUTS : u16 = 317;
+pub const K_STREAM_MAC_LUTS : u16 = 346;
+pub const K_STREAM_MAC_FFS : u16 = 32;
+pub const K_STREAM_MAC_DSP48 : u16 = 0;
+; The XC7A200T: slices from prjxray-db (SLICEL + SLICEM sites), the rest derived per slice and
+; matched against nextpnr-xilinx's bel counts (two SLICE_LUTX bels per LUT6 site).
+pub const K_CAP_SLICES : u16 = 33650;
+pub const K_CAP_LUT6 : u32 = 134600;
+pub const K_CAP_FF : u32 = 269200;
+pub const K_CAP_CARRY4 : u16 = 33650;
+pub const K_CAP_DSP : u16 = 740;
+pub const K_CAP_LUT_BELS : u32 = 269200;
+; Runs yosys refused, and runs that synthesised to no cells. Both are findings on the page.
+pub const K_REJECTED : u16 = 2;
+pub const K_EMPTY : u16 = 0;
+
+; --- Every word on the screen -------------------------------------------------------------------
+; {placeholders} are filled by tool.js from data.json; nothing else is added.
+pub const SAY_OP_LABEL : str = "Operation";
+pub const SAY_OPS : [4]str = ["Add", "Multiply", "MAC", "Compare"];
+pub const SAY_OP_TITLES : [4]str = ["a + b: W-bit operands, W+1 result bits, no clock", "a * b: W-bit unsigned operands, 2W result bits, no clock", "acc <= acc + a * b: 2W-bit accumulator, clock enable, synchronous reset", "a < b: W-bit unsigned operands, one result bit"];
+pub const SAY_METRIC_LABEL : str = "Count";
+pub const SAY_METRICS : [4]str = ["LUTs", "FFs", "CARRY4", "DSP"];
+pub const SAY_METRIC_LONG : [4]str = ["LUTs (LUT1..LUT6)", "flip-flops (FDRE, FDCE, FDSE, FDPE)", "CARRY4 carry blocks", "DSP48E1 blocks"];
+pub const SAY_DSP_LABEL : str = "DSP48E1";
+pub const SAY_DSP_ON : str = "allowed";
+pub const SAY_DSP_OFF : str = "-nodsp";
+pub const SAY_DSP_ON_TITLE : str = "synth_xilinx may map multiplies into DSP48E1 blocks";
+pub const SAY_DSP_OFF_TITLE : str = "synth_xilinx -nodsp: everything in LUTs, carry chains and flip-flops";
+pub const SAY_SCALE_LABEL : str = "Scale";
+pub const SAY_SCALE_LOG : str = "log";
+pub const SAY_SCALE_LIN : str = "linear";
+pub const SAY_VIEW_LABEL : str = "View";
+pub const SAY_VIEW_CHART : str = "Chart";
+pub const SAY_VIEW_TABLE : str = "Table";
+pub const SAY_X_AXIS : str = "operand width, bits (n trits drawn at n x log2(3) bits)";
+pub const SAY_Y_AXIS : str = "{metric}, {scale} scale";
+pub const SAY_SERIES : [4]str = ["binary {op}", "ternary adder, 2 wires per trit (reference)", "t27 spec, compiled to Verilog", "binary twin of a t27 adder"];
+pub const SAY_CAP : str = "XC7A200T: {n} {unit}";
+pub const SAY_CAP_ABOVE : str = "XC7A200T: {n} {unit}, far above this chart (switch to log)";
+pub const SAY_CAP_UNITS : [4]str = ["LUT6 sites", "flip-flops", "CARRY4", "DSP48E1"];
+pub const SAY_CHART_LABEL : str = "Cost against operand width. Hover or tap near a point for its exact cell counts.";
+pub const SAY_HINT : str = "hover or tap a point: exact counts";
+pub const SAY_TIP_BITS : str = "{bits}-bit operands";
+pub const SAY_TIP_TRITS : str = "{trits} trits = {bits} bits of information";
+pub const SAY_TIP_SHARE : str = "{pct}% of the XC7A200T's {unit}";
+pub const SAY_TIP_SECONDS : str = "yosys {s} s";
+pub const SAY_TIP_SPEC : str = "{spec} (module {top})";
+pub const SAY_COLS : [11]str = ["Design", "Bits", "Trits", "LUT", "FF", "CARRY4", "DSP48E1", "MUXF7", "MUXF8", "Share", "yosys s"];
+pub const SAY_SHARE_TITLE : str = "Share of the XC7A200T for the chosen count";
+pub const SAY_REFUSED : str = "refused";
+pub const SAY_NONE : str = "-";
+pub const SAY_NOTE_TITLE : str = "What is compared";
+pub const SAY_NOTES : [4]str = ["Binary: one LUT per bit plus a CARRY4 per four bits. Ternary reference: n trits at n x log2(3) bits of information, each trit two wires, decoded, added and re-encoded in binary LUTs: {tadd} LUTs for 15 trits against {add} for 24 bits. The t27 adder specs carry each trit in a u8 port and compute binary sums through sign neurons: {fa} LUTs for a one-bit full adder whose binary twin is {fa_twin}.", "Binary: one W x W product. The t27 point is comb_ternary_dot.t27: 27 one-trit products summed, drawn at its operand information (27 trits = 42.8 bits). Same information, a different operation: it multiplies nothing wider than a trit. Its own header says about 160 LUT6; this run counts {dot} LUTs.", "Binary: acc + a x b into a 2W-bit register. The t27 point is stream_ternary_mac.t27: one 27-trit dot product per clock into a 32-bit accumulator, drawn at 42.8 bits. Same operand information, a different operation.", "Binary only: a < b, one carry chain. No t27 spec in the corpus is a comparator, so there is no ternary point."];
+pub const SAY_HONEST : str = "Every ternary design here is binary-encoded and synthesised onto binary LUTs. That is not ternary hardware, and it says nothing about a native ternary circuit; it says what carrying trits through a binary FPGA costs. No t27 spec is an n-trit multiplier, so there is no ternary multiplier curve.";
+pub const SAY_PACK_NOTE : str = "Shares assume one LUT per LUT6 site; the packer can put two small LUTs in one. These are post-synthesis cell counts, before place and route: no timing, no power.";
+pub const SAY_FINDINGS_TITLE : str = "Findings";
+pub const SAY_FINDING_REJECTED : str = "{spec}: yosys refused the Verilog the compiler generated ({mode}). {detail}. The line: {line}";
+pub const SAY_FINDING_NO_PORTS : str = "{spec}: the generated module {top} has only clk, rst_n, en and ready, so no value can cross its boundary; even parsed, it would synthesise to nothing.";
+pub const SAY_FINDING_EMPTY : str = "{id}: synthesised to no logic cells.";
+pub const SAY_FINDING_OTHER : str = "{spec}: {detail}";
+pub const SAY_PROV_TITLE : str = "Where the numbers come from";
+pub const SAY_PROV_TOOLS : str = "{yosys}. {iverilog}. nextpnr-xilinx {nextpnr}.";
+pub const SAY_PROV_SYNTH : str = "Every module: {synth}, once with DSPs allowed and once with -nodsp. {runs} runs, {sum} s of yosys, {wall} s wall clock on {jobs} jobs.";
+pub const SAY_PROV_CAP : str = "Capacity: prjxray-db {file} lists {slicel} SLICEL + {slicem} SLICEM = {slices} slices (4 LUT6, 8 flip-flops, 1 CARRY4 each) and {dsp} DSP48E1. nextpnr-xilinx --pack-only on {chipdb} agrees: {lutx} SLICE_LUTX bels (2 per LUT6), {ffx} SLICE_FFX, {carry} CARRY4, {dspn} DSP48E1.";
+pub const SAY_PROV_CHECK : str = "The reference ternary adder was checked on all {cases} input pairs at {trits} trits under Icarus Verilog before synthesis: {verdict}.";
+pub const SAY_PASS : str = "all correct";
+pub const SAY_PROV_SPEC : str = "Spec {spec}, sha256 {sha}. Compiler {compiler}, sha256 {csha}. Run at {at}.";
+pub const SAY_COMMANDS : str = "Every command ({n})";
+pub const SAY_LOADING : str = "Reading the synthesis results...";
+pub const SAY_ERR_LOAD : str = "The synthesis results did not load: {why}";
+
+; --- Tests: each holds of this file, and scripts/widget-data/cost-curves.mjs runs them again with
+; every K_ fact replaced by the value computed from data.json.
+test add_luts_grow_with_width {
+ assert K_ADD_LUTS[1] > K_ADD_LUTS[0];
+ assert K_ADD_LUTS[2] > K_ADD_LUTS[1];
+ assert K_ADD_LUTS[3] > K_ADD_LUTS[2];
+ assert K_ADD_LUTS[4] > K_ADD_LUTS[3];
+ assert K_ADD_LUTS[5] > K_ADD_LUTS[4];
+ assert K_ADD_LUTS[6] > K_ADD_LUTS[5];
+ assert K_ADD_LUTS[7] > K_ADD_LUTS[6];
+}
+
+test nodsp_multiplier_luts_grow_with_width {
+ assert K_MUL_NODSP_LUTS[1] > K_MUL_NODSP_LUTS[0];
+ assert K_MUL_NODSP_LUTS[2] > K_MUL_NODSP_LUTS[1];
+ assert K_MUL_NODSP_LUTS[3] > K_MUL_NODSP_LUTS[2];
+ assert K_MUL_NODSP_LUTS[4] > K_MUL_NODSP_LUTS[3];
+ assert K_MUL_NODSP_LUTS[5] > K_MUL_NODSP_LUTS[4];
+ assert K_MUL_NODSP_LUTS[6] > K_MUL_NODSP_LUTS[5];
+ assert K_MUL_NODSP_LUTS[7] > K_MUL_NODSP_LUTS[6];
+}
+
+test capacity_is_counted_per_slice {
+ assert K_CAP_LUT6 == 4 * (K_CAP_SLICES as u32);
+ assert K_CAP_FF == 8 * (K_CAP_SLICES as u32);
+ assert K_CAP_CARRY4 == K_CAP_SLICES;
+ assert K_CAP_LUT_BELS == 2 * K_CAP_LUT6;
+}
+
+test hook_is_the_widest_nodsp_multiplier {
+ assert K_HOOK_LUTS == K_MUL_NODSP_LUTS[7];
+ assert (K_HOOK_BASIS_POINTS as u32) == (K_HOOK_LUTS as u32) * 10000 / K_CAP_LUT6;
+ assert K_MUL_DSP48[7] == 16;
+ assert K_WIDTHS[7] == 64;
+}
+
+test trit_counts_carry_the_bit_widths {
+ assert K_TRIT_COUNTS[0] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[0];
+ assert K_TRIT_COUNTS[0] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[0];
+ assert K_TRIT_COUNTS[1] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[1];
+ assert K_TRIT_COUNTS[1] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[1];
+ assert K_TRIT_COUNTS[2] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[2];
+ assert K_TRIT_COUNTS[2] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[2];
+ assert K_TRIT_COUNTS[3] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[3];
+ assert K_TRIT_COUNTS[3] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[3];
+ assert K_TRIT_COUNTS[4] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[4];
+ assert K_TRIT_COUNTS[4] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[4];
+ assert K_TRIT_COUNTS[5] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[5];
+ assert K_TRIT_COUNTS[5] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[5];
+ assert K_TRIT_COUNTS[6] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[6];
+ assert K_TRIT_COUNTS[6] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[6];
+ assert K_TRIT_COUNTS[7] * K_LOG2_3_MILLI / 1000 <= K_WIDTHS[7];
+ assert K_TRIT_COUNTS[7] * K_LOG2_3_MILLI / 1000 + 1 >= K_WIDTHS[7];
+ assert K_T27_INFO_MILLIBITS[3] <= 27 * K_LOG2_3_MILLI;
+ assert K_T27_INFO_MILLIBITS[3] + 1 >= 27 * K_LOG2_3_MILLI;
+ assert K_T27_INFO_MILLIBITS[4] == K_T27_INFO_MILLIBITS[3];
+}
+
+test binary_encoded_trits_cost_more_luts_at_equal_information {
+ assert K_TADD_LUTS[0] > K_ADD_LUTS[0];
+ assert K_TADD_LUTS[1] > K_ADD_LUTS[1];
+ assert K_TADD_LUTS[2] > K_ADD_LUTS[2];
+ assert K_TADD_LUTS[3] > K_ADD_LUTS[3];
+ assert K_TADD_LUTS[4] > K_ADD_LUTS[4];
+ assert K_TADD_LUTS[5] > K_ADD_LUTS[5];
+ assert K_TADD_LUTS[6] > K_ADD_LUTS[6];
+ assert K_TADD_LUTS[7] > K_ADD_LUTS[7];
+ assert K_T27_FA_LUTS > K_TWIN_FA1_LUTS;
+ assert K_T27_RIPPLE_LUTS > K_TWIN_ADD2_LUTS;
+}
+
+test the_stream_mac_uses_no_dsp {
+ assert K_STREAM_MAC_DSP48 == 0;
+ assert K_STREAM_MAC_FFS == 32;
+ assert K_REJECTED + K_EMPTY > 0;
+}
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/cost-curves/tool.js (273 lines of hand-written JavaScript).
+; The words above are unchanged and are the only words the view prints. Host protocol (jtag_verdict.t27's
+; plus, all optional, the host calls what the wasm exports):
+; w_init(hash) the page opens: state "loading"
+; w_theme(bytes) the nine colours of the shared frame, one per line, as getComputedStyle gives
+; them (--w-bg --w-panel --w-line --w-green --w-gold --w-red --w-text --w-muted
+; --w-subtle), trimmed, empty where a variable is not set (tool.js writes them
+; into the SVG as they come)
+; w_size(px, root_px, inner_h, embed) the width of #widget (root_px, 0 when not laid out), the window height
+; and whether the page is embedded; the host calls it after every draw and when the
+; page is resized (tool.js's ResizeObserver); 1 means draw again
+; w_data(i, bytes) data.json (K_DATA_FILES) has been fetched; w_data_failed(i, message) when it
+; could not be: the message is what tool.js shows, "HTTP 404" or the engine's own
+; w_click(i) button i was pressed (index among the page's buttons)
+; w_pointer(kind, x, y, bw, bh) the pointer over the chart: kind 0 moved or went down, 1 left (mouse); x and y
+; are relative to the SVG's box (getBoundingClientRect), bw and bh its size
+; w_tipsize(w, h) the tooltip is on screen and is w by h (offsetWidth, offsetHeight); 1: draw again
+; w_render(dst) -> len the page's HTML (0 on overflow); the host draws again while
+; w_libm_need(dst) -> n says n calls were missing in that pass: n records of three f64, op, a, b, with op
+; 0 sin 1 cos 2 atan2 3 log2 4 log10 5 pow 6 7 8 the shortest digits of a
+; (6: digits div 1e8, 7: digits mod 1e8, 8: the position of the decimal point),
+; which the host takes from Number.prototype.toExponential(); the host answers with
+; w_libm_give(src, n) the n results in order, and the pass is repeated until none is missing.
+; Why the libm service: Math.log2, Math.log10 and ** of the engine are not the portable libm's, and the chart
+; prints every coordinate as String(double), shortest digits that read back, which only the engine knows.
+; --
+; What is NOT carried over from tool.js (also reported in the commit message):
+; * data.json above 262144 bytes, 512 results, 64 findings, 64 specs or 2048 commands show the loading page;
+; a data.json that is JSON but lacks results, capacity, specs, findings, tools, timing, adder_check, spec,
+; compiler, commands, synth or generated_at (tool.js throws and leaves "Reading ...") also shows it;
+; * the tooltip is placed from the size the host reports after the content is drawn (tool.js reads
+; offsetWidth in the same turn), so in a real browser its first position can differ by a pixel or two;
+; * no non-ASCII case mapping (toLowerCase of the operation name).
+; --
+
+pub const K_DATA_FILES : [1]str = ["./data.json"];
+
+pub const W_CAP_DATA : u32 = 262144;
+pub const W_MAX_RES : u32 = 512;
+pub const W_MAX_FIND : u32 = 64;
+pub const W_MAX_SPECS : u32 = 64;
+pub const W_NO : u32 = 4294967295;
+
+pub var DATA : [262144]u8 = [0; 262144];
+pub var DATA_LEN : u32 = 0;
+pub var D_STATE : u8 = 0;
+pub var ERRMSG : [512]u8 = [0; 512];
+pub var ERRMSG_LEN : u32 = 0;
+pub var READY : bool = false;
+pub var STUCK : bool = false;
+
+// the data, read once
+pub var N_RES : u32 = 0;
+pub var R_FAM : [512]u8 = [0; 512];
+pub var R_OP : [512]u8 = [0; 512];
+pub var R_DSP : [512]u8 = [0; 512];
+pub var R_OK : [512]u8 = [0; 512];
+pub var R_BITS : [512]f64 = [0.0; 512];
+pub var R_TRITS : [512]f64 = [0.0; 512];
+pub var R_HAS_TRITS : [512]u8 = [0; 512];
+pub var R_SEC : [512]f64 = [0.0; 512];
+pub var R_MET : [3072]f64 = [0.0; 3072];
+pub var R_TOP : [512]i64 = [0; 512];
+pub var R_SPEC : [512]i64 = [0; 512];
+pub var R_ID : [512]i64 = [0; 512];
+pub var R_EL : [512]i64 = [0; 512];
+pub var CAP_V : [4]f64 = [0.0; 4];
+pub var J_RES : i64 = 0;
+pub var J_CAP : i64 = 0;
+pub var J_SPECS : i64 = 0;
+pub var J_FIND : i64 = 0;
+pub var J_TOOLS : i64 = 0;
+pub var J_TIMING : i64 = 0;
+pub var J_CHECK : i64 = 0;
+pub var J_SPEC : i64 = 0;
+pub var J_COMP : i64 = 0;
+pub var J_CMDS : i64 = 0;
+pub var J_ROOT : i64 = 0;
+
+// the page state
+pub var OP : u32 = 0;
+pub var METRIC : u32 = 0;
+pub var DSP : bool = false;
+pub var LOGSCALE : bool = true;
+pub var TABLE : bool = false;
+pub var WROOT : f64 = 0.0;
+pub var LAST_RW : f64 = 0.0;
+pub var INNER_H : f64 = 0.0;
+pub var EMBED : bool = false;
+pub var THEME : [288]u8 = [0; 288];
+pub var THEME_L : [9]u32 = [0; 9];
+
+// hover state of the chart (reset by every render the events cause)
+pub var HOVER_ON : bool = false;
+pub var HOVER_MARK : i32 = -1;
+pub var TIP_SHOWN : bool = false;
+pub var TIP_MARK : i32 = -1;
+pub var TIP_STYLE : bool = false;
+pub var TIP_LEFT : f64 = 0.0;
+pub var TIP_TOP : f64 = 0.0;
+pub var TIP_PENDING : bool = false;
+pub var BOX_W : f64 = 0.0;
+pub var BOX_H : f64 = 0.0;
+pub var HALO_X : f64 = 0.0;
+pub var HALO_Y : f64 = 0.0;
+pub var HALO_ON : bool = false;
+pub var HALO_SET : bool = false;
+
+// the marks of the chart as last drawn: for the pointer
+pub var MK_R : [512]u32 = [0; 512];
+pub var MK_S : [512]u8 = [0; 512];
+pub var MK_X : [512]f64 = [0.0; 512];
+pub var MK_Y : [512]f64 = [0.0; 512];
+pub var MK_N : u32 = 0;
+pub var CH_W : f64 = 0.0;
+pub var CH_H : f64 = 0.0;
+
+// the libm service
+pub var LB_OP : [16384]u8 = [0; 16384];
+pub var LB_A : [16384]f64 = [0.0; 16384];
+pub var LB_B : [16384]f64 = [0.0; 16384];
+pub var LB_R : [16384]f64 = [0.0; 16384];
+pub var LB_N : u32 = 0;
+pub var RQ_OP : [16384]u8 = [0; 16384];
+pub var RQ_A : [16384]f64 = [0.0; 16384];
+pub var RQ_B : [16384]f64 = [0.0; 16384];
+pub var RQ_N : u32 = 0;
+pub var RQ_SENT : u32 = 0;
+pub var MISSING : bool = false;
+
+// text scratch
+pub var FB : [65536]u8 = [0; 65536];
+pub var FB_N : u32 = 0;
+pub var FK_O : [32]u32 = [0; 32];
+pub var FK_L : [32]u32 = [0; 32];
+pub var FV_O : [32]u32 = [0; 32];
+pub var FV_L : [32]u32 = [0; 32];
+pub var F_N : u32 = 0;
+pub var TB : [4096]u8 = [0; 4096];
+pub var TB2 : [4096]u8 = [0; 4096];
+pub var NB : [64]u8 = [0; 64];
+
+// ------------------------------------------------------------------------------------------------
+// numbers, the libm service
+// ------------------------------------------------------------------------------------------------
+
+pub var R_MHAS : [3072]u8 = [0; 3072];
+pub var SM : u64 = 0;
+pub var SK : u32 = 0;
+
+fn pow10f(k: u32) -> f64 {
+ var p : f64 = 1.0;
+ var i : u32 = 0;
+ while (i < k) {
+ p = p * 10.0;
+ i = i + 1;
+ }
+ return p;
+}
+
+fn jsround(x: f64) -> f64 {
+ const r : f64 = @floor(x);
+ if (x - r >= 0.5) { return r + 1.0; }
+ return r;
+}
+
+fn neg_zero(x: f64) -> bool {
+ if (x != 0.0) { return false; }
+ var y : f64 = x;
+ return 1.0 / y < 0.0;
+}
+
+fn inf() -> f64 {
+ var z : f64 = 0.0;
+ return 1.0 / z;
+}
+
+fn jsmax(a: f64, b: f64) -> f64 {
+ if (a != a) { return a; }
+ if (b != b) { return b; }
+ if (a > b) { return a; }
+ return b;
+}
+
+fn jsmin(a: f64, b: f64) -> f64 {
+ if (a != a) { return a; }
+ if (b != b) { return b; }
+ if (a < b) { return a; }
+ return b;
+}
+
+/// The shortest decimal that reads back as v (v >= 0, finite, below 2^53, not a whole number): SM, SK.
+fn shortest_dec(v: f64) -> bool {
+ var k : u32 = 0;
+ while (k <= 20) {
+ const p : f64 = pow10f(k);
+ const m : f64 = jsround(v * p);
+ if (m >= 9007199254740992.0) { return false; }
+ if (m / p == v) {
+ SM = m as u64;
+ SK = k;
+ return true;
+ }
+ k = k + 1;
+ }
+ return false;
+}
+
+fn same_f64(a: f64, b: f64) -> bool {
+ if (a != a) { return b != b; }
+ if (a == b) {
+ if (a == 0.0) { return neg_zero(a) == neg_zero(b); }
+ return true;
+ }
+ return false;
+}
+
+/// A libm call: the answer when the host has given it, else 0.0 and the call is queued for the host.
+fn libm(op: u32, a: f64, b: f64) -> f64 {
+ var i : u32 = 0;
+ while (i < LB_N) {
+ if ((LB_OP[i as usize] as u32) == op and same_f64(LB_A[i as usize], a) and same_f64(LB_B[i as usize], b)) { return LB_R[i as usize]; }
+ i = i + 1;
+ }
+ MISSING = true;
+ var j : u32 = 0;
+ while (j < RQ_N) {
+ if ((RQ_OP[j as usize] as u32) == op and same_f64(RQ_A[j as usize], a) and same_f64(RQ_B[j as usize], b)) { return 0.0; }
+ j = j + 1;
+ }
+ if (RQ_N < 16384) {
+ RQ_OP[RQ_N as usize] = op as u8;
+ RQ_A[RQ_N as usize] = a;
+ RQ_B[RQ_N as usize] = b;
+ RQ_N = RQ_N + 1;
+ }
+ return 0.0;
+}
+
+pub fn w_libm_need(dst: []f64) -> u32 {
+ if (RQ_N == 0) { return 0; }
+ var k : u32 = 0;
+ while (k < RQ_N and ((k * 3 + 2) as usize) < dst.len) {
+ dst[(k * 3) as usize] = RQ_OP[k as usize] as f64;
+ dst[(k * 3 + 1) as usize] = RQ_A[k as usize];
+ dst[(k * 3 + 2) as usize] = RQ_B[k as usize];
+ k = k + 1;
+ }
+ RQ_SENT = k;
+ return k;
+}
+
+pub fn w_libm_give(src: []const f64, n: u32) -> u32 {
+ var k : u32 = 0;
+ while (k < n and k < RQ_SENT and (k as usize) < src.len) {
+ if (LB_N >= 16384) { LB_N = 0; }
+ LB_OP[LB_N as usize] = RQ_OP[k as usize];
+ LB_A[LB_N as usize] = RQ_A[k as usize];
+ LB_B[LB_N as usize] = RQ_B[k as usize];
+ LB_R[LB_N as usize] = src[k as usize];
+ LB_N = LB_N + 1;
+ k = k + 1;
+ }
+ RQ_N = 0;
+ RQ_SENT = 0;
+ MISSING = false;
+ return 0;
+}
+
+fn log2f(v: f64) -> f64 { return libm(3, v, 0.0); }
+fn log10f(v: f64) -> f64 { return libm(4, v, 0.0); }
+fn powf(a: f64, b: f64) -> f64 { return libm(5, a, b); }
+
+/// String(v): whole numbers below 2^53 directly; every other double through the host's shortest digits.
+fn put_js(o: []u8, n0: u32, v0: f64) -> u32 {
+ var n : u32 = n0;
+ if (v0 != v0) { return raw(o, n, "NaN"); }
+ if (v0 == inf()) { return raw(o, n, "Infinity"); }
+ if (v0 == 0.0 - inf()) { return raw(o, n, "-Infinity"); }
+ if (v0 == 0.0) { return put(o, n, 48); }
+ var v : f64 = v0;
+ if (v < 0.0) {
+ n = put(o, n, 45);
+ v = 0.0 - v0;
+ }
+ if (v == @floor(v) and v < 9007199254740992.0) { return num(o, n, v as u64); }
+ const hi : f64 = libm(6, v, 0.0);
+ const lo : f64 = libm(7, v, 0.0);
+ const pos : f64 = libm(8, v, 0.0);
+ if (MISSING) { return n; }
+ const m : u64 = (hi as u64) * 100000000 + (lo as u64);
+ var dg : [24]u8 = [0; 24];
+ const k : u32 = dec_to(dg[0..], m);
+ const p : i64 = pos as i64;
+ var i : u32 = 0;
+ if (p >= (k as i64) and p <= 21) {
+ while (i < k) { n = put(o, n, dg[i as usize]); i = i + 1; }
+ var z : i64 = p - (k as i64);
+ while (z > 0) { n = put(o, n, 48); z = z - 1; }
+ return n;
+ }
+ if (p > 0 and p <= 21) {
+ while (i < k) {
+ if ((i as i64) == p) { n = put(o, n, 46); }
+ n = put(o, n, dg[i as usize]);
+ i = i + 1;
+ }
+ return n;
+ }
+ if (p > -6 and p <= 0) {
+ n = raw(o, n, "0.");
+ var z : i64 = 0 - p;
+ while (z > 0) { n = put(o, n, 48); z = z - 1; }
+ while (i < k) { n = put(o, n, dg[i as usize]); i = i + 1; }
+ return n;
+ }
+ n = put(o, n, dg[0]);
+ if (k > 1) {
+ n = put(o, n, 46);
+ i = 1;
+ while (i < k) { n = put(o, n, dg[i as usize]); i = i + 1; }
+ }
+ n = put(o, n, 101);
+ var e : i64 = p - 1;
+ if (e < 0) {
+ n = put(o, n, 45);
+ e = 0 - e;
+ } else {
+ n = put(o, n, 43);
+ }
+ return num(o, n, e as u64);
+}
+
+/// Number(v).toLocaleString('en-US'): groups of three, at most three decimals, "-0" for a negative zero.
+fn put_locale(o: []u8, n0: u32, v0: f64) -> u32 {
+ var n : u32 = n0;
+ if (v0 != v0) { return raw(o, n, "NaN"); }
+ var v : f64 = v0;
+ if (v0 < 0.0 or neg_zero(v0)) {
+ n = put(o, n, 45);
+ v = 0.0 - v0;
+ if (v0 == 0.0) { v = 0.0; }
+ }
+ if (v == inf()) {
+ n = put(o, n, 226);
+ n = put(o, n, 136);
+ return put(o, n, 158);
+ }
+ if (v == @floor(v) and v < 9007199254740992.0) { return num_grouped(o, n, v as u64); }
+ var m : u64 = 0;
+ var k : u32 = 0;
+ if (v < 9007199254740992.0 and shortest_dec(v)) {
+ m = SM;
+ k = SK;
+ } else {
+ if (v >= 9007199254740.0) { return num_grouped(o, n, v as u64); }
+ // a double with more than 15 digits: three decimals of its value
+ m = jsround(v * 1000.0) as u64;
+ k = 3;
+ }
+ while (k > 3) {
+ const q : u64 = m / 10;
+ const r : u64 = m % 10;
+ m = q;
+ if (k == 4 and r >= 5) { m = m + 1; }
+ k = k - 1;
+ }
+ while (k > 0 and m % 10 == 0) {
+ m = m / 10;
+ k = k - 1;
+ }
+ var p : u64 = 1;
+ var i : u32 = 0;
+ while (i < k) {
+ p = p * 10;
+ i = i + 1;
+ }
+ n = num_grouped(o, n, m / p);
+ if (k > 0) {
+ n = put(o, n, 46);
+ var f : u64 = m % p;
+ var d : u32 = 0;
+ var div : u64 = p / 10;
+ while (d < k) {
+ n = put(o, n, ((f / div) as u8) + 48);
+ f = f % div;
+ if (div > 1) { div = div / 10; }
+ d = d + 1;
+ }
+ }
+ return n;
+}
+
+/// A CSS length number as the CSSOM writes it back (Chrome, measured): 6 significant digits, no trailing zeros.
+/// Only for v >= 1 (the tooltip's left and top are at least 4).
+fn put_css6(o: []u8, n0: u32, v: f64) -> u32 {
+ if (v >= 1000000.0 or v < 1.0) { return put_js(o, n0, v); }
+ var e : u32 = 0;
+ while (e < 6 and v >= pow10f(e + 1)) { e = e + 1; }
+ const k : u32 = 5 - e;
+ var m : u64 = jsround(v * pow10f(k)) as u64;
+ var ee : u32 = e;
+ if (m >= 1000000) {
+ m = 100000;
+ ee = e + 1;
+ }
+ var dg : [8]u8 = [0; 8];
+ const len : u32 = dec_to(dg[0..], m);
+ var n : u32 = n0;
+ var i : u32 = 0;
+ // the digits, the point after ee + 1 of them, a fraction without trailing zeros
+ var last : u32 = len;
+ while (last > ee + 1 and dg[(last - 1) as usize] == 48) { last = last - 1; }
+ while (i < last) {
+ if (i == ee + 1) { n = put(o, n, 46); }
+ n = put(o, n, dg[i as usize]);
+ i = i + 1;
+ }
+ return n;
+}
+
+fn put_r(o: []u8, n0: u32, v: f64, d: u32) -> u32 {
+ var p : f64 = 10.0;
+ if (d == 3) { p = 1000.0; }
+ return put_js(o, n0, jsround(v * p) / p);
+}
+
+// ------------------------------------------------------------------------------------------------
+// text and JSON helpers
+// ------------------------------------------------------------------------------------------------
+
+fn copy_to(dst: []u8, src: []const u8, cap: u32) -> u32 {
+ var n : u32 = src.len as u32;
+ if (n > cap) { n = cap; }
+ var k : u32 = 0;
+ while (k < n) {
+ dst[k as usize] = src[k as usize];
+ k = k + 1;
+ }
+ return n;
+}
+
+fn dsl() -> []const u8 {
+ return DATA[0..(DATA_LEN as usize)];
+}
+
+fn jstr_is(sp: i64, lit: []const u8) -> bool {
+ const d : []const u8 = dsl();
+ if (kind(d, sp) != J_STRING) { return false; }
+ if (str_len(d, sp) != (lit.len as u32)) { return false; }
+ var k : usize = 0;
+ while (k < lit.len) {
+ if (str_byte(d, sp, k as u32) != lit[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// Number(token): up to 15 significant digits and a small exponent through json_scan's reader (one correctly
+/// rounded division); anything else through the host, which gets the first 17 digits and the exponent (op 9).
+fn jnum(sp: i64) -> f64 {
+ const d : []const u8 = dsl();
+ if (kind(d, sp) != J_NUMBER) { return 0.0; }
+ var i : usize = span_lo(sp);
+ const hi : usize = span_hi(sp);
+ var neg : bool = false;
+ if (i < hi and d[i] == 45) { neg = true; i = i + 1; }
+ var dg : [64]u8 = [0; 64];
+ var cnt : u32 = 0;
+ var point : i64 = 0;
+ var seen_point : bool = false;
+ var lead : bool = true;
+ var frac_digits : i64 = 0;
+ while (i < hi and (is_dg(d[i]) or d[i] == 46)) {
+ if (d[i] == 46) {
+ seen_point = true;
+ } else {
+ if (seen_point) { frac_digits = frac_digits + 1; }
+ if (!(lead and d[i] == 48)) {
+ lead = false;
+ if (cnt < 64) { dg[cnt as usize] = d[i]; cnt = cnt + 1; } else { point = point + 1; }
+ }
+ }
+ i = i + 1;
+ }
+ var ex : i64 = 0;
+ if (i < hi and (d[i] == 101 or d[i] == 69)) {
+ i = i + 1;
+ var eneg : bool = false;
+ if (i < hi and d[i] == 43) { i = i + 1; }
+ if (i < hi and d[i] == 45) { eneg = true; i = i + 1; }
+ while (i < hi and is_dg(d[i])) {
+ if (ex < 100000) { ex = ex * 10 + ((d[i] - 48) as i64); }
+ i = i + 1;
+ }
+ if (eneg) { ex = 0 - ex; }
+ }
+ // value = digits x 10^(ex - frac_digits + point), digits without leading zeros
+ var e10 : i64 = ex - frac_digits + point;
+ while (cnt > 0 and dg[(cnt - 1) as usize] == 48) { cnt = cnt - 1; e10 = e10 + 1; }
+ if (cnt == 0) { return num_f64(d, sp); }
+ if (cnt <= 15 and e10 >= -22 and e10 <= 22) { return num_f64(d, sp); }
+ if (cnt > 17) {
+ e10 = e10 + ((cnt - 17) as i64);
+ cnt = 17;
+ }
+ var hi_v : f64 = 0.0;
+ var lo_v : f64 = 0.0;
+ var k : u32 = 0;
+ while (k < cnt) {
+ if (cnt - k > 8) { hi_v = hi_v * 10.0 + ((dg[k as usize] - 48) as f64); } else { lo_v = lo_v * 10.0 + ((dg[k as usize] - 48) as f64); }
+ k = k + 1;
+ }
+ var v : f64 = libm(9, hi_v, lo_v * 1000.0 + ((e10 + 500) as f64));
+ if (neg) { v = 0.0 - v; }
+ return v;
+}
+
+fn is_dg(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+fn jget(sp: i64, key: []const u8) -> i64 {
+ return obj_get(dsl(), sp, key);
+}
+
+fn jkind(sp: i64) -> u8 {
+ return kind(dsl(), sp);
+}
+
+/// JavaScript's truthiness of the JSON value at sp (absent counts as undefined).
+fn jtruthy(sp: i64) -> bool {
+ if (sp < 0) { return false; }
+ const d : []const u8 = dsl();
+ const k : u8 = kind(d, sp);
+ if (k == J_NULL or k == J_FALSE) { return false; }
+ if (k == J_STRING) { return str_len(d, sp) > 0; }
+ if (k == J_NUMBER) { return num_f64(d, sp) != 0.0 and num_f64(d, sp) == num_f64(d, sp); }
+ return true;
+}
+
+fn jnullish(sp: i64) -> bool {
+ if (sp < 0) { return true; }
+ return kind(dsl(), sp) == J_NULL;
+}
+
+/// The decoded string at sp, escaped as text.
+fn put_jtext(o: []u8, n0: u32, sp: i64) -> u32 {
+ const d : []const u8 = dsl();
+ var n : u32 = n0;
+ const len : u32 = str_len(d, sp);
+ var k : u32 = 0;
+ while (k < len) {
+ n = put_escaped(o, n, str_byte(d, sp, k));
+ k = k + 1;
+ }
+ return n;
+}
+
+/// The decoded string at sp, appended raw to FB; its offset in FB (length in FB_N difference).
+fn fb_str(sp: i64) -> u32 {
+ const d : []const u8 = dsl();
+ const len : u32 = str_len(d, sp);
+ var k : u32 = 0;
+ while (k < len and (FB_N as usize) < FB.len) {
+ FB[FB_N as usize] = str_byte(d, sp, k);
+ FB_N = FB_N + 1;
+ k = k + 1;
+ }
+ return len;
+}
+
+fn fb_lit(s: []const u8) -> u32 {
+ var k : usize = 0;
+ while (k < s.len and (FB_N as usize) < FB.len) {
+ FB[FB_N as usize] = s[k];
+ FB_N = FB_N + 1;
+ k = k + 1;
+ }
+ return s.len as u32;
+}
+
+/// String(value of the JSON value at sp) appended to FB; undefined for an absent value.
+fn fb_value(sp: i64) -> u32 {
+ if (sp < 0) { return fb_lit("undefined"); }
+ const d : []const u8 = dsl();
+ const k : u8 = kind(d, sp);
+ if (k == J_STRING) { return fb_str(sp); }
+ if (k == J_NULL) { return fb_lit("null"); }
+ if (k == J_TRUE) { return fb_lit("true"); }
+ if (k == J_FALSE) { return fb_lit("false"); }
+ if (k == J_NUMBER) {
+ const at : u32 = FB_N;
+ const m : u32 = put_js(FB[0..], FB_N, num_f64(d, sp));
+ if (m == OVER) { return 0; }
+ FB_N = m;
+ return m - at;
+ }
+ if (k == J_OBJECT) { return fb_lit("[object Object]"); }
+ // an array: its elements, joined by commas
+ const cnt : u32 = arr_len(d, sp);
+ const at : u32 = FB_N;
+ var i : u32 = 0;
+ while (i < cnt) {
+ if (i > 0) { fb_lit(","); }
+ const el : i64 = arr_at(d, sp, i);
+ if (kind(d, el) != J_NULL) { fb_value(el); }
+ i = i + 1;
+ }
+ return FB_N - at;
+}
+
+fn f_clear() {
+ FB_N = 0;
+ F_N = 0;
+}
+
+fn f_key(key: []const u8) {
+ FK_O[F_N as usize] = FB_N;
+ FK_L[F_N as usize] = fb_lit(key);
+}
+
+fn f_val_end(o: u32) {
+ FV_O[F_N as usize] = o;
+ FV_L[F_N as usize] = FB_N - o;
+ F_N = F_N + 1;
+}
+
+/// A variable whose value is the JSON value at sp.
+fn f_json(key: []const u8, sp: i64) {
+ f_key(key);
+ const o : u32 = FB_N;
+ fb_value(sp);
+ f_val_end(o);
+}
+
+fn f_text(key: []const u8, s: []const u8) {
+ f_key(key);
+ const o : u32 = FB_N;
+ fb_lit(s);
+ f_val_end(o);
+}
+
+fn f_locale(key: []const u8, v: f64) {
+ f_key(key);
+ const o : u32 = FB_N;
+ const m : u32 = put_locale(FB[0..], FB_N, v);
+ if (m != OVER) { FB_N = m; }
+ f_val_end(o);
+}
+
+fn f_js(key: []const u8, v: f64) {
+ f_key(key);
+ const o : u32 = FB_N;
+ const m : u32 = put_js(FB[0..], FB_N, v);
+ if (m != OVER) { FB_N = m; }
+ f_val_end(o);
+}
+
+fn f_none(key: []const u8) {
+ f_text(key, SAY_NONE);
+}
+
+/// fill(template, vars): each {name} that is a key is replaced by its value; the others stay as written.
+fn fill_out(o: []u8, n0: u32, t: []const u8) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ var closed : usize = 0;
+ if (t[i] == 123) {
+ var j : usize = i + 1;
+ while (j < t.len and is_word_byte(t[j])) { j = j + 1; }
+ if (j > i + 1 and j < t.len and t[j] == 125) { closed = j; }
+ }
+ if (closed > 0) {
+ var found : bool = false;
+ var q : u32 = 0;
+ while (q < F_N) {
+ if (!found and (FK_L[q as usize] as usize) == closed - i - 1) {
+ var same : bool = true;
+ var c : usize = 0;
+ while (c < closed - i - 1) {
+ if (FB[(FK_O[q as usize] as usize) + c] != t[i + 1 + c]) { same = false; }
+ c = c + 1;
+ }
+ if (same) {
+ found = true;
+ n = text(o, n, FB[(FV_O[q as usize] as usize)..((FV_O[q as usize] + FV_L[q as usize]) as usize)]);
+ }
+ }
+ q = q + 1;
+ }
+ if (!found) {
+ var c2 : usize = i;
+ while (c2 <= closed) { n = put(o, n, t[c2]); c2 = c2 + 1; }
+ }
+ i = closed + 1;
+ } else {
+ n = put_escaped(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the data
+// ------------------------------------------------------------------------------------------------
+
+fn fam_code(sp: i64) -> u8 {
+ if (jstr_is(sp, "binary")) { return 0; }
+ if (jstr_is(sp, "ternary-ref")) { return 1; }
+ if (jstr_is(sp, "t27")) { return 2; }
+ if (jstr_is(sp, "twin")) { return 3; }
+ return 255;
+}
+
+fn op_code(sp: i64) -> u8 {
+ var k : u32 = 0;
+ while ((k as usize) < K_OPS.len) {
+ if (jstr_is(sp, K_OPS[k as usize])) { return k as u8; }
+ k = k + 1;
+ }
+ return 255;
+}
+
+/// Read data.json once. 0 read, 1 not the shape tool.js reads, 2 over a capacity of this page.
+fn load_data() -> u32 {
+ const d : []const u8 = dsl();
+ const r : i64 = root(d);
+ if (r < 0) { return 1; }
+ if (kind(d, r) != J_OBJECT) { return 1; }
+ J_ROOT = r;
+ J_RES = obj_get(d, r, "results");
+ J_CAP = obj_get(d, r, "capacity");
+ J_SPECS = obj_get(d, r, "specs");
+ J_FIND = obj_get(d, r, "findings");
+ J_TOOLS = obj_get(d, r, "tools");
+ J_TIMING = obj_get(d, r, "timing");
+ J_CHECK = obj_get(d, r, "adder_check");
+ J_SPEC = obj_get(d, r, "spec");
+ J_COMP = obj_get(d, r, "compiler");
+ J_CMDS = obj_get(d, r, "commands");
+ if (kind(d, J_RES) != J_ARRAY or kind(d, J_CAP) != J_OBJECT or kind(d, J_SPECS) != J_ARRAY or kind(d, J_FIND) != J_ARRAY) { return 1; }
+ if (kind(d, J_TOOLS) != J_OBJECT or kind(d, J_TIMING) != J_OBJECT or kind(d, J_CHECK) != J_OBJECT or kind(d, J_SPEC) != J_OBJECT) { return 1; }
+ if (kind(d, J_COMP) != J_OBJECT or kind(d, J_CMDS) != J_ARRAY) { return 1; }
+ if (obj_get(d, r, "synth") < 0 or obj_get(d, r, "generated_at") < 0) { return 1; }
+ if (kind(d, obj_get(d, J_CAP, "nextpnr")) != J_OBJECT or kind(d, obj_get(d, J_CAP, "prjxray")) != J_OBJECT) { return 1; }
+ N_RES = arr_len(d, J_RES);
+ if (N_RES > W_MAX_RES or arr_len(d, J_FIND) > W_MAX_FIND or arr_len(d, J_SPECS) > W_MAX_SPECS or arr_len(d, J_CMDS) > 2048) { return 2; }
+ var i : u32 = 0;
+ while (i < N_RES) {
+ const e : i64 = arr_at(d, J_RES, i);
+ R_EL[i as usize] = e;
+ R_FAM[i as usize] = fam_code(obj_get(d, e, "family"));
+ R_OP[i as usize] = op_code(obj_get(d, e, "op"));
+ const dsp : i64 = obj_get(d, e, "dsp");
+ R_DSP[i as usize] = 2;
+ if (kind(d, dsp) == J_TRUE) { R_DSP[i as usize] = 1; }
+ if (kind(d, dsp) == J_FALSE) { R_DSP[i as usize] = 0; }
+ R_OK[i as usize] = 0;
+ if (jstr_is(obj_get(d, e, "status"), "ok")) { R_OK[i as usize] = 1; }
+ R_BITS[i as usize] = jnum(obj_get(d, e, "info_bits"));
+ const tr : i64 = obj_get(d, e, "trits");
+ R_HAS_TRITS[i as usize] = 0;
+ R_TRITS[i as usize] = 0.0;
+ if (!jnullish(tr)) {
+ R_HAS_TRITS[i as usize] = 1;
+ R_TRITS[i as usize] = jnum(tr);
+ }
+ R_SEC[i as usize] = jnum(obj_get(d, e, "seconds"));
+ const wd : i64 = obj_get(d, e, "width");
+ R_HAS_WID[i as usize] = 0;
+ R_WID[i as usize] = 0.0;
+ if (kind(d, wd) == J_NUMBER) {
+ R_HAS_WID[i as usize] = 1;
+ R_WID[i as usize] = jnum(wd);
+ }
+ R_TOP[i as usize] = obj_get(d, e, "top");
+ R_SPEC[i as usize] = obj_get(d, e, "spec");
+ R_ID[i as usize] = obj_get(d, e, "id");
+ const met : i64 = obj_get(d, e, "metrics");
+ var k : u32 = 0;
+ while (k < 6) {
+ var v : i64 = -1;
+ if (k == 0) { v = obj_get(d, met, "lut"); }
+ if (k == 1) { v = obj_get(d, met, "ff"); }
+ if (k == 2) { v = obj_get(d, met, "carry4"); }
+ if (k == 3) { v = obj_get(d, met, "dsp"); }
+ if (k == 4) { v = obj_get(d, met, "muxf7"); }
+ if (k == 5) { v = obj_get(d, met, "muxf8"); }
+ R_MHAS[(i * 6 + k) as usize] = 0;
+ R_MET[(i * 6 + k) as usize] = 0.0;
+ if (kind(d, v) == J_NUMBER) {
+ R_MHAS[(i * 6 + k) as usize] = 1;
+ R_MET[(i * 6 + k) as usize] = jnum(v);
+ }
+ if (kind(d, v) == J_NULL) { R_MHAS[(i * 6 + k) as usize] = 2; }
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ CAP_V[0] = jnum(obj_get(d, J_CAP, "lut"));
+ CAP_V[1] = jnum(obj_get(d, J_CAP, "ff"));
+ CAP_V[2] = jnum(obj_get(d, J_CAP, "carry4"));
+ CAP_V[3] = jnum(obj_get(d, J_CAP, "dsp"));
+ return 0;
+}
+
+pub var RELOAD : bool = false;
+
+pub fn w_init(h: []const u8) -> u32 {
+ D_STATE = 0;
+ READY = false;
+ STUCK = false;
+ OP = 0;
+ if (K_START_OP != 0) { OP = K_START_OP as u32; }
+ METRIC = 0;
+ DSP = false;
+ LOGSCALE = true;
+ TABLE = false;
+ WROOT = 0.0;
+ LAST_RW = 0.0;
+ LB_N = 0;
+ RQ_N = 0;
+ MISSING = false;
+ reset_hover();
+ return 0;
+}
+
+fn reset_hover() {
+ HOVER_ON = false;
+ HOVER_MARK = -1;
+ TIP_SHOWN = false;
+ TIP_MARK = -1;
+ TIP_STYLE = false;
+ TIP_PENDING = false;
+ HALO_ON = false;
+ HALO_SET = false;
+}
+
+/// render(): the page is built afresh, so nothing of the pointer survives it.
+fn do_render() {
+ reset_hover();
+}
+
+pub fn w_theme(s: []const u8) -> u32 {
+ var i : usize = 0;
+ var c : u32 = 0;
+ var l : u32 = 0;
+ var k : u32 = 0;
+ while (k < 9) { THEME_L[k as usize] = 0; k = k + 1; }
+ while (i < s.len and c < 9) {
+ if (s[i] == 10) {
+ THEME_L[c as usize] = l;
+ c = c + 1;
+ l = 0;
+ } else {
+ if (l < 32) { THEME[(c * 32 + l) as usize] = s[i]; l = l + 1; }
+ }
+ i = i + 1;
+ }
+ if (c < 9) { THEME_L[c as usize] = l; }
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ if (i != 0 or D_STATE != 0) { return 0; }
+ if (s.len > (W_CAP_DATA as usize)) {
+ STUCK = true;
+ D_STATE = 1;
+ return 0;
+ }
+ DATA_LEN = copy_to(DATA[0..], s, W_CAP_DATA);
+ D_STATE = 1;
+ MISSING = false;
+ if (load_data() != 0) {
+ STUCK = true;
+ return 0;
+ }
+ RELOAD = MISSING;
+ READY = true;
+ LAST_RW = WROOT;
+ do_render();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32, msg: []const u8) -> u32 {
+ if (i != 0 or D_STATE != 0) { return 0; }
+ D_STATE = 2;
+ ERRMSG_LEN = copy_to(ERRMSG[0..], msg, 512);
+ return 0;
+}
+
+pub fn w_size(px: f64, root_px: f64, inner_h: f64, embed: u32) -> u32 {
+ WROOT = root_px;
+ INNER_H = inner_h;
+ EMBED = embed != 0;
+ if (!READY) { return 0; }
+ if (@abs(root_px - LAST_RW) > 8.0) {
+ LAST_RW = root_px;
+ if (!TABLE) {
+ do_render();
+ return 1;
+ }
+ }
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ if (!READY) { return 0; }
+ var at : u32 = i;
+ const n0 : u32 = SAY_OPS.len as u32;
+ if (at < n0) {
+ OP = at;
+ do_render();
+ return 0;
+ }
+ at = at - n0;
+ const n1 : u32 = SAY_METRICS.len as u32;
+ if (at < n1) {
+ METRIC = at;
+ do_render();
+ return 0;
+ }
+ at = at - n1;
+ if (at < 2) {
+ DSP = at == 0;
+ do_render();
+ return 0;
+ }
+ at = at - 2;
+ if (at < 2) {
+ LOGSCALE = at == 0;
+ do_render();
+ return 0;
+ }
+ at = at - 2;
+ if (at < 2) {
+ TABLE = at == 1;
+ do_render();
+ }
+ return 0;
+}
+
+pub fn w_pointer(kind_of: u32, x: f64, y: f64, bw: f64, bh: f64) -> u32 {
+ if (!READY or TABLE) { return 0; }
+ if (kind_of == 1) {
+ TIP_SHOWN = false;
+ HALO_ON = false;
+ TIP_PENDING = false;
+ return 0;
+ }
+ BOX_W = bw;
+ BOX_H = bh;
+ const sx : f64 = (x / bw) * CW;
+ const sy : f64 = (y / bh) * CHH;
+ var best : i32 = -1;
+ var bd : f64 = 44.0 * 44.0;
+ var m : u32 = 0;
+ while (m < MK_N) {
+ const dx : f64 = MK_X[m as usize] - sx;
+ const dy : f64 = MK_Y[m as usize] - sy;
+ const dd : f64 = dx * dx + dy * dy;
+ if (dd < bd) {
+ bd = dd;
+ best = m as i32;
+ }
+ m = m + 1;
+ }
+ if (best < 0) {
+ TIP_SHOWN = false;
+ HALO_ON = false;
+ TIP_PENDING = false;
+ return 0;
+ }
+ HOVER_MARK = best;
+ HALO_X = MK_X[best as usize];
+ HALO_Y = MK_Y[best as usize];
+ HALO_SET = true;
+ HALO_ON = true;
+ TIP_MARK = best;
+ TIP_SHOWN = true;
+ TIP_PENDING = true;
+ return 0;
+}
+
+pub fn w_tipsize(tw: f64, th: f64) -> u32 {
+ if (!TIP_PENDING or TIP_MARK < 0) { return 0; }
+ TIP_PENDING = false;
+ const mx : f64 = MK_X[TIP_MARK as usize];
+ const my : f64 = MK_Y[TIP_MARK as usize];
+ const left : f64 = (mx / CW) * BOX_W;
+ const top : f64 = (my / CHH) * BOX_H;
+ var pick_left : f64 = left + 12.0;
+ if (left + 12.0 > BOX_W / 2.0) { pick_left = left - tw - 12.0; }
+ const l : f64 = jsmax(4.0, jsmin(BOX_W - tw - 4.0, pick_left));
+ const t : f64 = jsmax(4.0, jsmin(BOX_H - th - 4.0, top - th / 2.0));
+ if (l == l and t == t and l != inf() and t != inf() and l != 0.0 - inf() and t != 0.0 - inf()) {
+ TIP_LEFT = l;
+ TIP_TOP = t;
+ TIP_STYLE = true;
+ }
+ return 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the view
+// ------------------------------------------------------------------------------------------------
+
+pub var R_WID : [512]f64 = [0.0; 512];
+pub var R_HAS_WID : [512]u8 = [0; 512];
+pub var S_N : [4]u32 = [0; 4];
+pub var SP : [2048]u32 = [0; 2048];
+pub var CW : f64 = 0.0;
+pub var CHH : f64 = 0.0;
+pub var CPW : f64 = 0.0;
+pub var CPH : f64 = 0.0;
+pub var C_XLO : f64 = 0.0;
+pub var C_XHI : f64 = 0.0;
+pub var C_YTOP : f64 = 0.0;
+
+fn col(o: []u8, n0: u32, i: u32) -> u32 {
+ return text(o, n0, THEME[((i * 32) as usize)..((i * 32 + THEME_L[i as usize]) as usize)]);
+}
+
+fn series_color(s: u32) -> u32 {
+ if (s == 0) { return 3; }
+ if (s == 1) { return 4; }
+ if (s == 2) { return 5; }
+ return 7;
+}
+
+fn kind_name(s: u32) -> []const u8 {
+ if (s == 0) { return "binary"; }
+ if (s == 1) { return "ternary-ref"; }
+ if (s == 2) { return "t27"; }
+ return "twin";
+}
+
+fn lower_op(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ if ((OP as usize) < SAY_OPS.len) {
+ const w : []const u8 = SAY_OPS[OP as usize];
+ var k : usize = 0;
+ while (k < w.len and n < 64) {
+ var b : u8 = w[k];
+ if (b >= 65 and b <= 90) { b = b + 32; }
+ dst[n as usize] = b;
+ n = n + 1;
+ k = k + 1;
+ }
+ }
+ return n;
+}
+
+/// seriesName(kind) into o.
+fn series_name(o: []u8, n0: u32, s: u32) -> u32 {
+ f_clear();
+ var lo : [64]u8 = [0; 64];
+ const l : u32 = lower_op(lo[0..]);
+ f_text("op", lo[0..(l as usize)]);
+ if ((s as usize) >= SAY_SERIES.len) { return n0; }
+ return fill_out(o, n0, SAY_SERIES[s as usize]);
+}
+
+fn metric_ok(r: u32, k: u32) -> bool {
+ return R_MHAS[(r * 6 + k) as usize] == 1;
+}
+
+fn metric_v(r: u32, k: u32) -> f64 {
+ if (R_MHAS[(r * 6 + k) as usize] == 1) { return R_MET[(r * 6 + k) as usize]; }
+ if (R_MHAS[(r * 6 + k) as usize] == 2) { return 0.0; }
+ var z : f64 = 0.0;
+ return z / z;
+}
+
+/// The series of the chosen op, the dsp mode and each family, points sorted by information (stable).
+fn collect_series() {
+ var s : u32 = 0;
+ while (s < 4) {
+ S_N[s as usize] = 0;
+ var i : u32 = 0;
+ while (i < N_RES) {
+ if ((R_OP[i as usize] as u32) == OP and R_FAM[i as usize] == (s as u8)) {
+ var dsp_ok : bool = R_DSP[i as usize] == 0;
+ if (DSP) { dsp_ok = R_DSP[i as usize] == 1; }
+ if (dsp_ok) {
+ // insertion, after every point with information not above this one's
+ var at : u32 = S_N[s as usize];
+ while (at > 0 and R_BITS[SP[(s * 512 + at - 1) as usize] as usize] > R_BITS[i as usize]) {
+ SP[(s * 512 + at) as usize] = SP[(s * 512 + at - 1) as usize];
+ at = at - 1;
+ }
+ SP[(s * 512 + at) as usize] = i;
+ S_N[s as usize] = S_N[s as usize] + 1;
+ }
+ }
+ i = i + 1;
+ }
+ s = s + 1;
+ }
+}
+
+fn nice_max(v: f64) -> f64 {
+ if (v <= 1.0) { return 1.0; }
+ const p : f64 = powf(10.0, @floor(log10f(v)));
+ var q : u32 = 0;
+ while (q < 5) {
+ var m : f64 = 1.0;
+ if (q == 1) { m = 2.0; }
+ if (q == 2) { m = 2.5; }
+ if (q == 3) { m = 5.0; }
+ if (q == 4) { m = 10.0; }
+ if (m * p >= v) { return m * p; }
+ q = q + 1;
+ }
+ return 10.0 * p;
+}
+
+fn cx_of(bits: f64) -> f64 {
+ return 50.0 + (((log2f(bits) - C_XLO) / (C_XHI - C_XLO)) * CPW);
+}
+
+fn fy(v: f64, logs: bool) -> f64 {
+ if (logs) { return log10f(1.0 + v); }
+ return v;
+}
+
+fn cy_of(v: f64) -> f64 {
+ return 16.0 + CPH - ((fy(v, LOGSCALE) / C_YTOP) * CPH);
+}
+
+fn a_js(o: []u8, n0: u32, name: []const u8, v: f64) -> u32 {
+ var n : u32 = raw(o, n0, " ");
+ n = raw(o, n, name);
+ n = raw(o, n, "=\"");
+ n = put_js(o, n, v);
+ return raw(o, n, "\"");
+}
+
+fn a_col(o: []u8, n0: u32, name: []const u8, c: u32) -> u32 {
+ var n : u32 = raw(o, n0, " ");
+ n = raw(o, n, name);
+ n = raw(o, n, "=\"");
+ n = col(o, n, c);
+ return raw(o, n, "\"");
+}
+
+fn tick_text(o: []u8, n0: u32, x: f64, y: f64, anchor: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "");
+}
+
+fn cap_name(o: []u8, n0: u32, tmpl: []const u8) -> u32 {
+ f_clear();
+ f_locale("n", CAP_V[METRIC as usize]);
+ if ((METRIC as usize) < SAY_CAP_UNITS.len) { f_text("unit", SAY_CAP_UNITS[METRIC as usize]); } else { f_text("unit", "undefined"); }
+ return fill_out(o, n0, tmpl);
+}
+
+fn chart(o: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ const key : u32 = METRIC;
+ collect_series();
+ // dataMax over the ok points
+ var data_max : f64 = 1.0;
+ var s : u32 = 0;
+ while (s < 4) {
+ var q : u32 = 0;
+ while (q < S_N[s as usize]) {
+ const r : u32 = SP[(s * 512 + q) as usize];
+ if (R_OK[r as usize] == 1) { data_max = jsmax(data_max, metric_v(r, key)); }
+ q = q + 1;
+ }
+ s = s + 1;
+ }
+ const cap : f64 = CAP_V[key];
+ const cap_shown : bool = LOGSCALE or cap <= data_max * 1.5;
+ var rw : f64 = WROOT;
+ if (!(rw != 0.0 and rw == rw)) { rw = 640.0; }
+ const width : f64 = jsmax(300.0, jsmin(1120.0, rw));
+ var ch : f64 = 340.0;
+ if (K_CHART_H_PX != 0) { ch = K_CHART_H_PX as f64; }
+ var lim3 : f64 = 1000000000.0;
+ if (EMBED) { lim3 = INNER_H - 190.0; }
+ const height : f64 = jsround(jsmax(210.0, jsmin(jsmin(ch, width * 0.58), lim3)));
+ CW = width;
+ CHH = height;
+ CPW = width - 50.0 - 12.0;
+ CPH = height - 16.0 - 40.0;
+ C_XLO = log2f(0.8);
+ C_XHI = log2f(76.0);
+ var top_in : f64 = data_max;
+ if (cap_shown) { top_in = jsmax(data_max, cap); } else { top_in = jsmax(data_max, 0.0); }
+ if (LOGSCALE) { C_YTOP = fy(top_in * 1.6, true); } else { C_YTOP = nice_max(top_in * 1.05); }
+ n = raw(o, n, "
");
+ // grid and axes: y ticks
+ var t : u32 = 0;
+ var ny : u32 = 6;
+ if (LOGSCALE) { ny = 8; }
+ while (t < ny) {
+ var tv : f64 = 0.0;
+ if (LOGSCALE) {
+ if (t == 1) { tv = 1.0; }
+ if (t == 2) { tv = 10.0; }
+ if (t == 3) { tv = 100.0; }
+ if (t == 4) { tv = 1000.0; }
+ if (t == 5) { tv = 10000.0; }
+ if (t == 6) { tv = 100000.0; }
+ if (t == 7) { tv = 1000000.0; }
+ } else {
+ tv = (C_YTOP * (t as f64)) / 5.0;
+ }
+ var draw : bool = true;
+ if (LOGSCALE) { draw = fy(tv, true) <= C_YTOP; }
+ if (draw) {
+ const yy : f64 = cy_of(tv);
+ n = raw(o, n, "");
+ n = tick_text(o, n, 50.0 - 6.0, yy + 4.0, "end");
+ if (tv >= 1000.0) {
+ n = put_js(o, n, tv / 1000.0);
+ n = raw(o, n, "k");
+ } else {
+ n = put_r(o, n, tv, 1);
+ }
+ n = raw(o, n, "");
+ }
+ t = t + 1;
+ }
+ var xt : u32 = 0;
+ while (xt < 7) {
+ const tx : f64 = pow2(xt);
+ const xx : f64 = cx_of(tx);
+ n = raw(o, n, "");
+ n = tick_text(o, n, xx, 16.0 + CPH + 16.0, "middle");
+ n = put_js(o, n, tx);
+ n = raw(o, n, "");
+ xt = xt + 1;
+ }
+ // the x label
+ const xwords : []const u8 = SAY_X_AXIS;
+ const x_fits : bool = (xwords.len as f64) * 6.4 <= CPW;
+ n = raw(o, n, "");
+ n = text(o, n, xwords);
+ n = raw(o, n, "");
+ // the y label
+ n = raw(o, n, "");
+ f_clear();
+ if ((METRIC as usize) < SAY_METRIC_LONG.len) { f_text("metric", SAY_METRIC_LONG[METRIC as usize]); } else { f_text("metric", "undefined"); }
+ if (LOGSCALE) { f_text("scale", SAY_SCALE_LOG); } else { f_text("scale", SAY_SCALE_LIN); }
+ n = fill_out(o, n, SAY_Y_AXIS);
+ n = raw(o, n, "");
+ // capacity
+ if (cap_shown) {
+ const cyc : f64 = cy_of(cap);
+ n = raw(o, n, "");
+ n = raw(o, n, "");
+ n = cap_name(o, n, SAY_CAP);
+ n = raw(o, n, "");
+ }
+ // series
+ MK_N = 0;
+ s = 0;
+ while (s < 4) {
+ if (S_N[s as usize] > 0) {
+ var okn : u32 = 0;
+ var q : u32 = 0;
+ while (q < S_N[s as usize]) {
+ if (R_OK[SP[(s * 512 + q) as usize] as usize] == 1) { okn = okn + 1; }
+ q = q + 1;
+ }
+ if ((s == 0 or s == 1) and okn > 1) {
+ n = raw(o, n, "");
+ }
+ q = 0;
+ while (q < S_N[s as usize]) {
+ const r : u32 = SP[(s * 512 + q) as usize];
+ if (R_OK[r as usize] == 1) {
+ const cx : f64 = cx_of(R_BITS[r as usize]);
+ const cy : f64 = cy_of(metric_v(r, key));
+ if (s == 2) {
+ n = raw(o, n, "");
+ } else {
+ n = raw(o, n, "");
+ }
+ if (MK_N < 512) {
+ MK_R[MK_N as usize] = r;
+ MK_S[MK_N as usize] = s as u8;
+ MK_X[MK_N as usize] = cx;
+ MK_Y[MK_N as usize] = cy;
+ MK_N = MK_N + 1;
+ }
+ }
+ q = q + 1;
+ }
+ }
+ s = s + 1;
+ }
+ // the halo
+ n = raw(o, n, ""); } else { n = raw(o, n, " visibility=\"hidden\">"); }
+ n = raw(o, n, "");
+ // the tooltip
+ n = raw(o, n, "
");
+ if (TIP_MARK >= 0) { n = tip_body(o, n, TIP_MARK as u32); }
+ n = raw(o, n, "
");
+ s = 0;
+ while (s < 4) {
+ if (S_N[s as usize] > 0) {
+ n = raw(o, n, "
");
+ n = series_name(o, n, s);
+ n = raw(o, n, "
");
+ }
+ s = s + 1;
+ }
+ if (!cap_shown) {
+ n = raw(o, n, "
");
+ n = cap_name(o, n, SAY_CAP_ABOVE);
+ n = raw(o, n, "
");
+ }
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HINT);
+ n = raw(o, n, "
");
+ return n;
+}
+
+fn pow2(k: u32) -> f64 {
+ var p : f64 = 1.0;
+ var i : u32 = 0;
+ while (i < k) { p = p * 2.0; i = i + 1; }
+ return p;
+}
+
+/// The last path segment of the JSON string at sp, appended to FB; its length.
+fn fb_basename(sp: i64) -> u32 {
+ const d : []const u8 = dsl();
+ const len : u32 = str_len(d, sp);
+ var start : u32 = 0;
+ var k : u32 = 0;
+ while (k < len) {
+ if (str_byte(d, sp, k) == 47) { start = k + 1; }
+ k = k + 1;
+ }
+ k = start;
+ while (k < len and (FB_N as usize) < FB.len) {
+ FB[FB_N as usize] = str_byte(d, sp, k);
+ FB_N = FB_N + 1;
+ k = k + 1;
+ }
+ return len - start;
+}
+
+fn tip_body(o: []u8, n0: u32, mk: u32) -> u32 {
+ var n : u32 = n0;
+ const r : u32 = MK_R[mk as usize];
+ const s : u32 = MK_S[mk as usize] as u32;
+ const key : u32 = METRIC;
+ n = raw(o, n, "");
+ if (s == 2) {
+ f_clear();
+ f_key("spec");
+ const o1 : u32 = FB_N;
+ fb_basename(R_SPEC[r as usize]);
+ f_val_end(o1);
+ f_json("top", R_TOP[r as usize]);
+ n = fill_out(o, n, SAY_TIP_SPEC);
+ } else {
+ n = series_name(o, n, s);
+ n = raw(o, n, ": ");
+ n = put_tip_top(o, n, r);
+ }
+ n = raw(o, n, "
");
+ f_clear();
+ f_js("bits", jsround(R_BITS[r as usize] * 10.0) / 10.0);
+ if (R_HAS_TRITS[r as usize] == 1 and R_TRITS[r as usize] != 0.0 and R_TRITS[r as usize] == R_TRITS[r as usize]) {
+ f_js("trits", R_TRITS[r as usize]);
+ n = fill_out(o, n, SAY_TIP_TRITS);
+ } else {
+ n = fill_out(o, n, SAY_TIP_BITS);
+ }
+ n = raw(o, n, "
");
+ var i : u32 = 0;
+ while (i < 6) {
+ n = raw(o, n, "
");
+ if (((3 + i) as usize) < SAY_COLS.len) { n = text(o, n, SAY_COLS[(3 + i) as usize]); }
+ n = raw(o, n, "
");
+ if (metric_ok(r, i)) { n = put_locale(o, n, metric_v(r, i)); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "
");
+ i = i + 1;
+ }
+ n = raw(o, n, "
");
+ f_clear();
+ f_key("pct");
+ const o2 : u32 = FB_N;
+ if (metric_ok(r, key)) {
+ const m : u32 = put_r(FB[0..], FB_N, (100.0 * metric_v(r, key)) / CAP_V[key], 3);
+ if (m != OVER) { FB_N = m; }
+ } else {
+ fb_lit("null");
+ }
+ f_val_end(o2);
+ if ((METRIC as usize) < SAY_CAP_UNITS.len) { f_text("unit", SAY_CAP_UNITS[METRIC as usize]); } else { f_text("unit", "undefined"); }
+ n = fill_out(o, n, SAY_TIP_SHARE);
+ n = raw(o, n, "
");
+ f_clear();
+ f_js("s", R_SEC[r as usize]);
+ n = fill_out(o, n, SAY_TIP_SECONDS);
+ n = raw(o, n, "
");
+ return n;
+}
+
+fn put_tip_top(o: []u8, n0: u32, r: u32) -> u32 {
+ if (R_TOP[r as usize] < 0) { return raw(o, n0, "undefined"); }
+ return put_jtext(o, n0, R_TOP[r as usize]);
+}
+
+fn btn(o: []u8, n0: u32, pressed: bool, title: []const u8, has_title: bool, label: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "");
+ n = text(o, n, label);
+ return raw(o, n, "");
+}
+
+fn group_open(o: []u8, n0: u32, label: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, label);
+ return raw(o, n, "");
+}
+
+fn controls(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = group_open(o, n, SAY_OP_LABEL);
+ var i : u32 = 0;
+ while ((i as usize) < SAY_OPS.len) {
+ n = btn(o, n, i == OP, SAY_OP_TITLES[i as usize], (i as usize) < SAY_OP_TITLES.len, SAY_OPS[i as usize]);
+ i = i + 1;
+ }
+ n = raw(o, n, "
");
+ n = group_open(o, n, SAY_METRIC_LABEL);
+ i = 0;
+ while ((i as usize) < SAY_METRICS.len) {
+ n = btn(o, n, i == METRIC, SAY_METRIC_LONG[i as usize], (i as usize) < SAY_METRIC_LONG.len, SAY_METRICS[i as usize]);
+ i = i + 1;
+ }
+ n = raw(o, n, "
");
+ n = group_open(o, n, SAY_DSP_LABEL);
+ n = btn(o, n, DSP, SAY_DSP_ON_TITLE, true, SAY_DSP_ON);
+ n = btn(o, n, !DSP, SAY_DSP_OFF_TITLE, true, SAY_DSP_OFF);
+ n = raw(o, n, "
");
+ n = group_open(o, n, SAY_SCALE_LABEL);
+ n = btn(o, n, LOGSCALE, "", false, SAY_SCALE_LOG);
+ n = btn(o, n, !LOGSCALE, "", false, SAY_SCALE_LIN);
+ n = raw(o, n, "");
+ n = group_open(o, n, SAY_VIEW_LABEL);
+ n = btn(o, n, !TABLE, "", false, SAY_VIEW_CHART);
+ n = btn(o, n, TABLE, "", false, SAY_VIEW_TABLE);
+ n = raw(o, n, "");
+ return n;
+}
+
+fn table(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ var c : u32 = 0;
+ while ((c as usize) < SAY_COLS.len) {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_COLS[c as usize]);
+ n = raw(o, n, "
");
+ c = c + 1;
+ }
+ n = raw(o, n, "
");
+ collect_series();
+ const key : u32 = METRIC;
+ var s : u32 = 0;
+ while (s < 4) {
+ var q : u32 = 0;
+ while (q < S_N[s as usize]) {
+ const r : u32 = SP[(s * 512 + q) as usize];
+ n = raw(o, n, "
");
+ if (s == 2) {
+ f_clear();
+ f_key("x");
+ const o1 : u32 = FB_N;
+ fb_basename(R_SPEC[r as usize]);
+ f_val_end(o1);
+ n = text(o, n, FB[(FV_O[0] as usize)..((FV_O[0] + FV_L[0]) as usize)]);
+ } else {
+ if (R_TOP[r as usize] >= 0) { n = put_jtext(o, n, R_TOP[r as usize]); }
+ }
+ n = raw(o, n, "
");
+ n = put_r(o, n, R_BITS[r as usize], 1);
+ n = raw(o, n, "
");
+ if (R_HAS_TRITS[r as usize] == 1) { n = put_js(o, n, R_TRITS[r as usize]); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "
");
+ if (R_OK[r as usize] == 1) {
+ var k : u32 = 0;
+ while (k < 6) {
+ n = raw(o, n, "
");
+ if (metric_ok(r, k)) { n = put_locale(o, n, metric_v(r, k)); } else { n = text(o, n, SAY_NONE); }
+ n = raw(o, n, "
");
+ k = k + 1;
+ }
+ n = raw(o, n, "
");
+ if (metric_ok(r, key)) { n = put_r(o, n, (100.0 * metric_v(r, key)) / CAP_V[key], 3); } else { n = raw(o, n, "null"); }
+ n = raw(o, n, "%
");
+ } else {
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_REFUSED);
+ n = raw(o, n, "
");
+ }
+ n = raw(o, n, "
");
+ n = put_js(o, n, R_SEC[r as usize]);
+ n = raw(o, n, "
");
+ q = q + 1;
+ }
+ s = s + 1;
+ }
+ return raw(o, n, "
");
+}
+
+/// results.find(r => r.dsp === false && ...)?.metrics?.lut, as the number or SAY_NONE.
+fn put_nodsp(o: []u8, n0: u32, fam: u8, want_trits: bool, trits: f64, want_op: bool, op_i: u8, want_wid: bool, wid: f64, spec: i64, twin_lit: []const u8, want_twin: bool) -> u32 {
+ var i : u32 = 0;
+ while (i < N_RES) {
+ var hit : bool = R_DSP[i as usize] == 0 and R_FAM[i as usize] == fam;
+ if (hit and want_trits) { hit = R_HAS_TRITS[i as usize] == 1 and R_TRITS[i as usize] == trits; }
+ if (hit and want_op) { hit = R_OP[i as usize] == op_i; }
+ if (hit and want_wid) { hit = R_HAS_WID[i as usize] == 1 and R_WID[i as usize] == wid; }
+ if (hit and spec == -2) { hit = R_SPEC[i as usize] < 0; }
+ if (hit and spec >= 0) { hit = same_json_str(R_SPEC[i as usize], spec); }
+ if (hit and want_twin) { hit = jstr_is(obj_get(dsl(), R_EL[i as usize], "twin"), twin_lit); }
+ if (hit) {
+ if (metric_ok(i, 0)) { return put_locale(o, n0, metric_v(i, 0)); }
+ return text(o, n0, SAY_NONE);
+ }
+ i = i + 1;
+ }
+ return text(o, n0, SAY_NONE);
+}
+
+fn same_json_str(a: i64, b: i64) -> bool {
+ const d : []const u8 = dsl();
+ if (a < 0 or b < 0) { return false; }
+ if (kind(d, a) != J_STRING or kind(d, b) != J_STRING) { return false; }
+ const l : u32 = str_len(d, a);
+ if (l != str_len(d, b)) { return false; }
+ var k : u32 = 0;
+ while (k < l) {
+ if (str_byte(d, a, k) != str_byte(d, b, k)) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// data.specs.find(s => s.spec.endsWith(stem))?.spec
+fn spec_ending(stem: []const u8) -> i64 {
+ const d : []const u8 = dsl();
+ const n : u32 = arr_len(d, J_SPECS);
+ var i : u32 = 0;
+ while (i < n) {
+ const sp : i64 = obj_get(d, arr_at(d, J_SPECS, i), "spec");
+ if (kind(d, sp) == J_STRING) {
+ const l : u32 = str_len(d, sp);
+ if (l >= (stem.len as u32)) {
+ var ok_e : bool = true;
+ var k : u32 = 0;
+ while (k < (stem.len as u32)) {
+ if (str_byte(d, sp, l - (stem.len as u32) + k) != stem[k as usize]) { ok_e = false; }
+ k = k + 1;
+ }
+ if (ok_e) { return sp; }
+ }
+ }
+ i = i + 1;
+ }
+ return -2;
+}
+
+fn note(o: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(o, n0, "
");
+ n = text(o, n, SAY_NOTE_TITLE);
+ n = raw(o, n, "
");
+ // the words: tadd add fa fa_twin dot, each rendered to FB
+ f_clear();
+ f_key("tadd");
+ var o1 : u32 = FB_N;
+ var m : u32 = put_nodsp(FB[0..], FB_N, 1, true, 15.0, false, 0, false, 0.0, -1, "", false);
+ FB_N = m;
+ f_val_end(o1);
+ f_key("add");
+ o1 = FB_N;
+ m = put_nodsp(FB[0..], FB_N, 0, false, 0.0, true, 0, true, 24.0, -1, "", false);
+ FB_N = m;
+ f_val_end(o1);
+ f_key("fa");
+ o1 = FB_N;
+ var sp : i64 = spec_ending("ternary_full_adder.t27");
+ m = put_nodsp(FB[0..], FB_N, 2, false, 0.0, false, 0, false, 0.0, sp, "", false);
+ FB_N = m;
+ f_val_end(o1);
+ f_key("fa_twin");
+ o1 = FB_N;
+ m = put_nodsp(FB[0..], FB_N, 3, false, 0.0, false, 0, false, 0.0, -1, "fa1", true);
+ FB_N = m;
+ f_val_end(o1);
+ f_key("dot");
+ o1 = FB_N;
+ sp = spec_ending("comb_ternary_dot.t27");
+ m = put_nodsp(FB[0..], FB_N, 2, false, 0.0, false, 0, false, 0.0, sp, "", false);
+ FB_N = m;
+ f_val_end(o1);
+ if ((OP as usize) < SAY_NOTES.len) { n = fill_out(o, n, SAY_NOTES[OP as usize]); }
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_HONEST);
+ n = raw(o, n, "
");
+ n = text(o, n, SAY_PACK_NOTE);
+ return raw(o, n, "
");
+ f_clear();
+ f_json("cases", obj_get(d, J_CHECK, "cases"));
+ f_json("trits", obj_get(d, J_CHECK, "trits"));
+ if (jtruthy(obj_get(d, J_CHECK, "all_pass"))) { f_text("verdict", SAY_PASS); } else { f_text("verdict", SAY_REFUSED); }
+ n = fill_out(o, n, SAY_PROV_CHECK);
+ n = raw(o, n, "
");
+ f_clear();
+ f_json("spec", obj_get(d, J_SPEC, "path"));
+ f_key("sha");
+ var o1 : u32 = FB_N;
+ var shs : i64 = obj_get(d, J_SPEC, "sha256");
+ var k : u32 = 0;
+ while (k < 16 and k < str_len(d, shs)) { FB[FB_N as usize] = str_byte(d, shs, k); FB_N = FB_N + 1; k = k + 1; }
+ f_val_end(o1);
+ f_json("compiler", obj_get(d, J_COMP, "path"));
+ f_key("csha");
+ o1 = FB_N;
+ shs = obj_get(d, J_COMP, "sha256");
+ k = 0;
+ while (k < 16 and k < str_len(d, shs)) { FB[FB_N as usize] = str_byte(d, shs, k); FB_N = FB_N + 1; k = k + 1; }
+ f_val_end(o1);
+ f_json("at", obj_get(d, J_ROOT, "generated_at"));
+ n = fill_out(o, n, SAY_PROV_SPEC);
+ n = raw(o, n, "
");
+ f_clear();
+ f_js("n", arr_len(d, J_CMDS) as f64);
+ n = fill_out(o, n, SAY_COMMANDS);
+ n = raw(o, n, "
");
+ const cn : u32 = arr_len(d, J_CMDS);
+ var i : u32 = 0;
+ while (i < cn) {
+ if (i > 0) { n = put(o, n, 10); }
+ n = put_jtext(o, n, arr_at(d, J_CMDS, i));
+ i = i + 1;
+ }
+ return raw(o, n, "
");
+}
+
+fn f_locale_json(key: []const u8, sp: i64) {
+ f_key(key);
+ const o : u32 = FB_N;
+ if (jnullish(sp)) {
+ fb_lit(SAY_NONE);
+ } else {
+ const m : u32 = put_locale(FB[0..], FB_N, jnum(sp));
+ if (m != OVER) { FB_N = m; }
+ }
+ f_val_end(o);
+}
+
+/// The whole page, as tool.js leaves it. 0 on overflow. The host draws again while w_libm_need says so.
+pub fn w_render(dst: []u8) -> u32 {
+ RQ_N = 0;
+ MISSING = false;
+ if (LB_N > 14000) { LB_N = 0; }
+ if (RELOAD) {
+ // numbers of data.json that the host has to read (more than 15 digits, or a big exponent) are asked for now
+ load_data();
+ if (!MISSING) { RELOAD = false; }
+ }
+ var n : u32 = 0;
+ if (!READY) {
+ if (D_STATE == 2) {
+ n = raw(dst, n, "
");
+ f_clear();
+ f_text("why", ERRMSG[0..(ERRMSG_LEN as usize)]);
+ n = fill_out(dst, n, SAY_ERR_LOAD);
+ n = raw(dst, n, "
");
+ } else {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOADING);
+ n = raw(dst, n, "
");
+ }
+ return n;
+ }
+ n = raw(dst, n, "
");
+ n = controls(dst, n);
+ n = raw(dst, n, "
");
+ if ((OP as usize) < SAY_OP_TITLES.len) { n = text(dst, n, SAY_OP_TITLES[OP as usize]); }
+ n = raw(dst, n, "
");
+ if (TABLE) { n = table(dst, n); } else { n = chart(dst, n); }
+ n = note(dst, n);
+ n = findings(dst, n);
+ n = provenance(dst, n);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every name of the three libraries, so that they arrive whole. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8, w: []u32, d: []f64) -> u32 {
+ var n : u32 = 0;
+ n = n + ((OVER) as u32);
+ n = n + ((H_LT) as u32);
+ n = n + ((H_GT) as u32);
+ n = n + ((H_AMP) as u32);
+ n = n + ((H_QUOTE) as u32);
+ n = n + ((H_APOS) as u32);
+ n = n + ((H_SPACE) as u32);
+ n = n + ((H_SLASH) as u32);
+ n = n + ((H_EQ) as u32);
+ n = n + ((ok(0)) as u32);
+ n = n + ((put(o, 0, 0)) as u32);
+ n = n + ((raw(o, 0, s)) as u32);
+ n = n + ((put_escaped(o, 0, 0)) as u32);
+ n = n + ((text(o, 0, s)) as u32);
+ n = n + ((num(o, 0, 0)) as u32);
+ n = n + ((num_grouped(o, 0, 0)) as u32);
+ n = n + ((attr(o, 0, s, s)) as u32);
+ n = n + ((fill(o, 0, s, s, s, s, 0)) as u32);
+ n = n + ((name_is(s, 0, 0, s)) as u32);
+ n = n + ((is_word_byte(0)) as u32);
+ n = n + ((fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s)) as u32);
+ n = n + ((dec_to(o, 0)) as u32);
+ n = n + ((same_bytes(s, 0, s)) as u32);
+ n = n + ((NO_SPAN) as u32);
+ n = n + ((SPAN) as u32);
+ n = n + ((J_OBJECT) as u32);
+ n = n + ((J_ARRAY) as u32);
+ n = n + ((J_STRING) as u32);
+ n = n + ((J_NUMBER) as u32);
+ n = n + ((J_TRUE) as u32);
+ n = n + ((J_FALSE) as u32);
+ n = n + ((J_NULL) as u32);
+ n = n + ((J_NONE) as u32);
+ n = n + ((jspan(0, 0)) as u32);
+ n = n + ((span_lo(0)) as u32);
+ n = n + ((span_hi(0)) as u32);
+ n = n + ((skip_ws(s, 0)) as u32);
+ n = n + ((string_end(s, 0)) as u32);
+ n = n + ((value_end(s, 0)) as u32);
+ n = n + ((root(s)) as u32);
+ n = n + ((kind(s, 0)) as u32);
+ n = n + ((key_is(s, 0, 0, s)) as u32);
+ n = n + ((obj_get(s, 0, s)) as u32);
+ n = n + ((arr_len(s, 0)) as u32);
+ n = n + ((arr_at(s, 0, 0)) as u32);
+ n = n + ((hex4(s, 0)) as u32);
+ n = n + ((escape_at(s, 0, 0)) as u32);
+ n = n + ((cp_utf8_len(0)) as u32);
+ n = n + ((cp_utf8_byte(0, 0)) as u32);
+ n = n + ((str_len(s, 0)) as u32);
+ n = n + ((str_byte(s, 0, 0)) as u32);
+ n = n + ((is_true(s, 0)) as u32);
+ n = n + ((is_false(s, 0)) as u32);
+ n = n + ((is_null(s, 0)) as u32);
+ n = n + ((num_f64(s, 0)) as u32);
+ n = n + ((num_i64(s, 0)) as u32);
+ return n;
+}
+
+// ------------------------------------------------------------------------------------------------
+// tests
+// ------------------------------------------------------------------------------------------------
+
+pub var T_BUF : [900000]u8 = [0; 900000];
+pub var T_F : [49152]f64 = [0.0; 49152];
+
+fn t_has(n: u32, lit: []const u8) -> bool {
+ var i : u32 = 0;
+ const l : u32 = lit.len as u32;
+ if (n < l) { return false; }
+ while (i + l <= n) {
+ var k : u32 = 0;
+ while (k < l and T_BUF[(i + k) as usize] == lit[k as usize]) { k = k + 1; }
+ if (k == l) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn t_data() -> []const u8 {
+ return "{\"generated_at\":\"2026-10-04T17:45:21Z\",\"spec\":{\"path\":\"p/s.t27\",\"sha256\":\"0123456789abcdef0123\"},\"compiler\":{\"path\":\"c.wasm\",\"sha256\":\"fedcba9876543210ffff\"},\"tools\":{\"yosys\":\"Y 1\",\"iverilog\":\"I 2\",\"node\":\"n\"},\"synth\":\"synth_xilinx\",\"commands\":[\"a b\",\"c u32 {
+ var n : u32 = 0;
+ var rounds : u32 = 0;
+ n = w_render(T_BUF[0..900000]);
+ while (w_libm_need(T_F[0..49152]) > 0 and rounds < 8) {
+ var k : u32 = 0;
+ while (k < RQ_SENT) {
+ T_F[k as usize] = 1.0;
+ k = k + 1;
+ }
+ w_libm_give(T_F[0..49152], RQ_SENT);
+ n = w_render(T_BUF[0..900000]);
+ rounds = rounds + 1;
+ }
+ return n;
+}
+
+test numbers_are_written_like_javascript {
+ var o : [64]u8 = [0; 64];
+ LB_N = 0;
+ RQ_N = 0;
+ var n : u32 = put_js(o[0..], 0, 2.0);
+ assert same_bytes(o[0..], n, "2");
+ n = put_js(o[0..], 0, 0.0 - 40.0);
+ assert same_bytes(o[0..], n, "-40");
+ n = put_js(o[0..], 0, 12.34);
+ assert MISSING;
+ assert RQ_N == 3;
+ assert w_libm_need(T_F[0..49152]) == 3;
+ assert T_F[0] == 6.0;
+ assert T_F[3] == 7.0;
+ assert T_F[6] == 8.0;
+ T_F[0] = 0.0;
+ T_F[1] = 1234.0;
+ T_F[2] = 2.0;
+ w_libm_give(T_F[0..49152], 3);
+ n = put_js(o[0..], 0, 12.34);
+ assert same_bytes(o[0..], n, "12.34");
+ n = put_css6(o[0..], 0, 266.27761565156305);
+ assert same_bytes(o[0..], n, "266.278");
+ n = put_css6(o[0..], 0, 12.0);
+ assert same_bytes(o[0..], n, "12");
+ n = put_css6(o[0..], 0, 4.5);
+ assert same_bytes(o[0..], n, "4.5");
+ n = put_css6(o[0..], 0, 99999.96);
+ assert same_bytes(o[0..], n, "100000");
+ n = put_locale(o[0..], 0, 1234567.0);
+ assert same_bytes(o[0..], n, "1,234,567");
+ n = put_locale(o[0..], 0, 7.26);
+ assert same_bytes(o[0..], n, "7.26");
+ n = put_r(o[0..], 0, 4.96, 1);
+ assert same_bytes(o[0..], n, "5");
+}
+
+test fill_replaces_known_names_and_keeps_unknown_ones {
+ var o : [96]u8 = [0; 96];
+ f_clear();
+ f_text("a", "xReading the synthesis results...");
+ w_data_failed(0, "HTTP 404");
+ n = w_render(T_BUF[0..900000]);
+ assert t_has(n, "
The synthesis results did not load: HTTP 404
");
+ w_init("");
+ w_data(0, t_data());
+ assert OP == 1;
+ n = t_render();
+ assert n > 0;
+ assert t_has(n, "
");
+ assert t_has(n, "aria-pressed=\"true\" title=\"a * b: W-bit unsigned operands, 2W result bits, no clock\">Multiply");
+ assert t_has(n, "
a * b: W-bit unsigned operands, 2W result bits, no clock
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ if (DROP_MODE == 0) {
+ n = text(dst, n, SAY_DROP);
+ } else {
+ var sz : [32]u8 = [0; 32];
+ const sl : u32 = num_grouped(sz[0..], 0, FSIZE);
+ n = fill(dst, n, SAY_PICKED, FNAME[0..(FNAME_LEN as usize)], sz[0..(sl as usize)], "", 2);
+ }
+ n = raw(dst, n, "
");
+ n = btn_open(dst, n, 2);
+ n = text(dst, n, SAY_READ);
+ n = raw(dst, n, "");
+ n = btn_open(dst, n, 2);
+ n = text(dst, n, SAY_CLEAR);
+ n = raw(dst, n, "
");
+ if (STATUS_MODE == 0) { n = text(dst, n, SAY_LOCAL_NOTE); }
+ if (STATUS_MODE == 1) {
+ var cs : [32]u8 = [0; 32];
+ const cl : u32 = num_grouped(cs[0..], 0, W_TEXT_CAP as u64);
+ n = fill(dst, n, SAY_CUT, cs[0..(cl as usize)], "", "", 1);
+ }
+ if (STATUS_MODE == 2) { n = fill(dst, n, SAY_ERR_READ, FNAME[0..(FNAME_LEN as usize)], "", "", 1); }
+ n = raw(dst, n, "
");
+ if (RS_KIND == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ERR_LOAD);
+ n = raw(dst, n, "
");
+ }
+ if (RS_KIND == 1) { n = result_html(dst, n); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PNG_BUTTON);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HONEST);
+ n = raw(dst, n, "
");
+ n = verdict_word(dst, n, vk, true);
+ n = raw(dst, n, "");
+ var ls : [32]u8 = [0; 32];
+ const ll : u32 = num_grouped(ls[0..], 0, RES[0]);
+ n = fill(dst, n, SAY_VERDICT_LINE, SRC[0..(SRC_LEN as usize)], ls[0..(ll as usize)], "", 2);
+ n = raw(dst, n, "
");
+ if (has_version()) {
+ n = fill(dst, n, SAY_VERSION, TEXT[(RES[1] as usize)..((RES[1] + RES[2]) as usize)], "", "", 1);
+ } else {
+ n = text(dst, n, SAY_NO_VERSION);
+ }
+ n = raw(dst, n, "");
+ if (has_command()) {
+ n = raw(dst, n, "");
+ n = fill(dst, n, SAY_COMMAND, TEXT[(RES[3] as usize)..((RES[3] + RES[4]) as usize)], "", "", 1);
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "
");
+ if (vk == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_UNKNOWN_LINE);
+ n = raw(dst, n, "
");
+ }
+ if (INFO_ON) {
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_SOURCE_LINE, "", "", "", 1);
+ n = raw(dst, n, "");
+ n = info_kid(dst, n);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < 7) {
+ n = card_html(dst, n, i);
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = cells_html(dst, n);
+ return n;
+}
+
+/// v.word: the verdict's words, escaped for the page or as they are for the card.
+fn verdict_word(dst: []u8, n0: u32, vk: u32, esc: bool) -> u32 {
+ if (vk == 0) { return word_to(dst, n0, SAY_VERDICT_UNKNOWN, esc); }
+ if (vk == 1) { return word_to(dst, n0, SAY_VERDICT_REFUSED, esc); }
+ if (vk == 2) { return word_to(dst, n0, SAY_VERDICT_CLEAN, esc); }
+ if (vk == 3) { return word_to(dst, n0, SAY_VERDICT_ONE, esc); }
+ var a : [32]u8 = [0; 32];
+ const la : u32 = dec_to(a[0..], verdict_count() as u64);
+ if (esc) { return fill(dst, n0, SAY_VERDICT_SMELLS, a[0..(la as usize)], "", "", 1); }
+ return fill_plain(dst, n0, SAY_VERDICT_SMELLS, a[0..(la as usize)], "", "", 1);
+}
+
+fn word_to(dst: []u8, n0: u32, w: []const u8, esc: bool) -> u32 {
+ if (esc) { return text(dst, n0, w); }
+ return raw(dst, n0, w);
+}
+
+/// The item kinds the card of smell i lists (the glue reads kinds 0 .. 4): 9 for none.
+fn kind_of_smell(i: u32) -> u32 {
+ if (i == 0) { return W_IK_ERROR; }
+ if (i == 1) { return W_IK_LATCH; }
+ if (i == 2) { return W_IK_UNDRIVEN; }
+ if (i == 3) { return W_IK_CONFLICT; }
+ if (i == 4) { return W_IK_MEMORY; }
+ return 9;
+}
+
+fn item_count(kd: u32) -> u32 {
+ if (kd == 9) { return 0; }
+ var c : u32 = 0;
+ var q : usize = 0;
+ const ni : usize = RES[28] as usize;
+ while (q < ni) {
+ if (ITEMS[q * 8] == kd) { c = c + 1; }
+ q = q + 1;
+ }
+ return c;
+}
+
+/// the index (in ITEMS) of the j-th item of kind kd
+fn item_at(kd: u32, j: u32) -> u32 {
+ var c : u32 = 0;
+ var q : usize = 0;
+ const ni : usize = RES[28] as usize;
+ while (q < ni) {
+ if (ITEMS[q * 8] == kd) {
+ if (c == j) { return q as u32; }
+ c = c + 1;
+ }
+ q = q + 1;
+ }
+ return 0;
+}
+
+fn card_html(dst: []u8, n0: u32, i: u32) -> u32 {
+ const sn : u64 = smell_n(i);
+ var n : u32 = raw(dst, n0, "= 2) { n = raw(dst, n, "is-bad"); }
+ }
+ n = raw(dst, n, "\">
");
+ n = text(dst, n, SAY_SMELL_NAMES[i as usize]);
+ n = raw(dst, n, "
");
+ if (sn == 0) { n = text(dst, n, SAY_NONE); } else { n = smell_count_text(dst, n, i, true); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SMELL_MEANINGS[i as usize]);
+ n = raw(dst, n, "
");
+ const kd : u32 = kind_of_smell(i);
+ const total : u32 = item_count(kd);
+ if (total > 0) {
+ n = raw(dst, n, "
");
+ var shown : u32 = total;
+ if (shown > (K_LIST_MAX as u32)) { shown = K_LIST_MAX as u32; }
+ var j : u32 = 0;
+ while (j < shown) {
+ n = raw(dst, n, "
");
+ n = item_html(dst, n, kd, item_at(kd, j));
+ n = raw(dst, n, "
");
+ j = j + 1;
+ }
+ if (total > shown) {
+ var mb : [32]u8 = [0; 32];
+ const ml : u32 = dec_to(mb[0..], (total - shown) as u64);
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_MORE, mb[0..(ml as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_PASS_LABEL, SAY_SMELL_PASSES[i as usize], "", "", 1);
+ n = raw(dst, n, "
");
+ return n;
+}
+
+/// the evidence line of one item: at(e) for an error, the name for the rest
+fn item_html(dst: []u8, n0: u32, kd: u32, q: u32) -> u32 {
+ const b : usize = (q as usize) * 8;
+ if (kd == W_IK_ERROR) {
+ const line : u32 = ITEMS[b + 5];
+ const mo : usize = ITEMS[b + 3] as usize;
+ const ml : usize = ITEMS[b + 4] as usize;
+ if (line != NONE and line != 0) {
+ var fl : [32]u8 = [0; 32];
+ const fl_n : u32 = dec_to(fl[0..], line as u64);
+ var fo : usize = 0;
+ var fe : usize = 0;
+ if (ITEMS[b + 1] != NONE) {
+ fo = ITEMS[b + 1] as usize;
+ fe = fo + (ITEMS[b + 2] as usize);
+ }
+ return fill(dst, n0, SAY_REFUSED_LINE, TEXT[fo..fe], fl[0..(fl_n as usize)], TEXT[mo..(mo + ml)], 3);
+ }
+ return text(dst, n0, TEXT[mo..(mo + ml)]);
+ }
+ return text(dst, n0, item_a(q));
+}
+
+fn cells_html(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_CELLS_TITLE);
+ n = raw(dst, n, "
");
+ if (RES[14] != 1) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NO_STAT);
+ n = raw(dst, n, "
");
+ return n;
+ }
+ var tot : u64 = 0;
+ if (RES[15] == 1) { tot = RES[16]; }
+ var ta : [32]u8 = [0; 32];
+ const la : u32 = num_grouped(ta[0..], 0, tot);
+ n = raw(dst, n, "
");
+ if (RES[17] != 0) {
+ var tb : [32]u8 = [0; 32];
+ const lb : u32 = num_grouped(tb[0..], 0, RES[17]);
+ n = fill(dst, n, SAY_CELLS_SUB, ta[0..(la as usize)], tb[0..(lb as usize)], "", 2);
+ } else {
+ n = fill(dst, n, SAY_CELLS_TOTAL, ta[0..(la as usize)], "", "", 1);
+ }
+ n = raw(dst, n, "
");
+ var mx : u64 = 1;
+ var g : u32 = 0;
+ while (g < 10) {
+ if (RES[(18 + g) as usize] > mx) { mx = RES[(18 + g) as usize]; }
+ g = g + 1;
+ }
+ g = 0;
+ while (g < 10) {
+ const gn : u64 = RES[(18 + g) as usize];
+ if (gn > 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_GROUP_NAMES[g as usize]);
+ n = raw(dst, n, "");
+ n = num_grouped(dst, n, gn);
+ n = raw(dst, n, "
");
+ }
+ g = g + 1;
+ }
+ n = raw(dst, n, "
");
+ if (CELLS_OPEN) { n = btn_open(dst, n, 1); } else { n = btn_open(dst, n, 0); }
+ if (CELLS_OPEN) { n = text(dst, n, SAY_HIDE_CELLS); } else { n = text(dst, n, SAY_SHOW_CELLS); }
+ n = raw(dst, n, "
");
+ var c : u32 = 0;
+ while (c < (SAY_COLS.len as u32)) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COLS[c as usize]);
+ n = raw(dst, n, "
");
+ c = c + 1;
+ }
+ n = raw(dst, n, "
");
+ var r : u32 = 0;
+ while (r < ORD_N) {
+ const q : u32 = ORD[r as usize];
+ const t : []const u8 = item_a(q);
+ n = raw(dst, n, "
");
+ n = text(dst, n, t);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_GROUP_NAMES[group_of(t) as usize]);
+ n = raw(dst, n, "
");
+ n = num_grouped(dst, n, ITEMS[(q as usize) * 8 + 5] as u64);
+ n = raw(dst, n, "
");
+ r = r + 1;
+ }
+ n = raw(dst, n, "
");
+ return n;
+}
+
+// --- the card: tool.js savePng() -----------------------------------------------------------------
+
+fn fit_at(o: []u8, n0: u32, t: []const u8, maxw: f64, size: u32, weight: []const u8, x: f64, y: f64) -> u32 {
+ var font : [160]u8 = [0; 160];
+ var fl : u32 = raw(font[0..], 0, weight);
+ fl = put(font[0..], fl, 32);
+ fl = num(font[0..], fl, size as u64);
+ fl = raw(font[0..], fl, "px ");
+ fl = raw(font[0..], fl, W_MONO);
+ var s : usize = t.len;
+ if (s > (W_FIELD_CAP as usize)) { s = W_FIELD_CAP as usize; }
+ var n : u32 = cv_open(o, n0, 120, t[0..s]);
+ n = cv_arg_f(o, n, maxw);
+ n = cv_arg_s(o, n, font[0..(fl as usize)]);
+ n = cv_arg_f(o, n, x);
+ n = cv_arg_f(o, n, y);
+ return cv_close(o, n);
+}
+
+fn card_color(o: []u8, n0: u32, which: u32) -> u32 {
+ if (which == 0) { return cv_set_var(o, n0, "fillStyle", "--w-bg", "#000"); }
+ if (which == 1) { return cv_set_var(o, n0, "fillStyle", "--w-green", "#00ff88"); }
+ if (which == 2) { return cv_set_var(o, n0, "fillStyle", "--w-gold", "#ffd700"); }
+ if (which == 3) { return cv_set_var(o, n0, "fillStyle", "--w-red", "#ff4d6d"); }
+ if (which == 4) { return cv_set_var(o, n0, "fillStyle", "--w-text", "#e8fff7"); }
+ if (which == 5) { return cv_set_var(o, n0, "fillStyle", "--w-muted", "#adc9c0"); }
+ return cv_set_var(o, n0, "fillStyle", "--w-subtle", "#8fa9a0");
+}
+
+pub fn w_card(dst: []u8) -> u32 {
+ if (!HAS_RESULT) { return 0; }
+ const wd : f64 = K_CARD_W as f64;
+ const hd : f64 = K_CARD_H as f64;
+ var n : u32 = cv_canvas(dst, 0, 0);
+ n = card_color(dst, n, 0);
+ n = cv_call4(dst, n, "fillRect", 0.0, 0.0, wd, hd);
+ n = cv_set_str(dst, n, "strokeStyle", "rgba(0,255,136,0.35)");
+ n = cv_set_num(dst, n, "lineWidth", 2.0);
+ n = cv_call4(dst, n, "strokeRect", 14.0, 14.0, wd - 28.0, hd - 28.0);
+ n = cv_set_str(dst, n, "textBaseline", "alphabetic");
+ n = card_color(dst, n, 1);
+ n = fit_at(dst, n, SAY_CARD_BRAND, wd - 120.0, 20, "600", 48.0, 64.0);
+ const vk : u32 = verdict_key();
+ const vc : u32 = verdict_cls(vk);
+ if (vc == 0) { n = card_color(dst, n, 1); }
+ if (vc == 1) { n = card_color(dst, n, 3); }
+ if (vc == 2) { n = card_color(dst, n, 2); }
+ var vw : [64]u8 = [0; 64];
+ const vl : u32 = verdict_word(vw[0..], 0, vk, false);
+ n = fit_at(dst, n, vw[0..(vl as usize)], wd - 96.0, 64, "800", 48.0, 140.0);
+ n = card_color(dst, n, 5);
+ var lb : [3200]u8 = [0; 3200];
+ var ln : [32]u8 = [0; 32];
+ const lnl : u32 = num_grouped(ln[0..], 0, RES[0]);
+ const ll : u32 = fill_plain(lb[0..], 0, SAY_VERDICT_LINE, SRC[0..(SRC_LEN as usize)], ln[0..(lnl as usize)], "", 2);
+ n = fit_at(dst, n, lb[0..(ll as usize)], wd - 96.0, 22, "", 48.0, 178.0);
+ n = card_color(dst, n, 6);
+ var vb : [3200]u8 = [0; 3200];
+ var vn : u32 = 0;
+ if (has_version()) {
+ vn = fill_plain(vb[0..], 0, SAY_VERSION, TEXT[(RES[1] as usize)..((RES[1] + RES[2]) as usize)], "", "", 1);
+ } else {
+ vn = raw(vb[0..], 0, SAY_NO_VERSION);
+ }
+ n = fit_at(dst, n, vb[0..(vn as usize)], wd - 96.0, 18, "", 48.0, 206.0);
+ var y : f64 = 254.0;
+ var i : u32 = 0;
+ while (i < 7) {
+ const sn : u64 = smell_n(i);
+ var lc : u32 = 6;
+ if (sn != 0) {
+ const lv : u8 = K_SMELL_LEVELS[i as usize];
+ if (lv == 1) { lc = 2; }
+ if (lv >= 2) { lc = 3; }
+ }
+ n = card_color(dst, n, lc);
+ n = cv_call0(dst, n, "beginPath");
+ n = cv_arc(dst, n, 58.0, y - 8.0, 7.0, 0.0, 6.283185307179586, false);
+ n = cv_call0(dst, n, "fill");
+ if (sn != 0) { n = card_color(dst, n, 4); } else { n = card_color(dst, n, 6); }
+ if (sn != 0) {
+ n = fit_at(dst, n, SAY_SMELL_NAMES[i as usize], 520.0, 24, "700", 80.0, y);
+ } else {
+ n = fit_at(dst, n, SAY_SMELL_NAMES[i as usize], 520.0, 24, "", 80.0, y);
+ }
+ n = card_color(dst, n, lc);
+ var cb : [200]u8 = [0; 200];
+ var cl : u32 = 0;
+ if (sn != 0) { cl = smell_count_text(cb[0..], 0, i, false); } else { cl = raw(cb[0..], 0, SAY_NONE); }
+ n = fit_at(dst, n, cb[0..(cl as usize)], wd - 700.0, 24, "700", 620.0, y);
+ y = y + 40.0;
+ i = i + 1;
+ }
+ n = card_color(dst, n, 2);
+ var cells : [1200]u8 = [0; 1200];
+ var cn : u32 = 0;
+ if (RES[14] == 1) {
+ var t1 : [32]u8 = [0; 32];
+ var tot : u64 = 0;
+ if (RES[15] == 1) { tot = RES[16]; }
+ const t1l : u32 = num_grouped(t1[0..], 0, tot + RES[17]);
+ var gl : [900]u8 = [0; 900];
+ var gn : u32 = 0;
+ var g : u32 = 0;
+ var firstg : bool = true;
+ while (g < 10) {
+ const gv : u64 = RES[(18 + g) as usize];
+ if (gv > 0) {
+ if (!firstg) {
+ gn = put(gl[0..], gn, 44);
+ gn = put(gl[0..], gn, 32);
+ }
+ firstg = false;
+ gn = num_grouped(gl[0..], gn, gv);
+ gn = put(gl[0..], gn, 32);
+ gn = raw(gl[0..], gn, SAY_GROUP_NAMES[g as usize]);
+ }
+ g = g + 1;
+ }
+ cn = fill_plain(cells[0..], 0, SAY_CARD_CELLS, t1[0..(t1l as usize)], gl[0..(gn as usize)], "", 2);
+ } else {
+ cn = raw(cells[0..], 0, SAY_NO_STAT);
+ }
+ n = fit_at(dst, n, cells[0..(cn as usize)], wd - 96.0, 20, "", 48.0, y + 14.0);
+ n = card_color(dst, n, 6);
+ n = fit_at(dst, n, SAY_CARD_URL, wd - 96.0, 18, "", 48.0, hd - 44.0);
+ if (!cv_ok(n)) { return 0; }
+ return n;
+}
+
+// --- Tests of the native page -------------------------------------------------------------------------
+
+fn has(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (n < (want.len as u32)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+pub const W_TEST_LOG : str = "Yosys 0.9\n-- Running command `read_verilog a.v' --\nWarning: Latch inferred for signal `\\x' from process `p'\nWarning: Wire a [1] is used but has no driver.\nWarning: Wire a [0] is used but has no driver.\nRemoved 1200 unused cells and 3 unused wires.\n2. Printing statistics.\n\n=== t ===\n\n 9 cells\n 4 LUT2\n 3 FDRE\n 2 Lut\n 1 submodules\n";
+
+fn open_page() -> u32 {
+ w_init("");
+ w_base("http://h/w/");
+ w_data(0, "{\"samples\":[]}");
+ w_data(1, W_TEST_LOG);
+ w_data(2, "x");
+ w_data(3, "x");
+ w_data(4, W_TEST_LOG);
+ return 0;
+}
+
+test the_page_opens_on_the_last_sample_and_waits_for_its_data {
+ var o : [65536]u8 = [0; 65536];
+ w_init("");
+ w_base("http://h/w/");
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "aria-pressed=\"true\">Teaching sample");
+ assert has(o[0..], n, "href=\"http://h/w/samples/teaching.log\"");
+ assert !has(o[0..], n, "ss-verdict");
+ w_data(0, "{\"samples\":[]}");
+ w_data(4, W_TEST_LOG);
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "
");
+}
+
+test a_log_is_counted_and_its_cards_are_written {
+ open_page();
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert CNT[1] == 1;
+ assert CNT[2] == 1;
+ assert CNT[3] == 2;
+ assert CNT[6] == 1200;
+ assert verdict_key() == 4;
+ assert has(o[0..], n, "3 smells");
+ assert has(o[0..], n, "1, bits: 2");
+ assert has(o[0..], n, "cells: 1,200, wires: 3");
+ assert has(o[0..], n, "9 cells, and 1 more that yosys lists as submodules");
+}
+
+test cell_types_sort_by_count_then_locale_name {
+ open_page();
+ w_click(6);
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "aria-pressed=\"true\">Hide cell types");
+ assert !has(o[0..], n, "
");
+ w_file(0, "q.log", 5);
+ w_file_failed(0);
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "Could not read q.log.");
+ assert has(o[0..], m, "q.log: 5 bytes");
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader, the number rules and the
+/// canvas writers call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + num_grouped(o, 0, 0);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ n = n + CV_OVER;
+ n = n + (cv_ok(0) as u32);
+ n = n + cv_put(o, 0, 0);
+ n = n + cv_raw(o, 0, s);
+ n = n + (cv_fin(0.0) as u32);
+ n = n + cv_num(o, 0, 0.0);
+ n = n + cv_str(o, 0, s);
+ n = n + (cv_hex(0) as u32);
+ n = n + cv_open(o, 0, 0, s);
+ n = n + cv_close(o, 0);
+ n = n + cv_arg_f(o, 0, 0.0);
+ n = n + cv_arg_s(o, 0, s);
+ n = n + cv_arg_v(o, 0, s, s);
+ n = n + cv_arg_b(o, 0, false);
+ n = n + cv_arg_list(o, 0, 0.0, 0.0, 0);
+ n = n + cv_canvas(o, 0, 0);
+ n = n + cv_set_num(o, 0, s, 0.0);
+ n = n + cv_set_str(o, 0, s, s);
+ n = n + cv_set_var(o, 0, s, s, s);
+ n = n + cv_call0(o, 0, s);
+ n = n + cv_call2(o, 0, s, 0.0, 0.0);
+ n = n + cv_call4(o, 0, s, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_call6(o, 0, s, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_arc(o, 0, 0.0, 0.0, 0.0, 0.0, 0.0, false);
+ n = n + cv_text(o, 0, s, s, 0.0, 0.0);
+ n = n + cv_dash(o, 0, 0.0, 0.0, 0);
+ return n;
+}
From 769fba9ef619cd28aa9270090ec09dda8ec084cf Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 20:54:00 +0700
Subject: [PATCH 51/63] widgets: text_meas -- a memo of host text widths for
native cards (Closes #10938)
tm_width / tm_fit_left / tm_wrap: the card asks, the host measures on a scratch canvas,
the card is written again; 4 tests, 20 asserts. Used by vcd-wrapped, bit-diff, bit-autopsy.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/text_meas.t27 | 477 ++++++++++++++++++++++++++++++++++++
1 file changed, 477 insertions(+)
create mode 100644 specs/widgets/text_meas.t27
diff --git a/specs/widgets/text_meas.t27 b/specs/widgets/text_meas.t27
new file mode 100644
index 0000000000..05be0fd5f9
--- /dev/null
+++ b/specs/widgets/text_meas.t27
@@ -0,0 +1,477 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/text_meas.t27 -- text widths for a native widget card: the host measures, the wasm asks. Epic
+; gHashTag/t27#10937, issue #10938. A tool.js that draws a PNG card fits text to a width with
+; ctx.measureText(s).width and decides the layout from the answers (cut a name from the left until it fits, wrap a
+; sentence, shrink a headline). A wasm cannot call measureText, and the answers change the draw list, so the page asks
+; the way specs/widgets/canvas_out.t27 asks for nothing: it keeps a MEMO of (font, text) -> width. A width that is not
+; in the memo is added as a request and read as 0.0; the page then writes a draw list that is WRONG, sets the miss flag
+; (tm_missed) and the host throws that list away, measures every open request on a scratch canvas context and gives the
+; widths back (tm_give), and calls the page again, until nothing is missing. The memo survives, so a second card of
+; the same words costs one pass. tm_fit_left and tm_wrap ask for the next 31 candidates at once, so a loop that
+; JavaScript runs 60 times costs 2 passes, not 60.
+; --
+; Host protocol (the page exports it, the host loops; host.js):
+; w_meas_need(dst) -> bytes the open requests as font NUL text NUL records, 0 when none
+; w_meas_give(text) one width per open request, in the same order, as decimal text separated by LF
+; w_card(dst) -> len is called again while w_meas_need answers more than 0
+; The memo is a set of arrays the PAGE owns (this module keeps no state; t27c's `use` does not carry a `pub var`):
+; tm u32 x (8 + 4 * N) [0] entries, [1] bytes of pool used, [2] miss flag, [3] overflow flag; then per entry
+; font offset, font length, text offset, text length (offsets into pool)
+; pool u8 x P the bytes of every font and text asked
+; wid f64 x N the width of an entry (0.0 until given)
+; ok u8 x N 1 once the width was given
+; An entry is found by its exact bytes. When the arrays are full, tm_width sets the overflow flag and answers 0.0:
+; the card is drawn with wrong widths (the page cannot tell the host more).
+; ASCII only (L3), English only (LANG-EN).
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_text_meas;
+
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+
+pub const TM_HEAD : u32 = 8;
+pub const TM_AHEAD : u32 = 32;
+
+pub fn tm_reset(tm: []u32) {
+ tm[0] = 0;
+ tm[1] = 0;
+ tm[2] = 0;
+ tm[3] = 0;
+}
+
+pub fn tm_missed(tm: []const u32) -> bool {
+ return tm[2] != 0;
+}
+
+pub fn tm_clear_miss(tm: []u32) {
+ tm[2] = 0;
+}
+
+fn same_at(pool: []const u8, off: usize, len: usize, s: []const u8) -> bool {
+ if (len != s.len) { return false; }
+ var i : usize = 0;
+ while (i < len) {
+ if (pool[off + i] != s[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+/// The index of the entry (font, text), or -1.
+pub fn tm_find(tm: []const u32, pool: []const u8, font: []const u8, text: []const u8) -> i64 {
+ var i : u32 = 0;
+ const n : u32 = tm[0];
+ while (i < n) {
+ const b : usize = ((TM_HEAD + 4 * i) as usize);
+ if (same_at(pool, tm[b + 2] as usize, tm[b + 3] as usize, text) and same_at(pool, tm[b] as usize, tm[b + 1] as usize, font)) { return i as i64; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+fn pool_add(pool: []u8, at: u32, s: []const u8) {
+ var i : usize = 0;
+ while (i < s.len) {
+ pool[(at as usize) + i] = s[i];
+ i = i + 1;
+ }
+}
+
+/// Add the entry (font, text) when there is room. Returns its index, or -1 when the arrays are full.
+pub fn tm_add(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8, text: []const u8) -> i64 {
+ const n : u32 = tm[0];
+ const used : u32 = tm[1];
+ const need : u32 = (font.len as u32) + (text.len as u32);
+ if ((n as usize) >= wid.len or ((used + need) as usize) > pool.len or tm.len < ((TM_HEAD + 4 * (n + 1)) as usize)) {
+ tm[3] = 1;
+ return 0 - 1;
+ }
+ const b : usize = ((TM_HEAD + 4 * n) as usize);
+ tm[b] = used;
+ tm[b + 1] = font.len as u32;
+ tm[b + 2] = used + (font.len as u32);
+ tm[b + 3] = text.len as u32;
+ pool_add(pool, used, font);
+ pool_add(pool, used + (font.len as u32), text);
+ wid[n as usize] = 0.0;
+ ok[n as usize] = 0;
+ tm[0] = n + 1;
+ tm[1] = used + need;
+ return n as i64;
+}
+
+/// ctx.font = font; ctx.measureText(text).width -- from the memo; 0.0 and the miss flag when it is not known yet.
+pub fn tm_width(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8, text: []const u8) -> f64 {
+ var i : i64 = tm_find(tm, pool, font, text);
+ if (i < 0) { i = tm_add(tm, pool, wid, ok, font, text); }
+ if (i < 0) {
+ tm[2] = 1;
+ return 0.0;
+ }
+ if (ok[i as usize] == 0) {
+ tm[2] = 1;
+ return 0.0;
+ }
+ return wid[i as usize];
+}
+
+/// Ask for a width without using it (look-ahead).
+pub fn tm_ask(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8, text: []const u8) {
+ var i : i64 = tm_find(tm, pool, font, text);
+ if (i < 0) { i = tm_add(tm, pool, wid, ok, font, text); }
+}
+
+/// The open requests, font NUL text NUL each; the length written (0 when none). A request that does not fit stays open.
+pub fn tm_need(tm: []const u32, pool: []const u8, ok: []const u8, dst: []u8) -> u32 {
+ var n : u32 = 0;
+ var i : u32 = 0;
+ const cnt : u32 = tm[0];
+ while (i < cnt) {
+ if (ok[i as usize] == 0) {
+ const b : usize = ((TM_HEAD + 4 * i) as usize);
+ const fl : u32 = tm[b + 1];
+ const tl : u32 = tm[b + 3];
+ if ((n + fl + tl + 2) as usize > dst.len) { return n; }
+ var k : u32 = 0;
+ while (k < fl) {
+ dst[(n + k) as usize] = pool[(tm[b] + k) as usize];
+ k = k + 1;
+ }
+ dst[(n + fl) as usize] = 0;
+ k = 0;
+ while (k < tl) {
+ dst[(n + fl + 1 + k) as usize] = pool[(tm[b + 2] + k) as usize];
+ k = k + 1;
+ }
+ dst[(n + fl + 1 + tl) as usize] = 0;
+ n = n + fl + tl + 2;
+ }
+ i = i + 1;
+ }
+ return n;
+}
+
+/// The widths of the open requests, in order, as decimal text separated by LF. Returns how many were given.
+pub fn tm_give(tm: []const u32, ok: []u8, wid: []f64, s: []const u8) -> u32 {
+ var given : u32 = 0;
+ var i : u32 = 0;
+ var p : usize = 0;
+ const cnt : u32 = tm[0];
+ while (i < cnt and p <= s.len) {
+ if (ok[i as usize] == 0) {
+ var e : usize = p;
+ while (e < s.len and s[e] != 10) { e = e + 1; }
+ var w : f64 = js_parse_num(s, p, e);
+ if (js_is_nan(w)) { w = 0.0; }
+ wid[i as usize] = w;
+ ok[i as usize] = 1;
+ given = given + 1;
+ p = e + 1;
+ }
+ i = i + 1;
+ }
+ return given;
+}
+
+// --- JavaScript string units of UTF-8 text ----------------------------------------------------------
+
+/// The UTF-16 length of the UTF-8 bytes t[lo .. hi): a 4-byte character is 2 units, the others 1.
+pub fn tm_units(t: []const u8, lo: usize, hi: usize) -> usize {
+ var n : usize = 0;
+ var i : usize = lo;
+ while (i < hi) {
+ const b : u8 = t[i];
+ if (b >= 240) { n = n + 2; } else { if (b < 128 or b >= 192) { n = n + 1; } }
+ i = i + 1;
+ }
+ return n;
+}
+
+/// The byte length of the UTF-8 character that starts at t[i].
+pub fn tm_charlen(t: []const u8, i: usize, hi: usize) -> usize {
+ var l : usize = 1;
+ const b : u8 = t[i];
+ if (b >= 240) { l = 4; } else { if (b >= 224) { l = 3; } else { if (b >= 192) { l = 2; } } }
+ if (i + l > hi) { l = hi - i; }
+ return l;
+}
+
+fn put_dots(scratch: []u8, t: []const u8, lo: usize) -> usize {
+ scratch[0] = 46;
+ scratch[1] = 46;
+ scratch[2] = 46;
+ var k : usize = lo;
+ var n : usize = 3;
+ while (k < t.len) {
+ scratch[n] = t[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+/// tool.js: fit = (t, room) => { let v = String(t); if (measure(v) <= room) return v;
+/// while (v.length > 1 && measure(`...${v}`) > room) v = v.slice(1); return `...${v}` }
+/// Returns the byte offset in t from which the result is `...` + t[off..], or -1 when t fits whole.
+/// scratch needs t.len + 3 bytes. While widths are missing the answer is meaningless (the miss flag is set).
+pub fn tm_fit_left(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8, t: []const u8, room: f64, scratch: []u8) -> i64 {
+ const whole : f64 = tm_width(tm, pool, wid, ok, font, t);
+ if (tm[2] != 0) { return 0 - 1; }
+ if (whole <= room) { return 0 - 1; }
+ var lo : usize = 0;
+ while (tm_units(t, lo, t.len) > 1) {
+ var n : usize = put_dots(scratch, t, lo);
+ const w : f64 = tm_width(tm, pool, wid, ok, font, scratch[0..n]);
+ if (tm[2] != 0) {
+ // ask for the next candidates in one go
+ var a : u32 = 0;
+ var q : usize = lo;
+ while (a < TM_AHEAD and q < t.len) {
+ q = q + tm_charlen(t, q, t.len);
+ n = put_dots(scratch, t, q);
+ tm_ask(tm, pool, wid, ok, font, scratch[0..n]);
+ a = a + 1;
+ }
+ return 0 - 1;
+ }
+ if (w <= room) { return lo as i64; }
+ lo = lo + tm_charlen(t, lo, t.len);
+ }
+ return lo as i64;
+}
+
+/// tool.js: wrap = (t, room) => { lines = []; cur = ''; for (w of t.split(' ')) { next = cur ? `${cur} ${w}` : w;
+/// if (measure(next) > room && cur) { lines.push(cur); cur = w } else cur = next } if (cur) lines.push(cur) }
+/// The lines are substrings of t: line k is t[spans[2k] .. spans[2k + 1]). Returns the number of lines (at most
+/// spans.len / 2; the rest is dropped).
+pub fn tm_wrap(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8, t: []const u8, room: f64, spans: []u32) -> u32 {
+ var nl : u32 = 0;
+ var cl : usize = 0;
+ var ch : usize = 0;
+ var p : usize = 0;
+ var more : bool = true;
+ while (more) {
+ var e : usize = p;
+ while (e < t.len and t[e] != 32) { e = e + 1; }
+ var nlo : usize = p;
+ if (ch > cl) { nlo = cl; }
+ const w : f64 = tm_width(tm, pool, wid, ok, font, t[nlo..e]);
+ if (w > room and ch > cl) {
+ if (((nl + 1) as usize) * 2 <= spans.len) {
+ spans[(nl as usize) * 2] = cl as u32;
+ spans[(nl as usize) * 2 + 1] = ch as u32;
+ }
+ nl = nl + 1;
+ cl = p;
+ ch = e;
+ } else {
+ cl = nlo;
+ ch = e;
+ }
+ if (e >= t.len) { more = false; } else { p = e + 1; }
+ }
+ if (ch > cl) {
+ if (((nl + 1) as usize) * 2 <= spans.len) {
+ spans[(nl as usize) * 2] = cl as u32;
+ spans[(nl as usize) * 2 + 1] = ch as u32;
+ }
+ nl = nl + 1;
+ }
+ return nl;
+}
+
+fn m_same(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+pub var T_TM : [64]u32 = [0; 64];
+pub var T_POOL : [4096]u8 = [0; 4096];
+pub var T_WID : [12]f64 = [0.0; 12];
+pub var T_OK : [12]u8 = [0; 12];
+pub var T_NEED : [512]u8 = [0; 512];
+pub var T_SC : [64]u8 = [0; 64];
+pub var T_SP : [16]u32 = [0; 16];
+
+/// Give every open request width = 10 * bytes of its text (a stand-in for the host).
+fn t_give_all() -> u32 {
+ var s : [200]u8 = [0; 200];
+ var n : usize = 0;
+ var i : u32 = 0;
+ while (i < T_TM[0]) {
+ if (T_OK[i as usize] == 0) {
+ const tl : u32 = T_TM[(TM_HEAD + 4 * i + 3) as usize];
+ const w : u32 = tl * 10;
+ var d : [12]u8 = [0; 12];
+ var k : usize = 0;
+ var v : u32 = w;
+ if (v == 0) { d[0] = 48; k = 1; }
+ while (v > 0) {
+ d[k] = ((v % 10) as u8) + 48;
+ v = v / 10;
+ k = k + 1;
+ }
+ while (k > 0) {
+ k = k - 1;
+ s[n] = d[k];
+ n = n + 1;
+ }
+ s[n] = 10;
+ n = n + 1;
+ }
+ i = i + 1;
+ }
+ return tm_give(T_TM[0..64], T_OK[0..12], T_WID[0..12], s[0..n]);
+}
+
+test a_width_not_in_the_memo_is_a_request_and_the_miss_flag {
+ tm_reset(T_TM[0..64]);
+ var w : f64 = tm_width(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "bold 20px m", "abc");
+ assert w == 0.0;
+ assert tm_missed(T_TM[0..64]);
+ assert T_TM[0] == 1;
+ const n : u32 = tm_need(T_TM[0..64], T_POOL[0..4096], T_OK[0..12], T_NEED[0..512]);
+ assert n == 11 + 3 + 2;
+ assert T_NEED[0] == 98 and T_NEED[11] == 0 and T_NEED[12] == 97 and T_NEED[15] == 0;
+ assert t_give_all() == 1;
+ tm_clear_miss(T_TM[0..64]);
+ w = tm_width(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "bold 20px m", "abc");
+ assert w == 30.0;
+ assert !tm_missed(T_TM[0..64]);
+ assert tm_need(T_TM[0..64], T_POOL[0..4096], T_OK[0..12], T_NEED[0..512]) == 0;
+}
+
+test fit_left_cuts_from_the_left_and_asks_ahead {
+ tm_reset(T_TM[0..64]);
+ const t : []const u8 = "top.dut.counter";
+ var r : i64 = 0 - 2;
+ var k : u32 = 0;
+ var passes : u32 = 0;
+ while (k < 20) {
+ tm_clear_miss(T_TM[0..64]);
+ r = tm_fit_left(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", t, 80.0, T_SC[0..64]);
+ passes = passes + 1;
+ if (!tm_missed(T_TM[0..64])) { k = 99; } else { t_give_all(); k = k + 1; }
+ }
+ assert k == 99;
+ assert r == 10;
+ assert passes <= 3;
+ tm_reset(T_TM[0..64]);
+ k = 0;
+ while (k < 20) {
+ tm_clear_miss(T_TM[0..64]);
+ r = tm_fit_left(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", "ab", 80.0, T_SC[0..64]);
+ if (!tm_missed(T_TM[0..64])) { k = 99; } else { t_give_all(); k = k + 1; }
+ }
+ assert r == 0 - 1;
+}
+
+test wrap_breaks_where_the_next_word_no_longer_fits {
+ tm_reset(T_TM[0..64]);
+ const t : []const u8 = "aa bb cc";
+ var k : u32 = 0;
+ var nl : u32 = 0;
+ while (k < 8) {
+ tm_clear_miss(T_TM[0..64]);
+ nl = tm_wrap(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", t, 50.0, T_SP[0..16]);
+ if (!tm_missed(T_TM[0..64])) { k = 99; } else { t_give_all(); k = k + 1; }
+ }
+ assert k == 99;
+ assert nl == 2;
+ assert T_SP[0] == 0 and T_SP[1] == 5 and T_SP[2] == 6 and T_SP[3] == 8;
+}
+
+test units_count_utf16_and_charlen_steps_over_utf8 {
+ var u : [3]u8 = [195, 169, 120];
+ assert tm_units("abc", 0, 3) == 3;
+ assert tm_units(u[0..], 0, 3) == 2;
+ assert tm_charlen(u[0..], 0, 3) == 2;
+ assert tm_charlen(u[0..], 2, 3) == 1;
+}
+
+/// The splicer follows only the names a module itself mentions; this mentions every helper of js_num that
+/// js_parse_num calls. Never called.
+pub fn tm_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ return n;
+}
From 17b28061a8f902e5c40bf0f6ce84d3dc21f347c3 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 21:10:38 +0700
Subject: [PATCH 52/63] widgets: text_meas -- helpers are pub so a page can
import them; wrap test with a sentence (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/text_meas.t27 | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/specs/widgets/text_meas.t27 b/specs/widgets/text_meas.t27
index 05be0fd5f9..344993e2cb 100644
--- a/specs/widgets/text_meas.t27
+++ b/specs/widgets/text_meas.t27
@@ -84,7 +84,7 @@ pub fn tm_clear_miss(tm: []u32) {
tm[2] = 0;
}
-fn same_at(pool: []const u8, off: usize, len: usize, s: []const u8) -> bool {
+pub fn same_at(pool: []const u8, off: usize, len: usize, s: []const u8) -> bool {
if (len != s.len) { return false; }
var i : usize = 0;
while (i < len) {
@@ -106,7 +106,7 @@ pub fn tm_find(tm: []const u32, pool: []const u8, font: []const u8, text: []cons
return 0 - 1;
}
-fn pool_add(pool: []u8, at: u32, s: []const u8) {
+pub fn pool_add(pool: []u8, at: u32, s: []const u8) {
var i : usize = 0;
while (i < s.len) {
pool[(at as usize) + i] = s[i];
@@ -233,7 +233,7 @@ pub fn tm_charlen(t: []const u8, i: usize, hi: usize) -> usize {
return l;
}
-fn put_dots(scratch: []u8, t: []const u8, lo: usize) -> usize {
+pub fn put_dots(scratch: []u8, t: []const u8, lo: usize) -> usize {
scratch[0] = 46;
scratch[1] = 46;
scratch[2] = 46;
@@ -423,6 +423,21 @@ test wrap_breaks_where_the_next_word_no_longer_fits {
assert T_SP[0] == 0 and T_SP[1] == 5 and T_SP[2] == 6 and T_SP[3] == 8;
}
+test wrap_a_sentence_of_many_words_into_two_lines {
+ tm_reset(T_TM[0..64]);
+ const t : []const u8 = "Your busiest net was dut.a, with 47 changes.";
+ var k : u32 = 0;
+ var nl : u32 = 0;
+ while (k < 30) {
+ tm_clear_miss(T_TM[0..64]);
+ nl = tm_wrap(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", t, 400.0, T_SP[0..16]);
+ if (!tm_missed(T_TM[0..64]) or T_TM[3] != 0) { k = 99; } else { t_give_all(); k = k + 1; }
+ }
+ assert T_TM[3] == 0;
+ assert nl == 2;
+ assert T_SP[0] == 0 and T_SP[1] == 35 and T_SP[2] == 36;
+}
+
test units_count_utf16_and_charlen_steps_over_utf8 {
var u : [3]u8 = [195, 169, 120];
assert tm_units("abc", 0, 3) == 3;
From a17a5af8cfbe82b59fdc036cafd45673a8dd530a Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 21:10:39 +0700
Subject: [PATCH 53/63] widgets: vcd-wrapped drawn natively -- 51721-byte wasm,
2078 DOM comparisons, 0 differences (Closes #10938)
Page, VCD reader (folded from vcd_wrapped_logic.t27), file/drop UI and the PNG card as a
canvas_out draw list whose text widths come from text_meas.t27. Measured against the
original tool.js in two happy-dom windows: DOM, canvas call sequence, downloads.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/vcd_wrapped.t27 | 3295 +++++++++++++++++++++++++++++++++
1 file changed, 3295 insertions(+)
create mode 100644 specs/widgets/vcd_wrapped.t27
diff --git a/specs/widgets/vcd_wrapped.t27 b/specs/widgets/vcd_wrapped.t27
new file mode 100644
index 0000000000..361fadf9a0
--- /dev/null
+++ b/specs/widgets/vcd_wrapped.t27
@@ -0,0 +1,3295 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/vcd-wrapped.t27 -- your simulation, wrapped: a summary card for a VCD file
+; Source of truth for public/widgets/vcd-wrapped/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its
+; own and reads every word below from window.T27_WIDGET.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader drops a .vcd file (IEEE 1364 value change dump, from iverilog/vvp,
+; Verilator, GHDL or any simulator that writes one). The page reads it in the browser with
+; public/widgets/vcd-wrapped/vcdstats.js, streaming the file in chunks so a large dump is read in
+; the memory of its signal list, never its waveform. It shows one card: the value changes and bit
+; flips in the whole run, the busiest nets, the quietest nets, the signals that never leave X or Z,
+; the signals that end in X or Z, the simulated time span, and how many signals and scopes the dump
+; declares. The card can be saved as a 1200x630 PNG drawn in the page. The file never leaves the
+; browser. It is a summary, not a viewer: the test-waves widget draws waveforms.
+
+; HOW IT COUNTS: a value change is a recorded value that differs from the signal's previous value;
+; the first value a signal gets (usually from $dumpvars) is its start, not a change. Bit flips count
+; the bit positions that differ between the two values. One id code declared in several scopes is
+; one signal with several names. A signal stays X/Z when none of its values is fully known.
+; Parameters are constants and are counted apart. Values inside $dumpoff are skipped.
+
+; THE SAMPLES: three real Icarus Verilog 12.0 runs, made one at a time by
+; scripts/widget-data/vcd-wrapped.mjs. "counter" is specs/ternary/clocked_counter.t27 compiled to
+; Verilog by public/t27/t27_compiler.wasm in node and driven by a hand-written testbench,
+; scripts/widget-data/vcd-wrapped-counter-tb.v (not generated from the spec). "vsa-bind" and "tmu"
+; are RTL committed under fpga/openxc7-synth/ with their own committed testbenches, run as they
+; are. The tmu testbench fails all its tests in this repository: tmu.v reads tmu_test_w_wide.mem
+; and no such file is committed, so its outputs stay X; it is shipped because that is what a real
+; broken run looks like. Each VCD is shipped as vvp dumped it, not trimmed: all are under
+; K_MAX_SAMPLE_BYTES. Every K_ fact below is re-read from the shipped VCDs by
+; `node scripts/widget-data/vcd-wrapped.mjs --check`.
+
+; WHAT IT DOES NOT CLAIM: that a busy net is a bug, or that a quiet one is fine. A signal stuck at
+; X is often a real fault (an unloaded memory, a missing reset) and sometimes harmless (the locals
+; of a function never called). The card counts what the dump recorded; a signal the testbench did
+; not $dumpvars is not in it. It does not run a simulation.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_vcd_wrapped;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::num_grouped;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+use widgets::canvas_out::cv_ok;
+use widgets::canvas_out::cv_put;
+use widgets::canvas_out::cv_raw;
+use widgets::canvas_out::cv_fin;
+use widgets::canvas_out::cv_num;
+use widgets::canvas_out::cv_str;
+use widgets::canvas_out::cv_hex;
+use widgets::canvas_out::cv_open;
+use widgets::canvas_out::cv_close;
+use widgets::canvas_out::cv_arg_f;
+use widgets::canvas_out::cv_arg_s;
+use widgets::canvas_out::cv_arg_v;
+use widgets::canvas_out::cv_arg_b;
+use widgets::canvas_out::cv_arg_list;
+use widgets::canvas_out::cv_canvas;
+use widgets::canvas_out::cv_set_num;
+use widgets::canvas_out::cv_set_str;
+use widgets::canvas_out::cv_set_var;
+use widgets::canvas_out::cv_call0;
+use widgets::canvas_out::cv_call2;
+use widgets::canvas_out::cv_call4;
+use widgets::canvas_out::cv_call6;
+use widgets::canvas_out::cv_arc;
+use widgets::canvas_out::cv_text;
+use widgets::canvas_out::cv_dash;
+use widgets::canvas_out::CV_OVER;
+use widgets::text_meas::TM_HEAD;
+use widgets::text_meas::TM_AHEAD;
+use widgets::text_meas::tm_reset;
+use widgets::text_meas::tm_missed;
+use widgets::text_meas::tm_clear_miss;
+use widgets::text_meas::same_at;
+use widgets::text_meas::tm_find;
+use widgets::text_meas::pool_add;
+use widgets::text_meas::tm_add;
+use widgets::text_meas::tm_width;
+use widgets::text_meas::tm_ask;
+use widgets::text_meas::tm_need;
+use widgets::text_meas::tm_give;
+use widgets::text_meas::tm_units;
+use widgets::text_meas::tm_charlen;
+use widgets::text_meas::put_dots;
+use widgets::text_meas::tm_fit_left;
+use widgets::text_meas::tm_wrap;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "vcd-wrapped";
+pub const TITLE : str = "VCD wrapped: your simulation, summed up on one card";
+pub const DESCRIPTION : str = "Drop a .vcd from any simulator and get a Wrapped-style card: value changes, the busiest net, signals stuck at X or Z, the quietest ones and the time span, read in your browser.";
+pub const IMAGE_ALT : str = "A Wrapped-style summary card on black for a real Icarus Verilog 12.0 run of the t27 ClockedCounter: 1,418 value changes over 2.925 us of simulated time, the busiest net clk with 585 changes, 4 signals that never leave X or Z, and 14 signals in 4 scopes.";
+pub const HOOK : str = "Your testbench toggled clk 585 times and left 4 signals at X all run. Drop your .vcd and see your simulation, wrapped.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [7]str = ["Icarus Verilog version 12.0 (stable), iverilog and vvp; one run per sample, one at a time: iverilog -g2012 -s -o sim.vvp ; vvp -n sim.vvp; each command, the tool versions and the testbench's verdict lines in samples.json", "specs/ternary/clocked_counter.t27, compiled to Verilog by public/t27/t27_compiler.wasm in node (sha256 of spec and generated Verilog in samples.json)", "scripts/widget-data/vcd-wrapped-counter-tb.v: a hand-written testbench for the generated ClockedCounter, not from the spec; it checks the spec's own test, hold on en low, the u8 wrap and the asynchronous reset", "fpga/openxc7-synth/vsa_bind_16.v with fpga/openxc7-synth/tb/tb_vsa_bind_16.v, and fpga/openxc7-synth/tmu.v with fpga/openxc7-synth/tmu_tb.v, committed RTL and testbenches run unchanged", "The VCDs are shipped as vvp dumped them, not trimmed; each is under 200000 bytes", "public/widgets/vcd-wrapped/vcdstats.js: the one VCD reader, run in node for the samples and in the browser for your file", "node scripts/widget-data/vcd-wrapped.mjs --check: re-reads the shipped VCDs and fails if any K_ fact in this spec disagrees"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- The samples (the data script reads these) -------------------------------------------------
+pub const K_SAMPLES_JSON : str = "samples.json";
+pub const K_SAMPLE_IDS : [3]str = ["counter", "vsa-bind", "tmu"];
+; Where each sample's design comes from: a spec under public/t27/files/specs/ (compiled by the wasm
+; compiler), or RTL committed in this repository (path from the repository root).
+pub const K_SAMPLE_KINDS : [3]str = ["t27", "repo", "repo"];
+pub const K_SAMPLE_SOURCES : [3]str = ["ternary/clocked_counter.t27", "fpga/openxc7-synth/vsa_bind_16.v", "fpga/openxc7-synth/tmu.v"];
+; The sample the page opens with and the gallery card is drawn from.
+pub const K_CARD_SAMPLE : str = "counter";
+; A VCD over this many bytes is not shipped as a sample.
+pub const K_MAX_SAMPLE_BYTES : u32 = 200000;
+
+; --- Facts from the run (scripts/widget-data/vcd-wrapped.mjs re-reads every one from the VCDs) ----
+; One entry per K_SAMPLE_IDS, in order. Times are in the dump's own $timescale ticks (1ps for all three).
+pub const K_CHANGES : [3]u32 = [1418, 152, 1180];
+pub const K_FLIPS : [3]u32 = [2766, 751, 2585];
+pub const K_SIGNALS : [3]u32 = [14, 32, 129];
+pub const K_VARS : [3]u32 = [18, 36, 135];
+pub const K_SCOPES : [3]u32 = [4, 2, 70];
+pub const K_PARAMS : [3]u32 = [0, 0, 93];
+pub const K_STUCK_XZ : [3]u32 = [4, 0, 3];
+pub const K_ENDS_XZ : [3]u32 = [0, 0, 4];
+pub const K_NEVER_CHANGED : [3]u32 = [6, 1, 6];
+pub const K_SPAN : [3]u32 = [2925000, 450000, 1945000];
+pub const K_STAMPS : [3]u32 = [595, 46, 390];
+pub const K_BUSIEST_CHANGES : [3]u32 = [585, 45, 389];
+; The testbench's own verdict, from the lines vvp printed: checks passed, and checks run.
+pub const K_CHECKS_PASS : [3]u32 = [7, 6, 0];
+pub const K_CHECKS_TOTAL : [3]u32 = [7, 6, 8];
+
+; --- How it reads and draws ------------------------------------------------------------------------
+pub const K_MIN_TARGET_PX : u8 = 32;
+pub const K_CARD_W : u16 = 1200;
+pub const K_CARD_H : u16 = 630;
+; Rows in the busiest, quietest and X/Z lists.
+pub const K_LIST_MAX : u8 = 5;
+; A file is read in slices of this many bytes when the browser has no streaming reader.
+pub const K_SLICE_BYTES : u32 = 4194304;
+; Refuse a file whose $enddefinitions has not come within this many characters.
+pub const K_MAX_HEADER_CHARS : u32 = 67108864;
+
+; --- Every word on the screen -------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_SAMPLES_LABEL : str = "Real simulations";
+pub const SAY_SAMPLE_NAMES : [3]str = ["t27 ClockedCounter", "VSA bind, 16 trits", "TMU (fails in this repo)"];
+pub const SAY_SAMPLE_LINES : [3]str = ["specs/ternary/clocked_counter.t27, compiled to Verilog by this site's wasm compiler and run by Icarus Verilog with a hand-written testbench (not from the spec). The signals stuck at X are the locals of a multiply helper the compiler emits and never calls.", "fpga/openxc7-synth/vsa_bind_16.v with its own committed testbench, run unchanged by Icarus Verilog.", "fpga/openxc7-synth/tmu.v with its own committed testbench, run unchanged: tmu.v reads tmu_test_w_wide.mem, which is not in the repository, so its weights load as X and its tests fail. A real broken run."];
+pub const SAY_SAMPLE_VERDICT : str = "The testbench's own verdict: {0} of {1} checks passed.";
+pub const SAY_SAMPLE_NOTE : str = "Shipped as vvp dumped it, not trimmed: {0} bytes.";
+pub const SAY_OWN_LABEL : str = "Your VCD";
+pub const SAY_DROP : str = "Drop a .vcd here, or pick a file";
+pub const SAY_PICK : str = "Pick a .vcd file";
+pub const SAY_READING : str = "Reading {0}: {1}%";
+pub const SAY_PICKED : str = "{0}: {1} bytes";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab, in chunks; nothing is uploaded.";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_LOAD : str = "The sample simulations did not load.";
+; Why a file is refused, one entry per reason code vcdstats.js raises. {0} is the token, {1} the time.
+pub const SAY_ERR_CODES : [9]str = ["not-vcd", "binary", "empty", "no-defs", "no-vars", "bad-var", "timescale", "unknown-id", "bad-token"];
+pub const SAY_ERR_WORDS : [9]str = ["Not a VCD file: a VCD starts with a $ keyword such as $date or $timescale, and this starts with \"{0}\".", "Not a VCD file: it holds binary data. VCD is text; FST, LXT and GHW dumps are not read here.", "The file is empty.", "No $enddefinitions in this file: the header never ends, so this is not a whole VCD.", "The header declares no $var, so there is nothing to count.", "A $var line could not be read: \"{0}\".", "The $timescale could not be read: \"{0}\".", "A value change names id code \"{0}\" at time {1}, and no $var declares it.", "Unexpected text \"{0}\" after time {1}: not a time stamp, a value change or a $ keyword."];
+
+pub const SAY_KICKER : str = "Your simulation, wrapped";
+pub const SAY_HEADLINE : str = "{0} value changes";
+pub const SAY_SUBHEAD : str = "over {0} of simulated time";
+pub const SAY_BUSIEST_LINE : str = "Your busiest net was {0}, with {1} changes.";
+pub const SAY_NO_MOVERS : str = "No signal changed after its first value.";
+pub const SAY_TILE_NAMES : [6]str = ["bit flips", "signals", "scopes", "time stamps", "never leave X/Z", "end in X/Z"];
+pub const SAY_LIST_TITLES : [4]str = ["Busiest nets", "Quietest nets that moved", "Never left X or Z", "End the run in X or Z"];
+pub const SAY_ROW : str = "changes {0}, flips {1}";
+pub const SAY_WIDTH : str = "{0} bits";
+pub const SAY_WIDTH_ONE : str = "1 bit";
+pub const SAY_ALIASES : str = "also under {0} other names";
+pub const SAY_ALIASES_ONE : str = "also under 1 other name";
+pub const SAY_MORE : str = "and {0} more";
+pub const SAY_NONE : str = "none";
+pub const SAY_NEVER_LINE : str = "{0} of {1} signals never changed after their first value.";
+pub const SAY_PARAMS_LINE : str = "{0} parameters are constants and are counted apart.";
+pub const SAY_VARS_LINE : str = "{0} $var lines name {1} distinct signals.";
+pub const SAY_VERSION : str = "Dumped by {0}";
+pub const SAY_TIMESCALE : str = "$timescale {0}{1}";
+pub const SAY_UNITS : [6]str = ["s", "ms", "us", "ns", "ps", "fs"];
+pub const SAY_NO_TIMESCALE : str = "ticks (no $timescale)";
+pub const SAY_RULES : str = "A change is a value that differs from the one before; a signal's first value is not a change. Flips count the bits that differ. Values inside $dumpoff are skipped.";
+
+pub const SAY_PNG_BUTTON : str = "Save as PNG card";
+pub const SAY_PNG_FILE : str = "vcd-wrapped-{0}.png";
+pub const SAY_CARD_BRAND : str = "TRINITY S3AI / VCD WRAPPED";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/vcd-wrapped/ -- read in the browser, nothing uploaded";
+pub const SAY_HONEST : str = "A busy net is not a bug and a quiet one is not a pass. A signal stuck at X can be a real fault or a function never called. This counts what the dump recorded; it runs no simulation.";
+
+; --- Tests: each holds of this file, and scripts/widget-data/vcd-wrapped.mjs runs them again with
+; every K_ fact replaced by the value read from the shipped VCDs.
+test the_t27_counter_passes_its_checks_and_clk_is_busiest {
+ assert K_SAMPLE_KINDS[0] == "t27";
+ assert K_SAMPLE_IDS[0] == K_CARD_SAMPLE;
+ assert K_CHECKS_TOTAL[0] > 0;
+ assert K_CHECKS_PASS[0] == K_CHECKS_TOTAL[0];
+ assert K_BUSIEST_CHANGES[0] > 0;
+ assert K_BUSIEST_CHANGES[0] <= K_CHANGES[0];
+ assert K_STUCK_XZ[0] > 0;
+ assert K_ENDS_XZ[0] == 0;
+}
+
+test the_tmu_run_fails_its_checks_and_leaves_x {
+ assert K_SAMPLE_SOURCES[2] == "fpga/openxc7-synth/tmu.v";
+ assert K_CHECKS_TOTAL[2] > 0;
+ assert K_CHECKS_PASS[2] == 0;
+ assert K_STUCK_XZ[2] + K_ENDS_XZ[2] > 0;
+ assert K_PARAMS[2] > 0;
+}
+
+test the_counts_agree_with_each_other {
+ assert K_CHANGES[0] <= K_FLIPS[0];
+ assert K_CHANGES[1] <= K_FLIPS[1];
+ assert K_CHANGES[2] <= K_FLIPS[2];
+ assert K_SIGNALS[0] <= K_VARS[0];
+ assert K_SIGNALS[1] <= K_VARS[1];
+ assert K_SIGNALS[2] <= K_VARS[2];
+ assert K_NEVER_CHANGED[0] + K_PARAMS[0] <= K_SIGNALS[0];
+ assert K_NEVER_CHANGED[2] + K_PARAMS[2] <= K_SIGNALS[2];
+ assert K_STUCK_XZ[0] + K_ENDS_XZ[0] <= K_SIGNALS[0];
+ assert K_STUCK_XZ[2] + K_ENDS_XZ[2] <= K_SIGNALS[2];
+ assert K_SPAN[0] > 0;
+ assert K_SPAN[1] > 0;
+ assert K_SPAN[2] > 0;
+ assert K_STAMPS[1] > 0;
+}
+
+test the_vsa_bind_run_passes_clean {
+ assert K_SAMPLE_KINDS[1] == "repo";
+ assert K_CHECKS_TOTAL[1] > 0;
+ assert K_CHECKS_PASS[1] == K_CHECKS_TOTAL[1];
+ assert K_STUCK_XZ[1] == 0;
+ assert K_CHANGES[1] > 0;
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_CARD_W == 1200;
+ assert K_CARD_H == 630;
+ assert K_MIN_TARGET_PX >= 32;
+ assert K_MAX_SAMPLE_BYTES <= 200000;
+ assert SAY_ERR_CODES[0] == "not-vcd";
+ assert SAY_ERR_CODES[8] == "bad-token";
+}
+
+; ---- FOLDED: synth_smells_logic.t27 lines 39-1445 (renames: none) ----
+pub const MS : usize = 65536;
+
+pub const R_STARTED : usize = 0;
+pub const R_HDR : usize = 1;
+pub const R_CHARS : usize = 2;
+pub const R_CMD : usize = 3;
+pub const R_NCT : usize = 4;
+pub const R_CBN : usize = 5;
+pub const R_NSC : usize = 6;
+pub const R_SCOPES : usize = 7;
+pub const R_TSS : usize = 8;
+pub const R_TSM : usize = 9;
+pub const R_TSU : usize = 10;
+pub const R_VERO : usize = 11;
+pub const R_VERL : usize = 12;
+pub const R_VARS : usize = 13;
+pub const R_NSIG : usize = 14;
+pub const R_BLOCK : usize = 15;
+pub const R_PEND : usize = 16;
+pub const R_PENDL : usize = 17;
+pub const R_TSET : usize = 18;
+pub const R_STAMPS : usize = 19;
+pub const R_TFS : usize = 20;
+pub const R_MAXH : usize = 21;
+pub const R_HOLD : usize = 22;
+pub const R_TOKN : usize = 23;
+pub const R_NP : usize = 24;
+pub const R_VP : usize = 25;
+pub const R_ECODE : usize = 26;
+pub const R_EO : usize = 27;
+pub const R_EL : usize = 28;
+pub const R_ETS : usize = 29;
+pub const R_UNSUP : usize = 30;
+pub const R_EBN : usize = 31;
+
+pub var R : [40]u64 = [0; 40];
+pub var TF : [4]f64 = [0.0; 4];
+pub var SUM : [32]u64 = [0; 32];
+pub var SUT : [4]f64 = [0.0; 4];
+pub var SUV : [2]f64 = [0.0; 2];
+pub var P10 : [23]f64 = [0.0; 23];
+
+pub var TOKB : [1048576]u8 = [0; 1048576];
+pub var PVB : [1048576]u8 = [0; 1048576];
+pub var CB : [1048576]u8 = [0; 1048576];
+pub var CTO : [65536]u32 = [0; 65536];
+pub var CTL : [65536]u32 = [0; 65536];
+pub var HOLD : [8388608]u8 = [0; 8388608];
+pub var ERRB : [1048576]u8 = [0; 1048576];
+pub var NAMEP : [16777216]u8 = [0; 16777216];
+pub var VALP : [33554432]u8 = [0; 33554432];
+pub var VE : [65536]u8 = [0; 65536];
+pub var SCO : [1024]u32 = [0; 1024];
+pub var SCL : [1024]u32 = [0; 1024];
+
+pub var SIO : [65536]u32 = [0; 65536];
+pub var SIL : [65536]u32 = [0; 65536];
+pub var SNO : [65536]u32 = [0; 65536];
+pub var SNL : [65536]u32 = [0; 65536];
+pub var STO : [65536]u32 = [0; 65536];
+pub var STL : [65536]u32 = [0; 65536];
+pub var SWD : [65536]u32 = [0; 65536];
+pub var SFG : [65536]u8 = [0; 65536];
+pub var SVO : [65536]u32 = [0; 65536];
+pub var SVL : [65536]u32 = [0; 65536];
+pub var SVC : [65536]u32 = [0; 65536];
+pub var SHV : [65536]u8 = [0; 65536];
+pub var SVA : [65536]u64 = [0; 65536];
+pub var SCH : [65536]u64 = [0; 65536];
+pub var SFL : [65536]u64 = [0; 65536];
+pub var SEK : [65536]u8 = [0; 65536];
+pub var SAL : [65536]u32 = [0; 65536];
+pub var HT : [262144]u32 = [0; 262144];
+
+pub var LIVE : [65536]u32 = [0; 65536];
+pub var SRT : [65536]u32 = [0; 65536];
+pub var STMP : [65536]u32 = [0; 65536];
+pub var BUSY : [8]u32 = [0; 8];
+pub var QUIET : [8]u32 = [0; 8];
+pub var STUCK : [8]u32 = [0; 8];
+pub var ENDSX : [8]u32 = [0; 8];
+
+// ---------------------------------------------------------------- bytes and whitespace
+pub fn ws_len_at(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi) { return 0; }
+ const b : u8 = s[p];
+ if (b == 32 or (b >= 9 and b <= 13)) { return 1; }
+ if (b == 194 and p + 1 < hi and s[p + 1] == 160) { return 2; }
+ if (p + 3 <= hi) {
+ const b1 : u8 = s[p + 1];
+ const b2 : u8 = s[p + 2];
+ if (b == 225 and b1 == 154 and b2 == 128) { return 3; }
+ if (b == 226 and b1 == 128 and ((b2 >= 128 and b2 <= 138) or b2 == 168 or b2 == 169 or b2 == 175)) { return 3; }
+ if (b == 226 and b1 == 129 and b2 == 159) { return 3; }
+ if (b == 227 and b1 == 128 and b2 == 128) { return 3; }
+ if (b == 239 and b1 == 187 and b2 == 191) { return 3; }
+ }
+ return 0;
+}
+
+pub fn is_digit(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+/// The UTF-16 units of the UTF-8 bytes s[lo..hi).
+pub fn units_of(s: []const u8, lo: usize, hi: usize) -> u64 {
+ var n : u64 = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = s[k];
+ if (b < 128 or b >= 192) { n = n + 1; }
+ if (b >= 240) { n = n + 1; }
+ k = k + 1;
+ }
+ return n;
+}
+
+/// The bytes of the first `units` UTF-16 units of s[lo..hi) (whole characters only); returns -1 when a pair would be split.
+pub fn prefix_bytes(s: []const u8, lo: usize, hi: usize, units: u64) -> i64 {
+ var n : u64 = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = s[k];
+ var step : usize = 1;
+ var u : u64 = 1;
+ if (b >= 192 and b < 224) { step = 2; }
+ if (b >= 224 and b < 240) { step = 3; }
+ if (b >= 240) {
+ step = 4;
+ u = 2;
+ }
+ if (n + u > units) {
+ if (u == 2 and n + 1 == units) { R[R_UNSUP] = 1; }
+ return (k - lo) as i64;
+ }
+ n = n + u;
+ k = k + step;
+ }
+ return (hi - lo) as i64;
+}
+
+pub fn fail(code: u64, o: usize, l: usize, src: u32) -> i32 {
+ R[R_ECODE] = code;
+ R[R_EO] = 0;
+ R[R_EL] = l as u64;
+ var k : usize = 0;
+ while (k < l and k < 1048576) {
+ if (src == 0) { ERRB[k] = TOKB[o + k]; }
+ if (src == 1) { ERRB[k] = CB[o + k]; }
+ if (src == 2) { ERRB[k] = PVB[o + k]; }
+ if (src == 3) { ERRB[k] = HOLD[o + k]; }
+ k = k + 1;
+ }
+ return code as i32;
+}
+
+pub fn tok_is(w: []const u8) -> bool {
+ if ((R[R_TOKN] as usize) != w.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (TOKB[k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn cb_is(i: usize, w: []const u8) -> bool {
+ if ((CTL[i] as usize) != w.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (CB[(CTO[i] as usize) + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+// ---------------------------------------------------------------- Number(token) for a $var width
+/// -1 NaN or negative, -3 not carried, else the whole number (0 for anything in [0, 1)).
+pub fn js_width(o: usize, l: usize) -> i64 {
+ var p : usize = 0;
+ var neg : bool = false;
+ if (l == 0) { return -1; }
+ const t : []const u8 = CB[0 .. 1048576];
+ const b0 : u8 = t[o];
+ if (b0 == 43 or b0 == 45) {
+ neg = b0 == 45;
+ p = 1;
+ }
+ if (l - p == 8 and t[o + p] == 73 and t[o + p + 1] == 110 and t[o + p + 2] == 102 and t[o + p + 3] == 105 and t[o + p + 4] == 110 and t[o + p + 5] == 105 and t[o + p + 6] == 116 and t[o + p + 7] == 121) {
+ if (neg) { return -1; }
+ return -3;
+ }
+ if (p == 0 and l > 2 and t[o] == 48) {
+ const c : u8 = t[o + 1] | 32;
+ var base : u64 = 0;
+ if (c == 120) { base = 16; }
+ if (c == 111) { base = 8; }
+ if (c == 98) { base = 2; }
+ if (base > 0) {
+ var v : u64 = 0;
+ var k : usize = 2;
+ while (k < l) {
+ const d : u8 = t[o + k];
+ var dv : u64 = 99;
+ if (is_digit(d)) { dv = (d - 48) as u64; }
+ if ((d | 32) >= 97 and (d | 32) <= 102) { dv = ((d | 32) - 87) as u64; }
+ if (dv >= base) { return -1; }
+ if (v < 4294967296) { v = v * base + dv; }
+ k = k + 1;
+ }
+ if (v >= 2147483648) { return -3; }
+ return v as i64;
+ }
+ }
+ var m : u64 = 0;
+ var nd : u64 = 0;
+ var e10 : i64 = 0;
+ var digits : u64 = 0;
+ var seen : bool = false;
+ var big : bool = false;
+ while (p < l and is_digit(t[o + p])) {
+ digits = digits + 1;
+ seen = true;
+ if (m == 0 and t[o + p] == 48) {
+ m = 0;
+ } else {
+ if (nd < 19) {
+ m = m * 10 + ((t[o + p] - 48) as u64);
+ nd = nd + 1;
+ } else {
+ big = true;
+ }
+ }
+ p = p + 1;
+ }
+ if (p < l and t[o + p] == 46) {
+ p = p + 1;
+ while (p < l and is_digit(t[o + p])) {
+ digits = digits + 1;
+ seen = true;
+ if (m == 0 and t[o + p] == 48) {
+ m = 0;
+ } else {
+ if (nd < 19) {
+ m = m * 10 + ((t[o + p] - 48) as u64);
+ nd = nd + 1;
+ } else {
+ big = true;
+ }
+ }
+ e10 = e10 - 1;
+ p = p + 1;
+ }
+ }
+ if (!seen) { return -1; }
+ if (p < l and (t[o + p] == 101 or t[o + p] == 69)) {
+ p = p + 1;
+ var eneg : bool = false;
+ if (p < l and (t[o + p] == 43 or t[o + p] == 45)) {
+ eneg = t[o + p] == 45;
+ p = p + 1;
+ }
+ if (p >= l or !is_digit(t[o + p])) { return -1; }
+ var ev : i64 = 0;
+ while (p < l and is_digit(t[o + p])) {
+ if (ev < 100000) { ev = ev * 10 + ((t[o + p] - 48) as i64); }
+ p = p + 1;
+ }
+ if (eneg) { e10 = e10 - ev; } else { e10 = e10 + ev; }
+ }
+ if (p != l) { return -1; }
+ if (m == 0 and !big) { return 0; }
+ if (neg) { return -1; }
+ if (big) { return -3; }
+ while (m % 10 == 0) {
+ m = m / 10;
+ nd = nd - 1;
+ e10 = e10 + 1;
+ }
+ if ((nd as i64) + e10 <= 0) { return 0; }
+ if (e10 >= 0) {
+ if ((nd as i64) + e10 > 10) { return -3; }
+ var v2 : u64 = m;
+ var k2 : i64 = 0;
+ while (k2 < e10) {
+ v2 = v2 * 10;
+ k2 = k2 + 1;
+ }
+ if (v2 >= 2147483648) { return -3; }
+ return v2 as i64;
+ }
+ return -3;
+}
+
+// ---------------------------------------------------------------- the reader
+pub fn pool_put(w: []const u8, lo: usize, hi: usize, src: u32) -> i64 {
+ const at : u64 = R[R_NP];
+ if (at + ((hi - lo) as u64) >= 16777216) { return -1; }
+ var k : usize = lo;
+ while (k < hi) {
+ if (src == 0) { NAMEP[(at as usize) + (k - lo)] = TOKB[k]; }
+ if (src == 1) { NAMEP[(at as usize) + (k - lo)] = CB[k]; }
+ k = k + 1;
+ }
+ R[R_NP] = at + ((hi - lo) as u64);
+ return at as i64;
+}
+
+pub fn hash_id(o: usize, l: usize, src: u32) -> usize {
+ var h : u64 = 2166136261;
+ var k : usize = 0;
+ while (k < l) {
+ var b : u64 = 0;
+ if (src == 0) { b = TOKB[o + k] as u64; }
+ if (src == 1) { b = CB[o + k] as u64; }
+ h = ((h ^ b) * 16777619) % 4294967296;
+ k = k + 1;
+ }
+ return (h as usize) % 262144;
+}
+
+pub fn id_eq(si: usize, o: usize, l: usize, src: u32) -> bool {
+ if ((SIL[si] as usize) != l) { return false; }
+ var k : usize = 0;
+ while (k < l) {
+ var b : u8 = 0;
+ if (src == 0) { b = TOKB[o + k]; }
+ if (src == 1) { b = CB[o + k]; }
+ if (NAMEP[(SIO[si] as usize) + k] != b) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// byId.get(id): the signal index plus one, or 0.
+pub fn find_sig(o: usize, l: usize, src: u32) -> usize {
+ var p : usize = hash_id(o, l, src);
+ while (HT[p] != 0) {
+ if (id_eq((HT[p] - 1) as usize, o, l, src)) { return HT[p] as usize; }
+ p = (p + 1) % 262144;
+ }
+ return 0;
+}
+
+/// endCommand() for $var: tokens in CB.
+pub fn end_var() -> i32 {
+ const nct : usize = R[R_NCT] as usize;
+ if (nct < 4) { return bad_var_text(); }
+ const w : i64 = js_width(CTO[1] as usize, CTL[1] as usize);
+ if (w == -3) {
+ R[R_UNSUP] = 1;
+ return 90;
+ }
+ if (w < 0) { return bad_var_text(); }
+ if (w > 65536) { return -1; }
+ R[R_VARS] = R[R_VARS] + 1;
+ // name = [...scope, ref].join('.') plus the index when it has no ':'
+ const nm0 : u64 = R[R_NP];
+ var k : usize = 0;
+ var nlen : u64 = 0;
+ while (k < (R[R_NSC] as usize)) {
+ var q : usize = 0;
+ while (q < (SCL[k] as usize)) {
+ if (R[R_NP] >= 16777000) { return -1; }
+ NAMEP[R[R_NP] as usize] = NAMEP[(SCO[k] as usize) + q];
+ R[R_NP] = R[R_NP] + 1;
+ q = q + 1;
+ }
+ NAMEP[R[R_NP] as usize] = 46;
+ R[R_NP] = R[R_NP] + 1;
+ k = k + 1;
+ }
+ var q2 : usize = 0;
+ while (q2 < (CTL[3] as usize)) {
+ if (R[R_NP] >= 16777000) { return -1; }
+ NAMEP[R[R_NP] as usize] = CB[(CTO[3] as usize) + q2];
+ R[R_NP] = R[R_NP] + 1;
+ q2 = q2 + 1;
+ }
+ if (nct >= 5) {
+ var has_colon : bool = false;
+ var c : usize = 0;
+ while (c < (CTL[4] as usize)) {
+ if (CB[(CTO[4] as usize) + c] == 58) { has_colon = true; }
+ c = c + 1;
+ }
+ if (!has_colon) {
+ c = 0;
+ while (c < (CTL[4] as usize)) {
+ if (R[R_NP] >= 16777000) { return -1; }
+ NAMEP[R[R_NP] as usize] = CB[(CTO[4] as usize) + c];
+ R[R_NP] = R[R_NP] + 1;
+ c = c + 1;
+ }
+ }
+ }
+ nlen = R[R_NP] - nm0;
+ const ex : usize = find_sig(CTO[2] as usize, CTL[2] as usize, 1);
+ if (ex != 0) {
+ SAL[ex - 1] = SAL[ex - 1] + 1;
+ R[R_NP] = nm0;
+ return 0;
+ }
+ const si : usize = R[R_NSIG] as usize;
+ if (si >= MS) { return -1; }
+ const ido : i64 = pool_put(CB[0 .. 1048576], CTO[2] as usize, (CTO[2] + CTL[2]) as usize, 1);
+ const tyo : i64 = pool_put(CB[0 .. 1048576], CTO[0] as usize, (CTO[0] + CTL[0]) as usize, 1);
+ if (ido < 0 or tyo < 0) { return -1; }
+ SIO[si] = ido as u32;
+ SIL[si] = CTL[2];
+ SNO[si] = nm0 as u32;
+ SNL[si] = nlen as u32;
+ STO[si] = tyo as u32;
+ STL[si] = CTL[0];
+ var wd : u32 = w as u32;
+ if (wd < 1) { wd = 1; }
+ SWD[si] = wd;
+ var fl : u8 = 0;
+ if (cb_is(0, "real") or cb_is(0, "realtime")) { fl = fl + 1; }
+ if (cb_is(0, "parameter")) { fl = fl + 2; }
+ if (cb_is(0, "string")) { fl = fl + 4; }
+ SFG[si] = fl;
+ SHV[si] = 0;
+ SVL[si] = 0;
+ SVC[si] = 0;
+ SVA[si] = 0;
+ SCH[si] = 0;
+ SFL[si] = 0;
+ SEK[si] = 0;
+ SAL[si] = 0;
+ var p : usize = hash_id(CTO[2] as usize, CTL[2] as usize, 1);
+ while (HT[p] != 0) { p = (p + 1) % 262144; }
+ HT[p] = (si + 1) as u32;
+ R[R_NSIG] = (si + 1) as u64;
+ return 0;
+}
+
+pub fn bad_var_text() -> i32 {
+ // detail text = tokens joined with a space
+ var n : usize = 0;
+ var i : usize = 0;
+ while (i < (R[R_NCT] as usize)) {
+ if (i > 0 and n < 1048576) {
+ ERRB[n] = 32;
+ n = n + 1;
+ }
+ var k : usize = 0;
+ while (k < (CTL[i] as usize) and n < 1048576) {
+ ERRB[n] = CB[(CTO[i] as usize) + k];
+ n = n + 1;
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ R[R_ECODE] = 4;
+ R[R_EO] = 0;
+ R[R_EL] = n as u64;
+ return 4;
+}
+
+pub fn timescale_fail() -> i32 {
+ var n : usize = 0;
+ var i : usize = 0;
+ while (i < (R[R_NCT] as usize)) {
+ if (i > 0 and n < 1048576) {
+ ERRB[n] = 32;
+ n = n + 1;
+ }
+ var k : usize = 0;
+ while (k < (CTL[i] as usize) and n < 1048576) {
+ ERRB[n] = CB[(CTO[i] as usize) + k];
+ n = n + 1;
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ R[R_ECODE] = 3;
+ R[R_EO] = 0;
+ R[R_EL] = n as u64;
+ return 3;
+}
+
+/// endCommand(): the command word is R_CMD (1 timescale 2 version 3 scope 4 upscope 5 var 6 enddefinitions 7 other).
+pub fn end_command() -> i32 {
+ const cmd : u64 = R[R_CMD];
+ const nct : usize = R[R_NCT] as usize;
+ var rc : i32 = 0;
+ if (cmd == 1) {
+ // join without spaces, then ^(1|10|100)\s*(s|ms|us|ns|ps|fs)$
+ var n : usize = 0;
+ var i : usize = 0;
+ while (i < nct) {
+ var k : usize = 0;
+ while (k < (CTL[i] as usize) and n < 64) {
+ VE[n] = CB[(CTO[i] as usize) + k];
+ n = n + 1;
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ var d : usize = 0;
+ while (d < n and is_digit(VE[d])) { d = d + 1; }
+ var mul : u64 = 0;
+ if (d == 1 and VE[0] == 49) { mul = 1; }
+ if (d == 2 and VE[0] == 49 and VE[1] == 48) { mul = 10; }
+ if (d == 3 and VE[0] == 49 and VE[1] == 48 and VE[2] == 48) { mul = 100; }
+ var unit : u64 = 9;
+ if (n - d == 1 and VE[d] == 115) { unit = 0; }
+ if (n - d == 2 and VE[d + 1] == 115) {
+ if (VE[d] == 109) { unit = 1; }
+ if (VE[d] == 117) { unit = 2; }
+ if (VE[d] == 110) { unit = 3; }
+ if (VE[d] == 112) { unit = 4; }
+ if (VE[d] == 102) { unit = 5; }
+ }
+ if (mul == 0 or unit == 9 or n >= 64) { return timescale_fail(); }
+ R[R_TSS] = 1;
+ R[R_TSM] = mul;
+ R[R_TSU] = unit;
+ }
+ if (cmd == 2) {
+ // version = tokens joined by a space
+ const at : u64 = R[R_NP];
+ var i2 : usize = 0;
+ while (i2 < nct) {
+ if (i2 > 0) {
+ if (R[R_NP] >= 16777000) { return -1; }
+ NAMEP[R[R_NP] as usize] = 32;
+ R[R_NP] = R[R_NP] + 1;
+ }
+ var k2 : usize = 0;
+ while (k2 < (CTL[i2] as usize)) {
+ if (R[R_NP] >= 16777000) { return -1; }
+ NAMEP[R[R_NP] as usize] = CB[(CTO[i2] as usize) + k2];
+ R[R_NP] = R[R_NP] + 1;
+ k2 = k2 + 1;
+ }
+ i2 = i2 + 1;
+ }
+ R[R_VERO] = at;
+ R[R_VERL] = R[R_NP] - at;
+ }
+ if (cmd == 3) {
+ var pick : usize = 99;
+ if (nct >= 2) { pick = 1; } else { if (nct >= 1) { pick = 0; } }
+ const at2 : u64 = R[R_NP];
+ if (pick == 99) {
+ NAMEP[at2 as usize] = 63;
+ R[R_NP] = at2 + 1;
+ } else {
+ const o : i64 = pool_put(CB[0 .. 1048576], CTO[pick] as usize, (CTO[pick] + CTL[pick]) as usize, 1);
+ if (o < 0) { return -1; }
+ }
+ if (R[R_NSC] >= 1024) { return -1; }
+ SCO[R[R_NSC] as usize] = at2 as u32;
+ SCL[R[R_NSC] as usize] = (R[R_NP] - at2) as u32;
+ R[R_NSC] = R[R_NSC] + 1;
+ R[R_SCOPES] = R[R_SCOPES] + 1;
+ }
+ if (cmd == 4) {
+ if (R[R_NSC] > 0) { R[R_NSC] = R[R_NSC] - 1; }
+ }
+ if (cmd == 5) {
+ rc = end_var();
+ if (rc != 0) { return rc; }
+ }
+ if (cmd == 6) {
+ R[R_HDR] = 0;
+ if (R[R_VARS] == 0) { return fail(5, 0, 0, 0); }
+ }
+ R[R_CMD] = 0;
+ R[R_NCT] = 0;
+ R[R_CBN] = 0;
+ return 0;
+}
+
+pub fn header_token() -> i32 {
+ if (R[R_CMD] != 0) {
+ if (tok_is("$end")) { return end_command(); }
+ const n : usize = R[R_NCT] as usize;
+ const tl : usize = R[R_TOKN] as usize;
+ if (n >= 65536 or (R[R_CBN] as usize) + tl >= 1048576) { return -1; }
+ CTO[n] = R[R_CBN] as u32;
+ CTL[n] = tl as u32;
+ var k : usize = 0;
+ while (k < tl) {
+ CB[(R[R_CBN] as usize) + k] = TOKB[k];
+ k = k + 1;
+ }
+ R[R_CBN] = R[R_CBN] + (tl as u64);
+ R[R_NCT] = (n + 1) as u64;
+ return 0;
+ }
+ if (TOKB[0] != 36) {
+ const pb : i64 = prefix_bytes(TOKB[0 .. 1048576], 0, R[R_TOKN] as usize, 40);
+ return fail(2, 0, pb as usize, 0);
+ }
+ var c : u64 = 7;
+ if (tok_is("$timescale")) { c = 1; }
+ if (tok_is("$version")) { c = 2; }
+ if (tok_is("$scope")) { c = 3; }
+ if (tok_is("$upscope")) { c = 4; }
+ if (tok_is("$var")) { c = 5; }
+ if (tok_is("$enddefinitions")) { c = 6; }
+ R[R_CMD] = c;
+ return 0;
+}
+
+pub fn extend_into(vl: usize, width: usize) -> bool {
+ // PVB[0..vl) -> VE[0..width): the rightmost width characters, or padded on the left
+ if (vl >= width) {
+ var k : usize = 0;
+ while (k < width) {
+ VE[k] = PVB[vl - width + k];
+ k = k + 1;
+ }
+ return true;
+ }
+ var fill : u8 = 48;
+ if (vl > 0) {
+ const c : u8 = PVB[0];
+ if (c == 120 or c == 88) { fill = 120; }
+ if (c == 122 or c == 90) { fill = 122; }
+ }
+ var i : usize = 0;
+ while (i < width - vl) {
+ VE[i] = fill;
+ i = i + 1;
+ }
+ var j : usize = 0;
+ while (j < vl) {
+ VE[width - vl + j] = PVB[j];
+ j = j + 1;
+ }
+ return true;
+}
+
+pub fn is_unknown_char(c: u8) -> bool {
+ return c == 120 or c == 88 or c == 122 or c == 90 or c == 117 or c == 85 or c == 119 or c == 87 or c == 45;
+}
+
+pub fn lower(c: u8) -> u8 {
+ if (c >= 65 and c <= 90) { return c + 32; }
+ return c;
+}
+
+/// /^(nan|x|z)$/i on PVB[0..vl)
+pub fn is_nan_x_z(vl: usize) -> bool {
+ if (vl == 1) {
+ const c : u8 = lower(PVB[0]);
+ return c == 120 or c == 122;
+ }
+ if (vl == 3) { return lower(PVB[0]) == 110 and lower(PVB[1]) == 97 and lower(PVB[2]) == 110; }
+ return false;
+}
+
+pub fn store_value(si: usize, src_is_ve: bool, len: usize) -> bool {
+ if ((SVC[si] as usize) < len) {
+ if (R[R_VP] + (len as u64) >= 33554432) { return false; }
+ SVO[si] = R[R_VP] as u32;
+ SVC[si] = len as u32;
+ R[R_VP] = R[R_VP] + (len as u64);
+ }
+ var k : usize = 0;
+ while (k < len) {
+ if (src_is_ve) { VALP[(SVO[si] as usize) + k] = VE[k]; } else { VALP[(SVO[si] as usize) + k] = PVB[k]; }
+ k = k + 1;
+ }
+ SVL[si] = len as u32;
+ SHV[si] = 1;
+ return true;
+}
+
+/// record(id, value, kind): kind 0 scalar, 1 b, 2 r, 3 s. The value is in PVB[0..vl); the id is TOKB[io..ih).
+pub fn record(io: usize, ih: usize, vl: usize, kind: u32) -> i32 {
+ if (R[R_BLOCK] == 1) { return 0; }
+ const sp : usize = find_sig(io, ih - io, 0);
+ if (sp == 0) {
+ R[R_ETS] = R[R_TSET];
+ return fail(6, io, ih - io, 0);
+ }
+ const si : usize = sp - 1;
+ SVA[si] = SVA[si] + 1;
+ const fl : u8 = SFG[si];
+ const isstr : bool = kind == 2 or kind == 3 or (fl % 2) == 1 or fl >= 4;
+ var known : bool = false;
+ var len : usize = vl;
+ if (isstr) {
+ known = !is_nan_x_z(vl);
+ } else {
+ var z : usize = 0;
+ while (z < vl) {
+ if (PVB[z] >= 128) {
+ R[R_UNSUP] = 1;
+ return 90;
+ }
+ z = z + 1;
+ }
+ len = SWD[si] as usize;
+ extend_into(vl, len);
+ known = true;
+ var i : usize = 0;
+ while (i < len) {
+ if (is_unknown_char(VE[i])) { known = false; }
+ i = i + 1;
+ }
+ }
+ if (known) { SEK[si] = 1; }
+ if (SHV[si] == 0) {
+ if (!store_value(si, !isstr, len)) { return -1; }
+ return 0;
+ }
+ var same : bool = (SVL[si] as usize) == len;
+ var c : usize = 0;
+ while (c < len and same) {
+ var nb : u8 = PVB[c];
+ if (!isstr) { nb = VE[c]; }
+ if (VALP[(SVO[si] as usize) + c] != nb) { same = false; }
+ c = c + 1;
+ }
+ if (same) { return 0; }
+ SCH[si] = SCH[si] + 1;
+ if (isstr) {
+ SFL[si] = SFL[si] + 1;
+ } else {
+ // flipsBetween(old, new) loops over the OLD length; a shorter new value makes JS throw
+ var f : u64 = 0;
+ var d : usize = 0;
+ while (d < (SVL[si] as usize)) {
+ if (d >= len) {
+ R[R_UNSUP] = 1;
+ return 90;
+ }
+ if (lower(VALP[(SVO[si] as usize) + d]) != lower(VE[d])) { f = f + 1; }
+ d = d + 1;
+ }
+ SFL[si] = SFL[si] + f;
+ }
+ if (!store_value(si, !isstr, len)) { return -1; }
+ return 0;
+}
+
+pub fn set_time_from_token() -> bool {
+ // TOKB[1..n) are digits
+ const n : usize = R[R_TOKN] as usize;
+ if (n - 1 > 19) { return false; }
+ var v : u64 = 0;
+ var k : usize = 1;
+ while (k < n) {
+ v = v * 10 + ((TOKB[k] - 48) as u64);
+ k = k + 1;
+ }
+ TF[0] = v as f64;
+ return true;
+}
+
+pub fn body_token() -> i32 {
+ const n : usize = R[R_TOKN] as usize;
+ if (R[R_PEND] != 0) {
+ const kind : u32 = R[R_PEND] as u32;
+ const vl : usize = R[R_PENDL] as usize;
+ R[R_PEND] = 0;
+ return record(0, n, vl, kind);
+ }
+ if (R[R_BLOCK] == 2) {
+ if (tok_is("$end")) { R[R_BLOCK] = 0; }
+ return 0;
+ }
+ const c : u8 = TOKB[0];
+ if (c == 35) {
+ var ok : bool = n >= 2;
+ var k : usize = 1;
+ while (k < n) {
+ if (!is_digit(TOKB[k])) { ok = false; }
+ k = k + 1;
+ }
+ if (!ok) {
+ const pb : i64 = prefix_bytes(TOKB[0 .. 1048576], 0, n, 40);
+ R[R_ETS] = R[R_TSET];
+ return fail(7, 0, pb as usize, 0);
+ }
+ if (!set_time_from_token()) { R[R_UNSUP] = 1; return 90; }
+ R[R_TSET] = 1;
+ if (R[R_TFS] == 0) {
+ TF[1] = TF[0];
+ R[R_TFS] = 1;
+ }
+ TF[2] = TF[0];
+ R[R_STAMPS] = R[R_STAMPS] + 1;
+ return 0;
+ }
+ if (c == 36) {
+ if (tok_is("$end")) {
+ R[R_BLOCK] = 0;
+ } else {
+ if (tok_is("$dumpoff")) { R[R_BLOCK] = 1; } else {
+ if (tok_is("$comment")) { R[R_BLOCK] = 2; } else { R[R_BLOCK] = 3; }
+ }
+ }
+ return 0;
+ }
+ var pk : u32 = 0;
+ if (c == 98 or c == 66) { pk = 1; }
+ if (c == 114 or c == 82) { pk = 2; }
+ if (c == 115 or c == 83) { pk = 3; }
+ if (pk != 0) {
+ var k2 : usize = 1;
+ while (k2 < n) {
+ PVB[k2 - 1] = TOKB[k2];
+ k2 = k2 + 1;
+ }
+ R[R_PEND] = pk as u64;
+ R[R_PENDL] = (n - 1) as u64;
+ return 0;
+ }
+ const sc : bool = c == 48 or c == 49 or c == 120 or c == 88 or c == 122 or c == 90 or c == 117 or c == 85 or c == 119 or c == 87 or c == 108 or c == 76 or c == 104 or c == 72 or c == 45;
+ if (sc and n > 1) {
+ PVB[0] = lower(c);
+ return record(1, n, 1, 0);
+ }
+ const pb2 : i64 = prefix_bytes(TOKB[0 .. 1048576], 0, n, 40);
+ R[R_ETS] = R[R_TSET];
+ return fail(7, 0, pb2 as usize, 0);
+}
+
+pub fn token_done() -> i32 {
+ var rc : i32 = 0;
+ if (R[R_HDR] == 1) { rc = header_token(); } else { rc = body_token(); }
+ R[R_TOKN] = 0;
+ return rc;
+}
+
+/// Feed bytes through the tokenizer (JS \s splits; a trailing partial token stays in TOKB).
+pub fn feed(s: []const u8, lo: usize, hi: usize) -> i32 {
+ var p : usize = lo;
+ while (p < hi) {
+ const w : usize = ws_len_at(s, p, hi);
+ if (w > 0) {
+ if (R[R_TOKN] > 0) {
+ const rc : i32 = token_done();
+ if (rc != 0) { return rc; }
+ }
+ p = p + w;
+ } else {
+ if (R[R_TOKN] >= 1048576) { return -1; }
+ TOKB[R[R_TOKN] as usize] = s[p];
+ R[R_TOKN] = R[R_TOKN] + 1;
+ p = p + 1;
+ }
+ }
+ return 0;
+}
+
+pub fn vcd_begin(max_header: u64) -> i32 {
+ var f : usize = 0;
+ while (f < 40) {
+ R[f] = 0;
+ f = f + 1;
+ }
+ var h : usize = 0;
+ while (h < 262144) {
+ HT[h] = 0;
+ h = h + 1;
+ }
+ R[R_HDR] = 1;
+ R[R_MAXH] = max_header;
+ TF[0] = 0.0;
+ TF[1] = 0.0;
+ TF[2] = 0.0;
+ return 0;
+}
+
+pub fn vcd_chars() -> u64 {
+ return R[R_CHARS];
+}
+
+pub fn vcd_push(text: []const u8) -> i32 {
+ if (R[R_STARTED] == 0) {
+ const hb : i64 = prefix_bytes(text, 0, text.len, 4096);
+ const hl : usize = hb as usize;
+ var z : usize = 0;
+ while (z < hl) {
+ if (text[z] == 0) { return fail(1, 0, 0, 0); }
+ z = z + 1;
+ }
+ var p : usize = 0;
+ var w : usize = ws_len_at(text, p, hl);
+ while (w > 0) {
+ p = p + w;
+ w = ws_len_at(text, p, hl);
+ }
+ if (p >= hl) {
+ if (R[R_HOLD] + (text.len as u64) > 8388608) { return -1; }
+ var k : usize = 0;
+ while (k < text.len) {
+ HOLD[(R[R_HOLD] as usize) + k] = text[k];
+ k = k + 1;
+ }
+ R[R_HOLD] = R[R_HOLD] + (text.len as u64);
+ R[R_CHARS] = R[R_CHARS] + units_of(text, 0, text.len);
+ return 0;
+ }
+ if (text[p] != 36) {
+ var e : usize = p;
+ while (e < hl and ws_len_at(text, e, hl) == 0) { e = e + 1; }
+ const pb : i64 = prefix_bytes(text, p, e, 40);
+ R[R_ECODE] = 2;
+ R[R_EL] = pb as u64;
+ var q : usize = 0;
+ while (q < (pb as usize)) {
+ ERRB[q] = text[p + q];
+ q = q + 1;
+ }
+ return 2;
+ }
+ R[R_STARTED] = 1;
+ }
+ R[R_CHARS] = R[R_CHARS] + units_of(text, 0, text.len);
+ if (R[R_HOLD] > 0) {
+ const rc0 : i32 = feed(HOLD[0 .. 8388608], 0, R[R_HOLD] as usize);
+ R[R_HOLD] = 0;
+ if (rc0 != 0) { return rc0; }
+ }
+ const rc : i32 = feed(text, 0, text.len);
+ if (rc != 0) { return rc; }
+ if (R[R_HDR] == 1 and R[R_CHARS] > R[R_MAXH]) { return fail(8, 0, 0, 0); }
+ return 0;
+}
+
+// ---------------------------------------------------------------- summarize
+pub fn name_less(a: u32, b: u32) -> bool {
+ const la : usize = SNL[a as usize] as usize;
+ const lb : usize = SNL[b as usize] as usize;
+ var k : usize = 0;
+ while (k < la and k < lb) {
+ const x : u8 = NAMEP[(SNO[a as usize] as usize) + k];
+ const y : u8 = NAMEP[(SNO[b as usize] as usize) + k];
+ if (x != y) { return x < y; }
+ k = k + 1;
+ }
+ return la < lb;
+}
+
+/// mode 0 busiest (changes desc, flips desc, name), 1 quietest (changes asc, flips asc, name), 2 name.
+/// true when a sorts strictly before b.
+pub fn before(mode: u32, a: u32, b: u32) -> bool {
+ const ca : u64 = SCH[a as usize];
+ const cb : u64 = SCH[b as usize];
+ const fa : u64 = SFL[a as usize];
+ const fb : u64 = SFL[b as usize];
+ if (mode == 0) {
+ if (ca != cb) { return ca > cb; }
+ if (fa != fb) { return fa > fb; }
+ return name_less(a, b);
+ }
+ if (mode == 1) {
+ if (ca != cb) { return ca < cb; }
+ if (fa != fb) { return fa < fb; }
+ return name_less(a, b);
+ }
+ return name_less(a, b);
+}
+
+/// Stable merge sort of SRT[0..n) by `before`.
+pub fn sort_idx(n: usize, mode: u32) -> bool {
+ var w : usize = 1;
+ while (w < n) {
+ var lo : usize = 0;
+ while (lo < n) {
+ var mid : usize = lo + w;
+ if (mid > n) { mid = n; }
+ var hi : usize = lo + 2 * w;
+ if (hi > n) { hi = n; }
+ var a : usize = lo;
+ var b : usize = mid;
+ var o : usize = lo;
+ while (a < mid or b < hi) {
+ if (b >= hi or (a < mid and !before(mode, SRT[b], SRT[a]))) {
+ STMP[o] = SRT[a];
+ a = a + 1;
+ } else {
+ STMP[o] = SRT[b];
+ b = b + 1;
+ }
+ o = o + 1;
+ }
+ lo = lo + 2 * w;
+ }
+ var c : usize = 0;
+ while (c < n) {
+ SRT[c] = STMP[c];
+ c = c + 1;
+ }
+ w = w * 2;
+ }
+ return true;
+}
+
+pub fn has_xzuw(si: usize) -> bool {
+ var k : usize = 0;
+ while (k < (SVL[si] as usize)) {
+ const c : u8 = lower(VALP[(SVO[si] as usize) + k]);
+ if (c == 120 or c == 122 or c == 117 or c == 119 or c == 45) { return true; }
+ k = k + 1;
+ }
+ return false;
+}
+
+/// summarize(state, top).
+pub fn vcd_summarize(top: usize) -> i32 {
+ const n : usize = R[R_NSIG] as usize;
+ var nlive : usize = 0;
+ var changes : u64 = 0;
+ var flips : u64 = 0;
+ var i : usize = 0;
+ while (i < n) {
+ changes = changes + SCH[i];
+ flips = flips + SFL[i];
+ if (SFG[i] / 2 % 2 == 0) {
+ LIVE[nlive] = i as u32;
+ nlive = nlive + 1;
+ }
+ i = i + 1;
+ }
+ var nm : usize = 0;
+ var never : u64 = 0;
+ var j : usize = 0;
+ while (j < nlive) {
+ const s : usize = LIVE[j] as usize;
+ if (SCH[s] > 0) {
+ SRT[nm] = s as u32;
+ nm = nm + 1;
+ } else {
+ never = never + 1;
+ }
+ j = j + 1;
+ }
+ sort_idx(nm, 0);
+ var nb : usize = nm;
+ if (nb > top) { nb = top; }
+ var k : usize = 0;
+ while (k < nb and k < 8) {
+ BUSY[k] = SRT[k];
+ k = k + 1;
+ }
+ var m2 : usize = 0;
+ while (m2 < nm) {
+ j = 0;
+ m2 = nm;
+ }
+ // quietest: the movers again, other order
+ var q : usize = 0;
+ var t : usize = 0;
+ while (t < nlive) {
+ const s2 : usize = LIVE[t] as usize;
+ if (SCH[s2] > 0) {
+ SRT[q] = s2 as u32;
+ q = q + 1;
+ }
+ t = t + 1;
+ }
+ sort_idx(q, 1);
+ var nq : usize = q;
+ if (nq > top) { nq = top; }
+ k = 0;
+ while (k < nq and k < 8) {
+ QUIET[k] = SRT[k];
+ k = k + 1;
+ }
+ // stuck: values and never known; ends unknown: known once and the last value has x z u w -
+ var ns : usize = 0;
+ var ne : usize = 0;
+ var stuck_all : usize = 0;
+ var ends_all : usize = 0;
+ t = 0;
+ while (t < nlive) {
+ const s3 : usize = LIVE[t] as usize;
+ if (SVA[s3] > 0 and SEK[s3] == 0) {
+ SRT[stuck_all] = s3 as u32;
+ stuck_all = stuck_all + 1;
+ }
+ t = t + 1;
+ }
+ sort_idx(stuck_all, 2);
+ k = 0;
+ while (k < stuck_all and k < top and k < 8) {
+ STUCK[k] = SRT[k];
+ k = k + 1;
+ }
+ ns = stuck_all;
+ t = 0;
+ while (t < nlive) {
+ const s4 : usize = LIVE[t] as usize;
+ if (SVA[s4] > 0 and SEK[s4] == 1 and SHV[s4] == 1 and (SFG[s4] % 2) == 0 and SFG[s4] < 4 and has_xzuw(s4)) {
+ SRT[ends_all] = s4 as u32;
+ ends_all = ends_all + 1;
+ }
+ t = t + 1;
+ }
+ sort_idx(ends_all, 2);
+ k = 0;
+ while (k < ends_all and k < top and k < 8) {
+ ENDSX[k] = SRT[k];
+ k = k + 1;
+ }
+ ne = ends_all;
+ SUM[0] = R[R_TSS];
+ SUM[1] = R[R_TSM];
+ SUM[2] = R[R_TSU];
+ SUM[4] = R[R_VERO];
+ SUM[5] = R[R_VERL];
+ SUM[6] = R[R_STAMPS];
+ SUM[7] = R[R_CHARS];
+ SUM[8] = R[R_VARS];
+ SUM[9] = n as u64;
+ SUM[10] = (n - nlive) as u64;
+ SUM[11] = R[R_SCOPES];
+ SUM[12] = changes;
+ SUM[13] = flips;
+ SUM[14] = nb as u64;
+ SUM[15] = nq as u64;
+ SUM[16] = ns as u64;
+ var shown : usize = ns;
+ if (shown > top) { shown = top; }
+ SUM[17] = shown as u64;
+ SUM[18] = ne as u64;
+ var shown2 : usize = ne;
+ if (shown2 > top) { shown2 = top; }
+ SUM[19] = shown2 as u64;
+ SUM[20] = never;
+ SUM[21] = nm as u64;
+ SUT[0] = 0.0;
+ SUT[1] = 0.0;
+ if (R[R_TFS] == 1) {
+ SUT[0] = TF[1];
+ SUT[1] = TF[2];
+ }
+ SUT[2] = SUT[1] - SUT[0];
+ return 0;
+}
+
+pub fn vcd_end() -> i32 {
+ if (R[R_HOLD] > 0) {
+ const rc0 : i32 = feed(HOLD[0 .. 8388608], 0, R[R_HOLD] as usize);
+ R[R_HOLD] = 0;
+ if (rc0 != 0) { return rc0; }
+ }
+ if (R[R_TOKN] > 0) {
+ const rc : i32 = token_done();
+ if (rc != 0) { return rc; }
+ }
+ if (R[R_STARTED] == 0) { return fail(9, 0, 0, 0); }
+ if (R[R_HDR] == 1) { return fail(8, 0, 0, 0); }
+ if (R[R_PEND] != 0) {
+ // token: kind letter + value
+ var kc : u8 = 98;
+ if (R[R_PEND] == 2) { kc = 114; }
+ if (R[R_PEND] == 3) { kc = 115; }
+ ERRB[0] = kc;
+ var k : usize = 0;
+ while (k < (R[R_PENDL] as usize)) {
+ ERRB[k + 1] = PVB[k];
+ k = k + 1;
+ }
+ R[R_ECODE] = 7;
+ R[R_EL] = R[R_PENDL] + 1;
+ R[R_ETS] = R[R_TSET];
+ return 7;
+ }
+ return vcd_summarize(5);
+}
+
+/// vcdSummary(text, opts): chunks of 1048576 bytes.
+pub fn vcd_summary(text: []const u8, max_header: u64) -> i32 {
+ vcd_begin(max_header);
+ var i : usize = 0;
+ while (i < text.len) {
+ var e : usize = i + 1048576;
+ if (e > text.len) { e = text.len; }
+ const rc : i32 = vcd_push(text[i .. e]);
+ if (rc != 0) { return rc; }
+ i = e;
+ }
+ return vcd_end();
+}
+
+pub fn vcd_err_info(f: u32) -> u64 {
+ if (f == 0) { return R[R_ECODE]; }
+ if (f == 1) { return R[R_EO]; }
+ if (f == 2) { return R[R_EL]; }
+ if (f == 3) { return R[R_ETS]; }
+ return 0;
+}
+
+pub fn vcd_err_time() -> f64 {
+ return TF[0];
+}
+
+pub fn err_byte(i: u32) -> u8 {
+ return ERRB[i as usize];
+}
+
+pub fn pool_byte(i: u32) -> u8 {
+ return NAMEP[i as usize];
+}
+
+pub fn vs_info(f: u32) -> u64 {
+ return SUM[f as usize];
+}
+
+pub fn vs_time(f: u32) -> f64 {
+ return SUT[f as usize];
+}
+
+pub fn vs_row(kind: u32, i: u32, f: u32) -> u64 {
+ var s : usize = BUSY[i as usize] as usize;
+ if (kind == 1) { s = QUIET[i as usize] as usize; }
+ if (f == 0) { return SNO[s] as u64; }
+ if (f == 1) { return SNL[s] as u64; }
+ if (f == 2) { return SWD[s] as u64; }
+ if (f == 3) { return STO[s] as u64; }
+ if (f == 4) { return STL[s] as u64; }
+ if (f == 5) { return SCH[s]; }
+ if (f == 6) { return SFL[s]; }
+ return SAL[s] as u64;
+}
+
+pub fn vs_name(kind: u32, i: u32, f: u32) -> u64 {
+ var s : usize = STUCK[i as usize] as usize;
+ if (kind == 1) { s = ENDSX[i as usize] as usize; }
+ if (f == 0) { return SNO[s] as u64; }
+ return SNL[s] as u64;
+}
+
+// ---------------------------------------------------------------- spanUnits
+/// Small big integers (16 limbs of 32 bits) to place a double exactly against powers of ten.
+pub var BA : [20]u64 = [0; 20];
+pub var BB : [20]u64 = [0; 20];
+
+pub fn big_set(a: u32, v: u64) -> bool {
+ var k : usize = 0;
+ while (k < 20) {
+ if (a == 0) { BA[k] = 0; } else { BB[k] = 0; }
+ k = k + 1;
+ }
+ if (a == 0) {
+ BA[0] = v % 4294967296;
+ BA[1] = v / 4294967296;
+ } else {
+ BB[0] = v % 4294967296;
+ BB[1] = v / 4294967296;
+ }
+ return true;
+}
+
+pub fn big_mul(a: u32, m: u64) -> bool {
+ var carry : u64 = 0;
+ var k : usize = 0;
+ while (k < 20) {
+ var cur : u64 = 0;
+ if (a == 0) { cur = BA[k] * m + carry; } else { cur = BB[k] * m + carry; }
+ if (a == 0) { BA[k] = cur % 4294967296; } else { BB[k] = cur % 4294967296; }
+ carry = cur / 4294967296;
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn big_mul_pow(a: u32, base: u64, e: i64) -> bool {
+ var k : i64 = 0;
+ while (k < e) {
+ big_mul(a, base);
+ k = k + 1;
+ }
+ return true;
+}
+
+/// compare BA with BB: -1, 0, 1
+pub fn big_cmp() -> i32 {
+ var k : usize = 20;
+ while (k > 0) {
+ k = k - 1;
+ if (BA[k] > BB[k]) { return 1; }
+ if (BA[k] < BB[k]) { return -1; }
+ }
+ return 0;
+}
+
+/// Is val (= m * 2^e2) at least 10^p ? (p may be negative)
+pub fn val_ge_pow10(m: u64, e2: i64, p: i64) -> bool {
+ big_set(0, m);
+ big_set(1, 1);
+ if (e2 > 0) { big_mul_pow(0, 2, e2); } else { big_mul_pow(1, 2, 0 - e2); }
+ if (p < 0) { big_mul_pow(0, 10, 0 - p); } else { big_mul_pow(1, 10, p); }
+ return big_cmp() >= 0;
+}
+
+/// Number(val.toPrecision(4)) for a finite val >= 0 (set in SUV[0]); false when out of the carried range.
+pub fn to_precision4(val: f64) -> bool {
+ if (val == 0.0) {
+ SUV[0] = 0.0;
+ return true;
+ }
+ var x : f64 = val;
+ var e2 : i64 = 0;
+ while (x >= 9007199254740992.0) {
+ x = x / 2.0;
+ e2 = e2 + 1;
+ }
+ while (x < 4503599627370496.0) {
+ x = x * 2.0;
+ e2 = e2 - 1;
+ }
+ const m : u64 = x as u64;
+ var e0 : i64 = 0;
+ var y : f64 = val;
+ while (y >= 10.0) {
+ y = y / 10.0;
+ e0 = e0 + 1;
+ }
+ while (y < 1.0) {
+ y = y * 10.0;
+ e0 = e0 - 1;
+ }
+ while (!val_ge_pow10(m, e2, e0)) { e0 = e0 - 1; }
+ while (val_ge_pow10(m, e2, e0 + 1)) { e0 = e0 + 1; }
+ var q : i64 = e0 - 3;
+ // n = floor(val / 10^q + 1/2): the largest n with n * 2 * den <= 2 * num + den
+ var lo : u64 = 0;
+ var hi : u64 = 20000;
+ while (lo < hi) {
+ const mid : u64 = (lo + hi + 1) / 2;
+ // num = m * 2^e2 * 10^max(-q,0); den = 2^max(-e2,0) * 10^max(q,0)
+ big_set(0, m);
+ big_set(1, 1);
+ if (e2 > 0) { big_mul_pow(0, 2, e2); } else { big_mul_pow(1, 2, 0 - e2); }
+ if (q < 0) { big_mul_pow(0, 10, 0 - q); } else { big_mul_pow(1, 10, q); }
+ // left: mid * 2 * den (in BB), right: 2 * num + den
+ var k : usize = 0;
+ var carry : u64 = 0;
+ var tmp : [20]u64 = [0; 20];
+ while (k < 20) {
+ tmp[k] = BB[k];
+ k = k + 1;
+ }
+ big_mul(1, mid * 2);
+ // BA = 2 * num + den
+ big_mul(0, 2);
+ k = 0;
+ carry = 0;
+ while (k < 20) {
+ const cur : u64 = BA[k] + tmp[k] + carry;
+ BA[k] = cur % 4294967296;
+ carry = cur / 4294967296;
+ k = k + 1;
+ }
+ if (big_cmp() >= 0) { lo = mid; } else { hi = mid - 1; }
+ }
+ var n : u64 = lo;
+ if (n >= 10000) {
+ n = 1000;
+ q = q + 1;
+ }
+ if (q > 22 or q < -22) { return false; }
+ var r : f64 = n as f64;
+ if (q >= 0) { r = r * P10[q as usize]; } else { r = r / P10[(0 - q) as usize]; }
+ SUV[0] = r;
+ return true;
+}
+
+/// spanUnits(ticks, timescale): the value in su_value(), the unit index in the return (-1 without a timescale), -2 not carried.
+pub fn span_units(ticks: f64, has_ts: bool, mul: u64, exp: i64) -> i32 {
+ if (!has_ts) {
+ SUV[0] = ticks;
+ return -1;
+ }
+ P10[0] = 1.0;
+ var e : usize = 1;
+ while (e < 23) {
+ P10[e] = P10[e - 1] * 10.0;
+ e = e + 1;
+ }
+ const v : f64 = ticks * (mul as f64);
+ var i : i64 = 0;
+ while (i < 6) {
+ var ue : i64 = 0;
+ if (i == 1) { ue = -3; }
+ if (i == 2) { ue = -6; }
+ if (i == 3) { ue = -9; }
+ if (i == 4) { ue = -12; }
+ if (i == 5) { ue = -15; }
+ const shift : i64 = exp - ue;
+ var p : f64 = 1.0;
+ if (shift >= 0) { p = P10[shift as usize]; } else { p = 1.0 / P10[(0 - shift) as usize]; }
+ const val : f64 = v * p;
+ if (val >= 1.0 or i == 5) {
+ if (!to_precision4(val)) { return -2; }
+ return i as i32;
+ }
+ i = i + 1;
+ }
+ return -2;
+}
+
+pub fn su_value() -> f64 {
+ return SUV[0];
+}
+; ---- END FOLDED ----
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/vcd-wrapped/tool.js (362 lines of hand-written JavaScript)
+; and, as the page's reader, vcdstats.js. The words and numbers above are unchanged: the view prints
+; SAY_ constants and uses K_ lists, it writes no English of its own. "Save as PNG card" paints a
+; 1200 x 630 canvas that never enters the DOM and downloads it: the card is a draw list
+; (canvas_out.t27) and every width it needs is asked of the host (text_meas.t27).
+; --
+; THE READER IS FOLDED IN, NOT IMPORTED. specs/widgets/vcd_wrapped_logic.t27 is the differential-tested
+; port of vcdstats.js. Its tables are module-level `pub var`s and t27c's `use` does not carry a
+; `pub var` across modules, so vcd_summary cannot be imported. The block between the two "FOLDED"
+; markers is that module's text from `pub const MS` to the end of span_units / su_value, byte for byte
+; except the renames listed on the marker line (names that json_scan.t27, js_num.t27 or this spec also
+; declare). Change the reader there, then regenerate this region (the build script diffs it).
+; --
+; Host protocol (host.js; all exports of this module):
+; w_init(hash) the page opens on K_CARD_SAMPLE
+; w_data(i, bytes) file i of W_DATA_FILES was fetched (0 samples.json, 1 .. 3 the three VCDs); w_data_failed(i) when it was not
+; w_click(i) button i in document order: 0 .. 2 a sample, 3 "Save as PNG card"
+; w_file(z, name, size) -> 1 | 0 a file was picked or dropped on drop zone z: 1 = send its bytes
+; w_file_data(z, bytes) / w_file_failed(z) the bytes, or "could not be read"
+; w_drag(z, over) -> 1 | 0 the drag entered (1) or left/dropped (0) zone z; 1 = the page changed
+; w_effect(k, dst) -> len 'v' + control index = set that control's value to ''; 'p' + "width TAB height TAB file name" =
+; paint w_card on a new canvas of that size and download it as a PNG
+; w_card(dst) -> len the draw list of the card (canvas_out.t27 protocol 1; the host calls it again while w_meas_need answers)
+; w_meas_need(dst) -> len / w_meas_give(text) text_meas.t27: the widths the card asks for
+; w_render(dst) the page's HTML, length (0 on overflow)
+; Buffers (module-level, static): samples.json META 65536 bytes, the three sample VCDs 200000 bytes each (K_MAX_SAMPLE_BYTES is the
+; most a sample may have), the width memo 4096 entries / 512 KiB. The reader's own tables are in the folded block (65536 signals,
+; 16 MB of names ...).
+; Not carried over from JavaScript, said plainly:
+; - the sample's VCD is fetched from W_DATA_FILES, i.e. samples/.vcd; the original fetched info.vcd of samples.json when
+; it named another file;
+; - a sample VCD over 200000 bytes, or samples.json over 65536 bytes, counts as not loaded;
+; - the reader refuses (status 90 or -1 in the folded block) what it does not carry: the page then says SAY_ERR_READ with the file name;
+; - samples.json and the VCDs are fetched once, at the start; the original fetched the sample again on every click;
+; - the progress sentence SAY_READING appears while the original reads a big file in chunks; this page reads the whole
+; file in one call, so it never shows it;
+; - when a sample cannot be shown after a result exists, the reader reads the previous source again (state.result must stay): a previous
+; own file over 1 MiB cannot be read again and the card button is then disabled;
+; - the card cuts a name that is longer than 512 bytes to its last 512 before it fits it (the fitted text is the same);
+; - the card's file name lowercases ASCII only; a duplicate key in samples.json reads as the first one here, the last in JSON.parse.
+
+pub const W_META_CAP : u32 = 65536;
+pub const W_VCD_CAP : u32 = 200000;
+pub const W_NAME_CAP : u32 = 1000;
+pub const W_MAX_HEADER : u64 = 67108864;
+pub const W_DATA_FILES : [4]str = ["samples.json", "samples/counter.vcd", "samples/vsa-bind.vcd", "samples/tmu.vcd"];
+pub const W_MONO : str = "Menlo, ui-monospace, SFMono-Regular, monospace";
+pub const W_LINE_COLOR : str = "rgb(0,70,40)";
+pub const W_TRACK_COLOR : str = "rgb(0,32,20)";
+
+pub var META : [65536]u8 = [0; 65536];
+pub var META_LEN : u32 = 0;
+pub var META_ST : u32 = 0;
+pub var VCDS : [600000]u8 = [0; 600000];
+pub var VCD_LEN : [3]u32 = [0; 3];
+pub var VCD_ST : [3]u32 = [0; 3];
+pub var SAMPLE : i32 = 0 - 1;
+pub var AWAIT : bool = false;
+pub var RS_KIND : u32 = 0;
+pub var ERR_IDX : i32 = 0 - 1;
+pub var ERR_TXT : [1000]u8 = [0; 1000];
+pub var ERR_TXT_LEN : u32 = 0;
+pub var ERR_HAS_TIME : bool = false;
+pub var ERR_TIME : f64 = 0.0;
+pub var HAS_RESULT : bool = false;
+pub var LAST_KIND : u32 = 0;
+pub var LAST_SAMPLE : u32 = 0;
+pub var OWN : [1048576]u8 = [0; 1048576];
+pub var OWN_LEN : u32 = 0;
+pub var OWN_OK : bool = false;
+pub var SV_ON : bool = false;
+pub var SV_CLS : u32 = 0;
+pub var SV_A : [400]u8 = [0; 400];
+pub var SV_A_LEN : u32 = 0;
+pub var SV_B : [400]u8 = [0; 400];
+pub var SV_B_LEN : u32 = 0;
+pub var SN_ON : bool = false;
+pub var SN_NUM : [64]u8 = [0; 64];
+pub var SN_NUM_LEN : u32 = 0;
+pub var DROP_MODE : u32 = 0;
+pub var FNAME : [1000]u8 = [0; 1000];
+pub var FNAME_LEN : u32 = 0;
+pub var FSIZE : u64 = 0;
+pub var SRC : [1000]u8 = [0; 1000];
+pub var SRC_LEN : u32 = 0;
+pub var SLUG : [1000]u8 = [0; 1000];
+pub var SLUG_LEN : u32 = 0;
+pub var DRAGGING : bool = false;
+pub var EFF_CLEAR : bool = false;
+pub var EFF_PNG : bool = false;
+
+pub var TM : [16392]u32 = [0; 16392];
+pub var MPOOL : [524288]u8 = [0; 524288];
+pub var MWID : [4096]f64 = [0.0; 4096];
+pub var MOK : [4096]u8 = [0; 4096];
+pub var CUR_FONT : [160]u8 = [0; 160];
+pub var CUR_FL : u32 = 0;
+pub var FITB : [1200]u8 = [0; 1200];
+pub var WSP : [32]u32 = [0; 32];
+
+fn bom_skip(s: []const u8) -> usize {
+ if (s.len >= 3 and s[0] == 239 and s[1] == 187 and s[2] == 191) { return 3; }
+ return 0;
+}
+
+fn copy_small(dst: []u8, s: []const u8) -> u32 {
+ var n : usize = s.len;
+ if (n > dst.len) { n = dst.len; }
+ var k : usize = 0;
+ while (k < n) { dst[k] = s[k]; k = k + 1; }
+ return n as u32;
+}
+
+fn set_src(s: []const u8, slug: []const u8) -> u32 {
+ SRC_LEN = copy_small(SRC[0..], s);
+ SLUG_LEN = copy_small(SLUG[0..], slug);
+ return 0;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ META_LEN = 0;
+ META_ST = 0;
+ var i : usize = 0;
+ while (i < 3) {
+ VCD_LEN[i] = 0;
+ VCD_ST[i] = 0;
+ i = i + 1;
+ }
+ RS_KIND = 0;
+ HAS_RESULT = false;
+ LAST_KIND = 0;
+ SV_ON = false;
+ SV_CLS = 0;
+ SN_ON = false;
+ DROP_MODE = 0;
+ FNAME_LEN = 0;
+ FSIZE = 0;
+ SRC_LEN = 0;
+ SLUG_LEN = 0;
+ DRAGGING = false;
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ tm_reset(TM[0..16392]);
+ var first : i32 = 0;
+ var q : usize = 0;
+ while (q < K_SAMPLE_IDS.len) {
+ if (same_text(K_SAMPLE_IDS[q], K_CARD_SAMPLE)) { first = q as i32; }
+ q = q + 1;
+ }
+ show_sample(first);
+ return 0;
+}
+
+pub fn w_hash(h: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ return 0;
+}
+
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+// --- data ----------------------------------------------------------------------------------------
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ const from : usize = bom_skip(s);
+ const len : usize = s.len - from;
+ if (i == 0) {
+ if (len > (W_META_CAP as usize)) {
+ META_ST = 2;
+ } else {
+ var k : usize = 0;
+ while (k < len) { META[k] = s[from + k]; k = k + 1; }
+ META_LEN = len as u32;
+ if (jv_valid(META[0..len])) { META_ST = 1; } else { META_ST = 2; }
+ }
+ } else {
+ if (i <= 3) {
+ const j : usize = (i - 1) as usize;
+ if (s.len > (W_VCD_CAP as usize)) {
+ VCD_ST[j] = 2;
+ } else {
+ var k : usize = 0;
+ const base : usize = j * (W_VCD_CAP as usize);
+ while (k < s.len) { VCDS[base + k] = s[k]; k = k + 1; }
+ VCD_LEN[j] = s.len as u32;
+ VCD_ST[j] = 1;
+ }
+ }
+ }
+ resolve();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ if (i == 0) { META_ST = 2; }
+ if (i >= 1 and i <= 3) { VCD_ST[(i - 1) as usize] = 2; }
+ resolve();
+ return 0;
+}
+
+// --- choosing a sample ---------------------------------------------------------------------------
+
+fn show_sample(i: i32) -> u32 {
+ SAMPLE = i;
+ AWAIT = true;
+ SV_ON = false;
+ SN_ON = false;
+ DROP_MODE = 0;
+ EFF_CLEAR = true;
+ resolve();
+ return 0;
+}
+
+fn load_failed() -> u32 {
+ AWAIT = false;
+ RS_KIND = 2;
+ return 0;
+}
+
+/// showSample's async half: samples.json, then the VCD, then the reader; any failure is SAY_ERR_LOAD.
+fn resolve() -> u32 {
+ if (!AWAIT or SAMPLE < 0) { return 0; }
+ if (META_ST == 0) { return 0; }
+ if (META_ST == 2) {
+ load_failed();
+ return 0;
+ }
+ const j : usize = SAMPLE as usize;
+ if (VCD_ST[j] == 0) { return 0; }
+ if (VCD_ST[j] == 2) {
+ load_failed();
+ return 0;
+ }
+ const f : i64 = find_info(SAMPLE as u32);
+ if (f == 0 - 2) {
+ load_failed();
+ return 0;
+ }
+ if (f >= 0 and !verdict_ok(f)) {
+ load_failed();
+ return 0;
+ }
+ const base : usize = j * (W_VCD_CAP as usize);
+ const text : []const u8 = VCDS[base..(base + (VCD_LEN[j] as usize))];
+ const from : usize = bom_skip(text);
+ const rc : i32 = vcd_summary(text[from..text.len], W_MAX_HEADER);
+ if (rc != 0) {
+ restore_last();
+ load_failed();
+ return 0;
+ }
+ if (f >= 0) { sample_verdict(f); }
+ AWAIT = false;
+ set_src(SAY_SAMPLE_NAMES[j], K_SAMPLE_IDS[j]);
+ HAS_RESULT = true;
+ LAST_KIND = 1;
+ LAST_SAMPLE = j as u32;
+ RS_KIND = 1;
+ return 0;
+}
+
+/// The reader holds ONE summary; a failed read must leave state.result as it was, so read the previous source again.
+fn restore_last() -> u32 {
+ if (!HAS_RESULT) { return 0; }
+ if (LAST_KIND == 1) {
+ const base : usize = (LAST_SAMPLE as usize) * (W_VCD_CAP as usize);
+ const text : []const u8 = VCDS[base..(base + (VCD_LEN[LAST_SAMPLE as usize] as usize))];
+ vcd_summary(text[bom_skip(text)..text.len], W_MAX_HEADER);
+ return 0;
+ }
+ if (LAST_KIND == 2 and OWN_OK) {
+ const text : []const u8 = OWN[0..(OWN_LEN as usize)];
+ vcd_summary(text[bom_skip(text)..text.len], W_MAX_HEADER);
+ return 0;
+ }
+ HAS_RESULT = false;
+ return 0;
+}
+
+/// meta.samples?.find((s) => s.id === IDS[i]) ?? null: the span of the sample, -1 for null, -2 when JavaScript throws.
+fn find_info(i: u32) -> i64 {
+ const d : []const u8 = META[0..(META_LEN as usize)];
+ const r : i64 = root(d);
+ const rk : u8 = kind(d, r);
+ if (rk == J_NULL) { return 0 - 2; }
+ if (rk != J_OBJECT) { return 0 - 1; }
+ const sm : i64 = obj_get(d, r, "samples");
+ const sk : u8 = kind(d, sm);
+ if (sk == J_NONE or sk == J_NULL) { return 0 - 1; }
+ if (sk != J_ARRAY) { return 0 - 2; }
+ const cnt : u32 = arr_len(d, sm);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const el : i64 = arr_at(d, sm, q);
+ const ek : u8 = kind(d, el);
+ if (ek == J_NULL) { return 0 - 2; }
+ if (ek == J_OBJECT) {
+ const idv : i64 = obj_get(d, el, "id");
+ if (kind(d, idv) == J_STRING and str_is(d, idv, K_SAMPLE_IDS[i as usize])) { return el; }
+ }
+ q = q + 1;
+ }
+ return 0 - 1;
+}
+
+fn str_is(d: []const u8, sp: i64, want: []const u8) -> bool {
+ if (str_len(d, sp) != (want.len as u32)) { return false; }
+ var k : u32 = 0;
+ while (k < (want.len as u32)) {
+ if (str_byte(d, sp, k) != want[k as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// a === b for two JSON values (a missing value is undefined; objects and arrays are never equal)
+fn strict_eq(d: []const u8, a: i64, b: i64) -> bool {
+ const ka : u8 = kind(d, a);
+ const kb : u8 = kind(d, b);
+ if (ka != kb) { return false; }
+ if (ka == J_NONE or ka == J_NULL or ka == J_TRUE or ka == J_FALSE) { return true; }
+ if (ka == J_STRING) {
+ const la : u32 = str_len(d, a);
+ if (la != str_len(d, b)) { return false; }
+ var k : u32 = 0;
+ while (k < la) {
+ if (str_byte(d, a, k) != str_byte(d, b, k)) { return false; }
+ k = k + 1;
+ }
+ return true;
+ }
+ if (ka == J_NUMBER) { return num_f64(d, a) == num_f64(d, b); }
+ return false;
+}
+
+/// String(v ?? ''): null and undefined are the empty text
+fn fmt_arg(dst: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ const k : u8 = kind(d, sp);
+ if (k == J_NONE or k == J_NULL) { return n0; }
+ return jv_str(dst, n0, d, sp);
+}
+
+/// Would `info.verdict.pass` throw? (verdict missing or null)
+fn verdict_ok(f: i64) -> bool {
+ const d : []const u8 = META[0..(META_LEN as usize)];
+ const vk : u8 = kind(d, obj_get(d, f, "verdict"));
+ return !(vk == J_NONE or vk == J_NULL);
+}
+
+/// info.verdict.pass / .total into SV_A / SV_B, the class, and the note (num(info.vcd_bytes)).
+fn sample_verdict(f: i64) -> bool {
+ const d : []const u8 = META[0..(META_LEN as usize)];
+ const vd : i64 = obj_get(d, f, "verdict");
+ const vk : u8 = kind(d, vd);
+ var pass : i64 = 0 - 1;
+ var total : i64 = 0 - 1;
+ if (vk == J_OBJECT) {
+ pass = obj_get(d, vd, "pass");
+ total = obj_get(d, vd, "total");
+ }
+ var tmp : [400]u8 = [0; 400];
+ var n : u32 = fmt_arg(tmp[0..], 0, d, pass);
+ if (n == OVER) { n = 0; }
+ SV_A_LEN = copy_small(SV_A[0..], tmp[0..(n as usize)]);
+ n = fmt_arg(tmp[0..], 0, d, total);
+ if (n == OVER) { n = 0; }
+ SV_B_LEN = copy_small(SV_B[0..], tmp[0..(n as usize)]);
+ SV_ON = true;
+ if (strict_eq(d, pass, total)) { SV_CLS = 1; } else { SV_CLS = 2; }
+ // num(info.vcd_bytes)
+ const bv : i64 = obj_get(d, f, "vcd_bytes");
+ const bk : u8 = kind(d, bv);
+ var m : u32 = 0;
+ if (bk == J_NONE or bk == J_NULL) {
+ SN_NUM[0] = 45;
+ m = 1;
+ } else {
+ m = js_locale_num(SN_NUM[0..], 0, jv_num(d, bv));
+ if (m == OVER) { m = 0; }
+ }
+ SN_NUM_LEN = m;
+ SN_ON = true;
+ return true;
+}
+
+// --- reading a file ------------------------------------------------------------------------------
+
+fn own_picked() -> u32 {
+ SAMPLE = 0 - 1;
+ AWAIT = false;
+ SV_ON = false;
+ SN_ON = false;
+ return 0;
+}
+
+pub fn w_file(z: u32, name: []const u8, size: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ own_picked();
+ FNAME_LEN = copy_small(FNAME[0..], name);
+ FSIZE = size as u64;
+ DROP_MODE = 1;
+ return 1;
+}
+
+fn base_name_len() -> u32 {
+ var k : u32 = FNAME_LEN;
+ while (k > 0) {
+ k = k - 1;
+ if (FNAME[k as usize] == 46) { return k; }
+ }
+ return FNAME_LEN;
+}
+
+/// code index in SAY_ERR_CODES of a reader status, or -1
+fn err_index(rc: i32) -> i32 {
+ if (rc == 1) { return 1; }
+ if (rc == 2) { return 0; }
+ if (rc == 3) { return 6; }
+ if (rc == 4) { return 5; }
+ if (rc == 5) { return 4; }
+ if (rc == 6) { return 7; }
+ if (rc == 7) { return 8; }
+ if (rc == 8) { return 3; }
+ if (rc == 9) { return 2; }
+ return 0 - 1;
+}
+
+pub fn w_file_data(z: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ const from : usize = bom_skip(s);
+ const rc : i32 = vcd_summary(s[from..s.len], W_MAX_HEADER);
+ if (rc == 0) {
+ const b : u32 = base_name_len();
+ set_src(FNAME[0..(FNAME_LEN as usize)], FNAME[0..(b as usize)]);
+ HAS_RESULT = true;
+ LAST_KIND = 2;
+ OWN_OK = s.len <= 1048576;
+ if (OWN_OK) {
+ var q : usize = 0;
+ while (q < s.len) { OWN[q] = s[q]; q = q + 1; }
+ OWN_LEN = s.len as u32;
+ }
+ RS_KIND = 1;
+ return 0;
+ }
+ HAS_RESULT = false;
+ RS_KIND = 3;
+ ERR_IDX = err_index(rc);
+ ERR_TXT_LEN = 0;
+ ERR_HAS_TIME = vcd_err_info(3) != 0;
+ ERR_TIME = vcd_err_time();
+ var l : u32 = vcd_err_info(2) as u32;
+ if (l > 1000) { l = 1000; }
+ var k : u32 = 0;
+ while (k < l) {
+ ERR_TXT[k as usize] = err_byte(k);
+ k = k + 1;
+ }
+ ERR_TXT_LEN = l;
+ return 0;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ HAS_RESULT = false;
+ RS_KIND = 3;
+ ERR_IDX = 0 - 1;
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ const want : bool = over != 0;
+ if (want == DRAGGING) { return 0; }
+ DRAGGING = want;
+ return 1;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ if (i < (K_SAMPLE_IDS.len as u32)) {
+ show_sample(i as i32);
+ return 0;
+ }
+ if (i == (K_SAMPLE_IDS.len as u32) and HAS_RESULT) { EFF_PNG = true; }
+ return 0;
+}
+
+// --- effects -------------------------------------------------------------------------------------
+
+fn put_dec(dst: []u8, n0: u32, v: u64) -> u32 {
+ var tmp : [24]u8 = [0; 24];
+ const l : u32 = dec_to(tmp[0..], v);
+ var k : u32 = 0;
+ var n : u32 = n0;
+ while (k < l) {
+ dst[n as usize] = tmp[k as usize];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+fn slug_name(dst: []u8, n0: u32) -> u32 {
+ var any : bool = false;
+ var dash : bool = false;
+ var k : usize = 0;
+ var tmp : [1000]u8 = [0; 1000];
+ var tn : u32 = 0;
+ while (k < (SLUG_LEN as usize)) {
+ var c : u8 = SLUG[k];
+ if (c >= 65 and c <= 90) { c = c + 32; }
+ if ((c >= 97 and c <= 122) or (c >= 48 and c <= 57)) {
+ if (dash and any) {
+ tmp[tn as usize] = 45;
+ tn = tn + 1;
+ }
+ dash = false;
+ tmp[tn as usize] = c;
+ tn = tn + 1;
+ any = true;
+ } else {
+ dash = true;
+ }
+ k = k + 1;
+ }
+ if (!any) {
+ tmp[0] = 118;
+ tmp[1] = 99;
+ tmp[2] = 100;
+ tn = 3;
+ }
+ return fill_plain(dst, n0, SAY_PNG_FILE, tmp[0..(tn as usize)], "", "", 1);
+}
+
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (EFF_CLEAR and k < 1) {
+ dst[0] = 118;
+ dst[1] = k as u8;
+ return 2;
+ }
+ if (EFF_PNG and k == 0 and HAS_RESULT) {
+ var n : u32 = 0;
+ dst[0] = 112;
+ n = 1;
+ n = put_dec(dst, n, K_CARD_W as u64);
+ dst[n as usize] = 9;
+ n = n + 1;
+ n = put_dec(dst, n, K_CARD_H as u64);
+ dst[n as usize] = 9;
+ n = n + 1;
+ return slug_name(dst, n);
+ }
+ return 0;
+}
+
+/// {0} {1} {2} filled from k values, as fill() but the values and the template go out as they are (no HTML escaping).
+fn fill_plain(o: []u8, n0: u32, t: []const u8, a: []const u8, b: []const u8, c: []const u8, k: u32) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] < 48 + (k as u8) and t[i + 1] <= 50) {
+ if (t[i + 1] == 48) { n = put_all(o, n, a); }
+ if (t[i + 1] == 49) { n = put_all(o, n, b); }
+ if (t[i + 1] == 50) { n = put_all(o, n, c); }
+ i = i + 3;
+ } else {
+ n = put(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+fn put_all(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < s.len) {
+ n = put(o, n, s[i]);
+ i = i + 1;
+ }
+ return n;
+}
+
+// --- numbers from the reader ---------------------------------------------------------------------
+
+fn pool_slice(o: u64, l: u64) -> []const u8 {
+ return NAMEP[(o as usize)..((o + l) as usize)];
+}
+
+fn row_name(kd: u32, i: u32) -> []const u8 {
+ return pool_slice(vs_row(kd, i, 0), vs_row(kd, i, 1));
+}
+
+fn stuck_name(kd: u32, i: u32) -> []const u8 {
+ return pool_slice(vs_name(kd, i, 0), vs_name(kd, i, 1));
+}
+
+/// num(v) for a count
+fn grouped(dst: []u8, n0: u32, v: u64) -> u32 {
+ return num_grouped(dst, n0, v);
+}
+
+/// spanText(s): `${v} ${unit}` or `${num(v)} ${no timescale}` into dst; esc: HTML-escaped
+fn span_text(dst: []u8, n0: u32, esc: bool) -> u32 {
+ const ticks : f64 = vs_time(2);
+ const has : bool = vs_info(0) != 0;
+ const u : i32 = span_units(ticks, has, vs_info(1), 0 - 3 * (vs_info(2) as i64));
+ var n : u32 = n0;
+ if (u < 0) {
+ n = js_locale_num(dst, n, su_value());
+ n = put(dst, n, 32);
+ n = word_to(dst, n, SAY_NO_TIMESCALE, esc);
+ return n;
+ }
+ n = js_str_num(dst, n, su_value());
+ n = put(dst, n, 32);
+ return word_to(dst, n, SAY_UNITS[u as usize], esc);
+}
+
+fn word_to(dst: []u8, n0: u32, w: []const u8, esc: bool) -> u32 {
+ if (esc) { return text(dst, n0, w); }
+ return raw(dst, n0, w);
+}
+
+// --- the page --------------------------------------------------------------------------------------
+
+fn btn_open(dst: []u8, n0: u32, pressed: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_SAMPLES_LABEL);
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < (K_SAMPLE_IDS.len as u32)) {
+ var p : u32 = 0;
+ if (SAMPLE == (i as i32)) { p = 1; }
+ n = btn_open(dst, n, p);
+ n = text(dst, n, SAY_SAMPLE_NAMES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ if (SAMPLE >= 0) { n = text(dst, n, SAY_SAMPLE_LINES[SAMPLE as usize]); }
+ n = raw(dst, n, "
");
+ if (SV_ON) { n = fill(dst, n, SAY_SAMPLE_VERDICT, SV_A[0..(SV_A_LEN as usize)], SV_B[0..(SV_B_LEN as usize)], "", 2); }
+ n = raw(dst, n, "
");
+ if (SN_ON) { n = fill(dst, n, SAY_SAMPLE_NOTE, SN_NUM[0..(SN_NUM_LEN as usize)], "", "", 1); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ if (DROP_MODE == 0) {
+ n = text(dst, n, SAY_DROP);
+ } else {
+ var sz : [32]u8 = [0; 32];
+ const sl : u32 = num_grouped(sz[0..], 0, FSIZE);
+ n = fill(dst, n, SAY_PICKED, FNAME[0..(FNAME_LEN as usize)], sz[0..(sl as usize)], "", 2);
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOCAL_NOTE);
+ n = raw(dst, n, "
");
+ if (RS_KIND == 2) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_ERR_LOAD);
+ n = raw(dst, n, "
");
+ }
+ if (RS_KIND == 3) { n = error_html(dst, n); }
+ if (RS_KIND == 1) { n = result_html(dst, n); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PNG_BUTTON);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HONEST);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_RULES);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_KICKER);
+ n = raw(dst, n, "
");
+ const hq : u32 = headline_split();
+ n = text(dst, n, SAY_HEADLINE[0..(hq as usize)]);
+ n = raw(dst, n, "");
+ n = num_grouped(dst, n, changes);
+ n = raw(dst, n, "");
+ n = text(dst, n, headline_after());
+ n = raw(dst, n, "
");
+ var st : [200]u8 = [0; 200];
+ const stl : u32 = span_text(st[0..], 0, false);
+ n = fill(dst, n, SAY_SUBHEAD, st[0..(stl as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ if (vs_info(14) > 0) {
+ var cb : [32]u8 = [0; 32];
+ const cl : u32 = num_grouped(cb[0..], 0, vs_row(0, 0, 5));
+ n = fill(dst, n, SAY_BUSIEST_LINE, row_name(0, 0), cb[0..(cl as usize)], "", 2);
+ } else {
+ n = text(dst, n, SAY_NO_MOVERS);
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SRC[0..(SRC_LEN as usize)]);
+ n = raw(dst, n, "
");
+ var t : u32 = 0;
+ while (t < 6) {
+ const v : u64 = tile_value(t);
+ n = raw(dst, n, "
= 4 and v > 0) { n = raw(dst, n, " is-bad"); }
+ n = raw(dst, n, "\">");
+ n = num_grouped(dst, n, v);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_TILE_NAMES[t as usize]);
+ n = raw(dst, n, "
");
+ t = t + 1;
+ }
+ n = raw(dst, n, "
");
+ n = rows_html(dst, n, 0, "is-busy", true);
+ n = rows_html(dst, n, 1, "is-quiet", false);
+ n = names_html(dst, n, 0, 2);
+ n = names_html(dst, n, 1, 3);
+ n = raw(dst, n, "
");
+ var a : [32]u8 = [0; 32];
+ var b : [32]u8 = [0; 32];
+ var la : u32 = num_grouped(a[0..], 0, vs_info(20));
+ var lb : u32 = num_grouped(b[0..], 0, vs_info(9) - vs_info(10));
+ n = fill(dst, n, SAY_NEVER_LINE, a[0..(la as usize)], b[0..(lb as usize)], "", 2);
+ n = raw(dst, n, "");
+ la = num_grouped(a[0..], 0, vs_info(8));
+ lb = num_grouped(b[0..], 0, vs_info(9));
+ n = fill(dst, n, SAY_VARS_LINE, a[0..(la as usize)], b[0..(lb as usize)], "", 2);
+ n = raw(dst, n, "");
+ if (vs_info(10) != 0) {
+ la = num_grouped(a[0..], 0, vs_info(10));
+ n = raw(dst, n, "");
+ n = fill(dst, n, SAY_PARAMS_LINE, a[0..(la as usize)], "", "", 1);
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "");
+ if (vs_info(0) != 0) {
+ la = dec_to(a[0..], vs_info(1));
+ n = fill(dst, n, SAY_TIMESCALE, a[0..(la as usize)], SAY_UNITS[vs_info(2) as usize], "", 2);
+ } else {
+ n = text(dst, n, SAY_NO_TIMESCALE);
+ }
+ n = raw(dst, n, "");
+ if (vs_info(5) != 0) {
+ n = raw(dst, n, "");
+ n = fill(dst, n, SAY_VERSION, pool_slice(vs_info(4), vs_info(5)), "", "", 1);
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "
");
+ return n;
+}
+
+fn tile_value(t: u32) -> u64 {
+ if (t == 0) { return vs_info(13); }
+ if (t == 1) { return vs_info(9); }
+ if (t == 2) { return vs_info(11); }
+ if (t == 3) { return vs_info(6); }
+ if (t == 4) { return vs_info(16); }
+ return vs_info(18);
+}
+
+/// String(SAY_HEADLINE).split('{0}'): the length of the first piece
+fn headline_split() -> u32 {
+ var i : usize = 0;
+ while (i + 3 <= SAY_HEADLINE.len) {
+ if (SAY_HEADLINE[i] == 123 and SAY_HEADLINE[i + 1] == 48 and SAY_HEADLINE[i + 2] == 125) { return i as u32; }
+ i = i + 1;
+ }
+ return SAY_HEADLINE.len as u32;
+}
+
+/// the second piece: between the first and the second '{0}', or the end
+fn headline_after() -> []const u8 {
+ const from : usize = (headline_split() as usize) + 3;
+ if (from > SAY_HEADLINE.len) { return ""; }
+ var e : usize = from;
+ while (e + 3 <= SAY_HEADLINE.len) {
+ if (SAY_HEADLINE[e] == 123 and SAY_HEADLINE[e + 1] == 48 and SAY_HEADLINE[e + 2] == 125) { return SAY_HEADLINE[from..e]; }
+ e = e + 1;
+ }
+ return SAY_HEADLINE[from..SAY_HEADLINE.len];
+}
+
+fn rows_html(dst: []u8, n0: u32, kd: u32, cls: []const u8, bars: bool) -> u32 {
+ var cnt : u32 = vs_info(14) as u32;
+ if (kd == 1) { cnt = vs_info(15) as u32; }
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_LIST_TITLES[kd as usize]);
+ n = raw(dst, n, "
");
+ if (cnt == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NONE);
+ n = raw(dst, n, "
");
+ return n;
+ }
+ var mx : u64 = 1;
+ var i : u32 = 0;
+ while (i < cnt) {
+ if (vs_row(kd, i, 5) > mx) { mx = vs_row(kd, i, 5); }
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ i = 0;
+ while (i < cnt) {
+ const nm : []const u8 = row_name(kd, i);
+ const ch : u64 = vs_row(kd, i, 5);
+ n = raw(dst, n, "
");
+ n = text(dst, n, nm);
+ n = raw(dst, n, "");
+ var a : [32]u8 = [0; 32];
+ var b : [32]u8 = [0; 32];
+ const la : u32 = num_grouped(a[0..], 0, ch);
+ const lb : u32 = num_grouped(b[0..], 0, vs_row(kd, i, 6));
+ n = fill(dst, n, SAY_ROW, a[0..(la as usize)], b[0..(lb as usize)], "", 2);
+ n = raw(dst, n, "
");
+ if (bars) {
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "");
+ const wd : u64 = vs_row(kd, i, 2);
+ if (wd == 1) {
+ n = text(dst, n, SAY_WIDTH_ONE);
+ } else {
+ const lw : u32 = num_grouped(a[0..], 0, wd);
+ n = fill(dst, n, SAY_WIDTH, a[0..(lw as usize)], "", "", 1);
+ }
+ const al : u64 = vs_row(kd, i, 7);
+ if (al != 0) {
+ n = raw(dst, n, " / ");
+ if (al == 1) {
+ n = text(dst, n, SAY_ALIASES_ONE);
+ } else {
+ const lq : u32 = num_grouped(a[0..], 0, al);
+ n = fill(dst, n, SAY_ALIASES, a[0..(lq as usize)], "", "", 1);
+ }
+ }
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ const total : u64 = vs_info(21);
+ if (total > (cnt as u64)) {
+ var mb : [32]u8 = [0; 32];
+ const ml : u32 = num_grouped(mb[0..], 0, total - (cnt as u64));
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_MORE, mb[0..(ml as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "
");
+}
+
+/// namesList: kd 0 the signals that never leave X or Z, 1 the ones that end in X or Z; title index ti
+fn names_html(dst: []u8, n0: u32, kd: u32, ti: u32) -> u32 {
+ var cnt : u32 = vs_info(17) as u32;
+ var total : u64 = vs_info(16);
+ if (kd == 1) {
+ cnt = vs_info(19) as u32;
+ total = vs_info(18);
+ }
+ var n : u32 = raw(dst, n0, " 0) {
+ if (kd == 0) { n = raw(dst, n, "is-bad"); } else { n = raw(dst, n, "is-warn"); }
+ }
+ n = raw(dst, n, "\">
");
+ n = text(dst, n, SAY_LIST_TITLES[ti as usize]);
+ n = raw(dst, n, "
");
+ if (cnt == 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NONE);
+ n = raw(dst, n, "
");
+ return n;
+ }
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < cnt) {
+ const nm : []const u8 = stuck_name(kd, i);
+ n = raw(dst, n, "
");
+ n = text(dst, n, nm);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ if (total > (cnt as u64)) {
+ var mb : [32]u8 = [0; 32];
+ const ml : u32 = num_grouped(mb[0..], 0, total - (cnt as u64));
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_MORE, mb[0..(ml as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "
");
+}
+
+// --- the card: tool.js savePng() -----------------------------------------------------------------
+
+fn meas(t: []const u8) -> f64 {
+ return tm_width(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], CUR_FONT[0..(CUR_FL as usize)], t);
+}
+
+fn meas_font(size: u32, bold: bool, t: []const u8) -> f64 {
+ var f : [160]u8 = [0; 160];
+ const fl : u32 = font_text(f[0..], size, bold);
+ return tm_width(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], f[0..(fl as usize)], t);
+}
+
+fn font_text(f: []u8, size: u32, bold: bool) -> u32 {
+ var n : u32 = 0;
+ if (bold) { n = raw(f, 0, "bold "); }
+ n = num(f, n, size as u64);
+ n = raw(f, n, "px ");
+ return raw(f, n, W_MONO);
+}
+
+/// font(size, bold): g.font = ...
+fn set_font(o: []u8, n0: u32, size: u32, bold: bool) -> u32 {
+ CUR_FL = font_text(CUR_FONT[0..], size, bold);
+ return cv_set_str(o, n0, "font", CUR_FONT[0..(CUR_FL as usize)]);
+}
+
+fn card_color(o: []u8, n0: u32, which: u32) -> u32 {
+ if (which == 0) { return cv_set_var(o, n0, "fillStyle", "--w-bg", "#000"); }
+ if (which == 1) { return cv_set_var(o, n0, "fillStyle", "--w-green", "#00ff88"); }
+ if (which == 2) { return cv_set_var(o, n0, "fillStyle", "--w-gold", "#ffd700"); }
+ if (which == 3) { return cv_set_var(o, n0, "fillStyle", "--w-red", "#ff4d6d"); }
+ if (which == 4) { return cv_set_var(o, n0, "fillStyle", "--w-text", "#e8fff7"); }
+ if (which == 5) { return cv_set_var(o, n0, "fillStyle", "--w-muted", "#adc9c0"); }
+ if (which == 6) { return cv_set_var(o, n0, "fillStyle", "--w-subtle", "#8fa9a0"); }
+ if (which == 7) { return cv_set_str(o, n0, "fillStyle", W_LINE_COLOR); }
+ return cv_set_str(o, n0, "fillStyle", W_TRACK_COLOR);
+}
+
+fn stroke_color(o: []u8, n0: u32, which: u32) -> u32 {
+ if (which == 3) { return cv_set_var(o, n0, "strokeStyle", "--w-red", "#ff4d6d"); }
+ return cv_set_str(o, n0, "strokeStyle", W_LINE_COLOR);
+}
+
+/// text(t, x, y, color, align, base)
+fn text_at(o: []u8, n0: u32, t: []const u8, x: f64, y: f64, color: u32, align: []const u8, base: []const u8) -> u32 {
+ var n : u32 = card_color(o, n0, color);
+ n = cv_set_str(o, n, "textAlign", align);
+ n = cv_set_str(o, n, "textBaseline", base);
+ return cv_text(o, n, "fillText", t, x, y);
+}
+
+fn tail_of(t: []const u8) -> []const u8 {
+ if (t.len <= 512) { return t; }
+ var from : usize = t.len - 512;
+ while (from < t.len and t[from] >= 128 and t[from] < 192) { from = from + 1; }
+ return t[from..t.len];
+}
+
+/// fit(t, room) in the current font: the text to draw, in FITB (its length returned)
+fn fit_now(t0: []const u8, room: f64) -> u32 {
+ const t : []const u8 = tail_of(t0);
+ var sc : [600]u8 = [0; 600];
+ const lo : i64 = tm_fit_left(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], CUR_FONT[0..(CUR_FL as usize)], t, room, sc[0..600]);
+ var n : u32 = 0;
+ var from : usize = 0;
+ if (lo >= 0) {
+ n = raw(FITB[0..], 0, "...");
+ from = lo as usize;
+ }
+ var k : usize = from;
+ while (k < t.len and n < 1190) {
+ FITB[n as usize] = t[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+pub fn w_card(dst: []u8) -> u32 {
+ // the memo is cleared when it is more than half full or ran out: one card needs a few hundred entries
+ if (TM[3] != 0 or TM[0] > 2000 or TM[1] > 262144) { tm_reset(TM[0..16392]); }
+ tm_clear_miss(TM[0..16392]);
+ if (!HAS_RESULT) { return 0; }
+ const wd : f64 = K_CARD_W as f64;
+ const hd : f64 = K_CARD_H as f64;
+ const changes : u64 = vs_info(12);
+ var n : u32 = cv_canvas(dst, 0, 0);
+ n = card_color(dst, n, 0);
+ n = cv_call4(dst, n, "fillRect", 0.0, 0.0, wd, hd);
+ // brand row
+ n = card_color(dst, n, 1);
+ n = cv_call0(dst, n, "beginPath");
+ n = cv_call2(dst, n, "moveTo", 48.0, 40.0);
+ n = cv_call2(dst, n, "lineTo", 66.0, 40.0);
+ n = cv_call2(dst, n, "lineTo", 57.0, 55.0);
+ n = cv_call0(dst, n, "closePath");
+ n = cv_call0(dst, n, "fill");
+ n = set_font(dst, n, 20, true);
+ n = text_at(dst, n, SAY_CARD_BRAND, 78.0, 47.0, 1, "left", "middle");
+ n = set_font(dst, n, 17, false);
+ var ue : usize = 0;
+ while (ue < SAY_CARD_URL.len and SAY_CARD_URL[ue] != 32) { ue = ue + 1; }
+ n = text_at(dst, n, SAY_CARD_URL[0..ue], wd - 48.0, 47.0, 6, "right", "middle");
+ // left: the headline
+ n = set_font(dst, n, 26, true);
+ n = text_at(dst, n, SAY_KICKER, 48.0, 98.0, 2, "left", "alphabetic");
+ var hb : [200]u8 = [0; 200];
+ const hq : u32 = headline_split();
+ var hl : u32 = raw(hb[0..], 0, SAY_HEADLINE[0..(hq as usize)]);
+ hl = num_grouped(hb[0..], hl, changes);
+ var big : u32 = 124;
+ n = set_font(dst, n, big, true);
+ // look-ahead: every size the loop may reach
+ var sz : u32 = 124;
+ meas_font(sz, true, hb[0..(hl as usize)]);
+ while (sz > 48) {
+ sz = sz - 4;
+ meas_font(sz, true, hb[0..(hl as usize)]);
+ }
+ while (big > 48 and meas(hb[0..(hl as usize)]) > 600.0) {
+ big = big - 4;
+ n = set_font(dst, n, big, true);
+ }
+ n = text_at(dst, n, hb[0..(hl as usize)], 44.0, 238.0, 1, "left", "alphabetic");
+ n = set_font(dst, n, 36, true);
+ n = text_at(dst, n, trim_ascii(headline_after()), 48.0, 288.0, 4, "left", "alphabetic");
+ n = set_font(dst, n, 22, false);
+ var sub : [400]u8 = [0; 400];
+ var stx : [200]u8 = [0; 200];
+ const stl : u32 = span_text(stx[0..], 0, false);
+ const sbl : u32 = fill_plain(sub[0..], 0, SAY_SUBHEAD, stx[0..(stl as usize)], "", "", 1);
+ var fl : u32 = fit_now(sub[0..(sbl as usize)], 600.0);
+ n = text_at(dst, n, FITB[0..(fl as usize)], 48.0, 330.0, 5, "left", "alphabetic");
+ n = set_font(dst, n, 22, true);
+ var bl : [1200]u8 = [0; 1200];
+ var bn : u32 = 0;
+ if (vs_info(14) > 0) {
+ var cb : [32]u8 = [0; 32];
+ const cl : u32 = num_grouped(cb[0..], 0, vs_row(0, 0, 5));
+ bn = fill_plain(bl[0..], 0, SAY_BUSIEST_LINE, row_name(0, 0), cb[0..(cl as usize)], "", 2);
+ } else {
+ bn = raw(bl[0..], 0, SAY_NO_MOVERS);
+ }
+ const nlines : u32 = tm_wrap(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], CUR_FONT[0..(CUR_FL as usize)], bl[0..(bn as usize)], 600.0, WSP[0..32]);
+ var y : f64 = 384.0;
+ var li : u32 = 0;
+ while (li < nlines and li < 2) {
+ var line : [1200]u8 = [0; 1200];
+ const a : usize = WSP[(li * 2) as usize] as usize;
+ const e : usize = WSP[(li * 2 + 1) as usize] as usize;
+ const ll : u32 = raw(line[0..], 0, bl[a..e]);
+ fl = fit_now(line[0..(ll as usize)], 600.0);
+ n = text_at(dst, n, FITB[0..(fl as usize)], 48.0, y, 4, "left", "alphabetic");
+ y = y + 32.0;
+ li = li + 1;
+ }
+ // right: busiest nets as bars, then the names that never left X or Z
+ const x0 : f64 = 700.0;
+ const x1 : f64 = wd - 48.0;
+ n = card_color(dst, n, 7);
+ n = cv_call4(dst, n, "fillRect", x0 - 28.0, 80.0, 1.0, 366.0);
+ n = set_font(dst, n, 20, true);
+ n = text_at(dst, n, SAY_LIST_TITLES[0], x0, 98.0, 2, "left", "alphabetic");
+ var rows : u32 = vs_info(14) as u32;
+ if (rows > 5) { rows = 5; }
+ var most : f64 = 1.0;
+ var i : u32 = 0;
+ while (i < rows) {
+ const rc : f64 = vs_row(0, i, 5) as f64;
+ if (rc > most) { most = rc; }
+ i = i + 1;
+ }
+ y = 128.0;
+ i = 0;
+ while (i < rows) {
+ n = set_font(dst, n, 18, true);
+ var cnt : [32]u8 = [0; 32];
+ const cl : u32 = num_grouped(cnt[0..], 0, vs_row(0, i, 5));
+ const room : f64 = x1 - x0 - meas(cnt[0..(cl as usize)]) - 12.0;
+ fl = fit_now(row_name(0, i), room);
+ n = text_at(dst, n, FITB[0..(fl as usize)], x0, y, 4, "left", "alphabetic");
+ n = text_at(dst, n, cnt[0..(cl as usize)], x1, y, 1, "right", "alphabetic");
+ n = card_color(dst, n, 8);
+ n = cv_call4(dst, n, "fillRect", x0, y + 8.0, x1 - x0, 6.0);
+ n = card_color(dst, n, 1);
+ n = cv_call4(dst, n, "fillRect", x0, y + 8.0, js_max(3.0, ((x1 - x0) * (vs_row(0, i, 5) as f64)) / most), 6.0);
+ y = y + 46.0;
+ i = i + 1;
+ }
+ y = y + 8.0;
+ n = set_font(dst, n, 18, true);
+ var tc : u32 = 6;
+ if (vs_info(16) != 0) { tc = 3; }
+ n = text_at(dst, n, SAY_LIST_TITLES[2], x0, y, tc, "left", "alphabetic");
+ y = y + 26.0;
+ n = set_font(dst, n, 16, false);
+ var names : u32 = vs_info(17) as u32;
+ if (names > 3) { names = 3; }
+ i = 0;
+ while (i < names) {
+ fl = fit_now(stuck_name(0, i), x1 - x0);
+ n = text_at(dst, n, FITB[0..(fl as usize)], x0, y, 5, "left", "alphabetic");
+ y = y + 22.0;
+ i = i + 1;
+ }
+ const rest : u64 = vs_info(16) - (names as u64);
+ if (rest > 0) {
+ var mb : [32]u8 = [0; 32];
+ const ml : u32 = num_grouped(mb[0..], 0, rest);
+ var mt : [200]u8 = [0; 200];
+ const mtl : u32 = fill_plain(mt[0..], 0, SAY_MORE, mb[0..(ml as usize)], "", "", 1);
+ n = text_at(dst, n, mt[0..(mtl as usize)], x0, y, 6, "left", "alphabetic");
+ } else {
+ if (names == 0) { n = text_at(dst, n, SAY_NONE, x0, y, 6, "left", "alphabetic"); }
+ }
+ // tiles
+ const gap : f64 = 12.0;
+ const tw : f64 = (wd - 96.0 - gap * 5.0) / 6.0;
+ const ty : f64 = 470.0;
+ var t : u32 = 0;
+ while (t < 6) {
+ const v : u64 = tile_value(t);
+ const tx : f64 = 48.0 + (t as f64) * (tw + gap);
+ var bad : bool = false;
+ if (t >= 4 and v > 0) { bad = true; }
+ if (bad) { n = stroke_color(dst, n, 3); } else { n = stroke_color(dst, n, 7); }
+ n = cv_set_num(dst, n, "lineWidth", 2.0);
+ n = cv_call4(dst, n, "strokeRect", tx + 1.0, ty + 1.0, tw - 2.0, 90.0);
+ n = set_font(dst, n, 38, true);
+ var vb : [32]u8 = [0; 32];
+ const vl : u32 = num_grouped(vb[0..], 0, v);
+ fl = fit_now(vb[0..(vl as usize)], tw - 24.0);
+ if (bad) { n = text_at(dst, n, FITB[0..(fl as usize)], tx + 16.0, ty + 50.0, 3, "left", "alphabetic"); } else { n = text_at(dst, n, FITB[0..(fl as usize)], tx + 16.0, ty + 50.0, 1, "left", "alphabetic"); }
+ n = set_font(dst, n, 15, false);
+ fl = fit_now(SAY_TILE_NAMES[t as usize], tw - 24.0);
+ n = text_at(dst, n, FITB[0..(fl as usize)], tx + 16.0, ty + 76.0, 5, "left", "alphabetic");
+ t = t + 1;
+ }
+ // footer
+ n = set_font(dst, n, 16, true);
+ const dropw : f64 = meas(SAY_DROP);
+ n = text_at(dst, n, SAY_DROP, wd - 48.0, hd - 30.0, 2, "right", "middle");
+ n = set_font(dst, n, 16, false);
+ var ft : [1400]u8 = [0; 1400];
+ var fnl : u32 = copy_small(ft[0..], SRC[0..(SRC_LEN as usize)]);
+ if (vs_info(5) != 0) {
+ fnl = raw(ft[0..], fnl, " | ");
+ var vt : [600]u8 = [0; 600];
+ var vn : u32 = vs_info(5) as u32;
+ if (vn > 500) { vn = 500; }
+ const vv : u32 = fill_plain(vt[0..], 0, SAY_VERSION, pool_slice(vs_info(4), vn as u64), "", "", 1);
+ fnl = raw(ft[0..], fnl, vt[0..(vv as usize)]);
+ }
+ fl = fit_now(ft[0..(fnl as usize)], wd - 96.0 - dropw - 24.0);
+ n = text_at(dst, n, FITB[0..(fl as usize)], 48.0, hd - 30.0, 6, "left", "middle");
+ if (!cv_ok(n)) { return 0; }
+ return n;
+}
+
+fn trim_ascii(s: []const u8) -> []const u8 {
+ var a : usize = 0;
+ var b : usize = s.len;
+ while (a < b and (s[a] == 32 or (s[a] >= 9 and s[a] <= 13))) { a = a + 1; }
+ while (b > a and (s[b - 1] == 32 or (s[b - 1] >= 9 and s[b - 1] <= 13))) { b = b - 1; }
+ return s[a..b];
+}
+
+pub fn w_meas_need(dst: []u8) -> u32 {
+ return tm_need(TM[0..16392], MPOOL[0..524288], MOK[0..4096], dst);
+}
+
+pub fn w_meas_give(s: []const u8) -> u32 {
+ return tm_give(TM[0..16392], MOK[0..4096], MWID[0..4096], s);
+}
+
+// --- Tests of the native page -------------------------------------------------------------------------
+
+fn has(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (n < (want.len as u32)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+pub const W_TEST_VCD : str = "$timescale 1ps $end $scope module tb $end $var wire 1 ! clk $end $var wire 4 \" q $end $upscope $end $enddefinitions $end #0 0! b0 \" #5 1! b1 \" #10 0! b11 \" #15 1!";
+pub const W_TEST_META : str = "{\"samples\":[{\"id\":\"counter\",\"vcd_bytes\":1234,\"verdict\":{\"pass\":7,\"total\":7}},{\"id\":\"vsa-bind\",\"vcd_bytes\":9,\"verdict\":{\"pass\":1,\"total\":2}}]}";
+
+fn open_page() -> u32 {
+ w_init("");
+ w_data(0, W_TEST_META);
+ w_data(1, W_TEST_VCD);
+ w_data(2, W_TEST_VCD);
+ w_data(3, W_TEST_VCD);
+ return 0;
+}
+
+test the_page_opens_on_the_card_sample_and_waits_for_its_data {
+ var o : [131072]u8 = [0; 131072];
+ w_init("");
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "aria-pressed=\"true\">t27 ClockedCounter");
+ assert has(o[0..], n, "vw-png\" type=\"button\" disabled=\"\"");
+ assert !has(o[0..], n, "vw-top");
+ open_page();
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "
");
+ assert has(o[0..], m, "
The testbench's own verdict: 7 of 7 checks passed.
");
+ assert has(o[0..], m, "Shipped as vvp dumped it, not trimmed: 1,234 bytes.");
+ assert !has(o[0..], m, "disabled=\"\"");
+}
+
+test a_dump_is_summed_up_the_way_vcdstats_counts_it {
+ open_page();
+ var o : [131072]u8 = [0; 131072];
+ const n : u32 = w_render(o[0..]);
+ assert vs_info(12) == 5;
+ assert vs_info(13) == 5;
+ assert vs_info(6) == 4;
+ assert has(o[0..], n, "5 value changes");
+ assert has(o[0..], n, "over 15 ps of simulated time");
+ assert has(o[0..], n, "Your busiest net was tb.clk, with 3 changes.");
+ assert has(o[0..], n, "tb.clkchanges 3, flips 3");
+ assert has(o[0..], n, "$timescale 1ps");
+ assert has(o[0..], n, "
Never left X or Z
none
");
+}
+
+test a_file_that_is_not_a_vcd_is_refused_in_the_words_of_the_spec {
+ open_page();
+ assert w_file(0, "My Dump.VCD", 5) == 1;
+ w_file_data(0, "hello");
+ var o : [131072]u8 = [0; 131072];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
Not a VCD file: a VCD starts with a $ keyword such as $date or $timescale, and this starts with "hello".
");
+ assert has(o[0..], n, "My Dump.VCD: 5 bytes");
+ assert has(o[0..], n, "disabled=\"\"");
+ w_file_data(0, "");
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "
");
+}
+
+test the_card_asks_for_widths_until_it_has_them_and_names_its_file {
+ open_page();
+ w_file(0, "My Dump.VCD", 100);
+ w_file_data(0, W_TEST_VCD);
+ w_click(3);
+ var e : [256]u8 = [0; 256];
+ const l : u32 = w_effect(0, e[0..]);
+ assert l > 0;
+ assert has(e[0..], l, "vcd-wrapped-my-dump.png");
+ var c : [131072]u8 = [0; 131072];
+ var need : [65536]u8 = [0; 65536];
+ var g : [3000]u8 = [0; 3000];
+ var q : usize = 0;
+ while (q + 3 <= 3000) {
+ g[q] = 49;
+ g[q + 1] = 48;
+ g[q + 2] = 10;
+ q = q + 3;
+ }
+ var pass : u32 = 0;
+ var n : u32 = 0;
+ var open : u32 = 1;
+ while (open > 0 and pass < 60) {
+ n = w_card(c[0..]);
+ open = w_meas_need(need[0..]);
+ if (open > 0) { w_meas_give(g[0..]); }
+ pass = pass + 1;
+ }
+ assert open == 0;
+ assert pass >= 2;
+ assert has(c[0..], n, "[\"c\",0],[\"s\",\"fillStyle\",{\"v\":\"--w-bg\",\"f\":\"#000\"}],[\"m\",\"fillRect\",0,0,1200,630],");
+ assert has(c[0..], n, "[\"s\",\"font\",\"bold 20px Menlo, ui-monospace, SFMono-Regular, monospace\"]");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"TRINITY S3AI / VCD WRAPPED\",78,47],");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"5\",44,238],");
+ // every width is 10 px: a name of 6 letters fits, so nothing is cut
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"tb.clk\",700,128],");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"Your busiest net was tb.clk, with 3 changes.\",48,384],");
+}
+
+test samples_json_without_a_verdict_says_the_samples_did_not_load {
+ w_init("");
+ w_data(0, "{\"samples\":[{\"id\":\"counter\"}]}");
+ w_data(1, W_TEST_VCD);
+ var o : [131072]u8 = [0; 131072];
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
The sample simulations did not load.
");
+ assert !has(o[0..], n, "vw-top");
+ w_data_failed(2);
+ w_click(1);
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "
The sample simulations did not load.
");
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader, the number rules and the
+/// canvas writers call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8, m: []u32, f: []f64, k: []u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + num_grouped(o, 0, 0);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ n = n + CV_OVER;
+ n = n + (cv_ok(0) as u32);
+ n = n + cv_put(o, 0, 0);
+ n = n + cv_raw(o, 0, s);
+ n = n + (cv_fin(0.0) as u32);
+ n = n + cv_num(o, 0, 0.0);
+ n = n + cv_str(o, 0, s);
+ n = n + (cv_hex(0) as u32);
+ n = n + cv_open(o, 0, 0, s);
+ n = n + cv_close(o, 0);
+ n = n + cv_arg_f(o, 0, 0.0);
+ n = n + cv_arg_s(o, 0, s);
+ n = n + cv_arg_v(o, 0, s, s);
+ n = n + cv_arg_b(o, 0, false);
+ n = n + cv_arg_list(o, 0, 0.0, 0.0, 0);
+ n = n + cv_canvas(o, 0, 0);
+ n = n + cv_set_num(o, 0, s, 0.0);
+ n = n + cv_set_str(o, 0, s, s);
+ n = n + cv_set_var(o, 0, s, s, s);
+ n = n + cv_call0(o, 0, s);
+ n = n + cv_call2(o, 0, s, 0.0, 0.0);
+ n = n + cv_call4(o, 0, s, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_call6(o, 0, s, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_arc(o, 0, 0.0, 0.0, 0.0, 0.0, 0.0, false);
+ n = n + cv_text(o, 0, s, s, 0.0, 0.0);
+ n = n + cv_dash(o, 0, 0.0, 0.0, 0);
+ n = n + (TM_HEAD as u32);
+ n = n + (TM_AHEAD as u32);
+ tm_reset(m);
+ n = n + (tm_missed(m) as u32);
+ tm_clear_miss(m);
+ n = n + (same_at(s, 0, 0, s) as u32);
+ n = n + (tm_find(m, s, s, s) as u32);
+ pool_add(o, 0, s);
+ n = n + (tm_add(m, o, f, k, s, s) as u32);
+ n = n + ((tm_width(m, o, f, k, s, s) > 0.0) as u32);
+ tm_ask(m, o, f, k, s, s);
+ n = n + tm_need(m, s, k, o);
+ n = n + tm_give(m, k, f, s);
+ n = n + (tm_units(s, 0, 0) as u32);
+ n = n + (tm_charlen(s, 0, 0) as u32);
+ n = n + (put_dots(o, s, 0) as u32);
+ n = n + (tm_fit_left(m, o, f, k, s, s, 0.0, o) as u32);
+ n = n + tm_wrap(m, o, f, k, s, s, 0.0, m);
+ return n;
+}
From 2bd34e932e8bcce223cc4006b44666c114a35f35 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 21:11:13 +0700
Subject: [PATCH 54/63] widgets: stat-card drawn natively -- 36671-byte wasm,
1530 DOM and canvas comparisons, 0 differences (Closes #10938)
The whole page of the stat-card tool in .t27: the 1200x630 card as a canvas draw list (card text cut and
wrapped by widths the host measures: w_text_need / w_text_give), the facts table, the README badge SVG,
paste / pick / drop, PNG and SVG download, copy with its timer. stat_card_logic.t27 is folded into the module.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/stat_card.t27 | 3414 +++++++++++++++++++++++++++++++++++
1 file changed, 3414 insertions(+)
create mode 100644 specs/widgets/stat_card.t27
diff --git a/specs/widgets/stat_card.t27 b/specs/widgets/stat_card.t27
new file mode 100644
index 0000000000..830ec3f486
--- /dev/null
+++ b/specs/widgets/stat_card.t27
@@ -0,0 +1,3414 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/stat-card.t27 -- a tweetable card and a README badge from yosys `stat -json`
+; Source of truth for public/widgets/stat-card/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its
+; own and reads every word below from window.T27_WIDGET.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader pastes the JSON that yosys `stat -json` printed, or drops the file
+; `tee -o stat.json stat -json` wrote. The page reads it in the browser with
+; public/widgets/stat-card/statparse.js, sorts every cell type into LUT, FF, DSP or BRAM by the
+; four K_*_TYPES lists below, lists every other type under "other" with its count, and draws a
+; 1200x630 card (top module, the four counts, the other types, the yosys version from "creator")
+; and a small shields-like README badge as plain SVG text, to copy or download. The file never
+; leaves the page; nothing is sent.
+
+; THE SAMPLE: one real run of Yosys 0.67+post (git sha1 b8e7da6f) on this machine, made by
+; scripts/widget-data/stat-card.mjs: fpga/openxc7-synth/lm_head_matvec.v with its weight file
+; fpga/weights/lm_head_weights.mem (both in gHashTag/trinity), `synth_xilinx -flatten -top
+; lm_head_matvec`, then `tee -q -o stat.json stat -json`. The output is shipped byte for byte as
+; public/widgets/stat-card/sample.stat.json; the command, the version line and the sha256 of both
+; inputs are in sample.json. Every K_SAMPLE_ fact below is re-read from that file by
+; `node scripts/widget-data/stat-card.mjs --check`, which also runs this spec's tests on the
+; values it read and a negative control that must fail them.
+
+; THE TWO SHAPES: yosys 0.67 prints the per-type map as "num_cells_by_type"; older releases
+; print it as "cells". Both are read the same way. Only 0.67+post was run here; the "cells"
+; shape is read by the same code but was not checked against a real older yosys.
+
+; WHAT IT DOES NOT CLAIM: a yosys stat count is a synthesis count, not post-place-and-route
+; utilisation. A placer can pack, duplicate or remove cells: INV, for one, is its own cell here
+; and is listed under other. The card and the badge both say "yosys synth" for that reason. The
+; lists are 7-series cell names (synth_xilinx -family xc7, the default); cells of other families
+; or of a generic `synth` read as other. DSP 0 on the sample is what yosys reported for this
+; design: its ternary weights are added or subtracted, never multiplied.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_stat_card;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::canvas_out::cv_ok;
+use widgets::canvas_out::cv_put;
+use widgets::canvas_out::cv_raw;
+use widgets::canvas_out::cv_fin;
+use widgets::canvas_out::cv_num;
+use widgets::canvas_out::cv_str;
+use widgets::canvas_out::cv_hex;
+use widgets::canvas_out::cv_open;
+use widgets::canvas_out::cv_close;
+use widgets::canvas_out::cv_arg_f;
+use widgets::canvas_out::cv_arg_s;
+use widgets::canvas_out::cv_arg_v;
+use widgets::canvas_out::cv_arg_b;
+use widgets::canvas_out::cv_arg_list;
+use widgets::canvas_out::cv_canvas;
+use widgets::canvas_out::cv_set_num;
+use widgets::canvas_out::cv_set_str;
+use widgets::canvas_out::cv_set_var;
+use widgets::canvas_out::cv_call0;
+use widgets::canvas_out::cv_call2;
+use widgets::canvas_out::cv_call4;
+use widgets::canvas_out::cv_call6;
+use widgets::canvas_out::cv_arc;
+use widgets::canvas_out::cv_text;
+use widgets::canvas_out::cv_dash;
+use widgets::canvas_out::CV_OVER;
+use widgets::page_reader::match_in;
+use widgets::page_reader::pack;
+use widgets::page_reader::lo_of;
+use widgets::page_reader::hi_of;
+use widgets::page_reader::NO_TEXT;
+use widgets::page_reader::PACK;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "stat-card";
+pub const TITLE : str = "Stat card: your yosys stat -json as a card and a README badge";
+pub const DESCRIPTION : str = "Paste yosys stat -json: LUT, FF, DSP and BRAM counts, the top module and the yosys version on a 1200x630 card plus a README badge SVG. Read in your browser.";
+pub const IMAGE_ALT : str = "A stat card on black for a real yosys 0.67 synth_xilinx run of lm_head_matvec, a ternary matrix-vector unit: 138 LUT, 149 FF, 0 DSP and 4 BRAM (RAMB36E1), with 139 other cells listed by type (OBUF 51, CARRY4 32, INV 28, IBUF 23, MUXF7 3, BUFG 1, MUXF8 1), marked yosys synth, not place and route.";
+pub const HOOK : str = "Paste your yosys stat -json, get a card to post and a badge for your README: LUT, FF, DSP, BRAM, marked as a synth count.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159), run once by scripts/widget-data/stat-card.mjs: nice -n 10 yosys -q -p \"read_verilog fpga/openxc7-synth/lm_head_matvec.v; synth_xilinx -flatten -top lm_head_matvec; tee -q -o stat.json stat -json\"", "gHashTag/trinity fpga/openxc7-synth/lm_head_matvec.v (sha256 749f9a8ba8fee582...) and fpga/weights/lm_head_weights.mem (sha256 03402649d7959c99...), copied to a scratch directory with the same relative paths", "public/widgets/stat-card/sample.stat.json: the yosys output, byte for byte; sample.json: command, yosys -V line, exit code, seconds and both input sha256", "public/widgets/stat-card/statparse.js: the one parser and badge writer, run in node for the sample and in the browser for your file; card.png is drawn from sample.json by scripts/widget-data/stat-card-card.py", "node scripts/widget-data/stat-card.mjs --check: re-reads sample.stat.json and fails if any K_SAMPLE_ fact in this spec disagrees"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Which cell type is which (7-series names, as synth_xilinx writes them) ---------------------
+pub const K_LUT_TYPES : [6]str = ["LUT1", "LUT2", "LUT3", "LUT4", "LUT5", "LUT6"];
+pub const K_FF_TYPES : [4]str = ["FDRE", "FDSE", "FDCE", "FDPE"];
+pub const K_DSP_TYPES : [1]str = ["DSP48E1"];
+pub const K_BRAM_TYPES : [2]str = ["RAMB18E1", "RAMB36E1"];
+; yosys bookkeeping that is no hardware: shown on its own line, counted in no group.
+pub const K_INTERNAL_TYPES : [1]str = ["$scopeinfo"];
+
+; --- The sample (scripts/widget-data/stat-card.mjs re-reads every K_SAMPLE_ fact) ---------------
+pub const K_SAMPLE_JSON : str = "sample.json";
+pub const K_SAMPLE_STAT : str = "sample.stat.json";
+pub const K_SAMPLE_DESIGN : str = "fpga/openxc7-synth/lm_head_matvec.v";
+pub const K_SAMPLE_MEM : str = "fpga/weights/lm_head_weights.mem";
+pub const K_SAMPLE_TOP : str = "lm_head_matvec";
+pub const K_SAMPLE_YOSYS : str = "0.67+post";
+; Every cell type of the sample, in the order yosys printed them, and its count.
+pub const K_SAMPLE_TYPES : [14]str = ["BUFG", "CARRY4", "FDRE", "IBUF", "INV", "LUT2", "LUT3", "LUT4", "LUT5", "LUT6", "MUXF7", "MUXF8", "OBUF", "RAMB36E1"];
+pub const K_SAMPLE_COUNTS : [14]u16 = [1, 32, 149, 23, 28, 69, 3, 4, 50, 12, 3, 1, 51, 4];
+pub const K_SAMPLE_LUT : u16 = 138;
+pub const K_SAMPLE_FF : u16 = 149;
+pub const K_SAMPLE_DSP : u16 = 0;
+pub const K_SAMPLE_BRAM : u16 = 4;
+pub const K_SAMPLE_OTHER : u16 = 139;
+pub const K_SAMPLE_CELLS : u16 = 430;
+
+; --- How it draws -------------------------------------------------------------------------------
+pub const K_CARD_W : u16 = 1200;
+pub const K_CARD_H : u16 = 630;
+pub const K_MIN_TARGET_PX : u8 = 32;
+; A pasted text over this many characters is not read.
+pub const K_MAX_PASTE_CHARS : u32 = 4000000;
+; Other types named on the card before the rest are folded into one "+N more".
+pub const K_CARD_OTHER_MAX : u8 = 9;
+; The badge: height, font size, the width given to each character, padding, two fills.
+pub const K_BADGE_H : u8 = 20;
+pub const K_BADGE_FONT_PX : u8 = 11;
+pub const K_BADGE_CHAR_W : u8 = 7;
+pub const K_BADGE_PAD : u8 = 6;
+pub const K_BADGE_LABEL_FILL : str = "#555";
+pub const K_BADGE_VALUE_FILL : str = "#2e7d4f";
+
+; --- Every word on the screen -------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_SAMPLE_LABEL : str = "Real sample";
+pub const SAY_SAMPLE_NAME : str = "lm_head_matvec (yosys 0.67)";
+pub const SAY_SAMPLE_LINE : str = "A ternary matrix-vector unit from gHashTag/trinity, fpga/openxc7-synth/lm_head_matvec.v, synthesised on our machine with synth_xilinx -flatten. Its weights sit in block RAM and are added or subtracted, so yosys used no DSP.";
+pub const SAY_OPEN_SAMPLE : str = "Open the yosys output";
+pub const SAY_OWN_LABEL : str = "Your stat -json";
+pub const SAY_HOW : str = "In yosys, after synthesis: tee -q -o stat.json stat -json";
+pub const SAY_DROP : str = "Drop stat.json here, or pick a file";
+pub const SAY_PASTE_HINT : str = "...or paste what stat -json printed";
+pub const SAY_PASTE_LABEL : str = "yosys stat -json text";
+pub const SAY_READ : str = "Make the card";
+pub const SAY_CLEAR : str = "Clear";
+pub const SAY_PICKED : str = "{0}: {1} bytes";
+pub const SAY_PASTED : str = "pasted text";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab; nothing is sent.";
+pub const SAY_TOO_BIG : str = "{0} characters is more than this page reads ({1}).";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_LOAD : str = "The sample did not load.";
+; One per parse error code of statparse.js, in this order.
+pub const SAY_ERROR_IDS : [5]str = ["empty", "nojson", "badjson", "nomodules", "nocells"];
+pub const SAY_ERROR_TEXT : [5]str = ["Nothing to read yet.", "No JSON object in this text. Run stat with -json.", "The JSON does not parse. Paste all of it, from the first { to the last }.", "This JSON has no \"modules\": it is not yosys stat -json output.", "No per-type cell map (num_cells_by_type or cells) in this stat."];
+
+; The four groups, in card order, and the words around them.
+pub const SAY_GROUP_IDS : [4]str = ["lut", "ff", "dsp", "bram"];
+pub const SAY_GROUP_NAMES : [4]str = ["LUT", "FF", "DSP", "BRAM"];
+pub const SAY_GROUP_RULES : [4]str = ["LUT1 to LUT6", "FDRE, FDSE, FDCE, FDPE", "DSP48E1", "RAMB18E1, RAMB36E1"];
+pub const SAY_OTHER : str = "other";
+pub const SAY_OTHER_LINE : str = "Every other cell type, by count: none are dropped.";
+pub const SAY_OTHER_NONE : str = "none";
+pub const SAY_OTHER_MORE : str = "+{0} more";
+pub const SAY_INTERNAL : str = "yosys bookkeeping, no hardware: {0}";
+pub const SAY_HIER : str = "submodule instances, their cells counted above: {0}";
+pub const SAY_NO_XILINX : str = "No 7-series LUT, FF, DSP or BRAM cell in this stat. Was it synth_xilinx? Generic synth cells read as other.";
+pub const SAY_TOP : str = "top module";
+pub const SAY_NO_TOP : str = "top module not named";
+pub const SAY_VERSION : str = "yosys {0}";
+pub const SAY_NO_VERSION : str = "yosys version not in this JSON";
+pub const SAY_CELLS : str = "{0} cells";
+pub const SAY_SHAPE : str = "Read from \"{0}\" ({1}), {2} module(s).";
+pub const SAY_FROM : [2]str = ["the design totals", "the top module, submodules expanded"];
+pub const SAY_TABLE_HEAD : [3]str = ["cell type", "count", "counted as"];
+
+; The card and the badge.
+pub const SAY_CARD_BRAND : str = "TRINITY S3AI / STAT CARD";
+pub const SAY_CARD_KIND : str = "yosys synth, not place and route";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/stat-card";
+pub const SAY_PREVIEW_ALT : str = "The card for this stat: top module, LUT, FF, DSP and BRAM counts, other cells and the yosys version.";
+pub const SAY_PNG_BUTTON : str = "Save card PNG";
+pub const SAY_PNG_FILE : str = "stat-card-{0}.png";
+pub const SAY_BADGE_LABEL : str = "yosys synth";
+pub const SAY_BADGE_VALUE : str = "{0} LUT | {1} FF | {2} DSP | {3} BRAM";
+pub const SAY_BADGE_TITLE : str = "{0} -- top {1}, yosys {2}: synthesis cell counts, not place and route";
+pub const SAY_BADGE_HEAD : str = "README badge";
+pub const SAY_BADGE_NOTE : str = "Plain SVG, no fonts or links in it. Save it next to your README and point an image at it.";
+pub const SAY_BADGE_COPY : str = "Copy SVG";
+pub const SAY_BADGE_COPIED : str = "Copied";
+pub const SAY_BADGE_SAVE : str = "Download SVG";
+pub const SAY_BADGE_FILE : str = "yosys-synth-{0}.svg";
+pub const SAY_BADGE_TEXT_LABEL : str = "Badge SVG text";
+pub const SAY_HONEST : str = "These are yosys stat counts after synthesis, before place and route: a placer packs, duplicates and removes cells, so a P&R utilisation report can differ. The groups are 7-series cell names; every other type is listed under other.";
+
+; --- Tests --------------------------------------------------------------------------------------
+test the_four_lists_name_7_series_cells {
+ assert K_LUT_TYPES[0] == "LUT1";
+ assert K_LUT_TYPES[5] == "LUT6";
+ assert K_FF_TYPES[0] == "FDRE";
+ assert K_FF_TYPES[3] == "FDPE";
+ assert K_DSP_TYPES[0] == "DSP48E1";
+ assert K_BRAM_TYPES[0] == "RAMB18E1";
+ assert K_BRAM_TYPES[1] == "RAMB36E1";
+}
+
+test the_sample_luts_are_its_lut2_to_lut6_cells {
+ assert K_SAMPLE_TYPES[5] == "LUT2";
+ assert K_SAMPLE_TYPES[6] == "LUT3";
+ assert K_SAMPLE_TYPES[7] == "LUT4";
+ assert K_SAMPLE_TYPES[8] == "LUT5";
+ assert K_SAMPLE_TYPES[9] == "LUT6";
+ assert K_SAMPLE_LUT == K_SAMPLE_COUNTS[5] + K_SAMPLE_COUNTS[6] + K_SAMPLE_COUNTS[7] + K_SAMPLE_COUNTS[8] + K_SAMPLE_COUNTS[9];
+ assert K_SAMPLE_LUT == 138;
+}
+
+test the_sample_ffs_dsps_and_brams_come_from_their_lists {
+ assert K_SAMPLE_TYPES[2] == "FDRE";
+ assert K_SAMPLE_FF == K_SAMPLE_COUNTS[2];
+ assert same_text(K_SAMPLE_TYPES[13], K_BRAM_TYPES[1]);
+ assert K_SAMPLE_BRAM == K_SAMPLE_COUNTS[13];
+ assert K_SAMPLE_BRAM == 4;
+ assert K_SAMPLE_DSP == 0;
+}
+
+test every_other_sample_cell_is_counted_not_dropped {
+ assert K_SAMPLE_TYPES[4] == "INV";
+ assert K_SAMPLE_OTHER == K_SAMPLE_COUNTS[0] + K_SAMPLE_COUNTS[1] + K_SAMPLE_COUNTS[3] + K_SAMPLE_COUNTS[4] + K_SAMPLE_COUNTS[10] + K_SAMPLE_COUNTS[11] + K_SAMPLE_COUNTS[12];
+ assert K_SAMPLE_CELLS == K_SAMPLE_LUT + K_SAMPLE_FF + K_SAMPLE_DSP + K_SAMPLE_BRAM + K_SAMPLE_OTHER;
+ assert K_SAMPLE_CELLS == K_SAMPLE_COUNTS[0] + K_SAMPLE_COUNTS[1] + K_SAMPLE_COUNTS[2] + K_SAMPLE_COUNTS[3] + K_SAMPLE_COUNTS[4] + K_SAMPLE_COUNTS[5] + K_SAMPLE_COUNTS[6] + K_SAMPLE_COUNTS[7] + K_SAMPLE_COUNTS[8] + K_SAMPLE_COUNTS[9] + K_SAMPLE_COUNTS[10] + K_SAMPLE_COUNTS[11] + K_SAMPLE_COUNTS[12] + K_SAMPLE_COUNTS[13];
+}
+
+test the_card_and_badge_say_it_is_a_synth_count {
+ assert SAY_BADGE_LABEL == "yosys synth";
+ assert SAY_CARD_KIND == "yosys synth, not place and route";
+ assert K_CARD_W == 1200;
+ assert K_CARD_H == 630;
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+}
+
+; FOLD BEGIN -- the body of stat_card_logic.t27, from its first const to its last test, with these changes only:
+; js_round is sc_round (js_num has a js_round); ARB and TIT are 262144 bytes (were 4096) and arg_set
+; checks that size; arg_num writes String(x) by js_str_num (was: whole numbers below 1e15, else -2).
+pub const K_NULL : u8 = 0;
+pub const K_FALSE : u8 = 1;
+pub const K_TRUE : u8 = 2;
+pub const K_NUM : u8 = 3;
+pub const K_STR : u8 = 4;
+pub const K_ARR : u8 = 5;
+pub const K_OBJ : u8 = 6;
+
+pub const V_PN : usize = 0;
+pub const V_NN : usize = 1;
+pub const V_UNSUP : usize = 2;
+pub const V_JR : usize = 3;
+pub const V_JO : usize = 4;
+pub const V_JL : usize = 5;
+pub const V_ENT : usize = 6;
+pub const V_BTK : usize = 7;
+pub const V_CAP : usize = 8;
+pub const V_BC : usize = 9;
+pub const V_NT : usize = 10;
+pub const V_AN : usize = 11;
+pub const V_AB : usize = 12;
+
+pub var V : [16]u64 = [0; 16];
+pub var INF : [16]i64 = [0; 16];
+pub var POOL : [4194304]u8 = [0; 4194304];
+pub var NK : [131073]u8 = [0; 131073];
+pub var NA : [131073]u32 = [0; 131073];
+pub var NB : [131073]u32 = [0; 131073];
+pub var NX : [131073]u32 = [0; 131073];
+pub var KO : [131073]u32 = [0; 131073];
+pub var KL : [131073]u32 = [0; 131073];
+pub var EST : [131073]u32 = [0; 131073];
+pub var ECN : [131073]u32 = [0; 131073];
+pub var EIL : [131073]u8 = [0; 131073];
+pub var EBU : [131073]u8 = [0; 131073];
+pub var EKO : [131073]u32 = [0; 131073];
+pub var EKL : [131073]u32 = [0; 131073];
+pub var EVN : [131073]u32 = [0; 131073];
+pub var EUS : [131073]u8 = [0; 131073];
+pub var P10 : [23]f64 = [0.0; 23];
+pub var LKO : [16384]u32 = [0; 16384];
+pub var LKL : [16384]u32 = [0; 16384];
+pub var LVL : [16384]f64 = [0.0; 16384];
+pub var LCN : [32]u32 = [0; 32];
+pub var SEO : [32]u32 = [0; 32];
+pub var SEL : [32]u32 = [0; 32];
+pub var TYO : [16384]u32 = [0; 16384];
+pub var TYL : [16384]u32 = [0; 16384];
+pub var TYN : [16384]f64 = [0.0; 16384];
+pub var TYG : [16384]u8 = [0; 16384];
+pub var GRP : [8]f64 = [0.0; 8];
+pub var CELLS : [2]f64 = [0.0; 2];
+pub var VAL : [2048]u8 = [0; 2048];
+pub var TIT : [262144]u8 = [0; 262144];
+pub var ARB : [262144]u8 = [0; 262144];
+pub var ARO : [8]u32 = [0; 8];
+pub var ARL : [8]u32 = [0; 8];
+
+// ---------------------------------------------------------------- bytes and whitespace
+/// The length of the JavaScript whitespace character at s[p ..] (0 when none), UTF-8.
+pub fn ws_len_at(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi) { return 0; }
+ const b : u8 = s[p];
+ if (b == 32 or (b >= 9 and b <= 13)) { return 1; }
+ if (b == 194 and p + 1 < hi and s[p + 1] == 160) { return 2; }
+ if (p + 3 <= hi) {
+ const b1 : u8 = s[p + 1];
+ const b2 : u8 = s[p + 2];
+ if (b == 225 and b1 == 154 and b2 == 128) { return 3; }
+ if (b == 226 and b1 == 128 and ((b2 >= 128 and b2 <= 138) or b2 == 168 or b2 == 169 or b2 == 175)) { return 3; }
+ if (b == 226 and b1 == 129 and b2 == 159) { return 3; }
+ if (b == 227 and b1 == 128 and b2 == 128) { return 3; }
+ if (b == 239 and b1 == 187 and b2 == 191) { return 3; }
+ }
+ return 0;
+}
+
+/// The length of the JavaScript whitespace character that ends at e.
+pub fn ws_len_before(s: []const u8, lo: usize, e: usize) -> usize {
+ if (e > lo and ws_len_at(s, e - 1, e) == 1) { return 1; }
+ if (e >= lo + 2 and ws_len_at(s, e - 2, e) == 2) { return 2; }
+ if (e >= lo + 3 and ws_len_at(s, e - 3, e) == 3) { return 3; }
+ return 0;
+}
+
+/// String.prototype.trim: the packed [lo, hi).
+pub fn trim_bounds(s: []const u8) -> i64 {
+ var lo : usize = 0;
+ var hi : usize = s.len;
+ var w : usize = ws_len_at(s, lo, hi);
+ while (w > 0) {
+ lo = lo + w;
+ w = ws_len_at(s, lo, hi);
+ }
+ w = ws_len_before(s, lo, hi);
+ while (w > 0) {
+ hi = hi - w;
+ w = ws_len_before(s, lo, hi);
+ }
+ return pack(lo, hi);
+}
+
+pub fn jws(s: []const u8, p0: usize, hi: usize) -> usize {
+ var p : usize = p0;
+ while (p < hi and (s[p] == 32 or s[p] == 9 or s[p] == 10 or s[p] == 13)) { p = p + 1; }
+ return p;
+}
+
+pub fn is_digit(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+pub fn hexv(b: u8) -> i64 {
+ if (b >= 48 and b <= 57) { return (b - 48) as i64; }
+ if (b >= 97 and b <= 102) { return (b - 87) as i64; }
+ if (b >= 65 and b <= 70) { return (b - 55) as i64; }
+ return -1;
+}
+
+/// Four hex digits at s[p ..], or -1.
+pub fn hex4(s: []const u8, p: usize, hi: usize) -> i64 {
+ if (p + 4 > hi) { return -1; }
+ var v : i64 = 0;
+ var k : usize = 0;
+ while (k < 4) {
+ const h : i64 = hexv(s[p + k]);
+ if (h < 0) { return -1; }
+ v = v * 16 + h;
+ k = k + 1;
+ }
+ return v;
+}
+
+// ---------------------------------------------------------------- the string pool
+pub fn pool_put(b: u8) -> bool {
+ if (V[V_PN] >= 4194300) { return false; }
+ POOL[V[V_PN] as usize] = b;
+ V[V_PN] = V[V_PN] + 1;
+ return true;
+}
+
+pub fn put_utf8(cp: u32) -> bool {
+ if (cp < 128) { return pool_put(cp as u8); }
+ if (cp < 2048) {
+ if (!pool_put((192 + cp / 64) as u8)) { return false; }
+ return pool_put((128 + cp % 64) as u8);
+ }
+ if (cp < 65536) {
+ if (!pool_put((224 + cp / 4096) as u8)) { return false; }
+ if (!pool_put((128 + (cp / 64) % 64) as u8)) { return false; }
+ return pool_put((128 + cp % 64) as u8);
+ }
+ if (!pool_put((240 + cp / 262144) as u8)) { return false; }
+ if (!pool_put((128 + (cp / 4096) % 64) as u8)) { return false; }
+ if (!pool_put((128 + (cp / 64) % 64) as u8)) { return false; }
+ return pool_put((128 + cp % 64) as u8);
+}
+
+pub fn pool_put_u(k0: u64) -> bool {
+ var digits : u64 = 1;
+ var t : u64 = k0;
+ while (t >= 10) {
+ t = t / 10;
+ digits = digits * 10;
+ }
+ var d : u64 = digits;
+ var r : u64 = k0;
+ while (d > 0) {
+ if (!pool_put((48 + r / d) as u8)) { return false; }
+ r = r % d;
+ d = d / 10;
+ }
+ return true;
+}
+
+pub fn pool_eq(a: u32, al: u32, b: u32, bl: u32) -> bool {
+ if (al != bl) { return false; }
+ var k : u32 = 0;
+ while (k < al) {
+ if (POOL[(a + k) as usize] != POOL[(b + k) as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn pool_is(a: u32, al: u32, w: []const u8) -> bool {
+ if (al as usize != w.len) { return false; }
+ var k : u32 = 0;
+ while (k < al) {
+ if (POOL[(a + k) as usize] != w[k as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// bare(): the start and length of a pool string without one leading backslash.
+pub fn bare_off(o: u32, l: u32) -> u32 {
+ if (l > 0 and POOL[o as usize] == 92) { return o + 1; }
+ return o;
+}
+
+pub fn bare_len(o: u32, l: u32) -> u32 {
+ if (l > 0 and POOL[o as usize] == 92) { return l - 1; }
+ return l;
+}
+
+// ---------------------------------------------------------------- the JSON reader (JSON.parse grammar)
+pub fn esc_byte(e: u8) -> i32 {
+ if (e == 34) { return 34; }
+ if (e == 92) { return 92; }
+ if (e == 47) { return 47; }
+ if (e == 98) { return 8; }
+ if (e == 102) { return 12; }
+ if (e == 110) { return 10; }
+ if (e == 114) { return 13; }
+ if (e == 116) { return 9; }
+ return -1;
+}
+
+/// A string starting at the quote p: decoded into the pool (V_JO, V_JL); the end, -1 syntax, -3 limit.
+pub fn jstring(s: []const u8, p: usize, hi: usize) -> i64 {
+ var q : usize = p + 1;
+ V[V_JO] = V[V_PN];
+ while (q < hi) {
+ const b : u8 = s[q];
+ if (b == 34) {
+ V[V_JL] = V[V_PN] - V[V_JO];
+ return (q + 1) as i64;
+ }
+ if (b < 32) { return -1; }
+ if (b == 92) {
+ if (q + 1 >= hi) { return -1; }
+ const e : u8 = s[q + 1];
+ const sb : i32 = esc_byte(e);
+ if (sb >= 0) {
+ if (!pool_put(sb as u8)) { return -3; }
+ q = q + 2;
+ } else {
+ if (e != 117) { return -1; }
+ const h : i64 = hex4(s, q + 2, hi);
+ if (h < 0) { return -1; }
+ var cp : u32 = h as u32;
+ q = q + 6;
+ var emit : bool = true;
+ if (cp >= 55296 and cp <= 56319) {
+ emit = false;
+ if (q + 6 <= hi and s[q] == 92 and s[q + 1] == 117) {
+ const l : i64 = hex4(s, q + 2, hi);
+ if (l >= 56320 and l <= 57343) {
+ cp = 65536 + (cp - 55296) * 1024 + ((l as u32) - 56320);
+ q = q + 6;
+ emit = true;
+ }
+ }
+ if (!emit) { V[V_UNSUP] = 1; }
+ } else {
+ if (cp >= 56320 and cp <= 57343) {
+ emit = false;
+ V[V_UNSUP] = 1;
+ }
+ }
+ if (emit) {
+ if (!put_utf8(cp)) { return -3; }
+ }
+ }
+ } else {
+ if (!pool_put(b)) { return -3; }
+ q = q + 1;
+ }
+ }
+ return -1;
+}
+
+/// A number starting at p (JSON grammar): the end, or -1.
+pub fn jnumber(s: []const u8, p0: usize, hi: usize) -> i64 {
+ var p : usize = p0;
+ if (p < hi and s[p] == 45) { p = p + 1; }
+ if (p >= hi) { return -1; }
+ if (s[p] == 48) {
+ p = p + 1;
+ } else {
+ if (s[p] < 49 or s[p] > 57) { return -1; }
+ while (p < hi and is_digit(s[p])) { p = p + 1; }
+ }
+ if (p < hi and s[p] == 46) {
+ p = p + 1;
+ if (p >= hi or !is_digit(s[p])) { return -1; }
+ while (p < hi and is_digit(s[p])) { p = p + 1; }
+ }
+ if (p < hi and (s[p] == 101 or s[p] == 69)) {
+ p = p + 1;
+ if (p < hi and (s[p] == 43 or s[p] == 45)) { p = p + 1; }
+ if (p >= hi or !is_digit(s[p])) { return -1; }
+ while (p < hi and is_digit(s[p])) { p = p + 1; }
+ }
+ return p as i64;
+}
+
+pub fn alloc_node(kind: u8) -> usize {
+ if (V[V_NN] >= 131072) { return 0; }
+ V[V_NN] = V[V_NN] + 1;
+ const n : usize = V[V_NN] as usize;
+ NK[n] = kind;
+ NA[n] = 0;
+ NB[n] = 0;
+ NX[n] = 0;
+ KO[n] = 0;
+ KL[n] = 0;
+ EBU[n] = 0;
+ return n;
+}
+
+/// A value at p (no leading space): the end, -1 syntax, -3 limit. The node is left in V_JR.
+pub fn jvalue(s: []const u8, p: usize, hi: usize, depth: u32) -> i64 {
+ if (p >= hi) { return -1; }
+ const b : u8 = s[p];
+ if (b == 34) {
+ const r : i64 = jstring(s, p, hi);
+ if (r < 0) { return r; }
+ const n : usize = alloc_node(K_STR);
+ if (n == 0) { return -3; }
+ NA[n] = V[V_JO] as u32;
+ NB[n] = V[V_JL] as u32;
+ V[V_JR] = n as u64;
+ return r;
+ }
+ if (b == 45 or is_digit(b)) {
+ const r : i64 = jnumber(s, p, hi);
+ if (r < 0) { return r; }
+ const n : usize = alloc_node(K_NUM);
+ if (n == 0) { return -3; }
+ NA[n] = p as u32;
+ NB[n] = r as u32;
+ V[V_JR] = n as u64;
+ return r;
+ }
+ if (b == 116) {
+ if (!match_in(s, p, hi, "true")) { return -1; }
+ const n : usize = alloc_node(K_TRUE);
+ if (n == 0) { return -3; }
+ V[V_JR] = n as u64;
+ return (p + 4) as i64;
+ }
+ if (b == 102) {
+ if (!match_in(s, p, hi, "false")) { return -1; }
+ const n : usize = alloc_node(K_FALSE);
+ if (n == 0) { return -3; }
+ V[V_JR] = n as u64;
+ return (p + 5) as i64;
+ }
+ if (b == 110) {
+ if (!match_in(s, p, hi, "null")) { return -1; }
+ const n : usize = alloc_node(K_NULL);
+ if (n == 0) { return -3; }
+ V[V_JR] = n as u64;
+ return (p + 4) as i64;
+ }
+ if (b != 91 and b != 123) { return -1; }
+ if (depth >= 512) { return -3; }
+ const isobj : bool = b == 123;
+ var kind : u8 = K_ARR;
+ var close : u8 = 93;
+ if (isobj) {
+ kind = K_OBJ;
+ close = 125;
+ }
+ const n : usize = alloc_node(kind);
+ if (n == 0) { return -3; }
+ var q : usize = jws(s, p + 1, hi);
+ if (q < hi and s[q] == close) {
+ V[V_JR] = n as u64;
+ return (q + 1) as i64;
+ }
+ var last : usize = 0;
+ var more : bool = true;
+ while (more) {
+ var ko : u64 = 0;
+ var kl : u64 = 0;
+ if (isobj) {
+ if (q >= hi or s[q] != 34) { return -1; }
+ const kr : i64 = jstring(s, q, hi);
+ if (kr < 0) { return kr; }
+ ko = V[V_JO];
+ kl = V[V_JL];
+ q = jws(s, kr as usize, hi);
+ if (q >= hi or s[q] != 58) { return -1; }
+ q = jws(s, q + 1, hi);
+ }
+ const vr : i64 = jvalue(s, q, hi, depth + 1);
+ if (vr < 0) { return vr; }
+ const c : usize = V[V_JR] as usize;
+ KO[c] = ko as u32;
+ KL[c] = kl as u32;
+ if (last == 0) { NA[n] = c as u32; } else { NX[last] = c as u32; }
+ last = c;
+ NB[n] = NB[n] + 1;
+ q = jws(s, vr as usize, hi);
+ if (q >= hi) { return -1; }
+ if (s[q] == 44) {
+ q = jws(s, q + 1, hi);
+ } else {
+ if (s[q] != close) { return -1; }
+ more = false;
+ }
+ }
+ V[V_JR] = n as u64;
+ return (q + 1) as i64;
+}
+
+/// JSON.parse over s[lo..hi): 0 ok (root in V_JR), 1 syntax error, -1 a table limit.
+pub fn json_top(s: []const u8, lo: usize, hi: usize) -> i32 {
+ V[V_PN] = 0;
+ V[V_NN] = 0;
+ V[V_ENT] = 0;
+ V[V_UNSUP] = 0;
+ const r : i64 = jvalue(s, jws(s, lo, hi), hi, 0);
+ if (r == -3) { return -1; }
+ if (r < 0) { return 1; }
+ if (jws(s, r as usize, hi) != hi) { return 1; }
+ return 0;
+}
+
+// ---------------------------------------------------------------- objects as JavaScript sees them
+/// An array index key: "0", or digits without a leading zero, at most 4294967294.
+pub fn intlike(o: u32, l: u32) -> bool {
+ if (l == 0 or l > 10) { return false; }
+ if (l > 1 and POOL[o as usize] == 48) { return false; }
+ var v : u64 = 0;
+ var k : u32 = 0;
+ while (k < l) {
+ const b : u8 = POOL[(o + k) as usize];
+ if (!is_digit(b)) { return false; }
+ v = v * 10 + ((b - 48) as u64);
+ k = k + 1;
+ }
+ return v <= 4294967294;
+}
+
+/// The entries of an object (distinct keys in first-appearance order, the last duplicate's value) or
+/// an array (index keys), built once per node. False over the table limit.
+pub fn ent_build(n: usize) -> bool {
+ if (EBU[n] == 1) { return true; }
+ const start : usize = V[V_ENT] as usize;
+ var cnt : usize = 0;
+ var il : u8 = 0;
+ var c : usize = NA[n] as usize;
+ var k : u64 = 0;
+ while (c != 0) {
+ if (start + cnt >= 131072) { V[V_CAP] = 1; return false; }
+ if (NK[n] == K_ARR) {
+ const o : u64 = V[V_PN];
+ if (!pool_put_u(k)) { V[V_CAP] = 1; return false; }
+ EKO[start + cnt] = o as u32;
+ EKL[start + cnt] = (V[V_PN] - o) as u32;
+ EVN[start + cnt] = c as u32;
+ cnt = cnt + 1;
+ } else {
+ var j : usize = 0;
+ var found : bool = false;
+ while (j < cnt and !found) {
+ if (pool_eq(EKO[start + j], EKL[start + j], KO[c], KL[c])) {
+ EVN[start + j] = c as u32;
+ found = true;
+ }
+ j = j + 1;
+ }
+ if (!found) {
+ if (intlike(KO[c], KL[c])) { il = 1; }
+ EKO[start + cnt] = KO[c];
+ EKL[start + cnt] = KL[c];
+ EVN[start + cnt] = c as u32;
+ cnt = cnt + 1;
+ }
+ }
+ c = NX[c] as usize;
+ k = k + 1;
+ }
+ V[V_ENT] = (start + cnt) as u64;
+ EST[n] = start as u32;
+ ECN[n] = cnt as u32;
+ EIL[n] = il;
+ EBU[n] = 1;
+ return true;
+}
+
+/// obj.key for an object node: the last member with that key, or 0.
+pub fn obj_get(n: usize, w: []const u8) -> usize {
+ if (n == 0 or NK[n] != K_OBJ) { return 0; }
+ var c : usize = NA[n] as usize;
+ var r : usize = 0;
+ while (c != 0) {
+ if (pool_is(KO[c], KL[c], w)) { r = c; }
+ c = NX[c] as usize;
+ }
+ return r;
+}
+
+/// byTypeOf: the map node (num_cells_by_type, else cells) of a module node, or 0. V_BTK says which.
+pub fn by_type_of(m: usize) -> usize {
+ if (m == 0 or NK[m] != K_OBJ) { return 0; }
+ const a : usize = obj_get(m, "num_cells_by_type");
+ if (a != 0 and (NK[a] == K_ARR or NK[a] == K_OBJ)) {
+ V[V_BTK] = 0;
+ return a;
+ }
+ const b : usize = obj_get(m, "cells");
+ if (b != 0 and (NK[b] == K_ARR or NK[b] == K_OBJ)) {
+ V[V_BTK] = 1;
+ return b;
+ }
+ return 0;
+}
+
+/// Names that JavaScript finds on every plain object.
+pub fn proto_name(o: u32, l: u32) -> bool {
+ if (pool_is(o, l, "constructor")) { return true; }
+ if (pool_is(o, l, "__proto__")) { return true; }
+ if (pool_is(o, l, "toString")) { return true; }
+ if (pool_is(o, l, "valueOf")) { return true; }
+ if (pool_is(o, l, "hasOwnProperty")) { return true; }
+ if (pool_is(o, l, "isPrototypeOf")) { return true; }
+ if (pool_is(o, l, "propertyIsEnumerable")) { return true; }
+ if (pool_is(o, l, "toLocaleString")) { return true; }
+ if (pool_is(o, l, "__defineGetter__")) { return true; }
+ if (pool_is(o, l, "__defineSetter__")) { return true; }
+ if (pool_is(o, l, "__lookupGetter__")) { return true; }
+ if (pool_is(o, l, "__lookupSetter__")) { return true; }
+ return false;
+}
+
+// ---------------------------------------------------------------- numbers
+/// The value of a JSON number s[lo..hi) when it is exactly rounded here (see the header), else flags V_UNSUP.
+pub fn num_val(s: []const u8, lo: usize, hi: usize) -> f64 {
+ var p : usize = lo;
+ var neg : bool = false;
+ if (s[p] == 45) {
+ neg = true;
+ p = p + 1;
+ }
+ var m : u64 = 0;
+ var nd : u32 = 0;
+ var e10 : i64 = 0;
+ var bad : bool = false;
+ while (p < hi and is_digit(s[p])) {
+ const d : u64 = (s[p] - 48) as u64;
+ if (m == 0 and d == 0) {
+ m = 0;
+ } else {
+ if (nd < 19) {
+ m = m * 10 + d;
+ nd = nd + 1;
+ } else {
+ bad = true;
+ }
+ }
+ p = p + 1;
+ }
+ if (p < hi and s[p] == 46) {
+ p = p + 1;
+ while (p < hi and is_digit(s[p])) {
+ const d : u64 = (s[p] - 48) as u64;
+ if (m == 0 and d == 0) {
+ m = 0;
+ } else {
+ if (nd < 19) {
+ m = m * 10 + d;
+ nd = nd + 1;
+ } else {
+ bad = true;
+ }
+ }
+ e10 = e10 - 1;
+ p = p + 1;
+ }
+ }
+ if (p < hi and (s[p] == 101 or s[p] == 69)) {
+ p = p + 1;
+ var eneg : bool = false;
+ if (p < hi and (s[p] == 43 or s[p] == 45)) {
+ eneg = s[p] == 45;
+ p = p + 1;
+ }
+ var ev : i64 = 0;
+ while (p < hi and is_digit(s[p])) {
+ if (ev < 100000) { ev = ev * 10 + ((s[p] - 48) as i64); }
+ p = p + 1;
+ }
+ if (eneg) { e10 = e10 - ev; } else { e10 = e10 + ev; }
+ }
+ if (m == 0) { return 0.0; }
+ if (bad) {
+ V[V_UNSUP] = 1;
+ return 0.0;
+ }
+ while (m % 10 == 0) {
+ m = m / 10;
+ e10 = e10 + 1;
+ }
+ if (m > 9007199254740992 or e10 > 22 or e10 < -22) {
+ V[V_UNSUP] = 1;
+ return 0.0;
+ }
+ var v : f64 = m as f64;
+ if (e10 > 0) { v = v * P10[e10 as usize]; }
+ if (e10 < 0) { v = v / P10[(0 - e10) as usize]; }
+ if (neg) { return 0.0 - v; }
+ return v;
+}
+
+/// Number(value) for the kinds this port carries; a string, array or object flags V_UNSUP.
+pub fn conv(s: []const u8, n: usize) -> f64 {
+ const k : u8 = NK[n];
+ if (k == K_NULL or k == K_FALSE) { return 0.0; }
+ if (k == K_TRUE) { return 1.0; }
+ if (k == K_NUM) { return num_val(s, NA[n] as usize, NB[n] as usize); }
+ V[V_UNSUP] = 1;
+ return 0.0;
+}
+
+/// Number(n) || 0
+pub fn num_or_zero(v: f64) -> f64 {
+ if (v != v or v == 0.0) { return 0.0; }
+ return v;
+}
+
+// ---------------------------------------------------------------- the analysis
+pub fn find_module(ms: usize, mc: usize, o: u32, l: u32) -> i64 {
+ var j : usize = 0;
+ while (j < mc) {
+ if (pool_eq(bare_off(EKO[ms + j], EKL[ms + j]), bare_len(EKO[ms + j], EKL[ms + j]), o, l)) { return j as i64; }
+ j = j + 1;
+ }
+ return -1;
+}
+
+/// out[ct] = (out[ct] ?? 0) + x in the table of this level.
+pub fn lvl_add(level: usize, o: u32, l: u32, x: f64) -> bool {
+ const base : usize = level * 512;
+ const cnt : usize = LCN[level] as usize;
+ var j : usize = 0;
+ while (j < cnt) {
+ if (pool_eq(LKO[base + j], LKL[base + j], o, l)) {
+ LVL[base + j] = LVL[base + j] + x;
+ return true;
+ }
+ j = j + 1;
+ }
+ if (cnt >= 512) { return false; }
+ if (intlike(o, l)) { V[V_UNSUP] = 1; }
+ LKO[base + cnt] = o;
+ LKL[base + cnt] = l;
+ LVL[base + cnt] = 0.0 + x;
+ LCN[level] = (cnt + 1) as u32;
+ return true;
+}
+
+/// flatten(modules, name, seen) for the module at entry mi, into the table of `level`. 0 ok, -1 limit.
+pub fn flat(s: []const u8, level: usize, mi: usize, ms: usize, mc: usize) -> i32 {
+ if (level >= 32) { V[V_CAP] = 1; return -1; }
+ LCN[level] = 0;
+ const o : u32 = bare_off(EKO[mi], EKL[mi]);
+ const l : u32 = bare_len(EKO[mi], EKL[mi]);
+ const mp : usize = by_type_of(EVN[mi] as usize);
+ if (mp == 0) { return 0; }
+ var k : usize = 0;
+ while (k < level) {
+ if (pool_eq(SEO[k], SEL[k], o, l)) { return 0; }
+ k = k + 1;
+ }
+ SEO[level] = o;
+ SEL[level] = l;
+ if (!ent_build(mp)) { return -1; }
+ if (EIL[mp] == 1) { V[V_UNSUP] = 1; }
+ const es : usize = EST[mp] as usize;
+ const ec : usize = ECN[mp] as usize;
+ var i : usize = 0;
+ while (i < ec) {
+ const to : u32 = EKO[es + i];
+ const tl : u32 = EKL[es + i];
+ const vn : usize = EVN[es + i] as usize;
+ if (proto_name(to, tl)) { V[V_UNSUP] = 1; }
+ const j : i64 = find_module(ms, mc, bare_off(to, tl), bare_len(to, tl));
+ if (j >= 0) {
+ const r : i32 = flat(s, level + 1, ms + (j as usize), ms, mc);
+ if (r < 0) { return r; }
+ var c : usize = 0;
+ while (c < (LCN[level + 1] as usize)) {
+ const n : f64 = conv(s, vn);
+ const x : f64 = n * LVL[(level + 1) * 512 + c];
+ if (!lvl_add(level, LKO[(level + 1) * 512 + c], LKL[(level + 1) * 512 + c], x)) { V[V_CAP] = 1; return -1; }
+ c = c + 1;
+ }
+ } else {
+ const n : f64 = conv(s, vn);
+ if (!lvl_add(level, to, tl, n)) { V[V_CAP] = 1; return -1; }
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+/// The group of a type: the last list that holds it, else hier for a module name, else other.
+pub fn group_of(o: u32, l: u32, lists: []const u8, ms: usize, mc: usize) -> u8 {
+ var best : i32 = -1;
+ var a : usize = 0;
+ while (a < lists.len) {
+ var e : usize = a;
+ while (e < lists.len and lists[e] != 10) { e = e + 1; }
+ if (e > a + 1 and lists[a] >= 48 and lists[a] <= 52 and e - a - 1 == l as usize) {
+ var same : bool = true;
+ var k : usize = 0;
+ while (k < l as usize) {
+ if (lists[a + 1 + k] != POOL[(o as usize) + k]) { same = false; }
+ k = k + 1;
+ }
+ if (same) {
+ const c : i32 = (lists[a] - 48) as i32;
+ if (c > best) { best = c; }
+ }
+ }
+ a = e + 1;
+ }
+ if (best == 4) { return 5; }
+ if (best >= 0) { return best as u8; }
+ if (find_module(ms, mc, bare_off(o, l), bare_len(o, l)) >= 0) { return 6; }
+ return 4;
+}
+
+pub fn fin(code: i32) -> i32 {
+ if (V[V_CAP] == 1) { return -1; }
+ if (V[V_UNSUP] == 1) { return 9; }
+ return code;
+}
+
+/// The part of parseStat after JSON.parse: the root is in V_JR.
+pub fn analyze(s: []const u8, lists: []const u8) -> i32 {
+ const root : usize = V[V_JR] as usize;
+ if (NK[root] != K_OBJ) { return 4; }
+ const modn : usize = obj_get(root, "modules");
+ if (modn == 0 or (NK[modn] != K_ARR and NK[modn] != K_OBJ)) { return 4; }
+ if (!ent_build(modn)) { return -1; }
+ const ms : usize = EST[modn] as usize;
+ const mc : usize = ECN[modn] as usize;
+ if (EIL[modn] == 1) { V[V_UNSUP] = 1; }
+ INF[2] = mc as i64;
+ const cn : usize = obj_get(root, "creator");
+ if (cn != 0 and NK[cn] == K_STR) {
+ INF[4] = 1;
+ INF[5] = NA[cn] as i64;
+ INF[6] = NB[cn] as i64;
+ if (NB[cn] > 0) {
+ const cv : []const u8 = POOL[0 .. 4194304];
+ const co : usize = NA[cn] as usize;
+ const ce : usize = co + (NB[cn] as usize);
+ if (match_in(cv, co, ce, "Yosys")) {
+ var p : usize = co + 5;
+ const w0 : usize = p;
+ var w : usize = ws_len_at(cv, p, ce);
+ while (w > 0) {
+ p = p + w;
+ w = ws_len_at(cv, p, ce);
+ }
+ if (p > w0) {
+ var t : usize = p;
+ while (t < ce and ws_len_at(cv, t, ce) == 0) { t = t + 1; }
+ if (t > p) {
+ INF[7] = 1;
+ INF[8] = p as i64;
+ INF[9] = (t - p) as i64;
+ }
+ }
+ }
+ }
+ }
+ // topOf
+ var j : usize = 0;
+ while (j < mc) {
+ EUS[ms + j] = 0;
+ j = j + 1;
+ }
+ j = 0;
+ while (j < mc) {
+ const mp : usize = by_type_of(EVN[ms + j] as usize);
+ if (mp != 0) {
+ if (!ent_build(mp)) { return -1; }
+ var t : usize = 0;
+ while (t < (ECN[mp] as usize)) {
+ const to : u32 = EKO[(EST[mp] as usize) + t];
+ const tl : u32 = EKL[(EST[mp] as usize) + t];
+ var k : usize = 0;
+ while (k < mc) {
+ if (pool_eq(bare_off(EKO[ms + k], EKL[ms + k]), bare_len(EKO[ms + k], EKL[ms + k]), bare_off(to, tl), bare_len(to, tl))) { EUS[ms + k] = 1; }
+ k = k + 1;
+ }
+ t = t + 1;
+ }
+ }
+ j = j + 1;
+ }
+ var ti : usize = 0;
+ var have_top : bool = false;
+ j = 0;
+ while (j < mc and !have_top) {
+ if (EUS[ms + j] == 0) {
+ ti = j;
+ have_top = true;
+ }
+ j = j + 1;
+ }
+ if (!have_top and mc > 0) {
+ ti = 0;
+ have_top = true;
+ }
+ var top_len : u32 = 0;
+ if (have_top) {
+ INF[10] = 1;
+ INF[2] = mc as i64;
+ INF[12] = bare_len(EKO[ms + ti], EKL[ms + ti]) as i64;
+ INF[11] = bare_off(EKO[ms + ti], EKL[ms + ti]) as i64;
+ top_len = bare_len(EKO[ms + ti], EKL[ms + ti]);
+ }
+ // the map
+ const dn : usize = obj_get(root, "design");
+ const dmap : usize = by_type_of(dn);
+ var nt : usize = 0;
+ if (dmap != 0) {
+ INF[0] = V[V_BTK] as i64;
+ INF[1] = 0;
+ if (!ent_build(dmap)) { return -1; }
+ if (EIL[dmap] == 1) { V[V_UNSUP] = 1; }
+ const es : usize = EST[dmap] as usize;
+ var i : usize = 0;
+ while (i < (ECN[dmap] as usize)) {
+ if (nt >= 16384) { V[V_CAP] = 1; return fin(0); }
+ if (proto_name(EKO[es + i], EKL[es + i])) { V[V_UNSUP] = 1; }
+ TYO[nt] = EKO[es + i];
+ TYL[nt] = EKL[es + i];
+ TYN[nt] = num_or_zero(conv(s, EVN[es + i] as usize));
+ nt = nt + 1;
+ i = i + 1;
+ }
+ } else {
+ if (!have_top or top_len == 0) { return fin(5); }
+ const bt : usize = by_type_of(EVN[ms + ti] as usize);
+ if (bt == 0) { return fin(5); }
+ INF[0] = V[V_BTK] as i64;
+ INF[1] = 1;
+ const fr : i32 = flat(s, 0, ms + ti, ms, mc);
+ if (fr < 0) { return -1; }
+ var i : usize = 0;
+ while (i < (LCN[0] as usize)) {
+ if (nt >= 16384) { V[V_CAP] = 1; return fin(0); }
+ TYO[nt] = LKO[i];
+ TYL[nt] = LKL[i];
+ TYN[nt] = num_or_zero(LVL[i]);
+ nt = nt + 1;
+ i = i + 1;
+ }
+ }
+ var g : usize = 0;
+ while (g < 8) {
+ GRP[g] = 0.0;
+ g = g + 1;
+ }
+ var i2 : usize = 0;
+ while (i2 < nt) {
+ const gc : u8 = group_of(TYO[i2], TYL[i2], lists, ms, mc);
+ TYG[i2] = gc;
+ GRP[gc as usize] = GRP[gc as usize] + TYN[i2];
+ i2 = i2 + 1;
+ }
+ CELLS[0] = GRP[0] + GRP[1] + GRP[2] + GRP[3] + GRP[4];
+ INF[3] = nt as i64;
+ return fin(0);
+}
+
+/// parseStat. 0 ok, 1 empty, 2 nojson, 3 badjson, 4 nomodules, 5 nocells, 9 not carried, -1 a table limit.
+pub fn stat_parse(text: []const u8, lists: []const u8) -> i32 {
+ var f : usize = 0;
+ while (f < 16) {
+ INF[f] = 0;
+ V[f] = 0;
+ f = f + 1;
+ }
+ P10[0] = 1.0;
+ var e : usize = 1;
+ while (e < 23) {
+ P10[e] = P10[e - 1] * 10.0;
+ e = e + 1;
+ }
+ const tb : i64 = trim_bounds(text);
+ const tl : usize = lo_of(tb);
+ const th : usize = hi_of(tb);
+ if (tl == th) { return 1; }
+ var st : i32 = json_top(text, tl, th);
+ if (st < 0) { return -1; }
+ if (st == 1) {
+ var a : usize = tl;
+ while (a < th and text[a] != 123) { a = a + 1; }
+ var b : usize = th;
+ while (b > tl and text[b - 1] != 125) { b = b - 1; }
+ if (a >= th or b == tl or b - 1 <= a) { return 2; }
+ st = json_top(text, a, b);
+ if (st < 0) { return -1; }
+ if (st == 1) { return 3; }
+ }
+ if (V[V_UNSUP] == 1) { return 9; }
+ return analyze(text, lists);
+}
+
+pub fn st_info(f: u32) -> i64 {
+ return INF[f as usize];
+}
+
+pub fn st_type(i: u32, f: u32) -> i64 {
+ if (f == 0) { return TYO[i as usize] as i64; }
+ if (f == 1) { return TYL[i as usize] as i64; }
+ return TYG[i as usize] as i64;
+}
+
+pub fn st_n(i: u32) -> f64 {
+ return TYN[i as usize];
+}
+
+pub fn st_group(g: u32) -> f64 {
+ return GRP[g as usize];
+}
+
+pub fn st_cells() -> f64 {
+ return CELLS[0];
+}
+
+pub fn st_pool(i: u32) -> u8 {
+ return POOL[i as usize];
+}
+
+// ---------------------------------------------------------------- badgeSvg
+pub fn bput(out: []u8, b: u8) -> bool {
+ const c : usize = V[V_BC] as usize;
+ if (c < out.len) { out[c] = b; }
+ V[V_BC] = V[V_BC] + 1;
+ return true;
+}
+
+pub fn bputs(out: []u8, w: []const u8) -> bool {
+ var k : usize = 0;
+ while (k < w.len) {
+ bput(out, w[k]);
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn bput_u(out: []u8, k0: u64) -> bool {
+ var digits : u64 = 1;
+ var t : u64 = k0;
+ while (t >= 10) {
+ t = t / 10;
+ digits = digits * 10;
+ }
+ var d : u64 = digits;
+ var r : u64 = k0;
+ while (d > 0) {
+ bput(out, (48 + r / d) as u8);
+ r = r % d;
+ d = d / 10;
+ }
+ return true;
+}
+
+/// xml(): & < > " escaped, one pass.
+pub fn xml_put(out: []u8, w: []const u8, lo: usize, hi: usize) -> bool {
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = w[k];
+ if (b == 38) { bputs(out, "&"); }
+ else { if (b == 60) { bputs(out, "<"); }
+ else { if (b == 62) { bputs(out, ">"); }
+ else { if (b == 34) { bputs(out, """); }
+ else { bput(out, b); } } } }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// The UTF-16 length of a UTF-8 string (JS .length).
+pub fn utf16_len(w: []const u8, lo: usize, hi: usize) -> u64 {
+ var n : u64 = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = w[k];
+ if (b < 128 or b >= 192) { n = n + 1; }
+ if (b >= 240) { n = n + 1; }
+ k = k + 1;
+ }
+ return n;
+}
+
+/// fmt(): {0} {1} ... replaced by the arguments in ARB; an index with no argument becomes nothing.
+/// Writes into dst; returns the length, or -1 when dst is too small.
+pub fn fmt_into(dst: []u8, tpl: []const u8, nargs: u32) -> i64 {
+ var n : usize = 0;
+ var k : usize = 0;
+ while (k < tpl.len) {
+ var done : bool = false;
+ if (tpl[k] == 123) {
+ var q : usize = k + 1;
+ var idx : u64 = 0;
+ while (q < tpl.len and is_digit(tpl[q])) {
+ if (idx < 100000) { idx = idx * 10 + ((tpl[q] - 48) as u64); }
+ q = q + 1;
+ }
+ if (q > k + 1 and q < tpl.len and tpl[q] == 125) {
+ if (idx < (nargs as u64)) {
+ var a : usize = 0;
+ while (a < (ARL[idx as usize] as usize)) {
+ if (n >= dst.len) { return -1; }
+ dst[n] = ARB[(ARO[idx as usize] as usize) + a];
+ n = n + 1;
+ a = a + 1;
+ }
+ }
+ k = q + 1;
+ done = true;
+ }
+ }
+ if (!done) {
+ if (n >= dst.len) { return -1; }
+ dst[n] = tpl[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ }
+ return n as i64;
+}
+
+/// Append an argument (bytes) to ARB as argument number i; the cursor is the end of the last one.
+pub fn arg_set(i: usize, w: []const u8, lo: usize, hi: usize) -> bool {
+ var at : usize = 0;
+ if (i > 0) { at = (ARO[i - 1] + ARL[i - 1]) as usize; }
+ if (at + (hi - lo) > 262144) { return false; }
+ ARO[i] = at as u32;
+ ARL[i] = (hi - lo) as u32;
+ var k : usize = lo;
+ while (k < hi) {
+ ARB[at + (k - lo)] = w[k];
+ k = k + 1;
+ }
+ return true;
+}
+
+/// A number as JavaScript's String(x) writes it, into ARB as argument i (the reader's port wrote whole numbers below 1e15 only).
+pub fn arg_num(i: usize, x: f64) -> bool {
+ var at : usize = 0;
+ if (i > 0) { at = (ARO[i - 1] + ARL[i - 1]) as usize; }
+ if (at + 400 > 262144) { return false; }
+ ARO[i] = at as u32;
+ const e : u32 = js_str_num(ARB[0 .. 262144], at as u32, x);
+ if (e == JN_OVER) { return false; }
+ ARL[i] = e - (at as u32);
+ return true;
+}
+
+/// Math.round for a non-negative number: floor(x + 0.5) without the 0.49999999999999994 trap.
+pub fn sc_round(x: f64) -> i64 {
+ const f : i64 = x as i64;
+ if (x - (f as f64) >= 0.5) { return f + 1; }
+ return f;
+}
+
+pub fn badge_svg(label: []const u8, value_tpl: []const u8, title_tpl: []const u8, label_fill: []const u8, value_fill: []const u8, cw: i64, pad: i64, h: i64, font_px: i64, out: []u8) -> i64 {
+ if (!arg_num(0, GRP[0])) { return -2; }
+ if (!arg_num(1, GRP[1])) { return -2; }
+ if (!arg_num(2, GRP[2])) { return -2; }
+ if (!arg_num(3, GRP[3])) { return -2; }
+ const vl : i64 = fmt_into(VAL[0 .. 2048], value_tpl, 4);
+ if (vl < 0) { return -1; }
+ const vlen : usize = vl as usize;
+ // title args: value, top, version
+ var ok : bool = arg_set(0, VAL[0 .. 2048], 0, vlen);
+ if (!ok) { return -1; }
+ var to : usize = 0;
+ var tn : usize = 0;
+ if (INF[10] == 1) {
+ to = INF[11] as usize;
+ tn = INF[12] as usize;
+ }
+ ok = arg_set(1, POOL[0 .. 4194304], to, to + tn);
+ if (!ok) { return -1; }
+ var vo : usize = 0;
+ var vn : usize = 0;
+ if (INF[7] == 1) {
+ vo = INF[8] as usize;
+ vn = INF[9] as usize;
+ }
+ ok = arg_set(2, POOL[0 .. 4194304], vo, vo + vn);
+ if (!ok) { return -1; }
+ const tl : i64 = fmt_into(TIT[0 .. 262144], title_tpl, 3);
+ if (tl < 0) { return -1; }
+ const lw : i64 = (utf16_len(label, 0, label.len) as i64) * cw + 2 * pad;
+ const vw : i64 = (utf16_len(VAL[0 .. 2048], 0, vlen) as i64) * cw + 2 * pad;
+ const w : i64 = lw + vw;
+ const y : i64 = sc_round((h as f64) * 0.7);
+ V[V_BC] = 0;
+ bputs(out, "");
+ xml_put(out, TIT[0 .. 262144], 0, tl as usize);
+ bputs(out, "");
+ xml_put(out, label, 0, label.len);
+ bputs(out, "");
+ xml_put(out, VAL[0 .. 2048], 0, vlen);
+ bputs(out, "");
+ return V[V_BC] as i64;
+}
+
+// ---------------------------------------------------------------- tests
+pub var LST : [256]u8 = [0; 256];
+pub var LSN : usize = 0;
+pub var OUT : [2048]u8 = [0; 2048];
+
+pub fn ls_line(s: []const u8) -> usize {
+ var k : usize = 0;
+ while (k < s.len) {
+ LST[LSN] = s[k];
+ LSN = LSN + 1;
+ k = k + 1;
+ }
+ LST[LSN] = 10;
+ LSN = LSN + 1;
+ return LSN;
+}
+
+pub fn lists_default() -> usize {
+ LSN = 0;
+ ls_line("0LUT1");
+ ls_line("0LUT2");
+ ls_line("0LUT3");
+ ls_line("0LUT4");
+ ls_line("0LUT5");
+ ls_line("0LUT6");
+ ls_line("1FDRE");
+ ls_line("1FDSE");
+ ls_line("1FDCE");
+ ls_line("1FDPE");
+ ls_line("2DSP48E1");
+ ls_line("3RAMB18E1");
+ ls_line("3RAMB36E1");
+ ls_line("4$scopeinfo");
+ return LSN;
+}
+
+pub fn pool_text(o: i64, l: i64, w: []const u8) -> bool {
+ return pool_is(o as u32, l as u32, w);
+}
+
+test the_sample_stat_is_read_from_its_design_block {
+ lists_default();
+ const t : []const u8 = "{ \"creator\": \"Yosys 0.67+post (git sha1 b8e7da6f, Release)\", \"invocation\": \"stat -json \", \"modules\": { \"\\\\lm_head_matvec\": { \"num_wires\": 200, \"num_cells\": 430, \"num_cells_by_type\": { \"BUFG\": 1, \"CARRY4\": 32, \"FDRE\": 149, \"IBUF\": 23, \"INV\": 28, \"LUT2\": 69, \"LUT3\": 3, \"LUT4\": 4, \"LUT5\": 50, \"LUT6\": 12, \"MUXF7\": 3, \"MUXF8\": 1, \"OBUF\": 51, \"RAMB36E1\": 4 } } }, \"design\": { \"num_cells\": 430, \"num_cells_by_type\": { \"BUFG\": 1, \"CARRY4\": 32, \"FDRE\": 149, \"IBUF\": 23, \"INV\": 28, \"LUT2\": 69, \"LUT3\": 3, \"LUT4\": 4, \"LUT5\": 50, \"LUT6\": 12, \"MUXF7\": 3, \"MUXF8\": 1, \"OBUF\": 51, \"RAMB36E1\": 4 } } }";
+ assert stat_parse(t, LST[0 .. LSN]) == 0;
+ assert st_info(1) == 0;
+ assert st_info(0) == 0;
+ assert st_info(2) == 1;
+ assert st_info(3) == 14;
+ assert pool_text(st_info(11), st_info(12), "lm_head_matvec");
+ assert pool_text(st_info(8), st_info(9), "0.67+post");
+ assert st_group(0) == 138.0;
+ assert st_group(1) == 149.0;
+ assert st_group(2) == 0.0;
+ assert st_group(3) == 4.0;
+ assert st_group(4) == 139.0;
+ assert st_cells() == 430.0;
+ assert st_n(5) == 69.0;
+ assert pool_text(st_type(5, 0), st_type(5, 1), "LUT2");
+ assert st_type(5, 2) == 0;
+ assert st_type(13, 2) == 3;
+}
+
+test without_a_design_block_the_top_module_is_expanded {
+ lists_default();
+ const t : []const u8 = "{\"modules\": {\"\\\\sub\": {\"num_cells_by_type\": {\"LUT2\": 5, \"FDRE\": 1}}, \"\\\\top\": {\"num_cells_by_type\": {\"\\\\sub\": 2, \"LUT2\": 3, \"IBUF\": 1}}}}";
+ assert stat_parse(t, LST[0 .. LSN]) == 0;
+ assert st_info(1) == 1;
+ assert st_info(2) == 2;
+ assert pool_text(st_info(11), st_info(12), "top");
+ assert st_info(10) == 1;
+ assert st_info(3) == 3;
+ assert st_n(0) == 13.0;
+ assert pool_text(st_type(0, 0), st_type(0, 1), "LUT2");
+ assert st_n(1) == 2.0;
+ assert st_group(0) == 13.0;
+ assert st_group(1) == 2.0;
+ assert st_group(4) == 1.0;
+ assert st_cells() == 16.0;
+ assert st_info(4) == 0;
+ assert st_info(7) == 0;
+}
+
+test the_five_failures_are_told_apart {
+ lists_default();
+ assert stat_parse(" \t ", LST[0 .. LSN]) == 1;
+ assert stat_parse("hello yosys", LST[0 .. LSN]) == 2;
+ assert stat_parse("{ x }", LST[0 .. LSN]) == 3;
+ assert stat_parse("{\"a\": 1}", LST[0 .. LSN]) == 4;
+ assert stat_parse("[]", LST[0 .. LSN]) == 4;
+ assert stat_parse("null", LST[0 .. LSN]) == 4;
+ assert stat_parse("{\"modules\": null}", LST[0 .. LSN]) == 4;
+ assert stat_parse("{\"modules\": {}}", LST[0 .. LSN]) == 5;
+ assert stat_parse("{\"creator\": \"Yosys 0.9\", \"modules\": {\"\\\\m\": {}}}", LST[0 .. LSN]) == 5;
+ assert st_info(7) == 1;
+ assert pool_text(st_info(11), st_info(12), "m");
+ assert stat_parse("{\"modules\": {\"\": {\"cells\": {\"LUT2\": 1}}}}", LST[0 .. LSN]) == 5;
+}
+
+test json_inside_a_terminal_capture_is_found_by_its_braces {
+ lists_default();
+ const t : []const u8 = "yosys> stat -json {\"modules\": {\"top\": {\"cells\": {\"LUT6\": 7, \"FDRE\": 2}}}} bye";
+ assert stat_parse(t, LST[0 .. LSN]) == 0;
+ assert st_info(0) == 1;
+ assert st_info(1) == 1;
+ assert st_group(0) == 7.0;
+ assert st_group(1) == 2.0;
+ assert stat_parse("x {\"modules\": {\"top\": {\"cells\": {\"LUT6\": 7,}}}} y", LST[0 .. LSN]) == 3;
+ assert stat_parse("} {", LST[0 .. LSN]) == 2;
+}
+
+test strings_escapes_duplicates_and_odd_values_follow_javascript {
+ lists_default();
+ const t : []const u8 = "{\"creator\":\"Yosys x\\u0041y z\",\"modules\":{\"m\":{\"num_cells_by_type\":{\"LUT\\u0032\":1,\"X\":null,\"Y\":true,\"LUT2\":4,\"Z\":-0,\"W\":1.5e1,\"V\":2.50}}}}";
+ assert stat_parse(t, LST[0 .. LSN]) == 0;
+ assert pool_text(st_info(8), st_info(9), "xAy");
+ assert st_info(3) == 6;
+ assert pool_text(st_type(0, 0), st_type(0, 1), "LUT2");
+ assert st_n(0) == 4.0;
+ assert st_n(1) == 0.0;
+ assert st_n(2) == 1.0;
+ assert st_n(3) == 0.0;
+ assert st_n(4) == 15.0;
+ assert st_n(5) == 2.5;
+ assert st_group(4) == 18.5;
+ assert st_group(0) == 4.0;
+}
+
+test what_the_port_does_not_carry_is_refused_not_guessed {
+ lists_default();
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"LUT2\":\"5\"}}}}", LST[0 .. LSN]) == 9;
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"LUT2\":[5]}}}}", LST[0 .. LSN]) == 9;
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"constructor\":5}}}}", LST[0 .. LSN]) == 9;
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"7\":5}}}}", LST[0 .. LSN]) == 9;
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"A\":1e30}}}}", LST[0 .. LSN]) == 9;
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"A\":1}}},\"x\":\"\\ud800\"}", LST[0 .. LSN]) == 9;
+ assert stat_parse("{\"modules\":{\"m\":{\"cells\":{\"A\":1}}},\"x\":1e30}", LST[0 .. LSN]) == 0;
+}
+
+test hierarchy_cycles_and_group_priority_follow_flatten {
+ lists_default();
+ const t : []const u8 = "{\"modules\":{\"\\\\a\":{\"cells\":{\"\\\\b\":1,\"LUT2\":1}},\"\\\\b\":{\"cells\":{\"\\\\a\":1,\"FDRE\":1}}}}";
+ assert stat_parse(t, LST[0 .. LSN]) == 0;
+ assert st_info(3) == 2;
+ assert st_n(0) == 1.0;
+ assert st_n(1) == 1.0;
+ assert st_group(0) == 1.0;
+ assert st_group(1) == 1.0;
+ assert stat_parse("{\"design\":{\"cells\":{\"$scopeinfo\":3,\"m\":2,\"LUT3\":1}},\"modules\":{\"m\":{\"cells\":{}}}}", LST[0 .. LSN]) == 0;
+ assert st_group(5) == 3.0;
+ assert st_group(6) == 2.0;
+ assert st_group(0) == 1.0;
+ assert st_cells() == 1.0;
+}
+
+test the_badge_is_plain_svg_with_the_counts {
+ lists_default();
+ const t : []const u8 = "{\"creator\":\"Yosys 0.67+post (x)\",\"modules\":{\"\\\\t<\":{\"cells\":{\"LUT2\":138,\"FDRE\":149,\"RAMB36E1\":4}}}}";
+ assert stat_parse(t, LST[0 .. LSN]) == 0;
+ const n : i64 = badge_svg("yosys synth", "{0} LUT | {1} FF | {2} DSP | {3} BRAM", "{0} -- top {1}, yosys {2}: x", "#555", "#2e7d4f", 7, 6, 20, 11, OUT[0 .. 2048]);
+ assert n > 300;
+ assert n < 2048;
+ assert OUT[0] == 60;
+ assert OUT[(n - 1) as usize] == 62;
+ assert match_in(OUT[0 .. 2048], 0, 2048, " u32 {
+ S_PASTE_N = 0;
+ S_PASTE_U = 0;
+ S_PASTE_BIG = false;
+ S_CLR_PASTE = false;
+ S_CLR_FILE = false;
+ S_SMP_N = 0;
+ S_SMP_ST = 0;
+ S_HIDDEN = true;
+ S_PRESSED = false;
+ S_HAVE = false;
+ S_COPIED = false;
+ S_OVER = false;
+ S_BADGE_N = 0;
+ S_BURL_N = 0;
+ S_ALT_N = 0;
+ S_EF_N = 0;
+ R_NT = 0;
+ X_N = 0;
+ X_PN = 0;
+ X_MISS = false;
+ set_status_note();
+ return 0;
+}
+
+pub fn w_base(s: []const u8) -> u32 {
+ var k : usize = 0;
+ while (k < s.len and k < 1024) {
+ S_BASE[k] = s[k];
+ k = k + 1;
+ }
+ S_BASE_N = k as u32;
+ return 0;
+}
+
+/// The nine colours of the shared frame, one per line (bg panel line green gold red text muted subtle).
+pub fn w_theme(s: []const u8) -> u32 {
+ var i : usize = 0;
+ var c : u32 = 0;
+ var l : u32 = 0;
+ while (i < s.len and c < 9) {
+ if (s[i] == 10) {
+ S_TH_N[c as usize] = l;
+ c = c + 1;
+ l = 0;
+ } else {
+ if (l < 64) {
+ S_TH[(c * 64 + l) as usize] = s[i];
+ l = l + 1;
+ }
+ }
+ i = i + 1;
+ }
+ if (c < 9) { S_TH_N[c as usize] = l; }
+ return 0;
+}
+
+// --- small writers ------------------------------------------------------------------------------
+
+/// Two texts equal byte for byte (t27c's Zig target does not compare two str array elements with ==).
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+fn copy_into(dst: []u8, at: u32, s: []const u8) -> u32 {
+ var n : u32 = at;
+ var k : usize = 0;
+ while (k < s.len and (n as usize) < dst.len) {
+ dst[n as usize] = s[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+fn arg_text(i: usize, s: []const u8) -> bool {
+ return arg_set(i, s, 0, s.len);
+}
+
+/// fmt(tpl, args...) written HTML-escaped into the page; the arguments are already in ARB.
+fn put_say(dst: []u8, n0: u32, tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_FB[0..], tpl, nargs);
+ if (l < 0) { return OVER; }
+ return text(dst, n0, S_FB[0..(l as usize)]);
+}
+
+/// Scratch for a number written as text.
+pub var S_NUM : [64]u8 = [0; 64];
+
+fn locale_text(x: f64) -> u32 {
+ const e : u32 = js_locale_num(S_NUM[0..], 0, x);
+ if (e == OVER) { return 0; }
+ return e;
+}
+
+/// String(x) for a number, into S_NUM.
+fn plain_text(x: f64) -> u32 {
+ const e : u32 = js_str_num(S_NUM[0..], 0, x);
+ if (e == OVER) { return 0; }
+ return e;
+}
+
+fn put_locale(dst: []u8, n0: u32, x: f64) -> u32 {
+ return js_locale_num(dst, n0, x);
+}
+
+fn status_is(s: []const u8) -> u32 {
+ S_STATUS_N = copy_into(S_STATUS[0..], 0, s);
+ return 0;
+}
+
+fn set_status_note() -> u32 {
+ return status_is(SAY_LOCAL_NOTE);
+}
+
+/// status = fmt(tpl, args...) with the arguments in ARB.
+fn status_fmt(tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_STATUS[0..], tpl, nargs);
+ if (l < 0) {
+ S_STATUS_N = 0;
+ return 0;
+ }
+ S_STATUS_N = l as u32;
+ return 0;
+}
+
+/// refuse(text): the status shows text, the result hides, the sample button is not pressed.
+fn refuse_with(s: []const u8) -> u32 {
+ status_is(s);
+ S_HIDDEN = true;
+ S_PRESSED = false;
+ return 0;
+}
+
+fn refuse_fmt(tpl: []const u8, nargs: u32) -> u32 {
+ status_fmt(tpl, nargs);
+ S_HIDDEN = true;
+ S_PRESSED = false;
+ return 0;
+}
+
+fn refuse_err_read(nm: []const u8) -> u32 {
+ arg_text(0, nm);
+ return refuse_fmt(SAY_ERR_READ, 1);
+}
+
+// --- the group lists the reader needs, from the spec's own constants -----------------------------
+
+fn lists_add(group: u8, w: []const u8) -> u32 {
+ S_LISTS[S_LISTS_N as usize] = group;
+ const n : u32 = copy_into(S_LISTS[0..], S_LISTS_N + 1, w);
+ S_LISTS[n as usize] = 10;
+ S_LISTS_N = n + 1;
+ return S_LISTS_N;
+}
+
+fn build_lists() -> u32 {
+ S_LISTS_N = 0;
+ var i : usize = 0;
+ while (i < K_LUT_TYPES.len) {
+ lists_add(48, K_LUT_TYPES[i]);
+ i = i + 1;
+ }
+ i = 0;
+ while (i < K_FF_TYPES.len) {
+ lists_add(49, K_FF_TYPES[i]);
+ i = i + 1;
+ }
+ i = 0;
+ while (i < K_DSP_TYPES.len) {
+ lists_add(50, K_DSP_TYPES[i]);
+ i = i + 1;
+ }
+ i = 0;
+ while (i < K_BRAM_TYPES.len) {
+ lists_add(51, K_BRAM_TYPES[i]);
+ i = i + 1;
+ }
+ i = 0;
+ while (i < K_INTERNAL_TYPES.len) {
+ lists_add(52, K_INTERNAL_TYPES[i]);
+ i = i + 1;
+ }
+ return S_LISTS_N;
+}
+
+// --- the accepted result ---------------------------------------------------------------------------
+
+/// Keep what the reader just returned: the next read, accepted or not, overwrites the reader's tables.
+fn snapshot() -> u32 {
+ const plen : usize = V[V_PN] as usize;
+ var k : usize = 0;
+ while (k < plen) {
+ R_POOL[k] = POOL[k];
+ k = k + 1;
+ }
+ const nt : usize = INF[3] as usize;
+ var i : usize = 0;
+ while (i < nt) {
+ R_TO[i] = TYO[i];
+ R_TL[i] = TYL[i];
+ R_TG[i] = TYG[i];
+ R_TN[i] = TYN[i];
+ i = i + 1;
+ }
+ R_NT = nt as u32;
+ i = 0;
+ while (i < 8) {
+ R_GRP[i] = GRP[i];
+ i = i + 1;
+ }
+ R_CELLS = CELLS[0];
+ i = 0;
+ while (i < 16) {
+ R_INF[i] = INF[i];
+ i = i + 1;
+ }
+ return 0;
+}
+
+fn r_top_set() -> bool {
+ return R_INF[10] == 1 and R_INF[12] > 0;
+}
+
+fn r_ver_set() -> bool {
+ return R_INF[7] == 1 and R_INF[9] > 0;
+}
+
+/// The badge text: statparse.js badgeSvg(r, K), written by the reader into S_BADGE.
+fn make_badge() -> u32 {
+ S_BADGE_N = 0;
+ const n : i64 = badge_svg(SAY_BADGE_LABEL, SAY_BADGE_VALUE, SAY_BADGE_TITLE, K_BADGE_LABEL_FILL, K_BADGE_VALUE_FILL, K_BADGE_CHAR_W as i64, K_BADGE_PAD as i64, K_BADGE_H as i64, K_BADGE_FONT_PX as i64, S_BADGE[0..]);
+ if (n > 0 and n <= 1048576) { S_BADGE_N = n as u32; }
+ // img.alt = fmt(SAY_BADGE_VALUE, lut, ff, dsp, bram)
+ arg_num(0, GRP[0]);
+ arg_num(1, GRP[1]);
+ arg_num(2, GRP[2]);
+ arg_num(3, GRP[3]);
+ const l : i64 = fmt_into(S_ALT[0..], SAY_BADGE_VALUE, 4);
+ S_ALT_N = 0;
+ if (l >= 0) { S_ALT_N = l as u32; }
+ return 0;
+}
+
+/// show(r): the result opens, the card is drawn (w_draw), the facts and the badge follow.
+fn show_result() -> u32 {
+ make_badge();
+ snapshot();
+ S_HAVE = true;
+ S_HIDDEN = false;
+ S_COPIED = false;
+ S_EF[S_EF_N as usize] = 1;
+ S_EF_N = S_EF_N + 1;
+ return 0;
+}
+
+/// showSample(): read the bytes of sample.stat.json.
+fn show_sample() -> u32 {
+ if (S_SMP_ST == 0) { return 0; }
+ build_lists();
+ var code : i32 = 2;
+ if (S_SMP_ST == 1) { code = stat_parse(S_SMP[0..(S_SMP_N as usize)], S_LISTS[0..(S_LISTS_N as usize)]); }
+ if (S_SMP_ST != 1 or code != 0) {
+ status_is(SAY_ERR_LOAD);
+ return 0;
+ }
+ S_PRESSED = true;
+ set_status_note();
+ show_result();
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ S_EF_N = 0;
+ if (s.len > (W_SMP_CAP as usize)) {
+ S_SMP_ST = 2;
+ } else {
+ var k : usize = 0;
+ while (k < s.len) {
+ S_SMP[k] = s[k];
+ k = k + 1;
+ }
+ S_SMP_N = s.len as u32;
+ S_SMP_ST = 1;
+ }
+ show_sample();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ S_EF_N = 0;
+ S_SMP_ST = 2;
+ status_is(SAY_ERR_LOAD);
+ return 0;
+}
+
+// --- reading a text ---------------------------------------------------------------------------------
+
+/// The code of the reader's answer as the words tool.js says it: SAY_ERROR_TEXT in the order of SAY_ERROR_IDS.
+fn error_text(code: i32) -> []const u8 {
+ return SAY_ERROR_TEXT[((code - 1) as usize)];
+}
+
+/// readText(text, name): name is in S_NM. u16 is text.length of the JavaScript string.
+fn read_text(t: []const u8, u16: u64, plain_name: []const u8) -> u32 {
+ if (u16 > (K_MAX_PASTE_CHARS as u64)) {
+ const l1 : u32 = locale_text(u16 as f64);
+ arg_set(0, S_NUM[0..], 0, l1 as usize);
+ const l2 : u32 = locale_text(K_MAX_PASTE_CHARS as f64);
+ arg_set(1, S_NUM[0..], 0, l2 as usize);
+ return refuse_fmt(SAY_TOO_BIG, 2);
+ }
+ build_lists();
+ const code : i32 = stat_parse(t, S_LISTS[0..(S_LISTS_N as usize)]);
+ if (code == 0) {
+ S_PRESSED = false;
+ var n : u32 = copy_into(S_STATUS[0..], 0, S_NM[0..(S_NM_N as usize)]);
+ n = copy_into(S_STATUS[0..], n, ". ");
+ n = copy_into(S_STATUS[0..], n, SAY_LOCAL_NOTE);
+ S_STATUS_N = n;
+ show_result();
+ return 0;
+ }
+ if (code >= 1 and code <= 5) { return refuse_with(error_text(code)); }
+ return refuse_err_read(plain_name);
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ S_EF_N = 0;
+ if (i != 1) { return 0; }
+ S_PASTE_U = utf16_len(s, 0, s.len);
+ S_PASTE_BIG = s.len > (W_PASTE_CAP as usize);
+ var k : usize = 0;
+ while (k < s.len and k < (W_PASTE_CAP as usize)) {
+ S_PASTE[k] = s[k];
+ k = k + 1;
+ }
+ S_PASTE_N = k as u32;
+ return 0;
+}
+
+/// The "Make the card" button: readText(paste.value, SAY_PASTED).
+fn read_pasted() -> u32 {
+ S_NM_N = copy_into(S_NM[0..], 0, SAY_PASTED);
+ if (S_PASTE_BIG and S_PASTE_U <= (K_MAX_PASTE_CHARS as u64)) { return refuse_err_read(SAY_PASTED); }
+ return read_text(S_PASTE[0..(S_PASTE_N as usize)], S_PASTE_U, SAY_PASTED);
+}
+
+/// A file picked or dropped: readFile(file).
+pub fn w_file(name: []const u8, size: f64, data: []const u8, st: u32) -> u32 {
+ S_EF_N = 0;
+ // picked = fmt(SAY_PICKED, file.name, num(file.size))
+ arg_set(0, name, 0, name.len);
+ const sl : u32 = locale_text(size);
+ arg_set(1, S_NUM[0..], 0, sl as usize);
+ status_fmt(SAY_PICKED, 2);
+ S_NM_N = copy_into(S_NM[0..], 0, S_STATUS[0..(S_STATUS_N as usize)]);
+ S_FN_N = copy_into(S_FN[0..], 0, name);
+ if (size > (K_MAX_PASTE_CHARS as f64)) {
+ const l1 : u32 = locale_text(size);
+ arg_set(0, S_NUM[0..], 0, l1 as usize);
+ const l2 : u32 = locale_text(K_MAX_PASTE_CHARS as f64);
+ arg_set(1, S_NUM[0..], 0, l2 as usize);
+ return refuse_fmt(SAY_TOO_BIG, 2);
+ }
+ if (st != 0 or size > 2097152.0) { return refuse_err_read(S_FN[0..(S_FN_N as usize)]); }
+ return read_text(data, utf16_len(data, 0, data.len), S_FN[0..(S_FN_N as usize)]);
+}
+
+pub fn w_drag(kind_of: u32) -> u32 {
+ S_EF_N = 0;
+ const was : bool = S_OVER;
+ S_OVER = kind_of == 0;
+ if (was != S_OVER) { return 1; }
+ return 0;
+}
+
+// --- the card text: widths, from the host ------------------------------------------------------------
+
+fn x_same(i: u32, px: u32, bold: u32, s: []const u8) -> bool {
+ if (X_PX[i as usize] != px or X_BOLD[i as usize] != bold) { return false; }
+ if ((X_LEN[i as usize] as usize) != s.len) { return false; }
+ var k : usize = 0;
+ const o : usize = X_OFF[i as usize] as usize;
+ while (k < s.len) {
+ if (X_POOL[o + k] != s[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn x_find(px: u32, bold: u32, s: []const u8) -> i32 {
+ var i : u32 = 0;
+ while (i < X_N) {
+ if (x_same(i, px, bold, s)) { return i as i32; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+/// Ask the host for a width (once).
+fn x_ask(px: u32, bold: u32, s: []const u8) -> u32 {
+ if (x_find(px, bold, s) >= 0) { return 0; }
+ if (X_N >= 2048 or (X_PN as usize) + s.len > 262144) {
+ X_N = 0;
+ X_PN = 0;
+ }
+ if (s.len > 65536) { return 0; }
+ const i : usize = X_N as usize;
+ X_OFF[i] = X_PN;
+ X_LEN[i] = s.len as u32;
+ X_PX[i] = px;
+ X_BOLD[i] = bold;
+ X_ST[i] = 0;
+ X_W[i] = 0.0;
+ var k : usize = 0;
+ while (k < s.len) {
+ X_POOL[(X_PN as usize) + k] = s[k];
+ k = k + 1;
+ }
+ X_PN = X_PN + (s.len as u32);
+ X_N = X_N + 1;
+ return 1;
+}
+
+/// ctx.measureText(s).width in the font (px, bold); 0.0 and a request while the host has not answered.
+fn x_width(px: u32, bold: u32, s: []const u8) -> f64 {
+ const f : i32 = x_find(px, bold, s);
+ if (f >= 0 and X_ST[f as usize] == 2) {
+ X_MISS = false;
+ return X_W[f as usize];
+ }
+ X_MISS = true;
+ x_ask(px, bold, s);
+ return 0.0;
+}
+
+/// The font string of (px, bold) into S_FT.
+fn ft_make(px: u32, bold: u32) -> u32 {
+ var n : u32 = 0;
+ if (bold != 0) { n = copy_into(S_FT[0..], n, "bold "); }
+ var d : [12]u8 = [0; 12];
+ const dl : u32 = dec_to(d[0..], px as u64);
+ n = copy_into(S_FT[0..], n, d[0..(dl as usize)]);
+ n = copy_into(S_FT[0..], n, "px ");
+ n = copy_into(S_FT[0..], n, W_MONO);
+ S_FT_N = n;
+ return n;
+}
+
+fn put_u32le(dst: []u8, at: u32, v: u32) -> u32 {
+ dst[at as usize] = (v % 256) as u8;
+ dst[(at + 1) as usize] = ((v / 256) % 256) as u8;
+ dst[(at + 2) as usize] = ((v / 65536) % 256) as u8;
+ dst[(at + 3) as usize] = ((v / 16777216) % 256) as u8;
+ return at + 4;
+}
+
+/// Requests for the host: records of (u32 font length, u32 text length, font bytes, text bytes). The count.
+pub fn w_text_need(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ var c : u32 = 0;
+ var i : u32 = 0;
+ while (i < X_N) {
+ if (X_ST[i as usize] == 0) {
+ ft_make(X_PX[i as usize], X_BOLD[i as usize]);
+ const need : u32 = 8 + S_FT_N + X_LEN[i as usize];
+ if ((n as usize) + (need as usize) > dst.len) { return c; }
+ n = put_u32le(dst, n, S_FT_N);
+ n = put_u32le(dst, n, X_LEN[i as usize]);
+ n = copy_into(dst, n, S_FT[0..(S_FT_N as usize)]);
+ var k : u32 = 0;
+ while (k < X_LEN[i as usize]) {
+ dst[(n + k) as usize] = X_POOL[(X_OFF[i as usize] + k) as usize];
+ k = k + 1;
+ }
+ n = n + X_LEN[i as usize];
+ X_ST[i as usize] = 1;
+ c = c + 1;
+ }
+ i = i + 1;
+ }
+ return c;
+}
+
+/// The widths of the requests last sent, in order.
+pub fn w_text_give(src: []const f64, n: u32) -> u32 {
+ var i : u32 = 0;
+ var k : u32 = 0;
+ while (i < X_N and k < n and (k as usize) < src.len) {
+ if (X_ST[i as usize] == 1) {
+ X_W[i as usize] = src[k as usize];
+ X_ST[i as usize] = 2;
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+// --- sorting, slugs, the lists the card and the table use ---------------------------------------------
+
+/// S_IDX[0..cnt] ordered by count, largest first; equal counts keep their place (Array.prototype.sort is stable).
+fn sort_idx(cnt: u32) -> u32 {
+ var w : u32 = 1;
+ while (w < cnt) {
+ var lo : u32 = 0;
+ while (lo < cnt) {
+ var mid : u32 = lo + w;
+ if (mid > cnt) { mid = cnt; }
+ var hi : u32 = lo + w + w;
+ if (hi > cnt) { hi = cnt; }
+ var i : u32 = lo;
+ var j : u32 = mid;
+ var k : u32 = lo;
+ while (i < mid and j < hi) {
+ if (R_TN[S_IDX[j as usize] as usize] > R_TN[S_IDX[i as usize] as usize]) {
+ S_TMP[k as usize] = S_IDX[j as usize];
+ j = j + 1;
+ } else {
+ S_TMP[k as usize] = S_IDX[i as usize];
+ i = i + 1;
+ }
+ k = k + 1;
+ }
+ while (i < mid) {
+ S_TMP[k as usize] = S_IDX[i as usize];
+ i = i + 1;
+ k = k + 1;
+ }
+ while (j < hi) {
+ S_TMP[k as usize] = S_IDX[j as usize];
+ j = j + 1;
+ k = k + 1;
+ }
+ lo = lo + w + w;
+ }
+ var c : u32 = 0;
+ while (c < cnt) {
+ S_IDX[c as usize] = S_TMP[c as usize];
+ c = c + 1;
+ }
+ w = w * 2;
+ }
+ return cnt;
+}
+
+/// The types of group g, in the reader's order, into S_IDX; the count.
+fn collect(g: u8) -> u32 {
+ var c : u32 = 0;
+ var t : u32 = 0;
+ while (t < R_NT) {
+ if (R_TG[t as usize] == g) {
+ S_IDX[c as usize] = t;
+ c = c + 1;
+ }
+ t = t + 1;
+ }
+ return c;
+}
+
+pub var S_SLUG : [64]u8 = [0; 64];
+
+fn slug_ok(b: u8) -> bool {
+ if (b >= 65 and b <= 90) { return true; }
+ if (b >= 97 and b <= 122) { return true; }
+ if (b >= 48 and b <= 57) { return true; }
+ return b == 95 or b == 46 or b == 45;
+}
+
+/// slug(result.top): runs of other characters become one _, at most 60 characters, "design" when empty.
+fn make_slug() -> u32 {
+ var n : u32 = 0;
+ var run : bool = false;
+ if (r_top_set()) {
+ const lo : usize = R_INF[11] as usize;
+ const hi : usize = lo + (R_INF[12] as usize);
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = R_POOL[k];
+ if (slug_ok(b)) {
+ if (n < 60) {
+ S_SLUG[n as usize] = b;
+ n = n + 1;
+ }
+ run = false;
+ } else {
+ if (!run and n < 60) {
+ S_SLUG[n as usize] = 95;
+ n = n + 1;
+ }
+ run = true;
+ }
+ k = k + 1;
+ }
+ }
+ if (n == 0) { n = copy_into(S_SLUG[0..], 0, W_SLUG_EMPTY); }
+ return n;
+}
+
+/// S_SAVE = fmt(tpl, slug)
+fn save_name(tpl: []const u8) -> u32 {
+ const sl : u32 = make_slug();
+ arg_set(0, S_SLUG[0..(sl as usize)], 0, sl as usize);
+ const l : i64 = fmt_into(S_SAVE[0..], tpl, 1);
+ S_SAVE_N = 0;
+ if (l >= 0) { S_SAVE_N = l as u32; }
+ return S_SAVE_N;
+}
+
+fn push_effect(k: u8) -> u32 {
+ if (S_EF_N < 8) {
+ S_EF[S_EF_N as usize] = k;
+ S_EF_N = S_EF_N + 1;
+ }
+ return 0;
+}
+
+fn clear_own() -> u32 {
+ S_PASTE_N = 0;
+ S_PASTE_U = 0;
+ S_PASTE_BIG = false;
+ S_CLR_PASTE = true;
+ S_CLR_FILE = true;
+ set_status_note();
+ show_sample();
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ S_EF_N = 0;
+ if (i == 0) { show_sample(); }
+ if (i == 1) { read_pasted(); }
+ if (i == 2) { clear_own(); }
+ if (i == 3 and S_HAVE) {
+ save_name(SAY_PNG_FILE);
+ push_effect(4);
+ }
+ if (i == 4 and S_BADGE_N > 0) {
+ S_COPIED = true;
+ push_effect(3);
+ push_effect(2);
+ }
+ if (i == 5 and S_BADGE_N > 0) {
+ save_name(SAY_BADGE_FILE);
+ push_effect(5);
+ }
+ return 0;
+}
+
+pub fn w_timer(id: u32) -> u32 {
+ S_EF_N = 0;
+ S_COPIED = false;
+ return 0;
+}
+
+pub fn w_blob(slot: u32, url: []const u8) -> u32 {
+ S_EF_N = 0;
+ S_BURL_N = copy_into(S_BURL[0..], 0, url);
+ return 0;
+}
+
+/// Effects after the last event, in a fixed order; the list is consumed when the host reads its end.
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (k >= S_EF_N) {
+ S_EF_N = 0;
+ return 0;
+ }
+ const e : u8 = S_EF[k as usize];
+ if (e == 1) {
+ dst[0] = 98;
+ dst[1] = 0;
+ var n : u32 = copy_into(dst, 2, W_BADGE_MIME);
+ dst[n as usize] = 10;
+ n = copy_into(dst, n + 1, S_BADGE[0..(S_BADGE_N as usize)]);
+ return n;
+ }
+ if (e == 2) {
+ dst[0] = 116;
+ dst[1] = 0;
+ return copy_into(dst, 2, W_COPY_MS);
+ }
+ if (e == 3) {
+ dst[0] = 107;
+ return copy_into(dst, 1, S_BADGE[0..(S_BADGE_N as usize)]);
+ }
+ if (e == 4) {
+ dst[0] = 112;
+ dst[1] = 0;
+ return copy_into(dst, 2, S_SAVE[0..(S_SAVE_N as usize)]);
+ }
+ dst[0] = 100;
+ dst[1] = 0;
+ var m : u32 = copy_into(dst, 2, S_SAVE[0..(S_SAVE_N as usize)]);
+ dst[m as usize] = 10;
+ m = copy_into(dst, m + 1, W_BADGE_MIME);
+ dst[m as usize] = 10;
+ return copy_into(dst, m + 1, S_BADGE[0..(S_BADGE_N as usize)]);
+}
+
+pub fn w_value(i: u32, dst: []u8) -> u32 {
+ if (i == 0 and S_CLR_FILE) {
+ S_CLR_FILE = false;
+ return 0;
+ }
+ if (i == 1 and S_CLR_PASTE) {
+ S_CLR_PASTE = false;
+ return 0;
+ }
+ if (i == 2) {
+ if ((S_BADGE_N as usize) > dst.len) { return 4294967295; }
+ return copy_into(dst, 0, S_BADGE[0..(S_BADGE_N as usize)]);
+ }
+ return 4294967295;
+}
+
+// --- the page ---------------------------------------------------------------------------------------------
+
+pub const W_ACCEPT : str = ".json,.txt,application/json,text/plain";
+pub const W_ID_OTHER : str = "other";
+
+fn group_id(g: u8) -> []const u8 {
+ if (g < 4) { return SAY_GROUP_IDS[g as usize]; }
+ return W_ID_OTHER;
+}
+
+fn group_word(g: u8) -> []const u8 {
+ if (g < 4) { return SAY_GROUP_NAMES[g as usize]; }
+ return SAY_OTHER;
+}
+
+fn button(dst: []u8, n0: u32, label: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = text(dst, n, label);
+ return raw(dst, n, "");
+}
+
+/// "type n, type n": the types of one group, into S_FIT (ArgumentList of fmt for SAY_INTERNAL / SAY_HIER).
+fn list_of(g: u8) -> u32 {
+ var n : u32 = 0;
+ var t : u32 = 0;
+ var first : bool = true;
+ while (t < R_NT) {
+ if (R_TG[t as usize] == g) {
+ if (!first) { n = copy_into(S_FIT[0..], n, ", "); }
+ first = false;
+ n = copy_into(S_FIT[0..], n, R_POOL[(R_TO[t as usize] as usize)..((R_TO[t as usize] + R_TL[t as usize]) as usize)]);
+ n = copy_into(S_FIT[0..], n, " ");
+ const l : u32 = locale_text(R_TN[t as usize]);
+ n = copy_into(S_FIT[0..], n, S_NUM[0..(l as usize)]);
+ }
+ t = t + 1;
+ }
+ S_FIT_N = n;
+ return n;
+}
+
+fn facts_html(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ if (!S_HAVE) { return raw(dst, n, "
"); }
+ // fmt(SAY_SHAPE, r.key, SAY_FROM[...], r.modules)
+ var kn : u32 = 0;
+ arg_text(0, W_KEY_NAMES[R_INF[0] as usize]);
+ arg_text(1, SAY_FROM[R_INF[1] as usize]);
+ const ml : u32 = plain_text(R_INF[2] as f64);
+ arg_set(2, S_NUM[0..], 0, ml as usize);
+ n = raw(dst, n, "
");
+ n = put_say(dst, n, SAY_SHAPE, 3);
+ n = raw(dst, n, "
");
+ if (R_GRP[0] + R_GRP[1] + R_GRP[2] + R_GRP[3] == 0.0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NO_XILINX);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ var h : usize = 0;
+ while (h < SAY_TABLE_HEAD.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TABLE_HEAD[h]);
+ n = raw(dst, n, "
");
+ h = h + 1;
+ }
+ n = raw(dst, n, "
");
+ var g : u8 = 0;
+ while (g < 5) {
+ const c : u32 = sort_idx(collect(g));
+ var r : u32 = 0;
+ while (r < c) {
+ const t : u32 = S_IDX[r as usize];
+ n = raw(dst, n, "
");
+ n = text(dst, n, R_POOL[(R_TO[t as usize] as usize)..((R_TO[t as usize] + R_TL[t as usize]) as usize)]);
+ n = raw(dst, n, "
");
+ n = put_locale(dst, n, R_TN[t as usize]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, group_word(g));
+ n = raw(dst, n, "
");
+ r = r + 1;
+ }
+ g = g + 1;
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OTHER_LINE);
+ n = raw(dst, n, "
");
+ if (R_GRP[5] != 0.0) {
+ list_of(5);
+ arg_set(0, S_FIT[0..(S_FIT_N as usize)], 0, S_FIT_N as usize);
+ n = raw(dst, n, "
");
+ n = put_say(dst, n, SAY_INTERNAL, 1);
+ n = raw(dst, n, "
");
+ }
+ if (R_GRP[6] != 0.0) {
+ list_of(6);
+ arg_set(0, S_FIT[0..(S_FIT_N as usize)], 0, S_FIT_N as usize);
+ n = raw(dst, n, "
");
+ n = put_say(dst, n, SAY_HIER, 1);
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "
");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_SAMPLE_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SAMPLE_NAME);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = button(dst, n, SAY_READ);
+ n = button(dst, n, SAY_CLEAR);
+ n = raw(dst, n, "
");
+ n = text(dst, n, S_STATUS[0..(S_STATUS_N as usize)]);
+ n = raw(dst, n, "
");
+ n = button(dst, n, SAY_PNG_BUTTON);
+ n = raw(dst, n, "
");
+ n = facts_html(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BADGE_HEAD);
+ n = raw(dst, n, "
0) {
+ n = raw(dst, n, " src=\"");
+ n = text(dst, n, S_BURL[0..(S_BURL_N as usize)]);
+ n = raw(dst, n, "\"");
+ }
+ n = raw(dst, n, ">
");
+ if (S_COPIED) { n = button(dst, n, SAY_BADGE_COPIED); } else { n = button(dst, n, SAY_BADGE_COPY); }
+ n = button(dst, n, SAY_BADGE_SAVE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BADGE_NOTE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HONEST);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+// --- the card -----------------------------------------------------------------------------------------------
+
+fn color(o: []u8, n: u32, name: []const u8, ix: u32) -> u32 {
+ if (S_TH_N[ix as usize] == 0) { return cv_set_str(o, n, name, W_COLOR_FALLBACK); }
+ return cv_set_str(o, n, name, S_TH[((ix * 64) as usize)..((ix * 64 + S_TH_N[ix as usize]) as usize)]);
+}
+
+fn font(o: []u8, n: u32, px: u32, bold: u32) -> u32 {
+ ft_make(px, bold);
+ return cv_set_str(o, n, "font", S_FT[0..(S_FT_N as usize)]);
+}
+
+/// Bytes of the first k code points of s.
+fn cp_prefix_len(s: []const u8, k: u32) -> u32 {
+ var c : u32 = 0;
+ var i : usize = 0;
+ while (i < s.len) {
+ if (s[i] < 128 or s[i] >= 192) {
+ if (c == k) { return i as u32; }
+ c = c + 1;
+ }
+ i = i + 1;
+ }
+ return s.len as u32;
+}
+
+fn cp_count(s: []const u8) -> u32 {
+ var c : u32 = 0;
+ var i : usize = 0;
+ while (i < s.len) {
+ if (s[i] < 128 or s[i] >= 192) { c = c + 1; }
+ i = i + 1;
+ }
+ return c;
+}
+
+pub var S_CND : [262144]u8 = [0; 262144];
+
+fn cand_of(s: []const u8, k: u32) -> u32 {
+ const bl : u32 = cp_prefix_len(s, k);
+ const n : u32 = copy_into(S_CND[0..], 0, s[0..(bl as usize)]);
+ return copy_into(S_CND[0..], n, W_ELLIPSIS);
+}
+
+/// fitText(ctx, s, maxW): s when it fits, else the longest start of s that fits with "..." after it. Into S_FIT.
+fn fit(px: u32, bold: u32, s: []const u8, maxw: f64) -> u32 {
+ if (x_width(px, bold, s) <= maxw) {
+ S_FIT_N = copy_into(S_FIT[0..], 0, s);
+ return S_FIT_N;
+ }
+ var k : u32 = cp_count(s);
+ var more : bool = true;
+ while (k > 1 and more) {
+ const cn : u32 = cand_of(s, k);
+ const w : f64 = x_width(px, bold, S_CND[0..(cn as usize)]);
+ if (X_MISS) {
+ // the host has not measured this start of s yet: ask for the next shorter ones with it
+ var j : u32 = 1;
+ while (j < 32 and j < k) {
+ const cj : u32 = cand_of(s, k - j);
+ x_ask(px, bold, S_CND[0..(cj as usize)]);
+ j = j + 1;
+ }
+ more = false;
+ } else {
+ if (w > maxw) { k = k - 1; } else { more = false; }
+ }
+ }
+ S_FIT_N = cand_of(s, k);
+ var q : u32 = 0;
+ while (q < S_FIT_N) {
+ S_FIT[q as usize] = S_CND[q as usize];
+ q = q + 1;
+ }
+ return S_FIT_N;
+}
+
+fn line_set(ln: u32, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ var k : usize = 0;
+ while (k < s.len and n < W_LINE_CAP) {
+ S_LB[((ln * W_LINE_CAP) + n) as usize] = s[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ S_LL[ln as usize] = n;
+ return n;
+}
+
+/// wrapItems(ctx, items, maxW, maxLines): the items are S_IT[S_IO[i] ..+ S_IL[i]]; lines into S_LB, their count in S_LNN.
+fn wrap_items(ic: u32, px: u32, bold: u32, maxw: f64, maxl: u32) -> u32 {
+ S_LL[0] = 0;
+ S_LL[1] = 0;
+ var nl : u32 = 1;
+ var i : u32 = 0;
+ var go : bool = true;
+ while (i < ic and go) {
+ const it : []const u8 = S_IT[(S_IO[i as usize] as usize)..((S_IO[i as usize] + S_IL[i as usize]) as usize)];
+ const cur_n : u32 = S_LL[(nl - 1) as usize];
+ var cn : u32 = 0;
+ if (cur_n > 0) {
+ cn = copy_into(S_CAND[0..], 0, S_LB[(((nl - 1) * W_LINE_CAP) as usize)..(((nl - 1) * W_LINE_CAP + cur_n) as usize)]);
+ cn = copy_into(S_CAND[0..], cn, W_ITEM_GAP);
+ }
+ cn = copy_into(S_CAND[0..], cn, it);
+ const w : f64 = x_width(px, bold, S_CAND[0..(cn as usize)]);
+ if (w <= maxw or cur_n == 0) {
+ line_set(nl - 1, S_CAND[0..(cn as usize)]);
+ } else {
+ if (nl < maxl) {
+ line_set(nl, it);
+ nl = nl + 1;
+ } else {
+ fit(px, bold, S_CAND[0..(cn as usize)], maxw);
+ line_set(nl - 1, S_FIT[0..(S_FIT_N as usize)]);
+ go = false;
+ }
+ }
+ i = i + 1;
+ }
+ var c : u32 = 0;
+ var l : u32 = 0;
+ while (l < nl) {
+ if (S_LL[l as usize] > 0) {
+ if (c != l) {
+ var q : u32 = 0;
+ while (q < S_LL[l as usize]) {
+ S_LB[((c * W_LINE_CAP) + q) as usize] = S_LB[((l * W_LINE_CAP) + q) as usize];
+ q = q + 1;
+ }
+ S_LL[c as usize] = S_LL[l as usize];
+ }
+ c = c + 1;
+ }
+ l = l + 1;
+ }
+ S_LNN = c;
+ return c;
+}
+
+/// "type n" as an item (type, space, num(n)) appended to S_IT.
+fn item_add(ic: u32, t: u32, pos: u32) -> u32 {
+ var n : u32 = pos;
+ n = copy_into(S_IT[0..], n, R_POOL[(R_TO[t as usize] as usize)..((R_TO[t as usize] + R_TL[t as usize]) as usize)]);
+ n = copy_into(S_IT[0..], n, " ");
+ const l : u32 = locale_text(R_TN[t as usize]);
+ n = copy_into(S_IT[0..], n, S_NUM[0..(l as usize)]);
+ S_IO[ic as usize] = pos;
+ S_IL[ic as usize] = n - pos;
+ return n;
+}
+
+pub fn w_draw(o: []u8) -> u32 {
+ if (!S_HAVE) { return 0; }
+ const cw : f64 = K_CARD_W as f64;
+ const ch : f64 = K_CARD_H as f64;
+ var n : u32 = cv_canvas(o, 0, 0);
+ n = color(o, n, "fillStyle", 0);
+ n = cv_call4(o, n, "fillRect", 0.0, 0.0, cw, ch);
+ n = color(o, n, "strokeStyle", 2);
+ n = cv_set_num(o, n, "lineWidth", 2.0);
+ n = cv_call4(o, n, "strokeRect", 15.0, 15.0, cw - 30.0, ch - 30.0);
+ n = cv_set_str(o, n, "textBaseline", "alphabetic");
+ n = cv_set_str(o, n, "textAlign", "left");
+ n = color(o, n, "fillStyle", 3);
+ n = font(o, n, 20, 1);
+ n = cv_text(o, n, "fillText", SAY_CARD_BRAND, 40.0, 56.0);
+ n = cv_set_str(o, n, "textAlign", "right");
+ n = color(o, n, "fillStyle", 8);
+ n = font(o, n, 18, 0);
+ if (r_ver_set()) {
+ arg_set(0, R_POOL[0..], R_INF[8] as usize, (R_INF[8] + R_INF[9]) as usize);
+ const vl : i64 = fmt_into(S_FB[0..], SAY_VERSION, 1);
+ n = cv_text(o, n, "fillText", S_FB[0..(vl as usize)], cw - 40.0, 56.0);
+ } else {
+ n = cv_text(o, n, "fillText", SAY_NO_VERSION, cw - 40.0, 56.0);
+ }
+ n = cv_set_str(o, n, "textAlign", "left");
+ n = color(o, n, "fillStyle", 7);
+ n = font(o, n, 18, 0);
+ if (r_top_set()) { n = cv_text(o, n, "fillText", SAY_TOP, 40.0, 104.0); } else { n = cv_text(o, n, "fillText", SAY_NO_TOP, 40.0, 104.0); }
+ n = color(o, n, "fillStyle", 4);
+ n = font(o, n, 46, 1);
+ if (r_top_set()) {
+ fit(46, 1, R_POOL[(R_INF[11] as usize)..((R_INF[11] + R_INF[12]) as usize)], cw - 80.0);
+ } else {
+ fit(46, 1, "", cw - 80.0);
+ }
+ n = cv_text(o, n, "fillText", S_FIT[0..(S_FIT_N as usize)], 40.0, 156.0);
+ n = color(o, n, "fillStyle", 5);
+ n = font(o, n, 20, 1);
+ n = cv_text(o, n, "fillText", SAY_CARD_KIND, 40.0, 192.0);
+ const gap : f64 = 20.0;
+ const tw : f64 = (cw - 80.0 - gap * 3.0) / 4.0;
+ var i : u32 = 0;
+ while (i < 4) {
+ const x : f64 = 40.0 + (i as f64) * (tw + gap);
+ const y : f64 = 218.0;
+ n = color(o, n, "fillStyle", 1);
+ n = cv_call4(o, n, "fillRect", x, y, tw, 210.0);
+ n = color(o, n, "strokeStyle", 2);
+ n = cv_set_num(o, n, "lineWidth", 2.0);
+ n = cv_call4(o, n, "strokeRect", x, y, tw, 210.0);
+ n = cv_set_str(o, n, "textAlign", "left");
+ n = color(o, n, "fillStyle", 3);
+ n = font(o, n, 24, 1);
+ n = cv_text(o, n, "fillText", SAY_GROUP_NAMES[i as usize], x + 18.0, y + 40.0);
+ const cnt : f64 = R_GRP[i as usize];
+ if (cnt != 0.0) { n = color(o, n, "fillStyle", 6); } else { n = color(o, n, "fillStyle", 8); }
+ n = font(o, n, 64, 1);
+ const nl : u32 = locale_text(cnt);
+ fit(64, 1, S_NUM[0..(nl as usize)], tw - 36.0);
+ n = cv_text(o, n, "fillText", S_FIT[0..(S_FIT_N as usize)], x + 18.0, y + 118.0);
+ n = color(o, n, "fillStyle", 8);
+ n = font(o, n, 14, 0);
+ fit(14, 0, SAY_GROUP_RULES[i as usize], tw - 36.0);
+ n = cv_text(o, n, "fillText", S_FIT[0..(S_FIT_N as usize)], x + 18.0, y + 150.0);
+ // the types of this group, "TYPE n", wrapped
+ var ic : u32 = 0;
+ var pos : u32 = 0;
+ var t : u32 = 0;
+ while (t < R_NT) {
+ if (R_TG[t as usize] == (i as u8) and ic < 64) {
+ pos = item_add(ic, t, pos);
+ ic = ic + 1;
+ }
+ t = t + 1;
+ }
+ n = color(o, n, "fillStyle", 7);
+ n = font(o, n, 15, 0);
+ wrap_items(ic, 15, 0, tw - 36.0, 2);
+ var k : u32 = 0;
+ while (k < S_LNN) {
+ n = cv_text(o, n, "fillText", S_LB[((k * W_LINE_CAP) as usize)..((k * W_LINE_CAP + S_LL[k as usize]) as usize)], x + 18.0, y + 176.0 + (k as f64) * 20.0);
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ // the other types
+ const oc : u32 = sort_idx(collect(4));
+ var sc : u32 = 0;
+ var pos2 : u32 = 0;
+ var q : u32 = 0;
+ while (q < oc and q < (K_CARD_OTHER_MAX as u32)) {
+ pos2 = item_add(sc, S_IDX[q as usize], pos2);
+ sc = sc + 1;
+ q = q + 1;
+ }
+ if (oc > (K_CARD_OTHER_MAX as u32)) {
+ const rest : u32 = oc - (K_CARD_OTHER_MAX as u32);
+ const rl : u32 = plain_text(rest as f64);
+ arg_set(0, S_NUM[0..], 0, rl as usize);
+ const ml : i64 = fmt_into(S_IT[(pos2 as usize)..], SAY_OTHER_MORE, 1);
+ S_IO[sc as usize] = pos2;
+ S_IL[sc as usize] = ml as u32;
+ pos2 = pos2 + (ml as u32);
+ sc = sc + 1;
+ }
+ n = cv_set_str(o, n, "textAlign", "left");
+ n = color(o, n, "fillStyle", 3);
+ n = font(o, n, 22, 1);
+ var hn : u32 = copy_into(S_CAND[0..], 0, SAY_OTHER);
+ hn = copy_into(S_CAND[0..], hn, " ");
+ const gl : u32 = locale_text(R_GRP[4]);
+ hn = copy_into(S_CAND[0..], hn, S_NUM[0..(gl as usize)]);
+ n = cv_text(o, n, "fillText", S_CAND[0..(hn as usize)], 40.0, 474.0);
+ const hx : f64 = 40.0 + x_width(22, 1, S_CAND[0..(hn as usize)]) + 24.0;
+ n = color(o, n, "fillStyle", 7);
+ n = font(o, n, 18, 0);
+ if (sc > 0) {
+ wrap_items(sc, 18, 0, cw - 40.0 - hx, 2);
+ var k2 : u32 = 0;
+ while (k2 < S_LNN) {
+ n = cv_text(o, n, "fillText", S_LB[((k2 * W_LINE_CAP) as usize)..((k2 * W_LINE_CAP + S_LL[k2 as usize]) as usize)], hx, 474.0 + (k2 as f64) * 26.0);
+ k2 = k2 + 1;
+ }
+ } else {
+ n = cv_text(o, n, "fillText", SAY_OTHER_NONE, hx, 474.0);
+ }
+ n = color(o, n, "strokeStyle", 2);
+ n = cv_call0(o, n, "beginPath");
+ n = cv_call2(o, n, "moveTo", 40.0, 548.0);
+ n = cv_call2(o, n, "lineTo", cw - 40.0, 548.0);
+ n = cv_call0(o, n, "stroke");
+ n = color(o, n, "fillStyle", 3);
+ n = font(o, n, 20, 1);
+ n = cv_text(o, n, "fillText", SAY_CARD_URL, 40.0, 586.0);
+ n = cv_set_str(o, n, "textAlign", "right");
+ n = color(o, n, "fillStyle", 8);
+ n = font(o, n, 18, 0);
+ const cl : u32 = locale_text(R_CELLS);
+ arg_set(0, S_NUM[0..], 0, cl as usize);
+ const cf : i64 = fmt_into(S_FB[0..], SAY_CELLS, 1);
+ n = cv_text(o, n, "fillText", S_FB[0..(cf as usize)], cw - 40.0, 586.0);
+ if (!cv_ok(n)) { return 0; }
+ return n;
+}
+
+
+// --- Tests of the native page -----------------------------------------------------------------------------
+
+pub const W_TEST_STAT : str = "{\"creator\":\"Yosys 0.67+post (x)\",\"modules\":{\"\\\\top\":{\"num_cells_by_type\":{\"LUT2\":138,\"FDRE\":149,\"RAMB36E1\":4,\"OBUF\":51,\"CARRY4\":32}}}}";
+
+/// Does the first n bytes of o contain the text want?
+fn has_text(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (want.len > (n as usize)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn count_text(o: []const u8, n: u32, want: []const u8) -> u32 {
+ var c : u32 = 0;
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { c = c + 1; }
+ i = i + 1;
+ }
+ return c;
+}
+
+/// The host measures the q requests in req: 6 pixels a byte of text.
+fn give_widths(req: []const u8, q: u32) -> u32 {
+ var wd : [2048]f64 = [0.0; 2048];
+ var p : u32 = 0;
+ var i : u32 = 0;
+ while (i < q) {
+ const tl : u32 = (req[(p + 4) as usize] as u32) + 256 * (req[(p + 5) as usize] as u32);
+ wd[i as usize] = (tl as f64) * 6.0;
+ p = p + 8 + (req[p as usize] as u32) + tl;
+ i = i + 1;
+ }
+ return w_text_give(wd[0..], q);
+}
+
+/// The host answers every width the card asks for, until it asks no more. The number of rounds.
+fn answer_widths() -> u32 {
+ var req : [65536]u8 = [0; 65536];
+ var rounds : u32 = 0;
+ var go : bool = true;
+ var o : [65536]u8 = [0; 65536];
+ while (go and rounds < 300) {
+ w_draw(o[0..]);
+ const q : u32 = w_text_need(req[0..]);
+ if (q == 0) {
+ go = false;
+ } else {
+ give_widths(req[0..], q);
+ }
+ rounds = rounds + 1;
+ }
+ return rounds;
+}
+
+fn open_page() -> u32 {
+ w_init("");
+ w_base("http://x/y/");
+ var t : [128]u8 = [0; 128];
+ var n : u32 = 0;
+ var i : u32 = 1;
+ while (i <= 9) {
+ t[n as usize] = 35;
+ var k : u32 = 0;
+ while (k < 3) {
+ t[(n + 1 + 2 * k) as usize] = 48;
+ t[(n + 2 + 2 * k) as usize] = (48 + i) as u8;
+ k = k + 1;
+ }
+ t[(n + 7) as usize] = 10;
+ n = n + 8;
+ i = i + 1;
+ }
+ w_theme(t[0..(n as usize)]);
+ return 0;
+}
+
+test the_page_opens_with_the_sample_loading_and_the_result_hidden {
+ open_page();
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert n > 0;
+ assert has_text(o[0..], n, "
Read in this tab; nothing is sent.
");
+ assert has_text(o[0..], n, "");
+ assert has_text(o[0..], n, "aria-pressed=\"false\">lm_head_matvec (yosys 0.67)");
+ assert has_text(o[0..], n, "href=\"http://x/y/sample.stat.json\" target=\"_blank\" rel=\"noopener\">Open the yosys output");
+ assert has_text(o[0..], n, "");
+ assert has_text(o[0..], n, "lm_head_matvec (yosys 0.67)");
+ assert !has_text(o[0..], n, "hidden=\"\">
");
+ assert has_text(o[0..], n, "
Read from "num_cells_by_type" (the top module, submodules expanded), 1 module(s).
");
+ assert has_text(o[0..], n, "
LUT2
138
LUT
");
+ assert has_text(o[0..], n, "
RAMB36E1
4
BRAM
");
+ assert has_text(o[0..], n, "
OBUF
51
other
CARRY4
32
other
");
+ assert has_text(o[0..], n, "alt=\"138 LUT | 149 FF | 0 DSP | 4 BRAM\"");
+ assert S_BADGE_N > 300;
+ assert S_BADGE[0] == 60;
+ var e : [2048]u8 = [0; 2048];
+ const el : u32 = w_effect(0, e[0..]);
+ assert e[0] == 98 and e[1] == 0;
+ assert has_text(e[0..], el, "image/svg+xml");
+ assert e[15] == 10 and e[16] == 60;
+ assert w_effect(1, e[0..]) == 0;
+ w_blob(0, "blob:q/1");
+ const m : u32 = w_render(o[0..]);
+ assert has_text(o[0..], m, " src=\"blob:q/1\">
");
+}
+
+test the_card_asks_the_host_for_text_widths_and_draws_when_it_has_them {
+ open_page();
+ w_data(0, W_TEST_STAT);
+ var o : [65536]u8 = [0; 65536];
+ var req : [65536]u8 = [0; 65536];
+ w_draw(o[0..]);
+ const q : u32 = w_text_need(req[0..]);
+ assert q > 0;
+ assert w_text_need(req[0..]) == 0;
+ give_widths(req[0..], q);
+ const rounds : u32 = answer_widths();
+ assert rounds < 300;
+ const n : u32 = w_draw(o[0..]);
+ assert n > 0;
+ assert has_text(o[0..], n, "[\"c\",0],[\"s\",\"fillStyle\",\"#010101\"],[\"m\",\"fillRect\",0,0,1200,630],[\"s\",\"strokeStyle\",\"#030303\"],[\"s\",\"lineWidth\",2],[\"m\",\"strokeRect\",15,15,1170,600],[\"s\",\"textBaseline\",\"alphabetic\"],[\"s\",\"textAlign\",\"left\"],");
+ assert has_text(o[0..], n, "[\"s\",\"font\",\"bold 20px Menlo, ui-monospace, SFMono-Regular, monospace\"],[\"m\",\"fillText\",\"TRINITY S3AI / STAT CARD\",40,56],");
+ assert has_text(o[0..], n, "[\"m\",\"fillText\",\"yosys 0.67+post\",1160,56]");
+ assert has_text(o[0..], n, "[\"m\",\"fillText\",\"top\",40,156]");
+ assert has_text(o[0..], n, "[\"m\",\"fillText\",\"138\",58,336]");
+ assert has_text(o[0..], n, "[\"m\",\"fillText\",\"374 cells\",1160,586]");
+ assert count_text(o[0..], n, "\"strokeRect\"") == 5;
+ assert count_text(o[0..], n, "\"fillRect\"") == 5;
+}
+
+test a_name_wider_than_the_card_is_cut_with_three_dots_at_the_last_start_that_fits {
+ open_page();
+ w_data(0, "{\"modules\":{\"abcdefghijklmnopqrstuvwxyz\":{\"cells\":{\"LUT2\":1}}}}");
+ var o : [65536]u8 = [0; 65536];
+ // 6 pixels a byte, 46px font: the card allows 1120, so 186 bytes; this name fits
+ answer_widths();
+ var n : u32 = w_draw(o[0..]);
+ assert has_text(o[0..], n, "[\"m\",\"fillText\",\"abcdefghijklmnopqrstuvwxyz\",40,156]");
+ w_input(1, "{\"modules\":{\"abcdefghijklmnopqrstuvwxyz\":{\"cells\":{\"LUT2\":1}}}}");
+ var long : [400]u8 = [97; 400];
+ var j : [600]u8 = [0; 600];
+ var p : u32 = copy_into(j[0..], 0, "{\"modules\":{\"");
+ p = copy_into(j[0..], p, long[0..300]);
+ p = copy_into(j[0..], p, "\":{\"cells\":{\"LUT2\":1}}}}");
+ w_input(1, j[0..(p as usize)]);
+ w_click(1);
+ assert S_HAVE;
+ answer_widths();
+ n = w_draw(o[0..]);
+ // 1120 / 6 = 186 bytes; with the three dots, 186 - 3 = 183 letters
+ assert count_text(o[0..], n, "aaaaaaaaaa") > 10;
+ assert has_text(o[0..], n, "...\",40,156]");
+}
+
+test a_text_that_does_not_read_is_refused_and_the_old_card_stays {
+ open_page();
+ w_data(0, W_TEST_STAT);
+ var o : [65536]u8 = [0; 65536];
+ w_input(1, "hello yosys");
+ w_click(1);
+ assert S_HIDDEN;
+ assert !S_PRESSED;
+ assert S_HAVE;
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">No JSON object in this text. Run stat with -json.");
+ assert has_text(o[0..], n, "aria-live=\"polite\" hidden=\"\">");
+ answer_widths();
+ assert w_draw(o[0..]) > 0;
+ w_input(1, "");
+ w_click(1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">Nothing to read yet.");
+ w_input(1, "{\"a\": 1}");
+ w_click(1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "is not yosys stat -json output.");
+ w_input(1, "{\"modules\": {}}");
+ w_click(1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "No per-type cell map (num_cells_by_type or cells) in this stat.");
+ w_input(1, "{ x }");
+ w_click(1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "Paste all of it, from the first { to the last }.");
+}
+
+test a_pasted_text_that_reads_says_so_and_the_clear_button_goes_back_to_the_sample {
+ open_page();
+ w_data(0, "{\"modules\":{\"s\":{\"cells\":{\"LUT6\":1}}}}");
+ var o : [65536]u8 = [0; 65536];
+ w_input(1, W_TEST_STAT);
+ w_click(1);
+ assert !S_PRESSED;
+ assert !S_HIDDEN;
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">pasted text. Read in this tab; nothing is sent.");
+ assert has_text(o[0..], n, "
");
+}
+
+test a_picked_file_names_itself_in_the_status_and_a_big_one_is_refused {
+ open_page();
+ var o : [65536]u8 = [0; 65536];
+ w_file("stat.json", 1234.0, W_TEST_STAT, 0);
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">stat.json: 1,234 bytes. Read in this tab; nothing is sent.");
+ assert !S_HIDDEN;
+ w_file("big.json", 4000001.0, "", 0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">4,000,001 characters is more than this page reads (4,000,000).");
+ assert S_HIDDEN;
+ w_file("bad.json", 20.0, "", 1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">Could not read bad.json.");
+ w_file("empty.json", 0.0, "", 0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">Nothing to read yet.");
+ assert w_drag(0) == 1;
+ assert w_drag(0) == 0;
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "");
+ assert w_drag(2) == 1;
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "");
+}
+
+test the_save_names_cut_the_top_name_to_a_file_name_and_the_copy_button_counts_down {
+ open_page();
+ w_data(0, "{\"modules\":{\"my top/2 \\u00e9x\":{\"cells\":{\"LUT6\":1}}}}");
+ var e : [2048]u8 = [0; 2048];
+ w_click(3);
+ assert w_effect(0, e[0..]) == 26;
+ assert e[0] == 112;
+ assert has_text(e[0..], 26, "stat-card-my_top_2_x.png");
+ assert w_effect(1, e[0..]) == 0;
+ w_click(5);
+ const m : u32 = w_effect(0, e[0..]);
+ assert e[0] == 100;
+ assert has_text(e[0..], m, "yosys-synth-my_top_2_x.svg");
+ assert e[28] == 10 and e[42] == 10;
+ assert has_text(e[0..], m, "image/svg+xml");
+ assert e[43] == 60;
+ assert w_effect(1, e[0..]) == 0;
+ var o : [65536]u8 = [0; 65536];
+ w_click(4);
+ assert w_effect(0, e[0..]) > 100;
+ assert e[0] == 107;
+ assert w_effect(1, e[0..]) == 6;
+ assert e[0] == 116 and e[1] == 0 and e[2] == 49;
+ assert w_effect(2, e[0..]) == 0;
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "type=\"button\">Copied");
+ w_timer(0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "type=\"button\">Copy SVG");
+}
+
+test the_lists_the_reader_gets_are_the_specs_own_four_lists {
+ build_lists();
+ const a : usize = S_LISTS_N as usize;
+ assert a == lists_default() as usize;
+ var i : usize = 0;
+ while (i < a) {
+ assert S_LISTS[i] == LST[i];
+ i = i + 1;
+ }
+ assert same_text(W_DATA_FILES[0], K_SAMPLE_STAT);
+}
+
+test numbers_are_written_like_to_locale_string_and_fractions_survive_in_the_badge {
+ open_page();
+ w_data(0, "{\"modules\":{\"m\":{\"cells\":{\"LUT6\":1234567,\"FDRE\":2.5,\"X\":-3}}}}");
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "
LUT6
1,234,567
");
+ assert has_text(o[0..], n, "
FDRE
2.5
");
+ assert has_text(o[0..], n, "
X
-3
");
+ assert has_text(o[0..], n, "alt=\"1234567 LUT | 2.5 FF | 0 DSP | 0 BRAM\"");
+}
+
+test sorting_keeps_equal_counts_in_place_and_puts_big_counts_first {
+ open_page();
+ w_data(0, "{\"modules\":{\"m\":{\"cells\":{\"A\":5,\"B\":9,\"C\":5,\"D\":9,\"E\":1}}}}");
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "
B
9
other
D
9
other
A
5
other
C
");
+}
+
+test the_sample_that_does_not_load_says_so_and_a_failed_read_leaves_the_page_usable {
+ open_page();
+ w_data_failed(0);
+ var o : [65536]u8 = [0; 65536];
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">The sample did not load.");
+ assert S_HIDDEN;
+ w_data(0, "not json");
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">The sample did not load.");
+ w_input(1, W_TEST_STAT);
+ w_click(1);
+ assert !S_HIDDEN;
+ w_click(0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">The sample did not load.");
+ assert !S_HIDDEN;
+}
+
+test the_other_types_are_listed_up_to_nine_then_one_more_line {
+ open_page();
+ w_data(0, "{\"modules\":{\"m\":{\"cells\":{\"T1\":12,\"T2\":11,\"T3\":10,\"T4\":9,\"T5\":8,\"T6\":7,\"T7\":6,\"T8\":5,\"T9\":4,\"T10\":3,\"T11\":2}}}}");
+ var o : [65536]u8 = [0; 65536];
+ answer_widths();
+ const n : u32 = w_draw(o[0..]);
+ assert has_text(o[0..], n, "T9 4");
+ assert !has_text(o[0..], n, "T10 3");
+ assert has_text(o[0..], n, "+2 more");
+ assert has_text(o[0..], n, "\"other 77\"");
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions mention.
+/// This anchor mentions every helper the writer, the number rules and the canvas writers call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (match_in(s, 0, 0, s) as u32);
+ n = n + (pack(0, 0) as u32);
+ n = n + (lo_of(0) as u32);
+ n = n + (hi_of(0) as u32);
+ n = n + ((NO_TEXT < 0) as u32);
+ n = n + ((PACK > 0) as u32);
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + num_grouped(o, 0, 0);
+ n = n + CV_OVER;
+ n = n + (cv_ok(0) as u32);
+ n = n + cv_put(o, 0, 0);
+ n = n + cv_raw(o, 0, s);
+ n = n + (cv_fin(0.0) as u32);
+ n = n + cv_num(o, 0, 0.0);
+ n = n + cv_str(o, 0, s);
+ n = n + (cv_hex(0) as u32);
+ n = n + cv_open(o, 0, 0, s);
+ n = n + cv_close(o, 0);
+ n = n + cv_arg_f(o, 0, 0.0);
+ n = n + cv_arg_s(o, 0, s);
+ n = n + cv_arg_v(o, 0, s, s);
+ n = n + cv_arg_b(o, 0, false);
+ n = n + cv_arg_list(o, 0, 0.0, 0.0, 0);
+ n = n + cv_canvas(o, 0, 0);
+ n = n + cv_set_num(o, 0, s, 0.0);
+ n = n + cv_set_str(o, 0, s, s);
+ n = n + cv_set_var(o, 0, s, s, s);
+ n = n + cv_call0(o, 0, s);
+ n = n + cv_call2(o, 0, s, 0.0, 0.0);
+ n = n + cv_call4(o, 0, s, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_call6(o, 0, s, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_arc(o, 0, 0.0, 0.0, 0.0, 0.0, 0.0, false);
+ n = n + cv_text(o, 0, s, s, 0.0, 0.0);
+ n = n + cv_dash(o, 0, 0.0, 0.0, 0);
+ return n;
+}
From 48d90d650cb2eac92b58b50363104b805c6aad1b Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 21:43:28 +0700
Subject: [PATCH 55/63] widgets: lut-treemap drawn natively -- 41208-byte wasm,
2055 DOM comparisons, 0 differences (Closes #10938)
The whole page of the lut-treemap tool in .t27: the squarified treemap as absolutely placed tiles (their labels
left off where the host-measured text does not fit), the per-module table, the detail line, both drop boxes
(the stat and the flattened stat), the metric buttons, the map size from the host. lut_treemap_logic.t27 is
folded into the module; the reader keeps one parse, so the stat that is shown is read again from its text
whenever another text was read.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/lut_treemap.t27 | 4041 +++++++++++++++++++++++++++++++++
1 file changed, 4041 insertions(+)
create mode 100644 specs/widgets/lut_treemap.t27
diff --git a/specs/widgets/lut_treemap.t27 b/specs/widgets/lut_treemap.t27
new file mode 100644
index 0000000000..232ee6e80f
--- /dev/null
+++ b/specs/widgets/lut_treemap.t27
@@ -0,0 +1,4041 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/lut-treemap.t27 -- which submodule eats my LUTs: a treemap from yosys `stat -json`
+; Source of truth for public/widgets/lut-treemap/ (gHashTag/trinity, apps/website). The page is
+; written by scripts/widget-pages-from-spec.mjs from this file; tool.js holds no English of its
+; own and reads every word below from window.T27_WIDGET.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader drops the JSON that yosys `stat -json` wrote for a design that was NOT
+; flattened. In that JSON every module lists its own cells by type and, under the same map, how
+; many instances of each submodule it holds. public/widgets/lut-treemap/treemap.js multiplies down
+; the hierarchy: a module's instance count is the sum, over every path from the top, of the
+; product of the counts on that path; its share is instances x its own cells. The page draws one
+; tile per instance (or one tile marked xN when there are too many), sized by LUTs, FFs, DSPs or
+; BRAMs, with a table of every module: instances, cells of one, total, share. It checks that the
+; tiles add up to the top's total and to the total yosys printed itself. A second, optional drop
+; takes the stat of a flattened run of the same design and shows both totals side by side. Files
+; are read in the browser and never leave the page; nothing is sent.
+
+; THE SAMPLE: gf16_matmul4x4_p, a pipelined 4x4 matrix multiplier over GF16 (16 gf16_dot4_p, each
+; 3 gf16_add and 4 gf16_mul). Two of its files are in gHashTag/trinity, fpga/gf16-pipelined/, and
+; the combinational gf16_add and gf16_mul with ports a, b, result are in gHashTag/t27,
+; fpga/vivado/. scripts/widget-data/lut-treemap.mjs reads the four files out of git at the commits
+; named below, copies them to a scratch directory and runs Yosys 0.67+post twice: synth_xilinx
+; -top gf16_matmul4x4_p, and the same with -flatten, each followed by `tee -q -o stat
+; -json`. Both outputs are shipped byte for byte; sample.json holds the commands, the `yosys -V`
+; line, exit codes, seconds and the sha256 of every input. Every K_SAMPLE_ fact below is re-read
+; from those files by `node scripts/widget-data/lut-treemap.mjs --check`, which also runs this
+; spec's tests on the values it read and a negative control that must fail them.
+
+; WHAT IT DOES NOT CLAIM: these are synthesis cell counts, not post-place-and-route utilisation.
+; A hierarchical run keeps module boundaries, so yosys cannot optimise across them: the sample's
+; flattened run comes out at 18730 LUT against 20464 hierarchical. The per-module split is true of
+; the hierarchical build only, and the page says so whenever both totals are shown. stat -json
+; names no instances, so the copies of a module are numbered in counting order, not by instance
+; name. The cell lists are 7-series names (synth_xilinx, the default family); other cells are
+; counted under "other" and are not drawn.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_lut_treemap;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "lut-treemap";
+pub const TITLE : str = "LUT treemap: which submodule eats your LUTs, from yosys stat -json";
+pub const DESCRIPTION : str = "Drop yosys stat -json from a run without -flatten: a treemap of LUTs or FFs per submodule, each times its instance count, beside the flattened total. Read in your browser.";
+pub const IMAGE_ALT : str = "A treemap on black of a real yosys 0.67 synth_xilinx run of gf16_matmul4x4_p, a 4x4 GF16 matrix multiplier: 16 gf16_dot4_p boxes holding 112 tiles, one per instance. 48 gf16_add of 293 LUT make 14064 LUT (68.7 percent), 64 gf16_mul of 100 LUT make 6400 (31.3 percent): 20464 in all. Flattened, the same design is 18730 LUT (-8.5 percent).";
+pub const HOOK : str = "48 copies of a 293-LUT adder eat 69% of this matrix multiplier. Drop your yosys stat -json and see which submodule eats your LUTs.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [6]str = ["Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159), run by scripts/widget-data/lut-treemap.mjs in a scratch directory: nice -n 10 yosys -q -p \"read_verilog gf16_add.v gf16_mul.v gf16_dot4_p.v gf16_matmul4x4_p.v; synth_xilinx -top gf16_matmul4x4_p; tee -q -o stat.json stat -json\"", "the same command with synth_xilinx -flatten -top gf16_matmul4x4_p, written to flat.stat.json, for the flattened total", "gHashTag/trinity fpga/gf16-pipelined/gf16_dot4_p.v and gf16_matmul4x4_p.v at commit df6cb3e0f654; gHashTag/t27 fpga/vivado/gf16_add.v and gf16_mul.v at commit ea15cd54c8c9; read with git show, sha256 of each in sample.json", "public/widgets/lut-treemap/sample.stat.json and sample.flat.stat.json: the yosys output, byte for byte; sample.json: commands, yosys -V line, exit codes, seconds, input and output sha256", "public/widgets/lut-treemap/treemap.js: the one parser, instance multiplier and treemap layout, run in node for the sample and in the browser for your file; card.png is drawn from sample.json by scripts/widget-data/lut-treemap-card.py", "node scripts/widget-data/lut-treemap.mjs --check: re-reads both stat files and fails if any K_SAMPLE_ fact in this spec disagrees; scripts/widget-data/lut-treemap-paramod.v is the parser fixture for $paramod names"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Which cell type is which (7-series names, as synth_xilinx writes them) ---------------------
+pub const K_LUT_TYPES : [6]str = ["LUT1", "LUT2", "LUT3", "LUT4", "LUT5", "LUT6"];
+pub const K_FF_TYPES : [4]str = ["FDRE", "FDSE", "FDCE", "FDPE"];
+pub const K_DSP_TYPES : [1]str = ["DSP48E1"];
+pub const K_BRAM_TYPES : [2]str = ["RAMB18E1", "RAMB36E1"];
+; yosys bookkeeping that is no hardware: counted in no group.
+pub const K_INTERNAL_TYPES : [1]str = ["$scopeinfo"];
+
+; --- The sample (scripts/widget-data/lut-treemap.mjs re-reads every K_SAMPLE_ fact) -------------
+pub const K_SAMPLE_JSON : str = "sample.json";
+pub const K_SAMPLE_STAT : str = "sample.stat.json";
+pub const K_SAMPLE_FLAT_STAT : str = "sample.flat.stat.json";
+pub const K_SAMPLE_TRINITY_COMMIT : str = "df6cb3e0f6540a153fa4e55f591c59b7813f2270";
+pub const K_SAMPLE_TRINITY_FILES : [2]str = ["fpga/gf16-pipelined/gf16_dot4_p.v", "fpga/gf16-pipelined/gf16_matmul4x4_p.v"];
+pub const K_SAMPLE_T27_COMMIT : str = "ea15cd54c8c92bc84411d2d1ae20904e143ff0ea";
+pub const K_SAMPLE_T27_FILES : [2]str = ["fpga/vivado/gf16_add.v", "fpga/vivado/gf16_mul.v"];
+; read_verilog takes them in this order: leaves first.
+pub const K_SAMPLE_READ_ORDER : [4]str = ["gf16_add.v", "gf16_mul.v", "gf16_dot4_p.v", "gf16_matmul4x4_p.v"];
+pub const K_SAMPLE_TOP : str = "gf16_matmul4x4_p";
+pub const K_SAMPLE_YOSYS : str = "0.67+post";
+; Every module, in the order yosys printed them; how many instances sit under one top; the cells
+; of ONE instance, its own only (submodules not included).
+pub const K_SAMPLE_MODULES : [4]str = ["gf16_add", "gf16_dot4_p", "gf16_matmul4x4_p", "gf16_mul"];
+pub const K_SAMPLE_INST : [4]u16 = [48, 16, 1, 64];
+pub const K_SAMPLE_EACH_LUT : [4]u16 = [293, 0, 0, 100];
+pub const K_SAMPLE_EACH_FF : [4]u16 = [0, 112, 0, 0];
+pub const K_SAMPLE_EACH_DSP : [4]u16 = [0, 0, 0, 1];
+; Every "parent holds N of child" entry, as yosys listed them.
+pub const K_SAMPLE_EDGE_PARENT : [3]str = ["gf16_dot4_p", "gf16_dot4_p", "gf16_matmul4x4_p"];
+pub const K_SAMPLE_EDGE_CHILD : [3]str = ["gf16_add", "gf16_mul", "gf16_dot4_p"];
+pub const K_SAMPLE_EDGE_COUNT : [3]u16 = [3, 4, 16];
+; The top, submodules expanded, as treemap.js adds it up; and what yosys printed under "design".
+pub const K_SAMPLE_TOTAL_LUT : u16 = 20464;
+pub const K_SAMPLE_TOTAL_FF : u16 = 1792;
+pub const K_SAMPLE_TOTAL_DSP : u16 = 64;
+pub const K_SAMPLE_DESIGN_LUT : u16 = 20464;
+pub const K_SAMPLE_DESIGN_FF : u16 = 1792;
+pub const K_SAMPLE_DESIGN_DSP : u16 = 64;
+; The flattened run of the same four files.
+pub const K_SAMPLE_FLAT_LUT : u16 = 18730;
+pub const K_SAMPLE_FLAT_FF : u16 = 1792;
+pub const K_SAMPLE_FLAT_DSP : u16 = 64;
+; Tiles the LUT treemap draws for the sample (one per instance holding LUTs).
+pub const K_SAMPLE_LUT_TILES : u16 = 112;
+
+; --- How it draws -------------------------------------------------------------------------------
+pub const K_CARD_W : u16 = 1200;
+pub const K_CARD_H : u16 = 630;
+pub const K_MIN_TARGET_PX : u8 = 32;
+; A pasted or dropped text over this many characters is not read.
+pub const K_MAX_PASTE_CHARS : u32 = 8000000;
+; Instances of one submodule are drawn one tile each up to this count; more share one tile "xN".
+pub const K_EXPAND_MAX : u16 = 64;
+; No more tiles than this in one treemap; past it, instances share tiles.
+pub const K_MAX_TILES : u16 = 600;
+; A tile gets a text label only when it is at least this wide and tall, in CSS pixels.
+pub const K_LABEL_MIN_W : u8 = 46;
+pub const K_LABEL_MIN_H : u8 = 18;
+; Height of a module's name strip, and the inset around its children, in CSS pixels.
+pub const K_HEAD_PX : u8 = 16;
+pub const K_PAD_PX : u8 = 2;
+; Hues (degrees) given to modules in the order yosys printed them, round robin; tiles keep their
+; colour when the metric changes.
+pub const K_TILE_HUES : [8]u16 = [152, 46, 200, 340, 268, 20, 96, 184];
+
+; --- Every word on the screen -------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_SAMPLE_LABEL : str = "Real sample";
+pub const SAY_SAMPLE_NAME : str = "gf16_matmul4x4_p (yosys 0.67)";
+pub const SAY_SAMPLE_LINE : str = "A pipelined 4x4 matrix multiplier over GF16: 16 dot-product units, each 3 adders and 4 multipliers. From gHashTag/trinity and gHashTag/t27, synthesised on our machine with synth_xilinx, once with module boundaries kept and once flattened.";
+pub const SAY_OPEN_SAMPLE : str = "Open the yosys output";
+pub const SAY_OPEN_FLAT : str = "and the flattened one";
+pub const SAY_OWN_LABEL : str = "Your stat -json";
+pub const SAY_HOW : str = "synth_xilinx -top (no -flatten), then: tee -q -o stat.json stat -json";
+pub const SAY_DROP : str = "Drop stat.json here, or pick a file";
+pub const SAY_PASTE_HINT : str = "...or paste what stat -json printed";
+pub const SAY_PASTE_LABEL : str = "yosys stat -json text";
+pub const SAY_READ : str = "Draw the treemap";
+pub const SAY_CLEAR : str = "Back to the sample";
+pub const SAY_FLAT_LABEL : str = "Optional: the same design, flattened";
+pub const SAY_FLAT_DROP : str = "Drop the stat of a -flatten run to compare totals";
+pub const SAY_PICKED : str = "{0}: {1} bytes";
+pub const SAY_PASTED : str = "pasted text";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab; nothing is sent.";
+pub const SAY_TOO_BIG : str = "{0} characters is more than this page reads ({1}).";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_LOAD : str = "The sample did not load.";
+; One per parse error code of treemap.js, in this order.
+pub const SAY_ERROR_IDS : [6]str = ["empty", "nojson", "badjson", "nomodules", "nocells", "notop"];
+pub const SAY_ERROR_TEXT : [6]str = ["Nothing to read yet.", "No JSON object in this text. Run stat with -json.", "The JSON does not parse. Paste all of it, from the first { to the last }.", "This JSON has no \"modules\": it is not yosys stat -json output.", "No per-type cell map (num_cells_by_type or cells) in this stat.", "Every module is instantiated by another, so there is no top."];
+pub const SAY_FLAT_WARN : str = "This stat is flat: no module holds another, so there is one tile. Synthesise without -flatten (and without flatten in your script) to see the submodules.";
+pub const SAY_NOT_FLAT : str = "That stat still has submodules; the second drop is for a -flatten run.";
+
+; The four things a tile can be sized by, and the words around them.
+pub const SAY_METRIC_LABEL : str = "Size tiles by";
+pub const SAY_METRIC_IDS : [4]str = ["lut", "ff", "dsp", "bram"];
+pub const SAY_METRIC_NAMES : [4]str = ["LUT", "FF", "DSP", "BRAM"];
+pub const SAY_METRIC_EMPTY : str = "No {0} cells in this design: nothing to draw.";
+pub const SAY_TOTAL : str = "{0} {1} under {2}";
+pub const SAY_SUM_OK : str = "The tiles add up to {0}, the top's total; yosys printed {1} for the whole design.";
+pub const SAY_SUM_BAD : str = "The tiles add up to {0}, but yosys printed {1} for the whole design. This stat may hold modules outside the top.";
+pub const SAY_SUM_NO_DESIGN : str = "The tiles add up to {0}, the top's total. This stat has no design block to compare with.";
+pub const SAY_MAP_LABEL : str = "Treemap of {0} per submodule instance";
+pub const SAY_TILE_COPY : str = "{0} #{1} of {2}";
+pub const SAY_TILE_SHORT : str = "{0} #{1}";
+pub const SAY_TILE_GROUP : str = "{0} x{1}";
+pub const SAY_TILE_OWN : str = "{0}, own cells";
+pub const SAY_TILE_VALUE : str = "{0} {1}";
+pub const SAY_DETAIL_HINT : str = "Tap a tile or a row to see where it sits.";
+pub const SAY_DETAIL_PATH : str = "Path from the top";
+pub const SAY_DETAIL_LINE : str = "{0} {1}: {2} of the top's {3}.";
+pub const SAY_DETAIL_MODULE : str = "{0}: {1} x {2} = {3} {4}, {5} of the top.";
+pub const SAY_TABLE_HEAD : [5]str = ["module", "instances", "each", "total", "share"];
+pub const SAY_TABLE_CAPTION : str = "Every module under the top: instances x its own cells = total. Submodules are counted in their own rows.";
+pub const SAY_FLAT_CMP : str = "Flattened run of the same design: {0} {1}. Hierarchical: {2} ({3}). With boundaries gone yosys optimises across modules, so the split above is of the hierarchical build.";
+pub const SAY_FLAT_SAME : str = "Flattened run of the same design: {0} {1}, the same as hierarchical.";
+pub const SAY_NOTE_COPIES : str = "stat -json names no instances, so copies are numbered #1 to #N in counting order, not by instance name.";
+pub const SAY_NOTE_GROUPED : str = "Some instances share one tile marked xN: more than {0} of them, or past {1} tiles.";
+pub const SAY_HONEST : str = "These are yosys cell counts after synthesis, before place and route: a placer packs, duplicates and removes cells, so a P&R report can differ. Cells outside the four lists (carry, mux, buffers, INV) are not drawn.";
+
+; The card.
+pub const SAY_CARD_BRAND : str = "TRINITY S3AI / LUT TREEMAP";
+pub const SAY_CARD_TITLE : str = "Which submodule eats my LUTs?";
+pub const SAY_CARD_KIND : str = "yosys synth, not place and route";
+pub const SAY_CARD_ROW : str = "{0} x {1} LUT = {2} ({3})";
+pub const SAY_CARD_TILES : str = "{0} tiles, one per instance";
+pub const SAY_CARD_VERSION : str = "yosys {0}";
+pub const SAY_CARD_FLAT : str = "flattened: {0} LUT ({1})";
+pub const SAY_CARD_URL : str = "t27.ai/widgets/lut-treemap";
+
+; --- Tests --------------------------------------------------------------------------------------
+test the_tiles_add_up_to_the_top {
+ assert K_SAMPLE_TOTAL_LUT == K_SAMPLE_INST[0] * K_SAMPLE_EACH_LUT[0] + K_SAMPLE_INST[1] * K_SAMPLE_EACH_LUT[1] + K_SAMPLE_INST[2] * K_SAMPLE_EACH_LUT[2] + K_SAMPLE_INST[3] * K_SAMPLE_EACH_LUT[3];
+ assert K_SAMPLE_TOTAL_FF == K_SAMPLE_INST[0] * K_SAMPLE_EACH_FF[0] + K_SAMPLE_INST[1] * K_SAMPLE_EACH_FF[1] + K_SAMPLE_INST[2] * K_SAMPLE_EACH_FF[2] + K_SAMPLE_INST[3] * K_SAMPLE_EACH_FF[3];
+ assert K_SAMPLE_TOTAL_DSP == K_SAMPLE_INST[0] * K_SAMPLE_EACH_DSP[0] + K_SAMPLE_INST[1] * K_SAMPLE_EACH_DSP[1] + K_SAMPLE_INST[2] * K_SAMPLE_EACH_DSP[2] + K_SAMPLE_INST[3] * K_SAMPLE_EACH_DSP[3];
+ assert K_SAMPLE_TOTAL_LUT == 20464;
+}
+
+test yosys_agrees_with_the_sum {
+ assert K_SAMPLE_DESIGN_LUT == K_SAMPLE_TOTAL_LUT;
+ assert K_SAMPLE_DESIGN_FF == K_SAMPLE_TOTAL_FF;
+ assert K_SAMPLE_DESIGN_DSP == K_SAMPLE_TOTAL_DSP;
+}
+
+test instances_multiply_down_the_hierarchy {
+ assert K_SAMPLE_MODULES[2] == K_SAMPLE_TOP;
+ assert K_SAMPLE_INST[2] == 1;
+ assert K_SAMPLE_EDGE_PARENT[2] == K_SAMPLE_TOP;
+ assert same_text(K_SAMPLE_EDGE_CHILD[2], K_SAMPLE_MODULES[1]);
+ assert K_SAMPLE_INST[1] == K_SAMPLE_EDGE_COUNT[2];
+ assert same_text(K_SAMPLE_EDGE_CHILD[0], K_SAMPLE_MODULES[0]);
+ assert K_SAMPLE_INST[0] == K_SAMPLE_EDGE_COUNT[2] * K_SAMPLE_EDGE_COUNT[0];
+ assert same_text(K_SAMPLE_EDGE_CHILD[1], K_SAMPLE_MODULES[3]);
+ assert K_SAMPLE_INST[3] == K_SAMPLE_EDGE_COUNT[2] * K_SAMPLE_EDGE_COUNT[1];
+ assert K_SAMPLE_LUT_TILES == K_SAMPLE_INST[0] + K_SAMPLE_INST[3];
+}
+
+test flattening_moves_the_total {
+ assert K_SAMPLE_FLAT_LUT < K_SAMPLE_TOTAL_LUT;
+ assert K_SAMPLE_FLAT_LUT == 18730;
+ assert K_SAMPLE_FLAT_FF == K_SAMPLE_TOTAL_FF;
+ assert K_SAMPLE_FLAT_DSP == K_SAMPLE_TOTAL_DSP;
+}
+
+test the_four_lists_name_7_series_cells {
+ assert K_LUT_TYPES[0] == "LUT1";
+ assert K_LUT_TYPES[5] == "LUT6";
+ assert K_FF_TYPES[0] == "FDRE";
+ assert K_DSP_TYPES[0] == "DSP48E1";
+ assert K_BRAM_TYPES[1] == "RAMB36E1";
+ assert SAY_METRIC_IDS[0] == "lut";
+ assert SAY_METRIC_IDS[3] == "bram";
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_CARD_W == 1200;
+ assert K_CARD_H == 630;
+ assert K_MIN_TARGET_PX == 32;
+}
+
+; FOLD BEGIN -- the body of lut_treemap_logic.t27, from its first const to its last test, with this change only:
+; its module variable OVER (a bool) is LT_OVER, because html_out has a constant OVER.
+; --
+pub const E_OK : u32 = 0;
+pub const E_EMPTY : u32 = 1;
+pub const E_NOJSON : u32 = 2;
+pub const E_BADJSON : u32 = 3;
+pub const E_NOMODULES : u32 = 4;
+pub const E_NOCELLS : u32 = 5;
+pub const E_NOTOP : u32 = 6;
+pub const E_TOOLARGE : u32 = 7;
+
+pub const M_LUT : u32 = 0;
+pub const M_FF : u32 = 1;
+pub const M_DSP : u32 = 2;
+pub const M_BRAM : u32 = 3;
+pub const M_OTHER : u32 = 4;
+
+pub const J_NULL : u8 = 0;
+pub const J_FALSE : u8 = 1;
+pub const J_TRUE : u8 = 2;
+pub const J_NUM : u8 = 3;
+pub const J_STR : u8 = 4;
+pub const J_OBJ : u8 = 5;
+pub const J_ARR : u8 = 6;
+
+pub const B_BSLASH : u8 = 92;
+
+// --- State. One parse per instance. ------------------------------------------------------------
+pub var POOL : [2097152]u8 = [0; 2097152];
+pub var POOL_TOP : usize = 0;
+pub var LT_OVER : bool = false;
+
+pub var N_KIND : [65536]u8 = [0; 65536];
+pub var N_NUM : [65536]f64 = [0.0; 65536];
+pub var N_SOFF : [65536]usize = [0; 65536];
+pub var N_SLEN : [65536]usize = [0; 65536];
+pub var N_KOFF : [65536]usize = [0; 65536];
+pub var N_KLEN : [65536]usize = [0; 65536];
+pub var N_FIRST : [65536]i32 = [0; 65536];
+pub var N_LAST : [65536]i32 = [0; 65536];
+pub var N_NEXT : [65536]i32 = [0; 65536];
+pub var NODE_TOP : usize = 0;
+
+pub var J_POS : usize = 0;
+pub var J_END : usize = 0;
+pub var J_ERR : bool = false;
+pub var J_SOFF : usize = 0;
+pub var J_SLEN : usize = 0;
+
+pub var NM_OFF : [4096]usize = [0; 4096];
+pub var NM_LEN : [4096]usize = [0; 4096];
+pub var DP_OFF : [4096]usize = [0; 4096];
+pub var DP_LEN : [4096]usize = [0; 4096];
+pub var NM_N : usize = 0;
+
+pub var HASMOD : [4096]u8 = [0; 4096];
+pub var USED : [4096]u8 = [0; 4096];
+pub var OWN : [20480]f64 = [0.0; 20480];
+pub var SUB_START : [4096]usize = [0; 4096];
+pub var SUB_N : [4096]usize = [0; 4096];
+pub var SUB_NID : [32768]usize = [0; 32768];
+pub var SUB_CNT : [32768]f64 = [0.0; 32768];
+pub var SUB_TOP : usize = 0;
+
+pub var ORDER : [4096]usize = [0; 4096];
+pub var ORDER_N : usize = 0;
+pub var R_ORDER : [4096]usize = [0; 4096];
+pub var R_ORDER_N : usize = 0;
+pub var ROOTS : [4096]usize = [0; 4096];
+pub var ROOTS_N : usize = 0;
+pub var TOP : usize = 0;
+pub var FLAT : bool = false;
+pub var HAS_DESIGN : bool = false;
+pub var DESIGN : [5]f64 = [0.0; 5];
+pub var KEY_KIND : u32 = 0;
+pub var CR_OFF : usize = 0;
+pub var CR_LEN : usize = 0;
+pub var HAS_CREATOR : bool = false;
+pub var VER_OFF : usize = 0;
+pub var VER_LEN : usize = 0;
+pub var HAS_VERSION : bool = false;
+pub var ACC : [5]f64 = [0.0; 5];
+
+pub var TOT : [20480]f64 = [0.0; 20480];
+pub var TOT_DONE : [4096]u8 = [0; 4096];
+pub var ON_STACK : [4096]u8 = [0; 4096];
+
+pub var SEEN : [4096]u8 = [0; 4096];
+pub var INST_IN : [4096]u8 = [0; 4096];
+pub var INST : [4096]f64 = [0.0; 4096];
+pub var POST : [4096]usize = [0; 4096];
+pub var POST_N : usize = 0;
+pub var ROW_NID : [4096]usize = [0; 4096];
+pub var ROW_N : usize = 0;
+
+// The view tree.
+pub var T_NID : [32768]usize = [0; 32768];
+pub var T_COPIES : [32768]f64 = [0.0; 32768];
+pub var T_INDEX : [32768]f64 = [0.0; 32768];
+pub var T_OF : [32768]f64 = [0.0; 32768];
+pub var T_OWN : [32768]u8 = [0; 32768];
+pub var T_VALUE : [32768]f64 = [0.0; 32768];
+pub var T_PARENT : [32768]i32 = [0; 32768];
+pub var T_FIRST : [32768]i32 = [0; 32768];
+pub var T_LAST : [32768]i32 = [0; 32768];
+pub var T_NEXT : [32768]i32 = [0; 32768];
+pub var T_N : usize = 0;
+pub var T_FAIL : bool = false;
+pub var T_BUDGET : f64 = 0.0;
+pub var T_METRIC : usize = 0;
+pub var T_EXPAND : f64 = 0.0;
+pub var KS : [32768]usize = [0; 32768];
+pub var KS_TOP : usize = 0;
+pub var LEAF : [32768]usize = [0; 32768];
+pub var LEAF_N : usize = 0;
+
+// Squarify scratch and output stack.
+pub var SQ_V : [8192]f64 = [0.0; 8192];
+pub var SQ_ID : [8192]usize = [0; 8192];
+pub var RS_I : [8192]usize = [0; 8192];
+pub var RS_A : [8192]f64 = [0.0; 8192];
+pub var P_ITEM : [65536]usize = [0; 65536];
+pub var P_X : [65536]f64 = [0.0; 65536];
+pub var P_Y : [65536]f64 = [0.0; 65536];
+pub var P_W : [65536]f64 = [0.0; 65536];
+pub var P_H : [65536]f64 = [0.0; 65536];
+pub var P_TOP : usize = 0;
+pub var SQ_FAIL : bool = false;
+
+pub var R_NODE : [32768]usize = [0; 32768];
+pub var R_X : [32768]f64 = [0.0; 32768];
+pub var R_Y : [32768]f64 = [0.0; 32768];
+pub var R_W : [32768]f64 = [0.0; 32768];
+pub var R_H : [32768]f64 = [0.0; 32768];
+pub var R_DEPTH : [32768]u32 = [0; 32768];
+pub var R_HEAD : [32768]u8 = [0; 32768];
+pub var R_N : usize = 0;
+pub var L_HEAD : f64 = 16.0;
+pub var L_PAD : f64 = 2.0;
+
+pub var D_OFF : usize = 0;
+pub var D_LEN : usize = 0;
+
+// --- Bytes and whitespace (JavaScript's trim and \s: the same set) ------------------------------
+pub fn is_digit(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+/// The length of the whitespace character that starts with these bytes; avail says how many are valid.
+pub fn ws_fwd(b0: u8, b1: u8, b2: u8, avail: usize) -> usize {
+ if (avail == 0) { return 0; }
+ if (b0 == 32 or (b0 >= 9 and b0 <= 13)) { return 1; }
+ if (b0 == 194) {
+ if (avail >= 2 and b1 == 160) { return 2; }
+ return 0;
+ }
+ if (avail < 3) { return 0; }
+ if (b0 == 225) {
+ if (b1 == 154 and b2 == 128) { return 3; }
+ return 0;
+ }
+ if (b0 == 226) {
+ if (b1 == 128) {
+ if (b2 >= 128 and b2 <= 138) { return 3; }
+ if (b2 == 168 or b2 == 169 or b2 == 175) { return 3; }
+ }
+ if (b1 == 129 and b2 == 159) { return 3; }
+ return 0;
+ }
+ if (b0 == 227) {
+ if (b1 == 128 and b2 == 128) { return 3; }
+ return 0;
+ }
+ if (b0 == 239) {
+ if (b1 == 187 and b2 == 191) { return 3; }
+ return 0;
+ }
+ return 0;
+}
+
+/// The length of the whitespace character that ENDS with these bytes (c is the last); avail as above.
+pub fn ws_back(a: u8, m: u8, c: u8, avail: usize) -> usize {
+ if (avail == 0) { return 0; }
+ if (c == 32 or (c >= 9 and c <= 13)) { return 1; }
+ if (avail >= 2 and m == 194 and c == 160) { return 2; }
+ if (avail < 3) { return 0; }
+ if (a == 225 and m == 154 and c == 128) { return 3; }
+ if (a == 226 and m == 128) {
+ if (c >= 128 and c <= 138) { return 3; }
+ if (c == 168 or c == 169 or c == 175) { return 3; }
+ }
+ if (a == 226 and m == 129 and c == 159) { return 3; }
+ if (a == 227 and m == 128 and c == 128) { return 3; }
+ if (a == 239 and m == 187 and c == 191) { return 3; }
+ return 0;
+}
+
+pub fn ws_at(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi) { return 0; }
+ var b1 : u8 = 0;
+ var b2 : u8 = 0;
+ var avail : usize = 1;
+ if (p + 1 < hi) { b1 = s[p + 1]; avail = 2; }
+ if (p + 2 < hi) { b2 = s[p + 2]; avail = 3; }
+ return ws_fwd(s[p], b1, b2, avail);
+}
+
+pub fn ws_before(s: []const u8, lo: usize, e: usize) -> usize {
+ if (e <= lo) { return 0; }
+ var a : u8 = 0;
+ var m : u8 = 0;
+ var avail : usize = 1;
+ if (e >= lo + 2) { m = s[e - 2]; avail = 2; }
+ if (e >= lo + 3) { a = s[e - 3]; avail = 3; }
+ return ws_back(a, m, s[e - 1], avail);
+}
+
+pub fn pool_ws_at(p: usize, hi: usize) -> usize {
+ if (p >= hi) { return 0; }
+ var b1 : u8 = 0;
+ var b2 : u8 = 0;
+ var avail : usize = 1;
+ if (p + 1 < hi) { b1 = POOL[p + 1]; avail = 2; }
+ if (p + 2 < hi) { b2 = POOL[p + 2]; avail = 3; }
+ return ws_fwd(POOL[p], b1, b2, avail);
+}
+
+pub fn pool_ws_before(lo: usize, e: usize) -> usize {
+ if (e <= lo) { return 0; }
+ var a : u8 = 0;
+ var m : u8 = 0;
+ var avail : usize = 1;
+ if (e >= lo + 2) { m = POOL[e - 2]; avail = 2; }
+ if (e >= lo + 3) { a = POOL[e - 3]; avail = 3; }
+ return ws_back(a, m, POOL[e - 1], avail);
+}
+
+pub fn pool_push(b: u8) {
+ if (POOL_TOP >= 2097152) {
+ LT_OVER = true;
+ J_ERR = true;
+ return;
+ }
+ POOL[POOL_TOP] = b;
+ POOL_TOP = POOL_TOP + 1;
+}
+
+pub fn pool_byte(i: usize) -> u8 {
+ return POOL[i];
+}
+
+/// Do POOL[a .. a+len] and POOL[b .. b+len] hold the same bytes?
+pub fn pool_same(a: usize, b: usize, len: usize) -> bool {
+ var k : usize = 0;
+ while (k < len) {
+ if (POOL[a + k] != POOL[b + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// Is POOL[off .. off+len] exactly w?
+pub fn pool_eq(off: usize, len: usize, w: []const u8) -> bool {
+ if (len != w.len) { return false; }
+ var k : usize = 0;
+ while (k < len) {
+ if (POOL[off + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// Does POOL[off .. off+len] start with w?
+pub fn pool_prefix(off: usize, len: usize, w: []const u8) -> bool {
+ if (len < w.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (POOL[off + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn pool_push_uint(v: u64) {
+ var digits : u32 = 1;
+ var t : u64 = v / 10;
+ while (t > 0) {
+ digits = digits + 1;
+ t = t / 10;
+ }
+ var div : u64 = 1;
+ var k : u32 = 1;
+ while (k < digits) {
+ div = div * 10;
+ k = k + 1;
+ }
+ var r : u64 = v;
+ while (div > 0) {
+ pool_push(((r / div) as u8) + 48);
+ r = r % div;
+ div = div / 10;
+ }
+}
+
+pub fn push_utf8(cp: u32) {
+ if (cp < 128) {
+ pool_push(cp as u8);
+ return;
+ }
+ if (cp < 2048) {
+ pool_push(((cp / 64) + 192) as u8);
+ pool_push(((cp % 64) + 128) as u8);
+ return;
+ }
+ if (cp < 65536) {
+ pool_push(((cp / 4096) + 224) as u8);
+ pool_push((((cp / 64) % 64) + 128) as u8);
+ pool_push(((cp % 64) + 128) as u8);
+ return;
+ }
+ pool_push(((cp / 262144) + 240) as u8);
+ pool_push((((cp / 4096) % 64) + 128) as u8);
+ pool_push((((cp / 64) % 64) + 128) as u8);
+ pool_push(((cp % 64) + 128) as u8);
+}
+
+pub fn hexv(b: u8) -> i32 {
+ if (b >= 48 and b <= 57) { return (b as i32) - 48; }
+ if (b >= 97 and b <= 102) { return (b as i32) - 87; }
+ if (b >= 65 and b <= 70) { return (b as i32) - 55; }
+ return -1;
+}
+
+// --- A JSON reader with JSON.parse's grammar, into a node table --------------------------------
+pub fn new_node(kind: u8) -> i32 {
+ if (NODE_TOP >= 65536) {
+ LT_OVER = true;
+ J_ERR = true;
+ return -1;
+ }
+ const i : usize = NODE_TOP;
+ NODE_TOP = NODE_TOP + 1;
+ N_KIND[i] = kind;
+ N_NUM[i] = 0.0;
+ N_SOFF[i] = 0;
+ N_SLEN[i] = 0;
+ N_KOFF[i] = 0;
+ N_KLEN[i] = 0;
+ N_FIRST[i] = -1;
+ N_LAST[i] = -1;
+ N_NEXT[i] = -1;
+ return (i as i32);
+}
+
+pub fn j_ws(s: []const u8) {
+ while (J_POS < J_END) {
+ const b : u8 = s[J_POS];
+ if (b == 32 or b == 9 or b == 10 or b == 13) {
+ J_POS = J_POS + 1;
+ } else {
+ return;
+ }
+ }
+}
+
+/// Four hex digits at s[p ..]; -1 when they are not.
+pub fn hex4(s: []const u8, p: usize) -> i32 {
+ if (p + 4 > J_END) { return -1; }
+ var v : i32 = 0;
+ var k : usize = 0;
+ while (k < 4) {
+ const h : i32 = hexv(s[p + k]);
+ if (h < 0) { return -1; }
+ v = v * 16 + h;
+ k = k + 1;
+ }
+ return v;
+}
+
+/// A JSON string at J_POS (on its opening quote): the decoded bytes go to POOL, J_SOFF and J_SLEN name them.
+pub fn j_string(s: []const u8) -> bool {
+ J_POS = J_POS + 1;
+ J_SOFF = POOL_TOP;
+ while (J_POS < J_END) {
+ const b : u8 = s[J_POS];
+ if (b == 34) {
+ J_POS = J_POS + 1;
+ J_SLEN = POOL_TOP - J_SOFF;
+ return !LT_OVER;
+ }
+ if (b < 32) { return false; }
+ if (b == B_BSLASH) {
+ if (J_POS + 1 >= J_END) { return false; }
+ const c : u8 = s[J_POS + 1];
+ J_POS = J_POS + 2;
+ if (c == 34) { pool_push(34); }
+ if (c == 92) { pool_push(92); }
+ if (c == 47) { pool_push(47); }
+ if (c == 98) { pool_push(8); }
+ if (c == 102) { pool_push(12); }
+ if (c == 110) { pool_push(10); }
+ if (c == 114) { pool_push(13); }
+ if (c == 116) { pool_push(9); }
+ if (c == 117) {
+ const h1 : i32 = hex4(s, J_POS);
+ if (h1 < 0) { return false; }
+ J_POS = J_POS + 4;
+ var cp : u32 = h1 as u32;
+ if (cp >= 55296 and cp <= 56319) {
+ var joined : bool = false;
+ if (J_POS + 6 <= J_END and s[J_POS] == 92 and s[J_POS + 1] == 117) {
+ const h2 : i32 = hex4(s, J_POS + 2);
+ if (h2 >= 56320 and h2 <= 57343) {
+ cp = 65536 + (cp - 55296) * 1024 + ((h2 as u32) - 56320);
+ J_POS = J_POS + 6;
+ joined = true;
+ }
+ }
+ if (!joined) { cp = 65533; }
+ } else {
+ if (cp >= 56320 and cp <= 57343) { cp = 65533; }
+ }
+ push_utf8(cp);
+ }
+ if (c != 34 and c != 92 and c != 47 and c != 98 and c != 102 and c != 110 and c != 114 and c != 116 and c != 117) {
+ return false;
+ }
+ } else {
+ pool_push(b);
+ J_POS = J_POS + 1;
+ }
+ }
+ return false;
+}
+
+/// 10 to the power e, by repeated multiplication (exact up to 22).
+pub fn pow10(e: i32) -> f64 {
+ var v : f64 = 1.0;
+ var k : i32 = 0;
+ while (k < e) {
+ v = v * 10.0;
+ k = k + 1;
+ }
+ return v;
+}
+
+pub fn dec_value(m: f64, fd: i32, ex: i32) -> f64 {
+ if (m == 0.0) { return 0.0; }
+ const te : i32 = ex - fd;
+ if (te >= 0) { return m * pow10(te); }
+ return m / pow10(-te);
+}
+
+/// A JSON number at J_POS (a minus or a digit): RFC 8259's grammar.
+pub fn j_number(s: []const u8) -> f64 {
+ var neg : bool = false;
+ if (s[J_POS] == 45) {
+ neg = true;
+ J_POS = J_POS + 1;
+ }
+ if (J_POS >= J_END) {
+ J_ERR = true;
+ return 0.0;
+ }
+ var m : f64 = 0.0;
+ const b : u8 = s[J_POS];
+ if (b == 48) {
+ J_POS = J_POS + 1;
+ } else {
+ if (b >= 49 and b <= 57) {
+ while (J_POS < J_END and is_digit(s[J_POS])) {
+ m = m * 10.0 + (((s[J_POS] - 48) as u32) as f64);
+ J_POS = J_POS + 1;
+ }
+ } else {
+ J_ERR = true;
+ return 0.0;
+ }
+ }
+ var fd : i32 = 0;
+ if (J_POS < J_END and s[J_POS] == 46) {
+ J_POS = J_POS + 1;
+ const st : usize = J_POS;
+ while (J_POS < J_END and is_digit(s[J_POS])) {
+ m = m * 10.0 + (((s[J_POS] - 48) as u32) as f64);
+ fd = fd + 1;
+ J_POS = J_POS + 1;
+ }
+ if (J_POS == st) {
+ J_ERR = true;
+ return 0.0;
+ }
+ }
+ var ex : i32 = 0;
+ if (J_POS < J_END and (s[J_POS] == 101 or s[J_POS] == 69)) {
+ J_POS = J_POS + 1;
+ var eneg : bool = false;
+ if (J_POS < J_END and (s[J_POS] == 43 or s[J_POS] == 45)) {
+ eneg = s[J_POS] == 45;
+ J_POS = J_POS + 1;
+ }
+ const st2 : usize = J_POS;
+ while (J_POS < J_END and is_digit(s[J_POS])) {
+ if (ex < 100000) { ex = ex * 10 + (((s[J_POS] - 48) as u32) as i32); }
+ J_POS = J_POS + 1;
+ }
+ if (J_POS == st2) {
+ J_ERR = true;
+ return 0.0;
+ }
+ if (eneg) { ex = -ex; }
+ }
+ const v : f64 = dec_value(m, fd, ex);
+ if (neg) { return -v; }
+ return v;
+}
+
+pub fn lit_at(s: []const u8, w: []const u8) -> bool {
+ if (J_POS + w.len > J_END) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (s[J_POS + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn link_child(parent: i32, v: i32) {
+ const pu : usize = parent as usize;
+ const vu : usize = v as usize;
+ N_NEXT[vu] = -1;
+ if (N_LAST[pu] < 0) {
+ N_FIRST[pu] = v;
+ } else {
+ N_NEXT[(N_LAST[pu] as usize)] = v;
+ }
+ N_LAST[pu] = v;
+}
+
+/// The value of an array-index key ("0", "17", up to 4294967294), or -1.0 when the key is not one.
+pub fn index_key(off: usize, len: usize) -> f64 {
+ if (len == 0 or len > 10) { return -1.0; }
+ if (POOL[off] == 48) {
+ if (len == 1) { return 0.0; }
+ return -1.0;
+ }
+ var v : f64 = 0.0;
+ var k : usize = 0;
+ while (k < len) {
+ const b : u8 = POOL[off + k];
+ if (b < 48 or b > 57) { return -1.0; }
+ v = v * 10.0 + (((b - 48) as u32) as f64);
+ k = k + 1;
+ }
+ if (v > 4294967294.0) { return -1.0; }
+ return v;
+}
+
+/// Set the member koff/klen of object o to the value node v, with JavaScript's property order:
+/// a repeated key keeps its place and takes the new value, integer-like keys come first ascending.
+pub fn obj_put(o: i32, koff: usize, klen: usize, v: i32) {
+ const vu : usize = v as usize;
+ N_KOFF[vu] = koff;
+ N_KLEN[vu] = klen;
+ var c : i32 = N_FIRST[(o as usize)];
+ while (c >= 0) {
+ const cu : usize = c as usize;
+ if (N_KLEN[cu] == klen and pool_same(N_KOFF[cu], koff, klen)) {
+ N_KIND[cu] = N_KIND[vu];
+ N_NUM[cu] = N_NUM[vu];
+ N_SOFF[cu] = N_SOFF[vu];
+ N_SLEN[cu] = N_SLEN[vu];
+ N_FIRST[cu] = N_FIRST[vu];
+ N_LAST[cu] = N_LAST[vu];
+ return;
+ }
+ c = N_NEXT[cu];
+ }
+ const ik : f64 = index_key(koff, klen);
+ if (ik < 0.0) {
+ link_child(o, v);
+ return;
+ }
+ var prev : i32 = -1;
+ c = N_FIRST[(o as usize)];
+ while (c >= 0) {
+ const cu2 : usize = c as usize;
+ const ck : f64 = index_key(N_KOFF[cu2], N_KLEN[cu2]);
+ if (ck < 0.0 or ck > ik) { break; }
+ prev = c;
+ c = N_NEXT[cu2];
+ }
+ N_NEXT[vu] = c;
+ if (prev < 0) {
+ N_FIRST[(o as usize)] = v;
+ } else {
+ N_NEXT[(prev as usize)] = v;
+ }
+ if (c < 0) { N_LAST[(o as usize)] = v; }
+}
+
+pub fn j_object(s: []const u8, depth: u32) -> i32 {
+ const o : i32 = new_node(J_OBJ);
+ if (o < 0) { return -1; }
+ J_POS = J_POS + 1;
+ j_ws(s);
+ if (J_POS < J_END and s[J_POS] == 125) {
+ J_POS = J_POS + 1;
+ return o;
+ }
+ while (J_POS < J_END) {
+ j_ws(s);
+ if (J_POS >= J_END or s[J_POS] != 34) {
+ J_ERR = true;
+ return -1;
+ }
+ if (!j_string(s)) {
+ J_ERR = true;
+ return -1;
+ }
+ const koff : usize = J_SOFF;
+ const klen : usize = J_SLEN;
+ j_ws(s);
+ if (J_POS >= J_END or s[J_POS] != 58) {
+ J_ERR = true;
+ return -1;
+ }
+ J_POS = J_POS + 1;
+ const v : i32 = j_value(s, depth + 1);
+ if (v < 0 or J_ERR) {
+ J_ERR = true;
+ return -1;
+ }
+ obj_put(o, koff, klen, v);
+ j_ws(s);
+ if (J_POS >= J_END) {
+ J_ERR = true;
+ return -1;
+ }
+ const c : u8 = s[J_POS];
+ if (c == 44) {
+ J_POS = J_POS + 1;
+ } else {
+ if (c == 125) {
+ J_POS = J_POS + 1;
+ return o;
+ }
+ J_ERR = true;
+ return -1;
+ }
+ }
+ J_ERR = true;
+ return -1;
+}
+
+pub fn j_array(s: []const u8, depth: u32) -> i32 {
+ const a : i32 = new_node(J_ARR);
+ if (a < 0) { return -1; }
+ J_POS = J_POS + 1;
+ j_ws(s);
+ if (J_POS < J_END and s[J_POS] == 93) {
+ J_POS = J_POS + 1;
+ return a;
+ }
+ var idx : usize = 0;
+ while (J_POS < J_END) {
+ const v : i32 = j_value(s, depth + 1);
+ if (v < 0 or J_ERR) {
+ J_ERR = true;
+ return -1;
+ }
+ const koff : usize = POOL_TOP;
+ pool_push_uint(idx as u64);
+ if (LT_OVER) { return -1; }
+ N_KOFF[(v as usize)] = koff;
+ N_KLEN[(v as usize)] = POOL_TOP - koff;
+ link_child(a, v);
+ idx = idx + 1;
+ j_ws(s);
+ if (J_POS >= J_END) {
+ J_ERR = true;
+ return -1;
+ }
+ const c : u8 = s[J_POS];
+ if (c == 44) {
+ J_POS = J_POS + 1;
+ } else {
+ if (c == 93) {
+ J_POS = J_POS + 1;
+ return a;
+ }
+ J_ERR = true;
+ return -1;
+ }
+ }
+ J_ERR = true;
+ return -1;
+}
+
+pub fn j_value(s: []const u8, depth: u32) -> i32 {
+ if (depth > 256) {
+ J_ERR = true;
+ return -1;
+ }
+ j_ws(s);
+ if (J_POS >= J_END) {
+ J_ERR = true;
+ return -1;
+ }
+ const b : u8 = s[J_POS];
+ if (b == 123) { return j_object(s, depth); }
+ if (b == 91) { return j_array(s, depth); }
+ if (b == 34) {
+ const n : i32 = new_node(J_STR);
+ if (n < 0) { return -1; }
+ if (!j_string(s)) {
+ J_ERR = true;
+ return -1;
+ }
+ N_SOFF[(n as usize)] = J_SOFF;
+ N_SLEN[(n as usize)] = J_SLEN;
+ return n;
+ }
+ if (b == 116) {
+ if (!lit_at(s, "true")) {
+ J_ERR = true;
+ return -1;
+ }
+ J_POS = J_POS + 4;
+ return new_node(J_TRUE);
+ }
+ if (b == 102) {
+ if (!lit_at(s, "false")) {
+ J_ERR = true;
+ return -1;
+ }
+ J_POS = J_POS + 5;
+ return new_node(J_FALSE);
+ }
+ if (b == 110) {
+ if (!lit_at(s, "null")) {
+ J_ERR = true;
+ return -1;
+ }
+ J_POS = J_POS + 4;
+ return new_node(J_NULL);
+ }
+ if (b == 45 or is_digit(b)) {
+ const x : f64 = j_number(s);
+ if (J_ERR) { return -1; }
+ const n2 : i32 = new_node(J_NUM);
+ if (n2 < 0) { return -1; }
+ N_NUM[(n2 as usize)] = x;
+ return n2;
+ }
+ J_ERR = true;
+ return -1;
+}
+
+/// JSON.parse of s[lo .. hi]: the root node, or -1.
+pub fn j_parse(s: []const u8, lo: usize, hi: usize) -> i32 {
+ NODE_TOP = 0;
+ POOL_TOP = 0;
+ J_ERR = false;
+ J_POS = lo;
+ J_END = hi;
+ const v : i32 = j_value(s, 0);
+ if (v < 0 or J_ERR) { return -1; }
+ j_ws(s);
+ if (J_POS != J_END) { return -1; }
+ return v;
+}
+
+pub fn key_is(n: usize, w: []const u8) -> bool {
+ return pool_eq(N_KOFF[n], N_KLEN[n], w);
+}
+
+/// The member of object/array node n called key, or -1.
+pub fn find_child(n: i32, key: []const u8) -> i32 {
+ var c : i32 = N_FIRST[(n as usize)];
+ while (c >= 0) {
+ if (key_is((c as usize), key)) { return c; }
+ c = N_NEXT[(c as usize)];
+ }
+ return -1;
+}
+
+pub fn is_objlike(n: i32) -> bool {
+ if (n < 0) { return false; }
+ const k : u8 = N_KIND[(n as usize)];
+ return k == J_OBJ or k == J_ARR;
+}
+
+// --- Number(raw) || 0 --------------------------------------------------------------------------
+/// Number(string) for a plain decimal string (the rest reads as 0 here: see the header).
+pub fn str_num(off: usize, len: usize) -> f64 {
+ var lo : usize = off;
+ var hi : usize = off + len;
+ while (lo < hi) {
+ const w : usize = pool_ws_at(lo, hi);
+ if (w == 0) { break; }
+ lo = lo + w;
+ }
+ while (hi > lo) {
+ const w2 : usize = pool_ws_before(lo, hi);
+ if (w2 == 0) { break; }
+ hi = hi - w2;
+ }
+ if (hi == lo) { return 0.0; }
+ var p : usize = lo;
+ var neg : bool = false;
+ if (POOL[p] == 43 or POOL[p] == 45) {
+ neg = POOL[p] == 45;
+ p = p + 1;
+ }
+ if (pool_eq(p, hi - p, "Infinity")) {
+ if (neg) { return -pow10(400); }
+ return pow10(400);
+ }
+ var m : f64 = 0.0;
+ var nd : usize = 0;
+ while (p < hi and is_digit(POOL[p])) {
+ m = m * 10.0 + (((POOL[p] - 48) as u32) as f64);
+ nd = nd + 1;
+ p = p + 1;
+ }
+ var fd : i32 = 0;
+ if (p < hi and POOL[p] == 46) {
+ p = p + 1;
+ while (p < hi and is_digit(POOL[p])) {
+ m = m * 10.0 + (((POOL[p] - 48) as u32) as f64);
+ nd = nd + 1;
+ fd = fd + 1;
+ p = p + 1;
+ }
+ }
+ if (nd == 0) { return 0.0; }
+ var ex : i32 = 0;
+ if (p < hi and (POOL[p] == 101 or POOL[p] == 69)) {
+ p = p + 1;
+ var eneg : bool = false;
+ if (p < hi and (POOL[p] == 43 or POOL[p] == 45)) {
+ eneg = POOL[p] == 45;
+ p = p + 1;
+ }
+ const st : usize = p;
+ while (p < hi and is_digit(POOL[p])) {
+ if (ex < 100000) { ex = ex * 10 + (((POOL[p] - 48) as u32) as i32); }
+ p = p + 1;
+ }
+ if (p == st) { return 0.0; }
+ if (eneg) { ex = -ex; }
+ }
+ if (p != hi) { return 0.0; }
+ const v : f64 = dec_value(m, fd, ex);
+ if (neg) { return -v; }
+ return v;
+}
+
+pub fn num_of(v: i32) -> f64 {
+ const vu : usize = v as usize;
+ const k : u8 = N_KIND[vu];
+ if (k == J_NUM) {
+ const x : f64 = N_NUM[vu];
+ if (x == 0.0) { return 0.0; }
+ return x;
+ }
+ if (k == J_TRUE) { return 1.0; }
+ if (k == J_STR) { return str_num(N_SOFF[vu], N_SLEN[vu]); }
+ return 0.0;
+}
+
+// --- Cell type groups, the names, display names ------------------------------------------------
+/// 0 lut, 1 ff, 2 dsp, 3 bram, 4 other, 5 internal (counted nowhere), 6 an Object.prototype member
+/// (JS drops the count: group[type] is a function there).
+pub fn group_of(off: usize, len: usize) -> u8 {
+ if (pool_eq(off, len, "LUT1") or pool_eq(off, len, "LUT2") or pool_eq(off, len, "LUT3") or pool_eq(off, len, "LUT4") or pool_eq(off, len, "LUT5") or pool_eq(off, len, "LUT6")) { return 0; }
+ if (pool_eq(off, len, "FDRE") or pool_eq(off, len, "FDSE") or pool_eq(off, len, "FDCE") or pool_eq(off, len, "FDPE")) { return 1; }
+ if (pool_eq(off, len, "DSP48E1")) { return 2; }
+ if (pool_eq(off, len, "RAMB18E1") or pool_eq(off, len, "RAMB36E1")) { return 3; }
+ if (pool_eq(off, len, "$scopeinfo")) { return 5; }
+ if (pool_eq(off, len, "constructor") or pool_eq(off, len, "__defineGetter__") or pool_eq(off, len, "__defineSetter__") or pool_eq(off, len, "hasOwnProperty") or pool_eq(off, len, "__lookupGetter__") or pool_eq(off, len, "__lookupSetter__") or pool_eq(off, len, "isPrototypeOf") or pool_eq(off, len, "propertyIsEnumerable") or pool_eq(off, len, "toString") or pool_eq(off, len, "valueOf") or pool_eq(off, len, "__proto__") or pool_eq(off, len, "toLocaleString")) { return 6; }
+ return 4;
+}
+
+pub fn name_find(off: usize, len: usize) -> i32 {
+ var i : usize = 0;
+ while (i < NM_N) {
+ if (NM_LEN[i] == len and pool_same(NM_OFF[i], off, len)) { return (i as i32); }
+ i = i + 1;
+ }
+ return -1;
+}
+
+pub fn push_copy(off: usize, len: usize) {
+ var k : usize = 0;
+ while (k < len) {
+ pool_push(POOL[off + k]);
+ k = k + 1;
+ }
+}
+
+/// displayName of POOL[off .. off+len] (already bare): the result is appended to POOL at D_OFF, D_LEN long.
+pub fn disp_build(off: usize, len: usize) {
+ D_OFF = POOL_TOP;
+ if (!pool_prefix(off, len, "$paramod")) {
+ push_copy(off, len);
+ D_LEN = POOL_TOP - D_OFF;
+ return;
+ }
+ const end : usize = off + len;
+ // parts[0] ends at the first backslash; none: the whole name stands for itself
+ var p : usize = off;
+ while (p < end and POOL[p] != B_BSLASH) { p = p + 1; }
+ if (p >= end) {
+ push_copy(off, len);
+ D_LEN = POOL_TOP - D_OFF;
+ return;
+ }
+ p = p + 1;
+ const bstart : usize = p;
+ while (p < end and POOL[p] != B_BSLASH) { p = p + 1; }
+ push_copy(bstart, p - bstart);
+ while (p < end) {
+ // p is on a backslash: the next part runs to the next backslash or the end
+ p = p + 1;
+ const ps : usize = p;
+ while (p < end and POOL[p] != B_BSLASH) { p = p + 1; }
+ pool_push(32);
+ disp_param(ps, p - ps);
+ }
+ D_LEN = POOL_TOP - D_OFF;
+}
+
+/// One parameter part: "name=s32'0101" -> "name=5"; any other text stays as it is.
+pub fn disp_param(off: usize, len: usize) {
+ const end : usize = off + len;
+ var e : usize = off;
+ while (e < end and POOL[e] != 61) { e = e + 1; }
+ if (e >= end or e == off) {
+ push_copy(off, len);
+ return;
+ }
+ var q : usize = e + 1;
+ var signed : bool = false;
+ if (q < end and POOL[q] == 115) {
+ signed = true;
+ q = q + 1;
+ }
+ const d0 : usize = q;
+ while (q < end and is_digit(POOL[q])) { q = q + 1; }
+ if (q == d0 or q >= end or POOL[q] != 39) {
+ push_copy(off, len);
+ return;
+ }
+ q = q + 1;
+ const b0 : usize = q;
+ while (q < end and (POOL[q] == 48 or POOL[q] == 49)) { q = q + 1; }
+ if (q != end or q == b0) {
+ push_copy(off, len);
+ return;
+ }
+ const nb : usize = end - b0;
+ var v : f64 = 0.0;
+ var k : usize = 0;
+ while (k < nb) {
+ v = v * 2.0 + (((POOL[b0 + k] - 48) as u32) as f64);
+ k = k + 1;
+ }
+ if (signed and nb > 1 and POOL[b0] == 49) {
+ var w : f64 = 1.0;
+ var j : usize = 0;
+ while (j < nb) {
+ w = w * 2.0;
+ j = j + 1;
+ }
+ v = v - w;
+ }
+ push_copy(off, e - off);
+ pool_push(61);
+ if (v < 0.0) {
+ pool_push(45);
+ v = -v;
+ }
+ pool_push_uint(v as u64);
+}
+
+pub fn name_add(off: usize, len: usize) -> i32 {
+ if (NM_N >= 4096) {
+ LT_OVER = true;
+ return -1;
+ }
+ const i : usize = NM_N;
+ NM_N = NM_N + 1;
+ NM_OFF[i] = off;
+ NM_LEN[i] = len;
+ disp_build(off, len);
+ DP_OFF[i] = D_OFF;
+ DP_LEN[i] = D_LEN;
+ return (i as i32);
+}
+
+/// bare(): the number of leading bytes dropped, 1 for a leading backslash, else 0.
+pub fn bare_skip(name: []const u8) -> u32 {
+ if (name.len > 0 and name[0] == B_BSLASH) { return 1; }
+ return 0;
+}
+
+/// displayName(name) on a caller's text: the display bytes are read with display_byte(k).
+pub fn display_name(name: []const u8) -> u32 {
+ const save : usize = POOL_TOP;
+ const src : usize = POOL_TOP;
+ var k : usize = 0;
+ while (k < name.len) {
+ pool_push(name[k]);
+ k = k + 1;
+ }
+ var off : usize = src;
+ var len : usize = name.len;
+ if (len > 0 and POOL[off] == B_BSLASH) {
+ off = off + 1;
+ len = len - 1;
+ }
+ disp_build(off, len);
+ const r : u32 = D_LEN as u32;
+ POOL_TOP = save;
+ return r;
+}
+
+pub fn display_byte(k: usize) -> u8 {
+ return POOL[D_OFF + k];
+}
+
+// --- parseHier ---------------------------------------------------------------------------------
+pub fn reset_all() {
+ var i : usize = 0;
+ while (i < 4096) {
+ HASMOD[i] = 0;
+ USED[i] = 0;
+ TOT_DONE[i] = 0;
+ ON_STACK[i] = 0;
+ SEEN[i] = 0;
+ INST_IN[i] = 0;
+ SUB_START[i] = 0;
+ SUB_N[i] = 0;
+ i = i + 1;
+ }
+ NM_N = 0;
+ SUB_TOP = 0;
+ ORDER_N = 0;
+ R_ORDER_N = 0;
+ ROOTS_N = 0;
+ TOP = 0;
+ FLAT = false;
+ HAS_DESIGN = false;
+ KEY_KIND = 0;
+ HAS_CREATOR = false;
+ HAS_VERSION = false;
+ CR_OFF = 0;
+ CR_LEN = 0;
+ VER_OFF = 0;
+ VER_LEN = 0;
+ LT_OVER = false;
+ ROW_N = 0;
+ POST_N = 0;
+ T_N = 0;
+ T_FAIL = false;
+ R_N = 0;
+ LEAF_N = 0;
+ NODE_TOP = 0;
+ POOL_TOP = 0;
+ P_TOP = 0;
+ KS_TOP = 0;
+}
+
+/// byTypeOf(m): the cell-type map node of module node m (num_cells_by_type, else cells), or -1.
+/// KEY_KIND is NOT set here.
+pub fn by_type(m: i32) -> i32 {
+ if (!is_objlike(m)) { return -1; }
+ const a : i32 = find_child(m, "num_cells_by_type");
+ if (is_objlike(a)) { return a; }
+ const b : i32 = find_child(m, "cells");
+ if (is_objlike(b)) { return b; }
+ return -1;
+}
+
+pub fn by_type_kind(m: i32) -> u32 {
+ const a : i32 = find_child(m, "num_cells_by_type");
+ if (is_objlike(a)) { return 1; }
+ return 2;
+}
+
+/// splitTypes over the map node: the own cells go to ACC[0..5]; submodule entries are appended to
+/// the sub table when keep_subs.
+pub fn split_types(map: i32, keep_subs: bool) {
+ ACC[0] = 0.0;
+ ACC[1] = 0.0;
+ ACC[2] = 0.0;
+ ACC[3] = 0.0;
+ ACC[4] = 0.0;
+ var c : i32 = N_FIRST[(map as usize)];
+ while (c >= 0) {
+ const cu : usize = c as usize;
+ const n : f64 = num_of(c);
+ const off : usize = N_KOFF[cu];
+ const len : usize = N_KLEN[cu];
+ var boff : usize = off;
+ var blen : usize = len;
+ if (len > 0 and POOL[off] == B_BSLASH) {
+ boff = off + 1;
+ blen = len - 1;
+ }
+ const sub : i32 = name_find(boff, blen);
+ if (sub >= 0) {
+ if (keep_subs) {
+ if (SUB_TOP >= 32768) {
+ LT_OVER = true;
+ } else {
+ SUB_NID[SUB_TOP] = sub as usize;
+ SUB_CNT[SUB_TOP] = n;
+ SUB_TOP = SUB_TOP + 1;
+ }
+ }
+ } else {
+ const g : u8 = group_of(off, len);
+ if (g <= 4) { ACC[(g as usize)] = ACC[(g as usize)] + n; }
+ }
+ c = N_NEXT[cu];
+ }
+}
+
+pub fn sum_of(a0: f64, a1: f64, a2: f64, a3: f64, a4: f64) -> f64 {
+ return ((((0.0 + a0) + a1) + a2) + a3) + a4;
+}
+
+pub fn same_totals(a0: f64, a1: f64, a2: f64, a3: f64, a4: f64, b0: f64, b1: f64, b2: f64, b3: f64, b4: f64) -> bool {
+ return a0 == b0 and a1 == b1 and a2 == b2 and a3 == b3 and a4 == b4;
+}
+
+pub fn totals(n: usize) {
+ if (TOT_DONE[n] == 1) { return; }
+ if (HASMOD[n] == 0 or ON_STACK[n] == 1) { return; }
+ ON_STACK[n] = 1;
+ var o0 : f64 = OWN[n * 5];
+ var o1 : f64 = OWN[n * 5 + 1];
+ var o2 : f64 = OWN[n * 5 + 2];
+ var o3 : f64 = OWN[n * 5 + 3];
+ var o4 : f64 = OWN[n * 5 + 4];
+ var i : usize = 0;
+ while (i < SUB_N[n]) {
+ const si : usize = SUB_START[n] + i;
+ const ch : usize = SUB_NID[si];
+ const cnt : f64 = SUB_CNT[si];
+ totals(ch);
+ var t0 : f64 = 0.0;
+ var t1 : f64 = 0.0;
+ var t2 : f64 = 0.0;
+ var t3 : f64 = 0.0;
+ var t4 : f64 = 0.0;
+ if (TOT_DONE[ch] == 1) {
+ t0 = TOT[ch * 5];
+ t1 = TOT[ch * 5 + 1];
+ t2 = TOT[ch * 5 + 2];
+ t3 = TOT[ch * 5 + 3];
+ t4 = TOT[ch * 5 + 4];
+ }
+ o0 = o0 + cnt * t0;
+ o1 = o1 + cnt * t1;
+ o2 = o2 + cnt * t2;
+ o3 = o3 + cnt * t3;
+ o4 = o4 + cnt * t4;
+ i = i + 1;
+ }
+ ON_STACK[n] = 0;
+ TOT[n * 5] = o0;
+ TOT[n * 5 + 1] = o1;
+ TOT[n * 5 + 2] = o2;
+ TOT[n * 5 + 3] = o3;
+ TOT[n * 5 + 4] = o4;
+ TOT_DONE[n] = 1;
+}
+
+/// totalsOf(r, name)[k]: the cells under one instance of the module, submodules expanded.
+pub fn totals_of(n: usize, k: usize) -> f64 {
+ totals(n);
+ if (TOT_DONE[n] == 1) { return TOT[n * 5 + k]; }
+ return 0.0;
+}
+
+pub fn same_as_design(n: usize) -> bool {
+ if (!HAS_DESIGN) { return false; }
+ return same_totals(totals_of(n, 0), totals_of(n, 1), totals_of(n, 2), totals_of(n, 3), totals_of(n, 4), DESIGN[0], DESIGN[1], DESIGN[2], DESIGN[3], DESIGN[4]);
+}
+
+/// /^Yosys\s+(\S+)/ on the creator string: sets VER_OFF/VER_LEN/HAS_VERSION.
+pub fn find_version() {
+ HAS_VERSION = false;
+ const hi : usize = CR_OFF + CR_LEN;
+ if (!pool_prefix(CR_OFF, CR_LEN, "Yosys")) { return; }
+ var p : usize = CR_OFF + 5;
+ const w0 : usize = p;
+ while (p < hi) {
+ const w : usize = pool_ws_at(p, hi);
+ if (w == 0) { break; }
+ p = p + w;
+ }
+ if (p == w0) { return; }
+ const v0 : usize = p;
+ while (p < hi) {
+ const w2 : usize = pool_ws_at(p, hi);
+ if (w2 > 0) { break; }
+ p = p + 1;
+ }
+ if (p == v0) { return; }
+ VER_OFF = v0;
+ VER_LEN = p - v0;
+ HAS_VERSION = true;
+}
+
+/// parseHier(text, lists): the status code (0 ok, see the header).
+pub fn parse_hier(s: []const u8) -> u32 {
+ reset_all();
+ var lo : usize = 0;
+ var hi : usize = s.len;
+ while (lo < hi) {
+ const w : usize = ws_at(s, lo, hi);
+ if (w == 0) { break; }
+ lo = lo + w;
+ }
+ while (hi > lo) {
+ const w2 : usize = ws_before(s, lo, hi);
+ if (w2 == 0) { break; }
+ hi = hi - w2;
+ }
+ if (hi == lo) { return E_EMPTY; }
+ var root : i32 = j_parse(s, lo, hi);
+ if (LT_OVER) { return E_TOOLARGE; }
+ if (root < 0) {
+ var a : i64 = -1;
+ var b : i64 = -1;
+ var p : usize = lo;
+ while (p < hi) {
+ if (s[p] == 123 and a < 0) { a = p as i64; }
+ if (s[p] == 125) { b = p as i64; }
+ p = p + 1;
+ }
+ if (a < 0 or b <= a) { return E_NOJSON; }
+ root = j_parse(s, a as usize, (b as usize) + 1);
+ if (LT_OVER) { return E_TOOLARGE; }
+ if (root < 0) { return E_BADJSON; }
+ }
+ if (!is_objlike(root)) { return E_NOMODULES; }
+ const mods : i32 = find_child(root, "modules");
+ if (!is_objlike(mods)) { return E_NOMODULES; }
+ const cr : i32 = find_child(root, "creator");
+ if (cr >= 0 and N_KIND[(cr as usize)] == J_STR) {
+ HAS_CREATOR = true;
+ CR_OFF = N_SOFF[(cr as usize)];
+ CR_LEN = N_SLEN[(cr as usize)];
+ find_version();
+ }
+ // order: every key of modules, bare, duplicates kept; the names become ids
+ var c : i32 = N_FIRST[(mods as usize)];
+ while (c >= 0) {
+ const cu : usize = c as usize;
+ var boff : usize = N_KOFF[cu];
+ var blen : usize = N_KLEN[cu];
+ if (blen > 0 and POOL[boff] == B_BSLASH) {
+ boff = boff + 1;
+ blen = blen - 1;
+ }
+ var nid : i32 = name_find(boff, blen);
+ if (nid < 0) { nid = name_add(boff, blen); }
+ if (nid < 0 or ORDER_N >= 4096) { return E_TOOLARGE; }
+ ORDER[ORDER_N] = nid as usize;
+ ORDER_N = ORDER_N + 1;
+ c = N_NEXT[cu];
+ }
+ // modules: the cells of each entry that has a cell map; a later entry of the same name replaces
+ var any : bool = false;
+ c = N_FIRST[(mods as usize)];
+ var ei : usize = 0;
+ while (c >= 0) {
+ const cu2 : usize = c as usize;
+ const map : i32 = by_type(c);
+ if (map >= 0) {
+ if (KEY_KIND == 0) { KEY_KIND = by_type_kind(c); }
+ const nid2 : usize = ORDER[ei];
+ const start : usize = SUB_TOP;
+ split_types(map, true);
+ if (LT_OVER) { return E_TOOLARGE; }
+ HASMOD[nid2] = 1;
+ any = true;
+ OWN[nid2 * 5] = ACC[0];
+ OWN[nid2 * 5 + 1] = ACC[1];
+ OWN[nid2 * 5 + 2] = ACC[2];
+ OWN[nid2 * 5 + 3] = ACC[3];
+ OWN[nid2 * 5 + 4] = ACC[4];
+ SUB_START[nid2] = start;
+ SUB_N[nid2] = SUB_TOP - start;
+ }
+ ei = ei + 1;
+ c = N_NEXT[cu2];
+ }
+ if (!any) { return E_NOCELLS; }
+ var i : usize = 0;
+ while (i < NM_N) {
+ if (HASMOD[i] == 1) {
+ var j : usize = 0;
+ while (j < SUB_N[i]) {
+ USED[SUB_NID[SUB_START[i] + j]] = 1;
+ j = j + 1;
+ }
+ }
+ i = i + 1;
+ }
+ i = 0;
+ while (i < ORDER_N) {
+ const n : usize = ORDER[i];
+ if (HASMOD[n] == 1) {
+ R_ORDER[R_ORDER_N] = n;
+ R_ORDER_N = R_ORDER_N + 1;
+ if (USED[n] == 0) {
+ ROOTS[ROOTS_N] = n;
+ ROOTS_N = ROOTS_N + 1;
+ }
+ }
+ i = i + 1;
+ }
+ if (ROOTS_N == 0) { return E_NOTOP; }
+ const d : i32 = find_child(root, "design");
+ if (is_objlike(d)) {
+ const dm : i32 = by_type(d);
+ if (dm >= 0) {
+ split_types(dm, false);
+ HAS_DESIGN = true;
+ DESIGN[0] = ACC[0];
+ DESIGN[1] = ACC[1];
+ DESIGN[2] = ACC[2];
+ DESIGN[3] = ACC[3];
+ DESIGN[4] = ACC[4];
+ }
+ }
+ FLAT = true;
+ i = 0;
+ while (i < NM_N) {
+ if (HASMOD[i] == 1 and SUB_N[i] != 0) { FLAT = false; }
+ i = i + 1;
+ }
+ TOP = ROOTS[0];
+ if (ROOTS_N > 1) {
+ var found : bool = false;
+ if (HAS_DESIGN) {
+ i = 0;
+ while (i < ROOTS_N and !found) {
+ if (same_as_design(ROOTS[i])) {
+ TOP = ROOTS[i];
+ found = true;
+ }
+ i = i + 1;
+ }
+ }
+ if (!found) {
+ var best : f64 = 0.0;
+ i = 0;
+ while (i < ROOTS_N) {
+ const rn : usize = ROOTS[i];
+ const sm : f64 = sum_of(totals_of(rn, 0), totals_of(rn, 1), totals_of(rn, 2), totals_of(rn, 3), totals_of(rn, 4));
+ if (i == 0 or sm > best) {
+ best = sm;
+ TOP = rn;
+ }
+ i = i + 1;
+ }
+ }
+ }
+ return E_OK;
+}
+
+// --- getters for the glue ----------------------------------------------------------------------
+pub fn key_kind() -> u32 { return KEY_KIND; }
+pub fn name_n() -> usize { return NM_N; }
+pub fn name_off(n: usize) -> usize { return NM_OFF[n]; }
+pub fn name_len(n: usize) -> usize { return NM_LEN[n]; }
+pub fn disp_off(n: usize) -> usize { return DP_OFF[n]; }
+pub fn disp_len(n: usize) -> usize { return DP_LEN[n]; }
+pub fn has_module(n: usize) -> bool { return HASMOD[n] == 1; }
+pub fn own(n: usize, k: usize) -> f64 { return OWN[n * 5 + k]; }
+pub fn sub_n(n: usize) -> usize { return SUB_N[n]; }
+pub fn sub_nid(n: usize, i: usize) -> usize { return SUB_NID[SUB_START[n] + i]; }
+pub fn sub_count(n: usize, i: usize) -> f64 { return SUB_CNT[SUB_START[n] + i]; }
+pub fn order_n() -> usize { return R_ORDER_N; }
+pub fn order_nid(i: usize) -> usize { return R_ORDER[i]; }
+pub fn top_nid() -> usize { return TOP; }
+pub fn roots_n() -> usize { return ROOTS_N; }
+pub fn root_nid(i: usize) -> usize { return ROOTS[i]; }
+pub fn is_flat() -> bool { return FLAT; }
+pub fn has_design() -> bool { return HAS_DESIGN; }
+pub fn design_val(k: usize) -> f64 { return DESIGN[k]; }
+pub fn has_creator() -> bool { return HAS_CREATOR; }
+pub fn creator_off() -> usize { return CR_OFF; }
+pub fn creator_len() -> usize { return CR_LEN; }
+pub fn has_version() -> bool { return HAS_VERSION; }
+pub fn version_off() -> usize { return VER_OFF; }
+pub fn version_len() -> usize { return VER_LEN; }
+
+// --- instancesOf and moduleRows ----------------------------------------------------------------
+pub fn inst_visit(n: usize) {
+ if (SEEN[n] == 1 or HASMOD[n] == 0) { return; }
+ SEEN[n] = 1;
+ var i : usize = 0;
+ while (i < SUB_N[n]) {
+ inst_visit(SUB_NID[SUB_START[n] + i]);
+ i = i + 1;
+ }
+ POST[POST_N] = n;
+ POST_N = POST_N + 1;
+}
+
+/// How many instances of each module sit under one top, all paths added up.
+pub fn instances_of(top: usize) {
+ var i : usize = 0;
+ while (i < NM_N) {
+ SEEN[i] = 0;
+ INST_IN[i] = 0;
+ INST[i] = 0.0;
+ i = i + 1;
+ }
+ POST_N = 0;
+ inst_visit(top);
+ i = 0;
+ while (i < POST_N) {
+ INST_IN[POST[i]] = 1;
+ i = i + 1;
+ }
+ INST_IN[top] = 1;
+ INST[top] = 1.0;
+ var j : usize = POST_N;
+ while (j > 0) {
+ const n : usize = POST[j - 1];
+ var k : usize = 0;
+ while (k < SUB_N[n]) {
+ const si : usize = SUB_START[n] + k;
+ const ch : usize = SUB_NID[si];
+ if (INST_IN[ch] == 1) { INST[ch] = INST[ch] + INST[n] * SUB_CNT[si]; }
+ k = k + 1;
+ }
+ j = j - 1;
+ }
+}
+
+pub fn inst_in(n: usize) -> bool { return INST_IN[n] == 1; }
+pub fn inst_of(n: usize) -> f64 { return INST[n]; }
+
+/// moduleRows(r): one row per entry of r.order that sits under the top. Returns the row count.
+pub fn module_rows() -> usize {
+ instances_of(TOP);
+ ROW_N = 0;
+ var i : usize = 0;
+ while (i < R_ORDER_N) {
+ const n : usize = R_ORDER[i];
+ if (INST_IN[n] == 1) {
+ ROW_NID[ROW_N] = n;
+ ROW_N = ROW_N + 1;
+ }
+ i = i + 1;
+ }
+ return ROW_N;
+}
+
+pub fn row_nid(i: usize) -> usize { return ROW_NID[i]; }
+pub fn row_instances(i: usize) -> f64 { return INST[ROW_NID[i]]; }
+pub fn row_total(i: usize, k: usize) -> f64 { return INST[ROW_NID[i]] * OWN[ROW_NID[i] * 5 + k]; }
+
+// --- viewTree ----------------------------------------------------------------------------------
+pub fn per(n: usize) -> f64 {
+ return totals_of(n, T_METRIC);
+}
+
+pub fn tile_new(nid: usize, copies: f64, index: f64, of: f64, is_own: bool, value: f64, parent: i32) -> i32 {
+ if (T_N >= 32768) {
+ T_FAIL = true;
+ return -1;
+ }
+ const id : usize = T_N;
+ T_N = T_N + 1;
+ T_NID[id] = nid;
+ T_COPIES[id] = copies;
+ T_INDEX[id] = index;
+ T_OF[id] = of;
+ if (is_own) { T_OWN[id] = 1; } else { T_OWN[id] = 0; }
+ T_VALUE[id] = value;
+ T_PARENT[id] = parent;
+ T_FIRST[id] = -1;
+ T_LAST[id] = -1;
+ T_NEXT[id] = -1;
+ if (parent >= 0) {
+ const pu : usize = parent as usize;
+ if (T_LAST[pu] < 0) {
+ T_FIRST[pu] = id as i32;
+ } else {
+ T_NEXT[(T_LAST[pu] as usize)] = id as i32;
+ }
+ T_LAST[pu] = id as i32;
+ }
+ return (id as i32);
+}
+
+pub fn tile_build(nid: usize, copies: f64, index: f64, of: f64, parent: i32, depth: u32) -> i32 {
+ const id : i32 = tile_new(nid, copies, index, of, false, copies * per(nid), parent);
+ if (id < 0) { return -1; }
+ T_BUDGET = T_BUDGET - 1.0;
+ if (depth > 128) {
+ T_FAIL = true;
+ return id;
+ }
+ // kids: subs with a count above 0 and cells of this metric, widest first, ties in yosys order
+ const base : usize = KS_TOP;
+ var nk : usize = 0;
+ var i : usize = 0;
+ while (i < SUB_N[nid]) {
+ const si : usize = SUB_START[nid] + i;
+ const ch : usize = SUB_NID[si];
+ if (SUB_CNT[si] > 0.0 and per(ch) > 0.0) {
+ if (base + nk >= 32768) {
+ T_FAIL = true;
+ return id;
+ }
+ const key : f64 = SUB_CNT[si] * per(ch);
+ var j : usize = base + nk;
+ while (j > base) {
+ const pj : usize = KS[j - 1];
+ const pk : f64 = SUB_CNT[pj] * per(SUB_NID[pj]);
+ if (pk < key) {
+ KS[j] = KS[j - 1];
+ j = j - 1;
+ } else {
+ break;
+ }
+ }
+ KS[j] = si;
+ nk = nk + 1;
+ }
+ i = i + 1;
+ }
+ if (nk == 0) { return id; }
+ KS_TOP = base + nk;
+ if (OWN[nid * 5 + T_METRIC] > 0.0) {
+ tile_new(nid, copies, 0.0, 0.0, true, copies * OWN[nid * 5 + T_METRIC], id);
+ T_BUDGET = T_BUDGET - 1.0;
+ }
+ var q : usize = 0;
+ while (q < nk) {
+ const sj : usize = KS[base + q];
+ const cn : usize = SUB_NID[sj];
+ const cnt : f64 = SUB_CNT[sj];
+ var each : bool = false;
+ if (copies == 1.0 and cnt <= T_EXPAND) {
+ if (T_BUDGET - cnt * (1.0 + (SUB_N[cn] as f64)) >= 0.0) { each = true; }
+ }
+ if (each) {
+ var ii : f64 = 1.0;
+ while (ii <= cnt) {
+ tile_build(cn, 1.0, ii, cnt, id, depth + 1);
+ ii = ii + 1.0;
+ }
+ } else {
+ tile_build(cn, copies * cnt, 0.0, cnt, id, depth + 1);
+ }
+ q = q + 1;
+ }
+ KS_TOP = base;
+ return id;
+}
+
+/// viewTree(r, metric, { expandMax, maxTiles }): the tile count; tile 0 is the root.
+pub fn view_tree(metric: usize, expand_max: f64, max_tiles: f64) -> usize {
+ T_N = 0;
+ T_FAIL = false;
+ KS_TOP = 0;
+ T_METRIC = metric;
+ T_EXPAND = expand_max;
+ T_BUDGET = max_tiles;
+ tile_build(TOP, 1.0, 0.0, 0.0, -1, 0);
+ return T_N;
+}
+
+pub fn view_tree_failed() -> bool { return T_FAIL; }
+pub fn tile_nid(i: usize) -> usize { return T_NID[i]; }
+pub fn tile_copies(i: usize) -> f64 { return T_COPIES[i]; }
+pub fn tile_index(i: usize) -> f64 { return T_INDEX[i]; }
+pub fn tile_of(i: usize) -> f64 { return T_OF[i]; }
+pub fn tile_own(i: usize) -> bool { return T_OWN[i] == 1; }
+pub fn tile_value(i: usize) -> f64 { return T_VALUE[i]; }
+pub fn tile_parent(i: usize) -> i32 { return T_PARENT[i]; }
+pub fn tile_first(i: usize) -> i32 { return T_FIRST[i]; }
+pub fn tile_next(i: usize) -> i32 { return T_NEXT[i]; }
+
+pub fn leaves_walk(id: usize) {
+ if (T_FIRST[id] < 0) {
+ LEAF[LEAF_N] = id;
+ LEAF_N = LEAF_N + 1;
+ return;
+ }
+ var c : i32 = T_FIRST[id];
+ while (c >= 0) {
+ leaves_walk(c as usize);
+ c = T_NEXT[(c as usize)];
+ }
+}
+
+/// leavesOf(root): the leaf tiles in order; returns how many.
+pub fn leaves_n() -> usize {
+ LEAF_N = 0;
+ if (T_N > 0) { leaves_walk(0); }
+ return LEAF_N;
+}
+
+pub fn leaf_id(i: usize) -> usize { return LEAF[i]; }
+
+/// The leaves' values added left to right, as the page does with reduce.
+pub fn leaves_sum() -> f64 {
+ leaves_n();
+ var s : f64 = 0.0;
+ var i : usize = 0;
+ while (i < LEAF_N) {
+ s = s + T_VALUE[LEAF[i]];
+ i = i + 1;
+ }
+ return s;
+}
+
+// --- squarify (Bruls, Huizing, van Wijk 2000) --------------------------------------------------
+pub fn fmax(a: f64, b: f64) -> f64 {
+ if (b > a) { return b; }
+ return a;
+}
+
+/// worst(row, side) over the rest entries a .. b-1.
+pub fn sq_worst(a: usize, b: usize, side: f64) -> f64 {
+ var s : f64 = 0.0;
+ var i : usize = a;
+ while (i < b) {
+ s = s + RS_A[i];
+ i = i + 1;
+ }
+ var mx : f64 = 0.0;
+ i = a;
+ while (i < b) {
+ const ar : f64 = RS_A[i];
+ mx = fmax(fmax(mx, (side * side * ar) / (s * s)), (s * s) / (side * side * ar));
+ i = i + 1;
+ }
+ return mx;
+}
+
+pub fn sq_set(i: usize, v: f64) {
+ SQ_V[i] = v;
+ SQ_ID[i] = i;
+}
+
+/// The core: items SQ_V[0..n) (ids SQ_ID) into the rectangle; the placements are pushed on the
+/// P stack (from the old P_TOP); returns how many.
+pub fn sq_core(n: usize, x0: f64, y0: f64, w0: f64, h0: f64) -> usize {
+ var x : f64 = x0;
+ var y : f64 = y0;
+ var w : f64 = w0;
+ var h : f64 = h0;
+ var total : f64 = 0.0;
+ var i : usize = 0;
+ while (i < n) {
+ total = total + SQ_V[i];
+ i = i + 1;
+ }
+ if (total <= 0.0 or w <= 0.0 or h <= 0.0) { return 0; }
+ const scale : f64 = (w * h) / total;
+ var nr : usize = 0;
+ i = 0;
+ while (i < n) {
+ if (SQ_V[i] > 0.0) {
+ const ar : f64 = SQ_V[i] * scale;
+ var j : usize = nr;
+ while (j > 0) {
+ if (RS_A[j - 1] < ar) {
+ RS_A[j] = RS_A[j - 1];
+ RS_I[j] = RS_I[j - 1];
+ j = j - 1;
+ } else {
+ break;
+ }
+ }
+ RS_A[j] = ar;
+ RS_I[j] = i;
+ nr = nr + 1;
+ }
+ i = i + 1;
+ }
+ const first : usize = P_TOP;
+ var rs : usize = 0;
+ var ri : usize = 0;
+ while (ri < nr) {
+ const side : f64 = if_min(w, h);
+ var take : bool = false;
+ if (ri == rs) {
+ take = true;
+ } else {
+ if (sq_worst(rs, ri + 1, side) <= sq_worst(rs, ri, side)) { take = true; }
+ }
+ if (take) {
+ ri = ri + 1;
+ } else {
+ sq_place(rs, ri, x, y, w, h);
+ const s : f64 = sq_sum(rs, ri);
+ if (w >= h) {
+ const cw : f64 = s / h;
+ x = x + cw;
+ w = w - cw;
+ } else {
+ const rh : f64 = s / w;
+ y = y + rh;
+ h = h - rh;
+ }
+ rs = ri;
+ }
+ }
+ if (ri > rs) { sq_place(rs, ri, x, y, w, h); }
+ return P_TOP - first;
+}
+
+pub fn if_min(a: f64, b: f64) -> f64 {
+ if (b < a) { return b; }
+ return a;
+}
+
+pub fn sq_sum(a: usize, b: usize) -> f64 {
+ var s : f64 = 0.0;
+ var i : usize = a;
+ while (i < b) {
+ s = s + RS_A[i];
+ i = i + 1;
+ }
+ return s;
+}
+
+/// place(): lay the row rest[a .. b) along the shorter side.
+pub fn sq_place(a: usize, b: usize, x: f64, y: f64, w: f64, h: f64) {
+ const s : f64 = sq_sum(a, b);
+ var i : usize = a;
+ if (w >= h) {
+ const cw : f64 = s / h;
+ var yy : f64 = y;
+ while (i < b) {
+ const rh : f64 = RS_A[i] / cw;
+ sq_push(SQ_ID[RS_I[i]], x, yy, cw, rh);
+ yy = yy + rh;
+ i = i + 1;
+ }
+ } else {
+ const rh2 : f64 = s / w;
+ var xx : f64 = x;
+ while (i < b) {
+ const cw2 : f64 = RS_A[i] / rh2;
+ sq_push(SQ_ID[RS_I[i]], xx, y, cw2, rh2);
+ xx = xx + cw2;
+ i = i + 1;
+ }
+ }
+}
+
+pub fn sq_push(item: usize, x: f64, y: f64, w: f64, h: f64) {
+ if (P_TOP >= 65536) {
+ SQ_FAIL = true;
+ return;
+ }
+ P_ITEM[P_TOP] = item;
+ P_X[P_TOP] = x;
+ P_Y[P_TOP] = y;
+ P_W[P_TOP] = w;
+ P_H[P_TOP] = h;
+ P_TOP = P_TOP + 1;
+}
+
+/// squarify(items, x, y, w, h) for the items put with sq_set: the number of rectangles; read them
+/// with sq_out_*(j).
+pub fn squarify(n: usize, x: f64, y: f64, w: f64, h: f64) -> usize {
+ P_TOP = 0;
+ return sq_core(n, x, y, w, h);
+}
+
+pub fn sq_out_item(j: usize) -> usize { return P_ITEM[j]; }
+pub fn sq_out_x(j: usize) -> f64 { return P_X[j]; }
+pub fn sq_out_y(j: usize) -> f64 { return P_Y[j]; }
+pub fn sq_out_w(j: usize) -> f64 { return P_W[j]; }
+pub fn sq_out_h(j: usize) -> f64 { return P_H[j]; }
+
+// --- layoutTree --------------------------------------------------------------------------------
+pub fn rect_go(id: usize, x: f64, y: f64, w: f64, h: f64, depth: u32) {
+ if (R_N >= 32768) {
+ SQ_FAIL = true;
+ return;
+ }
+ const with_kids : bool = T_FIRST[id] >= 0;
+ var has_head : bool = false;
+ if (with_kids and h >= L_HEAD * 2.5 and w >= L_HEAD * 3.0) { has_head = true; }
+ const r : usize = R_N;
+ R_N = R_N + 1;
+ R_NODE[r] = id;
+ R_X[r] = x;
+ R_Y[r] = y;
+ R_W[r] = w;
+ R_H[r] = h;
+ R_DEPTH[r] = depth;
+ if (has_head) { R_HEAD[r] = 1; } else { R_HEAD[r] = 0; }
+ if (!with_kids) { return; }
+ const ix : f64 = x + L_PAD;
+ var top : f64 = L_PAD;
+ if (has_head) { top = L_HEAD; }
+ const iy : f64 = y + top;
+ const iw : f64 = w - 2.0 * L_PAD;
+ const ih : f64 = h - top - L_PAD;
+ if (iw < 2.0 or ih < 2.0) { return; }
+ var n : usize = 0;
+ var c : i32 = T_FIRST[id];
+ while (c >= 0) {
+ if (n >= 8192) {
+ SQ_FAIL = true;
+ return;
+ }
+ SQ_V[n] = T_VALUE[(c as usize)];
+ SQ_ID[n] = c as usize;
+ n = n + 1;
+ c = T_NEXT[(c as usize)];
+ }
+ const base : usize = P_TOP;
+ const cnt : usize = sq_core(n, ix, iy, iw, ih);
+ var k : usize = 0;
+ while (k < cnt) {
+ rect_go(P_ITEM[base + k], P_X[base + k], P_Y[base + k], P_W[base + k], P_H[base + k], depth + 1);
+ k = k + 1;
+ }
+ P_TOP = base;
+}
+
+/// layoutTree(root, x, y, w, h, { head, pad }) over the view tree: the rectangle count.
+pub fn layout_tree(x: f64, y: f64, w: f64, h: f64, head: f64, pad: f64) -> usize {
+ R_N = 0;
+ P_TOP = 0;
+ SQ_FAIL = false;
+ L_HEAD = head;
+ L_PAD = pad;
+ if (T_N > 0) { rect_go(0, x, y, w, h, 0); }
+ return R_N;
+}
+
+pub fn layout_failed() -> bool { return SQ_FAIL; }
+pub fn rect_node(i: usize) -> usize { return R_NODE[i]; }
+pub fn rect_x(i: usize) -> f64 { return R_X[i]; }
+pub fn rect_y(i: usize) -> f64 { return R_Y[i]; }
+pub fn rect_w(i: usize) -> f64 { return R_W[i]; }
+pub fn rect_h(i: usize) -> f64 { return R_H[i]; }
+pub fn rect_depth(i: usize) -> u32 { return R_DEPTH[i]; }
+pub fn rect_head(i: usize) -> bool { return R_HEAD[i] == 1; }
+
+// --- Tests: real inputs (the tool's own samples, minified byte for byte otherwise) and the answers the original treemap.js gives ---
+pub const SAMPLE : []const u8 = "{\"creator\":\"Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159, Release, AppleClang clang++ 21.0.0.21000099)\",\"invocation\":\"stat -json \",\"modules\":{\"\\\\gf16_add\":{\"num_wires\":263,\"num_wire_bits\":1123,\"num_pub_wires\":9,\"num_pub_wire_bits\":80,\"num_ports\":3,\"num_port_bits\":48,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":391,\"num_submodules\":0,\"num_cells_by_type\":{\"CARRY4\":31,\"INV\":12,\"LUT1\":11,\"LUT2\":73,\"LUT3\":78,\"LUT4\":23,\"LUT5\":24,\"LUT6\":84,\"MUXF7\":41,\"MUXF8\":14}},\"\\\\gf16_dot4_p\":{\"num_wires\":136,\"num_wire_bits\":466,\"num_pub_wires\":24,\"num_pub_wire_bits\":354,\"num_ports\":11,\"num_port_bits\":146,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":224,\"num_submodules\":7,\"num_cells_by_type\":{\"FDRE\":112,\"INV\":112,\"gf16_add\":3,\"gf16_mul\":4}},\"\\\\gf16_matmul4x4_p\":{\"num_wires\":101,\"num_wire_bits\":1541,\"num_pub_wires\":50,\"num_pub_wire_bits\":770,\"num_ports\":50,\"num_port_bits\":770,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":771,\"num_submodules\":16,\"num_cells_by_type\":{\"BUFG\":1,\"IBUF\":514,\"OBUF\":256,\"gf16_dot4_p\":16}},\"\\\\gf16_mul\":{\"num_wires\":135,\"num_wire_bits\":603,\"num_pub_wires\":12,\"num_pub_wire_bits\":120,\"num_ports\":3,\"num_port_bits\":48,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":159,\"num_submodules\":0,\"num_cells_by_type\":{\"CARRY4\":13,\"DSP48E1\":1,\"INV\":7,\"LUT1\":21,\"LUT2\":12,\"LUT3\":8,\"LUT4\":5,\"LUT5\":27,\"LUT6\":27,\"MUXF7\":35,\"MUXF8\":3}}},\"design\":{\"num_wires\":23541,\"num_wire_bits\":101493,\"num_pub_wires\":1634,\"num_pub_wire_bits\":17954,\"num_ports\":562,\"num_port_bits\":8482,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":33299,\"num_submodules\":16,\"num_cells_by_type\":{\"BUFG\":1,\"CARRY4\":2320,\"DSP48E1\":64,\"FDRE\":1792,\"IBUF\":514,\"INV\":2816,\"LUT1\":1872,\"LUT2\":4272,\"LUT3\":4256,\"LUT4\":1424,\"LUT5\":2880,\"LUT6\":5760,\"MUXF7\":4208,\"MUXF8\":864,\"OBUF\":256,\"gf16_dot4_p\":16}}}";
+
+pub const SAMPLE_FLAT : []const u8 = "{\"creator\":\"Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159, Release, AppleClang clang++ 21.0.0.21000099)\",\"invocation\":\"stat -json \",\"modules\":{\"\\\\gf16_matmul4x4_p\":{\"num_wires\":18323,\"num_wire_bits\":92285,\"num_pub_wires\":1554,\"num_pub_wire_bits\":16674,\"num_ports\":50,\"num_port_bits\":770,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":26668,\"num_submodules\":0,\"num_cells_by_type\":{\"$scopeinfo\":128,\"BUFG\":1,\"CARRY4\":2273,\"DSP48E1\":64,\"FDRE\":1792,\"IBUF\":514,\"INV\":1311,\"LUT1\":585,\"LUT2\":4394,\"LUT3\":4928,\"LUT4\":1206,\"LUT5\":2154,\"LUT6\":5463,\"MUXF7\":1224,\"MUXF8\":375,\"OBUF\":256}}},\"design\":{\"num_wires\":18323,\"num_wire_bits\":92285,\"num_pub_wires\":1554,\"num_pub_wire_bits\":16674,\"num_ports\":50,\"num_port_bits\":770,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":26668,\"num_submodules\":0,\"num_cells_by_type\":{\"$scopeinfo\":128,\"BUFG\":1,\"CARRY4\":2273,\"DSP48E1\":64,\"FDRE\":1792,\"IBUF\":514,\"INV\":1311,\"LUT1\":585,\"LUT2\":4394,\"LUT3\":4928,\"LUT4\":1206,\"LUT5\":2154,\"LUT6\":5463,\"MUXF7\":1224,\"MUXF8\":375,\"OBUF\":256}}}";
+
+pub const PARAMOD : []const u8 = "{\"creator\":\"Yosys 0.67+post (git sha1 b8e7da6f40ae8f552c116bf6c359b07c6533e159, Release, AppleClang clang++ 21.0.0.21000099)\",\"invocation\":\"stat -json \",\"modules\":{\"$paramod\\\\leaf\\\\W=s32'00000000000000000000000000000100\":{\"num_wires\":7,\"num_wire_bits\":25,\"num_pub_wires\":4,\"num_pub_wire_bits\":13,\"num_ports\":4,\"num_port_bits\":13,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":9,\"num_submodules\":0,\"num_cells_by_type\":{\"CARRY4\":1,\"FDRE\":4,\"LUT2\":4}},\"$paramod\\\\leaf\\\\W=s32'00000000000000000000000000001000\":{\"num_wires\":7,\"num_wire_bits\":49,\"num_pub_wires\":4,\"num_pub_wire_bits\":25,\"num_ports\":4,\"num_port_bits\":25,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":18,\"num_submodules\":0,\"num_cells_by_type\":{\"CARRY4\":2,\"FDRE\":8,\"LUT2\":8}},\"\\\\mid\":{\"num_wires\":5,\"num_wire_bits\":17,\"num_pub_wires\":5,\"num_pub_wire_bits\":17,\"num_ports\":4,\"num_port_bits\":13,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":0,\"num_submodules\":2,\"num_cells_by_type\":{\"$paramod\\\\leaf\\\\W=s32'00000000000000000000000000000100\":2}},\"\\\\pmtop\":{\"num_wires\":13,\"num_wire_bits\":67,\"num_pub_wires\":6,\"num_pub_wire_bits\":33,\"num_ports\":6,\"num_port_bits\":33,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":34,\"num_submodules\":3,\"num_cells_by_type\":{\"BUFG\":1,\"IBUF\":17,\"OBUF\":16,\"$paramod\\\\leaf\\\\W=s32'00000000000000000000000000001000\":1,\"mid\":2}}},\"design\":{\"num_wires\":58,\"num_wire_bits\":250,\"num_pub_wires\":36,\"num_pub_wire_bits\":144,\"num_ports\":34,\"num_port_bits\":136,\"num_memories\":0,\"num_memory_bits\":0,\"num_processes\":0,\"num_cells\":88,\"num_submodules\":3,\"num_cells_by_type\":{\"BUFG\":1,\"CARRY4\":6,\"FDRE\":24,\"IBUF\":17,\"LUT2\":24,\"OBUF\":16,\"$paramod\\\\leaf\\\\W=s32'00000000000000000000000000001000\":1,\"mid\":2}}}";
+
+pub fn nid_named(w: []const u8) -> i32 {
+ var i : usize = 0;
+ while (i < NM_N) {
+ if (pool_eq(NM_OFF[i], NM_LEN[i], w)) { return (i as i32); }
+ i = i + 1;
+ }
+ return -1;
+}
+
+pub fn disp_is(n: usize, w: []const u8) -> bool {
+ return pool_eq(DP_OFF[n], DP_LEN[n], w);
+}
+
+test the_sample_adds_up_to_yosys_total {
+ assert parse_hier(SAMPLE) == E_OK;
+ assert pool_eq(NM_OFF[TOP], NM_LEN[TOP], "gf16_matmul4x4_p");
+ assert totals_of(TOP, 0) == 20464.0;
+ assert totals_of(TOP, 1) == 1792.0;
+ assert totals_of(TOP, 2) == 64.0;
+ assert same_as_design(TOP);
+ assert has_creator();
+ assert pool_eq(VER_OFF, VER_LEN, "0.67+post");
+ assert key_kind() == 1;
+ assert is_flat() == false;
+ assert order_n() == 4;
+ assert roots_n() == 1;
+}
+
+test instances_multiply_down_the_hierarchy_and_rows_list_them {
+ assert parse_hier(SAMPLE) == E_OK;
+ assert module_rows() == 4;
+ const add : usize = nid_named("gf16_add") as usize;
+ const dot : usize = nid_named("gf16_dot4_p") as usize;
+ const mul : usize = nid_named("gf16_mul") as usize;
+ assert inst_of(add) == 48.0;
+ assert inst_of(dot) == 16.0;
+ assert inst_of(mul) == 64.0;
+ assert own(add, 0) == 293.0;
+ assert own(mul, 0) == 100.0;
+ assert own(mul, 2) == 1.0;
+ assert own(dot, 1) == 112.0;
+ assert row_total(0, 0) == 14064.0;
+ assert sub_n(dot) == 2;
+ assert sub_count(dot, 0) == 3.0;
+}
+
+test the_flat_run_has_one_module_and_fewer_luts {
+ assert parse_hier(SAMPLE_FLAT) == E_OK;
+ assert is_flat();
+ assert totals_of(TOP, 0) == 18730.0;
+ assert totals_of(TOP, 1) == 1792.0;
+ assert own(TOP, 4) > 0.0;
+ assert has_design();
+ assert design_val(0) == 18730.0;
+ assert sub_n(TOP) == 0;
+}
+
+test paramod_names_are_decoded_and_two_paths_are_added {
+ assert parse_hier(PARAMOD) == E_OK;
+ const l4 : usize = nid_named("$paramod\\leaf\\W=s32'00000000000000000000000000000100") as usize;
+ const l8 : usize = nid_named("$paramod\\leaf\\W=s32'00000000000000000000000000001000") as usize;
+ assert disp_is(l4, "leaf W=4");
+ assert disp_is(l8, "leaf W=8");
+ assert module_rows() == 4;
+ assert inst_of(l4) == 4.0;
+ assert inst_of(l8) == 1.0;
+ assert same_as_design(TOP);
+ assert display_name("$paramod\\m\\P=s8'11111111\\Q=8'11111111\\R=s4'0101") == 16;
+ assert display_byte(0) == 109;
+ assert display_name("\\plain") == 5;
+ assert bare_skip("\\x") == 1;
+}
+
+test the_view_tree_has_one_tile_per_instance_and_the_leaves_add_up {
+ assert parse_hier(SAMPLE) == E_OK;
+ assert view_tree(0, 64.0, 600.0) == 129;
+ assert view_tree_failed() == false;
+ assert leaves_n() == 112;
+ assert leaves_sum() == 20464.0;
+ assert tile_value(0) == 20464.0;
+ assert tile_own(0) == false;
+ assert view_tree(1, 64.0, 600.0) == 17;
+ assert leaves_sum() == 1792.0;
+ assert view_tree(0, 2.0, 600.0) == 4;
+ assert leaves_sum() == 20464.0;
+ assert view_tree(3, 64.0, 600.0) == 1;
+}
+
+test the_layout_places_every_tile_inside_the_map {
+ assert parse_hier(SAMPLE) == E_OK;
+ assert view_tree(0, 64.0, 600.0) > 0;
+ assert layout_tree(0.0, 0.0, 900.0, 600.0, 16.0, 2.0) == 129;
+ assert layout_failed() == false;
+ assert rect_x(0) == 0.0;
+ assert rect_w(0) == 900.0;
+ assert rect_head(0);
+ assert rect_x(1) == 2.0;
+ assert rect_y(1) == 16.0;
+ assert rect_w(1) == 168.00000000000003;
+ assert rect_h(1) == 194.0;
+ assert rect_x(128) == 858.6743626743628;
+ assert rect_h(128) == 61.34594214229872;
+ assert rect_depth(128) == 2;
+}
+
+test squarify_follows_bruls_huizing_van_wijk {
+ sq_set(0, 6.0);
+ sq_set(1, 6.0);
+ sq_set(2, 4.0);
+ sq_set(3, 3.0);
+ sq_set(4, 2.0);
+ sq_set(5, 2.0);
+ sq_set(6, 1.0);
+ assert squarify(7, 0.0, 0.0, 6.0, 4.0) == 7;
+ assert sq_out_item(0) == 0;
+ assert sq_out_x(0) == 0.0;
+ assert sq_out_w(0) == 3.0;
+ assert sq_out_h(0) == 2.0;
+ assert sq_out_x(6) == 5.4;
+ assert sq_out_y(6) == 2.3333333333333335;
+ assert sq_out_w(6) == 0.5999999999999996;
+ assert sq_out_h(6) == 1.6666666666666676;
+ assert squarify(0, 0.0, 0.0, 6.0, 4.0) == 0;
+}
+
+test parse_failures_are_named_the_way_the_page_says {
+ assert parse_hier("") == E_EMPTY;
+ assert parse_hier(" ") == E_EMPTY;
+ assert parse_hier("run stat with -json please") == E_NOJSON;
+ assert parse_hier("{\"modules\": }") == E_BADJSON;
+ assert parse_hier("[1, 2]") == E_NOMODULES;
+ assert parse_hier("{\"modules\": 5}") == E_NOMODULES;
+ assert parse_hier("{\"creator\": \"Yosys 0.9\", \"modules\": {\"a\": {}}}") == E_NOCELLS;
+ assert pool_eq(VER_OFF, VER_LEN, "0.9");
+ assert parse_hier("{\"modules\": {\"a\": {\"cells\": {\"b\": 1}}, \"b\": {\"cells\": {\"a\": 1}}}}") == E_NOTOP;
+}
+
+test json_quirks_are_read_the_way_javascript_reads_them {
+ assert parse_hier("log line {\"modules\": {\"\\\\a\": {\"cells\": {\"LUT2\": 7}}}} tail") == E_OK;
+ assert key_kind() == 2;
+ assert own(TOP, 0) == 7.0;
+ assert parse_hier("{\"modules\": {\"a\": {\"cells\": {\"LUT2\": 1, \"LUT2\": 5, \"FDRE\": \"3\", \"BUFG\": true}}}}") == E_OK;
+ assert own(TOP, 0) == 5.0;
+ assert own(TOP, 1) == 3.0;
+ assert own(TOP, 4) == 1.0;
+ assert parse_hier("{\"modules\": {\"b\": {\"cells\": {\"LUT1\": 1}}, \"2\": {\"cells\": {\"LUT1\": 2}}, \"1\": {\"cells\": {\"LUT1\": 3}}}}") == E_OK;
+ assert order_n() == 3;
+ assert pool_eq(NM_OFF[order_nid(0)], NM_LEN[order_nid(0)], "1");
+ assert pool_eq(NM_OFF[order_nid(2)], NM_LEN[order_nid(2)], "b");
+ assert parse_hier("{\"modules\": {\"a\": {\"cells\": {\"LUT1\": 1e2, \"LUT2\": 2.5E-1, \"LUT3\": 01}}}}") == E_BADJSON;
+ assert parse_hier("{\"modules\": {\"a\": {\"cells\": {\"LUT1\": 1e2, \"LUT2\": 2.5E-1, \"LUT3\": 0.5}}}}") == E_OK;
+ assert own(TOP, 0) == 100.75;
+}
+
+; FOLD END
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/lut-treemap/tool.js (322 lines of hand-written JavaScript). The
+; words and numbers above are unchanged. The reader, the instance multiplier, the view tree and the layout
+; (treemap.js, 289 lines) are lut_treemap_logic.t27, folded into this module (its text between "FOLD BEGIN"
+; and "FOLD END" is that module's body with the changes listed at FOLD BEGIN): `use` cannot carry a
+; `pub var` across modules and the reader keeps its tables in module-level arrays.
+; This tool's treemap is DOM, not canvas: absolutely placed
tiles, sized by the map box. The page
+; needs three things from the host that the browser alone knows, besides the events of the other pages:
+; w_measure_sel(dst) the selector of the box whose size the page needs: ".lt-map"
+; w_size(px, py) that box is now px wide and py tall (clientWidth, clientHeight); 1 when it changed
+; and the page has to be written again
+; w_mono(bytes) the CSS variable --w-mono (the labels are measured in that font)
+; w_text_need(dst), w_text_give(widths, n) the text width service (see stat_card.t27): the page
+; asks for ctx.measureText widths while it writes the tiles' labels
+; w_tile(id) a tile was clicked (its data-id)
+; w_theme/w_base/w_init/w_data/w_data_failed/w_input/w_click/w_file/w_file_data/w_file_failed/
+; w_drag/w_value/w_render/w_effect as in the other pages. Buttons in document order: the four metrics, the
+; table's rows, then sample, draw, back-to-sample; zones: 0 the stat, 1 the flattened stat.
+; HOW IT DIFFERS FROM tool.js, said plainly:
+; - the sample button re-reads the bytes fetched at load instead of fetching again;
+; - the reader keeps ONE parse at a time: the flattened drop keeps only its top totals, and the stat
+; that is shown is read again from the text this page kept whenever another text was read;
+; - a text the reader does not carry (its header) says "Could not read"; so does a cyclic hierarchy
+; (tool.js throws in viewTree) and a stat over the reader's capacities;
+; - a dropped file is read as bytes, not through File.text() (no UTF-8 repair, no BOM strip);
+; - the host writes the page at most 1 MiB; a treemap of many thousand tiles does not fit.
+
+; MEASURED 2026-10-19: t27c gen + a 21-line Zig export wrapper + zig build-exe wasm32-freestanding ReleaseSmall give
+; a 41208-byte wasm. The original page (tool.js with treemap.js) and this one ran in two DOM windows (happy-dom);
+; the box .lt-map has a size the harness sets (clientWidth and clientHeight), a ResizeObserver stand-in fires on
+; request, and ctx.measureText answers with one proportional-font stand-in (the same on both sides). After every
+; action the DOM, with the style of every tile and every textarea value, and the scrollIntoView calls of the two
+; were compared: 2055 comparisons (63551 tile elements seen), 0 differences. They cover the real sample with every
+; control (the four metrics, rows, tiles, both drop boxes by pick and by drop, drag over and leave, clear, sample,
+; a cancelled picker, nine map sizes from 0 x 0 to 1200 x 700), 1719 fuzzed actions on designs of 1 to 12 modules
+; (random hierarchies with 1 to 1000 instances of a child, $paramod names, markup, quotes, accents and CJK in
+; names, "cells" and "num_cells_by_type", design blocks that agree and that do not, two roots, the same module
+; under two keys, modules without a cell map, null/true/fraction counts, pretty and minified, terminal captures,
+; 16 texts that do not read), 25 variants of the two sample files plus a 404 and a non-JSON answer, events
+; before the data arrives, and the limits (8,000,000 and 8,000,001 characters, files of 8,000,001 and 3,000,000
+; bytes, 1.5 million multibyte characters, 1500 leaf instances against the tile budget of 600, 40 modules).
+; Stated differences, measured: a cyclic hierarchy (tool.js throws in viewTree, this page says "Could not
+; read"), a count given as a JSON array and a count given as hex text (the reader does not carry them).
+; Negative controls: a changed DOM node, a changed tile style, a measured width 0.5 px and 8 px too wide, a map
+; one pixel too tall and one pixel too narrow, a click on the neighbour tile, a scroll left out and a scroll
+; with another behaviour are each reported. NOT compared (happy-dom has no layout or fonts): the real box size,
+; the real font widths, the smooth scrolling itself.
+
+pub const W_DATA_FILES : [2]str = ["sample.stat.json", "sample.flat.stat.json"];
+pub const W_ACCEPT : str = ".json,.txt,application/json,text/plain";
+pub const W_MONO_FALLBACK : str = "monospace";
+pub const W_PX : u32 = 10;
+pub const W_MEASURE_SEL : str = ".lt-map";
+pub const W_SCROLL_SEL : str = ".lt-result";
+pub const W_SEP : str = " > ";
+pub const W_NUM_MAX_CHARS : u64 = 8000000;
+
+pub var S_SMP0 : [1048576]u8 = [0; 1048576];
+pub var S_SMP1 : [1048576]u8 = [0; 1048576];
+pub var S_SMP_N : [2]u32 = [0; 2];
+pub var S_SMP_ST : [2]u8 = [0; 2];
+pub var S_MAIN : [8388608]u8 = [0; 8388608];
+pub var S_MAIN_N : u32 = 0;
+pub var S_PASTE : [8388608]u8 = [0; 8388608];
+pub var S_PASTE_N : u32 = 0;
+pub var S_PASTE_U : u64 = 0;
+pub var S_PASTE_BIG : bool = false;
+pub var S_CLR_PASTE : bool = false;
+pub var S_CLR_FILE : [2]bool = [false; 2];
+pub var S_STATUS : [8192]u8 = [0; 8192];
+pub var S_STATUS_N : u32 = 0;
+pub var S_BASE : [1024]u8 = [0; 1024];
+pub var S_BASE_N : u32 = 0;
+pub var S_FAM : [256]u8 = [0; 256];
+pub var S_FAM_N : u32 = 0;
+pub var S_FB : [262144]u8 = [0; 262144];
+pub var S_NUM : [64]u8 = [0; 64];
+pub var S_NUM2 : [64]u8 = [0; 64];
+pub var S_PRESSED : bool = false;
+pub var S_OVER : [2]bool = [false; 2];
+pub var S_HAVE : bool = false;
+pub var S_HIDDEN : bool = true;
+pub var S_RENDERED : bool = false;
+pub var S_METRIC : u32 = 0;
+pub var S_SEL : i32 = 0 - 1;
+pub var S_TILE : i32 = 0 - 1;
+pub var S_FLAT : bool = false;
+pub var F_TOT : [5]f64 = [0.0; 5];
+pub var S_FLATLINE : [4096]u8 = [0; 4096];
+pub var S_FLATLINE_N : u32 = 0;
+pub var S_DIRTY : bool = false;
+pub var S_HUE : [4096]u32 = [0; 4096];
+pub var S_MAP_W : f64 = 0.0;
+pub var S_MAP_H : f64 = 0.0;
+pub var S_PLACED : [32768]u8 = [0; 32768];
+pub var S_PLACED_OK : bool = false;
+pub var S_EF : [4]u8 = [0; 4];
+pub var S_EF_N : u32 = 0;
+
+// the text width service (as in stat_card.t27, with the family the page gets from the host)
+pub var X_POOL : [524288]u8 = [0; 524288];
+pub var X_PN : u32 = 0;
+pub var X_OFF : [8192]u32 = [0; 8192];
+pub var X_LEN : [8192]u32 = [0; 8192];
+pub var X_W : [8192]f64 = [0.0; 8192];
+pub var X_ST : [8192]u8 = [0; 8192];
+pub var X_N : u32 = 0;
+pub var X_MISS : bool = false;
+
+/// Two texts equal byte for byte (t27c's Zig target does not compare two str array elements with ==).
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+fn copy_into(dst: []u8, at: u32, s: []const u8) -> u32 {
+ var n : u32 = at;
+ var k : usize = 0;
+ while (k < s.len and (n as usize) < dst.len) {
+ dst[n as usize] = s[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ S_SMP_N[0] = 0;
+ S_SMP_N[1] = 0;
+ S_SMP_ST[0] = 0;
+ S_SMP_ST[1] = 0;
+ S_MAIN_N = 0;
+ S_PASTE_N = 0;
+ S_PASTE_U = 0;
+ S_PASTE_BIG = false;
+ S_CLR_PASTE = false;
+ S_CLR_FILE[0] = false;
+ S_CLR_FILE[1] = false;
+ S_PRESSED = false;
+ S_OVER[0] = false;
+ S_OVER[1] = false;
+ S_HAVE = false;
+ S_HIDDEN = true;
+ S_RENDERED = false;
+ S_METRIC = 0;
+ S_SEL = 0 - 1;
+ S_TILE = 0 - 1;
+ S_FLAT = false;
+ S_FLATLINE_N = 0;
+ S_DIRTY = false;
+ S_MAP_W = 0.0;
+ S_MAP_H = 0.0;
+ S_PLACED_OK = false;
+ S_EF_N = 0;
+ X_N = 0;
+ X_PN = 0;
+ status_is(SAY_LOCAL_NOTE);
+ return 0;
+}
+
+pub fn w_base(s: []const u8) -> u32 {
+ S_BASE_N = copy_into(S_BASE[0..], 0, s);
+ return 0;
+}
+
+pub fn w_mono(s: []const u8) -> u32 {
+ S_FAM_N = copy_into(S_FAM[0..], 0, s);
+ return 0;
+}
+
+pub fn w_theme(s: []const u8) -> u32 {
+ return 0;
+}
+
+pub fn w_measure_sel(dst: []u8) -> u32 {
+ return copy_into(dst, 0, W_MEASURE_SEL);
+}
+
+/// The map box is now px wide and py tall. 1 when it changed.
+pub fn w_size(px: f64, py: f64) -> u32 {
+ const same : bool = px == S_MAP_W and py == S_MAP_H;
+ S_MAP_W = px;
+ S_MAP_H = py;
+ if (same or !S_HAVE or S_HIDDEN) { return 0; }
+ return 1;
+}
+
+// --- fmt(): the templates {0} {1} ... of the words, with the arguments in ARB -------------------------------
+
+pub var ARB : [262144]u8 = [0; 262144];
+pub var ARO : [8]u32 = [0; 8];
+pub var ARL : [8]u32 = [0; 8];
+
+/// fmt(tpl, args...): {0} {1} ... replaced by the arguments in ARB; an index with no argument becomes nothing.
+/// Writes into dst; returns the length, or -1 when dst is too small.
+pub fn fmt_into(dst: []u8, tpl: []const u8, nargs: u32) -> i64 {
+ var n : usize = 0;
+ var k : usize = 0;
+ while (k < tpl.len) {
+ var done : bool = false;
+ if (tpl[k] == 123) {
+ var q : usize = k + 1;
+ var idx : u64 = 0;
+ while (q < tpl.len and is_digit(tpl[q])) {
+ if (idx < 100000) { idx = idx * 10 + ((tpl[q] - 48) as u64); }
+ q = q + 1;
+ }
+ if (q > k + 1 and q < tpl.len and tpl[q] == 125) {
+ if (idx < (nargs as u64)) {
+ var a : usize = 0;
+ while (a < (ARL[idx as usize] as usize)) {
+ if (n >= dst.len) { return -1; }
+ dst[n] = ARB[(ARO[idx as usize] as usize) + a];
+ n = n + 1;
+ a = a + 1;
+ }
+ }
+ k = q + 1;
+ done = true;
+ }
+ }
+ if (!done) {
+ if (n >= dst.len) { return -1; }
+ dst[n] = tpl[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ }
+ return n as i64;
+}
+
+/// Argument i = the bytes w[lo .. hi] (arguments are set in order 0, 1, 2 ...).
+pub fn arg_set(i: usize, w: []const u8, lo: usize, hi: usize) -> bool {
+ var at : usize = 0;
+ if (i > 0) { at = (ARO[i - 1] + ARL[i - 1]) as usize; }
+ if (at + (hi - lo) > 262144) { return false; }
+ ARO[i] = at as u32;
+ ARL[i] = (hi - lo) as u32;
+ var k : usize = lo;
+ while (k < hi) {
+ ARB[at + (k - lo)] = w[k];
+ k = k + 1;
+ }
+ return true;
+}
+
+/// Argument i = String(x), a number the way JavaScript writes it.
+pub fn arg_num(i: usize, x: f64) -> bool {
+ var at : usize = 0;
+ if (i > 0) { at = (ARO[i - 1] + ARL[i - 1]) as usize; }
+ if (at + 400 > 262144) { return false; }
+ ARO[i] = at as u32;
+ const e : u32 = js_str_num(ARB[0 .. 262144], at as u32, x);
+ if (e == JN_OVER) { return false; }
+ ARL[i] = e - (at as u32);
+ return true;
+}
+
+// --- text and numbers -------------------------------------------------------------------------------
+
+fn arg_text(i: usize, s: []const u8) -> bool {
+ return arg_set(i, s, 0, s.len);
+}
+
+fn arg_f(i: usize, x: f64) -> bool {
+ return arg_num(i, x);
+}
+
+/// Number.toLocaleString('en-US') of x into S_NUM; the length.
+fn locale_text(x: f64) -> u32 {
+ const e : u32 = js_locale_num(S_NUM[0..], 0, x);
+ if (e == OVER) { return 0; }
+ return e;
+}
+
+/// arg i = num(x)
+fn arg_loc(i: usize, x: f64) -> bool {
+ const l : u32 = locale_text(x);
+ return arg_set(i, S_NUM[0..], 0, l as usize);
+}
+
+fn put_say(dst: []u8, n0: u32, tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_FB[0..], tpl, nargs);
+ if (l < 0) { return OVER; }
+ return text(dst, n0, S_FB[0..(l as usize)]);
+}
+
+fn status_is(s: []const u8) -> u32 {
+ S_STATUS_N = copy_into(S_STATUS[0..], 0, s);
+ return 0;
+}
+
+fn status_fmt(tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_STATUS[0..], tpl, nargs);
+ S_STATUS_N = 0;
+ if (l >= 0) { S_STATUS_N = l as u32; }
+ return 0;
+}
+
+/// The metric's name: SAY_METRIC_NAMES[IDS.indexOf(m)].
+fn metric_name() -> []const u8 {
+ return SAY_METRIC_NAMES[S_METRIC as usize];
+}
+
+/// "pct(a, b)": (100 * a / b).toFixed(1) + "%", or "-" when b is 0. Into dst.
+fn put_pct(dst: []u8, n0: u32, a: f64, b: f64) -> u32 {
+ if (b == 0.0 or b != b) { return raw(dst, n0, "-"); }
+ var n : u32 = js_to_fixed(dst, n0, (100.0 * a) / b, 1);
+ return raw(dst, n, "%");
+}
+
+fn pct_text(a: f64, b: f64) -> u32 {
+ S_PCT_N = put_pct(S_NUM2[0..], 0, a, b);
+ return S_PCT_N;
+}
+
+// --- the group lists (the reader holds them as literals) ----------------------------------------------
+
+// --- text width service ----------------------------------------------------------------------------------
+
+fn x_same(i: u32, s: []const u8) -> bool {
+ if ((X_LEN[i as usize] as usize) != s.len) { return false; }
+ var k : usize = 0;
+ const o : usize = X_OFF[i as usize] as usize;
+ while (k < s.len) {
+ if (X_POOL[o + k] != s[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn x_find(s: []const u8) -> i32 {
+ var i : u32 = 0;
+ while (i < X_N) {
+ if (x_same(i, s)) { return i as i32; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+fn x_ask(s: []const u8) -> u32 {
+ if (x_find(s) >= 0) { return 0; }
+ if (X_N >= 8192 or (X_PN as usize) + s.len > 524288) {
+ X_N = 0;
+ X_PN = 0;
+ }
+ if (s.len > 8192) { return 0; }
+ const i : usize = X_N as usize;
+ X_OFF[i] = X_PN;
+ X_LEN[i] = s.len as u32;
+ X_ST[i] = 0;
+ X_W[i] = 0.0;
+ var k : usize = 0;
+ while (k < s.len) {
+ X_POOL[(X_PN as usize) + k] = s[k];
+ k = k + 1;
+ }
+ X_PN = X_PN + (s.len as u32);
+ X_N = X_N + 1;
+ return 1;
+}
+
+/// ctx.measureText(s).width in the labels' font; 0.0 and a request while the host has not answered.
+fn x_width(s: []const u8) -> f64 {
+ const f : i32 = x_find(s);
+ if (f >= 0 and X_ST[f as usize] == 2) {
+ X_MISS = false;
+ return X_W[f as usize];
+ }
+ X_MISS = true;
+ x_ask(s);
+ return 0.0;
+}
+
+pub var S_FT : [512]u8 = [0; 512];
+pub var S_FT_N : u32 = 0;
+
+/// The labels' font: 10px and the family the host gave (monospace when it is empty).
+fn ft_make() -> u32 {
+ var n : u32 = copy_into(S_FT[0..], 0, "10px ");
+ if (S_FAM_N == 0) { n = copy_into(S_FT[0..], n, W_MONO_FALLBACK); } else { n = copy_into(S_FT[0..], n, S_FAM[0..(S_FAM_N as usize)]); }
+ S_FT_N = n;
+ return n;
+}
+
+fn put_u32le(dst: []u8, at: u32, v: u32) -> u32 {
+ dst[at as usize] = (v % 256) as u8;
+ dst[(at + 1) as usize] = ((v / 256) % 256) as u8;
+ dst[(at + 2) as usize] = ((v / 65536) % 256) as u8;
+ dst[(at + 3) as usize] = ((v / 16777216) % 256) as u8;
+ return at + 4;
+}
+
+/// Requests for the host: records of (u32 font length, u32 text length, font bytes, text bytes). The count.
+pub fn w_text_need(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ var c : u32 = 0;
+ var i : u32 = 0;
+ ft_make();
+ while (i < X_N) {
+ if (X_ST[i as usize] == 0) {
+ const need : u32 = 8 + S_FT_N + X_LEN[i as usize];
+ if ((n as usize) + (need as usize) > dst.len) { return c; }
+ n = put_u32le(dst, n, S_FT_N);
+ n = put_u32le(dst, n, X_LEN[i as usize]);
+ n = copy_into(dst, n, S_FT[0..(S_FT_N as usize)]);
+ var k : u32 = 0;
+ while (k < X_LEN[i as usize]) {
+ dst[(n + k) as usize] = X_POOL[(X_OFF[i as usize] + k) as usize];
+ k = k + 1;
+ }
+ n = n + X_LEN[i as usize];
+ X_ST[i as usize] = 1;
+ c = c + 1;
+ }
+ i = i + 1;
+ }
+ return c;
+}
+
+/// The widths of the requests last sent, in order.
+pub fn w_text_give(src: []const f64, n: u32) -> u32 {
+ var i : u32 = 0;
+ var k : u32 = 0;
+ while (i < X_N and k < n and (k as usize) < src.len) {
+ if (X_ST[i as usize] == 1) {
+ X_W[i as usize] = src[k as usize];
+ X_ST[i as usize] = 2;
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+// --- the parse and what the page keeps of it ------------------------------------------------------------
+
+/// The reader reads again the stat that is shown, when another text was read after it.
+fn ensure_main() -> u32 {
+ if (S_DIRTY and S_HAVE) {
+ parse_hier(S_MAIN[0..(S_MAIN_N as usize)]);
+ S_DIRTY = false;
+ }
+ return 0;
+}
+
+fn top_total(k: usize) -> f64 {
+ return totals_of(top_nid(), k);
+}
+
+/// use(r, flat): the page now shows the stat the reader holds.
+fn use_result() -> u32 {
+ S_HAVE = true;
+ S_SEL = 0 - 1;
+ S_TILE = 0 - 1;
+ S_PLACED_OK = false;
+ var i : usize = 0;
+ while (i < order_n()) {
+ S_HUE[order_nid(i)] = K_TILE_HUES[i % K_TILE_HUES.len] as u32;
+ i = i + 1;
+ }
+ if (top_total(S_METRIC as usize) == 0.0 or top_total(S_METRIC as usize) != top_total(S_METRIC as usize)) {
+ var m : u32 = 0;
+ var found : bool = false;
+ while (m < 4 and !found) {
+ const t : f64 = top_total(m as usize);
+ if (t != 0.0 and t == t) {
+ S_METRIC = m;
+ found = true;
+ }
+ m = m + 1;
+ }
+ if (!found) { S_METRIC = 0; }
+ }
+ do_render();
+ return 0;
+}
+
+fn set_flat_from_reader() -> u32 {
+ var k : usize = 0;
+ while (k < 5) {
+ F_TOT[k] = top_total(k);
+ k = k + 1;
+ }
+ return 0;
+}
+
+/// Read text t as the stat to show. The code of the reader's answer; the reader holds the stat only when it is 0.
+fn read_main(t: []const u8) -> u32 {
+ const code : u32 = parse_hier(t);
+ if (code != 0) { return code; }
+ // a cyclic hierarchy: tool.js throws in viewTree; this page refuses it
+ view_tree(0, K_EXPAND_MAX as f64, K_MAX_TILES as f64);
+ if (view_tree_failed()) { return 7; }
+ var m : usize = 1;
+ while (m < 4) {
+ view_tree(m, K_EXPAND_MAX as f64, K_MAX_TILES as f64);
+ if (view_tree_failed()) { return 7; }
+ m = m + 1;
+ }
+ return 0;
+}
+
+fn error_words(code: u32, plain_name: []const u8) -> u32 {
+ if (code >= 1 and code <= 6) {
+ status_is(SAY_ERROR_TEXT[((code - 1) as usize)]);
+ return 0;
+ }
+ arg_text(0, plain_name);
+ return status_fmt(SAY_ERR_READ, 1);
+}
+
+fn too_big(n: f64) -> u32 {
+ arg_loc(0, n);
+ arg_loc(1, W_NUM_MAX_CHARS as f64);
+ return status_fmt(SAY_TOO_BIG, 2);
+}
+
+/// readText(text, name): the text is t; its JavaScript length u16; name, in S_FB when it was a file's.
+fn read_text(t: []const u8, u16: u64, plain_name: []const u8, named: []const u8) -> u32 {
+ if (u16 > W_NUM_MAX_CHARS) {
+ too_big(u16 as f64);
+ return 0;
+ }
+ const code : u32 = read_main(t);
+ if (code != 0) {
+ S_DIRTY = true;
+ error_words(code, plain_name);
+ ensure_main();
+ return 0;
+ }
+ // the stat is accepted: keep its text, show it
+ var k : usize = 0;
+ while (k < t.len) {
+ S_MAIN[k] = t[k];
+ k = k + 1;
+ }
+ S_MAIN_N = t.len as u32;
+ S_DIRTY = false;
+ S_PRESSED = false;
+ S_FLAT = false;
+ var n : u32 = copy_into(S_STATUS[0..], 0, named);
+ n = copy_into(S_STATUS[0..], n, ". ");
+ n = copy_into(S_STATUS[0..], n, SAY_LOCAL_NOTE);
+ S_STATUS_N = n;
+ use_result();
+ S_EF[0] = 122;
+ S_EF_N = 1;
+ return 0;
+}
+
+/// The flattened stat text (readFlatText).
+fn read_flat_text(t: []const u8, u16: u64, plain_name: []const u8, named: []const u8) -> u32 {
+ if (u16 > W_NUM_MAX_CHARS) {
+ too_big(u16 as f64);
+ return 0;
+ }
+ const code : u32 = parse_hier(t);
+ S_DIRTY = true;
+ if (code != 0) {
+ error_words(code, plain_name);
+ ensure_main();
+ return 0;
+ }
+ if (!is_flat()) {
+ status_is(SAY_NOT_FLAT);
+ ensure_main();
+ return 0;
+ }
+ set_flat_from_reader();
+ var n : u32 = copy_into(S_STATUS[0..], 0, named);
+ n = copy_into(S_STATUS[0..], n, ". ");
+ n = copy_into(S_STATUS[0..], n, SAY_LOCAL_NOTE);
+ S_STATUS_N = n;
+ S_FLAT = true;
+ ensure_main();
+ if (S_HAVE) { do_render(); }
+ return 0;
+}
+
+/// showSample(): both files read, then shown; any failure says the sample did not load.
+fn show_sample() -> u32 {
+ if (S_SMP_ST[0] == 0 or S_SMP_ST[1] == 0) { return 0; }
+ if (S_SMP_ST[0] != 1 or S_SMP_ST[1] != 1) {
+ status_is(SAY_ERR_LOAD);
+ return 0;
+ }
+ // the flat first: only its totals are kept
+ const cf : u32 = parse_hier(S_SMP1[0..(S_SMP_N[1] as usize)]);
+ S_DIRTY = true;
+ if (cf != 0) {
+ status_is(SAY_ERR_LOAD);
+ ensure_main();
+ return 0;
+ }
+ set_flat_from_reader();
+ const cm : u32 = read_main(S_SMP0[0..(S_SMP_N[0] as usize)]);
+ if (cm != 0) {
+ status_is(SAY_ERR_LOAD);
+ ensure_main();
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < (S_SMP_N[0] as usize)) {
+ S_MAIN[k] = S_SMP0[k];
+ k = k + 1;
+ }
+ S_MAIN_N = S_SMP_N[0];
+ S_DIRTY = false;
+ S_PRESSED = true;
+ status_is(SAY_LOCAL_NOTE);
+ S_FLAT = true;
+ use_result();
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ S_EF_N = 0;
+ if (i > 1) { return 0; }
+ if (s.len > 1048576) {
+ S_SMP_ST[i as usize] = 2;
+ } else {
+ var k : usize = 0;
+ if (i == 0) {
+ while (k < s.len) {
+ S_SMP0[k] = s[k];
+ k = k + 1;
+ }
+ } else {
+ while (k < s.len) {
+ S_SMP1[k] = s[k];
+ k = k + 1;
+ }
+ }
+ S_SMP_N[i as usize] = s.len as u32;
+ S_SMP_ST[i as usize] = 1;
+ }
+ if (S_SMP_ST[i as usize] == 2) { status_is(SAY_ERR_LOAD); }
+ show_sample();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ S_EF_N = 0;
+ if (i > 1) { return 0; }
+ S_SMP_ST[i as usize] = 2;
+ status_is(SAY_ERR_LOAD);
+ return 0;
+}
+
+pub fn w_input(i: u32, s: []const u8) -> u32 {
+ S_EF_N = 0;
+ if (i != 1) { return 0; }
+ S_PASTE_U = utf16_len(s, 0, s.len);
+ S_PASTE_BIG = s.len > 8388608;
+ var k : usize = 0;
+ while (k < s.len and k < 8388608) {
+ S_PASTE[k] = s[k];
+ k = k + 1;
+ }
+ S_PASTE_N = k as u32;
+ return 0;
+}
+
+/// UTF-16 length of the first bytes of a UTF-8 text.
+fn utf16_len(w: []const u8, lo: usize, hi: usize) -> u64 {
+ var n : u64 = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = w[k];
+ if (b < 128 or b >= 192) { n = n + 1; }
+ if (b >= 240) { n = n + 1; }
+ k = k + 1;
+ }
+ return n;
+}
+
+fn read_pasted() -> u32 {
+ if (S_PASTE_BIG and S_PASTE_U <= W_NUM_MAX_CHARS) {
+ arg_text(0, SAY_PASTED);
+ return status_fmt(SAY_ERR_READ, 1);
+ }
+ return read_text(S_PASTE[0..(S_PASTE_N as usize)], S_PASTE_U, SAY_PASTED, SAY_PASTED);
+}
+
+pub var S_FNAME : [2048]u8 = [0; 2048];
+pub var S_FNAME_N : u32 = 0;
+pub var S_FPICK : [4096]u8 = [0; 4096];
+pub var S_FPICK_N : u32 = 0;
+
+/// A file was picked or dropped on zone z: 1 when this page wants its bytes.
+pub fn w_file(z: u32, name: []const u8, size: f64) -> u32 {
+ S_EF_N = 0;
+ S_FNAME_N = copy_into(S_FNAME[0..], 0, name);
+ arg_text(0, name);
+ arg_loc(1, size);
+ const l : i64 = fmt_into(S_FPICK[0..], SAY_PICKED, 2);
+ S_FPICK_N = 0;
+ if (l >= 0) { S_FPICK_N = l as u32; }
+ if (size > (W_NUM_MAX_CHARS as f64)) {
+ too_big(size);
+ return 0;
+ }
+ return 1;
+}
+
+pub fn w_file_data(z: u32, data: []const u8) -> u32 {
+ S_EF_N = 0;
+ if (z == 0) {
+ read_text(data, utf16_len(data, 0, data.len), S_FNAME[0..(S_FNAME_N as usize)], S_FPICK[0..(S_FPICK_N as usize)]);
+ } else {
+ read_flat_text(data, utf16_len(data, 0, data.len), S_FNAME[0..(S_FNAME_N as usize)], S_FPICK[0..(S_FPICK_N as usize)]);
+ }
+ return 0;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ S_EF_N = 0;
+ arg_text(0, S_FNAME[0..(S_FNAME_N as usize)]);
+ status_fmt(SAY_ERR_READ, 1);
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ S_EF_N = 0;
+ if (z > 1) { return 0; }
+ const was : bool = S_OVER[z as usize];
+ S_OVER[z as usize] = over != 0;
+ if (was != S_OVER[z as usize]) { return 1; }
+ return 0;
+}
+
+pub fn w_value(i: u32, dst: []u8) -> u32 {
+ if (i == 0 and S_CLR_FILE[0]) {
+ S_CLR_FILE[0] = false;
+ return 0;
+ }
+ if (i == 1 and S_CLR_PASTE) {
+ S_CLR_PASTE = false;
+ return 0;
+ }
+ if (i == 2 and S_CLR_FILE[1]) {
+ S_CLR_FILE[1] = false;
+ return 0;
+ }
+ return 4294967295;
+}
+
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (k >= S_EF_N) {
+ S_EF_N = 0;
+ return 0;
+ }
+ dst[0] = 122;
+ return copy_into(dst, 1, W_SCROLL_SEL);
+}
+
+// --- what the page keeps between events, and the page ------------------------------------------------------
+
+pub var S_NUM3 : [64]u8 = [0; 64];
+pub var S_ROWS : [4096]u32 = [0; 4096];
+pub var S_ROWS_TMP : [4096]u32 = [0; 4096];
+pub var S_ROWS_N : u32 = 0;
+pub var S_PTH : [64]u32 = [0; 64];
+
+fn sel_on() -> bool {
+ return S_SEL >= 0 and name_len(S_SEL as usize) > 0;
+}
+
+/// render(): the tree is built again (w_render does that), the result opens, the lines that keep their text are set.
+fn do_render() -> u32 {
+ if (!S_HAVE) { return 0; }
+ S_RENDERED = true;
+ S_HIDDEN = false;
+ view_tree(S_METRIC as usize, K_EXPAND_MAX as f64, K_MAX_TILES as f64);
+ if (S_FLAT) {
+ const m : usize = S_METRIC as usize;
+ const t : f64 = top_total(m);
+ const f : f64 = F_TOT[m];
+ arg_loc(0, f);
+ arg_text(1, metric_name());
+ if (f == t) {
+ status_flatline(SAY_FLAT_SAME, 2);
+ } else {
+ arg_loc(2, t);
+ // d = t[m] ? `${f >= t[m] ? '+' : ''}${(((f - t[m]) / t[m]) * 100).toFixed(1)}%` : '-'
+ var n : u32 = 0;
+ if (t != 0.0 and t == t) {
+ if (f >= t) { n = copy_into(S_NUM2[0..], n, "+"); }
+ n = js_to_fixed(S_NUM2[0..], n, (((f - t) / t) * 100.0), 1);
+ n = copy_into(S_NUM2[0..], n, "%");
+ } else {
+ n = copy_into(S_NUM2[0..], n, "-");
+ }
+ arg_set(3, S_NUM2[0..], 0, n as usize);
+ status_flatline(SAY_FLAT_CMP, 4);
+ }
+ }
+ return 0;
+}
+
+fn status_flatline(tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_FLATLINE[0..], tpl, nargs);
+ S_FLATLINE_N = 0;
+ if (l >= 0) { S_FLATLINE_N = l as u32; }
+ return 0;
+}
+
+fn set_metric(m: u32) -> u32 {
+ S_METRIC = m;
+ S_TILE = 0 - 1;
+ return do_render();
+}
+
+fn clear_own() -> u32 {
+ S_PASTE_N = 0;
+ S_PASTE_U = 0;
+ S_PASTE_BIG = false;
+ S_CLR_PASTE = true;
+ S_CLR_FILE[0] = true;
+ S_CLR_FILE[1] = true;
+ status_is(SAY_LOCAL_NOTE);
+ show_sample();
+ return 0;
+}
+
+/// The rows of the table, sorted: S_ROWS[0..S_ROWS_N] are indexes into the reader's rows.
+fn right_first(l: u32, r: u32) -> bool {
+ const m : usize = S_METRIC as usize;
+ const d1 : f64 = row_total(r as usize, m) - row_total(l as usize, m);
+ if (d1 != 0.0 and d1 == d1) { return d1 > 0.0; }
+ const d2 : f64 = row_instances(r as usize) - row_instances(l as usize);
+ if (d2 != 0.0 and d2 == d2) { return d2 > 0.0; }
+ return false;
+}
+
+fn sort_rows() -> u32 {
+ const cnt : u32 = module_rows() as u32;
+ S_ROWS_N = cnt;
+ var i : u32 = 0;
+ while (i < cnt) {
+ S_ROWS[i as usize] = i;
+ i = i + 1;
+ }
+ var w : u32 = 1;
+ while (w < cnt) {
+ var lo : u32 = 0;
+ while (lo < cnt) {
+ var mid : u32 = lo + w;
+ if (mid > cnt) { mid = cnt; }
+ var hi : u32 = lo + w + w;
+ if (hi > cnt) { hi = cnt; }
+ var a : u32 = lo;
+ var b : u32 = mid;
+ var k : u32 = lo;
+ while (a < mid and b < hi) {
+ if (right_first(S_ROWS[a as usize], S_ROWS[b as usize])) {
+ S_ROWS_TMP[k as usize] = S_ROWS[b as usize];
+ b = b + 1;
+ } else {
+ S_ROWS_TMP[k as usize] = S_ROWS[a as usize];
+ a = a + 1;
+ }
+ k = k + 1;
+ }
+ while (a < mid) {
+ S_ROWS_TMP[k as usize] = S_ROWS[a as usize];
+ a = a + 1;
+ k = k + 1;
+ }
+ while (b < hi) {
+ S_ROWS_TMP[k as usize] = S_ROWS[b as usize];
+ b = b + 1;
+ k = k + 1;
+ }
+ lo = lo + w + w;
+ }
+ var c : u32 = 0;
+ while (c < cnt) {
+ S_ROWS[c as usize] = S_ROWS_TMP[c as usize];
+ c = c + 1;
+ }
+ w = w * 2;
+ }
+ return cnt;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ S_EF_N = 0;
+ const rows : u32 = S_ROWS_N;
+ if (i < 4) {
+ set_metric(i);
+ ensure_main();
+ return 0;
+ }
+ if (i < 4 + rows) {
+ const nid : i32 = row_nid(S_ROWS[(i - 4) as usize] as usize) as i32;
+ if (S_SEL == nid) { S_SEL = 0 - 1; } else { S_SEL = nid; }
+ S_TILE = 0 - 1;
+ return 0;
+ }
+ const b : u32 = i - 4 - rows;
+ if (b == 0) { show_sample(); }
+ if (b == 1) { read_pasted(); }
+ if (b == 2) { clear_own(); }
+ ensure_main();
+ return 0;
+}
+
+/// A click on a tile: map.addEventListener('click').
+pub fn w_tile(id: u32) -> u32 {
+ S_EF_N = 0;
+ if (!S_HAVE) { return 0; }
+ if (!S_PLACED_OK or id >= 32768 or S_PLACED[id as usize] == 0) { return 0; }
+ S_TILE = id as i32;
+ S_SEL = tile_nid(id as usize) as i32;
+ return 0;
+}
+
+// --- writing the page -----------------------------------------------------------------------------------
+
+fn button(dst: []u8, n0: u32, label: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = text(dst, n, label);
+ return raw(dst, n, "");
+}
+
+fn put_px(dst: []u8, n0: u32, x: f64) -> u32 {
+ var e : u32 = js_to_fixed(S_NUM3[0..], 0, x, 1);
+ if (e >= 2 and S_NUM3[(e - 2) as usize] == 46 and S_NUM3[(e - 1) as usize] == 48) { e = e - 2; }
+ var n : u32 = raw(dst, n0, S_NUM3[0..(e as usize)]);
+ return raw(dst, n, "px");
+}
+
+fn disp_bytes(nid: usize) -> []const u8 {
+ return POOL[disp_off(nid)..(disp_off(nid) + disp_len(nid))];
+}
+
+/// fmt(tpl, display(nid), a, b): the tile words.
+fn tile_words(tpl: []const u8, nid: usize, x: f64, y: f64, nargs: u32) -> i64 {
+ arg_set(0, POOL[0..], disp_off(nid), disp_off(nid) + disp_len(nid));
+ if (nargs > 1) { arg_num(1, x); }
+ if (nargs > 2) { arg_num(2, y); }
+ return fmt_into(S_FB[0..], tpl, nargs);
+}
+
+/// tileName(node) into S_FB; the length.
+fn tile_name(t: u32) -> u32 {
+ const nid : usize = tile_nid(t as usize);
+ var l : i64 = 0;
+ if (tile_own(t as usize)) {
+ l = tile_words(SAY_TILE_OWN, nid, 0.0, 0.0, 1);
+ } else {
+ if (tile_index(t as usize) != 0.0) {
+ l = tile_words(SAY_TILE_COPY, nid, tile_index(t as usize), tile_of(t as usize), 3);
+ } else {
+ if (tile_copies(t as usize) > 1.0) {
+ l = tile_words(SAY_TILE_GROUP, nid, tile_copies(t as usize), 0.0, 2);
+ } else {
+ l = copy_into(S_FB[0..], 0, disp_bytes(nid)) as i64;
+ }
+ }
+ }
+ if (l < 0) { return 0; }
+ return l as u32;
+}
+
+/// tileLabel(node) into S_FB; the length.
+fn tile_label(t: u32) -> u32 {
+ const nid : usize = tile_nid(t as usize);
+ var l : i64 = 0;
+ if (tile_own(t as usize)) {
+ l = tile_words(SAY_TILE_OWN, nid, 0.0, 0.0, 1);
+ } else {
+ if (tile_index(t as usize) != 0.0) {
+ l = tile_words(SAY_TILE_SHORT, nid, tile_index(t as usize), 0.0, 2);
+ } else {
+ if (tile_copies(t as usize) > 1.0) {
+ l = tile_words(SAY_TILE_GROUP, nid, tile_copies(t as usize), 0.0, 2);
+ } else {
+ l = copy_into(S_FB[0..], 0, disp_bytes(nid)) as i64;
+ }
+ }
+ }
+ if (l < 0) { return 0; }
+ return l as u32;
+}
+
+pub var S_LAB : [8192]u8 = [0; 8192];
+pub var S_TTL : [8192]u8 = [0; 8192];
+
+fn put_map(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ const m : usize = S_METRIC as usize;
+ const w : f64 = S_MAP_W;
+ const hgt : f64 = S_MAP_H;
+ const tn : usize = view_tree(m, K_EXPAND_MAX as f64, K_MAX_TILES as f64);
+ const val0 : f64 = tile_value(0);
+ if (tn == 0 or val0 == 0.0 or val0 != val0 or w < 10.0 or hgt < 10.0) {
+ arg_text(0, metric_name());
+ n = raw(dst, n, "
");
+ n = put_say(dst, n, SAY_METRIC_EMPTY, 1);
+ return raw(dst, n, "
");
+ }
+ const rc : usize = layout_tree(0.0, 0.0, w, hgt, K_HEAD_PX as f64, K_PAD_PX as f64);
+ var z : usize = 0;
+ while (z < tn) {
+ S_PLACED[z] = 0;
+ z = z + 1;
+ }
+ S_PLACED_OK = true;
+ ft_make();
+ var i : usize = 0;
+ while (i < rc) {
+ const t : usize = rect_node(i);
+ S_PLACED[t] = 1;
+ const leaf : bool = tile_first(t) < 0;
+ const is_own : bool = tile_own(t);
+ const px : f64 = rect_x(i);
+ const py : f64 = rect_y(i);
+ const pw : f64 = rect_w(i);
+ const ph : f64 = rect_h(i);
+ // the text of the label: the display name of a plain leaf, else tileLabel
+ var ll : u32 = 0;
+ if (leaf and !is_own and !(tile_copies(t) > 1.0)) {
+ ll = copy_into(S_LAB[0..], 0, disp_bytes(tile_nid(t)));
+ } else {
+ const tl : u32 = tile_label(t as u32);
+ ll = copy_into(S_LAB[0..], 0, S_FB[0..(tl as usize)]);
+ }
+ var label : bool = false;
+ var head_or_h : bool = false;
+ if (leaf) { head_or_h = ph >= (K_LABEL_MIN_H as f64); } else { head_or_h = rect_head(i); }
+ if (head_or_h and pw >= (K_LABEL_MIN_W as f64)) {
+ label = x_width(S_LAB[0..(ll as usize)]) + 8.0 <= pw;
+ }
+ n = raw(dst, n, "
0.0)) { cw = 0.0; }
+ n = put_px(dst, n, cw);
+ n = raw(dst, n, "; height: ");
+ var chh : f64 = ph;
+ if (!(chh > 0.0)) { chh = 0.0; }
+ n = put_px(dst, n, chh);
+ n = raw(dst, n, "; --hue: ");
+ n = num(dst, n, S_HUE[tile_nid(t)] as u64);
+ n = raw(dst, n, ";\" title=\"");
+ const nl : u32 = tile_name(t as u32);
+ var tt : u32 = copy_into(S_TTL[0..], 0, S_FB[0..(nl as usize)]);
+ tt = copy_into(S_TTL[0..], tt, ": ");
+ arg_loc(0, tile_value(t));
+ arg_text(1, metric_name());
+ const vl : i64 = fmt_into(S_FB[0..], SAY_TILE_VALUE, 2);
+ if (vl >= 0) { tt = copy_into(S_TTL[0..], tt, S_FB[0..(vl as usize)]); }
+ n = text(dst, n, S_TTL[0..(tt as usize)]);
+ n = raw(dst, n, "\" data-id=\"");
+ n = num(dst, n, t as u64);
+ n = raw(dst, n, "\">");
+ if (label) {
+ n = raw(dst, n, "");
+ n = text(dst, n, S_LAB[0..(ll as usize)]);
+ n = raw(dst, n, "");
+ if (leaf and ph >= (K_LABEL_MIN_H as f64) * 2.0) {
+ n = raw(dst, n, "");
+ n = put_locale_num(dst, n, tile_value(t));
+ n = raw(dst, n, "");
+ }
+ }
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return n;
+}
+
+fn put_locale_num(dst: []u8, n0: u32, x: f64) -> u32 {
+ return js_locale_num(dst, n0, x);
+}
+
+fn put_table(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ const m : usize = S_METRIC as usize;
+ const top : f64 = top_total(m);
+ const cnt : u32 = sort_rows();
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TABLE_CAPTION);
+ n = raw(dst, n, "
");
+ var h : usize = 0;
+ while (h < SAY_TABLE_HEAD.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_TABLE_HEAD[h]);
+ n = raw(dst, n, "
");
+ h = h + 1;
+ }
+ n = raw(dst, n, "
");
+ var r : u32 = 0;
+ while (r < cnt) {
+ const ri : usize = S_ROWS[r as usize] as usize;
+ const nid : usize = row_nid(ri);
+ const picked : bool = S_SEL == (nid as i32);
+ n = raw(dst, n, "
");
+ n = text(dst, n, disp_bytes(nid));
+ n = raw(dst, n, "
");
+ n = put_locale_num(dst, n, row_instances(ri));
+ n = raw(dst, n, "
");
+ n = put_locale_num(dst, n, own(nid, m));
+ n = raw(dst, n, "
");
+ n = put_locale_num(dst, n, row_total(ri, m));
+ n = raw(dst, n, "
");
+ const tot : f64 = row_total(ri, m);
+ if (tot != 0.0 and tot == tot) { n = put_pct(dst, n, tot, top); } else { n = raw(dst, n, "-"); }
+ n = raw(dst, n, "
");
+ r = r + 1;
+ }
+ return raw(dst, n, "");
+}
+
+/// The path of tile t from the top, as the words of tool.js's pathText: each step is one tile.
+fn put_detail(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ const m : usize = S_METRIC as usize;
+ const top : f64 = top_total(m);
+ if (S_TILE >= 0 and S_PLACED_OK and S_PLACED[S_TILE as usize] == 1) {
+ const t : usize = S_TILE as usize;
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DETAIL_PATH);
+ n = raw(dst, n, " ");
+ n = text(dst, n, disp_bytes(top_nid()));
+ // the chain: an own tile has its parent's path
+ var c : i32 = t as i32;
+ if (tile_own(t)) { c = tile_parent(t); }
+ var depth : u32 = 0;
+ var q : i32 = c;
+ while (q > 0 and depth < 64) {
+ S_PTH[depth as usize] = q as u32;
+ depth = depth + 1;
+ q = tile_parent(q as usize);
+ }
+ while (depth > 0) {
+ depth = depth - 1;
+ const e : usize = S_PTH[depth as usize] as usize;
+ n = text(dst, n, W_SEP);
+ var l : i64 = 0;
+ if (tile_index(e) != 0.0) {
+ l = tile_words(SAY_TILE_COPY, tile_nid(e), tile_index(e), tile_of(e), 3);
+ } else {
+ l = tile_words(SAY_TILE_GROUP, tile_nid(e), tile_of(e), 0.0, 2);
+ }
+ if (l >= 0) { n = text(dst, n, S_FB[0..(l as usize)]); }
+ }
+ if (tile_own(t)) {
+ n = text(dst, n, W_SEP);
+ const ol : i64 = tile_words(SAY_TILE_OWN, tile_nid(t), 0.0, 0.0, 1);
+ if (ol >= 0) { n = text(dst, n, S_FB[0..(ol as usize)]); }
+ }
+ n = raw(dst, n, "
");
+ arg_loc(0, tile_value(t));
+ arg_text(1, metric_name());
+ pct_text(tile_value(t), top);
+ arg_set(2, S_NUM2[0..], 0, pct_len() as usize);
+ arg_loc(3, top);
+ n = put_say(dst, n, SAY_DETAIL_LINE, 4);
+ return raw(dst, n, "
");
+ }
+ if (sel_on()) {
+ // the row of the selected module, if it is under the top
+ const cnt : u32 = module_rows() as u32;
+ var r : u32 = 0;
+ var found : bool = false;
+ while (r < cnt and !found) {
+ if ((row_nid(r as usize) as i32) == S_SEL) { found = true; } else { r = r + 1; }
+ }
+ if (found) {
+ const nid : usize = S_SEL as usize;
+ arg_set(0, POOL[0..], disp_off(nid), disp_off(nid) + disp_len(nid));
+ arg_loc(1, row_instances(r as usize));
+ arg_loc(2, own(nid, m));
+ arg_loc(3, row_total(r as usize, m));
+ arg_text(4, metric_name());
+ pct_text(row_total(r as usize, m), top);
+ arg_set(5, S_NUM2[0..], 0, pct_len() as usize);
+ n = raw(dst, n, "
");
+ n = put_say(dst, n, SAY_DETAIL_MODULE, 6);
+ return raw(dst, n, "
");
+ }
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DETAIL_HINT);
+ return raw(dst, n, "
");
+}
+
+pub var S_PCT_N : u32 = 0;
+
+/// pct(a, b) into S_NUM2, its length into S_PCT_N.
+fn pct_len() -> u32 {
+ return S_PCT_N;
+}
+
+fn put_lines_and_map(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ const m : usize = S_METRIC as usize;
+ n = raw(dst, n, "
");
+ if (S_RENDERED) {
+ arg_loc(0, top_total(m));
+ arg_text(1, metric_name());
+ arg_set(2, POOL[0..], disp_off(top_nid()), disp_off(top_nid()) + disp_len(top_nid()));
+ n = put_say(dst, n, SAY_TOTAL, 3);
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_METRIC_LABEL);
+ n = raw(dst, n, "");
+ var b : u32 = 0;
+ while (b < 4) {
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_METRIC_NAMES[b as usize]);
+ n = raw(dst, n, "");
+ b = b + 1;
+ }
+ n = raw(dst, n, "
");
+ if (S_RENDERED and is_flat()) { n = text(dst, n, SAY_FLAT_WARN); }
+ n = raw(dst, n, "
");
+ if (S_RENDERED) {
+ arg_loc(0, sum);
+ if (!has_design()) {
+ n = put_say(dst, n, SAY_SUM_NO_DESIGN, 1);
+ } else {
+ arg_loc(1, design_val(m));
+ if (bad) { n = put_say(dst, n, SAY_SUM_BAD, 2); } else { n = put_say(dst, n, SAY_SUM_OK, 2); }
+ }
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, S_FLATLINE[0..(S_FLATLINE_N as usize)]);
+ n = raw(dst, n, "
");
+ if (S_RENDERED) { n = put_map(dst, n); }
+ n = raw(dst, n, "
");
+ if (S_RENDERED) { n = put_detail(dst, n); }
+ n = raw(dst, n, "
");
+ if (S_RENDERED) { n = put_table(dst, n); n = raw(dst, n, "
"); } else { n = raw(dst, n, "
"); }
+ if (S_RENDERED) {
+ var any_copy : bool = false;
+ var any_group : bool = false;
+ var t : usize = 0;
+ const tn : usize = view_tree(m, K_EXPAND_MAX as f64, K_MAX_TILES as f64);
+ while (t < tn) {
+ if (tile_index(t) > 0.0) { any_copy = true; }
+ if (!tile_own(t) and tile_index(t) == 0.0 and tile_of(t) > 0.0) { any_group = true; }
+ t = t + 1;
+ }
+ if (any_copy) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NOTE_COPIES);
+ n = raw(dst, n, "
");
+ }
+ if (any_group) {
+ arg_loc(0, K_EXPAND_MAX as f64);
+ arg_loc(1, K_MAX_TILES as f64);
+ n = raw(dst, n, "
");
+ n = put_say(dst, n, SAY_NOTE_GROUPED, 2);
+ n = raw(dst, n, "
");
+ }
+ }
+ return raw(dst, n, "
");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = put_lines_and_map(dst, n);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SAMPLE_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SAMPLE_NAME);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HOW);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = button(dst, n, SAY_READ);
+ n = button(dst, n, SAY_CLEAR);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FLAT_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FLAT_DROP);
+ n = raw(dst, n, "
");
+ n = text(dst, n, S_STATUS[0..(S_STATUS_N as usize)]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HONEST);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+
+// --- Tests of the native page -----------------------------------------------------------------------------
+
+/// Does the first n bytes of o contain the text want?
+fn has_text(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (want.len > (n as usize)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn count_text(o: []const u8, n: u32, want: []const u8) -> u32 {
+ var c : u32 = 0;
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { c = c + 1; }
+ i = i + 1;
+ }
+ return c;
+}
+
+/// The host measures the q requests in req: w bytes of text are w * 6 pixels, or 'wide' when asked for.
+fn give_widths(req: []const u8, q: u32, each: f64) -> u32 {
+ var wd : [8192]f64 = [0.0; 8192];
+ var p : u32 = 0;
+ var i : u32 = 0;
+ while (i < q and i < 8192) {
+ const tl : u32 = (req[(p + 4) as usize] as u32) + 256 * (req[(p + 5) as usize] as u32);
+ const fl : u32 = (req[p as usize] as u32) + 256 * (req[(p + 1) as usize] as u32);
+ wd[i as usize] = (tl as f64) * each;
+ p = p + 8 + fl + tl;
+ i = i + 1;
+ }
+ return w_text_give(wd[0..], q);
+}
+
+/// Write the page, answer the width requests, write it again, until no request is left. Returns the length.
+fn page_with_widths(o: []u8, each: f64) -> u32 {
+ var req : [262144]u8 = [0; 262144];
+ var n : u32 = 0;
+ var rounds : u32 = 0;
+ var go : bool = true;
+ while (go and rounds < 20) {
+ n = w_render(o);
+ const q : u32 = w_text_need(req[0..]);
+ if (q == 0) { go = false; } else { give_widths(req[0..], q, each); }
+ rounds = rounds + 1;
+ }
+ return n;
+}
+
+fn open_page() -> u32 {
+ w_init("");
+ w_base("http://x/y/");
+ w_mono("Fira Code, monospace");
+ return 0;
+}
+
+fn load_sample() -> u32 {
+ w_data(0, SAMPLE);
+ w_data(1, SAMPLE_FLAT);
+ w_size(900.0, 600.0);
+ return 0;
+}
+
+test the_page_opens_with_the_result_hidden_and_the_lines_unset {
+ open_page();
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert n > 0;
+ assert has_text(o[0..], n, "
");
+ assert has_text(o[0..], n, "LUT");
+ assert has_text(o[0..], n, "");
+ assert has_text(o[0..], n, "role=\"status\">Read in this tab; nothing is sent.");
+ assert has_text(o[0..], n, "href=\"http://x/y/sample.flat.stat.json\" target=\"_blank\" rel=\"noopener\">and the flattened one");
+ assert has_text(o[0..], n, "
") == false;
+}
+
+test the_sample_opens_129_tiles_the_sum_the_flat_line_and_the_table {
+ open_page();
+ load_sample();
+ assert S_HAVE and !S_HIDDEN and S_PRESSED and S_FLAT;
+ var o : [524288]u8 = [0; 524288];
+ const n : u32 = page_with_widths(o[0..], 1.0);
+ assert has_text(o[0..], n, "
20,464 LUT under gf16_matmul4x4_p
");
+ assert has_text(o[0..], n, "aria-pressed=\"true\" data-m=\"lut\">LUT");
+ assert has_text(o[0..], n, "
The tiles add up to 20,464, the top's total; yosys printed 20,464 for the whole design.
");
+ assert has_text(o[0..], n, "
Flattened run of the same design: 18,730 LUT. Hierarchical: 20,464 (-8.5%).");
+ assert has_text(o[0..], n, "
");
+ assert has_text(o[0..], n, "
");
+ assert count_text(o[0..], n, "
gf16_add
48
293
14,064
68.7%
");
+ assert has_text(o[0..], n, "
0
-
");
+ assert has_text(o[0..], n, "
Tap a tile or a row to see where it sits.
");
+ assert !has_text(o[0..], n, "Some instances share one tile");
+ assert has_text(o[0..], n, "stat -json names no instances");
+}
+
+test a_small_map_says_there_is_nothing_to_draw_and_a_resize_draws_it {
+ open_page();
+ w_data(0, SAMPLE);
+ w_data(1, SAMPLE_FLAT);
+ var o : [524288]u8 = [0; 524288];
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "
");
+}
+
+test labels_are_left_off_where_the_tile_is_too_narrow_for_them {
+ open_page();
+ load_sample();
+ var o : [524288]u8 = [0; 524288];
+ var n : u32 = page_with_widths(o[0..], 1.0);
+ const some : u32 = count_text(o[0..], n, "");
+ assert some > 0;
+ open_page();
+ load_sample();
+ n = page_with_widths(o[0..], 1000.0);
+ assert count_text(o[0..], n, "") == 0;
+ assert count_text(o[0..], n, "
Path from the top gf16_matmul4x4_p > gf16_dot4_p #1 of 16 > gf16_mul #1 of 4");
+ assert has_text(o[0..], n, "of the top's 20,464.");
+ assert has_text(o[0..], n, " is-dim");
+ w_click(4);
+ assert S_SEL == (row_nid(S_ROWS[0] as usize) as i32);
+ assert S_TILE == 0 - 1;
+ n = page_with_widths(o[0..], 1.0);
+ assert has_text(o[0..], n, "aria-pressed=\"true\" style=\"--hue: 152;\"");
+ assert has_text(o[0..], n, "
gf16_add: 48 x 293 = 14,064 LUT, 68.7% of the top.
");
+ assert has_text(o[0..], n, "aria-pressed=\"true\" data-m=\"ff\">FF");
+ assert has_text(o[0..], n, "Flattened run of the same design: 1,792 FF, the same as hierarchical.");
+ assert count_text(o[0..], n, "
No BRAM cells in this design: nothing to draw.");
+}
+
+test texts_that_do_not_read_say_why_and_keep_the_stat_that_is_shown {
+ open_page();
+ load_sample();
+ var o : [524288]u8 = [0; 524288];
+ w_input(1, "hello");
+ w_click(4 + S_ROWS_N + 1);
+ var n : u32 = page_with_widths(o[0..], 1.0);
+ assert has_text(o[0..], n, "role=\"status\">No JSON object in this text. Run stat with -json.");
+ assert has_text(o[0..], n, "
20,464 LUT under gf16_matmul4x4_p
");
+ assert count_text(o[0..], n, "
Every module is instantiated by another, so there is no top.");
+ w_input(1, "{\"modules\":{\"top\":{\"cells\":{\"LUT6\":3}}}}");
+ w_click(4 + S_ROWS_N + 1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">pasted text. Read in this tab; nothing is sent.");
+ assert has_text(o[0..], n, "
3 LUT under top
");
+ assert has_text(o[0..], n, "
This stat is flat: no module holds another, so there is one tile.");
+ assert has_text(o[0..], n, "
Flattened run of the same design: 18,730");
+ assert S_EF_N == 1;
+ var e : [64]u8 = [0; 64];
+ assert w_effect(0, e[0..]) == 1 + 10;
+ assert e[0] == 122;
+ assert w_effect(1, e[0..]) == 0;
+}
+
+test the_flat_drop_is_checked_and_compared_and_the_stat_shown_is_read_again {
+ open_page();
+ load_sample();
+ var o : [524288]u8 = [0; 524288];
+ assert w_file(1, "f.json", 100.0) == 1;
+ w_file_data(1, SAMPLE);
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">That stat still has submodules; the second drop is for a -flatten run.
");
+ assert has_text(o[0..], n, "
20,464 LUT under gf16_matmul4x4_p
");
+ w_file_data(1, "{\"modules\":{\"t\":{\"cells\":{\"LUT6\":20000}}}}");
+ n = page_with_widths(o[0..], 1.0);
+ assert has_text(o[0..], n, "role=\"status\">f.json: 100 bytes. Read in this tab; nothing is sent.");
+ assert has_text(o[0..], n, "Flattened run of the same design: 20,000 LUT. Hierarchical: 20,464 (-2.3%).");
+ assert count_text(o[0..], n, "
8,000,001 characters is more than this page reads (8,000,000).");
+ w_file_failed(0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">Could not read big.json.");
+ assert w_drag(1, 1) == 1;
+ assert w_drag(1, 1) == 0;
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "");
+}
+
+test clear_goes_back_to_the_sample_and_empties_the_inputs {
+ open_page();
+ load_sample();
+ var o : [524288]u8 = [0; 524288];
+ w_input(1, "{\"modules\":{\"top\":{\"cells\":{\"LUT6\":3}}}}");
+ w_click(4 + S_ROWS_N + 1);
+ assert !S_PRESSED;
+ w_click(4 + S_ROWS_N + 2);
+ assert S_PRESSED;
+ var v : [8]u8 = [0; 8];
+ assert w_value(0, v[0..]) == 0;
+ assert w_value(1, v[0..]) == 0;
+ assert w_value(2, v[0..]) == 0;
+ assert w_value(1, v[0..]) == 4294967295;
+ const n : u32 = page_with_widths(o[0..], 1.0);
+ assert has_text(o[0..], n, "
20,464 LUT under gf16_matmul4x4_p
");
+}
+
+test the_sample_that_does_not_load_says_so {
+ open_page();
+ w_data_failed(0);
+ var o : [65536]u8 = [0; 65536];
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">The sample did not load.");
+ assert S_HIDDEN;
+ w_data(1, SAMPLE_FLAT);
+ w_click(4 + S_ROWS_N);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">The sample did not load.");
+}
+
+test the_fold_reads_the_specs_own_lists_as_group_of_does {
+ open_page();
+ assert parse_hier("{\"modules\":{\"t\":{\"cells\":{\"LUT2\":1,\"FDSE\":2,\"DSP48E1\":3,\"RAMB18E1\":4,\"$scopeinfo\":5,\"XYZ\":6}}}}") == E_OK;
+ assert own(top_nid(), 0) == 1.0;
+ assert own(top_nid(), 1) == 2.0;
+ assert own(top_nid(), 2) == 3.0;
+ assert own(top_nid(), 3) == 4.0;
+ assert own(top_nid(), 4) == 6.0;
+ assert same_text(W_DATA_FILES[0], K_SAMPLE_STAT);
+ assert same_text(W_DATA_FILES[1], K_SAMPLE_FLAT_STAT);
+ assert same_text(K_LUT_TYPES[1], "LUT2");
+ assert same_text(K_FF_TYPES[1], "FDSE");
+ assert same_text(K_BRAM_TYPES[0], "RAMB18E1");
+ assert same_text(K_INTERNAL_TYPES[0], "$scopeinfo");
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions mention.
+/// This anchor mentions every helper the writer and the number rules call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + num_grouped(o, 0, 0);
+ return n;
+}
From 1b6b0758df67f8d3468b25eeb4b6243ea1c50faa Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 21:58:11 +0700
Subject: [PATCH 56/63] widgets: catalog records four canvas pages at
PORT_DRAW; dupe ledger re-blessed for the new folded helpers (Closes #11016)
---
specs/widgets/catalog.t27 | 16 ++-
tools/duplicate_bodies_baseline.txt | 214 +++++++++++++++++++++++++---
2 files changed, 206 insertions(+), 24 deletions(-)
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index b79e8d7e50..4438854604 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -37,7 +37,7 @@
; 14667449, lut-explain 1395124, bit-diff 275620, vcd-wrapped 76076, pin-map 180587, synth-smells 87094, fsm-sketch 91657 (its Math.sin/cos/atan2 stay with the host: V8 differs from libm in the last bit), bit-autopsy 96007.
; With these, all 12 pure logic files of the census (2775 lines of JavaScript) exist as .t27.
; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
-; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) fsm-sketch and cost-curves at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
+; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) fsm-sketch, cost-curves, lut-treemap, stat-card, synth-smells and vcd-wrapped at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
; comparisons vs tool.js, 0 mismatches; specs/widgets/race_the_bee.t27: 59, with 13 data variants, 0). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
@@ -91,7 +91,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [2, 2, 3, 3, 2, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 1, 3, 3, 1, 2, 2, 3, 3, 1, 3, 1, 3, 2];
+pub const TOOL_PORT : [28]u8 = [2, 2, 3, 3, 2, 3, 3, 2, 3, 1, 3, 3, 3, 3, 3, 1, 3, 3, 1, 3, 3, 3, 3, 1, 3, 1, 3, 3];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -227,12 +227,14 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test fifteen_pages_are_native_and_the_remaining_logic_files_are_ported {
- assert count_port(PORT_DRAW) == 15;
- assert count_port(PORT_LOGIC) == 8;
+test nineteen_pages_are_native_and_the_remaining_logic_files_are_ported {
+ assert count_port(PORT_DRAW) == 19;
+ assert count_port(PORT_LOGIC) == 4;
assert count_port(PORT_WORDS) == 5;
assert count_port(PORT_SEALED) == 0;
- assert TOOL_PORT[3] == PORT_DRAW;
- assert TOOL_PORT[6] == PORT_DRAW;
+ assert TOOL_PORT[13] == PORT_DRAW;
+ assert TOOL_PORT[19] == PORT_DRAW;
+ assert TOOL_PORT[20] == PORT_DRAW;
+ assert TOOL_PORT[27] == PORT_DRAW;
assert TOOL_PORT[7] == PORT_LOGIC;
}
diff --git a/tools/duplicate_bodies_baseline.txt b/tools/duplicate_bodies_baseline.txt
index 01884bc891..a5b792c133 100644
--- a/tools/duplicate_bodies_baseline.txt
+++ b/tools/duplicate_bodies_baseline.txt
@@ -7,8 +7,8 @@ sadd 31
build_tree 28,2
magmul 22,2
smul 20,2
+bytes_eq 14,3
tmul 13
-bytes_eq 10,3
relu 10
dot27 8,5
contrib 7
@@ -27,6 +27,8 @@ pack3 5
pow 5,2
scale_q 5
tp 5
+ws_len_at 5
+bom_skip 4
cv_same 4
fromRaw 4
gft_add 4
@@ -34,10 +36,15 @@ gft_mul 4
go_down 4
has_substring 4
match_at 4
+match_in 4
neuron4 4
nn_text 4
nullish 4
str_eq 4
+ws_at 4
+ws_back 4
+ws_before 4
+ws_fwd 4
arena_reserve 3
assign 3
block_seen_before 3
@@ -47,6 +54,7 @@ concat 3,2
copy_bytes_at 3
copy_to 3
cos 3
+count_of 3
crc_byte 3
crc_step 3
dest_count 3
@@ -54,12 +62,15 @@ digits_val 3
dnum 3
fill 3
floor_log2 3,2
+fmt_into 3
gadd 3
gmul 3
+has 3,2
hex_nibble_of 3
+hex_val 3,3
id_seen_before 3
+is_word 3,2
list_active 3
-match_in 3
mem_eql_at 3
negate 3
neuronN 3
@@ -67,19 +78,19 @@ on_comb 3,2,2
pack2 3
pow2_frac 3
publish_result_to_string 3
-put_all 3
+put_all 3,2
+skip_digits 3
+skip_ws 3,2
starts_with_at 3
strings_equal 3
strings_equal_inner 3
ternary_matmul 3
+trim_span 3
trit2gft 3
trit_negate 3
+utf16_len 3
wait_cycles 3
-ws_at 3
-ws_back 3
-ws_before 3
-ws_fwd 3
-ws_len_at 3
+ws_len_before 3
HirCoverPoint_validate 2
LinkSection_bss 2
LinkSection_data 2
@@ -88,11 +99,25 @@ adaptive_temperature_by_sacred_score 2
add 2
ads_equal 2
all_positive 2
+alloc_node 2
+analyze 2
+arg_num 2
+arg_set 2
asr9 2
assert_pass 2
axi4_full_master 2
axi4_lite_master 2
+bad_var_text 2
+bare_len 2
+bare_off 2
+bare_skip 2
+base_name_len 2
+before 2
+big_cmp 2
big_from_bits 2
+big_mul 2
+big_mul_pow 2
+big_set 2
bit_at 2
bitlen32 2
bn_add 2
@@ -112,13 +137,22 @@ bn_set_pow 2
bn_shl 2
bn_sub 2
bn_u64 2
-bom_skip 2
+body_token 2
booth_mul_i16 2
booth_mul_u32_inner 2
+bput 2
+bput_u 2
+bputs 2
+btn_open 2
build_report_frame 2
+button 2
+by_type 2
+by_type_kind 2
+by_type_of 2
bytes32_eq 2
bytes64_eq 2
carries_src2 2
+cb_is 2
cell_match 2
charged 2
chi_squared_flat 2
@@ -126,12 +160,16 @@ cl_copy 2
cl_data 2
cl_sel 2
cl_start 2
+clear_m 2
cmd_match 2
cmd_prompt 2
cmd_running_any 2
cmd_running_ltp 2
compress 2
contains 2
+conv 2
+copy_into 2
+copy_small 2
cordic_cos 2
cordic_sin 2
cordic_x_next 2
@@ -145,82 +183,161 @@ debug_state_bits 2
dec_f64 2
dec_len 2
dec_small 2
+dec_value 2
deque_clear 2
digits_end 2
+disp_build 2
+disp_param 2
+display_name 2
dot4 2
effect_wanted 2
encode 2
+end_command 2
+end_var 2
+ent_build 2
+esc_byte 2
exp2 2
+extend_into 2
extract_mantissa 2,2
+fail 2
+feed 2
+fill_plain 2
+fin 2
find_byte 2
+find_child 2
find_in 2
+find_info 2
find_inside 2
+find_item 2
+find_module 2
find_scheme_end 2
+find_sig 2
+find_version 2
fl_clear 2
+flat 2
fmt4 2
free_node 2
full_adder 2
generate_next_token_from_logits 2
+group_of 2,2,2
halves_o5 2
hard_sigmoid 2
has_at 2
has_cr 2
has_star_inner 2
+has_xzuw 2
+hash_id 2
hash_key 2
head_match 2
head_tail 2
-hex4 2
-hex_val 2,2
+header_token 2
+hex4 2,2,2
hexv 2
hw_place_before 2
+id_eq 2
id_provisioned 2
imports_anchor 2,2
+index_key 2
init_bin 2
init_hex 2
input_at 2
+inst_visit 2
+instances_of 2
int_to_bytes 2
+intlike 2
isDatabaseConfigured 2
is_class 2
is_counter_power_of_two 2
+is_exec 2
is_hex 2
+is_hit 2
is_indented 2
is_name_byte 2
+is_nan_x_z 2
+is_objlike 2
+is_pass_header 2
is_slice_site 2
+is_stat_module 2
+is_stat_start 2
is_text 2
-is_word 2,2
+is_type_char 2
+is_type_start 2
+is_unknown_char 2
is_word_char 2
+j_number 2
+j_object 2
+j_parse 2
+j_value 2
+j_ws 2
+jnumber 2
+js_round 2
+js_width 2
+json_top 2
jstr_copy 2
jstr_is 2
+jstring 2
+jvalue 2
+jws 2
kill 2
+layout_tree 2
lb_value 2
+leaves_n 2
+leaves_sum 2
+leaves_walk 2
led_config 2
led_counter_next 2
led_state_next 2
line_end 2
line_hi 2
+link_child 2
+lists_default 2
+lit_at 2
lit_is 2
lit_match 2
local_branch 2
+locale_text 2
log2_frac 2
log2v 2
log_frame 2
lookup 2
loop_match 2
lowest_bit 2
+ls_line 2
+lvl_add 2
+m_command 2
+m_conflict 2
+m_error 2
+m_latch 2
+m_memory 2
+m_problems 2
+m_removed 2
+m_summary 2
+m_undriven 2
mac_acc_read 2
matmul_probe 2
matmul_rect_probe 2
mem_eql 2
member_contains 2
minimize 2
+module_rows 2
+name_find 2
+name_less 2
neg_zero 2
nested_tail 2
new_graph 2
+nid_named 2
notebook_create 2
+num_of 2,2
+num_val 2
+obj_get 2
+obj_put 2
obs_seen_before 2
on_clock 2
opt_cmp 2
ordinary_if 2
+out_marker 2
+out_put 2
+out_span 2
out_str 2
parse_input 2
parse_literal 2
@@ -228,15 +345,36 @@ pi_bad 2
pi_bits 2
pi_push 2
pipeline_f32 2
+pool_eq 2,2
+pool_is 2
+pool_prefix 2
+pool_push_uint 2
+pool_put 2,2
+pool_put_u 2
+pool_same 2
+pool_ws_at 2
+pool_ws_before 2
pop64 2
+pow10 2
pow10_small 2
pow10f 2
+prefix_bytes 2
press_act 2
price_tokens 2
+proto_name 2
+push_copy 2
+push_utf8 2
put 2
+put_dec 2
+put_item 2
put_lit 2
put_range 2
+put_say 2
+put_u32le 2
+put_utf8 2
read_be32 2
+record 2
+rect_go 2
remove 2
rfind_in 2
rtrim_end 2
@@ -244,28 +382,45 @@ runRailwayLogsCommand 2
run_die_level_with 2
run_starts_job 2
rx_slice 2
+same_as_design 2
same_f64 2
same_range 2,2
same_text 2
+same_totals 2
sample_imm 2
sample_reg 2
session_status_str 2
+set_src 2
+set_time_from_token 2
shift_ir 2
short_block 2
shortest_dec 2
signmul 2
-skip_digits 2
-skip_ws 2,2
slices_equal 2
solve 2
+sort_idx 2
source_upload_file 2
+span_eq 2
+span_units 2
+split_lines 2
split_rest 2
+sq_core 2
+sq_place 2
+sq_push 2
+sq_sum 2
+sq_worst 2
+st_type 2
starts_s 2
starts_with 2,2
startup_config 2
+stat_parse 2
step_ef 2
step_match 2
+store_value 2
+str_is 2
+str_num 2
string_arena_reserve 2
+strip_quotes 2
support_mask 2
t_has 2
term_literals 2
@@ -274,9 +429,18 @@ text_append_str 2
text_append_u32 2
text_equal 2
text_of 2
+tile_build 2
+tile_new 2
+timescale_fail 2
+to_precision4 2
tok_end 2
+tok_is 2
+token_done 2
+totals 2
+totals_of 2
+trim_bounds 2
+trim_log 2
trim_pack 2
-trim_span 2
trit_add 2
trit_compare 2
trit_max 2
@@ -284,16 +448,32 @@ trit_min 2
trit_mul_closed 2
type_end 2
uart_tx_bit_at 2
+units_of 2
v_cs_chimera 2
+val_ge_pow10 2
validate_blink_state 2
validate_led_config 2
validate_trits 2
-w_drag 2,2
+vcd_begin 2
+vcd_end 2
+vcd_err_info 2
+vcd_push 2
+vcd_summarize 2
+vcd_summary 2
+view_tree 2
+vs_name 2
+vs_row 2
+w_base 2
+w_drag 2,2,2
+w_file 2
w_libm_need 2
+w_text_give 2
w_value 2
wave_segments 2
weight_sign 2
wide_tail 2
+wire_end 2
+word_to 2
writes_the_repo 2
-ws_len_before 2
+xml_put 2
xor2 2
From 20418b2621ef98315de70d8af77c0a0bc1cb00bc Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 22:04:31 +0700
Subject: [PATCH 57/63] widgets: fasm-skyline drawn natively -- 52245-byte
wasm, 2271 DOM and canvas comparisons, 0 differences (Closes #10938)
The whole page of the fasm-skyline tool in .t27: the isometric drawing as a binary draw list (bit-exact
doubles; text lists cost seconds at 150000 numbers), pan / pinch / wheel / key control, hover and tap
info, rotate and fit, the PNG with its card text wrapped to host-measured widths, a binary STL with
float32 rounding, FASM file counting, both data files decoded in the module. fasm_skyline_logic.t27 is
folded in unchanged.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/fasm_skyline.t27 | 4757 ++++++++++++++++++++++++++++++++
1 file changed, 4757 insertions(+)
create mode 100644 specs/widgets/fasm_skyline.t27
diff --git a/specs/widgets/fasm_skyline.t27 b/specs/widgets/fasm_skyline.t27
new file mode 100644
index 0000000000..3b2f94048b
--- /dev/null
+++ b/specs/widgets/fasm_skyline.t27
@@ -0,0 +1,4757 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/fasm-skyline.t27 -- a 7-series FASM file drawn as a skyline on the XC7A200T die
+; Source of truth for public/widgets/fasm-skyline/ (gHashTag/trinity, apps/website):
+; scripts/widget-pages-from-spec.mjs compiles this file, checks its constants, evaluates the tests
+; below and writes the page; the page hands every constant to tool.js as window.T27_WIDGET.
+; tool.js holds no English of its own: every word on screen is a SAY_ constant here, every number
+; the tool relies on is a K_ constant or a field of the two data files named below.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: the reader drops a 7-series .fasm (the text nextpnr-xilinx writes with --fasm). The
+; page counts the features each tile sets, one FASM line being one feature, the tile being the
+; text before the first '.', and draws the die isometrically: the floor is every tile of the
+; XC7A200T's 265 x 261 grid coloured by family, and each touched tile is a column whose height is
+; its feature count. It shows the tiles touched, the features set, the tallest tile and the share
+; of the die's configurable tiles touched, a table by family, and the tile under the pointer. The
+; reader can rotate, pan and zoom, and download the skyline as a PNG and as an STL. The file is
+; read with FileReader and sent nowhere.
+
+; HOW IT COUNTS (public/widgets/fasm-skyline/skyline.js, run by the page and by the data script):
+; '#' comments and '{...}' annotations are dropped; a line whose value is zero (a BRAM INIT word of
+; all zeros, for example) sets no bit and is counted apart, not as a feature; a 64-bit LUT INIT is
+; one feature, not 64. A tile name places itself only if prjxray-db has a tile of that type at the
+; grid cell the name maps to; every other name is listed as not drawn, never guessed at.
+
+; DATA PROVENANCE: grid.json and samples.json are written by scripts/widget-data/fasm-skyline.mjs
+; build, and its check job re-counts both FASM files with skyline.js and compares every K_ number
+; below. grid.json is derived from prjxray-db ab1fc60 artix7/xc7a200t/tilegrid.json (sha256 in
+; K_TILEGRID_SHA256); before writing it the script looks up all 69165 tiles through skyline.js and
+; each must land on its own grid_x and grid_y. Sample one, node.fasm, is the TRI-NET node v2
+; (hand-written Verilog) placed and routed for the ALINX AX7203 board, part xc7a200tfbg484-2: the
+; same XC7A200T die as the bench's QMTech Wukong V1, a different package and board. Sample two,
+; routed.fasm, is StreamTernaryMac generated from the t27 spec specs/ternary/stream_ternary_mac.t27
+; and placed and routed for xc7a200tfbg676-1 by the build-receipt widget's run of 2026-10-04 (its
+; receipt.json records the commands and this file's sha256). No new build was run for this widget.
+
+; WHAT IT DOES NOT CLAIM: that either design was programmed into a board here or met timing. A
+; feature count is not a bit count and not a resource count: an INT tile with 100 pips and a CLB
+; tile with 100 features are not the same amount of logic. The drawing is the XC7A200T's: a file
+; for another part is drawn only where its tile names are tiles of the 200T. The link-preview card
+; shows sample one; a static page cannot put the reader's own skyline into a link preview, so the
+; page offers the PNG to attach instead.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_fasm_skyline;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::num_grouped;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "fasm-skyline";
+pub const TITLE : str = "FASM skyline: a routed 7-series design as a city on the XC7A200T die";
+pub const DESCRIPTION : str = "Drop a .fasm: every tile it configures becomes a column as tall as the features it sets, on the full XC7A200T grid. Counted in your browser; PNG to share.";
+pub const IMAGE_ALT : str = "FASM skyline card: the XC7A200T die, 265 x 261 tiles, drawn isometrically with node.fasm for the ALINX AX7203 standing on it: 114803 features in 2148 tiles, 5.23% of the 41106 tiles that hold configuration bits. Tallest column BRAM_L_X6Y230, 402 features. Logic columns green, routing blue.";
+pub const HOOK : str = "114803 FASM features in 2148 tiles: 5.23% of an XC7A200T. Drop your own .fasm and watch the design stand up as a city.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [6]str = ["prjxray-db ab1fc60, artix7/xc7a200t/tilegrid.json (sha256 9908f8b3411195a2...): 69165 tiles with type, grid_x and grid_y, reduced to grid.json by scripts/widget-data/fasm-skyline.mjs build", "node.fasm (sha256 e413fb51f92e88cb..., 4330633 bytes): TRI-NET node v2, hand-written Verilog, top trinet_node_v2_ax7203, for the ALINX AX7203 board, part xc7a200tfbg484-2; built in /tmp/x7board/node0 and recorded in its manifest.json", "Yosys 0.67+post (git b8e7da6f): synth_xilinx -flatten -abc9 -nocarry -nodsp -arch xc7. nextpnr-xilinx 0.9.2-107-g7037c948: --chipdb xc7a200tfbg484-2.bin --freq 50 --seed 1 --placer heap --router router1 --timing-allow-fail --fasm node.fasm", "routed.fasm (sha256 daf92c6604a35eb3..., 206571 bytes): StreamTernaryMac, Verilog generated by t27_compiler.wasm (sha256 bb39b9a5ca412a26...) from specs/ternary/stream_ternary_mac.t27 (sha256 f46c60b0ba669110...); the build-receipt widget's run of 2026-10-04, recorded in public/widgets/build-receipt/receipt.json", "Yosys 0.67+post (git b8e7da6f): synth_xilinx -flatten -abc9 -arch xc7 -top build_receipt_top. nextpnr-xilinx 0.9.2-107-g7037c948: --chipdb xc7a200tfbg676-1.bin --xdc build-receipt.xdc --freq 80 --seed 1 --fasm routed.fasm", "scripts/widget-data/fasm-skyline.mjs with public/widgets/fasm-skyline/skyline.js: build writes grid.json and samples.json; check re-counts both FASM files and compares every K_ number of this spec. A file you drop is counted by the same skyline.js in your browser"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers the tool relies on --------------------------------------------------------------
+pub const K_DATA_GRID : str = "./grid.json";
+pub const K_DATA_SAMPLES : str = "./samples.json";
+; Tile families, the colour of a column and the colour of the floor under it.
+pub const K_FAMILIES : [7]str = ["CLB", "INT", "BRAM", "DSP", "IO", "CLK", "OTHER"];
+pub const K_FAMILY_COLORS : [7]str = ["#00ff88", "#00b4ff", "#be78ff", "#ff8c3c", "#ff4d6d", "#ffd700", "#8a8a8a"];
+pub const K_FLOOR_COLORS : [7]str = ["#0d2a1d", "#0a1a26", "#24163a", "#36200e", "#36121a", "#2e2a0c", "#141414"];
+; A tile type is of family K_TYPE_FAMILY[i] when it starts with K_TYPE_PREFIX[i]; first match wins,
+; no match is OTHER. The interface tiles beside BRAM, IO and the fabric are routing, so INT.
+pub const K_TYPE_PREFIX : [17]str = ["CLBLL", "CLBLM", "BRAM_INT_INTERFACE", "INT_INTERFACE", "IO_INT_INTERFACE", "INT_L", "INT_R", "BRAM_L", "BRAM_R", "DSP", "LIOB", "RIOB", "LIOI", "RIOI", "CLK", "HCLK", "CMT"];
+pub const K_TYPE_FAMILY : [17]str = ["CLB", "CLB", "INT", "INT", "INT", "INT", "INT", "BRAM", "BRAM", "DSP", "IO", "IO", "IO", "IO", "CLK", "CLK", "CLK"];
+; The die: prjxray-db ab1fc60 artix7/xc7a200t/tilegrid.json. A tile "holds configuration bits" when
+; its tilegrid entry has a non-empty bits object; the share of the die is measured against those.
+pub const K_TILEGRID_SHA256 : str = "9908f8b3411195a24249a79e4e060f5d34f45f22111cf2139a3ec06d683511a0";
+pub const K_GRID_W : u16 = 265;
+pub const K_GRID_H : u16 = 261;
+pub const K_DIE_TILES : u32 = 69165;
+pub const K_CONFIG_TILES : u32 = 41106;
+pub const K_CONFIG_FAMILY_TILES : [7]u32 = [16825, 22250, 365, 370, 520, 503, 273];
+; Sample one: node.fasm, TRI-NET node v2 for the ALINX AX7203 (xc7a200tfbg484-2).
+pub const K_NODE_SHA256 : str = "e413fb51f92e88cbee0cdd82d588aaaed8313d5ddbc3b5c25177c465329995f4";
+pub const K_NODE_FEATURES : u32 = 114803;
+pub const K_NODE_TILES : u32 = 2148;
+pub const K_NODE_CLEARED : u32 = 144;
+pub const K_NODE_TALLEST_TILE : str = "BRAM_L_X6Y230";
+pub const K_NODE_TALLEST : u32 = 402;
+pub const K_NODE_SHARE_BP : u32 = 523;
+pub const K_NODE_FAMILY_TILES : [7]u32 = [757, 1339, 1, 0, 10, 40, 1];
+pub const K_NODE_FAMILY_FEATURES : [7]u32 = [46524, 67650, 402, 0, 73, 143, 11];
+; Sample two: routed.fasm, StreamTernaryMac from a t27 spec (xc7a200tfbg676-1).
+pub const K_MAC_SHA256 : str = "daf92c6604a35eb356a0498c29b1558db5961f20037858a334109ad12e6a5215";
+pub const K_MAC_FEATURES : u32 = 5201;
+pub const K_MAC_TILES : u32 = 167;
+pub const K_MAC_CLEARED : u32 = 0;
+pub const K_MAC_TALLEST_TILE : str = "CLBLL_L_X2Y133";
+pub const K_MAC_TALLEST : u32 = 110;
+pub const K_MAC_SHARE_BP : u32 = 41;
+pub const K_MAC_FAMILY_TILES : [7]u32 = [47, 95, 0, 0, 4, 20, 1];
+pub const K_MAC_FAMILY_FEATURES : [7]u32 = [2682, 2401, 0, 0, 24, 84, 10];
+; Drawing, interaction and export. Heights are relative: the tallest column of a file is drawn
+; K_HEIGHT_PCT percent of the mean grid side tall, whatever its count.
+pub const K_HEIGHT_PCT : u8 = 45;
+pub const K_MIN_TARGET_PX : u8 = 32;
+pub const K_MAX_ZOOM : u8 = 60;
+pub const K_MAX_FILE_MB : u16 = 256;
+pub const K_PNG_W : u16 = 1200;
+pub const K_PNG_H : u16 = 630;
+
+; --- Words on the page ------------------------------------------------------------------------
+pub const SAY_LOADING : str = "Loading the XC7A200T tile grid...";
+pub const SAY_ERR_DATA : str = "The die map (grid.json) or the samples (samples.json) did not load, so nothing can be drawn.";
+pub const SAY_SAMPLES : [2]str = ["AX7203 node (sample)", "t27 MAC (sample)"];
+pub const SAY_SAMPLE_LINES : [2]str = ["TRI-NET node v2, hand-written Verilog, placed and routed for the ALINX AX7203 board, part xc7a200tfbg484-2: the same XC7A200T die as the QMTech Wukong V1 on our bench, a different package and board. Routed with --timing-allow-fail; no timing claim is made here.", "StreamTernaryMac, Verilog generated from the t27 spec specs/ternary/stream_ternary_mac.t27, placed and routed for xc7a200tfbg676-1 by the build-receipt widget's run of 2026-10-04. Not programmed into a board. No new build was run for this page; this is that run's file, same sha256."];
+pub const SAY_WHOLE_DIE : str = "Whole die";
+pub const SAY_FIT_DESIGN : str = "Fit design";
+pub const SAY_ROTATE : str = "Rotate";
+pub const SAY_DOWNLOAD_PNG : str = "Download PNG";
+pub const SAY_DOWNLOAD_STL : str = "Download STL";
+pub const SAY_CANVAS_LABEL : str = "Isometric drawing of the XC7A200T die: each configured tile is a column as tall as the FASM features it sets.";
+pub const SAY_HINT : str = "Drag to pan, wheel or pinch to zoom, tap a column for its tile. Keys: arrows, + and -, R to rotate.";
+pub const SAY_STAT_LABELS : [4]str = ["Tiles touched", "Features set", "Tallest tile", "Share of the die"];
+pub const SAY_STAT_TALLEST : str = "{0} features";
+pub const SAY_STAT_SHARE : str = "{0} of {1} tiles that hold configuration bits";
+pub const SAY_FAMILY_HEAD : [3]str = ["Family", "Tiles touched / on die", "Features"];
+pub const SAY_FAMILY_NAMES : [7]str = ["Logic (CLBLL, CLBLM)", "Routing (INT and interfaces)", "Block RAM", "DSP", "IO (IOB, IOI)", "Clocking (CLK, HCLK, CMT)", "Other"];
+pub const SAY_OF : str = "{0} / {1}";
+pub const SAY_PICK_NONE : str = "Point at or tap a column to see its tile.";
+pub const SAY_PICK_FIELDS : [5]str = ["Tile", "Type", "Family", "Features", "grid_x, grid_y"];
+pub const SAY_SUMMARY : str = "{0}: {1} features in {2} tiles";
+pub const SAY_SOURCE_SAMPLE : str = "Sample {0}, part {1}, sha256 {2}...";
+pub const SAY_SOURCE_FILE : str = "Your file {0}, {1} bytes, counted in this browser and sent nowhere. {2}";
+pub const SAY_CLEARED : str = "{0} lines set a value of zero (an empty BRAM INIT word, for example): they set no bit and are not counted.";
+pub const SAY_SCALE : str = "Heights are relative: the tallest column ({0} features) is drawn the same height in every file. Floor: the {1} x {2} tile grid.";
+pub const SAY_OFF_MAP : str = "{0} tiles ({1} features) are not tiles of the XC7A200T grid and are not drawn";
+pub const SAY_BAD_LINES : str = "{0} lines are not FASM features (no tile name before the first '.')";
+pub const SAY_HOW : str = "How this sample was made";
+pub const SAY_DROP : str = "Drop a .fasm here, or tap to pick one. It is read in this browser and never uploaded.";
+pub const SAY_READING : str = "Reading {0} in this browser...";
+pub const SAY_ERR_SIZE : str = "{0} is larger than {1} MB, the most this page reads.";
+pub const SAY_ERR_READ : str = "{0} could not be read.";
+pub const SAY_ERR_EMPTY : str = "{0} sets no FASM features: no line has a tile name and a feature.";
+pub const SAY_SHARE_NOTE : str = "A static page cannot put your skyline into a link preview: the preview card always shows the AX7203 sample. Download the PNG and attach it to your post instead.";
+pub const SAY_PNG_TITLE : str = "FASM skyline";
+pub const SAY_PNG_TILES : str = "{0} tiles touched ({1}% of the die)";
+pub const SAY_PNG_FEATURES : str = "{0} features set";
+pub const SAY_PNG_TALLEST : str = "tallest {0}: {1}";
+pub const SAY_PNG_BRAND : str = "Trinity S3AI";
+pub const SAY_PNG_FOOT : str = "t27.ai/widgets/fasm-skyline/";
+
+; --- Tests ---------------------------------------------------------------------------------------
+test the_die_counts_add_up {
+ assert (K_GRID_W as u32) * (K_GRID_H as u32) == K_DIE_TILES;
+ assert K_DIE_TILES == 69165;
+ assert K_CONFIG_FAMILY_TILES[0] + K_CONFIG_FAMILY_TILES[1] + K_CONFIG_FAMILY_TILES[2] + K_CONFIG_FAMILY_TILES[3] + K_CONFIG_FAMILY_TILES[4] + K_CONFIG_FAMILY_TILES[5] + K_CONFIG_FAMILY_TILES[6] == K_CONFIG_TILES;
+ assert K_CONFIG_TILES == 41106;
+ assert K_CONFIG_TILES < K_DIE_TILES;
+}
+
+test the_ax7203_node_sample_adds_up {
+ assert K_NODE_TILES == 2148;
+ assert K_NODE_FEATURES == 114803;
+ assert K_NODE_FAMILY_TILES[0] + K_NODE_FAMILY_TILES[1] + K_NODE_FAMILY_TILES[2] + K_NODE_FAMILY_TILES[3] + K_NODE_FAMILY_TILES[4] + K_NODE_FAMILY_TILES[5] + K_NODE_FAMILY_TILES[6] == K_NODE_TILES;
+ assert K_NODE_FAMILY_FEATURES[0] + K_NODE_FAMILY_FEATURES[1] + K_NODE_FAMILY_FEATURES[2] + K_NODE_FAMILY_FEATURES[3] + K_NODE_FAMILY_FEATURES[4] + K_NODE_FAMILY_FEATURES[5] + K_NODE_FAMILY_FEATURES[6] == K_NODE_FEATURES;
+ assert K_NODE_SHARE_BP == (K_NODE_TILES * 10000 + K_CONFIG_TILES / 2) / K_CONFIG_TILES;
+ assert K_NODE_TALLEST_TILE == "BRAM_L_X6Y230";
+ assert K_NODE_TALLEST <= K_NODE_FAMILY_FEATURES[2];
+ assert K_NODE_FAMILY_TILES[0] <= K_CONFIG_FAMILY_TILES[0];
+ assert K_NODE_FAMILY_TILES[1] <= K_CONFIG_FAMILY_TILES[1];
+}
+
+test the_t27_mac_sample_adds_up {
+ assert K_MAC_TILES == 167;
+ assert K_MAC_FEATURES == 5201;
+ assert K_MAC_FAMILY_TILES[0] + K_MAC_FAMILY_TILES[1] + K_MAC_FAMILY_TILES[2] + K_MAC_FAMILY_TILES[3] + K_MAC_FAMILY_TILES[4] + K_MAC_FAMILY_TILES[5] + K_MAC_FAMILY_TILES[6] == K_MAC_TILES;
+ assert K_MAC_FAMILY_FEATURES[0] + K_MAC_FAMILY_FEATURES[1] + K_MAC_FAMILY_FEATURES[2] + K_MAC_FAMILY_FEATURES[3] + K_MAC_FAMILY_FEATURES[4] + K_MAC_FAMILY_FEATURES[5] + K_MAC_FAMILY_FEATURES[6] == K_MAC_FEATURES;
+ assert K_MAC_SHARE_BP == (K_MAC_TILES * 10000 + K_CONFIG_TILES / 2) / K_CONFIG_TILES;
+ assert K_MAC_TALLEST <= K_MAC_FAMILY_FEATURES[0];
+ assert K_MAC_CLEARED == 0;
+ assert K_MAC_TILES < K_NODE_TILES;
+}
+
+test every_type_prefix_names_a_family {
+ assert same_text(K_TYPE_FAMILY[0], K_FAMILIES[0]);
+ assert same_text(K_TYPE_FAMILY[1], K_FAMILIES[0]);
+ assert same_text(K_TYPE_FAMILY[2], K_FAMILIES[1]);
+ assert same_text(K_TYPE_FAMILY[6], K_FAMILIES[1]);
+ assert same_text(K_TYPE_FAMILY[7], K_FAMILIES[2]);
+ assert same_text(K_TYPE_FAMILY[9], K_FAMILIES[3]);
+ assert same_text(K_TYPE_FAMILY[10], K_FAMILIES[4]);
+ assert same_text(K_TYPE_FAMILY[16], K_FAMILIES[5]);
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_MIN_TARGET_PX >= 32;
+ assert K_DATA_GRID == "./grid.json";
+ assert (K_PNG_W as u32) * 630 == (K_PNG_H as u32) * 1200;
+}
+
+; FOLD BEGIN -- the body of fasm_skyline_logic.t27, from its first const to its last test, unchanged.
+; --
+; --
+pub const V_PN : usize = 0;
+pub const V_FEAT : usize = 1;
+pub const V_COM : usize = 2;
+pub const V_CLR : usize = 3;
+pub const V_NH : usize = 4;
+pub const V_NT : usize = 5;
+pub const V_NB : usize = 6;
+pub const V_GW : usize = 8;
+pub const V_GH : usize = 9;
+pub const V_NTY : usize = 10;
+pub const V_NPF : usize = 11;
+pub const V_NSEG : usize = 12;
+pub const V_NEX : usize = 13;
+pub const V_GP : usize = 14;
+pub const V_FPL : usize = 15;
+pub const V_FFL : usize = 16;
+pub const V_FNL : usize = 17;
+pub const V_SM0 : usize = 20;
+
+pub var V : [32]u64 = [0; 32];
+pub var SC : [4]u64 = [0; 4];
+pub var LK : [4]i64 = [0; 4];
+pub var SV : [2]i64 = [0; 2];
+pub var POOL : [8388608]u8 = [0; 8388608];
+pub var LINE : [1048576]u8 = [0; 1048576];
+pub var VB : [1048576]u8 = [0; 1048576];
+pub var HDRO : [8]u32 = [0; 8];
+pub var HDRL : [8]u32 = [0; 8];
+pub var BADO : [200]u32 = [0; 200];
+pub var BADL : [200]u32 = [0; 200];
+pub var TLO : [65536]u32 = [0; 65536];
+pub var TLL : [65536]u32 = [0; 65536];
+pub var TCT : [65536]u32 = [0; 65536];
+pub var HT : [262144]u32 = [0; 262144];
+pub var SIDX : [65536]u32 = [0; 65536];
+pub var STMP : [65536]u32 = [0; 65536];
+
+pub var CELLS : [4194304]u8 = [0; 4194304];
+pub var GPOOL : [262144]u8 = [0; 262144];
+pub var GTO : [4096]u32 = [0; 4096];
+pub var GTL : [4096]u32 = [0; 4096];
+pub var PFO : [4096]u32 = [0; 4096];
+pub var PFL : [4096]u32 = [0; 4096];
+pub var PFA : [4096]u8 = [0; 4096];
+pub var SGA : [65536]i64 = [0; 65536];
+pub var SGP : [65536]i64 = [0; 65536];
+pub var SGK : [65536]i64 = [0; 65536];
+pub var SGV : [65536]i64 = [0; 65536];
+pub var SGN : [65536]i64 = [0; 65536];
+pub var SGD : [65536]i64 = [0; 65536];
+pub var SGE : [65536]i64 = [0; 65536];
+pub var EXO : [4096]u32 = [0; 4096];
+pub var EXL : [4096]u32 = [0; 4096];
+pub var EXX : [4096]i64 = [0; 4096];
+pub var EXY : [4096]i64 = [0; 4096];
+
+pub var FPB : [16384]u8 = [0; 16384];
+pub var FFB : [16384]u8 = [0; 16384];
+pub var FNB : [16384]u8 = [0; 16384];
+
+pub var COLT : [65536]u32 = [0; 65536];
+pub var COLX : [65536]u32 = [0; 65536];
+pub var COLY : [65536]u32 = [0; 65536];
+pub var COLL : [65536]u32 = [0; 65536];
+pub var COLF : [65536]i32 = [0; 65536];
+pub var OFFT : [65536]u32 = [0; 65536];
+pub var FMT : [64]u32 = [0; 64];
+pub var FMF : [64]u64 = [0; 64];
+
+// ---------------------------------------------------------------- whitespace and small helpers
+pub fn ws_len_at(s: []const u8, p: usize, hi: usize) -> usize {
+ if (p >= hi) { return 0; }
+ const b : u8 = s[p];
+ if (b == 32 or (b >= 9 and b <= 13)) { return 1; }
+ if (b == 194 and p + 1 < hi and s[p + 1] == 160) { return 2; }
+ if (p + 3 <= hi) {
+ const b1 : u8 = s[p + 1];
+ const b2 : u8 = s[p + 2];
+ if (b == 225 and b1 == 154 and b2 == 128) { return 3; }
+ if (b == 226 and b1 == 128 and ((b2 >= 128 and b2 <= 138) or b2 == 168 or b2 == 169 or b2 == 175)) { return 3; }
+ if (b == 226 and b1 == 129 and b2 == 159) { return 3; }
+ if (b == 227 and b1 == 128 and b2 == 128) { return 3; }
+ if (b == 239 and b1 == 187 and b2 == 191) { return 3; }
+ }
+ return 0;
+}
+
+pub fn ws_len_before(s: []const u8, lo: usize, e: usize) -> usize {
+ if (e > lo and ws_len_at(s, e - 1, e) == 1) { return 1; }
+ if (e >= lo + 2 and ws_len_at(s, e - 2, e) == 2) { return 2; }
+ if (e >= lo + 3 and ws_len_at(s, e - 3, e) == 3) { return 3; }
+ return 0;
+}
+
+/// String.prototype.trim on s[lo..hi): the new start in SC[0], the new end in SC[1].
+pub fn trim_range(s: []const u8, lo0: usize, hi0: usize) -> bool {
+ var lo : usize = lo0;
+ var hi : usize = hi0;
+ var w : usize = ws_len_at(s, lo, hi);
+ while (w > 0) {
+ lo = lo + w;
+ w = ws_len_at(s, lo, hi);
+ }
+ w = ws_len_before(s, lo, hi);
+ while (w > 0) {
+ hi = hi - w;
+ w = ws_len_before(s, lo, hi);
+ }
+ SC[0] = lo as u64;
+ SC[1] = hi as u64;
+ return true;
+}
+
+pub fn is_digit(b: u8) -> bool {
+ return b >= 48 and b <= 57;
+}
+
+pub fn digits_val(s: []const u8, a: usize, b: usize) -> u64 {
+ var v : u64 = 0;
+ var k : usize = a;
+ while (k < b) {
+ if (v < 100000000000000000) { v = v * 10 + ((s[k] - 48) as u64); }
+ k = k + 1;
+ }
+ return v;
+}
+
+pub fn find_byte(s: []const u8, from: usize, hi: usize, b: u8) -> i64 {
+ var p : usize = from;
+ while (p < hi) {
+ if (s[p] == b) { return p as i64; }
+ p = p + 1;
+ }
+ return -1;
+}
+
+pub fn pool_put_text(s: []const u8, lo: usize, hi: usize) -> i64 {
+ const at : u64 = V[V_PN];
+ if (at + ((hi - lo) as u64) >= 8388608) { return -1; }
+ var k : usize = lo;
+ while (k < hi) {
+ POOL[(at as usize) + (k - lo)] = s[k];
+ k = k + 1;
+ }
+ V[V_PN] = at + ((hi - lo) as u64);
+ return at as i64;
+}
+
+// ---------------------------------------------------------------- the FASM line rules
+/// The tile name test /^([A-Z0-9_]+)_X(\d+)Y(\d+)$/ : the length of the type, the two numbers in SC[0], SC[1]; -1 when it does not match.
+pub fn tile_split(s: []const u8, lo: usize, hi: usize) -> i64 {
+ var k : usize = lo;
+ while (k < hi) {
+ const b : u8 = s[k];
+ if (!((b >= 65 and b <= 90) or is_digit(b) or b == 95)) { return -1; }
+ k = k + 1;
+ }
+ var ys : usize = hi;
+ while (ys > lo and is_digit(s[ys - 1])) { ys = ys - 1; }
+ if (ys == hi) { return -1; }
+ if (ys == lo or s[ys - 1] != 89) { return -1; }
+ const xe : usize = ys - 1;
+ var xs : usize = xe;
+ while (xs > lo and is_digit(s[xs - 1])) { xs = xs - 1; }
+ if (xs == xe) { return -1; }
+ if (xs < lo + 2) { return -1; }
+ if (s[xs - 1] != 88 or s[xs - 2] != 95) { return -1; }
+ const tl : usize = (xs - 2) - lo;
+ if (tl < 1) { return -1; }
+ SC[0] = digits_val(s, xs, xe);
+ SC[1] = digits_val(s, ys, hi);
+ return tl as i64;
+}
+
+/// zeroValue(v): v = s[lo..hi). Underscores are dropped, the rest trimmed; a Verilog-style constant is zero
+/// when its digits are all 0, a bare number when it is all 0.
+pub fn zero_value(s: []const u8, lo: usize, hi: usize) -> bool {
+ var n : usize = 0;
+ var k : usize = lo;
+ while (k < hi) {
+ if (s[k] != 95) {
+ VB[n] = s[k];
+ n = n + 1;
+ }
+ k = k + 1;
+ }
+ const t : []const u8 = VB[0 .. 1048576];
+ trim_range(t, 0, n);
+ const a : usize = SC[0] as usize;
+ const b : usize = SC[1] as usize;
+ // (\d+)?'([bhdo])([0-9a-f]+)$ with the i flag
+ var p : usize = a;
+ while (p < b and is_digit(t[p])) { p = p + 1; }
+ if (p + 2 < b and t[p] == 39) {
+ const base : u8 = t[p + 1] | 32;
+ if (base == 98 or base == 104 or base == 100 or base == 111) {
+ var q : usize = p + 2;
+ var allhex : bool = true;
+ var allzero : bool = true;
+ while (q < b) {
+ const c : u8 = t[q];
+ const lc : u8 = c | 32;
+ if (is_digit(c) or (lc >= 97 and lc <= 102)) {
+ if (c != 48) { allzero = false; }
+ } else {
+ allhex = false;
+ }
+ q = q + 1;
+ }
+ if (allhex) { return allzero; }
+ }
+ }
+ // /^0+$/
+ if (b == a) { return false; }
+ var z : usize = a;
+ while (z < b) {
+ if (t[z] != 48) { return false; }
+ z = z + 1;
+ }
+ return true;
+}
+
+pub fn fnv(s: []const u8, lo: usize, hi: usize) -> u32 {
+ var h : u64 = 2166136261;
+ var k : usize = lo;
+ while (k < hi) {
+ h = (((h ^ (s[k] as u64)) * 16777619) % 4294967296);
+ k = k + 1;
+ }
+ return h as u32;
+}
+
+pub fn pool_eq_text(o: u32, l: u32, s: []const u8, lo: usize, hi: usize) -> bool {
+ if ((l as usize) != hi - lo) { return false; }
+ var k : usize = 0;
+ while (k < (l as usize)) {
+ if (POOL[(o as usize) + k] != s[lo + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// tiles.set(name, (tiles.get(name) || 0) + 1) for a name held in LINE[lo..hi).
+pub fn tile_add(lo: usize, hi: usize) -> bool {
+ const t : []const u8 = LINE[0 .. 1048576];
+ var idx : usize = (fnv(t, lo, hi) as usize) % 262144;
+ while (HT[idx] != 0) {
+ const ti : usize = (HT[idx] - 1) as usize;
+ if (pool_eq_text(TLO[ti], TLL[ti], t, lo, hi)) {
+ TCT[ti] = TCT[ti] + 1;
+ return true;
+ }
+ idx = (idx + 1) % 262144;
+ }
+ const nt : usize = V[V_NT] as usize;
+ if (nt >= 65536) { return false; }
+ const at : i64 = pool_put_text(t, lo, hi);
+ if (at < 0) { return false; }
+ TLO[nt] = at as u32;
+ TLL[nt] = (hi - lo) as u32;
+ TCT[nt] = 1;
+ HT[idx] = (nt + 1) as u32;
+ V[V_NT] = (nt + 1) as u64;
+ return true;
+}
+
+/// parseFasm. 0 ok, -1 over a limit.
+pub fn parse_fasm(text: []const u8) -> i32 {
+ var f : usize = 0;
+ while (f < 8) {
+ V[f] = 0;
+ f = f + 1;
+ }
+ var h : usize = 0;
+ while (h < 262144) {
+ HT[h] = 0;
+ h = h + 1;
+ }
+ const lt : []const u8 = LINE[0 .. 1048576];
+ var start : usize = 0;
+ const n : usize = text.len;
+ while (start <= n) {
+ var end : usize = start;
+ while (end < n and text[end] != 10) { end = end + 1; }
+ const ls : usize = start;
+ start = end + 1;
+ var le : usize = end;
+ if (le - ls > 1048576) { return -1; }
+ const hash : i64 = find_byte(text, ls, le, 35);
+ if (hash >= 0) {
+ const hp : usize = hash as usize;
+ trim_range(text, hp + 1, le);
+ const cl : usize = SC[0] as usize;
+ const ch : usize = SC[1] as usize;
+ trim_range(text, ls, hp);
+ if (SC[0] == SC[1]) {
+ V[V_COM] = V[V_COM] + 1;
+ if (V[V_NH] < 8 and ch > cl) {
+ const at : i64 = pool_put_text(text, cl, ch);
+ if (at < 0) { return -1; }
+ HDRO[V[V_NH] as usize] = at as u32;
+ HDRL[V[V_NH] as usize] = (ch - cl) as u32;
+ V[V_NH] = V[V_NH] + 1;
+ }
+ }
+ le = hp;
+ }
+ // line.replace(/\{[^}]*\}/g, '')
+ var m : usize = 0;
+ var i : usize = ls;
+ var done : bool = false;
+ while (!done) {
+ const ob : i64 = find_byte(text, i, le, 123);
+ if (ob < 0) {
+ done = true;
+ } else {
+ const cb : i64 = find_byte(text, (ob as usize) + 1, le, 125);
+ if (cb < 0) {
+ done = true;
+ } else {
+ var k : usize = i;
+ while (k < (ob as usize)) {
+ LINE[m] = text[k];
+ m = m + 1;
+ k = k + 1;
+ }
+ i = (cb as usize) + 1;
+ }
+ }
+ }
+ var k2 : usize = i;
+ while (k2 < le) {
+ LINE[m] = text[k2];
+ m = m + 1;
+ k2 = k2 + 1;
+ }
+ trim_range(lt, 0, m);
+ const a : usize = SC[0] as usize;
+ const b : usize = SC[1] as usize;
+ if (b > a) {
+ const eq : i64 = find_byte(lt, a, b, 61);
+ var nb : usize = b;
+ if (eq >= 0) { nb = eq as usize; }
+ trim_range(lt, a, nb);
+ const na : usize = SC[0] as usize;
+ const ne : usize = SC[1] as usize;
+ var cleared : bool = false;
+ if (eq >= 0) {
+ cleared = zero_value(lt, (eq as usize) + 1, b);
+ }
+ if (cleared) {
+ V[V_CLR] = V[V_CLR] + 1;
+ } else {
+ const dot : i64 = find_byte(lt, na, ne, 46);
+ var tl : i64 = -1;
+ var tend : usize = na;
+ if (dot > (na as i64)) {
+ tend = dot as usize;
+ tl = tile_split(lt, na, tend);
+ }
+ if (tl < 0) {
+ if (V[V_NB] < 200) {
+ const at : i64 = pool_put_text(lt, a, b);
+ if (at < 0) { return -1; }
+ BADO[V[V_NB] as usize] = at as u32;
+ BADL[V[V_NB] as usize] = (b - a) as u32;
+ V[V_NB] = V[V_NB] + 1;
+ }
+ } else {
+ V[V_FEAT] = V[V_FEAT] + 1;
+ if (!tile_add(na, tend)) { return -1; }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+pub fn fa_info(f: u32) -> u64 {
+ return V[f as usize + 1];
+}
+
+pub fn fa_header(i: u32, f: u32) -> u32 {
+ if (f == 0) { return HDRO[i as usize]; }
+ return HDRL[i as usize];
+}
+
+pub fn fa_bad(i: u32, f: u32) -> u32 {
+ if (f == 0) { return BADO[i as usize]; }
+ return BADL[i as usize];
+}
+
+pub fn fa_tile(i: u32, f: u32) -> u32 {
+ if (f == 0) { return TLO[i as usize]; }
+ if (f == 1) { return TLL[i as usize]; }
+ return TCT[i as usize];
+}
+
+pub fn pool(i: u32) -> u8 {
+ return POOL[i as usize];
+}
+
+// ---------------------------------------------------------------- the grid
+pub fn grid_begin(w: u32, h: u32) -> i32 {
+ V[V_GW] = w as u64;
+ V[V_GH] = h as u64;
+ V[V_NTY] = 0;
+ V[V_NPF] = 0;
+ V[V_NSEG] = 0;
+ V[V_NEX] = 0;
+ V[V_GP] = 0;
+ if ((w as u64) * (h as u64) > 4194304) { return -1; }
+ return 0;
+}
+
+pub fn gpool_put(s: []const u8, lo: usize, hi: usize) -> i64 {
+ const at : u64 = V[V_GP];
+ if (at + ((hi - lo) as u64) >= 262144) { return -1; }
+ var k : usize = lo;
+ while (k < hi) {
+ GPOOL[(at as usize) + (k - lo)] = s[k];
+ k = k + 1;
+ }
+ V[V_GP] = at + ((hi - lo) as u64);
+ return at as i64;
+}
+
+pub fn grid_types(list: []const u8) -> i32 {
+ var a : usize = 0;
+ while (a < list.len) {
+ var e : usize = a;
+ while (e < list.len and list[e] != 10) { e = e + 1; }
+ const at : i64 = gpool_put(list, a, e);
+ const n : usize = V[V_NTY] as usize;
+ if (at < 0 or n >= 4096) { return -1; }
+ GTO[n] = at as u32;
+ GTL[n] = (e - a) as u32;
+ V[V_NTY] = (n + 1) as u64;
+ a = e + 1;
+ }
+ return 0;
+}
+
+pub fn grid_rle(rle: []const u32) -> i32 {
+ const total : u64 = V[V_GW] * V[V_GH];
+ var i : u64 = 0;
+ var r : usize = 0;
+ while (r + 1 < rle.len) {
+ const v : u8 = (rle[r] % 256) as u8;
+ const c : u64 = rle[r + 1] as u64;
+ var k : u64 = 0;
+ while (k < c and i + k < total) {
+ CELLS[(i + k) as usize] = v;
+ k = k + 1;
+ }
+ i = i + c;
+ r = r + 2;
+ }
+ if (rle.len % 2 == 1) { return 1; }
+ if (i != total) { return 1; }
+ return 0;
+}
+
+pub fn grid_prefixes(axis: u32, list: []const u8) -> i32 {
+ var a : usize = 0;
+ while (a < list.len) {
+ var e : usize = a;
+ while (e < list.len and list[e] != 10) { e = e + 1; }
+ const at : i64 = gpool_put(list, a, e);
+ const n : usize = V[V_NPF] as usize;
+ if (at < 0 or n >= 4096) { return -1; }
+ PFO[n] = at as u32;
+ PFL[n] = (e - a) as u32;
+ PFA[n] = axis as u8;
+ V[V_NPF] = (n + 1) as u64;
+ a = e + 1;
+ }
+ return 0;
+}
+
+pub fn grid_segs(recs: []const i32) -> i32 {
+ var r : usize = 0;
+ while (r + 7 <= recs.len) {
+ const n : usize = V[V_NSEG] as usize;
+ if (n >= 65536) { return -1; }
+ SGA[n] = recs[r] as i64;
+ SGP[n] = recs[r + 1] as i64;
+ SGK[n] = recs[r + 2] as i64;
+ SGV[n] = recs[r + 3] as i64;
+ SGN[n] = recs[r + 4] as i64;
+ SGD[n] = recs[r + 5] as i64;
+ SGE[n] = recs[r + 6] as i64;
+ V[V_NSEG] = (n + 1) as u64;
+ r = r + 7;
+ }
+ return 0;
+}
+
+pub fn grid_except(names: []const u8, xy: []const i32) -> i32 {
+ var a : usize = 0;
+ var i : usize = 0;
+ while (a < names.len) {
+ var e : usize = a;
+ while (e < names.len and names[e] != 10) { e = e + 1; }
+ const at : i64 = gpool_put(names, a, e);
+ const n : usize = V[V_NEX] as usize;
+ if (at < 0 or n >= 4096 or 2 * i + 1 >= xy.len) { return -1; }
+ EXO[n] = at as u32;
+ EXL[n] = (e - a) as u32;
+ EXX[n] = xy[2 * i] as i64;
+ EXY[n] = xy[2 * i + 1] as i64;
+ V[V_NEX] = (n + 1) as u64;
+ i = i + 1;
+ a = e + 1;
+ }
+ return 0;
+}
+
+pub fn gp_eq(o: u32, l: u32, s: []const u8, lo: usize, hi: usize) -> bool {
+ if ((l as usize) != hi - lo) { return false; }
+ var k : usize = 0;
+ while (k < (l as usize)) {
+ if (GPOOL[(o as usize) + k] != s[lo + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// expand(segs).get(k): true with the value in SV[0] (the last segment and step that set key k), false when none did (JS undefined).
+pub fn seg_value(pf: i64, k: i64) -> bool {
+ var found : bool = false;
+ var s : usize = 0;
+ while (s < (V[V_NSEG] as usize)) {
+ if (SGP[s] == pf and SGN[s] > 0) {
+ const d : i64 = k - SGK[s];
+ var i : i64 = -1;
+ if (SGD[s] == 0) {
+ if (d == 0) { i = SGN[s] - 1; }
+ } else {
+ if (d % SGD[s] == 0) {
+ const q : i64 = d / SGD[s];
+ if (q >= 0 and q < SGN[s]) { i = q; }
+ }
+ }
+ if (i >= 0) {
+ SV[0] = SGV[s] + i * SGE[s];
+ found = true;
+ }
+ }
+ s = s + 1;
+ }
+ return found;
+}
+
+pub fn grid_at(gx: i64, gy: i64) -> i32 {
+ const idx : i64 = gx * (V[V_GH] as i64) + gy;
+ if (idx < 0 or idx >= (V[V_GW] as i64) * (V[V_GH] as i64)) { return -1; }
+ return CELLS[idx as usize] as i32;
+}
+
+/// grid.lookup(name) for name = s[lo..hi): true with LK = gx, gy, type length, type index; false for null.
+pub fn grid_lookup(s: []const u8, lo: usize, hi: usize) -> bool {
+ var gx : i64 = 0;
+ var gy : i64 = 0;
+ var tl : usize = hi - lo;
+ var ex : i64 = -1;
+ var e : usize = 0;
+ while (e < (V[V_NEX] as usize) and ex < 0) {
+ if (gp_eq(EXO[e], EXL[e], s, lo, hi)) { ex = e as i64; }
+ e = e + 1;
+ }
+ if (ex >= 0) {
+ gx = EXX[ex as usize];
+ gy = EXY[ex as usize];
+ const t0 : i64 = tile_split(s, lo, hi);
+ if (t0 >= 0) { tl = t0 as usize; }
+ } else {
+ const t1 : i64 = tile_split(s, lo, hi);
+ if (t1 < 0) { return false; }
+ tl = t1 as usize;
+ const x : i64 = SC[0] as i64;
+ const y : i64 = SC[1] as i64;
+ var px : i64 = -1;
+ var py : i64 = -1;
+ var p : usize = 0;
+ while (p < (V[V_NPF] as usize)) {
+ if (gp_eq(PFO[p], PFL[p], s, lo, lo + tl)) {
+ if (PFA[p] == 0) { px = p as i64; }
+ if (PFA[p] == 1) { py = p as i64; }
+ }
+ p = p + 1;
+ }
+ if (px < 0 or py < 0) { return false; }
+ if (!seg_value(px, x)) { return false; }
+ gx = SV[0];
+ if (!seg_value(py, y)) { return false; }
+ gy = SV[0];
+ }
+ var t : i64 = -1;
+ var k : usize = V[V_NTY] as usize;
+ while (k > 0 and t < 0) {
+ k = k - 1;
+ if (gp_eq(GTO[k], GTL[k], s, lo, lo + tl)) { t = k as i64; }
+ }
+ if (t < 0) { return false; }
+ if ((grid_at(gx, gy) as i64) != t) { return false; }
+ LK[0] = gx;
+ LK[1] = gy;
+ LK[2] = tl as i64;
+ LK[3] = t;
+ return true;
+}
+
+pub fn lk(f: u32) -> i64 {
+ return LK[f as usize];
+}
+
+// ---------------------------------------------------------------- families and the summary
+pub fn fam_set(prefixes: []const u8, families: []const u8, names: []const u8) -> i32 {
+ if (prefixes.len > 16384 or families.len > 16384 or names.len > 16384) { return -1; }
+ var k : usize = 0;
+ while (k < prefixes.len) {
+ FPB[k] = prefixes[k];
+ k = k + 1;
+ }
+ k = 0;
+ while (k < families.len) {
+ FFB[k] = families[k];
+ k = k + 1;
+ }
+ k = 0;
+ while (k < names.len) {
+ FNB[k] = names[k];
+ k = k + 1;
+ }
+ V[V_FPL] = prefixes.len as u64;
+ V[V_FFL] = families.len as u64;
+ V[V_FNL] = names.len as u64;
+ return 0;
+}
+
+/// The packed [start, end) of the i-th item (each ended by byte 10) of a list, or -1.
+pub fn item_of(buf: []const u8, used: usize, i: usize) -> i64 {
+ var a : usize = 0;
+ var n : usize = 0;
+ while (a < used) {
+ var e : usize = a;
+ while (e < used and buf[e] != 10) { e = e + 1; }
+ if (n == i) { return (a as i64) * 4294967296 + (e as i64); }
+ n = n + 1;
+ a = e + 1;
+ }
+ return -1;
+}
+
+pub fn count_items(buf: []const u8, used: usize) -> usize {
+ var a : usize = 0;
+ var n : usize = 0;
+ while (a < used) {
+ var e : usize = a;
+ while (e < used and buf[e] != 10) { e = e + 1; }
+ n = n + 1;
+ a = e + 1;
+ }
+ return n;
+}
+
+/// famNames.indexOf(item) where item is s[lo..hi) of the families list; -1 when absent.
+pub fn index_of_name(s: []const u8, lo: usize, hi: usize) -> i32 {
+ const names : []const u8 = FNB[0 .. 16384];
+ const used : usize = V[V_FNL] as usize;
+ var a : usize = 0;
+ var n : i32 = 0;
+ while (a < used) {
+ var e : usize = a;
+ while (e < used and names[e] != 10) { e = e + 1; }
+ if (e - a == hi - lo) {
+ var same : bool = true;
+ var k : usize = 0;
+ while (k < e - a) {
+ if (names[a + k] != s[lo + k]) { same = false; }
+ k = k + 1;
+ }
+ if (same) { return n; }
+ }
+ n = n + 1;
+ a = e + 1;
+ }
+ return -1;
+}
+
+/// familyOf(type, prefixes, families, famNames) with the three lists set by fam_set.
+pub fn family_of_range(s: []const u8, lo: usize, hi: usize) -> i32 {
+ const pf : []const u8 = FPB[0 .. 16384];
+ const fm : []const u8 = FFB[0 .. 16384];
+ const np : usize = count_items(pf, V[V_FPL] as usize);
+ var i : usize = 0;
+ while (i < np) {
+ const it : i64 = item_of(pf, V[V_FPL] as usize, i);
+ const a : usize = (it / 4294967296) as usize;
+ const e : usize = (it % 4294967296) as usize;
+ if (hi - lo >= e - a) {
+ var same : bool = true;
+ var k : usize = 0;
+ while (k < e - a) {
+ if (s[lo + k] != pf[a + k]) { same = false; }
+ k = k + 1;
+ }
+ if (same) {
+ const fi : i64 = item_of(fm, V[V_FFL] as usize, i);
+ if (fi < 0) { return -1; }
+ return index_of_name(fm, (fi / 4294967296) as usize, (fi % 4294967296) as usize);
+ }
+ }
+ i = i + 1;
+ }
+ return (count_items(FNB[0 .. 16384], V[V_FNL] as usize) as i32) - 1;
+}
+
+pub fn family_of(t: []const u8) -> i32 {
+ return family_of_range(t, 0, t.len);
+}
+
+pub fn name_less(a: u32, b: u32) -> bool {
+ const la : usize = TLL[a as usize] as usize;
+ const lb : usize = TLL[b as usize] as usize;
+ var k : usize = 0;
+ while (k < la and k < lb) {
+ const x : u8 = POOL[(TLO[a as usize] as usize) + k];
+ const y : u8 = POOL[(TLO[b as usize] as usize) + k];
+ if (x != y) { return x < y; }
+ k = k + 1;
+ }
+ return la < lb;
+}
+
+/// Sort SIDX[0..n) by tile name (default JS sort of ASCII strings), bottom-up merge sort.
+pub fn sort_names(n: usize) -> bool {
+ var i : usize = 0;
+ while (i < n) {
+ SIDX[i] = i as u32;
+ i = i + 1;
+ }
+ var w : usize = 1;
+ while (w < n) {
+ var lo : usize = 0;
+ while (lo < n) {
+ var mid : usize = lo + w;
+ if (mid > n) { mid = n; }
+ var hi : usize = lo + 2 * w;
+ if (hi > n) { hi = n; }
+ var a : usize = lo;
+ var b : usize = mid;
+ var o : usize = lo;
+ while (a < mid or b < hi) {
+ if (b >= hi or (a < mid and !name_less(SIDX[b], SIDX[a]))) {
+ STMP[o] = SIDX[a];
+ a = a + 1;
+ } else {
+ STMP[o] = SIDX[b];
+ b = b + 1;
+ }
+ o = o + 1;
+ }
+ lo = lo + 2 * w;
+ }
+ var c : usize = 0;
+ while (c < n) {
+ SIDX[c] = STMP[c];
+ c = c + 1;
+ }
+ w = w * 2;
+ }
+ return true;
+}
+
+/// summarize(parsed, grid, famOfType, nFam). Needs parse_fasm, the grid calls and fam_set done.
+pub fn summarize() -> i32 {
+ const nt : usize = V[V_NT] as usize;
+ sort_names(nt);
+ const nfam : usize = count_items(FNB[0 .. 16384], V[V_FNL] as usize);
+ var f : usize = 0;
+ while (f < 64) {
+ FMT[f] = 0;
+ FMF[f] = 0;
+ f = f + 1;
+ }
+ var ncol : usize = 0;
+ var noff : usize = 0;
+ var placed : u64 = 0;
+ var tallest : i64 = -1;
+ var tn : u64 = 0;
+ var i : usize = 0;
+ const pl : []const u8 = POOL[0 .. 8388608];
+ while (i < nt) {
+ const t : usize = SIDX[i] as usize;
+ const o : usize = TLO[t] as usize;
+ const l : usize = TLL[t] as usize;
+ const n : u64 = TCT[t] as u64;
+ if (!grid_lookup(pl, o, o + l)) {
+ OFFT[noff] = t as u32;
+ noff = noff + 1;
+ } else {
+ const tl : usize = LK[2] as usize;
+ const fam : i32 = family_of_range(pl, o, o + tl);
+ COLT[ncol] = t as u32;
+ COLX[ncol] = LK[0] as u32;
+ COLY[ncol] = LK[1] as u32;
+ COLL[ncol] = tl as u32;
+ COLF[ncol] = fam;
+ if (fam >= 0 and (fam as usize) < nfam and fam < 64) {
+ FMT[fam as usize] = FMT[fam as usize] + 1;
+ FMF[fam as usize] = FMF[fam as usize] + n;
+ }
+ placed = placed + n;
+ if (tallest < 0 or n > tn) {
+ tallest = ncol as i64;
+ tn = n;
+ }
+ ncol = ncol + 1;
+ }
+ i = i + 1;
+ }
+ V[V_SM0] = ncol as u64;
+ V[V_SM0 + 1] = noff as u64;
+ V[V_SM0 + 2] = V[V_FEAT];
+ V[V_SM0 + 3] = placed;
+ V[V_SM0 + 4] = ncol as u64;
+ V[V_SM0 + 5] = (tallest + 1) as u64;
+ return 0;
+}
+
+pub fn sm_info(f: u32) -> i64 {
+ if (f == 5) { return (V[V_SM0 + 5] as i64) - 1; }
+ return V[V_SM0 + (f as usize)] as i64;
+}
+
+pub fn sm_col(i: u32, f: u32) -> i64 {
+ const c : usize = i as usize;
+ if (f == 0) { return COLT[c] as i64; }
+ if (f == 1) { return COLX[c] as i64; }
+ if (f == 2) { return COLY[c] as i64; }
+ if (f == 3) { return COLL[c] as i64; }
+ if (f == 4) { return COLF[c] as i64; }
+ return TCT[COLT[c] as usize] as i64;
+}
+
+pub fn sm_off(i: u32, f: u32) -> i64 {
+ if (f == 0) { return OFFT[i as usize] as i64; }
+ return TCT[OFFT[i as usize] as usize] as i64;
+}
+
+pub fn sm_fam(k: u32, f: u32) -> u64 {
+ if (f == 0) { return FMT[k as usize] as u64; }
+ return FMF[k as usize];
+}
+
+// ---------------------------------------------------------------- tests
+pub var TXT : [8192]u8 = [0; 8192];
+pub var TXN : usize = 0;
+pub var LA : [256]u8 = [0; 256];
+pub var LB : [256]u8 = [0; 256];
+pub var LC : [256]u8 = [0; 256];
+pub var LAN : usize = 0;
+pub var LBN : usize = 0;
+pub var LCN : usize = 0;
+pub var RLE : [16]u32 = [0; 16];
+pub var SEG : [28]i32 = [0; 28];
+
+pub fn tx_reset() -> usize {
+ TXN = 0;
+ return 0;
+}
+
+pub fn tx_line(s: []const u8) -> usize {
+ var k : usize = 0;
+ while (k < s.len) {
+ TXT[TXN] = s[k];
+ TXN = TXN + 1;
+ k = k + 1;
+ }
+ TXT[TXN] = 10;
+ TXN = TXN + 1;
+ return TXN;
+}
+
+/// Lists, one item per byte-10 end, built from up to four items (an empty item is skipped).
+pub fn la_make(a: []const u8, b: []const u8, c: []const u8, d: []const u8) -> usize {
+ LAN = 0;
+ var w : u32 = 0;
+ while (w < 4) {
+ var s : []const u8 = a;
+ if (w == 1) { s = b; }
+ if (w == 2) { s = c; }
+ if (w == 3) { s = d; }
+ if (s.len > 0) {
+ var k : usize = 0;
+ while (k < s.len) {
+ LA[LAN] = s[k];
+ LAN = LAN + 1;
+ k = k + 1;
+ }
+ LA[LAN] = 10;
+ LAN = LAN + 1;
+ }
+ w = w + 1;
+ }
+ return LAN;
+}
+
+pub fn lb_make(a: []const u8, b: []const u8, c: []const u8, d: []const u8) -> usize {
+ LBN = 0;
+ var w : u32 = 0;
+ while (w < 4) {
+ var s : []const u8 = a;
+ if (w == 1) { s = b; }
+ if (w == 2) { s = c; }
+ if (w == 3) { s = d; }
+ if (s.len > 0) {
+ var k : usize = 0;
+ while (k < s.len) {
+ LB[LBN] = s[k];
+ LBN = LBN + 1;
+ k = k + 1;
+ }
+ LB[LBN] = 10;
+ LBN = LBN + 1;
+ }
+ w = w + 1;
+ }
+ return LBN;
+}
+
+pub fn lc_make(a: []const u8, b: []const u8, c: []const u8, d: []const u8) -> usize {
+ LCN = 0;
+ var w : u32 = 0;
+ while (w < 4) {
+ var s : []const u8 = a;
+ if (w == 1) { s = b; }
+ if (w == 2) { s = c; }
+ if (w == 3) { s = d; }
+ if (s.len > 0) {
+ var k : usize = 0;
+ while (k < s.len) {
+ LC[LCN] = s[k];
+ LCN = LCN + 1;
+ k = k + 1;
+ }
+ LC[LCN] = 10;
+ LCN = LCN + 1;
+ }
+ w = w + 1;
+ }
+ return LCN;
+}
+
+pub fn pool_is(o: u32, l: u32, w: []const u8) -> bool {
+ if ((l as usize) != w.len) { return false; }
+ return pool_eq_text(o, l, w, 0, w.len);
+}
+
+pub fn seg_set(i: usize, ax: i32, pf: i32, k0: i32, v0: i32, n: i32, dk: i32, dv: i32) -> usize {
+ SEG[i * 7] = ax;
+ SEG[i * 7 + 1] = pf;
+ SEG[i * 7 + 2] = k0;
+ SEG[i * 7 + 3] = v0;
+ SEG[i * 7 + 4] = n;
+ SEG[i * 7 + 5] = dk;
+ SEG[i * 7 + 6] = dv;
+ return i;
+}
+
+/// A small die: 3 columns of 2 cells, w = 3, h = 2. Types 0 CLBLL_L, 1 INT_L, 2 BRAM_L. Cells (index gx*2+gy):
+/// 0 is CLBLL_L, 1 to 3 are INT_L, 4 and 5 are BRAM_L. X numbers: CLBLL_L 7 -> gx 0; INT_L 5, 6 -> gx 1, 0;
+/// BRAM_L 10 -> gx 2. Y numbers for every type: 20, 21 -> gy 0, 1.
+pub fn small_die() -> i32 {
+ grid_begin(3, 2);
+ var n : usize = la_make("CLBLL_L", "INT_L", "BRAM_L", "");
+ grid_types(LA[0 .. n]);
+ RLE[0] = 0;
+ RLE[1] = 1;
+ RLE[2] = 1;
+ RLE[3] = 3;
+ RLE[4] = 2;
+ RLE[5] = 2;
+ const r : i32 = grid_rle(RLE[0 .. 6]);
+ grid_prefixes(0, LA[0 .. n]);
+ grid_prefixes(1, LA[0 .. n]);
+ seg_set(0, 0, 0, 7, 0, 1, 1, 0);
+ seg_set(1, 0, 1, 5, 1, 2, 1, -1);
+ seg_set(2, 0, 2, 10, 2, 1, 1, 0);
+ grid_segs(SEG[0 .. 21]);
+ seg_set(0, 1, 3, 20, 0, 2, 1, 1);
+ seg_set(1, 1, 4, 20, 0, 2, 1, 1);
+ seg_set(2, 1, 5, 20, 0, 2, 1, 1);
+ grid_segs(SEG[0 .. 21]);
+ return r;
+}
+
+test a_fasm_text_is_counted_by_tile {
+ tx_reset();
+ tx_line("# nextpnr-xilinx 0.9.2-107-g7037c948");
+ tx_line("# chipdb xc7a200tfbg484-2 version 1 generator prjxray ");
+ tx_line("CLBLL_L_X2Y10.SLICEL_X0.ALUT.INIT[63:0] = 64'hFFFF0000FFFF0000");
+ tx_line("CLBLL_L_X2Y10.SLICEL_X0.CLUT.INIT[63:0] = 64'h0000_0000_0000_0000");
+ tx_line("CLBLL_L_X2Y10.SLICEL_X0.FFSYNC");
+ tx_line("INT_L_X5Y20.IMUX_L12.LOGIC_OUTS_L3 # a comment after");
+ tx_line("INT_L_X5Y20.EE2BEG0.SS2END0 {annotation} ");
+ tx_line(" BRAM_L_X10Y20.BRAM_L.RAMB18_Y0.WRITE_WIDTH_A_18 = 1'b1");
+ tx_line("BRAM_L_X10Y20.BRAM_L.INIT = 0");
+ tx_line("garbage line without a tile");
+ tx_line("lowercase_X1Y2.FOO");
+ tx_line("");
+ tx_line(" ");
+ const t : []const u8 = TXT[0 .. TXN];
+ assert parse_fasm(t) == 0;
+ assert fa_info(0) == 5;
+ assert fa_info(1) == 2;
+ assert fa_info(2) == 2;
+ assert fa_info(3) == 2;
+ assert fa_info(4) == 3;
+ assert fa_info(5) == 2;
+ assert pool_is(fa_header(0, 0), fa_header(0, 1), "nextpnr-xilinx 0.9.2-107-g7037c948");
+ assert pool_is(fa_header(1, 0), fa_header(1, 1), "chipdb xc7a200tfbg484-2 version 1 generator prjxray");
+ assert pool_is(fa_tile(0, 0), fa_tile(0, 1), "CLBLL_L_X2Y10");
+ assert fa_tile(0, 2) == 2;
+ assert pool_is(fa_tile(1, 0), fa_tile(1, 1), "INT_L_X5Y20");
+ assert fa_tile(1, 2) == 2;
+ assert fa_tile(2, 2) == 1;
+ assert pool_is(fa_bad(0, 0), fa_bad(0, 1), "garbage line without a tile");
+ assert pool_is(fa_bad(1, 0), fa_bad(1, 1), "lowercase_X1Y2.FOO");
+}
+
+test zero_values_clear_a_feature_and_other_values_set_it {
+ tx_reset();
+ tx_line("T_X1Y1.A = 0");
+ tx_line("T_X1Y1.B = 00");
+ tx_line("T_X1Y1.C = 8'h00");
+ tx_line("T_X1Y1.D = 64'b0000_0000");
+ tx_line("T_X1Y1.E = 1");
+ tx_line("T_X1Y1.F = 8'h01");
+ tx_line("T_X1Y1.G = 'hf");
+ tx_line("T_X1Y1.H = 8'x0");
+ tx_line("T_X1Y1.I = 0'b");
+ tx_line("T_X1Y1.J = ");
+ tx_line("T_X1Y1.K = 12'D00A");
+ tx_line("T_X1Y1.L = _0_");
+ assert parse_fasm(TXT[0 .. TXN]) == 0;
+ assert fa_info(2) == 5;
+ assert fa_info(0) == 7;
+ assert fa_tile(0, 2) == 7;
+}
+
+test annotations_and_hashes_follow_the_javascript_order {
+ tx_reset();
+ tx_line("T_X1Y1.A {x # y}");
+ tx_line("{note} T_X1Y1.B");
+ tx_line("T_X1Y1.C {unclosed");
+ tx_line("T_X1Y1.D {a} = {b} 0");
+ tx_line("T_X1Y1.{q}E");
+ tx_line("T_X1Y1 .F");
+ tx_line("T_X1Y1.G = 1 # keep");
+ tx_line(".H = 1");
+ tx_line("T_X1Y1");
+ tx_line("T_X1Y1 = 1");
+ assert parse_fasm(TXT[0 .. TXN]) == 0;
+ assert fa_info(1) == 0;
+ assert fa_info(0) == 5;
+ assert fa_info(2) == 1;
+ assert fa_info(5) == 4;
+ assert fa_tile(0, 2) == 5;
+}
+
+test the_tile_name_is_split_into_type_and_numbers {
+ assert tile_split("CLBLL_L_X2Y10", 0, 13) == 7;
+ assert SC[0] == 2;
+ assert SC[1] == 10;
+ assert tile_split("A_X1Y2_X3Y4", 0, 11) == 6;
+ assert SC[0] == 3;
+ assert SC[1] == 4;
+ assert tile_split("A_X007Y0", 0, 8) == 1;
+ assert SC[0] == 7;
+ assert tile_split("_X1Y2", 0, 5) == -1;
+ assert tile_split("A_X1Y", 0, 5) == -1;
+ assert tile_split("A_X1Y2Y3", 0, 8) == -1;
+ assert tile_split("a_X1Y2", 0, 6) == -1;
+ assert tile_split("A_X1Y2 ", 0, 7) == -1;
+ assert tile_split("AX1Y2", 0, 5) == -1;
+}
+
+test the_grid_finds_a_tile_only_when_the_cell_holds_that_type {
+ assert small_die() == 0;
+ assert grid_lookup("CLBLL_L_X7Y20", 0, 13);
+ assert lk(0) == 0;
+ assert lk(1) == 0;
+ assert lk(2) == 7;
+ assert lk(3) == 0;
+ assert grid_lookup("INT_L_X5Y21", 0, 11);
+ assert lk(0) == 1;
+ assert lk(1) == 1;
+ assert grid_lookup("INT_L_X6Y21", 0, 11);
+ assert lk(0) == 0;
+ assert lk(1) == 1;
+ assert lk(3) == 1;
+ assert grid_lookup("BRAM_L_X10Y21", 0, 13);
+ assert lk(0) == 2;
+ assert lk(1) == 1;
+ assert grid_lookup("INT_L_X6Y20", 0, 11) == false;
+ assert grid_lookup("CLBLL_L_X7Y21", 0, 13) == false;
+ assert grid_lookup("INT_L_X9Y20", 0, 11) == false;
+ assert grid_lookup("NOPE_X1Y1", 0, 9) == false;
+ assert grid_lookup("not a tile", 0, 10) == false;
+ assert grid_at(2, 1) == 2;
+ assert grid_at(3, 0) == -1;
+ assert grid_at(0, 2) == 1;
+ assert grid_at(-1, 0) == -1;
+}
+
+test the_runs_must_cover_every_cell {
+ grid_begin(3, 2);
+ RLE[0] = 0;
+ RLE[1] = 5;
+ assert grid_rle(RLE[0 .. 2]) == 1;
+ RLE[1] = 7;
+ assert grid_rle(RLE[0 .. 2]) == 1;
+ RLE[1] = 6;
+ assert grid_rle(RLE[0 .. 2]) == 0;
+ RLE[2] = 9;
+ assert grid_rle(RLE[0 .. 3]) == 1;
+}
+
+test a_summary_totals_the_placed_tiles_by_family {
+ assert small_die() == 0;
+ var a : usize = la_make("CLB", "INT", "BRAM", "");
+ var b : usize = lb_make("logic", "routing", "memory", "");
+ var c : usize = lc_make("logic", "routing", "memory", "other");
+ assert fam_set(LA[0 .. a], LB[0 .. b], LC[0 .. c]) == 0;
+ assert family_of("CLBLL_L") == 0;
+ assert family_of("INT_L") == 1;
+ assert family_of("BRAM_L") == 2;
+ assert family_of("DSP_L") == 3;
+ a = la_make("INT", "XYZ", "", "");
+ b = lb_make("routing", "missing", "", "");
+ c = lc_make("logic", "routing", "", "");
+ assert fam_set(LA[0 .. a], LB[0 .. b], LC[0 .. c]) == 0;
+ assert family_of("INT_R") == 1;
+ assert family_of("XYZ1") == -1;
+ assert family_of("QQ") == 1;
+ a = la_make("CLB", "INT", "BRAM", "");
+ b = lb_make("logic", "routing", "memory", "");
+ c = lc_make("logic", "routing", "memory", "other");
+ assert fam_set(LA[0 .. a], LB[0 .. b], LC[0 .. c]) == 0;
+ tx_reset();
+ tx_line("INT_L_X5Y20.A");
+ tx_line("INT_L_X5Y20.B");
+ tx_line("CLBLL_L_X7Y20.A");
+ tx_line("BRAM_L_X10Y21.A");
+ tx_line("BRAM_L_X10Y21.B");
+ tx_line("BRAM_L_X10Y21.C");
+ tx_line("INT_L_X6Y20.A");
+ tx_line("INT_L_X6Y20.B");
+ tx_line("INT_L_X6Y20.C");
+ tx_line("DSP_L_X1Y1.A");
+ tx_line("CLBLL_L_X7Y21.A");
+ assert parse_fasm(TXT[0 .. TXN]) == 0;
+ assert summarize() == 0;
+ assert sm_info(0) == 3;
+ assert sm_info(1) == 3;
+ assert sm_info(2) == 11;
+ assert sm_info(3) == 6;
+ assert sm_fam(0, 0) == 1;
+ assert sm_fam(1, 0) == 1;
+ assert sm_fam(2, 0) == 1;
+ assert sm_fam(0, 1) == 1;
+ assert sm_fam(1, 1) == 2;
+ assert sm_fam(2, 1) == 3;
+ assert sm_info(5) >= 0;
+ assert sm_col(sm_info(5) as u32, 5) == 3;
+ assert sm_col(sm_info(5) as u32, 4) == 2;
+}
+
+; FOLD END
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/fasm-skyline/tool.js (533 lines of hand-written JavaScript). The
+; words and numbers above are unchanged. The counting (skyline.js, 122 lines) is fasm_skyline_logic.t27,
+; folded into this module (its text between "FOLD BEGIN" and "FOLD END" is that module's body with the
+; changes listed at FOLD BEGIN): `use` cannot carry a `pub var` across modules and the counting keeps its
+; tables in module-level arrays. This is the biggest canvas page: an isometric drawing of the XC7A200T
+; with up to thousands of columns, drag / pinch / wheel / key control, a PNG of the drawing with its card
+; text, and a binary STL. WHAT THE PAGE NEEDS FROM THE HOST beyond the other pages (each is a few lines
+; of host.js, none holds a word or a rule of this tool):
+; w_draw_cap() the draw list is far bigger than the page buffer (5 MB for sample one): the byte
+; size of the buffer the host must give w_bdraw / w_bdraw2
+; w_bdraw(dst), w_bdraw2(dst) the drawing, and the PNG's, as a BINARY draw list. Printing 150000 doubles as
+; shortest-digit text costs seconds in wasm; a double handed over as 8 bytes costs
+; nothing and is bit-exact. Record: op byte then its arguments, f64 little endian,
+; strings as u16 length + UTF-8:
+; 200 c(i) pick the i-th 1 beginPath 2 closePath 3 fill 4 stroke
+; 5 save 6 restore 7 moveTo(f f) 8 lineTo(f f) 9 translate(f f)
+; 10 fillRect(f f f f) 11 strokeRect(f f f f) 12 setTransform(f f f f f f)
+; 13 fillText(s f f)
+; 32 fillStyle(s) 33 strokeStyle(s) 34 lineWidth(f) 35 font(s)
+; w_bdraw is replayed on the page's canvas, w_bdraw2 on a new canvas of the size
+; w_effect names (K_PNG_W x K_PNG_H), which is then saved as a PNG
+; w_text_need(dst), w_text_give(widths, n) the text width service (see stat_card.t27), now keyed by the
+; whole font string: the PNG's text is wrapped to widths ctx.measureText gives
+; w_canvas_keep() the canvas is one element for the life of the page (pointer capture, focus)
+; w_view(width, dpr, embed, innerHeight, boxWidth) the numbers tool.js reads from the window and from the
+; box .fs-wrap (w_measure_sel); w_box(w, h) is the ResizeObserver of that box
+; w_cpointer(kind, id, x, y, mouse) pointer events on the canvas, x and y relative to its box; kind 0 down,
+; 1 move, 2 up, 3 cancel, 4 leave w_wheel(x, y, deltaY, ctrl) -> 2 when a libm call
+; is missing (the host answers w_libm_need and calls again) w_key(key) -> 1 when handled
+; w_effect(k, dst), w_bytes(dst, cap) 'P' save the drawing as a PNG named ..., 'D' save the bytes
+; w_bytes writes (the STL), 'z' none w_file_cap() the biggest file text to hand over
+; plus w_init w_data(i) w_data_failed(i) w_click(i) w_drag(kind) w_file(name,size,text,st) w_value w_render.
+; Buttons in document order: the samples, whole die, fit design, rotate, download PNG, download STL.
+; w_drop_root() a file dropped anywhere on the page is taken (tool.js listens on the page root)
+; HOW IT DIFFERS FROM tool.js, said plainly:
+; - the host replaces the page at every update and asks w_bdraw each time, so the identical drawing is
+; painted again after any update that tool.js did not paint for; the canvas element is kept;
+; - a file is read as text by the host and handed over whole up to w_file_cap bytes (256 MB): a file above
+; that, a line over 1 MB, more than 65536 tiles or 8 MB of tile names says "could not be read";
+; - sample and grid JSON that does not have the shape the data script writes says the die map did not load;
+; - Math.hypot is V8's algorithm (a Kahan sum of the squares of the scaled arguments); Math.exp is the
+; host's (libm service); Math.round / toFixed / toLocaleString are the js_num ones;
+; - a column's hull in the highlight keeps the rotation it was found in, as tool.js's objects do.
+
+; MEASURED 2026-10-19: t27c gen + a 26-line Zig export wrapper + zig build-exe wasm32-freestanding ReleaseSmall give
+; a 52245-byte wasm. The original page (tool.js with skyline.js) and this one ran in two DOM windows (happy-dom) with
+; a FAKE getContext('2d') that records every call and property assignment (one record for the page's canvas, one
+; for the PNG's detached canvas), ctx.measureText answered by one proportional-font stand-in, a box size / window
+; height / devicePixelRatio / embed class the harness sets, a ResizeObserver stand-in, and recorders for the saved
+; files (the STL by length and sha256, the PNG by name and canvas record). After every action the DOM, the
+; drawing, the PNG's canvas record, the saved files and whether the wheel was prevented were compared: 2271
+; comparisons, 0 differences. The real die and both samples (about 8 million canvas calls recorded): both sample
+; buttons, whole die, fit, four rotations, PNG and STL of both samples, five hover / tap pairs, pointer leave, three
+; wheel zooms (one with ctrl), a drag, a two finger pinch, eleven keys, embed, five box widths with dpr 0 to 2, three
+; details, a small file, a drop on the box and on the page, drag over and leave, an empty and a junk file; 24 small
+; dies with 0 to 3 samples each and 60 fuzzed actions apiece (pointer down / move / up / cancel / leave with mouse
+; and touch, pinch, wheel, keys, files and drops of FASM texts with comments, annotations, zero values, bad lines and
+; chipdb headers, window and dpr changes with the box observer, details), events before the data arrives, a 404,
+; a non-JSON answer, four malformed grid.json, a file of 2.8 MB (above the old 2 MiB host limit) and files
+; declared 256 and 300 MB. Stated differences, measured: 70000 distinct tiles and a line of 1.1 MB (the tool
+; reads them, this page says "could not be read"), and a samples.json that is JSON but has no samples list
+; (tool.js keeps the grid usable, this page does not). Negative controls: a changed DOM node, a skipped closePath, a
+; double read one ulp off, a pointer 0.25 px off, a wheel delta off by one, a PNG saved under another name, an STL
+; of the wrong type, a measured width 60 px too wide, a window height one pixel off, an exp one part in 1e7 off and
+; a wheel that is not prevented are each reported. NOT compared (happy-dom has no layout, fonts or pixels): pixels,
+; the real box and font widths, the bytes of the PNG, a real pointer capture, files near the 256 MB limit.
+
+pub const W_DATA_FILES : [2]str = ["./grid.json", "./samples.json"];
+pub const W_MEASURE_SEL : str = ".fs-wrap";
+pub const W_FILE_CAP : u32 = 268435456;
+pub const W_DRAW_CAP : u32 = 12582912;
+pub const W_PNG_FAMILY : str = "ui-monospace, Menlo, monospace";
+pub const W_PNG_BLACK : str = "#000";
+pub const W_PNG_ACCENT : str = "#00ff88";
+pub const W_PNG_GOLD : str = "#ffd700";
+pub const W_PNG_WHITE : str = "#e8fff7";
+pub const W_PNG_MUTED : str = "#adc9c0";
+pub const W_PNG_FOOT_COLOR : str = "#8fa9a0";
+pub const W_HOVER_COLOR : str = "#e8fff7";
+pub const W_PICK_COLOR : str = "#ffd700";
+pub const W_FLOOR_DEFAULT : str = "#111";
+pub const W_COL_DEFAULT : str = "#888";
+pub const W_PNG_SUFFIX : str = "-skyline.png";
+pub const W_STL_SUFFIX : str = "-skyline.stl";
+pub const W_STL_MIME : str = "model/stl";
+pub const W_ACCEPT : str = ".fasm,text/plain";
+pub const W_BRAND_FROM : str = "S3AI";
+pub const W_SUP3_A : u8 = 194;
+pub const W_SUP3_B : u8 = 179;
+
+// data and the decoded die
+pub var G_BUF : [1048576]u8 = [0; 1048576];
+pub var G_N : u32 = 0;
+pub var D_BUF : [1048576]u8 = [0; 1048576];
+pub var D_N : u32 = 0;
+pub var DATA_ST : [2]u8 = [0; 2];
+pub var S_GRID : bool = false;
+pub var S_NSAMP : u32 = 0;
+pub var G_W : u32 = 0;
+pub var G_H : u32 = 0;
+pub var FAMOF : [4096]i32 = [0; 4096];
+pub var RUN_F : [262144]i32 = [0; 262144];
+pub var RUN_GX : [262144]u32 = [0; 262144];
+pub var RUN_TOP : [262144]u32 = [0; 262144];
+pub var RUN_BOT : [262144]u32 = [0; 262144];
+pub var RUN_N : u32 = 0;
+pub var STG_T : [262144]u8 = [0; 262144];
+pub var STG_X : [262144]u8 = [0; 262144];
+pub var STG_Y : [262144]u8 = [0; 262144];
+pub var STG_E : [65536]u8 = [0; 65536];
+pub var STG_RLE : [262144]u32 = [0; 262144];
+pub var STG_SEG : [458752]i32 = [0; 458752];
+pub var STG_XY : [8192]i32 = [0; 8192];
+pub var STG_LIST : [16384]u8 = [0; 16384];
+pub var STG_LIST2 : [16384]u8 = [0; 16384];
+pub var STG_LIST3 : [16384]u8 = [0; 16384];
+
+// the loaded design, kept apart from the counting tables (the next file read overwrites those)
+pub var R_NPOOL : [8388608]u8 = [0; 8388608];
+pub var R_NPOOL_N : u32 = 0;
+pub var R_NCOL : u32 = 0;
+pub var R_CNO : [65536]u32 = [0; 65536];
+pub var R_CNL : [65536]u32 = [0; 65536];
+pub var R_CGX : [65536]u32 = [0; 65536];
+pub var R_CGY : [65536]u32 = [0; 65536];
+pub var R_CTL : [65536]u32 = [0; 65536];
+pub var R_CFAM : [65536]i32 = [0; 65536];
+pub var R_CN : [65536]u32 = [0; 65536];
+pub var R_NOFF : u32 = 0;
+pub var R_OFFNO : [300]u32 = [0; 300];
+pub var R_OFFNL : [300]u32 = [0; 300];
+pub var R_OFFN : [300]u32 = [0; 300];
+pub var R_FEAT : u64 = 0;
+pub var R_PLACED : u64 = 0;
+pub var R_TALL : i64 = 0 - 1;
+pub var R_FAMT : [64]u64 = [0; 64];
+pub var R_FAMF : [64]u64 = [0; 64];
+pub var S_HAVE : bool = false;
+pub var S_INFO : bool = false;
+pub var S_LABEL : [8192]u8 = [0; 8192];
+pub var S_LABEL_N : u32 = 0;
+pub var S_SRC : [16384]u8 = [0; 16384];
+pub var S_SRC_N : u32 = 0;
+pub var S_NOTE : [8192]u8 = [0; 8192];
+pub var S_NOTE_N : u32 = 0;
+pub var S_CMD : [131072]u8 = [0; 131072];
+pub var S_CMDO : [64]u32 = [0; 64];
+pub var S_CMDL : [64]u32 = [0; 64];
+pub var S_CMDN : u32 = 0;
+pub var S_BADTXT : [262144]u8 = [0; 262144];
+pub var S_BADO : [100]u32 = [0; 100];
+pub var S_BADL : [100]u32 = [0; 100];
+pub var S_BADN : u32 = 0;
+pub var S_CLEARED : u64 = 0;
+pub var S_STATUS : [8192]u8 = [0; 8192];
+pub var S_STATUS_N : u32 = 0;
+pub var S_PRESSED : i32 = 0 - 1;
+pub var S_SAMPLE_IX : u32 = 0;
+pub var S_OVER : bool = false;
+pub var S_CLR_FILE : bool = false;
+pub var S_FNAME : [4096]u8 = [0; 4096];
+pub var S_FNAME_N : u32 = 0;
+pub var S_FB : [262144]u8 = [0; 262144];
+pub var S_NUM : [64]u8 = [0; 64];
+pub var S_NUM2 : [64]u8 = [0; 64];
+pub var S_NUM3 : [64]u8 = [0; 64];
+
+// the view
+pub var S_ROT : u32 = 0;
+pub var S_VX : f64 = 0.0;
+pub var S_VY : f64 = 0.0;
+pub var S_VS : f64 = 1.0;
+pub var S_HK : f64 = 1.0;
+pub var S_CSSW : f64 = 0.0;
+pub var S_CSSH : f64 = 0.0;
+pub var S_LASTW : f64 = 0.0;
+pub var S_SIZED : bool = false;
+pub var S_CANVAS_W : u64 = 0;
+pub var S_CANVAS_H : u64 = 0;
+pub var S_DREW : bool = false;
+pub var S_GEN : u32 = 0;
+pub var L_CI : [65536]u32 = [0; 65536];
+pub var L_DEPTH : [65536]f64 = [0.0; 65536];
+pub var L_U : [65536]f64 = [0.0; 65536];
+pub var L_TMP : [65536]u32 = [0; 65536];
+pub var ENV_W : f64 = 0.0;
+pub var ENV_DPR : f64 = 1.0;
+pub var ENV_EMBED : u32 = 0;
+pub var ENV_IH : f64 = 768.0;
+pub var ENV_BW : f64 = 0.0;
+pub var PK_ON : bool = false;
+pub var PK_CI : u32 = 0;
+pub var PK_GEN : u32 = 0;
+pub var PK_HULL : [12]f64 = [0.0; 12];
+pub var HV_ON : bool = false;
+pub var HV_CI : u32 = 0;
+pub var HV_GEN : u32 = 0;
+pub var HV_HULL : [12]f64 = [0.0; 12];
+
+// pointers
+pub var PT_ID : [8]i64 = [0; 8];
+pub var PT_X : [8]f64 = [0.0; 8];
+pub var PT_Y : [8]f64 = [0.0; 8];
+pub var PT_N : u32 = 0;
+pub var DR_ON : bool = false;
+pub var DR_SX : f64 = 0.0;
+pub var DR_SY : f64 = 0.0;
+pub var DR_VX : f64 = 0.0;
+pub var DR_VY : f64 = 0.0;
+pub var DR_VS : f64 = 0.0;
+pub var DR_MOVED : bool = false;
+pub var PI_ON : bool = false;
+pub var PI_D : f64 = 0.0;
+pub var PI_S : f64 = 0.0;
+
+// effects and the text width service
+pub var S_EF : [4]u8 = [0; 4];
+pub var S_EF_N : u32 = 0;
+pub var S_SAVE : [4096]u8 = [0; 4096];
+pub var S_SAVE_N : u32 = 0;
+pub var X_POOL : [524288]u8 = [0; 524288];
+pub var X_PN : u32 = 0;
+pub var X_FO : [4096]u32 = [0; 4096];
+pub var X_FL : [4096]u32 = [0; 4096];
+pub var X_TO : [4096]u32 = [0; 4096];
+pub var X_TL : [4096]u32 = [0; 4096];
+pub var X_W : [4096]f64 = [0.0; 4096];
+pub var X_ST : [4096]u8 = [0; 4096];
+pub var X_N : u32 = 0;
+pub var X_MISS : bool = false;
+
+// the libm service (Math.exp for the wheel)
+pub var LB_OP : [64]u8 = [0; 64];
+pub var LB_A : [64]f64 = [0.0; 64];
+pub var LB_R : [64]f64 = [0.0; 64];
+pub var LB_N : u32 = 0;
+pub var RQ_OP : [64]u8 = [0; 64];
+pub var RQ_A : [64]f64 = [0.0; 64];
+pub var RQ_N : u32 = 0;
+pub var RQ_SENT : u32 = 0;
+pub var MISSING : bool = false;
+
+/// Two texts equal byte for byte (t27c's Zig target does not compare two str array elements with ==).
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+fn copy_into(dst: []u8, at: u32, s: []const u8) -> u32 {
+ var n : u32 = at;
+ var k : usize = 0;
+ while (k < s.len and (n as usize) < dst.len) {
+ dst[n as usize] = s[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+// --- fmt(): the templates {0} {1} ... of the words, with the arguments in ARB -------------------------------
+
+pub var ARB : [262144]u8 = [0; 262144];
+pub var ARO : [8]u32 = [0; 8];
+pub var ARL : [8]u32 = [0; 8];
+
+pub fn fmt_into(dst: []u8, tpl: []const u8, nargs: u32) -> i64 {
+ var n : usize = 0;
+ var k : usize = 0;
+ while (k < tpl.len) {
+ var done : bool = false;
+ if (tpl[k] == 123) {
+ var q : usize = k + 1;
+ var idx : u64 = 0;
+ while (q < tpl.len and is_digit(tpl[q])) {
+ if (idx < 100000) { idx = idx * 10 + ((tpl[q] - 48) as u64); }
+ q = q + 1;
+ }
+ if (q > k + 1 and q < tpl.len and tpl[q] == 125) {
+ if (idx < (nargs as u64)) {
+ var a : usize = 0;
+ while (a < (ARL[idx as usize] as usize)) {
+ if (n >= dst.len) { return -1; }
+ dst[n] = ARB[(ARO[idx as usize] as usize) + a];
+ n = n + 1;
+ a = a + 1;
+ }
+ }
+ k = q + 1;
+ done = true;
+ }
+ }
+ if (!done) {
+ if (n >= dst.len) { return -1; }
+ dst[n] = tpl[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ }
+ return n as i64;
+}
+
+pub fn arg_set(i: usize, w: []const u8, lo: usize, hi: usize) -> bool {
+ var at : usize = 0;
+ if (i > 0) { at = (ARO[i - 1] + ARL[i - 1]) as usize; }
+ if (at + (hi - lo) > 262144) { return false; }
+ ARO[i] = at as u32;
+ ARL[i] = (hi - lo) as u32;
+ var k : usize = lo;
+ while (k < hi) {
+ ARB[at + (k - lo)] = w[k];
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn arg_num(i: usize, x: f64) -> bool {
+ var at : usize = 0;
+ if (i > 0) { at = (ARO[i - 1] + ARL[i - 1]) as usize; }
+ if (at + 400 > 262144) { return false; }
+ ARO[i] = at as u32;
+ const e : u32 = js_str_num(ARB[0 .. 262144], at as u32, x);
+ if (e == JN_OVER) { return false; }
+ ARL[i] = e - (at as u32);
+ return true;
+}
+
+fn arg_text(i: usize, s: []const u8) -> bool {
+ return arg_set(i, s, 0, s.len);
+}
+
+/// arg i = num(x), Number.toLocaleString('en-US')
+fn arg_loc(i: usize, x: f64) -> bool {
+ const e : u32 = js_locale_num(S_NUM[0..], 0, x);
+ if (e == OVER) { return arg_set(i, S_NUM[0..], 0, 0); }
+ return arg_set(i, S_NUM[0..], 0, e as usize);
+}
+
+fn put_say(dst: []u8, n0: u32, tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_FB[0..], tpl, nargs);
+ if (l < 0) { return OVER; }
+ return text(dst, n0, S_FB[0..(l as usize)]);
+}
+
+fn status_is(s: []const u8) -> u32 {
+ S_STATUS_N = copy_into(S_STATUS[0..], 0, s);
+ return 0;
+}
+
+fn status_fmt(tpl: []const u8, nargs: u32) -> u32 {
+ const l : i64 = fmt_into(S_STATUS[0..], tpl, nargs);
+ S_STATUS_N = 0;
+ if (l >= 0) { S_STATUS_N = l as u32; }
+ return 0;
+}
+
+/// (100 * a / b).toFixed(2), or "0" when b is 0
+fn pct_into(dst: []u8, n0: u32, a: f64, b: f64) -> u32 {
+ if (b == 0.0 or b != b) { return raw(dst, n0, "0"); }
+ return js_to_fixed(dst, n0, (100.0 * a) / b, 2);
+}
+
+// --- boot ----------------------------------------------------------------------------------------------------
+
+pub fn w_init(h: []const u8) -> u32 {
+ DATA_ST[0] = 0;
+ DATA_ST[1] = 0;
+ G_N = 0;
+ D_N = 0;
+ S_GRID = false;
+ S_NSAMP = 0;
+ S_HAVE = false;
+ S_INFO = false;
+ S_PRESSED = 0 - 1;
+ S_SAMPLE_IX = 0;
+ S_OVER = false;
+ S_CLR_FILE = false;
+ S_ROT = 0;
+ S_VX = 0.0;
+ S_VY = 0.0;
+ S_VS = 1.0;
+ S_HK = 1.0;
+ S_CSSW = 0.0;
+ S_CSSH = 0.0;
+ S_LASTW = 0.0;
+ S_SIZED = false;
+ S_DREW = false;
+ S_GEN = 0;
+ PK_ON = false;
+ HV_ON = false;
+ PT_N = 0;
+ DR_ON = false;
+ PI_ON = false;
+ S_EF_N = 0;
+ X_N = 0;
+ X_PN = 0;
+ LB_N = 0;
+ RQ_N = 0;
+ MISSING = false;
+ R_NCOL = 0;
+ R_NOFF = 0;
+ S_CMDN = 0;
+ S_BADN = 0;
+ S_CLEARED = 0;
+ status_is(SAY_LOADING);
+ return 0;
+}
+
+pub fn w_view(width: f64, dpr: f64, embed: u32, ih: f64, bw: f64) -> u32 {
+ ENV_W = width;
+ ENV_DPR = dpr;
+ ENV_EMBED = embed;
+ ENV_IH = ih;
+ ENV_BW = bw;
+ return 0;
+}
+
+pub fn w_measure_sel(dst: []u8) -> u32 {
+ return copy_into(dst, 0, W_MEASURE_SEL);
+}
+
+pub fn w_canvas_keep() -> u32 {
+ return 1;
+}
+
+pub fn w_drop_root() -> u32 {
+ return 1;
+}
+
+pub fn w_draw_cap() -> u32 {
+ return W_DRAW_CAP;
+}
+
+pub fn w_file_cap() -> u32 {
+ return W_FILE_CAP;
+}
+
+// --- the two data files ------------------------------------------------------------------------------------------
+
+fn str_to(dst: []u8, at: u32, d: []const u8, sp: i64) -> u32 {
+ var n : u32 = at;
+ const l : u32 = str_len(d, sp);
+ var k : u32 = 0;
+ while (k < l and (n as usize) < dst.len) {
+ dst[n as usize] = str_byte(d, sp, k);
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+/// Iterate the members of the object at span sp: the member after position p (a position inside the object, p0 =
+/// span_lo + 1 for the first). Returns the key span in S_K and the value span in S_V, and the next position, or
+/// 0 when there is none.
+pub var S_K : i64 = 0;
+pub var S_V : i64 = 0;
+
+fn member_at(d: []const u8, sp: i64, p0: usize) -> usize {
+ const hi : usize = span_hi(sp);
+ var p : usize = skip_ws(d, p0);
+ if (p >= hi or d[p] != 34) { return 0; }
+ const ke : usize = string_end(d, p);
+ if (ke > hi) { return 0; }
+ const q : usize = skip_ws(d, ke);
+ if (q >= hi or d[q] != 58) { return 0; }
+ const v : usize = skip_ws(d, q + 1);
+ const ve : usize = value_end(d, v);
+ if (ve > hi) { return 0; }
+ S_K = jspan(p, ke);
+ S_V = jspan(v, ve);
+ var nx : usize = skip_ws(d, ve);
+ if (nx < hi and d[nx] == 44) { nx = skip_ws(d, nx + 1); }
+ if (nx == 0) { return 1; }
+ return nx;
+}
+
+fn whole(d: []const u8, sp: i64) -> i64 {
+ if (kind(d, sp) != J_NUMBER) { return 0 - 1; }
+ return num_i64(d, sp);
+}
+
+/// grid.json into the counting tables; false when it does not have the shape the data script writes.
+fn decode_grid() -> bool {
+ const d : []const u8 = G_BUF[0..(G_N as usize)];
+ const r : i64 = root(d);
+ if (kind(d, r) != J_OBJECT) { return false; }
+ const gw : i64 = whole(d, obj_get(d, r, "w"));
+ const gh : i64 = whole(d, obj_get(d, r, "h"));
+ if (gw <= 0 or gh <= 0 or gw > 4096 or gh > 4096) { return false; }
+ if (grid_begin(gw as u32, gh as u32) != 0) { return false; }
+ G_W = gw as u32;
+ G_H = gh as u32;
+ // types
+ const ts : i64 = obj_get(d, r, "types");
+ if (kind(d, ts) != J_ARRAY) { return false; }
+ var tn : u32 = 0;
+ var i : u32 = 0;
+ const nt : u32 = arr_len(d, ts);
+ while (i < nt) {
+ const e : i64 = arr_at(d, ts, i);
+ if (kind(d, e) != J_STRING) { return false; }
+ tn = str_to(STG_T[0..], tn, d, e);
+ if ((tn as usize) >= STG_T.len) { return false; }
+ STG_T[tn as usize] = 10;
+ tn = tn + 1;
+ i = i + 1;
+ }
+ if (grid_types(STG_T[0..(tn as usize)]) != 0) { return false; }
+ // rle
+ const rs : i64 = obj_get(d, r, "rle");
+ if (kind(d, rs) != J_ARRAY) { return false; }
+ const nr : u32 = arr_len(d, rs);
+ if ((nr as usize) > STG_RLE.len) { return false; }
+ i = 0;
+ while (i < nr) {
+ const v : i64 = whole(d, arr_at(d, rs, i));
+ if (v < 0) { return false; }
+ STG_RLE[i as usize] = v as u32;
+ i = i + 1;
+ }
+ if (grid_rle(STG_RLE[0..(nr as usize)]) != 0) { return false; }
+ // x and y: prefixes, then the segments of each
+ var xn : u32 = 0;
+ var yn : u32 = 0;
+ var nseg : u32 = 0;
+ var pnum : u32 = 0;
+ var axis : u32 = 0;
+ while (axis < 2) {
+ var key : []const u8 = "x";
+ if (axis == 1) { key = "y"; }
+ const os : i64 = obj_get(d, r, key);
+ if (kind(d, os) != J_OBJECT) { return false; }
+ var p : usize = span_lo(os) + 1;
+ var more : bool = true;
+ while (more) {
+ const nx : usize = member_at(d, os, p);
+ if (nx == 0) {
+ more = false;
+ } else {
+ // the key: a prefix
+ if (axis == 0) {
+ xn = str_to(STG_X[0..], xn, d, S_K);
+ STG_X[xn as usize] = 10;
+ xn = xn + 1;
+ } else {
+ yn = str_to(STG_Y[0..], yn, d, S_K);
+ STG_Y[yn as usize] = 10;
+ yn = yn + 1;
+ }
+ const segs : i64 = S_V;
+ if (kind(d, segs) != J_ARRAY) { return false; }
+ const ns : u32 = arr_len(d, segs);
+ var q : u32 = 0;
+ while (q < ns) {
+ const sg : i64 = arr_at(d, segs, q);
+ if (kind(d, sg) != J_ARRAY or arr_len(d, sg) < 5 or nseg >= 65536) { return false; }
+ STG_SEG[(nseg * 7) as usize] = axis as i32;
+ STG_SEG[(nseg * 7 + 1) as usize] = pnum as i32;
+ var f : u32 = 0;
+ while (f < 5) {
+ const nv : i64 = whole(d, arr_at(d, sg, f));
+ STG_SEG[(nseg * 7 + 2 + f) as usize] = nv as i32;
+ f = f + 1;
+ }
+ nseg = nseg + 1;
+ q = q + 1;
+ }
+ pnum = pnum + 1;
+ if (nx == 1) { more = false; } else { p = nx; }
+ }
+ }
+ axis = axis + 1;
+ }
+ if (grid_prefixes(0, STG_X[0..(xn as usize)]) != 0) { return false; }
+ if (grid_prefixes(1, STG_Y[0..(yn as usize)]) != 0) { return false; }
+ if (grid_segs(STG_SEG[0..((nseg * 7) as usize)]) != 0) { return false; }
+ // the exceptions: name -> [gx, gy]
+ const es : i64 = obj_get(d, r, "except");
+ if (kind(d, es) != J_OBJECT) { return false; }
+ var en : u32 = 0;
+ var exn : u32 = 0;
+ var ep : usize = span_lo(es) + 1;
+ var emore : bool = true;
+ while (emore) {
+ const nx2 : usize = member_at(d, es, ep);
+ if (nx2 == 0) {
+ emore = false;
+ } else {
+ en = str_to(STG_E[0..], en, d, S_K);
+ STG_E[en as usize] = 10;
+ en = en + 1;
+ const pr : i64 = S_V;
+ if (kind(d, pr) != J_ARRAY or arr_len(d, pr) < 2 or exn >= 4096) { return false; }
+ STG_XY[(exn * 2) as usize] = whole(d, arr_at(d, pr, 0)) as i32;
+ STG_XY[(exn * 2 + 1) as usize] = whole(d, arr_at(d, pr, 1)) as i32;
+ exn = exn + 1;
+ if (nx2 == 1) { emore = false; } else { ep = nx2; }
+ }
+ }
+ if (grid_except(STG_E[0..(en as usize)], STG_XY[0..((exn * 2) as usize)]) != 0) { return false; }
+ return true;
+}
+
+fn list_add(buf: []u8, at: u32, s: []const u8) -> u32 {
+ var n : u32 = copy_into(buf, at, s);
+ buf[n as usize] = 10;
+ return n + 1;
+}
+
+/// The family lists the counting needs, from the spec's own constants.
+fn set_families() -> bool {
+ var a : u32 = 0;
+ var b : u32 = 0;
+ var c : u32 = 0;
+ var i : usize = 0;
+ while (i < K_TYPE_PREFIX.len) {
+ a = list_add(STG_LIST[0..], a, K_TYPE_PREFIX[i]);
+ b = list_add(STG_LIST2[0..], b, K_TYPE_FAMILY[i]);
+ i = i + 1;
+ }
+ i = 0;
+ while (i < K_FAMILIES.len) {
+ c = list_add(STG_LIST3[0..], c, K_FAMILIES[i]);
+ i = i + 1;
+ }
+ return fam_set(STG_LIST[0..(a as usize)], STG_LIST2[0..(b as usize)], STG_LIST3[0..(c as usize)]) == 0;
+}
+
+/// state.runs: per column, the stretches of one family (the floor under the columns).
+fn make_runs() -> bool {
+ var t : u32 = 0;
+ while (t < (V[V_NTY] as u32)) {
+ const o : usize = GTO[t as usize] as usize;
+ FAMOF[t as usize] = family_of(GPOOL[o..(o + (GTL[t as usize] as usize))]);
+ t = t + 1;
+ }
+ RUN_N = 0;
+ var gx : u32 = 0;
+ while (gx < G_W) {
+ var start : u32 = 0;
+ var gy : u32 = 1;
+ while (gy <= G_H) {
+ const f : i32 = FAMOF[grid_at(gx as i64, start as i64) as usize];
+ var cut : bool = gy == G_H;
+ if (!cut) { cut = FAMOF[grid_at(gx as i64, gy as i64) as usize] != f; }
+ if (cut) {
+ if (RUN_N >= 262144) { return false; }
+ RUN_F[RUN_N as usize] = f;
+ RUN_GX[RUN_N as usize] = gx;
+ RUN_TOP[RUN_N as usize] = start;
+ RUN_BOT[RUN_N as usize] = gy - 1;
+ RUN_N = RUN_N + 1;
+ start = gy;
+ }
+ gy = gy + 1;
+ }
+ gx = gx + 1;
+ }
+ return true;
+}
+
+fn data_ready() -> u32 {
+ if (DATA_ST[0] == 2 or DATA_ST[1] == 2) {
+ status_is(SAY_ERR_DATA);
+ return 0;
+ }
+ if (DATA_ST[0] != 1 or DATA_ST[1] != 1) { return 0; }
+ if (!jv_valid(G_BUF[0..(G_N as usize)]) or !jv_valid(D_BUF[0..(D_N as usize)])) {
+ status_is(SAY_ERR_DATA);
+ return 0;
+ }
+ const sd : []const u8 = D_BUF[0..(D_N as usize)];
+ const sr : i64 = root(sd);
+ const sa : i64 = obj_get(sd, sr, "samples");
+ if (kind(sd, sr) != J_OBJECT or kind(sd, sa) != J_ARRAY) {
+ status_is(SAY_ERR_DATA);
+ return 0;
+ }
+ if (!decode_grid() or !set_families() or !make_runs()) {
+ status_is(SAY_ERR_DATA);
+ return 0;
+ }
+ S_NSAMP = arr_len(sd, sa);
+ S_GRID = true;
+ show_sample(0);
+ return 0;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ S_EF_N = 0;
+ if (i > 1) { return 0; }
+ if (s.len > 1048576) {
+ DATA_ST[i as usize] = 2;
+ } else {
+ var k : usize = 0;
+ if (i == 0) {
+ while (k < s.len) {
+ G_BUF[k] = s[k];
+ k = k + 1;
+ }
+ G_N = s.len as u32;
+ } else {
+ while (k < s.len) {
+ D_BUF[k] = s[k];
+ k = k + 1;
+ }
+ D_N = s.len as u32;
+ }
+ DATA_ST[i as usize] = 1;
+ }
+ data_ready();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ S_EF_N = 0;
+ if (i > 1) { return 0; }
+ DATA_ST[i as usize] = 2;
+ data_ready();
+ return 0;
+}
+
+// --- loading a design ---------------------------------------------------------------------------------------------
+
+/// tiles.set(name, cnt) for the name in LINE[lo..hi): a Map built from the sample's [name, n] pairs.
+fn tile_set(lo: usize, hi: usize, cnt: u32) -> bool {
+ const t : []const u8 = LINE[0 .. 1048576];
+ var idx : usize = (fnv(t, lo, hi) as usize) % 262144;
+ while (HT[idx] != 0) {
+ const ti : usize = (HT[idx] - 1) as usize;
+ if (pool_eq_text(TLO[ti], TLL[ti], t, lo, hi)) {
+ TCT[ti] = cnt;
+ return true;
+ }
+ idx = (idx + 1) % 262144;
+ }
+ const nt : usize = V[V_NT] as usize;
+ if (nt >= 65536) { return false; }
+ const at : i64 = pool_put_text(t, lo, hi);
+ if (at < 0) { return false; }
+ TLO[nt] = at as u32;
+ TLL[nt] = (hi - lo) as u32;
+ TCT[nt] = cnt;
+ HT[idx] = (nt + 1) as u32;
+ V[V_NT] = (nt + 1) as u64;
+ return true;
+}
+
+fn tiles_reset() -> u32 {
+ var f : usize = 0;
+ while (f < 8) {
+ V[f] = 0;
+ f = f + 1;
+ }
+ var h : usize = 0;
+ while (h < 262144) {
+ HT[h] = 0;
+ h = h + 1;
+ }
+ return 0;
+}
+
+/// The tile table of a sample from its "tiles" array of [name, n] pairs. false when it has not that shape.
+fn sample_tiles(d: []const u8, ts: i64) -> bool {
+ tiles_reset();
+ if (kind(d, ts) != J_ARRAY) { return false; }
+ const n : u32 = arr_len(d, ts);
+ var i : u32 = 0;
+ while (i < n) {
+ const pr : i64 = arr_at(d, ts, i);
+ if (kind(d, pr) != J_ARRAY or arr_len(d, pr) < 2) { return false; }
+ const nm : i64 = arr_at(d, pr, 0);
+ const ct : i64 = arr_at(d, pr, 1);
+ if (kind(d, nm) != J_STRING or kind(d, ct) != J_NUMBER) { return false; }
+ const l : u32 = str_to(LINE[0..], 0, d, nm);
+ const c : f64 = num_f64(d, ct);
+ if (!tile_set(0, l as usize, c as u32)) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+/// The counting of the table the logic holds now, kept apart from it.
+fn snapshot_sum() -> bool {
+ const ncol : u32 = sm_info(0) as u32;
+ R_NCOL = ncol;
+ R_NPOOL_N = 0;
+ var i : u32 = 0;
+ while (i < ncol) {
+ const t : usize = sm_col(i, 0) as usize;
+ const o : usize = TLO[t] as usize;
+ const l : usize = TLL[t] as usize;
+ R_CNO[i as usize] = R_NPOOL_N;
+ R_CNL[i as usize] = l as u32;
+ var k : usize = 0;
+ while (k < l) {
+ R_NPOOL[(R_NPOOL_N as usize) + k] = POOL[o + k];
+ k = k + 1;
+ }
+ R_NPOOL_N = R_NPOOL_N + (l as u32);
+ R_CGX[i as usize] = sm_col(i, 1) as u32;
+ R_CGY[i as usize] = sm_col(i, 2) as u32;
+ R_CTL[i as usize] = sm_col(i, 3) as u32;
+ R_CFAM[i as usize] = sm_col(i, 4) as i32;
+ R_CN[i as usize] = sm_col(i, 5) as u32;
+ i = i + 1;
+ }
+ R_NOFF = sm_info(1) as u32;
+ var j : u32 = 0;
+ while (j < R_NOFF and j < 300) {
+ const t2 : usize = sm_off(j, 0) as usize;
+ const o2 : usize = TLO[t2] as usize;
+ const l2 : usize = TLL[t2] as usize;
+ R_OFFNO[j as usize] = R_NPOOL_N;
+ R_OFFNL[j as usize] = l2 as u32;
+ var k2 : usize = 0;
+ while (k2 < l2) {
+ R_NPOOL[(R_NPOOL_N as usize) + k2] = POOL[o2 + k2];
+ k2 = k2 + 1;
+ }
+ R_NPOOL_N = R_NPOOL_N + (l2 as u32);
+ R_OFFN[j as usize] = sm_off(j, 1) as u32;
+ j = j + 1;
+ }
+ R_FEAT = sm_info(2) as u64;
+ R_PLACED = sm_info(3) as u64;
+ R_TALL = sm_info(5);
+ var f : u32 = 0;
+ while (f < 64) {
+ R_FAMT[f as usize] = sm_fam(f, 0);
+ R_FAMF[f as usize] = sm_fam(f, 1);
+ f = f + 1;
+ }
+ return true;
+}
+
+fn tallest_n() -> f64 {
+ if (R_TALL < 0) { return 0.0; }
+ return R_CN[R_TALL as usize] as f64;
+}
+
+/// load(): the table the logic holds is counted, kept, and the drawing starts again.
+fn load_design() -> u32 {
+ summarize();
+ snapshot_sum();
+ S_HAVE = true;
+ S_CLEARED = V[V_CLR];
+ PK_ON = false;
+ HV_ON = false;
+ arg_text(0, S_LABEL[0..(S_LABEL_N as usize)]);
+ arg_loc(1, R_PLACED as f64);
+ arg_loc(2, R_NCOL as f64);
+ status_fmt(SAY_SUMMARY, 3);
+ layout();
+ fit(false);
+ return 0;
+}
+
+/// showSample(i)
+fn show_sample(i: u32) -> u32 {
+ if (!S_GRID or i >= S_NSAMP) { return 0; }
+ const d : []const u8 = D_BUF[0..(D_N as usize)];
+ const sa : i64 = obj_get(d, root(d), "samples");
+ const s : i64 = arr_at(d, sa, i);
+ if (kind(d, s) != J_OBJECT) { return 0; }
+ S_SAMPLE_IX = i;
+ S_PRESSED = i as i32;
+ if (!sample_tiles(d, obj_get(d, s, "tiles"))) {
+ status_is(SAY_ERR_DATA);
+ return 0;
+ }
+ V[V_FEAT] = num_i64(d, obj_get(d, s, "features")) as u64;
+ V[V_CLR] = num_i64(d, obj_get(d, s, "cleared")) as u64;
+ S_LABEL_N = str_to(S_LABEL[0..], 0, d, obj_get(d, s, "file"));
+ // the source line: Sample {0}, part {1}, sha256 {2}... (the first 16 characters of the hash)
+ arg_set(0, S_LABEL[0..], 0, S_LABEL_N as usize);
+ var pl : u32 = str_to(S_FB[0..], 0, d, obj_get(d, s, "part"));
+ arg_set(1, S_FB[0..], 0, pl as usize);
+ var shl : u32 = str_to(S_FB[0..], pl, d, obj_get(d, s, "sha256"));
+ if (shl - pl > 16) { shl = pl + 16; }
+ arg_set(2, S_FB[0..], pl as usize, shl as usize);
+ const sl : i64 = fmt_into(S_SRC[0..], SAY_SOURCE_SAMPLE, 3);
+ S_SRC_N = 0;
+ if (sl >= 0) { S_SRC_N = sl as u32; }
+ S_NOTE_N = 0;
+ if ((i as usize) < SAY_SAMPLE_LINES.len) { S_NOTE_N = copy_into(S_NOTE[0..], 0, SAY_SAMPLE_LINES[i as usize]); }
+ // the commands
+ S_CMDN = 0;
+ const cs : i64 = obj_get(d, s, "commands");
+ var cpos : u32 = 0;
+ if (kind(d, cs) == J_ARRAY) {
+ const cn : u32 = arr_len(d, cs);
+ var c : u32 = 0;
+ while (c < cn and c < 64) {
+ const e : i64 = arr_at(d, cs, c);
+ S_CMDO[c as usize] = cpos;
+ if (kind(d, e) == J_STRING) { cpos = str_to(S_CMD[0..], cpos, d, e); }
+ S_CMDL[c as usize] = cpos - S_CMDO[c as usize];
+ c = c + 1;
+ }
+ S_CMDN = c;
+ }
+ S_BADN = 0;
+ return load_design();
+}
+
+// --- geometry --------------------------------------------------------------------------------------------------------
+
+pub var G_PX : f64 = 0.0;
+pub var G_PY : f64 = 0.0;
+
+/// rotPt(x, y) into G_PX, G_PY
+fn rot_pt(x: f64, y: f64) -> u32 {
+ const gw : f64 = G_W as f64;
+ const gh : f64 = G_H as f64;
+ if (S_ROT == 1) {
+ G_PX = gh - y;
+ G_PY = x;
+ return 0;
+ }
+ if (S_ROT == 2) {
+ G_PX = gw - x;
+ G_PY = gh - y;
+ return 0;
+ }
+ if (S_ROT == 3) {
+ G_PX = y;
+ G_PY = gw - x;
+ return 0;
+ }
+ G_PX = x;
+ G_PY = y;
+ return 0;
+}
+
+pub var G_U : f64 = 0.0;
+pub var G_V : f64 = 0.0;
+
+/// cellUV(gx, gy) into G_U, G_V
+fn cell_uv(gx: f64, gy: f64) -> u32 {
+ rot_pt(gx, gy);
+ const ax : f64 = G_PX;
+ const ay : f64 = G_PY;
+ rot_pt(gx + 1.0, gy + 1.0);
+ G_U = js_min(ax, G_PX);
+ G_V = js_min(ay, G_PY);
+ return 0;
+}
+
+fn px_of(u: f64, v: f64) -> f64 {
+ return u - v;
+}
+
+fn py_of(u: f64, v: f64, z: f64) -> f64 {
+ return (u + v) / 2.0 - z;
+}
+
+/// The six points of the hull of column ci (the outline of its box), x then y, into out[0..12].
+fn hull_of(ci: u32, out: []f64) -> u32 {
+ cell_uv(R_CGX[ci as usize] as f64, R_CGY[ci as usize] as f64);
+ const u : f64 = G_U;
+ const v : f64 = G_V;
+ const z : f64 = (R_CN[ci as usize] as f64) * S_HK;
+ out[0] = px_of(u, v);
+ out[1] = py_of(u, v, z);
+ out[2] = px_of(u + 1.0, v);
+ out[3] = py_of(u + 1.0, v, z);
+ out[4] = px_of(u + 1.0, v);
+ out[5] = py_of(u + 1.0, v, 0.0);
+ out[6] = px_of(u + 1.0, v + 1.0);
+ out[7] = py_of(u + 1.0, v + 1.0, 0.0);
+ out[8] = px_of(u, v + 1.0);
+ out[9] = py_of(u, v + 1.0, 0.0);
+ out[10] = px_of(u, v + 1.0);
+ out[11] = py_of(u, v + 1.0, z);
+ return 0;
+}
+
+/// layout(): the height scale and the columns in drawing order (far to near), stable.
+fn layout() -> u32 {
+ var mx : f64 = tallest_n();
+ if (mx == 0.0) { mx = 1.0; }
+ S_HK = ((K_HEIGHT_PCT as f64) / 100.0) * (((G_W as f64) + (G_H as f64)) / 2.0) / mx;
+ var i : u32 = 0;
+ while (i < R_NCOL) {
+ cell_uv(R_CGX[i as usize] as f64, R_CGY[i as usize] as f64);
+ L_DEPTH[i as usize] = G_U + G_V;
+ L_U[i as usize] = G_U;
+ L_CI[i as usize] = i;
+ i = i + 1;
+ }
+ // a stable merge sort by (depth, u)
+ const n : u32 = R_NCOL;
+ var w : u32 = 1;
+ while (w < n) {
+ var lo : u32 = 0;
+ while (lo < n) {
+ var mid : u32 = lo + w;
+ if (mid > n) { mid = n; }
+ var hi : u32 = lo + w + w;
+ if (hi > n) { hi = n; }
+ var a : u32 = lo;
+ var b : u32 = mid;
+ var k : u32 = lo;
+ while (a < mid or b < hi) {
+ var takeb : bool = false;
+ if (a >= mid) {
+ takeb = true;
+ } else {
+ if (b < hi) {
+ const ca : u32 = L_CI[a as usize];
+ const cb : u32 = L_CI[b as usize];
+ var d1 : f64 = L_DEPTH[cb as usize] - L_DEPTH[ca as usize];
+ // sort((a, b) => a.depth - b.depth || a.u - b.u): b goes first when the result is positive
+ var cmp : f64 = L_DEPTH[ca as usize] - L_DEPTH[cb as usize];
+ if (cmp == 0.0 or cmp != cmp) { cmp = L_U[ca as usize] - L_U[cb as usize]; }
+ takeb = cmp > 0.0;
+ }
+ }
+ if (takeb) {
+ L_TMP[k as usize] = L_CI[b as usize];
+ b = b + 1;
+ } else {
+ L_TMP[k as usize] = L_CI[a as usize];
+ a = a + 1;
+ }
+ k = k + 1;
+ }
+ lo = lo + w + w;
+ }
+ var c : u32 = 0;
+ while (c < n) {
+ L_CI[c as usize] = L_TMP[c as usize];
+ c = c + 1;
+ }
+ w = w * 2;
+ }
+ S_GEN = S_GEN + 1;
+ return 0;
+}
+
+pub var B_X0 : f64 = 0.0;
+pub var B_Y0 : f64 = 0.0;
+pub var B_X1 : f64 = 0.0;
+pub var B_Y1 : f64 = 0.0;
+
+fn bounds_add(x: f64, y: f64) -> u32 {
+ B_X0 = js_min(B_X0, x);
+ B_Y0 = js_min(B_Y0, y);
+ B_X1 = js_max(B_X1, x);
+ B_Y1 = js_max(B_Y1, y);
+ return 0;
+}
+
+/// bounds(design) into B_*
+fn bounds(design: bool) -> u32 {
+ B_X0 = js_inf(0.0);
+ B_Y0 = js_inf(0.0);
+ B_X1 = 0.0 - js_inf(0.0);
+ B_Y1 = 0.0 - js_inf(0.0);
+ if (!design or R_NCOL == 0) {
+ const gw : f64 = G_W as f64;
+ const gh : f64 = G_H as f64;
+ var c : u32 = 0;
+ while (c < 4) {
+ var cx : f64 = 0.0;
+ var cy : f64 = 0.0;
+ if (c == 1 or c == 3) { cx = gw; }
+ if (c == 2 or c == 3) { cy = gh; }
+ rot_pt(cx, cy);
+ bounds_add(px_of(G_PX, G_PY), py_of(G_PX, G_PY, 0.0));
+ c = c + 1;
+ }
+ }
+ var i : u32 = 0;
+ var hull : [12]f64 = [0.0; 12];
+ while (i < R_NCOL) {
+ hull_of(L_CI[i as usize], hull[0..]);
+ var p : u32 = 0;
+ while (p < 6) {
+ bounds_add(hull[(p * 2) as usize], hull[(p * 2 + 1) as usize]);
+ p = p + 1;
+ }
+ i = i + 1;
+ }
+ var pad : f64 = 2.0;
+ if (design) { pad = 6.0; }
+ B_X0 = B_X0 - pad;
+ B_Y0 = B_Y0 - pad;
+ B_X1 = B_X1 + pad;
+ B_Y1 = B_Y1 + pad;
+ return 0;
+}
+
+pub var F_S : f64 = 1.0;
+pub var F_X : f64 = 0.0;
+pub var F_Y : f64 = 0.0;
+
+/// fitView(bounds, w, hgt) into F_*
+fn fit_view(w: f64, hgt: f64) -> u32 {
+ F_S = js_min(w / (B_X1 - B_X0), hgt / (B_Y1 - B_Y0));
+ F_X = (B_X0 + B_X1) / 2.0 - w / F_S / 2.0;
+ F_Y = (B_Y0 + B_Y1) / 2.0 - hgt / F_S / 2.0;
+ return 0;
+}
+
+/// size(): the canvas gets the width of its box and a height that fits the window.
+fn size_canvas() -> u32 {
+ var bw : f64 = ENV_BW;
+ if (bw == 0.0 or bw != bw) { bw = 600.0; }
+ S_CSSW = bw;
+ var maxh : f64 = 0.0;
+ if (ENV_EMBED != 0) {
+ maxh = js_max(180.0, ENV_IH - 200.0);
+ } else {
+ maxh = js_max(260.0, ENV_IH * 0.66);
+ }
+ S_CSSH = js_round(js_min(S_CSSW * 0.62, maxh));
+ var dpr : f64 = ENV_DPR;
+ if (dpr == 0.0 or dpr != dpr) { dpr = 1.0; }
+ S_CANVAS_W = to_uint32(js_round(S_CSSW * dpr));
+ S_CANVAS_H = to_uint32(js_round(S_CSSH * dpr));
+ S_SIZED = true;
+ return 0;
+}
+
+/// A DOM unsigned long from a double: truncated, modulo 2^32.
+fn to_uint32(x: f64) -> u64 {
+ if (x != x or x - x != 0.0) { return 0; }
+ var t : f64 = js_floor(x);
+ if (x < 0.0) { t = js_ceil(x); }
+ if (t < 0.0) {
+ const m : f64 = t - 4294967296.0 * js_floor(t / 4294967296.0);
+ return m as u64;
+ }
+ const m2 : f64 = t - 4294967296.0 * js_floor(t / 4294967296.0);
+ return m2 as u64;
+}
+
+/// fit(design)
+fn fit(design: bool) -> u32 {
+ if (!S_GRID) { return 0; }
+ size_canvas();
+ bounds(design);
+ fit_view(S_CSSW, S_CSSH);
+ S_VS = F_S;
+ S_VX = F_X;
+ S_VY = F_Y;
+ S_DREW = true;
+ return 0;
+}
+
+fn rotate_view() -> u32 {
+ if (!S_GRID) { return 0; }
+ S_ROT = (S_ROT + 1) % 4;
+ layout();
+ fit(false);
+ return 0;
+}
+
+/// zoomAt(px, py, factor)
+fn zoom_at(px: f64, py: f64, factor: f64) -> u32 {
+ if (!S_GRID) { return 0; }
+ const s : f64 = S_VS;
+ const x : f64 = S_VX;
+ const y : f64 = S_VY;
+ bounds(false);
+ fit_view(S_CSSW, S_CSSH);
+ const base : f64 = F_S;
+ const ns : f64 = js_min(js_max(s * factor, base * 0.6), base * (K_MAX_ZOOM as f64));
+ const gx : f64 = x + px / s;
+ const gy : f64 = y + py / s;
+ S_VS = ns;
+ S_VX = gx - px / ns;
+ S_VY = gy - py / ns;
+ S_DREW = true;
+ return 0;
+}
+
+/// Math.hypot(a, b), V8's algorithm: the larger argument scales, a Kahan sum of the squares.
+fn hypot2(a0: f64, b0: f64) -> f64 {
+ var a : f64 = a0;
+ var b : f64 = b0;
+ if (a != a or b != b) {
+ if (a - a != 0.0 and a == a) { return js_inf(0.0); }
+ if (b - b != 0.0 and b == b) { return js_inf(0.0); }
+ return js_nan(0.0);
+ }
+ if (a < 0.0) { a = 0.0 - a; }
+ if (b < 0.0) { b = 0.0 - b; }
+ var mx : f64 = 0.0;
+ if (a > mx) { mx = a; }
+ if (b > mx) { mx = b; }
+ if (mx == js_inf(0.0)) { return mx; }
+ if (mx == 0.0) { return 0.0; }
+ var sum : f64 = 0.0;
+ var comp : f64 = 0.0;
+ var k : u32 = 0;
+ while (k < 2) {
+ var v : f64 = a;
+ if (k == 1) { v = b; }
+ const n : f64 = v / mx;
+ const summand : f64 = n * n - comp;
+ const prelim : f64 = sum + summand;
+ comp = (prelim - sum) - summand;
+ sum = prelim;
+ k = k + 1;
+ }
+ return @sqrt(sum) * mx;
+}
+
+/// inside(pts, x, y) for a hull of six points in hull[0..12]
+fn inside_hull(hull: []const f64, x: f64, y: f64) -> bool {
+ var sign : f64 = 0.0;
+ var i : u32 = 0;
+ while (i < 6) {
+ const j : u32 = (i + 1) % 6;
+ const ax : f64 = hull[(i * 2) as usize];
+ const ay : f64 = hull[(i * 2 + 1) as usize];
+ const bx : f64 = hull[(j * 2) as usize];
+ const by : f64 = hull[(j * 2 + 1) as usize];
+ const c : f64 = (bx - ax) * (y - ay) - (by - ay) * (x - ax);
+ if (c != 0.0) {
+ var sg : f64 = 1.0;
+ if (c < 0.0) { sg = 0.0 - 1.0; }
+ if (c != c) { sg = c; }
+ if (sign != 0.0 and sg != sign) { return false; }
+ sign = sg;
+ }
+ i = i + 1;
+ }
+ return true;
+}
+
+pub var N_FOUND : bool = false;
+pub var N_CI : u32 = 0;
+pub var N_HULL : [12]f64 = [0.0; 12];
+
+/// nearest(px, py): the column under the point, else the nearest top within half a touch target.
+fn nearest(px: f64, py: f64) -> u32 {
+ N_FOUND = false;
+ const x : f64 = S_VX;
+ const y : f64 = S_VY;
+ const s : f64 = S_VS;
+ const wx : f64 = px / s + x;
+ const wy : f64 = py / s + y;
+ var hull : [12]f64 = [0.0; 12];
+ var i : u32 = R_NCOL;
+ while (i > 0) {
+ i = i - 1;
+ const ci : u32 = L_CI[i as usize];
+ hull_of(ci, hull[0..]);
+ if (inside_hull(hull[0..], wx, wy)) {
+ N_FOUND = true;
+ N_CI = ci;
+ var q : u32 = 0;
+ while (q < 12) {
+ N_HULL[q as usize] = hull[q as usize];
+ q = q + 1;
+ }
+ return 0;
+ }
+ }
+ var bd : f64 = (K_MIN_TARGET_PX as f64) / 2.0;
+ var k : u32 = 0;
+ while (k < R_NCOL) {
+ const ci2 : u32 = L_CI[k as usize];
+ hull_of(ci2, hull[0..]);
+ const tx : f64 = hull[0];
+ const ty : f64 = hull[1];
+ const dd : f64 = hypot2((tx - x) * s - px, (ty - y) * s - py);
+ if (dd < bd) {
+ bd = dd;
+ N_FOUND = true;
+ N_CI = ci2;
+ var q2 : u32 = 0;
+ while (q2 < 12) {
+ N_HULL[q2 as usize] = hull[q2 as usize];
+ q2 = q2 + 1;
+ }
+ }
+ k = k + 1;
+ }
+ return 0;
+}
+
+// --- the events -----------------------------------------------------------------------------------------------------------
+
+fn pt_find(id: i64) -> i32 {
+ var i : u32 = 0;
+ while (i < PT_N) {
+ if (PT_ID[i as usize] == id) { return i as i32; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+fn pt_remove(id: i64) -> u32 {
+ const f : i32 = pt_find(id);
+ if (f < 0) { return 0; }
+ var i : u32 = f as u32;
+ while (i + 1 < PT_N) {
+ PT_ID[i as usize] = PT_ID[(i + 1) as usize];
+ PT_X[i as usize] = PT_X[(i + 1) as usize];
+ PT_Y[i as usize] = PT_Y[(i + 1) as usize];
+ i = i + 1;
+ }
+ PT_N = PT_N - 1;
+ return 0;
+}
+
+/// state.hover = the column found (or none); the info panel and the drawing follow.
+fn set_hover() -> u32 {
+ S_INFO = true;
+ HV_ON = N_FOUND;
+ HV_CI = N_CI;
+ HV_GEN = S_GEN;
+ var q : u32 = 0;
+ while (q < 12) {
+ HV_HULL[q as usize] = N_HULL[q as usize];
+ q = q + 1;
+ }
+ S_DREW = true;
+ return 0;
+}
+
+fn set_pick() -> u32 {
+ S_INFO = true;
+ PK_ON = N_FOUND;
+ PK_CI = N_CI;
+ PK_GEN = S_GEN;
+ var q : u32 = 0;
+ while (q < 12) {
+ PK_HULL[q as usize] = N_HULL[q as usize];
+ q = q + 1;
+ }
+ S_DREW = true;
+ return 0;
+}
+
+/// A pointer on the canvas. kind 0 down, 1 move, 2 up, 3 cancel, 4 leave; x, y in the canvas's box.
+pub fn w_cpointer(kind_of: u32, id: i64, x: f64, y: f64, mouse: u32) -> u32 {
+ S_EF_N = 0;
+ if (kind_of == 0) {
+ const f : i32 = pt_find(id);
+ if (f >= 0) {
+ PT_X[f as usize] = x;
+ PT_Y[f as usize] = y;
+ } else {
+ if (PT_N < 8) {
+ PT_ID[PT_N as usize] = id;
+ PT_X[PT_N as usize] = x;
+ PT_Y[PT_N as usize] = y;
+ PT_N = PT_N + 1;
+ }
+ }
+ if (PT_N == 1) {
+ DR_ON = true;
+ DR_SX = x;
+ DR_SY = y;
+ DR_VX = S_VX;
+ DR_VY = S_VY;
+ DR_VS = S_VS;
+ DR_MOVED = false;
+ }
+ if (PT_N == 2) {
+ PI_ON = true;
+ PI_D = hypot2(PT_X[0] - PT_X[1], PT_Y[0] - PT_Y[1]);
+ PI_S = S_VS;
+ DR_ON = false;
+ }
+ return 0;
+ }
+ if (kind_of == 1) {
+ const f2 : i32 = pt_find(id);
+ if (f2 >= 0) {
+ PT_X[f2 as usize] = x;
+ PT_Y[f2 as usize] = y;
+ }
+ if (PI_ON and PT_N == 2) {
+ const mx : f64 = (PT_X[0] + PT_X[1]) / 2.0;
+ const my : f64 = (PT_Y[0] + PT_Y[1]) / 2.0;
+ zoom_at(mx, my, (PI_S * hypot2(PT_X[0] - PT_X[1], PT_Y[0] - PT_Y[1]) / PI_D) / S_VS);
+ return 0;
+ }
+ if (DR_ON) {
+ const dx : f64 = x - DR_SX;
+ const dy : f64 = y - DR_SY;
+ var ax : f64 = dx;
+ if (ax < 0.0) { ax = 0.0 - ax; }
+ var ay : f64 = dy;
+ if (ay < 0.0) { ay = 0.0 - ay; }
+ if (ax + ay > 4.0) { DR_MOVED = true; }
+ if (DR_MOVED) {
+ S_VS = DR_VS;
+ S_VX = DR_VX - dx / DR_VS;
+ S_VY = DR_VY - dy / DR_VS;
+ S_DREW = true;
+ }
+ return 0;
+ }
+ if (mouse != 0) {
+ nearest(x, y);
+ var same : bool = false;
+ if (!N_FOUND and !HV_ON) { same = true; }
+ if (N_FOUND and HV_ON and N_CI == HV_CI and S_GEN == HV_GEN) { same = true; }
+ if (!same) { set_hover(); }
+ }
+ return 0;
+ }
+ if (kind_of == 2) {
+ if (DR_ON and !DR_MOVED) {
+ nearest(x, y);
+ set_pick();
+ }
+ pt_remove(id);
+ if (PT_N < 2) { PI_ON = false; }
+ if (PT_N == 0) { DR_ON = false; }
+ return 0;
+ }
+ if (kind_of == 3) {
+ pt_remove(id);
+ PI_ON = false;
+ DR_ON = false;
+ return 0;
+ }
+ if (HV_ON) {
+ HV_ON = false;
+ S_INFO = true;
+ S_DREW = true;
+ }
+ return 0;
+}
+
+/// The wheel over the canvas: zoomAt(x, y, Math.exp(-deltaY * (ctrl ? 0.01 : 0.0025))). 2: a libm call is missing.
+pub fn w_wheel(x: f64, y: f64, dy: f64, ctrl: u32) -> u32 {
+ S_EF_N = 0;
+ MISSING = false;
+ var k : f64 = 0.0025;
+ if (ctrl != 0) { k = 0.01; }
+ const arg : f64 = (0.0 - dy) * k;
+ const f : f64 = libm(16, arg);
+ if (MISSING) { return 2; }
+ zoom_at(x, y, f);
+ return 1;
+}
+
+pub fn w_key(key: []const u8) -> u32 {
+ S_EF_N = 0;
+ const step : f64 = 40.0 / S_VS;
+ if (key.len == 1 and (key[0] == 43 or key[0] == 61)) {
+ zoom_at(S_CSSW / 2.0, S_CSSH / 2.0, 1.4);
+ return 1;
+ }
+ if (key.len == 1 and key[0] == 45) {
+ zoom_at(S_CSSW / 2.0, S_CSSH / 2.0, 1.0 / 1.4);
+ return 1;
+ }
+ if (key.len == 1 and (key[0] == 114 or key[0] == 82)) {
+ rotate_view();
+ return 1;
+ }
+ if (same_text(key, "ArrowLeft")) {
+ S_VX = S_VX - step;
+ S_DREW = true;
+ return 1;
+ }
+ if (same_text(key, "ArrowRight")) {
+ S_VX = S_VX + step;
+ S_DREW = true;
+ return 1;
+ }
+ if (same_text(key, "ArrowUp")) {
+ S_VY = S_VY - step;
+ S_DREW = true;
+ return 1;
+ }
+ if (same_text(key, "ArrowDown")) {
+ S_VY = S_VY + step;
+ S_DREW = true;
+ return 1;
+ }
+ return 0;
+}
+
+/// The ResizeObserver of the box around the canvas: 1 when the drawing changed.
+pub fn w_box(w: f64, h: f64) -> u32 {
+ S_EF_N = 0;
+ if (!S_GRID or w == S_LASTW) { return 0; }
+ const ow : f64 = S_CSSW;
+ const oh : f64 = S_CSSH;
+ const ovx : f64 = S_VX;
+ const ovy : f64 = S_VY;
+ const ovs : f64 = S_VS;
+ S_LASTW = w;
+ size_canvas();
+ if (ow != 0.0 and ow == ow) {
+ const cx : f64 = ovx + ow / ovs / 2.0;
+ const cy : f64 = ovy + oh / ovs / 2.0;
+ const s : f64 = ovs * S_CSSW / ow;
+ S_VS = s;
+ S_VX = cx - S_CSSW / s / 2.0;
+ S_VY = cy - S_CSSH / s / 2.0;
+ }
+ S_DREW = true;
+ return 1;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ S_EF_N = 0;
+ const ns : u32 = SAY_SAMPLES.len as u32;
+ if (i < ns) {
+ show_sample(i);
+ return 0;
+ }
+ if (i == ns) { fit(false); }
+ if (i == ns + 1) { fit(true); }
+ if (i == ns + 2) { rotate_view(); }
+ if (i == ns + 3 and S_HAVE) {
+ S_SAVE_N = save_name(W_PNG_SUFFIX);
+ push_effect(80);
+ }
+ if (i == ns + 4 and S_HAVE) {
+ S_SAVE_N = save_name(W_STL_SUFFIX);
+ push_effect(68);
+ }
+ return 0;
+}
+
+fn push_effect(k: u8) -> u32 {
+ if (S_EF_N < 4) {
+ S_EF[S_EF_N as usize] = k;
+ S_EF_N = S_EF_N + 1;
+ }
+ return 0;
+}
+
+/// stem() + suffix into S_SAVE: String(label || ID).replace(/\.[^.]*$/, '').replace(/[^A-Za-z0-9_-]+/g, '-')
+fn save_name(suffix: []const u8) -> u32 {
+ var src : bool = true;
+ if (S_LABEL_N == 0) { src = false; }
+ var n : u32 = 0;
+ var run : bool = false;
+ var k : u32 = 0;
+ var end : u32 = 0;
+ // the label (or the id) without its last extension
+ var buf : [4096]u8 = [0; 4096];
+ var bl : u32 = 0;
+ if (src) { bl = copy_into(buf[0..], 0, S_LABEL[0..(S_LABEL_N as usize)]); } else { bl = copy_into(buf[0..], 0, ID); }
+ end = bl;
+ var dot : i32 = 0 - 1;
+ k = 0;
+ while (k < bl) {
+ if (buf[k as usize] == 46) { dot = k as i32; }
+ k = k + 1;
+ }
+ if (dot >= 0) {
+ // \.[^.]*$ : from the last dot to the end, when no dot follows it
+ end = dot as u32;
+ }
+ k = 0;
+ while (k < end) {
+ const b : u8 = buf[k as usize];
+ var okc : bool = false;
+ if (b >= 65 and b <= 90) { okc = true; }
+ if (b >= 97 and b <= 122) { okc = true; }
+ if (b >= 48 and b <= 57) { okc = true; }
+ if (b == 95 or b == 45) { okc = true; }
+ if (okc) {
+ if (n < 3000) { S_SAVE[n as usize] = b; n = n + 1; }
+ run = false;
+ } else {
+ if (!run and n < 3000) { S_SAVE[n as usize] = 45; n = n + 1; }
+ run = true;
+ }
+ k = k + 1;
+ }
+ n = copy_into(S_SAVE[0..], n, suffix);
+ return n;
+}
+
+pub fn w_drag(kind_of: u32) -> u32 {
+ S_EF_N = 0;
+ const was : bool = S_OVER;
+ S_OVER = kind_of == 0;
+ if (was != S_OVER) { return 1; }
+ return 0;
+}
+
+/// A file picked or dropped: takeFile(file). text is the file as the host decoded it.
+pub fn w_file(name: []const u8, size: f64, data: []const u8, st: u32) -> u32 {
+ S_EF_N = 0;
+ S_CLR_FILE = true;
+ S_FNAME_N = copy_into(S_FNAME[0..], 0, name);
+ if (!S_GRID) { return 0; }
+ arg_text(0, name);
+ if (size > (K_MAX_FILE_MB as f64) * 1048576.0) {
+ arg_num(1, K_MAX_FILE_MB as f64);
+ status_fmt(SAY_ERR_SIZE, 2);
+ return 0;
+ }
+ status_fmt(SAY_READING, 1);
+ if (st != 0 or size > (W_FILE_CAP as f64)) {
+ status_fmt(SAY_ERR_READ, 1);
+ return 0;
+ }
+ tiles_reset();
+ if (parse_fasm(data) != 0) {
+ status_fmt(SAY_ERR_READ, 1);
+ return 0;
+ }
+ if (V[V_FEAT] == 0) {
+ status_fmt(SAY_ERR_EMPTY, 1);
+ return 0;
+ }
+ S_PRESSED = 0 - 1;
+ // the chip line: the first header comment that starts with "chipdb "
+ var chip : u32 = 0;
+ var chipo : u32 = 0;
+ var found : bool = false;
+ var h : u32 = 0;
+ while (h < (V[V_NH] as u32) and !found) {
+ const ho : u32 = HDRO[h as usize];
+ const hl : u32 = HDRL[h as usize];
+ if (pool_is_prefix(ho, hl, "chipdb ")) {
+ found = true;
+ chipo = ho;
+ chip = hl;
+ }
+ h = h + 1;
+ }
+ arg_text(0, name);
+ arg_loc(1, size);
+ if (found) { arg_set(2, POOL[0..], chipo as usize, (chipo + chip) as usize); } else { arg_set(2, POOL[0..], 0, 0); }
+ const sl : i64 = fmt_into(S_SRC[0..], SAY_SOURCE_FILE, 3);
+ S_SRC_N = 0;
+ if (sl >= 0) { S_SRC_N = sl as u32; }
+ S_NOTE_N = 0;
+ S_CMDN = 0;
+ S_LABEL_N = copy_into(S_LABEL[0..], 0, name);
+ // the lines that are not features
+ S_BADN = V[V_NB] as u32;
+ var b : u32 = 0;
+ var bp : u32 = 0;
+ while (b < S_BADN and b < 100) {
+ S_BADO[b as usize] = bp;
+ var l : u32 = BADL[b as usize];
+ if (l > 2000) { l = 2000; }
+ var q : u32 = 0;
+ while (q < l) {
+ S_BADTXT[(bp + q) as usize] = POOL[(BADO[b as usize] + q) as usize];
+ q = q + 1;
+ }
+ S_BADL[b as usize] = l;
+ bp = bp + l;
+ b = b + 1;
+ }
+ return load_design();
+}
+
+fn pool_is_prefix(o: u32, l: u32, w: []const u8) -> bool {
+ if ((l as usize) < w.len) { return false; }
+ var k : usize = 0;
+ while (k < w.len) {
+ if (POOL[(o as usize) + k] != w[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn w_value(i: u32, dst: []u8) -> u32 {
+ if (i == 0 and S_CLR_FILE) {
+ S_CLR_FILE = false;
+ return 0;
+ }
+ return 4294967295;
+}
+
+// --- the libm service ---------------------------------------------------------------------------------------------
+
+fn libm(op: u32, a: f64) -> f64 {
+ var i : u32 = 0;
+ while (i < LB_N) {
+ if ((LB_OP[i as usize] as u32) == op and LB_A[i as usize] == a) { return LB_R[i as usize]; }
+ i = i + 1;
+ }
+ MISSING = true;
+ var j : u32 = 0;
+ while (j < RQ_N) {
+ if ((RQ_OP[j as usize] as u32) == op and RQ_A[j as usize] == a) { return 0.0; }
+ j = j + 1;
+ }
+ if (RQ_N < 64) {
+ RQ_OP[RQ_N as usize] = op as u8;
+ RQ_A[RQ_N as usize] = a;
+ RQ_N = RQ_N + 1;
+ }
+ return 0.0;
+}
+
+pub fn w_libm_need(dst: []f64) -> u32 {
+ if (RQ_N == 0) { return 0; }
+ var k : u32 = 0;
+ while (k < RQ_N and ((k * 3 + 2) as usize) < dst.len) {
+ dst[(k * 3) as usize] = RQ_OP[k as usize] as f64;
+ dst[(k * 3 + 1) as usize] = RQ_A[k as usize];
+ dst[(k * 3 + 2) as usize] = 0.0;
+ k = k + 1;
+ }
+ RQ_SENT = k;
+ return k;
+}
+
+pub fn w_libm_give(src: []const f64, n: u32) -> u32 {
+ var k : u32 = 0;
+ while (k < n and k < RQ_SENT and (k as usize) < src.len) {
+ if (LB_N >= 64) { LB_N = 0; }
+ LB_OP[LB_N as usize] = RQ_OP[k as usize];
+ LB_A[LB_N as usize] = RQ_A[k as usize];
+ LB_R[LB_N as usize] = src[k as usize];
+ LB_N = LB_N + 1;
+ k = k + 1;
+ }
+ RQ_N = 0;
+ RQ_SENT = 0;
+ MISSING = false;
+ return 0;
+}
+
+// --- the page --------------------------------------------------------------------------------------------------
+
+fn put_locale(dst: []u8, n0: u32, x: f64) -> u32 {
+ return js_locale_num(dst, n0, x);
+}
+
+fn put_col_name(dst: []u8, n0: u32, ci: u32) -> u32 {
+ return text(dst, n0, R_NPOOL[(R_CNO[ci as usize] as usize)..((R_CNO[ci as usize] + R_CNL[ci as usize]) as usize)]);
+}
+
+/// "background:" and the colour of family f, as tool.js writes the style attribute (undefined for no family)
+fn put_swatch(dst: []u8, n0: u32, fam: i32) -> u32 {
+ var n : u32 = raw(dst, n0, "= 0 and (fam as usize) < K_FAMILY_COLORS.len) { n = text(dst, n, K_FAMILY_COLORS[fam as usize]); } else { n = raw(dst, n, "undefined"); }
+ return raw(dst, n, "\">");
+}
+
+fn put_info(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ var on : bool = PK_ON;
+ var ci : u32 = PK_CI;
+ if (!PK_ON) {
+ on = HV_ON;
+ ci = HV_CI;
+ }
+ if (!on) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PICK_NONE);
+ return raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ var i : usize = 0;
+ while (i < SAY_PICK_FIELDS.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PICK_FIELDS[i]);
+ n = raw(dst, n, "
");
+ if (i == 0) {
+ n = put_swatch(dst, n, R_CFAM[ci as usize]);
+ n = put_col_name(dst, n, ci);
+ }
+ if (i == 1) {
+ n = text(dst, n, R_NPOOL[(R_CNO[ci as usize] as usize)..((R_CNO[ci as usize] + R_CTL[ci as usize]) as usize)]);
+ }
+ if (i == 2) {
+ const f : i32 = R_CFAM[ci as usize];
+ if (f >= 0 and (f as usize) < SAY_FAMILY_NAMES.len) {
+ n = text(dst, n, SAY_FAMILY_NAMES[f as usize]);
+ } else {
+ if (f >= 0 and (f as usize) < K_FAMILIES.len) { n = text(dst, n, K_FAMILIES[f as usize]); }
+ }
+ }
+ if (i == 3) { n = put_locale(dst, n, R_CN[ci as usize] as f64); }
+ if (i == 4) {
+ n = num(dst, n, R_CGX[ci as usize] as u64);
+ n = raw(dst, n, ", ");
+ n = num(dst, n, R_CGY[ci as usize] as u64);
+ }
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+fn put_stats(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAT_LABELS[0]);
+ n = raw(dst, n, "
");
+ n = put_locale(dst, n, R_NCOL as f64);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAT_LABELS[1]);
+ n = raw(dst, n, "
");
+ n = put_locale(dst, n, R_PLACED as f64);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAT_LABELS[2]);
+ n = raw(dst, n, "
");
+ if (R_TALL >= 0) {
+ n = put_col_name(dst, n, R_TALL as u32);
+ arg_loc(0, tallest_n());
+ n = raw(dst, n, "");
+ n = put_say(dst, n, SAY_STAT_TALLEST, 1);
+ n = raw(dst, n, "");
+ } else {
+ n = raw(dst, n, "-");
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STAT_LABELS[3]);
+ n = raw(dst, n, "
");
+ n = pct_into(dst, n, R_NCOL as f64, K_CONFIG_TILES as f64);
+ n = raw(dst, n, "%");
+ arg_loc(0, R_NCOL as f64);
+ arg_loc(1, K_CONFIG_TILES as f64);
+ n = put_say(dst, n, SAY_STAT_SHARE, 2);
+ return raw(dst, n, "
");
+}
+
+fn put_fams(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "
");
+ var h : usize = 0;
+ while (h < SAY_FAMILY_HEAD.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FAMILY_HEAD[h]);
+ n = raw(dst, n, "
");
+ h = h + 1;
+ }
+ n = raw(dst, n, "
");
+ var i : usize = 0;
+ while (i < K_FAMILIES.len) {
+ n = raw(dst, n, "
");
+ n = put_swatch(dst, n, i as i32);
+ if (i < SAY_FAMILY_NAMES.len) { n = text(dst, n, SAY_FAMILY_NAMES[i]); } else { n = text(dst, n, K_FAMILIES[i]); }
+ n = raw(dst, n, "
");
+ arg_loc(0, R_FAMT[i] as f64);
+ var cfg : f64 = 0.0;
+ if (i < K_CONFIG_FAMILY_TILES.len) { cfg = K_CONFIG_FAMILY_TILES[i] as f64; }
+ arg_loc(1, cfg);
+ n = put_say(dst, n, SAY_OF, 2);
+ n = raw(dst, n, "
");
+ n = put_locale(dst, n, R_FAMF[i] as f64);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "");
+}
+
+fn put_source(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = text(dst, n, S_SRC[0..(S_SRC_N as usize)]);
+ n = raw(dst, n, "");
+ if (S_NOTE_N > 0) {
+ n = raw(dst, n, "");
+ n = text(dst, n, S_NOTE[0..(S_NOTE_N as usize)]);
+ n = raw(dst, n, "");
+ }
+ if (S_CLEARED != 0) {
+ n = raw(dst, n, "");
+ arg_loc(0, S_CLEARED as f64);
+ n = put_say(dst, n, SAY_CLEARED, 1);
+ n = raw(dst, n, "");
+ }
+ n = raw(dst, n, "");
+ arg_loc(0, tallest_n());
+ arg_num(1, G_W as f64);
+ arg_num(2, G_H as f64);
+ n = put_say(dst, n, SAY_SCALE, 3);
+ return raw(dst, n, "");
+}
+
+fn put_details(dst: []u8, n0: u32) -> u32 {
+ var n : u32 = n0;
+ // off the map
+ n = raw(dst, n, "");
+ } else {
+ if (R_NOFF == 0) { n = raw(dst, n, " hidden=\"\""); }
+ n = raw(dst, n, ">");
+ arg_loc(0, R_NOFF as f64);
+ arg_loc(1, (R_FEAT as f64) - (R_PLACED as f64));
+ n = put_say(dst, n, SAY_OFF_MAP, 2);
+ n = raw(dst, n, "
");
+ var j : u32 = 0;
+ while (j < R_NOFF and j < 300) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, R_NPOOL[(R_OFFNO[j as usize] as usize)..((R_OFFNO[j as usize] + R_OFFNL[j as usize]) as usize)]);
+ n = raw(dst, n, " ");
+ n = num(dst, n, R_OFFN[j as usize] as u64);
+ n = raw(dst, n, "
");
+ j = j + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ // lines that are not features
+ n = raw(dst, n, "");
+ } else {
+ if (S_BADN == 0) { n = raw(dst, n, " hidden=\"\""); }
+ n = raw(dst, n, ">");
+ arg_loc(0, S_BADN as f64);
+ n = put_say(dst, n, SAY_BAD_LINES, 1);
+ n = raw(dst, n, "
");
+ var b : u32 = 0;
+ while (b < S_BADN and b < 100) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, S_BADTXT[(S_BADO[b as usize] as usize)..((S_BADO[b as usize] + S_BADL[b as usize]) as usize)]);
+ n = raw(dst, n, "
");
+ b = b + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ // how the sample was made
+ n = raw(dst, n, "");
+ } else {
+ if (S_CMDN == 0) { n = raw(dst, n, " hidden=\"\""); }
+ n = raw(dst, n, ">");
+ n = text(dst, n, SAY_HOW);
+ n = raw(dst, n, "
");
+ var c : u32 = 0;
+ while (c < S_CMDN) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, S_CMD[(S_CMDO[c as usize] as usize)..((S_CMDO[c as usize] + S_CMDL[c as usize]) as usize)]);
+ n = raw(dst, n, "
");
+ var i : usize = 0;
+ while (i < SAY_SAMPLES.len) {
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_SAMPLES[i]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ n = button(dst, n, SAY_WHOLE_DIE);
+ n = button(dst, n, SAY_FIT_DESIGN);
+ n = button(dst, n, SAY_ROTATE);
+ n = button(dst, n, SAY_DOWNLOAD_PNG);
+ n = button(dst, n, SAY_DOWNLOAD_STL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, S_STATUS[0..(S_STATUS_N as usize)]);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_HINT);
+ n = raw(dst, n, "
");
+ if (S_HAVE) { n = put_stats(dst, n); }
+ n = raw(dst, n, "
");
+ if (S_HAVE) { n = put_fams(dst, n); }
+ n = raw(dst, n, "
");
+ if (S_HAVE or S_INFO) { n = put_info(dst, n); }
+ n = raw(dst, n, "
");
+ if (S_HAVE) { n = put_source(dst, n); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SHARE_NOTE);
+ n = raw(dst, n, "
");
+ n = put_details(dst, n);
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+fn button(dst: []u8, n0: u32, label: []const u8) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ n = text(dst, n, label);
+ return raw(dst, n, "");
+}
+
+/// A CSS length in px as the style serialisation writes it: 273px, 12.5px.
+fn put_css_px(dst: []u8, n0: u32, x: f64) -> u32 {
+ const e : u32 = js_str_num(S_NUM3[0..], 0, x);
+ var n : u32 = raw(dst, n0, S_NUM3[0..(e as usize)]);
+ return raw(dst, n, "px");
+}
+
+// --- the binary draw list --------------------------------------------------------------------------------------------
+
+fn bw_b(o: []u8, n: u32, b: u8) -> u32 {
+ if (n == OVER or (n as usize) >= o.len) { return OVER; }
+ o[n as usize] = b;
+ return n + 1;
+}
+
+/// The IEEE 754 bits of a finite double (no subnormals, no NaN).
+fn f64_bits(x: f64) -> u64 {
+ if (x == 0.0) {
+ if ((1.0 / x) < 0.0) { return 9223372036854775808; }
+ return 0;
+ }
+ var sign : u64 = 0;
+ var ax : f64 = x;
+ if (x < 0.0) {
+ sign = 9223372036854775808;
+ ax = 0.0 - x;
+ }
+ if (x != x) { return 9221120237041090560; }
+ if (ax - ax != 0.0) { return sign + 9218868437227405312; }
+ const m : u64 = jn_mant(ax);
+ const e : i32 = jn_expo(ax);
+ const be : u64 = ((e + 52 + 1023) as u64);
+ return sign + be * 4503599627370496 + (m - 4503599627370496);
+}
+
+fn bw_f(o: []u8, n0: u32, x: f64) -> u32 {
+ var bits : u64 = f64_bits(x);
+ var n : u32 = n0;
+ var k : u32 = 0;
+ while (k < 8) {
+ n = bw_b(o, n, (bits % 256) as u8);
+ bits = bits / 256;
+ k = k + 1;
+ }
+ return n;
+}
+
+fn bw_s(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = bw_b(o, n0, (s.len % 256) as u8);
+ n = bw_b(o, n, ((s.len / 256) % 256) as u8);
+ var k : usize = 0;
+ while (k < s.len) {
+ n = bw_b(o, n, s[k]);
+ k = k + 1;
+ }
+ return n;
+}
+
+fn bw_move(o: []u8, n0: u32, x: f64, y: f64) -> u32 {
+ var n : u32 = bw_b(o, n0, 7);
+ n = bw_f(o, n, x);
+ return bw_f(o, n, y);
+}
+
+fn bw_line(o: []u8, n0: u32, x: f64, y: f64) -> u32 {
+ var n : u32 = bw_b(o, n0, 8);
+ n = bw_f(o, n, x);
+ return bw_f(o, n, y);
+}
+
+fn bw_rect(o: []u8, n0: u32, op: u8, a: f64, b: f64, c: f64, d: f64) -> u32 {
+ var n : u32 = bw_b(o, n0, op);
+ n = bw_f(o, n, a);
+ n = bw_f(o, n, b);
+ n = bw_f(o, n, c);
+ return bw_f(o, n, d);
+}
+
+fn bw_set_s(o: []u8, n0: u32, op: u8, s: []const u8) -> u32 {
+ const n : u32 = bw_b(o, n0, op);
+ return bw_s(o, n, s);
+}
+
+/// parseInt(hex.slice(1), 16): the leading hex digits after the first character.
+fn hex_value(s: []const u8) -> u64 {
+ var v : u64 = 0;
+ var i : usize = 1;
+ var go : bool = true;
+ while (i < s.len and go) {
+ const b : u8 = s[i];
+ var d : i32 = 0 - 1;
+ if (b >= 48 and b <= 57) { d = (b - 48) as i32; }
+ if (b >= 97 and b <= 102) { d = (b - 87) as i32; }
+ if (b >= 65 and b <= 70) { d = (b - 55) as i32; }
+ if (d < 0) {
+ go = false;
+ } else {
+ if (v < 1000000000000) { v = v * 16 + (d as u64); }
+ i = i + 1;
+ }
+ }
+ return v;
+}
+
+pub var S_SH : [64]u8 = [0; 64];
+
+/// shade(hex, f) into S_SH: rgb(r,g,b); the length.
+fn shade_into(hex: []const u8, f: f64) -> u32 {
+ const nv : u64 = hex_value(hex);
+ // n >> 16 & 255 etc. on a 32 bit integer
+ const n32 : u64 = nv % 4294967296;
+ var n : u32 = copy_into(S_SH[0..], 0, "rgb(");
+ var c : u32 = 0;
+ while (c < 3) {
+ var sh : u64 = 1;
+ if (c == 0) { sh = 65536; }
+ if (c == 1) { sh = 256; }
+ const comp : u64 = (n32 / sh) % 256;
+ const v : f64 = js_round((comp as f64) * f);
+ n = js_str_num(S_SH[0..], n, v);
+ if (c < 2) { n = copy_into(S_SH[0..], n, ","); }
+ c = c + 1;
+ }
+ return copy_into(S_SH[0..], n, ")");
+}
+
+/// poly(ctx, pts, X, Y): beginPath, moveTo, lineTo, closePath for points given by (u, v, z) triples of columns.
+pub var D_VX : f64 = 0.0;
+pub var D_VY : f64 = 0.0;
+pub var D_VS : f64 = 1.0;
+
+fn dx(x: f64) -> f64 {
+ return (x - D_VX) * D_VS;
+}
+
+fn dy(y: f64) -> f64 {
+ return (y - D_VY) * D_VS;
+}
+
+/// One point (x, y) of the pts into the path: first = moveTo, else lineTo.
+fn path_pt(o: []u8, n: u32, first: bool, x: f64, y: f64) -> u32 {
+ if (first) { return bw_move(o, n, dx(x), dy(y)); }
+ return bw_line(o, n, dx(x), dy(y));
+}
+
+/// The hull of an old pick or hover, a polygon of six points in h[0..12].
+fn poly_hull(o: []u8, n0: u32, h: []const f64) -> u32 {
+ var n : u32 = bw_b(o, n0, 1);
+ var i : u32 = 0;
+ while (i < 6) {
+ n = path_pt(o, n, i == 0, h[(i * 2) as usize], h[(i * 2 + 1) as usize]);
+ i = i + 1;
+ }
+ return bw_b(o, n, 2);
+}
+
+fn col_hex(fam: i32) -> []const u8 {
+ if (fam >= 0 and (fam as usize) < K_FAMILY_COLORS.len) { return K_FAMILY_COLORS[fam as usize]; }
+ return W_COL_DEFAULT;
+}
+
+/// render(ctx, w, hgt, view): the die floor and the columns, far to near.
+fn render_ops(o: []u8, n0: u32, w: f64, hgt: f64, vx: f64, vy: f64, vs: f64) -> u32 {
+ D_VX = vx;
+ D_VY = vy;
+ D_VS = vs;
+ var n : u32 = bw_set_s(o, n0, 32, W_PNG_BLACK);
+ n = bw_rect(o, n, 10, 0.0, 0.0, w, hgt);
+ if (!S_GRID) { return n; }
+ var r : u32 = 0;
+ while (r < RUN_N) {
+ const f : i32 = RUN_F[r as usize];
+ const gx : f64 = RUN_GX[r as usize] as f64;
+ const top : f64 = RUN_TOP[r as usize] as f64;
+ const bot : f64 = RUN_BOT[r as usize] as f64;
+ if (f >= 0 and (f as usize) < K_FLOOR_COLORS.len) { n = bw_set_s(o, n, 32, K_FLOOR_COLORS[f as usize]); } else { n = bw_set_s(o, n, 32, W_FLOOR_DEFAULT); }
+ n = bw_b(o, n, 1);
+ rot_pt(gx, top);
+ n = path_pt(o, n, true, px_of(G_PX, G_PY), py_of(G_PX, G_PY, 0.0));
+ rot_pt(gx + 1.0, top);
+ n = path_pt(o, n, false, px_of(G_PX, G_PY), py_of(G_PX, G_PY, 0.0));
+ rot_pt(gx + 1.0, bot + 1.0);
+ n = path_pt(o, n, false, px_of(G_PX, G_PY), py_of(G_PX, G_PY, 0.0));
+ rot_pt(gx, bot + 1.0);
+ n = path_pt(o, n, false, px_of(G_PX, G_PY), py_of(G_PX, G_PY, 0.0));
+ n = bw_b(o, n, 2);
+ n = bw_b(o, n, 3);
+ r = r + 1;
+ }
+ const edge : bool = vs > 6.0;
+ var i : u32 = 0;
+ var hull : [12]f64 = [0.0; 12];
+ while (i < R_NCOL) {
+ const ci : u32 = L_CI[i as usize];
+ cell_uv(R_CGX[ci as usize] as f64, R_CGY[ci as usize] as f64);
+ const u : f64 = G_U;
+ const v : f64 = G_V;
+ const z : f64 = (R_CN[ci as usize] as f64) * S_HK;
+ const hex : []const u8 = col_hex(R_CFAM[ci as usize]);
+ // left face
+ var sl : u32 = shade_into(hex, 0.55);
+ n = bw_set_s(o, n, 32, S_SH[0..(sl as usize)]);
+ n = bw_b(o, n, 1);
+ n = path_pt(o, n, true, px_of(u, v + 1.0), py_of(u, v + 1.0, z));
+ n = path_pt(o, n, false, px_of(u + 1.0, v + 1.0), py_of(u + 1.0, v + 1.0, z));
+ n = path_pt(o, n, false, px_of(u + 1.0, v + 1.0), py_of(u + 1.0, v + 1.0, 0.0));
+ n = path_pt(o, n, false, px_of(u, v + 1.0), py_of(u, v + 1.0, 0.0));
+ n = bw_b(o, n, 2);
+ n = bw_b(o, n, 3);
+ // right face
+ sl = shade_into(hex, 0.38);
+ n = bw_set_s(o, n, 32, S_SH[0..(sl as usize)]);
+ n = bw_b(o, n, 1);
+ n = path_pt(o, n, true, px_of(u + 1.0, v), py_of(u + 1.0, v, z));
+ n = path_pt(o, n, false, px_of(u + 1.0, v + 1.0), py_of(u + 1.0, v + 1.0, z));
+ n = path_pt(o, n, false, px_of(u + 1.0, v + 1.0), py_of(u + 1.0, v + 1.0, 0.0));
+ n = path_pt(o, n, false, px_of(u + 1.0, v), py_of(u + 1.0, v, 0.0));
+ n = bw_b(o, n, 2);
+ n = bw_b(o, n, 3);
+ // top
+ n = bw_set_s(o, n, 32, hex);
+ n = bw_b(o, n, 1);
+ n = path_pt(o, n, true, px_of(u, v), py_of(u, v, z));
+ n = path_pt(o, n, false, px_of(u + 1.0, v), py_of(u + 1.0, v, z));
+ n = path_pt(o, n, false, px_of(u + 1.0, v + 1.0), py_of(u + 1.0, v + 1.0, z));
+ n = path_pt(o, n, false, px_of(u, v + 1.0), py_of(u, v + 1.0, z));
+ n = bw_b(o, n, 2);
+ n = bw_b(o, n, 3);
+ if (edge) {
+ n = bw_set_s(o, n, 33, W_EDGE_STROKE);
+ n = bw_b(o, n, 34);
+ n = bw_f(o, n, 1.0);
+ hull_of(ci, hull[0..]);
+ n = poly_hull(o, n, hull[0..]);
+ n = bw_b(o, n, 4);
+ }
+ i = i + 1;
+ }
+ if (HV_ON) {
+ n = bw_set_s(o, n, 33, W_HOVER_COLOR);
+ n = bw_b(o, n, 34);
+ n = bw_f(o, n, 2.0);
+ n = poly_hull(o, n, HV_HULL[0..]);
+ n = bw_b(o, n, 4);
+ }
+ if (PK_ON) {
+ n = bw_set_s(o, n, 33, W_PICK_COLOR);
+ n = bw_b(o, n, 34);
+ n = bw_f(o, n, 2.0);
+ n = poly_hull(o, n, PK_HULL[0..]);
+ n = bw_b(o, n, 4);
+ }
+ return n;
+}
+
+pub const W_EDGE_STROKE : str = "rgba(0,0,0,0.5)";
+
+/// draw(): the drawing on the page's canvas.
+pub fn w_bdraw(o: []u8) -> u32 {
+ if (!S_DREW) { return 0; }
+ var n : u32 = bw_b(o, 0, 200);
+ n = bw_b(o, n, 0);
+ var dpr : f64 = ENV_DPR;
+ if (dpr == 0.0 or dpr != dpr) { dpr = 1.0; }
+ n = bw_b(o, n, 12);
+ n = bw_f(o, n, dpr);
+ n = bw_f(o, n, 0.0);
+ n = bw_f(o, n, 0.0);
+ n = bw_f(o, n, dpr);
+ n = bw_f(o, n, 0.0);
+ n = bw_f(o, n, 0.0);
+ n = render_ops(o, n, S_CSSW, S_CSSH, S_VX, S_VY, S_VS);
+ if (n == OVER) { return 0; }
+ return n;
+}
+
+// --- the text width service, keyed by the whole font string -------------------------------------------------------------------
+
+fn x_same(i: u32, font: []const u8, s: []const u8) -> bool {
+ if ((X_FL[i as usize] as usize) != font.len or (X_TL[i as usize] as usize) != s.len) { return false; }
+ var k : usize = 0;
+ const fo : usize = X_FO[i as usize] as usize;
+ while (k < font.len) {
+ if (X_POOL[fo + k] != font[k]) { return false; }
+ k = k + 1;
+ }
+ k = 0;
+ const to : usize = X_TO[i as usize] as usize;
+ while (k < s.len) {
+ if (X_POOL[to + k] != s[k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn x_find(font: []const u8, s: []const u8) -> i32 {
+ var i : u32 = 0;
+ while (i < X_N) {
+ if (x_same(i, font, s)) { return i as i32; }
+ i = i + 1;
+ }
+ return 0 - 1;
+}
+
+fn x_ask(font: []const u8, s: []const u8) -> u32 {
+ if (x_find(font, s) >= 0) { return 0; }
+ if (X_N >= 4096 or (X_PN as usize) + s.len + font.len > 524288) {
+ X_N = 0;
+ X_PN = 0;
+ }
+ if (s.len > 8192 or font.len > 256) { return 0; }
+ const i : usize = X_N as usize;
+ X_FO[i] = X_PN;
+ X_FL[i] = font.len as u32;
+ var k : usize = 0;
+ while (k < font.len) {
+ X_POOL[(X_PN as usize) + k] = font[k];
+ k = k + 1;
+ }
+ X_PN = X_PN + (font.len as u32);
+ X_TO[i] = X_PN;
+ X_TL[i] = s.len as u32;
+ k = 0;
+ while (k < s.len) {
+ X_POOL[(X_PN as usize) + k] = s[k];
+ k = k + 1;
+ }
+ X_PN = X_PN + (s.len as u32);
+ X_ST[i] = 0;
+ X_W[i] = 0.0;
+ X_N = X_N + 1;
+ return 1;
+}
+
+fn x_width(font: []const u8, s: []const u8) -> f64 {
+ const f : i32 = x_find(font, s);
+ if (f >= 0 and X_ST[f as usize] == 2) {
+ X_MISS = false;
+ return X_W[f as usize];
+ }
+ X_MISS = true;
+ x_ask(font, s);
+ return 0.0;
+}
+
+fn put_u32le(dst: []u8, at: u32, v: u32) -> u32 {
+ dst[at as usize] = (v % 256) as u8;
+ dst[(at + 1) as usize] = ((v / 256) % 256) as u8;
+ dst[(at + 2) as usize] = ((v / 65536) % 256) as u8;
+ dst[(at + 3) as usize] = ((v / 16777216) % 256) as u8;
+ return at + 4;
+}
+
+pub fn w_text_need(dst: []u8) -> u32 {
+ var n : u32 = 0;
+ var c : u32 = 0;
+ var i : u32 = 0;
+ while (i < X_N) {
+ if (X_ST[i as usize] == 0) {
+ const need : u32 = 8 + X_FL[i as usize] + X_TL[i as usize];
+ if ((n as usize) + (need as usize) > dst.len) { return c; }
+ n = put_u32le(dst, n, X_FL[i as usize]);
+ n = put_u32le(dst, n, X_TL[i as usize]);
+ var k : u32 = 0;
+ while (k < X_FL[i as usize]) {
+ dst[(n + k) as usize] = X_POOL[(X_FO[i as usize] + k) as usize];
+ k = k + 1;
+ }
+ n = n + X_FL[i as usize];
+ k = 0;
+ while (k < X_TL[i as usize]) {
+ dst[(n + k) as usize] = X_POOL[(X_TO[i as usize] + k) as usize];
+ k = k + 1;
+ }
+ n = n + X_TL[i as usize];
+ X_ST[i as usize] = 1;
+ c = c + 1;
+ }
+ i = i + 1;
+ }
+ return c;
+}
+
+pub fn w_text_give(src: []const f64, n: u32) -> u32 {
+ var i : u32 = 0;
+ var k : u32 = 0;
+ while (i < X_N and k < n and (k as usize) < src.len) {
+ if (X_ST[i as usize] == 1) {
+ X_W[i as usize] = src[k as usize];
+ X_ST[i as usize] = 2;
+ k = k + 1;
+ }
+ i = i + 1;
+ }
+ return 0;
+}
+
+// --- the PNG ----------------------------------------------------------------------------------------------------------------------
+
+pub var P_FONT : [128]u8 = [0; 128];
+pub var P_FONT_N : u32 = 0;
+pub var P_TXT : [262144]u8 = [0; 262144];
+
+/// ctx.font = `${spec} ui-monospace, Menlo, monospace` into P_FONT
+fn png_font(spec: []const u8) -> u32 {
+ var n : u32 = copy_into(P_FONT[0..], 0, spec);
+ n = copy_into(P_FONT[0..], n, " ");
+ n = copy_into(P_FONT[0..], n, W_PNG_FAMILY);
+ P_FONT_N = n;
+ return n;
+}
+
+fn is_word_b(b: u8) -> bool {
+ if (b >= 48 and b <= 57) { return true; }
+ if (b >= 65 and b <= 90) { return true; }
+ if (b >= 97 and b <= 122) { return true; }
+ return b == 95;
+}
+
+/// brand(s) = s.replace(/\bS3AI\b/g, S-superscript-3-AI) into dst
+fn brand_into(dst: []u8, at: u32, s: []const u8) -> u32 {
+ var n : u32 = at;
+ var i : usize = 0;
+ while (i < s.len) {
+ var hit : bool = false;
+ if (i + 4 <= s.len and s[i] == 83 and s[i + 1] == 51 and s[i + 2] == 65 and s[i + 3] == 73) {
+ const before : bool = i == 0 or !is_word_b(s[i - 1]);
+ const after : bool = i + 4 == s.len or !is_word_b(s[i + 4]);
+ if (before and after) { hit = true; }
+ }
+ if (hit) {
+ dst[n as usize] = 83;
+ dst[(n + 1) as usize] = W_SUP3_A;
+ dst[(n + 2) as usize] = W_SUP3_B;
+ dst[(n + 3) as usize] = 65;
+ dst[(n + 4) as usize] = 73;
+ n = n + 5;
+ i = i + 4;
+ } else {
+ dst[n as usize] = s[i];
+ n = n + 1;
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+/// wrapText(ctx, text, maxW) written as fillText records at x = 40, line by line from y; returns the new write position and y in P_Y.
+pub var P_Y : f64 = 0.0;
+
+fn wrap_text(o: []u8, n0: u32, t: []const u8, maxw: f64, size: f64) -> u32 {
+ var n : u32 = n0;
+ var a : usize = 0;
+ var p : usize = 0;
+ while (p < t.len) {
+ // the next word: up to and including the next space
+ var q : usize = p;
+ var done : bool = false;
+ while (q < t.len and !done) {
+ if (t[q] == 32) { done = true; }
+ q = q + 1;
+ }
+ if (p > a) {
+ const w : f64 = x_width(P_FONT[0..(P_FONT_N as usize)], t[a..q]);
+ if (w > maxw) {
+ n = bw_b(o, n, 13);
+ n = bw_s(o, n, t[a..p]);
+ n = bw_f(o, n, 40.0);
+ n = bw_f(o, n, P_Y);
+ P_Y = P_Y + size + 10.0;
+ a = p;
+ }
+ }
+ p = q;
+ }
+ if (a < t.len) {
+ n = bw_b(o, n, 13);
+ n = bw_s(o, n, t[a..t.len]);
+ n = bw_f(o, n, 40.0);
+ n = bw_f(o, n, P_Y);
+ P_Y = P_Y + size + 10.0;
+ }
+ return n;
+}
+
+/// One line of the card: font spec, colour, text (in P_TXT[0..len]).
+fn png_line(o: []u8, n0: u32, spec: []const u8, size: f64, col: []const u8, len: u32, maxw: f64) -> u32 {
+ png_font(spec);
+ var n : u32 = bw_set_s(o, n0, 35, P_FONT[0..(P_FONT_N as usize)]);
+ n = bw_set_s(o, n, 32, col);
+ n = wrap_text(o, n, P_TXT[0..(len as usize)], maxw, size);
+ P_Y = P_Y + 8.0;
+ return n;
+}
+
+/// downloadPng(): the drawing and the card text on a K_PNG_W x K_PNG_H canvas.
+pub fn w_bdraw2(o: []u8) -> u32 {
+ const w : f64 = K_PNG_W as f64;
+ const hgt : f64 = K_PNG_H as f64;
+ const ax : f64 = 380.0;
+ const ay : f64 = 20.0;
+ const aw : f64 = w - 400.0;
+ const ah : f64 = hgt - 40.0;
+ var n : u32 = bw_b(o, 0, 200);
+ n = bw_b(o, n, 0);
+ n = bw_b(o, n, 5);
+ n = bw_b(o, n, 9);
+ n = bw_f(o, n, ax);
+ n = bw_f(o, n, ay);
+ bounds(false);
+ fit_view(aw, ah);
+ n = render_ops(o, n, aw, ah, F_X, F_Y, F_S);
+ n = bw_b(o, n, 6);
+ n = bw_set_s(o, n, 32, W_PNG_BLACK);
+ n = bw_rect(o, n, 10, 0.0, 0.0, ax, hgt);
+ P_Y = 70.0;
+ const maxw : f64 = ax - 60.0;
+ // title
+ var l : u32 = copy_into(P_TXT[0..], 0, SAY_PNG_TITLE);
+ n = png_line(o, n, "700 44px", 44.0, W_PNG_ACCENT, l, maxw);
+ // the label
+ l = copy_into(P_TXT[0..], 0, S_LABEL[0..(S_LABEL_N as usize)]);
+ n = png_line(o, n, "600 22px", 22.0, W_PNG_GOLD, l, maxw);
+ // tiles touched
+ arg_loc(0, R_NCOL as f64);
+ var pn : u32 = pct_into(S_NUM3[0..], 0, R_NCOL as f64, K_CONFIG_TILES as f64);
+ arg_set(1, S_NUM3[0..], 0, pn as usize);
+ var fl : i64 = fmt_into(P_TXT[0..], SAY_PNG_TILES, 2);
+ l = 0;
+ if (fl >= 0) { l = fl as u32; }
+ n = png_line(o, n, "600 22px", 22.0, W_PNG_WHITE, l, maxw);
+ // features
+ arg_loc(0, R_PLACED as f64);
+ fl = fmt_into(P_TXT[0..], SAY_PNG_FEATURES, 1);
+ l = 0;
+ if (fl >= 0) { l = fl as u32; }
+ n = png_line(o, n, "600 22px", 22.0, W_PNG_WHITE, l, maxw);
+ // tallest
+ l = 0;
+ if (R_TALL >= 0) {
+ arg_set(0, R_NPOOL[0..], R_CNO[R_TALL as usize] as usize, (R_CNO[R_TALL as usize] + R_CNL[R_TALL as usize]) as usize);
+ arg_loc(1, tallest_n());
+ fl = fmt_into(P_TXT[0..], SAY_PNG_TALLEST, 2);
+ if (fl >= 0) { l = fl as u32; }
+ }
+ n = png_line(o, n, "400 18px", 18.0, W_PNG_MUTED, l, maxw);
+ // the families
+ png_font("600 18px");
+ n = bw_set_s(o, n, 35, P_FONT[0..(P_FONT_N as usize)]);
+ var i : usize = 0;
+ while (i < K_FAMILIES.len) {
+ if (R_FAMT[i] != 0) {
+ n = bw_set_s(o, n, 32, K_FAMILY_COLORS[i]);
+ n = bw_rect(o, n, 10, 40.0, P_Y - 13.0, 14.0, 14.0);
+ n = bw_set_s(o, n, 32, W_PNG_MUTED);
+ var t : u32 = copy_into(P_TXT[0..], 0, K_FAMILIES[i]);
+ t = copy_into(P_TXT[0..], t, " ");
+ const e : u32 = js_locale_num(P_TXT[0..], t, R_FAMT[i] as f64);
+ n = bw_b(o, n, 13);
+ n = bw_s(o, n, P_TXT[0..(e as usize)]);
+ n = bw_f(o, n, 62.0);
+ n = bw_f(o, n, P_Y);
+ P_Y = P_Y + 26.0;
+ }
+ i = i + 1;
+ }
+ // brand and foot
+ n = bw_set_s(o, n, 32, W_PNG_ACCENT);
+ png_font("700 18px");
+ n = bw_set_s(o, n, 35, P_FONT[0..(P_FONT_N as usize)]);
+ const bl : u32 = brand_into(P_TXT[0..], 0, SAY_PNG_BRAND);
+ n = bw_b(o, n, 13);
+ n = bw_s(o, n, P_TXT[0..(bl as usize)]);
+ n = bw_f(o, n, 40.0);
+ n = bw_f(o, n, hgt - 56.0);
+ n = bw_set_s(o, n, 32, W_PNG_FOOT_COLOR);
+ png_font("400 15px");
+ n = bw_set_s(o, n, 35, P_FONT[0..(P_FONT_N as usize)]);
+ n = bw_b(o, n, 13);
+ n = bw_s(o, n, SAY_PNG_FOOT);
+ n = bw_f(o, n, 40.0);
+ n = bw_f(o, n, hgt - 30.0);
+ if (n == OVER) { return 0; }
+ return n;
+}
+
+// --- the STL ----------------------------------------------------------------------------------------------------------------------------------
+
+/// The bits of a double rounded to a 32 bit float (round to nearest, ties to even).
+fn f32_bits(x: f64) -> u32 {
+ if (x == 0.0) {
+ if ((1.0 / x) < 0.0) { return 2147483648; }
+ return 0;
+ }
+ var sign : u32 = 0;
+ var ax : f64 = x;
+ if (x < 0.0) {
+ sign = 2147483648;
+ ax = 0.0 - x;
+ }
+ if (x != x) { return 2143289344; }
+ if (ax - ax != 0.0) { return sign + 2139095040; }
+ const m : u64 = jn_mant(ax);
+ const e : i32 = jn_expo(ax) + 52;
+ if (e > 127) { return sign + 2139095040; }
+ if (e < 0 - 126) {
+ // a subnormal float: units of 2^-149
+ var sh : i32 = (0 - 126) - e + 29;
+ if (sh > 60) { return sign; }
+ var q : u64 = m;
+ var low : u64 = 0;
+ var half : u64 = 1;
+ var k : i32 = 0;
+ while (k < sh) {
+ half = half * 2;
+ k = k + 1;
+ }
+ low = q % half;
+ q = q / half;
+ const h2 : u64 = half / 2;
+ if (low > h2 or (low == h2 and q % 2 == 1)) { q = q + 1; }
+ return sign + (q as u32);
+ }
+ var hi : u64 = m / 536870912;
+ const low2 : u64 = m % 536870912;
+ var ee : i32 = e;
+ if (low2 > 268435456 or (low2 == 268435456 and hi % 2 == 1)) { hi = hi + 1; }
+ if (hi == 16777216) {
+ hi = 8388608;
+ ee = ee + 1;
+ }
+ if (ee > 127) { return sign + 2139095040; }
+ return sign + (((ee + 127) as u32) * 8388608) + ((hi - 8388608) as u32);
+}
+
+fn put_f32(o: []u8, at: u32, x: f64) -> u32 {
+ var b : u32 = f32_bits(x);
+ var n : u32 = at;
+ var k : u32 = 0;
+ while (k < 4) {
+ if ((n as usize) < o.len) { o[n as usize] = (b % 256) as u8; }
+ b = b / 256;
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+/// One triangle: the normal and three corners, each three floats, and two bytes of zero.
+fn stl_tri(o: []u8, at: u32, nx: f64, ny: f64, nz: f64, a: []const f64, b: []const f64, c: []const f64) -> u32 {
+ var n : u32 = put_f32(o, at, nx);
+ n = put_f32(o, n, ny);
+ n = put_f32(o, n, nz);
+ var k : u32 = 0;
+ while (k < 3) {
+ n = put_f32(o, n, a[k as usize]);
+ k = k + 1;
+ }
+ k = 0;
+ while (k < 3) {
+ n = put_f32(o, n, b[k as usize]);
+ k = k + 1;
+ }
+ k = 0;
+ while (k < 3) {
+ n = put_f32(o, n, c[k as usize]);
+ k = k + 1;
+ }
+ if ((n as usize) + 1 < o.len) {
+ o[n as usize] = 0;
+ o[(n + 1) as usize] = 0;
+ }
+ return n + 2;
+}
+
+pub var S_BOX : [6]f64 = [0.0; 6];
+pub var S_PA : [3]f64 = [0.0; 3];
+pub var S_PB : [3]f64 = [0.0; 3];
+pub var S_PC : [3]f64 = [0.0; 3];
+pub var S_PD : [3]f64 = [0.0; 3];
+
+/// p(x, y, z) of the current box into out
+fn box_p(out: []f64, x: u32, y: u32, z: u32) -> u32 {
+ if (x != 0) { out[0] = S_BOX[3]; } else { out[0] = S_BOX[0]; }
+ if (y != 0) { out[1] = S_BOX[4]; } else { out[1] = S_BOX[1]; }
+ if (z != 0) { out[2] = S_BOX[5]; } else { out[2] = S_BOX[2]; }
+ return 0;
+}
+
+fn stl_quad(o: []u8, at: u32, nx: f64, ny: f64, nz: f64, a: u32, b: u32, c: u32, d: u32) -> u32 {
+ // the corner codes are 3 bits: x y z
+ box_p(S_PA[0..], (a / 4) % 2, (a / 2) % 2, a % 2);
+ box_p(S_PB[0..], (b / 4) % 2, (b / 2) % 2, b % 2);
+ box_p(S_PC[0..], (c / 4) % 2, (c / 2) % 2, c % 2);
+ box_p(S_PD[0..], (d / 4) % 2, (d / 2) % 2, d % 2);
+ var n : u32 = stl_tri(o, at, nx, ny, nz, S_PA[0..], S_PB[0..], S_PC[0..]);
+ n = stl_tri(o, n, nx, ny, nz, S_PA[0..], S_PC[0..], S_PD[0..]);
+ return n;
+}
+
+fn stl_box(o: []u8, at: u32) -> u32 {
+ var n : u32 = at;
+ n = stl_quad(o, n, 0.0, 0.0, 0.0 - 1.0, 0, 2, 6, 4);
+ n = stl_quad(o, n, 0.0, 0.0, 1.0, 1, 5, 7, 3);
+ n = stl_quad(o, n, 0.0, 0.0 - 1.0, 0.0, 0, 4, 5, 1);
+ n = stl_quad(o, n, 0.0, 1.0, 0.0, 2, 3, 7, 6);
+ n = stl_quad(o, n, 0.0 - 1.0, 0.0, 0.0, 0, 1, 3, 2);
+ n = stl_quad(o, n, 1.0, 0.0, 0.0, 4, 6, 7, 5);
+ return n;
+}
+
+fn stl_len() -> u32 {
+ return 84 + (R_NCOL + 1) * 12 * 50;
+}
+
+/// downloadStl(): a base plate the size of the die and one box per touched tile. Returns its length.
+pub fn w_bytes(o: []u8) -> u32 {
+ const total : u32 = stl_len();
+ if ((total as usize) > o.len) { return 0; }
+ var i : u32 = 0;
+ while (i < 84) {
+ o[i as usize] = 0;
+ i = i + 1;
+ }
+ // the header: `${ID} ${label}`.slice(0, 79), each UTF-16 unit & 127
+ var head : [4096]u8 = [0; 4096];
+ var hl : u32 = copy_into(head[0..], 0, ID);
+ hl = copy_into(head[0..], hl, " ");
+ hl = copy_into(head[0..], hl, S_LABEL[0..(S_LABEL_N as usize)]);
+ var units : u32 = 0;
+ var p : u32 = 0;
+ while (p < hl and units < 79) {
+ const b : u32 = head[p as usize] as u32;
+ var cp : u32 = b;
+ var len : u32 = 1;
+ if (b >= 240) {
+ cp = b % 8;
+ len = 4;
+ } else {
+ if (b >= 224) {
+ cp = b % 16;
+ len = 3;
+ } else {
+ if (b >= 192) {
+ cp = b % 32;
+ len = 2;
+ }
+ }
+ }
+ var q : u32 = 1;
+ while (q < len and p + q < hl) {
+ cp = cp * 64 + (head[(p + q) as usize] as u32) % 64;
+ q = q + 1;
+ }
+ if (cp >= 65536) {
+ const c2 : u32 = cp - 65536;
+ o[units as usize] = (((55296 + c2 / 1024) % 128) as u8);
+ units = units + 1;
+ if (units < 79) {
+ o[units as usize] = (((56320 + c2 % 1024) % 128) as u8);
+ units = units + 1;
+ }
+ } else {
+ o[units as usize] = ((cp % 128) as u8);
+ units = units + 1;
+ }
+ p = p + len;
+ }
+ const tris : u32 = (R_NCOL + 1) * 12;
+ o[80] = (tris % 256) as u8;
+ o[81] = ((tris / 256) % 256) as u8;
+ o[82] = ((tris / 65536) % 256) as u8;
+ o[83] = ((tris / 16777216) % 256) as u8;
+ var at : u32 = 84;
+ S_BOX[0] = 0.0;
+ S_BOX[1] = 0.0;
+ S_BOX[2] = 0.0 - 1.0;
+ S_BOX[3] = G_W as f64;
+ S_BOX[4] = G_H as f64;
+ S_BOX[5] = 0.0;
+ at = stl_box(o, at);
+ var c : u32 = 0;
+ while (c < R_NCOL) {
+ S_BOX[0] = R_CGX[c as usize] as f64;
+ S_BOX[1] = (G_H as f64) - (R_CGY[c as usize] as f64) - 1.0;
+ S_BOX[2] = 0.0;
+ S_BOX[3] = (R_CGX[c as usize] as f64) + 1.0;
+ S_BOX[4] = (G_H as f64) - (R_CGY[c as usize] as f64);
+ S_BOX[5] = (R_CN[c as usize] as f64) * S_HK;
+ at = stl_box(o, at);
+ c = c + 1;
+ }
+ return at;
+}
+
+// --- effects ---------------------------------------------------------------------------------------------------------------------------------------
+
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (k >= S_EF_N) {
+ S_EF_N = 0;
+ return 0;
+ }
+ const e : u8 = S_EF[k as usize];
+ if (e == 80) {
+ dst[0] = 80;
+ var n : u32 = js_str_num(dst, 1, K_PNG_W as f64);
+ dst[n as usize] = 10;
+ n = js_str_num(dst, n + 1, K_PNG_H as f64);
+ dst[n as usize] = 10;
+ return copy_into(dst, n + 1, S_SAVE[0..(S_SAVE_N as usize)]);
+ }
+ dst[0] = 68;
+ var m : u32 = copy_into(dst, 1, S_SAVE[0..(S_SAVE_N as usize)]);
+ dst[m as usize] = 10;
+ m = copy_into(dst, m + 1, W_STL_MIME);
+ dst[m as usize] = 10;
+ return js_str_num(dst, m + 1, stl_len() as f64);
+}
+
+
+// --- Tests of the native page -----------------------------------------------------------------------------
+
+pub const W_TEST_GRID : str = "{\"w\":3,\"h\":2,\"types\":[\"CLBLL_L\",\"INT_L\",\"BRAM_L\"],\"rle\":[0,1,1,3,2,2],\"x\":{\"CLBLL_L\":[[7,0,1,1,0]],\"INT_L\":[[5,1,2,1,-1]],\"BRAM_L\":[[10,2,1,1,0]]},\"y\":{\"CLBLL_L\":[[20,0,2,1,1]],\"INT_L\":[[20,0,2,1,1]],\"BRAM_L\":[[20,0,2,1,1]]},\"except\":{},\"bits\":[1,1,1]}";
+pub const W_TEST_SAMPLES : str = "{\"samples\":[{\"file\":\"a.fasm\",\"part\":\"xc7\",\"sha256\":\"0123456789abcdef0123456789abcdef\",\"features\":10,\"cleared\":1,\"commands\":[\"yosys -q\",\"nextpnr\"],\"tiles\":[[\"CLBLL_L_X7Y20\",3],[\"INT_L_X5Y20\",4],[\"BRAM_L_X10Y21\",2],[\"FOO_X1Y1\",1]]}]}";
+
+fn has_text(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (want.len > (n as usize)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+fn open_page() -> u32 {
+ w_init("");
+ w_view(800.0, 1.0, 0, 900.0, 800.0);
+ return 0;
+}
+
+fn load_mini() -> u32 {
+ w_data(0, W_TEST_GRID);
+ w_data(1, W_TEST_SAMPLES);
+ return 0;
+}
+
+test the_page_opens_loading_with_nothing_counted {
+ open_page();
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert n > 0;
+ assert has_text(o[0..], n, "
Loading the XC7A200T tile grid...
");
+ assert has_text(o[0..], n, "aria-pressed=\"false\">AX7203 node (sample)");
+ assert has_text(o[0..], n, "
");
+ assert has_text(o[0..], n, "");
+ var d : [64]u8 = [0; 64];
+ assert w_bdraw(d[0..]) == 0;
+}
+
+test the_mini_die_loads_and_the_first_sample_is_counted {
+ open_page();
+ load_mini();
+ assert S_GRID and S_HAVE;
+ assert S_PRESSED == 0;
+ var o : [65536]u8 = [0; 65536];
+ const n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">a.fasm: 9 features in 3 tiles");
+ assert has_text(o[0..], n, "aria-pressed=\"true\">AX7203 node (sample)");
+ assert has_text(o[0..], n, " width=\"800\" height=\"496\" style=\"height: 496px;\"");
+ assert has_text(o[0..], n, "
Tiles touched
3
Features set
9
");
+ assert has_text(o[0..], n, "
INT_L_X5Y204 features
");
+ assert has_text(o[0..], n, "
0.01%3 of 41,106 tiles that hold configuration bits
");
+ assert has_text(o[0..], n, "
Logic (CLBLL, CLBLM)
1 / 16,825
3
");
+ assert has_text(o[0..], n, "
DSP
0 / 370
0
");
+ assert has_text(o[0..], n, "
Point at or tap a column to see its tile.
");
+ assert has_text(o[0..], n, "Sample a.fasm, part xc7, sha256 0123456789abcdef...");
+ assert has_text(o[0..], n, "1 lines set a value of zero");
+ assert has_text(o[0..], n, "Heights are relative: the tallest column (4 features) is drawn the same height in every file. Floor: the 3 x 2 tile grid.");
+ assert has_text(o[0..], n, "1 tiles (1 features) are not tiles of the XC7A200T grid and are not drawn
FOO_X1Y1 1
");
+ assert has_text(o[0..], n, "0 lines are not FASM features");
+ assert has_text(o[0..], n, "How this sample was made
yosys -q
nextpnr
");
+}
+
+test the_drawing_is_a_binary_list_that_starts_with_the_canvas_and_the_transform {
+ open_page();
+ load_mini();
+ var o : [262144]u8 = [0; 262144];
+ const n : u32 = w_bdraw(o[0..]);
+ assert n > 200;
+ assert o[0] == 200 and o[1] == 0 and o[2] == 12;
+ // setTransform(1, 0, 0, 1, 0, 0): 1.0 is 3FF0000000000000, little endian
+ assert o[3] == 0 and o[9] == 240 and o[10] == 63;
+ // then fillStyle "#000" (op 32, length 4)
+ assert o[51] == 32 and o[52] == 4 and o[53] == 0 and o[54] == 35 and o[55] == 48;
+ assert f64_bits(1.0) == 4607182418800017408;
+ assert f64_bits(0.0 - 2.5) == 13836183955189006336;
+ assert f64_bits(0.0) == 0;
+ assert f64_bits(0.1) == 4591870180066957722;
+ assert f64_bits(123456.789) == 4683220299150161609;
+}
+
+test floats_round_to_32_bits_to_nearest_even {
+ assert f32_bits(1.5) == 1069547520;
+ assert f32_bits(0.1) == 1036831949;
+ assert f32_bits(0.0 - 3.0) == 3225419776;
+ assert f32_bits(16777217.0) == 1266679808;
+ assert f32_bits(16777219.0) == 1266679810;
+ assert f32_bits(1.401298464324817e-45) == 1;
+ assert f32_bits(0.0) == 0;
+ assert f32_bits(1e39) == 2139095040;
+}
+
+test the_stl_has_a_base_and_a_box_per_tile_and_a_header {
+ open_page();
+ load_mini();
+ var o : [4096]u8 = [0; 4096];
+ assert w_bytes(o[0..]) == 84 + 4 * 12 * 50;
+ assert o[0] == 102 and o[1] == 97 and o[2] == 115 and o[3] == 109;
+ assert o[12] == 32 and o[13] == 97;
+ assert o[80] == 48 and o[81] == 0 and o[82] == 0 and o[83] == 0;
+ // the first triangle: the normal (0, 0, -1) of the plate's bottom, then its first corner (0, 0, -1)
+ assert o[84] == 0 and o[91] == 0 and o[92] == 0 and o[93] == 0 and o[94] == 128 and o[95] == 191;
+ assert o[96] == 0 and o[103] == 0 and o[107] == 191;
+ assert o[132] == 0 and o[133] == 0;
+ var small : [64]u8 = [0; 64];
+ assert w_bytes(small[0..]) == 0;
+}
+
+test buttons_rotate_fit_and_save_with_the_names_of_the_loaded_file {
+ open_page();
+ load_mini();
+ assert S_ROT == 0;
+ w_click(4);
+ assert S_ROT == 1;
+ w_click(4);
+ w_click(4);
+ w_click(4);
+ assert S_ROT == 0;
+ var e : [256]u8 = [0; 256];
+ w_click(5);
+ const n : u32 = w_effect(0, e[0..]);
+ assert e[0] == 80;
+ assert has_text(e[0..], n, "1200");
+ assert has_text(e[0..], n, "a-skyline.png");
+ assert w_effect(1, e[0..]) == 0;
+ w_click(6);
+ const m : u32 = w_effect(0, e[0..]);
+ assert e[0] == 68;
+ assert has_text(e[0..], m, "a-skyline.stl");
+ assert has_text(e[0..], m, "model/stl");
+ assert has_text(e[0..], m, "2484");
+ S_LABEL_N = copy_into(S_LABEL[0..], 0, "my file.v2.fasm");
+ S_SAVE_N = save_name(W_PNG_SUFFIX);
+ assert has_text(S_SAVE[0..], S_SAVE_N, "my-file-v2-skyline.png");
+ assert S_SAVE[0] == 109;
+}
+
+test a_tap_picks_a_column_a_pointer_over_it_shows_it_and_leaving_clears_it {
+ open_page();
+ load_mini();
+ // the first column in the list, by its hull
+ cell_uv(R_CGX[0] as f64, R_CGY[0] as f64);
+ // a point inside it: the middle of its top face, in canvas pixels
+ const px : f64 = (px_of(G_U + 0.5, G_V + 0.5) - S_VX) * S_VS;
+ const py : f64 = (py_of(G_U + 0.5, G_V + 0.5, (R_CN[0] as f64) * S_HK) - S_VY) * S_VS;
+ w_cpointer(1, 1, px, py, 1);
+ assert HV_ON;
+ var o : [65536]u8 = [0; 65536];
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "
Tile
");
+ w_cpointer(0, 1, px, py, 1);
+ w_cpointer(2, 1, px, py, 1);
+ assert PK_ON;
+ w_cpointer(4, 1, 0.0, 0.0, 1);
+ assert !HV_ON;
+ assert PK_ON;
+ // a drag moves the view and picks nothing
+ const vx : f64 = S_VX;
+ w_cpointer(0, 2, 100.0, 100.0, 1);
+ w_cpointer(1, 2, 140.0, 100.0, 1);
+ w_cpointer(2, 2, 140.0, 100.0, 1);
+ assert S_VX != vx;
+ // a mouse leave with no hover does nothing
+ w_cpointer(4, 2, 0.0, 0.0, 1);
+}
+
+test the_wheel_asks_the_host_for_exp_then_zooms_and_the_keys_zoom_pan_rotate {
+ open_page();
+ load_mini();
+ const s0 : f64 = S_VS;
+ assert w_wheel(100.0, 100.0, 100.0, 0) == 2;
+ var req : [192]f64 = [0.0; 192];
+ assert w_libm_need(req[0..]) == 1;
+ assert req[0] == 16.0 and req[1] == 0.0 - 0.25;
+ var ans : [1]f64 = [0.7788007830714049; 1];
+ w_libm_give(ans[0..], 1);
+ assert w_wheel(100.0, 100.0, 100.0, 0) == 1;
+ assert S_VS < s0 or S_VS == s0;
+ const s1 : f64 = S_VS;
+ assert w_key("+") == 1;
+ assert S_VS > s1;
+ assert w_key("ArrowLeft") == 1;
+ assert w_key("q") == 0;
+ assert w_key("r") == 1;
+ assert S_ROT == 1;
+}
+
+test hypot_is_the_v8_one_and_the_pinch_and_resize_follow_tool_js {
+ assert hypot2(3.0, 4.0) == 5.0;
+ assert hypot2(0.0, 0.0) == 0.0;
+ assert hypot2(0.0 - 3.0, 4.0) == 5.0;
+ assert hypot2(1e200, 1e200) > 1.4e200;
+ open_page();
+ load_mini();
+ const s0 : f64 = S_VS;
+ w_view(400.0, 1.0, 0, 900.0, 400.0);
+ assert w_box(400.0, 300.0) == 1;
+ assert S_CSSW == 400.0;
+ assert S_VS < s0;
+ assert w_box(400.0, 300.0) == 0;
+}
+
+test a_file_is_counted_or_refused_with_the_words_of_the_page {
+ open_page();
+ load_mini();
+ var o : [65536]u8 = [0; 65536];
+ w_file("mine.fasm", 77.0, "CLBLL_L_X7Y20.A\nCLBLL_L_X7Y20.B\nINT_L_X5Y20.C = 0\n# chipdb x.bin\nFOO.f\n", 0);
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">mine.fasm: 2 features in 1 tiles");
+ assert has_text(o[0..], n, "aria-pressed=\"false\">AX7203 node (sample)");
+ assert has_text(o[0..], n, "Your file mine.fasm, 77 bytes, counted in this browser and sent nowhere. chipdb x.bin");
+ assert has_text(o[0..], n, "1 lines set a value of zero");
+ assert has_text(o[0..], n, "1 lines are not FASM features (no tile name before the first '.')
FOO.f
");
+ assert has_text(o[0..], n, "How this sample was made");
+ w_file("empty.fasm", 3.0, "# c\n", 0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">empty.fasm sets no FASM features: no line has a tile name and a feature.");
+ assert has_text(o[0..], n, "
Tiles touched
1
");
+ w_file("huge.fasm", 300000000.0, "", 0);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">huge.fasm is larger than 256 MB, the most this page reads.");
+ w_file("bad.fasm", 3.0, "x", 1);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">bad.fasm could not be read.");
+}
+
+test the_card_text_is_wrapped_at_spaces_to_the_widths_the_host_measures {
+ open_page();
+ load_mini();
+ var o : [262144]u8 = [0; 262144];
+ var req : [65536]u8 = [0; 65536];
+ var rounds : u32 = 0;
+ var go : bool = true;
+ while (go and rounds < 40) {
+ w_bdraw2(o[0..]);
+ const q : u32 = w_text_need(req[0..]);
+ if (q == 0) {
+ go = false;
+ } else {
+ var wd : [512]f64 = [0.0; 512];
+ var p : u32 = 0;
+ var i : u32 = 0;
+ while (i < q and i < 512) {
+ const fl : u32 = (req[p as usize] as u32) + 256 * (req[(p + 1) as usize] as u32);
+ const tl : u32 = (req[(p + 4) as usize] as u32) + 256 * (req[(p + 5) as usize] as u32);
+ wd[i as usize] = (tl as f64) * 9.0;
+ p = p + 8 + fl + tl;
+ i = i + 1;
+ }
+ w_text_give(wd[0..], q);
+ }
+ rounds = rounds + 1;
+ }
+ assert !go or rounds < 40;
+ const n : u32 = w_bdraw2(o[0..]);
+ assert n > 300;
+ assert o[0] == 200 and o[2] == 5 and o[3] == 9;
+ // the brand has the superscript three
+ assert has_text(o[0..], n, "Trinity S");
+ var t : [16]u8 = [0; 16];
+ assert brand_into(t[0..], 0, "Trinity S3AI") == 13;
+ assert t[9] == 194 and t[10] == 179;
+ assert brand_into(t[0..], 0, "S3AIX S3AI_") == 11;
+}
+
+test a_sample_that_is_not_on_the_die_or_a_broken_file_says_the_die_map_did_not_load {
+ open_page();
+ w_data(0, "{\"w\":3}");
+ w_data(1, W_TEST_SAMPLES);
+ var o : [65536]u8 = [0; 65536];
+ var n : u32 = w_render(o[0..]);
+ assert has_text(o[0..], n, "role=\"status\">The die map (grid.json) or the samples (samples.json) did not load, so nothing can be drawn.");
+ assert !S_GRID;
+ open_page();
+ w_data_failed(0);
+ w_data(1, W_TEST_SAMPLES);
+ n = w_render(o[0..]);
+ assert has_text(o[0..], n, "did not load, so nothing can be drawn.");
+ w_click(0);
+ w_click(4);
+ assert w_key("r") == 1;
+ assert !S_HAVE;
+}
+
+test the_text_widths_the_counting_and_the_spec_agree_on_the_families {
+ open_page();
+ load_mini();
+ assert same_text(W_DATA_FILES[0], K_DATA_GRID);
+ assert same_text(W_DATA_FILES[1], K_DATA_SAMPLES);
+ assert family_of("CLBLL_L") == 0;
+ assert family_of("INT_L") == 1;
+ assert family_of("BRAM_INT_INTERFACE_L") == 1;
+ assert family_of("BRAM_L") == 2;
+ assert family_of("DSP_L") == 3;
+ assert family_of("LIOB33") == 4;
+ assert family_of("CLK_BUFG") == 5;
+ assert family_of("NULL") == 6;
+ assert R_NCOL == 3 and R_NOFF == 1 and R_PLACED == 9 and R_FEAT == 10;
+ assert R_FAMT[0] == 1 and R_FAMT[1] == 1 and R_FAMT[2] == 1;
+ assert R_FAMF[1] == 4;
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions mention.
+/// This anchor mentions every helper the writer, the JSON reader and the number rules call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ n = n + num_grouped(o, 0, 0);
+ return n;
+}
From c1fcfffc6d37644a9a346c78e853ec88416ff428 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 22:06:03 +0700
Subject: [PATCH 58/63] widgets: catalog records fasm-skyline at PORT_DRAW;
dupe ledger re-blessed (Closes #11016)
---
specs/widgets/catalog.t27 | 15 +++---
tools/duplicate_bodies_baseline.txt | 74 ++++++++++++++++++++++-------
2 files changed, 63 insertions(+), 26 deletions(-)
diff --git a/specs/widgets/catalog.t27 b/specs/widgets/catalog.t27
index 4438854604..f56491d5b1 100644
--- a/specs/widgets/catalog.t27
+++ b/specs/widgets/catalog.t27
@@ -37,7 +37,7 @@
; 14667449, lut-explain 1395124, bit-diff 275620, vcd-wrapped 76076, pin-map 180587, synth-smells 87094, fsm-sketch 91657 (its Math.sin/cos/atan2 stay with the host: V8 differs from libm in the last bit), bit-autopsy 96007.
; With these, all 12 pure logic files of the census (2775 lines of JavaScript) exist as .t27.
; gatle is at PORT_LOGIC (specs/widgets/gatle_rule.t27: 866178 comparisons vs rule.js, 0 mismatches)
-; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) fsm-sketch, cost-curves, lut-treemap, stat-card, synth-smells and vcd-wrapped at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
+; and jtag-verdict, race-the-bee, t27-vs-nvfp4, t27-cell, build-receipt, uart-bucket, test-touch, pin-map, lut-depth, lut-explain, gf16-calc, resource-bars and fits-on (the first canvas page: its DOM and its canvas call sequence both compared) fsm-sketch, cost-curves, lut-treemap, stat-card, synth-smells, vcd-wrapped and fasm-skyline at PORT_DRAW (specs/widgets/jtag_verdict.t27: 3015 DOM
; comparisons vs tool.js, 0 mismatches; specs/widgets/race_the_bee.t27: 59, with 13 data variants, 0). Every other tool is at PORT_WORDS: its words live in its spec, its logic and drawing
; still live in JavaScript. A stage is reached on the t27 side first; the page in trinity keeps
; loading the hand-written file until that repo's migration PR deletes it. A tool moves up only when
@@ -91,7 +91,7 @@ pub const PORT_LOGIC : u8 = 2; // pure logic is .t27, generated to JS by gen-ts
pub const PORT_DRAW : u8 = 3; // the page is drawn by the trusted renderer from a plan
pub const PORT_SEALED : u8 = 4; // the spec seal keys the page, the data and the card
pub const PORT_STAGES : u8 = 5;
-pub const TOOL_PORT : [28]u8 = [2, 2, 3, 3, 2, 3, 3, 2, 3, 1, 3, 3, 3, 3, 3, 1, 3, 3, 1, 3, 3, 3, 3, 1, 3, 1, 3, 3];
+pub const TOOL_PORT : [28]u8 = [2, 2, 3, 3, 3, 3, 3, 2, 3, 1, 3, 3, 3, 3, 3, 1, 3, 3, 1, 3, 3, 3, 3, 1, 3, 1, 3, 3];
; Port waves, by the hand lines a tool still carries (logic + draw + css + data + card).
; Wave 1 is the small tools, wave 3 the largest; a wave is a batch of port issues.
@@ -227,14 +227,11 @@ test the_waves_cover_every_tool_once {
assert hand_lines(12) == 1432;
}
-test nineteen_pages_are_native_and_the_remaining_logic_files_are_ported {
- assert count_port(PORT_DRAW) == 19;
- assert count_port(PORT_LOGIC) == 4;
+test twenty_pages_are_native_and_the_remaining_logic_files_are_ported {
+ assert count_port(PORT_DRAW) == 20;
+ assert count_port(PORT_LOGIC) == 3;
assert count_port(PORT_WORDS) == 5;
assert count_port(PORT_SEALED) == 0;
- assert TOOL_PORT[13] == PORT_DRAW;
- assert TOOL_PORT[19] == PORT_DRAW;
- assert TOOL_PORT[20] == PORT_DRAW;
- assert TOOL_PORT[27] == PORT_DRAW;
+ assert TOOL_PORT[4] == PORT_DRAW;
assert TOOL_PORT[7] == PORT_LOGIC;
}
diff --git a/tools/duplicate_bodies_baseline.txt b/tools/duplicate_bodies_baseline.txt
index a5b792c133..5950e6c40d 100644
--- a/tools/duplicate_bodies_baseline.txt
+++ b/tools/duplicate_bodies_baseline.txt
@@ -7,7 +7,7 @@ sadd 31
build_tree 28,2
magmul 22,2
smul 20,2
-bytes_eq 14,3
+bytes_eq 15,3
tmul 13
relu 10
dot27 8,5
@@ -18,6 +18,7 @@ clear 6
eq_s 6
quantize 6,5
relu_prime 6
+ws_len_at 6
count 5
exp_approx 5
floor 5,4
@@ -27,13 +28,15 @@ pack3 5
pow 5,2
scale_q 5
tp 5
-ws_len_at 5
bom_skip 4
cv_same 4
+digits_val 4
+fmt_into 4
fromRaw 4
gft_add 4
gft_mul 4
go_down 4
+has 4,2
has_substring 4
match_at 4
match_in 4
@@ -45,27 +48,29 @@ ws_at 4
ws_back 4
ws_before 4
ws_fwd 4
+ws_len_before 4
arena_reserve 3
+arg_num 3
+arg_set 3
assign 3
block_seen_before 3
+button 3
category 3,2
check_balanced_braces 3
concat 3,2
copy_bytes_at 3
+copy_into 3
copy_to 3
cos 3
count_of 3
crc_byte 3
crc_step 3
dest_count 3
-digits_val 3
dnum 3
fill 3
floor_log2 3,2
-fmt_into 3
gadd 3
gmul 3
-has 3,2
hex_nibble_of 3
hex_val 3,3
id_seen_before 3
@@ -79,6 +84,8 @@ pack2 3
pow2_frac 3
publish_result_to_string 3
put_all 3,2
+put_say 3
+put_u32le 3
skip_digits 3
skip_ws 3,2
starts_with_at 3
@@ -89,8 +96,8 @@ trim_span 3
trit2gft 3
trit_negate 3
utf16_len 3
+w_text_give 3
wait_cycles 3
-ws_len_before 3
HirCoverPoint_validate 2
LinkSection_bss 2
LinkSection_data 2
@@ -101,8 +108,6 @@ ads_equal 2
all_positive 2
alloc_node 2
analyze 2
-arg_num 2
-arg_set 2
asr9 2
assert_pass 2
axi4_full_master 2
@@ -145,7 +150,6 @@ bput_u 2
bputs 2
btn_open 2
build_report_frame 2
-button 2
by_type 2
by_type_kind 2
by_type_of 2
@@ -168,13 +172,13 @@ cmd_running_ltp 2
compress 2
contains 2
conv 2
-copy_into 2
copy_small 2
cordic_cos 2
cordic_sin 2
cordic_x_next 2
cordic_y_next 2
cordic_z_next 2
+count_items 2
counter_24bit 2
cover_bits 2
cp_utf8_byte 2
@@ -199,11 +203,16 @@ esc_byte 2
exp2 2
extend_into 2
extract_mantissa 2,2
+fa_bad 2
+fa_header 2
+fa_tile 2
fail 2
+fam_set 2
+family_of_range 2
feed 2
fill_plain 2
fin 2
-find_byte 2
+find_byte 2,2
find_child 2
find_in 2
find_info 2
@@ -216,9 +225,20 @@ find_version 2
fl_clear 2
flat 2
fmt4 2
+fnv 2
free_node 2
full_adder 2
generate_next_token_from_logits 2
+gp_eq 2
+gpool_put 2
+grid_at 2
+grid_begin 2
+grid_except 2
+grid_lookup 2
+grid_prefixes 2
+grid_rle 2
+grid_segs 2
+grid_types 2
group_of 2,2,2
halves_o5 2
hard_sigmoid 2
@@ -238,6 +258,7 @@ id_eq 2
id_provisioned 2
imports_anchor 2,2
index_key 2
+index_of_name 2
init_bin 2
init_hex 2
input_at 2
@@ -264,6 +285,7 @@ is_type_char 2
is_type_start 2
is_unknown_char 2
is_word_char 2
+item_of 2
j_number 2
j_object 2
j_parse 2
@@ -279,8 +301,11 @@ jstring 2
jvalue 2
jws 2
kill 2
+la_make 2
layout_tree 2
+lb_make 2
lb_value 2
+lc_make 2
leaves_n 2
leaves_sum 2
leaves_walk 2
@@ -321,7 +346,7 @@ member_contains 2
minimize 2
module_rows 2
name_find 2
-name_less 2
+name_less 2,2
neg_zero 2
nested_tail 2
new_graph 2
@@ -339,6 +364,7 @@ out_marker 2
out_put 2
out_span 2
out_str 2
+parse_fasm 2
parse_input 2
parse_literal 2
pi_bad 2
@@ -346,10 +372,12 @@ pi_bits 2
pi_push 2
pipeline_f32 2
pool_eq 2,2
-pool_is 2
+pool_eq_text 2
+pool_is 2,2
pool_prefix 2
pool_push_uint 2
pool_put 2,2
+pool_put_text 2
pool_put_u 2
pool_same 2
pool_ws_at 2
@@ -369,8 +397,6 @@ put_dec 2
put_item 2
put_lit 2
put_range 2
-put_say 2
-put_u32le 2
put_utf8 2
read_be32 2
record 2
@@ -389,6 +415,8 @@ same_text 2
same_totals 2
sample_imm 2
sample_reg 2
+seg_set 2
+seg_value 2
session_status_str 2
set_src 2
set_time_from_token 2
@@ -397,8 +425,14 @@ short_block 2
shortest_dec 2
signmul 2
slices_equal 2
+sm_col 2
+sm_fam 2
+sm_info 2
+sm_off 2
+small_die 2
solve 2
sort_idx 2
+sort_names 2
source_upload_file 2
span_eq 2
span_units 2
@@ -414,6 +448,7 @@ starts_s 2
starts_with 2,2
startup_config 2
stat_parse 2
+status_fmt 2
step_ef 2
step_match 2
store_value 2
@@ -421,6 +456,7 @@ str_is 2
str_num 2
string_arena_reserve 2
strip_quotes 2
+summarize 2
support_mask 2
t_has 2
term_literals 2
@@ -429,8 +465,10 @@ text_append_str 2
text_append_u32 2
text_equal 2
text_of 2
+tile_add 2
tile_build 2
tile_new 2
+tile_split 2
timescale_fail 2
to_precision4 2
tok_end 2
@@ -441,11 +479,13 @@ totals_of 2
trim_bounds 2
trim_log 2
trim_pack 2
+trim_range 2
trit_add 2
trit_compare 2
trit_max 2
trit_min 2
trit_mul_closed 2
+tx_line 2
type_end 2
uart_tx_bit_at 2
units_of 2
@@ -464,10 +504,9 @@ view_tree 2
vs_name 2
vs_row 2
w_base 2
-w_drag 2,2,2
+w_drag 2,2,2,2
w_file 2
w_libm_need 2
-w_text_give 2
w_value 2
wave_segments 2
weight_sign 2
@@ -477,3 +516,4 @@ word_to 2
writes_the_repo 2
xml_put 2
xor2 2
+zero_value 2
From bfc9d4a60a54332506a22954537e03fa7f29589c Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 21:59:35 +0700
Subject: [PATCH 59/63] widgets: text_meas -- tm_fit_right, the
cut-from-the-right fit with three dots (Closes #10938)
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/text_meas.t27 | 132 ++++++++++++++++++++++++++++--------
1 file changed, 103 insertions(+), 29 deletions(-)
diff --git a/specs/widgets/text_meas.t27 b/specs/widgets/text_meas.t27
index 344993e2cb..e238d400c2 100644
--- a/specs/widgets/text_meas.t27
+++ b/specs/widgets/text_meas.t27
@@ -277,6 +277,57 @@ pub fn tm_fit_left(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8
return lo as i64;
}
+/// tool.js (bit-autopsy): fit = (ctx, s, w) => { s = String(s); if (measure(s) <= w) return s;
+/// while (s.length > 1 && measure(s + '...') > w) s = s.slice(0, -1); return s + '...' }
+/// Returns the byte length `hi` of the prefix t[0 .. hi) the result is made of (the result is that prefix + `...`), or -1 when t fits whole.
+/// scratch needs t.len + 3 bytes. While widths are missing the answer is meaningless (the miss flag is set).
+pub fn tm_fit_right(tm: []u32, pool: []u8, wid: []f64, ok: []u8, font: []const u8, t: []const u8, room: f64, scratch: []u8) -> i64 {
+ const whole : f64 = tm_width(tm, pool, wid, ok, font, t);
+ if (tm[2] != 0) { return 0 - 1; }
+ if (whole <= room) { return 0 - 1; }
+ var hi : usize = t.len;
+ while (tm_units(t, 0, hi) > 1) {
+ var k : usize = 0;
+ while (k < hi) {
+ scratch[k] = t[k];
+ k = k + 1;
+ }
+ scratch[hi] = 46;
+ scratch[hi + 1] = 46;
+ scratch[hi + 2] = 46;
+ const w : f64 = tm_width(tm, pool, wid, ok, font, scratch[0..(hi + 3)]);
+ if (tm[2] != 0) {
+ // ask for the next candidates in one go
+ var a : u32 = 0;
+ var q : usize = hi;
+ while (a < TM_AHEAD and q > 0) {
+ q = q - tm_char_before(t, q);
+ var j : usize = 0;
+ while (j < q) {
+ scratch[j] = t[j];
+ j = j + 1;
+ }
+ scratch[q] = 46;
+ scratch[q + 1] = 46;
+ scratch[q + 2] = 46;
+ tm_ask(tm, pool, wid, ok, font, scratch[0..(q + 3)]);
+ a = a + 1;
+ }
+ return 0 - 1;
+ }
+ if (w <= room) { return hi as i64; }
+ hi = hi - tm_char_before(t, hi);
+ }
+ return hi as i64;
+}
+
+/// The byte length of the UTF-8 character that ends at t[i - 1].
+pub fn tm_char_before(t: []const u8, i: usize) -> usize {
+ var l : usize = 1;
+ while (l < i and l < 4 and t[i - l] >= 128 and t[i - l] < 192) { l = l + 1; }
+ return l;
+}
+
/// tool.js: wrap = (t, room) => { lines = []; cur = ''; for (w of t.split(' ')) { next = cur ? `${cur} ${w}` : w;
/// if (measure(next) > room && cur) { lines.push(cur); cur = w } else cur = next } if (cur) lines.push(cur) }
/// The lines are substrings of t: line k is t[spans[2k] .. spans[2k + 1]). Returns the number of lines (at most
@@ -327,10 +378,10 @@ fn m_same(a: []const u8, b: []const u8) -> bool {
return true;
}
-pub var T_TM : [64]u32 = [0; 64];
-pub var T_POOL : [4096]u8 = [0; 4096];
-pub var T_WID : [12]f64 = [0.0; 12];
-pub var T_OK : [12]u8 = [0; 12];
+pub var T_TM : [300]u32 = [0; 300];
+pub var T_POOL : [8192]u8 = [0; 8192];
+pub var T_WID : [64]f64 = [0.0; 64];
+pub var T_OK : [64]u8 = [0; 64];
pub var T_NEED : [512]u8 = [0; 512];
pub var T_SC : [64]u8 = [0; 64];
pub var T_SP : [16]u32 = [0; 16];
@@ -363,60 +414,60 @@ fn t_give_all() -> u32 {
}
i = i + 1;
}
- return tm_give(T_TM[0..64], T_OK[0..12], T_WID[0..12], s[0..n]);
+ return tm_give(T_TM[0..300], T_OK[0..64], T_WID[0..64], s[0..n]);
}
test a_width_not_in_the_memo_is_a_request_and_the_miss_flag {
- tm_reset(T_TM[0..64]);
- var w : f64 = tm_width(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "bold 20px m", "abc");
+ tm_reset(T_TM[0..300]);
+ var w : f64 = tm_width(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "bold 20px m", "abc");
assert w == 0.0;
- assert tm_missed(T_TM[0..64]);
+ assert tm_missed(T_TM[0..300]);
assert T_TM[0] == 1;
- const n : u32 = tm_need(T_TM[0..64], T_POOL[0..4096], T_OK[0..12], T_NEED[0..512]);
+ const n : u32 = tm_need(T_TM[0..300], T_POOL[0..8192], T_OK[0..64], T_NEED[0..512]);
assert n == 11 + 3 + 2;
assert T_NEED[0] == 98 and T_NEED[11] == 0 and T_NEED[12] == 97 and T_NEED[15] == 0;
assert t_give_all() == 1;
- tm_clear_miss(T_TM[0..64]);
- w = tm_width(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "bold 20px m", "abc");
+ tm_clear_miss(T_TM[0..300]);
+ w = tm_width(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "bold 20px m", "abc");
assert w == 30.0;
- assert !tm_missed(T_TM[0..64]);
- assert tm_need(T_TM[0..64], T_POOL[0..4096], T_OK[0..12], T_NEED[0..512]) == 0;
+ assert !tm_missed(T_TM[0..300]);
+ assert tm_need(T_TM[0..300], T_POOL[0..8192], T_OK[0..64], T_NEED[0..512]) == 0;
}
test fit_left_cuts_from_the_left_and_asks_ahead {
- tm_reset(T_TM[0..64]);
+ tm_reset(T_TM[0..300]);
const t : []const u8 = "top.dut.counter";
var r : i64 = 0 - 2;
var k : u32 = 0;
var passes : u32 = 0;
while (k < 20) {
- tm_clear_miss(T_TM[0..64]);
- r = tm_fit_left(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", t, 80.0, T_SC[0..64]);
+ tm_clear_miss(T_TM[0..300]);
+ r = tm_fit_left(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "f", t, 80.0, T_SC[0..64]);
passes = passes + 1;
- if (!tm_missed(T_TM[0..64])) { k = 99; } else { t_give_all(); k = k + 1; }
+ if (!tm_missed(T_TM[0..300])) { k = 99; } else { t_give_all(); k = k + 1; }
}
assert k == 99;
assert r == 10;
assert passes <= 3;
- tm_reset(T_TM[0..64]);
+ tm_reset(T_TM[0..300]);
k = 0;
while (k < 20) {
- tm_clear_miss(T_TM[0..64]);
- r = tm_fit_left(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", "ab", 80.0, T_SC[0..64]);
- if (!tm_missed(T_TM[0..64])) { k = 99; } else { t_give_all(); k = k + 1; }
+ tm_clear_miss(T_TM[0..300]);
+ r = tm_fit_left(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "f", "ab", 80.0, T_SC[0..64]);
+ if (!tm_missed(T_TM[0..300])) { k = 99; } else { t_give_all(); k = k + 1; }
}
assert r == 0 - 1;
}
test wrap_breaks_where_the_next_word_no_longer_fits {
- tm_reset(T_TM[0..64]);
+ tm_reset(T_TM[0..300]);
const t : []const u8 = "aa bb cc";
var k : u32 = 0;
var nl : u32 = 0;
while (k < 8) {
- tm_clear_miss(T_TM[0..64]);
- nl = tm_wrap(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", t, 50.0, T_SP[0..16]);
- if (!tm_missed(T_TM[0..64])) { k = 99; } else { t_give_all(); k = k + 1; }
+ tm_clear_miss(T_TM[0..300]);
+ nl = tm_wrap(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "f", t, 50.0, T_SP[0..16]);
+ if (!tm_missed(T_TM[0..300])) { k = 99; } else { t_give_all(); k = k + 1; }
}
assert k == 99;
assert nl == 2;
@@ -424,20 +475,43 @@ test wrap_breaks_where_the_next_word_no_longer_fits {
}
test wrap_a_sentence_of_many_words_into_two_lines {
- tm_reset(T_TM[0..64]);
+ tm_reset(T_TM[0..300]);
const t : []const u8 = "Your busiest net was dut.a, with 47 changes.";
var k : u32 = 0;
var nl : u32 = 0;
while (k < 30) {
- tm_clear_miss(T_TM[0..64]);
- nl = tm_wrap(T_TM[0..64], T_POOL[0..4096], T_WID[0..12], T_OK[0..12], "f", t, 400.0, T_SP[0..16]);
- if (!tm_missed(T_TM[0..64]) or T_TM[3] != 0) { k = 99; } else { t_give_all(); k = k + 1; }
+ tm_clear_miss(T_TM[0..300]);
+ nl = tm_wrap(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "f", t, 400.0, T_SP[0..16]);
+ if (!tm_missed(T_TM[0..300]) or T_TM[3] != 0) { k = 99; } else { t_give_all(); k = k + 1; }
}
assert T_TM[3] == 0;
assert nl == 2;
assert T_SP[0] == 0 and T_SP[1] == 35 and T_SP[2] == 36;
}
+test fit_right_cuts_from_the_right_and_adds_dots {
+ tm_reset(T_TM[0..300]);
+ const t : []const u8 = "header and IDCODE agree";
+ var r : i64 = 0 - 2;
+ var k : u32 = 0;
+ while (k < 30) {
+ tm_clear_miss(T_TM[0..300]);
+ r = tm_fit_right(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "f", t, 120.0, T_SC[0..64]);
+ if (!tm_missed(T_TM[0..300]) or T_TM[3] != 0) { k = 99; } else { t_give_all(); k = k + 1; }
+ }
+ // t_give_all makes a text of n bytes 10 n wide: the prefix and "..." fit in 120 when prefix <= 9
+ assert T_TM[3] == 0;
+ assert r == 9;
+ tm_reset(T_TM[0..300]);
+ k = 0;
+ while (k < 30) {
+ tm_clear_miss(T_TM[0..300]);
+ r = tm_fit_right(T_TM[0..300], T_POOL[0..8192], T_WID[0..64], T_OK[0..64], "f", "short", 120.0, T_SC[0..64]);
+ if (!tm_missed(T_TM[0..300])) { k = 99; } else { t_give_all(); k = k + 1; }
+ }
+ assert r == 0 - 1;
+}
+
test units_count_utf16_and_charlen_steps_over_utf8 {
var u : [3]u8 = [195, 169, 120];
assert tm_units("abc", 0, 3) == 3;
From 25438af7c5b14b90797b5a09202b02bc331698e9 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 22:03:52 +0700
Subject: [PATCH 60/63] widgets: sha256_hex -- SHA-256 as 64 hex digits for the
native pages that show a digest (Closes #10938)
3 tests (empty, abc, 56 and 1000 bytes); checked in the pages against crypto.subtle on 9.7 MB streams.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/sha256_hex.t27 | 166 +++++++++++++++++++++++++++++++++++
1 file changed, 166 insertions(+)
create mode 100644 specs/widgets/sha256_hex.t27
diff --git a/specs/widgets/sha256_hex.t27 b/specs/widgets/sha256_hex.t27
new file mode 100644
index 0000000000..daff9f9e20
--- /dev/null
+++ b/specs/widgets/sha256_hex.t27
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/sha256_hex.t27 -- SHA-256 of a byte slice as 64 lowercase hex digits, for the native widget pages that
+; show a file's digest (bit-diff, bit-autopsy). Epic gHashTag/t27#10937, issue #10938. The tool.js files called
+; crypto.subtle.digest('SHA-256', bytes) and printed the bytes as hex; a native page has no crypto, so the digest is
+; computed here (FIPS 180-4), once, in the language of the project.
+; --
+; STATELESS: the caller gives two scratch arrays, h (8 u32) and w (64 u32), and the 64-byte output; nothing is kept.
+; The message may be any length below 2^32 bytes (the length in bits is written as a 64-bit big-endian number).
+; Words are held in u64 and reduced modulo 2^32 after every addition, so no operation depends on overflow behaviour.
+; ASCII only (L3), English only (LANG-EN).
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widgets_sha256_hex;
+
+pub const SH_K : [64]u32 = [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221,
+ 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580,
+ 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986,
+ 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895,
+ 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037,
+ 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344,
+ 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779,
+ 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298];
+
+pub const SH_M32 : u64 = 4294967296;
+
+pub fn sh_rotr(x: u64, n: u64) -> u64 {
+ return ((x >> n) | (x << (32 - n))) & 4294967295;
+}
+
+pub fn sh_hexd(v: u64) -> u8 {
+ if (v < 10) { return (48 + v) as u8; }
+ return (87 + v) as u8;
+}
+
+/// One 64-byte block of the message (or of the padding) at m[at ..] into h.
+pub fn sh_block(h: []u32, w: []u32, m: []const u8, at: usize) -> u32 {
+ var t : usize = 0;
+ while (t < 16) {
+ w[t] = (((m[at + 4 * t] as u32) << 24) | ((m[at + 4 * t + 1] as u32) << 16) | ((m[at + 4 * t + 2] as u32) << 8) | (m[at + 4 * t + 3] as u32));
+ t = t + 1;
+ }
+ while (t < 64) {
+ const a : u64 = w[t - 15] as u64;
+ const b : u64 = w[t - 2] as u64;
+ const s0 : u64 = sh_rotr(a, 7) ^ sh_rotr(a, 18) ^ (a >> 3);
+ const s1 : u64 = sh_rotr(b, 17) ^ sh_rotr(b, 19) ^ (b >> 10);
+ w[t] = ((((w[t - 16] as u64) + s0 + (w[t - 7] as u64) + s1) % SH_M32) as u32);
+ t = t + 1;
+ }
+ var a : u64 = h[0] as u64;
+ var b : u64 = h[1] as u64;
+ var c : u64 = h[2] as u64;
+ var d : u64 = h[3] as u64;
+ var e : u64 = h[4] as u64;
+ var f : u64 = h[5] as u64;
+ var g : u64 = h[6] as u64;
+ var hh : u64 = h[7] as u64;
+ t = 0;
+ while (t < 64) {
+ const s1 : u64 = sh_rotr(e, 6) ^ sh_rotr(e, 11) ^ sh_rotr(e, 25);
+ const ch : u64 = (e & f) ^ ((4294967295 - e) & g);
+ const t1 : u64 = (hh + s1 + ch + (SH_K[t] as u64) + (w[t] as u64)) % SH_M32;
+ const s0 : u64 = sh_rotr(a, 2) ^ sh_rotr(a, 13) ^ sh_rotr(a, 22);
+ const mj : u64 = (a & b) ^ (a & c) ^ (b & c);
+ const t2 : u64 = (s0 + mj) % SH_M32;
+ hh = g;
+ g = f;
+ f = e;
+ e = (d + t1) % SH_M32;
+ d = c;
+ c = b;
+ b = a;
+ a = (t1 + t2) % SH_M32;
+ t = t + 1;
+ }
+ h[0] = (((h[0] as u64) + a) % SH_M32) as u32;
+ h[1] = (((h[1] as u64) + b) % SH_M32) as u32;
+ h[2] = (((h[2] as u64) + c) % SH_M32) as u32;
+ h[3] = (((h[3] as u64) + d) % SH_M32) as u32;
+ h[4] = (((h[4] as u64) + e) % SH_M32) as u32;
+ h[5] = (((h[5] as u64) + f) % SH_M32) as u32;
+ h[6] = (((h[6] as u64) + g) % SH_M32) as u32;
+ h[7] = (((h[7] as u64) + hh) % SH_M32) as u32;
+ return 0;
+}
+
+/// SHA-256 of msg as 64 hex digits in out[0 .. 64]. h has 8 words and w has 64 words of scratch. Returns 64, or 0 when out is short.
+pub fn sha256_hex(msg: []const u8, h: []u32, w: []u32, out: []u8) -> u32 {
+ if (out.len < 64 or h.len < 8 or w.len < 64) { return 0; }
+ h[0] = 1779033703;
+ h[1] = 3144134277;
+ h[2] = 1013904242;
+ h[3] = 2773480762;
+ h[4] = 1359893119;
+ h[5] = 2600822924;
+ h[6] = 528734635;
+ h[7] = 1541459225;
+ const n : usize = msg.len;
+ var done : usize = 0;
+ while (done + 64 <= n) {
+ sh_block(h, w, msg, done);
+ done = done + 64;
+ }
+ // the last one or two blocks: the rest, 0x80, zeros, the length in bits
+ var tail : [128]u8 = [0; 128];
+ var r : usize = n - done;
+ var k : usize = 0;
+ while (k < r) {
+ tail[k] = msg[done + k];
+ k = k + 1;
+ }
+ tail[r] = 128;
+ var total : usize = 64;
+ if (r >= 56) { total = 128; }
+ const bits : u64 = (n as u64) * 8;
+ k = 0;
+ while (k < 8) {
+ tail[total - 1 - k] = ((bits >> ((8 * k) as u64)) & 255) as u8;
+ k = k + 1;
+ }
+ sh_block(h, w, tail[0..128], 0);
+ if (total == 128) { sh_block(h, w, tail[0..128], 64); }
+ var i : usize = 0;
+ while (i < 8) {
+ var j : u64 = 0;
+ while (j < 8) {
+ out[i * 8 + (j as usize)] = sh_hexd(((h[i] as u64) >> (28 - 4 * j)) & 15);
+ j = j + 1;
+ }
+ i = i + 1;
+ }
+ return 64;
+}
+
+fn sh_same(o: []const u8, want: []const u8) -> bool {
+ if (o.len < want.len) { return false; }
+ var i : usize = 0;
+ while (i < want.len) {
+ if (o[i] != want[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+pub var SH_H : [8]u32 = [0; 8];
+pub var SH_W : [64]u32 = [0; 64];
+pub var SH_O : [64]u8 = [0; 64];
+
+test the_digest_of_nothing_and_of_abc {
+ assert sha256_hex("", SH_H[0..8], SH_W[0..64], SH_O[0..64]) == 64;
+ assert sh_same(SH_O[0..64], "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+ assert sha256_hex("abc", SH_H[0..8], SH_W[0..64], SH_O[0..64]) == 64;
+ assert sh_same(SH_O[0..64], "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
+}
+
+test a_message_that_needs_two_padding_blocks {
+ // 56 bytes: the length no longer fits behind the 0x80 byte
+ assert sha256_hex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", SH_H[0..8], SH_W[0..64], SH_O[0..64]) == 64;
+ assert sh_same(SH_O[0..64], "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
+}
+
+test a_message_of_several_blocks {
+ var big : [1000]u8 = [97; 1000];
+ assert sha256_hex(big[0..1000], SH_H[0..8], SH_W[0..64], SH_O[0..64]) == 64;
+ assert sh_same(SH_O[0..64], "41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3");
+}
From 9d74fe5e5d2c248ed908af39047ce1d8ae6f91b8 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 22:03:53 +0700
Subject: [PATCH 61/63] widgets: bit-diff drawn natively -- 55742-byte wasm,
2025 DOM comparisons, 0 differences (Closes #10938)
Page, bitstream reader (folded from bit_diff_logic.t27), two drop zones, SHA-256, the comparison
written as the same JSON the demo uses, and the frame map as a canvas_out draw list. Measured
against the original tool.js in two happy-dom windows: DOM, last canvas paint, downloads; 46
demo.json variants, XC7A200T-sized streams (9.7 MB).
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/bit_diff.t27 | 3464 ++++++++++++++++++++++++++++++++++++
1 file changed, 3464 insertions(+)
create mode 100644 specs/widgets/bit_diff.t27
diff --git a/specs/widgets/bit_diff.t27 b/specs/widgets/bit_diff.t27
new file mode 100644
index 0000000000..ec5995aeb9
--- /dev/null
+++ b/specs/widgets/bit_diff.t27
@@ -0,0 +1,3464 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/bit-diff.t27 -- two Xilinx 7-series bitstreams compared frame by frame
+; Source of truth for public/widgets/bit-diff/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: reads two bitstreams the reader drops or picks (.bit with its header, or a raw
+; .bin), in the reader's browser, with FileReader. It reads the .bit header (design, part, date,
+; time), finds the sync word, walks the type 1 and type 2 configuration packets, collects the FDRI
+; frame data, gives each 101-word frame its frame address (FAR) from the die's row and column
+; layout, and reports: byte-identical or not, the header side by side, the packet and command
+; sequence side by side, the first frame that differs with its FAR decoded, and a map of which
+; frames differ. The file never leaves the page; nothing is uploaded.
+
+; DATA PROVENANCE: the two demo pairs are summaries computed by the same parser
+; (public/widgets/bit-diff/bitparse.js) run in node by scripts/widget-data/bit-diff.mjs on the
+; real files; the bitstreams themselves are not shipped (about 9.7 MB each). Pair one is the
+; x7-board recording (t27.ai/term/x7-board/): ours.bit, written by bitwalk from frames t27 placed,
+; against trinet_node0.bit, written by openXC7's xc7frames2bit, both for xc7a200tfbg484-2. Pair two
+; is two real Ethernet builds of the AX7203 node from trinity-fpga artifacts/bitstreams (E2 PHY
+; status, E3 ARP and ICMP), so the demo also shows what a difference looks like. The row and column
+; layout (geometry.json) is read from prjxray-db ab1fc60 part.json for the four Artix-7 dies; the
+; frame mapping was checked against bitwalk's .frames file: all 20230 listed frames sit at their
+; addresses in the .bit, word for word apart from the ECC word the .bit writer fills in.
+
+; WHAT IT DOES NOT CLAIM: that two identical bitstreams were built from the same sources, or that
+; two different ones behave differently on a board. It does not read compressed (MFWR) or encrypted
+; (CBC) bitstreams, readback files, or devices other than 7-series; a part with no geometry gets
+; frames in stream order with only the first address known. Frame addresses come from prjxray-db,
+; not from AMD documentation.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_bit_diff;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::num_grouped;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+use widgets::canvas_out::cv_ok;
+use widgets::canvas_out::cv_put;
+use widgets::canvas_out::cv_raw;
+use widgets::canvas_out::cv_fin;
+use widgets::canvas_out::cv_num;
+use widgets::canvas_out::cv_str;
+use widgets::canvas_out::cv_hex;
+use widgets::canvas_out::cv_open;
+use widgets::canvas_out::cv_close;
+use widgets::canvas_out::cv_arg_f;
+use widgets::canvas_out::cv_arg_s;
+use widgets::canvas_out::cv_arg_v;
+use widgets::canvas_out::cv_arg_b;
+use widgets::canvas_out::cv_arg_list;
+use widgets::canvas_out::cv_canvas;
+use widgets::canvas_out::cv_set_num;
+use widgets::canvas_out::cv_set_str;
+use widgets::canvas_out::cv_set_var;
+use widgets::canvas_out::cv_call0;
+use widgets::canvas_out::cv_call2;
+use widgets::canvas_out::cv_call4;
+use widgets::canvas_out::cv_call6;
+use widgets::canvas_out::cv_arc;
+use widgets::canvas_out::cv_text;
+use widgets::canvas_out::cv_dash;
+use widgets::canvas_out::CV_OVER;
+use widgets::sha256_hex::SH_K;
+use widgets::sha256_hex::SH_M32;
+use widgets::sha256_hex::sh_rotr;
+use widgets::sha256_hex::sh_hexd;
+use widgets::sha256_hex::sh_block;
+use widgets::sha256_hex::sha256_hex;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "bit-diff";
+pub const TITLE : str = "Bitstream diff: two 7-series .bit files, frame by frame";
+pub const DESCRIPTION : str = "Drop two Xilinx 7-series bitstreams: header, packets and every 101-word frame compared in your browser, the first differing frame address decoded. Nothing is uploaded.";
+pub const IMAGE_ALT : str = "Bitstream diff card: t27's ours.bit against openXC7's trinet_node0.bit for the XC7A200T, byte-identical across all 24,080 configuration frames, with the same sha256 68249475b57e623f; beside it a frame map of two Ethernet builds where 590 frames differ.";
+pub const HOOK : str = "Check the byte-identical claim yourself: drop t27's .bit and openXC7's and see every frame compared, or the first one that is not.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [5]str = ["t27.ai/term/x7-board/ recording: /tmp/x7board/node0/ours.bit (bitwalk) and trinet_node0.bit (openXC7 xc7frames2bit), sha256 68249475b57e623f05564e3d839cfb0c7e7b3e21fe89a014953f12fdd53f95b3", "trinity-fpga artifacts/bitstreams: e2_eth_phy_status_zinv_t1.bit (sha256 501ab904...) and e3_eth_arp_icmp_zinv_t1.bit (sha256 e75a6ff7...)", "prjxray-db ab1fc60 artix7 part.json: rows, columns and frames per column of xc7a35t, xc7a50t, xc7a100t, xc7a200t", "AMD UG470 7 Series FPGAs Configuration: packet header, register and command codes, frame address fields", "scripts/widget-data/bit-diff.mjs: the summaries, computed by public/widgets/bit-diff/bitparse.js in node"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+; 0xAA995566, the word after which the configuration logic reads packets.
+pub const K_SYNC_WORD : u32 = 2862175590;
+pub const K_FRAME_WORDS : u8 = 101;
+; Word 50 of a frame carries the frame ECC in bits 0..12; the rest of it is configuration.
+pub const K_ECC_WORD : u8 = 50;
+pub const K_ECC_MASK : u16 = 8191;
+; Every configuration row in the FDRI stream ends with two pad frames.
+pub const K_PAD_FRAMES_PER_ROW : u8 = 2;
+; XC7A200T: IDCODE 0x03636093, five clock-region rows (two top, three bottom), each with 3660
+; CLB/IO/CLK frames and 1152 block RAM frames (prjxray-db part.json).
+pub const K_XC7A200T_IDCODE : u32 = 56844435;
+pub const K_XC7A200T_ROWS : u8 = 5;
+pub const K_XC7A200T_CLB_ROW_FRAMES : u16 = 3660;
+pub const K_XC7A200T_BRAM_ROW_FRAMES : u16 = 1152;
+pub const K_XC7A200T_FRAMES : u32 = 24080;
+; Cells per band in the frame map.
+pub const K_STRIP_BUCKETS : u16 = 160;
+; Frames compared between two pauses that let the page draw.
+pub const K_CHUNK_FRAMES : u16 = 2000;
+
+; --- Words on screen ------------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_DEMO_LABEL : str = "Real pairs, precomputed";
+pub const SAY_DEMO_IDS : [2]str = ["x7-board", "e2-e3"];
+pub const SAY_DEMO_NAMES : [2]str = ["t27 vs openXC7", "E2 vs E3 Ethernet"];
+pub const SAY_DEMO_LINES : [2]str = ["ours.bit, written by bitwalk from t27's frames, against trinet_node0.bit from openXC7's xc7frames2bit: the x7-board recording.", "Two real builds of the AX7203 node: E2 (PHY status) and E3 (ARP and ICMP), from trinity-fpga artifacts."];
+pub const SAY_DEMO_NOTE : str = "Computed in node by this page's own parser on the real files; the 9.7 MB bitstreams are not shipped. Drop them below to compute it again here.";
+pub const SAY_OWN_LABEL : str = "Your two files";
+pub const SAY_DROP_A : str = "A: drop or pick a .bit or .bin";
+pub const SAY_DROP_B : str = "B: drop or pick a .bit or .bin";
+pub const SAY_PICKED : str = "{0}: {1} bytes";
+pub const SAY_COMPARE : str = "Compare";
+pub const SAY_CLEAR : str = "Clear";
+pub const SAY_READING : str = "Reading {0}";
+pub const SAY_COMPARING : str = "Comparing frames: {0} of {1}";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab with FileReader; nothing is uploaded.";
+pub const SAY_IDENTICAL : str = "Byte-identical";
+pub const SAY_FRAMES_SAME : str = "Frames identical";
+pub const SAY_DIFFERS : str = "Not identical";
+pub const SAY_SAME_LINE : str = "{0} configuration frames, every byte the same; sha256 {1}";
+pub const SAY_FRAMES_SAME_LINE : str = "All {0} configuration frames are the same; the files differ outside them.";
+pub const SAY_DIFF_LINE : str = "{0} of {1} frames differ";
+pub const SAY_ONLY_ONE_LINE : str = "{0} frames are in one file only";
+pub const SAY_BY_POSITION : str = "Frames paired by position: no shared frame-address layout for these files.";
+pub const SAY_HEADER_TITLE : str = "Header and stream";
+pub const SAY_COL_FIELD : str = "field";
+pub const SAY_COL_A : str = "A";
+pub const SAY_COL_B : str = "B";
+pub const SAY_FIELDS : [13]str = ["file", "bytes", "sha256", "design", "part", "date", "time", "IDCODE", "sync word at byte", "frames", "FDRI writes", "bytes after DESYNC", "notes"];
+pub const SAY_NO_HEADER : str = "none (raw .bin)";
+pub const SAY_FIRST_TITLE : str = "First differing frame";
+pub const SAY_FAR_FIELDS : [6]str = ["frame address", "block type", "half", "row", "column", "minor"];
+pub const SAY_BLOCKS : [3]str = ["CLB, IO, CLK", "block RAM content", "CFG CLB"];
+pub const SAY_BLOCKS_SHORT : [3]str = ["CLB", "BRAM", "CFG"];
+pub const SAY_HALVES : [2]str = ["top", "bottom"];
+pub const SAY_FIRST_LINE : str = "Frame {0} of the stream: {1} words and {2} bits differ, {3} of those bits in the ECC field.";
+pub const SAY_ONLY_ONE_FIRST : str = "Frame {0} of the stream is in one file only.";
+pub const SAY_NO_FAR : str = "unknown";
+pub const SAY_WORD_HEAD : [3]str = ["word", "A", "B"];
+pub const SAY_MAP_TITLE : str = "Frame map";
+pub const SAY_MAP_LINE : str = "One band per configuration row, in frame-address order from left to right; each cell is a run of frames.";
+pub const SAY_LEGEND : [3]str = ["same", "differs", "in one file only"];
+pub const SAY_PACKETS_TITLE : str = "Packets and commands";
+pub const SAY_PACKETS_SAME : str = "The same {0} packet lines in both files (repeats folded).";
+pub const SAY_PACKETS_DIFF : str = "{0} packet lines differ.";
+pub const SAY_SHOW_PACKETS : str = "Show packets";
+pub const SAY_HIDE_PACKETS : str = "Hide packets";
+pub const SAY_WARN_IDS : [6]str = ["nosync", "encrypted", "compressed", "truncated", "badheader", "partialframe"];
+pub const SAY_WARN_TEXT : [6]str = ["no sync word 0xAA995566 found", "encrypted (writes CBC): frames not readable", "compressed (writes MFWR): frames not all present", "file ends inside a packet", "words that are not packet headers", "FDRI data is not a whole number of frames"];
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_DEMO : str = "The demo summary did not load.";
+
+; --- Tests ----------------------------------------------------------------------------------
+test the_sync_word_is_aa995566 {
+ assert K_SYNC_WORD == 2862175590;
+ assert K_SYNC_WORD == 170 * 16777216 + 153 * 65536 + 85 * 256 + 102;
+}
+
+test a_frame_is_101_words_and_ecc_sits_inside_it {
+ assert K_FRAME_WORDS == 101;
+ assert K_ECC_WORD < K_FRAME_WORDS;
+ assert K_ECC_MASK == 8191;
+}
+
+test the_xc7a200t_stream_holds_24080_frame_slots {
+ assert K_XC7A200T_FRAMES == K_XC7A200T_ROWS * (K_XC7A200T_CLB_ROW_FRAMES + K_PAD_FRAMES_PER_ROW) + K_XC7A200T_ROWS * (K_XC7A200T_BRAM_ROW_FRAMES + K_PAD_FRAMES_PER_ROW);
+ assert K_XC7A200T_FRAMES != K_XC7A200T_ROWS * (K_XC7A200T_CLB_ROW_FRAMES + K_XC7A200T_BRAM_ROW_FRAMES);
+ assert K_XC7A200T_IDCODE == 56844435;
+}
+
+test the_demo_opens_on_the_x7_board_pair {
+ assert SAY_DEMO_IDS[0] == "x7-board";
+ assert SAY_DEMO_IDS[1] == "e2-e3";
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+}
+
+; ---- FOLDED: synth_smells_logic.t27 lines 55-1408 (renames: none) ----
+pub const SYNC_WORD : u32 = 2862175590;
+pub const FRAME_WORDS : u32 = 101;
+pub const ECC_WORD : u32 = 50;
+pub const ECC_MASK : u32 = 8191;
+
+pub const REG_FAR : u32 = 1;
+pub const REG_FDRI : u32 = 2;
+pub const REG_CMD : u32 = 4;
+pub const REG_MFWR : u32 = 10;
+pub const REG_CBC : u32 = 11;
+pub const REG_IDCODE : u32 = 12;
+
+pub const MP : usize = 65536;
+pub const MW : usize = 16384;
+pub const MF : usize = 262144;
+
+pub const I_SIZE : usize = 0;
+pub const I_KIND : usize = 1;
+pub const I_HEND : usize = 2;
+pub const I_SYNC : usize = 3;
+pub const I_IDS : usize = 4;
+pub const I_ID : usize = 5;
+pub const I_NPK : usize = 6;
+pub const I_NFD : usize = 7;
+pub const I_WORDS : usize = 8;
+pub const I_END : usize = 9;
+pub const I_DESYNC : usize = 10;
+pub const I_FW : usize = 11;
+pub const I_FRAMES : usize = 12;
+pub const I_NW : usize = 13;
+pub const I_W0 : usize = 14;
+pub const I_HDR : usize = 20;
+pub const I_E : usize = 32;
+pub const I_MAPPED : usize = 33;
+pub const I_GEOM : usize = 34;
+
+pub var INS : [128]i64 = [0; 128];
+pub var PKO : [131072]u32 = [0; 131072];
+pub var PKT : [131072]u8 = [0; 131072];
+pub var PKP : [131072]i32 = [0; 131072];
+pub var PKR : [131072]i32 = [0; 131072];
+pub var PKC : [131072]u32 = [0; 131072];
+pub var PKD : [131072]u32 = [0; 131072];
+pub var PKH : [131072]u8 = [0; 131072];
+pub var PKV : [131072]u32 = [0; 131072];
+pub var PKX : [131072]u32 = [0; 131072];
+pub var FDO : [32768]u32 = [0; 32768];
+pub var FDW : [32768]u32 = [0; 32768];
+pub var FDN : [32768]u8 = [0; 32768];
+pub var FDF : [32768]u32 = [0; 32768];
+pub var OFFS : [524288]u32 = [0; 524288];
+pub var FARS : [524288]u32 = [0; 524288];
+pub var FARN : [524288]u8 = [0; 524288];
+pub var DIFF : [262144]u8 = [0; 262144];
+pub var CMP : [32]i64 = [0; 32];
+pub var CLW : [8]u32 = [0; 8];
+pub var CLA : [8]u32 = [0; 8];
+pub var CLB : [8]u32 = [0; 8];
+pub var FDL : [8]i64 = [0; 8];
+
+pub var GID : [8]u32 = [0; 8];
+pub var GNO : [8]u32 = [0; 8];
+pub var GNL : [8]u32 = [0; 8];
+pub var GRO : [8]u32 = [0; 8];
+pub var GRN : [8]u32 = [0; 8];
+pub var RWB : [1024]u32 = [0; 1024];
+pub var RWK : [1024]u32 = [0; 1024];
+pub var RWR : [1024]u32 = [0; 1024];
+pub var RWC : [1024]u32 = [0; 1024];
+pub var RWO : [1024]u32 = [0; 1024];
+pub var CNT : [65536]u32 = [0; 65536];
+pub var GPL : [4096]u8 = [0; 4096];
+pub var GV : [8]u64 = [0; 8];
+pub var SLOT : [262144]u32 = [0; 262144];
+pub var SLN : [262144]u8 = [0; 262144];
+pub var HTS : [524288]u32 = [0; 524288];
+pub var HTB : [524288]u32 = [0; 524288];
+pub var HTM : [524288]u32 = [0; 524288];
+pub var MIK : [262144]u32 = [0; 262144];
+pub var MIV : [262144]u32 = [0; 262144];
+pub var STC : [1048576]u8 = [0; 1048576];
+pub var STRI : [8]i64 = [0; 8];
+pub var SQB : [2097152]u8 = [0; 2097152];
+pub var SQN : [2]u64 = [0; 2];
+pub var LB : [512]u8 = [0; 512];
+pub var PREV : [512]u8 = [0; 512];
+pub var LBN : [2]u64 = [0; 2];
+pub var MAGIC : [13]u8 = [0, 9, 15, 240, 15, 240, 15, 240, 15, 240, 0, 0, 1];
+pub var DP : [4300000]u16 = [0; 4300000];
+pub var DAO : [65536]u32 = [0; 65536];
+pub var DAL : [65536]u32 = [0; 65536];
+pub var DBO : [65536]u32 = [0; 65536];
+pub var DBL : [65536]u32 = [0; 65536];
+pub var DRO : [140000]u8 = [0; 140000];
+pub var DRA : [140000]u32 = [0; 140000];
+pub var DRB : [140000]u32 = [0; 140000];
+pub var LDN : [4]u64 = [0; 4];
+
+// ---------------------------------------------------------------- bytes
+/// A byte, or 0 past the end (what JavaScript's `undefined << n` gives).
+pub fn bat(u: []const u8, i: usize) -> u64 {
+ if (i < u.len) { return u[i] as u64; }
+ return 0;
+}
+
+pub fn be32(u: []const u8, o: usize) -> u32 {
+ return (bat(u, o) * 16777216 + bat(u, o + 1) * 65536 + bat(u, o + 2) * 256 + bat(u, o + 3)) as u32;
+}
+
+/// The position of the sync word's first byte at or after `from` within `limit`, or -1.
+pub fn find_sync(u: []const u8, from: usize, limit: usize) -> i64 {
+ var end : i64 = (u.len as i64) - 3;
+ const lim : i64 = (from as i64) + (limit as i64);
+ if (lim < end) { end = lim; }
+ var i : i64 = from as i64;
+ while (i < end) {
+ const p : usize = i as usize;
+ if (u[p] == 170 and u[p + 1] == 153 and u[p + 2] == 85 and u[p + 3] == 102) { return i; }
+ i = i + 1;
+ }
+ return -1;
+}
+
+pub fn bytes_identical(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+/// FAR fields, UG470 table 5-24: f 0 block 1 bottom 2 row 3 column 4 minor.
+pub fn decode_far(far: u32, f: u32) -> u32 {
+ if (f == 0) { return (far / 8388608) % 8; }
+ if (f == 1) { return (far / 4194304) % 2; }
+ if (f == 2) { return (far / 131072) % 32; }
+ if (f == 3) { return (far / 128) % 1024; }
+ return far % 128;
+}
+
+pub fn encode_far(block: u32, bottom: u32, row: u32, column: u32, minor: u32) -> u32 {
+ return ((block % 8) * 8388608 + (bottom % 2) * 4194304 + (row % 32) * 131072 + (column % 1024) * 128 + (minor % 128)) as u32;
+}
+
+// ---------------------------------------------------------------- names and the line builder
+pub fn lb_reset() -> bool {
+ LBN[0] = 0;
+ return true;
+}
+
+pub fn lb_byte(b: u8) -> bool {
+ if (LBN[0] < 512) { LB[LBN[0] as usize] = b; }
+ LBN[0] = LBN[0] + 1;
+ return true;
+}
+
+pub fn lb_put(w: []const u8) -> bool {
+ var k : usize = 0;
+ while (k < w.len) {
+ lb_byte(w[k]);
+ k = k + 1;
+ }
+ return true;
+}
+
+pub fn lb_dec(v: u64) -> bool {
+ var digits : u64 = 1;
+ var t : u64 = v;
+ while (t >= 10) {
+ t = t / 10;
+ digits = digits * 10;
+ }
+ var d : u64 = digits;
+ var r : u64 = v;
+ while (d > 0) {
+ lb_byte((48 + r / d) as u8);
+ r = r % d;
+ d = d / 10;
+ }
+ return true;
+}
+
+pub fn hex_digit(v: u64) -> u8 {
+ if (v < 10) { return (48 + v) as u8; }
+ return (87 + v) as u8;
+}
+
+pub fn lb_hex8(v: u32) -> bool {
+ lb_put("0x");
+ var sh : u64 = 268435456;
+ var r : u64 = v as u64;
+ var k : u32 = 0;
+ while (k < 8) {
+ lb_byte(hex_digit(r / sh));
+ r = r % sh;
+ sh = sh / 16;
+ k = k + 1;
+ }
+ return true;
+}
+
+/// REG_NAMES[reg] or "REG" ("REGnull" for no register).
+pub fn lb_reg(reg: i32) -> bool {
+ if (reg < 0) { return lb_put("REGnull"); }
+ if (reg == 0) { return lb_put("CRC"); }
+ if (reg == 1) { return lb_put("FAR"); }
+ if (reg == 2) { return lb_put("FDRI"); }
+ if (reg == 3) { return lb_put("FDRO"); }
+ if (reg == 4) { return lb_put("CMD"); }
+ if (reg == 5) { return lb_put("CTL0"); }
+ if (reg == 6) { return lb_put("MASK"); }
+ if (reg == 7) { return lb_put("STAT"); }
+ if (reg == 8) { return lb_put("LOUT"); }
+ if (reg == 9) { return lb_put("COR0"); }
+ if (reg == 10) { return lb_put("MFWR"); }
+ if (reg == 11) { return lb_put("CBC"); }
+ if (reg == 12) { return lb_put("IDCODE"); }
+ if (reg == 13) { return lb_put("AXSS"); }
+ if (reg == 14) { return lb_put("COR1"); }
+ if (reg == 16) { return lb_put("WBSTAR"); }
+ if (reg == 17) { return lb_put("TIMER"); }
+ if (reg == 19) { return lb_put("RBCRC_SW"); }
+ if (reg == 22) { return lb_put("BOOTSTS"); }
+ if (reg == 24) { return lb_put("CTL1"); }
+ if (reg == 31) { return lb_put("BSPI"); }
+ lb_put("REG");
+ return lb_dec(reg as u64);
+}
+
+/// CMD_NAMES[v] ?? v
+pub fn lb_cmd(v: u32) -> bool {
+ if (v == 0) { return lb_put("NULL"); }
+ if (v == 1) { return lb_put("WCFG"); }
+ if (v == 2) { return lb_put("MFW"); }
+ if (v == 3) { return lb_put("LFRM"); }
+ if (v == 4) { return lb_put("RCFG"); }
+ if (v == 5) { return lb_put("START"); }
+ if (v == 6) { return lb_put("RCAP"); }
+ if (v == 7) { return lb_put("RCRC"); }
+ if (v == 8) { return lb_put("AGHIGH"); }
+ if (v == 9) { return lb_put("SWITCH"); }
+ if (v == 10) { return lb_put("GRESTORE"); }
+ if (v == 11) { return lb_put("SHUTDOWN"); }
+ if (v == 12) { return lb_put("GCAPTURE"); }
+ if (v == 13) { return lb_put("DESYNC"); }
+ if (v == 15) { return lb_put("IPROG"); }
+ if (v == 16) { return lb_put("CRCC"); }
+ if (v == 17) { return lb_put("LTIMER"); }
+ if (v == 18) { return lb_put("BSPI_READ"); }
+ if (v == 19) { return lb_put("FALL_EDGE"); }
+ return lb_dec(v as u64);
+}
+
+/// Fill the name buffer: kind 0 REG_NAMES[code], 1 CMD_NAMES[code], 2 BLOCK_NAMES[code]; the length, -1 when there is no such name.
+pub fn name_of(kind: u32, code: u32) -> i64 {
+ lb_reset();
+ if (kind == 0) {
+ if (code > 31) { return -1; }
+ lb_reg(code as i32);
+ if (LB[0] == 82 and LB[1] == 69 and LB[2] == 71 and LBN[0] > 3 and LB[3] >= 48 and LB[3] <= 57) { return -1; }
+ }
+ if (kind == 1) {
+ lb_cmd(code);
+ if (LB[0] >= 48 and LB[0] <= 57) { return -1; }
+ }
+ if (kind == 2) {
+ if (code == 0) { lb_put("CLB_IO_CLK"); }
+ if (code == 1) { lb_put("BLOCK_RAM"); }
+ if (code == 2) { lb_put("CFG_CLB"); }
+ if (code > 2) { return -1; }
+ }
+ return LBN[0] as i64;
+}
+
+pub fn hex8_into(v: u32) -> i64 {
+ lb_reset();
+ lb_hex8(v);
+ return LBN[0] as i64;
+}
+
+pub fn name_byte(i: u32) -> u8 {
+ return LB[i as usize];
+}
+
+// ---------------------------------------------------------------- parseBitstream
+pub fn warn(inst: usize, code: i64) -> bool {
+ const b : usize = inst * 64;
+ var k : usize = 0;
+ while (k < (INS[b + I_NW] as usize)) {
+ if (INS[b + I_W0 + k] == code) { return true; }
+ k = k + 1;
+ }
+ if (INS[b + I_NW] >= 6) { return false; }
+ INS[b + I_W0 + (INS[b + I_NW] as usize)] = code;
+ INS[b + I_NW] = INS[b + I_NW] + 1;
+ return true;
+}
+
+/// The text(start, len) of the header: bytes up to a NUL, a position past the end counts as a NUL character appended.
+pub fn header_text(u: []const u8, b: usize, slot: usize, start: usize, len: usize) -> bool {
+ var i : usize = start;
+ var inr : i64 = 0;
+ var pad : i64 = 0;
+ var stop : bool = false;
+ while (i < start + len and !stop) {
+ if (i < u.len) {
+ if (u[i] == 0) {
+ stop = true;
+ } else {
+ inr = inr + 1;
+ }
+ } else {
+ pad = pad + 1;
+ }
+ if (!stop) { i = i + 1; }
+ }
+ INS[b + I_HDR + slot * 3] = start as i64;
+ INS[b + I_HDR + slot * 3 + 1] = inr;
+ INS[b + I_HDR + slot * 3 + 2] = pad;
+ return true;
+}
+
+pub fn parse_header(u: []const u8, b: usize) -> bool {
+ var i : usize = 0;
+ while (i < 13) {
+ if (i >= u.len or u[i] != MAGIC[i]) { return false; }
+ i = i + 1;
+ }
+ var k : usize = 0;
+ while (k < 12) {
+ INS[b + I_HDR + k] = 0;
+ k = k + 1;
+ }
+ var o : usize = 13;
+ while (o < u.len and o < 4096) {
+ const key : u8 = u[o];
+ if (key == 101) {
+ INS[b + I_E] = be32(u, o + 1) as i64;
+ INS[b + I_HEND] = (o + 5) as i64;
+ return true;
+ }
+ if (key < 97 or key > 100) { return false; }
+ const len : usize = ((bat(u, o + 1) * 256) + bat(u, o + 2)) as usize;
+ header_text(u, b, (key - 97) as usize, o + 3, len);
+ o = o + 3 + len;
+ }
+ return false;
+}
+
+pub fn pk_push(inst: usize, off: usize, ty: u8, op: i32, reg: i32, count: u32, data: usize, hasv: u8, value: u32, x: u32) -> bool {
+ const b : usize = inst * 64;
+ const k : usize = INS[b + I_NPK] as usize;
+ if (k >= MP) { return false; }
+ const s : usize = inst * MP + k;
+ PKO[s] = off as u32;
+ PKT[s] = ty;
+ PKP[s] = op;
+ PKR[s] = reg;
+ PKC[s] = count;
+ PKD[s] = data as u32;
+ PKH[s] = hasv;
+ PKV[s] = value;
+ PKX[s] = x;
+ INS[b + I_NPK] = INS[b + I_NPK] + 1;
+ return true;
+}
+
+pub fn fd_push(inst: usize, off: usize, words: u32, fnull: u8, far: u32) -> bool {
+ const b : usize = inst * 64;
+ const k : usize = INS[b + I_NFD] as usize;
+ if (k >= MW) { return false; }
+ const s : usize = inst * MW + k;
+ FDO[s] = off as u32;
+ FDW[s] = words;
+ FDN[s] = fnull;
+ FDF[s] = far;
+ INS[b + I_NFD] = INS[b + I_NFD] + 1;
+ INS[b + I_FW] = INS[b + I_FW] + (words as i64);
+ return true;
+}
+
+/// parseBitstream(u8). 0 ok, -1 over a table limit.
+pub fn bs_parse(inst: u32, u: []const u8) -> i32 {
+ const ii : usize = inst as usize;
+ const b : usize = ii * 64;
+ var f : usize = 0;
+ while (f < 64) {
+ INS[b + f] = 0;
+ f = f + 1;
+ }
+ INS[b + I_SIZE] = u.len as i64;
+ INS[b + I_SYNC] = -1;
+ INS[b + I_END] = -1;
+ INS[b + I_FRAMES] = -1;
+ INS[b + I_GEOM] = -1;
+ if (parse_header(u, b)) {
+ INS[b + I_KIND] = 1;
+ } else {
+ INS[b + I_HEND] = 0;
+ }
+ const sync : i64 = find_sync(u, INS[b + I_HEND] as usize, 1048576);
+ INS[b + I_SYNC] = sync;
+ if (sync < 0) {
+ warn(ii, 1);
+ return 0;
+ }
+ var o : usize = (sync as usize) + 4;
+ var far_null : bool = true;
+ var far : u32 = 0;
+ var last_reg : i32 = -1;
+ const n : usize = u.len;
+ var brk : bool = false;
+ while (o + 4 <= n and !brk) {
+ const w : u32 = be32(u, o);
+ const ty : u32 = w / 536870912;
+ if (ty == 1) {
+ const op : u32 = (w / 134217728) % 4;
+ const reg : u32 = (w / 8192) % 16384;
+ const count : u32 = w % 2048;
+ var hasv : u8 = 0;
+ var value : u32 = 0;
+ var finish : bool = false;
+ if (op == 2 and count > 0 and o + 4 + (count as usize) * 4 <= n) {
+ if (reg == REG_CMD) {
+ value = be32(u, o + 4);
+ hasv = 1;
+ } else {
+ if (reg == REG_FAR) {
+ far = be32(u, o + 4);
+ far_null = false;
+ value = far;
+ hasv = 1;
+ } else {
+ if (count == 1) {
+ value = be32(u, o + 4);
+ hasv = 1;
+ }
+ }
+ }
+ if (reg == REG_IDCODE) {
+ if (hasv == 1) {
+ INS[b + I_IDS] = 1;
+ INS[b + I_ID] = value as i64;
+ } else {
+ INS[b + I_IDS] = 2;
+ INS[b + I_ID] = 0;
+ }
+ }
+ if (reg == REG_CBC) { warn(ii, 2); }
+ if (reg == REG_MFWR) { warn(ii, 3); }
+ if (reg == REG_FDRI) {
+ var fnl : u8 = 0;
+ if (far_null) { fnl = 1; }
+ if (!fd_push(ii, o + 4, count, fnl, far)) { return -1; }
+ }
+ if (reg == REG_CMD and value == 13) {
+ if (!pk_push(ii, o, 1, op as i32, reg as i32, count, o + 4, hasv, value, 0)) { return -1; }
+ o = o + 4 + (count as usize) * 4;
+ INS[b + I_DESYNC] = 1;
+ finish = true;
+ brk = true;
+ }
+ }
+ if (!finish) {
+ last_reg = reg as i32;
+ if (!pk_push(ii, o, 1, op as i32, reg as i32, count, o + 4, hasv, value, 0)) { return -1; }
+ if (op == 0) { o = o + 4; } else { o = o + 4 + (count as usize) * 4; }
+ }
+ } else {
+ if (ty == 2) {
+ const op : u32 = (w / 134217728) % 4;
+ const count : u32 = w % 134217728;
+ const reg : i32 = last_reg;
+ if (o + 4 + (count as usize) * 4 > n) {
+ warn(ii, 4);
+ if (!pk_push(ii, o, 2, op as i32, reg, count, o + 4, 0, 0, 0)) { return -1; }
+ brk = true;
+ } else {
+ if (op == 2 and reg == 2) {
+ var fnl : u8 = 0;
+ if (far_null) { fnl = 1; }
+ if (!fd_push(ii, o + 4, count, fnl, far)) { return -1; }
+ }
+ if (!pk_push(ii, o, 2, op as i32, reg, count, o + 4, 0, 0, 0)) { return -1; }
+ o = o + 4 + (count as usize) * 4;
+ }
+ } else {
+ if (w == 4294967295 or w == 0) {
+ if (!pk_push(ii, o, 0, 0, -1, 0, 0, 0, 0, w)) { return -1; }
+ o = o + 4;
+ } else {
+ warn(ii, 5);
+ if (!pk_push(ii, o, ty as u8, -1, -1, 0, 0, 0, 0, w)) { return -1; }
+ o = o + 4;
+ }
+ }
+ }
+ }
+ INS[b + I_END] = o as i64;
+ INS[b + I_WORDS] = (((o as i64) - sync) / 4);
+ const fw : i64 = INS[b + I_FW];
+ INS[b + I_FRAMES] = fw / 101;
+ if (fw % 101 != 0) { warn(ii, 6); }
+ return 0;
+}
+
+pub fn bs_info(inst: u32, f: u32) -> i64 {
+ return INS[(inst as usize) * 64 + (f as usize)];
+}
+
+pub fn bs_pk(inst: u32, k: u32, f: u32) -> i64 {
+ const s : usize = (inst as usize) * MP + (k as usize);
+ if (f == 0) { return PKO[s] as i64; }
+ if (f == 1) { return PKT[s] as i64; }
+ if (f == 2) { return PKP[s] as i64; }
+ if (f == 3) { return PKR[s] as i64; }
+ if (f == 4) { return PKC[s] as i64; }
+ if (f == 5) { return PKD[s] as i64; }
+ if (f == 6) { return PKH[s] as i64; }
+ if (f == 7) { return PKV[s] as i64; }
+ return PKX[s] as i64;
+}
+
+pub fn bs_fdri(inst: u32, k: u32, f: u32) -> i64 {
+ const s : usize = (inst as usize) * MW + (k as usize);
+ if (f == 0) { return FDO[s] as i64; }
+ if (f == 1) { return FDW[s] as i64; }
+ if (f == 2) { return FDN[s] as i64; }
+ return FDF[s] as i64;
+}
+
+// ---------------------------------------------------------------- packetLine and packetSequence
+/// packetLine(pk) into the line buffer; the length.
+pub fn packet_line(inst: u32, k: u32) -> i64 {
+ const s : usize = (inst as usize) * MP + (k as usize);
+ lb_reset();
+ const ty : u8 = PKT[s];
+ if (ty == 0) {
+ if (PKX[s] == 0) { lb_put("ZERO"); } else { lb_put("DUMMY"); }
+ return LBN[0] as i64;
+ }
+ const op : i32 = PKP[s];
+ if (op == -1) {
+ lb_put("WORD ");
+ lb_hex8(PKX[s]);
+ return LBN[0] as i64;
+ }
+ if (op == 0) {
+ lb_put("NOP");
+ return LBN[0] as i64;
+ }
+ const reg : i32 = PKR[s];
+ if (reg == 4 and PKH[s] == 1) {
+ lb_put("CMD ");
+ lb_cmd(PKV[s]);
+ return LBN[0] as i64;
+ }
+ if (op == 1) { lb_put("READ"); }
+ if (op == 2) { lb_put("WRITE"); }
+ if (op == 3) { lb_put("RSVD"); }
+ if (reg == 2) {
+ lb_put(" FDRI ");
+ lb_dec(PKC[s] as u64);
+ if (ty == 2) { lb_put(" (type 2)"); }
+ return LBN[0] as i64;
+ }
+ lb_put(" ");
+ lb_reg(reg);
+ if (PKH[s] == 1 and PKC[s] == 1) {
+ lb_put(" ");
+ lb_hex8(PKV[s]);
+ return LBN[0] as i64;
+ }
+ lb_put(" x");
+ lb_dec(PKC[s] as u64);
+ return LBN[0] as i64;
+}
+
+pub fn sq_put(inst: usize, b: u8) -> bool {
+ if (SQN[inst] >= 1048576) { return false; }
+ SQB[inst * 1048576 + (SQN[inst] as usize)] = b;
+ SQN[inst] = SQN[inst] + 1;
+ return true;
+}
+
+pub fn sq_flush(inst: usize, plen: usize, cnt: u64) -> bool {
+ var k : usize = 0;
+ while (k < plen) {
+ if (!sq_put(inst, PREV[k])) { return false; }
+ k = k + 1;
+ }
+ if (cnt > 1) {
+ sq_put(inst, 32);
+ sq_put(inst, 42);
+ sq_put(inst, 32);
+ var digits : u64 = 1;
+ var t : u64 = cnt;
+ while (t >= 10) {
+ t = t / 10;
+ digits = digits * 10;
+ }
+ var d : u64 = digits;
+ var r : u64 = cnt;
+ while (d > 0) {
+ sq_put(inst, (48 + r / d) as u8);
+ r = r % d;
+ d = d / 10;
+ }
+ }
+ return sq_put(inst, 10);
+}
+
+/// packetSequence(parsed): the packet lines with runs folded, each ended by byte 10, in SQB. The byte count, -1 over the limit.
+pub fn bs_seq(inst: u32) -> i64 {
+ const ii : usize = inst as usize;
+ SQN[ii] = 0;
+ const npk : usize = INS[ii * 64 + I_NPK] as usize;
+ var have : bool = false;
+ var plen : usize = 0;
+ var cnt : u64 = 0;
+ var k : usize = 0;
+ while (k < npk) {
+ const l : i64 = packet_line(inst, k as u32);
+ if (l > 512) { return -1; }
+ const ln : usize = l as usize;
+ var same : bool = false;
+ if (have and ln == plen) {
+ same = true;
+ var c : usize = 0;
+ while (c < ln) {
+ if (LB[c] != PREV[c]) { same = false; }
+ c = c + 1;
+ }
+ }
+ if (same) {
+ cnt = cnt + 1;
+ } else {
+ if (have) {
+ if (!sq_flush(ii, plen, cnt)) { return -1; }
+ }
+ var c2 : usize = 0;
+ while (c2 < ln) {
+ PREV[c2] = LB[c2];
+ c2 = c2 + 1;
+ }
+ plen = ln;
+ cnt = 1;
+ have = true;
+ }
+ k = k + 1;
+ }
+ if (have) {
+ if (!sq_flush(ii, plen, cnt)) { return -1; }
+ }
+ return SQN[ii] as i64;
+}
+
+pub fn seq_byte(inst: u32, i: u32) -> u8 {
+ return SQB[(inst as usize) * 1048576 + (i as usize)];
+}
+
+// ---------------------------------------------------------------- lineDiff
+pub fn ld_items(list: []const u8, o: bool) -> i64 {
+ var a : usize = 0;
+ var n : usize = 0;
+ while (a < list.len) {
+ var e : usize = a;
+ while (e < list.len and list[e] != 10) { e = e + 1; }
+ if (n >= 65536) { return -1; }
+ if (o) {
+ DAO[n] = a as u32;
+ DAL[n] = (e - a) as u32;
+ } else {
+ DBO[n] = a as u32;
+ DBL[n] = (e - a) as u32;
+ }
+ n = n + 1;
+ a = e + 1;
+ }
+ return n as i64;
+}
+
+pub fn ld_eq(a: []const u8, i: usize, b: []const u8, j: usize) -> bool {
+ if (DAL[i] != DBL[j]) { return false; }
+ var k : usize = 0;
+ while (k < (DAL[i] as usize)) {
+ if (a[(DAO[i] as usize) + k] != b[(DBO[j] as usize) + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// lineDiff(a, b): rows in ld_row. 0 ok, 1 null (too big: n*m > 4e6), -1 over a table limit.
+pub fn line_diff(a: []const u8, b: []const u8) -> i32 {
+ const na : i64 = ld_items(a, true);
+ const nb : i64 = ld_items(b, false);
+ if (na < 0 or nb < 0) { return -1; }
+ const n : usize = na as usize;
+ const m : usize = nb as usize;
+ if ((n as u64) * (m as u64) > 4000000) { return 1; }
+ const w : usize = m + 1;
+ if ((n + 1) * w > 4300000) { return -1; }
+ var c : usize = 0;
+ while (c < w) {
+ DP[n * w + c] = 0;
+ c = c + 1;
+ }
+ var r : usize = 0;
+ while (r <= n) {
+ DP[r * w + m] = 0;
+ r = r + 1;
+ }
+ var i : usize = n;
+ while (i > 0) {
+ i = i - 1;
+ var j : usize = m;
+ while (j > 0) {
+ j = j - 1;
+ if (ld_eq(a, i, b, j)) {
+ DP[i * w + j] = DP[(i + 1) * w + j + 1] + 1;
+ } else {
+ const x : u16 = DP[(i + 1) * w + j];
+ const y : u16 = DP[i * w + j + 1];
+ if (x > y) { DP[i * w + j] = x; } else { DP[i * w + j] = y; }
+ }
+ }
+ }
+ var p : usize = 0;
+ var q : usize = 0;
+ var rows : usize = 0;
+ while (p < n or q < m) {
+ if (rows >= 140000) { return -1; }
+ if (p < n and q < m and ld_eq(a, p, b, q)) {
+ DRO[rows] = 61;
+ DRA[rows] = p as u32;
+ DRB[rows] = q as u32;
+ p = p + 1;
+ q = q + 1;
+ } else {
+ if (q < m and (p >= n or DP[p * w + q + 1] >= DP[(p + 1) * w + q])) {
+ DRO[rows] = 43;
+ DRA[rows] = 0;
+ DRB[rows] = q as u32;
+ q = q + 1;
+ } else {
+ DRO[rows] = 45;
+ DRA[rows] = p as u32;
+ DRB[rows] = 0;
+ p = p + 1;
+ }
+ }
+ rows = rows + 1;
+ }
+ LDN[0] = rows as u64;
+ return 0;
+}
+
+pub fn ld_count() -> u64 {
+ return LDN[0];
+}
+
+/// A row of the diff: f 0 the op byte ('=', '+', '-'), 1 the index in a, 2 the index in b.
+pub fn ld_row(i: u32, f: u32) -> u32 {
+ if (f == 0) { return DRO[i as usize] as u32; }
+ if (f == 1) { return DRA[i as usize]; }
+ return DRB[i as usize];
+}
+
+// ---------------------------------------------------------------- geometry and mapFrames
+pub fn geom_begin() -> i32 {
+ var f : usize = 0;
+ while (f < 8) {
+ GV[f] = 0;
+ f = f + 1;
+ }
+ return 0;
+}
+
+/// One geometry: idcode, name, rows (i32: block, bottom, row, ncols, then the ncols frame counts, per row).
+pub fn geom_add(idcode: u32, name: []const u8, rows: []const i32) -> i32 {
+ const g : usize = GV[0] as usize;
+ if (g >= 8) { return -1; }
+ if (GV[3] + (name.len as u64) > 4096) { return -1; }
+ GID[g] = idcode;
+ GNO[g] = GV[3] as u32;
+ GNL[g] = name.len as u32;
+ var k : usize = 0;
+ while (k < name.len) {
+ GPL[(GV[3] as usize) + k] = name[k];
+ k = k + 1;
+ }
+ GV[3] = GV[3] + (name.len as u64);
+ GRO[g] = GV[1] as u32;
+ var p : usize = 0;
+ var nr : u32 = 0;
+ while (p + 4 <= rows.len) {
+ const r : usize = GV[1] as usize;
+ if (r >= 1024) { return -1; }
+ const nc : usize = rows[p + 3] as usize;
+ if (p + 4 + nc > rows.len or GV[2] + (nc as u64) > 65536) { return -1; }
+ RWB[r] = rows[p] as u32;
+ RWK[r] = rows[p + 1] as u32;
+ RWR[r] = rows[p + 2] as u32;
+ RWC[r] = nc as u32;
+ RWO[r] = GV[2] as u32;
+ var c : usize = 0;
+ while (c < nc) {
+ CNT[(GV[2] as usize) + c] = rows[p + 4 + c] as u32;
+ c = c + 1;
+ }
+ GV[2] = GV[2] + (nc as u64);
+ GV[1] = GV[1] + 1;
+ nr = nr + 1;
+ p = p + 4 + nc;
+ }
+ GRN[g] = nr;
+ GV[0] = GV[0] + 1;
+ return 0;
+}
+
+/// frameSlots(geom): the address of every frame slot, pad frames null (SLN = 1). The count, -1 over the limit.
+pub fn slots_build(g: u32) -> i64 {
+ var n : usize = 0;
+ const gi : usize = g as usize;
+ var r : usize = 0;
+ while (r < (GRN[gi] as usize)) {
+ const ri : usize = (GRO[gi] as usize) + r;
+ var c : usize = 0;
+ while (c < (RWC[ri] as usize)) {
+ const cnt : u32 = CNT[(RWO[ri] as usize) + c];
+ var mi : u32 = 0;
+ while (mi < cnt) {
+ if (n >= 262144) { return -1; }
+ SLOT[n] = encode_far(RWB[ri], RWK[ri], RWR[ri], c as u32, mi);
+ SLN[n] = 0;
+ n = n + 1;
+ mi = mi + 1;
+ }
+ c = c + 1;
+ }
+ if (n + 2 > 262144) { return -1; }
+ SLN[n] = 1;
+ SLN[n + 1] = 1;
+ SLOT[n] = 0;
+ SLOT[n + 1] = 0;
+ n = n + 2;
+ r = r + 1;
+ }
+ GV[4] = n as u64;
+ return n as i64;
+}
+
+pub fn slot_get(i: u32) -> i64 {
+ if (SLN[i as usize] == 1) { return -1; }
+ return SLOT[i as usize] as i64;
+}
+
+pub fn hslot(key: u32) -> usize {
+ return (((key as u64) * 2654435761) % 4294967296 % 524288) as usize;
+}
+
+/// mapFrames(parsed, geometries) for stream inst: offsets, addresses, mapped, geometry index.
+pub fn bs_map(inst: u32) -> i32 {
+ const ii : usize = inst as usize;
+ const b : usize = ii * 64;
+ var gi : i64 = -1;
+ if (INS[b + I_IDS] == 1) {
+ var g : usize = 0;
+ while (g < (GV[0] as usize) and gi < 0) {
+ if ((GID[g] as i64) == INS[b + I_ID]) { gi = g as i64; }
+ g = g + 1;
+ }
+ }
+ var nsl : i64 = 0;
+ if (gi >= 0) {
+ nsl = slots_build(gi as u32);
+ if (nsl < 0) { return -1; }
+ var h : usize = 0;
+ while (h < 524288) {
+ HTS[h] = 0;
+ h = h + 1;
+ }
+ var s : usize = 0;
+ while (s < (nsl as usize)) {
+ if (SLN[s] == 0) {
+ var p : usize = hslot(SLOT[s]);
+ var done : bool = false;
+ while (!done) {
+ if (HTS[p] == 0) {
+ HTS[p] = (s + 1) as u32;
+ done = true;
+ } else {
+ if (SLOT[(HTS[p] - 1) as usize] == SLOT[s]) {
+ HTS[p] = (s + 1) as u32;
+ done = true;
+ } else {
+ p = (p + 1) % 524288;
+ }
+ }
+ }
+ }
+ s = s + 1;
+ }
+ }
+ var frames : i64 = INS[b + I_FRAMES];
+ if (frames < 0) { frames = 0; }
+ if (frames > (MF as i64)) { return -1; }
+ var z : usize = 0;
+ while (z < (frames as usize)) {
+ FARN[ii * MF + z] = 1;
+ FARS[ii * MF + z] = 0;
+ OFFS[ii * MF + z] = 0;
+ z = z + 1;
+ }
+ var mapped : bool = gi >= 0;
+ var k : usize = 0;
+ var wi : usize = 0;
+ while (wi < (INS[b + I_NFD] as usize)) {
+ const s2 : usize = ii * MW + wi;
+ const nf : usize = ((FDW[s2] / 101) as usize);
+ var at : i64 = -1;
+ if (gi >= 0 and FDN[s2] == 0) {
+ var p2 : usize = hslot(FDF[s2]);
+ var found : bool = false;
+ while (HTS[p2] != 0 and !found) {
+ if (SLOT[(HTS[p2] - 1) as usize] == FDF[s2]) {
+ found = true;
+ } else {
+ p2 = (p2 + 1) % 524288;
+ }
+ }
+ if (found) { at = (HTS[p2] - 1) as i64; }
+ }
+ if (at < 0 and gi >= 0 and FDN[s2] == 0 and FDF[s2] == 0) { at = 0; }
+ if (at < 0) { mapped = false; }
+ var f : usize = 0;
+ while (f < nf and k < (frames as usize)) {
+ const q : usize = ii * MF + k;
+ OFFS[q] = FDO[s2] + (f as u32) * 404;
+ if (at >= 0 and (at as usize) + f < (nsl as usize)) {
+ const sx : usize = (at as usize) + f;
+ if (SLN[sx] == 0) {
+ FARN[q] = 0;
+ FARS[q] = SLOT[sx];
+ }
+ } else {
+ if (f == 0 and FDN[s2] == 0) {
+ FARN[q] = 0;
+ FARS[q] = FDF[s2];
+ }
+ }
+ f = f + 1;
+ k = k + 1;
+ }
+ wi = wi + 1;
+ }
+ if (mapped) { INS[b + I_MAPPED] = 1; } else { INS[b + I_MAPPED] = 0; }
+ INS[b + I_GEOM] = gi;
+ return 0;
+}
+
+pub fn bs_off(inst: u32, k: u32) -> u32 {
+ return OFFS[(inst as usize) * MF + (k as usize)];
+}
+
+pub fn bs_far(inst: u32, k: u32, f: u32) -> u32 {
+ if (f == 0) { return FARN[(inst as usize) * MF + (k as usize)] as u32; }
+ return FARS[(inst as usize) * MF + (k as usize)];
+}
+
+// ---------------------------------------------------------------- frames compared
+/// frameDelta(a, ao, b, bo): words, bits, ecc, and the first 8 differing words.
+pub fn frame_delta(a: []const u8, ao: usize, b: []const u8, bo: usize) -> bool {
+ var words : i64 = 0;
+ var bits : i64 = 0;
+ var ecc : i64 = 0;
+ var listed : usize = 0;
+ var w : usize = 0;
+ while (w < 101) {
+ const x : u32 = be32(a, ao + w * 4);
+ const y : u32 = be32(b, bo + w * 4);
+ if (x != y) {
+ words = words + 1;
+ const v : u64 = xor32(x, y) as u64;
+ if (w == 50) {
+ var e : u64 = v % 8192;
+ while (e > 0) {
+ ecc = ecc + ((e % 2) as i64);
+ e = e / 2;
+ }
+ }
+ var t : u64 = v;
+ while (t > 0) {
+ bits = bits + ((t % 2) as i64);
+ t = t / 2;
+ }
+ if (listed < 8) {
+ CLW[listed] = w as u32;
+ CLA[listed] = x;
+ CLB[listed] = y;
+ listed = listed + 1;
+ }
+ }
+ w = w + 1;
+ }
+ FDL[0] = words;
+ FDL[1] = bits;
+ FDL[2] = ecc;
+ FDL[3] = listed as i64;
+ return true;
+}
+
+pub fn xor32(x: u32, y: u32) -> u32 {
+ var r : u64 = 0;
+ var m : u64 = 1;
+ var a : u64 = x as u64;
+ var b : u64 = y as u64;
+ var k : u32 = 0;
+ while (k < 32) {
+ if ((a % 2) != (b % 2)) { r = r + m; }
+ a = a / 2;
+ b = b / 2;
+ m = m * 2;
+ k = k + 1;
+ }
+ return r as u32;
+}
+
+pub fn same_frame(a: []const u8, ao: usize, b: []const u8, bo: usize) -> bool {
+ var i : usize = 0;
+ while (i < 404) {
+ if (a[ao + i] != b[bo + i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+/// compareFrames(ua, pa, ma, ub, pb, mb): needs bs_parse and bs_map for both streams. 0 ok, -1 over a limit.
+pub fn compare_frames(ua: []const u8, ub: []const u8) -> i32 {
+ const fa : usize = INS[I_FRAMES] as usize;
+ const fb : usize = INS[64 + I_FRAMES] as usize;
+ const by_address : bool = INS[I_MAPPED] == 1 and INS[64 + I_MAPPED] == 1 and INS[I_GEOM] >= 0 and INS[64 + I_GEOM] >= 0 and same_geom_name(INS[I_GEOM] as usize, INS[64 + I_GEOM] as usize);
+ var n : usize = fa;
+ if (fb > n) { n = fb; }
+ if (n > MF) { return -1; }
+ if (by_address) {
+ var h : usize = 0;
+ while (h < 524288) {
+ HTB[h] = 0;
+ h = h + 1;
+ }
+ var i : usize = 0;
+ while (i < fb) {
+ if (FARN[MF + i] == 0) {
+ var p : usize = hslot(FARS[MF + i]);
+ var done : bool = false;
+ while (!done) {
+ if (HTB[p] == 0) {
+ HTB[p] = (i + 1) as u32;
+ done = true;
+ } else {
+ if (FARS[MF + ((HTB[p] - 1) as usize)] == FARS[MF + i]) {
+ HTB[p] = (i + 1) as u32;
+ done = true;
+ } else {
+ p = (p + 1) % 524288;
+ }
+ }
+ }
+ }
+ i = i + 1;
+ }
+ }
+ var differing : i64 = 0;
+ var only_one : i64 = 0;
+ var first : i64 = -1;
+ var i2 : usize = 0;
+ while (i2 < n) {
+ var j : i64 = i2 as i64;
+ if (by_address and i2 < fa and FARN[i2] == 0) {
+ j = pair_of(FARS[i2]);
+ }
+ if (i2 >= fa or j < 0 or j >= (fb as i64)) {
+ DIFF[i2] = 2;
+ only_one = only_one + 1;
+ if (first < 0) { first = i2 as i64; }
+ } else {
+ if (!same_frame(ua, OFFS[i2] as usize, ub, OFFS[MF + (j as usize)] as usize)) {
+ DIFF[i2] = 1;
+ differing = differing + 1;
+ if (first < 0) { first = i2 as i64; }
+ } else {
+ DIFF[i2] = 0;
+ }
+ }
+ i2 = i2 + 1;
+ }
+ CMP[0] = n as i64;
+ CMP[1] = fa as i64;
+ CMP[2] = fb as i64;
+ CMP[3] = differing;
+ CMP[4] = only_one;
+ CMP[5] = first;
+ CMP[6] = 0;
+ if (by_address) { CMP[6] = 1; }
+ CMP[7] = 0;
+ if (first >= 0) {
+ const fi : usize = first as usize;
+ CMP[8] = first;
+ CMP[9] = 1;
+ CMP[10] = 0;
+ if (fi < fa and FARN[fi] == 0) {
+ CMP[9] = 0;
+ CMP[10] = FARS[fi] as i64;
+ }
+ CMP[11] = -1;
+ CMP[12] = -1;
+ CMP[13] = -1;
+ CMP[14] = 0;
+ if (DIFF[fi] == 1) {
+ CMP[7] = 1;
+ var j2 : usize = fi;
+ if (by_address and FARN[fi] == 0) {
+ j2 = pair_of(FARS[fi]) as usize;
+ }
+ frame_delta(ua, OFFS[fi] as usize, ub, OFFS[MF + j2] as usize);
+ CMP[11] = FDL[0];
+ CMP[12] = FDL[1];
+ CMP[13] = FDL[2];
+ CMP[14] = FDL[3];
+ } else {
+ CMP[7] = 2;
+ }
+ }
+ return 0;
+}
+
+pub fn same_geom_name(a: usize, b: usize) -> bool {
+ if (GNL[a] != GNL[b]) { return false; }
+ var k : usize = 0;
+ while (k < (GNL[a] as usize)) {
+ if (GPL[(GNO[a] as usize) + k] != GPL[(GNO[b] as usize) + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// pairB.get(far): the last frame of stream B with that address, -1 when none.
+pub fn pair_of(far: u32) -> i64 {
+ var p : usize = hslot(far);
+ while (HTB[p] != 0) {
+ if (FARS[MF + ((HTB[p] - 1) as usize)] == far) { return (HTB[p] - 1) as i64; }
+ p = (p + 1) % 524288;
+ }
+ return -1;
+}
+
+pub fn cmp_info(f: u32) -> i64 {
+ return CMP[f as usize];
+}
+
+pub fn cmp_list(i: u32, f: u32) -> u32 {
+ if (f == 0) { return CLW[i as usize]; }
+ if (f == 1) { return CLA[i as usize]; }
+ return CLB[i as usize];
+}
+
+pub fn cmp_diff(i: u32) -> u8 {
+ return DIFF[i as usize];
+}
+
+// ---------------------------------------------------------------- stripOf
+/// stripOf(cmp, map, geom, buckets) for stream A's map. geom is the geometry index (-1 none).
+/// 0 ok, -1 over a limit.
+pub fn strip_of(buckets: u32, geom: i64) -> i32 {
+ if (buckets == 0 or buckets > 1024) { return -1; }
+ const nb : usize = buckets as usize;
+ const frames : usize = CMP[0] as usize;
+ if (geom < 0 or CMP[6] == 0) {
+ var c : usize = 0;
+ while (c < nb) {
+ STC[c] = 0;
+ c = c + 1;
+ }
+ const per : f64 = (frames as f64) / (buckets as f64);
+ var i : usize = 0;
+ while (i < frames) {
+ var cc : usize = ((i as f64) / per) as usize;
+ if (cc > nb - 1) { cc = nb - 1; }
+ if (DIFF[i] > STC[cc]) { STC[cc] = DIFF[i]; }
+ i = i + 1;
+ }
+ STRI[0] = 1;
+ STRI[1] = buckets as i64;
+ STRI[2] = 0;
+ return 0;
+ }
+ const gi : usize = geom as usize;
+ const nr : usize = GRN[gi] as usize;
+ if (nr * nb > 1048576) { return -1; }
+ var z : usize = 0;
+ while (z < nr * nb) {
+ STC[z] = 0;
+ z = z + 1;
+ }
+ // minorIndex: encodeFar(block, bottom, row, c, 0) -> running frame count, last entry wins
+ var h : usize = 0;
+ while (h < 524288) {
+ HTM[h] = 0;
+ h = h + 1;
+ }
+ var nm : usize = 0;
+ var r : usize = 0;
+ while (r < nr) {
+ const ri : usize = (GRO[gi] as usize) + r;
+ var s : u32 = 0;
+ var c2 : usize = 0;
+ while (c2 < (RWC[ri] as usize)) {
+ const key : u32 = encode_far(RWB[ri], RWK[ri], RWR[ri], c2 as u32, 0);
+ var p : usize = hslot(key);
+ var done : bool = false;
+ while (!done) {
+ if (HTM[p] == 0) {
+ if (nm >= 262144) { return -1; }
+ MIK[nm] = key;
+ MIV[nm] = s;
+ nm = nm + 1;
+ HTM[p] = nm as u32;
+ done = true;
+ } else {
+ if (MIK[(HTM[p] - 1) as usize] == key) {
+ MIV[(HTM[p] - 1) as usize] = s;
+ done = true;
+ } else {
+ p = (p + 1) % 524288;
+ }
+ }
+ }
+ s = s + CNT[(RWO[ri] as usize) + c2];
+ c2 = c2 + 1;
+ }
+ r = r + 1;
+ }
+ var i2 : usize = 0;
+ while (i2 < frames) {
+ if (i2 < (INS[I_FRAMES] as usize) and FARN[i2] == 0) {
+ const far : u32 = FARS[i2];
+ const rk : u32 = far / 131072;
+ // the last row whose key matches
+ var ri2 : i64 = -1;
+ var q : usize = 0;
+ while (q < nr) {
+ const rr : usize = (GRO[gi] as usize) + q;
+ const rkey : u64 = ((RWB[rr] as u64) * 64 + (RWK[rr] as u64) * 32 + (RWR[rr] as u64)) % 4294967296;
+ if (rkey == (rk as u64)) { ri2 = q as i64; }
+ q = q + 1;
+ }
+ if (ri2 >= 0) {
+ const rr2 : usize = (GRO[gi] as usize) + (ri2 as usize);
+ var total : u64 = 0;
+ var cc2 : usize = 0;
+ while (cc2 < (RWC[rr2] as usize)) {
+ total = total + (CNT[(RWO[rr2] as usize) + cc2] as u64);
+ cc2 = cc2 + 1;
+ }
+ var base : u64 = 0;
+ var p2 : usize = hslot(far - (far % 128));
+ var found : bool = false;
+ while (HTM[p2] != 0 and !found) {
+ if (MIK[(HTM[p2] - 1) as usize] == far - (far % 128)) {
+ found = true;
+ base = MIV[(HTM[p2] - 1) as usize] as u64;
+ } else {
+ p2 = (p2 + 1) % 524288;
+ }
+ }
+ const pos : u64 = base + ((far % 128) as u64);
+ var skip : bool = false;
+ var cidx : usize = nb - 1;
+ if (total == 0) {
+ if (pos == 0) { skip = true; }
+ } else {
+ const cf : f64 = ((pos as f64) / (total as f64)) * (buckets as f64);
+ const ci : u64 = cf as u64;
+ if (ci < (nb as u64) - 1) { cidx = ci as usize; }
+ }
+ if (!skip) {
+ const cell : usize = (ri2 as usize) * nb + cidx;
+ if (DIFF[i2] > STC[cell]) { STC[cell] = DIFF[i2]; }
+ }
+ }
+ }
+ i2 = i2 + 1;
+ }
+ STRI[0] = nr as i64;
+ STRI[1] = buckets as i64;
+ STRI[2] = 1;
+ STRI[3] = geom;
+ return 0;
+}
+
+pub fn strip_info(f: u32) -> i64 {
+ return STRI[f as usize];
+}
+
+/// Row metadata of the strip: f 0 has meta (1 on the geometry path), 1 block, 2 bottom, 3 row.
+pub fn strip_row(r: u32, f: u32) -> i64 {
+ if (STRI[2] == 0) { return 0; }
+ const ri : usize = (GRO[STRI[3] as usize] as usize) + (r as usize);
+ if (f == 0) { return 1; }
+ if (f == 1) { return RWB[ri] as i64; }
+ if (f == 2) { return RWK[ri] as i64; }
+ return RWR[ri] as i64;
+}
+
+pub fn strip_cell(r: u32, c: u32) -> u8 {
+ return STC[(r as usize) * (STRI[1] as usize) + (c as usize)];
+}
+
+/// compareBitstreams: parse, map and compare two streams (the geometries set), strip, and the identity and packet checks.
+/// 0 ok, 9 a stream has no sync word (not carried), -1 over a limit. cmp_info(15) = identical, (16) = packetsSame.
+pub fn compare_all(ua: []const u8, ub: []const u8, buckets: u32) -> i32 {
+ if (bs_parse(0, ua) < 0) { return -1; }
+ if (bs_parse(1, ub) < 0) { return -1; }
+ if (INS[I_FRAMES] < 0 or INS[64 + I_FRAMES] < 0) { return 9; }
+ if (bs_map(0) < 0) { return -1; }
+ if (bs_map(1) < 0) { return -1; }
+ if (compare_frames(ua, ub) < 0) { return -1; }
+ var gsel : i64 = -1;
+ if (INS[I_GEOM] >= 0) {
+ var g : usize = 0;
+ while (g < (GV[0] as usize) and gsel < 0) {
+ if (same_geom_name(g, INS[I_GEOM] as usize)) { gsel = g as i64; }
+ g = g + 1;
+ }
+ }
+ if (strip_of(buckets, gsel) < 0) { return -1; }
+ const la : i64 = bs_seq(0);
+ const lb : i64 = bs_seq(1);
+ if (la < 0 or lb < 0) { return -1; }
+ var same : bool = la == lb;
+ var k : usize = 0;
+ while (same and k < (la as usize)) {
+ if (SQB[k] != SQB[1048576 + k]) { same = false; }
+ k = k + 1;
+ }
+ CMP[16] = 0;
+ if (same) { CMP[16] = 1; }
+ CMP[15] = 0;
+ if (bytes_identical(ua, ub)) { CMP[15] = 1; }
+ return 0;
+}
+; ---- END FOLDED ----
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/bit-diff/tool.js (274 lines of hand-written JavaScript) and,
+; as the page's reader, bitparse.js. The words and numbers above are unchanged: the view prints SAY_
+; constants and uses K_ numbers, it writes no English of its own.
+; --
+; ONE VIEW, TWO SOURCES. tool.js draws one object, the comparison v, whether it comes from demo.json
+; (precomputed in node by bitparse.js) or from two files the reader drops (compareBitstreams). This page
+; keeps that shape: the view is always a JSON text. For a demo pair it is the pair's span inside
+; demo.json; for two files the page runs compare_all (the folded reader) and WRITES the same object as
+; JSON (bd_write_view) into OWJ, then draws from it. So one set of drawing rules serves both, and the
+; written JSON is checked against bitparse.js's own result for the same files.
+; --
+; THE READER IS FOLDED IN, NOT IMPORTED. specs/widgets/bit_diff_logic.t27 is the differential-tested port
+; of bitparse.js. Its tables are module-level `pub var`s and t27c's `use` does not carry a `pub var`
+; across modules, so compare_all cannot be imported. The block between the two "FOLDED" markers is that
+; module's text from `pub const SYNC_WORD` to the end of compare_all, byte for byte except the renames
+; on the marker line (names that json_scan.t27, js_num.t27 or this spec also declare).
+; --
+; Host protocol (host.js; all exports of this module):
+; w_base(url) unused (the page has no link)
+; w_init(hash) the page opens; demo.json and geometry.json are awaited
+; w_view(width, dpr, embed, canvasW, parentW) the numbers tool.js reads from the window: the frame map's clientWidth and that of its
+; section, devicePixelRatio. The host calls it before every event and again when the map's size changes.
+; w_data(i, bytes) file i of W_DATA_FILES was fetched (0 demo.json, 1 geometry.json); w_data_failed(i) when it was not
+; w_click(i) button i in document order: 0 .. 1 a demo pair, 2 Compare, 3 Clear, 4 Show / Hide packets
+; w_file(z, name, size) -> 1 | 0 a file was picked or dropped on drop zone z (0 = A, 1 = B): 1 = send its bytes
+; w_drop_n(count) -> wanted a drop of `count` files: the number of files the page wants (2 puts the first in A and the second in B)
+; w_file_data(z, bytes) / w_file_failed(z) the bytes, or "could not be read"
+; w_drag(z, over) -> 1 | 0 the drag entered (1) or left/dropped (0) zone z; 1 = the page changed
+; w_effect(k, dst) -> len 'v' + control index = set that control's value to ''
+; w_draw(dst) -> len the draw list of the frame map (canvas_out.t27 protocol 1, the "p" prefix of a css variable described below)
+; w_render(dst) the page's HTML, length (0 on overflow)
+; ONE EXTENSION OF THE DRAW LIST: an argument {"v":"--w-mono","f":"monospace","p":"10px "} is the font of the map, `'10px ' + (cssVar || 'monospace')`: the
+; host writes p first, then the variable (or its fallback). Written with cv_open / cv_raw / cv_close of canvas_out.t27.
+; Buffers (module-level, static): demo.json 262144 bytes, geometry.json 65536, the two files 16 MiB each (a bitstream of the XC7A200T is 9.7 MB), the
+; JSON of an own comparison 4 MiB, the two packet lists 1 MiB each. The folded reader has its own tables (65536 packets, 262144 frames ...).
+; Not carried over from JavaScript, said plainly:
+; - the files are read when picked (the host hands the bytes at once); tool.js read them when Compare was pressed, so a read that fails
+; shows its error at Compare, as there, but a file that changes in between is the file as of the pick;
+; - a stream with no sync word is drawn as tool.js draws it (frame counts NaN: the view carries a `nan` key for that, which only this page writes);
+; a file over 16 MiB and a comparison over a reader table limit (the folded reader returns -1) show the sentence SAY_ERR_READ with the
+; first file's name, where tool.js would run out of memory;
+; - a header string with bytes above 127 is shown as the characters U+0080 .. U+00FF it is in JavaScript (String.fromCharCode); a header
+; string that runs past the end of the file (NUL padding) is not claimed;
+; - demo.json is trusted to be shaped as bit-diff.mjs writes it: a pair without `frames`, `a` or `b`, a `fdri`, `warnings` list that is not an
+; array or a `packets` list that is not an array of lines makes the page behave as when tool.js throws (see bd_view_ok); a line with a newline
+; in it, a value over 8192 bytes, a strip digit other than 0, 1, 2 are not carried;
+; - the map is painted again every time the page is drawn (tool.js paints once per view and at every resize); the colours are those of the moment of
+; the repaint, tool.js keeps the colours of its last paint if a theme variable changes in between;
+; - the geometry file is read once; geometry.json must be an array of up to 8 devices;
+; - a duplicate key in a JSON file reads as the first one here, the last in JSON.parse.
+
+pub const W_DEMO_CAP : u32 = 262144;
+pub const W_GEOM_CAP : u32 = 65536;
+pub const W_FILE_CAP : u32 = 16777216;
+pub const W_DATA_FILES : [2]str = ["demo.json", "geometry.json"];
+pub const W_OWJ_CAP : u32 = 4194304;
+pub const W_MONO_VAR : str = "--w-mono";
+
+pub var DEMO : [262144]u8 = [0; 262144];
+pub var DEMO_LEN : u32 = 0;
+pub var DEMO_ST : u32 = 0;
+pub var GEOM : [65536]u8 = [0; 65536];
+pub var GEOM_LEN : u32 = 0;
+pub var GEOM_ST : u32 = 0;
+pub var FILE_A : [16777216]u8 = [0; 16777216];
+pub var FILE_B : [16777216]u8 = [0; 16777216];
+pub var OWJ : [4194304]u8 = [0; 4194304];
+pub var OWJ_LEN : u32 = 0;
+pub var LSA : [1048576]u8 = [0; 1048576];
+pub var LSB : [1048576]u8 = [0; 1048576];
+pub var LSA_LEN : u32 = 0;
+pub var LSB_LEN : u32 = 0;
+pub var LD_RC : i32 = 0;
+pub var HAVE : [2]bool = [false; 2];
+pub var FLEN : [2]u32 = [0; 2];
+pub var FSZ : [2]u64 = [0; 2];
+pub var FNAME : [2000]u8 = [0; 2000];
+pub var FNAME_LEN : [2]u32 = [0; 2];
+pub var FREAD : [2]u32 = [0; 2];
+pub var ZT : [4]u32 = [0; 4];
+pub var LAST_SLOT : u32 = 0;
+pub var DROP_PAIR : bool = false;
+pub var DROP_K : u32 = 0;
+pub var VIEW_SRC : u32 = 0;
+pub var VIEW_SP : i64 = 0 - 1;
+pub var DEMO_SEL : i32 = 0 - 1;
+pub var DEMO_LINE : bool = false;
+pub var ST_MODE : u32 = 0;
+pub var ST_TXT : [1100]u8 = [0; 1100];
+pub var ST_LEN : u32 = 0;
+pub var DEMO_READY : bool = false;
+pub var PK_OPEN : bool = false;
+pub var PK_HID : bool = true;
+pub var ENV_CW : f64 = 0.0;
+pub var ENV_PW : f64 = 0.0;
+pub var ENV_DPR : f64 = 1.0;
+pub var TH : bool = false;
+pub var EFF_CLEAR : u32 = 0;
+pub var SHA_H : [8]u32 = [0; 8];
+pub var SHA_W : [64]u32 = [0; 64];
+pub var SHA_A : [64]u8 = [0; 64];
+pub var SHA_B : [64]u8 = [0; 64];
+pub var TA : [8192]u8 = [0; 8192];
+pub var TB : [8192]u8 = [0; 8192];
+pub var TC : [8192]u8 = [0; 8192];
+pub var TD : [8192]u8 = [0; 8192];
+pub var CELLBUF : [262144]u8 = [0; 262144];
+pub var CELLO : [32]u32 = [0; 32];
+pub var CELLL : [32]u32 = [0; 32];
+pub var CELLK : [32]u8 = [0; 32];
+pub var GROWS : [70000]i32 = [0; 70000];
+
+pub fn w_base(u: []const u8) -> u32 {
+ return 0;
+}
+
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+fn copy_to(dst: []u8, s: []const u8) -> u32 {
+ var n : usize = s.len;
+ if (n > dst.len) { n = dst.len; }
+ var k : usize = 0;
+ while (k < n) { dst[k] = s[k]; k = k + 1; }
+ return n as u32;
+}
+
+pub fn w_view(width: f64, dpr: f64, embed: u32, cw: f64, pw: f64) -> u32 {
+ ENV_CW = cw;
+ ENV_PW = pw;
+ ENV_DPR = dpr;
+ return 0;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ DEMO_LEN = 0;
+ DEMO_ST = 0;
+ GEOM_LEN = 0;
+ GEOM_ST = 0;
+ HAVE[0] = false;
+ HAVE[1] = false;
+ FREAD[0] = 0;
+ FREAD[1] = 0;
+ ZT[0] = 0;
+ ZT[1] = 0;
+ ZT[2] = 0;
+ ZT[3] = 0;
+ VIEW_SRC = 0;
+ DEMO_SEL = 0 - 1;
+ DEMO_LINE = false;
+ ST_MODE = 0;
+ DEMO_READY = false;
+ PK_OPEN = false;
+ PK_HID = true;
+ EFF_CLEAR = 0;
+ DROP_PAIR = false;
+ DROP_K = 0;
+ return 0;
+}
+
+pub fn w_hash(h: []const u8) -> u32 {
+ EFF_CLEAR = 0;
+ return 0;
+}
+
+// --- data -----------------------------------------------------------------------------------------
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = 0;
+ var from : usize = 0;
+ if (s.len >= 3 and s[0] == 239 and s[1] == 187 and s[2] == 191) { from = 3; }
+ const len : usize = s.len - from;
+ if (i == 0) {
+ if (len > (W_DEMO_CAP as usize)) {
+ DEMO_ST = 2;
+ } else {
+ var k : usize = 0;
+ while (k < len) { DEMO[k] = s[from + k]; k = k + 1; }
+ DEMO_LEN = len as u32;
+ if (jv_valid(DEMO[0..len])) { DEMO_ST = 1; } else { DEMO_ST = 2; }
+ }
+ }
+ if (i == 1) {
+ if (len > (W_GEOM_CAP as usize)) {
+ GEOM_ST = 2;
+ } else {
+ var k : usize = 0;
+ while (k < len) { GEOM[k] = s[from + k]; k = k + 1; }
+ GEOM_LEN = len as u32;
+ if (jv_valid(GEOM[0..len])) { GEOM_ST = 1; } else { GEOM_ST = 2; }
+ }
+ }
+ start_check();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ EFF_CLEAR = 0;
+ if (i == 0) { DEMO_ST = 2; }
+ if (i == 1) { GEOM_ST = 2; }
+ start_check();
+ return 0;
+}
+
+/// The start of tool.js once both files have answered: Promise.all, then showDemo(0); a failure is SAY_ERR_DEMO.
+fn start_check() -> u32 {
+ if (DEMO_ST == 0 or GEOM_ST == 0) { return 0; }
+ if (DEMO_READY) { return 0; }
+ if (DEMO_ST == 2 or GEOM_ST == 2) {
+ ST_MODE = 1;
+ return 0;
+ }
+ DEMO_READY = true;
+ if (!show_demo(0)) { ST_MODE = 1; }
+ return 0;
+}
+
+/// pairs.find((p) => p.label === id): the span, -1 none, -2 when JavaScript throws
+fn demo_find(id: []const u8) -> i64 {
+ const d : []const u8 = DEMO[0..(DEMO_LEN as usize)];
+ const r : i64 = root(d);
+ const rk : u8 = kind(d, r);
+ if (rk == J_NULL or rk == J_NONE) { return 0 - 3; }
+ if (rk != J_OBJECT) { return 0 - 2; }
+ const ps : i64 = obj_get(d, r, "pairs");
+ if (kind(d, ps) != J_ARRAY) { return 0 - 2; }
+ const cnt : u32 = arr_len(d, ps);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const el : i64 = arr_at(d, ps, q);
+ const ek : u8 = kind(d, el);
+ if (ek == J_NULL) { return 0 - 2; }
+ if (ek == J_OBJECT) {
+ const lb : i64 = obj_get(d, el, "label");
+ if (kind(d, lb) == J_STRING and str_is(d, lb, id)) { return el; }
+ }
+ q = q + 1;
+ }
+ return 0 - 1;
+}
+
+fn str_is(d: []const u8, sp: i64, want: []const u8) -> bool {
+ if (str_len(d, sp) != (want.len as u32)) { return false; }
+ var k : u32 = 0;
+ while (k < (want.len as u32)) {
+ if (str_byte(d, sp, k) != want[k as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// showDemo(i): false when JavaScript throws (the buttons and the line are already set then)
+fn show_demo(i: u32) -> bool {
+ if (!DEMO_READY) { return true; }
+ const f : i64 = demo_find(SAY_DEMO_IDS[i as usize]);
+ if (f == 0 - 3) { return true; }
+ if (f == 0 - 2) { return false; }
+ if (f == 0 - 1) { return true; }
+ DEMO_SEL = i as i32;
+ DEMO_LINE = true;
+ ST_MODE = 0;
+ return set_view(1, f);
+}
+
+// --- the view ------------------------------------------------------------------------------------
+
+/// render(v): the view is built once in a scratch buffer (which tells whether JavaScript would throw), then kept.
+fn set_view(src: u32, sp: i64) -> bool {
+ var scratch : [262144]u8 = [0; 262144];
+ const d : []const u8 = view_bytes(src);
+ TH = false;
+ const n : u32 = view_html(scratch[0..], 0, d, sp);
+ if (TH) { return false; }
+ VIEW_SRC = src;
+ VIEW_SP = sp;
+ PK_HID = true;
+ packets_prepare(d, sp);
+ if (ld_changed() > 0 or PK_OPEN) { PK_HID = false; }
+ return true;
+}
+
+fn view_bytes(src: u32) -> []const u8 {
+ if (src == 2) { return OWJ[0..(OWJ_LEN as usize)]; }
+ return DEMO[0..(DEMO_LEN as usize)];
+}
+
+// --- reading the view (JSON) ---------------------------------------------------------------------
+
+/// x.key: undefined for a value that is no object; JavaScript throws when x is null or undefined
+fn jm(d: []const u8, sp: i64, key: []const u8) -> i64 {
+ const k : u8 = kind(d, sp);
+ if (k == J_NONE or k == J_NULL) {
+ TH = true;
+ return NO_SPAN;
+ }
+ return obj_get(d, sp, key);
+}
+
+/// x?.key
+fn jq(d: []const u8, sp: i64, key: []const u8) -> i64 {
+ return obj_get(d, sp, key);
+}
+
+fn is_nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+/// num(v): '-' for null / undefined, else Number(v).toLocaleString('en-US')
+fn num_j(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (is_nullish(d, sp)) { return put(o, n0, 45); }
+ return js_locale_num(o, n0, jv_num(d, sp));
+}
+
+/// String(v ?? '') raw into o
+fn str_j(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (is_nullish(d, sp)) { return n0; }
+ return jv_str(o, n0, d, sp);
+}
+
+fn is_number_zero(d: []const u8, sp: i64) -> bool {
+ return kind(d, sp) == J_NUMBER and jv_num(d, sp) == 0.0;
+}
+
+/// x ? ... : ... (JavaScript truthiness of a value; a missing value is false)
+fn truthy(d: []const u8, sp: i64) -> bool {
+ if (kind(d, sp) == J_NONE) { return false; }
+ return jv_truthy(d, sp);
+}
+
+fn arr_or_none(d: []const u8, sp: i64) -> bool {
+ return kind(d, sp) == J_ARRAY;
+}
+
+/// SAY_X?.[i] ?? String(v): the word of a number that indexes the list, else String(v)
+fn word_or(o: []u8, n0: u32, words_n: u32, d: []const u8, sp: i64, which: u32) -> u32 {
+ if (kind(d, sp) == J_NUMBER) {
+ const v : f64 = jv_num(d, sp);
+ if (v >= 0.0 and v < (words_n as f64) and js_floor(v) == v) {
+ const i : usize = v as usize;
+ if (which == 0) { return raw(o, n0, SAY_BLOCKS_SHORT[i]); }
+ if (which == 1) { return raw(o, n0, SAY_HALVES[i]); }
+ if (which == 2) { return raw(o, n0, SAY_BLOCKS[i]); }
+ }
+ }
+ return jv_str(o, n0, d, sp);
+}
+
+// --- the verdict ---------------------------------------------------------------------------------
+
+fn verdict_html(dst: []u8, n0: u32, d: []const u8, v: i64) -> u32 {
+ const fr : i64 = jm(d, v, "frames");
+ const a : i64 = jm(d, v, "a");
+ const differing : i64 = jm(d, fr, "differing");
+ const only_one : i64 = jm(d, fr, "onlyOne");
+ const a_frames : i64 = jm(d, a, "frames");
+ const total : i64 = jq(d, fr, "total");
+ const nan : bool = truthy(d, jq(d, fr, "nan"));
+ const identical : bool = truthy(d, jq(d, v, "identical"));
+ const same_frames : bool = is_number_zero(d, differing) and is_number_zero(d, only_one) and jv_num(d, a_frames) > 0.0;
+ var n : u32 = raw(dst, n0, "
");
+ if (identical or same_frames) { n = raw(dst, n, "="); } else { n = raw(dst, n, "!="); }
+ n = raw(dst, n, "
");
+ if (identical) { n = text(dst, n, SAY_IDENTICAL); } else { if (same_frames) { n = text(dst, n, SAY_FRAMES_SAME); } else { n = text(dst, n, SAY_DIFFERS); } }
+ n = raw(dst, n, "
");
+ var la : u32 = 0;
+ var lb : u32 = 0;
+ if (identical) {
+ if (nan) { la = raw(TA[0..], 0, "NaN"); } else { la = num_j(TA[0..], 0, d, total); }
+ // (v.a.sha256 || '').slice(0, 16)
+ const sh : i64 = jq(d, a, "sha256");
+ lb = 0;
+ if (truthy(d, sh)) { lb = str_slice16(TB[0..], d, sh); }
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_SAME_LINE, TA[0..(la as usize)], TB[0..(lb as usize)], "", 2);
+ n = raw(dst, n, "
");
+ } else {
+ if (same_frames) {
+ if (nan) { la = raw(TA[0..], 0, "NaN"); } else { la = num_j(TA[0..], 0, d, total); }
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_FRAMES_SAME_LINE, TA[0..(la as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ } else {
+ la = num_j(TA[0..], 0, d, differing);
+ if (nan) { lb = raw(TB[0..], 0, "NaN"); } else { lb = num_j(TB[0..], 0, d, total); }
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_DIFF_LINE, TA[0..(la as usize)], TB[0..(lb as usize)], "", 2);
+ n = raw(dst, n, "
");
+ if (truthy(d, only_one)) {
+ la = num_j(TA[0..], 0, d, only_one);
+ n = raw(dst, n, "
");
+ n = fill(dst, n, SAY_ONLY_ONE_LINE, TA[0..(la as usize)], "", "", 1);
+ n = raw(dst, n, "
");
+ }
+ }
+ }
+ if (!truthy(d, jq(d, fr, "byAddress")) and truthy(d, total) and !nan) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BY_POSITION);
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "
");
+}
+
+/// the first 16 UTF-16 units of the string at sp, raw, into o
+fn str_slice16(o: []u8, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) != J_STRING) { return str_j(o, 0, d, sp); }
+ var n : u32 = 0;
+ var units : u32 = 0;
+ const len : u32 = str_len(d, sp);
+ var k : u32 = 0;
+ while (k < len and units < 16) {
+ const b : u8 = str_byte(d, sp, k);
+ if (b >= 240) { units = units + 2; } else { if (b < 128 or b >= 192) { units = units + 1; } }
+ n = put(o, n, b);
+ k = k + 1;
+ // the rest of a multi-byte character
+ while (k < len and str_byte(d, sp, k) >= 128 and str_byte(d, sp, k) < 192) {
+ n = put(o, n, str_byte(d, sp, k));
+ k = k + 1;
+ }
+ }
+ return n;
+}
+
+// --- the header table ----------------------------------------------------------------------------
+
+/// the 13 values of one side s into CELLBUF slots base .. base + 12; raw says the value is compared as it is
+fn cells_of(d: []const u8, s: i64, base: u32, pos0: u32) -> u32 {
+ var pos : u32 = pos0;
+ var i : u32 = 0;
+ while (i < 13) {
+ var l : u32 = 0;
+ var raw_k : u8 = 0;
+ const dst : []u8 = CELLBUF[(pos as usize)..262144];
+ if (i == 0) { l = str_j(dst, 0, d, jm(d, s, "file")); raw_k = kind(d, jq(d, s, "file")); }
+ if (i == 1) { l = num_j(dst, 0, d, jq(d, s, "bytes")); }
+ if (i == 2) {
+ const sh : i64 = jq(d, s, "sha256");
+ if (truthy(d, sh)) { l = str_slice16(dst, d, sh); } else { l = raw(dst, 0, "-"); }
+ }
+ const hd : i64 = jq(d, s, "header");
+ const hdt : bool = truthy(d, hd);
+ if (i == 3) { if (hdt) { l = str_j(dst, 0, d, jm(d, hd, "design")); raw_k = kind(d, jq(d, hd, "design")); } else { l = raw(dst, 0, SAY_NO_HEADER); } }
+ if (i == 4) { if (hdt) { l = str_j(dst, 0, d, jq(d, hd, "part")); raw_k = kind(d, jq(d, hd, "part")); } else { l = raw(dst, 0, "-"); } }
+ if (i == 5) { if (hdt) { l = str_j(dst, 0, d, jq(d, hd, "date")); raw_k = kind(d, jq(d, hd, "date")); } else { l = raw(dst, 0, "-"); } }
+ if (i == 6) { if (hdt) { l = str_j(dst, 0, d, jq(d, hd, "time")); raw_k = kind(d, jq(d, hd, "time")); } else { l = raw(dst, 0, "-"); } }
+ if (i == 7) {
+ const ic : i64 = jq(d, s, "idcode");
+ if (is_nullish(d, ic)) { l = raw(dst, 0, "-"); } else { l = str_j(dst, 0, d, ic); raw_k = kind(d, ic); }
+ }
+ if (i == 8) { l = num_j(dst, 0, d, jq(d, s, "sync")); }
+ if (i == 9) { l = num_j(dst, 0, d, jq(d, s, "frames")); }
+ if (i == 10) { l = fdri_text(dst, d, jm(d, s, "fdri")); }
+ if (i == 11) { l = num_j(dst, 0, d, jq(d, s, "trailing")); }
+ if (i == 12) { l = warn_text(dst, d, jq(d, s, "warnings")); }
+ if (l == OVER) { l = 0; }
+ CELLO[(base + i) as usize] = pos;
+ CELLL[(base + i) as usize] = l;
+ CELLK[(base + i) as usize] = raw_k;
+ pos = pos + l;
+ i = i + 1;
+ }
+ return pos;
+}
+
+/// s.fdri.map((f) => fmt('{0} @ {1}', num(f.words), f.far ?? '-')).join(', ') || '-'
+fn fdri_text(dst: []u8, d: []const u8, fd: i64) -> u32 {
+ if (kind(d, fd) != J_ARRAY) {
+ TH = true;
+ return 0;
+ }
+ const cnt : u32 = arr_len(d, fd);
+ if (cnt == 0) { return raw(dst, 0, "-"); }
+ var n : u32 = 0;
+ var q : u32 = 0;
+ while (q < cnt) {
+ const f : i64 = arr_at(d, fd, q);
+ if (q > 0) { n = raw(dst, n, ", "); }
+ n = num_j(dst, n, d, jm(d, f, "words"));
+ n = raw(dst, n, " @ ");
+ const far : i64 = jq(d, f, "far");
+ if (is_nullish(d, far)) { n = raw(dst, n, "-"); } else { n = jv_str(dst, n, d, far); }
+ q = q + 1;
+ }
+ return n;
+}
+
+/// warnText(ids): '-' when empty, else the texts joined by '; '
+fn warn_text(dst: []u8, d: []const u8, ids: i64) -> u32 {
+ if (is_nullish(d, ids)) { return raw(dst, 0, "-"); }
+ const k : u8 = kind(d, ids);
+ if (k != J_ARRAY) {
+ // a string or an object: .length decides, .map is not there
+ if (k == J_STRING and str_len(d, ids) == 0) { return raw(dst, 0, "-"); }
+ if (k != J_STRING and k != J_OBJECT) { return raw(dst, 0, "-"); }
+ if (k == J_OBJECT) { return raw(dst, 0, "-"); }
+ TH = true;
+ return 0;
+ }
+ const cnt : u32 = arr_len(d, ids);
+ if (cnt == 0) { return raw(dst, 0, "-"); }
+ var n : u32 = 0;
+ var q : u32 = 0;
+ while (q < cnt) {
+ if (q > 0) { n = raw(dst, n, "; "); }
+ const id : i64 = arr_at(d, ids, q);
+ var hit : i32 = 0 - 1;
+ if (kind(d, id) == J_STRING) {
+ var w : usize = 0;
+ while (w < SAY_WARN_IDS.len) {
+ if (str_is(d, id, SAY_WARN_IDS[w])) { hit = w as i32; }
+ w = w + 1;
+ }
+ }
+ if (hit >= 0) { n = raw(dst, n, SAY_WARN_TEXT[hit as usize]); } else { n = jv_str(dst, n, d, id); }
+ q = q + 1;
+ }
+ return n;
+}
+
+fn cell_same(i: u32) -> bool {
+ const a : u32 = i;
+ const b : u32 = 13 + i;
+ if (CELLL[a as usize] != CELLL[b as usize]) { return false; }
+ if (CELLK[a as usize] != CELLK[b as usize]) { return false; }
+ var k : u32 = 0;
+ while (k < CELLL[a as usize]) {
+ if (CELLBUF[(CELLO[a as usize] + k) as usize] != CELLBUF[(CELLO[b as usize] + k) as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+fn cell_html(dst: []u8, n0: u32, idx: u32) -> u32 {
+ return text(dst, n0, CELLBUF[(CELLO[idx as usize] as usize)..((CELLO[idx as usize] + CELLL[idx as usize]) as usize)]);
+}
+
+fn header_html(dst: []u8, n0: u32, d: []const u8, v: i64) -> u32 {
+ const a : i64 = jm(d, v, "a");
+ const b : i64 = jm(d, v, "b");
+ var pos : u32 = cells_of(d, a, 0, 0);
+ pos = cells_of(d, b, 13, pos);
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_HEADER_TITLE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_FIELD);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_A);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_B);
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < 13) {
+ if (!cell_same(i)) { n = raw(dst, n, "
"); } else { n = raw(dst, n, "
"); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FIELDS[i as usize]);
+ n = raw(dst, n, "
");
+ n = cell_html(dst, n, i);
+ n = raw(dst, n, "
");
+ n = cell_html(dst, n, 13 + i);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+/// title: the full sha256 (h() leaves out a null / undefined / false attribute)
+fn title_attr(dst: []u8, n0: u32, d: []const u8, s: i64) -> u32 {
+ const sh : i64 = jq(d, s, "sha256");
+ const k : u8 = kind(d, sh);
+ if (k == J_NONE or k == J_NULL or k == J_FALSE) { return n0; }
+ const l : u32 = jv_str(TC[0..], 0, d, sh);
+ if (l == OVER) { return n0; }
+ return attr(dst, n0, "title", TC[0..(l as usize)]);
+}
+
+// --- the first differing frame -------------------------------------------------------------------
+
+fn first_html(dst: []u8, n0: u32, d: []const u8, v: i64) -> u32 {
+ const fr : i64 = jm(d, v, "frames");
+ const f : i64 = jq(d, fr, "first");
+ if (!truthy(d, f)) { return n0; }
+ const dec : i64 = jq(d, f, "decoded");
+ const dect : bool = truthy(d, dec);
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_FIRST_TITLE);
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < 6) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FAR_FIELDS[i as usize]);
+ n = raw(dst, n, "
");
+ var l : u32 = 0;
+ if (dect) {
+ if (i == 0) { l = jv_str(TA[0..], 0, d, jq(d, f, "far")); }
+ if (i == 1) { l = word_or(TA[0..], 0, 3, d, jq(d, dec, "block"), 2); }
+ if (i == 2) { l = word_or(TA[0..], 0, 2, d, jq(d, dec, "bottom"), 1); }
+ if (i == 3) { l = jv_str(TA[0..], 0, d, jq(d, dec, "row")); }
+ if (i == 4) { l = jv_str(TA[0..], 0, d, jq(d, dec, "column")); }
+ if (i == 5) { l = jv_str(TA[0..], 0, d, jq(d, dec, "minor")); }
+ } else {
+ if (i == 0) {
+ const far : i64 = jq(d, f, "far");
+ if (is_nullish(d, far)) { l = raw(TA[0..], 0, SAY_NO_FAR); } else { l = jv_str(TA[0..], 0, d, far); }
+ } else {
+ l = raw(TA[0..], 0, "-");
+ }
+ }
+ if (l == OVER) { l = 0; }
+ n = text(dst, n, TA[0..(l as usize)]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ const words : i64 = jq(d, f, "words");
+ var la : u32 = num_j(TA[0..], 0, d, jq(d, f, "index"));
+ if (kind(d, words) == J_NULL) {
+ n = fill(dst, n, SAY_ONLY_ONE_FIRST, TA[0..(la as usize)], "", "", 1);
+ } else {
+ const lb : u32 = num_j(TB[0..], 0, d, words);
+ const lc : u32 = num_j(TC[0..], 0, d, jq(d, f, "bits"));
+ const ld : u32 = num_j(TD[0..], 0, d, jq(d, f, "ecc"));
+ n = fill_four(dst, n, SAY_FIRST_LINE, TA[0..(la as usize)], TB[0..(lb as usize)], TC[0..(lc as usize)], TD[0..(ld as usize)]);
+ }
+ n = raw(dst, n, "
");
+ const lst : i64 = jq(d, f, "list");
+ if (truthy(d, lst) and arr_len(d, lst) > 0) {
+ n = raw(dst, n, "
");
+ var h : usize = 0;
+ while (h < SAY_WORD_HEAD.len) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_WORD_HEAD[h]);
+ n = raw(dst, n, "
");
+ h = h + 1;
+ }
+ n = raw(dst, n, "
");
+ const cnt : u32 = arr_len(d, lst);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const x : i64 = arr_at(d, lst, q);
+ n = raw(dst, n, "
");
+ var lw : u32 = str_j(TA[0..], 0, d, jm(d, x, "word"));
+ n = text(dst, n, TA[0..(lw as usize)]);
+ n = raw(dst, n, "
");
+ lw = str_j(TA[0..], 0, d, jq(d, x, "a"));
+ n = text(dst, n, TA[0..(lw as usize)]);
+ n = raw(dst, n, "
");
+ lw = str_j(TA[0..], 0, d, jq(d, x, "b"));
+ n = text(dst, n, TA[0..(lw as usize)]);
+ n = raw(dst, n, "
");
+ q = q + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "");
+}
+
+/// {0} {1} {2} {3} filled from four values (html_out.fill takes three)
+fn fill_four(o: []u8, n0: u32, t: []const u8, a: []const u8, b: []const u8, c: []const u8, e: []const u8) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] <= 51) {
+ if (t[i + 1] == 48) { n = text(o, n, a); }
+ if (t[i + 1] == 49) { n = text(o, n, b); }
+ if (t[i + 1] == 50) { n = text(o, n, c); }
+ if (t[i + 1] == 51) { n = text(o, n, e); }
+ i = i + 3;
+ } else {
+ n = put_escaped(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+// --- the frame map -------------------------------------------------------------------------------
+
+fn strip_rows(d: []const u8, v: i64) -> i64 {
+ const s : i64 = jq(d, v, "strip");
+ if (!truthy(d, s)) { return NO_SPAN; }
+ if (kind(d, s) != J_ARRAY) {
+ TH = true;
+ return NO_SPAN;
+ }
+ return s;
+}
+
+fn map_rows(d: []const u8, v: i64) -> u32 {
+ const s : i64 = strip_rows(d, v);
+ if (s < 0) { return 0; }
+ return arr_len(d, s);
+}
+
+fn map_w() -> f64 {
+ var w : f64 = 600.0;
+ if (ENV_CW != 0.0 and ENV_CW == ENV_CW) {
+ w = ENV_CW;
+ } else {
+ if (ENV_PW != 0.0 and ENV_PW == ENV_PW) { w = ENV_PW; }
+ }
+ return js_max(240.0, w);
+}
+
+fn map_dpr() -> f64 {
+ if (ENV_DPR == 0.0 or ENV_DPR != ENV_DPR) { return 1.0; }
+ return ENV_DPR;
+}
+
+fn map_band(rows: u32) -> f64 {
+ if (rows > 1) { return 16.0; }
+ return 28.0;
+}
+
+/// labelW = rows[0]?.block === null ? 0 : 64
+fn map_label(d: []const u8, v: i64) -> f64 {
+ const s : i64 = strip_rows(d, v);
+ if (s < 0 or arr_len(d, s) == 0) { return 64.0; }
+ const r0 : i64 = arr_at(d, s, 0);
+ if (kind(d, jq(d, r0, "block")) == J_NULL) { return 0.0; }
+ return 64.0;
+}
+
+fn map_html(dst: []u8, n0: u32, d: []const u8, v: i64) -> u32 {
+ const rows : u32 = map_rows(d, v);
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_MAP_TITLE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_MAP_LINE);
+ n = raw(dst, n, "
");
+ var i : usize = 0;
+ while (i < SAY_LEGEND.len) {
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_LEGEND[i]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+pub fn w_draw(dst: []u8) -> u32 {
+ if (VIEW_SRC == 0) { return 0; }
+ const d : []const u8 = view_bytes(VIEW_SRC);
+ const v : i64 = VIEW_SP;
+ const rows : u32 = map_rows(d, v);
+ const wcss : f64 = map_w();
+ const band : f64 = map_band(rows);
+ const label : f64 = map_label(d, v);
+ const hcss : f64 = (rows as f64) * (band + 4.0);
+ const dpr : f64 = map_dpr();
+ var n : u32 = cv_canvas(dst, 0, 0);
+ n = cv_call6(dst, n, "setTransform", dpr, 0.0, 0.0, dpr, 0.0, 0.0);
+ n = cv_call4(dst, n, "clearRect", 0.0, 0.0, wcss, hcss);
+ n = cv_open(dst, n, 115, "font");
+ n = cv_raw(dst, n, ",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"10px \"}");
+ n = cv_close(dst, n);
+ n = cv_set_str(dst, n, "textBaseline", "middle");
+ const s : i64 = strip_rows(d, v);
+ var ri : u32 = 0;
+ while (ri < rows) {
+ const r : i64 = arr_at(d, s, ri);
+ const y : f64 = (ri as f64) * (band + 4.0);
+ if (label != 0.0) {
+ n = cv_set_var(dst, n, "fillStyle", "--w-muted", "#adc9c0");
+ var l : u32 = word_or(TA[0..], 0, 3, d, jq(d, r, "block"), 0);
+ l = put(TA[0..], l, 32);
+ // (HALVES?.[r.bottom] ?? r.bottom).slice(0, 3)
+ var hb : [64]u8 = [0; 64];
+ const hl : u32 = word_or(hb[0..], 0, 2, d, jq(d, r, "bottom"), 1);
+ var hk : u32 = 0;
+ while (hk < hl and hk < 3) {
+ l = put(TA[0..], l, hb[hk as usize]);
+ hk = hk + 1;
+ }
+ l = put(TA[0..], l, 32);
+ l = jv_str(TA[0..], l, d, jq(d, r, "row"));
+ n = cv_text(dst, n, "fillText", TA[0..(l as usize)], 0.0, y + band / 2.0);
+ }
+ const cells : i64 = jq(d, r, "cells");
+ const cnt : u32 = str_len(d, cells);
+ const cw : f64 = (wcss - label) / (cnt as f64);
+ var c : u32 = 0;
+ while (c < cnt) {
+ const sdig : u8 = str_byte(d, cells, c);
+ if (sdig == 48) {
+ n = cv_set_str(dst, n, "fillStyle", "rgba(0,255,136,0.28)");
+ } else {
+ if (sdig == 49) { n = cv_set_var(dst, n, "fillStyle", "--w-gold", "#ffd700"); }
+ if (sdig == 50) { n = cv_set_var(dst, n, "fillStyle", "--w-red", "#ff4d6d"); }
+ }
+ var wd : f64 = cw;
+ if (cw > 3.0) { wd = cw - 1.0; }
+ n = cv_call4(dst, n, "fillRect", label + (c as f64) * cw, y, js_max(1.0, wd), band);
+ c = c + 1;
+ }
+ ri = ri + 1;
+ }
+ if (!cv_ok(n)) { return 0; }
+ return n;
+}
+
+// --- packets --------------------------------------------------------------------------------------
+
+/// the packet lists of both sides into LSA / LSB, one line each ended by LF, and their line diff
+fn packets_prepare(d: []const u8, v: i64) -> u32 {
+ const a : i64 = jq(d, v, "a");
+ const b : i64 = jq(d, v, "b");
+ LSA_LEN = list_text(LSA[0..], d, jq(d, a, "packets"));
+ LSB_LEN = list_text(LSB[0..], d, jq(d, b, "packets"));
+ LD_RC = line_diff(LSA[0..(LSA_LEN as usize)], LSB[0..(LSB_LEN as usize)]);
+ return 0;
+}
+
+fn list_text(dst: []u8, d: []const u8, ls: i64) -> u32 {
+ if (kind(d, ls) != J_ARRAY) {
+ TH = true;
+ return 0;
+ }
+ var n : u32 = 0;
+ const cnt : u32 = arr_len(d, ls);
+ var q : u32 = 0;
+ while (q < cnt) {
+ n = jv_str(dst, n, d, arr_at(d, ls, q));
+ if (n == OVER) { return 0; }
+ n = put(dst, n, 10);
+ q = q + 1;
+ }
+ return n;
+}
+
+fn ld_changed() -> u64 {
+ if (LD_RC != 0) { return 0; }
+ var c : u64 = 0;
+ var i : u64 = 0;
+ const cnt : u64 = ld_count();
+ while (i < cnt) {
+ if (ld_row(i as u32, 0) != 61) { c = c + 1; }
+ i = i + 1;
+ }
+ return c;
+}
+
+fn packets_html(dst: []u8, n0: u32, d: []const u8, v: i64) -> u32 {
+ const a : i64 = jm(d, v, "a");
+ const pa : i64 = jm(d, a, "packets");
+ if (kind(d, pa) != J_ARRAY) { TH = true; }
+ const changed : u64 = ld_changed();
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_PACKETS_TITLE);
+ n = raw(dst, n, "
");
+ if (changed > 0) {
+ const l : u32 = num_grouped(TA[0..], 0, changed);
+ n = fill(dst, n, SAY_PACKETS_DIFF, TA[0..(l as usize)], "", "", 1);
+ } else {
+ const l : u32 = num_grouped(TA[0..], 0, arr_len(d, pa) as u64);
+ n = fill(dst, n, SAY_PACKETS_SAME, TA[0..(l as usize)], "", "", 1);
+ }
+ n = raw(dst, n, "
"); n = text(dst, n, SAY_SHOW_PACKETS); } else { n = raw(dst, n, "true\">"); n = text(dst, n, SAY_HIDE_PACKETS); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_A);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_B);
+ n = raw(dst, n, "
");
+ if (LD_RC == 0) {
+ const cnt : u64 = ld_count();
+ var i : u64 = 0;
+ while (i < cnt) {
+ const op : u32 = ld_row(i as u32, 0);
+ const ia : u32 = ld_row(i as u32, 1);
+ const ib : u32 = ld_row(i as u32, 2);
+ if (op == 61) { n = raw(dst, n, "
"); } else { n = raw(dst, n, "
"); }
+ if (op != 43) { n = text(dst, n, LSA[(DAO[ia as usize] as usize)..((DAO[ia as usize] + DAL[ia as usize]) as usize)]); }
+ n = raw(dst, n, "
");
+ if (op != 45) { n = text(dst, n, LSB[(DBO[ib as usize] as usize)..((DBO[ib as usize] + DBL[ib as usize]) as usize)]); }
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ }
+ return raw(dst, n, "
");
+}
+
+// --- the whole view ------------------------------------------------------------------------------
+
+fn view_html(dst: []u8, n0: u32, d: []const u8, v: i64) -> u32 {
+ var n : u32 = verdict_html(dst, n0, d, v);
+ n = header_html(dst, n, d, v);
+ n = first_html(dst, n, d, v);
+ n = map_html(dst, n, d, v);
+ n = packets_html(dst, n, d, v);
+ return n;
+}
+
+// --- the page ------------------------------------------------------------------------------------
+
+fn btn_open(dst: []u8, n0: u32, cls: []const u8, pressed: u32, disabled: bool) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+}
+
+fn zone_html(dst: []u8, n0: u32, z: u32) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+ if (HAVE[z as usize]) {
+ var sz : [32]u8 = [0; 32];
+ const sl : u32 = num_grouped(sz[0..], 0, FSZ[z as usize]);
+ n = fill(dst, n, SAY_PICKED, FNAME[(z as usize * 1000)..((z as usize) * 1000 + (FNAME_LEN[z as usize] as usize))], sz[0..(sl as usize)], "", 2);
+ } else {
+ if (z == 0) { n = text(dst, n, SAY_DROP_A); } else { n = text(dst, n, SAY_DROP_B); }
+ }
+ return raw(dst, n, "");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_DEMO_LABEL);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < (SAY_DEMO_IDS.len as u32)) {
+ var p : u32 = 0;
+ if (DEMO_SEL == (i as i32)) { p = 1; }
+ n = btn_open(dst, n, "t27-btn", p, false);
+ n = text(dst, n, SAY_DEMO_NAMES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ if (DEMO_LINE and DEMO_SEL >= 0) {
+ n = text(dst, n, SAY_DEMO_LINES[DEMO_SEL as usize]);
+ n = text(dst, n, " ");
+ n = text(dst, n, SAY_DEMO_NOTE);
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ n = zone_html(dst, n, 0);
+ n = zone_html(dst, n, 1);
+ n = raw(dst, n, "
");
+ n = btn_open(dst, n, "t27-btn bd-go", 2, !(HAVE[0] and HAVE[1]));
+ n = text(dst, n, SAY_COMPARE);
+ n = raw(dst, n, "");
+ n = btn_open(dst, n, "t27-btn", 2, false);
+ n = text(dst, n, SAY_CLEAR);
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_LOCAL_NOTE);
+ n = raw(dst, n, "
");
+ if (ST_MODE == 1) { n = text(dst, n, SAY_ERR_DEMO); }
+ if (ST_MODE == 2) { n = text(dst, n, ST_TXT[0..(ST_LEN as usize)]); }
+ n = raw(dst, n, "
");
+ if (VIEW_SRC != 0) {
+ TH = false;
+ n = view_html(dst, n, view_bytes(VIEW_SRC), VIEW_SP);
+ }
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+// --- events ----------------------------------------------------------------------------------------
+
+fn zone_add(z: u32, tok: u32) -> u32 {
+ var t : u32 = 0;
+ while (t < 2) {
+ if (ZT[(z * 2 + t) as usize] == tok) { return 0; }
+ t = t + 1;
+ }
+ if (ZT[(z * 2) as usize] == 0) {
+ ZT[(z * 2) as usize] = tok;
+ } else {
+ ZT[(z * 2 + 1) as usize] = tok;
+ }
+ return 0;
+}
+
+fn zone_remove(z: u32, tok: u32) -> u32 {
+ if (ZT[(z * 2) as usize] == tok) {
+ ZT[(z * 2) as usize] = ZT[(z * 2 + 1) as usize];
+ ZT[(z * 2 + 1) as usize] = 0;
+ return 0;
+ }
+ if (ZT[(z * 2 + 1) as usize] == tok) { ZT[(z * 2 + 1) as usize] = 0; }
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ EFF_CLEAR = 0;
+ if (z > 1) { return 0; }
+ var has : bool = ZT[(z * 2) as usize] == 2 or ZT[(z * 2 + 1) as usize] == 2;
+ if (over != 0) {
+ if (has) { return 0; }
+ zone_add(z, 2);
+ return 1;
+ }
+ if (!has) { return 0; }
+ zone_remove(z, 2);
+ return 1;
+}
+
+/// A drop of `count` files: 2 files on two empty zones fill A and B, anything else takes the first file.
+pub fn w_drop_n(count: u32) -> u32 {
+ DROP_K = 0;
+ DROP_PAIR = false;
+ if (count >= 2 and !HAVE[0] and !HAVE[1]) {
+ DROP_PAIR = true;
+ return 2;
+ }
+ return 1;
+}
+
+pub fn w_file(z: u32, name: []const u8, size: u32) -> u32 {
+ EFF_CLEAR = 0;
+ var slot : u32 = z;
+ if (slot > 1) { slot = 1; }
+ if (DROP_PAIR) {
+ slot = DROP_K;
+ DROP_K = DROP_K + 1;
+ if (DROP_K >= 2) { DROP_PAIR = false; }
+ }
+ LAST_SLOT = slot;
+ HAVE[slot as usize] = true;
+ FREAD[slot as usize] = 0;
+ FNAME_LEN[slot as usize] = copy_to(FNAME[((slot as usize) * 1000)..((slot as usize) * 1000 + 1000)], name);
+ FSZ[slot as usize] = size as u64;
+ zone_add(slot, 1);
+ return 1;
+}
+
+pub fn w_file_data(z: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = 0;
+ const slot : u32 = LAST_SLOT;
+ if (s.len > (W_FILE_CAP as usize)) {
+ FREAD[slot as usize] = 3;
+ return 0;
+ }
+ var k : usize = 0;
+ if (slot == 0) {
+ while (k < s.len) { FILE_A[k] = s[k]; k = k + 1; }
+ } else {
+ while (k < s.len) { FILE_B[k] = s[k]; k = k + 1; }
+ }
+ FLEN[slot as usize] = s.len as u32;
+ FREAD[slot as usize] = 1;
+ return 0;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ EFF_CLEAR = 0;
+ FREAD[LAST_SLOT as usize] = 2;
+ return 0;
+}
+
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (EFF_CLEAR != 0 and k < EFF_CLEAR) {
+ dst[0] = 118;
+ dst[1] = k as u8;
+ return 2;
+ }
+ return 0;
+}
+
+pub fn w_click(i: u32) -> u32 {
+ EFF_CLEAR = 0;
+ if (i < (SAY_DEMO_IDS.len as u32)) {
+ show_demo(i);
+ return 0;
+ }
+ if (i == (SAY_DEMO_IDS.len as u32)) {
+ do_compare();
+ return 0;
+ }
+ if (i == (SAY_DEMO_IDS.len as u32) + 1) {
+ HAVE[0] = false;
+ HAVE[1] = false;
+ zone_remove(0, 1);
+ zone_remove(1, 1);
+ EFF_CLEAR = 2;
+ ST_MODE = 0;
+ return 0;
+ }
+ if (i == (SAY_DEMO_IDS.len as u32) + 2 and VIEW_SRC != 0) {
+ PK_HID = !PK_HID;
+ PK_OPEN = !PK_HID;
+ }
+ return 0;
+}
+
+fn set_status_read_error(slot: u32) -> u32 {
+ ST_MODE = 2;
+ ST_LEN = fill_plain1(ST_TXT[0..], SAY_ERR_READ, FNAME[((slot as usize) * 1000)..((slot as usize) * 1000 + (FNAME_LEN[slot as usize] as usize))]);
+ return 0;
+}
+
+/// t with {0} replaced by a, nothing escaped
+fn fill_plain1(o: []u8, t: []const u8, a: []const u8) -> u32 {
+ var n : u32 = 0;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 1] == 48 and t[i + 2] == 125) {
+ var k : usize = 0;
+ while (k < a.len) {
+ n = put(o, n, a[k]);
+ k = k + 1;
+ }
+ i = i + 3;
+ } else {
+ n = put(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ if (n == OVER) { return 0; }
+ return n;
+}
+
+fn load_geoms() -> u32 {
+ geom_begin();
+ if (GEOM_ST != 1) { return 0; }
+ const d : []const u8 = GEOM[0..(GEOM_LEN as usize)];
+ const r : i64 = root(d);
+ if (kind(d, r) != J_ARRAY) { return 0; }
+ const cnt : u32 = arr_len(d, r);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const g : i64 = arr_at(d, r, q);
+ const rows : i64 = obj_get(d, g, "rows");
+ var p : usize = 0;
+ const nr : u32 = arr_len(d, rows);
+ var ri : u32 = 0;
+ while (ri < nr) {
+ const row : i64 = arr_at(d, rows, ri);
+ const cols : i64 = arr_at(d, row, 3);
+ const nc : u32 = arr_len(d, cols);
+ if (p + 4 + (nc as usize) > 70000) { return 0; }
+ GROWS[p] = num_i64(d, arr_at(d, row, 0)) as i32;
+ GROWS[p + 1] = num_i64(d, arr_at(d, row, 1)) as i32;
+ GROWS[p + 2] = num_i64(d, arr_at(d, row, 2)) as i32;
+ GROWS[p + 3] = nc as i32;
+ var c : u32 = 0;
+ while (c < nc) {
+ GROWS[p + 4 + (c as usize)] = num_i64(d, arr_at(d, cols, c)) as i32;
+ c = c + 1;
+ }
+ p = p + 4 + (nc as usize);
+ ri = ri + 1;
+ }
+ const nm : i64 = obj_get(d, g, "name");
+ var name : [200]u8 = [0; 200];
+ var nl : u32 = 0;
+ if (kind(d, nm) == J_STRING) {
+ const sl : u32 = str_len(d, nm);
+ var k : u32 = 0;
+ while (k < sl and k < 200) {
+ name[k as usize] = str_byte(d, nm, k);
+ k = k + 1;
+ }
+ nl = k;
+ }
+ geom_add(num_i64(d, obj_get(d, g, "idcode")) as u32, name[0..(nl as usize)], GROWS[0..p]);
+ q = q + 1;
+ }
+ return 0;
+}
+
+fn do_compare() -> u32 {
+ if (!(HAVE[0] and HAVE[1])) { return 0; }
+ var slot : u32 = 0;
+ while (slot < 2) {
+ if (FREAD[slot as usize] == 2 or FREAD[slot as usize] == 3) {
+ set_status_read_error(slot);
+ return 0;
+ }
+ slot = slot + 1;
+ }
+ load_geoms();
+ const ua : []const u8 = FILE_A[0..(FLEN[0] as usize)];
+ const ub : []const u8 = FILE_B[0..(FLEN[1] as usize)];
+ const rc : i32 = compare_all(ua, ub, 160);
+ if (rc != 0 and rc != 9) {
+ set_status_read_error(0);
+ return 0;
+ }
+ if (rc == 9) {
+ // a stream with no sync word: tool.js draws a view whose frame counts are NaN; the sides still have their packets and map
+ if (bs_info(0, 3) >= 0) { bs_map(0); }
+ if (bs_info(1, 3) >= 0) { bs_map(1); }
+ if (bs_seq(0) < 0 or bs_seq(1) < 0) {
+ set_status_read_error(0);
+ return 0;
+ }
+ }
+ sha256_hex(ua, SHA_H[0..8], SHA_W[0..64], SHA_A[0..64]);
+ sha256_hex(ub, SHA_H[0..8], SHA_W[0..64], SHA_B[0..64]);
+ OWJ_LEN = bd_write_view(ua, ub, rc == 9);
+ if (OWJ_LEN == OVER) {
+ set_status_read_error(0);
+ return 0;
+ }
+ const d : []const u8 = OWJ[0..(OWJ_LEN as usize)];
+ if (!set_view(2, root(d))) {
+ set_status_read_error(0);
+ return 0;
+ }
+ DEMO_SEL = 0 - 1;
+ DEMO_LINE = false;
+ ST_MODE = 0;
+ return 0;
+}
+
+// --- the comparison as JSON (what compareBitstreams returns) -----------------------------------
+
+fn jw_raw(o: []u8, n: u32, s: []const u8) -> u32 {
+ return raw(o, n, s);
+}
+
+fn jw_u(o: []u8, n: u32, v: u64) -> u32 {
+ return num(o, n, v);
+}
+
+/// a JSON string from UTF-8 bytes
+fn jw_utf8(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = put(o, n0, 34);
+ var i : usize = 0;
+ while (i < s.len) {
+ n = jw_byte(o, n, s[i]);
+ i = i + 1;
+ }
+ return put(o, n, 34);
+}
+
+fn jw_byte(o: []u8, n0: u32, b: u8) -> u32 {
+ var n : u32 = n0;
+ if (b == 34 or b == 92) {
+ n = put(o, n, 92);
+ return put(o, n, b);
+ }
+ if (b < 32) {
+ n = raw(o, n, "\\u00");
+ n = put(o, n, sh_hexd((b / 16) as u64));
+ return put(o, n, sh_hexd((b % 16) as u64));
+ }
+ return put(o, n, b);
+}
+
+/// a JSON string from the bytes of a header field: each byte is the character U+00xx (String.fromCharCode), then `pad` NULs
+fn jw_latin1(o: []u8, n0: u32, u: []const u8, start: usize, len: usize, pad: usize) -> u32 {
+ var n : u32 = put(o, n0, 34);
+ var i : usize = 0;
+ while (i < len) {
+ const b : u8 = u[start + i];
+ if (b >= 128) {
+ n = raw(o, n, "\\u00");
+ n = put(o, n, sh_hexd((b / 16) as u64));
+ n = put(o, n, sh_hexd((b % 16) as u64));
+ } else {
+ n = jw_byte(o, n, b);
+ }
+ i = i + 1;
+ }
+ i = 0;
+ while (i < pad) {
+ n = raw(o, n, "\\u0000");
+ i = i + 1;
+ }
+ return put(o, n, 34);
+}
+
+fn jw_hex8(o: []u8, n0: u32, v: u32) -> u32 {
+ var n : u32 = raw(o, n0, "\"0x");
+ var k : u64 = 0;
+ while (k < 8) {
+ n = put(o, n, sh_hexd(((v as u64) >> (28 - 4 * k)) & 15));
+ k = k + 1;
+ }
+ return put(o, n, 34);
+}
+
+fn jw_bool(o: []u8, n: u32, b: bool) -> u32 {
+ if (b) { return raw(o, n, "true"); }
+ return raw(o, n, "false");
+}
+
+fn jw_side(o: []u8, n0: u32, inst: u32, u: []const u8, sha: []const u8) -> u32 {
+ var n : u32 = raw(o, n0, "{\"file\":");
+ n = jw_utf8(o, n, FNAME[((inst as usize) * 1000)..((inst as usize) * 1000 + (FNAME_LEN[inst as usize] as usize))]);
+ n = raw(o, n, ",\"bytes\":");
+ n = jw_u(o, n, bs_info(inst, 0) as u64);
+ n = raw(o, n, ",\"sha256\":");
+ n = jw_utf8(o, n, sha);
+ n = raw(o, n, ",\"kind\":");
+ if (bs_info(inst, 1) == 1) { n = raw(o, n, "\"bit\""); } else { n = raw(o, n, "\"bin\""); }
+ n = raw(o, n, ",\"header\":");
+ if (bs_info(inst, 1) != 1) {
+ n = raw(o, n, "null");
+ } else {
+ n = raw(o, n, "{\"design\":");
+ n = jw_latin1(o, n, u, bs_info(inst, 20) as usize, bs_info(inst, 21) as usize, bs_info(inst, 22) as usize);
+ n = raw(o, n, ",\"part\":");
+ n = jw_latin1(o, n, u, bs_info(inst, 23) as usize, bs_info(inst, 24) as usize, bs_info(inst, 25) as usize);
+ n = raw(o, n, ",\"date\":");
+ n = jw_latin1(o, n, u, bs_info(inst, 26) as usize, bs_info(inst, 27) as usize, bs_info(inst, 28) as usize);
+ n = raw(o, n, ",\"time\":");
+ n = jw_latin1(o, n, u, bs_info(inst, 29) as usize, bs_info(inst, 30) as usize, bs_info(inst, 31) as usize);
+ n = raw(o, n, ",\"length\":");
+ n = jw_u(o, n, bs_info(inst, 32) as u64);
+ n = raw(o, n, "}");
+ }
+ n = raw(o, n, ",\"sync\":");
+ if (bs_info(inst, 3) < 0) { n = raw(o, n, "-1"); } else { n = jw_u(o, n, bs_info(inst, 3) as u64); }
+ n = raw(o, n, ",\"idcode\":");
+ const ids : i64 = bs_info(inst, 4);
+ if (ids == 0) { n = raw(o, n, "null"); }
+ if (ids == 1) { n = jw_hex8(o, n, bs_info(inst, 5) as u32); }
+ if (ids == 2) { n = jw_hex8(o, n, 0); }
+ if (bs_info(inst, 12) >= 0) {
+ n = raw(o, n, ",\"frames\":");
+ n = jw_u(o, n, bs_info(inst, 12) as u64);
+ }
+ n = raw(o, n, ",\"fdri\":[");
+ const nf : u32 = bs_info(inst, 7) as u32;
+ var k : u32 = 0;
+ while (k < nf) {
+ if (k > 0) { n = put(o, n, 44); }
+ n = raw(o, n, "{\"words\":");
+ n = jw_u(o, n, bs_fdri(inst, k, 1) as u64);
+ n = raw(o, n, ",\"far\":");
+ if (bs_fdri(inst, k, 2) != 0) { n = raw(o, n, "null"); } else { n = jw_hex8(o, n, bs_fdri(inst, k, 3) as u32); }
+ n = raw(o, n, "}");
+ k = k + 1;
+ }
+ n = raw(o, n, "],\"trailing\":");
+ if (bs_info(inst, 9) < 0) { n = raw(o, n, "null"); } else { n = jw_u(o, n, (bs_info(inst, 0) - bs_info(inst, 9)) as u64); }
+ n = raw(o, n, ",\"warnings\":[");
+ const nw : u32 = bs_info(inst, 13) as u32;
+ k = 0;
+ while (k < nw) {
+ if (k > 0) { n = put(o, n, 44); }
+ const code : i64 = bs_info(inst, 14 + k);
+ n = put(o, n, 34);
+ n = raw(o, n, SAY_WARN_IDS[(code - 1) as usize]);
+ n = put(o, n, 34);
+ k = k + 1;
+ }
+ n = raw(o, n, "],\"mapped\":");
+ n = jw_bool(o, n, bs_info(inst, 33) == 1);
+ n = raw(o, n, ",\"geometry\":");
+ const gi : i64 = bs_info(inst, 34);
+ if (gi < 0) {
+ n = raw(o, n, "null");
+ } else {
+ n = jw_utf8(o, n, GPL[(GNO[gi as usize] as usize)..((GNO[gi as usize] + GNL[gi as usize]) as usize)]);
+ }
+ n = raw(o, n, ",\"packets\":[");
+ const sl : usize = SQN[inst as usize] as usize;
+ var q : usize = 0;
+ var first : bool = true;
+ while (q < sl) {
+ var e : usize = q;
+ while (e < sl and SQB[(inst as usize) * 1048576 + e] != 10) { e = e + 1; }
+ if (!first) { n = put(o, n, 44); }
+ first = false;
+ n = put(o, n, 34);
+ var c : usize = q;
+ while (c < e) {
+ n = jw_byte(o, n, SQB[(inst as usize) * 1048576 + c]);
+ c = c + 1;
+ }
+ n = put(o, n, 34);
+ q = e + 1;
+ }
+ return raw(o, n, "]}");
+}
+
+fn bd_write_view(ua: []const u8, ub: []const u8, nan: bool) -> u32 {
+ var n : u32 = raw(OWJ[0..], 0, "{\"label\":\"own\",\"identical\":");
+ if (nan) { n = jw_bool(OWJ[0..], n, bytes_identical(ua, ub)); } else { n = jw_bool(OWJ[0..], n, cmp_info(15) == 1); }
+ n = raw(OWJ[0..], n, ",\"a\":");
+ n = jw_side(OWJ[0..], n, 0, ua, SHA_A[0..64]);
+ n = raw(OWJ[0..], n, ",\"b\":");
+ n = jw_side(OWJ[0..], n, 1, ub, SHA_B[0..64]);
+ if (nan) {
+ n = raw(OWJ[0..], n, ",\"frames\":{\"total\":0,\"nan\":true,\"differing\":0,\"onlyOne\":0,\"byAddress\":false,\"first\":null},\"packetsSame\":");
+ n = jw_bool(OWJ[0..], n, seq_same());
+ n = raw(OWJ[0..], n, ",\"strip\":[{\"block\":null,\"bottom\":null,\"row\":null,\"cells\":\"");
+ var z : u32 = 0;
+ while (z < 160) {
+ n = put(OWJ[0..], n, 48);
+ z = z + 1;
+ }
+ return raw(OWJ[0..], n, "\"}]}");
+ }
+ n = raw(OWJ[0..], n, ",\"frames\":{\"total\":");
+ n = jw_u(OWJ[0..], n, cmp_info(0) as u64);
+ n = raw(OWJ[0..], n, ",\"differing\":");
+ n = jw_u(OWJ[0..], n, cmp_info(3) as u64);
+ n = raw(OWJ[0..], n, ",\"onlyOne\":");
+ n = jw_u(OWJ[0..], n, cmp_info(4) as u64);
+ n = raw(OWJ[0..], n, ",\"byAddress\":");
+ n = jw_bool(OWJ[0..], n, cmp_info(6) == 1);
+ n = raw(OWJ[0..], n, ",\"first\":");
+ const fk : i64 = cmp_info(7);
+ if (fk == 0) {
+ n = raw(OWJ[0..], n, "null");
+ } else {
+ n = raw(OWJ[0..], n, "{\"index\":");
+ n = jw_u(OWJ[0..], n, cmp_info(8) as u64);
+ n = raw(OWJ[0..], n, ",\"far\":");
+ if (cmp_info(9) == 1) {
+ n = raw(OWJ[0..], n, "null,\"decoded\":null");
+ } else {
+ const far : u32 = cmp_info(10) as u32;
+ n = jw_hex8(OWJ[0..], n, far);
+ n = raw(OWJ[0..], n, ",\"decoded\":{\"far\":");
+ n = jw_u(OWJ[0..], n, far as u64);
+ n = raw(OWJ[0..], n, ",\"block\":");
+ n = jw_u(OWJ[0..], n, decode_far(far, 0) as u64);
+ n = raw(OWJ[0..], n, ",\"bottom\":");
+ n = jw_u(OWJ[0..], n, decode_far(far, 1) as u64);
+ n = raw(OWJ[0..], n, ",\"row\":");
+ n = jw_u(OWJ[0..], n, decode_far(far, 2) as u64);
+ n = raw(OWJ[0..], n, ",\"column\":");
+ n = jw_u(OWJ[0..], n, decode_far(far, 3) as u64);
+ n = raw(OWJ[0..], n, ",\"minor\":");
+ n = jw_u(OWJ[0..], n, decode_far(far, 4) as u64);
+ n = raw(OWJ[0..], n, "}");
+ }
+ if (fk == 1) {
+ n = raw(OWJ[0..], n, ",\"words\":");
+ n = jw_u(OWJ[0..], n, cmp_info(11) as u64);
+ n = raw(OWJ[0..], n, ",\"bits\":");
+ n = jw_u(OWJ[0..], n, cmp_info(12) as u64);
+ n = raw(OWJ[0..], n, ",\"ecc\":");
+ n = jw_u(OWJ[0..], n, cmp_info(13) as u64);
+ } else {
+ n = raw(OWJ[0..], n, ",\"words\":null,\"bits\":null,\"ecc\":null");
+ }
+ n = raw(OWJ[0..], n, ",\"list\":[");
+ if (fk == 1) {
+ const nl : u32 = cmp_info(14) as u32;
+ var k : u32 = 0;
+ while (k < nl) {
+ if (k > 0) { n = put(OWJ[0..], n, 44); }
+ n = raw(OWJ[0..], n, "{\"word\":");
+ n = jw_u(OWJ[0..], n, cmp_list(k, 0) as u64);
+ n = raw(OWJ[0..], n, ",\"a\":");
+ n = jw_hex8(OWJ[0..], n, cmp_list(k, 1));
+ n = raw(OWJ[0..], n, ",\"b\":");
+ n = jw_hex8(OWJ[0..], n, cmp_list(k, 2));
+ n = raw(OWJ[0..], n, "}");
+ k = k + 1;
+ }
+ }
+ n = raw(OWJ[0..], n, "]}");
+ }
+ n = raw(OWJ[0..], n, "},\"packetsSame\":");
+ n = jw_bool(OWJ[0..], n, cmp_info(16) == 1);
+ n = raw(OWJ[0..], n, ",\"strip\":[");
+ const nr : u32 = strip_info(0) as u32;
+ const nb : u32 = strip_info(1) as u32;
+ var r : u32 = 0;
+ while (r < nr) {
+ if (r > 0) { n = put(OWJ[0..], n, 44); }
+ if (strip_row(r, 0) == 1) {
+ n = raw(OWJ[0..], n, "{\"block\":");
+ n = jw_u(OWJ[0..], n, strip_row(r, 1) as u64);
+ n = raw(OWJ[0..], n, ",\"bottom\":");
+ n = jw_u(OWJ[0..], n, strip_row(r, 2) as u64);
+ n = raw(OWJ[0..], n, ",\"row\":");
+ n = jw_u(OWJ[0..], n, strip_row(r, 3) as u64);
+ } else {
+ n = raw(OWJ[0..], n, "{\"block\":null,\"bottom\":null,\"row\":null");
+ }
+ n = raw(OWJ[0..], n, ",\"cells\":\"");
+ var c : u32 = 0;
+ while (c < nb) {
+ n = put(OWJ[0..], n, 48 + strip_cell(r, c));
+ c = c + 1;
+ }
+ n = raw(OWJ[0..], n, "\"}");
+ r = r + 1;
+ }
+ n = raw(OWJ[0..], n, "]}");
+ return n;
+}
+
+/// packetSequence(a) equals packetSequence(b)
+fn seq_same() -> bool {
+ if (SQN[0] != SQN[1]) { return false; }
+ var k : usize = 0;
+ while (k < (SQN[0] as usize)) {
+ if (SQB[k] != SQB[1048576 + k]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+// --- Tests of the native page -------------------------------------------------------------------------
+
+fn has(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (n < (want.len as u32)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+pub const W_TEST_DEMO : str = "{\"schema\":1,\"pairs\":[{\"label\":\"x7-board\",\"identical\":true,\"a\":{\"file\":\"ours.bit\",\"bytes\":12,\"sha256\":\"68249475b57e623f05564e3d839cfb0c7e7b3e21fe89a014953f12fdd53f95b3\",\"header\":null,\"sync\":4,\"idcode\":\"0x03636093\",\"frames\":24080,\"fdri\":[{\"words\":5,\"far\":\"0x00000000\"}],\"trailing\":2,\"warnings\":[\"truncated\"],\"packets\":[\"NOP\",\"CMD DESYNC\"]},\"b\":{\"file\":\"b.bit\",\"bytes\":12,\"sha256\":\"68249475b57e623f05564e3d839cfb0c7e7b3e21fe89a014953f12fdd53f95b3\",\"header\":null,\"sync\":4,\"idcode\":\"0x03636093\",\"frames\":24080,\"fdri\":[],\"trailing\":2,\"warnings\":[],\"packets\":[\"NOP\",\"CMD RCRC\",\"CMD DESYNC\"]},\"frames\":{\"total\":24080,\"differing\":0,\"onlyOne\":0,\"byAddress\":true,\"first\":null},\"strip\":[{\"block\":0,\"bottom\":1,\"row\":2,\"cells\":\"0120\"}]}]}";
+
+fn open_page() -> u32 {
+ w_init("");
+ w_data(0, W_TEST_DEMO);
+ w_data(1, "[]");
+ return 0;
+}
+
+test the_demo_pair_is_drawn_from_demo_json {
+ var o : [262144]u8 = [0; 262144];
+ open_page();
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
=
Byte-identical
24,080 configuration frames, every byte the same; sha256 68249475b57e623f
");
+ assert has(o[0..], n, "
FDRI writes
5 @ 0x00000000
-
");
+ assert has(o[0..], n, "
notes
file ends inside a packet
-
");
+ assert has(o[0..], n, "
68249475b57e623f
");
+ assert has(o[0..], n, "
sync word at byte
4
4
");
+}
+
+test the_packet_lines_are_diffed_and_the_list_opens_when_they_differ {
+ var o : [262144]u8 = [0; 262144];
+ open_page();
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
1 packet lines differ.
");
+ assert has(o[0..], n, "aria-expanded=\"true\">Hide packets
NOP
NOP
CMD RCRC
CMD DESYNC
CMD DESYNC
");
+ w_click(4);
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "aria-expanded=\"false\">Show packets
");
+ w_click(3);
+ var e : [8]u8 = [0; 8];
+ assert w_effect(0, e[0..]) == 2;
+ assert w_effect(1, e[0..]) == 2;
+ assert w_effect(2, e[0..]) == 0;
+ const m : u32 = w_render(o[0..]);
+ assert has(o[0..], m, "A: drop or pick a .bit or .bin");
+ assert has(o[0..], m, "disabled=\"\">Compare");
+ assert has(o[0..], m, "");
+}
+
+/// The splicer follows only the names a module itself mentions, not the names the imported functions
+/// mention. This anchor mentions every helper the writer, the JSON reader, the number rules and the
+/// canvas writers call. Never called.
+pub fn imports_anchor(o: []u8, s: []const u8, m: []u32) -> u32 {
+ var n : u32 = 0;
+ n = n + (ok(0) as u32);
+ n = n + OVER;
+ n = n + put(o, 0, 0);
+ n = n + raw(o, 0, s);
+ n = n + put_escaped(o, 0, 0);
+ n = n + text(o, 0, s);
+ n = n + num(o, 0, 0);
+ n = n + attr(o, 0, s, s);
+ n = n + fill(o, 0, s, s, s, s, 0);
+ n = n + fill_named(o, 0, s, s, s, s, s, s, s, s, s, s, s, s, s);
+ n = n + dec_to(o, 0);
+ n = n + (name_is(s, 0, 0, s) as u32);
+ n = n + (is_word_byte(0) as u32);
+ n = n + (H_LT as u32);
+ n = n + (H_GT as u32);
+ n = n + (H_AMP as u32);
+ n = n + (H_QUOTE as u32);
+ n = n + (H_APOS as u32);
+ n = n + (H_SPACE as u32);
+ n = n + (H_SLASH as u32);
+ n = n + (H_EQ as u32);
+ n = n + num_grouped(o, 0, 0);
+ n = n + ((NO_SPAN < 0) as u32);
+ n = n + ((SPAN > 0) as u32);
+ n = n + (J_NONE as u32);
+ n = n + (J_OBJECT as u32);
+ n = n + (J_ARRAY as u32);
+ n = n + (J_STRING as u32);
+ n = n + (J_NUMBER as u32);
+ n = n + (J_TRUE as u32);
+ n = n + (J_FALSE as u32);
+ n = n + (J_NULL as u32);
+ n = n + (jspan(0, 0) as u32);
+ n = n + (span_lo(0) as u32);
+ n = n + (span_hi(0) as u32);
+ n = n + (skip_ws(s, 0) as u32);
+ n = n + (string_end(s, 0) as u32);
+ n = n + (value_end(s, 0) as u32);
+ n = n + (root(s) as u32);
+ n = n + (kind(s, 0) as u32);
+ n = n + (key_is(s, 0, 0, s) as u32);
+ n = n + (obj_get(s, 0, s) as u32);
+ n = n + arr_len(s, 0);
+ n = n + (arr_at(s, 0, 0) as u32);
+ n = n + hex4(s, 0);
+ n = n + escape_at(s, 0, 0);
+ n = n + cp_utf8_len(0);
+ n = n + (cp_utf8_byte(0, 0) as u32);
+ n = n + str_len(s, 0);
+ n = n + (str_byte(s, 0, 0) as u32);
+ n = n + JN_OVER;
+ n = n + JN_CAP;
+ n = n + ((JN_TWO52 > 0.0) as u32);
+ n = n + ((JN_TWO53 > 0.0) as u32);
+ n = n + jn_put(o, 0, 0);
+ n = n + jn_raw(o, 0, s);
+ n = n + jn_digits(o, 0);
+ n = n + jn_mul(o, 0, 0);
+ n = n + jn_mul2(o, 0, 0);
+ n = n + jn_mul5(o, 0, 0);
+ n = n + jn_shift(o, 0, 0);
+ n = n + (jn_cmp(s, s, 0) as u32);
+ n = n + (jn_expo(0.0) as u32);
+ n = n + (jn_mant(0.0) as u32);
+ n = n + ((jn_make(0, 0) > 0.0) as u32);
+ n = n + jn_exact(o, 0, 0);
+ n = n + jn_fscale(0);
+ n = n + (js_is_nan(0.0) as u32);
+ n = n + ((js_nan(0.0) > 0.0) as u32);
+ n = n + ((js_inf(1.0) > 0.0) as u32);
+ n = n + ((js_floor(0.0) > 0.0) as u32);
+ n = n + ((js_ceil(0.0) > 0.0) as u32);
+ n = n + ((js_max(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_min(0.0, 0.0) > 0.0) as u32);
+ n = n + ((js_round(0.0) > 0.0) as u32);
+ n = n + (jn_shortest(o, 1.0) as u32);
+ n = n + jn_copy(o, s, 0);
+ n = n + jn_put_run(o, 0, s, 0, 0);
+ n = n + jn_layout(o, 0, s, 0, 0);
+ n = n + js_str_num(o, 0, 0.0);
+ n = n + js_to_fixed(o, 0, 0.0, 0);
+ n = n + js_locale_num(o, 0, 0.0);
+ n = n + (jn_num_ok(s, 0, 0) as u32);
+ n = n + (jn_cmp_text(s, 0, 0, 0, 0) as u32);
+ n = n + ((js_parse_num(s, 0, 0) > 0.0) as u32);
+ n = n + ((jn_dec_value(s, 0, 0) > 0.0) as u32);
+ n = n + (jn_abs(0) as u32);
+ n = n + js_str_u64(o, 0, 0);
+ n = n + jv_str_to(o, 0, s, 0);
+ n = n + jv_text_str(o, 0, s, 0);
+ n = n + ((jv_numval(s, 0) > 0.0) as u32);
+ n = n + jv_str(o, 0, s, 0);
+ n = n + jv_text(o, 0, s, 0);
+ n = n + (jv_is(s, 0, s) as u32);
+ n = n + (jv_truthy(s, 0) as u32);
+ n = n + (jv_is_ws(0) as u32);
+ n = n + ((jv_number_of_text(s, 0) > 0.0) as u32);
+ n = n + ((jv_num(s, 0) > 0.0) as u32);
+ n = n + jv_vput_str(o, 0, s, s);
+ n = n + jv_vput_f64(o, 0, s, 0.0);
+ n = n + jv_vput_val(o, 0, s, s, 0);
+ n = n + jv_vput_defined(o, 0, s, s, 0);
+ n = n + jv_vput_fixed(o, 0, s, 0.0, 0);
+ n = n + jv_kid(o, 0, s, 0);
+ n = n + jv_find(s, 0, s);
+ n = n + jv_fill(o, 0, s, s, 0, true);
+ n = n + (jv_string_ok(s, 0) as u32);
+ n = n + (jv_number_ok(s, 0) as u32);
+ n = n + (jv_lit_ok(s, 0, s) as u32);
+ n = n + (jv_valid(s) as u32);
+ n = n + jv_ws_at(s, 0, 0);
+ n = n + jv_ws_before(s, 0, 0);
+ n = n + jv_digit_value(0);
+ n = n + jv_fill_raw(o, 0, s, s, 0, true);
+ n = n + jv_fill_with(o, 0, s, s, 0, true, true);
+ n = n + CV_OVER;
+ n = n + (cv_ok(0) as u32);
+ n = n + cv_put(o, 0, 0);
+ n = n + cv_raw(o, 0, s);
+ n = n + (cv_fin(0.0) as u32);
+ n = n + cv_num(o, 0, 0.0);
+ n = n + cv_str(o, 0, s);
+ n = n + (cv_hex(0) as u32);
+ n = n + cv_open(o, 0, 0, s);
+ n = n + cv_close(o, 0);
+ n = n + cv_arg_f(o, 0, 0.0);
+ n = n + cv_arg_s(o, 0, s);
+ n = n + cv_arg_v(o, 0, s, s);
+ n = n + cv_arg_b(o, 0, false);
+ n = n + cv_arg_list(o, 0, 0.0, 0.0, 0);
+ n = n + cv_canvas(o, 0, 0);
+ n = n + cv_set_num(o, 0, s, 0.0);
+ n = n + cv_set_str(o, 0, s, s);
+ n = n + cv_set_var(o, 0, s, s, s);
+ n = n + cv_call0(o, 0, s);
+ n = n + cv_call2(o, 0, s, 0.0, 0.0);
+ n = n + cv_call4(o, 0, s, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_call6(o, 0, s, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+ n = n + cv_arc(o, 0, 0.0, 0.0, 0.0, 0.0, 0.0, false);
+ n = n + cv_text(o, 0, s, s, 0.0, 0.0);
+ n = n + cv_dash(o, 0, 0.0, 0.0, 0);
+ n = n + (SH_K[0] as u32);
+ n = n + ((SH_M32 > 0) as u32);
+ n = n + (sh_rotr(0, 0) as u32);
+ n = n + (sh_hexd(0) as u32);
+ n = n + sh_block(m, m, s, 0);
+ n = n + sha256_hex(s, m, m, o);
+ return n;
+}
From b80c685faded68f40c4a90a06e199afdf42f0cb5 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 19 Oct 2026 22:54:59 +0700
Subject: [PATCH 62/63] widgets: bit-autopsy drawn natively -- 58804-byte wasm,
1491 DOM comparisons, 0 differences (Closes #10938)
Page, bitstream autopsy reader (folded from bit_autopsy_logic.t27), file/drop UI, SHA-256, the
result written as the JSON the samples use, the on-page frame map and the PNG card as canvas_out
draw lists with text_meas widths. Measured against the original tool.js: DOM, canvas (card exactly,
map last paint), downloads; 40 samples.json/data variants, 100T and 200T sized streams.
Co-Authored-By: Claude Sonnet 5.5
---
specs/widgets/bit_autopsy.t27 | 3087 +++++++++++++++++++++++++++++++++
1 file changed, 3087 insertions(+)
create mode 100644 specs/widgets/bit_autopsy.t27
diff --git a/specs/widgets/bit_autopsy.t27 b/specs/widgets/bit_autopsy.t27
new file mode 100644
index 0000000000..8c8c285744
--- /dev/null
+++ b/specs/widgets/bit_autopsy.t27
@@ -0,0 +1,3087 @@
+// SPDX-License-Identifier: Apache-2.0
+; specs/widgets/bit-autopsy.t27 -- what a Xilinx 7-series bitstream says about itself, read in the browser
+; Source of truth for public/widgets/bit-autopsy/ (gHashTag/trinity): scripts/widget-pages-from-spec.mjs
+; compiles this file, checks its constants, evaluates the tests below and writes the page; the page
+; hands every constant to tool.js as window.T27_WIDGET. tool.js holds no English of its own: every
+; word on screen is a SAY_ constant here, every number the tool relies on is a K_ constant.
+; ASCII only (L3), English only (LANG-EN).
+
+; WHAT IT DOES: reads one bitstream the reader drops or picks (.bit with its header, or a raw .bin),
+; in the reader's browser, with FileReader. It prints the header fields as written (design, part,
+; date, time), counts the bytes and the zero bytes, finds the sync word, walks the type 1 and type 2
+; configuration packets, takes the IDCODE the stream writes (packet 0x30018001 and the word after it)
+; and names the part from a table of 7-series IDCODEs with the version nibble masked; anything not
+; in the table is "unknown". It counts the FDRI frames, the frames with any bit set (the ECC field of
+; word 50 left out), the command and CRC packets, and draws which frames are non-zero, one band per
+; configuration row. A Download PNG button saves that autopsy as a card drawn on a canvas.
+
+; DATA PROVENANCE: the six samples are summaries computed by the same parser
+; (public/widgets/bit-autopsy/autopsy.js) run in node by scripts/widget-data/bit-autopsy.mjs on the
+; real files; the bitstreams themselves are not shipped (3.8 MB and 9.7 MB). Five are the bitstreams
+; committed to gHashTag/t27 under fpga/openxc7-synth/ at commit c129a5b9e9f0; one is the build-receipt
+; widget's XC7A200T bitstream. Every sample's frame count, non-zero frame count and set-bit count
+; equals what prjxray bitread (commit c9f02d85) prints for the same file; the script exits 1 if not.
+; The IDCODE table is openFPGALoader v1.1.1 src/part.hpp (the idcode widget's parts.json), checked
+; against every Artix-7 die in prjxray-db ab1fc60; the row and column layout is prjxray-db part.json.
+
+; WHAT IT DOES NOT CLAIM: it does not decode the design. Which LUTs, wires or pins a frame sets is
+; not shown, and a non-zero frame count is not a size of the logic. It does not say whether a board
+; will accept the file; the bench line quotes UG470 on IDCODE mismatch, it did not program a board.
+; It refuses encrypted (CBC) and compressed (MFWR) bitstreams, and reads only 7-series streams.
+; phi^2 + 1/phi^2 = 3 | TRINITY
+
+module widget_bit_autopsy;
+
+use widgets::html_out::ok;
+use widgets::html_out::OVER;
+use widgets::html_out::put;
+use widgets::html_out::raw;
+use widgets::html_out::put_escaped;
+use widgets::html_out::text;
+use widgets::html_out::num;
+use widgets::html_out::attr;
+use widgets::html_out::fill;
+use widgets::html_out::fill_named;
+use widgets::html_out::dec_to;
+use widgets::html_out::name_is;
+use widgets::html_out::is_word_byte;
+use widgets::html_out::H_LT;
+use widgets::html_out::H_GT;
+use widgets::html_out::H_AMP;
+use widgets::html_out::H_QUOTE;
+use widgets::html_out::H_APOS;
+use widgets::html_out::H_SPACE;
+use widgets::html_out::H_SLASH;
+use widgets::html_out::H_EQ;
+use widgets::html_out::num_grouped;
+use widgets::json_scan::NO_SPAN;
+use widgets::json_scan::SPAN;
+use widgets::json_scan::J_NONE;
+use widgets::json_scan::J_OBJECT;
+use widgets::json_scan::J_ARRAY;
+use widgets::json_scan::J_STRING;
+use widgets::json_scan::J_NUMBER;
+use widgets::json_scan::J_TRUE;
+use widgets::json_scan::J_FALSE;
+use widgets::json_scan::J_NULL;
+use widgets::json_scan::jspan;
+use widgets::json_scan::span_lo;
+use widgets::json_scan::span_hi;
+use widgets::json_scan::skip_ws;
+use widgets::json_scan::string_end;
+use widgets::json_scan::value_end;
+use widgets::json_scan::root;
+use widgets::json_scan::kind;
+use widgets::json_scan::key_is;
+use widgets::json_scan::obj_get;
+use widgets::json_scan::arr_len;
+use widgets::json_scan::arr_at;
+use widgets::json_scan::hex4;
+use widgets::json_scan::escape_at;
+use widgets::json_scan::cp_utf8_len;
+use widgets::json_scan::cp_utf8_byte;
+use widgets::json_scan::str_len;
+use widgets::json_scan::str_byte;
+use widgets::js_num::JN_OVER;
+use widgets::js_num::JN_CAP;
+use widgets::js_num::JN_TWO52;
+use widgets::js_num::JN_TWO53;
+use widgets::js_num::jn_put;
+use widgets::js_num::jn_raw;
+use widgets::js_num::jn_digits;
+use widgets::js_num::jn_mul;
+use widgets::js_num::jn_mul2;
+use widgets::js_num::jn_mul5;
+use widgets::js_num::jn_shift;
+use widgets::js_num::jn_cmp;
+use widgets::js_num::jn_expo;
+use widgets::js_num::jn_mant;
+use widgets::js_num::jn_make;
+use widgets::js_num::jn_exact;
+use widgets::js_num::jn_fscale;
+use widgets::js_num::js_is_nan;
+use widgets::js_num::js_nan;
+use widgets::js_num::js_inf;
+use widgets::js_num::js_floor;
+use widgets::js_num::js_ceil;
+use widgets::js_num::js_max;
+use widgets::js_num::js_min;
+use widgets::js_num::js_round;
+use widgets::js_num::jn_shortest;
+use widgets::js_num::jn_copy;
+use widgets::js_num::jn_put_run;
+use widgets::js_num::jn_layout;
+use widgets::js_num::js_str_num;
+use widgets::js_num::js_to_fixed;
+use widgets::js_num::js_locale_num;
+use widgets::js_num::jn_num_ok;
+use widgets::js_num::jn_cmp_text;
+use widgets::js_num::js_parse_num;
+use widgets::js_num::jn_dec_value;
+use widgets::js_num::jn_abs;
+use widgets::js_num::js_str_u64;
+use widgets::js_val::jv_str_to;
+use widgets::js_val::jv_text_str;
+use widgets::js_val::jv_numval;
+use widgets::js_val::jv_str;
+use widgets::js_val::jv_text;
+use widgets::js_val::jv_is;
+use widgets::js_val::jv_truthy;
+use widgets::js_val::jv_is_ws;
+use widgets::js_val::jv_number_of_text;
+use widgets::js_val::jv_num;
+use widgets::js_val::jv_vput_str;
+use widgets::js_val::jv_vput_f64;
+use widgets::js_val::jv_vput_val;
+use widgets::js_val::jv_vput_defined;
+use widgets::js_val::jv_vput_fixed;
+use widgets::js_val::jv_kid;
+use widgets::js_val::jv_find;
+use widgets::js_val::jv_fill;
+use widgets::js_val::jv_string_ok;
+use widgets::js_val::jv_number_ok;
+use widgets::js_val::jv_lit_ok;
+use widgets::js_val::jv_valid;
+use widgets::js_val::jv_ws_at;
+use widgets::js_val::jv_ws_before;
+use widgets::js_val::jv_digit_value;
+use widgets::js_val::jv_fill_raw;
+use widgets::js_val::jv_fill_with;
+use widgets::canvas_out::cv_ok;
+use widgets::canvas_out::cv_put;
+use widgets::canvas_out::cv_raw;
+use widgets::canvas_out::cv_fin;
+use widgets::canvas_out::cv_num;
+use widgets::canvas_out::cv_str;
+use widgets::canvas_out::cv_hex;
+use widgets::canvas_out::cv_open;
+use widgets::canvas_out::cv_close;
+use widgets::canvas_out::cv_arg_f;
+use widgets::canvas_out::cv_arg_s;
+use widgets::canvas_out::cv_arg_v;
+use widgets::canvas_out::cv_arg_b;
+use widgets::canvas_out::cv_arg_list;
+use widgets::canvas_out::cv_canvas;
+use widgets::canvas_out::cv_set_num;
+use widgets::canvas_out::cv_set_str;
+use widgets::canvas_out::cv_set_var;
+use widgets::canvas_out::cv_call0;
+use widgets::canvas_out::cv_call2;
+use widgets::canvas_out::cv_call4;
+use widgets::canvas_out::cv_call6;
+use widgets::canvas_out::cv_arc;
+use widgets::canvas_out::cv_text;
+use widgets::canvas_out::cv_dash;
+use widgets::canvas_out::CV_OVER;
+use widgets::sha256_hex::SH_K;
+use widgets::sha256_hex::SH_M32;
+use widgets::sha256_hex::sh_rotr;
+use widgets::sha256_hex::sh_hexd;
+use widgets::sha256_hex::sh_block;
+use widgets::sha256_hex::sha256_hex;
+use widgets::text_meas::TM_HEAD;
+use widgets::text_meas::TM_AHEAD;
+use widgets::text_meas::tm_reset;
+use widgets::text_meas::tm_missed;
+use widgets::text_meas::tm_clear_miss;
+use widgets::text_meas::same_at;
+use widgets::text_meas::tm_find;
+use widgets::text_meas::pool_add;
+use widgets::text_meas::tm_add;
+use widgets::text_meas::tm_width;
+use widgets::text_meas::tm_ask;
+use widgets::text_meas::tm_need;
+use widgets::text_meas::tm_give;
+use widgets::text_meas::tm_units;
+use widgets::text_meas::tm_charlen;
+use widgets::text_meas::put_dots;
+use widgets::text_meas::tm_fit_left;
+use widgets::text_meas::tm_fit_right;
+use widgets::text_meas::tm_char_before;
+use widgets::text_meas::tm_wrap;
+
+pub const KIND : str = "widget-tool";
+pub const ID : str = "bit-autopsy";
+pub const TITLE : str = "Bitstream autopsy: which FPGA a 7-series .bit really targets";
+pub const DESCRIPTION : str = "Drop a Xilinx 7-series .bit: header, the IDCODE it writes and the part that names, frames, zero bytes and packets, read in your browser. Nothing is uploaded.";
+pub const IMAGE_ALT : str = "Bitstream autopsy card: blink_j26.bit, committed to gHashTag/t27, writes IDCODE 0x03631093, an XC7A100T, while the bench board is an XC7A200T (0x03636093); all 5 t27 .bit files do. 134 of its 9,448 frames are non-zero and 99.96 percent of its 3,825,921 bytes are zero, with a frame map per row. The build-receipt .bit writes the bench IDCODE.";
+pub const HOOK : str = "Five bitstreams committed to t27 write the XC7A100T IDCODE; the bench is a 200T. Drop your .bit and see which chip it asks for.";
+pub const CATEGORY : str = "fpga";
+pub const DATA_SOURCES : [6]str = ["gHashTag/t27 commit c129a5b9e9f0: fpga/openxc7-synth/blink_j26.bit, find_led.bit, static_d5.bit, test_top.bit and phi_temporal/temporal_heartbeat.bit (read with git show; t27#6222)", "build-receipt widget bitstream build_receipt.bit for xc7a200tfbg676-1, sha256 f1da88bc8f4660ff3e68dd050ae9277553a59897739f7a5b0b768fc7868e25a6", "prjxray bitread commit c9f02d85 (2025-06-05): bitread -part_file prjxray-db/artix7//part.yaml -z -o and -x -z -o; frame, non-zero frame and set-bit counts equal ours for all six samples", "openFPGALoader v1.1.1 (85be4fa0) src/part.hpp via the idcode widget's parts.json, checked against prjxray-db ab1fc60 artix7 part.json (rows, columns, IDCODEs)", "AMD UG470 7 Series FPGAs Configuration: bitstream header, sync word, packet types, registers, commands, IDCODE check and ID_ERROR", "scripts/widget-data/bit-autopsy.mjs: samples.json, parts.json and geometry.json, computed by public/widgets/bit-autopsy/autopsy.js in node"];
+pub const READS_LOCAL_FILES : bool = true;
+pub const SENDS_NOTHING : bool = true;
+
+; --- Numbers the tool relies on ------------------------------------------------------------
+; 0xAA995566, the word after which the configuration logic reads packets.
+pub const K_SYNC_WORD : u32 = 2862175590;
+pub const K_FRAME_WORDS : u8 = 101;
+; 0x0FFFFFFF: the IDCODE compared without its top four bits, the silicon version.
+pub const K_IDCODE_MASK : u32 = 268435455;
+; Every configuration row in the FDRI stream ends with two pad frames.
+pub const K_PAD_FRAMES_PER_ROW : u8 = 2;
+; The bench: three QMTech Wukong V1 boards, XC7A200T, IDCODE 0x03636093 with the version masked
+; (gHashTag/t27 fpga/HARDWARE_SSOT.md). The XC7A100T is 0x03631093.
+pub const K_BENCH_IDCODE : u32 = 56844435;
+pub const K_XC7A100T_IDCODE : u32 = 56823955;
+; Clock-region rows per die, and the two block types an FDRI stream writes (CLB/IO/CLK, block RAM).
+pub const K_XC7A100T_ROWS : u8 = 4;
+pub const K_XC7A200T_ROWS : u8 = 5;
+pub const K_BLOCK_TYPES_WRITTEN : u8 = 2;
+; The six samples, in the order of SAY_SAMPLE_IDS; scripts/widget-data/bit-autopsy.mjs exits 1 if
+; any of these differs from what the parser and prjxray bitread find in the files.
+pub const K_SAMPLE_IDCODES : [6]u32 = [56823955, 56844435, 56823955, 56823955, 56823955, 56823955];
+pub const K_SAMPLE_FRAME_SLOTS : [6]u32 = [9464, 24080, 9464, 9464, 9464, 9464];
+pub const K_SAMPLE_CONFIG_FRAMES : [6]u32 = [9448, 24060, 9448, 9448, 9448, 9448];
+pub const K_SAMPLE_NONZERO_FRAMES : [6]u32 = [134, 230, 152, 9, 75, 137];
+pub const K_SAMPLE_BYTES : [6]u32 = [3825921, 9730757, 3825920, 3825921, 3825920, 3825905];
+; Cells per band in the frame map.
+pub const K_STRIP_BUCKETS : u16 = 160;
+; The card the Download PNG button saves.
+pub const K_CARD_W : u16 = 1200;
+pub const K_CARD_H : u16 = 630;
+
+; --- Words on screen ------------------------------------------------------------------------
+; Templates take {0}, {1}, ... in order.
+pub const SAY_SAMPLE_LABEL : str = "Real bitstreams, precomputed";
+pub const SAY_SAMPLE_IDS : [6]str = ["blink_j26", "build_receipt", "find_led", "static_d5", "test_top", "temporal_heartbeat"];
+pub const SAY_SAMPLE_NAMES : [6]str = ["blink_j26", "build receipt", "find_led", "static_d5", "test_top", "temporal_heartbeat"];
+pub const SAY_SAMPLE_LINES : [6]str = ["gHashTag/t27 fpga/openxc7-synth/blink_j26.bit, built 2026-05-08 by openXC7.", "The build-receipt widget's bitstream for xc7a200tfbg676-1, built 2026-10-04.", "gHashTag/t27 fpga/openxc7-synth/find_led.bit, built 2026-05-08 by openXC7.", "gHashTag/t27 fpga/openxc7-synth/static_d5.bit, built 2026-05-08 by openXC7.", "gHashTag/t27 fpga/openxc7-synth/test_top.bit, built 2026-05-08 by openXC7.", "gHashTag/t27 fpga/openxc7-synth/phi_temporal/temporal_heartbeat.bit, built 2026-05-09."];
+pub const SAY_SAMPLE_NOTE : str = "Computed in node by this page's own parser on the real files and checked against prjxray bitread; the bitstreams are not shipped. Drop one below to compute it again here.";
+pub const SAY_OWN_LABEL : str = "Your file";
+pub const SAY_DROP : str = "Drop or pick a .bit or .bin";
+pub const SAY_READING : str = "Reading {0}";
+pub const SAY_LOCAL_NOTE : str = "Read in this tab with FileReader; nothing is uploaded.";
+pub const SAY_ERR_READ : str = "Could not read {0}.";
+pub const SAY_ERR_DATA : str = "The sample summaries did not load.";
+
+pub const SAY_VERDICT_AGREE : str = "The header says {0}; the IDCODE it writes says {1}. They agree.";
+pub const SAY_VERDICT_DISAGREE : str = "The header says {0}, but the IDCODE it writes says {1}.";
+pub const SAY_VERDICT_NO_HEADER : str = "No header (raw .bin). The IDCODE it writes says {0}.";
+pub const SAY_VERDICT_NO_IDCODE : str = "The stream writes no IDCODE, so it names no part.";
+pub const SAY_VERDICT_UNKNOWN : str = "The IDCODE {0} is not in the 7-series table here: unknown part.";
+pub const SAY_BENCH_MATCH : str = "Against t27's bench (XC7A200T, 0x03636093): same IDCODE, version masked.";
+pub const SAY_BENCH_MISMATCH : str = "Against t27's bench (XC7A200T, 0x03636093): a different chip. UG470: a device whose IDCODE differs from the one written stops with ID_ERROR. See t27#6222.";
+pub const SAY_UNKNOWN : str = "unknown";
+
+pub const SAY_STATS : [6]str = ["bytes", "zero bytes", "configuration frames", "non-zero frames", "bits set, ECC left out", "CMD packets, CRC writes"];
+pub const SAY_PERCENT : str = "{0} percent";
+pub const SAY_PERCENT_SHORT : str = "{0}%";
+pub const SAY_OF : str = "{0} of {1}";
+
+pub const SAY_HEADER_TITLE : str = "Header and stream";
+pub const SAY_COL_FIELD : str = "field";
+pub const SAY_COL_VALUE : str = "value";
+pub const SAY_FIELDS : [12]str = ["file", "bytes", "sha256", "design", "part (header)", "date", "time", "header length field", "sync word at byte", "IDCODE written", "part (from IDCODE)", "frame layout"];
+pub const SAY_NO_HEADER : str = "none (raw .bin)";
+pub const SAY_LENGTH_OK : str = "{0}, matches the file";
+pub const SAY_LENGTH_BAD : str = "{0}, does not match the file";
+pub const SAY_LAYOUT_MAPPED : str = "{0}, frame addresses from prjxray-db";
+pub const SAY_LAYOUT_STREAM : str = "no layout for this part: frames in stream order";
+
+pub const SAY_MAP_TITLE : str = "Which frames are non-zero";
+pub const SAY_MAP_LINE : str = "One band per configuration row, in frame-address order from left to right; each cell is a run of frames, brighter when more of them have a bit set.";
+pub const SAY_LEGEND : [3]str = ["all zero", "some frames set", "no frame here"];
+pub const SAY_BLOCKS_SHORT : [3]str = ["CLB", "BRAM", "CFG"];
+pub const SAY_HALVES : [2]str = ["top", "bot"];
+pub const SAY_STREAM_BAND : str = "stream";
+pub const SAY_BLOCK_LINE : str = "{0}: {1} of {2} frames non-zero";
+
+pub const SAY_PACKETS_TITLE : str = "Packets and commands";
+pub const SAY_PACKET_FIELDS : [8]str = ["type 1", "type 2", "NOP", "register writes", "CMD", "CRC writes", "FAR writes", "FDRI writes"];
+pub const SAY_COMMANDS : str = "Commands in order";
+
+pub const SAY_REFUSE_IDS : [3]str = ["nosync", "encrypted", "compressed"];
+pub const SAY_REFUSE_TEXT : [3]str = ["No sync word 0xAA995566 found: this is not a 7-series configuration stream.", "Encrypted bitstream (it writes CBC or sets CTL0 DEC). The frames are ciphertext; this page does not count them.", "Compressed bitstream (it writes MFWR). Not every frame is present; this page does not count them."];
+pub const SAY_WARN_IDS : [3]str = ["truncated", "badheader", "partialframe"];
+pub const SAY_WARN_TEXT : [3]str = ["The file ends inside a packet.", "Some words are not packet headers.", "FDRI data is not a whole number of 101-word frames."];
+
+pub const SAY_NOT_DECODING : str = "This reads the packets and counts frames. It does not decode the design: which LUTs, wires or pins a frame sets is not shown.";
+pub const SAY_DOWNLOAD : str = "Download PNG";
+pub const SAY_PREVIEW_NOTE : str = "A static page cannot put your result into a link preview: a shared link shows the fixed card of this page. Download the PNG and attach it to your post.";
+pub const SAY_CARD_TITLE : str = "Bitstream autopsy";
+pub const SAY_CARD_FOOT : str = "t27.ai/widgets/bit-autopsy/ -- read in the browser, nothing uploaded";
+pub const SAY_BRAND : str = "Trinity S3AI";
+pub const SAY_CARD_PARTS : str = "header {0} / IDCODE {1}";
+pub const SAY_CARD_AGREE : [2]str = ["header and IDCODE agree", "header and IDCODE disagree"];
+pub const SAY_CARD_BENCH : [2]str = ["t27 bench XC7A200T: same IDCODE", "t27 bench is XC7A200T: a different chip (UG470 ID_ERROR)"];
+
+; --- Tests ----------------------------------------------------------------------------------
+test the_sync_word_and_the_idcode_mask {
+ assert K_SYNC_WORD == 170 * 16777216 + 153 * 65536 + 85 * 256 + 102;
+ assert K_IDCODE_MASK == 268435455;
+ assert K_FRAME_WORDS == 101;
+}
+
+test blink_j26_writes_the_xc7a100t_idcode_not_the_bench_one {
+ assert SAY_SAMPLE_IDS[0] == "blink_j26";
+ assert K_SAMPLE_IDCODES[0] == K_XC7A100T_IDCODE;
+ assert K_SAMPLE_IDCODES[0] != K_BENCH_IDCODE;
+ assert K_XC7A100T_IDCODE == 3 * 16777216 + 99 * 65536 + 16 * 256 + 147;
+}
+
+test every_t27_sample_is_a_100t_and_the_receipt_is_the_bench_200t {
+ assert K_SAMPLE_IDCODES[1] == K_BENCH_IDCODE;
+ assert K_BENCH_IDCODE == 3 * 16777216 + 99 * 65536 + 96 * 256 + 147;
+ assert K_SAMPLE_IDCODES[2] == K_XC7A100T_IDCODE;
+ assert K_SAMPLE_IDCODES[3] == K_XC7A100T_IDCODE;
+ assert K_SAMPLE_IDCODES[4] == K_XC7A100T_IDCODE;
+ assert K_SAMPLE_IDCODES[5] == K_XC7A100T_IDCODE;
+}
+
+test frame_slots_are_config_frames_plus_two_pads_per_row {
+ assert K_SAMPLE_FRAME_SLOTS[0] == K_SAMPLE_CONFIG_FRAMES[0] + K_PAD_FRAMES_PER_ROW * K_XC7A100T_ROWS * K_BLOCK_TYPES_WRITTEN;
+ assert K_SAMPLE_FRAME_SLOTS[1] == K_SAMPLE_CONFIG_FRAMES[1] + K_PAD_FRAMES_PER_ROW * K_XC7A200T_ROWS * K_BLOCK_TYPES_WRITTEN;
+ assert K_SAMPLE_FRAME_SLOTS[1] == 24080;
+ assert K_SAMPLE_FRAME_SLOTS[0] * K_FRAME_WORDS * 4 < K_SAMPLE_BYTES[0];
+}
+
+test most_frames_are_zero {
+ assert K_SAMPLE_NONZERO_FRAMES[0] == 134;
+ assert K_SAMPLE_NONZERO_FRAMES[0] * 50 < K_SAMPLE_CONFIG_FRAMES[0];
+ assert K_SAMPLE_NONZERO_FRAMES[1] * 100 < K_SAMPLE_CONFIG_FRAMES[1];
+ assert K_SAMPLE_NONZERO_FRAMES[3] == 9;
+}
+
+test the_page_reads_files_and_sends_nothing {
+ assert READS_LOCAL_FILES == true;
+ assert SENDS_NOTHING == true;
+ assert CATEGORY == "fpga";
+ assert K_CARD_W == 1200;
+ assert K_CARD_H == 630;
+}
+
+; ---- FOLDED: synth_smells_logic.t27 lines 64-851 (renames: none) ----
+pub const SYNC_WORD : u32 = 2862274918;
+pub const FRAME_WORDS : u32 = 101;
+pub const ECC_WORD : u32 = 50;
+pub const ECC_MASK : u32 = 8191;
+pub const IDCODE_MASK : u32 = 268435455;
+pub const PAD_FRAMES_PER_ROW : u32 = 2;
+
+pub const NONE : u32 = 4294967295;
+pub const NONE64 : u64 = 18446744073709551615;
+
+pub const REG_CRC : u32 = 0;
+pub const REG_FAR : u32 = 1;
+pub const REG_FDRI : u32 = 2;
+pub const REG_CMD : u32 = 4;
+pub const REG_CTL0 : u32 = 5;
+pub const REG_MFWR : u32 = 10;
+pub const REG_CBC : u32 = 11;
+pub const REG_IDCODE : u32 = 12;
+pub const CMD_DESYNC : u32 = 13;
+pub const CTL0_DEC : u32 = 64;
+
+pub const REG_NAMES : [32]str = ["CRC", "FAR", "FDRI", "FDRO", "CMD", "CTL0", "MASK", "STAT", "LOUT", "COR0", "MFWR", "CBC", "IDCODE", "AXSS", "COR1", "", "WBSTAR", "TIMER", "", "RBCRC_SW", "", "", "BOOTSTS", "", "CTL1", "", "", "", "", "", "", "BSPI"];
+pub const CMD_NAMES : [20]str = ["NULL", "WCFG", "MFW", "LFRM", "RCFG", "START", "RCAP", "RCRC", "AGHIGH", "SWITCH", "GRESTORE", "SHUTDOWN", "GCAPTURE", "DESYNC", "", "IPROG", "CRCC", "LTIMER", "BSPI_READ", "FALL_EDGE"];
+
+/// Does CMD_NAMES have an entry for this CMD value?
+pub fn cmd_name_known(v: u32) -> bool {
+ return v < 20 and v != 14;
+}
+
+// ------------------------------------------------------------------------------------------------
+// small pure functions
+// ------------------------------------------------------------------------------------------------
+
+pub fn be32(u: []const u8, o: usize) -> u32 {
+ return ((u[o] as u32) << 24) | ((u[o + 1] as u32) << 16) | ((u[o + 2] as u32) << 8) | (u[o + 3] as u32);
+}
+
+pub fn popcount(x: u32) -> u32 {
+ var v : u32 = x - ((x >> 1) & 1431655765);
+ v = (v & 858993459) + ((v >> 2) & 858993459);
+ const w : u32 = (v + (v >> 4)) & 252645135;
+ return ((((w as u64) * 16843009) >> 24) & 255) as u32;
+}
+
+pub fn encode_far(block: u32, bottom: u32, row: u32, column: u32, minor: u32) -> u32 {
+ return ((block & 7) << 23) | ((bottom & 1) << 22) | ((row & 31) << 17) | ((column & 1023) << 7) | (minor & 127);
+}
+
+/// "0x" and eight lower-case hex digits of v into out[0 .. 10]; returns 10.
+pub fn hex8(v: u32, out: []u8) -> usize {
+ out[0] = 48;
+ out[1] = 120;
+ var i : usize = 0;
+ while (i < 8) {
+ const d : u32 = (v >> ((28 - 4 * (i as u32)) as u32)) & 15;
+ if (d < 10) { out[2 + i] = (48 + d) as u8; } else { out[2 + i] = (87 + d) as u8; }
+ i = i + 1;
+ }
+ return 10;
+}
+
+/// One character of the strip: '-' no frame there, '0' none non-zero, '1' to '9' the share of non-zero frames.
+pub fn shade_char(hit: u32, all: u32) -> u8 {
+ if (all == 0) { return 45; }
+ if (hit == 0) { return 48; }
+ var d : f64 = @ceil(((hit as f64) / (all as f64)) * 9.0);
+ if (d < 1.0) { d = 1.0; }
+ return (48 + (d as u32)) as u8;
+}
+
+/// The offset of the sync word at or after `from`, searching `limit` bytes, or -1.
+pub fn find_sync(u: []const u8, from: usize, limit: usize) -> i64 {
+ var end : usize = 0;
+ if (u.len >= 3) { end = u.len - 3; }
+ if (from + limit < end) { end = from + limit; }
+ var i : usize = from;
+ while (i < end) {
+ if (u[i] == 170 and u[i + 1] == 153 and u[i + 2] == 85 and u[i + 3] == 102) { return i as i64; }
+ i = i + 1;
+ }
+ return -1;
+}
+
+/// The text of a header field: from start, at most len bytes, stopping at a NUL or the end.
+pub fn field_len(u: []const u8, start: usize, len: usize) -> usize {
+ var i : usize = start;
+ while (i < start + len and i < u.len and u[i] != 0) { i = i + 1; }
+ if (i < start) { return 0; }
+ return i - start;
+}
+
+/// parseHeader: the .bit header; see "header layout". Returns whether it parsed.
+pub fn parse_header(u: []const u8, hd: []u64) -> bool {
+ var k : usize = 0;
+ while (k < 12) {
+ hd[k] = 0;
+ k = k + 1;
+ }
+ if (u.len < 13) { return false; }
+ if (u[0] != 0 or u[1] != 9 or u[2] != 15 or u[3] != 240 or u[4] != 15 or u[5] != 240 or u[6] != 15 or u[7] != 240 or u[8] != 15 or u[9] != 240 or u[10] != 0 or u[11] != 0 or u[12] != 1) { return false; }
+ var o : usize = 13;
+ // design, part, date, time: offset and length of the last field of each kind, empty by default
+ var fo : [4]usize = [0; 4];
+ var fl : [4]usize = [0; 4];
+ while (o + 3 <= u.len and o < 4096) {
+ const key : u8 = u[o];
+ if (key == 101) {
+ if (o + 5 > u.len) { return false; }
+ hd[0] = 1;
+ var q : usize = 0;
+ while (q < 4) {
+ hd[1 + 2 * q] = fo[q] as u64;
+ hd[2 + 2 * q] = fl[q] as u64;
+ q = q + 1;
+ }
+ hd[9] = be32(u, o + 1) as u64;
+ hd[10] = (o + 5) as u64;
+ return true;
+ }
+ if (key < 97 or key > 100) { return false; }
+ const len : usize = ((u[o + 1] as usize) << 8) | (u[o + 2] as usize);
+ const slot : usize = (key - 97) as usize;
+ fo[slot] = o + 3;
+ fl[slot] = field_len(u, o + 3, len);
+ o = o + 3 + len;
+ }
+ return false;
+}
+
+/// dieOf: the lower-cased "xc7", letters, digits prefix of s into out; its length, or -1.
+pub fn die_of(s: []const u8, out: []u8) -> i64 {
+ if (s.len < 5) { return -1; }
+ if ((s[0] | 32) != 120 or (s[1] | 32) != 99 or s[2] != 55) { return -1; }
+ var i : usize = 3;
+ while (i < s.len and ((s[i] >= 97 and s[i] <= 122) or (s[i] >= 65 and s[i] <= 90))) { i = i + 1; }
+ if (i == 3) { return -1; }
+ const d : usize = i;
+ while (i < s.len and s[i] >= 48 and s[i] <= 57) { i = i + 1; }
+ if (i == d) { return -1; }
+ var k : usize = 0;
+ while (k < i) {
+ var c : u8 = s[k];
+ if (c >= 65 and c <= 90) { c = c + 32; }
+ if (k < out.len) { out[k] = c; }
+ k = k + 1;
+ }
+ return i as i64;
+}
+
+/// lookupIdcode: the index of the first entry whose idcode equals `code` once the version nibble is masked
+/// on both sides, or -1.
+pub fn lookup_idcode(code: u32, parts: []const u32) -> i64 {
+ const key : u32 = code & IDCODE_MASK;
+ var i : usize = 0;
+ while (i < parts.len) {
+ if ((parts[i] & IDCODE_MASK) == key) { return i as i64; }
+ i = i + 1;
+ }
+ return -1;
+}
+
+/// Slots of geometry g (its offset in `geoms` after the count): rows * (columns' frames + PAD) entries.
+pub fn geom_slot_count(geoms: []const u32, at: usize) -> usize {
+ var p : usize = at + 2;
+ const nrows : usize = geoms[at + 1] as usize;
+ var total : usize = 0;
+ var r : usize = 0;
+ while (r < nrows) {
+ const ncols : usize = geoms[p + 3] as usize;
+ var c : usize = 0;
+ while (c < ncols) {
+ total = total + (geoms[p + 4 + c] as usize);
+ c = c + 1;
+ }
+ total = total + (PAD_FRAMES_PER_ROW as usize);
+ p = p + 4 + ncols;
+ r = r + 1;
+ }
+ return total;
+}
+
+/// frameSlots of the geometry at `at`: the FAR of every frame slot in FDRI order, NONE for the pad slots.
+/// Returns how many; out must hold geom_slot_count entries.
+pub fn frame_slots(geoms: []const u32, at: usize, out: []u32) -> usize {
+ var p : usize = at + 2;
+ const nrows : usize = geoms[at + 1] as usize;
+ var n : usize = 0;
+ var r : usize = 0;
+ while (r < nrows) {
+ const block : u32 = geoms[p];
+ const bottom : u32 = geoms[p + 1];
+ const row : u32 = geoms[p + 2];
+ const ncols : usize = geoms[p + 3] as usize;
+ var c : usize = 0;
+ while (c < ncols) {
+ const frames : u32 = geoms[p + 4 + c];
+ var mi : u32 = 0;
+ while (mi < frames) {
+ if (n < out.len) { out[n] = encode_far(block, bottom, row, c as u32, mi); }
+ n = n + 1;
+ mi = mi + 1;
+ }
+ c = c + 1;
+ }
+ var k : u32 = 0;
+ while (k < PAD_FRAMES_PER_ROW) {
+ if (n < out.len) { out[n] = NONE; }
+ n = n + 1;
+ k = k + 1;
+ }
+ p = p + 4 + ncols;
+ r = r + 1;
+ }
+ return n;
+}
+
+// ------------------------------------------------------------------------------------------------
+// walkPackets
+// ------------------------------------------------------------------------------------------------
+
+pub fn add_refuse(wp: []u64, code: u64) {
+ var i : usize = 0;
+ while (i < (wp[15] as usize)) {
+ if (wp[16 + i] == code) { return; }
+ i = i + 1;
+ }
+ if (wp[15] < 2) {
+ wp[16 + (wp[15] as usize)] = code;
+ wp[15] = wp[15] + 1;
+ }
+}
+
+pub fn add_warning(wp: []u64, code: u64) {
+ var i : usize = 0;
+ while (i < (wp[18] as usize)) {
+ if (wp[19 + i] == code) { return; }
+ i = i + 1;
+ }
+ if (wp[18] < 2) {
+ wp[19 + (wp[18] as usize)] = code;
+ wp[18] = wp[18] + 1;
+ }
+}
+
+pub fn push_fdri(fdri: []u32, wp: []u64, off: usize, words: usize, far_present: bool, far: u32) {
+ const k : usize = wp[21] as usize;
+ if ((k + 1) * 4 <= fdri.len) {
+ fdri[k * 4] = off as u32;
+ fdri[k * 4 + 1] = words as u32;
+ if (far_present) { fdri[k * 4 + 2] = 1; } else { fdri[k * 4 + 2] = 0; }
+ fdri[k * 4 + 3] = far;
+ } else {
+ wp[23] = 1;
+ }
+ wp[21] = (k + 1) as u64;
+}
+
+/// walkPackets: see "packet layout". Returns 0, or 1 when fdri or cmds was too small.
+pub fn walk_packets(u: []const u8, sync: usize, wp: []u64, fdri: []u32, cmds: []u32) -> u32 {
+ var z : usize = 0;
+ while (z < 24) {
+ wp[z] = 0;
+ z = z + 1;
+ }
+ var o : usize = sync + 4;
+ var far_present : bool = false;
+ var far : u32 = 0;
+ var last_reg : u32 = NONE;
+ const n : usize = u.len;
+ var go : bool = true;
+ while (o + 4 <= n and go) {
+ const w : u32 = be32(u, o);
+ const typ : u32 = w >> 29;
+ if (typ == 1) {
+ const op : u32 = (w >> 27) & 3;
+ const reg : u32 = (w >> 13) & 16383;
+ const count : usize = (w & 2047) as usize;
+ if (op == 0) {
+ wp[6] = wp[6] + 1;
+ o = o + 4;
+ continue;
+ }
+ wp[4] = wp[4] + 1;
+ if ((o as u64) + 4 + (count as u64) * 4 > (n as u64)) {
+ add_warning(wp, 1);
+ o = n;
+ go = false;
+ continue;
+ }
+ if (op == 1) { wp[9] = wp[9] + 1; }
+ var advanced : bool = false;
+ if (op == 2) {
+ wp[8] = wp[8] + 1;
+ var has_v : bool = count > 0;
+ var v : u32 = 0;
+ if (has_v) { v = be32(u, o + 4); }
+ if (reg == REG_CMD) {
+ wp[10] = wp[10] + 1;
+ if (has_v) {
+ const k : usize = wp[22] as usize;
+ if (k < cmds.len) { cmds[k] = v; } else { wp[23] = 1; }
+ wp[22] = (k + 1) as u64;
+ }
+ } else {
+ if (reg == REG_CRC) {
+ wp[11] = wp[11] + 1;
+ } else {
+ if (reg == REG_FAR) {
+ wp[12] = wp[12] + 1;
+ far_present = has_v;
+ far = v;
+ } else {
+ if (reg == REG_IDCODE) {
+ if (has_v) { wp[0] = 1; wp[1] = v as u64; } else { wp[0] = 0; wp[1] = 0; }
+ } else {
+ if (reg == REG_CBC) {
+ add_refuse(wp, 1);
+ } else {
+ if (reg == REG_CTL0 and has_v and (v & CTL0_DEC) != 0) {
+ add_refuse(wp, 1);
+ } else {
+ if (reg == REG_MFWR) { add_refuse(wp, 2); }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (reg == REG_FDRI) {
+ wp[13] = wp[13] + 1;
+ if (count > 0) { push_fdri(fdri, wp, o + 4, count, far_present, far); }
+ }
+ if (reg == REG_CMD and has_v and v == CMD_DESYNC) {
+ o = o + 4 + count * 4;
+ wp[3] = 1;
+ advanced = true;
+ go = false;
+ }
+ }
+ if (!advanced) {
+ last_reg = reg;
+ o = o + 4 + count * 4;
+ }
+ } else {
+ if (typ == 2) {
+ const op : u32 = (w >> 27) & 3;
+ const count : usize = (w & 134217727) as usize;
+ wp[5] = wp[5] + 1;
+ if ((o as u64) + 4 + (count as u64) * 4 > (n as u64)) {
+ add_warning(wp, 1);
+ o = n;
+ go = false;
+ continue;
+ }
+ if (op == 2) {
+ wp[8] = wp[8] + 1;
+ if (last_reg == REG_FDRI) {
+ wp[13] = wp[13] + 1;
+ push_fdri(fdri, wp, o + 4, count, far_present, far);
+ }
+ }
+ o = o + 4 + count * 4;
+ } else {
+ if (w == 4294967295 or w == 0) {
+ wp[7] = wp[7] + 1;
+ o = o + 4;
+ } else {
+ wp[14] = wp[14] + 1;
+ add_warning(wp, 2);
+ o = o + 4;
+ }
+ }
+ }
+ }
+ wp[2] = o as u64;
+ if (wp[23] != 0) { return 1; }
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// the autopsy
+// ------------------------------------------------------------------------------------------------
+
+/// The offset in `geoms` of the geometry whose idcode matches `code` (version nibble masked), or -1.
+pub fn find_geom(geoms: []const u32, code: u32) -> i64 {
+ const count : usize = geoms[0] as usize;
+ var at : usize = 1;
+ var g : usize = 0;
+ while (g < count) {
+ if ((geoms[at] & IDCODE_MASK) == (code & IDCODE_MASK)) { return at as i64; }
+ const nrows : usize = geoms[at + 1] as usize;
+ var p : usize = at + 2;
+ var r : usize = 0;
+ while (r < nrows) {
+ p = p + 4 + (geoms[p + 3] as usize);
+ r = r + 1;
+ }
+ at = p;
+ g = g + 1;
+ }
+ return -1;
+}
+
+pub fn geom_index_of(geoms: []const u32, at: usize) -> usize {
+ const count : usize = geoms[0] as usize;
+ var a : usize = 1;
+ var g : usize = 0;
+ while (g < count) {
+ if (a == at) { return g; }
+ const nrows : usize = geoms[a + 1] as usize;
+ var p : usize = a + 2;
+ var r : usize = 0;
+ while (r < nrows) {
+ p = p + 4 + (geoms[p + 3] as usize);
+ r = r + 1;
+ }
+ a = p;
+ g = g + 1;
+ }
+ return 0;
+}
+
+/// The k-th line of a newline-separated text, as (start, end).
+pub fn nth_line(t: []const u8, k: usize) -> i64 {
+ var line : usize = 0;
+ var start : usize = 0;
+ var i : usize = 0;
+ while (i <= t.len) {
+ if (i == t.len or t[i] == 10) {
+ if (line == k) { return ((start as i64) * 4294967296) + (i as i64); }
+ line = line + 1;
+ start = i + 1;
+ }
+ i = i + 1;
+ }
+ return ((t.len as i64) * 4294967296) + (t.len as i64);
+}
+
+/// autopsy: see "autopsy layout" in the header.
+pub fn autopsy(u: []const u8, geoms: []const u32, parts: []const u32, models: []const u8, buckets: usize, res: []u64, lists: []u32, cells: []u8, strs: []u8, fdri: []u32, cmds: []u32, nz: []u8, fars: []u32, slots: []u32, hitall: []u32) -> u32 {
+ var z : usize = 0;
+ while (z < 64) {
+ res[z] = 0;
+ z = z + 1;
+ }
+ res[13] = NONE64;
+ res[17] = NONE64;
+ res[19] = NONE64;
+ res[21] = NONE64;
+ res[22] = 2;
+ res[12] = 2;
+ res[51] = NONE64;
+ if (buckets == 0) { return 5; }
+ res[0] = u.len as u64;
+ var zeros : u64 = 0;
+ var i : usize = 0;
+ while (i < u.len) {
+ if (u[i] == 0) { zeros = zeros + 1; }
+ i = i + 1;
+ }
+ res[23] = zeros;
+ var hd : [12]u64 = [0; 12];
+ var from : usize = 0;
+ var header_die_len : i64 = -1;
+ var strs_used : usize = 0;
+ if (parse_header(u, hd[0 .. 12])) {
+ res[1] = 1;
+ res[2] = 1;
+ z = 0;
+ while (z < 8) {
+ res[3 + z] = hd[1 + z];
+ z = z + 1;
+ }
+ res[11] = hd[9];
+ if (hd[9] == ((u.len as u64) - hd[10])) { res[12] = 1; } else { res[12] = 0; }
+ const po : usize = hd[3] as usize;
+ const pl : usize = hd[4] as usize;
+ header_die_len = die_of(u[po .. po + pl], strs[0 .. strs.len]);
+ if (header_die_len > (strs.len as i64)) { return 1; }
+ if (header_die_len >= 0) {
+ res[18] = 0;
+ res[19] = header_die_len as u64;
+ strs_used = header_die_len as usize;
+ }
+ from = hd[10] as usize;
+ }
+ const sy : i64 = find_sync(u, from, 1048576);
+ if (sy < 0) {
+ res[24] = 1;
+ res[25] = 3;
+ return 0;
+ }
+ res[13] = sy as u64;
+ var wp : [24]u64 = [0; 24];
+ const wst : u32 = walk_packets(u, sy as usize, wp[0 .. 24], fdri, cmds);
+ if (wst != 0) { return 1; }
+ res[42] = 1;
+ z = 0;
+ while (z < 11) {
+ res[31 + z] = wp[4 + z];
+ z = z + 1;
+ }
+ res[43] = wp[22];
+ z = 0;
+ while (z < (wp[22] as usize)) {
+ if (z < lists.len) { lists[z] = cmds[z]; } else { return 1; }
+ z = z + 1;
+ }
+ res[27] = wp[18];
+ res[28] = wp[19];
+ res[29] = wp[20];
+ res[24] = wp[15];
+ res[25] = wp[16];
+ res[26] = wp[17];
+ var have_id : bool = wp[0] == 1;
+ var idc : u32 = 0;
+ if (have_id) {
+ idc = wp[1] as u32;
+ res[14] = 1;
+ res[15] = wp[1];
+ res[16] = (idc >> 28) as u64;
+ const di : i64 = lookup_idcode(idc, parts);
+ if (di >= 0) {
+ res[17] = di as u64;
+ const ln : i64 = nth_line(models, di as usize);
+ const ms : usize = (ln / 4294967296) as usize;
+ const me : usize = (ln % 4294967296) as usize;
+ const idl : i64 = die_of(models[ms .. me], strs[strs_used .. strs.len]);
+ if (idl > ((strs.len - strs_used) as i64)) { return 1; }
+ if (idl >= 0) {
+ res[20] = strs_used as u64;
+ res[21] = idl as u64;
+ strs_used = strs_used + (idl as usize);
+ }
+ }
+ if (res[21] != NONE64 and res[19] != NONE64) {
+ // agree: the two lower-cased dies are the same text
+ res[22] = 0;
+ if (res[21] == res[19]) {
+ var same : bool = true;
+ var q : usize = 0;
+ while (q < (res[19] as usize)) {
+ if (strs[(res[18] as usize) + q] != strs[(res[20] as usize) + q]) { same = false; }
+ q = q + 1;
+ }
+ if (same) { res[22] = 1; }
+ }
+ }
+ }
+ if (wp[15] > 0) { return 0; }
+
+ // the frames
+ var frame_words : u64 = 0;
+ z = 0;
+ while (z < (wp[21] as usize)) {
+ frame_words = frame_words + (fdri[z * 4 + 1] as u64);
+ z = z + 1;
+ }
+ res[44] = frame_words;
+ const frame_slots_n : usize = (frame_words / (FRAME_WORDS as u64)) as usize;
+ res[45] = frame_slots_n as u64;
+ if (frame_words % (FRAME_WORDS as u64) != 0) {
+ // partialframe is appended after the packet warnings
+ const wc : usize = res[27] as usize;
+ if (wc < 3) {
+ res[28 + wc] = 3;
+ res[27] = (wc + 1) as u64;
+ }
+ }
+ if (nz.len < frame_slots_n or fars.len < frame_slots_n) { return 3; }
+ z = 0;
+ while (z < frame_slots_n) {
+ nz[z] = 0;
+ z = z + 1;
+ }
+ var k : usize = 0;
+ var nonzero_frames : u64 = 0;
+ var ones : u64 = 0;
+ var e : usize = 0;
+ while (e < (wp[21] as usize)) {
+ const woff : usize = fdri[e * 4] as usize;
+ const nf : usize = ((fdri[e * 4 + 1] as u64) / (FRAME_WORDS as u64)) as usize;
+ var f : usize = 0;
+ while (f < nf and k < frame_slots_n) {
+ const base : usize = woff + f * (FRAME_WORDS as usize) * 4;
+ var fones : u64 = 0;
+ var wi : usize = 0;
+ while (wi < (FRAME_WORDS as usize)) {
+ var v : u32 = be32(u, base + wi * 4);
+ if (wi == (ECC_WORD as usize)) { v = v & (4294967295 - ECC_MASK); }
+ if (v != 0) { fones = fones + (popcount(v) as u64); }
+ wi = wi + 1;
+ }
+ nz[k] = 0;
+ if (fones != 0) {
+ nz[k] = 1;
+ nonzero_frames = nonzero_frames + 1;
+ ones = ones + fones;
+ }
+ f = f + 1;
+ k = k + 1;
+ }
+ e = e + 1;
+ }
+ res[48] = nonzero_frames;
+ res[49] = ones;
+
+ // frame addresses from the die's layout
+ var geom_at : i64 = -1;
+ if (have_id) { geom_at = find_geom(geoms, idc); }
+ var mapped : bool = false;
+ var nslots : usize = 0;
+ if (geom_at >= 0) {
+ const ga : usize = geom_at as usize;
+ const need : usize = geom_slot_count(geoms, ga);
+ if (slots.len < need) { return 1; }
+ nslots = frame_slots(geoms, ga, slots[0 .. need]);
+ // fars[j]: NONE for a pad slot, NONE - 1 for undefined, else the FAR
+ var ok : bool = true;
+ var j : usize = 0;
+ e = 0;
+ while (e < (wp[21] as usize)) {
+ const nf : usize = ((fdri[e * 4 + 1] as u64) / (FRAME_WORDS as u64)) as usize;
+ var at : i64 = -1;
+ if (fdri[e * 4 + 2] == 1) {
+ var s : usize = 0;
+ while (s < nslots and at < 0) {
+ if (slots[s] != NONE and slots[s] == fdri[e * 4 + 3]) { at = s as i64; }
+ s = s + 1;
+ }
+ }
+ if (at < 0) { ok = false; }
+ var f : usize = 0;
+ while (f < nf and j < frame_slots_n) {
+ fars[j] = NONE - 1;
+ if (at >= 0 and (at as usize) + f < nslots) { fars[j] = slots[(at as usize) + f]; }
+ f = f + 1;
+ j = j + 1;
+ }
+ e = e + 1;
+ }
+ // frames never reached keep the initial `undefined` of fill(undefined)
+ while (j < frame_slots_n) {
+ fars[j] = NONE - 1;
+ j = j + 1;
+ }
+ if (ok) {
+ mapped = true;
+ res[50] = 1;
+ res[51] = geom_index_of(geoms, ga) as u64;
+ }
+ }
+ if (mapped) {
+ const ga : usize = geom_at as usize;
+ const nrows : usize = geoms[ga + 1] as usize;
+ var pads : u64 = 0;
+ var q : usize = 0;
+ while (q < frame_slots_n) {
+ if (fars[q] == NONE) { pads = pads + 1; }
+ q = q + 1;
+ }
+ res[46] = pads;
+ res[47] = (frame_slots_n as u64) - pads;
+ if (hitall.len < 2 * buckets * nrows) { return 1; }
+ var h : usize = 0;
+ while (h < 2 * buckets * nrows) {
+ hitall[h] = 0;
+ h = h + 1;
+ }
+ // geometry rows: p offsets
+ var fi : usize = 0;
+ while (fi < frame_slots_n) {
+ const far : u32 = fars[fi];
+ if (far != NONE and far != NONE - 1) {
+ const blk : u32 = (far >> 23) & 7;
+ if (blk < 3) {
+ res[52 + 2 * (blk as usize)] = res[52 + 2 * (blk as usize)] + 1;
+ if (nz[fi] == 1) { res[53 + 2 * (blk as usize)] = res[53 + 2 * (blk as usize)] + 1; }
+ }
+ // the row: the last geometry row with the same block, bottom and row
+ var ri : i64 = -1;
+ var rp : usize = ga + 2;
+ var r : usize = 0;
+ var rpos : usize = 0;
+ while (r < nrows) {
+ const gb : u32 = geoms[rp];
+ const gbo : u32 = geoms[rp + 1];
+ const gr : u32 = geoms[rp + 2];
+ if (((gb << 6) | (gbo << 5) | gr) == (far >> 17)) {
+ ri = r as i64;
+ rpos = rp;
+ }
+ rp = rp + 4 + (geoms[rp + 3] as usize);
+ r = r + 1;
+ }
+ if (ri >= 0) {
+ // total frames of the row, and the start of the column named by the FAR
+ const ncols : usize = geoms[rpos + 3] as usize;
+ var total : u64 = 0;
+ var c : usize = 0;
+ while (c < ncols) {
+ total = total + (geoms[rpos + 4 + c] as u64);
+ c = c + 1;
+ }
+ // colStart: the last row with this block/bottom/row key and a column of that number
+ var cstart : u64 = 0;
+ var rp2 : usize = ga + 2;
+ var r2 : usize = 0;
+ const want : u32 = far & (4294967295 - 127);
+ while (r2 < nrows) {
+ const nc2 : usize = geoms[rp2 + 3] as usize;
+ var acc : u64 = 0;
+ var c2 : usize = 0;
+ while (c2 < nc2) {
+ if (encode_far(geoms[rp2], geoms[rp2 + 1], geoms[rp2 + 2], c2 as u32, 0) == want) { cstart = acc; }
+ acc = acc + (geoms[rp2 + 4 + c2] as u64);
+ c2 = c2 + 1;
+ }
+ rp2 = rp2 + 4 + nc2;
+ r2 = r2 + 1;
+ }
+ const pos : u64 = cstart + ((far & 127) as u64);
+ const ratio : f64 = ((pos as f64) / (total as f64)) * (buckets as f64);
+ var bi : f64 = @floor(ratio);
+ if (ratio == ratio) {
+ if (bi > ((buckets - 1) as f64)) { bi = (buckets - 1) as f64; }
+ const b : usize = bi as usize;
+ const base : usize = (ri as usize) * 2 * buckets;
+ hitall[base + buckets + b] = hitall[base + buckets + b] + 1;
+ if (nz[fi] == 1) { hitall[base + b] = hitall[base + b] + 1; }
+ }
+ }
+ }
+ fi = fi + 1;
+ }
+ // the strip: one entry per geometry row
+ res[58] = nrows as u64;
+ const lbase : usize = res[43] as usize;
+ if (lists.len < lbase + 3 * nrows or cells.len < buckets * nrows) { return 1; }
+ var r : usize = 0;
+ var rp : usize = ga + 2;
+ while (r < nrows) {
+ lists[lbase + r * 3] = geoms[rp];
+ lists[lbase + r * 3 + 1] = geoms[rp + 1];
+ lists[lbase + r * 3 + 2] = geoms[rp + 2];
+ var b : usize = 0;
+ while (b < buckets) {
+ cells[r * buckets + b] = shade_char(hitall[r * 2 * buckets + b], hitall[r * 2 * buckets + buckets + b]);
+ b = b + 1;
+ }
+ rp = rp + 4 + (geoms[rp + 3] as usize);
+ r = r + 1;
+ }
+ } else {
+ res[47] = frame_slots_n as u64;
+ if (frame_slots_n > 0) {
+ if (hitall.len < 2 * buckets) { return 1; }
+ var h : usize = 0;
+ while (h < 2 * buckets) {
+ hitall[h] = 0;
+ h = h + 1;
+ }
+ var per : f64 = (frame_slots_n as f64) / (buckets as f64);
+ if (per == 0.0 or per != per) { per = 1.0; }
+ var fi : usize = 0;
+ while (fi < frame_slots_n) {
+ var cf : f64 = @floor((fi as f64) / per);
+ if (cf > ((buckets - 1) as f64)) { cf = (buckets - 1) as f64; }
+ const c : usize = cf as usize;
+ hitall[buckets + c] = hitall[buckets + c] + 1;
+ if (nz[fi] == 1) { hitall[c] = hitall[c] + 1; }
+ fi = fi + 1;
+ }
+ res[58] = 1;
+ const lbase : usize = res[43] as usize;
+ if (lists.len < lbase + 3 or cells.len < buckets) { return 1; }
+ lists[lbase] = NONE;
+ lists[lbase + 1] = NONE;
+ lists[lbase + 2] = NONE;
+ var b : usize = 0;
+ while (b < buckets) {
+ cells[b] = shade_char(hitall[b], hitall[buckets + b]);
+ b = b + 1;
+ }
+ }
+ }
+ return 0;
+}
+; ---- END FOLDED ----
+
+
+; =================================================================================================
+; THE NATIVE PAGE (epic gHashTag/t27#10937, issue #10938, stage "page" of specs/t27f/widget_factory.t27)
+; Everything below replaces public/widgets/bit-autopsy/tool.js (376 lines of hand-written JavaScript) and, as the
+; page's reader, autopsy.js. The words and numbers above are unchanged: the view prints SAY_ constants and
+; uses K_ numbers, it writes no English of its own.
+; --
+; ONE VIEW, TWO SOURCES (as bit_diff.t27). tool.js draws one object `a`, whether it comes from samples.json
+; (precomputed in node) or from the file the reader drops (autopsy()). The view of this page is always a JSON
+; text: a sample's span inside samples.json, or the JSON of an own file written by ba_write_view from the folded
+; reader's result. One set of drawing rules serves both.
+; --
+; THE READER IS FOLDED IN, NOT IMPORTED. specs/widgets/bit_autopsy_logic.t27 is the differential-tested port of
+; autopsy.js. Its parser keeps no state but is called with eleven buffers that are module-level `pub var`s in
+; the page, and t27c's `use` does not carry a `pub var` across modules; the block between the two "FOLDED"
+; markers is that module's text from `pub const SYNC_WORD` to the end of autopsy, byte for byte except the renames
+; on the marker line (names json_scan.t27, js_num.t27 or this spec also declare).
+; --
+; Host protocol (host.js; all exports of this module):
+; w_init(hash) the page opens; samples.json, geometry.json and parts.json are awaited
+; w_view(width, dpr, embed, canvasW, parentW) the window numbers tool.js reads: the frame map's clientWidth and devicePixelRatio
+; w_data(i, bytes) / w_data_failed(i) file i of W_DATA_FILES (0 samples.json, 1 geometry.json, 2 parts.json)
+; w_click(i) button i in document order: 0 .. 5 a sample, 6 "Download PNG"
+; w_file(z, name, size) -> 1 | 0 a file was picked or dropped on the zone: 1 = send its bytes
+; w_file_data(z, bytes) / w_file_failed(z) the bytes, or "could not be read"
+; w_drag(z, over) -> 1 | 0 the drag entered (1) or left/dropped (0): 1 = the page changed
+; w_effect(k, dst) -> len 'v' + control index = set that control's value to '' (the file input, after every file);
+; 'p' + "width TAB height TAB file name" = paint w_card on a new canvas and download it as a PNG
+; w_draw(dst) -> len the draw list of the frame map; w_card(dst) -> len the draw list of the card
+; w_meas_need(dst) / w_meas_give(text) text_meas.t27: the widths the card asks for
+; w_render(dst) the page's HTML, length (0 on overflow)
+; DRAW LIST EXTENSIONS (host.js): a css-variable argument may carry a prefix, {"v":"--w-mono","f":"monospace","p":"11px "}: the font is
+; `p + (cssVar || f)`; a font named for text_meas is written {--var|fallback} where the variable goes.
+; Buffers (module-level, static): samples.json 262144 bytes, geometry.json 65536, parts.json 65536, the file 16 MiB, the JSON of an own
+; file 1 MiB, the reader's frame tables 524288 frames.
+; Not carried over from JavaScript, said plainly:
+; - the file is read when picked (the host hands the bytes at once), not when processed; a file over 16 MiB, or one the reader cannot hold
+; (more than 4096 FDRI writes, 65536 commands), shows SAY_ERR_READ;
+; - samples.json is trusted to be shaped as bit-autopsy.mjs writes it (see bd_view: leaf values may be null, missing or odd; a sample
+; that is no object is not claimed); a header string with bytes above 127 shows as the characters U+0080 .. U+00FF it is in JavaScript;
+; - the map is painted again every time the page is drawn (tool.js paints once per view and at every resize), with the colours of that moment;
+; - the card cuts a name that is longer than 512 bytes to its first 512 before it fits it (same text);
+; - the card's file name keeps ASCII letters and digits and . _ - as tool.js does; every other run becomes one underscore;
+; - a duplicate key in a JSON file reads as the first one here, the last in JSON.parse; a log of the progress sentence SAY_READING is not shown (the
+; file is read in one call).
+
+pub const W_SAMPLES_CAP : u32 = 262144;
+pub const W_GEOM_CAP : u32 = 65536;
+pub const W_PARTS_CAP : u32 = 65536;
+pub const W_FILE_CAP : u32 = 16777216;
+pub const W_OWJ_CAP : u32 = 1048576;
+pub const W_DATA_FILES : [3]str = ["samples.json", "geometry.json", "parts.json"];
+pub const W_BUCKETS : u32 = 160;
+
+pub var SAMPLES : [262144]u8 = [0; 262144];
+pub var SAMPLES_LEN : u32 = 0;
+pub var SAMPLES_ST : u32 = 0;
+pub var GEOM : [65536]u8 = [0; 65536];
+pub var GEOM_LEN : u32 = 0;
+pub var GEOM_ST : u32 = 0;
+pub var PARTS : [65536]u8 = [0; 65536];
+pub var PARTS_LEN : u32 = 0;
+pub var PARTS_ST : u32 = 0;
+pub var FILE : [16777216]u8 = [0; 16777216];
+pub var FLEN : u32 = 0;
+pub var FREAD : u32 = 0;
+pub var OWJ : [1048576]u8 = [0; 1048576];
+pub var OWJ_LEN : u32 = 0;
+pub var NAME : [2000]u8 = [0; 2000];
+pub var NAME_LEN : u32 = 0;
+pub var FNAME : [1000]u8 = [0; 1000];
+pub var FNAME_LEN : u32 = 0;
+pub var READY : bool = false;
+pub var SEL : i32 = 0 - 1;
+pub var LINE_ON : bool = false;
+pub var ST_MODE : u32 = 0;
+pub var HAS_VIEW : bool = false;
+pub var VIEW_SRC : u32 = 0;
+pub var VIEW_SP : i64 = 0 - 1;
+pub var DL_ON : bool = false;
+pub var DRAGGING : bool = false;
+pub var EFF_CLEAR : bool = false;
+pub var EFF_PNG : bool = false;
+pub var ENV_CW : f64 = 0.0;
+pub var ENV_DPR : f64 = 1.0;
+pub var TH : bool = false;
+
+pub var TA : [8192]u8 = [0; 8192];
+pub var TB : [8192]u8 = [0; 8192];
+pub var TC : [8192]u8 = [0; 8192];
+pub var TD : [8192]u8 = [0; 8192];
+pub var SHA_H : [8]u32 = [0; 8];
+pub var SHA_W : [64]u32 = [0; 64];
+pub var SHA_O : [64]u8 = [0; 64];
+
+pub var G_FLAT : [70000]u32 = [0; 70000];
+pub var G_LEN : u32 = 0;
+pub var P_IDS : [4096]u32 = [0; 4096];
+pub var P_N : u32 = 0;
+pub var P_MODELS : [65536]u8 = [0; 65536];
+pub var P_MODELS_LEN : u32 = 0;
+pub var A_RES : [64]u64 = [0; 64];
+pub var A_LISTS : [100000]u32 = [0; 100000];
+pub var A_CELLS : [65536]u8 = [0; 65536];
+pub var A_STRS : [2048]u8 = [0; 2048];
+pub var A_FDRI : [16384]u32 = [0; 16384];
+pub var A_CMDS : [65536]u32 = [0; 65536];
+pub var A_NZ : [524288]u8 = [0; 524288];
+pub var A_FARS : [524288]u32 = [0; 524288];
+pub var A_SLOTS : [524288]u32 = [0; 524288];
+pub var A_HIT : [65536]u32 = [0; 65536];
+
+pub var TM : [16392]u32 = [0; 16392];
+pub var MPOOL : [524288]u8 = [0; 524288];
+pub var MWID : [4096]f64 = [0.0; 4096];
+pub var MOK : [4096]u8 = [0; 4096];
+pub var CUR_FONT : [160]u8 = [0; 160];
+pub var CUR_FL : u32 = 0;
+pub var FITB : [1200]u8 = [0; 1200];
+pub var WSP : [32]u32 = [0; 32];
+
+pub fn same_text(a: []const u8, b: []const u8) -> bool {
+ if (a.len != b.len) { return false; }
+ var i : usize = 0;
+ while (i < a.len) {
+ if (a[i] != b[i]) { return false; }
+ i = i + 1;
+ }
+ return true;
+}
+
+fn copy_to(dst: []u8, s: []const u8) -> u32 {
+ var n : usize = s.len;
+ if (n > dst.len) { n = dst.len; }
+ var k : usize = 0;
+ while (k < n) { dst[k] = s[k]; k = k + 1; }
+ return n as u32;
+}
+
+pub fn w_view(width: f64, dpr: f64, embed: u32, cw: f64, pw: f64) -> u32 {
+ ENV_CW = cw;
+ ENV_DPR = dpr;
+ return 0;
+}
+
+pub fn w_init(h: []const u8) -> u32 {
+ SAMPLES_LEN = 0;
+ SAMPLES_ST = 0;
+ GEOM_LEN = 0;
+ GEOM_ST = 0;
+ PARTS_LEN = 0;
+ PARTS_ST = 0;
+ READY = false;
+ SEL = 0 - 1;
+ LINE_ON = false;
+ ST_MODE = 0;
+ HAS_VIEW = false;
+ VIEW_SRC = 0;
+ DL_ON = false;
+ DRAGGING = false;
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ NAME_LEN = 0;
+ FREAD = 0;
+ tm_reset(TM[0..16392]);
+ return 0;
+}
+
+pub fn w_hash(h: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ return 0;
+}
+
+// --- data -----------------------------------------------------------------------------------------
+
+fn store(dst: []u8, s: []const u8, cap: u32) -> u32 {
+ var from : usize = 0;
+ if (s.len >= 3 and s[0] == 239 and s[1] == 187 and s[2] == 191) { from = 3; }
+ const len : usize = s.len - from;
+ if (len > (cap as usize)) { return OVER; }
+ var k : usize = 0;
+ while (k < len) { dst[k] = s[from + k]; k = k + 1; }
+ return len as u32;
+}
+
+pub fn w_data(i: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ if (i == 0) {
+ const l : u32 = store(SAMPLES[0..], s, W_SAMPLES_CAP);
+ if (l == OVER) { SAMPLES_ST = 2; } else {
+ SAMPLES_LEN = l;
+ if (jv_valid(SAMPLES[0..(l as usize)])) { SAMPLES_ST = 1; } else { SAMPLES_ST = 2; }
+ }
+ }
+ if (i == 1) {
+ const l : u32 = store(GEOM[0..], s, W_GEOM_CAP);
+ if (l == OVER) { GEOM_ST = 2; } else {
+ GEOM_LEN = l;
+ if (jv_valid(GEOM[0..(l as usize)])) { GEOM_ST = 1; } else { GEOM_ST = 2; }
+ }
+ }
+ if (i == 2) {
+ const l : u32 = store(PARTS[0..], s, W_PARTS_CAP);
+ if (l == OVER) { PARTS_ST = 2; } else {
+ PARTS_LEN = l;
+ if (jv_valid(PARTS[0..(l as usize)])) { PARTS_ST = 1; } else { PARTS_ST = 2; }
+ }
+ }
+ start_check();
+ return 0;
+}
+
+pub fn w_data_failed(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ if (i == 0) { SAMPLES_ST = 2; }
+ if (i == 1) { GEOM_ST = 2; }
+ if (i == 2) { PARTS_ST = 2; }
+ start_check();
+ return 0;
+}
+
+/// Promise.all of the three files, then state.samples = s.samples ...; showSample(0). A failure of any of them, or a throw, is SAY_ERR_DATA.
+fn start_check() -> u32 {
+ if (SAMPLES_ST == 0 or GEOM_ST == 0 or PARTS_ST == 0) { return 0; }
+ if (READY) { return 0; }
+ if (SAMPLES_ST == 2 or GEOM_ST == 2 or PARTS_ST == 2) {
+ ST_MODE = 1;
+ return 0;
+ }
+ const sd : []const u8 = SAMPLES[0..(SAMPLES_LEN as usize)];
+ if (kind(sd, root(sd)) == J_NULL) {
+ // s.samples throws
+ ST_MODE = 1;
+ return 0;
+ }
+ READY = true;
+ load_tables();
+ const pd : []const u8 = PARTS[0..(PARTS_LEN as usize)];
+ if (kind(pd, root(pd)) == J_NULL) {
+ // p.fpga throws: state.samples is set, showSample(0) never runs
+ ST_MODE = 1;
+ return 0;
+ }
+ if (show_sample(0) == 2) { ST_MODE = 1; }
+ return 0;
+}
+
+/// state.samples?.find((x) => x.id === id): the span, -1 none, -2 when JavaScript throws
+fn sample_find(id: []const u8) -> i64 {
+ if (!READY) { return 0 - 1; }
+ const d : []const u8 = SAMPLES[0..(SAMPLES_LEN as usize)];
+ const r : i64 = root(d);
+ const ss : i64 = obj_get(d, r, "samples");
+ const sk : u8 = kind(d, ss);
+ if (sk == J_NONE or sk == J_NULL) { return 0 - 1; }
+ if (sk != J_ARRAY) { return 0 - 2; }
+ const cnt : u32 = arr_len(d, ss);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const el : i64 = arr_at(d, ss, q);
+ if (kind(d, el) == J_NULL) { return 0 - 2; }
+ const idv : i64 = obj_get(d, el, "id");
+ if (kind(d, idv) == J_STRING and str_is(d, idv, id)) { return el; }
+ q = q + 1;
+ }
+ return 0 - 1;
+}
+
+fn str_is(d: []const u8, sp: i64, want: []const u8) -> bool {
+ if (str_len(d, sp) != (want.len as u32)) { return false; }
+ var k : u32 = 0;
+ while (k < (want.len as u32)) {
+ if (str_byte(d, sp, k) != want[k as usize]) { return false; }
+ k = k + 1;
+ }
+ return true;
+}
+
+/// (s.from?.path || s.from?.file || s.id).split('/').pop()
+fn sample_name(d: []const u8, s: i64) -> u32 {
+ const fr : i64 = obj_get(d, s, "from");
+ var v : i64 = obj_get(d, fr, "path");
+ if (!truthy(d, v)) { v = obj_get(d, fr, "file"); }
+ if (!truthy(d, v)) { v = obj_get(d, s, "id"); }
+ var n : u32 = jv_str(TA[0..], 0, d, v);
+ if (n == OVER) { n = 0; }
+ var from : u32 = 0;
+ var k : u32 = 0;
+ while (k < n) {
+ if (TA[k as usize] == 47) { from = k + 1; }
+ k = k + 1;
+ }
+ var o : u32 = 0;
+ k = from;
+ while (k < n and o < 2000) {
+ NAME[o as usize] = TA[k as usize];
+ o = o + 1;
+ k = k + 1;
+ }
+ return o;
+}
+
+/// showSample(i): 0 shown, 1 no such sample (the status says so), 2 JavaScript throws
+fn show_sample(i: u32) -> u32 {
+ const id : []const u8 = SAY_SAMPLE_IDS[i as usize];
+ const s : i64 = sample_find(id);
+ if (s == 0 - 2) { return 2; }
+ if (s < 0) {
+ ST_MODE = 1;
+ return 1;
+ }
+ SEL = i as i32;
+ LINE_ON = true;
+ ST_MODE = 0;
+ NAME_LEN = sample_name(SAMPLES[0..(SAMPLES_LEN as usize)], s);
+ set_view(1, s);
+ return 0;
+}
+
+fn set_view(src: u32, sp: i64) -> u32 {
+ HAS_VIEW = true;
+ VIEW_SRC = src;
+ VIEW_SP = sp;
+ DL_ON = true;
+ return 0;
+}
+
+fn view_bytes(src: u32) -> []const u8 {
+ if (src == 2) { return OWJ[0..(OWJ_LEN as usize)]; }
+ return SAMPLES[0..(SAMPLES_LEN as usize)];
+}
+
+// --- reading the view (JSON) ---------------------------------------------------------------------
+
+fn jq(d: []const u8, sp: i64, key: []const u8) -> i64 {
+ return obj_get(d, sp, key);
+}
+
+fn is_nullish(d: []const u8, sp: i64) -> bool {
+ const k : u8 = kind(d, sp);
+ return k == J_NONE or k == J_NULL;
+}
+
+fn truthy(d: []const u8, sp: i64) -> bool {
+ if (kind(d, sp) == J_NONE) { return false; }
+ return jv_truthy(d, sp);
+}
+
+/// num(v): '-' for null / undefined, else Number(v).toLocaleString('en-US')
+fn num_j(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (is_nullish(d, sp)) { return put(o, n0, 45); }
+ return js_locale_num(o, n0, jv_num(d, sp));
+}
+
+/// String(v ?? '') raw
+fn str_j(o: []u8, n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (is_nullish(d, sp)) { return n0; }
+ return jv_str(o, n0, d, sp);
+}
+
+fn arr_has(d: []const u8, arr: i64, want: []const u8) -> bool {
+ if (kind(d, arr) != J_ARRAY) { return false; }
+ const cnt : u32 = arr_len(d, arr);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const el : i64 = arr_at(d, arr, q);
+ if (kind(d, el) == J_STRING and str_is(d, el, want)) { return true; }
+ q = q + 1;
+ }
+ return false;
+}
+
+/// pct(a, b) = b ? ((a / b) * 100).toFixed(2) : '0' (a and b are JSON values, used as numbers)
+fn pct_j(o: []u8, n0: u32, d: []const u8, a: i64, b: i64) -> u32 {
+ const bv : f64 = jv_num(d, b);
+ if (bv == 0.0 or bv != bv or kind(d, b) == J_NONE) { return put(o, n0, 48); }
+ return js_to_fixed(o, n0, (jv_num(d, a) / bv) * 100.0, 2);
+}
+
+/// word of SAY_X[index] ?? String(v): a number that indexes the list, else String(v)
+fn word_or(o: []u8, n0: u32, which: u32, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) == J_NUMBER) {
+ const v : f64 = jv_num(d, sp);
+ if (v >= 0.0 and js_floor(v) == v) {
+ if (which == 0 and v < 3.0) { return raw(o, n0, SAY_BLOCKS_SHORT[v as usize]); }
+ if (which == 1 and v < 2.0) { return raw(o, n0, SAY_HALVES[v as usize]); }
+ }
+ }
+ return jv_str(o, n0, d, sp);
+}
+
+/// partName(d): d ? `${d.model} (${d.family})` : SAY_UNKNOWN
+fn part_name(o: []u8, n0: u32, d: []const u8, dev: i64) -> u32 {
+ if (!truthy(d, dev)) { return raw(o, n0, SAY_UNKNOWN); }
+ var n : u32 = jv_str(o, n0, d, jq(d, dev, "model"));
+ n = raw(o, n, " (");
+ n = jv_str(o, n, d, jq(d, dev, "family"));
+ return raw(o, n, ")");
+}
+
+/// onBench(a): 0 false, 1 true, 2 null
+fn on_bench(d: []const u8, a: i64) -> u32 {
+ const ic : i64 = jq(d, a, "idcode");
+ if (kind(d, ic) == J_NULL) { return 2; }
+ const v : f64 = jv_num(d, ic);
+ var m : u64 = 0;
+ if (v == v and v >= 0.0 and v < 4294967296.0) { m = (v as u64) % 268435456; }
+ // (Number(code) & IDCODE_MASK) >>> 0 for a number below 2^32 (a code above is not a 7-series IDCODE)
+ if (m == ((K_BENCH_IDCODE as u64) % 268435456)) { return 1; }
+ return 0;
+}
+
+// --- the page ------------------------------------------------------------------------------------
+
+fn btn_open(dst: []u8, n0: u32, cls: []const u8, pressed: u32, disabled: bool) -> u32 {
+ var n : u32 = raw(dst, n0, "");
+}
+
+pub fn w_render(dst: []u8) -> u32 {
+ var n : u32 = raw(dst, 0, "
");
+ n = text(dst, n, SAY_SAMPLE_LABEL);
+ n = raw(dst, n, "");
+ var i : u32 = 0;
+ while (i < (SAY_SAMPLE_IDS.len as u32)) {
+ var p : u32 = 0;
+ if (SEL == (i as i32)) { p = 1; }
+ n = btn_open(dst, n, "t27-btn", p, false);
+ n = text(dst, n, SAY_SAMPLE_NAMES[i as usize]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ if (LINE_ON and SEL >= 0) { n = text(dst, n, SAY_SAMPLE_LINES[SEL as usize]); }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_SAMPLE_NOTE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_OWN_LABEL);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_DROP);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_LOCAL_NOTE);
+ n = raw(dst, n, "
");
+ if (ST_MODE == 1) { n = text(dst, n, SAY_ERR_DATA); }
+ if (ST_MODE == 2) { n = fill(dst, n, SAY_ERR_READ, FNAME[0..(FNAME_LEN as usize)], "", "", 1); }
+ if (ST_MODE == 3) { n = fill(dst, n, SAY_READING, FNAME[0..(FNAME_LEN as usize)], "", "", 1); }
+ n = raw(dst, n, "
");
+ if (HAS_VIEW) {
+ TH = false;
+ n = view_html(dst, n, view_bytes(VIEW_SRC), VIEW_SP);
+ }
+ n = raw(dst, n, "
");
+ n = btn_open(dst, n, "t27-btn ba-go", 2, !DL_ON);
+ n = text(dst, n, SAY_DOWNLOAD);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PREVIEW_NOTE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_NOT_DECODING);
+ n = raw(dst, n, "
");
+ if (!ok(n)) { return 0; }
+ return n;
+}
+
+// --- the view ------------------------------------------------------------------------------------
+
+fn view_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ const refuse : i64 = jq(d, a, "refuse");
+ const okv : bool = !(kind(d, refuse) == J_ARRAY and arr_len(d, refuse) > 0) and !(truthy(d, refuse) and kind(d, refuse) != J_ARRAY);
+ var n : u32 = verdict_html(dst, n0, d, a);
+ n = refusals_html(dst, n, d, a);
+ // replaceChildren(..., ok ? tiles(a) : null, ok ? mapSection(a) : null, headerTable(a), packetSection(a)): a null argument becomes the text "null"
+ if (okv) { n = tiles_html(dst, n, d, a); } else { n = raw(dst, n, "null"); }
+ if (okv) { n = map_section_html(dst, n, d, a); } else { n = raw(dst, n, "null"); }
+ n = header_html(dst, n, d, a);
+ n = packet_html(dst, n, d, a);
+ return n;
+}
+
+fn verdict_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ var n : u32 = n0;
+ if (arr_has(d, jq(d, a, "refuse"), "nosync")) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, NAME[0..(NAME_LEN as usize)]);
+ return raw(dst, n, "
");
+ }
+ const ic : i64 = jq(d, a, "idcode");
+ const dev : i64 = jq(d, a, "device");
+ const hd : i64 = jq(d, a, "header");
+ const agree : i64 = jq(d, a, "agree");
+ var tone : u32 = 0;
+ var line : u32 = 0;
+ var t1 : u32 = 0;
+ var t2 : u32 = 0;
+ if (kind(d, ic) == J_NULL) {
+ line = 1;
+ tone = 1;
+ } else {
+ if (!truthy(d, dev)) {
+ line = 2;
+ tone = 1;
+ } else {
+ if (!truthy(d, hd)) {
+ line = 3;
+ } else {
+ if (kind(d, agree) == J_FALSE) {
+ line = 4;
+ tone = 2;
+ } else {
+ line = 5;
+ }
+ }
+ }
+ }
+ // `${partName(a.device)}, ${a.idcode}`
+ t2 = part_name(TB[0..], 0, d, dev);
+ t2 = raw(TB[0..], t2, ", ");
+ t2 = jv_str(TB[0..], t2, d, ic);
+ n = raw(dst, n, "
");
+ n = text(dst, n, NAME[0..(NAME_LEN as usize)]);
+ n = raw(dst, n, "
");
+ if (line == 1) { n = text(dst, n, SAY_VERDICT_NO_IDCODE); }
+ if (line == 2) {
+ t1 = str_j(TA[0..], 0, d, ic);
+ n = fill(dst, n, SAY_VERDICT_UNKNOWN, TA[0..(t1 as usize)], "", "", 1);
+ }
+ if (line == 3) { n = fill(dst, n, SAY_VERDICT_NO_HEADER, TB[0..(t2 as usize)], "", "", 1); }
+ if (line == 4 or line == 5) {
+ t1 = str_j(TA[0..], 0, d, jq(d, hd, "part"));
+ if (line == 4) { n = fill(dst, n, SAY_VERDICT_DISAGREE, TA[0..(t1 as usize)], TB[0..(t2 as usize)], "", 2); } else { n = fill(dst, n, SAY_VERDICT_AGREE, TA[0..(t1 as usize)], TB[0..(t2 as usize)], "", 2); }
+ }
+ n = raw(dst, n, "
");
+ const bnch : u32 = on_bench(d, a);
+ if (bnch != 2) {
+ if (bnch == 1) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BENCH_MATCH);
+ } else {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_BENCH_MISMATCH);
+ }
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "
");
+}
+
+fn id_text(o: []u8, n0: u32, d: []const u8, id: i64, ids_n: u32, which: u32) -> u32 {
+ // SAY_REFUSE_TEXT?.[ids.indexOf(id)] ?? id
+ if (kind(d, id) == J_STRING) {
+ var w : u32 = 0;
+ while (w < ids_n) {
+ if (which == 0 and str_is(d, id, SAY_REFUSE_IDS[w as usize])) { return raw(o, n0, SAY_REFUSE_TEXT[w as usize]); }
+ if (which == 1 and str_is(d, id, SAY_WARN_IDS[w as usize])) { return raw(o, n0, SAY_WARN_TEXT[w as usize]); }
+ w = w + 1;
+ }
+ }
+ return jv_str(o, n0, d, id);
+}
+
+fn refusals_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ var n : u32 = n0;
+ var pass : u32 = 0;
+ while (pass < 2) {
+ var arr : i64 = jq(d, a, "refuse");
+ if (pass == 1) { arr = jq(d, a, "warnings"); }
+ if (kind(d, arr) == J_ARRAY) {
+ const cnt : u32 = arr_len(d, arr);
+ var q : u32 = 0;
+ while (q < cnt) {
+ if (pass == 0) { n = raw(dst, n, "
"); } else { n = raw(dst, n, "
"); }
+ const l : u32 = id_text(TA[0..], 0, d, arr_at(d, arr, q), 3, pass);
+ n = text(dst, n, TA[0..(l as usize)]);
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < 6) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_STATS[i as usize]);
+ n = raw(dst, n, "
");
+ var l : u32 = 0;
+ var sub : u32 = 0;
+ var has_sub : bool = false;
+ const bytes : i64 = jq(d, a, "bytes");
+ if (i == 0) { l = num_j(TA[0..], 0, d, bytes); }
+ if (i == 1) {
+ const p : u32 = pct_j(TB[0..], 0, d, jq(d, a, "zeroBytes"), bytes);
+ l = fill_plain(TA[0..], 0, SAY_PERCENT, TB[0..(p as usize)], "", "", 1);
+ const x : u32 = num_j(TB[0..], 0, d, jq(d, a, "zeroBytes"));
+ const y : u32 = num_j(TC[0..], 0, d, bytes);
+ sub = fill_plain(TD[0..], 0, SAY_OF, TB[0..(x as usize)], TC[0..(y as usize)], "", 2);
+ has_sub = sub > 0;
+ }
+ if (i == 2) { l = num_j(TA[0..], 0, d, jq(d, a, "configFrames")); }
+ if (i == 3) {
+ l = num_j(TA[0..], 0, d, jq(d, a, "nonzeroFrames"));
+ const p : u32 = pct_j(TB[0..], 0, d, jq(d, a, "nonzeroFrames"), jq(d, a, "configFrames"));
+ sub = fill_plain(TD[0..], 0, SAY_PERCENT, TB[0..(p as usize)], "", "", 1);
+ has_sub = sub > 0;
+ }
+ if (i == 4) { l = num_j(TA[0..], 0, d, jq(d, a, "ones")); }
+ if (i == 5) {
+ const pk : i64 = jq(d, a, "packets");
+ if (truthy(d, pk)) {
+ l = num_j(TA[0..], 0, d, jq(d, pk, "cmd"));
+ l = raw(TA[0..], l, ", ");
+ l = num_j(TA[0..], l, d, jq(d, pk, "crc"));
+ } else {
+ l = raw(TA[0..], 0, "-");
+ }
+ }
+ n = text(dst, n, TA[0..(l as usize)]);
+ n = raw(dst, n, "
");
+ if (has_sub) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, TD[0..(sub as usize)]);
+ n = raw(dst, n, "
");
+ }
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ return raw(dst, n, "
");
+}
+
+/// {0} {1} {2} filled from k values, the values and the template as they are (no HTML escaping)
+fn fill_plain(o: []u8, n0: u32, t: []const u8, a: []const u8, b: []const u8, c: []const u8, k: u32) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < t.len) {
+ if (t[i] == 123 and i + 2 < t.len and t[i + 2] == 125 and t[i + 1] >= 48 and t[i + 1] < 48 + (k as u8) and t[i + 1] <= 50) {
+ if (t[i + 1] == 48) { n = put_all(o, n, a); }
+ if (t[i + 1] == 49) { n = put_all(o, n, b); }
+ if (t[i + 1] == 50) { n = put_all(o, n, c); }
+ i = i + 3;
+ } else {
+ n = put(o, n, t[i]);
+ i = i + 1;
+ }
+ }
+ return n;
+}
+
+fn put_all(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = n0;
+ var i : usize = 0;
+ while (i < s.len) {
+ n = put(o, n, s[i]);
+ i = i + 1;
+ }
+ return n;
+}
+
+fn header_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ const hd : i64 = jq(d, a, "header");
+ const hdt : bool = truthy(d, hd);
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_HEADER_TITLE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_FIELD);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COL_VALUE);
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < 12) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_FIELDS[i as usize]);
+ n = raw(dst, n, "
");
+ if (i == 0) { n = text(dst, n, NAME[0..(NAME_LEN as usize)]); }
+ if (i == 1) { const l : u32 = num_j(TA[0..], 0, d, jq(d, a, "bytes")); n = text(dst, n, TA[0..(l as usize)]); }
+ if (i == 2) {
+ const sh : i64 = jq(d, a, "sha256");
+ if (truthy(d, sh)) { n = jv_kid(dst, n, d, sh); } else { n = raw(dst, n, "-"); }
+ }
+ if (i >= 3 and i <= 6) {
+ if (hdt) {
+ if (i == 3) { n = jv_kid(dst, n, d, jq(d, hd, "design")); }
+ if (i == 4) { n = jv_kid(dst, n, d, jq(d, hd, "part")); }
+ if (i == 5) { n = jv_kid(dst, n, d, jq(d, hd, "date")); }
+ if (i == 6) { n = jv_kid(dst, n, d, jq(d, hd, "time")); }
+ } else {
+ if (i == 3) { n = text(dst, n, SAY_NO_HEADER); } else { n = raw(dst, n, "-"); }
+ }
+ }
+ if (i == 7) {
+ if (hdt) {
+ const l : u32 = num_j(TA[0..], 0, d, jq(d, hd, "length"));
+ if (truthy(d, jq(d, a, "headerLengthOk"))) { n = fill(dst, n, SAY_LENGTH_OK, TA[0..(l as usize)], "", "", 1); } else { n = fill(dst, n, SAY_LENGTH_BAD, TA[0..(l as usize)], "", "", 1); }
+ } else {
+ n = raw(dst, n, "-");
+ }
+ }
+ if (i == 8) {
+ const sy : i64 = jq(d, a, "sync");
+ if (jv_num(d, sy) >= 0.0) {
+ const l : u32 = num_j(TA[0..], 0, d, sy);
+ n = text(dst, n, TA[0..(l as usize)]);
+ } else {
+ n = raw(dst, n, "-");
+ }
+ }
+ if (i == 9) {
+ const ic : i64 = jq(d, a, "idcode");
+ if (is_nullish(d, ic)) { n = raw(dst, n, "-"); } else { n = jv_kid(dst, n, d, ic); }
+ }
+ if (i == 10) {
+ if (kind(d, jq(d, a, "idcode")) == J_NULL) {
+ n = raw(dst, n, "-");
+ } else {
+ const l : u32 = part_name(TA[0..], 0, d, jq(d, a, "device"));
+ n = text(dst, n, TA[0..(l as usize)]);
+ }
+ }
+ if (i == 11) {
+ if (truthy(d, jq(d, a, "mapped"))) {
+ const l : u32 = str_j(TA[0..], 0, d, jq(d, a, "geometry"));
+ n = fill(dst, n, SAY_LAYOUT_MAPPED, TA[0..(l as usize)], "", "", 1);
+ } else {
+ n = text(dst, n, SAY_LAYOUT_STREAM);
+ }
+ }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PACKETS_TITLE);
+ n = raw(dst, n, "
");
+ var i : u32 = 0;
+ while (i < 8) {
+ var key : []const u8 = "type1";
+ if (i == 1) { key = "type2"; }
+ if (i == 2) { key = "nop"; }
+ if (i == 3) { key = "writes"; }
+ if (i == 4) { key = "cmd"; }
+ if (i == 5) { key = "crc"; }
+ if (i == 6) { key = "far"; }
+ if (i == 7) { key = "fdri"; }
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_PACKET_FIELDS[i as usize]);
+ n = raw(dst, n, "
");
+ const l : u32 = num_j(TA[0..], 0, d, jq(d, pk, key));
+ n = text(dst, n, TA[0..(l as usize)]);
+ n = raw(dst, n, "
");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ const cm : i64 = jq(d, a, "commands");
+ if (truthy(d, cm) and arr_len(d, cm) > 0) {
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_COMMANDS);
+ n = raw(dst, n, " ");
+ const cnt : u32 = arr_len(d, cm);
+ var q : u32 = 0;
+ while (q < cnt) {
+ if (q > 0) { n = raw(dst, n, " > "); }
+ n = jv_text(dst, n, d, arr_at(d, cm, q));
+ q = q + 1;
+ }
+ n = raw(dst, n, "
");
+ }
+ return raw(dst, n, "");
+}
+
+// --- the frame map -------------------------------------------------------------------------------
+
+fn strip_of(d: []const u8, a: i64) -> i64 {
+ const s : i64 = jq(d, a, "strip");
+ if (kind(d, s) != J_ARRAY) { return NO_SPAN; }
+ return s;
+}
+
+fn map_w() -> f64 {
+ var w : f64 = 600.0;
+ if (ENV_CW != 0.0 and ENV_CW == ENV_CW) { w = ENV_CW; }
+ return js_max(280.0, w);
+}
+
+fn map_dpr() -> f64 {
+ if (ENV_DPR == 0.0 or ENV_DPR != ENV_DPR) { return 1.0; }
+ return ENV_DPR;
+}
+
+fn map_section_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 {
+ const strip : i64 = strip_of(d, a);
+ if (strip < 0 or arr_len(d, strip) == 0) { return raw(dst, n0, "null"); }
+ const rows : u32 = arr_len(d, strip);
+ const wcss : f64 = map_w();
+ var band : f64 = 16.0;
+ if (wcss < 480.0) { band = 12.0; }
+ const hcss : f64 = (rows as f64) * (band + 3.0);
+ const dpr : f64 = map_dpr();
+ var n : u32 = raw(dst, n0, "
");
+ n = text(dst, n, SAY_MAP_TITLE);
+ n = raw(dst, n, "
");
+ n = text(dst, n, SAY_MAP_LINE);
+ n = raw(dst, n, "
");
+ var i : usize = 0;
+ while (i < 3) {
+ n = raw(dst, n, "");
+ n = text(dst, n, SAY_LEGEND[i]);
+ n = raw(dst, n, "");
+ i = i + 1;
+ }
+ n = raw(dst, n, "
");
+ const bl : i64 = jq(d, a, "blocks");
+ if (kind(d, bl) == J_ARRAY) {
+ const cnt : u32 = arr_len(d, bl);
+ var q : u32 = 0;
+ while (q < cnt) {
+ const b : i64 = arr_at(d, bl, q);
+ n = raw(dst, n, "
");
+ const x : u32 = word_or(TA[0..], 0, 0, d, jq(d, b, "block"));
+ const y : u32 = num_j(TB[0..], 0, d, jq(d, b, "nonzero"));
+ const z : u32 = num_j(TC[0..], 0, d, jq(d, b, "frames"));
+ n = fill(dst, n, SAY_BLOCK_LINE, TA[0..(x as usize)], TB[0..(y as usize)], TC[0..(z as usize)], 3);
+ n = raw(dst, n, "
");
+ q = q + 1;
+ }
+ }
+ return raw(dst, n, "");
+}
+
+/// bandName(b): the label of a strip entry, into o
+fn band_name(o: []u8, n0: u32, d: []const u8, b: i64) -> u32 {
+ if (kind(d, jq(d, b, "block")) == J_NULL) { return raw(o, n0, SAY_STREAM_BAND); }
+ var n : u32 = word_or(o, n0, 0, d, jq(d, b, "block"));
+ n = put(o, n, 32);
+ n = word_or(o, n, 1, d, jq(d, b, "bottom"));
+ n = put(o, n, 32);
+ return jv_str(o, n, d, jq(d, b, "row"));
+}
+
+/// drawStrip(ctx, strip, x, y, w, bandH, gap, labelW, c, font): font is the size text before "px " in the map's font
+fn draw_strip(o: []u8, n0: u32, d: []const u8, strip: i64, x: f64, y: f64, w: f64, band: f64, gap: f64, label: f64, px: f64) -> u32 {
+ var n : u32 = cv_open(o, n0, 115, "font");
+ n = cv_raw(o, n, ",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"");
+ n = js_str_num(o, n, px);
+ n = cv_raw(o, n, "px \"}");
+ n = cv_close(o, n);
+ n = cv_set_str(o, n, "textBaseline", "middle");
+ const rows : u32 = arr_len(d, strip);
+ var ri : u32 = 0;
+ while (ri < rows) {
+ const b : i64 = arr_at(d, strip, ri);
+ const by : f64 = y + (ri as f64) * (band + gap);
+ n = cv_set_var(o, n, "fillStyle", "--w-muted", "#8fa9a0");
+ n = cv_set_str(o, n, "textAlign", "left");
+ const l : u32 = band_name(TA[0..], 0, d, b);
+ n = cv_text(o, n, "fillText", TA[0..(l as usize)], x, by + band / 2.0);
+ const cells : i64 = jq(d, b, "cells");
+ const cnt : u32 = str_len(d, cells);
+ const cw : f64 = (w - label) / (cnt as f64);
+ var j : u32 = 0;
+ while (j < cnt) {
+ const ch : u8 = str_byte(d, cells, j);
+ if (ch == 45) {
+ n = cv_set_var(o, n, "fillStyle", "--w-line", "#1d2b27");
+ n = cv_set_num(o, n, "globalAlpha", 0.5);
+ } else {
+ if (ch == 48) {
+ n = cv_set_var(o, n, "fillStyle", "--w-green", "#00ff88");
+ n = cv_set_num(o, n, "globalAlpha", 0.16);
+ } else {
+ n = cv_set_var(o, n, "fillStyle", "--w-gold", "#ffd700");
+ n = cv_set_num(o, n, "globalAlpha", 0.35 + (0.65 * ((ch - 48) as f64)) / 9.0);
+ }
+ }
+ n = cv_call4(o, n, "fillRect", x + label + (j as f64) * cw, by, js_max(1.0, cw - 0.4), band);
+ j = j + 1;
+ }
+ n = cv_set_num(o, n, "globalAlpha", 1.0);
+ ri = ri + 1;
+ }
+ return n;
+}
+
+pub fn w_draw(dst: []u8) -> u32 {
+ if (!HAS_VIEW) { return 0; }
+ const d : []const u8 = view_bytes(VIEW_SRC);
+ const a : i64 = VIEW_SP;
+ const refuse : i64 = jq(d, a, "refuse");
+ if (kind(d, refuse) == J_ARRAY and arr_len(d, refuse) > 0) { return 0; }
+ const strip : i64 = strip_of(d, a);
+ if (strip < 0 or arr_len(d, strip) == 0) { return 0; }
+ const wcss : f64 = map_w();
+ const narrow : bool = wcss < 480.0;
+ var band : f64 = 16.0;
+ var label : f64 = 92.0;
+ var px : f64 = 11.0;
+ if (narrow) {
+ band = 12.0;
+ label = 66.0;
+ px = 9.0;
+ }
+ const hcss : f64 = (arr_len(d, strip) as f64) * (band + 3.0);
+ const dpr : f64 = map_dpr();
+ var n : u32 = cv_canvas(dst, 0, 0);
+ n = cv_call6(dst, n, "setTransform", dpr, 0.0, 0.0, dpr, 0.0, 0.0);
+ n = cv_call4(dst, n, "clearRect", 0.0, 0.0, wcss, hcss);
+ n = draw_strip(dst, n, d, strip, 0.0, 0.0, wcss, band, 3.0, label, px);
+ if (!cv_ok(n)) { return 0; }
+ return n;
+}
+
+// --- tables for the reader (geometry.json and parts.json) ----------------------------------------
+
+/// geometry.json flat as autopsy() wants it: [count], then per device idcode, rows, per row block, bottom, row, columns, the column frame counts;
+/// parts.json: the IDCODE of each entry (low 32 bits of Number(p[0])) and its model, one per line.
+fn load_tables() -> u32 {
+ G_LEN = 0;
+ P_N = 0;
+ P_MODELS_LEN = 0;
+ if (GEOM_ST == 1) {
+ const d : []const u8 = GEOM[0..(GEOM_LEN as usize)];
+ const r : i64 = root(d);
+ if (kind(d, r) == J_ARRAY) {
+ const cnt : u32 = arr_len(d, r);
+ var p : usize = 1;
+ var done : u32 = 0;
+ var q : u32 = 0;
+ while (q < cnt) {
+ const g : i64 = arr_at(d, r, q);
+ const rows : i64 = obj_get(d, g, "rows");
+ const nr : u32 = arr_len(d, rows);
+ if (p + 2 > 70000) { q = cnt; } else {
+ const start : usize = p;
+ G_FLAT[p] = num_i64(d, obj_get(d, g, "idcode")) as u32;
+ G_FLAT[p + 1] = nr;
+ p = p + 2;
+ var ri : u32 = 0;
+ var fits : bool = true;
+ while (ri < nr and fits) {
+ const row : i64 = arr_at(d, rows, ri);
+ const cols : i64 = arr_at(d, row, 3);
+ const nc : u32 = arr_len(d, cols);
+ if (p + 4 + (nc as usize) > 70000) { fits = false; } else {
+ G_FLAT[p] = num_i64(d, arr_at(d, row, 0)) as u32;
+ G_FLAT[p + 1] = num_i64(d, arr_at(d, row, 1)) as u32;
+ G_FLAT[p + 2] = num_i64(d, arr_at(d, row, 2)) as u32;
+ G_FLAT[p + 3] = nc;
+ var c : u32 = 0;
+ while (c < nc) {
+ G_FLAT[p + 4 + (c as usize)] = num_i64(d, arr_at(d, cols, c)) as u32;
+ c = c + 1;
+ }
+ p = p + 4 + (nc as usize);
+ ri = ri + 1;
+ }
+ }
+ if (!fits) { p = start; q = cnt; } else { done = done + 1; q = q + 1; }
+ }
+ }
+ G_FLAT[0] = done;
+ G_LEN = p as u32;
+ }
+ }
+ if (PARTS_ST == 1) {
+ const d : []const u8 = PARTS[0..(PARTS_LEN as usize)];
+ const r : i64 = root(d);
+ const fp : i64 = obj_get(d, r, "fpga");
+ if (kind(d, fp) == J_ARRAY) {
+ const cnt : u32 = arr_len(d, fp);
+ var q : u32 = 0;
+ while (q < cnt and q < 4096) {
+ const e : i64 = arr_at(d, fp, q);
+ const v : f64 = jv_num(d, arr_at(d, e, 0));
+ var id : u32 = 0;
+ if (v == v and v >= 0.0 and v < 4294967296.0) { id = v as u32; }
+ P_IDS[q as usize] = id;
+ const ml : u32 = jv_str(P_MODELS[(P_MODELS_LEN as usize)..65536], 0, d, arr_at(d, e, 2));
+ P_MODELS_LEN = P_MODELS_LEN + ml;
+ P_MODELS[P_MODELS_LEN as usize] = 10;
+ P_MODELS_LEN = P_MODELS_LEN + 1;
+ q = q + 1;
+ }
+ P_N = q;
+ }
+ }
+ return 0;
+}
+
+// --- events ----------------------------------------------------------------------------------------
+
+pub fn w_click(i: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ if (i < (SAY_SAMPLE_IDS.len as u32)) {
+ show_sample(i);
+ return 0;
+ }
+ if (i == (SAY_SAMPLE_IDS.len as u32) and HAS_VIEW) { EFF_PNG = true; }
+ return 0;
+}
+
+pub fn w_drag(z: u32, over: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ const want : bool = over != 0;
+ if (want == DRAGGING) { return 0; }
+ DRAGGING = want;
+ return 1;
+}
+
+pub fn w_file(z: u32, name: []const u8, size: u32) -> u32 {
+ EFF_CLEAR = false;
+ EFF_PNG = false;
+ // press(-1), the sample line goes, status says Reading
+ SEL = 0 - 1;
+ LINE_ON = false;
+ FNAME_LEN = copy_to(FNAME[0..], name);
+ ST_MODE = 3;
+ FREAD = 0;
+ return 1;
+}
+
+pub fn w_file_failed(z: u32) -> u32 {
+ EFF_CLEAR = true;
+ EFF_PNG = false;
+ ST_MODE = 2;
+ return 0;
+}
+
+pub fn w_file_data(z: u32, s: []const u8) -> u32 {
+ EFF_CLEAR = true;
+ EFF_PNG = false;
+ if (s.len > (W_FILE_CAP as usize)) {
+ ST_MODE = 2;
+ return 0;
+ }
+ var k : usize = 0;
+ while (k < s.len) { FILE[k] = s[k]; k = k + 1; }
+ FLEN = s.len as u32;
+ const u : []const u8 = FILE[0..(FLEN as usize)];
+ const rc : u32 = autopsy(u, G_FLAT[0..(G_LEN as usize)], P_IDS[0..(P_N as usize)], P_MODELS[0..(P_MODELS_LEN as usize)], W_BUCKETS as usize, A_RES[0..64], A_LISTS[0..100000], A_CELLS[0..65536], A_STRS[0..2048], A_FDRI[0..16384], A_CMDS[0..65536], A_NZ[0..524288], A_FARS[0..524288], A_SLOTS[0..524288], A_HIT[0..65536]);
+ if (rc != 0) {
+ ST_MODE = 2;
+ return 0;
+ }
+ sha256_hex(u, SHA_H[0..8], SHA_W[0..64], SHA_O[0..64]);
+ const l : u32 = ba_write_view(u);
+ if (l == OVER) {
+ ST_MODE = 2;
+ return 0;
+ }
+ OWJ_LEN = l;
+ NAME_LEN = copy_to(NAME[0..], FNAME[0..(FNAME_LEN as usize)]);
+ ST_MODE = 0;
+ set_view(2, root(OWJ[0..(OWJ_LEN as usize)]));
+ return 0;
+}
+
+pub fn w_effect(k: u32, dst: []u8) -> u32 {
+ if (EFF_CLEAR and k < 1) {
+ dst[0] = 118;
+ dst[1] = 0;
+ return 2;
+ }
+ if (EFF_PNG and k == 0 and HAS_VIEW) {
+ var n : u32 = 0;
+ dst[0] = 112;
+ n = 1;
+ n = put_dec(dst, n, K_CARD_W as u64);
+ dst[n as usize] = 9;
+ n = n + 1;
+ n = put_dec(dst, n, K_CARD_H as u64);
+ dst[n as usize] = 9;
+ n = n + 1;
+ // bit-autopsy-${name.replace(/[^A-Za-z0-9._-]+/g, '_')}.png
+ var tmp : [2100]u8 = [0; 2100];
+ var tn : u32 = 0;
+ var in_run : bool = false;
+ var q : u32 = 0;
+ while (q < NAME_LEN) {
+ const c : u8 = NAME[q as usize];
+ if ((c >= 65 and c <= 90) or (c >= 97 and c <= 122) or (c >= 48 and c <= 57) or c == 46 or c == 95 or c == 45) {
+ tmp[tn as usize] = c;
+ tn = tn + 1;
+ in_run = false;
+ } else {
+ if (!in_run) {
+ tmp[tn as usize] = 95;
+ tn = tn + 1;
+ }
+ in_run = true;
+ }
+ q = q + 1;
+ }
+ n = raw(dst, n, "bit-autopsy-");
+ n = raw(dst, n, tmp[0..(tn as usize)]);
+ return raw(dst, n, ".png");
+ }
+ return 0;
+}
+
+fn put_dec(dst: []u8, n0: u32, v: u64) -> u32 {
+ var tmp : [24]u8 = [0; 24];
+ const l : u32 = dec_to(tmp[0..], v);
+ var k : u32 = 0;
+ var n : u32 = n0;
+ while (k < l) {
+ dst[n as usize] = tmp[k as usize];
+ n = n + 1;
+ k = k + 1;
+ }
+ return n;
+}
+
+// --- an own file as JSON (what autopsy() returns) ----------------------------------------------
+
+fn jw_utf8(o: []u8, n0: u32, s: []const u8) -> u32 {
+ var n : u32 = put(o, n0, 34);
+ var i : usize = 0;
+ while (i < s.len) {
+ n = jw_byte(o, n, s[i]);
+ i = i + 1;
+ }
+ return put(o, n, 34);
+}
+
+fn jw_byte(o: []u8, n0: u32, b: u8) -> u32 {
+ var n : u32 = n0;
+ if (b == 34 or b == 92) {
+ n = put(o, n, 92);
+ return put(o, n, b);
+ }
+ if (b < 32) {
+ n = raw(o, n, "\\u00");
+ n = put(o, n, sh_hexd((b / 16) as u64));
+ return put(o, n, sh_hexd((b % 16) as u64));
+ }
+ return put(o, n, b);
+}
+
+/// the bytes of a header field as the characters U+00xx (String.fromCharCode)
+fn jw_latin1(o: []u8, n0: u32, u: []const u8, start: usize, len: usize) -> u32 {
+ var n : u32 = put(o, n0, 34);
+ var i : usize = 0;
+ while (i < len) {
+ const b : u8 = u[start + i];
+ if (b >= 128) {
+ n = raw(o, n, "\\u00");
+ n = put(o, n, sh_hexd((b / 16) as u64));
+ n = put(o, n, sh_hexd((b % 16) as u64));
+ } else {
+ n = jw_byte(o, n, b);
+ }
+ i = i + 1;
+ }
+ return put(o, n, 34);
+}
+
+fn jw_u(o: []u8, n: u32, v: u64) -> u32 {
+ return num(o, n, v);
+}
+
+fn jw_hex8(o: []u8, n0: u32, v: u32) -> u32 {
+ var n : u32 = raw(o, n0, "\"0x");
+ var k : u64 = 0;
+ while (k < 8) {
+ n = put(o, n, sh_hexd(((v as u64) >> (28 - 4 * k)) & 15));
+ k = k + 1;
+ }
+ return put(o, n, 34);
+}
+
+fn jw_bool(o: []u8, n: u32, b: bool) -> u32 {
+ if (b) { return raw(o, n, "true"); }
+ return raw(o, n, "false");
+}
+
+fn jw_cmd(o: []u8, n0: u32, v: u32) -> u32 {
+ var n : u32 = put(o, n0, 34);
+ if (cmd_name_known(v)) {
+ n = raw(o, n, CMD_NAMES[v as usize]);
+ } else {
+ n = raw(o, n, "CMD");
+ n = num(o, n, v as u64);
+ }
+ return put(o, n, 34);
+}
+
+fn ba_write_view(u: []const u8) -> u32 {
+ const r : []const u64 = A_RES[0..64];
+ var n : u32 = raw(OWJ[0..], 0, "{\"bytes\":");
+ n = jw_u(OWJ[0..], n, r[0]);
+ n = raw(OWJ[0..], n, ",\"kind\":");
+ if (r[1] == 1) { n = raw(OWJ[0..], n, "\"bit\""); } else { n = raw(OWJ[0..], n, "\"bin\""); }
+ n = raw(OWJ[0..], n, ",\"sha256\":");
+ n = jw_utf8(OWJ[0..], n, SHA_O[0..64]);
+ n = raw(OWJ[0..], n, ",\"header\":");
+ if (r[2] == 1) {
+ n = raw(OWJ[0..], n, "{\"design\":");
+ n = jw_latin1(OWJ[0..], n, u, r[3] as usize, r[4] as usize);
+ n = raw(OWJ[0..], n, ",\"part\":");
+ n = jw_latin1(OWJ[0..], n, u, r[5] as usize, r[6] as usize);
+ n = raw(OWJ[0..], n, ",\"date\":");
+ n = jw_latin1(OWJ[0..], n, u, r[7] as usize, r[8] as usize);
+ n = raw(OWJ[0..], n, ",\"time\":");
+ n = jw_latin1(OWJ[0..], n, u, r[9] as usize, r[10] as usize);
+ n = raw(OWJ[0..], n, ",\"length\":");
+ n = jw_u(OWJ[0..], n, r[11]);
+ n = raw(OWJ[0..], n, "}");
+ } else {
+ n = raw(OWJ[0..], n, "null");
+ }
+ n = raw(OWJ[0..], n, ",\"headerLengthOk\":");
+ if (r[12] == 2) { n = raw(OWJ[0..], n, "null"); } else { n = jw_bool(OWJ[0..], n, r[12] == 1); }
+ n = raw(OWJ[0..], n, ",\"sync\":");
+ if (r[13] == NONE64) { n = raw(OWJ[0..], n, "-1"); } else { n = jw_u(OWJ[0..], n, r[13]); }
+ n = raw(OWJ[0..], n, ",\"idcode\":");
+ if (r[14] == 1) { n = jw_hex8(OWJ[0..], n, r[15] as u32); } else { n = raw(OWJ[0..], n, "null"); }
+ n = raw(OWJ[0..], n, ",\"idcodeVersion\":");
+ if (r[14] == 1) { n = jw_u(OWJ[0..], n, r[16]); } else { n = raw(OWJ[0..], n, "null"); }
+ n = raw(OWJ[0..], n, ",\"device\":");
+ if (r[17] == NONE64) {
+ n = raw(OWJ[0..], n, "null");
+ } else {
+ // {idcode, family, model}: the entry of parts.json, as it is written there
+ const pd : []const u8 = PARTS[0..(PARTS_LEN as usize)];
+ const e : i64 = arr_at(pd, obj_get(pd, root(pd), "fpga"), r[17] as u32);
+ n = raw(OWJ[0..], n, "{\"idcode\":");
+ n = jw_json_value(n, pd, arr_at(pd, e, 0));
+ n = raw(OWJ[0..], n, ",\"family\":");
+ n = jw_json_value(n, pd, arr_at(pd, e, 1));
+ n = raw(OWJ[0..], n, ",\"model\":");
+ n = jw_json_value(n, pd, arr_at(pd, e, 2));
+ n = raw(OWJ[0..], n, "}");
+ }
+ n = raw(OWJ[0..], n, ",\"headerDie\":");
+ if (r[19] == NONE64) { n = raw(OWJ[0..], n, "null"); } else { n = jw_utf8(OWJ[0..], n, A_STRS[(r[18] as usize)..((r[18] + r[19]) as usize)]); }
+ n = raw(OWJ[0..], n, ",\"idDie\":");
+ if (r[21] == NONE64) { n = raw(OWJ[0..], n, "null"); } else { n = jw_utf8(OWJ[0..], n, A_STRS[(r[20] as usize)..((r[20] + r[21]) as usize)]); }
+ n = raw(OWJ[0..], n, ",\"agree\":");
+ if (r[22] == 2) { n = raw(OWJ[0..], n, "null"); } else { n = jw_bool(OWJ[0..], n, r[22] == 1); }
+ n = raw(OWJ[0..], n, ",\"zeroBytes\":");
+ n = jw_u(OWJ[0..], n, r[23]);
+ n = raw(OWJ[0..], n, ",\"refuse\":[");
+ var k : u64 = 0;
+ while (k < r[24]) {
+ if (k > 0) { n = put(OWJ[0..], n, 44); }
+ n = put(OWJ[0..], n, 34);
+ const code : u64 = r[25 + (k as usize)];
+ if (code == 1) { n = raw(OWJ[0..], n, SAY_REFUSE_IDS[1]); }
+ if (code == 2) { n = raw(OWJ[0..], n, SAY_REFUSE_IDS[2]); }
+ if (code == 3) { n = raw(OWJ[0..], n, SAY_REFUSE_IDS[0]); }
+ n = put(OWJ[0..], n, 34);
+ k = k + 1;
+ }
+ n = raw(OWJ[0..], n, "],\"warnings\":[");
+ k = 0;
+ while (k < r[27]) {
+ if (k > 0) { n = put(OWJ[0..], n, 44); }
+ n = put(OWJ[0..], n, 34);
+ n = raw(OWJ[0..], n, SAY_WARN_IDS[(r[28 + (k as usize)] - 1) as usize]);
+ n = put(OWJ[0..], n, 34);
+ k = k + 1;
+ }
+ n = raw(OWJ[0..], n, "],\"packets\":");
+ if (r[42] == 1) {
+ n = raw(OWJ[0..], n, "{\"type1\":");
+ n = jw_u(OWJ[0..], n, r[31]);
+ n = raw(OWJ[0..], n, ",\"type2\":");
+ n = jw_u(OWJ[0..], n, r[32]);
+ n = raw(OWJ[0..], n, ",\"nop\":");
+ n = jw_u(OWJ[0..], n, r[33]);
+ n = raw(OWJ[0..], n, ",\"pad\":");
+ n = jw_u(OWJ[0..], n, r[34]);
+ n = raw(OWJ[0..], n, ",\"writes\":");
+ n = jw_u(OWJ[0..], n, r[35]);
+ n = raw(OWJ[0..], n, ",\"reads\":");
+ n = jw_u(OWJ[0..], n, r[36]);
+ n = raw(OWJ[0..], n, ",\"cmd\":");
+ n = jw_u(OWJ[0..], n, r[37]);
+ n = raw(OWJ[0..], n, ",\"crc\":");
+ n = jw_u(OWJ[0..], n, r[38]);
+ n = raw(OWJ[0..], n, ",\"far\":");
+ n = jw_u(OWJ[0..], n, r[39]);
+ n = raw(OWJ[0..], n, ",\"fdri\":");
+ n = jw_u(OWJ[0..], n, r[40]);
+ n = raw(OWJ[0..], n, ",\"other\":");
+ n = jw_u(OWJ[0..], n, r[41]);
+ n = raw(OWJ[0..], n, "}");
+ } else {
+ n = raw(OWJ[0..], n, "null");
+ }
+ n = raw(OWJ[0..], n, ",\"commands\":[");
+ k = 0;
+ while (k < r[43]) {
+ if (k > 0) { n = put(OWJ[0..], n, 44); }
+ n = jw_cmd(OWJ[0..], n, A_LISTS[k as usize]);
+ k = k + 1;
+ }
+ n = raw(OWJ[0..], n, "],\"frameWords\":");
+ n = jw_u(OWJ[0..], n, r[44]);
+ n = raw(OWJ[0..], n, ",\"frameSlots\":");
+ n = jw_u(OWJ[0..], n, r[45]);
+ n = raw(OWJ[0..], n, ",\"padFrames\":");
+ n = jw_u(OWJ[0..], n, r[46]);
+ n = raw(OWJ[0..], n, ",\"configFrames\":");
+ n = jw_u(OWJ[0..], n, r[47]);
+ n = raw(OWJ[0..], n, ",\"nonzeroFrames\":");
+ n = jw_u(OWJ[0..], n, r[48]);
+ n = raw(OWJ[0..], n, ",\"ones\":");
+ n = jw_u(OWJ[0..], n, r[49]);
+ n = raw(OWJ[0..], n, ",\"blocks\":");
+ if (r[50] == 1) {
+ n = put(OWJ[0..], n, 91);
+ var first : bool = true;
+ var b : usize = 0;
+ while (b < 3) {
+ if (r[52 + 2 * b] > 0) {
+ if (!first) { n = put(OWJ[0..], n, 44); }
+ first = false;
+ n = raw(OWJ[0..], n, "{\"block\":");
+ n = jw_u(OWJ[0..], n, b as u64);
+ n = raw(OWJ[0..], n, ",\"frames\":");
+ n = jw_u(OWJ[0..], n, r[52 + 2 * b]);
+ n = raw(OWJ[0..], n, ",\"nonzero\":");
+ n = jw_u(OWJ[0..], n, r[53 + 2 * b]);
+ n = raw(OWJ[0..], n, "}");
+ }
+ b = b + 1;
+ }
+ n = put(OWJ[0..], n, 93);
+ } else {
+ n = raw(OWJ[0..], n, "null");
+ }
+ n = raw(OWJ[0..], n, ",\"geometry\":");
+ if (r[51] == NONE64) {
+ n = raw(OWJ[0..], n, "null");
+ } else {
+ const gd : []const u8 = GEOM[0..(GEOM_LEN as usize)];
+ n = jw_json_value(n, gd, obj_get(gd, arr_at(gd, root(gd), r[51] as u32), "name"));
+ }
+ n = raw(OWJ[0..], n, ",\"mapped\":");
+ n = jw_bool(OWJ[0..], n, r[50] == 1);
+ n = raw(OWJ[0..], n, ",\"strip\":[");
+ const ne : u64 = r[58];
+ const lb : usize = r[43] as usize;
+ var s : u64 = 0;
+ while (s < ne) {
+ if (s > 0) { n = put(OWJ[0..], n, 44); }
+ const bl : u32 = A_LISTS[lb + (s as usize) * 3];
+ n = raw(OWJ[0..], n, "{\"block\":");
+ if (bl == NONE) { n = raw(OWJ[0..], n, "null"); } else { n = jw_u(OWJ[0..], n, bl as u64); }
+ n = raw(OWJ[0..], n, ",\"bottom\":");
+ if (A_LISTS[lb + (s as usize) * 3 + 1] == NONE) { n = raw(OWJ[0..], n, "null"); } else { n = jw_u(OWJ[0..], n, A_LISTS[lb + (s as usize) * 3 + 1] as u64); }
+ n = raw(OWJ[0..], n, ",\"row\":");
+ if (A_LISTS[lb + (s as usize) * 3 + 2] == NONE) { n = raw(OWJ[0..], n, "null"); } else { n = jw_u(OWJ[0..], n, A_LISTS[lb + (s as usize) * 3 + 2] as u64); }
+ n = raw(OWJ[0..], n, ",\"cells\":\"");
+ var c : usize = 0;
+ while (c < (W_BUCKETS as usize)) {
+ n = put(OWJ[0..], n, A_CELLS[(s as usize) * (W_BUCKETS as usize) + c]);
+ c = c + 1;
+ }
+ n = raw(OWJ[0..], n, "\"}");
+ s = s + 1;
+ }
+ n = raw(OWJ[0..], n, "]}");
+ return n;
+}
+
+/// a value of a JSON text copied as it is (a string keeps its quotes and escapes)
+fn jw_json_value(n0: u32, d: []const u8, sp: i64) -> u32 {
+ if (kind(d, sp) == J_NONE) { return raw(OWJ[0..], n0, "null"); }
+ return raw(OWJ[0..], n0, d[span_lo(sp)..span_hi(sp)]);
+}
+
+// --- the card ----------------------------------------------------------------------------------------
+
+fn meas(t: []const u8) -> f64 {
+ return tm_width(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], CUR_FONT[0..(CUR_FL as usize)], t);
+}
+
+/// font(w, px): g.font = `${w} ${px}px ${c.mono}`, the family named {--w-mono|monospace}
+fn set_font(o: []u8, n0: u32, weight: u32, px: f64) -> u32 {
+ var n : u32 = num(CUR_FONT[0..], 0, weight as u64);
+ n = put(CUR_FONT[0..], n, 32);
+ n = js_str_num(CUR_FONT[0..], n, px);
+ n = raw(CUR_FONT[0..], n, "px {--w-mono|monospace}");
+ CUR_FL = n;
+ var m : u32 = cv_open(o, n0, 115, "font");
+ m = cv_raw(o, m, ",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"");
+ m = num(o, m, weight as u64);
+ m = cv_raw(o, m, " ");
+ m = js_str_num(o, m, px);
+ m = cv_raw(o, m, "px \"}");
+ return cv_close(o, m);
+}
+
+fn card_color(o: []u8, n0: u32, which: u32) -> u32 {
+ if (which == 1) { return cv_set_var(o, n0, "fillStyle", "--w-green", "#00ff88"); }
+ if (which == 2) { return cv_set_var(o, n0, "fillStyle", "--w-gold", "#ffd700"); }
+ if (which == 3) { return cv_set_var(o, n0, "fillStyle", "--w-red", "#ff4d6d"); }
+ if (which == 4) { return cv_set_var(o, n0, "fillStyle", "--w-text", "#e8fff7"); }
+ if (which == 5) { return cv_set_var(o, n0, "fillStyle", "--w-muted", "#8fa9a0"); }
+ if (which == 6) { return cv_set_var(o, n0, "fillStyle", "--w-subtle", "#5f7a71"); }
+ if (which == 7) { return cv_set_str(o, n0, "fillStyle", "#000"); }
+ return cv_set_str(o, n0, "fillStyle", "#fff");
+}
+
+fn tail_of(t: []const u8) -> []const u8 {
+ if (t.len <= 512) { return t; }
+ return t[0..512];
+}
+
+/// fit(ctx, s, w) in the current font into FITB
+fn fit_now(t0: []const u8, room: f64) -> u32 {
+ const t : []const u8 = tail_of(t0);
+ var sc : [600]u8 = [0; 600];
+ const hi : i64 = tm_fit_right(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], CUR_FONT[0..(CUR_FL as usize)], t, room, sc[0..600]);
+ var n : u32 = 0;
+ var upto : usize = t.len;
+ if (hi >= 0) { upto = hi as usize; }
+ var k : usize = 0;
+ while (k < upto and n < 1190) {
+ FITB[n as usize] = t[k];
+ n = n + 1;
+ k = k + 1;
+ }
+ if (hi >= 0) { n = raw(FITB[0..], n, "..."); }
+ return n;
+}
+
+pub fn w_card(dst: []u8) -> u32 {
+ if (TM[3] != 0 or TM[0] > 2000 or TM[1] > 262144) { tm_reset(TM[0..16392]); }
+ tm_clear_miss(TM[0..16392]);
+ if (!HAS_VIEW) { return 0; }
+ const d : []const u8 = view_bytes(VIEW_SRC);
+ const a : i64 = VIEW_SP;
+ const wd : f64 = K_CARD_W as f64;
+ const ht : f64 = K_CARD_H as f64;
+ var n : u32 = cv_canvas(dst, 0, 0);
+ n = card_color(dst, n, 7);
+ n = cv_call4(dst, n, "fillRect", 0.0, 0.0, wd, ht);
+ n = cv_set_str(dst, n, "textBaseline", "alphabetic");
+ n = card_color(dst, n, 2);
+ n = cv_call0(dst, n, "beginPath");
+ n = cv_call2(dst, n, "moveTo", 48.0, 48.0);
+ n = cv_call2(dst, n, "lineTo", 76.0, 48.0);
+ n = cv_call2(dst, n, "lineTo", 62.0, 70.0);
+ n = cv_call0(dst, n, "closePath");
+ n = cv_call0(dst, n, "fill");
+ n = set_font(dst, n, 700, 24.0);
+ n = cv_text(dst, n, "fillText", SAY_BRAND, 88.0, 68.0);
+ n = cv_set_str(dst, n, "textAlign", "right");
+ n = card_color(dst, n, 5);
+ n = set_font(dst, n, 400, 20.0);
+ n = cv_text(dst, n, "fillText", SAY_CARD_TITLE, wd - 48.0, 68.0);
+ n = cv_set_str(dst, n, "textAlign", "left");
+ n = card_color(dst, n, 8);
+ n = set_font(dst, n, 800, 46.0);
+ var fl : u32 = fit_now(NAME[0..(NAME_LEN as usize)], wd - 96.0);
+ n = cv_text(dst, n, "fillText", FITB[0..(fl as usize)], 48.0, 140.0);
+ const refuse : i64 = jq(d, a, "refuse");
+ const refused : bool = kind(d, refuse) == J_ARRAY and arr_len(d, refuse) > 0;
+ var y : f64 = 190.0;
+ if (refused) {
+ n = card_color(dst, n, 3);
+ n = set_font(dst, n, 700, 26.0);
+ // wrap(ctx, refusals(a)[0]?.textContent, Wd - 96).slice(0, 3)
+ var rt : [1200]u8 = [0; 1200];
+ const rl : u32 = id_text(rt[0..], 0, d, arr_at(d, refuse, 0), 3, 0);
+ const nl : u32 = tm_wrap(TM[0..16392], MPOOL[0..524288], MWID[0..4096], MOK[0..4096], CUR_FONT[0..(CUR_FL as usize)], rt[0..(rl as usize)], wd - 96.0, WSP[0..32]);
+ var li : u32 = 0;
+ while (li < nl and li < 3) {
+ n = cv_text(dst, n, "fillText", rt[(WSP[(li * 2) as usize] as usize)..(WSP[(li * 2 + 1) as usize] as usize)], 48.0, y);
+ y = y + 36.0;
+ li = li + 1;
+ }
+ } else {
+ n = set_font(dst, n, 400, 24.0);
+ n = card_color(dst, n, 4);
+ // fmt(CARD_PARTS, a.header?.part ?? NO_HEADER, a.idcode ? `${a.idcode} ${a.device?.model ?? UNKNOWN}` : UNKNOWN)
+ const hd : i64 = jq(d, a, "header");
+ const part : i64 = jq(d, hd, "part");
+ var p0 : [600]u8 = [0; 600];
+ var p1 : [600]u8 = [0; 600];
+ var l0 : u32 = 0;
+ var l1 : u32 = 0;
+ if (is_nullish(d, part)) { l0 = raw(p0[0..], 0, SAY_NO_HEADER); } else { l0 = jv_str(p0[0..], 0, d, part); }
+ if (l0 > 580) { l0 = 580; }
+ const ic : i64 = jq(d, a, "idcode");
+ if (truthy(d, ic)) {
+ l1 = jv_str(p1[0..], 0, d, ic);
+ l1 = put(p1[0..], l1, 32);
+ const mo : i64 = jq(d, jq(d, a, "device"), "model");
+ if (is_nullish(d, mo)) { l1 = raw(p1[0..], l1, SAY_UNKNOWN); } else { l1 = jv_str(p1[0..], l1, d, mo); }
+ } else {
+ l1 = raw(p1[0..], 0, SAY_UNKNOWN);
+ }
+ if (l1 > 580) { l1 = 580; }
+ var cp : [1300]u8 = [0; 1300];
+ const cpl : u32 = fill_plain(cp[0..], 0, SAY_CARD_PARTS, p0[0..(l0 as usize)], p1[0..(l1 as usize)], "", 2);
+ fl = fit_now(cp[0..(cpl as usize)], wd - 96.0);
+ n = cv_text(dst, n, "fillText", FITB[0..(fl as usize)], 48.0, y);
+ y = y + 38.0;
+ const ag : i64 = jq(d, a, "agree");
+ if (kind(d, ag) != J_NULL) {
+ const agree : bool = truthy(d, ag);
+ if (agree) { n = card_color(dst, n, 1); } else { n = card_color(dst, n, 3); }
+ n = set_font(dst, n, 700, 24.0);
+ if (agree) { n = cv_text(dst, n, "fillText", SAY_CARD_AGREE[0], 48.0, y); } else { n = cv_text(dst, n, "fillText", SAY_CARD_AGREE[1], 48.0, y); }
+ y = y + 36.0;
+ }
+ const bnch : u32 = on_bench(d, a);
+ if (bnch != 2) {
+ if (bnch == 1) { n = card_color(dst, n, 1); } else { n = card_color(dst, n, 2); }
+ n = set_font(dst, n, 700, 26.0);
+ if (bnch == 1) { fl = fit_now(SAY_CARD_BENCH[0], wd - 96.0); } else { fl = fit_now(SAY_CARD_BENCH[1], wd - 96.0); }
+ n = cv_text(dst, n, "fillText", FITB[0..(fl as usize)], 48.0, y);
+ y = y + 36.0;
+ }
+ }
+ // stats
+ const sw : f64 = (wd - 96.0) / 4.0;
+ var i : u32 = 0;
+ while (i < 4) {
+ const x : f64 = 48.0 + (i as f64) * sw;
+ const bytes : i64 = jq(d, a, "bytes");
+ var vl : u32 = 0;
+ if (i == 0) { vl = num_j(TA[0..], 0, d, bytes); }
+ if (i == 1) {
+ const p : u32 = pct_j(TB[0..], 0, d, jq(d, a, "zeroBytes"), bytes);
+ vl = fill_plain(TA[0..], 0, SAY_PERCENT_SHORT, TB[0..(p as usize)], "", "", 1);
+ }
+ if (i == 2) { vl = num_j(TA[0..], 0, d, jq(d, a, "configFrames")); }
+ if (i == 3) { vl = num_j(TA[0..], 0, d, jq(d, a, "nonzeroFrames")); }
+ n = card_color(dst, n, 2);
+ n = set_font(dst, n, 800, 38.0);
+ fl = fit_now(TA[0..(vl as usize)], sw - 16.0);
+ n = cv_text(dst, n, "fillText", FITB[0..(fl as usize)], x, 330.0);
+ n = card_color(dst, n, 5);
+ n = set_font(dst, n, 400, 17.0);
+ fl = fit_now(SAY_STATS[i as usize], sw - 16.0);
+ n = cv_text(dst, n, "fillText", FITB[0..(fl as usize)], x, 358.0);
+ i = i + 1;
+ }
+ // strip
+ const strip : i64 = strip_of(d, a);
+ if (!refused and strip >= 0 and arr_len(d, strip) > 0) {
+ const cnt : f64 = arr_len(d, strip) as f64;
+ const top : f64 = 392.0;
+ const bottom : f64 = ht - 70.0;
+ var band : f64 = js_min(22.0, (bottom - top - 3.0 * (cnt - 1.0)) / cnt);
+ n = draw_strip_card(dst, n, d, strip, 48.0, top, wd - 96.0, band, 3.0, 120.0, js_min(15.0, band));
+ }
+ n = card_color(dst, n, 6);
+ n = set_font(dst, n, 400, 17.0);
+ n = cv_set_str(dst, n, "textAlign", "left");
+ fl = fit_now(SAY_CARD_FOOT, wd - 96.0);
+ n = cv_text(dst, n, "fillText", FITB[0..(fl as usize)], 48.0, ht - 32.0);
+ if (!cv_ok(n)) { return 0; }
+ return n;
+}
+
+/// drawStrip with the font of the card: font(400, min(15, bandH)); the same records as the map
+fn draw_strip_card(o: []u8, n0: u32, d: []const u8, strip: i64, x: f64, y: f64, w: f64, band: f64, gap: f64, label: f64, px: f64) -> u32 {
+ var n : u32 = cv_open(o, n0, 115, "font");
+ n = cv_raw(o, n, ",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"400 ");
+ n = js_str_num(o, n, px);
+ n = cv_raw(o, n, "px \"}");
+ n = cv_close(o, n);
+ n = cv_set_str(o, n, "textBaseline", "middle");
+ const rows : u32 = arr_len(d, strip);
+ var ri : u32 = 0;
+ while (ri < rows) {
+ const b : i64 = arr_at(d, strip, ri);
+ const by : f64 = y + (ri as f64) * (band + gap);
+ n = cv_set_var(o, n, "fillStyle", "--w-muted", "#8fa9a0");
+ n = cv_set_str(o, n, "textAlign", "left");
+ const l : u32 = band_name(TA[0..], 0, d, b);
+ n = cv_text(o, n, "fillText", TA[0..(l as usize)], x, by + band / 2.0);
+ const cells : i64 = jq(d, b, "cells");
+ const cnt : u32 = str_len(d, cells);
+ const cw : f64 = (w - label) / (cnt as f64);
+ var j : u32 = 0;
+ while (j < cnt) {
+ const ch : u8 = str_byte(d, cells, j);
+ if (ch == 45) {
+ n = cv_set_var(o, n, "fillStyle", "--w-line", "#1d2b27");
+ n = cv_set_num(o, n, "globalAlpha", 0.5);
+ } else {
+ if (ch == 48) {
+ n = cv_set_var(o, n, "fillStyle", "--w-green", "#00ff88");
+ n = cv_set_num(o, n, "globalAlpha", 0.16);
+ } else {
+ n = cv_set_var(o, n, "fillStyle", "--w-gold", "#ffd700");
+ n = cv_set_num(o, n, "globalAlpha", 0.35 + (0.65 * ((ch - 48) as f64)) / 9.0);
+ }
+ }
+ n = cv_call4(o, n, "fillRect", x + label + (j as f64) * cw, by, js_max(1.0, cw - 0.4), band);
+ j = j + 1;
+ }
+ n = cv_set_num(o, n, "globalAlpha", 1.0);
+ ri = ri + 1;
+ }
+ return n;
+}
+
+pub fn w_meas_need(dst: []u8) -> u32 {
+ return tm_need(TM[0..16392], MPOOL[0..524288], MOK[0..4096], dst);
+}
+
+pub fn w_meas_give(s: []const u8) -> u32 {
+ return tm_give(TM[0..16392], MOK[0..4096], MWID[0..4096], s);
+}
+
+// --- Tests of the native page -------------------------------------------------------------------------
+
+fn has(o: []const u8, n: u32, want: []const u8) -> bool {
+ if (n < (want.len as u32)) { return false; }
+ var i : usize = 0;
+ while (i + want.len <= (n as usize)) {
+ var k : usize = 0;
+ while (k < want.len and o[i + k] == want[k]) { k = k + 1; }
+ if (k == want.len) { return true; }
+ i = i + 1;
+ }
+ return false;
+}
+
+pub const W_TEST_SAMPLES : str = "{\"samples\":[{\"id\":\"blink_j26\",\"from\":{\"path\":\"fpga/x/blink_j26.bit\"},\"sha256\":\"abc\",\"bytes\":1000,\"kind\":\"bit\",\"header\":{\"design\":\"d\",\"part\":\"xc7a100tfgg676-1\",\"date\":\"2026/05/08\",\"time\":\"19:33:22\",\"length\":900},\"headerLengthOk\":true,\"sync\":181,\"idcode\":\"0x03631093\",\"device\":{\"idcode\":\"0x03631093\",\"family\":\"artix a7 100t\",\"model\":\"xc7a100\"},\"agree\":true,\"zeroBytes\":900,\"refuse\":[],\"warnings\":[\"partialframe\"],\"packets\":{\"type1\":25,\"type2\":1,\"nop\":120,\"pad\":0,\"writes\":26,\"reads\":0,\"cmd\":10,\"crc\":0,\"far\":2,\"fdri\":2,\"other\":0},\"commands\":[\"NULL\",\"RCRC\"],\"configFrames\":9448,\"nonzeroFrames\":134,\"ones\":1898,\"blocks\":[{\"block\":0,\"frames\":7656,\"nonzero\":134}],\"geometry\":\"xc7a100t\",\"mapped\":true,\"strip\":[{\"block\":0,\"bottom\":0,\"row\":0,\"cells\":\"-0149\"}]}]}";
+
+fn open_page() -> u32 {
+ w_init("");
+ w_data(0, W_TEST_SAMPLES);
+ w_data(1, "[]");
+ w_data(2, "{\"fpga\":[]}");
+ return 0;
+}
+
+test the_first_sample_is_drawn_from_samples_json_when_the_three_files_have_arrived {
+ var o : [262144]u8 = [0; 262144];
+ w_init("");
+ w_data(0, W_TEST_SAMPLES);
+ w_data(1, "[]");
+ const a : u32 = w_render(o[0..]);
+ assert !has(o[0..], a, "ba-verdict");
+ w_data(2, "{\"fpga\":[]}");
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "aria-pressed=\"true\">blink_j26");
+ assert has(o[0..], n, "
blink_j26.bit
The header says xc7a100tfgg676-1; the IDCODE it writes says artix a7 100t, 0x03631093. They agree.
") == false;
+ assert has(o[0..], n, "
blink_j26.bit
The header says xc7a100tfgg676-1; the IDCODE it writes says xc7a100 (artix a7 100t), 0x03631093. They agree.
");
+ assert has(o[0..], n, "
FDRI data is not a whole number of 101-word frames.
");
+ assert has(o[0..], n, "
zero bytes
90.00 percent
900 of 1,000
");
+ assert has(o[0..], n, "
non-zero frames
134
1.42 percent
");
+ assert has(o[0..], n, "
CMD packets, CRC writes
10, 0
");
+ assert has(o[0..], n, "
Commands in order NULL > RCRC
");
+ assert has(o[0..], n, "
CLB: 134 of 7,656 frames non-zero
");
+ assert has(o[0..], n, "
frame layout
xc7a100t, frame addresses from prjxray-db
");
+}
+
+test the_map_is_a_draw_list_of_one_band_per_strip_row {
+ var o : [262144]u8 = [0; 262144];
+ var c : [262144]u8 = [0; 262144];
+ open_page();
+ w_view(900.0, 2.0, 0, 540.0, 540.0);
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "");
+ const l : u32 = w_draw(c[0..]);
+ assert has(c[0..], l, "[\"m\",\"setTransform\",2,0,0,2,0,0],[\"m\",\"clearRect\",0,0,540,19],");
+ assert has(c[0..], l, "[\"s\",\"font\",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"11px \"}]");
+ assert has(c[0..], l, "[\"m\",\"fillText\",\"CLB top 0\",0,8],");
+ assert has(c[0..], l, "[\"s\",\"fillStyle\",{\"v\":\"--w-line\",\"f\":\"#1d2b27\"}],[\"s\",\"globalAlpha\",0.5],[\"m\",\"fillRect\",92,0,89.19999999999999,16],");
+ assert has(c[0..], l, "[\"s\",\"fillStyle\",{\"v\":\"--w-green\",\"f\":\"#00ff88\"}],[\"s\",\"globalAlpha\",0.16],");
+ assert has(c[0..], l, "[\"s\",\"fillStyle\",{\"v\":\"--w-gold\",\"f\":\"#ffd700\"}],[\"s\",\"globalAlpha\",0.4222222222222222],");
+}
+
+test a_stream_without_a_sync_word_is_refused_and_its_null_slots_are_the_text_null {
+ var o : [262144]u8 = [0; 262144];
+ open_page();
+ assert w_file(0, "My Dump.bit", 5) == 1;
+ w_file_data(0, "hello");
+ const n : u32 = w_render(o[0..]);
+ assert has(o[0..], n, "
My Dump.bit
No sync word 0xAA995566 found: this is not a 7-series configuration stream.
nullnull");
+ assert has(o[0..], n, "
sync word at byte
-
");
+ assert has(o[0..], n, "null
");
+ var e : [8]u8 = [0; 8];
+ assert w_effect(0, e[0..]) == 2;
+ assert e[0] == 118 and e[1] == 0;
+}
+
+test the_card_asks_for_widths_and_names_its_file {
+ open_page();
+ w_click(6);
+ var e : [512]u8 = [0; 512];
+ const l : u32 = w_effect(0, e[0..]);
+ assert l > 0;
+ assert has(e[0..], l, "bit-autopsy-blink_j26.bit.png");
+ var c : [262144]u8 = [0; 262144];
+ var need : [65536]u8 = [0; 65536];
+ var g : [3000]u8 = [0; 3000];
+ var q : usize = 0;
+ while (q + 3 <= 3000) {
+ g[q] = 49;
+ g[q + 1] = 48;
+ g[q + 2] = 10;
+ q = q + 3;
+ }
+ var pass : u32 = 0;
+ var n : u32 = 0;
+ var open : u32 = 1;
+ while (open > 0 and pass < 80) {
+ n = w_card(c[0..]);
+ open = w_meas_need(need[0..]);
+ if (open > 0) { w_meas_give(g[0..]); }
+ pass = pass + 1;
+ }
+ assert open == 0;
+ assert has(c[0..], n, "[\"c\",0],[\"s\",\"fillStyle\",\"#000\"],[\"m\",\"fillRect\",0,0,1200,630],");
+ assert has(c[0..], n, "[\"s\",\"font\",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"700 24 px \"}]") == false;
+ assert has(c[0..], n, "[\"s\",\"font\",{\"v\":\"--w-mono\",\"f\":\"monospace\",\"p\":\"700 24px \"}]");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"Trinity S3AI\",88,68],");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"header xc7a100tfgg676-1 / IDCODE 0x03631093 xc7a100\",48,190],");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"header and IDCODE agree\",48,228],");
+ assert has(c[0..], n, "[\"m\",\"fillText\",\"1,000\",48,330],");
+}
+
+test a_failed_data_file_or_a_failed_read_says_so {
+ var o : [262144]u8 = [0; 262144];
+ w_init("");
+ w_data_failed(1);
+ w_data(0, W_TEST_SAMPLES);
+ w_data(2, "{\"fpga\":[]}");
+ const a : u32 = w_render(o[0..]);
+ assert has(o[0..], a, "