Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .trinity/seals/loop_TriLoopLesson.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"built_by": "t27c-bootstrap@0.5.2+0fba674ef",
"built_by": "t27c-bootstrap@0.5.2+42a4860c4",
"closure": [
{
"spec": "specs/claw/text.t27",
Expand All @@ -11,23 +11,23 @@
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:b43f8e5932e50e39962700c70d21c804ecac1296da76238bc38a6d908d1442be",
"gen_hash_rust": "sha256:5b03509aa928f0f6ae57f16317efdbab2ce35120e04299cc7c68ab01c7628a95",
"gen_hash_verilog": "sha256:3d1754b8cb60953c8f360d30ccef8482f51efa3e495137efeab74f35945fd6c0",
"gen_hash_zig": "sha256:3351777f5fc3125f092cf3882414ee7a46582ca37ea287cecbe72c77b27f58e0",
"gen_hash_c": "sha256:d563caba34950b1a7eb7540c3db655b14387e7cb7c5044d91b2b7b5ed5dc44ac",
"gen_hash_rust": "sha256:2c51a212ceca517b28dc3af6c034acbbdafce4278c7076a7f0abe43b83cb4b26",
"gen_hash_verilog": "sha256:4b059e9731893ebc3e894960fa912994555d860e2fee9d2071f03fb864a44060",
"gen_hash_zig": "sha256:dda4a7b05cf7f6cdd974333cc253cf7fd40467fc883e7ba0f4e65d9d5d52925f",
"module": "TriLoopLesson",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-19T16:23:39Z",
"sealed_at": "2026-10-19T17:26:43Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:9964c79e37666e3673d2d32b4d98bb0a4b2d9639f5ff3a9d8372f4f4fa1cadcb",
"spec_hash": "sha256:2a7e5e4e999c5418374f2987c45e6bfbb7b542c05c4f24e0ad185cd65b1bcb53",
"spec_path": "specs/tri/loop/lesson.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 21,
"total": 21,
"passed": 22,
"total": 22,
"vacuous": 0
},
"toolchain": {
Expand Down
54 changes: 49 additions & 5 deletions gen/rust/tri/loop/lesson.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from .t27 spec
// DO NOT EDIT — generated by t27c
// NOT LOWERED BY THIS BACKEND: 21 test(s), 2 invariant(s), 0 function(s) with no body.
// NOT LOWERED BY THIS BACKEND: 22 test(s), 2 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.

Expand Down Expand Up @@ -396,11 +396,39 @@ pub fn fence_char(f: &'static str, at: usize, stop: usize) -> u8 {
return 0;
}

pub fn fence_after(fence: u8, fc: u8) -> u8 {
pub fn fence_len(f: &'static str, at: usize, stop: usize) -> usize {
let c: u8 = fence_char(f, at, stop);
if (c == 0) {
return 0;
}
let mut k: usize = at;
while (((k < stop) && (k < (at + 3))) && (f.as_bytes()[(k) as usize] == 32)) {
k = (k + 1);
}
let mut n: usize = 0;
while (((k + n) < stop) && (f.as_bytes()[((k + n)) as usize] == c)) {
n = (n + 1);
}
return n;
}

pub fn fence_bare(f: &'static str, at: usize, stop: usize) -> bool {
let n: usize = fence_len(f, at, stop);
if (n == 0) {
return false;
}
let mut k: usize = at;
while (((k < stop) && (k < (at + 3))) && (f.as_bytes()[(k) as usize] == 32)) {
k = (k + 1);
}
return blank_line(f, (k + n), stop);
}

pub fn fence_after(fence: u8, fc: u8, closes: bool) -> u8 {
if (fence == 0) {
return fc;
}
if (fc == fence) {
if ((fc == fence) && closes) {
return 0;
}
return fence;
Expand Down Expand Up @@ -433,13 +461,17 @@ pub fn is_traps(f: &'static str, at: usize, stop: usize) -> bool {
pub fn trap_line(f: &'static str) -> usize {
let mut at: usize = 0;
let mut fence: u8 = 0;
let mut flen: usize = 0;
while (at < f.len()) {
let stop: usize = line_end(f, at);
let fc: u8 = fence_char(f, at, stop);
if ((((fence == 0) && (fc == 0)) && (heading_level(f, at, stop) > 0)) && is_traps(f, at, stop)) {
return at;
}
fence = fence_after(fence, fc);
if (fence == 0) {
flen = fence_len(f, at, stop);
}
fence = fence_after(fence, fc, (fence_bare(f, at, stop) && (fence_len(f, at, stop) >= flen)));
at = next_line(f, at);
}
return f.len();
Expand All @@ -449,6 +481,7 @@ pub fn section_end(f: &'static str, h: usize) -> usize {
let level: usize = heading_level(f, h, line_end(f, h));
let mut at: usize = next_line(f, h);
let mut fence: u8 = 0;
let mut flen: usize = 0;
while (at < f.len()) {
let stop: usize = line_end(f, at);
let fc: u8 = fence_char(f, at, stop);
Expand All @@ -458,7 +491,10 @@ pub fn section_end(f: &'static str, h: usize) -> usize {
return at;
}
}
fence = fence_after(fence, fc);
if (fence == 0) {
flen = fence_len(f, at, stop);
}
fence = fence_after(fence, fc, (fence_bare(f, at, stop) && (fence_len(f, at, stop) >= flen)));
at = next_line(f, at);
}
return f.len();
Expand Down Expand Up @@ -790,6 +826,14 @@ pub const G: &'static str = "## Traps\n\n- 2026-10-19 use curl (#5)\n- tls\n- pl

pub const BL: &'static str = "- 2026-10-19 use curl (#5)";

pub const FX1: &'static str = "intro\n```\ncode\n``` numbers\nmore code\n```\n## Traps\n\n- 2026-10-19 one lesson that is long enough\n";

pub const FX2: &'static str = "````\n```\n## Traps\n````\n## Traps\n\n- x lesson that is long enough\n";

pub const FX3: &'static str = "```\n``` numbers\ntext\n```\n";

pub const FX4: &'static str = "```\n``` numbers\ntext\n```\n\n## Traps\n\n- 2026-10-19 a new lesson that is long enough to keep\n";

pub fn put_str(dst: &mut [u8], cap: usize, w: usize, s: &'static str, s_len: usize) -> usize {
let mut i: usize = 0;
while (i < s_len) {
Expand Down
68 changes: 62 additions & 6 deletions specs/tri/loop/lesson.t27
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
; - otherwise ADD, or ADD_SECTION when the file has no Traps section and one is created at the end.
; --
; The Traps section is the first heading, at any level, whose title opens with `Traps` or `Trap` (so
; `## Traps`, `## Traps that cost hours` and `## Traps met on this Mac`), outside a code fence. It runs to the
; `## Traps`, `## Traps that cost hours` and `## Traps met on this Mac`), outside a code fence (a fence closes
; on a bare line of the same character at least as long as its opener, as on GitHub, so "``` numbers" inside a
; block is text and one odd line cannot hide the section, #11110). It runs to the
; next heading of the same or a higher level, or to the end of the file. A row goes after the last line of
; the section that is not blank; a section with nothing in it gets a blank line first.
; The row is `- <date> <lesson> (<ref>)`, with no ` (<ref>)` when the ref is empty, then a newline.
Expand Down Expand Up @@ -384,10 +386,38 @@ pub fn fence_char(f: string, at: usize, stop: usize) -> u8 {
return 0;
}

/// The fence state after a line: outside (0) a line that opens a fence enters it, inside a line with the same fence leaves it.
pub fn fence_after(fence: u8, fc: u8) -> u8 {
/// The length of the run of fence characters that opens the line (up to three spaces before it); 0 when the line is no fence line.
pub fn fence_len(f: string, at: usize, stop: usize) -> usize {
const c : u8 = fence_char(f, at, stop);
if (c == 0) { return 0; }
var k : usize = at;
while (k < stop and k < at + 3 and f[k] == 32) {
k = k + 1;
}
var n : usize = 0;
while (k + n < stop and f[k + n] == c) {
n = n + 1;
}
return n;
}

/// A fence line with nothing after its run but spaces, tabs and carriage returns. A fence line with an info string opens a block and
/// can never close one, so "``` numbers" inside a block is text.
pub fn fence_bare(f: string, at: usize, stop: usize) -> bool {
const n : usize = fence_len(f, at, stop);
if (n == 0) { return false; }
var k : usize = at;
while (k < stop and k < at + 3 and f[k] == 32) {
k = k + 1;
}
return blank_line(f, k + n, stop);
}

/// The fence state after a line: outside (0) a line that opens a fence enters it; inside, a line leaves it only when it is the same
/// fence (`closes`: bare and at least as long as the opener, as on GitHub).
pub fn fence_after(fence: u8, fc: u8, closes: bool) -> u8 {
if (fence == 0) { return fc; }
if (fc == fence) { return 0; }
if (fc == fence and closes) { return 0; }
return fence;
}

Expand All @@ -412,11 +442,13 @@ pub fn is_traps(f: string, at: usize, stop: usize) -> bool {
pub fn trap_line(f: string) -> usize {
var at : usize = 0;
var fence : u8 = 0;
var flen : usize = 0;
while (at < f.len) {
const stop : usize = line_end(f, at);
const fc : u8 = fence_char(f, at, stop);
if (fence == 0 and fc == 0 and heading_level(f, at, stop) > 0 and is_traps(f, at, stop)) { return at; }
fence = fence_after(fence, fc);
if (fence == 0) { flen = fence_len(f, at, stop); }
fence = fence_after(fence, fc, fence_bare(f, at, stop) and fence_len(f, at, stop) >= flen);
at = next_line(f, at);
}
return f.len;
Expand All @@ -427,14 +459,16 @@ pub fn section_end(f: string, h: usize) -> usize {
const level : usize = heading_level(f, h, line_end(f, h));
var at : usize = next_line(f, h);
var fence : u8 = 0;
var flen : usize = 0;
while (at < f.len) {
const stop : usize = line_end(f, at);
const fc : u8 = fence_char(f, at, stop);
if (fence == 0 and fc == 0) {
const l : usize = heading_level(f, at, stop);
if (l > 0 and l <= level) { return at; }
}
fence = fence_after(fence, fc);
if (fence == 0) { flen = fence_len(f, at, stop); }
fence = fence_after(fence, fc, fence_bare(f, at, stop) and fence_len(f, at, stop) >= flen);
at = next_line(f, at);
}
return f.len;
Expand Down Expand Up @@ -897,6 +931,28 @@ test the_section_is_found_and_bounded {
assert add(F, "second lesson that is long enough", "") == O_DUPLICATE;
}

const FX1 : string = "intro\n```\ncode\n``` numbers\nmore code\n```\n## Traps\n\n- 2026-10-19 one lesson that is long enough\n";
const FX2 : string = "````\n```\n## Traps\n````\n## Traps\n\n- x lesson that is long enough\n";
const FX3 : string = "```\n``` numbers\ntext\n```\n";
const FX4 : string = "```\n``` numbers\ntext\n```\n\n## Traps\n\n- 2026-10-19 a new lesson that is long enough to keep\n";

// Scenario (#11110): a fence line with an info string does not close a fence, a shorter fence line does not close a longer one, so a
// file with one such line keeps its Traps section findable and a second lesson is a plain ADD, not a second heading.
test an_info_string_fence_line_does_not_close_a_fence {
assert fence_len("```rust", 0, 7) == 3 and fence_len(" ~~~~", 0, 6) == 4 and fence_len("text", 0, 4) == 0 and fence_len(" ```", 0, 7) == 0;
assert fence_bare("```", 0, 3) and fence_bare("``` ", 0, 5) and fence_bare("~~~~", 0, 4);
assert !fence_bare("``` numbers", 0, 11) and !fence_bare("```rust", 0, 7) and !fence_bare("text", 0, 4);
assert fence_after(96, 96, false) == 96 and fence_after(96, 96, true) == 0 and fence_after(96, 126, true) == 96 and fence_after(0, 96, false) == 96;
assert trap_line(FX1) == 41 and section_end(FX1, 41) == FX1.len;
assert add(FX1, "another lesson that is different enough", "") == O_ADD;
assert trap_line(FX2) == 23;
assert add(FX2, "another lesson that is different enough", "") == O_ADD;
assert add(FX3, NEW, "") == O_ADD_SECTION;
assert trap_line(FX4) < FX4.len;
assert add(FX4, "another lesson that is different enough", "") == O_ADD;
assert add(FX4, NEW, "") == O_DUPLICATE;
}

// Scenario: the row goes after the last line of the section that is not blank, before its trailing blank line.
test the_row_goes_after_the_last_line_of_the_section {
assert patch_at(F) == 145;
Expand Down
Loading