From 811d11813db34bb2216b64b4bfdab6c83bc8d0b9 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 01:06:28 +0700 Subject: [PATCH] fix(t27b): a port named after a t27 keyword is spelled with a trailing underscore (Closes #10061) The t27 lexer reserves `in`, `for`, `test`, `module`, `var`, ..., so a hardware module could not declare the Verilog port `in` that CVDP 8x3_priority_encoder_0001 requires: `const in : u8 = undefined;` is a parse error (KwIn), and the spec never reached Verilog. verilog_plan.t27 records the rule: a name that is a t27 keyword followed by exactly one `_` (`in_`) is written in Verilog as the keyword (`in`). It holds the keyword list (T27_KEYWORDS, the 29 words the bootstrap lexer reserves) and name_end, which drops that `_` and nothing else; `data_`, `in__`, `_` and `xin_` are unchanged. put_name in verilog_emit.t27 reads name_end, so the declaration and every read agree, and a stem that is also a Verilog keyword (`for_`) is escaped as before (`\for `). The rename is injective, since no t27 name can be a keyword. A raw-identifier syntax would need a lexer change in t27c (Rust), which is out of bounds. Tests: a_t27_keyword_with_one_trailing_underscore_names_the_keyword and an_input_spelled_in_underscore_is_the_port_in; negative control a_name_that_is_no_keyword_keeps_every_underscore, plus `data_` asserts in the emit fixture. gen/rust regenerated with t27c gen-rust; VerilogPlan and VerilogEmit resealed, and AstShape, TsPlan and EmitKit, whose seal closures record verilog_plan's hash. All on the Railway lab. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27b_T27bAstShape.json | 6 +- .trinity/seals/t27b_T27bEmitKit.json | 6 +- .trinity/seals/t27b_T27bTsPlan.json | 6 +- .trinity/seals/t27b_T27bVerilogEmit.json | 20 +++--- .trinity/seals/t27b_T27bVerilogPlan.json | 18 +++--- gen/rust/tri/t27b/verilog_emit.rs | 29 +++++++-- gen/rust/tri/t27b/verilog_plan.rs | 22 ++++++- specs/tri/t27b/verilog_emit.t27 | 79 ++++++++++++++++++++++-- specs/tri/t27b/verilog_plan.t27 | 55 ++++++++++++++++- 9 files changed, 203 insertions(+), 38 deletions(-) diff --git a/.trinity/seals/t27b_T27bAstShape.json b/.trinity/seals/t27b_T27bAstShape.json index f10a59d35e..93b42ce620 100644 --- a/.trinity/seals/t27b_T27bAstShape.json +++ b/.trinity/seals/t27b_T27bAstShape.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+f787afb", + "built_by": "t27c-bootstrap@0.5.2+063da04", "closure": [ { "spec": "specs/tri/t27b/ast_scan.t27", @@ -7,7 +7,7 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428" + "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -18,7 +18,7 @@ "module": "T27bAstShape", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T17:33:08Z", + "sealed_at": "2026-10-17T18:12:16Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:fef40e3e7885265523c48d4a936c033bbc929b80dbf3ec06f79332dd63834134", "spec_path": "specs/tri/t27b/ast_shape.t27", diff --git a/.trinity/seals/t27b_T27bEmitKit.json b/.trinity/seals/t27b_T27bEmitKit.json index 368aacdc08..97c18377d9 100644 --- a/.trinity/seals/t27b_T27bEmitKit.json +++ b/.trinity/seals/t27b_T27bEmitKit.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+9b160b143", + "built_by": "t27c-bootstrap@0.5.2+063da04", "closure": [ { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428" + "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bEmitKit", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T17:54:26Z", + "sealed_at": "2026-10-17T18:12:16Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:7a31a1e4383104b0b59b2a339ccbe6d87eb69f56f28e5bff95befa18b0dba795", "spec_path": "specs/tri/t27b/emit_kit.t27", diff --git a/.trinity/seals/t27b_T27bTsPlan.json b/.trinity/seals/t27b_T27bTsPlan.json index 56d1b99659..980733249e 100644 --- a/.trinity/seals/t27b_T27bTsPlan.json +++ b/.trinity/seals/t27b_T27bTsPlan.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+a1b584b29", + "built_by": "t27c-bootstrap@0.5.2+063da04", "closure": [ { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:6178c276f6928f211b1cb65f470a93a5fcfbb11119bad7a26030072d777e8e70" + "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bTsPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T17:23:01Z", + "sealed_at": "2026-10-17T18:12:17Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:d046e9702ec1a0a9db5b9d8666e0bc8f7f38361f1bc320e5d9420931f7887e46", "spec_path": "specs/tri/t27b/ts_plan.t27", diff --git a/.trinity/seals/t27b_T27bVerilogEmit.json b/.trinity/seals/t27b_T27bVerilogEmit.json index 9a9149b323..d581e90647 100644 --- a/.trinity/seals/t27b_T27bVerilogEmit.json +++ b/.trinity/seals/t27b_T27bVerilogEmit.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+9b160b143", + "built_by": "t27c-bootstrap@0.5.2+063da04", "closure": [ { "spec": "specs/tri/t27b/emit_kit; // byte writers, to_front, line_near, resolve and symbols, test trees.t27", @@ -11,27 +11,27 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428" + "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:24f73f039c0ba7fa8a523d428bb49d715d43cbbb784f12d9ff08cde73b3035e1", - "gen_hash_rust": "sha256:827184630e8660cf8a16957f96bffd91a136365061ce2b7ad32287a0380421fc", - "gen_hash_verilog": "sha256:cc260a30a0d7d86562173ec7e5037d4e12b388faaf267a083783b72f24356760", - "gen_hash_zig": "sha256:5970a4a3ef8dfa8c720bee5f09573e5914ce023f6c7d86caecc080dd81a23d1a", + "gen_hash_c": "sha256:d4a0c1d16b2e75c1d7028eae7133c8af8359b1e90ada94289435881e2c31a2dc", + "gen_hash_rust": "sha256:daa741c306509d8baabb4130273593065066784a63697e693d5287bca45ce7d8", + "gen_hash_verilog": "sha256:81f1f5903b2800ed2d684e7ceb986eb7106e8ec9f6978d329afe7313b1565a3c", + "gen_hash_zig": "sha256:abf0fbf7e8028b15f30438cae0a89bb32a2b5257576e7f1d2f1027958b1d6d6f", "module": "T27bVerilogEmit", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T17:54:27Z", + "sealed_at": "2026-10-17T18:12:17Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:e34f866a304927d4ff6b2eca2485c74551490d6dd79aae40001249debc14cc76", + "spec_hash": "sha256:b28baac32b8d291619dfe2664fe4167287f6705ec901fa0e87200ee95523a84d", "spec_path": "specs/tri/t27b/verilog_emit.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 7, - "total": 7, + "passed": 8, + "total": 8, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27b_T27bVerilogPlan.json b/.trinity/seals/t27b_T27bVerilogPlan.json index 5e92056a97..51eaafd264 100644 --- a/.trinity/seals/t27b_T27bVerilogPlan.json +++ b/.trinity/seals/t27b_T27bVerilogPlan.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+f787afb", + "built_by": "t27c-bootstrap@0.5.2+063da04", "closure": [ { "spec": "specs/tri/t27b/ast_walk.t27", @@ -7,23 +7,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:88edabc42fca646e9a444d9f82370d78ecb67a5afa554f949f57fb4eb573a7f4", - "gen_hash_rust": "sha256:b02233d200b6cad6d160305768928911509d98199f0aee9d208bf9f14a1e9065", - "gen_hash_verilog": "sha256:c490c6a07fb43b4ae5fd69949d9725baf10e9332277667b7365ce040d605280d", - "gen_hash_zig": "sha256:30a577020d433ae2de867124d570b67a3f4799d03909c83c38f3f723a33d3a55", + "gen_hash_c": "sha256:5b61c4481375289a5498e39d2f7ea3ea430fdc8d100fc0d6faa3428f67919518", + "gen_hash_rust": "sha256:5feafbe5476ebc8fb7f3abfb05bd0225bc8fd14c3f3bc6b479421845a052d422", + "gen_hash_verilog": "sha256:62cec2367aab19770b0e706753ee7905e30050c2fb52b807bfcf909cc6c674e0", + "gen_hash_zig": "sha256:3d3bc805bd698fc8e6470c867572bcdedf3d090f9f59bc5c8b1f1b3b760f2637", "module": "T27bVerilogPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T17:33:05Z", + "sealed_at": "2026-10-17T18:12:18Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428", + "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708", "spec_path": "specs/tri/t27b/verilog_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 14, - "total": 14, + "passed": 16, + "total": 16, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tri/t27b/verilog_emit.rs b/gen/rust/tri/t27b/verilog_emit.rs index 516efcca8f..305e503502 100644 --- a/gen/rust/tri/t27b/verilog_emit.rs +++ b/gen/rust/tri/t27b/verilog_emit.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 7 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 8 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. @@ -15,12 +15,13 @@ pub const M_TEST: u8 = 3; pub const M_TBFUNC: u8 = 4; pub fn put_name(out: &mut [u8], at: usize, text: &[u8], a: usize, b: usize) -> usize { - if verilog_keyword(&text[(a) as usize..(b) as usize]) { + let e: usize = name_end(text, a, b); + if verilog_keyword(&text[(a) as usize..(e) as usize]) { let mut q: usize = put_text(out, at, "\\"); - q = put_bytes(out, q, text, a, b); + q = put_bytes(out, q, text, a, e); return put_text(out, q, " "); } - return put_bytes(out, at, text, a, b); + return put_bytes(out, at, text, a, e); } pub fn put_range(out: &mut [u8], at: usize, t: i64) -> usize { @@ -2999,6 +3000,8 @@ pub const ROLE_PARAM: u8 = 2; pub const ROLE_PORT: u8 = 1; +pub const T27_KEYWORDS: [&'static str; 29] = ["and", "bench", "break", "const", "continue", "else", "enum", "false", "fn", "for", "if", "in", "invariant", "let", "module", "or", "pragma", "pub", "return", "struct", "switch", "test", "true", "try", "use", "using", "var", "void", "while"]; + pub const TY_BAD: i64 = -1; pub const TY_BOOL: i64 = 65; @@ -3350,6 +3353,13 @@ pub fn meta_word(meta: &[u8], i: usize, w: usize) -> usize { return u32_at(meta, ((i * META) + (w * 4))); } +pub fn name_end(text: &[u8], a: usize, b: usize) -> usize { + if (((b > (a + 1)) && (text[((b - 1)) as usize] == 95)) && t27_keyword(&text[(a) as usize..((b - 1)) as usize])) { + return (b - 1); + } + return b; +} + pub fn op_bitwise(s: &[u8]) -> bool { return ((same(s, "&") || same(s, "|")) || same(s, "^")); } @@ -3535,6 +3545,17 @@ pub fn steps_up(nodes: &[u8], text: &[u8], s: usize, va: usize, vb: usize) -> i6 return cc; } +pub fn t27_keyword(s: &[u8]) -> bool { + let mut j: usize = 0; + while (j < 29) { + if same(s, T27_KEYWORDS[(j) as usize]) { + return true; + } + j = (j + 1); + } + return false; +} + pub fn tie_folds(s: &[u8], v: u64) -> bool { return ((same(s, "rst_n") || same(s, "en")) && (v != 0)); } diff --git a/gen/rust/tri/t27b/verilog_plan.rs b/gen/rust/tri/t27b/verilog_plan.rs index 8ddf9551a4..4ece161f37 100644 --- a/gen/rust/tri/t27b/verilog_plan.rs +++ b/gen/rust/tri/t27b/verilog_plan.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 14 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 16 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. @@ -820,6 +820,26 @@ pub fn verilog_keyword(s: &[u8]) -> bool { return false; } +pub const T27_KEYWORDS: [&'static str; 29] = ["and", "bench", "break", "const", "continue", "else", "enum", "false", "fn", "for", "if", "in", "invariant", "let", "module", "or", "pragma", "pub", "return", "struct", "switch", "test", "true", "try", "use", "using", "var", "void", "while"]; + +pub fn t27_keyword(s: &[u8]) -> bool { + let mut j: usize = 0; + while (j < 29) { + if same(s, T27_KEYWORDS[(j) as usize]) { + return true; + } + j = (j + 1); + } + return false; +} + +pub fn name_end(text: &[u8], a: usize, b: usize) -> usize { + if (((b > (a + 1)) && (text[((b - 1)) as usize] == 95)) && t27_keyword(&text[(a) as usize..((b - 1)) as usize])) { + return (b - 1); + } + return b; +} + pub const PT: &'static str = "i<16+=1body<=+jcontinue_expr"; pub const PT2: &'static str = "i<16+=1body<=+jundefinedxxxx"; diff --git a/specs/tri/t27b/verilog_emit.t27 b/specs/tri/t27b/verilog_emit.t27 index 9a069a1aa4..b0f07270d3 100644 --- a/specs/tri/t27b/verilog_emit.t27 +++ b/specs/tri/t27b/verilog_emit.t27 @@ -37,14 +37,16 @@ pub const M_TBFUNC : u8 = 4; // Writing bytes. // ---------------------------------------------------------------------------------------------------------- -/// Writes the name text[a .. b], as an escaped identifier (`\wait `) when it is a Verilog keyword. +/// Writes the name text[a .. b]: without the trailing `_` of a t27 keyword (`in_` is `in`, name_end), and as +/// an escaped identifier (`\wait `) when what is left is a Verilog keyword. pub fn put_name(out: []u8, at: usize, text: []const u8, a: usize, b: usize) -> usize { - if (verilog_keyword(text[a .. b])) { + const e : usize = name_end(text, a, b); + if (verilog_keyword(text[a .. e])) { var q : usize = put_text(out, at, "\\"); - q = put_bytes(out, q, text, a, b); + q = put_bytes(out, q, text, a, e); return put_text(out, q, " "); } - return put_bytes(out, at, text, a, b); + return put_bytes(out, at, text, a, e); } /// Writes the range of a scalar type, `[W-1:0] `, or nothing for one bit. @@ -2096,6 +2098,75 @@ test numbers_ranges_literals_and_names_are_spelled { assert n4 == 6 and out[0] == 92 and out[5] == 32; const n5 : usize = put_name(out[0 .. 64], 0, "xseed", 1, 5); assert n5 == 4 and holds(out[0 .. 64], 4, "seed"); + // a t27 keyword with its `_`: the keyword; one that is a Verilog keyword too is then escaped + const n6 : usize = put_name(out[0 .. 64], 0, "xin_", 1, 4); + assert n6 == 2 and out[0] == 105 and out[1] == 110; + const n7 : usize = put_name(out[0 .. 64], 0, "for_", 0, 4); + assert n7 == 5 and holds(out[0 .. 64], 5, "\\for "); + // and a name that is no keyword keeps its `_` + const n8 : usize = put_name(out[0 .. 64], 0, "data_", 0, 5); + assert n8 == 5 and holds(out[0 .. 64], 5, "data_"); +} + +test an_input_spelled_in_underscore_is_the_port_in { + // module P { const in_ : u8 = undefined; pub const out : u8 = in_; } + // text: P 0..1, in_ 1..4, u8 4..6, undefined 6..15, out 15..18, data_ 18..23, for_ 23..27 + const tx : str = "Pin_u8undefinedoutdata_for_"; + var nb : [360]u8 = undefined; + tnode(nb[0 .. 360], 0, K_MODULE, 5, 0, 1); + tnode(nb[0 .. 360], 1, K_CONST_DECL, 3, 1, 4); + vset(nb[0 .. 360], 1, F_TYPE, 4, 6); + tnode(nb[0 .. 360], 2, K_EXPR_IDENTIFIER, 3, 6, 15); + tnode(nb[0 .. 360], 3, K_CONST_DECL, 5, 15, 18); + vset(nb[0 .. 360], 3, F_TYPE, 4, 6); + tnode(nb[0 .. 360], 4, K_EXPR_IDENTIFIER, 5, 1, 4); + var mb : [80]u8 = undefined; + var i : usize = 0; + while (i < 5) { + tmeta(mb[0 .. 80], i, 0, i + 1); + i = i + 1; + } + tmeta(mb[0 .. 80], 3, FLAG_PUB, 4); + var wk : [80]u8 = undefined; + i = 0; + while (i < 80) { + wk[i] = 0; + i = i + 1; + } + var out : [4096]u8 = undefined; + const r : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 80], out[0 .. 4096]); + assert r > 0; + const n : usize = @intCast(r); + assert holds(out[0 .. 4096], n, "input wire [7:0] in,") or holds(out[0 .. 4096], n, "input wire [7:0] in\n"); + assert holds(out[0 .. 4096], n, "assign out = in;"); + assert !holds(out[0 .. 4096], n, "in_"); + // negative control: an input `data_` is no keyword, and keeps its `_` at the port and at the read + vset(nb[0 .. 360], 1, F_NAME, 18, 23); + vset(nb[0 .. 360], 4, F_NAME, 18, 23); + i = 0; + while (i < 80) { + wk[i] = 0; + i = i + 1; + } + const r2 : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 80], out[0 .. 4096]); + assert r2 > 0; + const n2 : usize = @intCast(r2); + assert holds(out[0 .. 4096], n2, "input wire [7:0] data_"); + assert holds(out[0 .. 4096], n2, "assign out = data_;"); + // `for_` is the port `for`, which is a Verilog keyword too: escaped, at the port and at the read + vset(nb[0 .. 360], 1, F_NAME, 23, 27); + vset(nb[0 .. 360], 4, F_NAME, 23, 27); + i = 0; + while (i < 80) { + wk[i] = 0; + i = i + 1; + } + const r3 : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 80], out[0 .. 4096]); + assert r3 > 0; + const n3 : usize = @intCast(r3); + assert holds(out[0 .. 4096], n3, "input wire [7:0] \\for "); + assert holds(out[0 .. 4096], n3, "assign out = \\for ;"); + assert !holds(out[0 .. 4096], n3, "for_"); } test a_clocked_counter_lowers_to_a_register_and_an_always_block { diff --git a/specs/tri/t27b/verilog_plan.t27 b/specs/tri/t27b/verilog_plan.t27 index 69a71d6392..a8d2407351 100644 --- a/specs/tri/t27b/verilog_plan.t27 +++ b/specs/tri/t27b/verilog_plan.t27 @@ -41,6 +41,17 @@ ; so a divided clock is checked at the rate it runs at and no constraint file is written by hand. A ; declaration that names no instance output, or whose value is 0, is refused by name: a constraint that ; constrains nothing would leave its clock at the global target without a word. +; - A NAME THAT IS A T27 KEYWORD (#10061). The lexer reserves `in`, `for`, `test` and the rest of +; T27_KEYWORDS, so `const in : u8 = undefined;` does not parse, and a fixed interface that needs a port +; `in` (CVDP 8x3_priority_encoder) could not be declared. Such a name is spelled with one trailing `_`: +; `const in_ : u8 = undefined;` is the port `in`. The `_` is dropped wherever t27b writes the name +; (name_end, read by the writer's put_name), so the port, every read of it and the test bench's `dut.in` +; agree. Only a keyword loses its `_`: `data_` stays `data_`, `in__` stays `in__`, and a stem that is also a +; Verilog keyword is then escaped as any other (`for_` is `\for `). No two t27 names meet: a t27 name is +; never a keyword, so nothing else can be spelled `in`. Why a rename and not a raw identifier (`@"in"`): +; a raw form is new syntax, a change to the lexer and parser of t27c in Rust, and every backend would have +; to learn it; the rename is spelled in the syntax already parsed, is decided here, and touches only the +; names t27b writes into Verilog. The CPU path (`t27b test`) still sees `in_`. ; Ports. A module with `fn on_clock` has the inputs clk, rst_n and en (each unless a net of that name ; exists) and one input per parameter of on_clock, except a parameter named `_`, which is ; that instance's output (BOUND). The outputs are the `pub var`s (output reg) and the `pub const` nets @@ -705,7 +716,8 @@ pub fn trip_bound(op: []const u8, kok: bool, k: u64, sok: bool, step: u64) -> i6 } // ---------------------------------------------------------------------------------------------------------- -// Names. A t27 name that is a Verilog keyword is written as an escaped identifier (`\wait `). +// Names. A t27 name that is a Verilog keyword is written as an escaped identifier (`\wait `); a t27 keyword +// spelled with one trailing `_` is written as the keyword (`in_` is `in`, #10061). // ---------------------------------------------------------------------------------------------------------- pub const VERILOG_KEYWORDS : [103]str = ["always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1", "case", "casex", "casez", "cell", "cmos", "config", "deassign", "default", "defparam", "design", "disable", "edge", "else", "end", "endcase", "endconfig", "endfunction", "endgenerate", "endmodule", "endprimitive", "endspecify", "endtable", "endtask", "event", "for", "force", "forever", "fork", "function", "generate", "genvar", "highz0", "highz1", "if", "ifnone", "incdir", "include", "initial", "inout", "input", "instance", "integer", "join", "large", "liblist", "library", "localparam", "macromodule", "medium", "module", "nand", "negedge", "nmos", "nor", "noshowcancelled", "not", "notif0", "notif1", "or", "output", "parameter", "pmos", "posedge", "primitive", "pull0", "pull1", "pulldown", "pullup", "pulsestyle_onevent", "pulsestyle_ondetect", "rcmos", "real", "realtime", "reg", "release", "repeat", "rnmos", "rpmos", "rtran", "rtranif0", "rtranif1", "scalared", "showcancelled", "signed", "small", "specify", "specparam", "strong0", "strong1", "supply0", "supply1", "table", "task", "time", "tran"]; @@ -732,6 +744,28 @@ pub fn verilog_keyword(s: []const u8) -> bool { return false; } +/// The words the t27 lexer reserves: check_keyword in bootstrap/src/compiler.rs, one per Kw token kind, and +/// `let`, which it reads as `const`. A word missing here only keeps its `_` in Verilog; a word here that the +/// lexer does not reserve could meet a real name, so the list is that table and nothing more. +pub const T27_KEYWORDS : [29]str = ["and", "bench", "break", "const", "continue", "else", "enum", "false", "fn", "for", "if", "in", "invariant", "let", "module", "or", "pragma", "pub", "return", "struct", "switch", "test", "true", "try", "use", "using", "var", "void", "while"]; + +/// Is s a word the t27 lexer reserves? +pub fn t27_keyword(s: []const u8) -> bool { + var j : usize = 0; + while (j < 29) { + if (same(s, T27_KEYWORDS[j])) { return true; } + j = j + 1; + } + return false; +} + +/// Where the Verilog name of text[a .. b] ends: before its one trailing `_` when what comes before it is a t27 +/// keyword (`in_` is written `in`), otherwise at b (#10061). +pub fn name_end(text: []const u8, a: usize, b: usize) -> usize { + if (b > a + 1 and text[b - 1] == 95 and t27_keyword(text[a .. b - 1])) { return b - 1; } + return b; +} + // ---------------------------------------------------------------------------------------------------------- // Tests. Node buffers are written as ast_walk.t27 lays them out (a record of REC bytes per node in preorder); // the texts are slices of PT. @@ -962,6 +996,25 @@ test verilog_keywords_are_escaped_by_name { assert verilog_keyword("wait") and verilog_keyword("reg") and verilog_keyword("module"); assert verilog_keyword("while") and verilog_keyword("xor") and verilog_keyword("logic"); assert !verilog_keyword("seed") and !verilog_keyword("waits") and !verilog_keyword(""); +} + +test a_t27_keyword_with_one_trailing_underscore_names_the_keyword { + assert t27_keyword("in") and t27_keyword("for") and t27_keyword("let") and t27_keyword("continue"); + assert t27_keyword("and") and t27_keyword("while") and t27_keyword("pragma"); + assert !t27_keyword("input") and !t27_keyword("i") and !t27_keyword("") and !t27_keyword("in_"); + assert !t27_keyword("undefined") and !t27_keyword("out") and !t27_keyword("clk"); + assert name_end("in_", 0, 3) == 2 and name_end("for_", 0, 4) == 3 and name_end("test_", 0, 5) == 4; + // a slice of a longer text: `in_` at 1 .. 4 of `xin_y` + assert name_end("xin_y", 1, 4) == 3; +} + +test a_name_that_is_no_keyword_keeps_every_underscore { + // negative controls: an ordinary name, the keyword itself, a non-keyword stem, two underscores, `_` alone + assert name_end("out", 0, 3) == 3 and name_end("in", 0, 2) == 2; + assert name_end("data_", 0, 5) == 5 and name_end("inx_", 0, 4) == 4 and name_end("clk_", 0, 4) == 4; + assert name_end("in__", 0, 4) == 4 and name_end("_", 0, 1) == 1 and name_end("__", 0, 2) == 2; + // the stem is the whole name before the `_`, not a suffix of it: `xin_` keeps its `_` + assert name_end("xin_", 0, 4) == 4; assert same("ExprIndex", kind_label(K_EXPR_INDEX)) and same("StmtExpr", kind_label(K_STMT_EXPR)); assert same("node", kind_label(99)); }