From 4b4d09024a5d3e74160185bc699fffff113e546e Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 17:03:11 +0700 Subject: [PATCH 1/3] claw: suggest_feed -- a spec that `t27c fmt` would reformat becomes a suggestion review on the pull request (Closes #10365) specs/tri/fmt_source.t27 is the formatter behind `t27c fmt`, now lossless: a leading tab becomes four spaces, trailing space/tab/CR are dropped, trailing blank lines are dropped and the file ends with one newline; every other byte is copied through, and fmt_accept refuses to print unless the non-whitespace byte stream is identical. The old AST printer dropped every `;` comment and wrote `i += 1` as `i = 1`. run_fmt in bootstrap/src/main.rs is now thin I/O glue around bootstrap/gen/rust/tri/fmt_source.rs (t27c gen-rust output); the stale pretty-printer is deleted. specs/claw/suggest_feed.t27 is the driver: head file + the @@ headers of the pull request diff -> formatted text -> changed line ranges -> rows for suggest.t27 -> review JSON (gen/c/claw/suggest_feed.c). The new claw-suggest job of gate-topology.yml mints the TRI CLAW token with exactly the pr-suggestions row of github_surface.t27 (pull_requests write, contents read), reads head blobs with git show (no symlink is followed) and posts the review as event COMMENT. That row moves PLANNED -> LIVE (census 10/1 -> 9/2). spec-guards regenerates and diffs both generated files. Census re-bless: gates shell jobs 89 -> 91, run: steps 316 -> 320, quiet "named a path but not quiet" 204 -> 208 (the new job plus the census base catching up). Shrink offset: spent scripts/gen_w{672,681,682,683,698,709,713,716,718,721,728,729,731,732}.py, all ported under specs/port/scripts/ and called by nothing. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/gate-topology.yml | 29 + .github/workflows/spec-guards.yml | 4 +- AGENTS.md | 1 + bootstrap/gen/rust/tri/fmt_source.rs | 110 +++ bootstrap/src/main.rs | 260 +----- docs/claw/README.md | 15 + gen/c/claw/suggest_feed.c | 1280 ++++++++++++++++++++++++++ scripts/gen_w672.py | 145 --- scripts/gen_w681.py | 145 --- scripts/gen_w682.py | 145 --- scripts/gen_w683.py | 145 --- scripts/gen_w698.py | 145 --- scripts/gen_w709.py | 145 --- scripts/gen_w713.py | 145 --- scripts/gen_w716.py | 145 --- scripts/gen_w718.py | 145 --- scripts/gen_w721.py | 145 --- scripts/gen_w728.py | 145 --- scripts/gen_w729.py | 145 --- scripts/gen_w731.py | 145 --- scripts/gen_w732.py | 145 --- specs/claw/github_surface.t27 | 6 +- specs/claw/suggest_feed.t27 | 706 ++++++++++++++ specs/tri/fmt_source.t27 | 271 ++++++ tools/census/quiet.txt | 2 +- tools/census/shell.txt | 6 +- 26 files changed, 2437 insertions(+), 2283 deletions(-) create mode 100644 bootstrap/gen/rust/tri/fmt_source.rs create mode 100644 gen/c/claw/suggest_feed.c delete mode 100644 scripts/gen_w672.py delete mode 100644 scripts/gen_w681.py delete mode 100644 scripts/gen_w682.py delete mode 100644 scripts/gen_w683.py delete mode 100644 scripts/gen_w698.py delete mode 100644 scripts/gen_w709.py delete mode 100644 scripts/gen_w713.py delete mode 100644 scripts/gen_w716.py delete mode 100644 scripts/gen_w718.py delete mode 100644 scripts/gen_w721.py delete mode 100644 scripts/gen_w728.py delete mode 100644 scripts/gen_w729.py delete mode 100644 scripts/gen_w731.py delete mode 100644 scripts/gen_w732.py create mode 100644 specs/claw/suggest_feed.t27 create mode 100644 specs/tri/fmt_source.t27 diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 0865b5c19f..6741b76cc7 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -136,3 +136,32 @@ jobs: printf '#include "gen/c/claw/sarif_feed.c"\n#include \n#include \nint main(void){size_t k=1<<22,n;uint8_t*b=malloc(k),*o=malloc(k);n=fread(b,1,k,stdin);size_t m=feed_sarif(b,n,o,k);if(n==k||m>k)return 3;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - git diff --name-only --diff-filter=d HEAD^1 HEAD -- '*.t27' | while read -r f; do printf '%s\t%s\t%s\n' "$f" "$(/tmp/t27c spec-status "$f" 2>&1 &1 >/dev/null /tmp/claw.sarif - {if: steps.have.outputs.ok, uses: github/codeql-action/upload-sarif@v4, with: {sarif_file: /tmp/claw.sarif, category: claw/sarif, token: "${{ steps.tok.outputs.token }}"}} + + # claw/suggest: a changed .t27 spec that `t27c fmt` (specs/tri/fmt_source.t27, whitespace only) would reformat becomes a + # suggestion review from the TRI CLAW App. Placement is specs/claw/suggest.t27, the driver is specs/claw/suggest_feed.t27 + # (gen/c/claw/suggest_feed.c); this job mints the token (pull_requests write + contents read, the pr-suggestions row of + # specs/claw/github_surface.t27), feeds the driver the head files and the diff headers, and posts the review it prints. + # Event COMMENT only, never an approval. Head files are read with git show, so a symlink in the PR is never followed. + claw-suggest: + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + permissions: {contents: read} + steps: + - id: have + env: {K: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", I: "${{ vars.CLAW_APP_ID }}"} + run: if [ -n "$K" ] && [ -n "$I" ]; then echo ok=1 >> "$GITHUB_OUTPUT"; else echo "claw/suggest posted nothing - secret CLAW_APP_PRIVATE_KEY or variable CLAW_APP_ID is not set (docs/claw/README.md)." >> "$GITHUB_STEP_SUMMARY"; fi + - {id: tok, if: steps.have.outputs.ok, uses: actions/create-github-app-token@v2, with: {app-id: "${{ vars.CLAW_APP_ID }}", private-key: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", owner: gHashTag, repositories: t27, permission-pull-requests: write, permission-contents: read}} + - {if: steps.have.outputs.ok, uses: actions/checkout@v6, with: {ref: "${{ github.event.pull_request.head.sha }}", persist-credentials: false}} + - if: steps.have.outputs.ok + env: {GH_TOKEN: "${{ steps.tok.outputs.token }}", R: "${{ github.repository }}", N: "${{ github.event.pull_request.number }}", S: "${{ github.event.pull_request.head.sha }}"} + run: | + printf '#include "gen/c/claw/suggest_feed.c"\n#include \n#include \nint main(void){size_t k=1<<24,n;uint8_t*b=malloc(k),*o=malloc(k);uint32_t s[9];n=fread(b,1,k,stdin);if(n==k)return 3;size_t m=suggest_review(b,n,o,k,s);fprintf(stderr,"claw/suggest: posted %%u, outside the diff %%u, over the caps %%u, too big %%u, unreadable %%u, files %%u, refused %%u, bad input %%u, truncated %%u\\n",s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8]);if(m>k)return 4;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - + gh api --paginate "repos/$R/pulls/$N/files" > /tmp/files.json + { printf 'S\t%s\n' "$S"; jq -r '.[]|select(.status!="removed" and (.filename|endswith(".t27")))|.filename' /tmp/files.json | while read -r f; do + case "$f" in *[!A-Za-z0-9_./-]*) continue;; esac + git show "$S:$f" > /tmp/blob 2>/dev/null /tmp/review.json 2> /tmp/stats || { echo "::warning::claw/suggest could not build a review"; exit 0; } + cat /tmp/stats >> "$GITHUB_STEP_SUMMARY" + if [ -s /tmp/review.json ]; then gh api -X POST "repos/$R/pulls/$N/reviews" --input /tmp/review.json -q .html_url >> "$GITHUB_STEP_SUMMARY"; fi diff --git a/.github/workflows/spec-guards.yml b/.github/workflows/spec-guards.yml index 5de6a1f4d6..f22aa89fc8 100644 --- a/.github/workflows/spec-guards.yml +++ b/.github/workflows/spec-guards.yml @@ -127,8 +127,10 @@ jobs: ./target/release/t27c gen-c specs/claw/gates.t27 > gen/c/claw/gates.c ./target/release/t27c gen-c specs/claw/t27f_gate.t27 > gen/c/claw/t27f_gate.c ./target/release/t27c gen-c specs/claw/sarif_feed.t27 > gen/c/claw/sarif_feed.c + ./target/release/t27c gen-c specs/claw/suggest_feed.t27 > gen/c/claw/suggest_feed.c + ./target/release/t27c gen-rust specs/tri/fmt_source.t27 > bootstrap/gen/rust/tri/fmt_source.rs ./target/release/t27c gen-json specs/igla/own_model_board.t27 > gen/json/igla/own_model_board.json - git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c gen/c/claw/sarif_feed.c gen/json/igla/own_model_board.json + git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c gen/c/claw/sarif_feed.c gen/c/claw/suggest_feed.c bootstrap/gen/rust/tri/fmt_source.rs gen/json/igla/own_model_board.json # #7565: test only the specs this change can affect. specs/ci/affected.t27 picks them (the # changed specs and every spec that imports one through `use`, transitively); the graph is the diff --git a/AGENTS.md b/AGENTS.md index 6fb4789576..058e12f2f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,6 +143,7 @@ reusable workflow. Epic #9271. | The GitHub App | [github.com/apps/tri-claw](https://github.com/apps/tri-claw), id 5166765, installed on all repositories. It is the reviewer-bee identity ([`tools/bees/README.md`](tools/bees/README.md)): bees approve under its name, so a review is never the author approving itself. It also posts the `claw/t27f` check below | | T27F check `claw/t27f` (#10268): changed specs and their direct importers, judged by their seals | law [`specs/claw/t27f_gate.t27`](specs/claw/t27f_gate.t27), lowered to `gen/c/claw/t27f_gate.c`; run every 10 minutes by the `claw-t27f` job of `.github/workflows/gate-topology.yml` | | SARIF alerts `claw/sarif` (#10356): a changed spec that does not parse or is unwritten, as a code-scanning alert | adapter [`specs/claw/sarif_feed.t27`](specs/claw/sarif_feed.t27) over [`specs/claw/sarif.t27`](specs/claw/sarif.t27), lowered to `gen/c/claw/sarif_feed.c`; run on every pull request by the `claw-sarif` job of `.github/workflows/gate-topology.yml` | +| Suggestion review (#10365): a changed spec that `t27c fmt` would reformat, as a "Commit suggestion" review | driver [`specs/claw/suggest_feed.t27`](specs/claw/suggest_feed.t27) over [`specs/claw/suggest.t27`](specs/claw/suggest.t27) and the lossless formatter [`specs/tri/fmt_source.t27`](specs/tri/fmt_source.t27), lowered to `gen/c/claw/suggest_feed.c`; run on every pull request by the `claw-suggest` job of `.github/workflows/gate-topology.yml` | The `claw/t27f` check: one scheduled job in this repository posts it on every open pull request that touches `.t27`, in each repository the App is installed diff --git a/bootstrap/gen/rust/tri/fmt_source.rs b/bootstrap/gen/rust/tri/fmt_source.rs new file mode 100644 index 0000000000..164615c710 --- /dev/null +++ b/bootstrap/gen/rust/tri/fmt_source.rs @@ -0,0 +1,110 @@ +// Generated from .t27 spec +// DO NOT EDIT — generated by t27c +// NOT LOWERED BY THIS BACKEND: 15 test(s), 0 invariant(s), 0 function(s) with no body. +// This backend emits declarations only. The spec's checks live in +// the Zig and Verilog outputs; do not read this file as verified. + +pub const TAB_WIDTH: usize = 4; + +pub fn put_byte(dst: &mut [u8], cap: usize, w: usize, b: u8) -> usize { + if (w < cap) { + dst[(w) as usize] = b; + } + return (w + 1); +} + +pub fn is_space(b: u8) -> bool { + return ((((b == b' ') || (b == b'\t')) || (b == b'\n')) || (b == 13)); +} + +pub fn line_stop(src: &[u8], n: usize, s: usize) -> usize { + let mut i: usize = s; + while (i < n) { + if (src[(i) as usize] == b'\n') { + return i; + } + i += 1; + } + return n; +} + +pub fn trim_end(src: &[u8], s: usize, e: usize) -> usize { + let mut x: usize = e; + while (x > s) { + let b: u8 = src[((x - 1)) as usize]; + if (((b != b' ') && (b != b'\t')) && (b != 13)) { + return x; + } + x -= 1; + } + return x; +} + +pub fn fmt_source(src: &[u8], n: usize, out: &mut [u8], cap: usize) -> usize { + let mut w: usize = 0; + let mut pending: usize = 0; + let mut ls: usize = 0; + while (ls < n) { + let le: usize = line_stop(src, n, ls); + let ee: usize = trim_end(src, ls, le); + if (ee == ls) { + pending += 1; + } else { + while (pending > 0) { + w = put_byte(out, cap, w, b'\n'); + pending -= 1; + } + let mut i: usize = ls; + while ((i < ee) && ((src[(i) as usize] == b' ') || (src[(i) as usize] == b'\t'))) { + if (src[(i) as usize] == b'\t') { + w = put_byte(out, cap, w, b' '); + w = put_byte(out, cap, w, b' '); + w = put_byte(out, cap, w, b' '); + w = put_byte(out, cap, w, b' '); + } else { + w = put_byte(out, cap, w, b' '); + } + i += 1; + } + while (i < ee) { + w = put_byte(out, cap, w, src[(i) as usize]); + i += 1; + } + w = put_byte(out, cap, w, b'\n'); + } + ls = (le + 1); + } + return w; +} + +pub fn next_solid(t: &[u8], n: usize, p: usize) -> usize { + let mut i: usize = p; + while (i < n) { + if (is_space(t[(i) as usize]) == false) { + return i; + } + i += 1; + } + return n; +} + +pub fn fmt_exact(a: &[u8], an: usize, b: &[u8], bn: usize) -> bool { + let mut i: usize = next_solid(a, an, 0); + let mut j: usize = next_solid(b, bn, 0); + while ((i < an) && (j < bn)) { + if (a[(i) as usize] != b[(j) as usize]) { + return false; + } + i = next_solid(a, an, (i + 1)); + j = next_solid(b, bn, (j + 1)); + } + return ((i >= an) && (j >= bn)); +} + +pub fn fmt_accept(src: &[u8], n: usize, out: &[u8], m: usize, cap: usize) -> bool { + if (m > cap) { + return false; + } + return fmt_exact(src, n, out, m); +} + diff --git a/bootstrap/src/main.rs b/bootstrap/src/main.rs index 4c82df2283..9c5481aa0b 100644 --- a/bootstrap/src/main.rs +++ b/bootstrap/src/main.rs @@ -90,6 +90,11 @@ mod sha256; #[path = "../gen/rust/tri/crypto/ed25519.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_assignments, unused_variables, non_snake_case, non_upper_case_globals, clippy::all)] mod ed25519; +// specs/tri/fmt_source.t27 (#10365), lowered by `t27c gen-rust`: the lossless formatter behind `t27c fmt`. +// Never hand-edit it; regenerate. +#[path = "../gen/rust/tri/fmt_source.rs"] +#[allow(dead_code, unused_parens, unused_mut, unused_assignments, unused_variables, non_snake_case, non_upper_case_globals, clippy::all)] +mod fmt_source; mod memory; mod trit_stdlib; mod behavior_sva; @@ -1543,7 +1548,7 @@ enum Commands { input: String, }, - /// Pretty-print .t27 from AST + /// Normalise .t27 whitespace, keeping every comment and token (specs/tri/fmt_source.t27) Fmt { input: String, }, @@ -8703,250 +8708,15 @@ fn run_explain(input_path: &str) -> anyhow::Result<()> { } fn run_fmt(input_path: &str) -> anyhow::Result<()> { - let source = fs::read_to_string(input_path)?; - let ast = compiler::Compiler::parse_ast(&source).map_err(|e| anyhow::anyhow!("{}", e))?; - - fn fmt_expr(node: &compiler::Node) -> String { - match node.kind { - compiler::NodeKind::ExprLiteral => node.value.clone(), - compiler::NodeKind::ExprIdentifier => node.name.clone(), - compiler::NodeKind::ExprBinary => { - if node.children.len() >= 2 { - let l = fmt_expr(&node.children[0]); - let r = fmt_expr(&node.children[1]); - format!("{} {} {}", l, node.extra_op, r) - } else { - "()".to_string() - } - } - compiler::NodeKind::ExprUnary => { - if !node.children.is_empty() { - format!("{}{}", node.extra_op, fmt_expr(&node.children[0])) - } else { - node.extra_op.clone() - } - } - compiler::NodeKind::ExprCall => { - let args: Vec = node.children.iter().map(fmt_expr).collect(); - format!("{}({})", node.name, args.join(", ")) - } - compiler::NodeKind::ExprFieldAccess => { - if !node.children.is_empty() { - format!("{}.{}", fmt_expr(&node.children[0]), node.name) - } else { - node.name.clone() - } - } - compiler::NodeKind::ExprIndex => { - if node.children.len() >= 2 { - format!("{}[{}]", fmt_expr(&node.children[0]), fmt_expr(&node.children[1])) - } else { - "()".to_string() - } - } - compiler::NodeKind::ExprEnumValue => format!("{}::{}", node.name, node.extra_field), - compiler::NodeKind::ExprStructLit => { - let fields: Vec = node.children.iter().map(|c| { - let val = if c.children.is_empty() { "".to_string() } else { format!(" = {}", fmt_expr(&c.children[0])) }; - format!("{}:{}", c.name, val) - }).collect(); - format!("{} {{ {} }}", node.name, fields.join(", ")) - } - compiler::NodeKind::ExprArrayLiteral => { - let elems: Vec = node.children.iter().map(fmt_expr).collect(); - format!("[{}]", elems.join(", ")) - } - _ => format!("/* {:?} */", node.kind), - } - } - - fn fmt_stmt(node: &compiler::Node, indent: usize) -> String { - let pad = " ".repeat(indent); - let mut out = String::new(); - match node.kind { - compiler::NodeKind::StmtLocal => { - let kw = if node.extra_mutable { "var" } else { "const" }; - if node.children.is_empty() { - if node.extra_type.is_empty() { - out.push_str(&format!("{}{} {};\n", pad, kw, node.name)); - } else { - out.push_str(&format!("{}{} {}: {};\n", pad, kw, node.name, node.extra_type)); - } - } else { - let val = fmt_expr(&node.children[0]); - if node.extra_type.is_empty() { - out.push_str(&format!("{}{} {} = {};\n", pad, kw, node.name, val)); - } else { - out.push_str(&format!("{}{} {}: {} = {};\n", pad, kw, node.name, node.extra_type, val)); - } - } - } - compiler::NodeKind::StmtAssign => { - if node.children.len() >= 2 { - let target = fmt_expr(&node.children[0]); - let val = fmt_expr(&node.children[1]); - out.push_str(&format!("{}{} = {};\n", pad, target, val)); - } - } - compiler::NodeKind::ExprReturn => { - if node.children.is_empty() { - out.push_str(&format!("{}return;\n", pad)); - } else { - out.push_str(&format!("{}return {};\n", pad, fmt_expr(&node.children[0]))); - } - } - compiler::NodeKind::StmtExpr => { - if node.children.len() == 1 { - out.push_str(&format!("{}{};\n", pad, fmt_expr(&node.children[0]))); - } - } - compiler::NodeKind::StmtIf => { - out.push_str(&pad); - out.push_str("if ("); - if !node.children.is_empty() { - out.push_str(&fmt_expr(&node.children[0])); - } - out.push_str(") {\n"); - if node.children.len() > 1 { - for s in &node.children[1].children { - out.push_str(&fmt_stmt(s, indent + 1)); - } - } - out.push_str(&format!("{}}}\n", pad)); - if node.children.len() > 2 { - out.push_str(&format!("{} else {{\n", pad)); - for s in &node.children[2].children { - out.push_str(&fmt_stmt(s, indent + 1)); - } - out.push_str(&format!("{}}}\n", pad)); - } - } - compiler::NodeKind::StmtWhile => { - out.push_str(&pad); - out.push_str("while ("); - if !node.children.is_empty() { - out.push_str(&fmt_expr(&node.children[0])); - } - out.push_str(") {\n"); - if node.children.len() > 1 { - for s in &node.children[1].children { - out.push_str(&fmt_stmt(s, indent + 1)); - } - } - out.push_str(&format!("{}}}\n", pad)); - } - compiler::NodeKind::StmtFor => { - out.push_str(&pad); - out.push_str("for ("); - if !node.children.is_empty() { - out.push_str(&fmt_expr(&node.children[0])); - } - if node.children.len() > 1 { - out.push_str(&format!(") |{}| {{\n", node.children[1].name)); - } else { - out.push_str(") {\n"); - } - if node.children.len() > 2 { - for s in &node.children[2].children { - out.push_str(&fmt_stmt(s, indent + 1)); - } - } - out.push_str(&format!("{}}}\n", pad)); - } - compiler::NodeKind::StmtBreak => { - out.push_str(&format!("{}break;\n", pad)); - } - compiler::NodeKind::StmtContinue => { - out.push_str(&format!("{}continue;\n", pad)); - } - _ => { - out.push_str(&format!("{}// {:?}\n", pad, node.kind)); - } - } - out - } - - fn fmt_node(node: &compiler::Node, indent: usize) -> String { - let pad = " ".repeat(indent); - let mut out = String::new(); - match node.kind { - compiler::NodeKind::Module => { - out.push_str(&format!("{}module {} {{\n", pad, node.name)); - for child in &node.children { - out.push_str(&fmt_node(child, indent + 1)); - } - out.push_str(&format!("{}}}\n", pad)); - } - compiler::NodeKind::UseDecl => { - out.push_str(&format!("{}using {};\n", pad, node.value)); - } - compiler::NodeKind::ConstDecl => { - if node.children.is_empty() { - out.push_str(&format!("{}const {}: {};\n", pad, node.name, node.extra_type)); - } else { - out.push_str(&format!("{}const {} = {};\n", pad, node.name, fmt_expr(&node.children[0]))); - } - } - compiler::NodeKind::EnumDecl => { - out.push_str(&format!("{}enum {} {{\n", pad, node.name)); - for child in &node.children { - if child.kind == compiler::NodeKind::EnumVariant { - if child.value.is_empty() { - out.push_str(&format!(" {}{},\n", pad, child.name)); - } else { - out.push_str(&format!(" {}{} = {},\n", pad, child.name, child.value)); - } - } - } - out.push_str(&format!("{}}}\n", pad)); - } - compiler::NodeKind::StructDecl => { - out.push_str(&format!("{}struct {} {{\n", pad, node.name)); - for child in &node.children { - if child.kind == compiler::NodeKind::ExprIdentifier && !child.name.is_empty() { - out.push_str(&format!(" {}{}: {},\n", pad, child.name, child.extra_type)); - } - } - out.push_str(&format!("{}}}\n", pad)); - } - compiler::NodeKind::FnDecl => { - let params: Vec = node.params.iter().map(|(n, t)| { - if t.is_empty() { n.clone() } else { format!("{}: {}", n, t) } - }).collect(); - let ret = if node.extra_return_type.is_empty() { String::new() } else { format!(" -> {}", node.extra_return_type) }; - out.push_str(&format!("{}fn {}({}){} {{\n", pad, node.name, params.join(", "), ret)); - for child in &node.children { - out.push_str(&fmt_stmt(child, indent + 1)); - } - out.push_str(&format!("{}}}\n\n", pad)); - } - compiler::NodeKind::TestBlock => { - out.push_str(&format!("{}test {} {{\n", pad, node.name)); - for child in &node.children { - out.push_str(&fmt_stmt(child, indent + 1)); - } - out.push_str(&format!("{}}}\n\n", pad)); - } - compiler::NodeKind::InvariantBlock => { - out.push_str(&format!("{}invariant {} {}\n\n", pad, node.name, node.value)); - } - compiler::NodeKind::BenchBlock => { - out.push_str(&format!("{}bench {} {{\n", pad, node.name)); - for child in &node.children { - out.push_str(&fmt_stmt(child, indent + 1)); - } - out.push_str(&format!("{}}}\n\n", pad)); - } - _ => { - for child in &node.children { - out.push_str(&fmt_node(child, indent)); - } - } - } - out - } - - print!("{}", fmt_node(&ast, 0)); + // The formatter is specs/tri/fmt_source.t27 (lossless: whitespace only); this is its I/O glue. + let src = fs::read(input_path)?; + let cap = src.len() * 4 + 1; + let mut out = vec![0u8; cap]; + let m = fmt_source::fmt_source(&src, src.len(), &mut out, cap); + if !fmt_source::fmt_accept(&src, src.len(), &out, m, cap) { + anyhow::bail!("fmt: refused, the output would not keep every non-whitespace byte of {}", input_path); + } + std::io::Write::write_all(&mut std::io::stdout(), &out[..m])?; Ok(()) } diff --git a/docs/claw/README.md b/docs/claw/README.md index d2dc9af471..c40941e83e 100644 --- a/docs/claw/README.md +++ b/docs/claw/README.md @@ -14,6 +14,7 @@ behind one front door, with the gate logic in `.t27` specs. Epic | GitHub App manifest | release step, see `docs/claw/RELEASE.md` | T4, deferred to App setup | | T27F check in every `.t27` repository | `specs/claw/t27f_gate.t27`, the `claw-t27f` job of `gate-topology.yml` | #10268, live once the owner steps below are done | | SARIF code-scanning alerts on a pull request | `specs/claw/sarif_feed.t27` over `specs/claw/sarif.t27`, the `claw-sarif` job of `gate-topology.yml` | #10356, live once the owner steps below are done | +| Suggestion review on a pull request | `specs/claw/suggest_feed.t27` over `specs/claw/suggest.t27` and `specs/tri/fmt_source.t27`, the `claw-suggest` job of `gate-topology.yml` | #10365, live once the owner steps below are done | | Service skeleton | Railway, thin I/O glue only | T5, merged (spec contract) | | Positioning / pricing | `docs/claw/competitors.md` | T6, started | @@ -44,6 +45,20 @@ Rules T27C002 and T27C003 need the t27c test runner, which needs zig, and are no variable of steps 3 and 4 above, the App permission `security_events: write`, and code scanning enabled on the repository; without them the job uploads nothing and says so in its step summary. +## Suggestion reviews on a pull request (#10365) + +On every pull request in gHashTag/t27 the `claw-suggest` job of `gate-topology.yml` reads each changed `.t27` file +from the head commit (`git show`, so a symlink is never followed) and gives it, with the `@@` headers of the +GitHub diff, to `specs/claw/suggest_feed.t27` (lowered to `gen/c/claw/suggest_feed.c`). The driver formats the file with +`specs/tri/fmt_source.t27` -- the formatter behind `t27c fmt`, which only normalises whitespace (leading tab to four +spaces, no trailing space or CR, one final newline) and refuses to print anything if a single non-whitespace byte +differs -- compares the two texts line by line, and hands the changed ranges to `specs/claw/suggest.t27`, which +decides placement: event COMMENT only, right side only, lines outside the diff skipped, at most 20 comments, and a +hunk it cannot place poisons its whole file. The job posts the review it prints as one pull request review with the +token of the `pr-suggestions` row of `specs/claw/github_surface.t27`: `pull_requests` write and `contents` read. +An already formatted spec posts nothing. It needs the secret and variable of steps 3 and 4 above and the App +permission `pull_requests: write`; without them the job posts nothing and says so in its step summary. + ## The inventory spec and how to regenerate it `specs/claw/gates.t27` holds three parallel `[67]str`-shaped arrays — `NAMES` diff --git a/gen/c/claw/suggest_feed.c b/gen/c/claw/suggest_feed.c new file mode 100644 index 0000000000..adb70309b6 --- /dev/null +++ b/gen/c/claw/suggest_feed.c @@ -0,0 +1,1280 @@ +/* ============================================================================ + Generated from t27 spec: ClawSuggestFeed + DO NOT EDIT - generated by t27c gen-c + phi^2 + 1/phi^2 = 3 | TRINITY + ============================================================================ */ + +#include +#include +#include +#include +#define t27_assert(c, m) do { if (!(c)) { __builtin_trap(); } } while (0) +#define assert_eq(a, b) do { if ((a) != (b)) { __builtin_trap(); } } while (0) + +#ifndef CLAWSUGGESTFEED_H +#define CLAWSUGGESTFEED_H + +/* ------------------------------------------------------- + Constants + ------------------------------------------------------- */ + +#define MAX_FILES 64 +#define MAX_HUNKS 512 +#define MAX_RANGES 2048 +#define FILE_CAP 131072 +#define FMT_CAP 524289 +#define ARENA_CAP 262144 +#define K_BAD 99 +#define NO_NUM 4294967295 +#define ST_POSTED 0 +#define ST_OUTSIDE 1 +#define ST_CAPPED 2 +#define ST_TOO_BIG 3 +#define ST_UNKNOWN 4 +#define ST_FILES 5 +#define ST_REFUSED 6 +#define ST_BAD_INPUT 7 +#define ST_TRUNCATED 8 +#define STATS_LEN 9 +#define J_HEAD "{\"commit_id\":\"" +#define J_HEAD_LEN 14 +#define J_EVENT "\",\"event\":\"COMMENT\",\"body\":\"" +#define J_EVENT_LEN 28 +#define J_COMMENTS "\",\"comments\":[" +#define J_COMMENTS_LEN 14 +#define J_PATH "{\"path\":\"" +#define J_PATH_LEN 9 +#define J_SIDE "\",\"side\":\"RIGHT\",\"line\":" +#define J_SIDE_LEN 24 +#define J_START ",\"start_line\":" +#define J_START_LEN 14 +#define J_START_SIDE ",\"start_side\":\"RIGHT\"" +#define J_START_SIDE_LEN 21 +#define J_BODY ",\"body\":\"```suggestion\\n" +#define J_BODY_LEN 24 +#define J_END "```\"}" +#define J_END_LEN 5 +#define J_NOTE_A "TRI CLAW formatting suggestions: " +#define J_NOTE_A_LEN 33 +#define J_NOTE_B " left out (outside this diff, over the caps, or unreadable). Whitespace only: every comment and token is kept." +#define J_NOTE_B_LEN 110 +#define J_TAIL "]}" +#define J_TAIL_LEN 2 +#define EVENT "COMMENT" +#define K_DELETE 1 +#define K_INSERT 2 +#define K_REPLACE 0 +#define MAX_COMMENTS 20 +#define MAX_SPAN 40 +#define SIDE "RIGHT" +#define V_CAP 2 +#define V_OUTSIDE 1 +#define V_POST 0 +#define V_TOO_BIG 3 +#define V_UNKNOWN 255 + +/* ------------------------------------------------------- + Function prototypes + ------------------------------------------------------- */ + +size_t sf_tab(uint8_t* t, size_t e, size_t s); +uint32_t sf_num(uint8_t* t, size_t s, size_t e); +bool sf_sha_ok(uint8_t* t, size_t s, size_t e); +bool sf_path_ok(uint8_t* t, size_t s, size_t e); +bool sf_line_ok(uint8_t* t, size_t s, size_t e); +uint8_t sf_hexd(uint8_t d); +size_t sf_esc(uint8_t* dst, size_t cap, size_t w, uint8_t b); +bool sf_same_line(uint8_t* a, size_t os, size_t oe, uint8_t* b, size_t ns, size_t ne); +uint32_t sf_lines(uint8_t* t, size_t n); +size_t suggest_review(uint8_t* inp, size_t n, uint8_t* out, size_t out_cap, uint32_t* stats); +uint8_t byte_at(uint8_t* t, size_t n, size_t i); +uint32_t comment_line(uint32_t start, uint32_t count); +uint32_t comment_start_line(uint32_t start, uint32_t count); +uint32_t count_verdict(uint8_t v, size_t nh, uint8_t* out); +const char* event_for(const char* requested, size_t requested_len); +bool file_poisoned(uint32_t file, size_t nh, uint32_t* hfile, uint32_t* hstart, uint32_t* hcount, uint8_t* hkind); +uint32_t first_line(uint32_t start); +bool header_new_range(uint8_t* t, size_t n, uint32_t* out); +bool hunk_unknown(size_t i, size_t nh, uint32_t* hfile, uint32_t* hstart, uint32_t* hcount, uint8_t* hkind); +bool in_one_range(uint32_t file, uint32_t first, uint32_t last, uint32_t* dfile, uint32_t* dstart, uint32_t* dlen, size_t nd); +bool is_digit(uint8_t b); +uint32_t last_line(uint32_t start, uint32_t count); +size_t num_end(uint8_t* t, size_t n, size_t p); +uint32_t num_val(uint8_t* t, size_t p, size_t e); +uint32_t plan(size_t nh, uint32_t* hfile, uint32_t* hstart, uint32_t* hcount, uint8_t* hkind, size_t nd, uint32_t* dfile, uint32_t* dstart, uint32_t* dlen, uint8_t* out); +bool shape_ok(uint8_t kind, uint32_t start, uint32_t count); +bool should_post(uint32_t posted); +const char* side_for(const char* requested, size_t requested_len); +bool buf_has(uint8_t* buf, size_t buf_len, const char* needle, size_t needle_len); +uint8_t digit_byte(uint32_t d); +size_t put_count(uint8_t* dst, size_t cap, size_t w, uint32_t v); +size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len); +bool str_eq(const char* a, size_t alen, const char* b, size_t blen); +bool fmt_accept(uint8_t* src, size_t n, uint8_t* out, size_t m, size_t cap); +bool fmt_exact(uint8_t* a, size_t an, uint8_t* b, size_t bn); +size_t fmt_source(uint8_t* src, size_t n, uint8_t* out, size_t cap); +bool is_space(uint8_t b); +size_t line_stop(uint8_t* src, size_t n, size_t s); +size_t next_solid(uint8_t* t, size_t n, size_t p); +size_t put_byte(uint8_t* dst, size_t cap, size_t w, uint8_t b); +size_t trim_end(uint8_t* src, size_t s, size_t e); + +/* ------------------------------------------------------- + Function implementations + ------------------------------------------------------- */ + +size_t sf_tab(uint8_t* t, size_t e, size_t s) { + size_t i = s; + while ((i < e)) { + if ((t[i] == '\t')) { + return i; + } + i += 1; + } + return e; +} + +uint32_t sf_num(uint8_t* t, size_t s, size_t e) { + if ((e <= s)) { + return NO_NUM; + } + if (((e - s) > 9)) { + return NO_NUM; + } + uint32_t v = 0; + size_t i = s; + while ((i < e)) { + if (((t[i] < '0') || (t[i] > '9'))) { + return NO_NUM; + } + v = ((v * 10) + ((uint32_t)((t[i] - '0')))); + i += 1; + } + return v; +} + +bool sf_sha_ok(uint8_t* t, size_t s, size_t e) { + if (((e - s) != 40)) { + return false; + } + size_t i = s; + while ((i < e)) { + uint8_t b = t[i]; + if ((((b < '0') || (b > '9')) && ((b < 'a') || (b > 'f')))) { + return false; + } + i += 1; + } + return true; +} + +bool sf_path_ok(uint8_t* t, size_t s, size_t e) { + if ((e <= s)) { + return false; + } + if ((((e - s) > 200) || ((e - s) < 5))) { + return false; + } + if ((t[s] == '/')) { + return false; + } + if (((((t[(e - 4)] != '.') || (t[(e - 3)] != 't')) || (t[(e - 2)] != '2')) || (t[(e - 1)] != '7'))) { + return false; + } + size_t i = s; + while ((i < e)) { + uint8_t b = t[i]; + bool word = ((((b >= 'a') && (b <= 'z')) || ((b >= 'A') && (b <= 'Z'))) || ((b >= '0') && (b <= '9'))); + if ((((((word == false) && (b != '_')) && (b != '.')) && (b != '/')) && (b != '-'))) { + return false; + } + if ((((b == '.') && ((i + 1) < e)) && (t[(i + 1)] == '.'))) { + return false; + } + i += 1; + } + return true; +} + +bool sf_line_ok(uint8_t* t, size_t s, size_t e) { + size_t ticks = 0; + size_t i = s; + while ((i < e)) { + uint8_t b = t[i]; + if ((((b < 32) && (b != '\t')) || (b > 126))) { + return false; + } + if ((b == '`')) { + ticks += 1; + if ((ticks >= 3)) { + return false; + } + } else { + ticks = 0; + } + i += 1; + } + return true; +} + +uint8_t sf_hexd(uint8_t d) { + if ((d < 10)) { + return digit_byte(((uint32_t)(d))); + } + if ((d == 10)) { + return 'a'; + } + if ((d == 11)) { + return 'b'; + } + if ((d == 12)) { + return 'c'; + } + if ((d == 13)) { + return 'd'; + } + if ((d == 14)) { + return 'e'; + } + return 'f'; +} + +size_t sf_esc(uint8_t* dst, size_t cap, size_t w, uint8_t b) { + if ((b == 34)) { + return put_str(dst, cap, w, "\\\"", 2); + } + if ((b == 92)) { + return put_str(dst, cap, w, "\\\\", 2); + } + if ((b == '\n')) { + return put_str(dst, cap, w, "\\n", 2); + } + if ((b == '\t')) { + return put_str(dst, cap, w, "\\t", 2); + } + if ((b < 32)) { + size_t x = put_str(dst, cap, w, "\\u00", 4); + x = put_byte(dst, cap, x, sf_hexd((b / 16))); + return put_byte(dst, cap, x, sf_hexd((b % 16))); + } + return put_byte(dst, cap, w, b); +} + +bool sf_same_line(uint8_t* a, size_t os, size_t oe, uint8_t* b, size_t ns, size_t ne) { + if (((oe - os) != (ne - ns))) { + return false; + } + size_t k = 0; + while (((os + k) < oe)) { + if ((a[(os + k)] != b[(ns + k)])) { + return false; + } + k += 1; + } + return true; +} + +uint32_t sf_lines(uint8_t* t, size_t n) { + uint32_t c = 0; + size_t i = 0; + while ((i < n)) { + if ((t[i] == '\n')) { + c += 1; + } + i += 1; + } + if (((n > 0) && (t[(n - 1)] != '\n'))) { + c += 1; + } + return c; +} + +size_t suggest_review(uint8_t* inp, size_t n, uint8_t* out, size_t out_cap, uint32_t* stats) { + size_t k = 0; + while ((k < STATS_LEN)) { + stats[k] = 0; + k += 1; + } + size_t fps[64] = {0}; + size_t fpe[64] = {0}; + uint32_t hfile[512] = {0}; + uint32_t hstart[512] = {0}; + uint32_t hcount[512] = {0}; + uint8_t hkind[512] = {0}; + size_t hoff[512] = {0}; + size_t hlen[512] = {0}; + uint8_t verdict[512] = {0}; + uint32_t dfile[2048] = {0}; + uint32_t dstart[2048] = {0}; + uint32_t dlen[2048] = {0}; + uint8_t arena[262144] = {0}; + uint8_t fc[131072] = {0}; + uint8_t fo[524289] = {0}; + uint8_t hb[128] = {0}; + uint32_t rng[2] = {0}; + size_t sha_s = 0; + size_t sha_e = 0; + size_t nf = 0; + size_t nh = 0; + size_t nd = 0; + size_t aw = 0; + size_t cur = MAX_FILES; + bool bad = false; + size_t p = 0; + while (((p < n) && (bad == false))) { + size_t e = line_stop(inp, n, p); + uint8_t tag = inp[p]; + if ((((tag == 'S') && (e > (p + 1))) && (inp[(p + 1)] == '\t'))) { + sha_s = (p + 2); + sha_e = e; + p = (e + 1); + } else if ((((tag == 'H') && (e > (p + 1))) && (inp[(p + 1)] == '\t'))) { + size_t hl = (e - (p + 2)); + bool ok = false; + if ((cur < MAX_FILES)) { + if ((hl < 128)) { + size_t i = 0; + while ((i < hl)) { + hb[i] = inp[((p + 2) + i)]; + i += 1; + } + ok = header_new_range(&hb, hl, &rng); + } + uint32_t cf = ((uint32_t)(cur)); + if ((ok && (nd < MAX_RANGES))) { + dfile[nd] = cf; + dstart[nd] = rng[0]; + dlen[nd] = rng[1]; + nd += 1; + } else if (ok) { + stats[ST_TRUNCATED] = 1; + } else { + size_t j = 0; + while ((j < nh)) { + if ((hfile[j] == cf)) { + hkind[j] = K_BAD; + } + j += 1; + } + } + } + p = (e + 1); + } else if ((((tag == 'F') && (e > (p + 1))) && (inp[(p + 1)] == '\t'))) { + size_t t2 = sf_tab(inp, e, (p + 2)); + uint32_t len = sf_num(inp, (t2 + 1), e); + size_t clen = ((size_t)(len)); + if ((((t2 >= e) || (len == NO_NUM)) || (((e + 1) + clen) > n))) { + bad = true; + } else { + size_t ps = (p + 2); + size_t cs = (e + 1); + p = (cs + clen); + stats[ST_FILES] += 1; + cur = MAX_FILES; + bool refuse = false; + if ((((nf >= MAX_FILES) || (sf_path_ok(inp, ps, t2) == false)) || (clen > FILE_CAP))) { + refuse = true; + } + size_t m = 0; + if ((refuse == false)) { + size_t i = 0; + while ((i < clen)) { + fc[i] = inp[(cs + i)]; + i += 1; + } + m = fmt_source(&fc, clen, &fo, FMT_CAP); + if ((fmt_accept(&fc, clen, &fo, m, FMT_CAP) == false)) { + refuse = true; + } + } + if (refuse) { + stats[ST_REFUSED] += 1; + } else { + fps[nf] = ps; + fpe[nf] = t2; + cur = nf; + nf += 1; + size_t os = 0; + size_t ns = 0; + uint32_t line = 1; + uint32_t run = 0; + size_t roff = 0; + bool rbad = false; + while ((ns < m)) { + size_t oe = line_stop(&fc, clen, os); + size_t ne = line_stop(&fo, m, ns); + if ((sf_same_line(&fc, os, oe, &fo, ns, ne) == false)) { + if ((run == 0)) { + run = line; + roff = aw; + rbad = false; + } + if ((sf_line_ok(&fo, ns, ne) == false)) { + rbad = true; + } + size_t q = ns; + while ((q < ne)) { + aw = put_byte(&arena, ARENA_CAP, aw, fo[q]); + q += 1; + } + aw = put_byte(&arena, ARENA_CAP, aw, '\n'); + } else if ((run != 0)) { + if ((nh < MAX_HUNKS)) { + hfile[nh] = ((uint32_t)(cur)); + hstart[nh] = run; + hcount[nh] = (line - run); + hkind[nh] = K_REPLACE; + if (rbad) { + hkind[nh] = K_BAD; + } + hoff[nh] = roff; + hlen[nh] = (aw - roff); + nh += 1; + } else { + stats[ST_TRUNCATED] = 1; + } + run = 0; + } + os = (oe + 1); + ns = (ne + 1); + line += 1; + } + if ((run != 0)) { + if ((nh < MAX_HUNKS)) { + hfile[nh] = ((uint32_t)(cur)); + hstart[nh] = run; + hcount[nh] = (line - run); + hkind[nh] = K_REPLACE; + if (rbad) { + hkind[nh] = K_BAD; + } + hoff[nh] = roff; + hlen[nh] = (aw - roff); + nh += 1; + } else { + stats[ST_TRUNCATED] = 1; + } + } + uint32_t total = sf_lines(&fc, clen); + if ((total >= line)) { + if ((nh < MAX_HUNKS)) { + hfile[nh] = ((uint32_t)(cur)); + hstart[nh] = line; + hcount[nh] = ((total - line) + 1); + hkind[nh] = K_DELETE; + hoff[nh] = aw; + hlen[nh] = 0; + nh += 1; + } else { + stats[ST_TRUNCATED] = 1; + } + } + } + } + } else { + bad = true; + } + } + if (((bad || (aw > ARENA_CAP)) || (sf_sha_ok(inp, sha_s, sha_e) == false))) { + stats[ST_BAD_INPUT] = 1; + return 0; + } + uint32_t posted = plan(nh, &hfile, &hstart, &hcount, &hkind, nd, &dfile, &dstart, &dlen, &verdict); + stats[ST_POSTED] = posted; + stats[ST_OUTSIDE] = count_verdict(V_OUTSIDE, nh, &verdict); + stats[ST_CAPPED] = count_verdict(V_CAP, nh, &verdict); + stats[ST_TOO_BIG] = count_verdict(V_TOO_BIG, nh, &verdict); + stats[ST_UNKNOWN] = count_verdict(V_UNKNOWN, nh, &verdict); + if ((should_post(posted) == false)) { + return 0; + } + size_t w = put_str(out, out_cap, 0, J_HEAD, J_HEAD_LEN); + size_t c = sha_s; + while ((c < sha_e)) { + w = put_byte(out, out_cap, w, inp[c]); + c += 1; + } + w = put_str(out, out_cap, w, J_EVENT, J_EVENT_LEN); + w = put_str(out, out_cap, w, J_NOTE_A, J_NOTE_A_LEN); + w = put_count(out, out_cap, w, posted); + w = put_str(out, out_cap, w, " posted, ", 9); + w = put_count(out, out_cap, w, (((uint32_t)(nh)) - posted)); + w = put_str(out, out_cap, w, J_NOTE_B, J_NOTE_B_LEN); + w = put_str(out, out_cap, w, J_COMMENTS, J_COMMENTS_LEN); + bool first = true; + size_t i = 0; + while ((i < nh)) { + if ((verdict[i] == V_POST)) { + if ((first == false)) { + w = put_byte(out, out_cap, w, ','); + } + first = false; + w = put_str(out, out_cap, w, J_PATH, J_PATH_LEN); + size_t x = fps[((size_t)(hfile[i]))]; + while ((x < fpe[((size_t)(hfile[i]))])) { + w = put_byte(out, out_cap, w, inp[x]); + x += 1; + } + w = put_str(out, out_cap, w, J_SIDE, J_SIDE_LEN); + w = put_count(out, out_cap, w, comment_line(hstart[i], hcount[i])); + uint32_t sl = comment_start_line(hstart[i], hcount[i]); + if ((sl != 0)) { + w = put_str(out, out_cap, w, J_START, J_START_LEN); + w = put_count(out, out_cap, w, sl); + w = put_str(out, out_cap, w, J_START_SIDE, J_START_SIDE_LEN); + } + w = put_str(out, out_cap, w, J_BODY, J_BODY_LEN); + size_t y = hoff[i]; + while ((y < (hoff[i] + hlen[i]))) { + w = sf_esc(out, out_cap, w, arena[y]); + y += 1; + } + w = put_str(out, out_cap, w, J_END, J_END_LEN); + } + i += 1; + } + w = put_str(out, out_cap, w, J_TAIL, J_TAIL_LEN); + return w; +} + +uint8_t byte_at(uint8_t* t, size_t n, size_t i) { + if ((i >= n)) { + return 0; + } + return t[i]; +} + +uint32_t comment_line(uint32_t start, uint32_t count) { + return last_line(start, count); +} + +uint32_t comment_start_line(uint32_t start, uint32_t count) { + if ((last_line(start, count) > start)) { + return start; + } + return 0; +} + +uint32_t count_verdict(uint8_t v, size_t nh, uint8_t* out) { + uint32_t n = 0; + size_t i = 0; + while ((i < nh)) { + if ((out[i] == v)) { + n += 1; + } + i += 1; + } + return n; +} + +const char* event_for(const char* requested, size_t requested_len) { + if (str_eq(requested, requested_len, EVENT, 7)) { + return EVENT; + } + return ""; +} + +bool file_poisoned(uint32_t file, size_t nh, uint32_t* hfile, uint32_t* hstart, uint32_t* hcount, uint8_t* hkind) { + size_t i = 0; + while ((i < nh)) { + if ((hfile[i] == file)) { + if (hunk_unknown(i, nh, hfile, hstart, hcount, hkind)) { + return true; + } + } + i += 1; + } + return false; +} + +uint32_t first_line(uint32_t start) { + return start; +} + +bool header_new_range(uint8_t* t, size_t n, uint32_t* out) { + if ((byte_at(t, n, 0) != '@')) { + return false; + } + if ((byte_at(t, n, 1) != '@')) { + return false; + } + if ((byte_at(t, n, 2) != ' ')) { + return false; + } + if ((byte_at(t, n, 3) != '-')) { + return false; + } + size_t p = 4; + size_t e = num_end(t, n, p); + if ((e == p)) { + return false; + } + p = e; + if ((byte_at(t, n, p) == ',')) { + p += 1; + e = num_end(t, n, p); + if ((e == p)) { + return false; + } + p = e; + } + if ((byte_at(t, n, p) != ' ')) { + return false; + } + if ((byte_at(t, n, (p + 1)) != '+')) { + return false; + } + p += 2; + e = num_end(t, n, p); + if ((e == p)) { + return false; + } + if (((e - p) > 7)) { + return false; + } + __auto_type start = num_val(t, p, e); + p = e; + uint32_t len = 1; + if ((byte_at(t, n, p) == ',')) { + p += 1; + e = num_end(t, n, p); + if ((e == p)) { + return false; + } + if (((e - p) > 7)) { + return false; + } + len = num_val(t, p, e); + p = e; + } + if ((byte_at(t, n, p) != ' ')) { + return false; + } + if ((byte_at(t, n, (p + 1)) != '@')) { + return false; + } + if ((byte_at(t, n, (p + 2)) != '@')) { + return false; + } + out[0] = start; + out[1] = len; + return true; +} + +bool hunk_unknown(size_t i, size_t nh, uint32_t* hfile, uint32_t* hstart, uint32_t* hcount, uint8_t* hkind) { + if ((shape_ok(hkind[i], hstart[i], hcount[i]) == false)) { + return true; + } + __auto_type f1 = first_line(hstart[i]); + __auto_type l1 = last_line(hstart[i], hcount[i]); + size_t j = 0; + while ((j < nh)) { + if (((j != i) && (hfile[j] == hfile[i]))) { + if (shape_ok(hkind[j], hstart[j], hcount[j])) { + __auto_type f2 = first_line(hstart[j]); + __auto_type l2 = last_line(hstart[j], hcount[j]); + if (((f1 <= l2) && (f2 <= l1))) { + return true; + } + } + } + j += 1; + } + return false; +} + +bool in_one_range(uint32_t file, uint32_t first, uint32_t last, uint32_t* dfile, uint32_t* dstart, uint32_t* dlen, size_t nd) { + size_t k = 0; + while ((k < nd)) { + if (((dfile[k] == file) && (dlen[k] >= 1))) { + if (((dstart[k] <= first) && (last <= ((dstart[k] + dlen[k]) - 1)))) { + return true; + } + } + k += 1; + } + return false; +} + +bool is_digit(uint8_t b) { + return ((b >= '0') && (b <= '9')); +} + +uint32_t last_line(uint32_t start, uint32_t count) { + if ((count == 0)) { + return start; + } + return ((start + count) - 1); +} + +size_t num_end(uint8_t* t, size_t n, size_t p) { + size_t e = p; + while ((e < n)) { + if ((is_digit(t[e]) == false)) { + return e; + } + e += 1; + } + return e; +} + +uint32_t num_val(uint8_t* t, size_t p, size_t e) { + uint32_t v = 0; + size_t i = p; + while ((i < e)) { + v = ((v * 10) + ((uint32_t)((t[i] - '0')))); + i += 1; + } + return v; +} + +uint32_t plan(size_t nh, uint32_t* hfile, uint32_t* hstart, uint32_t* hcount, uint8_t* hkind, size_t nd, uint32_t* dfile, uint32_t* dstart, uint32_t* dlen, uint8_t* out) { + uint32_t posted = 0; + size_t i = 0; + while ((i < nh)) { + out[i] = V_UNKNOWN; + if ((file_poisoned(hfile[i], nh, hfile, hstart, hcount, hkind) == false)) { + __auto_type f = first_line(hstart[i]); + __auto_type l = last_line(hstart[i], hcount[i]); + if ((((l - f) + 1) > MAX_SPAN)) { + out[i] = V_TOO_BIG; + } else if ((in_one_range(hfile[i], f, l, dfile, dstart, dlen, nd) == false)) { + out[i] = V_OUTSIDE; + } else if ((posted >= MAX_COMMENTS)) { + out[i] = V_CAP; + } else { + out[i] = V_POST; + posted += 1; + } + } + i += 1; + } + return posted; +} + +bool shape_ok(uint8_t kind, uint32_t start, uint32_t count) { + if ((start < 1)) { + return false; + } + if ((kind == K_INSERT)) { + return (count == 0); + } + if (((kind == K_REPLACE) || (kind == K_DELETE))) { + return (count >= 1); + } + return false; +} + +bool should_post(uint32_t posted) { + return (posted >= 1); +} + +const char* side_for(const char* requested, size_t requested_len) { + if (str_eq(requested, requested_len, SIDE, 5)) { + return SIDE; + } + return ""; +} + +bool buf_has(uint8_t* buf, size_t buf_len, const char* needle, size_t needle_len) { + if ((needle_len > buf_len)) { + return false; + } + size_t start = 0; + while (((start + needle_len) <= buf_len)) { + size_t k = 0; + size_t diff = 0; + while ((k < needle_len)) { + if ((buf[(start + k)] != needle[k])) { + diff += 1; + } + k += 1; + } + if ((diff == 0)) { + return true; + } + start += 1; + } + return false; +} + +uint8_t digit_byte(uint32_t d) { + uint8_t b = '0'; + if ((d == 1)) { + b = '1'; + } + if ((d == 2)) { + b = '2'; + } + if ((d == 3)) { + b = '3'; + } + if ((d == 4)) { + b = '4'; + } + if ((d == 5)) { + b = '5'; + } + if ((d == 6)) { + b = '6'; + } + if ((d == 7)) { + b = '7'; + } + if ((d == 8)) { + b = '8'; + } + if ((d == 9)) { + b = '9'; + } + return b; +} + +size_t put_count(uint8_t* dst, size_t cap, size_t w, uint32_t v) { + uint8_t tmp[16] = {0}; + size_t n = 0; + uint32_t x = v; + if ((x == 0)) { + tmp[0] = digit_byte(0); + n = 1; + } + while ((x > 0)) { + tmp[n] = digit_byte((x % 10)); + n += 1; + x = (x / 10); + } + size_t k = 0; + while ((k < n)) { + if (((w + k) < cap)) { + dst[(w + k)] = tmp[((n - 1) - k)]; + } + k += 1; + } + return (w + n); +} + +size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len) { + size_t i = 0; + while ((i < s_len)) { + if (((w + i) < cap)) { + dst[(w + i)] = s[i]; + } + i += 1; + } + return (w + s_len); +} + +bool str_eq(const char* a, size_t alen, const char* b, size_t blen) { + if ((alen != blen)) { + return false; + } + size_t diff = 0; + size_t i = 0; + while ((i < alen)) { + if ((a[i] != b[i])) { + diff += 1; + } + i += 1; + } + return (diff == 0); +} + +bool fmt_accept(uint8_t* src, size_t n, uint8_t* out, size_t m, size_t cap) { + if ((m > cap)) { + return false; + } + return fmt_exact(src, n, out, m); +} + +bool fmt_exact(uint8_t* a, size_t an, uint8_t* b, size_t bn) { + size_t i = next_solid(a, an, 0); + size_t j = next_solid(b, bn, 0); + while (((i < an) && (j < bn))) { + if ((a[i] != b[j])) { + return false; + } + i = next_solid(a, an, (i + 1)); + j = next_solid(b, bn, (j + 1)); + } + return ((i >= an) && (j >= bn)); +} + +size_t fmt_source(uint8_t* src, size_t n, uint8_t* out, size_t cap) { + size_t w = 0; + size_t pending = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = line_stop(src, n, ls); + size_t ee = trim_end(src, ls, le); + if ((ee == ls)) { + pending += 1; + } else { + while ((pending > 0)) { + w = put_byte(out, cap, w, '\n'); + pending -= 1; + } + size_t i = ls; + while (((i < ee) && ((src[i] == ' ') || (src[i] == '\t')))) { + if ((src[i] == '\t')) { + w = put_byte(out, cap, w, ' '); + w = put_byte(out, cap, w, ' '); + w = put_byte(out, cap, w, ' '); + w = put_byte(out, cap, w, ' '); + } else { + w = put_byte(out, cap, w, ' '); + } + i += 1; + } + while ((i < ee)) { + w = put_byte(out, cap, w, src[i]); + i += 1; + } + w = put_byte(out, cap, w, '\n'); + } + ls = (le + 1); + } + return w; +} + +bool is_space(uint8_t b) { + return ((((b == ' ') || (b == '\t')) || (b == '\n')) || (b == 13)); +} + +size_t line_stop(uint8_t* src, size_t n, size_t s) { + size_t i = s; + while ((i < n)) { + if ((src[i] == '\n')) { + return i; + } + i += 1; + } + return n; +} + +size_t next_solid(uint8_t* t, size_t n, size_t p) { + size_t i = p; + while ((i < n)) { + if ((is_space(t[i]) == false)) { + return i; + } + i += 1; + } + return n; +} + +size_t put_byte(uint8_t* dst, size_t cap, size_t w, uint8_t b) { + if ((w < cap)) { + dst[w] = b; + } + return (w + 1); +} + +size_t trim_end(uint8_t* src, size_t s, size_t e) { + size_t x = e; + while ((x > s)) { + uint8_t b = src[(x - 1)]; + if ((((b != ' ') && (b != '\t')) && (b != 13))) { + return x; + } + x -= 1; + } + return x; +} + +/* ------------------------------------------------------- + Tests + ------------------------------------------------------- */ + +void test_the_json_pieces_end_where_their_lengths_say(void) { + uint8_t b[160] = {0}; + size_t k = put_str(&b, 160, 0, J_HEAD, J_HEAD_LEN); + assert((b[(k - 1)] == 34)); + assert((b[(k - 2)] == ':')); + k = put_str(&b, 160, 0, J_EVENT, J_EVENT_LEN); + assert((b[(k - 1)] == 34)); + assert((b[(k - 2)] == ':')); + k = put_str(&b, 160, 0, J_COMMENTS, J_COMMENTS_LEN); + assert((b[(k - 1)] == '[')); + assert((b[(k - 2)] == ':')); + k = put_str(&b, 160, 0, J_PATH, J_PATH_LEN); + assert((b[(k - 1)] == 34)); + assert((b[(k - 2)] == ':')); + k = put_str(&b, 160, 0, J_SIDE, J_SIDE_LEN); + assert((b[(k - 1)] == ':')); + assert((b[(k - 2)] == 34)); + k = put_str(&b, 160, 0, J_START, J_START_LEN); + assert((b[(k - 1)] == ':')); + assert((b[(k - 2)] == 34)); + k = put_str(&b, 160, 0, J_START_SIDE, J_START_SIDE_LEN); + assert((b[(k - 1)] == 34)); + assert((b[(k - 2)] == 'T')); + k = put_str(&b, 160, 0, J_BODY, J_BODY_LEN); + assert((b[(k - 1)] == 'n')); + assert((b[(k - 2)] == 92)); + k = put_str(&b, 160, 0, J_END, J_END_LEN); + assert((b[(k - 1)] == '}')); + assert((b[(k - 2)] == 34)); + k = put_str(&b, 160, 0, J_TAIL, J_TAIL_LEN); + assert((b[(k - 1)] == '}')); + assert((b[(k - 2)] == ']')); + k = put_str(&b, 160, 0, J_NOTE_A, J_NOTE_A_LEN); + assert((b[(k - 1)] == ' ')); + assert((b[(k - 2)] == ':')); + k = put_str(&b, 160, 0, J_NOTE_B, J_NOTE_B_LEN); + assert((b[(k - 1)] == '.')); + assert((b[(k - 2)] == 't')); +} + +void test_the_formatter_bound_is_four_times_the_file_cap_plus_one(void) { + size_t cap = FILE_CAP; + assert((FMT_CAP == ((cap * 4) + 1))); +} + +void test_the_review_is_a_comment_on_the_right_side(void) { + assert(str_eq(event_for(EVENT, 7), 7, "COMMENT", 7)); + assert(str_eq(side_for(SIDE, 5), 5, "RIGHT", 5)); + assert(buf_has(J_EVENT, J_EVENT_LEN, "COMMENT", 7)); + assert(buf_has(J_SIDE, J_SIDE_LEN, "RIGHT", 5)); +} + +void test_a_commit_id_is_forty_lowercase_hex_digits(void) { + assert((sf_sha_ok("0123456789abcdef0123456789abcdef01234567", 0, 40) == true)); + assert((sf_sha_ok("0123456789abcdef0123456789abcdef01234567", 0, 39) == false)); + assert((sf_sha_ok("0123456789ABCDEF0123456789abcdef01234567", 0, 40) == false)); + assert((sf_sha_ok("0123456789abcdef0123456789abcdef0123456g", 0, 40) == false)); +} + +void test_only_plain_t27_paths_are_taken(void) { + assert((sf_path_ok("specs/x.t27", 0, 11) == true)); + assert((sf_path_ok("a-b_c.d/e.t27", 0, 13) == true)); + assert((sf_path_ok("x.rs", 0, 4) == false)); + assert((sf_path_ok("/specs/x.t27", 0, 12) == false)); + assert((sf_path_ok("specs/../x.t27", 0, 14) == false)); + assert((sf_path_ok("sp ecs/x.t27", 0, 12) == false)); + assert((sf_path_ok("sp\"ecs/x.t27", 0, 12) == false)); + assert((sf_path_ok(".t27", 0, 4) == false)); +} + +void test_a_line_with_a_code_fence_or_a_control_byte_is_not_suggested(void) { + assert((sf_line_ok("x = 1; ; ok", 0, 11) == true)); + assert((sf_line_ok("x ``` y", 0, 7) == false)); + assert((sf_line_ok("x `` ` y", 0, 8) == true)); + assert((sf_line_ok("x\ty", 0, 3) == true)); +} + +void test_an_unformatted_line_inside_the_diff_becomes_a_suggestion(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + size_t m = suggest_review(&inp, w, &out, 4096, &st); + assert((m > 0)); + assert((m <= 4096)); + assert((st[0] == 1)); + assert((st[7] == 0)); + assert((buf_has(&out, m, "{\"commit_id\":\"0123456789abcdef0123456789abcdef01234567\",\"event\":\"COMMENT\"", 73) == true)); + assert((buf_has(&out, m, "{\"path\":\"specs/x.t27\",\"side\":\"RIGHT\",\"line\":1,\"body\":\"```suggestion\\na = 1;\\n```\"}", 82) == true)); + assert((out[(m - 1)] == '}')); + assert((out[(m - 2)] == ']')); +} + +void test_a_formatted_file_gets_no_review(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t7\n", 16); + w = put_str(&inp, 512, w, "a = 1;\n", 7); + w = put_str(&inp, 512, w, "H\t@@ -1,1 +1,1 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[0] == 0)); + assert((st[5] == 1)); + assert((st[7] == 0)); +} + +void test_a_fix_outside_the_diff_is_counted_and_not_posted(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -5,1 +5,1 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[0] == 0)); + assert((st[1] == 1)); +} + +void test_a_comment_and_a_compound_assignment_survive_in_the_suggestion(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t18\n", 17); + w = put_str(&inp, 512, w, "\ti += 1; ; keep \n", 18); + w = put_str(&inp, 512, w, "H\t@@ -1,1 +1,1 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + size_t m = suggest_review(&inp, w, &out, 4096, &st); + assert((st[0] == 1)); + assert((buf_has(&out, m, "```suggestion\\n i += 1; ; keep\\n```", 38) == true)); +} + +void test_a_run_of_lines_is_one_multi_line_suggestion(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t13\n", 17); + w = put_str(&inp, 512, w, "a; \nb; \nc;\n", 13); + w = put_str(&inp, 512, w, "H\t@@ -1,3 +1,3 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + size_t m = suggest_review(&inp, w, &out, 4096, &st); + assert((st[0] == 1)); + assert((buf_has(&out, m, "\"line\":2,\"start_line\":1,\"start_side\":\"RIGHT\",\"body\":\"```suggestion\\na;\\nb;\\n```\"}", 81) == true)); +} + +void test_trailing_blank_lines_become_an_empty_suggestion(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t5\n", 16); + w = put_str(&inp, 512, w, "a;\n\n\n", 5); + w = put_str(&inp, 512, w, "H\t@@ -1,3 +1,3 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + size_t m = suggest_review(&inp, w, &out, 4096, &st); + assert((st[0] == 1)); + assert((buf_has(&out, m, "\"start_line\":2", 14) == true)); + assert((buf_has(&out, m, "\"body\":\"```suggestion\\n```\"}", 28) == true)); +} + +void test_a_line_that_would_close_the_fence_withholds_its_file(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t8\n", 16); + w = put_str(&inp, 512, w, "x ``` \n", 8); + w = put_str(&inp, 512, w, "H\t@@ -1,1 +1,1 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[0] == 0)); + assert((st[4] == 1)); +} + +void test_an_unreadable_diff_header_withholds_its_file(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ nonsense @@\n", 17); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[0] == 0)); + assert((st[4] == 1)); +} + +void test_a_bad_commit_id_posts_nothing(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\tnot-a-sha\n", 12); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[7] == 1)); +} + +void test_a_stream_cut_short_or_with_an_unknown_record_posts_nothing(void) { + uint8_t inp[512] = {0}; + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t99\n", 17); + w = put_str(&inp, 512, w, "a = 1; \n", 9); + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[7] == 1)); + w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "X\tfoo\n", 6); + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[7] == 1)); +} + +void test_a_path_that_is_not_a_t27_file_is_refused(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tsrc/x.rs\t16\n", 14); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + uint8_t out[4096] = {0}; + uint32_t st[9] = {0}; + assert((suggest_review(&inp, w, &out, 4096, &st) == 0)); + assert((st[5] == 1)); + assert((st[6] == 1)); + assert((st[7] == 0)); +} + +void test_twenty_five_fixes_post_twenty_and_count_five_over_the_cap(void) { + uint8_t inp[4096] = {0}; + uint8_t out[16384] = {0}; + uint32_t st[9] = {0}; + size_t w = put_str(&inp, 4096, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 4096, w, "F\tspecs/x.t27\t200\n", 18); + for (size_t k = 0; k < 25; k++) { + assert((k < 25)); + w = put_str(&inp, 4096, w, "a; \nb;\n", 8); + } + w = put_str(&inp, 4096, w, "H\t@@ -1,50 +1,50 @@\n", 20); + size_t m = suggest_review(&inp, w, &out, 16384, &st); + assert((m > 0)); + assert((m <= 16384)); + assert((st[0] == 20)); + assert((st[2] == 5)); +} + +void test_an_output_buffer_that_is_too_small_is_reported_by_its_size(void) { + uint8_t inp[512] = {0}; + size_t w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + uint8_t out[32] = {0}; + uint32_t st[9] = {0}; + size_t need = suggest_review(&inp, w, &out, 32, &st); + assert((need > 32)); +} + + +/* ------------------------------------------------------- + Test runner (compile with -DT27_TEST_MAIN to execute) + ------------------------------------------------------- */ + +#ifdef T27_TEST_MAIN +#include +int main(void) { + test_the_json_pieces_end_where_their_lengths_say(); + test_the_formatter_bound_is_four_times_the_file_cap_plus_one(); + test_the_review_is_a_comment_on_the_right_side(); + test_a_commit_id_is_forty_lowercase_hex_digits(); + test_only_plain_t27_paths_are_taken(); + test_a_line_with_a_code_fence_or_a_control_byte_is_not_suggested(); + test_an_unformatted_line_inside_the_diff_becomes_a_suggestion(); + test_a_formatted_file_gets_no_review(); + test_a_fix_outside_the_diff_is_counted_and_not_posted(); + test_a_comment_and_a_compound_assignment_survive_in_the_suggestion(); + test_a_run_of_lines_is_one_multi_line_suggestion(); + test_trailing_blank_lines_become_an_empty_suggestion(); + test_a_line_that_would_close_the_fence_withholds_its_file(); + test_an_unreadable_diff_header_withholds_its_file(); + test_a_bad_commit_id_posts_nothing(); + test_a_stream_cut_short_or_with_an_unknown_record_posts_nothing(); + test_a_path_that_is_not_a_t27_file_is_refused(); + test_twenty_five_fixes_post_twenty_and_count_five_over_the_cap(); + test_an_output_buffer_that_is_too_small_is_reported_by_its_size(); + printf("All %d tests passed.\n", 19); + return 0; +} +#endif /* T27_TEST_MAIN */ + +#endif /* CLAWSUGGESTFEED_H */ diff --git a/scripts/gen_w672.py b/scripts/gen_w672.py deleted file mode 100644 index 6ec6a4014b..0000000000 --- a/scripts/gen_w672.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w672_bench_module_163x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w672_bench_module_163x2p6_aos_var_call_write.t27") - -OUTER = 163 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 73 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w672_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w681.py b/scripts/gen_w681.py deleted file mode 100644 index 07d11b1c2d..0000000000 --- a/scripts/gen_w681.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w681_bench_module_181x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w681_bench_module_181x2p6_aos_var_call_write.t27") - -OUTER = 181 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 73 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w681_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w682.py b/scripts/gen_w682.py deleted file mode 100644 index f9727e5ede..0000000000 --- a/scripts/gen_w682.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w682_bench_module_183x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w682_bench_module_183x2p6_aos_var_call_write.t27") - -OUTER = 183 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 73 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w682_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w683.py b/scripts/gen_w683.py deleted file mode 100644 index eb3017aa55..0000000000 --- a/scripts/gen_w683.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w683_bench_module_185x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w683_bench_module_185x2p6_aos_var_call_write.t27") - -OUTER = 185 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 92 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w683_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w698.py b/scripts/gen_w698.py deleted file mode 100644 index 652b44be01..0000000000 --- a/scripts/gen_w698.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w698_bench_module_215x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w698_bench_module_215x2p6_aos_var_call_write.t27") - -OUTER = 215 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 99 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w698_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w709.py b/scripts/gen_w709.py deleted file mode 100644 index 482f7ce45c..0000000000 --- a/scripts/gen_w709.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w709_bench_module_237x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w709_bench_module_237x2p6_aos_var_call_write.t27") - -OUTER = 237 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 118 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w709_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w713.py b/scripts/gen_w713.py deleted file mode 100644 index 0565762d39..0000000000 --- a/scripts/gen_w713.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w713_bench_module_245x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w713_bench_module_245x2p6_aos_var_call_write.t27") - -OUTER = 245 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 122 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w713_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w716.py b/scripts/gen_w716.py deleted file mode 100644 index 16e58aa6ec..0000000000 --- a/scripts/gen_w716.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w716_bench_module_251x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w716_bench_module_251x2p6_aos_var_call_write.t27") - -OUTER = 251 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 125 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w716_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w718.py b/scripts/gen_w718.py deleted file mode 100644 index 85f184fe97..0000000000 --- a/scripts/gen_w718.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w718_bench_module_255x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w718_bench_module_255x2p6_aos_var_call_write.t27") - -OUTER = 255 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 127 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w718_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w721.py b/scripts/gen_w721.py deleted file mode 100644 index 5833d311b6..0000000000 --- a/scripts/gen_w721.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w721_bench_module_261x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w721_bench_module_261x2p6_aos_var_call_write.t27") - -OUTER = 261 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 130 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w721_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w728.py b/scripts/gen_w728.py deleted file mode 100644 index 79e11ee824..0000000000 --- a/scripts/gen_w728.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w728_bench_module_275x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w728_bench_module_275x2p6_aos_var_call_write.t27") - -OUTER = 275 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 137 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w728_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w729.py b/scripts/gen_w729.py deleted file mode 100644 index 2927955643..0000000000 --- a/scripts/gen_w729.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w729_bench_module_277x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w729_bench_module_277x2p6_aos_var_call_write.t27") - -OUTER = 277 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 138 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w729_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w731.py b/scripts/gen_w731.py deleted file mode 100644 index 95c2dc8270..0000000000 --- a/scripts/gen_w731.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w731_bench_module_281x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w731_bench_module_281x2p6_aos_var_call_write.t27") - -OUTER = 281 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 140 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w731_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w732.py b/scripts/gen_w732.py deleted file mode 100644 index 2097a1eabc..0000000000 --- a/scripts/gen_w732.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w732_bench_module_283x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w732_bench_module_283x2p6_aos_var_call_write.t27") - -OUTER = 283 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 141 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w732_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/specs/claw/github_surface.t27 b/specs/claw/github_surface.t27 index 286c4ba796..9e87054f87 100644 --- a/specs/claw/github_surface.t27 +++ b/specs/claw/github_surface.t27 @@ -84,7 +84,7 @@ pub const LEVELS : [22]u8 = [ ]; pub const STATUS : [22]u8 = [ - S_PLANNED, S_PLANNED, S_LIVE, S_PLANNED, S_PLANNED, + S_PLANNED, S_PLANNED, S_LIVE, S_PLANNED, S_LIVE, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_OWNER, S_ACTIONS, S_REFUSED, S_NEEDS_HOOK, S_NEEDS_HOOK, S_UNAVAILABLE, S_REFUSED, S_REFUSED, @@ -268,8 +268,8 @@ test surface_rows_are_filled_and_unique { } test surface_census_adds_up { - assert count_status(S_PLANNED) == 10; - assert count_status(S_LIVE) == 1; + assert count_status(S_PLANNED) == 9; + assert count_status(S_LIVE) == 2; assert count_status(S_OWNER) == 1; assert count_status(S_ACTIONS) == 1; assert count_status(S_NEEDS_HOOK) == 2; diff --git a/specs/claw/suggest_feed.t27 b/specs/claw/suggest_feed.t27 new file mode 100644 index 0000000000..945b72538b --- /dev/null +++ b/specs/claw/suggest_feed.t27 @@ -0,0 +1,706 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/suggest_feed.t27 -- the driver between a pull request and specs/claw/suggest.t27, and the +; one function the claw-suggest job calls: PR files and diff headers in, one review document out. +; Issue #10365. Owner order 2026-10-18: wire the merged TRI CLAW law specs to a real pull request. +; -- +; Where things live: +; - Placement (which hunk may be posted, where, how many): suggest.t27 plan. This file never decides it. +; - The fix text: tri/fmt_source.t27 fmt_source, the lossless whitespace formatter that is also +; `t27c fmt`. A suggestion therefore never drops a comment or rewrites `+=`. +; - The input, one stream on stdin, written by the job with git and gh and no logic of its own: +; S TAB <40 hex> the PR head commit the comments anchor on +; F TAB TAB LF a changed .t27 file as it is at that commit +; H TAB <@@ -a,b +c,d @@ ...> one diff header of that file (the `patch` of pr-files), 0 or more +; The bytes of a file follow its F line and are counted, so a file that holds a line starting with +; `H` cannot be mistaken for a header. +; - The mapping: format the file, compare old line k with new line k (the formatter never merges or +; splits lines), group each run of differing lines into one K_REPLACE hunk whose replacement is the +; new lines, turn the blank lines the formatter drops at the end of the file into one K_DELETE hunk, +; hand all hunks and all diff ranges to plan, and render a review from the V_POST ones. +; - The output: a pull-request review document, event COMMENT, side RIGHT, one comment per posted +; hunk with a ```suggestion block. 0 bytes when there is nothing to post. +; -- +; Fail closed (suggest.t27 law 5, applied to this file's own reads): a path that is not a plain .t27 +; path, a file too big to hold, a line with a byte outside printable ASCII or with a code fence in it, +; a diff header that does not parse, a sha that is not 40 hex digits, a stream cut short or a record +; that is not S, F or H -- each withholds the file or the whole review. An unread fact never becomes a +; comment. A formatter result that fails fmt_accept (it would lose a non-whitespace byte) withholds +; its file. +; The honest limit: whitespace only. A file whose only defect is a missing final newline gets no +; suggestion, because a line-based comment cannot express it; `t27c fmt` on the author's side still +; fixes it. phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawSuggestFeed; + +use claw::suggest; +use claw::text; +use tri::fmt_source; + +/// Files, hunks and diff ranges one review can carry; a stream with more is read up to the cap. +pub const MAX_FILES : usize = 64; +pub const MAX_HUNKS : usize = 512; +pub const MAX_RANGES : usize = 2048; +/// Largest file held, the formatter's output bound (4 * FILE_CAP + 1) and the replacement arena. +pub const FILE_CAP : usize = 131072; +pub const FMT_CAP : usize = 524289; +pub const ARENA_CAP : usize = 262144; +/// A hunk kind suggest.t27 does not know: it poisons its file (law 5). +pub const K_BAD : u8 = 99; +pub const NO_NUM : u32 = 4294967295; + +/// Indices into the stats array the caller passes (9 entries). +pub const ST_POSTED : usize = 0; +pub const ST_OUTSIDE : usize = 1; +pub const ST_CAPPED : usize = 2; +pub const ST_TOO_BIG : usize = 3; +pub const ST_UNKNOWN : usize = 4; +pub const ST_FILES : usize = 5; +pub const ST_REFUSED : usize = 6; +pub const ST_BAD_INPUT : usize = 7; +pub const ST_TRUNCATED : usize = 8; +pub const STATS_LEN : usize = 9; + +/// The pieces of the review document, each with its byte length (tests pin the lengths). +pub const J_HEAD : str = "{\"commit_id\":\""; +pub const J_HEAD_LEN : usize = 14; +pub const J_EVENT : str = "\",\"event\":\"COMMENT\",\"body\":\""; +pub const J_EVENT_LEN : usize = 28; +pub const J_COMMENTS : str = "\",\"comments\":["; +pub const J_COMMENTS_LEN : usize = 14; +pub const J_PATH : str = "{\"path\":\""; +pub const J_PATH_LEN : usize = 9; +pub const J_SIDE : str = "\",\"side\":\"RIGHT\",\"line\":"; +pub const J_SIDE_LEN : usize = 24; +pub const J_START : str = ",\"start_line\":"; +pub const J_START_LEN : usize = 14; +pub const J_START_SIDE : str = ",\"start_side\":\"RIGHT\""; +pub const J_START_SIDE_LEN : usize = 21; +pub const J_BODY : str = ",\"body\":\"```suggestion\\n"; +pub const J_BODY_LEN : usize = 24; +pub const J_END : str = "```\"}"; +pub const J_END_LEN : usize = 5; +pub const J_NOTE_A : str = "TRI CLAW formatting suggestions: "; +pub const J_NOTE_A_LEN : usize = 33; +pub const J_NOTE_B : str = " left out (outside this diff, over the caps, or unreadable). Whitespace only: every comment and token is kept."; +pub const J_NOTE_B_LEN : usize = 110; +pub const J_TAIL : str = "]}"; +pub const J_TAIL_LEN : usize = 2; + +/// End of the field that starts at s: the next TAB below e, or e. +fn sf_tab(t : []const u8, e : usize, s : usize) -> usize { + var i : usize = s; + while (i < e) { + if (t[i] == '\t') { return i; } + i += 1; + } + return e; +} + +/// Decimal value of t[s..e); NO_NUM when empty, longer than 9 digits or not all digits. +fn sf_num(t : []const u8, s : usize, e : usize) -> u32 { + if (e <= s) { return NO_NUM; } + if (e - s > 9) { return NO_NUM; } + var v : u32 = 0; + var i : usize = s; + while (i < e) { + if (t[i] < '0' or t[i] > '9') { return NO_NUM; } + v = v * 10 + ((t[i] - '0')) as u32; + i += 1; + } + return v; +} + +/// Whether t[s..e) is 40 lowercase hex digits (a commit id). +fn sf_sha_ok(t : []const u8, s : usize, e : usize) -> bool { + if (e - s != 40) { return false; } + var i : usize = s; + while (i < e) { + const b : u8 = t[i]; + if ((b < '0' or b > '9') and (b < 'a' or b > 'f')) { return false; } + i += 1; + } + return true; +} + +/// Whether t[s..e) is a plain repository path to a .t27 file: letters, digits, `_ . / -`, no leading +/// slash, no `..`, at most 200 bytes, ending in `.t27`. Nothing else is ever written into the JSON. +fn sf_path_ok(t : []const u8, s : usize, e : usize) -> bool { + if (e <= s) { return false; } + if (e - s > 200 or e - s < 5) { return false; } + if (t[s] == '/') { return false; } + if (t[e - 4] != '.' or t[e - 3] != 't' or t[e - 2] != '2' or t[e - 1] != '7') { return false; } + var i : usize = s; + while (i < e) { + const b : u8 = t[i]; + const word : bool = (b >= 'a' and b <= 'z') or (b >= 'A' and b <= 'Z') or (b >= '0' and b <= '9'); + if (word == false and b != '_' and b != '.' and b != '/' and b != '-') { return false; } + if (b == '.' and i + 1 < e and t[i + 1] == '.') { return false; } + i += 1; + } + return true; +} + +/// Whether t[s..e) can go into a suggestion: printable ASCII or tab, and no run of three backticks. +fn sf_line_ok(t : []const u8, s : usize, e : usize) -> bool { + var ticks : usize = 0; + var i : usize = s; + while (i < e) { + const b : u8 = t[i]; + if ((b < 32 and b != '\t') or b > 126) { return false; } + if (b == '`') { + ticks += 1; + if (ticks >= 3) { return false; } + } else { + ticks = 0; + } + i += 1; + } + return true; +} + +fn sf_hexd(d : u8) -> u8 { + if (d < 10) { return digit_byte(d as u32); } + if (d == 10) { return 'a'; } + if (d == 11) { return 'b'; } + if (d == 12) { return 'c'; } + if (d == 13) { return 'd'; } + if (d == 14) { return 'e'; } + return 'f'; +} + +/// One byte of replacement text as JSON string content. +fn sf_esc(dst : []u8, cap : usize, w : usize, b : u8) -> usize { + if (b == 34) { return put_str(dst, cap, w, "\\\"", 2); } + if (b == 92) { return put_str(dst, cap, w, "\\\\", 2); } + if (b == '\n') { return put_str(dst, cap, w, "\\n", 2); } + if (b == '\t') { return put_str(dst, cap, w, "\\t", 2); } + if (b < 32) { + var x : usize = put_str(dst, cap, w, "\\u00", 4); + x = put_byte(dst, cap, x, sf_hexd(b / 16)); + return put_byte(dst, cap, x, sf_hexd(b % 16)); + } + return put_byte(dst, cap, w, b); +} + +/// Whether old line [os, oe) of a equals new line [ns, ne) of b. +fn sf_same_line(a : []const u8, os : usize, oe : usize, b : []const u8, ns : usize, ne : usize) -> bool { + if (oe - os != ne - ns) { return false; } + var k : usize = 0; + while (os + k < oe) { + if (a[os + k] != b[ns + k]) { return false; } + k += 1; + } + return true; +} + +/// Number of lines in t[0..n): newline-terminated ones, plus a last one without a newline. +fn sf_lines(t : []const u8, n : usize) -> u32 { + var c : u32 = 0; + var i : usize = 0; + while (i < n) { + if (t[i] == '\n') { c += 1; } + i += 1; + } + if (n > 0 and t[n - 1] != '\n') { c += 1; } + return c; +} + +/// The review document for the stream inp[0..n), into out. Returns the bytes it needs: 0 when there +/// is nothing to post (or the stream is unreadable, which stats[ST_BAD_INPUT] says). When the result +/// exceeds out_cap the buffer holds a clipped document that must not be posted. +pub fn suggest_review(inp : []const u8, n : usize, out : []u8, out_cap : usize, stats : []u32) -> usize { + var k : usize = 0; + while (k < STATS_LEN) { + stats[k] = 0; + k += 1; + } + var fps : [64]usize = undefined; + var fpe : [64]usize = undefined; + var hfile : [512]u32 = undefined; + var hstart : [512]u32 = undefined; + var hcount : [512]u32 = undefined; + var hkind : [512]u8 = undefined; + var hoff : [512]usize = undefined; + var hlen : [512]usize = undefined; + var verdict : [512]u8 = undefined; + var dfile : [2048]u32 = undefined; + var dstart : [2048]u32 = undefined; + var dlen : [2048]u32 = undefined; + var arena : [262144]u8 = undefined; + var fc : [131072]u8 = undefined; + var fo : [524289]u8 = undefined; + var hb : [128]u8 = undefined; + var rng : [2]u32 = undefined; + var sha_s : usize = 0; + var sha_e : usize = 0; + var nf : usize = 0; + var nh : usize = 0; + var nd : usize = 0; + var aw : usize = 0; + var cur : usize = MAX_FILES; + var bad : bool = false; + var p : usize = 0; + while (p < n and bad == false) { + const e : usize = line_stop(inp, n, p); + const tag : u8 = inp[p]; + if (tag == 'S' and e > p + 1 and inp[p + 1] == '\t') { + sha_s = p + 2; + sha_e = e; + p = e + 1; + } else if (tag == 'H' and e > p + 1 and inp[p + 1] == '\t') { + const hl : usize = e - (p + 2); + var ok : bool = false; + if (cur < MAX_FILES) { + if (hl < 128) { + var i : usize = 0; + while (i < hl) { + hb[i] = inp[p + 2 + i]; + i += 1; + } + ok = header_new_range(&hb, hl, &rng); + } + const cf : u32 = cur as u32; + if (ok and nd < MAX_RANGES) { + dfile[nd] = cf; + dstart[nd] = rng[0]; + dlen[nd] = rng[1]; + nd += 1; + } else if (ok) { + stats[ST_TRUNCATED] = 1; + } else { + // A header we cannot read: this file's diff ranges are unknown, so withhold it. + var j : usize = 0; + while (j < nh) { + if (hfile[j] == cf) { hkind[j] = K_BAD; } + j += 1; + } + } + } + p = e + 1; + } else if (tag == 'F' and e > p + 1 and inp[p + 1] == '\t') { + const t2 : usize = sf_tab(inp, e, p + 2); + const len : u32 = sf_num(inp, t2 + 1, e); + const clen : usize = len as usize; + if (t2 >= e or len == NO_NUM or e + 1 + clen > n) { + bad = true; + } else { + const ps : usize = p + 2; + const cs : usize = e + 1; + p = cs + clen; + stats[ST_FILES] += 1; + cur = MAX_FILES; + var refuse : bool = false; + if (nf >= MAX_FILES or sf_path_ok(inp, ps, t2) == false or clen > FILE_CAP) { refuse = true; } + var m : usize = 0; + if (refuse == false) { + var i : usize = 0; + while (i < clen) { + fc[i] = inp[cs + i]; + i += 1; + } + m = fmt_source(&fc, clen, &fo, FMT_CAP); + if (fmt_accept(&fc, clen, &fo, m, FMT_CAP) == false) { refuse = true; } + } + if (refuse) { + stats[ST_REFUSED] += 1; + } else { + fps[nf] = ps; + fpe[nf] = t2; + cur = nf; + nf += 1; + // Line k of the old text against line k of the new text. + var os : usize = 0; + var ns : usize = 0; + var line : u32 = 1; + var run : u32 = 0; + var roff : usize = 0; + var rbad : bool = false; + while (ns < m) { + const oe : usize = line_stop(&fc, clen, os); + const ne : usize = line_stop(&fo, m, ns); + if (sf_same_line(&fc, os, oe, &fo, ns, ne) == false) { + if (run == 0) { + run = line; + roff = aw; + rbad = false; + } + if (sf_line_ok(&fo, ns, ne) == false) { rbad = true; } + var q : usize = ns; + while (q < ne) { + aw = put_byte(&arena, ARENA_CAP, aw, fo[q]); + q += 1; + } + aw = put_byte(&arena, ARENA_CAP, aw, '\n'); + } else if (run != 0) { + if (nh < MAX_HUNKS) { + hfile[nh] = cur as u32; + hstart[nh] = run; + hcount[nh] = line - run; + hkind[nh] = K_REPLACE; + if (rbad) { hkind[nh] = K_BAD; } + hoff[nh] = roff; + hlen[nh] = aw - roff; + nh += 1; + } else { + stats[ST_TRUNCATED] = 1; + } + run = 0; + } + os = oe + 1; + ns = ne + 1; + line += 1; + } + if (run != 0) { + if (nh < MAX_HUNKS) { + hfile[nh] = cur as u32; + hstart[nh] = run; + hcount[nh] = line - run; + hkind[nh] = K_REPLACE; + if (rbad) { hkind[nh] = K_BAD; } + hoff[nh] = roff; + hlen[nh] = aw - roff; + nh += 1; + } else { + stats[ST_TRUNCATED] = 1; + } + } + // Blank lines the formatter dropped at the end of the file. + const total : u32 = sf_lines(&fc, clen); + if (total >= line) { + if (nh < MAX_HUNKS) { + hfile[nh] = cur as u32; + hstart[nh] = line; + hcount[nh] = total - line + 1; + hkind[nh] = K_DELETE; + hoff[nh] = aw; + hlen[nh] = 0; + nh += 1; + } else { + stats[ST_TRUNCATED] = 1; + } + } + } + } + } else { + bad = true; + } + } + if (bad or aw > ARENA_CAP or sf_sha_ok(inp, sha_s, sha_e) == false) { + stats[ST_BAD_INPUT] = 1; + return 0; + } + const posted : u32 = plan(nh, &hfile, &hstart, &hcount, &hkind, nd, &dfile, &dstart, &dlen, &verdict); + stats[ST_POSTED] = posted; + stats[ST_OUTSIDE] = count_verdict(V_OUTSIDE, nh, &verdict); + stats[ST_CAPPED] = count_verdict(V_CAP, nh, &verdict); + stats[ST_TOO_BIG] = count_verdict(V_TOO_BIG, nh, &verdict); + stats[ST_UNKNOWN] = count_verdict(V_UNKNOWN, nh, &verdict); + if (should_post(posted) == false) { return 0; } + var w : usize = put_str(out, out_cap, 0, J_HEAD, J_HEAD_LEN); + var c : usize = sha_s; + while (c < sha_e) { + w = put_byte(out, out_cap, w, inp[c]); + c += 1; + } + w = put_str(out, out_cap, w, J_EVENT, J_EVENT_LEN); + w = put_str(out, out_cap, w, J_NOTE_A, J_NOTE_A_LEN); + w = put_count(out, out_cap, w, posted); + w = put_str(out, out_cap, w, " posted, ", 9); + w = put_count(out, out_cap, w, nh as u32 - posted); + w = put_str(out, out_cap, w, J_NOTE_B, J_NOTE_B_LEN); + w = put_str(out, out_cap, w, J_COMMENTS, J_COMMENTS_LEN); + var first : bool = true; + var i : usize = 0; + while (i < nh) { + if (verdict[i] == V_POST) { + if (first == false) { w = put_byte(out, out_cap, w, ','); } + first = false; + w = put_str(out, out_cap, w, J_PATH, J_PATH_LEN); + var x : usize = fps[hfile[i] as usize]; + while (x < fpe[hfile[i] as usize]) { + w = put_byte(out, out_cap, w, inp[x]); + x += 1; + } + w = put_str(out, out_cap, w, J_SIDE, J_SIDE_LEN); + w = put_count(out, out_cap, w, comment_line(hstart[i], hcount[i])); + const sl : u32 = comment_start_line(hstart[i], hcount[i]); + if (sl != 0) { + w = put_str(out, out_cap, w, J_START, J_START_LEN); + w = put_count(out, out_cap, w, sl); + w = put_str(out, out_cap, w, J_START_SIDE, J_START_SIDE_LEN); + } + w = put_str(out, out_cap, w, J_BODY, J_BODY_LEN); + var y : usize = hoff[i]; + while (y < hoff[i] + hlen[i]) { + w = sf_esc(out, out_cap, w, arena[y]); + y += 1; + } + w = put_str(out, out_cap, w, J_END, J_END_LEN); + } + i += 1; + } + w = put_str(out, out_cap, w, J_TAIL, J_TAIL_LEN); + return w; +} + +; The tests build every input byte by byte with explicit lengths (gen-c has no .len on strings). + +test the_json_pieces_end_where_their_lengths_say { + var b : [160]u8 = undefined; + var k : usize = put_str(&b, 160, 0, J_HEAD, J_HEAD_LEN); + assert b[k - 1] == 34; + assert b[k - 2] == ':'; + k = put_str(&b, 160, 0, J_EVENT, J_EVENT_LEN); + assert b[k - 1] == 34; + assert b[k - 2] == ':'; + k = put_str(&b, 160, 0, J_COMMENTS, J_COMMENTS_LEN); + assert b[k - 1] == '['; + assert b[k - 2] == ':'; + k = put_str(&b, 160, 0, J_PATH, J_PATH_LEN); + assert b[k - 1] == 34; + assert b[k - 2] == ':'; + k = put_str(&b, 160, 0, J_SIDE, J_SIDE_LEN); + assert b[k - 1] == ':'; + assert b[k - 2] == 34; + k = put_str(&b, 160, 0, J_START, J_START_LEN); + assert b[k - 1] == ':'; + assert b[k - 2] == 34; + k = put_str(&b, 160, 0, J_START_SIDE, J_START_SIDE_LEN); + assert b[k - 1] == 34; + assert b[k - 2] == 'T'; + k = put_str(&b, 160, 0, J_BODY, J_BODY_LEN); + assert b[k - 1] == 'n'; + assert b[k - 2] == 92; + k = put_str(&b, 160, 0, J_END, J_END_LEN); + assert b[k - 1] == '}'; + assert b[k - 2] == 34; + k = put_str(&b, 160, 0, J_TAIL, J_TAIL_LEN); + assert b[k - 1] == '}'; + assert b[k - 2] == ']'; + k = put_str(&b, 160, 0, J_NOTE_A, J_NOTE_A_LEN); + assert b[k - 1] == ' '; + assert b[k - 2] == ':'; + k = put_str(&b, 160, 0, J_NOTE_B, J_NOTE_B_LEN); + assert b[k - 1] == '.'; + assert b[k - 2] == 't'; +} + +test the_formatter_bound_is_four_times_the_file_cap_plus_one { + var cap : usize = FILE_CAP; + assert FMT_CAP == cap * 4 + 1; +} + +test the_review_is_a_comment_on_the_right_side { + assert str_eq(event_for(EVENT, 7), 7, "COMMENT", 7); + assert str_eq(side_for(SIDE, 5), 5, "RIGHT", 5); + assert buf_has(J_EVENT, J_EVENT_LEN, "COMMENT", 7); + assert buf_has(J_SIDE, J_SIDE_LEN, "RIGHT", 5); +} + +test a_commit_id_is_forty_lowercase_hex_digits { + assert sf_sha_ok("0123456789abcdef0123456789abcdef01234567", 0, 40) == true; + assert sf_sha_ok("0123456789abcdef0123456789abcdef01234567", 0, 39) == false; + assert sf_sha_ok("0123456789ABCDEF0123456789abcdef01234567", 0, 40) == false; + assert sf_sha_ok("0123456789abcdef0123456789abcdef0123456g", 0, 40) == false; +} + +test only_plain_t27_paths_are_taken { + assert sf_path_ok("specs/x.t27", 0, 11) == true; + assert sf_path_ok("a-b_c.d/e.t27", 0, 13) == true; + assert sf_path_ok("x.rs", 0, 4) == false; + assert sf_path_ok("/specs/x.t27", 0, 12) == false; + assert sf_path_ok("specs/../x.t27", 0, 14) == false; + assert sf_path_ok("sp ecs/x.t27", 0, 12) == false; + assert sf_path_ok("sp\"ecs/x.t27", 0, 12) == false; + assert sf_path_ok(".t27", 0, 4) == false; +} + +test a_line_with_a_code_fence_or_a_control_byte_is_not_suggested { + assert sf_line_ok("x = 1; ; ok", 0, 11) == true; + assert sf_line_ok("x ``` y", 0, 7) == false; + assert sf_line_ok("x `` ` y", 0, 8) == true; + assert sf_line_ok("x\ty", 0, 3) == true; +} + +test an_unformatted_line_inside_the_diff_becomes_a_suggestion { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + const m : usize = suggest_review(&inp, w, &out, 4096, &st); + assert m > 0; + assert m <= 4096; + assert st[0] == 1; + assert st[7] == 0; + assert buf_has(&out, m, "{\"commit_id\":\"0123456789abcdef0123456789abcdef01234567\",\"event\":\"COMMENT\"", 73) == true; + assert buf_has(&out, m, "{\"path\":\"specs/x.t27\",\"side\":\"RIGHT\",\"line\":1,\"body\":\"```suggestion\\na = 1;\\n```\"}", 82) == true; + assert out[m - 1] == '}'; + assert out[m - 2] == ']'; +} + +test a_formatted_file_gets_no_review { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t7\n", 16); + w = put_str(&inp, 512, w, "a = 1;\n", 7); + w = put_str(&inp, 512, w, "H\t@@ -1,1 +1,1 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[0] == 0; + assert st[5] == 1; + assert st[7] == 0; +} + +test a_fix_outside_the_diff_is_counted_and_not_posted { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -5,1 +5,1 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[0] == 0; + assert st[1] == 1; +} + +test a_comment_and_a_compound_assignment_survive_in_the_suggestion { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t18\n", 17); + w = put_str(&inp, 512, w, "\ti += 1; ; keep \n", 18); + w = put_str(&inp, 512, w, "H\t@@ -1,1 +1,1 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + const m : usize = suggest_review(&inp, w, &out, 4096, &st); + assert st[0] == 1; + assert buf_has(&out, m, "```suggestion\\n i += 1; ; keep\\n```", 38) == true; +} + +test a_run_of_lines_is_one_multi_line_suggestion { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t13\n", 17); + w = put_str(&inp, 512, w, "a; \nb; \nc;\n", 13); + w = put_str(&inp, 512, w, "H\t@@ -1,3 +1,3 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + const m : usize = suggest_review(&inp, w, &out, 4096, &st); + assert st[0] == 1; + assert buf_has(&out, m, "\"line\":2,\"start_line\":1,\"start_side\":\"RIGHT\",\"body\":\"```suggestion\\na;\\nb;\\n```\"}", 81) == true; +} + +test trailing_blank_lines_become_an_empty_suggestion { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t5\n", 16); + w = put_str(&inp, 512, w, "a;\n\n\n", 5); + w = put_str(&inp, 512, w, "H\t@@ -1,3 +1,3 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + const m : usize = suggest_review(&inp, w, &out, 4096, &st); + assert st[0] == 1; + assert buf_has(&out, m, "\"start_line\":2", 14) == true; + assert buf_has(&out, m, "\"body\":\"```suggestion\\n```\"}", 28) == true; +} + +test a_line_that_would_close_the_fence_withholds_its_file { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t8\n", 16); + w = put_str(&inp, 512, w, "x ``` \n", 8); + w = put_str(&inp, 512, w, "H\t@@ -1,1 +1,1 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[0] == 0; + assert st[4] == 1; +} + +test an_unreadable_diff_header_withholds_its_file { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ nonsense @@\n", 17); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[0] == 0; + assert st[4] == 1; +} + +test a_bad_commit_id_posts_nothing { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\tnot-a-sha\n", 12); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[7] == 1; +} + +test a_stream_cut_short_or_with_an_unknown_record_posts_nothing { + var inp : [512]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t99\n", 17); + w = put_str(&inp, 512, w, "a = 1; \n", 9); + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[7] == 1; + w = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "X\tfoo\n", 6); + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[7] == 1; +} + +test a_path_that_is_not_a_t27_file_is_refused { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tsrc/x.rs\t16\n", 14); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + var out : [4096]u8 = undefined; + var st : [9]u32 = undefined; + assert suggest_review(&inp, w, &out, 4096, &st) == 0; + assert st[5] == 1; + assert st[6] == 1; + assert st[7] == 0; +} + +test twenty_five_fixes_post_twenty_and_count_five_over_the_cap { + var inp : [4096]u8 = undefined; + var out : [16384]u8 = undefined; + var st : [9]u32 = undefined; + var w : usize = put_str(&inp, 4096, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 4096, w, "F\tspecs/x.t27\t200\n", 18); + for k in 0..25 { + assert k < 25; + w = put_str(&inp, 4096, w, "a; \nb;\n", 8); + } + w = put_str(&inp, 4096, w, "H\t@@ -1,50 +1,50 @@\n", 20); + const m : usize = suggest_review(&inp, w, &out, 16384, &st); + assert m > 0; + assert m <= 16384; + assert st[0] == 20; + assert st[2] == 5; +} + +test an_output_buffer_that_is_too_small_is_reported_by_its_size { + var inp : [512]u8 = undefined; + var w : usize = put_str(&inp, 512, 0, "S\t0123456789abcdef0123456789abcdef01234567\n", 43); + w = put_str(&inp, 512, w, "F\tspecs/x.t27\t16\n", 17); + w = put_str(&inp, 512, w, "a = 1; \nb = 2;\n", 16); + w = put_str(&inp, 512, w, "H\t@@ -1,2 +1,2 @@\n", 18); + var out : [32]u8 = undefined; + var st : [9]u32 = undefined; + const need : usize = suggest_review(&inp, w, &out, 32, &st); + assert need > 32; +} diff --git a/specs/tri/fmt_source.t27 b/specs/tri/fmt_source.t27 new file mode 100644 index 0000000000..2ddbf8520f --- /dev/null +++ b/specs/tri/fmt_source.t27 @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/fmt_source.t27 -- `t27c fmt`, lossless. Issue #10365 (the formatter half of the TRI CLAW +; suggestion review, specs/claw/suggest.t27, #10286). +; Owner order 2026-10-18: make `t27c fmt` comment-preserving and exact; today it drops every `;` +; comment and rewrites `i += 1` as `i = 1`, so a suggestion built from it would propose deleting the +; comments of a spec. +; -- +; Why not a better AST printer. The old printer rebuilt the file from the parse tree, and the tree +; keeps no comments and no compound assignment. Fixing that means teaching the parser to carry both +; and the printer to place them: a large change, and each gap loses text silently. This file takes the +; other road: it never rebuilds anything. It normalises the WHITESPACE of the source and copies every +; other byte through unchanged, so a comment, an operator, a literal and a string cannot be lost +; because nothing here looks at them. +; -- +; The four rules, each pinned by a test: +; 1. A leading tab on a line becomes four spaces (a space stays a space). +; 2. Trailing spaces, tabs and carriage returns are dropped from every line. +; 3. Blank lines at the end of the file are dropped, and the last line ends with exactly one newline. +; 4. Everything else, including blank lines inside the file, comment lines and the text of a line after +; its indent, is copied byte for byte. +; Line k of the output is line k of the input for every line the output has: nothing is merged or +; split, so a driver can compare the two files line by line (specs/claw/suggest_feed.t27 does). +; -- +; The exactness check. fmt_exact compares the NON-whitespace bytes of the source and of the output, +; in order. The formatter is correct only when that stream is identical; `t27c fmt` prints nothing and +; fails when it is not, so a bug here can refuse to format but cannot damage a file. +; The honest limit: this is a whitespace normaliser, not a style engine. It does not re-indent by brace +; depth, wrap long lines or reorder anything; a spec that is badly indented but has no tabs and no +; trailing space is already "formatted" by these rules. +; ASCII only (L3). phi^2 + 1/phi^2 = 3 | TRINITY + +module TriFmtSource; + +/// Spaces a leading tab expands to. +pub const TAB_WIDTH : usize = 4; + +fn put_byte(dst : []u8, cap : usize, w : usize, b : u8) -> usize { + if (w < cap) { dst[w] = b; } + return w + 1; +} + +/// A byte the formatter may add, drop or move: space, tab, newline, carriage return. +pub fn is_space(b : u8) -> bool { + return b == ' ' or b == '\t' or b == '\n' or b == 13; +} + +/// End of the line that starts at s: the index of its newline, or n when it has none. +fn line_stop(src : []const u8, n : usize, s : usize) -> usize { + var i : usize = s; + while (i < n) { + if (src[i] == '\n') { return i; } + i += 1; + } + return n; +} + +/// End of src[s..e) after its trailing spaces, tabs and carriage returns are dropped. +fn trim_end(src : []const u8, s : usize, e : usize) -> usize { + var x : usize = e; + while (x > s) { + const b : u8 = src[x - 1]; + if (b != ' ' and b != '\t' and b != 13) { return x; } + x -= 1; + } + return x; +} + +/// Normalise src[0..n) into out[0..cap). Returns the bytes the output needs; when that exceeds +/// cap the buffer holds a clipped result the caller must not use. +pub fn fmt_source(src : []const u8, n : usize, out : []u8, cap : usize) -> usize { + var w : usize = 0; + var pending : usize = 0; + var ls : usize = 0; + while (ls < n) { + const le : usize = line_stop(src, n, ls); + const ee : usize = trim_end(src, ls, le); + if (ee == ls) { + pending += 1; + } else { + while (pending > 0) { + w = put_byte(out, cap, w, '\n'); + pending -= 1; + } + var i : usize = ls; + while (i < ee and (src[i] == ' ' or src[i] == '\t')) { + if (src[i] == '\t') { + w = put_byte(out, cap, w, ' '); + w = put_byte(out, cap, w, ' '); + w = put_byte(out, cap, w, ' '); + w = put_byte(out, cap, w, ' '); + } else { + w = put_byte(out, cap, w, ' '); + } + i += 1; + } + while (i < ee) { + w = put_byte(out, cap, w, src[i]); + i += 1; + } + w = put_byte(out, cap, w, '\n'); + } + ls = le + 1; + } + return w; +} + +/// Index of the next byte at or after p that is not whitespace; n when there is none. +fn next_solid(t : []const u8, n : usize, p : usize) -> usize { + var i : usize = p; + while (i < n) { + if (is_space(t[i]) == false) { return i; } + i += 1; + } + return n; +} + +/// Whether a[0..an) and b[0..bn) hold the same non-whitespace bytes in the same order. +pub fn fmt_exact(a : []const u8, an : usize, b : []const u8, bn : usize) -> bool { + var i : usize = next_solid(a, an, 0); + var j : usize = next_solid(b, bn, 0); + while (i < an and j < bn) { + if (a[i] != b[j]) { return false; } + i = next_solid(a, an, i + 1); + j = next_solid(b, bn, j + 1); + } + return i >= an and j >= bn; +} + +/// Whether the formatted text fits cap and keeps every non-whitespace byte: the only case in which +/// `t27c fmt` may print it. +pub fn fmt_accept(src : []const u8, n : usize, out : []const u8, m : usize, cap : usize) -> bool { + if (m > cap) { return false; } + return fmt_exact(src, n, out, m); +} + +test a_comment_survives { + var out : [128]u8 = undefined; + const src : str = "; keep me\nfn f() {\n ; and me\n x = 1;\n}\n"; + const m : usize = fmt_source(src, src.len, &out, 128); + assert m == src.len; + for i in 0..m { assert out[i] == src[i]; } +} + +test a_compound_assignment_survives { + var out : [64]u8 = undefined; + const src : str = " i += 1;\n j -= 2;\n k *= 3;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == src.len; + for i in 0..m { assert out[i] == src[i]; } +} + +test a_leading_tab_becomes_four_spaces { + var out : [64]u8 = undefined; + const src : str = "\tx = 1;\n\t\ty = 2;\n"; + const want : str = " x = 1;\n y = 2;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == want.len; + for i in 0..m { assert out[i] == want[i]; } +} + +test a_tab_after_the_indent_is_kept { + var out : [64]u8 = undefined; + const src : str = " a\tb;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == src.len; + for i in 0..m { assert out[i] == src[i]; } +} + +test trailing_space_and_tab_are_dropped { + var out : [64]u8 = undefined; + const src : str = "a = 1; \nb = 2;\t\nc = 3;\n"; + const want : str = "a = 1;\nb = 2;\nc = 3;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == want.len; + for i in 0..m { assert out[i] == want[i]; } +} + +test a_carriage_return_before_the_newline_is_dropped { + // "a;" CR LF "b;" CR LF, built byte by byte: the CR is byte 13. + var src : [8]u8 = undefined; + src[0] = 'a'; src[1] = ';'; src[2] = 13; src[3] = '\n'; + src[4] = 'b'; src[5] = ';'; src[6] = 13; src[7] = '\n'; + var out : [16]u8 = undefined; + const m : usize = fmt_source(&src, 8, &out, 16); + assert m == 6; + assert out[0] == 'a'; + assert out[1] == ';'; + assert out[2] == '\n'; + assert out[3] == 'b'; + assert out[4] == ';'; + assert out[5] == '\n'; +} + +test a_missing_final_newline_is_added { + var out : [64]u8 = undefined; + const src : str = "a = 1;\nb = 2;"; + const want : str = "a = 1;\nb = 2;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == want.len; + for i in 0..m { assert out[i] == want[i]; } +} + +test blank_lines_at_the_end_are_dropped_and_inner_ones_kept { + var out : [64]u8 = undefined; + const src : str = "a;\n\n\nb;\n \n\n"; + const want : str = "a;\n\n\nb;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == want.len; + for i in 0..m { assert out[i] == want[i]; } +} + +test blank_lines_at_the_start_are_kept { + var out : [64]u8 = undefined; + const src : str = "\n\na;\n"; + const m : usize = fmt_source(src, src.len, &out, 64); + assert m == src.len; + for i in 0..m { assert out[i] == src[i]; } +} + +test an_empty_or_blank_file_formats_to_nothing { + var out : [16]u8 = undefined; + assert fmt_source("", 0, &out, 16) == 0; + assert fmt_source(" \n\t\n\n", 5, &out, 16) == 0; +} + +test formatting_twice_changes_nothing { + var a : [128]u8 = undefined; + var b : [128]u8 = undefined; + const src : str = "\tfn f() { \n\t\t; c \n\n\n\t\tx += 1;\t\n\t}\n\n"; + const m : usize = fmt_source(src, src.len, &a, 128); + const k : usize = fmt_source(&a, m, &b, 128); + assert k == m; + for i in 0..m { assert a[i] == b[i]; } +} + +test an_overflowing_result_reports_the_bytes_it_needs { + var out : [4]u8 = undefined; + const src : str = "\t\tabcdef\n"; + const m : usize = fmt_source(src, src.len, &out, 4); + assert m == 15; + assert fmt_accept(src, src.len, &out, m, 4) == false; +} + +test exactness_ignores_only_whitespace { + assert fmt_exact("a b\n", 5, "a b", 3) == true; + assert fmt_exact("a b", 3, "ab", 2) == true; + assert fmt_exact("a b", 3, "a c", 3) == false; + assert fmt_exact("a b", 3, "a", 1) == false; + assert fmt_exact("a", 1, "a b", 3) == false; + assert fmt_exact("; x\n", 4, "x\n", 2) == false; + assert fmt_exact("i += 1", 6, "i = 1", 5) == false; + assert fmt_exact("", 0, " \n", 3) == true; +} + +test a_dropped_comment_is_refused { + var out : [64]u8 = undefined; + const src : str = "; note\nx = 1;\n"; + const lossy : str = "x = 1;\n"; + assert fmt_accept(src, src.len, lossy, lossy.len, 64) == false; + const m : usize = fmt_source(src, src.len, &out, 64); + assert fmt_accept(src, src.len, &out, m, 64) == true; +} + +test the_output_of_every_sample_is_exact { + var out : [256]u8 = undefined; + var samples : [4]str = ["\ta; \n\n\n", "; c\n\tb += 1; ; trailing\n", "x \ny\t\n", "no newline"]; + for i in 0..4 { + const m : usize = fmt_source(samples[i], samples[i].len, &out, 256); + assert fmt_accept(samples[i], samples[i].len, &out, m, 256) == true; + } +} diff --git a/tools/census/quiet.txt b/tools/census/quiet.txt index aa3f93081b..cbcbd4bf6f 100644 --- a/tools/census/quiet.txt +++ b/tools/census/quiet.txt @@ -2,7 +2,7 @@ GATE STEPS WHOSE PASS SURVIVES THE SUBJECT GOING MISSING workflow files read 67 steps in a quiet shape 33 - named a path but not quiet 204 (--excluded prints them) + named a path but not quiet 208 (--excluded prints them) by shape: failure branch passes 14 `… 2>/dev/null … || echo PASSED` diff --git a/tools/census/shell.txt b/tools/census/shell.txt index 5f856d6146..1d773ed370 100644 --- a/tools/census/shell.txt +++ b/tools/census/shell.txt @@ -1,11 +1,11 @@ WHICH INTERPRETER EACH GATE STEP IS HANDED TO, AND WHO SAYS SO workflow files read 67 - jobs 89 - run: steps 316 + jobs 91 + run: steps 320 who names the shell: - the runner does 295 no container, so bash -eo pipefail + the runner does 299 no container, so bash -eo pipefail a `shell:` key does 6 NOBODY 15 a container and no `shell:` key From 068c80a4c08ef832554c737967272fba6e0e54d0 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 17:09:57 +0700 Subject: [PATCH 2/3] claw-suggest: accept CLAW_APP_ID as a variable or a secret -- it is a secret in gHashTag/t27 Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/gate-topology.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 6741b76cc7..5a47dc19b3 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -148,9 +148,9 @@ jobs: permissions: {contents: read} steps: - id: have - env: {K: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", I: "${{ vars.CLAW_APP_ID }}"} - run: if [ -n "$K" ] && [ -n "$I" ]; then echo ok=1 >> "$GITHUB_OUTPUT"; else echo "claw/suggest posted nothing - secret CLAW_APP_PRIVATE_KEY or variable CLAW_APP_ID is not set (docs/claw/README.md)." >> "$GITHUB_STEP_SUMMARY"; fi - - {id: tok, if: steps.have.outputs.ok, uses: actions/create-github-app-token@v2, with: {app-id: "${{ vars.CLAW_APP_ID }}", private-key: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", owner: gHashTag, repositories: t27, permission-pull-requests: write, permission-contents: read}} + env: {K: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", I: "${{ vars.CLAW_APP_ID || secrets.CLAW_APP_ID }}"} + run: if [ -n "$K" ] && [ -n "$I" ]; then echo ok=1 >> "$GITHUB_OUTPUT"; else echo "claw/suggest posted nothing - secret CLAW_APP_PRIVATE_KEY or CLAW_APP_ID (variable or secret) is not set (docs/claw/README.md)." >> "$GITHUB_STEP_SUMMARY"; fi + - {id: tok, if: steps.have.outputs.ok, uses: actions/create-github-app-token@v2, with: {app-id: "${{ vars.CLAW_APP_ID || secrets.CLAW_APP_ID }}", private-key: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", owner: gHashTag, repositories: t27, permission-pull-requests: write, permission-contents: read}} - {if: steps.have.outputs.ok, uses: actions/checkout@v6, with: {ref: "${{ github.event.pull_request.head.sha }}", persist-credentials: false}} - if: steps.have.outputs.ok env: {GH_TOKEN: "${{ steps.tok.outputs.token }}", R: "${{ github.repository }}", N: "${{ github.event.pull_request.number }}", S: "${{ github.event.pull_request.head.sha }}"} From 565cdadeb7bff674c3297140449d04cb536918a7 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 17:04:33 +0700 Subject: [PATCH 3/3] proof only, never merge: tab-indented consts in suggest.t27 so claw-suggest has something to suggest Co-Authored-By: Claude Sonnet 5.5 --- specs/claw/suggest.t27 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/claw/suggest.t27 b/specs/claw/suggest.t27 index 32bfc227bf..2dfced5cde 100644 --- a/specs/claw/suggest.t27 +++ b/specs/claw/suggest.t27 @@ -45,12 +45,12 @@ use claw::text; /// The only review event this module ever lets through. pub const EVENT : str = "COMMENT"; /// The only side a suggestion anchors on. -pub const SIDE : str = "RIGHT"; + pub const SIDE : str = "RIGHT"; /// The Markdown fence that opens a suggestion block; it closes with a bare fence line. pub const FENCE : str = "```suggestion"; /// Comments per review and lines per suggestion (law 4). -pub const MAX_COMMENTS : u32 = 20; + pub const MAX_COMMENTS : u32 = 20; pub const MAX_SPAN : u32 = 40; /// Hunk kinds.