From 1b6e7bbd7f4ed1cc3c1f34f563e17d6f2f60c8a1 Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Tue, 16 Jun 2026 15:24:17 +1000 Subject: [PATCH 1/3] Fix double translation of procedure in section title --- src/translation/checks/translate.rs | 11 ++++++++--- src/translation/translator.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/translation/checks/translate.rs b/src/translation/checks/translate.rs index 363bbcef..ed30e36d 100644 --- a/src/translation/checks/translate.rs +++ b/src/translation/checks/translate.rs @@ -1871,9 +1871,14 @@ init : () -> () let Operation::Sequence(section_body) = body.as_ref() else { panic!("expected Section body Sequence"); }; - // First the title's hoisted `` Application, then the descent into - // the section's first (and only) declared procedure, also `init`. - assert_eq!(section_body.len(), 2, "title's Application is hoisted"); + // The title's hoisted `` Application is the section's single entry: + // an explicit invocation in the heading suppresses the synthetic descent + // into the first declared procedure, so the body holds one Invoke, not two. + assert_eq!( + section_body.len(), + 1, + "title's Application is the sole entry" + ); let Operation::Invoke(invocable) = §ion_body[0] else { panic!("expected Invoke, got {:?}", section_body[0]); }; diff --git a/src/translation/translator.rs b/src/translation/translator.rs index d92bfdaa..3700dbd2 100644 --- a/src/translation/translator.rs +++ b/src/translation/translator.rs @@ -50,6 +50,19 @@ pub fn translate<'i>(document: &'i Document<'i>) -> Result, Vec` in the section heading. +fn descends(ops: &[Operation<'_>]) -> bool { + ops.iter() + .any(|op| { + if let Operation::Invoke(_) = op { + true + } else { + false + } + }) +} + #[derive(Debug, Eq, PartialEq)] pub enum TranslationError<'i> { DuplicateProcedure(language::Identifier<'i>), @@ -299,9 +312,19 @@ impl<'i> Translator<'i> { } } language::Technique::Procedures(procedures) => { - // A section whose body declares procedures descends - // into the first, its entry point. - if let Some(procedure) = procedures.first() { + // A section descends into the first procedure its body + // declares, its entry point. A heading that already + // invokes one explicitly (a + // + // II. Do it now + // + // in the title) has hoisted that invoke above and is + // the descent already, pre-empting this one so the + // procedure isn't run twice. + if let Some(procedure) = procedures + .first() + .filter(|_| !descends(&body_ops)) + { body_ops.push(Operation::Invoke(Invocable { target: SubroutineRef::Unresolved(procedure.name), arguments: Vec::new(), From f4fdd4f9bff16f4e5acb8f47f9a98dcbcfa25577 Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Tue, 16 Jun 2026 16:25:42 +1000 Subject: [PATCH 2/3] Record a Begin in PFFTT for all steps and scopes --- src/runner/checks/runner.rs | 3 +- src/runner/runner.rs | 21 +++++++++ src/runner/state.rs | 4 +- src/translation/translator.rs | 6 +-- tests/samples/runner/InspectHatches.pfftt | 39 ++++++++-------- tests/samples/runner/PatioCleaning.pfftt | 57 +++++++++++++---------- 6 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/runner/checks/runner.rs b/src/runner/checks/runner.rs index c5a41bc8..bfbbcfb9 100644 --- a/src/runner/checks/runner.rs +++ b/src/runner/checks/runner.rs @@ -1908,7 +1908,8 @@ test : .is_empty() }) .collect(); - let record = parse_record(lines[2]).expect("parse record"); + // Start, the entry `Begin`, the step `Begin`, then the step's outcome. + let record = parse_record(lines[3]).expect("parse record"); assert_eq!( record.state, State::Done(Some(RecordValue::Literal("Yes".to_string()))) diff --git a/src/runner/runner.rs b/src/runner/runner.rs index 912b381b..b7bba9ab 100644 --- a/src/runner/runner.rs +++ b/src/runner/runner.rs @@ -208,6 +208,7 @@ impl<'i, D: Driver> Runner<'i, D> { self.driver .display(&description); } + self.begin_scope(&qualified)?; } let result = self.walk(&mut env, &entry.body); // A named entry procedure is a structural scope: a completed run closes @@ -502,6 +503,8 @@ impl<'i, D: Driver> Runner<'i, D> { state: State::Invoke(InvokeTarget::Procedure(name.to_string())), })?; + self.begin_scope(&lexical)?; + let saved = self .path .replace(lexical_segments); @@ -745,6 +748,7 @@ impl<'i, D: Driver> Runner<'i, D> { .pop(); return Ok(Outcome::Done(Value::Unitus)); } + self.begin_scope(&qualified)?; let result = self.perform_section(env, numeral, title, body); self.path .pop(); @@ -909,6 +913,23 @@ impl<'i, D: Driver> Runner<'i, D> { Ok(outcome) } + /// Open a structural scope — the entry procedure, a Section, or an invoked + /// procedure — pairing with the `Done` its `seal_scope` records on close, so + /// every scope's address is bracketed `Begin`…`Done` just as a step's is. + fn begin_scope(&mut self, qualified: &str) -> Result<(), RunnerError> { + let run_id = self + .appender + .run_id(); + self.appender + .append(&Record { + recorded: now_iso8601(), + run_id, + path: qualified.to_string(), + state: State::Begin, + })?; + Ok(()) + } + /// Sign off a completed structural scope — a Section at its close, or the /// whole run at the entry procedure. fn seal_scope(&mut self, qualified: &str, outcome: Outcome) -> Result { diff --git a/src/runner/state.rs b/src/runner/state.rs index 74a04694..c08425d9 100644 --- a/src/runner/state.rs +++ b/src/runner/state.rs @@ -46,8 +46,8 @@ pub struct Record { /// A lifecycle or step-outcome event; the keyword written into each PFFTT /// record line. `Start`, `Stop`, and `Resume` are run-lifecycle events emitted -/// at the root path `/`; `Begin` marks the moment work starts on a step (paired -/// with the eventual `Done`, `Skip`, or `Fail`). `Stop` records a deliberate +/// at the root path `/`; `Begin` marks entry into a step or scope (paired with the +/// eventual `Done`, `Skip`, or `Fail` at the same path). `Stop` records a deliberate /// quit — the run stays resumable, and the record distinguishes the quit from a /// crash (which records nothing). /// `Invoke` records dispatch into another procedure (the return is diff --git a/src/translation/translator.rs b/src/translation/translator.rs index 3700dbd2..ecd2bf1d 100644 --- a/src/translation/translator.rs +++ b/src/translation/translator.rs @@ -314,10 +314,10 @@ impl<'i> Translator<'i> { language::Technique::Procedures(procedures) => { // A section descends into the first procedure its body // declares, its entry point. A heading that already - // invokes one explicitly (a - // + // invokes one explicitly (a + // // II. Do it now - // + // // in the title) has hoisted that invoke above and is // the descent already, pre-empting this one so the // procedure isn't run twice. diff --git a/tests/samples/runner/InspectHatches.pfftt b/tests/samples/runner/InspectHatches.pfftt index 41144ba4..5c8b7064 100644 --- a/tests/samples/runner/InspectHatches.pfftt +++ b/tests/samples/runner/InspectHatches.pfftt @@ -1,20 +1,21 @@ 2026-06-14T05:58:08.700Z 000001 / Start file://tests/samples/runner/InspectHatches.tq -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[1]/1 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[1]/1 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[1]/2 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[1]/2 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[1]/3 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[1]/3 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[2]/1 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[2]/1 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[2]/2 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[2]/2 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[2]/3 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[2]/3 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[3]/1 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[3]/1 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[3]/2 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[3]/2 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[3]/3 Begin -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches:/[3]/3 Done () -2026-06-14T05:58:08.701Z 000001 /inspect_access_hatches: Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches: Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[1]/1 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[1]/1 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[1]/2 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[1]/2 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[1]/3 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[1]/3 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[2]/1 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[2]/1 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[2]/2 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[2]/2 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[2]/3 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[2]/3 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[3]/1 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[3]/1 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[3]/2 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[3]/2 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[3]/3 Begin +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches:/[3]/3 Done () +2026-06-14T05:58:08.700Z 000001 /inspect_access_hatches: Done () diff --git a/tests/samples/runner/PatioCleaning.pfftt b/tests/samples/runner/PatioCleaning.pfftt index 00dc55cd..54735e49 100644 --- a/tests/samples/runner/PatioCleaning.pfftt +++ b/tests/samples/runner/PatioCleaning.pfftt @@ -1,5 +1,8 @@ 2026-06-14T05:58:08.773Z 000002 / Start file://tests/samples/runner/PatioCleaning.tq +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning: Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I Invoke setup_machine: +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I/setup_machine: Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I/setup_machine:/1 Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I/setup_machine:/1/a Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I/setup_machine:/1/a Done () @@ -28,10 +31,14 @@ 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I/setup_machine:/5 Done () 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I/setup_machine: Done () 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/I Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/II Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/II Invoke clean_patio: +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/II/clean_patio: Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/II/clean_patio: Done () 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/II Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III Invoke tidy_up: +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up: Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/1 Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/1 Done () 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/2 Begin @@ -40,28 +47,28 @@ 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/a Begin 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/a Done () 2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/b Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/b Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/c Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/c Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/3 Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/4 Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/d Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/d Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/e Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/e Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/4 Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/5 Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/f Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/f Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/g Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/g Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/5 Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/6 Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/h Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/h Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/j Begin -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/j Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up:/6 Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III/tidy_up: Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning:/III Done () -2026-06-14T05:58:08.774Z 000002 /high_pressure_cleaning: Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/b Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/c Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3/c Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/3 Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/4 Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/d Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/d Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/e Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/4/e Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/4 Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/5 Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/f Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/f Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/g Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/5/g Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/5 Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/6 Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/h Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/h Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/j Begin +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/6/j Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up:/6 Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III/tidy_up: Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning:/III Done () +2026-06-14T05:58:08.773Z 000002 /high_pressure_cleaning: Done () From 1cfc6a99fbabeb91be30fe39007eb917d4f90e55 Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Tue, 16 Jun 2026 16:51:57 +1000 Subject: [PATCH 3/3] Unify procedure announcement logic --- src/runner/checks/runner.rs | 4 +- src/runner/runner.rs | 115 +++++++++++++++--------------------- 2 files changed, 50 insertions(+), 69 deletions(-) diff --git a/src/runner/checks/runner.rs b/src/runner/checks/runner.rs index bfbbcfb9..0e8f520d 100644 --- a/src/runner/checks/runner.rs +++ b/src/runner/checks/runner.rs @@ -2158,7 +2158,7 @@ fn deferred_invoke_is_prompted_and_recorded() { .filter(|record| record.path == "/") .map(|record| record.state) .collect(); - assert_eq!(settled, vec![State::Skip]); + assert_eq!(settled, vec![State::Begin, State::Skip]); // Under an automatic run there is no operator to attest the external work // and nothing executed it, so it records Skip rather than a fabricated Done. @@ -2181,5 +2181,5 @@ fn deferred_invoke_is_prompted_and_recorded() { .filter(|record| record.path == "/") .map(|record| record.state) .collect(); - assert_eq!(settled, vec![State::Skip]); + assert_eq!(settled, vec![State::Begin, State::Skip]); } diff --git a/src/runner/runner.rs b/src/runner/runner.rs index b7bba9ab..ff4c6500 100644 --- a/src/runner/runner.rs +++ b/src/runner/runner.rs @@ -14,7 +14,8 @@ use super::state::{ }; use crate::language; use crate::program::{ - Executable, ExecutableRef, Invocable, Locale, Operation, Ordinal, Program, SubroutineRef, + Executable, ExecutableRef, Invocable, Locale, Operation, Ordinal, Program, Subroutine, + SubroutineRef, }; use crate::value::Value; @@ -176,39 +177,8 @@ impl<'i, D: Driver> Runner<'i, D> { let qualified = self .path .render(); - self.driver - .enter(&qualified); - let declaration = crate::formatting::formatter::render_declaration( - name, - entry.parameters, - entry.signature, - self.driver - .renderer(), - ); - self.driver - .display(&declaration); - if let Some(t) = entry.title { - let title_text = crate::formatting::formatter::render_title( - t, - self.driver - .renderer(), - ); - self.driver - .display(&title_text); - } - if !entry - .description - .is_empty() - { - let description = crate::formatting::formatter::render_description( - entry.description, - self.driver - .renderer(), - ); - self.driver - .display(&description); - } self.begin_scope(&qualified)?; + self.announce_procedure(entry, name, &qualified); } let result = self.walk(&mut env, &entry.body); // A named entry procedure is a structural scope: a completed run closes @@ -508,40 +478,7 @@ impl<'i, D: Driver> Runner<'i, D> { let saved = self .path .replace(lexical_segments); - self.driver - .enter(&lexical); - - let declaration = crate::formatting::formatter::render_declaration( - name, - subroutine.parameters, - subroutine.signature, - self.driver - .renderer(), - ); - self.driver - .display(&declaration); - - if let Some(t) = subroutine.title { - let title_text = crate::formatting::formatter::render_title( - t, - self.driver - .renderer(), - ); - self.driver - .display(&title_text); - } - if !subroutine - .description - .is_empty() - { - let description = crate::formatting::formatter::render_description( - subroutine.description, - self.driver - .renderer(), - ); - self.driver - .display(&description); - } + self.announce_procedure(subroutine, name, &lexical); // Walk the callee's body in its own `local` environment, // then sign off its scope; a Quit or error skips the @@ -603,6 +540,7 @@ impl<'i, D: Driver> Runner<'i, D> { return Ok(Outcome::Done(Value::Unitus)); } + self.begin_scope(&qualified)?; self.driver .announce(&format!("<{}>", ext.value)); let input = self @@ -913,6 +851,49 @@ impl<'i, D: Driver> Runner<'i, D> { Ok(outcome) } + /// Show a named procedure's heading on descent: the driver's `↘` enter line + /// followed by the procedure's declaration, title, and description. Shared by + /// the entry procedure and every invoked one. + fn announce_procedure( + &mut self, + subroutine: &'i Subroutine<'i>, + name: &'i str, + qualified: &str, + ) { + self.driver + .enter(qualified); + let declaration = crate::formatting::formatter::render_declaration( + name, + subroutine.parameters, + subroutine.signature, + self.driver + .renderer(), + ); + self.driver + .display(&declaration); + if let Some(t) = subroutine.title { + let title_text = crate::formatting::formatter::render_title( + t, + self.driver + .renderer(), + ); + self.driver + .display(&title_text); + } + if !subroutine + .description + .is_empty() + { + let description = crate::formatting::formatter::render_description( + subroutine.description, + self.driver + .renderer(), + ); + self.driver + .display(&description); + } + } + /// Open a structural scope — the entry procedure, a Section, or an invoked /// procedure — pairing with the `Done` its `seal_scope` records on close, so /// every scope's address is bracketed `Begin`…`Done` just as a step's is.