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, ""); + 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_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, "

"); + q = q + 1; + } + } + pass = pass + 1; + } + return n; +} + +fn tiles_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 { + var n : u32 = raw(dst, n0, "
"); + 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, "

"); + var i : u32 = 0; + while (i < 12) { + n = raw(dst, n, ""); + i = i + 1; + } + return 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, "
"); + 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, "
"); +} + +fn packet_html(dst: []u8, n0: u32, d: []const u8, a: i64) -> u32 { + const pk : i64 = jq(d, a, "packets"); + if (!truthy(d, pk)) { return raw(dst, n0, "null"); } + var n : u32 = raw(dst, n0, "

"); + 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 layoutxc7a100t, 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, "

The sample summaries did not load.

"); + open_page(); + w_file(0, "q.bit", 5); + w_file_failed(0); + const m : u32 = w_render(o[0..]); + assert has(o[0..], m, "

Could not read q.bit.

"); +} + +/// 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 + (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); + 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_fit_right(m, o, f, k, s, s, 0.0, o) as u32); + n = n + (tm_char_before(s, 0) as u32); + n = n + tm_wrap(m, o, f, k, s, s, 0.0, m); + return n; +} 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, "

"); + 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 = 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, "
"); + 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, "
"); + n = text(dst, n, SAY_FIELDS[i as usize]); + n = 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, ""); + 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, ""); + q = q + 1; + } + n = raw(dst, n, "
"); + n = text(dst, n, SAY_WORD_HEAD[h]); + 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, "
"); + } + 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, "

"); +} + +// --- 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, ""); + 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 writes5 @ 0x00000000-"); + assert has(o[0..], n, "notesfile ends inside a packet-"); + assert has(o[0..], n, "68249475b57e623f"); + assert has(o[0..], n, "sync word at byte44"); +} + +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
NOPNOPCMD RCRCCMD DESYNCCMD DESYNC"); + w_click(4); + const m : u32 = w_render(o[0..]); + assert has(o[0..], m, "aria-expanded=\"false\">Show packets"); + 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, ""); + 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 (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, ""); + } else { + n = raw(o, n, ""); + } + n = raw(o, n, ""); + q = q + 1; + } + s = s + 1; + } + return raw(o, n, "
"); + n = text(o, n, SAY_COLS[c 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 (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, "%"); + n = text(o, n, SAY_REFUSED); + n = raw(o, n, ""); + n = put_js(o, n, R_SEC[r as usize]); + n = 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, "

"); +} + +fn findings(o: []u8, n0: u32) -> u32 { + const d : []const u8 = dsl(); + const cnt : u32 = arr_len(d, J_FIND); + if (cnt == 0) { return n0; } + var n : u32 = raw(o, n0, "

"); + n = text(o, n, SAY_FINDINGS_TITLE); + n = raw(o, n, "

    "); + var i : u32 = 0; + while (i < cnt) { + const f : i64 = arr_at(d, J_FIND, i); + n = raw(o, n, "
  • "); + f_clear(); + if (jstr_is(obj_get(d, f, "kind"), "rejected")) { + var r : i64 = -1; + var q : u32 = 0; + while (q < N_RES and r < 0) { + if (same_json_str(R_ID[q as usize], obj_get(d, f, "id"))) { r = q as i64; } + q = q + 1; + } + const sp : i64 = obj_get(d, f, "spec"); + if (jnullish(sp)) { f_json("spec", obj_get(d, f, "id")); } else { f_json("spec", sp); } + if (r >= 0 and R_DSP[r as usize] == 1) { f_text("mode", SAY_DSP_ON); } else { f_text("mode", SAY_DSP_OFF); } + f_json("detail", obj_get(d, f, "detail")); + const ln : i64 = obj_get(d, f, "line"); + if (jnullish(ln)) { f_none("line"); } else { f_json("line", ln); } + n = fill_out(o, n, SAY_FINDING_REJECTED); + } else { + if (jstr_is(obj_get(d, f, "kind"), "no-data-ports")) { + const sp : i64 = obj_get(d, f, "spec"); + f_json("spec", sp); + var topv : i64 = -1; + var j : u32 = 0; + const ns : u32 = arr_len(d, J_SPECS); + while (j < ns and topv < 0) { + const se : i64 = arr_at(d, J_SPECS, j); + if (same_json_str(obj_get(d, se, "spec"), sp)) { + topv = obj_get(d, se, "top"); + if (topv < 0) { topv = -2; } + } + j = j + 1; + } + if (topv >= 0) { f_json("top", topv); } else { f_json("top", -1); } + n = fill_out(o, n, SAY_FINDING_NO_PORTS); + } else { + if (jstr_is(obj_get(d, f, "kind"), "empty")) { + // fill(template, f): every key of f is a variable + n = fill_with_object(o, n, SAY_FINDING_EMPTY, f); + } else { + const sp : i64 = obj_get(d, f, "spec"); + if (jnullish(sp)) { f_json("spec", obj_get(d, f, "id")); } else { f_json("spec", sp); } + f_json("detail", obj_get(d, f, "detail")); + n = fill_out(o, n, SAY_FINDING_OTHER); + } + } + } + n = raw(o, n, "
  • "); + i = i + 1; + } + return raw(o, n, "
"); +} + +/// fill(t, obj): a {name} is replaced when obj has that key (its String() value); else it stays. +fn fill_with_object(o: []u8, n0: u32, t: []const u8, obj: i64) -> 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) { + const v : i64 = obj_get(dsl(), obj, t[(i + 1)..closed]); + if (v >= 0) { + f_clear(); + fb_value(v); + n = text(o, n, FB[0..(FB_N as usize)]); + } else { + 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; +} + +fn slice_text(o: []u8, n0: u32, sp: i64, count: u32) -> u32 { + const d : []const u8 = dsl(); + var n : u32 = n0; + const len : u32 = str_len(d, sp); + var k : u32 = 0; + while (k < len and k < count) { + n = put_escaped(o, n, str_byte(d, sp, k)); + k = k + 1; + } + return n; +} + +fn provenance(o: []u8, n0: u32) -> u32 { + const d : []const u8 = dsl(); + const c : i64 = J_CAP; + const np : i64 = obj_get(d, c, "nextpnr"); + const px : i64 = obj_get(d, c, "prjxray"); + var n : u32 = raw(o, n0, "

"); + n = text(o, n, SAY_PROV_TITLE); + n = raw(o, n, "

"); + f_clear(); + f_json("yosys", obj_get(d, J_TOOLS, "yosys")); + f_json("iverilog", obj_get(d, J_TOOLS, "iverilog")); + f_json("nextpnr", obj_get(d, np, "version")); + n = fill_out(o, n, SAY_PROV_TOOLS); + n = raw(o, n, "

"); + f_clear(); + f_json("synth", obj_get(d, J_ROOT, "synth")); + f_json("runs", obj_get(d, J_TIMING, "runs")); + f_json("sum", obj_get(d, J_TIMING, "sum_seconds")); + f_json("wall", obj_get(d, J_TIMING, "wall_seconds")); + f_json("jobs", obj_get(d, J_TIMING, "jobs")); + n = fill_out(o, n, SAY_PROV_SYNTH); + n = raw(o, n, "

"); + f_clear(); + f_json("file", obj_get(d, px, "file")); + f_locale_json("slicel", obj_get(d, px, "SLICEL")); + f_locale_json("slicem", obj_get(d, px, "SLICEM")); + f_locale_json("slices", obj_get(d, c, "slices")); + f_locale_json("dsp", obj_get(d, c, "dsp")); + f_json("chipdb", obj_get(d, np, "chipdb")); + f_locale_json("lutx", obj_get(d, np, "SLICE_LUTX")); + f_locale_json("ffx", obj_get(d, np, "SLICE_FFX")); + f_locale_json("carry", obj_get(d, np, "CARRY4")); + f_locale_json("dspn", obj_get(d, np, "DSP48E1")); + n = fill_out(o, n, SAY_PROV_CAP); + n = 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

"); + assert t_has(n, "role=\"img\" aria-label=\"Cost against operand width."); + assert t_has(n, ""); + assert t_has(n, ""); + assert t_has(n, "
  • zz: synthesised to no logic cells.
  • "); + assert t_has(n, "Every command (2)
    a b\nc<d
    "); + assert t_has(n, "Run at 2026-10-04T17:45:21Z."); + assert MK_N == 2; + w_pointer(0, CW / 2.0, CHH / 2.0, CW, CHH); +} + +test the_buttons_change_the_view { + w_init(""); + w_data(0, t_data()); + w_click(0); + assert OP == 0; + w_click(5); + assert METRIC == 1; + w_click(8); + assert DSP; + w_click(11); + assert LOGSCALE == false; + w_click(13); + assert TABLE; + const n : u32 = t_render(); + assert t_has(n, ""); + assert w_size(0.0, 700.0, 800.0, 0) == 0; + assert w_size(0.0, 900.0, 800.0, 0) == 0; + w_click(12); + assert TABLE == false; + assert w_size(0.0, 1000.0, 800.0, 0) == 1; +} 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, "
    Design
    "); + var i : usize = 0; + while (i < SAY_PICK_FIELDS.len) { + n = raw(dst, n, ""); + i = i + 1; + } + return 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, "
    "); +} + +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, "
    • "); + c = c + 1; + } + n = raw(dst, n, "
    "); + } + return n; +} + +pub fn w_render(dst: []u8) -> u32 { + var n : u32 = raw(dst, 0, "
    "); + var i : usize = 0; + while (i < SAY_SAMPLES.len) { + 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_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, ""); +} + +/// 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,8253"); + assert has_text(o[0..], n, "DSP0 / 3700"); + 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, "
    "); + 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, "
    "); + assert has(n, "exponent"); + assert has(n, "
    decoded, exactly
    1.6171875
    error
    -0.0008465
    relative error
    -0.0005232
    rounding
    rounded down in magnitude
    "); + assert has(n, "0.") == false; + assert has(n, "
    these trits are
    about 1.61804603 (11 trits)
    error
    1.204e-5
    "); + assert has(n, "
    decoded value, encoded again: 0x3F3C
    same
    trits read back, written again
    same
    "); + assert has(n, "

    Layout read from t27/files/specs/numeric/gf16.t27: S1 E6 M9, bias 31.

    20 of 20 vectors from gf16_conformance_v0.json encode bit-exact on this page; tie vectors 4 of 4.

    "); + assert has(n, "
    ") == false; + assert has(n, "
    class
    infinity
    "); + assert has(n, "

    Infinity and NaN have no ternary digits.

    "); + 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, "

    "); + assert has(n, "0x7E00"); + assert CUR_OK == false; + w_input(0, " "); + n = render(); + assert has(n, "

    "); + start("#x=1e60"); + n = render(); + assert has(n, "

    More than 48 trits before the point; not drawn.

    "); +} 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/lut_depth.t27 b/specs/widgets/lut_depth.t27 new file mode 100644 index 0000000000..b2a126a043 --- /dev/null +++ b/specs/widgets/lut_depth.t27 @@ -0,0 +1,1843 @@ +// 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 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; +} + +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 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; +} + +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, ""); + 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, "
    "); + } + 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, ""); + h = h + 1; + } + n = raw(dst, n, ""); + if (have) { n = render_table(dst, n, t, PICK); } + return raw(dst, n, "
    "); + n = text(dst, n, SAY_TABLE_HEAD[h]); + n = 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_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, "

    "); + 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, ""); + 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, "

    Reading the data files...

    "); + assert has(n, "aria-pressed=\"true\">Decode"); + assert has(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, "

    "); + 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, ""); + 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, ""); + assert locale_cmp("a", "A") < 0; + assert locale_cmp("A1", "a1") > 0; + assert locale_cmp("LUT2", "LUT10") > 0; + assert locale_cmp("a_", "a") > 0; + assert locale_cmp("a$", "a1") < 0; + assert locale_cmp("Lut", "LUT2") < 0; + assert locale_cmp("x", "x") == 0; +} + +test whitespace_paste_reads_nothing_and_clear_empties_the_controls { + open_page(); + w_input(1, " \n "); + w_click(4); + assert SAMPLE == 3; + w_input(1, "Yosys 1.0\n"); + w_click(4); + assert SAMPLE == 0 - 1; + assert SRC_LEN == 11; + w_click(5); + var e : [64]u8 = [0; 64]; + assert w_effect(0, e[0..]) == 2; + assert e[0] == 118 and e[1] == 0; + assert w_effect(1, e[0..]) == 2; + assert w_effect(2, e[0..]) == 0; + assert SAMPLE == 3; +} + +test the_card_is_a_draw_list_and_the_download_names_its_file { + open_page(); + w_file(0, "My Log (2).TXT", 100); + w_file_data(0, W_TEST_LOG); + w_click(7); + var e : [256]u8 = [0; 256]; + const l : u32 = w_effect(0, e[0..]); + assert l > 0; + assert has(e[0..], l, "1200"); + assert has(e[0..], l, "synth-smells-my-log-2.png"); + var c : [65536]u8 = [0; 65536]; + const n : u32 = w_card(c[0..]); + assert n > 0; + 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, "[\"x\",\"TRINITY S3AI / SYNTH SMELLS\",1080,\"600 20px Menlo, ui-monospace, SFMono-Regular, monospace\",48,64],"); + assert w_effect(0, e[0..]) > 0; + w_hash(""); + assert w_effect(0, e[0..]) == 0; +} + +test a_failed_sample_load_says_so_and_a_file_read_failure_names_the_file { + w_init(""); + w_data_failed(0); + var o : [65536]u8 = [0; 65536]; + const n : u32 = w_render(o[0..]); + assert has(o[0..], n, "

    The sample logs did not load.

    "); + 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; +} 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/text_meas.t27 b/specs/widgets/text_meas.t27 new file mode 100644 index 0000000000..e238d400c2 --- /dev/null +++ b/specs/widgets/text_meas.t27 @@ -0,0 +1,566 @@ +// 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; +} + +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) { + 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; +} + +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]; + 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; +} + +pub 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 (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 +/// 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 : [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]; + +/// 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..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..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..300]); + assert T_TM[0] == 1; + 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..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..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..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..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..300])) { k = 99; } else { t_give_all(); k = k + 1; } + } + assert k == 99; + assert r == 10; + assert passes <= 3; + tm_reset(T_TM[0..300]); + k = 0; + while (k < 20) { + 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..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..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; + 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..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..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; + 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; +} 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; } 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, ""); + 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, "

    "); + 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_HONEST); + n = raw(dst, n, "

    "); + n = text(dst, n, SAY_RULES); + n = raw(dst, n, "

    "); + if (!ok(n)) { return 0; } + return n; +} + +/// errorWords(e) ?? fmt(SAY_ERR_READ, f.name) +fn error_html(dst: []u8, n0: u32) -> u32 { + var n : u32 = raw(dst, n0, "

    "); + if (ERR_IDX < 0) { + n = fill(dst, n, SAY_ERR_READ, FNAME[0..(FNAME_LEN as usize)], "", "", 1); + } else { + var tb : [64]u8 = [0; 64]; + var tl : u32 = 0; + if (ERR_HAS_TIME and (ERR_IDX == 7 or ERR_IDX == 8)) { + tl = js_locale_num(tb[0..], 0, ERR_TIME); + } else { + tb[0] = 45; + tl = 1; + } + n = fill(dst, n, SAY_ERR_WORDS[ERR_IDX as usize], ERR_TXT[0..(ERR_TXT_LEN as usize)], tb[0..(tl as usize)], "", 2); + } + return raw(dst, n, "

    "); +} + +fn num_text(dst: []u8, n0: u32, v: u64) -> u32 { + return num_grouped(dst, n0, v); +} + +fn result_html(dst: []u8, n0: u32) -> u32 { + const changes : u64 = vs_info(12); + var n : u32 = raw(dst, n0, "

    "); + 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, "

    The file is empty.

    "); + w_file_failed(0); + const k : u32 = w_render(o[0..]); + assert has(o[0..], k, "

    Could not read My Dump.VCD.

    "); +} + +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; +} diff --git a/tools/duplicate_bodies_baseline.txt b/tools/duplicate_bodies_baseline.txt index ecca4d5c2c..f2aa72da09 100644 --- a/tools/duplicate_bodies_baseline.txt +++ b/tools/duplicate_bodies_baseline.txt @@ -7,16 +7,19 @@ sadd 31 build_tree 28,2 magmul 22,2 smul 20,2 +bytes_eq 18,3 tmul 13 relu 10 -bytes_eq 8,3 dot27 8,5 contrib 7 activate 6 clamp 6,2 clear 6 +eq_s 6 +is_nullish 6 quantize 6,5 relu_prime 6 +ws_len_at 6 count 5 exp_approx 5 floor 5,4 @@ -26,31 +29,56 @@ pack3 5 pow 5,2 scale_q 5 tp 5 +bom_skip 4 +copy_small 4 +cv_same 4 +digits_val 4 +fmt_into 4 fromRaw 4 gft_add 4 gft_mul 4 go_down 4 +has 4,4 has_substring 4 match_at 4 +match_in 4 neuron4 4 +nn_text 4 str_eq 4 +str_is 4 +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 +dnum 3 fill 3 +fill_plain 3 floor_log2 3,2 gadd 3 gmul 3 +hex_digit 3 hex_nibble_of 3 +hex_val 3,3 id_seen_before 3 +is_word 3,2 list_active 3 mem_eql_at 3 negate 3 @@ -59,12 +87,22 @@ on_comb 3,2,2 pack2 3 pow2_frac 3 publish_result_to_string 3 +put_all 3,3 +put_dec 3 +put_say 3 +put_u32le 3 +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 +w_drag 3,2,2,2 +w_text_give 3 wait_cycles 3 HirCoverPoint_validate 2 LinkSection_bss 2 @@ -72,119 +110,484 @@ LinkSection_data 2 LinkSection_text 2 adaptive_temperature_by_sacred_score 2 add 2 +add_refuse 2 +add_warning 2 ads_equal 2 all_positive 2 +alloc_node 2 +analyze 2 asr9 2 assert_pass 2 +autopsy 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 +be32 2,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 +bn_add_small 2 +bn_bitlen 2 +bn_cmp 2 +bn_copy 2 +bn_dec 2 +bn_divmod 2 +bn_divmod_small 2 +bn_from_u64 2 +bn_mul 2 +bn_mul_pow10 2 +bn_mul_small 2 +bn_norm 2 +bn_set_pow 2 +bn_shl 2 +bn_sub 2 +bn_u64 2 +body_token 2 booth_mul_i16 2 booth_mul_u32_inner 2 +bput 2 +bput_u 2 +bputs 2 +bs_far 2 +bs_fdri 2 +bs_map 2 +bs_parse 2 +bs_pk 2 +bs_seq 2 +btn_open 2,2 build_report_frame 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 +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 +cmp_list 2 +compare_all 2 +compare_frames 2 +compress 2 +contains 2 +conv 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 +cp_utf8_len 2 debug_state_bits 2 +dec_f64 2 +dec_len 2 +dec_small 2 +dec_value 2 +decode_far 2 deque_clear 2 -digits_val 2 +die_of 2 +digits_end 2 +disp_build 2 +disp_param 2 +display_name 2 dot4 2 effect_wanted 2 encode 2 +encode_far 2,2 +end_command 2 +end_var 2 +ent_build 2 +esc_byte 2 exp2 2 +extend_into 2 extract_mantissa 2,2 -find_byte 2 +fa_bad 2 +fa_header 2 +fa_tile 2 +fail 2 +fam_set 2 +family_of_range 2 +fd_push 2 +feed 2 +field_len 2 +fin 2 +find_byte 2,2 +find_child 2 +find_geom 2 +find_in 2 +find_info 2 +find_inside 2 +find_item 2 +find_module 2 find_scheme_end 2 +find_sig 2 +find_sync 2,2 +find_version 2 +fl_clear 2 +flat 2 +fmt4 2 +fnv 2 +frame_delta 2 +frame_slots 2 free_node 2 full_adder 2 generate_next_token_from_logits 2 +geom_add 2 +geom_begin 2 +geom_index_of 2 +geom_slot_count 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 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 +header_text 2 +header_token 2 +hex4 2,2,2 +hex8 2 +hexv 2 +hslot 2 hw_place_before 2 +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 +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_type_char 2 +is_type_start 2 +is_unknown_char 2 +is_word_b 2 is_word_char 2 +item_of 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 +jw_bool 2 +jw_byte 2 +jw_hex8 2 +jw_utf8 2 +jws 2 kill 2 +la_make 2 +layout_tree 2 +lb_byte 2 +lb_cmd 2 +lb_dec 2 +lb_hex8 2 +lb_make 2 +lb_put 2 +lb_reg 2 +lb_value 2 +lc_make 2 +ld_eq 2 +ld_items 2 +ld_row 2 +leaves_n 2 +leaves_sum 2 +leaves_walk 2 led_config 2 led_counter_next 2 led_state_next 2 +line_diff 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 +lookup_idcode 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 +map_dpr 2 matmul_probe 2 matmul_rect_probe 2 +meas 2 mem_eql 2 member_contains 2 +minimize 2 +module_rows 2 +name_find 2 +name_less 2,2 +name_of 2 +neg_zero 2 nested_tail 2 new_graph 2 +nid_named 2 notebook_create 2 +nth_line 2 +num_j 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 +packet_line 2 +pair_of 2 +parse_fasm 2 +parse_header 2,2 +parse_input 2 +parse_literal 2 +pi_bad 2 +pi_bits 2 +pi_push 2 pipeline_f32 2 +pk_push 2 +pool_eq 2,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 +pool_ws_before 2 +pop64 2 +popcount 2 +pow10 2 +pow10_small 2 +pow10f 2 +prefix_bytes 2 +press_act 2 price_tokens 2 +proto_name 2 +push_copy 2 +push_fdri 2 +push_utf8 2 put 2 +put_item 2 put_lit 2 put_range 2 +put_utf8 2 read_be32 2 +record 2 +rect_go 2 remove 2 +rfind_in 2 +rtrim_end 2 runRailwayLogsCommand 2 run_die_level_with 2 run_starts_job 2 rx_slice 2 -same 2 +same_as_design 2 +same_f64 2 +same_frame 2 +same_geom_name 2 +same_range 2,2 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 +shade_char 2 shift_ir 2 +short_block 2 +shortest_dec 2 signmul 2 slices_equal 2 +slot_get 2 +slots_build 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 +split_lines 2 split_rest 2 +sq_core 2 +sq_flush 2 +sq_place 2 +sq_push 2 +sq_put 2 +sq_sum 2 +sq_worst 2 +st_type 2 +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 +str_j 2 +str_num 2 string_arena_reserve 2 +strip_cell 2 +strip_of 2 +strip_quotes 2 +strip_row 2 +summarize 2 +support_mask 2 +t_has 2 +term_literals 2 text_append_byte 2 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 +tok_is 2 +token_done 2 +totals 2 +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 +truthy 2 +tx_line 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 +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_file 2 +w_libm_need 2 +w_meas_give 2 +w_meas_need 2 +w_value 2 +walk_packets 2 +warn 2 wave_segments 2 weight_sign 2 wide_tail 2 +wire_end 2 +word_to 2 writes_the_repo 2 -ws_at 2 -ws_back 2 -ws_before 2 -ws_fwd 2 -ws_len_at 2 -ws_len_before 2 +xml_put 2 xor2 2 +xor32 2 +zero_value 2
    LUT2LUTs4
    FDREFlip-flops3
    LutOther2