Skip to content

Commit 23721f3

Browse files
authored
Correct results of steps with mixed prose and function calls (#123)
The output of exec() calls was already recorded in a `Return` line, but that value was being duplicated as the enclosing step's `Result`. That led to clarifying the place of code inlines in descriptive paragraphs, and realizing we could translate the prose as a no-op `Operation::Prose` variant so that if trailing prose is present then the result of the step is then correctly the `Unitus` value. This extended into the anonymous "Step 0" created when consequential operations are present in a procedure description. This has been re-translated as `Operation::Prelude` and then if function calls are present then they are properly entered and recorded. Cleanup noise from test output.
2 parents 014d99e + a057434 commit 23721f3

16 files changed

Lines changed: 288 additions & 64 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "technique"
3-
version = "0.6.2"
3+
version = "0.6.3"
44
edition = "2021"
55
description = "A domain specific language for procedures."
66
authors = [ "Andrew Cowie" ]

src/formatting/formatter.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,7 @@ pub fn render_description<'i>(paragraphs: &'i [Paragraph<'i>], renderer: &dyn Re
256256
}
257257

258258
/// Render step's without descending into nested subscopes.
259-
pub fn render_step<'i>(
260-
scope: &'i Scope,
261-
subs: &'i Substitutions,
262-
renderer: &dyn Render,
263-
) -> String {
259+
pub fn render_step<'i>(scope: &'i Scope, subs: &'i Substitutions, renderer: &dyn Render) -> String {
264260
let mut sub = Formatter::new(78);
265261
sub.substitutions = Some(subs);
266262
match scope {

src/linking/linker.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fn link_operation<'i>(
8080
link_operation(arg, library, problems);
8181
}
8282
}
83-
Operation::Sequence(ops) => {
83+
Operation::Sequence(ops) | Operation::Prologue(ops) => {
8484
for op in ops {
8585
link_operation(op, library, problems);
8686
}
@@ -114,6 +114,7 @@ fn link_operation<'i>(
114114
Operation::Variable(_)
115115
| Operation::Number(_)
116116
| Operation::Multiline(_, _)
117+
| Operation::Prose(_)
117118
| Operation::Hole => {}
118119
}
119120
}

src/program/types.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,13 @@ pub enum Operation<'i> {
128128
Invoke(Invocable<'i>),
129129
Execute(Executable<'i>),
130130
Hole,
131+
Prose(&'i str),
131132
Sequence(Vec<Operation<'i>>),
133+
/// The executable work hoisted from a procedure's description is an
134+
/// anonymous "step 0". Present only when the description carries
135+
/// instructions, not for prose alone. Its outcome folds into the
136+
/// procedure's own sign-off rather than prompting itself.
137+
Prologue(Vec<Operation<'i>>),
132138
Section {
133139
numeral: &'i str,
134140
title: Option<Box<Operation<'i>>>,

src/resolution/resolver.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ fn resolve_operation<'i>(
105105
resolve_operation(arg, known, arities, problems);
106106
}
107107
}
108-
Operation::Sequence(ops) | Operation::List(ops) => {
108+
Operation::Sequence(ops) | Operation::List(ops) | Operation::Prologue(ops) => {
109109
for op in ops {
110110
resolve_operation(op, known, arities, problems);
111111
}
@@ -144,6 +144,7 @@ fn resolve_operation<'i>(
144144
Operation::Variable(_)
145145
| Operation::Number(_)
146146
| Operation::Multiline(_, _)
147+
| Operation::Prose(_)
147148
| Operation::Hole => {}
148149
}
149150
}
@@ -172,7 +173,7 @@ fn gather_iterated<'i>(op: &Operation<'i>, iterated: &mut HashSet<&'i str>) {
172173
gather_iterated(body, iterated);
173174
}
174175
Operation::Bind { value, .. } => gather_iterated(value, iterated),
175-
Operation::Sequence(ops) | Operation::List(ops) => {
176+
Operation::Sequence(ops) | Operation::List(ops) | Operation::Prologue(ops) => {
176177
for op in ops {
177178
gather_iterated(op, iterated);
178179
}
@@ -209,6 +210,7 @@ fn gather_iterated<'i>(op: &Operation<'i>, iterated: &mut HashSet<&'i str>) {
209210
Operation::Variable(_)
210211
| Operation::Number(_)
211212
| Operation::Multiline(_, _)
213+
| Operation::Prose(_)
212214
| Operation::Hole => {}
213215
}
214216
}
@@ -231,7 +233,7 @@ fn mark_iterated<'i>(op: &mut Operation<'i>, iterated: &HashSet<&str>) {
231233
}
232234
mark_iterated(body, iterated);
233235
}
234-
Operation::Sequence(ops) | Operation::List(ops) => {
236+
Operation::Sequence(ops) | Operation::List(ops) | Operation::Prologue(ops) => {
235237
for op in ops {
236238
mark_iterated(op, iterated);
237239
}
@@ -268,6 +270,7 @@ fn mark_iterated<'i>(op: &mut Operation<'i>, iterated: &HashSet<&str>) {
268270
Operation::Variable(_)
269271
| Operation::Number(_)
270272
| Operation::Multiline(_, _)
273+
| Operation::Prose(_)
271274
| Operation::Hole => {}
272275
}
273276
}
@@ -322,7 +325,7 @@ fn check_scope<'i>(
322325
}
323326
check_scope(body, scope, problems);
324327
}
325-
Operation::Sequence(ops) | Operation::List(ops) => {
328+
Operation::Sequence(ops) | Operation::List(ops) | Operation::Prologue(ops) => {
326329
for op in ops {
327330
check_scope(op, scope, problems);
328331
}
@@ -356,7 +359,10 @@ fn check_scope<'i>(
356359
check_scope(&entry.value, scope, problems);
357360
}
358361
}
359-
Operation::Number(_) | Operation::Multiline(_, _) | Operation::Hole => {}
362+
Operation::Number(_)
363+
| Operation::Multiline(_, _)
364+
| Operation::Prose(_)
365+
| Operation::Hole => {}
360366
}
361367
}
362368

src/runner/checks/library.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn text(s: &str) -> Value {
1515
// evaluator takes once a call is resolved.
1616
fn call(name: &str, args: &[Value]) -> Result<Value, RunnerError> {
1717
let library = Library::core();
18-
let context = Context::native();
18+
let context = Context::capture();
1919
let id = library
2020
.resolve(name)
2121
.expect("builtin registered");
@@ -27,7 +27,7 @@ fn call(name: &str, args: &[Value]) -> Result<Value, RunnerError> {
2727
fn call_system(name: &str, args: &[Value]) -> Result<Value, RunnerError> {
2828
let mut library = Library::core();
2929
library.extend(Library::system());
30-
let context = Context::native();
30+
let context = Context::capture();
3131
let id = library
3232
.resolve(name)
3333
.expect("builtin registered");

src/runner/checks/runner.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,70 @@ check :
13101310
.any(|r| r.path == "/check:/2"));
13111311
}
13121312

1313+
#[test]
1314+
fn description_instruction_records_under_step_zero() {
1315+
// An exec in the procedure description runs in the anonymous step-0
1316+
// Prologue scope, recording under the `/0` path ahead of the steps.
1317+
let source = r#"
1318+
% technique v1
1319+
1320+
check :
1321+
1322+
Prepare the ground { exec("true") } before the steps.
1323+
1324+
1. Do the work.
1325+
"#
1326+
.trim_ascii();
1327+
let document = parsing::parse(Path::new("Test.tq"), source).expect("parsed");
1328+
let mut program = translate(&document).expect("translated");
1329+
resolve(&mut program).expect("resolve");
1330+
let mut library = Library::core();
1331+
library.extend(Library::system());
1332+
crate::linking::link(&mut program, &library).expect("linked");
1333+
1334+
let mut fixture = StoreFixture::new("description-step-zero");
1335+
let mut runner = Runner::new(
1336+
&program,
1337+
fixture.take_appender(),
1338+
HashMap::new(),
1339+
Automatic::with_handle(Vec::new()),
1340+
library,
1341+
);
1342+
runner
1343+
.run(Environment::new())
1344+
.expect("run");
1345+
1346+
let pfftt = fixture.pfftt_contents();
1347+
let records: Vec<_> = pfftt
1348+
.lines()
1349+
.filter_map(|line| parse_record(line).ok())
1350+
.collect();
1351+
let zero: Vec<_> = records
1352+
.iter()
1353+
.filter(|r| r.path == "/check:/0")
1354+
.map(|r| &r.state)
1355+
.collect();
1356+
// The /0 scope is bracketed Begin…Done with the exec's trace between.
1357+
let State::Begin = zero[0] else {
1358+
panic!("expected Begin first at /check:/0, got {:?}", zero[0]);
1359+
};
1360+
let State::Done(_) = zero[zero.len() - 1] else {
1361+
panic!(
1362+
"expected Done last at /check:/0, got {:?}",
1363+
zero[zero.len() - 1]
1364+
);
1365+
};
1366+
assert!(zero
1367+
.iter()
1368+
.any(|state| {
1369+
if let State::Execute { .. } = state {
1370+
true
1371+
} else {
1372+
false
1373+
}
1374+
}));
1375+
}
1376+
13131377
#[test]
13141378
fn loop_inside_step_produces_one_result() {
13151379
let mut fixture = StoreFixture::new("loop-in-step");
@@ -2615,7 +2679,7 @@ cleanup :
26152679
&program,
26162680
Appender::memory(),
26172681
completed,
2618-
Automatic::new(false),
2682+
Automatic::with_handle(Vec::new()),
26192683
Library::stub(),
26202684
);
26212685
let outcome = runner

src/runner/evaluator.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ pub fn evaluate<'i>(
199199
// A `?` reached outside a procedure invocation has no parameter name
200200
// to defer against; it stands for an as-yet-unsupplied value.
201201
Operation::Hole => Ok(Value::Futurae(String::new())),
202-
Operation::Section { .. }
202+
Operation::Prose(_)
203+
| Operation::Prologue(_)
204+
| Operation::Section { .. }
203205
| Operation::Step { .. }
204206
| Operation::Loop { .. }
205207
| Operation::Invoke(_) => Ok(Value::Unitus),

src/runner/path.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::language;
1010
#[derive(Debug, Clone, Eq, PartialEq)]
1111
pub enum PathSegment<'i> {
1212
Section(&'i str),
13+
Prologue,
1314
DependentStep(&'i str),
1415
ParallelStep(usize),
1516
Iteration(usize),
@@ -125,6 +126,7 @@ pub fn render_path(segments: &[PathSegment]) -> String {
125126
fn render_segment(segment: &PathSegment) -> Option<String> {
126127
match segment {
127128
PathSegment::Section(numeral) => Some(numeral.to_string()),
129+
PathSegment::Prologue => Some("0".to_string()),
128130
PathSegment::DependentStep(ordinal) => Some(ordinal.to_string()),
129131
PathSegment::ParallelStep(index) => Some(format!("-{}", index)),
130132
PathSegment::Iteration(number) => Some(format!("[{}]", number)), // you can't "index" into it!

0 commit comments

Comments
 (0)