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
7 changes: 4 additions & 3 deletions src/runner/checks/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())))
Expand Down Expand Up @@ -2157,7 +2158,7 @@ fn deferred_invoke_is_prompted_and_recorded() {
.filter(|record| record.path == "/<https://example.com/probe>")
.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.
Expand All @@ -2180,5 +2181,5 @@ fn deferred_invoke_is_prompted_and_recorded() {
.filter(|record| record.path == "/<https://example.com/probe>")
.map(|record| record.state)
.collect();
assert_eq!(settled, vec![State::Skip]);
assert_eq!(settled, vec![State::Begin, State::Skip]);
}
136 changes: 69 additions & 67 deletions src/runner/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -176,38 +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
Expand Down Expand Up @@ -502,43 +473,12 @@ 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);
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
Expand Down Expand Up @@ -600,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
Expand Down Expand Up @@ -745,6 +686,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();
Expand Down Expand Up @@ -909,6 +851,66 @@ 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.
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<Outcome, RunnerError> {
Expand Down
4 changes: 2 additions & 2 deletions src/runner/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/translation/checks/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1871,9 +1871,14 @@ init : () -> ()
let Operation::Sequence(section_body) = body.as_ref() else {
panic!("expected Section body Sequence");
};
// First the title's hoisted `<init>` 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 `<init>` 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) = &section_body[0] else {
panic!("expected Invoke, got {:?}", section_body[0]);
};
Expand Down
29 changes: 26 additions & 3 deletions src/translation/translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ pub fn translate<'i>(document: &'i Document<'i>) -> Result<Program<'i>, Vec<Tran
}
}

// Whether a section body already holds a procedure descent: an Invoke
// hoisted from an explicit `<name>` 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>),
Expand Down Expand Up @@ -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 <thing>
//
// 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(),
Expand Down
39 changes: 20 additions & 19 deletions tests/samples/runner/InspectHatches.pfftt
Original file line number Diff line number Diff line change
@@ -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 ()
57 changes: 32 additions & 25 deletions tests/samples/runner/PatioCleaning.pfftt
Original file line number Diff line number Diff line change
@@ -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 ()
Expand Down Expand Up @@ -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
Expand All @@ -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 ()