From b0c1b9d5bc9743ebf5a9d88299c9856a2a17a080 Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Wed, 6 May 2026 18:28:58 +1000 Subject: [PATCH 1/9] Add Span type for parser --- .gitignore | 1 + src/language/types.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 33c0e46e..d14796c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.vscode /target +/plans # rendering artifacts *.pdf diff --git a/src/language/types.rs b/src/language/types.rs index a0ead232..0e5c965c 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -2,6 +2,13 @@ use crate::regex::*; +/// Byte range within the original source. `length` excludes trailing whitespace. +#[derive(Copy, Clone, Default, Eq, Debug, PartialEq)] +pub struct Span { + pub offset: usize, + pub length: usize, +} + #[derive(Eq, Debug, PartialEq)] pub struct Document<'i> { pub source: Option<&'i str>, From b5627631443022095e1fd9663305e5ec5b160e5d Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Wed, 6 May 2026 21:53:27 +1000 Subject: [PATCH 2/9] Add span field to Identifier --- src/domain/engine.rs | 26 ++--- src/domain/recipe/adapter.rs | 4 +- src/editor/server.rs | 2 +- src/formatting/formatter.rs | 20 ++-- src/language/types.rs | 56 ++++++++--- src/parsing/checks/parser.rs | 172 +++++++++++++++++----------------- src/parsing/checks/verify.rs | 102 ++++++++++---------- src/parsing/parser.rs | 86 +++++++++++------ src/problem/messages.rs | 84 ++++++++--------- tests/formatting/formatter.rs | 36 +++---- 10 files changed, 320 insertions(+), 268 deletions(-) diff --git a/src/domain/engine.rs b/src/domain/engine.rs index 95000ac3..fce6c61f 100644 --- a/src/domain/engine.rs +++ b/src/domain/engine.rs @@ -127,7 +127,7 @@ impl<'i> Scope<'i> { Scope::AttributeBlock { attributes, .. } => attributes .iter() .filter_map(|attr| match attr { - Attribute::Role(id) => Some(id.0), + Attribute::Role(id) => Some(id.value), _ => None, }) .collect::>() @@ -142,7 +142,7 @@ impl<'i> Scope<'i> { Scope::AttributeBlock { attributes, .. } => attributes .iter() .filter_map(|attr| match attr { - Attribute::Place(id) => Some(id.0), + Attribute::Place(id) => Some(id.value), _ => None, }) .collect::>() @@ -232,7 +232,7 @@ impl<'i> Procedure<'i> { /// Returns the procedure name. pub fn name(&self) -> &'i str { self.name - .0 + .value } } @@ -268,7 +268,7 @@ fn render_expression_parts(expr: &Expression) -> (String, Vec) { format!( "{}(", func.target - .0 + .value ), body, ); @@ -285,13 +285,13 @@ fn render_expression(expr: &Expression) -> String { Expression::Foreach(ids, inner) => { let vars = if ids.len() == 1 { ids[0] - .0 + .value .to_string() } else { format!( "({})", ids.iter() - .map(|id| id.0) + .map(|id| id.value) .collect::>() .join(", ") ) @@ -300,7 +300,7 @@ fn render_expression(expr: &Expression) -> String { } Expression::Application(inv) => { let name = match &inv.target { - Target::Local(id) => id.0, + Target::Local(id) => id.value, Target::Remote(ext) => ext.0, }; if let Some(params) = &inv.parameters { @@ -322,13 +322,13 @@ fn render_expression(expr: &Expression) -> String { format!( "{}({})", func.target - .0, + .value, args.join(", ") ) } Expression::Multiline(_, lines) => lines.join("\n"), Expression::Variable(id) => { - id.0.to_string() + id.value.to_string() } Expression::Binding(inner, _) => render_expression(inner), Expression::String(pieces) => { @@ -476,7 +476,7 @@ impl<'i> Paragraph<'i> { fn invocation_name(inv: &crate::language::Invocation<'i>) -> &'i str { match &inv.target { - crate::language::Target::Local(id) => id.0, + crate::language::Target::Local(id) => id.value, crate::language::Target::Remote(ext) => ext.0, } } @@ -547,7 +547,7 @@ mod check { fn local<'a>(name: &'a str) -> Invocation<'a> { Invocation { - target: Target::Local(Identifier(name)), + target: Target::Local(Identifier::dummy(name)), parameters: None, } } @@ -603,7 +603,7 @@ mod check { fn binding_with_invocation() { let p = Paragraph(vec![Descriptive::Binding( Box::new(Descriptive::Application(local("observe"))), - vec![Identifier("e")], + vec![Identifier::dummy("e")], )]); assert_eq!(p.text(), ""); assert_eq!(p.invocations(), vec!["observe"]); @@ -614,7 +614,7 @@ mod check { #[test] fn foreach_expression() { let p = Paragraph(vec![Descriptive::CodeInline(Expression::Foreach( - vec![Identifier("design")], + vec![Identifier::dummy("design")], Box::new(Expression::Application(local("implement"))), ))]); assert_eq!(p.text(), ""); diff --git a/src/domain/recipe/adapter.rs b/src/domain/recipe/adapter.rs index cf123ce0..121fbc27 100644 --- a/src/domain/recipe/adapter.rs +++ b/src/domain/recipe/adapter.rs @@ -307,7 +307,7 @@ fn builtin_from_expression(expr: &language::Expression) -> Option { language::Expression::Application(inv) => builtin_from_invocation(inv), language::Expression::Execution(func) => builtin_suffix( func.target - .0, + .value, &func.parameters, ), language::Expression::Binding(inner, _) => builtin_from_expression(inner), @@ -317,7 +317,7 @@ fn builtin_from_expression(expr: &language::Expression) -> Option { fn builtin_from_invocation(inv: &language::Invocation) -> Option { let name = match &inv.target { - language::Target::Local(id) => id.0, + language::Target::Local(id) => id.value, _ => return None, }; match &inv.parameters { diff --git a/src/editor/server.rs b/src/editor/server.rs index 996df2c4..340d2d18 100644 --- a/src/editor/server.rs +++ b/src/editor/server.rs @@ -499,7 +499,7 @@ impl TechniqueLanguageServer { let name = procedure .name - .0; + .value; // Calculate the byte offset of the name using pointer arithmetic let offset = calculate_slice_offset(content, name).unwrap_or(0); diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index 74e67ed5..b0a0a72e 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -94,7 +94,7 @@ pub fn render_forma<'i>(forma: &'i Forma, renderer: &dyn Render) -> String { } pub fn render_identifier<'i>(identifier: &'i Identifier, renderer: &dyn Render) -> String { - renderer.style(Syntax::Declaration, identifier.0) + renderer.style(Syntax::Declaration, identifier.value) } pub fn render_response<'i>(response: &'i Response, renderer: &dyn Render) -> String { @@ -151,7 +151,7 @@ pub fn render_procedure_declaration<'i>(procedure: &'i Procedure, renderer: &dyn Syntax::Declaration, procedure .name - .0, + .value, ); if let Some(parameters) = &procedure.parameters { sub.append_parameters(parameters); @@ -478,7 +478,7 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::BlockBegin, ""); let name = &procedure.name; - self.add_fragment_reference(Syntax::Declaration, name.0); + self.add_fragment_reference(Syntax::Declaration, name.value); if let Some(parameters) = &procedure.parameters { // note that append_arguments() is for general expression @@ -615,7 +615,7 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::Structure, ","); self.add_fragment_reference(Syntax::Neutral, " "); } - self.add_fragment_reference(Syntax::Variable, variable.0); + self.add_fragment_reference(Syntax::Variable, variable.value); } self.add_fragment_reference(Syntax::Structure, ")"); } @@ -976,11 +976,11 @@ impl<'i> Formatter<'i> { match attribute { Attribute::Role(name) => { self.add_fragment_reference(Syntax::Attribute, "@"); - self.add_fragment_reference(Syntax::Attribute, name.0); + self.add_fragment_reference(Syntax::Attribute, name.value); } Attribute::Place(name) => { self.add_fragment_reference(Syntax::Attribute, "^"); - self.add_fragment_reference(Syntax::Attribute, name.0); + self.add_fragment_reference(Syntax::Attribute, name.value); } } } @@ -989,7 +989,7 @@ impl<'i> Formatter<'i> { pub fn append_expression(&mut self, expression: &'i Expression) { match expression { Expression::Variable(identifier) => { - self.add_fragment_reference(Syntax::Variable, identifier.0); + self.add_fragment_reference(Syntax::Variable, identifier.value); } Expression::String(pieces) => { self.add_fragment_reference(Syntax::Quote, "\""); @@ -1083,7 +1083,7 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::Structure, ","); self.add_fragment_reference(Syntax::Neutral, " "); } - self.add_fragment_reference(Syntax::Variable, variable.0); + self.add_fragment_reference(Syntax::Variable, variable.value); } if variables.len() > 1 { self.add_fragment_reference(Syntax::Structure, ")"); @@ -1127,7 +1127,7 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::Quote, "<"); match &invocation.target { Target::Local(identifier) => { - self.add_fragment_reference(Syntax::Invocation, identifier.0) + self.add_fragment_reference(Syntax::Invocation, identifier.value) } Target::Remote(external) => self.add_fragment_reference(Syntax::Invocation, external.0), } @@ -1162,7 +1162,7 @@ impl<'i> Formatter<'i> { Syntax::Function, function .target - .0, + .value, ); self.add_fragment_reference(Syntax::Structure, "("); diff --git a/src/language/types.rs b/src/language/types.rs index 0e5c965c..ae84c962 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -69,8 +69,26 @@ impl<'i> Procedure<'i> { } } -#[derive(Eq, Debug, PartialEq)] -pub struct Identifier<'i>(pub &'i str); +#[derive(Eq, Debug)] +pub struct Identifier<'i> { + pub value: &'i str, + pub span: Span, +} + +/// Equality is structural: spans are diagnostic metadata, not part of identity. +impl PartialEq for Identifier<'_> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl<'i> Identifier<'i> { + /// Test helper: builds an `Identifier` with a default span. Real parser + /// output always carries a real span; only fixtures should use this. + pub fn dummy(value: &'i str) -> Self { + Identifier { value, span: Span::default() } + } +} #[derive(Eq, Debug, PartialEq)] pub struct External<'i>(pub &'i str); @@ -253,14 +271,14 @@ pub(crate) fn validate_domain(input: &str) -> Option<&str> { } } -pub(crate) fn validate_identifier(input: &str) -> Option> { +pub(crate) fn validate_identifier(input: &str, span: Span) -> Option> { if input.len() == 0 { return None; } let re = regex!(r"^[a-z][a-z0-9_]*$"); if re.is_match(input) { - Some(Identifier(input)) + Some(Identifier { value: input, span }) } else { None } @@ -386,18 +404,28 @@ mod check { #[test] fn identifier_rules() { - assert_eq!(validate_identifier("a"), Some(Identifier("a"))); - assert_eq!(validate_identifier("ab"), Some(Identifier("ab"))); - assert_eq!(validate_identifier("johnny5"), Some(Identifier("johnny5"))); - assert_eq!(validate_identifier("Pizza"), None); - assert_eq!(validate_identifier("pizZa"), None); - assert!(validate_identifier("0trust").is_none()); + let s = Span::default(); + assert_eq!( + validate_identifier("a", s), + Some(Identifier::dummy("a")) + ); + assert_eq!( + validate_identifier("ab", s), + Some(Identifier::dummy("ab")) + ); + assert_eq!( + validate_identifier("johnny5", s), + Some(Identifier::dummy("johnny5")) + ); + assert_eq!(validate_identifier("Pizza", s), None); + assert_eq!(validate_identifier("pizZa", s), None); + assert!(validate_identifier("0trust", s).is_none()); assert_eq!( - validate_identifier("make_dinner"), - Some(Identifier("make_dinner")) + validate_identifier("make_dinner", s), + Some(Identifier::dummy("make_dinner")) ); - assert!(validate_identifier("MakeDinner").is_none()); - assert!(validate_identifier("make-dinner").is_none()); + assert!(validate_identifier("MakeDinner", s).is_none()); + assert!(validate_identifier("make-dinner", s).is_none()); } #[test] diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index 775d8ca5..c67e2bd2 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -140,11 +140,11 @@ fn identifier_rules() { let mut input = Parser::new(); input.initialize("p"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier("p"))); + assert_eq!(result, Ok(Identifier::dummy("p"))); input.initialize("cook_pizza"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier("cook_pizza"))); + assert_eq!(result, Ok(Identifier::dummy("cook_pizza"))); input.initialize("cook-pizza"); let result = input.read_identifier(); @@ -203,7 +203,7 @@ fn declaration_simple() { assert!(is_procedure_declaration(input.source)); let result = input.parse_procedure_declaration(); - assert_eq!(result, Ok((Identifier("making_coffee"), None, None))); + assert_eq!(result, Ok((Identifier::dummy("making_coffee"), None, None))); } #[test] @@ -216,7 +216,7 @@ fn declaration_full() { assert_eq!( result, Ok(( - Identifier("f"), + Identifier::dummy("f"), None, Some(Signature { requires: Genus::Single(Forma("A")), @@ -232,7 +232,7 @@ fn declaration_full() { assert_eq!( result, Ok(( - Identifier("making_coffee"), + Identifier::dummy("making_coffee"), None, Some(Signature { requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), @@ -289,7 +289,7 @@ making_coffee : assert_eq!( result, Ok(( - Identifier("making_coffee"), + Identifier::dummy("making_coffee"), None, Some(Signature { requires: Genus::Single(Forma("Ingredients")), @@ -312,8 +312,8 @@ making_coffee(b, m) : assert_eq!( result, Ok(( - Identifier("making_coffee"), - Some(vec![Identifier("b"), Identifier("m")]), + Identifier::dummy("making_coffee"), + Some(vec![Identifier::dummy("b"), Identifier::dummy("m")]), Some(Signature { requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), provides: Genus::Single(Forma("Coffee")) @@ -415,19 +415,19 @@ fn taking_until() { // Test take_until() with an identifier up to a limiting character input.initialize("hello,world"); let result = input.take_until(&[','], |inner| inner.read_identifier()); - assert_eq!(result, Ok(Identifier("hello"))); + assert_eq!(result, Ok(Identifier::dummy("hello"))); assert_eq!(input.source, ",world"); // Test take_until() with whitespace delimiters input.initialize("test \t\nmore"); let result = input.take_until(&[' ', '\t', '\n'], |inner| inner.read_identifier()); - assert_eq!(result, Ok(Identifier("test"))); + assert_eq!(result, Ok(Identifier::dummy("test"))); assert_eq!(input.source, " \t\nmore"); // Test take_until() when no delimiter found (it should take everything) input.initialize("onlytext"); let result = input.take_until(&[',', ';'], |inner| inner.read_identifier()); - assert_eq!(result, Ok(Identifier("onlytext"))); + assert_eq!(result, Ok(Identifier::dummy("onlytext"))); assert_eq!(input.source, ""); } @@ -441,7 +441,7 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Local(Identifier("hello")), + target: Target::Local(Identifier::dummy("hello")), parameters: None }) ); @@ -452,7 +452,7 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Local(Identifier("hello_world")), + target: Target::Local(Identifier::dummy("hello_world")), parameters: Some(vec![]) }) ); @@ -463,11 +463,11 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Local(Identifier("greetings")), + target: Target::Local(Identifier::dummy("greetings")), parameters: Some(vec![ - Expression::Variable(Identifier("name")), - Expression::Variable(Identifier("title")), - Expression::Variable(Identifier("occupation")) + Expression::Variable(Identifier::dummy("name")), + Expression::Variable(Identifier::dummy("title")), + Expression::Variable(Identifier::dummy("occupation")) ]) }) ); @@ -1090,7 +1090,7 @@ fn code_blocks() { // Test simple identifier in code block input.initialize("{ count }"); let result = input.read_code_block(); - assert_eq!(result, Ok(vec![Expression::Variable(Identifier("count"))])); + assert_eq!(result, Ok(vec![Expression::Variable(Identifier::dummy("count"))])); // Test function with simple parameter input.initialize("{ sum(count) }"); @@ -1098,8 +1098,8 @@ fn code_blocks() { assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("sum"), - parameters: vec![Expression::Variable(Identifier("count"))] + target: Identifier::dummy("sum"), + parameters: vec![Expression::Variable(Identifier::dummy("count"))] })]) ); @@ -1109,11 +1109,11 @@ fn code_blocks() { assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("consume"), + target: Identifier::dummy("consume"), parameters: vec![ - Expression::Variable(Identifier("apple")), - Expression::Variable(Identifier("banana")), - Expression::Variable(Identifier("chocolate")) + Expression::Variable(Identifier::dummy("apple")), + Expression::Variable(Identifier::dummy("banana")), + Expression::Variable(Identifier::dummy("chocolate")) ] })]) ); @@ -1124,7 +1124,7 @@ fn code_blocks() { assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::String(vec![Piece::Text("Hello, World")])] })]) ); @@ -1139,7 +1139,7 @@ echo "Done"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec!["ls -l", "echo \"Done\""] @@ -1153,7 +1153,7 @@ echo "Done"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("timer"), + target: Identifier::dummy("timer"), parameters: vec![Expression::Number(Numeric::Scientific(Quantity { mantissa: Decimal { number: 3, @@ -1172,7 +1172,7 @@ echo "Done"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("measure"), + target: Identifier::dummy("measure"), parameters: vec![Expression::Number(Numeric::Integral(100))] })]) ); @@ -1183,7 +1183,7 @@ echo "Done"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("seq"), + target: Identifier::dummy("seq"), parameters: vec![ Expression::Number(Numeric::Integral(1)), Expression::Number(Numeric::Integral(6)) @@ -1197,7 +1197,7 @@ echo "Done"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("wait"), + target: Identifier::dummy("wait"), parameters: vec![ Expression::Number(Numeric::Scientific(Quantity { mantissa: Decimal { @@ -1232,7 +1232,7 @@ fn multiline() { assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec![ @@ -1257,7 +1257,7 @@ echo "Done"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline(None, vec!["ls -l", "echo \"Done\""])] })]) ); @@ -1276,7 +1276,7 @@ echo "Ending"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline( Some("shell"), vec![ @@ -1307,7 +1307,7 @@ echo "Ending"```) }"#, assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline( Some("python"), vec![ @@ -1332,7 +1332,7 @@ echo test assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline(None, vec!["echo test"])] })]) ); @@ -1351,7 +1351,7 @@ echo test assert_eq!( result, Ok(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline( Some("yaml"), vec![ @@ -1422,12 +1422,12 @@ fn tablets() { }, Pair { label: "message", - value: Expression::Variable(Identifier("msg")) + value: Expression::Variable(Identifier::dummy("msg")) }, Pair { label: "timestamp", value: Expression::Execution(Function { - target: Identifier("now"), + target: Identifier::dummy("now"), parameters: vec![] }) } @@ -1456,7 +1456,7 @@ fn tablets() { }, Pair { label: "status", - value: Expression::Variable(Identifier("active")) + value: Expression::Variable(Identifier::dummy("active")) } ])]) ); @@ -1492,25 +1492,25 @@ fn reading_identifiers() { // Parse a basic identifier input.initialize("hello"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier("hello"))); + assert_eq!(result, Ok(Identifier::dummy("hello"))); assert_eq!(input.source, ""); // Parse an identifier with trailing content input.initialize("count more"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier("count"))); + assert_eq!(result, Ok(Identifier::dummy("count"))); assert_eq!(input.source, " more"); // Parse an identifier with leading whitespace and trailing content input.initialize(" \t test_name after"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier("test_name"))); + assert_eq!(result, Ok(Identifier::dummy("test_name"))); assert_eq!(input.source, " after"); // Parse an identifier with various delimiters input.initialize("name(param)"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier("name"))); + assert_eq!(result, Ok(Identifier::dummy("name"))); assert_eq!(input.source, "(param)"); } @@ -1523,8 +1523,8 @@ fn test_foreach_expression() { assert_eq!( result, Ok(vec![Expression::Foreach( - vec![Identifier("item")], - Box::new(Expression::Variable(Identifier("items"))) + vec![Identifier::dummy("item")], + Box::new(Expression::Variable(Identifier::dummy("items"))) )]) ); } @@ -1538,12 +1538,12 @@ fn foreach_tuple_pattern() { assert_eq!( result, Ok(vec![Expression::Foreach( - vec![Identifier("design"), Identifier("component")], + vec![Identifier::dummy("design"), Identifier::dummy("component")], Box::new(Expression::Execution(Function { - target: Identifier("zip"), + target: Identifier::dummy("zip"), parameters: vec![ - Expression::Variable(Identifier("designs")), - Expression::Variable(Identifier("components")) + Expression::Variable(Identifier::dummy("designs")), + Expression::Variable(Identifier::dummy("components")) ] })) )]) @@ -1555,13 +1555,13 @@ fn foreach_tuple_pattern() { assert_eq!( result, Ok(vec![Expression::Foreach( - vec![Identifier("a"), Identifier("b"), Identifier("c")], + vec![Identifier::dummy("a"), Identifier::dummy("b"), Identifier::dummy("c")], Box::new(Expression::Execution(Function { - target: Identifier("zip"), + target: Identifier::dummy("zip"), parameters: vec![ - Expression::Variable(Identifier("list1")), - Expression::Variable(Identifier("list2")), - Expression::Variable(Identifier("list3")) + Expression::Variable(Identifier::dummy("list1")), + Expression::Variable(Identifier::dummy("list2")), + Expression::Variable(Identifier::dummy("list3")) ] })) )]) @@ -1578,10 +1578,10 @@ fn tuple_binding_expression() { result, Ok(vec![Expression::Binding( Box::new(Expression::Application(Invocation { - target: Target::Local(Identifier("get_coordinates")), + target: Target::Local(Identifier::dummy("get_coordinates")), parameters: Some(vec![]) })), - vec![Identifier("x"), Identifier("y")] + vec![Identifier::dummy("x"), Identifier::dummy("y")] )]) ); } @@ -1595,7 +1595,7 @@ fn test_repeat_expression() { assert_eq!( result, Ok(vec![Expression::Repeat(Box::new(Expression::Variable( - Identifier("count") + Identifier::dummy("count") )))]) ); } @@ -1620,7 +1620,7 @@ fn test_repeat_keyword_boundary() { // Should parse as identifier, not repeat assert_eq!( result, - Ok(vec![Expression::Variable(Identifier("repeater"))]) + Ok(vec![Expression::Variable(Identifier::dummy("repeater"))]) ); } @@ -1645,9 +1645,9 @@ fn splitting_by() { assert_eq!( result, Ok(vec![ - Identifier("apple"), - Identifier("banana"), - Identifier("cherry") + Identifier::dummy("apple"), + Identifier::dummy("banana"), + Identifier::dummy("cherry") ]) ); assert_eq!(input.source, ""); @@ -1658,16 +1658,16 @@ fn splitting_by() { assert_eq!( result, Ok(vec![ - Identifier("un"), - Identifier("deux"), - Identifier("trois") + Identifier::dummy("un"), + Identifier::dummy("deux"), + Identifier::dummy("trois") ]) ); // Ensure a single item (no delimiter present in input) works input.initialize("seulement"); let result = input.take_split_by(',', |inner| inner.read_identifier()); - assert_eq!(result, Ok(vec![Identifier("seulement")])); + assert_eq!(result, Ok(vec![Identifier::dummy("seulement")])); // an empty chunk causes an error input.initialize("un,,trois"); @@ -1792,12 +1792,12 @@ fn reading_attributes() { // Test simple role input.initialize("@chef"); let result = input.read_attributes(); - assert_eq!(result, Ok(vec![Attribute::Role(Identifier("chef"))])); + assert_eq!(result, Ok(vec![Attribute::Role(Identifier::dummy("chef"))])); // Test simple place input.initialize("^kitchen"); let result = input.read_attributes(); - assert_eq!(result, Ok(vec![Attribute::Place(Identifier("kitchen"))])); + assert_eq!(result, Ok(vec![Attribute::Place(Identifier::dummy("kitchen"))])); // Test multiple roles input.initialize("@master_chef + @barista"); @@ -1805,8 +1805,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier("master_chef")), - Attribute::Role(Identifier("barista")) + Attribute::Role(Identifier::dummy("master_chef")), + Attribute::Role(Identifier::dummy("barista")) ]) ); @@ -1816,8 +1816,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Place(Identifier("kitchen")), - Attribute::Place(Identifier("bath_room")) + Attribute::Place(Identifier::dummy("kitchen")), + Attribute::Place(Identifier::dummy("bath_room")) ]) ); @@ -1827,8 +1827,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier("chef")), - Attribute::Place(Identifier("bathroom")) + Attribute::Role(Identifier::dummy("chef")), + Attribute::Place(Identifier::dummy("bathroom")) ]) ); @@ -1838,8 +1838,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Place(Identifier("kitchen")), - Attribute::Role(Identifier("barista")) + Attribute::Place(Identifier::dummy("kitchen")), + Attribute::Role(Identifier::dummy("barista")) ]) ); @@ -1849,10 +1849,10 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier("chef")), - Attribute::Place(Identifier("kitchen")), - Attribute::Role(Identifier("barista")), - Attribute::Place(Identifier("dining_room")) + Attribute::Role(Identifier::dummy("chef")), + Attribute::Place(Identifier::dummy("kitchen")), + Attribute::Role(Identifier::dummy("barista")), + Attribute::Place(Identifier::dummy("dining_room")) ]) ); @@ -1891,7 +1891,7 @@ fn step_with_role_assignment() { "Check the patient's vital signs" )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("nurse"))], + attributes: vec![Attribute::Role(Identifier::dummy("nurse"))], subscopes: vec![], }] } @@ -1922,7 +1922,7 @@ fn substep_with_role_assignment() { "Verify patient identity" )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("surgeon"))], + attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text("Check ID")])], @@ -1955,7 +1955,7 @@ fn parallel_step_with_role_assignment() { ordinal: "1", description: vec![Paragraph(vec![Descriptive::Text("Monitor patient vitals")])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("nursing_team"))], + attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], subscopes: vec![Scope::ParallelBlock { bullet: '-', description: vec![Paragraph(vec![Descriptive::Text("Check readings")])], @@ -1992,7 +1992,7 @@ fn two_roles_with_substeps() { description: vec![Paragraph(vec![Descriptive::Text("Review events.")])], subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("surgeon"))], + attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text( @@ -2002,7 +2002,7 @@ fn two_roles_with_substeps() { }] }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("nurse"))], + attributes: vec![Attribute::Role(Identifier::dummy("nurse"))], subscopes: vec![Scope::DependentBlock { ordinal: "b", description: vec![Paragraph(vec![Descriptive::Text( @@ -2193,7 +2193,7 @@ This is { exec(a, Descriptive::CodeInline(Expression::Execution(func)) => { assert_eq!( func.target - .0, + .value, "exec" ); assert_eq!( @@ -2202,7 +2202,7 @@ This is { exec(a, 3 ); // Check that all parameters were parsed correctly - if let Expression::Variable(Identifier(name)) = &func.parameters[0] { + if let Expression::Variable(Identifier { value: name, .. }) = &func.parameters[0] { assert_eq!(*name, "a"); } else { panic!("First parameter should be variable 'a'"); diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index 2e1e5ec1..26399cc6 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -58,7 +58,7 @@ making_coffee : (Beans, Milk) -> Coffee assert_eq!( procedure, Ok(Procedure { - name: Identifier("making_coffee"), + name: Identifier::dummy("making_coffee"), parameters: None, signature: Some(Signature { requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), @@ -85,7 +85,7 @@ second : C -> D assert_eq!( procedure, Ok(Procedure { - name: Identifier("first"), + name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -99,7 +99,7 @@ second : C -> D assert_eq!( procedure, Ok(Procedure { - name: Identifier("second"), + name: Identifier::dummy("second"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("C")), @@ -124,8 +124,8 @@ making_coffee(e) : Ingredients -> Coffee assert_eq!( procedure, Ok(Procedure { - name: Identifier("making_coffee"), - parameters: Some(vec![Identifier("e")]), + name: Identifier::dummy("making_coffee"), + parameters: Some(vec![Identifier::dummy("e")]), signature: Some(Signature { requires: Genus::Single(Forma("Ingredients")), provides: Genus::Single(Forma("Coffee")) @@ -156,7 +156,7 @@ This is the first one. assert_eq!( procedure, Ok(Procedure { - name: Identifier("first"), + name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -211,7 +211,7 @@ This is the first one. assert_eq!( procedure, Ok(Procedure { - name: Identifier("first"), + name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -277,7 +277,7 @@ This is the first one. assert_eq!( procedure, Ok(Procedure { - name: Identifier("first"), + name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -361,7 +361,7 @@ fn realistic_procedure() { assert_eq!( procedure, Procedure { - name: Identifier("before_anesthesia"), + name: Identifier::dummy("before_anesthesia"), parameters: None, signature: None, elements: vec![ @@ -515,7 +515,7 @@ label_the_specimens : assert_eq!( procedure, Ok(Procedure { - name: Identifier("label_the_specimens"), + name: Identifier::dummy("label_the_specimens"), parameters: None, signature: None, elements: vec![Element::Steps(vec![Scope::DependentBlock { @@ -524,7 +524,7 @@ label_the_specimens : subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("nursing_team"))], + attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], subscopes: vec![ Scope::ParallelBlock { bullet: '-', @@ -545,7 +545,7 @@ label_the_specimens : ] }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("admin_staff"))], + attributes: vec![Attribute::Role(Identifier::dummy("admin_staff"))], subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text( @@ -597,7 +597,7 @@ before_leaving : assert_eq!( procedure, Procedure { - name: Identifier("before_leaving"), + name: Identifier::dummy("before_leaving"), parameters: None, signature: None, elements: vec![ @@ -632,12 +632,12 @@ before_leaving : subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Foreach( - vec![Identifier("specimen")], - Box::new(Expression::Variable(Identifier("specimens"))) + vec![Identifier::dummy("specimen")], + Box::new(Expression::Variable(Identifier::dummy("specimens"))) )], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier( - "nursing_team" + attributes: vec![Attribute::Role(Identifier::dummy( + "nursing_team", ))], subscopes: vec![Scope::DependentBlock { ordinal: "a", @@ -671,7 +671,7 @@ before_leaving : subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("surgeon"))], + attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph(vec![ @@ -685,7 +685,7 @@ before_leaving : }] }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("anesthetist"))], + attributes: vec![Attribute::Role(Identifier::dummy("anesthetist"))], subscopes: vec![Scope::DependentBlock { ordinal: "b", description: vec![Paragraph(vec![ @@ -699,7 +699,7 @@ before_leaving : }] }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("nursing_team"))], + attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], subscopes: vec![Scope::DependentBlock { ordinal: "c", description: vec![Paragraph(vec![ @@ -764,7 +764,7 @@ fn parallel_role_assignments() { subscopes: substeps, } = &scopes[0] { - assert_eq!(*attributes, vec![Attribute::Role(Identifier("surgeon"))]); + assert_eq!(*attributes, vec![Attribute::Role(Identifier::dummy("surgeon"))]); assert_eq!(substeps.len(), 3); // a, b, c } else { panic!("Expected AttributedBlock for surgeon"); @@ -778,7 +778,7 @@ fn parallel_role_assignments() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier("anaesthetist"))] + vec![Attribute::Role(Identifier::dummy("anaesthetist"))] ); assert_eq!(substeps.len(), 1); // d } else { @@ -793,7 +793,7 @@ fn parallel_role_assignments() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier("nursing_team"))] + vec![Attribute::Role(Identifier::dummy("nursing_team"))] ); assert_eq!(substeps.len(), 2); // e, f } else { @@ -849,7 +849,7 @@ fn multiple_roles_with_dependent_substeps() { subscopes: substeps, } = &scopes[0] { - assert_eq!(*attributes, vec![Attribute::Role(Identifier("surgeon"))]); + assert_eq!(*attributes, vec![Attribute::Role(Identifier::dummy("surgeon"))]); assert_eq!(substeps.len(), 3); } else { panic!("Expected AttributedBlock for surgeon"); @@ -863,7 +863,7 @@ fn multiple_roles_with_dependent_substeps() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier("anaesthetist"))] + vec![Attribute::Role(Identifier::dummy("anaesthetist"))] ); assert_eq!(substeps.len(), 2); } else { @@ -878,7 +878,7 @@ fn multiple_roles_with_dependent_substeps() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier("nursing_team"))] + vec![Attribute::Role(Identifier::dummy("nursing_team"))] ); assert_eq!(substeps.len(), 3); } else { @@ -933,7 +933,7 @@ fn mixed_substeps_in_roles() { description: vec![Paragraph(vec![Descriptive::Text("Emergency response")])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("team_lead"))], + attributes: vec![Attribute::Role(Identifier::dummy("team_lead"))], subscopes: vec![ Scope::DependentBlock { ordinal: "a", @@ -1028,7 +1028,7 @@ fn naked_bindings() { descriptive, Ok(vec![Paragraph(vec![Descriptive::Binding( Box::new(Descriptive::Text("What is the result?")), - vec![Identifier("answer")] + vec![Identifier::dummy("answer")] )])]) ); @@ -1041,7 +1041,7 @@ fn naked_bindings() { Ok(vec![Paragraph(vec![ Descriptive::Binding( Box::new(Descriptive::Text("Enter your name")), - vec![Identifier("name")] + vec![Identifier::dummy("name")] ), Descriptive::Text("Continue with next step") ])]) @@ -1059,14 +1059,14 @@ fn naked_bindings() { Descriptive::Text("First"), Descriptive::Binding( Box::new(Descriptive::Application(Invocation { - target: Target::Local(Identifier("do_something")), + target: Target::Local(Identifier::dummy("do_something")), parameters: None, })), - vec![Identifier("result")] + vec![Identifier::dummy("result")] ), Descriptive::Binding( Box::new(Descriptive::Text("then describe the outcome")), - vec![Identifier("description")] + vec![Identifier::dummy("description")] ) ])]) ); @@ -1115,7 +1115,7 @@ second_section_second_procedure : source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("main_procedure"), + name: Identifier::dummy("main_procedure"), parameters: None, signature: None, elements: vec![Element::Steps(vec![ @@ -1124,13 +1124,13 @@ second_section_second_procedure : title: Some(Paragraph(vec![Descriptive::Text("First Section")])), body: Technique::Procedures(vec![ Procedure { - name: Identifier("first_section_first_procedure"), + name: Identifier::dummy("first_section_first_procedure"), parameters: None, signature: None, elements: vec![Element::Title("One dot One")] }, Procedure { - name: Identifier("first_section_second_procedure"), + name: Identifier::dummy("first_section_second_procedure"), parameters: None, signature: None, elements: vec![Element::Title("One dot Two")] @@ -1142,13 +1142,13 @@ second_section_second_procedure : title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), body: Technique::Procedures(vec![ Procedure { - name: Identifier("second_section_first_procedure"), + name: Identifier::dummy("second_section_first_procedure"), parameters: None, signature: None, elements: vec![Element::Title("Two dot One")] }, Procedure { - name: Identifier("second_section_second_procedure"), + name: Identifier::dummy("second_section_second_procedure"), parameters: None, signature: None, elements: vec![Element::Title("Two dot Two")] @@ -1206,8 +1206,8 @@ procedure_four : Concept -> Architecture } = &steps[0] { assert_eq!(section1_procs.len(), 2); - assert_eq!(section1_procs[0].name, Identifier("procedure_one")); - assert_eq!(section1_procs[1].name, Identifier("procedure_two")); + assert_eq!(section1_procs[0].name, Identifier::dummy("procedure_one")); + assert_eq!(section1_procs[1].name, Identifier::dummy("procedure_two")); } else { panic!("First section should contain procedures"); } @@ -1219,8 +1219,8 @@ procedure_four : Concept -> Architecture } = &steps[1] { assert_eq!(section2_procs.len(), 2); - assert_eq!(section2_procs[0].name, Identifier("procedure_three")); - assert_eq!(section2_procs[1].name, Identifier("procedure_four")); + assert_eq!(section2_procs[0].name, Identifier::dummy("procedure_three")); + assert_eq!(section2_procs[1].name, Identifier::dummy("procedure_four")); } else { panic!("Second section should contain procedures"); } @@ -1270,7 +1270,7 @@ III. Implementation source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("main_procedure"), + name: Identifier::dummy("main_procedure"), parameters: None, signature: None, elements: vec![Element::Steps(vec![ @@ -1286,7 +1286,7 @@ III. Implementation )])), body: Technique::Procedures(vec![ Procedure { - name: Identifier("requirements_and_architecture"), + name: Identifier::dummy("requirements_and_architecture"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("Concept")), @@ -1301,11 +1301,11 @@ III. Implementation description: vec![Paragraph(vec![ Descriptive::Text("Define Requirements"), Descriptive::Application(Invocation { - target: Target::Local(Identifier( - "define_requirements" + target: Target::Local(Identifier::dummy( + "define_requirements", )), parameters: Some(vec![Expression::Variable( - Identifier("concept") + Identifier::dummy("concept") )]), }), ])], @@ -1317,11 +1317,11 @@ III. Implementation description: vec![Paragraph(vec![ Descriptive::Text("Determine Architecture"), Descriptive::Application(Invocation { - target: Target::Local(Identifier( - "determine_architecture" + target: Target::Local(Identifier::dummy( + "determine_architecture", )), parameters: Some(vec![Expression::Variable( - Identifier("concept") + Identifier::dummy("concept") )]), }), ])], @@ -1331,7 +1331,7 @@ III. Implementation ])], }, Procedure { - name: Identifier("define_requirements"), + name: Identifier::dummy("define_requirements"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("Concept")), @@ -1340,7 +1340,7 @@ III. Implementation elements: vec![], }, Procedure { - name: Identifier("determine_architecture"), + name: Identifier::dummy("determine_architecture"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("Concept")), diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index bd96e717..b81dcc9b 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -717,6 +717,19 @@ impl<'i> Parser<'i> { parser } + /// Compute a span for `slice`, which must be a sub-slice of `self.source` + /// (or be derived from one). Uses pointer arithmetic against the current + /// `self.source` to recover the slice's offset within it. + fn span_of(&self, slice: &str) -> Span { + let inner = (slice.as_ptr() as usize) - (self + .source + .as_ptr() as usize); + Span { + offset: self.offset + inner, + length: slice.len(), + } + } + // because test cases and trivial single-line examples might omit an // ending newline, this also returns Ok if end of input is reached. fn require_newline(&mut self) -> Result<(), ParsingError> { @@ -926,11 +939,13 @@ impl<'i> Parser<'i> { let text = one.as_str(); let (name, parameters) = if let Some((before, list)) = text.split_once('(') { let before = before.trim(); - let name = validate_identifier(before).ok_or(ParsingError::InvalidIdentifier( - self.offset, - before.len(), - before.to_string(), - ))?; + let name = validate_identifier(before, self.span_of(before)).ok_or( + ParsingError::InvalidIdentifier( + self.offset, + before.len(), + before.to_string(), + ), + )?; // Extract parameters from parentheses if !list.ends_with(')') { @@ -943,13 +958,12 @@ impl<'i> Parser<'i> { } else { let mut params = Vec::new(); for item in list.split(',') { - let param = validate_identifier(item.trim_ascii()).ok_or( + let trimmed = item.trim_ascii(); + let param = validate_identifier(trimmed, self.span_of(trimmed)).ok_or( ParsingError::InvalidIdentifier( self.offset, - item.trim_ascii() - .len(), - item.trim_ascii() - .to_string(), + trimmed.len(), + trimmed.to_string(), ), )?; params.push(param); @@ -979,11 +993,13 @@ impl<'i> Parser<'i> { return Err(ParsingError::InvalidParameters(error_offset, param_width)); } - let name = validate_identifier(text).ok_or(ParsingError::InvalidIdentifier( - self.offset, - text.len(), - text.to_string(), - ))?; + let name = validate_identifier(text, self.span_of(text)).ok_or( + ParsingError::InvalidIdentifier( + self.offset, + text.len(), + text.to_string(), + ), + )?; (name, None) }; @@ -1487,7 +1503,7 @@ impl<'i> Parser<'i> { .unwrap(); // is_function() already checked let text = &content[0..paren]; - let target = validate_identifier(text); + let target = validate_identifier(text, self.span_of(text)); if target.is_none() { if text.contains(' ') || text.contains('<') || text.contains('>') { @@ -1513,8 +1529,8 @@ impl<'i> Parser<'i> { let identifier = self.read_identifier()?; if self.source.starts_with('"') { return Err(ParsingError::InvalidFunction( - self.offset - identifier.0.len(), - identifier.0.len(), + identifier.span.offset, + identifier.span.length, )); } Ok(Expression::Variable(identifier)) @@ -1787,11 +1803,13 @@ impl<'i> Parser<'i> { Some(i) => &content[0..i], }; - let identifier = validate_identifier(possible).ok_or(ParsingError::InvalidIdentifier( - self.offset, - possible.len(), - possible.to_string(), - ))?; + let identifier = validate_identifier(possible, self.span_of(possible)).ok_or( + ParsingError::InvalidIdentifier( + self.offset, + possible.len(), + possible.to_string(), + ), + )?; self.advance(possible.len()); @@ -2032,9 +2050,10 @@ impl<'i> Parser<'i> { if content.starts_with("https://") { Ok(Target::Remote(External(content))) } else { - let identifier = validate_identifier(content).ok_or_else(|| { - ParsingError::InvalidInvocation(start_offset + 1, content.len()) - })?; + let identifier = + validate_identifier(content, inner.span_of(content)).ok_or_else(|| { + ParsingError::InvalidInvocation(start_offset + 1, content.len()) + })?; Ok(Target::Local(identifier)) } }) @@ -2564,7 +2583,12 @@ impl<'i> Parser<'i> { // Check if it's the special @* "reset attribute" role if trimmed == "@*" { - let identifier = Identifier("*"); + // span points at the `*` character + let star = &trimmed[1..]; + let identifier = Identifier { + value: "*", + span: inner.span_of(star), + }; attributes.push(Attribute::Role(identifier)); } // Check if it's a regular role '@' @@ -2573,8 +2597,8 @@ impl<'i> Parser<'i> { .get(1) .ok_or(ParsingError::Expected(inner.offset, 0, "role name after @"))? .as_str(); - let identifier = - validate_identifier(role_name).ok_or(ParsingError::InvalidIdentifier( + let identifier = validate_identifier(role_name, inner.span_of(role_name)) + .ok_or(ParsingError::InvalidIdentifier( inner.offset, role_name.len(), role_name.to_string(), @@ -2591,8 +2615,8 @@ impl<'i> Parser<'i> { "place name after ^", ))? .as_str(); - let identifier = - validate_identifier(place_name).ok_or(ParsingError::InvalidIdentifier( + let identifier = validate_identifier(place_name, inner.span_of(place_name)) + .ok_or(ParsingError::InvalidIdentifier( inner.offset, place_name.len(), place_name.to_string(), diff --git a/src/problem/messages.rs b/src/problem/messages.rs index 0b9e03a9..eeb2ab5e 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -38,10 +38,10 @@ there was no more input remaining in the current scope. ParsingError::MissingParenthesis(_, _) => { let examples = vec![Descriptive::Binding( Box::new(Descriptive::Application(Invocation { - target: Target::Local(Identifier("mix_pangalactic_gargle_blaster")), + target: Target::Local(Identifier::dummy("mix_pangalactic_gargle_blaster")), parameters: None, })), - vec![Identifier("zaphod"), Identifier("trillian")], + vec![Identifier::dummy("zaphod"), Identifier::dummy("trillian")], )]; ( @@ -126,25 +126,25 @@ to be used when rendering the Technique. Common domains include ParsingError::InvalidIdentifier(_, _, _) => { let examples = vec![ Procedure { - name: Identifier("make_coffee"), + name: Identifier::dummy("make_coffee"), parameters: None, signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("attempt1"), + name: Identifier::dummy("attempt1"), parameters: None, signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("i"), + name: Identifier::dummy("i"), parameters: None, signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("l33t_hax0r"), + name: Identifier::dummy("l33t_hax0r"), parameters: None, signature: None, elements: Vec::new(), @@ -275,25 +275,25 @@ this form. ParsingError::InvalidDeclaration(_, _) => { let examples = vec![ Procedure { - name: Identifier("f"), + name: Identifier::dummy("f"), parameters: None, signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("implementation"), + name: Identifier::dummy("implementation"), parameters: None, signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("make_coffee"), + name: Identifier::dummy("make_coffee"), parameters: None, signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("f"), + name: Identifier::dummy("f"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -302,7 +302,7 @@ this form. elements: Vec::new(), }, Procedure { - name: Identifier("implementation"), + name: Identifier::dummy("implementation"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("Design")), @@ -311,7 +311,7 @@ this form. elements: Vec::new(), }, Procedure { - name: Identifier("make_coffee"), + name: Identifier::dummy("make_coffee"), parameters: None, signature: Some(Signature { requires: Genus::Naked(vec![Forma("Beans"), Forma("Milk")]), @@ -320,7 +320,7 @@ this form. elements: Vec::new(), }, Procedure { - name: Identifier("make_coffee"), + name: Identifier::dummy("make_coffee"), parameters: None, signature: Some(Signature { requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), @@ -329,8 +329,8 @@ this form. elements: Vec::new(), }, Procedure { - name: Identifier("make_coffee"), - parameters: Some(vec![Identifier("b"), Identifier("m")]), + name: Identifier::dummy("make_coffee"), + parameters: Some(vec![Identifier::dummy("b"), Identifier::dummy("m")]), signature: Some(Signature { requires: Genus::Naked(vec![Forma("Beans"), Forma("Milk")]), provides: Genus::Single(Forma("Coffee")), @@ -377,19 +377,19 @@ Finally, variables can be assigned for the names of the input parameters: ParsingError::InvalidParameters(_, _) => { let examples = vec![ Procedure { - name: Identifier("create_bypass"), - parameters: Some(vec![Identifier("a"), Identifier("b")]), + name: Identifier::dummy("create_bypass"), + parameters: Some(vec![Identifier::dummy("a"), Identifier::dummy("b")]), signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("bulldoze"), - parameters: Some(vec![Identifier("c")]), + name: Identifier::dummy("bulldoze"), + parameters: Some(vec![Identifier::dummy("c")]), signature: None, elements: Vec::new(), }, Procedure { - name: Identifier("lawsuit"), + name: Identifier::dummy("lawsuit"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("Council")), @@ -398,8 +398,8 @@ Finally, variables can be assigned for the names of the input parameters: elements: Vec::new(), }, Procedure { - name: Identifier("lawsuit"), - parameters: Some(vec![Identifier("c")]), + name: Identifier::dummy("lawsuit"), + parameters: Some(vec![Identifier::dummy("c")]), signature: Some(Signature { requires: Genus::Single(Forma("Council")), provides: Genus::List(Forma("Penny")), @@ -456,12 +456,12 @@ author of the Technique. ParsingError::InvalidInvocation(_, _) => { let examples = vec![ Invocation { - target: Target::Local(Identifier("make_coffee")), + target: Target::Local(Identifier::dummy("make_coffee")), parameters: None, }, Invocation { - target: Target::Local(Identifier("check_vitals")), - parameters: Some(vec![Expression::Variable(Identifier("patient"))]), + target: Target::Local(Identifier::dummy("check_vitals")), + parameters: Some(vec![Expression::Variable(Identifier::dummy("patient"))]), }, ]; @@ -487,18 +487,18 @@ If the procedure takes parameters they can be specified in parenthesis: ParsingError::InvalidFunction(_, _) => { let examples = vec![ Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::String(vec![Piece::Text("ls -la")])], }, Function { - target: Identifier("now"), + target: Identifier::dummy("now"), parameters: vec![], }, Function { - target: Identifier("calculate"), + target: Identifier::dummy("calculate"), parameters: vec![ - Expression::Variable(Identifier("a")), - Expression::Variable(Identifier("b")), + Expression::Variable(Identifier::dummy("a")), + Expression::Variable(Identifier::dummy("b")), ], }, ]; @@ -526,13 +526,13 @@ expressions as parameters as required: ParsingError::InvalidCodeBlock(_, _) => { let examples = vec![ Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::String(vec![Piece::Text("command")])], }), Expression::Repeat(Box::new(Expression::Number(Numeric::Integral(5)))), Expression::Foreach( - vec![Identifier("patient")], - Box::new(Expression::Variable(Identifier("patients"))), + vec![Identifier::dummy("patient")], + Box::new(Expression::Variable(Identifier::dummy("patients"))), ), ]; @@ -634,16 +634,16 @@ parallel steps, but again this is not compulsory. let examples = vec![ Scope::AttributeBlock { attributes: vec![ - Attribute::Role(Identifier("president_of_the_galaxy")), - Attribute::Role(Identifier("femme_fatale")), + Attribute::Role(Identifier::dummy("president_of_the_galaxy")), + Attribute::Role(Identifier::dummy("femme_fatale")), ], subscopes: vec![], }, Scope::AttributeBlock { attributes: vec![ - Attribute::Place(Identifier("milliways")), - Attribute::Role(Identifier("waiter")), - Attribute::Role(Identifier("dish_of_the_day")), + Attribute::Place(Identifier::dummy("milliways")), + Attribute::Role(Identifier::dummy("waiter")), + Attribute::Role(Identifier::dummy("dish_of_the_day")), ], subscopes: vec![], }, @@ -672,12 +672,12 @@ nested underneath a role or place assignment. ParsingError::InvalidForeach(_, _) => { let examples = vec![ Expression::Foreach( - vec![Identifier("patient")], - Box::new(Expression::Variable(Identifier("patients"))), + vec![Identifier::dummy("patient")], + Box::new(Expression::Variable(Identifier::dummy("patients"))), ), Expression::Foreach( - vec![Identifier("name"), Identifier("value")], - Box::new(Expression::Variable(Identifier("data"))), + vec![Identifier::dummy("name"), Identifier::dummy("value")], + Box::new(Expression::Variable(Identifier::dummy("data"))), ), ]; diff --git a/tests/formatting/formatter.rs b/tests/formatting/formatter.rs index 61fd1c8b..aaa98d42 100644 --- a/tests/formatting/formatter.rs +++ b/tests/formatting/formatter.rs @@ -46,7 +46,7 @@ mod verify { source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("first"), + name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -76,7 +76,7 @@ first : A -> B }), body: Some(Technique::Procedures(vec![ Procedure { - name: Identifier("first"), + name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("A")), @@ -85,7 +85,7 @@ first : A -> B elements: vec![], }, Procedure { - name: Identifier("second"), + name: Identifier::dummy("second"), parameters: None, signature: Some(Signature { requires: Genus::List(Forma("Thing")), @@ -118,7 +118,7 @@ second : [Thing] -> (Who, Where, Why) source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("win_le_tour"), + name: Identifier::dummy("win_le_tour"), parameters: None, signature: Some(Signature { requires: Genus::Single(Forma("Bicycle")), @@ -182,11 +182,11 @@ win_le_tour : Bicycle -> YellowJersey source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("vibe_coding"), + name: Identifier::dummy("vibe_coding"), parameters: None, signature: None, elements: vec![Element::CodeBlock(vec![Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline(Some("bash"), vec!["rm -rf /"])], })])], }])), @@ -216,7 +216,7 @@ vibe_coding : source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("action"), + name: Identifier::dummy("action"), parameters: None, signature: None, elements: vec![ @@ -228,7 +228,7 @@ vibe_coding : description: vec![Paragraph(vec![ Descriptive::Text("To take the action, we must:"), Descriptive::CodeInline(Expression::Execution(Function { - target: Identifier("exec"), + target: Identifier::dummy("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec!["rm -rf /"], @@ -266,7 +266,7 @@ We must take action! source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("journal"), + name: Identifier::dummy("journal"), parameters: None, signature: None, elements: vec![ @@ -279,19 +279,19 @@ We must take action! "Record event as it happens", )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier("journalist"))], + attributes: vec![Attribute::Role(Identifier::dummy("journalist"))], subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Tablet(vec![ Pair { label: "timestamp", value: Expression::Execution(Function { - target: Identifier("now"), + target: Identifier::dummy("now"), parameters: vec![], }), }, Pair { label: "message", - value: Expression::Variable(Identifier("msg")), + value: Expression::Variable(Identifier::dummy("msg")), }, ])], subscopes: vec![], @@ -330,7 +330,7 @@ Record everything, with timestamps. source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("before_leaving"), + name: Identifier::dummy("before_leaving"), parameters: None, signature: None, elements: vec![ @@ -360,11 +360,11 @@ Record everything, with timestamps. )])], subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Foreach( - vec![Identifier("specimen")], - Box::new(Expression::Variable(Identifier("specimens"))), + vec![Identifier::dummy("specimen")], + Box::new(Expression::Variable(Identifier::dummy("specimens"))), )], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier( + attributes: vec![Attribute::Role(Identifier::dummy( "nursing_team", ))], subscopes: vec![Scope::DependentBlock { @@ -424,7 +424,7 @@ before_leaving : source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("main_procedure"), + name: Identifier::dummy("main_procedure"), parameters: None, signature: None, elements: vec![Element::Steps(vec![ @@ -470,7 +470,7 @@ III. source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier("test_procedure"), + name: Identifier::dummy("test_procedure"), parameters: None, signature: None, elements: vec![Element::Steps(vec![ From 5601867eba087635a05c4693345b9cd0527653ca Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Wed, 6 May 2026 22:26:11 +1000 Subject: [PATCH 3/9] Update Forma to carry Span --- src/domain/engine.rs | 4 +- src/formatting/formatter.rs | 43 +++++---- src/language/types.rs | 164 +++++++++++++++++++++------------- src/parsing/checks/parser.rs | 34 +++---- src/parsing/checks/verify.rs | 42 ++++----- src/parsing/parser.rs | 32 ++++--- src/problem/messages.rs | 54 +++++------ tests/formatting/formatter.rs | 16 ++-- 8 files changed, 219 insertions(+), 170 deletions(-) diff --git a/src/domain/engine.rs b/src/domain/engine.rs index fce6c61f..780f3cac 100644 --- a/src/domain/engine.rs +++ b/src/domain/engine.rs @@ -301,7 +301,7 @@ fn render_expression(expr: &Expression) -> String { Expression::Application(inv) => { let name = match &inv.target { Target::Local(id) => id.value, - Target::Remote(ext) => ext.0, + Target::Remote(ext) => ext.value, }; if let Some(params) = &inv.parameters { let args: Vec<_> = params @@ -477,7 +477,7 @@ impl<'i> Paragraph<'i> { fn invocation_name(inv: &crate::language::Invocation<'i>) -> &'i str { match &inv.target { crate::language::Target::Local(id) => id.value, - crate::language::Target::Remote(ext) => ext.0, + crate::language::Target::Remote(ext) => ext.value, } } } diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index b0a0a72e..8a264c43 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -621,7 +621,7 @@ impl<'i> Formatter<'i> { } pub fn append_forma(&mut self, forma: &'i Forma) { - self.add_fragment_reference(Syntax::Forma, forma.0) + self.add_fragment_reference(Syntax::Forma, forma.value) } fn append_paragraphs(&mut self, paragraphs: &'i Vec) { @@ -1129,7 +1129,7 @@ impl<'i> Formatter<'i> { Target::Local(identifier) => { self.add_fragment_reference(Syntax::Invocation, identifier.value) } - Target::Remote(external) => self.add_fragment_reference(Syntax::Invocation, external.0), + Target::Remote(external) => self.add_fragment_reference(Syntax::Invocation, external.value), } self.add_fragment_reference(Syntax::Quote, ">"); if let Some(parameters) = &invocation.parameters { @@ -1408,7 +1408,8 @@ mod check { fn genus() { let mut output = Formatter::new(78); - output.append_forma(&Forma("Jedi")); + let forma = Forma::dummy("Jedi"); + output.append_forma(&forma); assert_eq!(output.to_string(), "Jedi"); output.reset(); @@ -1416,19 +1417,21 @@ mod check { assert_eq!(output.to_string(), "()"); output.reset(); - output.append_genus(&Genus::Single(Forma("Stormtrooper"))); + let single = Genus::Single(Forma::dummy("Stormtrooper")); + output.append_genus(&single); assert_eq!(output.to_string(), "Stormtrooper"); output.reset(); - output.append_genus(&Genus::List(Forma("Pilot"))); + let list = Genus::List(Forma::dummy("Pilot")); + output.append_genus(&list); assert_eq!(output.to_string(), "[Pilot]"); output.reset(); let genus = Genus::Tuple(vec![ - Forma("Kid"), - Forma("Pilot"), - Forma("Scoundrel"), - Forma("Princess"), + Forma::dummy("Kid"), + Forma::dummy("Pilot"), + Forma::dummy("Scoundrel"), + Forma::dummy("Princess"), ]); output.append_genus(&genus); assert_eq!(output.to_string(), "(Kid, Pilot, Scoundrel, Princess)"); @@ -1440,23 +1443,25 @@ mod check { fn signatures() { let mut output = Formatter::new(78); - output.append_signature(&Signature { - requires: Genus::Single(Forma("Alderaan")), - provides: Genus::Single(Forma("AsteroidField")), - }); + let sig = Signature { + requires: Genus::Single(Forma::dummy("Alderaan")), + provides: Genus::Single(Forma::dummy("AsteroidField")), + }; + output.append_signature(&sig); assert_eq!(output.to_string(), "Alderaan -> AsteroidField"); output.reset(); - output.append_signature(&Signature { - requires: Genus::List(Forma("Clone")), - provides: Genus::Single(Forma("Army")), - }); + let sig = Signature { + requires: Genus::List(Forma::dummy("Clone")), + provides: Genus::Single(Forma::dummy("Army")), + }; + output.append_signature(&sig); assert_eq!(output.to_string(), "[Clone] -> Army"); output.reset(); let signature = Signature { - requires: Genus::Single(Forma("TaxationOfTradeRoutes")), - provides: Genus::Tuple(vec![Forma("Rebels"), Forma("Empire")]), + requires: Genus::Single(Forma::dummy("TaxationOfTradeRoutes")), + provides: Genus::Tuple(vec![Forma::dummy("Rebels"), Forma::dummy("Empire")]), }; output.append_signature(&signature); assert_eq!( diff --git a/src/language/types.rs b/src/language/types.rs index ae84c962..1aef18ba 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -75,7 +75,6 @@ pub struct Identifier<'i> { pub span: Span, } -/// Equality is structural: spans are diagnostic metadata, not part of identity. impl PartialEq for Identifier<'_> { fn eq(&self, other: &Self) -> bool { self.value == other.value @@ -83,15 +82,30 @@ impl PartialEq for Identifier<'_> { } impl<'i> Identifier<'i> { - /// Test helper: builds an `Identifier` with a default span. Real parser - /// output always carries a real span; only fixtures should use this. - pub fn dummy(value: &'i str) -> Self { - Identifier { value, span: Span::default() } + /// Test helper: builds an `Identifier` with a default span. See also the + /// `PartialEq` instance. + pub const fn dummy(value: &'i str) -> Self { + Identifier { value, span: Span { offset: 0, length: 0 } } } } -#[derive(Eq, Debug, PartialEq)] -pub struct External<'i>(pub &'i str); +#[derive(Eq, Debug)] +pub struct External<'i> { + pub value: &'i str, + pub span: Span, +} + +impl PartialEq for External<'_> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl<'i> External<'i> { + pub const fn dummy(value: &'i str) -> Self { + External { value, span: Span { offset: 0, length: 0 } } + } +} #[derive(Eq, Debug, PartialEq)] pub enum Target<'i> { @@ -99,8 +113,23 @@ pub enum Target<'i> { Remote(External<'i>), } -#[derive(Eq, Debug, PartialEq)] -pub struct Forma<'i>(pub &'i str); +#[derive(Eq, Debug)] +pub struct Forma<'i> { + pub value: &'i str, + pub span: Span, +} + +impl PartialEq for Forma<'_> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl<'i> Forma<'i> { + pub const fn dummy(value: &'i str) -> Self { + Forma { value, span: Span { offset: 0, length: 0 } } + } +} #[derive(Eq, Debug, PartialEq)] pub enum Genus<'i> { @@ -284,7 +313,7 @@ pub(crate) fn validate_identifier(input: &str, span: Span) -> Option Option> { +pub(crate) fn validate_forma(input: &str, span: Span) -> Option> { if input.len() == 0 { return None; } @@ -305,15 +334,24 @@ pub(crate) fn validate_forma(input: &str) -> Option> { } } - Some(Forma(input)) + Some(Forma { value: input, span }) +} + +/// `child` must be a sub-slice of `parent`. +fn sub_span(parent: &str, child: &str, parent_span: Span) -> Span { + let inner = (child.as_ptr() as usize) - (parent.as_ptr() as usize); + Span { + offset: parent_span.offset + inner, + length: child.len(), + } } -fn parse_tuple(input: &str) -> Option>> { +fn parse_tuple(input: &str, span: Span) -> Option>> { let mut formas: Vec = Vec::new(); for text in input.split(",") { let text = text.trim_ascii(); - let forma = validate_forma(text)?; + let forma = validate_forma(text, sub_span(input, text, span))?; formas.push(forma); } @@ -321,7 +359,7 @@ fn parse_tuple(input: &str) -> Option>> { } /// This one copes with (and discards) any internal whitespace encountered. -pub(crate) fn validate_genus(input: &str) -> Option> { +pub(crate) fn validate_genus(input: &str, span: Span) -> Option> { let first = input .chars() .next() @@ -340,7 +378,7 @@ pub(crate) fn validate_genus(input: &str) -> Option> { return None; } - let forma = validate_forma(content)?; + let forma = validate_forma(content, sub_span(input, content, span))?; Some(Genus::List(forma)) } @@ -356,7 +394,7 @@ pub(crate) fn validate_genus(input: &str) -> Option> { return Some(Genus::Unit); } - let formas = parse_tuple(content)?; + let formas = parse_tuple(content, sub_span(input, content, span))?; Some(Genus::Tuple(formas)) } _ => { @@ -366,10 +404,10 @@ pub(crate) fn validate_genus(input: &str) -> Option> { // Check if this is a bare tuple (comma-separated but non-parenthesized) if input.contains(',') { - let formas = parse_tuple(input)?; + let formas = parse_tuple(input, span)?; Some(Genus::Naked(formas)) } else { - let forma = validate_forma(input)?; + let forma = validate_forma(input, span)?; Some(Genus::Single(forma)) } } @@ -430,128 +468,128 @@ mod check { #[test] fn forma_rules() { - assert_eq!(validate_forma("A"), Some(Forma("A"))); - assert_eq!(validate_forma("Beans"), Some(Forma("Beans"))); - assert_eq!(validate_forma("lower"), None); + assert_eq!(validate_forma("A", Span::default()), Some(Forma::dummy("A"))); + assert_eq!(validate_forma("Beans", Span::default()), Some(Forma::dummy("Beans"))); + assert_eq!(validate_forma("lower", Span::default()), None); } #[test] fn genus_rules_single() { - assert_eq!(validate_genus("A"), Some(Genus::Single(Forma("A")))); + assert_eq!(validate_genus("A", Span::default()), Some(Genus::Single(Forma::dummy("A")))); } #[test] fn genus_rules_list() { - assert_eq!(validate_genus("[A]"), Some(Genus::List(Forma("A")))); + assert_eq!(validate_genus("[A]", Span::default()), Some(Genus::List(Forma::dummy("A")))); // Test list with whitespace assert_eq!( - validate_genus("[ Input ]"), - Some(Genus::List(Forma("Input"))) + validate_genus("[ Input ]", Span::default()), + Some(Genus::List(Forma::dummy("Input"))) ); assert_eq!( - validate_genus("[\tOutput\t]"), - Some(Genus::List(Forma("Output"))) + validate_genus("[\tOutput\t]", Span::default()), + Some(Genus::List(Forma::dummy("Output"))) ); // Test malformed lists - assert_eq!(validate_genus("[Input"), None); - assert_eq!(validate_genus("Input]"), None); + assert_eq!(validate_genus("[Input", Span::default()), None); + assert_eq!(validate_genus("Input]", Span::default()), None); } #[test] fn genus_rules_tuple_parens() { assert_eq!( - validate_genus("(A, B)"), - Some(Genus::Tuple(vec![Forma("A"), Forma("B")])) + validate_genus("(A, B)", Span::default()), + Some(Genus::Tuple(vec![Forma::dummy("A"), Forma::dummy("B")])) ); assert_eq!( - validate_genus("(Coffee, Tea)"), - Some(Genus::Tuple(vec![Forma("Coffee"), Forma("Tea")])) + validate_genus("(Coffee, Tea)", Span::default()), + Some(Genus::Tuple(vec![Forma::dummy("Coffee"), Forma::dummy("Tea")])) ); // not actually sure whether we should be normalizing this? Probably // not, because formatting and linting is a separate concern. - assert_eq!(validate_genus("(A)"), Some(Genus::Tuple(vec![Forma("A")]))); + assert_eq!(validate_genus("(A)", Span::default()), Some(Genus::Tuple(vec![Forma::dummy("A")]))); // Test parenthesized tuples with whitespace assert_eq!( - validate_genus("( A , B )"), - Some(Genus::Tuple(vec![Forma("A"), Forma("B")])) + validate_genus("( A , B )", Span::default()), + Some(Genus::Tuple(vec![Forma::dummy("A"), Forma::dummy("B")])) ); assert_eq!( - validate_genus("(\tA\t,\tB\t)"), - Some(Genus::Tuple(vec![Forma("A"), Forma("B")])) + validate_genus("(\tA\t,\tB\t)", Span::default()), + Some(Genus::Tuple(vec![Forma::dummy("A"), Forma::dummy("B")])) ); // Test malformed tuples - assert_eq!(validate_genus("(Input"), None); - assert_eq!(validate_genus("Input)"), None); + assert_eq!(validate_genus("(Input", Span::default()), None); + assert_eq!(validate_genus("Input)", Span::default()), None); } #[test] fn genus_rules_tuple_bare() { assert_eq!( - validate_genus("A, B"), - Some(Genus::Naked(vec![Forma("A"), Forma("B")])) + validate_genus("A, B", Span::default()), + Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) ); assert_eq!( - validate_genus("Coffee, Tea"), - Some(Genus::Naked(vec![Forma("Coffee"), Forma("Tea")])) + validate_genus("Coffee, Tea", Span::default()), + Some(Genus::Naked(vec![Forma::dummy("Coffee"), Forma::dummy("Tea")])) ); assert_eq!( - validate_genus("Input, Data, Config"), + validate_genus("Input, Data, Config", Span::default()), Some(Genus::Naked(vec![ - Forma("Input"), - Forma("Data"), - Forma("Config") + Forma::dummy("Input"), + Forma::dummy("Data"), + Forma::dummy("Config") ])) ); assert_eq!( - validate_genus("A,B"), - Some(Genus::Naked(vec![Forma("A"), Forma("B")])) + validate_genus("A,B", Span::default()), + Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) ); assert_eq!( - validate_genus("A , B"), - Some(Genus::Naked(vec![Forma("A"), Forma("B")])) + validate_genus("A , B", Span::default()), + Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) ); // Test edge cases with whitespace assert_eq!( - validate_genus(" A , B "), - Some(Genus::Naked(vec![Forma("A"), Forma("B")])) + validate_genus(" A , B ", Span::default()), + Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) ); assert_eq!( - validate_genus("\tA\t,\tB\t"), - Some(Genus::Naked(vec![Forma("A"), Forma("B")])) + validate_genus("\tA\t,\tB\t", Span::default()), + Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) ); } #[test] fn genus_rules_unit() { - assert_eq!(validate_genus("()"), Some(Genus::Unit)); + assert_eq!(validate_genus("()", Span::default()), Some(Genus::Unit)); // Test unit with whitespace - assert_eq!(validate_genus("( )"), Some(Genus::Unit)); - assert_eq!(validate_genus("(\t)"), Some(Genus::Unit)); + assert_eq!(validate_genus("( )", Span::default()), Some(Genus::Unit)); + assert_eq!(validate_genus("(\t)", Span::default()), Some(Genus::Unit)); } #[test] fn genus_rules_malformed() { // Test malformed brackets/parens - assert_eq!(validate_genus("[Input"), None); - assert_eq!(validate_genus("Input]"), None); - assert_eq!(validate_genus("(Input"), None); - assert_eq!(validate_genus("Input)"), None); + assert_eq!(validate_genus("[Input", Span::default()), None); + assert_eq!(validate_genus("Input]", Span::default()), None); + assert_eq!(validate_genus("(Input", Span::default()), None); + assert_eq!(validate_genus("Input)", Span::default()), None); } #[test] fn license_rules() { diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index c67e2bd2..99399ab6 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -159,8 +159,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")) + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")) }) ); @@ -169,8 +169,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::Single(Forma("Beans")), - provides: Genus::Single(Forma("Coffee")) + requires: Genus::Single(Forma::dummy("Beans")), + provides: Genus::Single(Forma::dummy("Coffee")) }) ); @@ -179,8 +179,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::List(Forma("Bits")), - provides: Genus::Single(Forma("Bob")) + requires: Genus::List(Forma::dummy("Bits")), + provides: Genus::Single(Forma::dummy("Bob")) }) ); @@ -189,8 +189,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::Single(Forma("Complex")), - provides: Genus::Tuple(vec![Forma("Real"), Forma("Imaginary")]) + requires: Genus::Single(Forma::dummy("Complex")), + provides: Genus::Tuple(vec![Forma::dummy("Real"), Forma::dummy("Imaginary")]) }) ); } @@ -219,8 +219,8 @@ fn declaration_full() { Identifier::dummy("f"), None, Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")) + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")) }) )) ); @@ -235,8 +235,8 @@ fn declaration_full() { Identifier::dummy("making_coffee"), None, Some(Signature { - requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::List(Forma("Coffee")) + requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::List(Forma::dummy("Coffee")) }) )) ); @@ -292,8 +292,8 @@ making_coffee : Identifier::dummy("making_coffee"), None, Some(Signature { - requires: Genus::Single(Forma("Ingredients")), - provides: Genus::Single(Forma("Coffee")) + requires: Genus::Single(Forma::dummy("Ingredients")), + provides: Genus::Single(Forma::dummy("Coffee")) }) )) ); @@ -315,8 +315,8 @@ making_coffee(b, m) : Identifier::dummy("making_coffee"), Some(vec![Identifier::dummy("b"), Identifier::dummy("m")]), Some(Signature { - requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::Single(Forma("Coffee")) + requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::Single(Forma::dummy("Coffee")) }) )) ); @@ -480,7 +480,7 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Remote(External("https://example.com/proc")), + target: Target::Remote(External::dummy("https://example.com/proc")), parameters: None }) ); diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index 26399cc6..4e811b2b 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -61,8 +61,8 @@ making_coffee : (Beans, Milk) -> Coffee name: Identifier::dummy("making_coffee"), parameters: None, signature: Some(Signature { - requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::Single(Forma("Coffee")) + requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::Single(Forma::dummy("Coffee")) }), elements: vec![], }) @@ -88,8 +88,8 @@ second : C -> D name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")) + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")) }), elements: vec![], }) @@ -102,8 +102,8 @@ second : C -> D name: Identifier::dummy("second"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("C")), - provides: Genus::Single(Forma("D")) + requires: Genus::Single(Forma::dummy("C")), + provides: Genus::Single(Forma::dummy("D")) }), elements: vec![], }) @@ -127,8 +127,8 @@ making_coffee(e) : Ingredients -> Coffee name: Identifier::dummy("making_coffee"), parameters: Some(vec![Identifier::dummy("e")]), signature: Some(Signature { - requires: Genus::Single(Forma("Ingredients")), - provides: Genus::Single(Forma("Coffee")) + requires: Genus::Single(Forma::dummy("Ingredients")), + provides: Genus::Single(Forma::dummy("Coffee")) }), elements: vec![], }) @@ -159,8 +159,8 @@ This is the first one. name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")) + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")) }), elements: vec![ Element::Title("The First"), @@ -214,8 +214,8 @@ This is the first one. name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")) + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")) }), elements: vec![ Element::Title("The First"), @@ -280,8 +280,8 @@ This is the first one. name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")) + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")) }), elements: vec![ Element::Title("The First"), @@ -1289,10 +1289,10 @@ III. Implementation name: Identifier::dummy("requirements_and_architecture"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("Concept")), + requires: Genus::Single(Forma::dummy("Concept")), provides: Genus::Naked(vec![ - Forma("Requirements"), - Forma("Architecture") + Forma::dummy("Requirements"), + Forma::dummy("Architecture") ]), }), elements: vec![Element::Steps(vec![ @@ -1334,8 +1334,8 @@ III. Implementation name: Identifier::dummy("define_requirements"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("Concept")), - provides: Genus::Single(Forma("Requirements")), + requires: Genus::Single(Forma::dummy("Concept")), + provides: Genus::Single(Forma::dummy("Requirements")), }), elements: vec![], }, @@ -1343,8 +1343,8 @@ III. Implementation name: Identifier::dummy("determine_architecture"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("Concept")), - provides: Genus::Single(Forma("Architecture")), + requires: Genus::Single(Forma::dummy("Concept")), + provides: Genus::Single(Forma::dummy("Architecture")), }), elements: vec![], }, diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index b81dcc9b..dfcd9189 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -717,9 +717,7 @@ impl<'i> Parser<'i> { parser } - /// Compute a span for `slice`, which must be a sub-slice of `self.source` - /// (or be derived from one). Uses pointer arithmetic against the current - /// `self.source` to recover the slice's offset within it. + /// `slice` must be a sub-slice of `self.source`. fn span_of(&self, slice: &str) -> Span { let inner = (slice.as_ptr() as usize) - (self .source @@ -897,14 +895,20 @@ impl<'i> Parser<'i> { "a Genus for the provides", ))?; - let requires = validate_genus(one.as_str()).ok_or(ParsingError::InvalidGenus( - self.offset + one.start(), - one.len(), - ))?; - let provides = validate_genus(two.as_str()).ok_or(ParsingError::InvalidGenus( - self.offset + two.start(), - two.len(), - ))?; + let one_span = Span { + offset: self.offset + one.start(), + length: one.len(), + }; + let two_span = Span { + offset: self.offset + two.start(), + length: two.len(), + }; + let requires = validate_genus(one.as_str(), one_span).ok_or( + ParsingError::InvalidGenus(one_span.offset, one_span.length), + )?; + let provides = validate_genus(two.as_str(), two_span).ok_or( + ParsingError::InvalidGenus(two_span.offset, two_span.length), + )?; Ok(Signature { requires, provides }) } @@ -2048,7 +2052,10 @@ impl<'i> Parser<'i> { .source .trim(); if content.starts_with("https://") { - Ok(Target::Remote(External(content))) + Ok(Target::Remote(External { + value: content, + span: inner.span_of(content), + })) } else { let identifier = validate_identifier(content, inner.span_of(content)).ok_or_else(|| { @@ -2583,7 +2590,6 @@ impl<'i> Parser<'i> { // Check if it's the special @* "reset attribute" role if trimmed == "@*" { - // span points at the `*` character let star = &trimmed[1..]; let identifier = Identifier { value: "*", diff --git a/src/problem/messages.rs b/src/problem/messages.rs index eeb2ab5e..d2a75ba5 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -174,9 +174,9 @@ letters, numbers, and underscores. Valid examples include: } ParsingError::InvalidForma(_, _) => { let examples = vec![ - Forma("Coffee"), - Forma("Ingredients"), - Forma("PatientRecord"), + Forma::dummy("Coffee"), + Forma::dummy("Ingredients"), + Forma::dummy("PatientRecord"), ]; ( @@ -200,10 +200,10 @@ For example: } ParsingError::InvalidGenus(_, _) => { let examples = vec![ - Genus::Single(Forma("Coffee")), - Genus::Tuple(vec![Forma("Beans"), Forma("Water")]), - Genus::Naked(vec![Forma("Beans"), Forma("Water")]), - Genus::List(Forma("Patient")), + Genus::Single(Forma::dummy("Coffee")), + Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Water")]), + Genus::Naked(vec![Forma::dummy("Beans"), Forma::dummy("Water")]), + Genus::List(Forma::dummy("Patient")), Genus::Unit, ]; @@ -237,16 +237,16 @@ doesn't have an input or result, per se. ParsingError::InvalidSignature(_, _) => { let examples = vec![ Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")), + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")), }, Signature { - requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::Single(Forma("Coffee")), + requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::Single(Forma::dummy("Coffee")), }, Signature { - requires: Genus::List(Forma("FunctionalRequirement")), - provides: Genus::Single(Forma("Architecture")), + requires: Genus::List(Forma::dummy("FunctionalRequirement")), + provides: Genus::Single(Forma::dummy("Architecture")), }, ]; @@ -296,8 +296,8 @@ this form. name: Identifier::dummy("f"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")), + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")), }), elements: Vec::new(), }, @@ -305,8 +305,8 @@ this form. name: Identifier::dummy("implementation"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("Design")), - provides: Genus::Single(Forma("Product")), + requires: Genus::Single(Forma::dummy("Design")), + provides: Genus::Single(Forma::dummy("Product")), }), elements: Vec::new(), }, @@ -314,8 +314,8 @@ this form. name: Identifier::dummy("make_coffee"), parameters: None, signature: Some(Signature { - requires: Genus::Naked(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::Single(Forma("Coffee")), + requires: Genus::Naked(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::Single(Forma::dummy("Coffee")), }), elements: Vec::new(), }, @@ -323,8 +323,8 @@ this form. name: Identifier::dummy("make_coffee"), parameters: None, signature: Some(Signature { - requires: Genus::Tuple(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::Single(Forma("Coffee")), + requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::Single(Forma::dummy("Coffee")), }), elements: Vec::new(), }, @@ -332,8 +332,8 @@ this form. name: Identifier::dummy("make_coffee"), parameters: Some(vec![Identifier::dummy("b"), Identifier::dummy("m")]), signature: Some(Signature { - requires: Genus::Naked(vec![Forma("Beans"), Forma("Milk")]), - provides: Genus::Single(Forma("Coffee")), + requires: Genus::Naked(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), + provides: Genus::Single(Forma::dummy("Coffee")), }), elements: Vec::new(), }, @@ -392,8 +392,8 @@ Finally, variables can be assigned for the names of the input parameters: name: Identifier::dummy("lawsuit"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("Council")), - provides: Genus::List(Forma("Penny")), + requires: Genus::Single(Forma::dummy("Council")), + provides: Genus::List(Forma::dummy("Penny")), }), elements: Vec::new(), }, @@ -401,8 +401,8 @@ Finally, variables can be assigned for the names of the input parameters: name: Identifier::dummy("lawsuit"), parameters: Some(vec![Identifier::dummy("c")]), signature: Some(Signature { - requires: Genus::Single(Forma("Council")), - provides: Genus::List(Forma("Penny")), + requires: Genus::Single(Forma::dummy("Council")), + provides: Genus::List(Forma::dummy("Penny")), }), elements: Vec::new(), }, diff --git a/tests/formatting/formatter.rs b/tests/formatting/formatter.rs index aaa98d42..e09d613f 100644 --- a/tests/formatting/formatter.rs +++ b/tests/formatting/formatter.rs @@ -49,8 +49,8 @@ mod verify { name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")), + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")), }), elements: vec![], }])), @@ -79,8 +79,8 @@ first : A -> B name: Identifier::dummy("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("A")), - provides: Genus::Single(Forma("B")), + requires: Genus::Single(Forma::dummy("A")), + provides: Genus::Single(Forma::dummy("B")), }), elements: vec![], }, @@ -88,8 +88,8 @@ first : A -> B name: Identifier::dummy("second"), parameters: None, signature: Some(Signature { - requires: Genus::List(Forma("Thing")), - provides: Genus::Tuple(vec![Forma("Who"), Forma("Where"), Forma("Why")]), + requires: Genus::List(Forma::dummy("Thing")), + provides: Genus::Tuple(vec![Forma::dummy("Who"), Forma::dummy("Where"), Forma::dummy("Why")]), }), elements: vec![], }, @@ -121,8 +121,8 @@ second : [Thing] -> (Who, Where, Why) name: Identifier::dummy("win_le_tour"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma("Bicycle")), - provides: Genus::Single(Forma("YellowJersey")), + requires: Genus::Single(Forma::dummy("Bicycle")), + provides: Genus::Single(Forma::dummy("YellowJersey")), }), elements: vec![Element::Steps(vec![ Scope::DependentBlock { From cb40b825f4cd4a0d08982108c9f725686537f0bf Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Wed, 6 May 2026 23:55:10 +1000 Subject: [PATCH 4/9] Add Span to Scope, Procedure, and Element --- src/domain/engine.rs | 12 +- src/formatting/formatter.rs | 21 +- src/language/types.rs | 191 +++++- src/parsing/checks/parser.rs | 77 ++- src/parsing/checks/verify.rs | 1053 +++++++++++++++++++-------------- src/parsing/parser.rs | 140 +++-- src/problem/messages.rs | 18 + tests/formatting/formatter.rs | 420 +++++++------ 8 files changed, 1207 insertions(+), 725 deletions(-) diff --git a/src/domain/engine.rs b/src/domain/engine.rs index 780f3cac..4e435be8 100644 --- a/src/domain/engine.rs +++ b/src/domain/engine.rs @@ -45,7 +45,7 @@ impl<'i> Procedure<'i> { self.elements .iter() .flat_map(|element| match element { - Element::Steps(steps) => steps.iter(), + Element::Steps(steps, _) => steps.iter(), _ => [].iter(), }) } @@ -55,7 +55,7 @@ impl<'i> Procedure<'i> { self.elements .iter() .flat_map(|element| match element { - Element::Description(paragraphs) => paragraphs.iter(), + Element::Description(paragraphs, _) => paragraphs.iter(), _ => [].iter(), }) } @@ -115,7 +115,7 @@ impl<'i> Scope<'i> { /// Returns an iterator over responses if this is a ResponseBlock. pub fn responses(&self) -> impl Iterator> { let slice: &[Response<'i>] = match self { - Scope::ResponseBlock { responses } => responses, + Scope::ResponseBlock { responses, .. } => responses, _ => &[], }; slice.iter() @@ -327,9 +327,9 @@ fn render_expression(expr: &Expression) -> String { ) } Expression::Multiline(_, lines) => lines.join("\n"), - Expression::Variable(id) => { - id.value.to_string() - } + Expression::Variable(id) => id + .value + .to_string(), Expression::Binding(inner, _) => render_expression(inner), Expression::String(pieces) => { let mut result = String::new(); diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index 8a264c43..fbe88cb3 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -503,7 +503,7 @@ impl<'i> Formatter<'i> { let mut elements = procedure .elements .iter(); - if let Some(Element::Title(_)) = procedure + if let Some(Element::Title(_, _)) = procedure .elements .first() { @@ -525,21 +525,21 @@ impl<'i> Formatter<'i> { fn append_element(&mut self, element: &'i Element) { match element { - Element::Title(title) => { + Element::Title(title, _) => { self.add_fragment_reference(Syntax::Newline, "\n"); self.add_fragment_reference(Syntax::Header, "# "); self.add_fragment_reference(Syntax::Title, title); self.add_fragment_reference(Syntax::Newline, "\n"); } - Element::Description(paragraphs) => { + Element::Description(paragraphs, _) => { self.add_fragment_reference(Syntax::Newline, "\n"); self.append_paragraphs(paragraphs); } - Element::Steps(steps) => { + Element::Steps(steps, _) => { self.add_fragment_reference(Syntax::Newline, "\n"); self.append_steps(steps); } - Element::CodeBlock(expressions) => { + Element::CodeBlock(expressions, _) => { self.add_fragment_reference(Syntax::Structure, "{"); self.add_fragment_reference(Syntax::Newline, "\n"); @@ -747,6 +747,7 @@ impl<'i> Formatter<'i> { ordinal, description: content, subscopes: scopes, + .. } => { self.indent(); self.add_fragment_string(Syntax::StepItem, format!("{}.", ordinal)); @@ -771,6 +772,7 @@ impl<'i> Formatter<'i> { bullet, description, subscopes, + .. } => { self.indent(); self.add_fragment_string(Syntax::StepItem, bullet.to_string()); @@ -824,6 +826,7 @@ impl<'i> Formatter<'i> { Scope::AttributeBlock { attributes, subscopes, + .. } => { if subscopes.len() == 0 { self.indent(); @@ -861,6 +864,7 @@ impl<'i> Formatter<'i> { Scope::CodeBlock { expressions, subscopes: substeps, + .. } => { let has_separator = expressions .iter() @@ -921,7 +925,7 @@ impl<'i> Formatter<'i> { self.append_scopes(substeps); self.decrease(4); } - Scope::ResponseBlock { responses } => { + Scope::ResponseBlock { responses, .. } => { self.increase(4); self.indent(); self.append_responses(responses); @@ -931,6 +935,7 @@ impl<'i> Formatter<'i> { numeral, title, body, + .. } => { self.add_fragment_reference(Syntax::StepItem, numeral); self.add_fragment_reference(Syntax::Structure, "."); @@ -1129,7 +1134,9 @@ impl<'i> Formatter<'i> { Target::Local(identifier) => { self.add_fragment_reference(Syntax::Invocation, identifier.value) } - Target::Remote(external) => self.add_fragment_reference(Syntax::Invocation, external.value), + Target::Remote(external) => { + self.add_fragment_reference(Syntax::Invocation, external.value) + } } self.add_fragment_reference(Syntax::Quote, ">"); if let Some(parameters) = &invocation.parameters { diff --git a/src/language/types.rs b/src/language/types.rs index 1aef18ba..f9c6615e 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -42,20 +42,42 @@ pub enum Technique<'i> { Empty, } -#[derive(Eq, Debug, PartialEq)] +#[derive(Eq, Debug)] pub enum Element<'i> { - Title(&'i str), - Description(Vec>), - Steps(Vec>), - CodeBlock(Vec>), + Title(&'i str, Span), + Description(Vec>, Span), + Steps(Vec>, Span), + CodeBlock(Vec>, Span), } -#[derive(Eq, Debug, PartialEq)] +impl PartialEq for Element<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Element::Title(a, _), Element::Title(b, _)) => a == b, + (Element::Description(a, _), Element::Description(b, _)) => a == b, + (Element::Steps(a, _), Element::Steps(b, _)) => a == b, + (Element::CodeBlock(a, _), Element::CodeBlock(b, _)) => a == b, + _ => false, + } + } +} + +#[derive(Eq, Debug)] pub struct Procedure<'i> { pub name: Identifier<'i>, pub parameters: Option>>, pub signature: Option>, pub elements: Vec>, + pub span: Span, +} + +impl PartialEq for Procedure<'_> { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.parameters == other.parameters + && self.signature == other.signature + && self.elements == other.elements + } } impl<'i> Procedure<'i> { @@ -63,7 +85,7 @@ impl<'i> Procedure<'i> { self.elements .iter() .find_map(|element| match element { - Element::Title(title) => return Some(*title), + Element::Title(title, _) => return Some(*title), _ => None, }) } @@ -85,7 +107,13 @@ impl<'i> Identifier<'i> { /// Test helper: builds an `Identifier` with a default span. See also the /// `PartialEq` instance. pub const fn dummy(value: &'i str) -> Self { - Identifier { value, span: Span { offset: 0, length: 0 } } + Identifier { + value, + span: Span { + offset: 0, + length: 0, + }, + } } } @@ -103,7 +131,13 @@ impl PartialEq for External<'_> { impl<'i> External<'i> { pub const fn dummy(value: &'i str) -> Self { - External { value, span: Span { offset: 0, length: 0 } } + External { + value, + span: Span { + offset: 0, + length: 0, + }, + } } } @@ -127,7 +161,13 @@ impl PartialEq for Forma<'_> { impl<'i> Forma<'i> { pub const fn dummy(value: &'i str) -> Self { - Forma { value, span: Span { offset: 0, length: 0 } } + Forma { + value, + span: Span { + offset: 0, + length: 0, + }, + } } } @@ -169,35 +209,40 @@ pub enum Descriptive<'i> { // types for Steps within procedures -#[derive(Eq, Debug, PartialEq)] +#[derive(Eq, Debug)] pub enum Scope<'i> { DependentBlock { ordinal: &'i str, description: Vec>, subscopes: Vec>, + span: Span, }, ParallelBlock { bullet: char, description: Vec>, subscopes: Vec>, + span: Span, }, // Attribute scope: @role (or other attributes) with substeps AttributeBlock { attributes: Vec>, subscopes: Vec>, + span: Span, }, // Code block scope: { foreach ... } with substeps CodeBlock { expressions: Vec>, subscopes: Vec>, + span: Span, }, // Response block scope: 'Yes' | 'No' responses ResponseBlock { responses: Vec>, + span: Span, }, // Section chunk scope: organizational container with technique content @@ -205,9 +250,88 @@ pub enum Scope<'i> { numeral: &'i str, title: Option>, body: Technique<'i>, + span: Span, }, } +impl PartialEq for Scope<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Scope::DependentBlock { + ordinal: a1, + description: a2, + subscopes: a3, + .. + }, + Scope::DependentBlock { + ordinal: b1, + description: b2, + subscopes: b3, + .. + }, + ) => a1 == b1 && a2 == b2 && a3 == b3, + ( + Scope::ParallelBlock { + bullet: a1, + description: a2, + subscopes: a3, + .. + }, + Scope::ParallelBlock { + bullet: b1, + description: b2, + subscopes: b3, + .. + }, + ) => a1 == b1 && a2 == b2 && a3 == b3, + ( + Scope::AttributeBlock { + attributes: a1, + subscopes: a2, + .. + }, + Scope::AttributeBlock { + attributes: b1, + subscopes: b2, + .. + }, + ) => a1 == b1 && a2 == b2, + ( + Scope::CodeBlock { + expressions: a1, + subscopes: a2, + .. + }, + Scope::CodeBlock { + expressions: b1, + subscopes: b2, + .. + }, + ) => a1 == b1 && a2 == b2, + ( + Scope::ResponseBlock { responses: a, .. }, + Scope::ResponseBlock { responses: b, .. }, + ) => a == b, + ( + Scope::SectionChunk { + numeral: a1, + title: a2, + body: a3, + .. + }, + Scope::SectionChunk { + numeral: b1, + title: b2, + body: b3, + .. + }, + ) => a1 == b1 && a2 == b2 && a3 == b3, + _ => false, + } + } +} + // enum responses like 'Yes' | 'No' #[derive(Eq, Debug, PartialEq)] @@ -443,14 +567,8 @@ mod check { #[test] fn identifier_rules() { let s = Span::default(); - assert_eq!( - validate_identifier("a", s), - Some(Identifier::dummy("a")) - ); - assert_eq!( - validate_identifier("ab", s), - Some(Identifier::dummy("ab")) - ); + assert_eq!(validate_identifier("a", s), Some(Identifier::dummy("a"))); + assert_eq!(validate_identifier("ab", s), Some(Identifier::dummy("ab"))); assert_eq!( validate_identifier("johnny5", s), Some(Identifier::dummy("johnny5")) @@ -468,19 +586,31 @@ mod check { #[test] fn forma_rules() { - assert_eq!(validate_forma("A", Span::default()), Some(Forma::dummy("A"))); - assert_eq!(validate_forma("Beans", Span::default()), Some(Forma::dummy("Beans"))); + assert_eq!( + validate_forma("A", Span::default()), + Some(Forma::dummy("A")) + ); + assert_eq!( + validate_forma("Beans", Span::default()), + Some(Forma::dummy("Beans")) + ); assert_eq!(validate_forma("lower", Span::default()), None); } #[test] fn genus_rules_single() { - assert_eq!(validate_genus("A", Span::default()), Some(Genus::Single(Forma::dummy("A")))); + assert_eq!( + validate_genus("A", Span::default()), + Some(Genus::Single(Forma::dummy("A"))) + ); } #[test] fn genus_rules_list() { - assert_eq!(validate_genus("[A]", Span::default()), Some(Genus::List(Forma::dummy("A")))); + assert_eq!( + validate_genus("[A]", Span::default()), + Some(Genus::List(Forma::dummy("A"))) + ); // Test list with whitespace assert_eq!( @@ -507,13 +637,19 @@ mod check { assert_eq!( validate_genus("(Coffee, Tea)", Span::default()), - Some(Genus::Tuple(vec![Forma::dummy("Coffee"), Forma::dummy("Tea")])) + Some(Genus::Tuple(vec![ + Forma::dummy("Coffee"), + Forma::dummy("Tea") + ])) ); // not actually sure whether we should be normalizing this? Probably // not, because formatting and linting is a separate concern. - assert_eq!(validate_genus("(A)", Span::default()), Some(Genus::Tuple(vec![Forma::dummy("A")]))); + assert_eq!( + validate_genus("(A)", Span::default()), + Some(Genus::Tuple(vec![Forma::dummy("A")])) + ); // Test parenthesized tuples with whitespace assert_eq!( @@ -540,7 +676,10 @@ mod check { assert_eq!( validate_genus("Coffee, Tea", Span::default()), - Some(Genus::Naked(vec![Forma::dummy("Coffee"), Forma::dummy("Tea")])) + Some(Genus::Naked(vec![ + Forma::dummy("Coffee"), + Forma::dummy("Tea") + ])) ); assert_eq!( diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index 99399ab6..36a81e1f 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -559,6 +559,7 @@ fn read_toplevel_steps() { ordinal: "1", description: vec![Paragraph(vec![Descriptive::Text("First step")])], subscopes: vec![], + span: Span::default(), }) ); @@ -578,6 +579,7 @@ fn read_toplevel_steps() { "a top-level task to be one in parallel with" )]),], subscopes: vec![], + span: Span::default(), }) ); let result = input.read_step_parallel(); @@ -587,6 +589,7 @@ fn read_toplevel_steps() { bullet: '-', description: vec![Paragraph(vec![Descriptive::Text("another top-level task")]),], subscopes: vec![], + span: Span::default(), }) ); @@ -605,6 +608,7 @@ fn read_toplevel_steps() { "Have you done the first thing in the first one?" )])], subscopes: vec![], + span: Span::default(), }) ); @@ -627,6 +631,7 @@ fn reading_substeps_basic() { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text("First subordinate task")])], subscopes: vec![], + span: Span::default(), }) ); @@ -639,6 +644,7 @@ fn reading_substeps_basic() { bullet: '-', description: vec![Paragraph(vec![Descriptive::Text("Parallel task")])], subscopes: vec![], + span: Span::default(), }) ); } @@ -666,13 +672,16 @@ fn single_step_with_dependent_substeps() { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text("First substep")])], subscopes: vec![], + span: Span::default(), }, Scope::DependentBlock { ordinal: "b", description: vec![Paragraph(vec![Descriptive::Text("Second substep")])], subscopes: vec![], + span: Span::default(), }, ], + span: Span::default(), }) ); } @@ -700,13 +709,16 @@ fn single_step_with_parallel_substeps() { bullet: '-', description: vec![Paragraph(vec![Descriptive::Text("First substep")])], subscopes: vec![], + span: Span::default(), }, Scope::ParallelBlock { bullet: '-', description: vec![Paragraph(vec![Descriptive::Text("Second substep")])], subscopes: vec![], + span: Span::default(), }, ], + span: Span::default(), }) ); } @@ -734,7 +746,9 @@ fn multiple_steps_with_substeps() { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text("Substep")])], subscopes: vec![], + span: Span::default(), }], + span: Span::default(), }) ); @@ -744,6 +758,7 @@ fn multiple_steps_with_substeps() { ordinal: "2", description: vec![Paragraph(vec![Descriptive::Text("Second step")])], subscopes: vec![], + span: Span::default(), }) ); } @@ -810,9 +825,12 @@ fn read_step_with_content() { value: "No", condition: Some("but I have an excuse") } - ] - }] + ], + span: Span::default(), + }], + span: Span::default(), }], + span: Span::default(), }) ); @@ -850,7 +868,7 @@ This is the first one. .elements .iter() .find_map(|element| match element { - Element::Steps(steps) => Some(steps), + Element::Steps(steps, _) => Some(steps), _ => None, }); assert_eq!( @@ -1090,7 +1108,10 @@ fn code_blocks() { // Test simple identifier in code block input.initialize("{ count }"); let result = input.read_code_block(); - assert_eq!(result, Ok(vec![Expression::Variable(Identifier::dummy("count"))])); + assert_eq!( + result, + Ok(vec![Expression::Variable(Identifier::dummy("count"))]) + ); // Test function with simple parameter input.initialize("{ sum(count) }"); @@ -1555,7 +1576,11 @@ fn foreach_tuple_pattern() { assert_eq!( result, Ok(vec![Expression::Foreach( - vec![Identifier::dummy("a"), Identifier::dummy("b"), Identifier::dummy("c")], + vec![ + Identifier::dummy("a"), + Identifier::dummy("b"), + Identifier::dummy("c") + ], Box::new(Expression::Execution(Function { target: Identifier::dummy("zip"), parameters: vec![ @@ -1797,7 +1822,10 @@ fn reading_attributes() { // Test simple place input.initialize("^kitchen"); let result = input.read_attributes(); - assert_eq!(result, Ok(vec![Attribute::Place(Identifier::dummy("kitchen"))])); + assert_eq!( + result, + Ok(vec![Attribute::Place(Identifier::dummy("kitchen"))]) + ); // Test multiple roles input.initialize("@master_chef + @barista"); @@ -1893,7 +1921,9 @@ fn step_with_role_assignment() { subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role(Identifier::dummy("nurse"))], subscopes: vec![], - }] + span: Span::default(), + }], + span: Span::default(), } ); } @@ -1926,9 +1956,12 @@ fn substep_with_role_assignment() { subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text("Check ID")])], - subscopes: vec![] - }] - }] + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), + }], + span: Span::default(), } ); } @@ -1959,9 +1992,12 @@ fn parallel_step_with_role_assignment() { subscopes: vec![Scope::ParallelBlock { bullet: '-', description: vec![Paragraph(vec![Descriptive::Text("Check readings")])], - subscopes: vec![] - }] - }] + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), + }], + span: Span::default(), } ); } @@ -1998,8 +2034,10 @@ fn two_roles_with_substeps() { description: vec![Paragraph(vec![Descriptive::Text( "What are the steps?" )])], - subscopes: vec![] - }] + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), }, Scope::AttributeBlock { attributes: vec![Attribute::Role(Identifier::dummy("nurse"))], @@ -2008,10 +2046,13 @@ fn two_roles_with_substeps() { description: vec![Paragraph(vec![Descriptive::Text( "What are the concerns?" )])], - subscopes: vec![] - }] + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), } - ] + ], + span: Span::default(), } ); } diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index 4e811b2b..1b1c1174 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -65,6 +65,7 @@ making_coffee : (Beans, Milk) -> Coffee provides: Genus::Single(Forma::dummy("Coffee")) }), elements: vec![], + span: Span::default(), }) ); } @@ -92,6 +93,7 @@ second : C -> D provides: Genus::Single(Forma::dummy("B")) }), elements: vec![], + span: Span::default(), }) ); @@ -106,6 +108,7 @@ second : C -> D provides: Genus::Single(Forma::dummy("D")) }), elements: vec![], + span: Span::default(), }) ); } @@ -131,6 +134,7 @@ making_coffee(e) : Ingredients -> Coffee provides: Genus::Single(Forma::dummy("Coffee")) }), elements: vec![], + span: Span::default(), }) ); } @@ -163,29 +167,36 @@ This is the first one. provides: Genus::Single(Forma::dummy("B")) }), elements: vec![ - Element::Title("The First"), - Element::Description(vec![Paragraph(vec![Descriptive::Text( - "This is the first one." - )])]), - Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( - "Do the first thing in the first one." - )])], + Element::Title("The First", Span::default()), + Element::Description( + vec![Paragraph(vec![Descriptive::Text("This is the first one.")])], + Span::default() + ), + Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text( + "Do the first thing in the first one." + )])], - subscopes: vec![] - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( - "Do the second thing in the first one." - )])], + subscopes: vec![], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", + description: vec![Paragraph(vec![Descriptive::Text( + "Do the second thing in the first one." + )])], - subscopes: vec![] - } - ]) + subscopes: vec![], + span: Span::default(), + } + ], + Span::default() + ) ], + span: Span::default(), }) ); } @@ -218,39 +229,47 @@ This is the first one. provides: Genus::Single(Forma::dummy("B")) }), elements: vec![ - Element::Title("The First"), - Element::Description(vec![Paragraph(vec![Descriptive::Text( - "This is the first one." - )])]), - Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( - "Have you done the first thing in the first one?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { - value: "Yes", - condition: None - }, - Response { - value: "No", - condition: Some("but I have an excuse") - } - ] - }], - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( - "Do the second thing in the first one." - )])], + Element::Title("The First", Span::default()), + Element::Description( + vec![Paragraph(vec![Descriptive::Text("This is the first one.")])], + Span::default() + ), + Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text( + "Have you done the first thing in the first one?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "Yes", + condition: None + }, + Response { + value: "No", + condition: Some("but I have an excuse") + } + ], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", + description: vec![Paragraph(vec![Descriptive::Text( + "Do the second thing in the first one." + )])], - subscopes: vec![], - } - ]) + subscopes: vec![], + span: Span::default(), + } + ], + Span::default() + ) ], + span: Span::default(), }) ); } @@ -284,46 +303,55 @@ This is the first one. provides: Genus::Single(Forma::dummy("B")) }), elements: vec![ - Element::Title("The First"), - Element::Description(vec![Paragraph(vec![Descriptive::Text( - "This is the first one." - )])]), - Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( - "Have you done the first thing in the first one?" - )])], + Element::Title("The First", Span::default()), + Element::Description( + vec![Paragraph(vec![Descriptive::Text("This is the first one.")])], + Span::default() + ), + Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text( + "Have you done the first thing in the first one?" + )])], - subscopes: vec![Scope::DependentBlock { - ordinal: "a", + subscopes: vec![Scope::DependentBlock { + ordinal: "a", + description: vec![Paragraph(vec![Descriptive::Text( + "Do the first thing. Then ask yourself if you are done:" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "Yes", + condition: None + }, + Response { + value: "No", + condition: Some("but I have an excuse") + } + ], + span: Span::default(), + }], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", description: vec![Paragraph(vec![Descriptive::Text( - "Do the first thing. Then ask yourself if you are done:" + "Do the second thing in the first one." )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { - value: "Yes", - condition: None - }, - Response { - value: "No", - condition: Some("but I have an excuse") - } - ] - }] - }] - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( - "Do the second thing in the first one." - )])], - subscopes: vec![], - } - ]) + subscopes: vec![], + span: Span::default(), + } + ], + Span::default() + ) ], + span: Span::default(), }) ); } @@ -365,132 +393,153 @@ fn realistic_procedure() { parameters: None, signature: None, elements: vec![ - Element::Title("Before induction of anaesthesia"), - Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![ - Descriptive::Text( - "Has the patient confirmed his/her identity, site, procedure," - ), - Descriptive::Text("and consent?") - ])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![Response { - value: "Yes", - condition: None - }] - }], - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( - "Is the site marked?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { + Element::Title("Before induction of anaesthesia", Span::default()), + Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![ + Descriptive::Text( + "Has the patient confirmed his/her identity, site, procedure," + ), + Descriptive::Text("and consent?") + ])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![Response { value: "Yes", condition: None - }, - Response { - value: "Not Applicable", - condition: None - } - ] - }], - }, - Scope::DependentBlock { - ordinal: "3", - description: vec![Paragraph(vec![Descriptive::Text( - "Is the anaesthesia machine and medication check complete?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![Response { - value: "Yes", - condition: None - }] - }], - }, - Scope::DependentBlock { - ordinal: "4", - description: vec![Paragraph(vec![Descriptive::Text( - "Is the pulse oximeter on the patient and functioning?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![Response { - value: "Yes", - condition: None - }] - }], - }, - Scope::DependentBlock { - ordinal: "5", - description: vec![Paragraph(vec![Descriptive::Text( - "Does the patient have a:" - )])], - - subscopes: vec![ - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Known allergy?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { - value: "No", - condition: None - }, - Response { - value: "Yes", - condition: None - } - ] }], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Difficult airway or aspiration risk?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { - value: "No", - condition: None - }, - Response { - value: "Yes", - condition: Some("and equipment/assistance available") - } - ] + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", + description: vec![Paragraph(vec![Descriptive::Text( + "Is the site marked?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "Yes", + condition: None + }, + Response { + value: "Not Applicable", + condition: None + } + ], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "3", + description: vec![Paragraph(vec![Descriptive::Text( + "Is the anaesthesia machine and medication check complete?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![Response { + value: "Yes", + condition: None }], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Risk of blood loss > 500 mL?" - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { - value: "No", - condition: None - }, - Response { - value: "Yes", - condition: Some( - "and two IVs planned and fluids available" - ) - } - ] + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "4", + description: vec![Paragraph(vec![Descriptive::Text( + "Is the pulse oximeter on the patient and functioning?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![Response { + value: "Yes", + condition: None }], - } - ] - } - ]) - ] + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "5", + description: vec![Paragraph(vec![Descriptive::Text( + "Does the patient have a:" + )])], + + subscopes: vec![ + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Known allergy?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "No", + condition: None + }, + Response { + value: "Yes", + condition: None + } + ], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Difficult airway or aspiration risk?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "No", + condition: None + }, + Response { + value: "Yes", + condition: Some( + "and equipment/assistance available" + ) + } + ], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Risk of blood loss > 500 mL?" + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "No", + condition: None + }, + Response { + value: "Yes", + condition: Some( + "and two IVs planned and fluids available" + ) + } + ], + span: Span::default(), + }], + span: Span::default(), + } + ], + span: Span::default(), + } + ], + Span::default() + ) + ], + span: Span::default(), } ); } @@ -518,45 +567,55 @@ label_the_specimens : name: Identifier::dummy("label_the_specimens"), parameters: None, signature: None, - elements: vec![Element::Steps(vec![Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Specimen labelling")])], + elements: vec![Element::Steps( + vec![Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text("Specimen labelling")])], + + subscopes: vec![ + Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], + subscopes: vec![ + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Label blood tests" + )])], - subscopes: vec![ - Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], - subscopes: vec![ - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Label blood tests" - )])], + subscopes: vec![], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Label tissue samples" + )])], - subscopes: vec![], - }, - Scope::ParallelBlock { - bullet: '-', + subscopes: vec![], + span: Span::default(), + } + ], + span: Span::default(), + }, + Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy("admin_staff"))], + subscopes: vec![Scope::DependentBlock { + ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text( - "Label tissue samples" + "Prepare the envelopes" )])], subscopes: vec![], - } - ] - }, - Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("admin_staff"))], - subscopes: vec![Scope::DependentBlock { - ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( - "Prepare the envelopes" - )])], - - subscopes: vec![], - }] - } - ], - }])], + span: Span::default(), + }], + span: Span::default(), + } + ], + span: Span::default(), + }], + Span::default() + )], + span: Span::default(), }) ); } @@ -601,121 +660,148 @@ before_leaving : parameters: None, signature: None, elements: vec![ - Element::Title("Before patient leaves operating room"), - Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Verbally confirm:")])], - - subscopes: vec![ - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "The name of the surgical procedure(s)." - )])], + Element::Title("Before patient leaves operating room", Span::default()), + Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text( + "Verbally confirm:" + )])], - subscopes: vec![], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Completion of instrument, sponge, and needle counts." - )])], + subscopes: vec![ + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "The name of the surgical procedure(s)." + )])], - subscopes: vec![], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Specimen labelling" - )])], + subscopes: vec![], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Completion of instrument, sponge, and needle counts." + )])], - subscopes: vec![Scope::CodeBlock { - expressions: vec![Expression::Foreach( - vec![Identifier::dummy("specimen")], - Box::new(Expression::Variable(Identifier::dummy("specimens"))) - )], - subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy( - "nursing_team", - ))], - subscopes: vec![Scope::DependentBlock { - ordinal: "a", - description: vec![Paragraph(vec![ + subscopes: vec![], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Specimen labelling" + )])], + + subscopes: vec![Scope::CodeBlock { + expressions: vec![Expression::Foreach( + vec![Identifier::dummy("specimen")], + Box::new(Expression::Variable(Identifier::dummy( + "specimens" + ))) + )], + subscopes: vec![Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy( + "nursing_team", + ))], + subscopes: vec![Scope::DependentBlock { + ordinal: "a", + description: vec![Paragraph(vec![ Descriptive::Text( "Read specimen labels aloud, including patient" ), Descriptive::Text("name.") ])], - subscopes: vec![] - }] - }] - }] - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Whether there are any equipment problems to be addressed." - )])], + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), + }], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Whether there are any equipment problems to be addressed." + )])], - subscopes: vec![], - } - ] - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( - "Post-operative care:" - )])], + subscopes: vec![], + span: Span::default(), + } + ], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", + description: vec![Paragraph(vec![Descriptive::Text( + "Post-operative care:" + )])], - subscopes: vec![ - Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], - subscopes: vec![Scope::DependentBlock { - ordinal: "a", - description: vec![Paragraph(vec![ + subscopes: vec![ + Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], + subscopes: vec![Scope::DependentBlock { + ordinal: "a", + description: vec![Paragraph(vec![ Descriptive::Text( "What are the key concerns for recovery and management" ), Descriptive::Text("of this patient?") ])], - subscopes: vec![], - }] - }, - Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("anesthetist"))], - subscopes: vec![Scope::DependentBlock { - ordinal: "b", - description: vec![Paragraph(vec![ + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy( + "anesthetist" + ))], + subscopes: vec![Scope::DependentBlock { + ordinal: "b", + description: vec![Paragraph(vec![ Descriptive::Text( "What are the key concerns for recovery and management" ), Descriptive::Text("of this patient?") ])], - subscopes: vec![], - }] - }, - Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], - subscopes: vec![Scope::DependentBlock { - ordinal: "c", - description: vec![Paragraph(vec![ + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy( + "nursing_team" + ))], + subscopes: vec![Scope::DependentBlock { + ordinal: "c", + description: vec![Paragraph(vec![ Descriptive::Text( "What are the key concerns for recovery and management" ), Descriptive::Text("of this patient?") ])], - subscopes: vec![], - }] - } - ], - } - ]) + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), + } + ], + span: Span::default(), + } + ], + Span::default() + ) ], + span: Span::default(), } ); } @@ -747,6 +833,7 @@ fn parallel_role_assignments() { ordinal, description: content, subscopes: scopes, + .. }) => { assert_eq!(ordinal, "5"); assert_eq!( @@ -762,9 +849,13 @@ fn parallel_role_assignments() { if let Scope::AttributeBlock { attributes, subscopes: substeps, + .. } = &scopes[0] { - assert_eq!(*attributes, vec![Attribute::Role(Identifier::dummy("surgeon"))]); + assert_eq!( + *attributes, + vec![Attribute::Role(Identifier::dummy("surgeon"))] + ); assert_eq!(substeps.len(), 3); // a, b, c } else { panic!("Expected AttributedBlock for surgeon"); @@ -774,6 +865,7 @@ fn parallel_role_assignments() { if let Scope::AttributeBlock { attributes, subscopes: substeps, + .. } = &scopes[1] { assert_eq!( @@ -789,6 +881,7 @@ fn parallel_role_assignments() { if let Scope::AttributeBlock { attributes, subscopes: substeps, + .. } = &scopes[2] { assert_eq!( @@ -833,6 +926,7 @@ fn multiple_roles_with_dependent_substeps() { ordinal, description: content, subscopes: scopes, + .. }) => { assert_eq!(ordinal, "1"); assert_eq!( @@ -847,9 +941,13 @@ fn multiple_roles_with_dependent_substeps() { if let Scope::AttributeBlock { attributes, subscopes: substeps, + .. } = &scopes[0] { - assert_eq!(*attributes, vec![Attribute::Role(Identifier::dummy("surgeon"))]); + assert_eq!( + *attributes, + vec![Attribute::Role(Identifier::dummy("surgeon"))] + ); assert_eq!(substeps.len(), 3); } else { panic!("Expected AttributedBlock for surgeon"); @@ -859,6 +957,7 @@ fn multiple_roles_with_dependent_substeps() { if let Scope::AttributeBlock { attributes, subscopes: substeps, + .. } = &scopes[1] { assert_eq!( @@ -874,6 +973,7 @@ fn multiple_roles_with_dependent_substeps() { if let Scope::AttributeBlock { attributes, subscopes: substeps, + .. } = &scopes[2] { assert_eq!( @@ -939,7 +1039,8 @@ fn mixed_substeps_in_roles() { ordinal: "a", description: vec![Paragraph(vec![Descriptive::Text("Assess situation")])], - subscopes: vec![] + subscopes: vec![], + span: Span::default(), }, Scope::DependentBlock { ordinal: "b", @@ -954,7 +1055,8 @@ fn mixed_substeps_in_roles() { "Monitor communications" )])], - subscopes: vec![] + subscopes: vec![], + span: Span::default(), }, Scope::ParallelBlock { bullet: '-', @@ -962,18 +1064,23 @@ fn mixed_substeps_in_roles() { "Track resources" )])], - subscopes: vec![] + subscopes: vec![], + span: Span::default(), } - ] + ], + span: Span::default(), }, Scope::DependentBlock { ordinal: "c", description: vec![Paragraph(vec![Descriptive::Text("File report")])], - subscopes: vec![] + subscopes: vec![], + span: Span::default(), } - ] - }] + ], + span: Span::default(), + }], + span: Span::default(), } ); } @@ -1010,9 +1117,12 @@ fn substeps_with_responses() { value: "No", condition: None, }, - ] + ], + span: Span::default(), }], + span: Span::default(), }], + span: Span::default(), }) ); } @@ -1118,44 +1228,54 @@ second_section_second_procedure : name: Identifier::dummy("main_procedure"), parameters: None, signature: None, - elements: vec![Element::Steps(vec![ - Scope::SectionChunk { - numeral: "I", - title: Some(Paragraph(vec![Descriptive::Text("First Section")])), - body: Technique::Procedures(vec![ - Procedure { - name: Identifier::dummy("first_section_first_procedure"), - parameters: None, - signature: None, - elements: vec![Element::Title("One dot One")] - }, - Procedure { - name: Identifier::dummy("first_section_second_procedure"), - parameters: None, - signature: None, - elements: vec![Element::Title("One dot Two")] - } - ]), - }, - Scope::SectionChunk { - numeral: "II", - title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), - body: Technique::Procedures(vec![ - Procedure { - name: Identifier::dummy("second_section_first_procedure"), - parameters: None, - signature: None, - elements: vec![Element::Title("Two dot One")] - }, - Procedure { - name: Identifier::dummy("second_section_second_procedure"), - parameters: None, - signature: None, - elements: vec![Element::Title("Two dot Two")] - } - ]), - }, - ])], + elements: vec![Element::Steps( + vec![ + Scope::SectionChunk { + numeral: "I", + title: Some(Paragraph(vec![Descriptive::Text("First Section")])), + body: Technique::Procedures(vec![ + Procedure { + name: Identifier::dummy("first_section_first_procedure"), + parameters: None, + signature: None, + elements: vec![Element::Title("One dot One", Span::default())], + span: Span::default(), + }, + Procedure { + name: Identifier::dummy("first_section_second_procedure"), + parameters: None, + signature: None, + elements: vec![Element::Title("One dot Two", Span::default())], + span: Span::default(), + } + ]), + span: Span::default(), + }, + Scope::SectionChunk { + numeral: "II", + title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), + body: Technique::Procedures(vec![ + Procedure { + name: Identifier::dummy("second_section_first_procedure"), + parameters: None, + signature: None, + elements: vec![Element::Title("Two dot One", Span::default())], + span: Span::default(), + }, + Procedure { + name: Identifier::dummy("second_section_second_procedure"), + parameters: None, + signature: None, + elements: vec![Element::Title("Two dot Two", Span::default())], + span: Span::default(), + } + ]), + span: Span::default(), + }, + ], + Span::default() + )], + span: Span::default(), }])), } ); @@ -1192,7 +1312,7 @@ procedure_four : Concept -> Architecture // Verify that both sections contain their respective procedures if let Some(Technique::Procedures(procs)) = document.body { let main_proc = &procs[0]; - if let Some(Element::Steps(steps)) = main_proc + if let Some(Element::Steps(steps, _)) = main_proc .elements .first() { @@ -1273,89 +1393,108 @@ III. Implementation name: Identifier::dummy("main_procedure"), parameters: None, signature: None, - elements: vec![Element::Steps(vec![ - Scope::SectionChunk { - numeral: "I", - title: Some(Paragraph(vec![Descriptive::Text("Concept")])), - body: Technique::Empty, - }, - Scope::SectionChunk { - numeral: "II", - title: Some(Paragraph(vec![Descriptive::Text( - "Requirements Definition and Architecture" - )])), - body: Technique::Procedures(vec![ - Procedure { - name: Identifier::dummy("requirements_and_architecture"), - parameters: None, - signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Concept")), - provides: Genus::Naked(vec![ - Forma::dummy("Requirements"), - Forma::dummy("Architecture") - ]), - }), - elements: vec![Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![ - Descriptive::Text("Define Requirements"), - Descriptive::Application(Invocation { - target: Target::Local(Identifier::dummy( - "define_requirements", - )), - parameters: Some(vec![Expression::Variable( - Identifier::dummy("concept") - )]), - }), - ])], - - subscopes: vec![], - }, - Scope::DependentBlock { - ordinal: "3", - description: vec![Paragraph(vec![ - Descriptive::Text("Determine Architecture"), - Descriptive::Application(Invocation { - target: Target::Local(Identifier::dummy( - "determine_architecture", - )), - parameters: Some(vec![Expression::Variable( - Identifier::dummy("concept") - )]), - }), - ])], - - subscopes: vec![], - }, - ])], - }, - Procedure { - name: Identifier::dummy("define_requirements"), - parameters: None, - signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Concept")), - provides: Genus::Single(Forma::dummy("Requirements")), - }), - elements: vec![], - }, - Procedure { - name: Identifier::dummy("determine_architecture"), - parameters: None, - signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Concept")), - provides: Genus::Single(Forma::dummy("Architecture")), - }), - elements: vec![], - }, - ]), - }, - Scope::SectionChunk { - numeral: "III", - title: Some(Paragraph(vec![Descriptive::Text("Implementation")])), - body: Technique::Empty, - }, - ])], + elements: vec![Element::Steps( + vec![ + Scope::SectionChunk { + numeral: "I", + title: Some(Paragraph(vec![Descriptive::Text("Concept")])), + body: Technique::Empty, + span: Span::default(), + }, + Scope::SectionChunk { + numeral: "II", + title: Some(Paragraph(vec![Descriptive::Text( + "Requirements Definition and Architecture" + )])), + body: Technique::Procedures(vec![ + Procedure { + name: Identifier::dummy("requirements_and_architecture"), + parameters: None, + signature: Some(Signature { + requires: Genus::Single(Forma::dummy("Concept")), + provides: Genus::Naked(vec![ + Forma::dummy("Requirements"), + Forma::dummy("Architecture") + ]), + }), + elements: vec![Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "2", + description: vec![Paragraph(vec![ + Descriptive::Text("Define Requirements"), + Descriptive::Application(Invocation { + target: Target::Local(Identifier::dummy( + "define_requirements", + )), + parameters: Some(vec![ + Expression::Variable( + Identifier::dummy("concept") + ) + ]), + }), + ])], + + subscopes: vec![], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "3", + description: vec![Paragraph(vec![ + Descriptive::Text("Determine Architecture"), + Descriptive::Application(Invocation { + target: Target::Local(Identifier::dummy( + "determine_architecture", + )), + parameters: Some(vec![ + Expression::Variable( + Identifier::dummy("concept") + ) + ]), + }), + ])], + + subscopes: vec![], + span: Span::default(), + }, + ], + Span::default() + )], + span: Span::default(), + }, + Procedure { + name: Identifier::dummy("define_requirements"), + parameters: None, + signature: Some(Signature { + requires: Genus::Single(Forma::dummy("Concept")), + provides: Genus::Single(Forma::dummy("Requirements")), + }), + elements: vec![], + span: Span::default(), + }, + Procedure { + name: Identifier::dummy("determine_architecture"), + parameters: None, + signature: Some(Signature { + requires: Genus::Single(Forma::dummy("Concept")), + provides: Genus::Single(Forma::dummy("Architecture")), + }), + elements: vec![], + span: Span::default(), + }, + ]), + span: Span::default(), + }, + Scope::SectionChunk { + numeral: "III", + title: Some(Paragraph(vec![Descriptive::Text("Implementation")])), + body: Technique::Empty, + span: Span::default(), + }, + ], + Span::default() + )], + span: Span::default(), }])), } ) diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index dfcd9189..9acb761b 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -311,7 +311,7 @@ impl<'i> Parser<'i> { if is_section(self.source) { match self.read_section() { Ok(section) => { - if let Some(Element::Steps(ref mut steps)) = procedure + if let Some(Element::Steps(ref mut steps, _)) = procedure .elements .last_mut() { @@ -320,7 +320,10 @@ impl<'i> Parser<'i> { // Create a new Steps element if one doesn't exist procedure .elements - .push(Element::Steps(vec![section])); + .push(Element::Steps( + vec![section], + Span::default(), + )); } } Err(error) => { @@ -719,9 +722,10 @@ impl<'i> Parser<'i> { /// `slice` must be a sub-slice of `self.source`. fn span_of(&self, slice: &str) -> Span { - let inner = (slice.as_ptr() as usize) - (self - .source - .as_ptr() as usize); + let inner = (slice.as_ptr() as usize) + - (self + .source + .as_ptr() as usize); Span { offset: self.offset + inner, length: slice.len(), @@ -903,12 +907,10 @@ impl<'i> Parser<'i> { offset: self.offset + two.start(), length: two.len(), }; - let requires = validate_genus(one.as_str(), one_span).ok_or( - ParsingError::InvalidGenus(one_span.offset, one_span.length), - )?; - let provides = validate_genus(two.as_str(), two_span).ok_or( - ParsingError::InvalidGenus(two_span.offset, two_span.length), - )?; + let requires = validate_genus(one.as_str(), one_span) + .ok_or(ParsingError::InvalidGenus(one_span.offset, one_span.length))?; + let provides = validate_genus(two.as_str(), two_span) + .ok_or(ParsingError::InvalidGenus(two_span.offset, two_span.length))?; Ok(Signature { requires, provides }) } @@ -944,11 +946,7 @@ impl<'i> Parser<'i> { let (name, parameters) = if let Some((before, list)) = text.split_once('(') { let before = before.trim(); let name = validate_identifier(before, self.span_of(before)).ok_or( - ParsingError::InvalidIdentifier( - self.offset, - before.len(), - before.to_string(), - ), + ParsingError::InvalidIdentifier(self.offset, before.len(), before.to_string()), )?; // Extract parameters from parentheses @@ -998,11 +996,7 @@ impl<'i> Parser<'i> { } let name = validate_identifier(text, self.span_of(text)).ok_or( - ParsingError::InvalidIdentifier( - self.offset, - text.len(), - text.to_string(), - ), + ParsingError::InvalidIdentifier(self.offset, text.len(), text.to_string()), )?; (name, None) }; @@ -1100,6 +1094,7 @@ impl<'i> Parser<'i> { } let content = parser.source; + let elem_start = parser.offset; if is_procedure_title(content) { match parser.take_block_lines( @@ -1113,7 +1108,13 @@ impl<'i> Parser<'i> { Ok(text) }, ) { - Ok(title) => elements.push(Element::Title(title)), + Ok(title) => { + let span = Span { + offset: elem_start, + length: parser.offset - elem_start, + }; + elements.push(Element::Title(title, span)); + } Err(error) => { self.problems .push(error); @@ -1122,7 +1123,13 @@ impl<'i> Parser<'i> { } } else if is_code_block(content) { match parser.read_code_block() { - Ok(expressions) => elements.push(Element::CodeBlock(expressions)), + Ok(expressions) => { + let span = Span { + offset: elem_start, + length: parser.offset - elem_start, + }; + elements.push(Element::CodeBlock(expressions, span)); + } Err(error) => { self.problems .push(error); @@ -1132,7 +1139,13 @@ impl<'i> Parser<'i> { } else if is_attribute_pattern(content) { if is_attribute_assignment(content) { match parser.read_attribute_scope() { - Ok(attribute_block) => elements.push(Element::Steps(vec![attribute_block])), + Ok(attribute_block) => { + let span = Span { + offset: elem_start, + length: parser.offset - elem_start, + }; + elements.push(Element::Steps(vec![attribute_block], span)); + } Err(error) => { self.problems .push(error); @@ -1173,7 +1186,11 @@ impl<'i> Parser<'i> { } } if !steps.is_empty() { - elements.push(Element::Steps(steps)); + let span = Span { + offset: elem_start, + length: parser.offset - elem_start, + }; + elements.push(Element::Steps(steps, span)); } } else if malformed_step_pattern(content) { // Store error but continue parsing @@ -1207,7 +1224,11 @@ impl<'i> Parser<'i> { ) { Ok(description) => { if !description.is_empty() { - elements.push(Element::Description(description)); + let span = Span { + offset: elem_start, + length: parser.offset - elem_start, + }; + elements.push(Element::Description(description, span)); } } Err(error) => { @@ -1224,6 +1245,7 @@ impl<'i> Parser<'i> { parameters: declaration.1, signature: declaration.2, elements, + span: Span::default(), }) } @@ -1302,6 +1324,7 @@ impl<'i> Parser<'i> { numeral, title, body: Technique::Empty, + span: Span::default(), }) } else if is_procedure_declaration(outer.source) { // Section contains procedures @@ -1328,6 +1351,7 @@ impl<'i> Parser<'i> { numeral, title, body: Technique::Procedures(procedures), + span: Span::default(), }) } else { // Section contains steps - parse as steps @@ -1354,6 +1378,7 @@ impl<'i> Parser<'i> { numeral, title, body: Technique::Steps(steps), + span: Span::default(), }) } }) @@ -1531,10 +1556,17 @@ impl<'i> Parser<'i> { Ok(Expression::Execution(function)) } else { let identifier = self.read_identifier()?; - if self.source.starts_with('"') { + if self + .source + .starts_with('"') + { return Err(ParsingError::InvalidFunction( - identifier.span.offset, - identifier.span.length, + identifier + .span + .offset, + identifier + .span + .length, )); } Ok(Expression::Variable(identifier)) @@ -1808,11 +1840,7 @@ impl<'i> Parser<'i> { }; let identifier = validate_identifier(possible, self.span_of(possible)).ok_or( - ParsingError::InvalidIdentifier( - self.offset, - possible.len(), - possible.to_string(), - ), + ParsingError::InvalidIdentifier(self.offset, possible.len(), possible.to_string()), )?; self.advance(possible.len()); @@ -2081,6 +2109,7 @@ impl<'i> Parser<'i> { fn read_step_dependent(&mut self) -> Result, ParsingError> { self.take_block_lines(is_step_dependent, is_step_dependent, |outer| { outer.trim_whitespace(); + let start = outer.offset; // Parse ordinal let re = regex!(r"^\s*(\d+)\.\s+"); @@ -2113,6 +2142,10 @@ impl<'i> Parser<'i> { ordinal: number, description: text, subscopes: scopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }); }) } @@ -2121,6 +2154,7 @@ impl<'i> Parser<'i> { fn read_step_parallel(&mut self) -> Result, ParsingError> { self.take_block_lines(is_step_parallel, is_step_parallel, |outer| { outer.trim_whitespace(); + let start = outer.offset; // Parse bullet if !outer @@ -2141,6 +2175,10 @@ impl<'i> Parser<'i> { bullet: '-', description: text, subscopes: scopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }); }) } @@ -2151,6 +2189,7 @@ impl<'i> Parser<'i> { is_substep_dependent, |line| is_substep_dependent(line), |outer| { + let start = outer.offset; let content = outer.source; let re = regex!(r"^\s*([a-hj-uwy])\.\s+"); let cap = re @@ -2184,6 +2223,10 @@ impl<'i> Parser<'i> { ordinal: letter, description: text, subscopes: scopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }) }, ) @@ -2195,6 +2238,7 @@ impl<'i> Parser<'i> { is_substep_parallel, |line| is_substep_parallel(line), |outer| { + let start = outer.offset; let re = regex!(r"^\s*-\s+"); let zero = re .find(outer.source) @@ -2215,6 +2259,10 @@ impl<'i> Parser<'i> { bullet: '-', description: text, subscopes: scopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }) }, ) @@ -2226,6 +2274,7 @@ impl<'i> Parser<'i> { is_subsubstep_dependent, |line| is_subsubstep_dependent(line), |outer| { + let start = outer.offset; let content = outer.source; let re = regex!(r"^\s*([ivx]+)\.\s+"); let cap = re @@ -2256,6 +2305,10 @@ impl<'i> Parser<'i> { ordinal: numeral, description: text, subscopes: scopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }) }, ) @@ -2686,8 +2739,15 @@ impl<'i> Parser<'i> { } else if malformed_response_pattern(content) { return Err(ParsingError::InvalidResponse(self.offset, 0)); } else if is_enum_response(content) { + let responses_start = self.offset; let responses = self.read_responses()?; - scopes.push(Scope::ResponseBlock { responses }); + scopes.push(Scope::ResponseBlock { + responses, + span: Span { + offset: responses_start, + length: self.offset - responses_start, + }, + }); } else { break; } @@ -2698,12 +2758,17 @@ impl<'i> Parser<'i> { /// Parse an attribute block (role or place assignment) with its subscopes fn read_attribute_scope(&mut self) -> Result, ParsingError> { self.take_block_lines(is_attribute_assignment, is_attribute_assignment, |outer| { + let start = outer.offset; let attributes = outer.read_attributes()?; let subscopes = outer.read_scopes()?; Ok(Scope::AttributeBlock { attributes, subscopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }) }) } @@ -2719,12 +2784,17 @@ impl<'i> Parser<'i> { false // Never stop - consume all remaining content }, |outer| { + let start = outer.offset; let expressions = outer.read_code_block()?; let subscopes = outer.read_scopes()?; Ok(Scope::CodeBlock { expressions, subscopes, + span: Span { + offset: start, + length: outer.offset - start, + }, }) }, ) diff --git a/src/problem/messages.rs b/src/problem/messages.rs index d2a75ba5..94189602 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -130,24 +130,28 @@ to be used when rendering the Technique. Common domains include parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("attempt1"), parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("i"), parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("l33t_hax0r"), parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, ]; @@ -279,18 +283,21 @@ this form. parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("implementation"), parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("make_coffee"), parameters: None, signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("f"), @@ -300,6 +307,7 @@ this form. provides: Genus::Single(Forma::dummy("B")), }), elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("implementation"), @@ -309,6 +317,7 @@ this form. provides: Genus::Single(Forma::dummy("Product")), }), elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("make_coffee"), @@ -318,6 +327,7 @@ this form. provides: Genus::Single(Forma::dummy("Coffee")), }), elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("make_coffee"), @@ -327,6 +337,7 @@ this form. provides: Genus::Single(Forma::dummy("Coffee")), }), elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("make_coffee"), @@ -336,6 +347,7 @@ this form. provides: Genus::Single(Forma::dummy("Coffee")), }), elements: Vec::new(), + span: Span::default(), }, ]; @@ -381,12 +393,14 @@ Finally, variables can be assigned for the names of the input parameters: parameters: Some(vec![Identifier::dummy("a"), Identifier::dummy("b")]), signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("bulldoze"), parameters: Some(vec![Identifier::dummy("c")]), signature: None, elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("lawsuit"), @@ -396,6 +410,7 @@ Finally, variables can be assigned for the names of the input parameters: provides: Genus::List(Forma::dummy("Penny")), }), elements: Vec::new(), + span: Span::default(), }, Procedure { name: Identifier::dummy("lawsuit"), @@ -405,6 +420,7 @@ Finally, variables can be assigned for the names of the input parameters: provides: Genus::List(Forma::dummy("Penny")), }), elements: Vec::new(), + span: Span::default(), }, ]; @@ -638,6 +654,7 @@ parallel steps, but again this is not compulsory. Attribute::Role(Identifier::dummy("femme_fatale")), ], subscopes: vec![], + span: Span::default(), }, Scope::AttributeBlock { attributes: vec![ @@ -646,6 +663,7 @@ parallel steps, but again this is not compulsory. Attribute::Role(Identifier::dummy("dish_of_the_day")), ], subscopes: vec![], + span: Span::default(), }, ]; diff --git a/tests/formatting/formatter.rs b/tests/formatting/formatter.rs index e09d613f..3d422727 100644 --- a/tests/formatting/formatter.rs +++ b/tests/formatting/formatter.rs @@ -53,6 +53,7 @@ mod verify { provides: Genus::Single(Forma::dummy("B")), }), elements: vec![], + span: Span::default(), }])), }; @@ -83,15 +84,21 @@ first : A -> B provides: Genus::Single(Forma::dummy("B")), }), elements: vec![], + span: Span::default(), }, Procedure { name: Identifier::dummy("second"), parameters: None, signature: Some(Signature { requires: Genus::List(Forma::dummy("Thing")), - provides: Genus::Tuple(vec![Forma::dummy("Who"), Forma::dummy("Where"), Forma::dummy("Why")]), + provides: Genus::Tuple(vec![ + Forma::dummy("Who"), + Forma::dummy("Where"), + Forma::dummy("Why"), + ]), }), elements: vec![], + span: Span::default(), }, ])), }; @@ -124,38 +131,47 @@ second : [Thing] -> (Who, Where, Why) requires: Genus::Single(Forma::dummy("Bicycle")), provides: Genus::Single(Forma::dummy("YellowJersey")), }), - elements: vec![Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Eat breakfast.")])], - subscopes: vec![], - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text("Win a stage:")])], - subscopes: vec![ - Scope::DependentBlock { - ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( - "Ride really fast, then", - )])], - subscopes: vec![], - }, - Scope::DependentBlock { - ordinal: "b", - description: vec![Paragraph(vec![Descriptive::Text( - "Win the sprint.", - )])], - subscopes: vec![], - }, - ], - }, - Scope::DependentBlock { - ordinal: "3", - description: vec![Paragraph(vec![Descriptive::Text("Eat dinner.")])], - subscopes: vec![], - }, - ])], + elements: vec![Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text("Eat breakfast.")])], + subscopes: vec![], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", + description: vec![Paragraph(vec![Descriptive::Text("Win a stage:")])], + subscopes: vec![ + Scope::DependentBlock { + ordinal: "a", + description: vec![Paragraph(vec![Descriptive::Text( + "Ride really fast, then", + )])], + subscopes: vec![], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "b", + description: vec![Paragraph(vec![Descriptive::Text( + "Win the sprint.", + )])], + subscopes: vec![], + span: Span::default(), + }, + ], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "3", + description: vec![Paragraph(vec![Descriptive::Text("Eat dinner.")])], + subscopes: vec![], + span: Span::default(), + }, + ], + Span::default(), + )], + span: Span::default(), }])), }; @@ -185,10 +201,14 @@ win_le_tour : Bicycle -> YellowJersey name: Identifier::dummy("vibe_coding"), parameters: None, signature: None, - elements: vec![Element::CodeBlock(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline(Some("bash"), vec!["rm -rf /"])], - })])], + elements: vec![Element::CodeBlock( + vec![Expression::Execution(Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline(Some("bash"), vec!["rm -rf /"])], + })], + Span::default(), + )], + span: Span::default(), }])), }; @@ -220,24 +240,30 @@ vibe_coding : parameters: None, signature: None, elements: vec![ - Element::Description(vec![Paragraph(vec![Descriptive::Text( - "We must take action!", - )])]), - Element::Steps(vec![Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![ - Descriptive::Text("To take the action, we must:"), - Descriptive::CodeInline(Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("bash"), - vec!["rm -rf /"], - )], - })), - ])], - subscopes: vec![], - }]), + Element::Description( + vec![Paragraph(vec![Descriptive::Text("We must take action!")])], + Span::default(), + ), + Element::Steps( + vec![Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![ + Descriptive::Text("To take the action, we must:"), + Descriptive::CodeInline(Expression::Execution(Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("bash"), + vec!["rm -rf /"], + )], + })), + ])], + subscopes: vec![], + span: Span::default(), + }], + Span::default(), + ), ], + span: Span::default(), }])), }; @@ -270,35 +296,45 @@ We must take action! parameters: None, signature: None, elements: vec![ - Element::Description(vec![Paragraph(vec![Descriptive::Text( - "Record everything, with timestamps.", - )])]), - Element::Steps(vec![Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Record event as it happens", + Element::Description( + vec![Paragraph(vec![Descriptive::Text( + "Record everything, with timestamps.", )])], - subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("journalist"))], - subscopes: vec![Scope::CodeBlock { - expressions: vec![Expression::Tablet(vec![ - Pair { - label: "timestamp", - value: Expression::Execution(Function { - target: Identifier::dummy("now"), - parameters: vec![], - }), - }, - Pair { - label: "message", - value: Expression::Variable(Identifier::dummy("msg")), - }, - ])], - subscopes: vec![], + Span::default(), + ), + Element::Steps( + vec![Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Record event as it happens", + )])], + subscopes: vec![Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy("journalist"))], + subscopes: vec![Scope::CodeBlock { + expressions: vec![Expression::Tablet(vec![ + Pair { + label: "timestamp", + value: Expression::Execution(Function { + target: Identifier::dummy("now"), + parameters: vec![], + }), + }, + Pair { + label: "message", + value: Expression::Variable(Identifier::dummy("msg")), + }, + ])], + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), }], + span: Span::default(), }], - }]), + Span::default(), + ), ], + span: Span::default(), }])), }; @@ -334,62 +370,78 @@ Record everything, with timestamps. parameters: None, signature: None, elements: vec![ - Element::Title("Before patient leaves operating room"), - Element::Steps(vec![Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Verbally confirm:")])], - subscopes: vec![ - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "The name of the surgical procedure(s).", - )])], - subscopes: vec![], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Completion of instrument, sponge, and needle counts.", - )])], - subscopes: vec![], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Specimen labelling", - )])], - subscopes: vec![Scope::CodeBlock { - expressions: vec![Expression::Foreach( - vec![Identifier::dummy("specimen")], - Box::new(Expression::Variable(Identifier::dummy("specimens"))), - )], - subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy( - "nursing_team", - ))], - subscopes: vec![Scope::DependentBlock { - ordinal: "a", - description: vec![Paragraph(vec![ + Element::Title("Before patient leaves operating room", Span::default()), + Element::Steps( + vec![Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text( + "Verbally confirm:", + )])], + subscopes: vec![ + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "The name of the surgical procedure(s).", + )])], + subscopes: vec![], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Completion of instrument, sponge, and needle counts.", + )])], + subscopes: vec![], + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Specimen labelling", + )])], + subscopes: vec![Scope::CodeBlock { + expressions: vec![Expression::Foreach( + vec![Identifier::dummy("specimen")], + Box::new(Expression::Variable(Identifier::dummy( + "specimens", + ))), + )], + subscopes: vec![Scope::AttributeBlock { + attributes: vec![Attribute::Role(Identifier::dummy( + "nursing_team", + ))], + subscopes: vec![Scope::DependentBlock { + ordinal: "a", + description: vec![Paragraph(vec![ Descriptive::Text( "Read specimen labels aloud, including patient", ), Descriptive::Text("name."), ])], - subscopes: vec![], + subscopes: vec![], + span: Span::default(), + }], + span: Span::default(), }], + span: Span::default(), }], - }], - }, - Scope::ParallelBlock { - bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( - "Whether there are any equipment problems to be addressed.", - )])], - subscopes: vec![], - }, - ], - }]), + span: Span::default(), + }, + Scope::ParallelBlock { + bullet: '-', + description: vec![Paragraph(vec![Descriptive::Text( + "Whether there are any equipment problems to be addressed.", + )])], + subscopes: vec![], + span: Span::default(), + }, + ], + span: Span::default(), + }], + Span::default(), + ), ], + span: Span::default(), }])), }; let result = format_with_renderer(&document, 60); @@ -427,23 +479,30 @@ before_leaving : name: Identifier::dummy("main_procedure"), parameters: None, signature: None, - elements: vec![Element::Steps(vec![ - Scope::SectionChunk { - numeral: "I", - title: Some(Paragraph(vec![Descriptive::Text("First Section")])), - body: Technique::Procedures(vec![]), - }, - Scope::SectionChunk { - numeral: "II", - title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), - body: Technique::Procedures(vec![]), - }, - Scope::SectionChunk { - numeral: "III", - title: None, - body: Technique::Procedures(vec![]), - }, - ])], + elements: vec![Element::Steps( + vec![ + Scope::SectionChunk { + numeral: "I", + title: Some(Paragraph(vec![Descriptive::Text("First Section")])), + body: Technique::Procedures(vec![]), + span: Span::default(), + }, + Scope::SectionChunk { + numeral: "II", + title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), + body: Technique::Procedures(vec![]), + span: Span::default(), + }, + Scope::SectionChunk { + numeral: "III", + title: None, + body: Technique::Procedures(vec![]), + span: Span::default(), + }, + ], + Span::default(), + )], + span: Span::default(), }])), }; @@ -473,42 +532,51 @@ III. name: Identifier::dummy("test_procedure"), parameters: None, signature: None, - elements: vec![Element::Steps(vec![ - Scope::DependentBlock { - ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Main step")])], - subscopes: vec![Scope::DependentBlock { - ordinal: "a", + elements: vec![Element::Steps( + vec![ + Scope::DependentBlock { + ordinal: "1", + description: vec![Paragraph(vec![Descriptive::Text("Main step")])], + subscopes: vec![Scope::DependentBlock { + ordinal: "a", + description: vec![Paragraph(vec![Descriptive::Text( + "Substep with response", + )])], + subscopes: vec![Scope::ResponseBlock { + responses: vec![ + Response { + value: "Yes", + condition: None, + }, + Response { + value: "No", + condition: None, + }, + ], + span: Span::default(), + }], + span: Span::default(), + }], + span: Span::default(), + }, + Scope::DependentBlock { + ordinal: "2", description: vec![Paragraph(vec![Descriptive::Text( - "Substep with response", + "Simple step with response", )])], subscopes: vec![Scope::ResponseBlock { - responses: vec![ - Response { - value: "Yes", - condition: None, - }, - Response { - value: "No", - condition: None, - }, - ], - }], - }], - }, - Scope::DependentBlock { - ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( - "Simple step with response", - )])], - subscopes: vec![Scope::ResponseBlock { - responses: vec![Response { - value: "Confirmed", - condition: None, + responses: vec![Response { + value: "Confirmed", + condition: None, + }], + span: Span::default(), }], - }], - }, - ])], + span: Span::default(), + }, + ], + Span::default(), + )], + span: Span::default(), }])), }; From 3bc53614a6a385be03c69b090238b4f0e8b7115f Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Thu, 7 May 2026 00:34:02 +1000 Subject: [PATCH 5/9] Add Span to Expressioin --- src/domain/engine.rs | 61 ++-- src/domain/nasa_esa_iss/adapter.rs | 17 +- src/domain/recipe/adapter.rs | 10 +- src/domain/source/adapter.rs | 11 +- src/domain/source/typst.rs | 6 +- src/formatting/formatter.rs | 34 +- src/language/types.rs | 47 ++- src/parsing/checks/parser.rs | 534 ++++++++++++++++++----------- src/parsing/checks/verify.rs | 14 +- src/parsing/parser.rs | 129 ++++--- src/problem/messages.rs | 51 ++- tests/formatting/formatter.rs | 76 ++-- 12 files changed, 604 insertions(+), 386 deletions(-) diff --git a/src/domain/engine.rs b/src/domain/engine.rs index 4e435be8..25a62c86 100644 --- a/src/domain/engine.rs +++ b/src/domain/engine.rs @@ -156,7 +156,7 @@ impl<'i> Scope<'i> { match self { Scope::CodeBlock { expressions, .. } => { if expressions.len() == 1 { - if let Expression::Tablet(pairs) = &expressions[0] { + if let Expression::Tablet(pairs, _) = &expressions[0] { return Some(pairs); } } @@ -252,10 +252,10 @@ impl<'i> Response<'i> { /// Returns (expression_text, body_lines) where body_lines captures multiline /// content separately for distinct styling. fn render_expression_parts(expr: &Expression) -> (String, Vec) { - if let Expression::Execution(func) = expr { + if let Expression::Execution(func, _) = expr { let mut body = Vec::new(); for param in &func.parameters { - if let Expression::Multiline(_, lines) = param { + if let Expression::Multiline(_, lines, _) = param { body.extend( lines .iter() @@ -279,10 +279,10 @@ fn render_expression_parts(expr: &Expression) -> (String, Vec) { fn render_expression(expr: &Expression) -> String { match expr { - Expression::Repeat(inner) => { + Expression::Repeat(inner, _) => { format!("repeat {}", render_expression(inner)) } - Expression::Foreach(ids, inner) => { + Expression::Foreach(ids, inner, _) => { let vars = if ids.len() == 1 { ids[0] .value @@ -298,7 +298,7 @@ fn render_expression(expr: &Expression) -> String { }; format!("foreach {} in {}", vars, render_expression(inner)) } - Expression::Application(inv) => { + Expression::Application(inv, _) => { let name = match &inv.target { Target::Local(id) => id.value, Target::Remote(ext) => ext.value, @@ -313,7 +313,7 @@ fn render_expression(expr: &Expression) -> String { format!("<{}>", name) } } - Expression::Execution(func) => { + Expression::Execution(func, _) => { let args: Vec<_> = func .parameters .iter() @@ -326,12 +326,12 @@ fn render_expression(expr: &Expression) -> String { args.join(", ") ) } - Expression::Multiline(_, lines) => lines.join("\n"), - Expression::Variable(id) => id + Expression::Multiline(_, lines, _) => lines.join("\n"), + Expression::Variable(id, _) => id .value .to_string(), - Expression::Binding(inner, _) => render_expression(inner), - Expression::String(pieces) => { + Expression::Binding(inner, _, _) => render_expression(inner), + Expression::String(pieces, _) => { let mut result = String::new(); for piece in pieces { match piece { @@ -341,9 +341,9 @@ fn render_expression(expr: &Expression) -> String { } result } - Expression::Number(Numeric::Scientific(q)) => q.to_string(), - Expression::Number(Numeric::Integral(n)) => n.to_string(), - Expression::Tablet(_) => String::new(), + Expression::Number(Numeric::Scientific(q), _) => q.to_string(), + Expression::Number(Numeric::Integral(n), _) => n.to_string(), + Expression::Tablet(_, _) => String::new(), Expression::Separator => String::new(), } } @@ -411,16 +411,16 @@ impl<'i> Paragraph<'i> { fn expression_content(expr: &crate::language::Expression<'i>) -> String { match expr { - crate::language::Expression::Application(invocation) => { + crate::language::Expression::Application(invocation, _) => { Self::invocation_name(invocation).to_string() } - crate::language::Expression::Repeat(inner) => { + crate::language::Expression::Repeat(inner, _) => { format!("repeat {}", Self::expression_content(inner)) } - crate::language::Expression::Foreach(_, inner) => { + crate::language::Expression::Foreach(_, inner, _) => { format!("foreach {}", Self::expression_content(inner)) } - crate::language::Expression::Binding(inner, _) => Self::expression_content(inner), + crate::language::Expression::Binding(inner, _, _) => Self::expression_content(inner), _ => String::new(), } } @@ -458,16 +458,16 @@ impl<'i> Paragraph<'i> { expr: &crate::language::Expression<'i>, ) { match expr { - crate::language::Expression::Application(inv) => { + crate::language::Expression::Application(inv, _) => { targets.push(Self::invocation_name(inv)); } - crate::language::Expression::Repeat(inner) => { + crate::language::Expression::Repeat(inner, _) => { Self::extract_expression_invocations(targets, inner); } - crate::language::Expression::Foreach(_, inner) => { + crate::language::Expression::Foreach(_, inner, _) => { Self::extract_expression_invocations(targets, inner); } - crate::language::Expression::Binding(inner, _) => { + crate::language::Expression::Binding(inner, _, _) => { Self::extract_expression_invocations(targets, inner); } _ => {} @@ -543,7 +543,9 @@ impl Prose { #[cfg(test)] mod check { - use crate::language::{Descriptive, Expression, Identifier, Invocation, Paragraph, Target}; + use crate::language::{ + Descriptive, Expression, Identifier, Invocation, Paragraph, Span, Target, + }; fn local<'a>(name: &'a str) -> Invocation<'a> { Invocation { @@ -590,9 +592,13 @@ mod check { // CodeInline with repeat: { repeat } #[test] fn repeat_expression() { - let p = Paragraph(vec![Descriptive::CodeInline(Expression::Repeat(Box::new( - Expression::Application(local("incident_action_cycle")), - )))]); + let p = Paragraph(vec![Descriptive::CodeInline(Expression::Repeat( + Box::new(Expression::Application( + local("incident_action_cycle"), + Span::default(), + )), + Span::default(), + ))]); assert_eq!(p.text(), ""); assert_eq!(p.invocations(), vec!["incident_action_cycle"]); assert_eq!(p.content(), "repeat incident_action_cycle"); @@ -615,7 +621,8 @@ mod check { fn foreach_expression() { let p = Paragraph(vec![Descriptive::CodeInline(Expression::Foreach( vec![Identifier::dummy("design")], - Box::new(Expression::Application(local("implement"))), + Box::new(Expression::Application(local("implement"), Span::default())), + Span::default(), ))]); assert_eq!(p.text(), ""); assert_eq!(p.invocations(), vec!["implement"]); diff --git a/src/domain/nasa_esa_iss/adapter.rs b/src/domain/nasa_esa_iss/adapter.rs index 630cecce..3880643f 100644 --- a/src/domain/nasa_esa_iss/adapter.rs +++ b/src/domain/nasa_esa_iss/adapter.rs @@ -113,7 +113,9 @@ fn rewrite_expression(expr: &str) -> String { if let Some(rest) = expr.strip_prefix("foreach ") { if let Some((var, seq_expr)) = rest.split_once(" in ") { if let Some((start, end)) = parse_seq(seq_expr) { - let values: Vec = (start..=end).map(|n| n.to_string()).collect(); + let values: Vec = (start..=end) + .map(|n| n.to_string()) + .collect(); return format!("foreach {} {}", var.trim(), values.join(" ")); } } @@ -123,7 +125,16 @@ fn rewrite_expression(expr: &str) -> String { /// Parse `seq(A, B)` into a (start, end) pair. fn parse_seq(s: &str) -> Option<(i64, i64)> { - let inner = s.strip_prefix("seq(")?.strip_suffix(')')?; + let inner = s + .strip_prefix("seq(")? + .strip_suffix(')')?; let (a, b) = inner.split_once(", ")?; - Some((a.trim().parse().ok()?, b.trim().parse().ok()?)) + Some(( + a.trim() + .parse() + .ok()?, + b.trim() + .parse() + .ok()?, + )) } diff --git a/src/domain/recipe/adapter.rs b/src/domain/recipe/adapter.rs index 121fbc27..eed669e5 100644 --- a/src/domain/recipe/adapter.rs +++ b/src/domain/recipe/adapter.rs @@ -254,8 +254,8 @@ fn strip_ordinals(steps: &mut Vec) { /// Format an expression value as a human-readable quantity string. fn format_value(expr: &language::Expression) -> String { match expr { - language::Expression::Number(language::Numeric::Scientific(q)) => q.to_string(), - language::Expression::Number(language::Numeric::Integral(n)) => n.to_string(), + language::Expression::Number(language::Numeric::Scientific(q), _) => q.to_string(), + language::Expression::Number(language::Numeric::Integral(n), _) => n.to_string(), _ => String::new(), } } @@ -304,13 +304,13 @@ fn builtin_from_descriptive(d: &language::Descriptive) -> Option { fn builtin_from_expression(expr: &language::Expression) -> Option { match expr { - language::Expression::Application(inv) => builtin_from_invocation(inv), - language::Expression::Execution(func) => builtin_suffix( + language::Expression::Application(inv, _) => builtin_from_invocation(inv), + language::Expression::Execution(func, _) => builtin_suffix( func.target .value, &func.parameters, ), - language::Expression::Binding(inner, _) => builtin_from_expression(inner), + language::Expression::Binding(inner, _, _) => builtin_from_expression(inner), _ => None, } } diff --git a/src/domain/source/adapter.rs b/src/domain/source/adapter.rs index 8e997e84..1efba76c 100644 --- a/src/domain/source/adapter.rs +++ b/src/domain/source/adapter.rs @@ -48,11 +48,13 @@ fn coalesce(fragments: Vec) -> Vec { && frag.syntax != "BlockBegin" && frag.syntax != "BlockEnd" { - last.content.push_str(&frag.content); + last.content + .push_str(&frag.content); continue; } if is_text_whitespace(&frag) { - last.content.push_str(&frag.content); + last.content + .push_str(&frag.content); continue; } } @@ -64,10 +66,11 @@ fn coalesce(fragments: Vec) -> Vec { fn is_text_whitespace(frag: &Fragment) -> bool { (frag.syntax == "Neutral" || frag.syntax == "Description") - && !frag.content.is_empty() + && !frag + .content + .is_empty() && frag .content .bytes() .all(|b| b == b' ') } - diff --git a/src/domain/source/typst.rs b/src/domain/source/typst.rs index d956c07c..8aae3ce5 100644 --- a/src/domain/source/typst.rs +++ b/src/domain/source/typst.rs @@ -55,11 +55,7 @@ impl Render for Fragment { } else if self.syntax == "Newline" { out.raw(&format!("#{}()\n", func)); } else { - out.raw(&format!( - "#{}(\"{}\")", - func, - escape_string(&self.content) - )); + out.raw(&format!("#{}(\"{}\")", func, escape_string(&self.content))); } } } diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index fbe88cb3..6a05891f 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -330,7 +330,7 @@ impl<'i> Formatter<'i> { fn render_inline_code(&self, expr: &'i Expression) -> Vec<(Syntax, Cow<'i, str>)> { match expr { - Expression::Tablet(_) | Expression::Multiline(_, _) => { + Expression::Tablet(_, _) | Expression::Multiline(_, _, _) => { // These are not inline, caller should handle specially Vec::new() } @@ -657,7 +657,7 @@ impl<'i> Formatter<'i> { line.add_breakable(syntax, text); } Descriptive::CodeInline(expr) => match expr { - Expression::Tablet(_) => { + Expression::Tablet(_, _) => { line.flush(); self.add_fragment_reference(Syntax::Structure, "{"); self.append_char('\n'); @@ -669,7 +669,7 @@ impl<'i> Formatter<'i> { line = self.builder(); line.add_word(Syntax::Structure, "}"); } - Expression::Multiline(_, _) => { + Expression::Multiline(_, _, _) => { line.flush(); self.add_fragment_reference(Syntax::Structure, "{"); self.increase(4); @@ -681,11 +681,11 @@ impl<'i> Formatter<'i> { line.add_word(Syntax::Structure, "}"); } _ => match expr { - Expression::Execution(func) + Expression::Execution(func, _) if func .parameters .iter() - .any(|p| matches!(p, Expression::Multiline(_, _))) => + .any(|p| matches!(p, Expression::Multiline(_, _, _))) => { line.flush(); self.add_fragment_reference(Syntax::Neutral, " "); @@ -878,7 +878,7 @@ impl<'i> Formatter<'i> { let inline = if has_separator { true } else if expressions.len() == 1 { - if let Expression::Tablet(_) = &expressions[0] { + if let Expression::Tablet(_, _) = &expressions[0] { false } else { true @@ -993,10 +993,10 @@ impl<'i> Formatter<'i> { pub fn append_expression(&mut self, expression: &'i Expression) { match expression { - Expression::Variable(identifier) => { + Expression::Variable(identifier, _) => { self.add_fragment_reference(Syntax::Variable, identifier.value); } - Expression::String(pieces) => { + Expression::String(pieces, _) => { self.add_fragment_reference(Syntax::Quote, "\""); for piece in pieces { match piece { @@ -1014,8 +1014,8 @@ impl<'i> Formatter<'i> { } self.add_fragment_reference(Syntax::Quote, "\""); } - Expression::Number(numeric) => self.append_numeric(numeric), - Expression::Multiline(lang, lines) => { + Expression::Number(numeric, _) => self.append_numeric(numeric), + Expression::Multiline(lang, lines, _) => { self.append_char('\n'); self.indent(); @@ -1046,12 +1046,12 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::Quote, "```"); self.append_char('\n'); } - Expression::Repeat(expression) => { + Expression::Repeat(expression, _) => { self.add_fragment_reference(Syntax::Keyword, "repeat"); self.add_fragment_reference(Syntax::Neutral, " "); self.append_expression(expression); } - Expression::Foreach(variables, expression) => { + Expression::Foreach(variables, expression, _) => { self.add_fragment_reference(Syntax::Keyword, "foreach"); self.add_fragment_reference(Syntax::Neutral, " "); self.append_variables(variables); @@ -1060,16 +1060,16 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::Neutral, " "); self.append_expression(expression); } - Expression::Application(invocation) => self.append_application(invocation), - Expression::Execution(function) => self.append_function(function), - Expression::Binding(expression, variables) => { + Expression::Application(invocation, _) => self.append_application(invocation), + Expression::Execution(function, _) => self.append_function(function), + Expression::Binding(expression, variables, _) => { self.append_expression(expression); self.add_fragment_reference(Syntax::Neutral, " "); self.add_fragment_reference(Syntax::Structure, "~"); self.add_fragment_reference(Syntax::Neutral, " "); self.append_variables(variables); } - Expression::Tablet(pairs) => self.append_tablet(pairs), + Expression::Tablet(pairs, _) => self.append_tablet(pairs), Expression::Separator => {} } } @@ -1175,7 +1175,7 @@ impl<'i> Formatter<'i> { let mut has_multiline = false; for parameter in &function.parameters { - if let Expression::Multiline(_, _) = parameter { + if let Expression::Multiline(_, _, _) = parameter { has_multiline = true; break; } diff --git a/src/language/types.rs b/src/language/types.rs index f9c6615e..d34b52bd 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -368,21 +368,46 @@ pub enum Piece<'i> { Interpolation(Expression<'i>), } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Eq)] pub enum Expression<'i> { - Variable(Identifier<'i>), - String(Vec>), - Number(Numeric<'i>), - Multiline(Option<&'i str>, Vec<&'i str>), - Repeat(Box>), - Foreach(Vec>, Box>), - Application(Invocation<'i>), - Execution(Function<'i>), - Binding(Box>, Vec>), - Tablet(Vec>), + Variable(Identifier<'i>, Span), + String(Vec>, Span), + Number(Numeric<'i>, Span), + Multiline(Option<&'i str>, Vec<&'i str>, Span), + Repeat(Box>, Span), + Foreach(Vec>, Box>, Span), + Application(Invocation<'i>, Span), + Execution(Function<'i>, Span), + Binding(Box>, Vec>, Span), + Tablet(Vec>, Span), Separator, } +impl PartialEq for Expression<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Expression::Variable(a, _), Expression::Variable(b, _)) => a == b, + (Expression::String(a, _), Expression::String(b, _)) => a == b, + (Expression::Number(a, _), Expression::Number(b, _)) => a == b, + (Expression::Multiline(a1, a2, _), Expression::Multiline(b1, b2, _)) => { + a1 == b1 && a2 == b2 + } + (Expression::Repeat(a, _), Expression::Repeat(b, _)) => a == b, + (Expression::Foreach(a1, a2, _), Expression::Foreach(b1, b2, _)) => { + a1 == b1 && a2 == b2 + } + (Expression::Application(a, _), Expression::Application(b, _)) => a == b, + (Expression::Execution(a, _), Expression::Execution(b, _)) => a == b, + (Expression::Binding(a1, a2, _), Expression::Binding(b1, b2, _)) => { + a1 == b1 && a2 == b2 + } + (Expression::Tablet(a, _), Expression::Tablet(b, _)) => a == b, + (Expression::Separator, Expression::Separator) => true, + _ => false, + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum Numeric<'i> { Integral(i64), diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index 36a81e1f..15a60371 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -465,9 +465,9 @@ fn reading_invocations() { Ok(Invocation { target: Target::Local(Identifier::dummy("greetings")), parameters: Some(vec![ - Expression::Variable(Identifier::dummy("name")), - Expression::Variable(Identifier::dummy("title")), - Expression::Variable(Identifier::dummy("occupation")) + Expression::Variable(Identifier::dummy("name"), Span::default()), + Expression::Variable(Identifier::dummy("title"), Span::default()), + Expression::Variable(Identifier::dummy("occupation"), Span::default()) ]) }) ); @@ -1110,7 +1110,10 @@ fn code_blocks() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Variable(Identifier::dummy("count"))]) + Ok(vec![Expression::Variable( + Identifier::dummy("count"), + Span::default() + )]) ); // Test function with simple parameter @@ -1118,10 +1121,16 @@ fn code_blocks() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("sum"), - parameters: vec![Expression::Variable(Identifier::dummy("count"))] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("sum"), + parameters: vec![Expression::Variable( + Identifier::dummy("count"), + Span::default() + )] + }, + Span::default() + )]) ); // Test function with multiple parameters @@ -1129,14 +1138,17 @@ fn code_blocks() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("consume"), - parameters: vec![ - Expression::Variable(Identifier::dummy("apple")), - Expression::Variable(Identifier::dummy("banana")), - Expression::Variable(Identifier::dummy("chocolate")) - ] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("consume"), + parameters: vec![ + Expression::Variable(Identifier::dummy("apple"), Span::default()), + Expression::Variable(Identifier::dummy("banana"), Span::default()), + Expression::Variable(Identifier::dummy("chocolate"), Span::default()) + ] + }, + Span::default() + )]) ); // Test function with text parameter @@ -1144,10 +1156,16 @@ fn code_blocks() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::String(vec![Piece::Text("Hello, World")])] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::String( + vec![Piece::Text("Hello, World")], + Span::default() + )] + }, + Span::default() + )]) ); // Test function with multiline string parameter @@ -1159,13 +1177,17 @@ echo "Done"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("bash"), - vec!["ls -l", "echo \"Done\""] - )] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("bash"), + vec!["ls -l", "echo \"Done\""], + Span::default() + )] + }, + Span::default() + )]) ); // Test function with quantity parameter (like timer with duration) @@ -1173,18 +1195,24 @@ echo "Done"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("timer"), - parameters: vec![Expression::Number(Numeric::Scientific(Quantity { - mantissa: Decimal { - number: 3, - precision: 0 - }, - uncertainty: None, - magnitude: None, - symbol: "hr" - }))] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("timer"), + parameters: vec![Expression::Number( + Numeric::Scientific(Quantity { + mantissa: Decimal { + number: 3, + precision: 0 + }, + uncertainty: None, + magnitude: None, + symbol: "hr" + }), + Span::default() + )] + }, + Span::default() + )]) ); // Test function with integer quantity parameter @@ -1192,10 +1220,13 @@ echo "Done"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("measure"), - parameters: vec![Expression::Number(Numeric::Integral(100))] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("measure"), + parameters: vec![Expression::Number(Numeric::Integral(100), Span::default())] + }, + Span::default() + )]) ); // Test function with multiple integer parameters @@ -1203,13 +1234,16 @@ echo "Done"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("seq"), - parameters: vec![ - Expression::Number(Numeric::Integral(1)), - Expression::Number(Numeric::Integral(6)) - ] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("seq"), + parameters: vec![ + Expression::Number(Numeric::Integral(1), Span::default()), + Expression::Number(Numeric::Integral(6), Span::default()) + ] + }, + Span::default() + )]) ); // Test function with decimal quantity parameter @@ -1217,21 +1251,27 @@ echo "Done"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("wait"), - parameters: vec![ - Expression::Number(Numeric::Scientific(Quantity { - mantissa: Decimal { - number: 25, - precision: 1 - }, - uncertainty: None, - magnitude: None, - symbol: "s" - })), - Expression::String(vec![Piece::Text("yes")]) - ] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("wait"), + parameters: vec![ + Expression::Number( + Numeric::Scientific(Quantity { + mantissa: Decimal { + number: 25, + precision: 1 + }, + uncertainty: None, + magnitude: None, + symbol: "s" + }), + Span::default() + ), + Expression::String(vec![Piece::Text("yes")], Span::default()) + ] + }, + Span::default() + )]) ); } @@ -1252,20 +1292,24 @@ fn multiline() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("bash"), - vec![ - "./stuff", - "", - "if [ true ]", - "then", - " ./other args", - "fi" - ] - )] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("bash"), + vec![ + "./stuff", + "", + "if [ true ]", + "then", + " ./other args", + "fi" + ], + Span::default() + )] + }, + Span::default() + )]) ); // Test multiline without language tag @@ -1277,10 +1321,17 @@ echo "Done"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline(None, vec!["ls -l", "echo \"Done\""])] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + None, + vec!["ls -l", "echo \"Done\""], + Span::default() + )] + }, + Span::default() + )]) ); // Test multiline with intentional empty lines in the middle @@ -1296,20 +1347,24 @@ echo "Ending"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("shell"), - vec![ - "echo \"Starting\"", - "", - "echo \"Middle section\"", - "", - "", - "echo \"Ending\"" - ] - )] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("shell"), + vec![ + "echo \"Starting\"", + "", + "echo \"Middle section\"", + "", + "", + "echo \"Ending\"" + ], + Span::default() + )] + }, + Span::default() + )]) ); // Test that internal indentation relative to the base is preserved, @@ -1327,20 +1382,24 @@ echo "Ending"```) }"#, let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("python"), - vec![ - "def hello():", - " print(\"Hello\")", - " if True:", - " print(\"World\")", - "", - "hello()" - ] - )] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("python"), + vec![ + "def hello():", + " print(\"Hello\")", + " if True:", + " print(\"World\")", + "", + "hello()" + ], + Span::default() + )] + }, + Span::default() + )]) ); // Test that a trailing empty line from the closing delimiter is removed @@ -1352,10 +1411,17 @@ echo test let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline(None, vec!["echo test"])] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + None, + vec!["echo test"], + Span::default() + )] + }, + Span::default() + )]) ); // Test various indentation edge cases @@ -1371,20 +1437,24 @@ echo test let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("yaml"), - vec![ - "name: test", - "items:", - " - item1", - " - item2", - "config:", - " enabled: true" - ] - )] - })]) + Ok(vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("yaml"), + vec![ + "name: test", + "items:", + " - item1", + " - item2", + "config:", + " enabled: true" + ], + Span::default() + )] + }, + Span::default() + )]) ); } @@ -1397,10 +1467,13 @@ fn tablets() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Tablet(vec![Pair { - label: "name", - value: Expression::String(vec![Piece::Text("Johannes Grammerly")]) - }])]) + Ok(vec![Expression::Tablet( + vec![Pair { + label: "name", + value: Expression::String(vec![Piece::Text("Johannes Grammerly")], Span::default()) + }], + Span::default() + )]) ); // Test multiline tablet with string values @@ -1413,16 +1486,22 @@ fn tablets() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Tablet(vec![ - Pair { - label: "name", - value: Expression::String(vec![Piece::Text("Alice of Chains")]) - }, - Pair { - label: "age", - value: Expression::String(vec![Piece::Text("29")]) - } - ])]) + Ok(vec![Expression::Tablet( + vec![ + Pair { + label: "name", + value: Expression::String( + vec![Piece::Text("Alice of Chains")], + Span::default() + ) + }, + Pair { + label: "age", + value: Expression::String(vec![Piece::Text("29")], Span::default()) + } + ], + Span::default() + )]) ); // Test tablet with mixed value types @@ -1436,29 +1515,38 @@ fn tablets() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Tablet(vec![ - Pair { - label: "answer", - value: Expression::Number(Numeric::Integral(42)) - }, - Pair { - label: "message", - value: Expression::Variable(Identifier::dummy("msg")) - }, - Pair { - label: "timestamp", - value: Expression::Execution(Function { - target: Identifier::dummy("now"), - parameters: vec![] - }) - } - ])]) + Ok(vec![Expression::Tablet( + vec![ + Pair { + label: "answer", + value: Expression::Number(Numeric::Integral(42), Span::default()) + }, + Pair { + label: "message", + value: Expression::Variable(Identifier::dummy("msg"), Span::default()) + }, + Pair { + label: "timestamp", + value: Expression::Execution( + Function { + target: Identifier::dummy("now"), + parameters: vec![] + }, + Span::default() + ) + } + ], + Span::default() + )]) ); // Test empty tablet input.initialize("{ [ ] }"); let result = input.read_code_block(); - assert_eq!(result, Ok(vec![Expression::Tablet(vec![])])); + assert_eq!( + result, + Ok(vec![Expression::Tablet(vec![], Span::default())]) + ); // Test tablet with interpolated string values input.initialize( @@ -1470,16 +1558,22 @@ fn tablets() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Tablet(vec![ - Pair { - label: "context", - value: Expression::String(vec![Piece::Text("Details about the thing")]) - }, - Pair { - label: "status", - value: Expression::Variable(Identifier::dummy("active")) - } - ])]) + Ok(vec![Expression::Tablet( + vec![ + Pair { + label: "context", + value: Expression::String( + vec![Piece::Text("Details about the thing")], + Span::default() + ) + }, + Pair { + label: "status", + value: Expression::Variable(Identifier::dummy("active"), Span::default()) + } + ], + Span::default() + )]) ); } @@ -1490,20 +1584,35 @@ fn numeric_literals() { // Test simple integer input.initialize("{ 42 }"); let result = input.read_code_block(); - assert_eq!(result, Ok(vec![Expression::Number(Numeric::Integral(42))])); + assert_eq!( + result, + Ok(vec![Expression::Number( + Numeric::Integral(42), + Span::default() + )]) + ); // Test negative integer input.initialize("{ -123 }"); let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Number(Numeric::Integral(-123))]) + Ok(vec![Expression::Number( + Numeric::Integral(-123), + Span::default() + )]) ); // Test zero input.initialize("{ 0 }"); let result = input.read_code_block(); - assert_eq!(result, Ok(vec![Expression::Number(Numeric::Integral(0))])); + assert_eq!( + result, + Ok(vec![Expression::Number( + Numeric::Integral(0), + Span::default() + )]) + ); } #[test] @@ -1545,7 +1654,11 @@ fn test_foreach_expression() { result, Ok(vec![Expression::Foreach( vec![Identifier::dummy("item")], - Box::new(Expression::Variable(Identifier::dummy("items"))) + Box::new(Expression::Variable( + Identifier::dummy("items"), + Span::default() + )), + Span::default() )]) ); } @@ -1560,13 +1673,17 @@ fn foreach_tuple_pattern() { result, Ok(vec![Expression::Foreach( vec![Identifier::dummy("design"), Identifier::dummy("component")], - Box::new(Expression::Execution(Function { - target: Identifier::dummy("zip"), - parameters: vec![ - Expression::Variable(Identifier::dummy("designs")), - Expression::Variable(Identifier::dummy("components")) - ] - })) + Box::new(Expression::Execution( + Function { + target: Identifier::dummy("zip"), + parameters: vec![ + Expression::Variable(Identifier::dummy("designs"), Span::default()), + Expression::Variable(Identifier::dummy("components"), Span::default()) + ] + }, + Span::default() + )), + Span::default() )]) ); @@ -1581,14 +1698,18 @@ fn foreach_tuple_pattern() { Identifier::dummy("b"), Identifier::dummy("c") ], - Box::new(Expression::Execution(Function { - target: Identifier::dummy("zip"), - parameters: vec![ - Expression::Variable(Identifier::dummy("list1")), - Expression::Variable(Identifier::dummy("list2")), - Expression::Variable(Identifier::dummy("list3")) - ] - })) + Box::new(Expression::Execution( + Function { + target: Identifier::dummy("zip"), + parameters: vec![ + Expression::Variable(Identifier::dummy("list1"), Span::default()), + Expression::Variable(Identifier::dummy("list2"), Span::default()), + Expression::Variable(Identifier::dummy("list3"), Span::default()) + ] + }, + Span::default() + )), + Span::default() )]) ); } @@ -1602,11 +1723,15 @@ fn tuple_binding_expression() { assert_eq!( result, Ok(vec![Expression::Binding( - Box::new(Expression::Application(Invocation { - target: Target::Local(Identifier::dummy("get_coordinates")), - parameters: Some(vec![]) - })), - vec![Identifier::dummy("x"), Identifier::dummy("y")] + Box::new(Expression::Application( + Invocation { + target: Target::Local(Identifier::dummy("get_coordinates")), + parameters: Some(vec![]) + }, + Span::default() + )), + vec![Identifier::dummy("x"), Identifier::dummy("y")], + Span::default() )]) ); } @@ -1619,9 +1744,13 @@ fn test_repeat_expression() { let result = input.read_code_block(); assert_eq!( result, - Ok(vec![Expression::Repeat(Box::new(Expression::Variable( - Identifier::dummy("count") - )))]) + Ok(vec![Expression::Repeat( + Box::new(Expression::Variable( + Identifier::dummy("count"), + Span::default() + )), + Span::default() + )]) ); } @@ -1645,7 +1774,10 @@ fn test_repeat_keyword_boundary() { // Should parse as identifier, not repeat assert_eq!( result, - Ok(vec![Expression::Variable(Identifier::dummy("repeater"))]) + Ok(vec![Expression::Variable( + Identifier::dummy("repeater"), + Span::default() + )]) ); } @@ -2231,7 +2363,7 @@ This is { exec(a, // Second element should be the multiline code inline match &descriptives[1] { - Descriptive::CodeInline(Expression::Execution(func)) => { + Descriptive::CodeInline(Expression::Execution(func, _)) => { assert_eq!( func.target .value, @@ -2243,7 +2375,7 @@ This is { exec(a, 3 ); // Check that all parameters were parsed correctly - if let Expression::Variable(Identifier { value: name, .. }) = &func.parameters[0] { + if let Expression::Variable(Identifier { value: name, .. }, _) = &func.parameters[0] { assert_eq!(*name, "a"); } else { panic!("First parameter should be variable 'a'"); diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index 1b1c1174..6ba9740d 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -697,9 +697,11 @@ before_leaving : subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Foreach( vec![Identifier::dummy("specimen")], - Box::new(Expression::Variable(Identifier::dummy( - "specimens" - ))) + Box::new(Expression::Variable( + Identifier::dummy("specimens"), + Span::default() + )), + Span::default(), )], subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role(Identifier::dummy( @@ -1429,7 +1431,8 @@ III. Implementation )), parameters: Some(vec![ Expression::Variable( - Identifier::dummy("concept") + Identifier::dummy("concept"), + Span::default(), ) ]), }), @@ -1448,7 +1451,8 @@ III. Implementation )), parameters: Some(vec![ Expression::Variable( - Identifier::dummy("concept") + Identifier::dummy("concept"), + Span::default(), ) ]), }), diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index 9acb761b..87c96f9e 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -732,6 +732,13 @@ impl<'i> Parser<'i> { } } + fn span_since(&self, start: usize) -> Span { + Span { + offset: start, + length: self.offset - start, + } + } + // because test cases and trivial single-line examples might omit an // ending newline, this also returns Ok if end of input is reached. fn require_newline(&mut self) -> Result<(), ParsingError> { @@ -1109,10 +1116,7 @@ impl<'i> Parser<'i> { }, ) { Ok(title) => { - let span = Span { - offset: elem_start, - length: parser.offset - elem_start, - }; + let span = parser.span_since(elem_start); elements.push(Element::Title(title, span)); } Err(error) => { @@ -1124,10 +1128,7 @@ impl<'i> Parser<'i> { } else if is_code_block(content) { match parser.read_code_block() { Ok(expressions) => { - let span = Span { - offset: elem_start, - length: parser.offset - elem_start, - }; + let span = parser.span_since(elem_start); elements.push(Element::CodeBlock(expressions, span)); } Err(error) => { @@ -1140,10 +1141,7 @@ impl<'i> Parser<'i> { if is_attribute_assignment(content) { match parser.read_attribute_scope() { Ok(attribute_block) => { - let span = Span { - offset: elem_start, - length: parser.offset - elem_start, - }; + let span = parser.span_since(elem_start); elements.push(Element::Steps(vec![attribute_block], span)); } Err(error) => { @@ -1186,10 +1184,7 @@ impl<'i> Parser<'i> { } } if !steps.is_empty() { - let span = Span { - offset: elem_start, - length: parser.offset - elem_start, - }; + let span = parser.span_since(elem_start); elements.push(Element::Steps(steps, span)); } } else if malformed_step_pattern(content) { @@ -1224,10 +1219,7 @@ impl<'i> Parser<'i> { ) { Ok(description) => { if !description.is_empty() { - let span = Span { - offset: elem_start, - length: parser.offset - elem_start, - }; + let span = parser.span_since(elem_start); elements.push(Element::Description(description, span)); } } @@ -1454,7 +1446,7 @@ impl<'i> Parser<'i> { } let start = inner.offset; let expression = inner.read_expression()?; - let is_variable = if let Expression::Variable(_) = &expression { + let is_variable = if let Expression::Variable(_, _) = &expression { true } else { false @@ -1488,6 +1480,7 @@ impl<'i> Parser<'i> { fn read_expression(&mut self) -> Result, ParsingError> { self.trim_whitespace(); + let start = self.offset; let content = self .source .trim_ascii_start(); @@ -1514,15 +1507,18 @@ impl<'i> Parser<'i> { self.read_tablet_expression() } else if is_numeric(content) { let numeric = self.read_numeric()?; - Ok(Expression::Number(numeric)) + let span = self.span_since(start); + Ok(Expression::Number(numeric, span)) } else if is_string_literal(content) { let parts = self.take_block_chars("a string literal", '"', '"', false, |inner| { inner.parse_string_pieces(inner.source) })?; - Ok(Expression::String(parts)) + let span = self.span_since(start); + Ok(Expression::String(parts, span)) } else if is_invocation(content) { let invocation = self.read_invocation()?; - Ok(Expression::Application(invocation)) + let span = self.span_since(start); + Ok(Expression::Application(invocation, span)) } else if is_function(content) { // Extract the entire text before the opening parenthesis self.trim_whitespace(); @@ -1553,7 +1549,8 @@ impl<'i> Parser<'i> { let parameters = self.read_parameters()?; let function = Function { target, parameters }; - Ok(Expression::Execution(function)) + let span = self.span_since(start); + Ok(Expression::Execution(function, span)) } else { let identifier = self.read_identifier()?; if self @@ -1569,7 +1566,8 @@ impl<'i> Parser<'i> { .length, )); } - Ok(Expression::Variable(identifier)) + let span = identifier.span; + Ok(Expression::Variable(identifier, span)) } } @@ -1578,6 +1576,8 @@ impl<'i> Parser<'i> { // - identifier // - (identifier, identifier, ...) + let start = self.offset; + // Skip "foreach" keyword - we already know it's there from starts_with check self.advance(7); self.trim_whitespace(); @@ -1599,7 +1599,8 @@ impl<'i> Parser<'i> { let expression = self.read_expression()?; - Ok(Expression::Foreach(identifiers, Box::new(expression))) + let span = self.span_since(start); + Ok(Expression::Foreach(identifiers, Box::new(expression), span)) } fn read_identifiers(&mut self) -> Result>, ParsingError> { @@ -1651,6 +1652,7 @@ impl<'i> Parser<'i> { fn read_repeat_expression(&mut self) -> Result, ParsingError> { // Parse "repeat " + let start = self.offset; self.advance(6); self.trim_whitespace(); @@ -1659,10 +1661,13 @@ impl<'i> Parser<'i> { // parsing. let expression = self.read_expression()?; - Ok(Expression::Repeat(Box::new(expression))) + let span = self.span_since(start); + Ok(Expression::Repeat(Box::new(expression), span)) } fn read_binding_expression(&mut self) -> Result, ParsingError> { + let start = self.offset; + // Parse the expression before the ~ operator let expression = self.take_until(&['~'], |inner| { let start_pos = inner.offset; @@ -1690,11 +1695,13 @@ impl<'i> Parser<'i> { let identifiers = self.read_identifiers()?; - Ok(Expression::Binding(Box::new(expression), identifiers)) + let span = self.span_since(start); + Ok(Expression::Binding(Box::new(expression), identifiers, span)) } fn read_tablet_expression(&mut self) -> Result, ParsingError> { - self.take_block_chars("a tablet", '[', ']', true, |outer| { + let start = self.offset; + let pairs = self.take_block_chars("a tablet", '[', ']', true, |outer| { let mut pairs = Vec::new(); loop { @@ -1755,8 +1762,10 @@ impl<'i> Parser<'i> { outer.trim_whitespace(); } - Ok(Expression::Tablet(pairs)) - }) + Ok(pairs) + })?; + let span = self.span_since(start); + Ok(Expression::Tablet(pairs, span)) } fn parse_string_pieces(&mut self, raw: &'i str) -> Result>, ParsingError> { @@ -2142,10 +2151,7 @@ impl<'i> Parser<'i> { ordinal: number, description: text, subscopes: scopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }); }) } @@ -2175,10 +2181,7 @@ impl<'i> Parser<'i> { bullet: '-', description: text, subscopes: scopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }); }) } @@ -2223,10 +2226,7 @@ impl<'i> Parser<'i> { ordinal: letter, description: text, subscopes: scopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }) }, ) @@ -2259,10 +2259,7 @@ impl<'i> Parser<'i> { bullet: '-', description: text, subscopes: scopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }) }, ) @@ -2305,10 +2302,7 @@ impl<'i> Parser<'i> { ordinal: numeral, description: text, subscopes: scopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }) }, ) @@ -2543,6 +2537,7 @@ impl<'i> Parser<'i> { break; } + let param_start = outer.offset; if content.starts_with("```") { let (lang, lines) = outer .take_block_delimited("```", |inner| inner.parse_multiline_content()) @@ -2554,16 +2549,19 @@ impl<'i> Parser<'i> { ) => ParsingError::InvalidMultiline(offset, 0), _ => err, })?; - params.push(Expression::Multiline(lang, lines)); + let span = outer.span_since(param_start); + params.push(Expression::Multiline(lang, lines, span)); } else if content.starts_with("\"") { let parts = outer.take_block_chars("a string literal", '"', '"', false, |inner| { inner.parse_string_pieces(inner.source) })?; - params.push(Expression::String(parts)); + let span = outer.span_since(param_start); + params.push(Expression::String(parts, span)); } else if is_numeric_quantity(content) { let numeric = outer.read_numeric_quantity()?; - params.push(Expression::Number(numeric)); + let span = outer.span_since(param_start); + params.push(Expression::Number(numeric, span)); } else if is_numeric_integral(content) || content .as_bytes() @@ -2576,10 +2574,12 @@ impl<'i> Parser<'i> { .is_some_and(|b| b.is_ascii_digit()) { let decimal = outer.read_decimal_part()?; - params.push(Expression::Number(Numeric::Integral(decimal.number))); + let span = outer.span_since(param_start); + params.push(Expression::Number(Numeric::Integral(decimal.number), span)); } else { let name = outer.read_identifier()?; - params.push(Expression::Variable(name)); + let span = name.span; + params.push(Expression::Variable(name, span)); } // Handle comma separation @@ -2743,10 +2743,7 @@ impl<'i> Parser<'i> { let responses = self.read_responses()?; scopes.push(Scope::ResponseBlock { responses, - span: Span { - offset: responses_start, - length: self.offset - responses_start, - }, + span: self.span_since(responses_start), }); } else { break; @@ -2765,10 +2762,7 @@ impl<'i> Parser<'i> { Ok(Scope::AttributeBlock { attributes, subscopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }) }) } @@ -2791,10 +2785,7 @@ impl<'i> Parser<'i> { Ok(Scope::CodeBlock { expressions, subscopes, - span: Span { - offset: start, - length: outer.offset - start, - }, + span: outer.span_since(start), }) }, ) diff --git a/src/problem/messages.rs b/src/problem/messages.rs index 94189602..d1810742 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -477,7 +477,10 @@ author of the Technique. }, Invocation { target: Target::Local(Identifier::dummy("check_vitals")), - parameters: Some(vec![Expression::Variable(Identifier::dummy("patient"))]), + parameters: Some(vec![Expression::Variable( + Identifier::dummy("patient"), + Span::default(), + )]), }, ]; @@ -504,7 +507,10 @@ If the procedure takes parameters they can be specified in parenthesis: let examples = vec![ Function { target: Identifier::dummy("exec"), - parameters: vec![Expression::String(vec![Piece::Text("ls -la")])], + parameters: vec![Expression::String( + vec![Piece::Text("ls -la")], + Span::default(), + )], }, Function { target: Identifier::dummy("now"), @@ -513,8 +519,8 @@ If the procedure takes parameters they can be specified in parenthesis: Function { target: Identifier::dummy("calculate"), parameters: vec![ - Expression::Variable(Identifier::dummy("a")), - Expression::Variable(Identifier::dummy("b")), + Expression::Variable(Identifier::dummy("a"), Span::default()), + Expression::Variable(Identifier::dummy("b"), Span::default()), ], }, ]; @@ -541,14 +547,27 @@ expressions as parameters as required: } ParsingError::InvalidCodeBlock(_, _) => { let examples = vec![ - Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::String(vec![Piece::Text("command")])], - }), - Expression::Repeat(Box::new(Expression::Number(Numeric::Integral(5)))), + Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::String( + vec![Piece::Text("command")], + Span::default(), + )], + }, + Span::default(), + ), + Expression::Repeat( + Box::new(Expression::Number(Numeric::Integral(5), Span::default())), + Span::default(), + ), Expression::Foreach( vec![Identifier::dummy("patient")], - Box::new(Expression::Variable(Identifier::dummy("patients"))), + Box::new(Expression::Variable( + Identifier::dummy("patients"), + Span::default(), + )), + Span::default(), ), ]; @@ -691,11 +710,19 @@ nested underneath a role or place assignment. let examples = vec![ Expression::Foreach( vec![Identifier::dummy("patient")], - Box::new(Expression::Variable(Identifier::dummy("patients"))), + Box::new(Expression::Variable( + Identifier::dummy("patients"), + Span::default(), + )), + Span::default(), ), Expression::Foreach( vec![Identifier::dummy("name"), Identifier::dummy("value")], - Box::new(Expression::Variable(Identifier::dummy("data"))), + Box::new(Expression::Variable( + Identifier::dummy("data"), + Span::default(), + )), + Span::default(), ), ]; diff --git a/tests/formatting/formatter.rs b/tests/formatting/formatter.rs index 3d422727..5b893a1d 100644 --- a/tests/formatting/formatter.rs +++ b/tests/formatting/formatter.rs @@ -202,10 +202,17 @@ win_le_tour : Bicycle -> YellowJersey parameters: None, signature: None, elements: vec![Element::CodeBlock( - vec![Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline(Some("bash"), vec!["rm -rf /"])], - })], + vec![Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("bash"), + vec!["rm -rf /"], + Span::default(), + )], + }, + Span::default(), + )], Span::default(), )], span: Span::default(), @@ -249,13 +256,17 @@ vibe_coding : ordinal: "1", description: vec![Paragraph(vec![ Descriptive::Text("To take the action, we must:"), - Descriptive::CodeInline(Expression::Execution(Function { - target: Identifier::dummy("exec"), - parameters: vec![Expression::Multiline( - Some("bash"), - vec!["rm -rf /"], - )], - })), + Descriptive::CodeInline(Expression::Execution( + Function { + target: Identifier::dummy("exec"), + parameters: vec![Expression::Multiline( + Some("bash"), + vec!["rm -rf /"], + Span::default(), + )], + }, + Span::default(), + )), ])], subscopes: vec![], span: Span::default(), @@ -311,19 +322,28 @@ We must take action! subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role(Identifier::dummy("journalist"))], subscopes: vec![Scope::CodeBlock { - expressions: vec![Expression::Tablet(vec![ - Pair { - label: "timestamp", - value: Expression::Execution(Function { - target: Identifier::dummy("now"), - parameters: vec![], - }), - }, - Pair { - label: "message", - value: Expression::Variable(Identifier::dummy("msg")), - }, - ])], + expressions: vec![Expression::Tablet( + vec![ + Pair { + label: "timestamp", + value: Expression::Execution( + Function { + target: Identifier::dummy("now"), + parameters: vec![], + }, + Span::default(), + ), + }, + Pair { + label: "message", + value: Expression::Variable( + Identifier::dummy("msg"), + Span::default(), + ), + }, + ], + Span::default(), + )], subscopes: vec![], span: Span::default(), }], @@ -402,9 +422,11 @@ Record everything, with timestamps. subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Foreach( vec![Identifier::dummy("specimen")], - Box::new(Expression::Variable(Identifier::dummy( - "specimens", - ))), + Box::new(Expression::Variable( + Identifier::dummy("specimens"), + Span::default(), + )), + Span::default(), )], subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role(Identifier::dummy( From b9cc8609e20a8462c1bf3ef12af77785a284ce5a Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Thu, 7 May 2026 09:09:53 +1000 Subject: [PATCH 6/9] Add Spans to Attribute, Response, Paragraph, and Metadata --- src/domain/engine.rs | 16 +-- src/formatting/formatter.rs | 4 +- src/language/types.rs | 63 +++++++-- src/parsing/checks/parser.rs | 242 ++++++++++++++++++++++++++-------- src/parsing/checks/verify.rs | 242 +++++++++++++++++++++------------- src/parsing/parser.rs | 16 ++- src/problem/messages.rs | 19 ++- tests/formatting/formatter.rs | 65 +++++---- 8 files changed, 469 insertions(+), 198 deletions(-) diff --git a/src/domain/engine.rs b/src/domain/engine.rs index 25a62c86..6ec8f4e3 100644 --- a/src/domain/engine.rs +++ b/src/domain/engine.rs @@ -127,7 +127,7 @@ impl<'i> Scope<'i> { Scope::AttributeBlock { attributes, .. } => attributes .iter() .filter_map(|attr| match attr { - Attribute::Role(id) => Some(id.value), + Attribute::Role(id, _) => Some(id.value), _ => None, }) .collect::>() @@ -142,7 +142,7 @@ impl<'i> Scope<'i> { Scope::AttributeBlock { attributes, .. } => attributes .iter() .filter_map(|attr| match attr { - Attribute::Place(id) => Some(id.value), + Attribute::Place(id, _) => Some(id.value), _ => None, }) .collect::>() @@ -557,7 +557,7 @@ mod check { // Pure text: "Ensure physical and digital safety" #[test] fn text_only_paragraph() { - let p = Paragraph(vec![Descriptive::Text( + let p = Paragraph::new(vec![Descriptive::Text( "Ensure physical and digital safety", )]); assert_eq!(p.text(), "Ensure physical and digital safety"); @@ -570,7 +570,7 @@ mod check { // Bare invocation: #[test] fn invocation_only_paragraph() { - let p = Paragraph(vec![Descriptive::Application(local("ensure_safety"))]); + let p = Paragraph::new(vec![Descriptive::Application(local("ensure_safety"))]); assert_eq!(p.text(), ""); assert_eq!(p.invocations(), vec!["ensure_safety"]); assert_eq!(p.content(), "ensure_safety"); @@ -580,7 +580,7 @@ mod check { // Text is present so content() returns just the text. #[test] fn mixed_text_and_invocation() { - let p = Paragraph(vec![ + let p = Paragraph::new(vec![ Descriptive::Text("Define Requirements"), Descriptive::Application(local("define_requirements")), ]); @@ -592,7 +592,7 @@ mod check { // CodeInline with repeat: { repeat } #[test] fn repeat_expression() { - let p = Paragraph(vec![Descriptive::CodeInline(Expression::Repeat( + let p = Paragraph::new(vec![Descriptive::CodeInline(Expression::Repeat( Box::new(Expression::Application( local("incident_action_cycle"), Span::default(), @@ -607,7 +607,7 @@ mod check { // Binding wrapping an invocation: (s) ~ e #[test] fn binding_with_invocation() { - let p = Paragraph(vec![Descriptive::Binding( + let p = Paragraph::new(vec![Descriptive::Binding( Box::new(Descriptive::Application(local("observe"))), vec![Identifier::dummy("e")], )]); @@ -619,7 +619,7 @@ mod check { // CodeInline with foreach: { foreach design in designs } #[test] fn foreach_expression() { - let p = Paragraph(vec![Descriptive::CodeInline(Expression::Foreach( + let p = Paragraph::new(vec![Descriptive::CodeInline(Expression::Foreach( vec![Identifier::dummy("design")], Box::new(Expression::Application(local("implement"), Span::default())), Span::default(), diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index 6a05891f..7749903a 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -979,11 +979,11 @@ impl<'i> Formatter<'i> { self.add_fragment_reference(Syntax::Neutral, " + "); } match attribute { - Attribute::Role(name) => { + Attribute::Role(name, _) => { self.add_fragment_reference(Syntax::Attribute, "@"); self.add_fragment_reference(Syntax::Attribute, name.value); } - Attribute::Place(name) => { + Attribute::Place(name, _) => { self.add_fragment_reference(Syntax::Attribute, "^"); self.add_fragment_reference(Syntax::Attribute, name.value); } diff --git a/src/language/types.rs b/src/language/types.rs index d34b52bd..ab52c1b3 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -16,12 +16,22 @@ pub struct Document<'i> { pub body: Option>, } -#[derive(Eq, Debug, PartialEq)] +#[derive(Eq, Debug)] pub struct Metadata<'i> { pub version: u8, pub license: Option<&'i str>, pub copyright: Option<&'i str>, pub domain: Option<&'i str>, + pub span: Span, +} + +impl PartialEq for Metadata<'_> { + fn eq(&self, other: &Self) -> bool { + self.version == other.version + && self.license == other.license + && self.copyright == other.copyright + && self.domain == other.domain + } } impl Default for Metadata<'_> { @@ -31,6 +41,7 @@ impl Default for Metadata<'_> { license: None, copyright: None, domain: None, + span: Span::default(), } } } @@ -196,8 +207,20 @@ pub struct Invocation<'i> { // types for descriptive content -#[derive(Eq, Debug, PartialEq)] -pub struct Paragraph<'i>(pub Vec>); +#[derive(Eq, Debug)] +pub struct Paragraph<'i>(pub Vec>, pub Span); + +impl PartialEq for Paragraph<'_> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl<'i> Paragraph<'i> { + pub fn new(descriptives: Vec>) -> Self { + Paragraph(descriptives, Span::default()) + } +} #[derive(Eq, Debug, PartialEq)] pub enum Descriptive<'i> { @@ -334,18 +357,35 @@ impl PartialEq for Scope<'_> { // enum responses like 'Yes' | 'No' -#[derive(Eq, Debug, PartialEq)] +#[derive(Eq, Debug)] pub struct Response<'i> { pub value: &'i str, pub condition: Option<&'i str>, + pub span: Span, +} + +impl PartialEq for Response<'_> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.condition == other.condition + } } // attributes like @chef -#[derive(Eq, Debug, PartialEq)] +#[derive(Eq, Debug)] pub enum Attribute<'i> { - Role(Identifier<'i>), - Place(Identifier<'i>), + Role(Identifier<'i>, Span), + Place(Identifier<'i>, Span), +} + +impl PartialEq for Attribute<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Attribute::Role(a, _), Attribute::Role(b, _)) => a == b, + (Attribute::Place(a, _), Attribute::Place(b, _)) => a == b, + _ => false, + } + } } // now types used within code blocks @@ -582,7 +622,11 @@ pub fn validate_response(input: &str) -> Option> { None => None, }; - Some(Response { value, condition }) + Some(Response { + value, + condition, + span: Span::default(), + }) } #[cfg(test)] @@ -789,6 +833,7 @@ mod check { license: None, copyright: None, domain: None, + span: Span::default(), }; t1 @@ -801,6 +846,7 @@ mod check { license: None, copyright: None, domain: None, + span: Span::default(), }; assert_eq!(Metadata::default(), t1); @@ -810,6 +856,7 @@ mod check { license: Some("MIT"), copyright: Some("ACME, Inc"), domain: Some("checklist"), + span: Span::default(), }; let t3 = maker(); diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index 15a60371..6f54cc85 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -557,7 +557,7 @@ fn read_toplevel_steps() { result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("First step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("First step")])], subscopes: vec![], span: Span::default(), }) @@ -575,7 +575,7 @@ fn read_toplevel_steps() { result, Ok(Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "a top-level task to be one in parallel with" )]),], subscopes: vec![], @@ -587,7 +587,9 @@ fn read_toplevel_steps() { result, Ok(Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text("another top-level task")]),], + description: vec![Paragraph::new(vec![Descriptive::Text( + "another top-level task" + )]),], subscopes: vec![], span: Span::default(), }) @@ -604,7 +606,7 @@ fn read_toplevel_steps() { result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Have you done the first thing in the first one?" )])], subscopes: vec![], @@ -629,7 +631,9 @@ fn reading_substeps_basic() { result, Ok(Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text("First subordinate task")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "First subordinate task" + )])], subscopes: vec![], span: Span::default(), }) @@ -642,7 +646,7 @@ fn reading_substeps_basic() { result, Ok(Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text("Parallel task")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Parallel task")])], subscopes: vec![], span: Span::default(), }) @@ -666,17 +670,17 @@ fn single_step_with_dependent_substeps() { result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Main step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Main step")])], subscopes: vec![ Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text("First substep")])], + description: vec![Paragraph::new(vec![Descriptive::Text("First substep")])], subscopes: vec![], span: Span::default(), }, Scope::DependentBlock { ordinal: "b", - description: vec![Paragraph(vec![Descriptive::Text("Second substep")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Second substep")])], subscopes: vec![], span: Span::default(), }, @@ -703,17 +707,17 @@ fn single_step_with_parallel_substeps() { result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Main step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Main step")])], subscopes: vec![ Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text("First substep")])], + description: vec![Paragraph::new(vec![Descriptive::Text("First substep")])], subscopes: vec![], span: Span::default(), }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text("Second substep")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Second substep")])], subscopes: vec![], span: Span::default(), }, @@ -741,10 +745,10 @@ fn multiple_steps_with_substeps() { first_result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("First step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("First step")])], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text("Substep")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Substep")])], subscopes: vec![], span: Span::default(), }], @@ -756,7 +760,7 @@ fn multiple_steps_with_substeps() { second_result, Ok(Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text("Second step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Second step")])], subscopes: vec![], span: Span::default(), }) @@ -807,23 +811,25 @@ fn read_step_with_content() { result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Have you done the first thing in the first one?" )])], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Do the first thing. Then ask yourself if you are done:" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "No", - condition: Some("but I have an excuse") + condition: Some("but I have an excuse"), + span: Span::default() } ], span: Span::default(), @@ -1846,15 +1852,18 @@ fn splitting_by() { Ok(vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "No", - condition: None + condition: None, + span: Span::default() }, Response { value: "Maybe", - condition: None + condition: None, + span: Span::default() } ]) ); @@ -1871,7 +1880,8 @@ fn reading_responses() { result, Ok(vec![Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }]) ); @@ -1883,11 +1893,13 @@ fn reading_responses() { Ok(vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "No", - condition: None + condition: None, + span: Span::default() } ]) ); @@ -1900,15 +1912,18 @@ fn reading_responses() { Ok(vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "No", - condition: None + condition: None, + span: Span::default() }, Response { value: "Not Applicable", - condition: None + condition: None, + span: Span::default() } ]) ); @@ -1920,7 +1935,8 @@ fn reading_responses() { result, Ok(vec![Response { value: "Yes", - condition: Some("and equipment available") + condition: Some("and equipment available"), + span: Span::default() }]) ); @@ -1932,11 +1948,13 @@ fn reading_responses() { Ok(vec![ Response { value: "Option A", - condition: None + condition: None, + span: Span::default() }, Response { value: "Option B", - condition: None + condition: None, + span: Span::default() } ]) ); @@ -1949,14 +1967,23 @@ fn reading_attributes() { // Test simple role input.initialize("@chef"); let result = input.read_attributes(); - assert_eq!(result, Ok(vec![Attribute::Role(Identifier::dummy("chef"))])); + assert_eq!( + result, + Ok(vec![Attribute::Role( + Identifier::dummy("chef"), + Span::default() + )]) + ); // Test simple place input.initialize("^kitchen"); let result = input.read_attributes(); assert_eq!( result, - Ok(vec![Attribute::Place(Identifier::dummy("kitchen"))]) + Ok(vec![Attribute::Place( + Identifier::dummy("kitchen"), + Span::default() + )]) ); // Test multiple roles @@ -1965,8 +1992,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier::dummy("master_chef")), - Attribute::Role(Identifier::dummy("barista")) + Attribute::Role(Identifier::dummy("master_chef"), Span::default()), + Attribute::Role(Identifier::dummy("barista"), Span::default()) ]) ); @@ -1976,8 +2003,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Place(Identifier::dummy("kitchen")), - Attribute::Place(Identifier::dummy("bath_room")) + Attribute::Place(Identifier::dummy("kitchen"), Span::default()), + Attribute::Place(Identifier::dummy("bath_room"), Span::default()) ]) ); @@ -1987,8 +2014,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier::dummy("chef")), - Attribute::Place(Identifier::dummy("bathroom")) + Attribute::Role(Identifier::dummy("chef"), Span::default()), + Attribute::Place(Identifier::dummy("bathroom"), Span::default()) ]) ); @@ -1998,8 +2025,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Place(Identifier::dummy("kitchen")), - Attribute::Role(Identifier::dummy("barista")) + Attribute::Place(Identifier::dummy("kitchen"), Span::default()), + Attribute::Role(Identifier::dummy("barista"), Span::default()) ]) ); @@ -2009,10 +2036,10 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier::dummy("chef")), - Attribute::Place(Identifier::dummy("kitchen")), - Attribute::Role(Identifier::dummy("barista")), - Attribute::Place(Identifier::dummy("dining_room")) + Attribute::Role(Identifier::dummy("chef"), Span::default()), + Attribute::Place(Identifier::dummy("kitchen"), Span::default()), + Attribute::Role(Identifier::dummy("barista"), Span::default()), + Attribute::Place(Identifier::dummy("dining_room"), Span::default()) ]) ); @@ -2027,6 +2054,94 @@ fn reading_attributes() { assert!(result.is_err()); } +#[test] +fn attribute_spans_include_marker() { + let mut input = Parser::new(); + + input.initialize("@chef"); + let result = input + .read_attributes() + .unwrap(); + assert_eq!( + result[0], + Attribute::Role(Identifier::dummy("chef"), Span::default()) + ); + if let Attribute::Role(id, span) = &result[0] { + assert_eq!( + *span, + Span { + offset: 0, + length: 5 + } + ); + assert_eq!( + id.span, + Span { + offset: 1, + length: 4 + } + ); + } + + input.initialize("^kitchen"); + let result = input + .read_attributes() + .unwrap(); + if let Attribute::Place(id, span) = &result[0] { + assert_eq!( + *span, + Span { + offset: 0, + length: 8 + } + ); + assert_eq!( + id.span, + Span { + offset: 1, + length: 7 + } + ); + } + + input.initialize("@waiter + ^milliways"); + let result = input + .read_attributes() + .unwrap(); + if let Attribute::Role(id, span) = &result[0] { + assert_eq!( + *span, + Span { + offset: 0, + length: 7 + } + ); + assert_eq!( + id.span, + Span { + offset: 1, + length: 6 + } + ); + } + if let Attribute::Place(id, span) = &result[1] { + assert_eq!( + *span, + Span { + offset: 10, + length: 10 + } + ); + assert_eq!( + id.span, + Span { + offset: 11, + length: 9 + } + ); + } +} + #[test] fn step_with_role_assignment() { let mut input = Parser::new(); @@ -2047,11 +2162,11 @@ fn step_with_role_assignment() { Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Check the patient's vital signs" )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nurse"))], + attributes: vec![Attribute::Role(Identifier::dummy("nurse"), Span::default())], subscopes: vec![], span: Span::default(), }], @@ -2080,14 +2195,17 @@ fn substep_with_role_assignment() { scope, Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Verify patient identity" )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], + attributes: vec![Attribute::Role( + Identifier::dummy("surgeon"), + Span::default() + )], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text("Check ID")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Check ID")])], subscopes: vec![], span: Span::default(), }], @@ -2118,12 +2236,17 @@ fn parallel_step_with_role_assignment() { scope, Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Monitor patient vitals")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Monitor patient vitals" + )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], + attributes: vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default() + )], subscopes: vec![Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text("Check readings")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Check readings")])], subscopes: vec![], span: Span::default(), }], @@ -2157,13 +2280,16 @@ fn two_roles_with_substeps() { scope, Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Review events.")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Review events.")])], subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], + attributes: vec![Attribute::Role( + Identifier::dummy("surgeon"), + Span::default() + )], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "What are the steps?" )])], subscopes: vec![], @@ -2172,10 +2298,10 @@ fn two_roles_with_substeps() { span: Span::default(), }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nurse"))], + attributes: vec![Attribute::Role(Identifier::dummy("nurse"), Span::default())], subscopes: vec![Scope::DependentBlock { ordinal: "b", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "What are the concerns?" )])], subscopes: vec![], diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index 6ba9740d..e0ddaafa 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -20,7 +20,8 @@ fn technique_header() { version: 1, license: None, copyright: None, - domain: None + domain: None, + span: Span::default(), }) ); @@ -39,7 +40,8 @@ fn technique_header() { version: 1, license: Some("MIT"), copyright: Some("ACME, Inc"), - domain: Some("checklist") + domain: Some("checklist"), + span: Span::default(), }) ); } @@ -169,14 +171,16 @@ This is the first one. elements: vec![ Element::Title("The First", Span::default()), Element::Description( - vec![Paragraph(vec![Descriptive::Text("This is the first one.")])], + vec![Paragraph::new(vec![Descriptive::Text( + "This is the first one." + )])], Span::default() ), Element::Steps( vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Do the first thing in the first one." )])], @@ -185,7 +189,7 @@ This is the first one. }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Do the second thing in the first one." )])], @@ -231,25 +235,29 @@ This is the first one. elements: vec![ Element::Title("The First", Span::default()), Element::Description( - vec![Paragraph(vec![Descriptive::Text("This is the first one.")])], + vec![Paragraph::new(vec![Descriptive::Text( + "This is the first one." + )])], Span::default() ), Element::Steps( vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Have you done the first thing in the first one?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "No", - condition: Some("but I have an excuse") + condition: Some("but I have an excuse"), + span: Span::default() } ], span: Span::default(), @@ -258,7 +266,7 @@ This is the first one. }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Do the second thing in the first one." )])], @@ -305,31 +313,35 @@ This is the first one. elements: vec![ Element::Title("The First", Span::default()), Element::Description( - vec![Paragraph(vec![Descriptive::Text("This is the first one.")])], + vec![Paragraph::new(vec![Descriptive::Text( + "This is the first one." + )])], Span::default() ), Element::Steps( vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Have you done the first thing in the first one?" )])], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Do the first thing. Then ask yourself if you are done:" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "No", - condition: Some("but I have an excuse") + condition: Some("but I have an excuse"), + span: Span::default() } ], span: Span::default(), @@ -340,7 +352,7 @@ This is the first one. }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Do the second thing in the first one." )])], @@ -398,7 +410,7 @@ fn realistic_procedure() { vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text( "Has the patient confirmed his/her identity, site, procedure," ), @@ -407,7 +419,8 @@ fn realistic_procedure() { subscopes: vec![Scope::ResponseBlock { responses: vec![Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }], span: Span::default(), }], @@ -415,18 +428,20 @@ fn realistic_procedure() { }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Is the site marked?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }, Response { value: "Not Applicable", - condition: None + condition: None, + span: Span::default() } ], span: Span::default(), @@ -435,13 +450,14 @@ fn realistic_procedure() { }, Scope::DependentBlock { ordinal: "3", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Is the anaesthesia machine and medication check complete?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }], span: Span::default(), }], @@ -449,13 +465,14 @@ fn realistic_procedure() { }, Scope::DependentBlock { ordinal: "4", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Is the pulse oximeter on the patient and functioning?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![Response { value: "Yes", - condition: None + condition: None, + span: Span::default() }], span: Span::default(), }], @@ -463,25 +480,27 @@ fn realistic_procedure() { }, Scope::DependentBlock { ordinal: "5", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Does the patient have a:" )])], subscopes: vec![ Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Known allergy?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "No", - condition: None + condition: None, + span: Span::default() }, Response { value: "Yes", - condition: None + condition: None, + span: Span::default() } ], span: Span::default(), @@ -490,20 +509,22 @@ fn realistic_procedure() { }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Difficult airway or aspiration risk?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "No", - condition: None + condition: None, + span: Span::default() }, Response { value: "Yes", condition: Some( "and equipment/assistance available" - ) + ), + span: Span::default(), } ], span: Span::default(), @@ -512,20 +533,22 @@ fn realistic_procedure() { }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Risk of blood loss > 500 mL?" )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "No", - condition: None + condition: None, + span: Span::default() }, Response { value: "Yes", condition: Some( "and two IVs planned and fluids available" - ) + ), + span: Span::default(), } ], span: Span::default(), @@ -570,15 +593,20 @@ label_the_specimens : elements: vec![Element::Steps( vec![Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Specimen labelling")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Specimen labelling" + )])], subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nursing_team"))], + attributes: vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default() + )], subscopes: vec![ Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Label blood tests" )])], @@ -587,7 +615,7 @@ label_the_specimens : }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Label tissue samples" )])], @@ -598,10 +626,13 @@ label_the_specimens : span: Span::default(), }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("admin_staff"))], + attributes: vec![Attribute::Role( + Identifier::dummy("admin_staff"), + Span::default() + )], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Prepare the envelopes" )])], @@ -665,14 +696,14 @@ before_leaving : vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Verbally confirm:" )])], subscopes: vec![ Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "The name of the surgical procedure(s)." )])], @@ -681,7 +712,7 @@ before_leaving : }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Completion of instrument, sponge, and needle counts." )])], @@ -690,7 +721,7 @@ before_leaving : }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Specimen labelling" )])], @@ -704,12 +735,13 @@ before_leaving : Span::default(), )], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy( - "nursing_team", - ))], + attributes: vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default(), + )], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text( "Read specimen labels aloud, including patient" ), @@ -727,7 +759,7 @@ before_leaving : }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Whether there are any equipment problems to be addressed." )])], @@ -739,16 +771,19 @@ before_leaving : }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Post-operative care:" )])], subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("surgeon"))], + attributes: vec![Attribute::Role( + Identifier::dummy("surgeon"), + Span::default() + )], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text( "What are the key concerns for recovery and management" ), @@ -761,12 +796,13 @@ before_leaving : span: Span::default(), }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy( - "anesthetist" - ))], + attributes: vec![Attribute::Role( + Identifier::dummy("anesthetist"), + Span::default(), + )], subscopes: vec![Scope::DependentBlock { ordinal: "b", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text( "What are the key concerns for recovery and management" ), @@ -779,12 +815,13 @@ before_leaving : span: Span::default(), }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy( - "nursing_team" - ))], + attributes: vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default(), + )], subscopes: vec![Scope::DependentBlock { ordinal: "c", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text( "What are the key concerns for recovery and management" ), @@ -840,7 +877,7 @@ fn parallel_role_assignments() { assert_eq!(ordinal, "5"); assert_eq!( content, - vec![Paragraph(vec![Descriptive::Text( + vec![Paragraph::new(vec![Descriptive::Text( "Review anticipated critical events." )])] ); @@ -856,7 +893,10 @@ fn parallel_role_assignments() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier::dummy("surgeon"))] + vec![Attribute::Role( + Identifier::dummy("surgeon"), + Span::default() + )] ); assert_eq!(substeps.len(), 3); // a, b, c } else { @@ -872,7 +912,10 @@ fn parallel_role_assignments() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier::dummy("anaesthetist"))] + vec![Attribute::Role( + Identifier::dummy("anaesthetist"), + Span::default() + )] ); assert_eq!(substeps.len(), 1); // d } else { @@ -888,7 +931,10 @@ fn parallel_role_assignments() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier::dummy("nursing_team"))] + vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default() + )] ); assert_eq!(substeps.len(), 2); // e, f } else { @@ -933,7 +979,7 @@ fn multiple_roles_with_dependent_substeps() { assert_eq!(ordinal, "1"); assert_eq!( content, - vec![Paragraph(vec![Descriptive::Text( + vec![Paragraph::new(vec![Descriptive::Text( "Review surgical procedure" )])] ); @@ -948,7 +994,10 @@ fn multiple_roles_with_dependent_substeps() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier::dummy("surgeon"))] + vec![Attribute::Role( + Identifier::dummy("surgeon"), + Span::default() + )] ); assert_eq!(substeps.len(), 3); } else { @@ -964,7 +1013,10 @@ fn multiple_roles_with_dependent_substeps() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier::dummy("anaesthetist"))] + vec![Attribute::Role( + Identifier::dummy("anaesthetist"), + Span::default() + )] ); assert_eq!(substeps.len(), 2); } else { @@ -980,7 +1032,10 @@ fn multiple_roles_with_dependent_substeps() { { assert_eq!( *attributes, - vec![Attribute::Role(Identifier::dummy("nursing_team"))] + vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default() + )] ); assert_eq!(substeps.len(), 3); } else { @@ -1032,28 +1087,35 @@ fn mixed_substeps_in_roles() { step, Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Emergency response")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Emergency response" + )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("team_lead"))], + attributes: vec![Attribute::Role( + Identifier::dummy("team_lead"), + Span::default() + )], subscopes: vec![ Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text("Assess situation")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Assess situation" + )])], subscopes: vec![], span: Span::default(), }, Scope::DependentBlock { ordinal: "b", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Coordinate response" )])], subscopes: vec![ Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Monitor communications" )])], @@ -1062,7 +1124,7 @@ fn mixed_substeps_in_roles() { }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Track resources" )])], @@ -1074,7 +1136,7 @@ fn mixed_substeps_in_roles() { }, Scope::DependentBlock { ordinal: "c", - description: vec![Paragraph(vec![Descriptive::Text("File report")])], + description: vec![Paragraph::new(vec![Descriptive::Text("File report")])], subscopes: vec![], span: Span::default(), @@ -1104,20 +1166,24 @@ fn substeps_with_responses() { result, Ok(Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Main step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Main step")])], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text("Substep with response")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Substep with response" + )])], subscopes: vec![Scope::ResponseBlock { responses: vec![ Response { value: "Yes", condition: None, + span: Span::default() }, Response { value: "No", condition: None, + span: Span::default() }, ], span: Span::default(), @@ -1138,7 +1204,7 @@ fn naked_bindings() { let descriptive = input.read_descriptive(); assert_eq!( descriptive, - Ok(vec![Paragraph(vec![Descriptive::Binding( + Ok(vec![Paragraph::new(vec![Descriptive::Binding( Box::new(Descriptive::Text("What is the result?")), vec![Identifier::dummy("answer")] )])]) @@ -1150,7 +1216,7 @@ fn naked_bindings() { let descriptive = input.read_descriptive(); assert_eq!( descriptive, - Ok(vec![Paragraph(vec![ + Ok(vec![Paragraph::new(vec![ Descriptive::Binding( Box::new(Descriptive::Text("Enter your name")), vec![Identifier::dummy("name")] @@ -1167,7 +1233,7 @@ fn naked_bindings() { let descriptive = input.read_descriptive(); assert_eq!( descriptive, - Ok(vec![Paragraph(vec![ + Ok(vec![Paragraph::new(vec![ Descriptive::Text("First"), Descriptive::Binding( Box::new(Descriptive::Application(Invocation { @@ -1234,7 +1300,7 @@ second_section_second_procedure : vec![ Scope::SectionChunk { numeral: "I", - title: Some(Paragraph(vec![Descriptive::Text("First Section")])), + title: Some(Paragraph::new(vec![Descriptive::Text("First Section")])), body: Technique::Procedures(vec![ Procedure { name: Identifier::dummy("first_section_first_procedure"), @@ -1255,7 +1321,7 @@ second_section_second_procedure : }, Scope::SectionChunk { numeral: "II", - title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), + title: Some(Paragraph::new(vec![Descriptive::Text("Second Section")])), body: Technique::Procedures(vec![ Procedure { name: Identifier::dummy("second_section_first_procedure"), @@ -1399,13 +1465,13 @@ III. Implementation vec![ Scope::SectionChunk { numeral: "I", - title: Some(Paragraph(vec![Descriptive::Text("Concept")])), + title: Some(Paragraph::new(vec![Descriptive::Text("Concept")])), body: Technique::Empty, span: Span::default(), }, Scope::SectionChunk { numeral: "II", - title: Some(Paragraph(vec![Descriptive::Text( + title: Some(Paragraph::new(vec![Descriptive::Text( "Requirements Definition and Architecture" )])), body: Technique::Procedures(vec![ @@ -1423,7 +1489,7 @@ III. Implementation vec![ Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text("Define Requirements"), Descriptive::Application(Invocation { target: Target::Local(Identifier::dummy( @@ -1443,7 +1509,7 @@ III. Implementation }, Scope::DependentBlock { ordinal: "3", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text("Determine Architecture"), Descriptive::Application(Invocation { target: Target::Local(Identifier::dummy( @@ -1491,7 +1557,7 @@ III. Implementation }, Scope::SectionChunk { numeral: "III", - title: Some(Paragraph(vec![Descriptive::Text("Implementation")])), + title: Some(Paragraph::new(vec![Descriptive::Text("Implementation")])), body: Technique::Empty, span: Span::default(), }, diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index 87c96f9e..8b0f4a01 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -841,6 +841,8 @@ impl<'i> Parser<'i> { } fn read_technique_header(&mut self) -> Result, ParsingError> { + let start = self.offset; + // Process magic line let version = if is_magic_line(self.source) { let result = self.read_magic_line()?; @@ -873,6 +875,7 @@ impl<'i> Parser<'i> { license, copyright, domain, + span: self.span_since(start), }) } @@ -2335,15 +2338,18 @@ impl<'i> Parser<'i> { // standalone CodeBlock wrapped in a Paragraph // FIXME this needs to be promoted to a Scope::CodeBlock? Or better yet shouldnt' be here? + let para_start = outer.offset; let expressions = outer.read_code_block()?; + let para_span = outer.span_since(para_start); for expr in expressions { if let Expression::Separator = expr { continue; } - results.push(Paragraph(vec![Descriptive::CodeInline(expr)])); + results.push(Paragraph(vec![Descriptive::CodeInline(expr)], para_span)); } } else { // Paragraph container + let para_start = outer.offset; let descriptives = outer.take_paragraph(|parser| { let mut content = vec![]; @@ -2441,7 +2447,7 @@ impl<'i> Parser<'i> { })?; if !descriptives.is_empty() { - results.push(Paragraph(descriptives)); + results.push(Paragraph(descriptives, outer.span_since(para_start))); } } } @@ -2648,7 +2654,7 @@ impl<'i> Parser<'i> { value: "*", span: inner.span_of(star), }; - attributes.push(Attribute::Role(identifier)); + attributes.push(Attribute::Role(identifier, inner.span_of(trimmed))); } // Check if it's a regular role '@' else if let Some(captures) = regex!(r"^@([a-z][a-z0-9_]*)$").captures(trimmed) { @@ -2662,7 +2668,7 @@ impl<'i> Parser<'i> { role_name.len(), role_name.to_string(), ))?; - attributes.push(Attribute::Role(identifier)); + attributes.push(Attribute::Role(identifier, inner.span_of(trimmed))); } // Check if it's a place '^' else if let Some(captures) = regex!(r"^\^([a-z][a-z0-9_]*)$").captures(trimmed) { @@ -2680,7 +2686,7 @@ impl<'i> Parser<'i> { place_name.len(), place_name.to_string(), ))?; - attributes.push(Attribute::Place(identifier)); + attributes.push(Attribute::Place(identifier, inner.span_of(trimmed))); } else { // Check if this looks like a malformed attribute (starts with @ or ^) if is_attribute_pattern(trimmed) { diff --git a/src/problem/messages.rs b/src/problem/messages.rs index d1810742..a9fa363f 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -669,17 +669,20 @@ parallel steps, but again this is not compulsory. let examples = vec![ Scope::AttributeBlock { attributes: vec![ - Attribute::Role(Identifier::dummy("president_of_the_galaxy")), - Attribute::Role(Identifier::dummy("femme_fatale")), + Attribute::Role( + Identifier::dummy("president_of_the_galaxy"), + Span::default(), + ), + Attribute::Role(Identifier::dummy("femme_fatale"), Span::default()), ], subscopes: vec![], span: Span::default(), }, Scope::AttributeBlock { attributes: vec![ - Attribute::Place(Identifier::dummy("milliways")), - Attribute::Role(Identifier::dummy("waiter")), - Attribute::Role(Identifier::dummy("dish_of_the_day")), + Attribute::Place(Identifier::dummy("milliways"), Span::default()), + Attribute::Role(Identifier::dummy("waiter"), Span::default()), + Attribute::Role(Identifier::dummy("dish_of_the_day"), Span::default()), ], subscopes: vec![], span: Span::default(), @@ -751,28 +754,34 @@ a list of tuples. Response { value: "Rock", condition: None, + span: Span::default(), }, Response { value: "Paper", condition: None, + span: Span::default(), }, Response { value: "Scissors", condition: None, + span: Span::default(), }, ], vec![Response { value: "Confirmed", condition: None, + span: Span::default(), }], vec![ Response { value: "Yes", condition: Some("but with explanation"), + span: Span::default(), }, Response { value: "No", condition: None, + span: Span::default(), }, ], ]; diff --git a/tests/formatting/formatter.rs b/tests/formatting/formatter.rs index 5b893a1d..0ee65d36 100644 --- a/tests/formatting/formatter.rs +++ b/tests/formatting/formatter.rs @@ -26,6 +26,7 @@ mod verify { license: Some("MIT"), copyright: None, domain: Some("checklist"), + span: Span::default(), }), body: None, }; @@ -74,6 +75,7 @@ first : A -> B license: Some("PD"), copyright: Some("2025 The First Procedure Society, Inc"), domain: None, + span: Span::default(), }), body: Some(Technique::Procedures(vec![ Procedure { @@ -135,17 +137,21 @@ second : [Thing] -> (Who, Where, Why) vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Eat breakfast.")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Eat breakfast.", + )])], subscopes: vec![], span: Span::default(), }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text("Win a stage:")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Win a stage:", + )])], subscopes: vec![ Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Ride really fast, then", )])], subscopes: vec![], @@ -153,7 +159,7 @@ second : [Thing] -> (Who, Where, Why) }, Scope::DependentBlock { ordinal: "b", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Win the sprint.", )])], subscopes: vec![], @@ -164,7 +170,9 @@ second : [Thing] -> (Who, Where, Why) }, Scope::DependentBlock { ordinal: "3", - description: vec![Paragraph(vec![Descriptive::Text("Eat dinner.")])], + description: vec![Paragraph::new(vec![Descriptive::Text( + "Eat dinner.", + )])], subscopes: vec![], span: Span::default(), }, @@ -248,13 +256,15 @@ vibe_coding : signature: None, elements: vec![ Element::Description( - vec![Paragraph(vec![Descriptive::Text("We must take action!")])], + vec![Paragraph::new(vec![Descriptive::Text( + "We must take action!", + )])], Span::default(), ), Element::Steps( vec![Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text("To take the action, we must:"), Descriptive::CodeInline(Expression::Execution( Function { @@ -308,7 +318,7 @@ We must take action! signature: None, elements: vec![ Element::Description( - vec![Paragraph(vec![Descriptive::Text( + vec![Paragraph::new(vec![Descriptive::Text( "Record everything, with timestamps.", )])], Span::default(), @@ -316,11 +326,14 @@ We must take action! Element::Steps( vec![Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Record event as it happens", )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("journalist"))], + attributes: vec![Attribute::Role( + Identifier::dummy("journalist"), + Span::default(), + )], subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Tablet( vec![ @@ -394,13 +407,13 @@ Record everything, with timestamps. Element::Steps( vec![Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Verbally confirm:", )])], subscopes: vec![ Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "The name of the surgical procedure(s).", )])], subscopes: vec![], @@ -408,7 +421,7 @@ Record everything, with timestamps. }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Completion of instrument, sponge, and needle counts.", )])], subscopes: vec![], @@ -416,7 +429,7 @@ Record everything, with timestamps. }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Specimen labelling", )])], subscopes: vec![Scope::CodeBlock { @@ -429,12 +442,13 @@ Record everything, with timestamps. Span::default(), )], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy( - "nursing_team", - ))], + attributes: vec![Attribute::Role( + Identifier::dummy("nursing_team"), + Span::default(), + )], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![ + description: vec![Paragraph::new(vec![ Descriptive::Text( "Read specimen labels aloud, including patient", ), @@ -451,7 +465,7 @@ Record everything, with timestamps. }, Scope::ParallelBlock { bullet: '-', - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Whether there are any equipment problems to be addressed.", )])], subscopes: vec![], @@ -505,13 +519,13 @@ before_leaving : vec![ Scope::SectionChunk { numeral: "I", - title: Some(Paragraph(vec![Descriptive::Text("First Section")])), + title: Some(Paragraph::new(vec![Descriptive::Text("First Section")])), body: Technique::Procedures(vec![]), span: Span::default(), }, Scope::SectionChunk { numeral: "II", - title: Some(Paragraph(vec![Descriptive::Text("Second Section")])), + title: Some(Paragraph::new(vec![Descriptive::Text("Second Section")])), body: Technique::Procedures(vec![]), span: Span::default(), }, @@ -558,10 +572,10 @@ III. vec![ Scope::DependentBlock { ordinal: "1", - description: vec![Paragraph(vec![Descriptive::Text("Main step")])], + description: vec![Paragraph::new(vec![Descriptive::Text("Main step")])], subscopes: vec![Scope::DependentBlock { ordinal: "a", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Substep with response", )])], subscopes: vec![Scope::ResponseBlock { @@ -569,10 +583,12 @@ III. Response { value: "Yes", condition: None, + span: Span::default(), }, Response { value: "No", condition: None, + span: Span::default(), }, ], span: Span::default(), @@ -583,13 +599,14 @@ III. }, Scope::DependentBlock { ordinal: "2", - description: vec![Paragraph(vec![Descriptive::Text( + description: vec![Paragraph::new(vec![Descriptive::Text( "Simple step with response", )])], subscopes: vec![Scope::ResponseBlock { responses: vec![Response { value: "Confirmed", condition: None, + span: Span::default(), }], span: Span::default(), }], From daf9d83eb644290caee244cafaca6f4aa7044235 Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Thu, 7 May 2026 09:14:44 +1000 Subject: [PATCH 7/9] Rename test constructors to ::new() --- src/domain/engine.rs | 6 +- src/formatting/formatter.rs | 26 ++--- src/language/types.rs | 63 +++++----- src/parsing/checks/parser.rs | 214 +++++++++++++++++----------------- src/parsing/checks/verify.rs | 144 +++++++++++------------ src/problem/messages.rs | 138 +++++++++++----------- tests/formatting/formatter.rs | 56 ++++----- 7 files changed, 313 insertions(+), 334 deletions(-) diff --git a/src/domain/engine.rs b/src/domain/engine.rs index 6ec8f4e3..32c797a7 100644 --- a/src/domain/engine.rs +++ b/src/domain/engine.rs @@ -549,7 +549,7 @@ mod check { fn local<'a>(name: &'a str) -> Invocation<'a> { Invocation { - target: Target::Local(Identifier::dummy(name)), + target: Target::Local(Identifier::new(name)), parameters: None, } } @@ -609,7 +609,7 @@ mod check { fn binding_with_invocation() { let p = Paragraph::new(vec![Descriptive::Binding( Box::new(Descriptive::Application(local("observe"))), - vec![Identifier::dummy("e")], + vec![Identifier::new("e")], )]); assert_eq!(p.text(), ""); assert_eq!(p.invocations(), vec!["observe"]); @@ -620,7 +620,7 @@ mod check { #[test] fn foreach_expression() { let p = Paragraph::new(vec![Descriptive::CodeInline(Expression::Foreach( - vec![Identifier::dummy("design")], + vec![Identifier::new("design")], Box::new(Expression::Application(local("implement"), Span::default())), Span::default(), ))]); diff --git a/src/formatting/formatter.rs b/src/formatting/formatter.rs index 7749903a..8d5c8902 100644 --- a/src/formatting/formatter.rs +++ b/src/formatting/formatter.rs @@ -1415,7 +1415,7 @@ mod check { fn genus() { let mut output = Formatter::new(78); - let forma = Forma::dummy("Jedi"); + let forma = Forma::new("Jedi"); output.append_forma(&forma); assert_eq!(output.to_string(), "Jedi"); @@ -1424,21 +1424,21 @@ mod check { assert_eq!(output.to_string(), "()"); output.reset(); - let single = Genus::Single(Forma::dummy("Stormtrooper")); + let single = Genus::Single(Forma::new("Stormtrooper")); output.append_genus(&single); assert_eq!(output.to_string(), "Stormtrooper"); output.reset(); - let list = Genus::List(Forma::dummy("Pilot")); + let list = Genus::List(Forma::new("Pilot")); output.append_genus(&list); assert_eq!(output.to_string(), "[Pilot]"); output.reset(); let genus = Genus::Tuple(vec![ - Forma::dummy("Kid"), - Forma::dummy("Pilot"), - Forma::dummy("Scoundrel"), - Forma::dummy("Princess"), + Forma::new("Kid"), + Forma::new("Pilot"), + Forma::new("Scoundrel"), + Forma::new("Princess"), ]); output.append_genus(&genus); assert_eq!(output.to_string(), "(Kid, Pilot, Scoundrel, Princess)"); @@ -1451,24 +1451,24 @@ mod check { let mut output = Formatter::new(78); let sig = Signature { - requires: Genus::Single(Forma::dummy("Alderaan")), - provides: Genus::Single(Forma::dummy("AsteroidField")), + requires: Genus::Single(Forma::new("Alderaan")), + provides: Genus::Single(Forma::new("AsteroidField")), }; output.append_signature(&sig); assert_eq!(output.to_string(), "Alderaan -> AsteroidField"); output.reset(); let sig = Signature { - requires: Genus::List(Forma::dummy("Clone")), - provides: Genus::Single(Forma::dummy("Army")), + requires: Genus::List(Forma::new("Clone")), + provides: Genus::Single(Forma::new("Army")), }; output.append_signature(&sig); assert_eq!(output.to_string(), "[Clone] -> Army"); output.reset(); let signature = Signature { - requires: Genus::Single(Forma::dummy("TaxationOfTradeRoutes")), - provides: Genus::Tuple(vec![Forma::dummy("Rebels"), Forma::dummy("Empire")]), + requires: Genus::Single(Forma::new("TaxationOfTradeRoutes")), + provides: Genus::Tuple(vec![Forma::new("Rebels"), Forma::new("Empire")]), }; output.append_signature(&signature); assert_eq!( diff --git a/src/language/types.rs b/src/language/types.rs index ab52c1b3..8d7dd17e 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -117,7 +117,7 @@ impl PartialEq for Identifier<'_> { impl<'i> Identifier<'i> { /// Test helper: builds an `Identifier` with a default span. See also the /// `PartialEq` instance. - pub const fn dummy(value: &'i str) -> Self { + pub const fn new(value: &'i str) -> Self { Identifier { value, span: Span { @@ -141,7 +141,7 @@ impl PartialEq for External<'_> { } impl<'i> External<'i> { - pub const fn dummy(value: &'i str) -> Self { + pub const fn new(value: &'i str) -> Self { External { value, span: Span { @@ -171,7 +171,7 @@ impl PartialEq for Forma<'_> { } impl<'i> Forma<'i> { - pub const fn dummy(value: &'i str) -> Self { + pub const fn new(value: &'i str) -> Self { Forma { value, span: Span { @@ -636,18 +636,18 @@ mod check { #[test] fn identifier_rules() { let s = Span::default(); - assert_eq!(validate_identifier("a", s), Some(Identifier::dummy("a"))); - assert_eq!(validate_identifier("ab", s), Some(Identifier::dummy("ab"))); + assert_eq!(validate_identifier("a", s), Some(Identifier::new("a"))); + assert_eq!(validate_identifier("ab", s), Some(Identifier::new("ab"))); assert_eq!( validate_identifier("johnny5", s), - Some(Identifier::dummy("johnny5")) + Some(Identifier::new("johnny5")) ); assert_eq!(validate_identifier("Pizza", s), None); assert_eq!(validate_identifier("pizZa", s), None); assert!(validate_identifier("0trust", s).is_none()); assert_eq!( validate_identifier("make_dinner", s), - Some(Identifier::dummy("make_dinner")) + Some(Identifier::new("make_dinner")) ); assert!(validate_identifier("MakeDinner", s).is_none()); assert!(validate_identifier("make-dinner", s).is_none()); @@ -655,13 +655,10 @@ mod check { #[test] fn forma_rules() { - assert_eq!( - validate_forma("A", Span::default()), - Some(Forma::dummy("A")) - ); + assert_eq!(validate_forma("A", Span::default()), Some(Forma::new("A"))); assert_eq!( validate_forma("Beans", Span::default()), - Some(Forma::dummy("Beans")) + Some(Forma::new("Beans")) ); assert_eq!(validate_forma("lower", Span::default()), None); } @@ -670,7 +667,7 @@ mod check { fn genus_rules_single() { assert_eq!( validate_genus("A", Span::default()), - Some(Genus::Single(Forma::dummy("A"))) + Some(Genus::Single(Forma::new("A"))) ); } @@ -678,18 +675,18 @@ mod check { fn genus_rules_list() { assert_eq!( validate_genus("[A]", Span::default()), - Some(Genus::List(Forma::dummy("A"))) + Some(Genus::List(Forma::new("A"))) ); // Test list with whitespace assert_eq!( validate_genus("[ Input ]", Span::default()), - Some(Genus::List(Forma::dummy("Input"))) + Some(Genus::List(Forma::new("Input"))) ); assert_eq!( validate_genus("[\tOutput\t]", Span::default()), - Some(Genus::List(Forma::dummy("Output"))) + Some(Genus::List(Forma::new("Output"))) ); // Test malformed lists @@ -701,15 +698,12 @@ mod check { fn genus_rules_tuple_parens() { assert_eq!( validate_genus("(A, B)", Span::default()), - Some(Genus::Tuple(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Tuple(vec![Forma::new("A"), Forma::new("B")])) ); assert_eq!( validate_genus("(Coffee, Tea)", Span::default()), - Some(Genus::Tuple(vec![ - Forma::dummy("Coffee"), - Forma::dummy("Tea") - ])) + Some(Genus::Tuple(vec![Forma::new("Coffee"), Forma::new("Tea")])) ); // not actually sure whether we should be normalizing this? Probably @@ -717,18 +711,18 @@ mod check { assert_eq!( validate_genus("(A)", Span::default()), - Some(Genus::Tuple(vec![Forma::dummy("A")])) + Some(Genus::Tuple(vec![Forma::new("A")])) ); // Test parenthesized tuples with whitespace assert_eq!( validate_genus("( A , B )", Span::default()), - Some(Genus::Tuple(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Tuple(vec![Forma::new("A"), Forma::new("B")])) ); assert_eq!( validate_genus("(\tA\t,\tB\t)", Span::default()), - Some(Genus::Tuple(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Tuple(vec![Forma::new("A"), Forma::new("B")])) ); // Test malformed tuples @@ -740,45 +734,42 @@ mod check { fn genus_rules_tuple_bare() { assert_eq!( validate_genus("A, B", Span::default()), - Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Naked(vec![Forma::new("A"), Forma::new("B")])) ); assert_eq!( validate_genus("Coffee, Tea", Span::default()), - Some(Genus::Naked(vec![ - Forma::dummy("Coffee"), - Forma::dummy("Tea") - ])) + Some(Genus::Naked(vec![Forma::new("Coffee"), Forma::new("Tea")])) ); assert_eq!( validate_genus("Input, Data, Config", Span::default()), Some(Genus::Naked(vec![ - Forma::dummy("Input"), - Forma::dummy("Data"), - Forma::dummy("Config") + Forma::new("Input"), + Forma::new("Data"), + Forma::new("Config") ])) ); assert_eq!( validate_genus("A,B", Span::default()), - Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Naked(vec![Forma::new("A"), Forma::new("B")])) ); assert_eq!( validate_genus("A , B", Span::default()), - Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Naked(vec![Forma::new("A"), Forma::new("B")])) ); // Test edge cases with whitespace assert_eq!( validate_genus(" A , B ", Span::default()), - Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Naked(vec![Forma::new("A"), Forma::new("B")])) ); assert_eq!( validate_genus("\tA\t,\tB\t", Span::default()), - Some(Genus::Naked(vec![Forma::dummy("A"), Forma::dummy("B")])) + Some(Genus::Naked(vec![Forma::new("A"), Forma::new("B")])) ); } diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index 6f54cc85..5983b8f2 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -140,11 +140,11 @@ fn identifier_rules() { let mut input = Parser::new(); input.initialize("p"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier::dummy("p"))); + assert_eq!(result, Ok(Identifier::new("p"))); input.initialize("cook_pizza"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier::dummy("cook_pizza"))); + assert_eq!(result, Ok(Identifier::new("cook_pizza"))); input.initialize("cook-pizza"); let result = input.read_identifier(); @@ -159,8 +159,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")) + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")) }) ); @@ -169,8 +169,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::Single(Forma::dummy("Beans")), - provides: Genus::Single(Forma::dummy("Coffee")) + requires: Genus::Single(Forma::new("Beans")), + provides: Genus::Single(Forma::new("Coffee")) }) ); @@ -179,8 +179,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::List(Forma::dummy("Bits")), - provides: Genus::Single(Forma::dummy("Bob")) + requires: Genus::List(Forma::new("Bits")), + provides: Genus::Single(Forma::new("Bob")) }) ); @@ -189,8 +189,8 @@ fn signatures() { assert_eq!( result, Ok(Signature { - requires: Genus::Single(Forma::dummy("Complex")), - provides: Genus::Tuple(vec![Forma::dummy("Real"), Forma::dummy("Imaginary")]) + requires: Genus::Single(Forma::new("Complex")), + provides: Genus::Tuple(vec![Forma::new("Real"), Forma::new("Imaginary")]) }) ); } @@ -203,7 +203,7 @@ fn declaration_simple() { assert!(is_procedure_declaration(input.source)); let result = input.parse_procedure_declaration(); - assert_eq!(result, Ok((Identifier::dummy("making_coffee"), None, None))); + assert_eq!(result, Ok((Identifier::new("making_coffee"), None, None))); } #[test] @@ -216,11 +216,11 @@ fn declaration_full() { assert_eq!( result, Ok(( - Identifier::dummy("f"), + Identifier::new("f"), None, Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")) + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")) }) )) ); @@ -232,11 +232,11 @@ fn declaration_full() { assert_eq!( result, Ok(( - Identifier::dummy("making_coffee"), + Identifier::new("making_coffee"), None, Some(Signature { - requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::List(Forma::dummy("Coffee")) + requires: Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::List(Forma::new("Coffee")) }) )) ); @@ -289,11 +289,11 @@ making_coffee : assert_eq!( result, Ok(( - Identifier::dummy("making_coffee"), + Identifier::new("making_coffee"), None, Some(Signature { - requires: Genus::Single(Forma::dummy("Ingredients")), - provides: Genus::Single(Forma::dummy("Coffee")) + requires: Genus::Single(Forma::new("Ingredients")), + provides: Genus::Single(Forma::new("Coffee")) }) )) ); @@ -312,11 +312,11 @@ making_coffee(b, m) : assert_eq!( result, Ok(( - Identifier::dummy("making_coffee"), - Some(vec![Identifier::dummy("b"), Identifier::dummy("m")]), + Identifier::new("making_coffee"), + Some(vec![Identifier::new("b"), Identifier::new("m")]), Some(Signature { - requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::Single(Forma::dummy("Coffee")) + requires: Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::Single(Forma::new("Coffee")) }) )) ); @@ -415,19 +415,19 @@ fn taking_until() { // Test take_until() with an identifier up to a limiting character input.initialize("hello,world"); let result = input.take_until(&[','], |inner| inner.read_identifier()); - assert_eq!(result, Ok(Identifier::dummy("hello"))); + assert_eq!(result, Ok(Identifier::new("hello"))); assert_eq!(input.source, ",world"); // Test take_until() with whitespace delimiters input.initialize("test \t\nmore"); let result = input.take_until(&[' ', '\t', '\n'], |inner| inner.read_identifier()); - assert_eq!(result, Ok(Identifier::dummy("test"))); + assert_eq!(result, Ok(Identifier::new("test"))); assert_eq!(input.source, " \t\nmore"); // Test take_until() when no delimiter found (it should take everything) input.initialize("onlytext"); let result = input.take_until(&[',', ';'], |inner| inner.read_identifier()); - assert_eq!(result, Ok(Identifier::dummy("onlytext"))); + assert_eq!(result, Ok(Identifier::new("onlytext"))); assert_eq!(input.source, ""); } @@ -441,7 +441,7 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Local(Identifier::dummy("hello")), + target: Target::Local(Identifier::new("hello")), parameters: None }) ); @@ -452,7 +452,7 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Local(Identifier::dummy("hello_world")), + target: Target::Local(Identifier::new("hello_world")), parameters: Some(vec![]) }) ); @@ -463,11 +463,11 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Local(Identifier::dummy("greetings")), + target: Target::Local(Identifier::new("greetings")), parameters: Some(vec![ - Expression::Variable(Identifier::dummy("name"), Span::default()), - Expression::Variable(Identifier::dummy("title"), Span::default()), - Expression::Variable(Identifier::dummy("occupation"), Span::default()) + Expression::Variable(Identifier::new("name"), Span::default()), + Expression::Variable(Identifier::new("title"), Span::default()), + Expression::Variable(Identifier::new("occupation"), Span::default()) ]) }) ); @@ -480,7 +480,7 @@ fn reading_invocations() { assert_eq!( result, Ok(Invocation { - target: Target::Remote(External::dummy("https://example.com/proc")), + target: Target::Remote(External::new("https://example.com/proc")), parameters: None }) ); @@ -1117,7 +1117,7 @@ fn code_blocks() { assert_eq!( result, Ok(vec![Expression::Variable( - Identifier::dummy("count"), + Identifier::new("count"), Span::default() )]) ); @@ -1129,9 +1129,9 @@ fn code_blocks() { result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("sum"), + target: Identifier::new("sum"), parameters: vec![Expression::Variable( - Identifier::dummy("count"), + Identifier::new("count"), Span::default() )] }, @@ -1146,11 +1146,11 @@ fn code_blocks() { result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("consume"), + target: Identifier::new("consume"), parameters: vec![ - Expression::Variable(Identifier::dummy("apple"), Span::default()), - Expression::Variable(Identifier::dummy("banana"), Span::default()), - Expression::Variable(Identifier::dummy("chocolate"), Span::default()) + Expression::Variable(Identifier::new("apple"), Span::default()), + Expression::Variable(Identifier::new("banana"), Span::default()), + Expression::Variable(Identifier::new("chocolate"), Span::default()) ] }, Span::default() @@ -1164,7 +1164,7 @@ fn code_blocks() { result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::String( vec![Piece::Text("Hello, World")], Span::default() @@ -1185,7 +1185,7 @@ echo "Done"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec!["ls -l", "echo \"Done\""], @@ -1203,7 +1203,7 @@ echo "Done"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("timer"), + target: Identifier::new("timer"), parameters: vec![Expression::Number( Numeric::Scientific(Quantity { mantissa: Decimal { @@ -1228,7 +1228,7 @@ echo "Done"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("measure"), + target: Identifier::new("measure"), parameters: vec![Expression::Number(Numeric::Integral(100), Span::default())] }, Span::default() @@ -1242,7 +1242,7 @@ echo "Done"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("seq"), + target: Identifier::new("seq"), parameters: vec![ Expression::Number(Numeric::Integral(1), Span::default()), Expression::Number(Numeric::Integral(6), Span::default()) @@ -1259,7 +1259,7 @@ echo "Done"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("wait"), + target: Identifier::new("wait"), parameters: vec![ Expression::Number( Numeric::Scientific(Quantity { @@ -1300,7 +1300,7 @@ fn multiline() { result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec![ @@ -1329,7 +1329,7 @@ echo "Done"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( None, vec!["ls -l", "echo \"Done\""], @@ -1355,7 +1355,7 @@ echo "Ending"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("shell"), vec![ @@ -1390,7 +1390,7 @@ echo "Ending"```) }"#, result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("python"), vec![ @@ -1419,7 +1419,7 @@ echo test result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( None, vec!["echo test"], @@ -1445,7 +1445,7 @@ echo test result, Ok(vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("yaml"), vec![ @@ -1529,13 +1529,13 @@ fn tablets() { }, Pair { label: "message", - value: Expression::Variable(Identifier::dummy("msg"), Span::default()) + value: Expression::Variable(Identifier::new("msg"), Span::default()) }, Pair { label: "timestamp", value: Expression::Execution( Function { - target: Identifier::dummy("now"), + target: Identifier::new("now"), parameters: vec![] }, Span::default() @@ -1575,7 +1575,7 @@ fn tablets() { }, Pair { label: "status", - value: Expression::Variable(Identifier::dummy("active"), Span::default()) + value: Expression::Variable(Identifier::new("active"), Span::default()) } ], Span::default() @@ -1628,25 +1628,25 @@ fn reading_identifiers() { // Parse a basic identifier input.initialize("hello"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier::dummy("hello"))); + assert_eq!(result, Ok(Identifier::new("hello"))); assert_eq!(input.source, ""); // Parse an identifier with trailing content input.initialize("count more"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier::dummy("count"))); + assert_eq!(result, Ok(Identifier::new("count"))); assert_eq!(input.source, " more"); // Parse an identifier with leading whitespace and trailing content input.initialize(" \t test_name after"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier::dummy("test_name"))); + assert_eq!(result, Ok(Identifier::new("test_name"))); assert_eq!(input.source, " after"); // Parse an identifier with various delimiters input.initialize("name(param)"); let result = input.read_identifier(); - assert_eq!(result, Ok(Identifier::dummy("name"))); + assert_eq!(result, Ok(Identifier::new("name"))); assert_eq!(input.source, "(param)"); } @@ -1659,9 +1659,9 @@ fn test_foreach_expression() { assert_eq!( result, Ok(vec![Expression::Foreach( - vec![Identifier::dummy("item")], + vec![Identifier::new("item")], Box::new(Expression::Variable( - Identifier::dummy("items"), + Identifier::new("items"), Span::default() )), Span::default() @@ -1678,13 +1678,13 @@ fn foreach_tuple_pattern() { assert_eq!( result, Ok(vec![Expression::Foreach( - vec![Identifier::dummy("design"), Identifier::dummy("component")], + vec![Identifier::new("design"), Identifier::new("component")], Box::new(Expression::Execution( Function { - target: Identifier::dummy("zip"), + target: Identifier::new("zip"), parameters: vec![ - Expression::Variable(Identifier::dummy("designs"), Span::default()), - Expression::Variable(Identifier::dummy("components"), Span::default()) + Expression::Variable(Identifier::new("designs"), Span::default()), + Expression::Variable(Identifier::new("components"), Span::default()) ] }, Span::default() @@ -1700,17 +1700,17 @@ fn foreach_tuple_pattern() { result, Ok(vec![Expression::Foreach( vec![ - Identifier::dummy("a"), - Identifier::dummy("b"), - Identifier::dummy("c") + Identifier::new("a"), + Identifier::new("b"), + Identifier::new("c") ], Box::new(Expression::Execution( Function { - target: Identifier::dummy("zip"), + target: Identifier::new("zip"), parameters: vec![ - Expression::Variable(Identifier::dummy("list1"), Span::default()), - Expression::Variable(Identifier::dummy("list2"), Span::default()), - Expression::Variable(Identifier::dummy("list3"), Span::default()) + Expression::Variable(Identifier::new("list1"), Span::default()), + Expression::Variable(Identifier::new("list2"), Span::default()), + Expression::Variable(Identifier::new("list3"), Span::default()) ] }, Span::default() @@ -1731,12 +1731,12 @@ fn tuple_binding_expression() { Ok(vec![Expression::Binding( Box::new(Expression::Application( Invocation { - target: Target::Local(Identifier::dummy("get_coordinates")), + target: Target::Local(Identifier::new("get_coordinates")), parameters: Some(vec![]) }, Span::default() )), - vec![Identifier::dummy("x"), Identifier::dummy("y")], + vec![Identifier::new("x"), Identifier::new("y")], Span::default() )]) ); @@ -1752,7 +1752,7 @@ fn test_repeat_expression() { result, Ok(vec![Expression::Repeat( Box::new(Expression::Variable( - Identifier::dummy("count"), + Identifier::new("count"), Span::default() )), Span::default() @@ -1781,7 +1781,7 @@ fn test_repeat_keyword_boundary() { assert_eq!( result, Ok(vec![Expression::Variable( - Identifier::dummy("repeater"), + Identifier::new("repeater"), Span::default() )]) ); @@ -1808,9 +1808,9 @@ fn splitting_by() { assert_eq!( result, Ok(vec![ - Identifier::dummy("apple"), - Identifier::dummy("banana"), - Identifier::dummy("cherry") + Identifier::new("apple"), + Identifier::new("banana"), + Identifier::new("cherry") ]) ); assert_eq!(input.source, ""); @@ -1821,16 +1821,16 @@ fn splitting_by() { assert_eq!( result, Ok(vec![ - Identifier::dummy("un"), - Identifier::dummy("deux"), - Identifier::dummy("trois") + Identifier::new("un"), + Identifier::new("deux"), + Identifier::new("trois") ]) ); // Ensure a single item (no delimiter present in input) works input.initialize("seulement"); let result = input.take_split_by(',', |inner| inner.read_identifier()); - assert_eq!(result, Ok(vec![Identifier::dummy("seulement")])); + assert_eq!(result, Ok(vec![Identifier::new("seulement")])); // an empty chunk causes an error input.initialize("un,,trois"); @@ -1970,7 +1970,7 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![Attribute::Role( - Identifier::dummy("chef"), + Identifier::new("chef"), Span::default() )]) ); @@ -1981,7 +1981,7 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![Attribute::Place( - Identifier::dummy("kitchen"), + Identifier::new("kitchen"), Span::default() )]) ); @@ -1992,8 +1992,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier::dummy("master_chef"), Span::default()), - Attribute::Role(Identifier::dummy("barista"), Span::default()) + Attribute::Role(Identifier::new("master_chef"), Span::default()), + Attribute::Role(Identifier::new("barista"), Span::default()) ]) ); @@ -2003,8 +2003,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Place(Identifier::dummy("kitchen"), Span::default()), - Attribute::Place(Identifier::dummy("bath_room"), Span::default()) + Attribute::Place(Identifier::new("kitchen"), Span::default()), + Attribute::Place(Identifier::new("bath_room"), Span::default()) ]) ); @@ -2014,8 +2014,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier::dummy("chef"), Span::default()), - Attribute::Place(Identifier::dummy("bathroom"), Span::default()) + Attribute::Role(Identifier::new("chef"), Span::default()), + Attribute::Place(Identifier::new("bathroom"), Span::default()) ]) ); @@ -2025,8 +2025,8 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Place(Identifier::dummy("kitchen"), Span::default()), - Attribute::Role(Identifier::dummy("barista"), Span::default()) + Attribute::Place(Identifier::new("kitchen"), Span::default()), + Attribute::Role(Identifier::new("barista"), Span::default()) ]) ); @@ -2036,10 +2036,10 @@ fn reading_attributes() { assert_eq!( result, Ok(vec![ - Attribute::Role(Identifier::dummy("chef"), Span::default()), - Attribute::Place(Identifier::dummy("kitchen"), Span::default()), - Attribute::Role(Identifier::dummy("barista"), Span::default()), - Attribute::Place(Identifier::dummy("dining_room"), Span::default()) + Attribute::Role(Identifier::new("chef"), Span::default()), + Attribute::Place(Identifier::new("kitchen"), Span::default()), + Attribute::Role(Identifier::new("barista"), Span::default()), + Attribute::Place(Identifier::new("dining_room"), Span::default()) ]) ); @@ -2064,7 +2064,7 @@ fn attribute_spans_include_marker() { .unwrap(); assert_eq!( result[0], - Attribute::Role(Identifier::dummy("chef"), Span::default()) + Attribute::Role(Identifier::new("chef"), Span::default()) ); if let Attribute::Role(id, span) = &result[0] { assert_eq!( @@ -2166,7 +2166,7 @@ fn step_with_role_assignment() { "Check the patient's vital signs" )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nurse"), Span::default())], + attributes: vec![Attribute::Role(Identifier::new("nurse"), Span::default())], subscopes: vec![], span: Span::default(), }], @@ -2199,10 +2199,7 @@ fn substep_with_role_assignment() { "Verify patient identity" )])], subscopes: vec![Scope::AttributeBlock { - attributes: vec![Attribute::Role( - Identifier::dummy("surgeon"), - Span::default() - )], + attributes: vec![Attribute::Role(Identifier::new("surgeon"), Span::default())], subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph::new(vec![Descriptive::Text("Check ID")])], @@ -2241,7 +2238,7 @@ fn parallel_step_with_role_assignment() { )])], subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default() )], subscopes: vec![Scope::ParallelBlock { @@ -2283,10 +2280,7 @@ fn two_roles_with_substeps() { description: vec![Paragraph::new(vec![Descriptive::Text("Review events.")])], subscopes: vec![ Scope::AttributeBlock { - attributes: vec![Attribute::Role( - Identifier::dummy("surgeon"), - Span::default() - )], + attributes: vec![Attribute::Role(Identifier::new("surgeon"), Span::default())], subscopes: vec![Scope::DependentBlock { ordinal: "a", description: vec![Paragraph::new(vec![Descriptive::Text( @@ -2298,7 +2292,7 @@ fn two_roles_with_substeps() { span: Span::default(), }, Scope::AttributeBlock { - attributes: vec![Attribute::Role(Identifier::dummy("nurse"), Span::default())], + attributes: vec![Attribute::Role(Identifier::new("nurse"), Span::default())], subscopes: vec![Scope::DependentBlock { ordinal: "b", description: vec![Paragraph::new(vec![Descriptive::Text( diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index e0ddaafa..bd98c548 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -60,11 +60,11 @@ making_coffee : (Beans, Milk) -> Coffee assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("making_coffee"), + name: Identifier::new("making_coffee"), parameters: None, signature: Some(Signature { - requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::Single(Forma::dummy("Coffee")) + requires: Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::Single(Forma::new("Coffee")) }), elements: vec![], span: Span::default(), @@ -88,11 +88,11 @@ second : C -> D assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("first"), + name: Identifier::new("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")) + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")) }), elements: vec![], span: Span::default(), @@ -103,11 +103,11 @@ second : C -> D assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("second"), + name: Identifier::new("second"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("C")), - provides: Genus::Single(Forma::dummy("D")) + requires: Genus::Single(Forma::new("C")), + provides: Genus::Single(Forma::new("D")) }), elements: vec![], span: Span::default(), @@ -129,11 +129,11 @@ making_coffee(e) : Ingredients -> Coffee assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("making_coffee"), - parameters: Some(vec![Identifier::dummy("e")]), + name: Identifier::new("making_coffee"), + parameters: Some(vec![Identifier::new("e")]), signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Ingredients")), - provides: Genus::Single(Forma::dummy("Coffee")) + requires: Genus::Single(Forma::new("Ingredients")), + provides: Genus::Single(Forma::new("Coffee")) }), elements: vec![], span: Span::default(), @@ -162,11 +162,11 @@ This is the first one. assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("first"), + name: Identifier::new("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")) + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")) }), elements: vec![ Element::Title("The First", Span::default()), @@ -226,11 +226,11 @@ This is the first one. assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("first"), + name: Identifier::new("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")) + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")) }), elements: vec![ Element::Title("The First", Span::default()), @@ -304,11 +304,11 @@ This is the first one. assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("first"), + name: Identifier::new("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")) + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")) }), elements: vec![ Element::Title("The First", Span::default()), @@ -401,7 +401,7 @@ fn realistic_procedure() { assert_eq!( procedure, Procedure { - name: Identifier::dummy("before_anesthesia"), + name: Identifier::new("before_anesthesia"), parameters: None, signature: None, elements: vec![ @@ -587,7 +587,7 @@ label_the_specimens : assert_eq!( procedure, Ok(Procedure { - name: Identifier::dummy("label_the_specimens"), + name: Identifier::new("label_the_specimens"), parameters: None, signature: None, elements: vec![Element::Steps( @@ -600,7 +600,7 @@ label_the_specimens : subscopes: vec![ Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default() )], subscopes: vec![ @@ -627,7 +627,7 @@ label_the_specimens : }, Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("admin_staff"), + Identifier::new("admin_staff"), Span::default() )], subscopes: vec![Scope::DependentBlock { @@ -687,7 +687,7 @@ before_leaving : assert_eq!( procedure, Procedure { - name: Identifier::dummy("before_leaving"), + name: Identifier::new("before_leaving"), parameters: None, signature: None, elements: vec![ @@ -727,16 +727,16 @@ before_leaving : subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Foreach( - vec![Identifier::dummy("specimen")], + vec![Identifier::new("specimen")], Box::new(Expression::Variable( - Identifier::dummy("specimens"), + Identifier::new("specimens"), Span::default() )), Span::default(), )], subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default(), )], subscopes: vec![Scope::DependentBlock { @@ -778,7 +778,7 @@ before_leaving : subscopes: vec![ Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("surgeon"), + Identifier::new("surgeon"), Span::default() )], subscopes: vec![Scope::DependentBlock { @@ -797,7 +797,7 @@ before_leaving : }, Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("anesthetist"), + Identifier::new("anesthetist"), Span::default(), )], subscopes: vec![Scope::DependentBlock { @@ -816,7 +816,7 @@ before_leaving : }, Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default(), )], subscopes: vec![Scope::DependentBlock { @@ -893,10 +893,7 @@ fn parallel_role_assignments() { { assert_eq!( *attributes, - vec![Attribute::Role( - Identifier::dummy("surgeon"), - Span::default() - )] + vec![Attribute::Role(Identifier::new("surgeon"), Span::default())] ); assert_eq!(substeps.len(), 3); // a, b, c } else { @@ -913,7 +910,7 @@ fn parallel_role_assignments() { assert_eq!( *attributes, vec![Attribute::Role( - Identifier::dummy("anaesthetist"), + Identifier::new("anaesthetist"), Span::default() )] ); @@ -932,7 +929,7 @@ fn parallel_role_assignments() { assert_eq!( *attributes, vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default() )] ); @@ -994,10 +991,7 @@ fn multiple_roles_with_dependent_substeps() { { assert_eq!( *attributes, - vec![Attribute::Role( - Identifier::dummy("surgeon"), - Span::default() - )] + vec![Attribute::Role(Identifier::new("surgeon"), Span::default())] ); assert_eq!(substeps.len(), 3); } else { @@ -1014,7 +1008,7 @@ fn multiple_roles_with_dependent_substeps() { assert_eq!( *attributes, vec![Attribute::Role( - Identifier::dummy("anaesthetist"), + Identifier::new("anaesthetist"), Span::default() )] ); @@ -1033,7 +1027,7 @@ fn multiple_roles_with_dependent_substeps() { assert_eq!( *attributes, vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default() )] ); @@ -1093,7 +1087,7 @@ fn mixed_substeps_in_roles() { subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("team_lead"), + Identifier::new("team_lead"), Span::default() )], subscopes: vec![ @@ -1206,7 +1200,7 @@ fn naked_bindings() { descriptive, Ok(vec![Paragraph::new(vec![Descriptive::Binding( Box::new(Descriptive::Text("What is the result?")), - vec![Identifier::dummy("answer")] + vec![Identifier::new("answer")] )])]) ); @@ -1219,7 +1213,7 @@ fn naked_bindings() { Ok(vec![Paragraph::new(vec![ Descriptive::Binding( Box::new(Descriptive::Text("Enter your name")), - vec![Identifier::dummy("name")] + vec![Identifier::new("name")] ), Descriptive::Text("Continue with next step") ])]) @@ -1237,14 +1231,14 @@ fn naked_bindings() { Descriptive::Text("First"), Descriptive::Binding( Box::new(Descriptive::Application(Invocation { - target: Target::Local(Identifier::dummy("do_something")), + target: Target::Local(Identifier::new("do_something")), parameters: None, })), - vec![Identifier::dummy("result")] + vec![Identifier::new("result")] ), Descriptive::Binding( Box::new(Descriptive::Text("then describe the outcome")), - vec![Identifier::dummy("description")] + vec![Identifier::new("description")] ) ])]) ); @@ -1293,7 +1287,7 @@ second_section_second_procedure : source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("main_procedure"), + name: Identifier::new("main_procedure"), parameters: None, signature: None, elements: vec![Element::Steps( @@ -1303,14 +1297,14 @@ second_section_second_procedure : title: Some(Paragraph::new(vec![Descriptive::Text("First Section")])), body: Technique::Procedures(vec![ Procedure { - name: Identifier::dummy("first_section_first_procedure"), + name: Identifier::new("first_section_first_procedure"), parameters: None, signature: None, elements: vec![Element::Title("One dot One", Span::default())], span: Span::default(), }, Procedure { - name: Identifier::dummy("first_section_second_procedure"), + name: Identifier::new("first_section_second_procedure"), parameters: None, signature: None, elements: vec![Element::Title("One dot Two", Span::default())], @@ -1324,14 +1318,14 @@ second_section_second_procedure : title: Some(Paragraph::new(vec![Descriptive::Text("Second Section")])), body: Technique::Procedures(vec![ Procedure { - name: Identifier::dummy("second_section_first_procedure"), + name: Identifier::new("second_section_first_procedure"), parameters: None, signature: None, elements: vec![Element::Title("Two dot One", Span::default())], span: Span::default(), }, Procedure { - name: Identifier::dummy("second_section_second_procedure"), + name: Identifier::new("second_section_second_procedure"), parameters: None, signature: None, elements: vec![Element::Title("Two dot Two", Span::default())], @@ -1394,8 +1388,8 @@ procedure_four : Concept -> Architecture } = &steps[0] { assert_eq!(section1_procs.len(), 2); - assert_eq!(section1_procs[0].name, Identifier::dummy("procedure_one")); - assert_eq!(section1_procs[1].name, Identifier::dummy("procedure_two")); + assert_eq!(section1_procs[0].name, Identifier::new("procedure_one")); + assert_eq!(section1_procs[1].name, Identifier::new("procedure_two")); } else { panic!("First section should contain procedures"); } @@ -1407,8 +1401,8 @@ procedure_four : Concept -> Architecture } = &steps[1] { assert_eq!(section2_procs.len(), 2); - assert_eq!(section2_procs[0].name, Identifier::dummy("procedure_three")); - assert_eq!(section2_procs[1].name, Identifier::dummy("procedure_four")); + assert_eq!(section2_procs[0].name, Identifier::new("procedure_three")); + assert_eq!(section2_procs[1].name, Identifier::new("procedure_four")); } else { panic!("Second section should contain procedures"); } @@ -1458,7 +1452,7 @@ III. Implementation source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("main_procedure"), + name: Identifier::new("main_procedure"), parameters: None, signature: None, elements: vec![Element::Steps( @@ -1476,13 +1470,13 @@ III. Implementation )])), body: Technique::Procedures(vec![ Procedure { - name: Identifier::dummy("requirements_and_architecture"), + name: Identifier::new("requirements_and_architecture"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Concept")), + requires: Genus::Single(Forma::new("Concept")), provides: Genus::Naked(vec![ - Forma::dummy("Requirements"), - Forma::dummy("Architecture") + Forma::new("Requirements"), + Forma::new("Architecture") ]), }), elements: vec![Element::Steps( @@ -1492,12 +1486,12 @@ III. Implementation description: vec![Paragraph::new(vec![ Descriptive::Text("Define Requirements"), Descriptive::Application(Invocation { - target: Target::Local(Identifier::dummy( + target: Target::Local(Identifier::new( "define_requirements", )), parameters: Some(vec![ Expression::Variable( - Identifier::dummy("concept"), + Identifier::new("concept"), Span::default(), ) ]), @@ -1512,12 +1506,12 @@ III. Implementation description: vec![Paragraph::new(vec![ Descriptive::Text("Determine Architecture"), Descriptive::Application(Invocation { - target: Target::Local(Identifier::dummy( + target: Target::Local(Identifier::new( "determine_architecture", )), parameters: Some(vec![ Expression::Variable( - Identifier::dummy("concept"), + Identifier::new("concept"), Span::default(), ) ]), @@ -1533,21 +1527,21 @@ III. Implementation span: Span::default(), }, Procedure { - name: Identifier::dummy("define_requirements"), + name: Identifier::new("define_requirements"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Concept")), - provides: Genus::Single(Forma::dummy("Requirements")), + requires: Genus::Single(Forma::new("Concept")), + provides: Genus::Single(Forma::new("Requirements")), }), elements: vec![], span: Span::default(), }, Procedure { - name: Identifier::dummy("determine_architecture"), + name: Identifier::new("determine_architecture"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Concept")), - provides: Genus::Single(Forma::dummy("Architecture")), + requires: Genus::Single(Forma::new("Concept")), + provides: Genus::Single(Forma::new("Architecture")), }), elements: vec![], span: Span::default(), diff --git a/src/problem/messages.rs b/src/problem/messages.rs index a9fa363f..8a11a2f0 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -38,10 +38,10 @@ there was no more input remaining in the current scope. ParsingError::MissingParenthesis(_, _) => { let examples = vec![Descriptive::Binding( Box::new(Descriptive::Application(Invocation { - target: Target::Local(Identifier::dummy("mix_pangalactic_gargle_blaster")), + target: Target::Local(Identifier::new("mix_pangalactic_gargle_blaster")), parameters: None, })), - vec![Identifier::dummy("zaphod"), Identifier::dummy("trillian")], + vec![Identifier::new("zaphod"), Identifier::new("trillian")], )]; ( @@ -126,28 +126,28 @@ to be used when rendering the Technique. Common domains include ParsingError::InvalidIdentifier(_, _, _) => { let examples = vec![ Procedure { - name: Identifier::dummy("make_coffee"), + name: Identifier::new("make_coffee"), parameters: None, signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("attempt1"), + name: Identifier::new("attempt1"), parameters: None, signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("i"), + name: Identifier::new("i"), parameters: None, signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("l33t_hax0r"), + name: Identifier::new("l33t_hax0r"), parameters: None, signature: None, elements: Vec::new(), @@ -178,9 +178,9 @@ letters, numbers, and underscores. Valid examples include: } ParsingError::InvalidForma(_, _) => { let examples = vec![ - Forma::dummy("Coffee"), - Forma::dummy("Ingredients"), - Forma::dummy("PatientRecord"), + Forma::new("Coffee"), + Forma::new("Ingredients"), + Forma::new("PatientRecord"), ]; ( @@ -204,10 +204,10 @@ For example: } ParsingError::InvalidGenus(_, _) => { let examples = vec![ - Genus::Single(Forma::dummy("Coffee")), - Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Water")]), - Genus::Naked(vec![Forma::dummy("Beans"), Forma::dummy("Water")]), - Genus::List(Forma::dummy("Patient")), + Genus::Single(Forma::new("Coffee")), + Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Water")]), + Genus::Naked(vec![Forma::new("Beans"), Forma::new("Water")]), + Genus::List(Forma::new("Patient")), Genus::Unit, ]; @@ -241,16 +241,16 @@ doesn't have an input or result, per se. ParsingError::InvalidSignature(_, _) => { let examples = vec![ Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")), + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")), }, Signature { - requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::Single(Forma::dummy("Coffee")), + requires: Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::Single(Forma::new("Coffee")), }, Signature { - requires: Genus::List(Forma::dummy("FunctionalRequirement")), - provides: Genus::Single(Forma::dummy("Architecture")), + requires: Genus::List(Forma::new("FunctionalRequirement")), + provides: Genus::Single(Forma::new("Architecture")), }, ]; @@ -279,72 +279,72 @@ this form. ParsingError::InvalidDeclaration(_, _) => { let examples = vec![ Procedure { - name: Identifier::dummy("f"), + name: Identifier::new("f"), parameters: None, signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("implementation"), + name: Identifier::new("implementation"), parameters: None, signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("make_coffee"), + name: Identifier::new("make_coffee"), parameters: None, signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("f"), + name: Identifier::new("f"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")), + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")), }), elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("implementation"), + name: Identifier::new("implementation"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Design")), - provides: Genus::Single(Forma::dummy("Product")), + requires: Genus::Single(Forma::new("Design")), + provides: Genus::Single(Forma::new("Product")), }), elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("make_coffee"), + name: Identifier::new("make_coffee"), parameters: None, signature: Some(Signature { - requires: Genus::Naked(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::Single(Forma::dummy("Coffee")), + requires: Genus::Naked(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::Single(Forma::new("Coffee")), }), elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("make_coffee"), + name: Identifier::new("make_coffee"), parameters: None, signature: Some(Signature { - requires: Genus::Tuple(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::Single(Forma::dummy("Coffee")), + requires: Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::Single(Forma::new("Coffee")), }), elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("make_coffee"), - parameters: Some(vec![Identifier::dummy("b"), Identifier::dummy("m")]), + name: Identifier::new("make_coffee"), + parameters: Some(vec![Identifier::new("b"), Identifier::new("m")]), signature: Some(Signature { - requires: Genus::Naked(vec![Forma::dummy("Beans"), Forma::dummy("Milk")]), - provides: Genus::Single(Forma::dummy("Coffee")), + requires: Genus::Naked(vec![Forma::new("Beans"), Forma::new("Milk")]), + provides: Genus::Single(Forma::new("Coffee")), }), elements: Vec::new(), span: Span::default(), @@ -389,35 +389,35 @@ Finally, variables can be assigned for the names of the input parameters: ParsingError::InvalidParameters(_, _) => { let examples = vec![ Procedure { - name: Identifier::dummy("create_bypass"), - parameters: Some(vec![Identifier::dummy("a"), Identifier::dummy("b")]), + name: Identifier::new("create_bypass"), + parameters: Some(vec![Identifier::new("a"), Identifier::new("b")]), signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("bulldoze"), - parameters: Some(vec![Identifier::dummy("c")]), + name: Identifier::new("bulldoze"), + parameters: Some(vec![Identifier::new("c")]), signature: None, elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("lawsuit"), + name: Identifier::new("lawsuit"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Council")), - provides: Genus::List(Forma::dummy("Penny")), + requires: Genus::Single(Forma::new("Council")), + provides: Genus::List(Forma::new("Penny")), }), elements: Vec::new(), span: Span::default(), }, Procedure { - name: Identifier::dummy("lawsuit"), - parameters: Some(vec![Identifier::dummy("c")]), + name: Identifier::new("lawsuit"), + parameters: Some(vec![Identifier::new("c")]), signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Council")), - provides: Genus::List(Forma::dummy("Penny")), + requires: Genus::Single(Forma::new("Council")), + provides: Genus::List(Forma::new("Penny")), }), elements: Vec::new(), span: Span::default(), @@ -472,13 +472,13 @@ author of the Technique. ParsingError::InvalidInvocation(_, _) => { let examples = vec![ Invocation { - target: Target::Local(Identifier::dummy("make_coffee")), + target: Target::Local(Identifier::new("make_coffee")), parameters: None, }, Invocation { - target: Target::Local(Identifier::dummy("check_vitals")), + target: Target::Local(Identifier::new("check_vitals")), parameters: Some(vec![Expression::Variable( - Identifier::dummy("patient"), + Identifier::new("patient"), Span::default(), )]), }, @@ -506,21 +506,21 @@ If the procedure takes parameters they can be specified in parenthesis: ParsingError::InvalidFunction(_, _) => { let examples = vec![ Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::String( vec![Piece::Text("ls -la")], Span::default(), )], }, Function { - target: Identifier::dummy("now"), + target: Identifier::new("now"), parameters: vec![], }, Function { - target: Identifier::dummy("calculate"), + target: Identifier::new("calculate"), parameters: vec![ - Expression::Variable(Identifier::dummy("a"), Span::default()), - Expression::Variable(Identifier::dummy("b"), Span::default()), + Expression::Variable(Identifier::new("a"), Span::default()), + Expression::Variable(Identifier::new("b"), Span::default()), ], }, ]; @@ -549,7 +549,7 @@ expressions as parameters as required: let examples = vec![ Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::String( vec![Piece::Text("command")], Span::default(), @@ -562,9 +562,9 @@ expressions as parameters as required: Span::default(), ), Expression::Foreach( - vec![Identifier::dummy("patient")], + vec![Identifier::new("patient")], Box::new(Expression::Variable( - Identifier::dummy("patients"), + Identifier::new("patients"), Span::default(), )), Span::default(), @@ -670,19 +670,19 @@ parallel steps, but again this is not compulsory. Scope::AttributeBlock { attributes: vec![ Attribute::Role( - Identifier::dummy("president_of_the_galaxy"), + Identifier::new("president_of_the_galaxy"), Span::default(), ), - Attribute::Role(Identifier::dummy("femme_fatale"), Span::default()), + Attribute::Role(Identifier::new("femme_fatale"), Span::default()), ], subscopes: vec![], span: Span::default(), }, Scope::AttributeBlock { attributes: vec![ - Attribute::Place(Identifier::dummy("milliways"), Span::default()), - Attribute::Role(Identifier::dummy("waiter"), Span::default()), - Attribute::Role(Identifier::dummy("dish_of_the_day"), Span::default()), + Attribute::Place(Identifier::new("milliways"), Span::default()), + Attribute::Role(Identifier::new("waiter"), Span::default()), + Attribute::Role(Identifier::new("dish_of_the_day"), Span::default()), ], subscopes: vec![], span: Span::default(), @@ -712,17 +712,17 @@ nested underneath a role or place assignment. ParsingError::InvalidForeach(_, _) => { let examples = vec![ Expression::Foreach( - vec![Identifier::dummy("patient")], + vec![Identifier::new("patient")], Box::new(Expression::Variable( - Identifier::dummy("patients"), + Identifier::new("patients"), Span::default(), )), Span::default(), ), Expression::Foreach( - vec![Identifier::dummy("name"), Identifier::dummy("value")], + vec![Identifier::new("name"), Identifier::new("value")], Box::new(Expression::Variable( - Identifier::dummy("data"), + Identifier::new("data"), Span::default(), )), Span::default(), diff --git a/tests/formatting/formatter.rs b/tests/formatting/formatter.rs index 0ee65d36..15656f00 100644 --- a/tests/formatting/formatter.rs +++ b/tests/formatting/formatter.rs @@ -47,11 +47,11 @@ mod verify { source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("first"), + name: Identifier::new("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")), + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")), }), elements: vec![], span: Span::default(), @@ -79,24 +79,24 @@ first : A -> B }), body: Some(Technique::Procedures(vec![ Procedure { - name: Identifier::dummy("first"), + name: Identifier::new("first"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("A")), - provides: Genus::Single(Forma::dummy("B")), + requires: Genus::Single(Forma::new("A")), + provides: Genus::Single(Forma::new("B")), }), elements: vec![], span: Span::default(), }, Procedure { - name: Identifier::dummy("second"), + name: Identifier::new("second"), parameters: None, signature: Some(Signature { - requires: Genus::List(Forma::dummy("Thing")), + requires: Genus::List(Forma::new("Thing")), provides: Genus::Tuple(vec![ - Forma::dummy("Who"), - Forma::dummy("Where"), - Forma::dummy("Why"), + Forma::new("Who"), + Forma::new("Where"), + Forma::new("Why"), ]), }), elements: vec![], @@ -127,11 +127,11 @@ second : [Thing] -> (Who, Where, Why) source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("win_le_tour"), + name: Identifier::new("win_le_tour"), parameters: None, signature: Some(Signature { - requires: Genus::Single(Forma::dummy("Bicycle")), - provides: Genus::Single(Forma::dummy("YellowJersey")), + requires: Genus::Single(Forma::new("Bicycle")), + provides: Genus::Single(Forma::new("YellowJersey")), }), elements: vec![Element::Steps( vec![ @@ -206,13 +206,13 @@ win_le_tour : Bicycle -> YellowJersey source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("vibe_coding"), + name: Identifier::new("vibe_coding"), parameters: None, signature: None, elements: vec![Element::CodeBlock( vec![Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec!["rm -rf /"], @@ -251,7 +251,7 @@ vibe_coding : source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("action"), + name: Identifier::new("action"), parameters: None, signature: None, elements: vec![ @@ -268,7 +268,7 @@ vibe_coding : Descriptive::Text("To take the action, we must:"), Descriptive::CodeInline(Expression::Execution( Function { - target: Identifier::dummy("exec"), + target: Identifier::new("exec"), parameters: vec![Expression::Multiline( Some("bash"), vec!["rm -rf /"], @@ -313,7 +313,7 @@ We must take action! source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("journal"), + name: Identifier::new("journal"), parameters: None, signature: None, elements: vec![ @@ -331,7 +331,7 @@ We must take action! )])], subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("journalist"), + Identifier::new("journalist"), Span::default(), )], subscopes: vec![Scope::CodeBlock { @@ -341,7 +341,7 @@ We must take action! label: "timestamp", value: Expression::Execution( Function { - target: Identifier::dummy("now"), + target: Identifier::new("now"), parameters: vec![], }, Span::default(), @@ -350,7 +350,7 @@ We must take action! Pair { label: "message", value: Expression::Variable( - Identifier::dummy("msg"), + Identifier::new("msg"), Span::default(), ), }, @@ -399,7 +399,7 @@ Record everything, with timestamps. source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("before_leaving"), + name: Identifier::new("before_leaving"), parameters: None, signature: None, elements: vec![ @@ -434,16 +434,16 @@ Record everything, with timestamps. )])], subscopes: vec![Scope::CodeBlock { expressions: vec![Expression::Foreach( - vec![Identifier::dummy("specimen")], + vec![Identifier::new("specimen")], Box::new(Expression::Variable( - Identifier::dummy("specimens"), + Identifier::new("specimens"), Span::default(), )), Span::default(), )], subscopes: vec![Scope::AttributeBlock { attributes: vec![Attribute::Role( - Identifier::dummy("nursing_team"), + Identifier::new("nursing_team"), Span::default(), )], subscopes: vec![Scope::DependentBlock { @@ -512,7 +512,7 @@ before_leaving : source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("main_procedure"), + name: Identifier::new("main_procedure"), parameters: None, signature: None, elements: vec![Element::Steps( @@ -565,7 +565,7 @@ III. source: None, header: None, body: Some(Technique::Procedures(vec![Procedure { - name: Identifier::dummy("test_procedure"), + name: Identifier::new("test_procedure"), parameters: None, signature: None, elements: vec![Element::Steps( From 852ba4806e1eb95fea61f8f1ad4b021aa7fb9335 Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Thu, 7 May 2026 09:56:34 +1000 Subject: [PATCH 8/9] Use Span for ParsingError --- src/editor/server.rs | 101 ++++---- src/language/types.rs | 28 +- src/parsing/checks/errors.rs | 46 ++-- src/parsing/checks/parser.rs | 78 ++---- src/parsing/parser.rs | 486 ++++++++++++++++++----------------- src/problem/messages.rs | 64 ++--- 6 files changed, 384 insertions(+), 419 deletions(-) diff --git a/src/editor/server.rs b/src/editor/server.rs index 340d2d18..c17df3b6 100644 --- a/src/editor/server.rs +++ b/src/editor/server.rs @@ -701,123 +701,123 @@ impl TechniqueLanguageServer { }; let (message, severity) = match &error { - ParsingError::IllegalParserState(_, _) => ( + ParsingError::IllegalParserState(_) => ( "Internal parser error".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::Unimplemented(_, _) => ( + ParsingError::Unimplemented(_) => ( "Unimplemented feature".to_string(), DiagnosticSeverity::WARNING, ), - ParsingError::Unrecognized(_, _) => { + ParsingError::Unrecognized(_) => { ("Unrecognized syntax".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::UnexpectedEndOfInput(_, _) => ( + ParsingError::UnexpectedEndOfInput(_) => ( "Unexpected end of input".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::Expected(_, _, expected) => { + ParsingError::Expected(_, expected) => { (format!("Expected {}", expected), DiagnosticSeverity::ERROR) } - ParsingError::ExpectedMatchingChar(_, _, subject, start, end) => ( + ParsingError::ExpectedMatchingChar(_, subject, start, end) => ( format!("Expected matching '{}' for '{}' in {}", end, start, subject), DiagnosticSeverity::ERROR, ), - ParsingError::MissingParenthesis(_, _) => ( + ParsingError::MissingParenthesis(_) => ( "Require parenthesis around multiple parameters in binding".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidCharacter(_, _, ch) => ( + ParsingError::InvalidCharacter(_, ch) => ( format!("Invalid character '{}'", ch), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidHeader(_, _) => { + ParsingError::InvalidHeader(_) => { ("Invalid header line".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::InvalidIdentifier(_, _, id) => ( + ParsingError::InvalidIdentifier(_, id) => ( format!("Invalid identifier '{}'", id), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidForma(_, _) => ( + ParsingError::InvalidForma(_) => ( "Invalid forma in signature".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidGenus(_, _) => ( + ParsingError::InvalidGenus(_) => ( "Invalid genus in signature".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidSignature(_, _) => ( + ParsingError::InvalidSignature(_) => ( "Invalid signature in procedure declaration".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidParameters(_, _) => ( + ParsingError::InvalidParameters(_) => ( "Malformed parameters in procedure declaration".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidDeclaration(_, _) => ( + ParsingError::InvalidDeclaration(_) => ( "Invalid procedure declaration".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidSection(_, _) => ( + ParsingError::InvalidSection(_) => ( "Invalid section heading".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidInvocation(_, _) => ( + ParsingError::InvalidInvocation(_) => ( "Invalid procedure Invocation".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidFunction(_, _) => ( + ParsingError::InvalidFunction(_) => ( "Invalid function call".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidCodeBlock(_, _) => { + ParsingError::InvalidCodeBlock(_) => { ("Invalid code block".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::InvalidStep(_, _) => { + ParsingError::InvalidStep(_) => { ("Invalid step".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::InvalidSubstep(_, _) => { + ParsingError::InvalidSubstep(_) => { ("Invalid substep".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::InvalidAttribute(_, _) => ( + ParsingError::InvalidAttribute(_) => ( "Invalid attribute assignment".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidResponse(_, _) => { + ParsingError::InvalidResponse(_) => { ("Invalid response".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::InvalidMultiline(_, _) => ( + ParsingError::InvalidMultiline(_) => ( "Invalid multiline content".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidForeach(_, _) => ( + ParsingError::InvalidForeach(_) => ( "Invalid foreach expression".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidIntegral(_, _) => ( + ParsingError::InvalidIntegral(_) => ( "Invalid integral number".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidQuantity(_, _) => { + ParsingError::InvalidQuantity(_) => { ("Invalid quantity".to_string(), DiagnosticSeverity::ERROR) } - ParsingError::InvalidQuantityDecimal(_, _) => ( + ParsingError::InvalidQuantityDecimal(_) => ( "Invalid quantity decimal".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidQuantityUncertainty(_, _) => ( + ParsingError::InvalidQuantityUncertainty(_) => ( "Invalid quantity uncertainty".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidQuantityMagnitude(_, _) => ( + ParsingError::InvalidQuantityMagnitude(_) => ( "Invalid quantity magnitude".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::InvalidQuantitySymbol(_, _) => ( + ParsingError::InvalidQuantitySymbol(_) => ( "Invalid quantity symbol".to_string(), DiagnosticSeverity::ERROR, ), - ParsingError::UnclosedInterpolation(_, _) => ( + ParsingError::UnclosedInterpolation(_) => ( "Unclosed interpolation".to_string(), DiagnosticSeverity::ERROR, ), @@ -869,6 +869,7 @@ fn offset_to_position(text: &str, offset: usize) -> Position { #[cfg(test)] mod tests { use super::*; + use technique::language::Span; #[test] fn test_calculate_str_offsets() { @@ -936,17 +937,17 @@ mod tests { fn test_parsing_error_types() { // Test that all error types can be converted to messages without panicking let test_errors = vec![ - ParsingError::IllegalParserState(0, 0), - ParsingError::Unimplemented(0, 0), - ParsingError::Unrecognized(0, 0), - ParsingError::UnexpectedEndOfInput(0, 0), - ParsingError::Expected(0, 0, "test"), - ParsingError::ExpectedMatchingChar(0, 0, "test", '(', ')'), - ParsingError::MissingParenthesis(0, 0), - ParsingError::InvalidCharacter(0, 0, 'x'), - ParsingError::InvalidHeader(0, 0), - ParsingError::InvalidIdentifier(0, 0, "test".to_string()), - ParsingError::InvalidDeclaration(0, 0), + ParsingError::IllegalParserState(Span::new(0, 0)), + ParsingError::Unimplemented(Span::new(0, 0)), + ParsingError::Unrecognized(Span::new(0, 0)), + ParsingError::UnexpectedEndOfInput(Span::new(0, 0)), + ParsingError::Expected(Span::new(0, 0), "test"), + ParsingError::ExpectedMatchingChar(Span::new(0, 0), "test", '(', ')'), + ParsingError::MissingParenthesis(Span::new(0, 0)), + ParsingError::InvalidCharacter(Span::new(0, 0), 'x'), + ParsingError::InvalidHeader(Span::new(0, 0)), + ParsingError::InvalidIdentifier(Span::new(0, 0), "test".to_string()), + ParsingError::InvalidDeclaration(Span::new(0, 0)), ]; // This shouldn't panic - just test that all enum variants are handled @@ -956,25 +957,25 @@ mod tests { // Test message generation (this was formerly in convert_parsing_errors) match &error { - ParsingError::IllegalParserState(_, _) => { + ParsingError::IllegalParserState(_) => { assert_eq!("Internal parser error", "Internal parser error") } - ParsingError::Unimplemented(_, _) => { + ParsingError::Unimplemented(_) => { assert_eq!("Unimplemented feature", "Unimplemented feature") } - ParsingError::Unrecognized(_, _) => { + ParsingError::Unrecognized(_) => { assert_eq!("Unrecognized syntax", "Unrecognized syntax") } - ParsingError::UnexpectedEndOfInput(_, _) => { + ParsingError::UnexpectedEndOfInput(_) => { assert_eq!("Unexpected end of input", "Unexpected end of input") } - ParsingError::Expected(_, _, expected) => assert_eq!(*expected, "test"), - ParsingError::ExpectedMatchingChar(_, _, subject, start, end) => { + ParsingError::Expected(_, expected) => assert_eq!(*expected, "test"), + ParsingError::ExpectedMatchingChar(_, subject, start, end) => { assert_eq!(*subject, "test"); assert_eq!(*start, '('); assert_eq!(*end, ')'); } - ParsingError::InvalidDeclaration(_, _) => { + ParsingError::InvalidDeclaration(_) => { assert_eq!("Invalid declaration", "Invalid declaration") } _ => {} // Other variants tested implicitly diff --git a/src/language/types.rs b/src/language/types.rs index 8d7dd17e..7df1fc6d 100644 --- a/src/language/types.rs +++ b/src/language/types.rs @@ -3,12 +3,18 @@ use crate::regex::*; /// Byte range within the original source. `length` excludes trailing whitespace. -#[derive(Copy, Clone, Default, Eq, Debug, PartialEq)] +#[derive(Copy, Clone, Default, Eq, Debug, PartialEq, PartialOrd, Ord)] pub struct Span { pub offset: usize, pub length: usize, } +impl Span { + pub const fn new(offset: usize, length: usize) -> Self { + Span { offset, length } + } +} + #[derive(Eq, Debug, PartialEq)] pub struct Document<'i> { pub source: Option<&'i str>, @@ -120,10 +126,7 @@ impl<'i> Identifier<'i> { pub const fn new(value: &'i str) -> Self { Identifier { value, - span: Span { - offset: 0, - length: 0, - }, + span: Span::new(0, 0), } } } @@ -144,10 +147,7 @@ impl<'i> External<'i> { pub const fn new(value: &'i str) -> Self { External { value, - span: Span { - offset: 0, - length: 0, - }, + span: Span::new(0, 0), } } } @@ -174,10 +174,7 @@ impl<'i> Forma<'i> { pub const fn new(value: &'i str) -> Self { Forma { value, - span: Span { - offset: 0, - length: 0, - }, + span: Span::new(0, 0), } } } @@ -529,10 +526,7 @@ pub(crate) fn validate_forma(input: &str, span: Span) -> Option> { /// `child` must be a sub-slice of `parent`. fn sub_span(parent: &str, child: &str, parent_span: Span) -> Span { let inner = (child.as_ptr() as usize) - (parent.as_ptr() as usize); - Span { - offset: parent_span.offset + inner, - length: child.len(), - } + Span::new(parent_span.offset + inner, child.len()) } fn parse_tuple(input: &str, span: Span) -> Option>> { diff --git a/src/parsing/checks/errors.rs b/src/parsing/checks/errors.rs index 34fba4f2..9e882132 100644 --- a/src/parsing/checks/errors.rs +++ b/src/parsing/checks/errors.rs @@ -30,7 +30,7 @@ fn invalid_identifier_uppercase_start() { Making_Coffee : Ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidIdentifier(0, 13, "Making_Coffee".to_string()), + ParsingError::InvalidIdentifier(Span::new(0, 13), "Making_Coffee".to_string()), ); } @@ -41,7 +41,7 @@ fn invalid_identifier_mixed_case() { makeCoffee : Ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidIdentifier(0, 10, "makeCoffee".to_string()), + ParsingError::InvalidIdentifier(Span::new(0, 10), "makeCoffee".to_string()), ); } @@ -52,7 +52,7 @@ fn invalid_identifier_with_dashes() { make-coffee : Ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidIdentifier(0, 11, "make-coffee".to_string()), + ParsingError::InvalidIdentifier(Span::new(0, 11), "make-coffee".to_string()), ); } @@ -63,7 +63,7 @@ fn invalid_identifier_with_spaces() { make coffee : Ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidParameters(5, 6), + ParsingError::InvalidParameters(Span::new(5, 6)), ); } @@ -74,7 +74,7 @@ fn invalid_signature_wrong_arrow() { making_coffee : Ingredients => Coffee "# .trim_ascii(), - ParsingError::InvalidSignature(28, 0), + ParsingError::InvalidSignature(Span::new(28, 0)), ); } @@ -85,7 +85,7 @@ fn invalid_genus_lowercase_forma() { making_coffee : ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidGenus(16, 11), + ParsingError::InvalidGenus(Span::new(16, 11)), ); } @@ -96,7 +96,7 @@ fn invalid_genus_both_lowercase() { making_coffee : ingredients -> coffee "# .trim_ascii(), - ParsingError::InvalidGenus(16, 11), + ParsingError::InvalidGenus(Span::new(16, 11)), ); } @@ -107,7 +107,7 @@ fn invalid_signature_missing_arrow() { making_coffee : Ingredients Coffee "# .trim_ascii(), - ParsingError::InvalidSignature(28, 0), + ParsingError::InvalidSignature(Span::new(28, 0)), ); } @@ -118,7 +118,7 @@ fn invalid_declaration_missing_colon() { making_coffee Ingredients -> Coffee "# .trim_ascii(), - ParsingError::Unrecognized(0, 0), + ParsingError::Unrecognized(Span::new(0, 0)), ); } @@ -129,7 +129,7 @@ fn invalid_identifier_in_parameters() { making_coffee(BadParam) : Ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidIdentifier(0, 8, "BadParam".to_string()), + ParsingError::InvalidIdentifier(Span::new(0, 8), "BadParam".to_string()), ); } @@ -140,7 +140,7 @@ fn invalid_identifier_empty() { : Ingredients -> Coffee "# .trim_ascii(), - ParsingError::InvalidDeclaration(0, 0), + ParsingError::InvalidDeclaration(Span::new(0, 0)), ); } @@ -153,7 +153,7 @@ making_coffee : A. First step (should be lowercase 'a.') "# .trim_ascii(), - ParsingError::InvalidStep(21, 0), + ParsingError::InvalidStep(Span::new(21, 0)), ); } @@ -167,7 +167,7 @@ making_coffee : "Yes" | "No" "# .trim_ascii(), - ParsingError::InvalidResponse(52, 0), + ParsingError::InvalidResponse(Span::new(52, 0)), ); } @@ -181,7 +181,7 @@ making_coffee : This is missing closing backticks "# .trim_ascii(), - ParsingError::InvalidMultiline(24, 0), + ParsingError::InvalidMultiline(Span::new(24, 0)), ); } @@ -194,7 +194,7 @@ making_coffee : 1. Do something { exec("command" "# .trim_ascii(), - ParsingError::ExpectedMatchingChar(37, 0, "a code block", '{', '}'), + ParsingError::ExpectedMatchingChar(Span::new(37, 0), "a code block", '{', '}'), ); } @@ -207,7 +207,7 @@ making_coffee : i. Wrong case section "# .trim_ascii(), - ParsingError::InvalidStep(21, 0), + ParsingError::InvalidStep(Span::new(21, 0)), ); } @@ -220,7 +220,7 @@ making_coffee : 1. Do '), + ParsingError::ExpectedMatchingChar(Span::new(27, 0), "an invocation", '<', '>'), ); } @@ -233,7 +233,7 @@ making_coffee : 1. Do something { exec("command" } "# .trim_ascii(), - ParsingError::ExpectedMatchingChar(43, 0, "parameters for a function", '(', ')'), + ParsingError::ExpectedMatchingChar(Span::new(43, 0), "parameters for a function", '(', ')'), ); } @@ -246,7 +246,7 @@ making_coffee : 1. Do something { re peat() } "# .trim_ascii(), - ParsingError::InvalidCodeBlock(39, 10), + ParsingError::InvalidCodeBlock(Span::new(39, 10)), ); } @@ -259,7 +259,7 @@ making_coffee : 1. Do something { re peat () } "# .trim_ascii(), - ParsingError::InvalidCodeBlock(39, 18), + ParsingError::InvalidCodeBlock(Span::new(39, 18)), ); } @@ -272,7 +272,7 @@ making_coffee : 1. { repeat '), + ParsingError::ExpectedMatchingChar(Span::new(33, 0), "an invocation", '<', '>'), ); } @@ -286,7 +286,7 @@ making_coffee : A. This should be lowercase "# .trim_ascii(), - ParsingError::InvalidSubstep(43, 0), + ParsingError::InvalidSubstep(Span::new(43, 0)), ); } @@ -299,6 +299,6 @@ robot : Your plastic pal who's fun to be with! { re peat } "# .trim_ascii(), - ParsingError::InvalidCodeBlock(50, 3), + ParsingError::InvalidCodeBlock(Span::new(50, 3)), ); } diff --git a/src/parsing/checks/parser.rs b/src/parsing/checks/parser.rs index 5983b8f2..deb8f3e6 100644 --- a/src/parsing/checks/parser.rs +++ b/src/parsing/checks/parser.rs @@ -617,7 +617,7 @@ fn read_toplevel_steps() { // Test invalid step input.initialize("Not a step"); let result = input.read_step_dependent(); - assert_eq!(result, Err(ParsingError::InvalidStep(0, 0))); + assert_eq!(result, Err(ParsingError::InvalidStep(Span::new(0, 0)))); } #[test] @@ -1767,7 +1767,10 @@ fn test_foreach_keyword_boundary() { input.initialize("{ foreachitem in items }"); let result = input.read_code_block(); - assert_eq!(result, Err(ParsingError::InvalidCodeBlock(2, 12))); + assert_eq!( + result, + Err(ParsingError::InvalidCodeBlock(Span::new(2, 12))) + ); } #[test] @@ -1845,7 +1848,8 @@ fn splitting_by() { // different split character input.initialize("'Yes'|'No'|'Maybe'"); let result = input.take_split_by('|', |inner| { - validate_response(inner.source).ok_or(ParsingError::IllegalParserState(inner.offset, 0)) + validate_response(inner.source) + .ok_or(ParsingError::IllegalParserState(Span::new(inner.offset, 0))) }); assert_eq!( result, @@ -2067,20 +2071,8 @@ fn attribute_spans_include_marker() { Attribute::Role(Identifier::new("chef"), Span::default()) ); if let Attribute::Role(id, span) = &result[0] { - assert_eq!( - *span, - Span { - offset: 0, - length: 5 - } - ); - assert_eq!( - id.span, - Span { - offset: 1, - length: 4 - } - ); + assert_eq!(*span, Span::new(0, 5)); + assert_eq!(id.span, Span::new(1, 4)); } input.initialize("^kitchen"); @@ -2088,20 +2080,8 @@ fn attribute_spans_include_marker() { .read_attributes() .unwrap(); if let Attribute::Place(id, span) = &result[0] { - assert_eq!( - *span, - Span { - offset: 0, - length: 8 - } - ); - assert_eq!( - id.span, - Span { - offset: 1, - length: 7 - } - ); + assert_eq!(*span, Span::new(0, 8)); + assert_eq!(id.span, Span::new(1, 7)); } input.initialize("@waiter + ^milliways"); @@ -2109,36 +2089,12 @@ fn attribute_spans_include_marker() { .read_attributes() .unwrap(); if let Attribute::Role(id, span) = &result[0] { - assert_eq!( - *span, - Span { - offset: 0, - length: 7 - } - ); - assert_eq!( - id.span, - Span { - offset: 1, - length: 6 - } - ); + assert_eq!(*span, Span::new(0, 7)); + assert_eq!(id.span, Span::new(1, 6)); } if let Attribute::Place(id, span) = &result[1] { - assert_eq!( - *span, - Span { - offset: 10, - length: 10 - } - ); - assert_eq!( - id.span, - Span { - offset: 11, - length: 9 - } - ); + assert_eq!(*span, Span::new(10, 10)); + assert_eq!(id.span, Span::new(11, 9)); } } @@ -2337,7 +2293,7 @@ fn parse_collecting_errors_basic() { assert!(errors.len() > 0); assert!(errors .iter() - .any(|e| matches!(e, ParsingError::InvalidHeader(_, _)))); + .any(|e| matches!(e, ParsingError::InvalidHeader(_)))); } } @@ -2434,7 +2390,7 @@ fn test_redundant_error_removal_unclosed_interpolation() { // Should get the specific UnclosedInterpolation error, not a generic // one match result { - Err(ParsingError::UnclosedInterpolation(_, _)) => { + Err(ParsingError::UnclosedInterpolation(_)) => { // Good - we got the specific error } Err(other) => { diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index 8b0f4a01..fb2f921d 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -26,115 +26,88 @@ pub fn parse_with_recovery<'i>( #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ParsingError { // lowest priority - IllegalParserState(usize, usize), // offset, width (0 = unknown) - Unimplemented(usize, usize), - Unrecognized(usize, usize), // improve this - UnexpectedEndOfInput(usize, usize), - Expected(usize, usize, &'static str), // offset, width, expected - ExpectedMatchingChar(usize, usize, &'static str, char, char), - MissingParenthesis(usize, usize), + IllegalParserState(Span), + Unimplemented(Span), + Unrecognized(Span), + UnexpectedEndOfInput(Span), + Expected(Span, &'static str), + ExpectedMatchingChar(Span, &'static str, char, char), + MissingParenthesis(Span), // more specific errors - InvalidCharacter(usize, usize, char), - InvalidHeader(usize, usize), - InvalidIdentifier(usize, usize, String), - InvalidForma(usize, usize), - InvalidGenus(usize, usize), - InvalidSignature(usize, usize), - InvalidParameters(usize, usize), - InvalidDeclaration(usize, usize), - InvalidSection(usize, usize), - InvalidInvocation(usize, usize), - InvalidFunction(usize, usize), - InvalidCodeBlock(usize, usize), - InvalidStep(usize, usize), - InvalidSubstep(usize, usize), - InvalidAttribute(usize, usize), - InvalidResponse(usize, usize), - InvalidMultiline(usize, usize), - InvalidForeach(usize, usize), - InvalidIntegral(usize, usize), - InvalidQuantity(usize, usize), - InvalidQuantityDecimal(usize, usize), - InvalidQuantityUncertainty(usize, usize), - InvalidQuantityMagnitude(usize, usize), - InvalidQuantitySymbol(usize, usize), + InvalidCharacter(Span, char), + InvalidHeader(Span), + InvalidIdentifier(Span, String), + InvalidForma(Span), + InvalidGenus(Span), + InvalidSignature(Span), + InvalidParameters(Span), + InvalidDeclaration(Span), + InvalidSection(Span), + InvalidInvocation(Span), + InvalidFunction(Span), + InvalidCodeBlock(Span), + InvalidStep(Span), + InvalidSubstep(Span), + InvalidAttribute(Span), + InvalidResponse(Span), + InvalidMultiline(Span), + InvalidForeach(Span), + InvalidIntegral(Span), + InvalidQuantity(Span), + InvalidQuantityDecimal(Span), + InvalidQuantityUncertainty(Span), + InvalidQuantityMagnitude(Span), + InvalidQuantitySymbol(Span), // highest priority - UnclosedInterpolation(usize, usize), + UnclosedInterpolation(Span), } impl ParsingError { - pub fn offset(&self) -> usize { + pub fn span(&self) -> Span { match self { - ParsingError::IllegalParserState(offset, _) => *offset, - ParsingError::Unimplemented(offset, _) => *offset, - ParsingError::Unrecognized(offset, _) => *offset, - ParsingError::Expected(offset, _, _) => *offset, - ParsingError::ExpectedMatchingChar(offset, _, _, _, _) => *offset, - ParsingError::MissingParenthesis(offset, _) => *offset, - ParsingError::UnclosedInterpolation(offset, _) => *offset, - ParsingError::InvalidHeader(offset, _) => *offset, - ParsingError::InvalidCharacter(offset, _, _) => *offset, - ParsingError::UnexpectedEndOfInput(offset, _) => *offset, - ParsingError::InvalidIdentifier(offset, _, _) => *offset, - ParsingError::InvalidForma(offset, _) => *offset, - ParsingError::InvalidGenus(offset, _) => *offset, - ParsingError::InvalidSignature(offset, _) => *offset, - ParsingError::InvalidDeclaration(offset, _) => *offset, - ParsingError::InvalidParameters(offset, _) => *offset, - ParsingError::InvalidSection(offset, _) => *offset, - ParsingError::InvalidInvocation(offset, _) => *offset, - ParsingError::InvalidFunction(offset, _) => *offset, - ParsingError::InvalidCodeBlock(offset, _) => *offset, - ParsingError::InvalidMultiline(offset, _) => *offset, - ParsingError::InvalidStep(offset, _) => *offset, - ParsingError::InvalidSubstep(offset, _) => *offset, - ParsingError::InvalidAttribute(offset, _) => *offset, - ParsingError::InvalidForeach(offset, _) => *offset, - ParsingError::InvalidResponse(offset, _) => *offset, - ParsingError::InvalidIntegral(offset, _) => *offset, - ParsingError::InvalidQuantity(offset, _) => *offset, - ParsingError::InvalidQuantityDecimal(offset, _) => *offset, - ParsingError::InvalidQuantityUncertainty(offset, _) => *offset, - ParsingError::InvalidQuantityMagnitude(offset, _) => *offset, - ParsingError::InvalidQuantitySymbol(offset, _) => *offset, + ParsingError::IllegalParserState(span) + | ParsingError::Unimplemented(span) + | ParsingError::Unrecognized(span) + | ParsingError::UnexpectedEndOfInput(span) + | ParsingError::MissingParenthesis(span) + | ParsingError::InvalidHeader(span) + | ParsingError::InvalidForma(span) + | ParsingError::InvalidGenus(span) + | ParsingError::InvalidSignature(span) + | ParsingError::InvalidParameters(span) + | ParsingError::InvalidDeclaration(span) + | ParsingError::InvalidSection(span) + | ParsingError::InvalidInvocation(span) + | ParsingError::InvalidFunction(span) + | ParsingError::InvalidCodeBlock(span) + | ParsingError::InvalidStep(span) + | ParsingError::InvalidSubstep(span) + | ParsingError::InvalidAttribute(span) + | ParsingError::InvalidResponse(span) + | ParsingError::InvalidMultiline(span) + | ParsingError::InvalidForeach(span) + | ParsingError::InvalidIntegral(span) + | ParsingError::InvalidQuantity(span) + | ParsingError::InvalidQuantityDecimal(span) + | ParsingError::InvalidQuantityUncertainty(span) + | ParsingError::InvalidQuantityMagnitude(span) + | ParsingError::InvalidQuantitySymbol(span) + | ParsingError::UnclosedInterpolation(span) => *span, + ParsingError::Expected(span, _) + | ParsingError::ExpectedMatchingChar(span, _, _, _) + | ParsingError::InvalidCharacter(span, _) + | ParsingError::InvalidIdentifier(span, _) => *span, } } + pub fn offset(&self) -> usize { + self.span() + .offset + } + pub fn width(&self) -> usize { - match self { - ParsingError::IllegalParserState(_, width) => *width, - ParsingError::Unimplemented(_, width) => *width, - ParsingError::Unrecognized(_, width) => *width, - ParsingError::Expected(_, width, _) => *width, - ParsingError::ExpectedMatchingChar(_, width, _, _, _) => *width, - ParsingError::MissingParenthesis(_, width) => *width, - ParsingError::UnclosedInterpolation(_, width) => *width, - ParsingError::InvalidHeader(_, width) => *width, - ParsingError::InvalidCharacter(_, width, _) => *width, - ParsingError::UnexpectedEndOfInput(_, width) => *width, - ParsingError::InvalidIdentifier(_, width, _) => *width, - ParsingError::InvalidForma(_, width) => *width, - ParsingError::InvalidGenus(_, width) => *width, - ParsingError::InvalidSignature(_, width) => *width, - ParsingError::InvalidDeclaration(_, width) => *width, - ParsingError::InvalidParameters(_, width) => *width, - ParsingError::InvalidSection(_, width) => *width, - ParsingError::InvalidInvocation(_, width) => *width, - ParsingError::InvalidFunction(_, width) => *width, - ParsingError::InvalidCodeBlock(_, width) => *width, - ParsingError::InvalidMultiline(_, width) => *width, - ParsingError::InvalidStep(_, width) => *width, - ParsingError::InvalidSubstep(_, width) => *width, - ParsingError::InvalidAttribute(_, width) => *width, - ParsingError::InvalidForeach(_, width) => *width, - ParsingError::InvalidResponse(_, width) => *width, - ParsingError::InvalidIntegral(_, width) => *width, - ParsingError::InvalidQuantity(_, width) => *width, - ParsingError::InvalidQuantityDecimal(_, width) => *width, - ParsingError::InvalidQuantityUncertainty(_, width) => *width, - ParsingError::InvalidQuantityMagnitude(_, width) => *width, - ParsingError::InvalidQuantitySymbol(_, width) => *width, - } + self.span() + .length } } @@ -289,7 +262,7 @@ impl<'i> Parser<'i> { } } else { self.problems - .push(ParsingError::Unrecognized(self.offset, 0)); + .push(ParsingError::Unrecognized(Span::new(self.offset, 0))); self.skip_to_next_line(); } } @@ -364,7 +337,7 @@ impl<'i> Parser<'i> { } } else { self.problems - .push(ParsingError::Unrecognized(self.offset, 0)); + .push(ParsingError::Unrecognized(Span::new(self.offset, 0))); self.skip_to_next_line(); } } @@ -535,15 +508,13 @@ impl<'i> Parser<'i> { if !begun { return Err(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "the start character", )); } if l == 0 { return Err(ParsingError::ExpectedMatchingChar( - self.offset, - 0, + Span::new(self.offset, 0), subject, start_char, end_char, @@ -582,8 +553,7 @@ impl<'i> Parser<'i> { .source .find(delimiter) .ok_or(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "a starting delimiter", ))?; @@ -592,8 +562,7 @@ impl<'i> Parser<'i> { let end = self.source[start..] .find(delimiter) .ok_or(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "the corresponding end delimiter", ))?; @@ -655,8 +624,7 @@ impl<'i> Parser<'i> { let trimmed = chunk.trim_ascii(); if trimmed.is_empty() { return Err(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "non-empty content between delimiters", )); } @@ -726,17 +694,11 @@ impl<'i> Parser<'i> { - (self .source .as_ptr() as usize); - Span { - offset: self.offset + inner, - length: slice.len(), - } + Span::new(self.offset + inner, slice.len()) } fn span_since(&self, start: usize) -> Span { - Span { - offset: start, - length: self.offset - start, - } + Span::new(start, self.offset - start) } // because test cases and trivial single-line examples might omit an @@ -755,7 +717,7 @@ impl<'i> Parser<'i> { } else if c.is_ascii_whitespace() { continue; } else { - return Err(ParsingError::InvalidCharacter(self.offset, 0, c)); + return Err(ParsingError::InvalidCharacter(Span::new(self.offset, 0), c)); } } @@ -779,7 +741,10 @@ impl<'i> Parser<'i> { Ok(1) } else { let error_offset = analyze_magic_line(inner.source); - Err(ParsingError::InvalidHeader(inner.offset + error_offset, 0)) + Err(ParsingError::InvalidHeader(Span::new( + inner.offset + error_offset, + 0, + ))) } }) } @@ -792,7 +757,7 @@ impl<'i> Parser<'i> { let cap = re .captures(inner.source) - .ok_or(ParsingError::InvalidHeader(inner.offset, 0))?; + .ok_or(ParsingError::InvalidHeader(Span::new(inner.offset, 0)))?; // Now to extracting the values we need. We get the license code from // the first capture. It must be present otherwise we don't have a @@ -801,10 +766,13 @@ impl<'i> Parser<'i> { let one = cap .get(1) - .ok_or(ParsingError::Expected(inner.offset, 0, "the license name"))?; + .ok_or(ParsingError::Expected( + Span::new(inner.offset, 0), + "the license name", + ))?; let result = validate_license(one.as_str()) - .ok_or(ParsingError::InvalidHeader(inner.offset, 0))?; + .ok_or(ParsingError::InvalidHeader(Span::new(inner.offset, 0)))?; let license = Some(result); // Now dig out the copyright, if present: @@ -812,7 +780,7 @@ impl<'i> Parser<'i> { let copyright = match cap.get(2) { Some(two) => { let result = validate_copyright(two.as_str()) - .ok_or(ParsingError::InvalidHeader(inner.offset, 0))?; + .ok_or(ParsingError::InvalidHeader(Span::new(inner.offset, 0)))?; Some(result) } None => None, @@ -828,14 +796,17 @@ impl<'i> Parser<'i> { let cap = re .captures(inner.source) - .ok_or(ParsingError::InvalidHeader(inner.offset, 0))?; + .ok_or(ParsingError::InvalidHeader(Span::new(inner.offset, 0)))?; let one = cap .get(1) - .ok_or(ParsingError::Expected(inner.offset, 0, "a domain name"))?; + .ok_or(ParsingError::Expected( + Span::new(inner.offset, 0), + "a domain name", + ))?; let result = validate_domain(one.as_str()) - .ok_or(ParsingError::InvalidHeader(inner.offset, 0))?; + .ok_or(ParsingError::InvalidHeader(Span::new(inner.offset, 0)))?; Ok(Some(result)) }) } @@ -849,7 +820,7 @@ impl<'i> Parser<'i> { self.require_newline()?; result } else { - Err(ParsingError::Expected(0, 0, "The % symbol"))? + Err(ParsingError::Expected(Span::new(0, 0), "The % symbol"))? }; // Process SPDX line @@ -886,41 +857,35 @@ impl<'i> Parser<'i> { Some(c) => c, None => { let arrow_offset = analyze_malformed_signature(self.source); - return Err(ParsingError::InvalidSignature( + return Err(ParsingError::InvalidSignature(Span::new( self.offset + arrow_offset, 0, - )); + ))); } }; let one = cap .get(1) .ok_or(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "a Genus for the requires", ))?; let two = cap .get(2) .ok_or(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "a Genus for the provides", ))?; - let one_span = Span { - offset: self.offset + one.start(), - length: one.len(), - }; - let two_span = Span { - offset: self.offset + two.start(), - length: two.len(), - }; - let requires = validate_genus(one.as_str(), one_span) - .ok_or(ParsingError::InvalidGenus(one_span.offset, one_span.length))?; - let provides = validate_genus(two.as_str(), two_span) - .ok_or(ParsingError::InvalidGenus(two_span.offset, two_span.length))?; + let one_span = Span::new(self.offset + one.start(), one.len()); + let two_span = Span::new(self.offset + two.start(), two.len()); + let requires = validate_genus(one.as_str(), one_span).ok_or(ParsingError::InvalidGenus( + Span::new(one_span.offset, one_span.length), + ))?; + let provides = validate_genus(two.as_str(), two_span).ok_or(ParsingError::InvalidGenus( + Span::new(two_span.offset, two_span.length), + ))?; Ok(Signature { requires, provides }) } @@ -942,13 +907,12 @@ impl<'i> Parser<'i> { let cap = re .captures(self.source) - .ok_or(ParsingError::InvalidDeclaration(self.offset, 0))?; + .ok_or(ParsingError::InvalidDeclaration(Span::new(self.offset, 0)))?; let one = cap .get(1) .ok_or(ParsingError::Expected( - self.offset, - 0, + Span::new(self.offset, 0), "an Identifier for the procedure declaration", ))?; @@ -956,12 +920,15 @@ impl<'i> Parser<'i> { let (name, parameters) = if let Some((before, list)) = text.split_once('(') { let before = before.trim(); let name = validate_identifier(before, self.span_of(before)).ok_or( - ParsingError::InvalidIdentifier(self.offset, before.len(), before.to_string()), + ParsingError::InvalidIdentifier( + Span::new(self.offset, before.len()), + before.to_string(), + ), )?; // Extract parameters from parentheses if !list.ends_with(')') { - return Err(ParsingError::InvalidDeclaration(self.offset, 0)); + return Err(ParsingError::InvalidDeclaration(Span::new(self.offset, 0))); } let list = &list[..list.len() - 1].trim_ascii(); @@ -973,8 +940,7 @@ impl<'i> Parser<'i> { let trimmed = item.trim_ascii(); let param = validate_identifier(trimmed, self.span_of(trimmed)).ok_or( ParsingError::InvalidIdentifier( - self.offset, - trimmed.len(), + Span::new(self.offset, trimmed.len()), trimmed.to_string(), ), )?; @@ -1002,11 +968,17 @@ impl<'i> Parser<'i> { - text.as_ptr() as isize; let error_offset = self.offset + one.start() + first_param_pos as usize; let param_width = text.len() - first_param_pos as usize; - return Err(ParsingError::InvalidParameters(error_offset, param_width)); + return Err(ParsingError::InvalidParameters(Span::new( + error_offset, + param_width, + ))); } let name = validate_identifier(text, self.span_of(text)).ok_or( - ParsingError::InvalidIdentifier(self.offset, text.len(), text.to_string()), + ParsingError::InvalidIdentifier( + Span::new(self.offset, text.len()), + text.to_string(), + ), )?; (name, None) }; @@ -1032,7 +1004,7 @@ impl<'i> Parser<'i> { Ok(title) } else { // we shouldn't have invoked this unless we have a title to parse! - Err(ParsingError::IllegalParserState(self.offset, 0)) + Err(ParsingError::IllegalParserState(Span::new(self.offset, 0))) } } @@ -1155,7 +1127,10 @@ impl<'i> Parser<'i> { } } else { self.problems - .push(ParsingError::InvalidAttribute(parser.offset, content.len())); + .push(ParsingError::InvalidAttribute(Span::new( + parser.offset, + content.len(), + ))); parser.skip_to_next_line(); } } else if is_step(content) { @@ -1193,7 +1168,7 @@ impl<'i> Parser<'i> { } else if malformed_step_pattern(content) { // Store error but continue parsing self.problems - .push(ParsingError::InvalidStep(parser.offset, 0)); + .push(ParsingError::InvalidStep(Span::new(parser.offset, 0))); parser.skip_to_next_line(); } else { match parser.take_block_lines( @@ -1396,11 +1371,16 @@ impl<'i> Parser<'i> { let re = regex!(r"^\s*([IVX]+)\.\s*(.*)$"); let cap = re .captures(line) - .ok_or(ParsingError::InvalidSection(self.offset, 0))?; + .ok_or(ParsingError::InvalidSection(Span::new(self.offset, 0)))?; let numeral = match cap.get(1) { Some(one) => one.as_str(), - None => return Err(ParsingError::Expected(self.offset, 0, "section header")), + None => { + return Err(ParsingError::Expected( + Span::new(self.offset, 0), + "section header", + )) + } }; // Though section text appear as titles, they are in fact steps and so @@ -1419,7 +1399,7 @@ impl<'i> Parser<'i> { let paragraphs = parser.read_descriptive()?; if paragraphs.len() != 1 { - return Err(ParsingError::InvalidSection(self.offset, 0)); + return Err(ParsingError::InvalidSection(Span::new(self.offset, 0))); } let paragraph = paragraphs .into_iter() @@ -1469,12 +1449,12 @@ impl<'i> Parser<'i> { .is_empty() { let width = inner.offset - start; - return Err(ParsingError::InvalidCodeBlock(start, width)); + return Err(ParsingError::InvalidCodeBlock(Span::new(start, width))); } } if expressions.is_empty() { - return Err(ParsingError::InvalidCodeBlock(inner.offset, 0)); + return Err(ParsingError::InvalidCodeBlock(Span::new(inner.offset, 0))); } Ok(expressions) @@ -1498,14 +1478,14 @@ impl<'i> Parser<'i> { self.advance(tilde_pos + 1); // Move past ~ self.trim_whitespace(); } - return Err(ParsingError::MissingParenthesis(self.offset, 0)); + return Err(ParsingError::MissingParenthesis(Span::new(self.offset, 0))); } else if is_repeat_keyword(content) { self.read_repeat_expression() } else if is_foreach_keyword(content) { self.read_foreach_expression() } else if content.starts_with("foreach ") { // Malformed foreach expression - return Err(ParsingError::InvalidForeach(self.offset, 0)); + return Err(ParsingError::InvalidForeach(Span::new(self.offset, 0))); } else if content.starts_with('[') { self.read_tablet_expression() } else if is_numeric(content) { @@ -1540,9 +1520,15 @@ impl<'i> Parser<'i> { } else { content.len() }; - return Err(ParsingError::InvalidCodeBlock(self.offset, width)); + return Err(ParsingError::InvalidCodeBlock(Span::new( + self.offset, + width, + ))); } else { - return Err(ParsingError::InvalidFunction(self.offset, text.len())); + return Err(ParsingError::InvalidFunction(Span::new( + self.offset, + text.len(), + ))); } } @@ -1560,14 +1546,7 @@ impl<'i> Parser<'i> { .source .starts_with('"') { - return Err(ParsingError::InvalidFunction( - identifier - .span - .offset, - identifier - .span - .length, - )); + return Err(ParsingError::InvalidFunction(identifier.span)); } let span = identifier.span; Ok(Expression::Variable(identifier, span)) @@ -1596,7 +1575,7 @@ impl<'i> Parser<'i> { .unwrap() .is_ascii_whitespace() { - return Err(ParsingError::InvalidForeach(self.offset, 0)); + return Err(ParsingError::InvalidForeach(Span::new(self.offset, 0))); } self.trim_whitespace(); @@ -1641,7 +1620,7 @@ impl<'i> Parser<'i> { } if identifiers.is_empty() { - return Err(ParsingError::InvalidForeach(outer.offset, 0)); + return Err(ParsingError::InvalidForeach(Span::new(outer.offset, 0))); } Ok(identifiers) @@ -1688,7 +1667,7 @@ impl<'i> Parser<'i> { + inner .source .len(); - Err(ParsingError::InvalidCodeBlock(start_pos, width)) + Err(ParsingError::InvalidCodeBlock(Span::new(start_pos, width))) } })?; @@ -1723,8 +1702,7 @@ impl<'i> Parser<'i> { .starts_with('"') { return Err(ParsingError::Expected( - outer.offset, - 0, + Span::new(outer.offset, 0), "a string label for the field, in double-quotes", )); } @@ -1739,8 +1717,7 @@ impl<'i> Parser<'i> { .starts_with('=') { return Err(ParsingError::Expected( - outer.offset, - 0, + Span::new(outer.offset, 0), "a '=' after the field name to indicate what value is to be assigned to it", )); } @@ -1753,7 +1730,10 @@ impl<'i> Parser<'i> { let content = inner.source; if content.is_empty() { - return Err(ParsingError::Expected(inner.offset, 0, "value expression")); + return Err(ParsingError::Expected( + Span::new(inner.offset, 0), + "value expression", + )); }; inner.read_expression() @@ -1820,10 +1800,10 @@ impl<'i> Parser<'i> { } None => { // Unmatched brace - point to the opening brace position - return Err(ParsingError::UnclosedInterpolation( + return Err(ParsingError::UnclosedInterpolation(Span::new( self.offset + absolute_brace_start, 0, - )); + ))); } } } else { @@ -1852,7 +1832,10 @@ impl<'i> Parser<'i> { }; let identifier = validate_identifier(possible, self.span_of(possible)).ok_or( - ParsingError::InvalidIdentifier(self.offset, possible.len(), possible.to_string()), + ParsingError::InvalidIdentifier( + Span::new(self.offset, possible.len()), + possible.to_string(), + ), )?; self.advance(possible.len()); @@ -1871,7 +1854,7 @@ impl<'i> Parser<'i> { } else if is_numeric_quantity(content) { self.read_numeric_quantity() } else { - Err(ParsingError::InvalidQuantity(self.offset, 0)) + Err(ParsingError::InvalidQuantity(Span::new(self.offset, 0))) } } @@ -1886,7 +1869,7 @@ impl<'i> Parser<'i> { self.advance(content.len()); Ok(Numeric::Integral(amount)) } else { - Err(ParsingError::InvalidIntegral(self.offset, 0)) + Err(ParsingError::InvalidIntegral(Span::new(self.offset, 0))) } } @@ -1945,7 +1928,10 @@ impl<'i> Parser<'i> { .source .starts_with("10") { - return Err(ParsingError::InvalidQuantityMagnitude(self.offset, 0)); + return Err(ParsingError::InvalidQuantityMagnitude(Span::new( + self.offset, + 0, + ))); } self.advance(2); // Skip "10" @@ -1958,7 +1944,10 @@ impl<'i> Parser<'i> { } else if let Some(exp) = self.read_exponent_superscript() { Some(exp) } else { - return Err(ParsingError::InvalidQuantityMagnitude(self.offset, 0)); + return Err(ParsingError::InvalidQuantityMagnitude(Span::new( + self.offset, + 0, + ))); } } else { None @@ -1988,10 +1977,16 @@ impl<'i> Parser<'i> { self.advance(decimal_str.len()); Ok(decimal) } else { - Err(ParsingError::InvalidQuantityDecimal(self.offset, 0)) + Err(ParsingError::InvalidQuantityDecimal(Span::new( + self.offset, + 0, + ))) } } else { - Err(ParsingError::InvalidQuantityDecimal(self.offset, 0)) + Err(ParsingError::InvalidQuantityDecimal(Span::new( + self.offset, + 0, + ))) } } @@ -2005,10 +2000,16 @@ impl<'i> Parser<'i> { self.advance(decimal_str.len()); Ok(decimal) } else { - Err(ParsingError::InvalidQuantityUncertainty(self.offset, 0)) + Err(ParsingError::InvalidQuantityUncertainty(Span::new( + self.offset, + 0, + ))) } } else { - Err(ParsingError::InvalidQuantityUncertainty(self.offset, 0)) + Err(ParsingError::InvalidQuantityUncertainty(Span::new( + self.offset, + 0, + ))) } } @@ -2022,10 +2023,16 @@ impl<'i> Parser<'i> { self.advance(exp_str.len()); Ok(exp) } else { - Err(ParsingError::InvalidQuantityMagnitude(self.offset, 0)) + Err(ParsingError::InvalidQuantityMagnitude(Span::new( + self.offset, + 0, + ))) } } else { - Err(ParsingError::InvalidQuantityMagnitude(self.offset, 0)) + Err(ParsingError::InvalidQuantityMagnitude(Span::new( + self.offset, + 0, + ))) } } @@ -2068,15 +2075,18 @@ impl<'i> Parser<'i> { valid_end = byte_offset + ch.len_utf8(); } else { // Invalid character found - point directly at it - return Err(ParsingError::InvalidQuantitySymbol( + return Err(ParsingError::InvalidQuantitySymbol(Span::new( self.offset + byte_offset, ch.len_utf8(), - )); + ))); } } if valid_end == 0 { - return Err(ParsingError::InvalidQuantitySymbol(self.offset, 1)); + return Err(ParsingError::InvalidQuantitySymbol(Span::new( + self.offset, + 1, + ))); } let symbol = &self.source[..valid_end]; @@ -2099,7 +2109,7 @@ impl<'i> Parser<'i> { } else { let identifier = validate_identifier(content, inner.span_of(content)).ok_or_else(|| { - ParsingError::InvalidInvocation(start_offset + 1, content.len()) + ParsingError::InvalidInvocation(Span::new(start_offset + 1, content.len())) })?; Ok(Target::Local(identifier)) } @@ -2127,13 +2137,12 @@ impl<'i> Parser<'i> { let re = regex!(r"^\s*(\d+)\.\s+"); let cap = re .captures(outer.source) - .ok_or(ParsingError::InvalidStep(outer.offset, 0))?; + .ok_or(ParsingError::InvalidStep(Span::new(outer.offset, 0)))?; let number = cap .get(1) .ok_or(ParsingError::Expected( - outer.offset, - 0, + Span::new(outer.offset, 0), "the ordinal Step number", ))? .as_str(); @@ -2170,7 +2179,7 @@ impl<'i> Parser<'i> { .source .starts_with('-') { - return Err(ParsingError::IllegalParserState(outer.offset, 0)); + return Err(ParsingError::IllegalParserState(Span::new(outer.offset, 0))); } outer.advance(1); // skip over '-' outer.trim_whitespace(); @@ -2200,13 +2209,12 @@ impl<'i> Parser<'i> { let re = regex!(r"^\s*([a-hj-uwy])\.\s+"); let cap = re .captures(content) - .ok_or(ParsingError::InvalidStep(outer.offset, 0))?; + .ok_or(ParsingError::InvalidStep(Span::new(outer.offset, 0)))?; let letter = cap .get(1) .ok_or(ParsingError::Expected( - outer.offset, - 0, + Span::new(outer.offset, 0), "the ordinal Sub-Step letter", ))? .as_str(); @@ -2245,7 +2253,7 @@ impl<'i> Parser<'i> { let re = regex!(r"^\s*-\s+"); let zero = re .find(outer.source) - .ok_or(ParsingError::InvalidStep(outer.offset, 0))?; + .ok_or(ParsingError::InvalidStep(Span::new(outer.offset, 0)))?; // Skip past the dash and space let l = zero.len(); @@ -2279,13 +2287,12 @@ impl<'i> Parser<'i> { let re = regex!(r"^\s*([ivx]+)\.\s+"); let cap = re .captures(content) - .ok_or(ParsingError::InvalidStep(outer.offset, 0))?; + .ok_or(ParsingError::InvalidStep(Span::new(outer.offset, 0)))?; let numeral = cap .get(1) .ok_or(ParsingError::Expected( - outer.offset, - 0, + Span::new(outer.offset, 0), "the ordinal roman numeral indicating a sub-substep", ))? .as_str(); @@ -2372,7 +2379,10 @@ impl<'i> Parser<'i> { .starts_with("```") { // Multiline blocks are not allowed in descriptive text - return Err(ParsingError::InvalidMultiline(parser.offset, 0)); + return Err(ParsingError::InvalidMultiline(Span::new( + parser.offset, + 0, + ))); } else if c == '<' { let invocation = parser.read_invocation()?; parser.trim_whitespace(); @@ -2389,7 +2399,7 @@ impl<'i> Parser<'i> { .starts_with(',') { return Err(ParsingError::MissingParenthesis( - start_pos, 0, + Span::new(start_pos, 0), )); } @@ -2409,8 +2419,7 @@ impl<'i> Parser<'i> { // Check for invalid multiline patterns in text if content.contains("```") { return Err(ParsingError::InvalidMultiline( - inner.offset, - 0, + Span::new(inner.offset, 0), )); } Ok(content) @@ -2429,7 +2438,7 @@ impl<'i> Parser<'i> { .starts_with(',') { return Err(ParsingError::MissingParenthesis( - start_pos, 0, + Span::new(start_pos, 0), )); } @@ -2460,7 +2469,8 @@ impl<'i> Parser<'i> { /// Parse enum responses like 'Yes' | 'No' | 'Not Applicable' fn read_responses(&mut self) -> Result>, ParsingError> { self.take_split_by('|', |inner| { - validate_response(inner.source).ok_or(ParsingError::InvalidResponse(inner.offset, 0)) + validate_response(inner.source) + .ok_or(ParsingError::InvalidResponse(Span::new(inner.offset, 0))) }) } @@ -2503,7 +2513,7 @@ impl<'i> Parser<'i> { .trim_ascii() .is_empty() { - return Err(ParsingError::InvalidMultiline(self.offset, 0)); + return Err(ParsingError::InvalidMultiline(Span::new(self.offset, 0))); } result.push(after) @@ -2548,11 +2558,9 @@ impl<'i> Parser<'i> { let (lang, lines) = outer .take_block_delimited("```", |inner| inner.parse_multiline_content()) .map_err(|err| match err { - ParsingError::Expected( - offset, - _, - "the corresponding end delimiter", - ) => ParsingError::InvalidMultiline(offset, 0), + ParsingError::Expected(span, "the corresponding end delimiter") => { + ParsingError::InvalidMultiline(Span::new(span.offset, 0)) + } _ => err, })?; let span = outer.span_since(param_start); @@ -2660,12 +2668,14 @@ impl<'i> Parser<'i> { else if let Some(captures) = regex!(r"^@([a-z][a-z0-9_]*)$").captures(trimmed) { let role_name = captures .get(1) - .ok_or(ParsingError::Expected(inner.offset, 0, "role name after @"))? + .ok_or(ParsingError::Expected( + Span::new(inner.offset, 0), + "role name after @", + ))? .as_str(); let identifier = validate_identifier(role_name, inner.span_of(role_name)) .ok_or(ParsingError::InvalidIdentifier( - inner.offset, - role_name.len(), + Span::new(inner.offset, role_name.len()), role_name.to_string(), ))?; attributes.push(Attribute::Role(identifier, inner.span_of(trimmed))); @@ -2675,15 +2685,13 @@ impl<'i> Parser<'i> { let place_name = captures .get(1) .ok_or(ParsingError::Expected( - inner.offset, - 0, + Span::new(inner.offset, 0), "place name after ^", ))? .as_str(); let identifier = validate_identifier(place_name, inner.span_of(place_name)) .ok_or(ParsingError::InvalidIdentifier( - inner.offset, - place_name.len(), + Span::new(inner.offset, place_name.len()), place_name.to_string(), ))?; attributes.push(Attribute::Place(identifier, inner.span_of(trimmed))); @@ -2691,9 +2699,12 @@ impl<'i> Parser<'i> { // Check if this looks like a malformed attribute (starts with @ or ^) if is_attribute_pattern(trimmed) { // This might be multiple attributes without proper + joiners - return Err(ParsingError::InvalidAttribute(inner.offset, line.len())); + return Err(ParsingError::InvalidAttribute(Span::new( + inner.offset, + line.len(), + ))); } else { - return Err(ParsingError::InvalidStep(inner.offset, 0)); + return Err(ParsingError::InvalidStep(Span::new(inner.offset, 0))); } } } @@ -2720,7 +2731,10 @@ impl<'i> Parser<'i> { let block = self.read_attribute_scope()?; scopes.push(block); } else { - return Err(ParsingError::InvalidAttribute(self.offset, content.len())); + return Err(ParsingError::InvalidAttribute(Span::new( + self.offset, + content.len(), + ))); } } else if is_substep_dependent(content) { let block = self.read_substep_dependent()?; @@ -2741,9 +2755,9 @@ impl<'i> Parser<'i> { let block = self.read_code_scope()?; scopes.push(block); } else if malformed_step_pattern(content) { - return Err(ParsingError::InvalidSubstep(self.offset, 0)); + return Err(ParsingError::InvalidSubstep(Span::new(self.offset, 0))); } else if malformed_response_pattern(content) { - return Err(ParsingError::InvalidResponse(self.offset, 0)); + return Err(ParsingError::InvalidResponse(Span::new(self.offset, 0))); } else if is_enum_response(content) { let responses_start = self.offset; let responses = self.read_responses()?; diff --git a/src/problem/messages.rs b/src/problem/messages.rs index 8a11a2f0..a1a4a8c0 100644 --- a/src/problem/messages.rs +++ b/src/problem/messages.rs @@ -4,26 +4,26 @@ use technique::{formatting::Render, language::*, parsing::ParsingError}; /// Generate problem and detail messages for parsing errors using AST construction pub fn generate_error_message<'i>(error: &ParsingError, renderer: &dyn Render) -> (String, String) { match error { - ParsingError::IllegalParserState(_, _) => ( + ParsingError::IllegalParserState(_) => ( "Illegal parser state".to_string(), "Internal parser error. This should not have happened! Sorry.".to_string(), ), - ParsingError::Unimplemented(_, _) => ( + ParsingError::Unimplemented(_) => ( "Feature not yet implemented".to_string(), "This feature is planned but not yet available.".to_string(), ), - ParsingError::Unrecognized(_, _) => ( + ParsingError::Unrecognized(_) => ( "Unrecognized input".to_string(), "The parser encountered unexpected content".to_string(), ), - ParsingError::Expected(_, _, value) => ( + ParsingError::Expected(_, value) => ( format!("Expected {}", value), format!( "The parser was looking for {} but found something else.", value ), ), - ParsingError::ExpectedMatchingChar(_, _, subject, start, end) => ( + ParsingError::ExpectedMatchingChar(_, subject, start, end) => ( format!("Expected matching character '{}'", end), format!( r#" @@ -35,7 +35,7 @@ there was no more input remaining in the current scope. .trim_ascii() .to_string(), ), - ParsingError::MissingParenthesis(_, _) => { + ParsingError::MissingParenthesis(_) => { let examples = vec![Descriptive::Binding( Box::new(Descriptive::Application(Invocation { target: Target::Local(Identifier::new("mix_pangalactic_gargle_blaster")), @@ -59,7 +59,7 @@ enclose those names in parenthesis. For example: .to_string(), ) } - ParsingError::UnclosedInterpolation(_, _) => ( + ParsingError::UnclosedInterpolation(_) => ( "Unclosed string interpolation".to_string(), r#" Every '{' that starts an interpolation within a string must have a @@ -69,7 +69,7 @@ literal resumes. .trim_ascii() .to_string(), ), - ParsingError::InvalidHeader(_, _) => { + ParsingError::InvalidHeader(_) => { // Format the sample metadata using the same code as the formatter let mut formatted_example = String::new(); formatted_example @@ -115,15 +115,15 @@ to be used when rendering the Technique. Common domains include ), ) } - ParsingError::InvalidCharacter(_, _, c) => ( + ParsingError::InvalidCharacter(_, c) => ( format!("Invalid character '{}'", c), "This character is not allowed here.".to_string(), ), - ParsingError::UnexpectedEndOfInput(_, _) => ( + ParsingError::UnexpectedEndOfInput(_) => ( "Unexpected end of input".to_string(), "The file ended before the parser expected it to".to_string(), ), - ParsingError::InvalidIdentifier(_, _, _) => { + ParsingError::InvalidIdentifier(_, _) => { let examples = vec![ Procedure { name: Identifier::new("make_coffee"), @@ -176,7 +176,7 @@ letters, numbers, and underscores. Valid examples include: .to_string(), ) } - ParsingError::InvalidForma(_, _) => { + ParsingError::InvalidForma(_) => { let examples = vec![ Forma::new("Coffee"), Forma::new("Ingredients"), @@ -202,7 +202,7 @@ For example: .to_string(), ) } - ParsingError::InvalidGenus(_, _) => { + ParsingError::InvalidGenus(_) => { let examples = vec![ Genus::Single(Forma::new("Coffee")), Genus::Tuple(vec![Forma::new("Beans"), Forma::new("Water")]), @@ -238,7 +238,7 @@ doesn't have an input or result, per se. .to_string(), ) } - ParsingError::InvalidSignature(_, _) => { + ParsingError::InvalidSignature(_) => { let examples = vec![ Signature { requires: Genus::Single(Forma::new("A")), @@ -276,7 +276,7 @@ this form. .to_string(), ) } - ParsingError::InvalidDeclaration(_, _) => { + ParsingError::InvalidDeclaration(_) => { let examples = vec![ Procedure { name: Identifier::new("f"), @@ -386,7 +386,7 @@ Finally, variables can be assigned for the names of the input parameters: .to_string(), ) } - ParsingError::InvalidParameters(_, _) => { + ParsingError::InvalidParameters(_) => { let examples = vec![ Procedure { name: Identifier::new("create_bypass"), @@ -449,7 +449,7 @@ declarations (and in fact the same): .to_string(), ) } - ParsingError::InvalidSection(_, _) => { + ParsingError::InvalidSection(_) => { // Roman numeral sections don't have AST representation ( "Invalid section heading".to_string(), @@ -469,7 +469,7 @@ author of the Technique. .to_string(), ) } - ParsingError::InvalidInvocation(_, _) => { + ParsingError::InvalidInvocation(_) => { let examples = vec![ Invocation { target: Target::Local(Identifier::new("make_coffee")), @@ -503,7 +503,7 @@ If the procedure takes parameters they can be specified in parenthesis: .to_string(), ) } - ParsingError::InvalidFunction(_, _) => { + ParsingError::InvalidFunction(_) => { let examples = vec![ Function { target: Identifier::new("exec"), @@ -545,7 +545,7 @@ expressions as parameters as required: .to_string(), ) } - ParsingError::InvalidCodeBlock(_, _) => { + ParsingError::InvalidCodeBlock(_) => { let examples = vec![ Expression::Execution( Function { @@ -589,7 +589,7 @@ Inline code blocks are enclosed in braces: .to_string(), ) } - ParsingError::InvalidMultiline(_, _) => ( + ParsingError::InvalidMultiline(_) => ( "Invalid multi-line string".to_string(), r#" Multi-line strings can be written by surrounding the content in triple @@ -619,7 +619,7 @@ it may be used by output templates when rendering the procedure. .trim_ascii() .to_string(), ), - ParsingError::InvalidStep(_, _) => ( + ParsingError::InvalidStep(_) => ( "Invalid step format".to_string(), r#" Steps must start with a number or lower-case letter (in the case of dependent @@ -639,7 +639,7 @@ dash. They can be done in either order, or concurrently: .trim_ascii() .to_string(), ), - ParsingError::InvalidSubstep(_, _) => ( + ParsingError::InvalidSubstep(_) => ( "Invalid substep format".to_string(), r#" Substeps can be nested below top-level dependent steps or top-level parallel @@ -665,7 +665,7 @@ parallel steps, but again this is not compulsory. .trim_ascii() .to_string(), ), - ParsingError::InvalidAttribute(_, _) => { + ParsingError::InvalidAttribute(_) => { let examples = vec![ Scope::AttributeBlock { attributes: vec![ @@ -709,7 +709,7 @@ nested underneath a role or place assignment. .to_string(), ) } - ParsingError::InvalidForeach(_, _) => { + ParsingError::InvalidForeach(_) => { let examples = vec![ Expression::Foreach( vec![Identifier::new("patient")], @@ -748,7 +748,7 @@ a list of tuples. .to_string(), ) } - ParsingError::InvalidResponse(_, _) => { + ParsingError::InvalidResponse(_) => { let examples = vec![ vec![ Response { @@ -816,7 +816,7 @@ By convention the response values are Proper Case. .to_string(), ) } - ParsingError::InvalidIntegral(_, _) => { + ParsingError::InvalidIntegral(_) => { let examples = vec![ Numeric::Integral(42), Numeric::Integral(-123), @@ -844,7 +844,7 @@ Integers cannot contain decimal points or units."#, .to_string(), ) } - ParsingError::InvalidQuantity(_, _) => { + ParsingError::InvalidQuantity(_) => { let examples = vec![ Numeric::Scientific(Quantity { mantissa: Decimal { @@ -909,7 +909,7 @@ a magnitude: .to_string(), ) } - ParsingError::InvalidQuantityDecimal(_, _) => ( + ParsingError::InvalidQuantityDecimal(_) => ( "Invalid number in quantity".to_string(), r#" The numeric part of a quantity may be positive or negative, and may have a @@ -922,7 +922,7 @@ Values less than 1 must have a leading '0' before the decimal."# .trim_ascii() .to_string(), ), - ParsingError::InvalidQuantityUncertainty(_, _) => ( + ParsingError::InvalidQuantityUncertainty(_) => ( "Invalid uncertainty in quantity".to_string(), r#" Uncertainty values must be positive numbers: @@ -933,7 +933,7 @@ You can use '±' or `+/-`, followed by a decimal."# .trim_ascii() .to_string(), ), - ParsingError::InvalidQuantityMagnitude(_, _) => ( + ParsingError::InvalidQuantityMagnitude(_) => ( "Invalid magnitude format".to_string(), r#" The magnitude of a quantity can be expressed in the usual scientific format @@ -948,7 +948,7 @@ The base must be 10, and the exponent must be an integer."# .trim_ascii() .to_string(), ), - ParsingError::InvalidQuantitySymbol(_, _) => { + ParsingError::InvalidQuantitySymbol(_) => { let examples = vec![ Numeric::Scientific(Quantity { mantissa: Decimal { From 0cb797fbaf4a50eaae9dce31af108a6c3072536c Mon Sep 17 00:00:00 2001 From: Andrew Cowie Date: Thu, 7 May 2026 10:32:14 +1000 Subject: [PATCH 9/9] Fix subranges in Procedures and Respones --- src/parsing/checks/verify.rs | 87 +++++++++++++++++++++++++++++++ src/parsing/parser.rs | 19 +++++-- tests/samples/KnownSpanLengths.tq | 14 +++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 tests/samples/KnownSpanLengths.tq diff --git a/src/parsing/checks/verify.rs b/src/parsing/checks/verify.rs index bd98c548..e6fef53a 100644 --- a/src/parsing/checks/verify.rs +++ b/src/parsing/checks/verify.rs @@ -1563,3 +1563,90 @@ III. Implementation } ) } + +#[test] +fn spans_are_populated() { + let source = std::fs::read_to_string("tests/samples/KnownSpanLengths.tq").unwrap(); + + let mut input = Parser::new(); + input.initialize(&source); + + let metadata = input + .read_technique_header() + .unwrap(); + assert_eq!(metadata.span, Span::new(0, 42)); + + input.trim_whitespace(); + let procedure = input + .read_procedure() + .unwrap(); + assert_eq!(procedure.span, Span::new(43, 231)); + assert_eq!( + procedure + .name + .span, + Span::new(43, 5) + ); + + let params = procedure + .parameters + .as_ref() + .unwrap(); + assert_eq!(params[0].span, Span::new(49, 4)); + + if let Genus::Single(f) = &procedure + .signature + .as_ref() + .unwrap() + .requires + { + assert_eq!(f.span, Span::new(57, 6)); + } + if let Genus::Single(f) = &procedure + .signature + .as_ref() + .unwrap() + .provides + { + assert_eq!(f.span, Span::new(67, 8)); + } + + if let Element::Title(_, span) = &procedure.elements[0] { + assert_eq!(*span, Span::new(77, 14)); + } + if let Element::Description(_, span) = &procedure.elements[1] { + assert_eq!(*span, Span::new(92, 23)); + } + if let Element::Steps(scopes, span) = &procedure.elements[2] { + assert_eq!(*span, Span::new(119, 155)); + + if let Scope::DependentBlock { + span, subscopes, .. + } = &scopes[0] + { + assert_eq!(*span, Span::new(119, 41)); + if let Scope::AttributeBlock { + attributes, span, .. + } = &subscopes[0] + { + assert_eq!(*span, Span::new(151, 9)); + if let Attribute::Role(id, attr_span) = &attributes[0] { + assert_eq!(*attr_span, Span::new(151, 8)); + assert_eq!(id.span, Span::new(152, 7)); + } + } + } + if let Scope::DependentBlock { + span, subscopes, .. + } = &scopes[1] + { + assert_eq!(*span, Span::new(164, 110)); + + if let Scope::ResponseBlock { responses, .. } = &subscopes[0] { + assert_eq!(responses[0].span, Span::new(211, 6)); + assert_eq!(responses[1].span, Span::new(220, 21)); + assert_eq!(responses[2].span, Span::new(244, 29)); + } + } + } +} diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index fb2f921d..8db7587f 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -618,6 +618,7 @@ impl<'i> Parser<'i> { F: Fn(&mut Parser<'i>) -> Result, { let content = self.source; + let base = content.as_ptr() as usize; let mut results = Vec::new(); for chunk in content.split(delimiter) { @@ -628,7 +629,8 @@ impl<'i> Parser<'i> { "non-empty content between delimiters", )); } - let mut parser = self.subparser(0, trimmed); + let indent = trimmed.as_ptr() as usize - base; + let mut parser = self.subparser(indent, trimmed); results.push(function(&mut parser)?); self.problems .extend(parser.problems); @@ -1059,6 +1061,8 @@ impl<'i> Parser<'i> { &mut self, parser: &mut Parser<'i>, ) -> Result, ParsingError> { + let start = parser.offset; + // Extract the declaration with recovery let declaration = match self.parse_declaration(parser) { Ok(decl) => decl, @@ -1215,7 +1219,7 @@ impl<'i> Parser<'i> { parameters: declaration.1, signature: declaration.2, elements, - span: Span::default(), + span: parser.span_since(start), }) } @@ -2469,8 +2473,15 @@ impl<'i> Parser<'i> { /// Parse enum responses like 'Yes' | 'No' | 'Not Applicable' fn read_responses(&mut self) -> Result>, ParsingError> { self.take_split_by('|', |inner| { - validate_response(inner.source) - .ok_or(ParsingError::InvalidResponse(Span::new(inner.offset, 0))) + let mut resp = validate_response(inner.source) + .ok_or(ParsingError::InvalidResponse(Span::new(inner.offset, 0)))?; + resp.span = Span::new( + inner.offset, + inner + .source + .len(), + ); + Ok(resp) }) } diff --git a/tests/samples/KnownSpanLengths.tq b/tests/samples/KnownSpanLengths.tq new file mode 100644 index 00000000..1990c0f4 --- /dev/null +++ b/tests/samples/KnownSpanLengths.tq @@ -0,0 +1,14 @@ +% technique v1 +! MIT; © ACME +& checklist + +greet(name) : Person -> Greeting + +# Hello World + +Say hello to someone. + + 1. Look at the person. + @greeter + 2. Say { exec("hello") } to them. + 'Done' | 'Skipped' but too shy | 'Elvis Has Left The Building'