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
1 change: 1 addition & 0 deletions src/domain/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ fn render_expression(expr: &Expression) -> String {
}
result
}
Expression::Response(value, _) => format!("'{}'", value),
Expression::Number(Numeric::Scientific(q), _) => q.to_string(),
Expression::Number(Numeric::Integral(n), _) => n.to_string(),
Expression::Pair(pair, _) => {
Expand Down
5 changes: 5 additions & 0 deletions src/formatting/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,11 @@ impl<'i> Formatter<'i> {
}
self.add_fragment_reference(Syntax::Quote, "\"");
}
Expression::Response(value, _) => {
self.add_fragment_reference(Syntax::Quote, "'");
self.add_fragment_reference(Syntax::Response, value);
self.add_fragment_reference(Syntax::Quote, "'");
}
Expression::Number(numeric, _) => self.append_numeric(numeric),
Expression::Multiline(lang, lines, _) => {
self.append_char('\n');
Expand Down
2 changes: 2 additions & 0 deletions src/language/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ pub enum Piece<'i> {
pub enum Expression<'i> {
Variable(Identifier<'i>, Span),
String(Vec<Piece<'i>>, Span),
Response(&'i str, Span),
Number(Numeric<'i>, Span),
Multiline(Option<&'i str>, Vec<&'i str>, Span),
Repeat(Box<Expression<'i>>, Span),
Expand All @@ -456,6 +457,7 @@ impl PartialEq for Expression<'_> {
match (self, other) {
(Expression::Variable(a, _), Expression::Variable(b, _)) => a == b,
(Expression::String(a, _), Expression::String(b, _)) => a == b,
(Expression::Response(a, _), Expression::Response(b, _)) => a == b,
(Expression::Number(a, _), Expression::Number(b, _)) => a == b,
(Expression::Multiline(a1, a2, _), Expression::Multiline(b1, b2, _)) => {
a1 == b1 && a2 == b2
Expand Down
1 change: 1 addition & 0 deletions src/linking/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ fn link_operation<'i>(
}
Operation::Variable(_)
| Operation::Number(_)
| Operation::Response(_)
| Operation::Multiline(_, _)
| Operation::Prose(_)
| Operation::Hole
Expand Down
24 changes: 24 additions & 0 deletions src/parsing/checks/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,30 @@ fn unit_as_expression() {
);
}

#[test]
fn response_as_expression() {
// A single-quoted value is a response literal usable anywhere an
// expression is expected: bare in a code block, or as an argument.
let mut input = Parser::new();
input.initialize("'Monarchy'");
assert_eq!(
input.read_expression(),
Ok(Expression::Response("Monarchy", Span::default()))
);

input.initialize("evaluate('Democracy')");
assert_eq!(
input.read_expression(),
Ok(Expression::Execution(
Function {
target: Identifier::new("evaluate"),
parameters: vec![Expression::Response("Democracy", Span::default())]
},
Span::default()
))
);
}

#[test]
fn declaration_simple() {
let mut input = Parser::new();
Expand Down
17 changes: 17 additions & 0 deletions src/parsing/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,13 @@ impl<'i> Parser<'i> {
})?;
let span = self.span_since(start);
Ok(Expression::String(parts, span))
} else if is_enum_response(content) {
let value =
self.take_block_chars("a response literal", '\'', '\'', false, |inner| {
Ok(inner.source)
})?;
let span = self.span_since(start);
Ok(Expression::Response(value, span))
} else if is_invocation(content) {
let invocation = self.read_invocation()?;
let span = self.span_since(start);
Expand Down Expand Up @@ -2590,6 +2597,16 @@ impl<'i> Parser<'i> {
})?;
let span = outer.span_since(param_start);
params.push(Expression::String(parts, span));
} else if content.starts_with('\'') {
let value = outer.take_block_chars(
"a response literal",
'\'',
'\'',
false,
|inner| Ok(inner.source),
)?;
let span = outer.span_since(param_start);
params.push(Expression::Response(value, span));
} else if is_numeric_quantity(content) {
let numeric = outer.read_numeric_quantity()?;
let span = outer.span_since(param_start);
Expand Down
1 change: 1 addition & 0 deletions src/program/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub enum Operation<'i> {
Variable(language::Identifier<'i>),
Number(language::Numeric<'i>),
String(Vec<Fragment<'i>>),
Response(&'i str),
Multiline(Option<&'i str>, Vec<&'i str>),
Tablet(Vec<Entry<'i>>),
List(Vec<Operation<'i>>),
Expand Down
4 changes: 4 additions & 0 deletions src/resolution/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ fn resolve_operation<'i>(
}
Operation::Variable(_)
| Operation::Number(_)
| Operation::Response(_)
| Operation::Multiline(_, _)
| Operation::Prose(_)
| Operation::Hole
Expand Down Expand Up @@ -210,6 +211,7 @@ fn gather_iterated<'i>(op: &Operation<'i>, iterated: &mut HashSet<&'i str>) {
}
Operation::Variable(_)
| Operation::Number(_)
| Operation::Response(_)
| Operation::Multiline(_, _)
| Operation::Prose(_)
| Operation::Hole
Expand Down Expand Up @@ -271,6 +273,7 @@ fn mark_iterated<'i>(op: &mut Operation<'i>, iterated: &HashSet<&str>) {
}
Operation::Variable(_)
| Operation::Number(_)
| Operation::Response(_)
| Operation::Multiline(_, _)
| Operation::Prose(_)
| Operation::Hole
Expand Down Expand Up @@ -363,6 +366,7 @@ fn check_scope<'i>(
}
}
Operation::Number(_)
| Operation::Response(_)
| Operation::Multiline(_, _)
| Operation::Prose(_)
| Operation::Hole
Expand Down
45 changes: 42 additions & 3 deletions src/runner/checks/driver.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use crate::runner::driver::{
Automatic, Console, Driver, Event, Kind, Mock, Prompt, Standing, UserInput, draw, edit,
is_list_forma,
Automatic, Console, Driver, Event, Kind, Mock, Prompt, Standing, UserInput, draw, draw_action,
edit, is_list_forma,
};
use crate::value::{Numeric, Value};

Expand Down Expand Up @@ -136,7 +136,12 @@ fn automatic_declines_action_and_choice_as_skip() {
// taken as Done just like a plain Computable.
let mut p = Automatic::with_handle(Vec::new());
assert_eq!(
p.action("/I/1", "click", "Click", "Actions"),
p.action(
"/I/1",
"click",
"Click",
&Value::Literali("Actions".to_string())
),
UserInput::Skip
);
assert_eq!(
Expand Down Expand Up @@ -387,6 +392,40 @@ fn esc_menu_disables_edit_for_unit_and_complex() {
);
}

#[test]
fn action_response_label_is_coloured() {
// A response literal argument (e.g. `scroll('BOTTOM')`) shows on the action
// line in the Response orange (0xf5, 0x79, 0x00), not the default white a
// plain string label gets.
let it = Prompt::begin(&[], Value::Unitus);

let mut out: Vec<u8> = Vec::new();
draw_action(
&mut out,
"I/1",
"Scroll to",
&Value::Enumerati("BOTTOM".to_string()),
&it,
)
.expect("draw");
let coloured = String::from_utf8(out).expect("utf8");
assert!(coloured.contains("BOTTOM"));
assert!(coloured.contains("245;121;0"));

let mut out: Vec<u8> = Vec::new();
draw_action(
&mut out,
"I/1",
"Type",
&Value::Literali("hello".to_string()),
&it,
)
.expect("draw");
let plain = String::from_utf8(out).expect("utf8");
assert!(plain.contains("hello"));
assert!(!plain.contains("245;121;0"));
}

#[test]
fn menu_shows_greyed_edit_when_unavailable() {
// Edit is always listed so it stays discoverable; for a non-editable value
Expand Down
56 changes: 38 additions & 18 deletions src/runner/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub trait Driver {
/// trace; Skip / Fail decline and record the step; Quit stops. Unlike
/// `command` there is no edit buffer. `Automatic` returns `Done` without
/// prompting.
fn action(&mut self, qualified: &str, name: &str, verb: &str, label: &str) -> UserInput;
fn action(&mut self, qualified: &str, name: &str, verb: &str, value: &Value) -> UserInput;

/// Open a Section: the grey `↘ /fqp` descent bracket (matching the `↙`
/// the section's close marker) followed by its prose heading —
Expand Down Expand Up @@ -242,7 +242,7 @@ pub trait Verdict {
qualified: &str,
name: &str,
verb: &str,
label: &str,
value: &Value,
) -> UserInput;

fn acquire<O: Output>(
Expand Down Expand Up @@ -426,9 +426,9 @@ impl Verdict for Interactive {
qualified: &str,
name: &str,
verb: &str,
label: &str,
value: &Value,
) -> UserInput {
prompt_action(out.surface(), qualified, name, verb, label)
prompt_action(out.surface(), qualified, name, verb, value)
}

fn acquire<O: Output>(
Expand Down Expand Up @@ -515,9 +515,9 @@ impl Verdict for Batch {
_qualified: &str,
_name: &str,
verb: &str,
label: &str,
value: &Value,
) -> UserInput {
out.show_action(verb, label);
out.show_action(verb, &value.label());
// An unattended run cannot attest a physical action was performed.
UserInput::Skip
}
Expand Down Expand Up @@ -620,9 +620,15 @@ impl<O: Output, V: Verdict> Driver for Interface<O, V> {
.command(&mut self.out, qualified, script)
}

fn action(&mut self, qualified: &str, name: &str, verb: &str, label: &str) -> UserInput {
fn action(
&mut self,
qualified: &str,
name: &str,
verb: &str,
value: &Value,
) -> UserInput {
self.verdict
.action(&mut self.out, qualified, name, verb, label)
.action(&mut self.out, qualified, name, verb, value)
}

fn seal(&mut self, qualified: &str, produced: Value, kind: Kind) -> UserInput {
Expand Down Expand Up @@ -779,11 +785,11 @@ fn prompt_action(
qualified: &str,
name: &str,
verb: &str,
label: &str,
value: &Value,
) -> UserInput {
let qualified = display_path(qualified);
let result = interact(out, Prompt::begin(&[], Value::Unitus), |o, i| {
draw_action(o, &qualified, verb, label, i)
draw_action(o, &qualified, verb, value, i)
});
let _ = queue!(
&mut out,
Expand Down Expand Up @@ -1485,9 +1491,10 @@ fn draw_action(
mut out: &mut dyn Write,
qualified: &str,
verb: &str,
label: &str,
value: &Value,
interaction: &Prompt,
) -> io::Result<()> {
let label = value.label();
queue!(
&mut out,
cursor::MoveToColumn(0),
Expand All @@ -1498,10 +1505,17 @@ fn draw_action(
write!(out, "{}", verb)?;
queue!(&mut out, ResetColor)?;
if !label.is_empty() {
write!(out, " {}", label)?;
match value {
Value::Enumerati(_) => {
queue!(&mut out, SetForegroundColor(RESPONSE))?;
write!(out, " {}", label)?;
queue!(&mut out, ResetColor)?;
}
_ => write!(out, " {}", label)?,
}
}
write!(out, " {} ", PROMPT_SYMBOL.blue())?;
let prefix = action_prefix_width(qualified, verb, label);
let prefix = action_prefix_width(qualified, verb, &label);
let (cursor_col, end_col) = draw_tail(out, interaction, prefix)?;
place_cursor(out, cursor_col, end_col)
}
Expand Down Expand Up @@ -1925,15 +1939,21 @@ impl<D: Driver, W: Write> Driver for Transcript<D, W> {
.command(qualified, script)
}

fn action(&mut self, qualified: &str, name: &str, verb: &str, label: &str) -> UserInput {
fn action(
&mut self,
qualified: &str,
name: &str,
verb: &str,
value: &Value,
) -> UserInput {
self.emit(Trace::Execute {
path: qualified.to_string(),
script: format!("{} {}", verb, label)
script: format!("{} {}", verb, value.label())
.trim_end()
.to_string(),
});
self.inner
.action(qualified, name, verb, label)
.action(qualified, name, verb, value)
}

fn section(&mut self, qualified: &str, numeral: &str, title: &str) {
Expand Down Expand Up @@ -2154,13 +2174,13 @@ impl Driver for Mock {
/// Records the action and auto-confirms it (`Done`) without draining the
/// answer queue — like `command`, an action is orthogonal to the step
/// verdicts a test drives.
fn action(&mut self, qualified: &str, name: &str, verb: &str, label: &str) -> UserInput {
fn action(&mut self, qualified: &str, name: &str, verb: &str, value: &Value) -> UserInput {
self.events
.push(Event::Action {
qualified: qualified.to_string(),
name: name.to_string(),
verb: verb.to_string(),
label: label.to_string(),
label: value.label(),
});
UserInput::Done(Value::Unitus)
}
Expand Down
2 changes: 2 additions & 0 deletions src/runner/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ fn kind(value: &Value) -> &'static str {
match value {
Value::Unitus => "unit",
Value::Literali(_) => "string",
Value::Enumerati(_) => "response",
Value::Quanticle(_) => "quantity",
Value::Tabularum(_) => "tablet",
Value::Arraeum(_) => "list",
Expand Down Expand Up @@ -147,6 +148,7 @@ pub fn evaluate<'i>(
)
}),
Operation::Number(n) => Ok(Value::Quanticle(Numeric::from(n))),
Operation::Response(value) => Ok(Value::Enumerati(value.to_string())),
Operation::String(fragments) => {
let mut text = String::new();
for fragment in fragments {
Expand Down
Loading