11use std:: path:: Path ;
2+ use tracing:: debug;
23
34use crate :: error:: * ;
45use crate :: language:: * ;
@@ -15,7 +16,10 @@ pub fn parse_via_taking<'i>(
1516 let result = input. parse_from_start ( ) ;
1617 match result {
1718 Ok ( technique) => Ok ( technique) ,
18- Err ( error) => Err ( make_error ( input, error) ) ,
19+ Err ( error) => {
20+ debug ! ( ?error) ;
21+ Err ( make_error ( input, error) )
22+ }
1923 }
2024}
2125
@@ -52,6 +56,7 @@ pub enum ParsingError<'i> {
5256 InvalidCodeBlock ( usize ) ,
5357 InvalidMultiline ( usize ) ,
5458 InvalidStep ( usize ) ,
59+ InvalidSubstep ( usize ) ,
5560 InvalidForeach ( usize ) ,
5661 InvalidResponse ( usize ) ,
5762 InvalidNumeric ( usize ) ,
@@ -79,6 +84,7 @@ impl<'i> ParsingError<'i> {
7984 ParsingError :: InvalidCodeBlock ( offset) => * offset,
8085 ParsingError :: InvalidMultiline ( offset) => * offset,
8186 ParsingError :: InvalidStep ( offset) => * offset,
87+ ParsingError :: InvalidSubstep ( offset) => * offset,
8288 ParsingError :: InvalidForeach ( offset) => * offset,
8389 ParsingError :: InvalidResponse ( offset) => * offset,
8490 ParsingError :: InvalidNumeric ( offset) => * offset,
@@ -330,15 +336,45 @@ it may be used by output templates when rendering the procedure.
330336 ParsingError :: InvalidStep ( _) => (
331337 "Invalid step format" . to_string ( ) ,
332338 r#"
333- Steps must start with a number or letter (in the case of dependent steps and
334- sub-steps, respectively) followed by a '.', or a dash (for tasks that can
335- execute in parallel):
339+ Steps must start with a number or lower-case letter (in the case of dependent
340+ steps and sub-steps, respectively) followed by a '.':
336341
337342 1. First step
338343 2. Second step
339344 a. First substep
340345 b. Second substep
341- - Parallel task
346+
347+ Steps or substeps that can execute in parallel can instead be marked with a
348+ dash. They can be done in either order, or concurrently:
349+
350+ - Do one thing
351+ - And another
352+ "#
353+ . trim_ascii ( )
354+ . to_string ( ) ,
355+ ) ,
356+ ParsingError :: InvalidSubstep ( _) => (
357+ "Invalid substep format" . to_string ( ) ,
358+ r#"
359+ Substeps can be nested below top-level depenent steps or top-level parallel
360+ steps. So both of these are valid:
361+
362+ 1. First top-level step.
363+ a. First substep in first dependent step.
364+ b. Second substep in first dependent step.
365+
366+ and
367+
368+ - First top-level step to be done in any order.
369+ a. First substep in first parallel step.
370+ b. Second substep in first parallel step.
371+
372+ The ordinal must be a lowercase letter and not a roman numeral. By convention
373+ substeps are indented by 4 characters, but that is not required.
374+
375+ Note also that the substeps can be consecutively numbered, which allows each
376+ substep to be uniquely identified when they are grouped under different
377+ parallel steps, but again this is not compulsory.
342378 "#
343379 . trim_ascii ( )
344380 . to_string ( ) ,
@@ -1083,7 +1119,7 @@ impl<'i> Parser<'i> {
10831119 if !steps. is_empty ( ) {
10841120 elements. push ( Element :: Steps ( steps) ) ;
10851121 }
1086- } else if is_invalid_step_pattern ( content) {
1122+ } else if malformed_step_pattern ( content) {
10871123 // Detect and reject invalid step patterns
10881124 return Err ( ParsingError :: InvalidStep ( outer. offset ) ) ;
10891125 } else {
@@ -1093,13 +1129,13 @@ impl<'i> Parser<'i> {
10931129 !is_step ( line)
10941130 && !is_procedure_title ( line)
10951131 && !is_code_block ( line)
1096- && !is_invalid_step_pattern ( line)
1132+ && !malformed_step_pattern ( line)
10971133 } ,
10981134 |line| {
10991135 is_step ( line)
11001136 || is_procedure_title ( line)
11011137 || is_code_block ( line)
1102- || is_invalid_step_pattern ( line)
1138+ || malformed_step_pattern ( line)
11031139 } ,
11041140 |inner| {
11051141 let content = inner. source ;
@@ -1695,7 +1731,8 @@ impl<'i> Parser<'i> {
16951731 || is_subsubstep_dependent ( line)
16961732 || is_role_assignment ( line)
16971733 || is_enum_response ( line)
1698- || is_invalid_response_pattern ( line)
1734+ || malformed_step_pattern ( line)
1735+ || malformed_response_pattern ( line)
16991736 || is_code_block ( line)
17001737 } ,
17011738 |outer| {
@@ -1917,32 +1954,29 @@ impl<'i> Parser<'i> {
19171954 /// Trim any leading whitespace (space, tab, newline) from the front of
19181955 /// the current parser text.
19191956 fn trim_whitespace ( & mut self ) {
1920- let mut l = 0 ;
1921-
19221957 if self
19231958 . source
19241959 . is_empty ( )
19251960 {
19261961 return ;
19271962 }
19281963
1929- for c in self
1964+ let bytes = self
19301965 . source
1931- . chars ( )
1932- {
1933- if c == '\n' {
1934- l += 1 ;
1935- continue ;
1936- } else if c. is_ascii_whitespace ( ) {
1937- l += 1 ;
1938- continue ;
1939- } else {
1940- break ;
1966+ . as_bytes ( ) ;
1967+ let mut i = 0 ;
1968+
1969+ while i < bytes. len ( ) {
1970+ match bytes[ i] {
1971+ b' ' | b'\t' | b'\n' | b'\r' => {
1972+ i += 1 ;
1973+ }
1974+ _ => break ,
19411975 }
19421976 }
19431977
1944- self . source = & self . source [ l ..] ;
1945- self . offset += l ;
1978+ self . source = & self . source [ i ..] ;
1979+ self . offset += i ;
19461980 }
19471981
19481982 /// Parse role assignments like @surgeon, @nurse, or @marketing + @sales
@@ -2008,7 +2042,9 @@ impl<'i> Parser<'i> {
20082042 } else if is_code_block ( content) {
20092043 let block = self . read_code_scope ( ) ?;
20102044 scopes. push ( block) ;
2011- } else if is_invalid_response_pattern ( content) {
2045+ } else if malformed_step_pattern ( content) {
2046+ return Err ( ParsingError :: InvalidSubstep ( self . offset ) ) ;
2047+ } else if malformed_response_pattern ( content) {
20122048 return Err ( ParsingError :: InvalidResponse ( self . offset ) ) ;
20132049 } else if is_enum_response ( content) {
20142050 let responses = self . read_responses ( ) ?;
@@ -2233,7 +2269,7 @@ fn is_procedure_title(content: &str) -> bool {
22332269// I'm not sure about anchoring this one on start and end, seeing as how it
22342270// will be used when scanning.
22352271fn is_invocation ( content : & str ) -> bool {
2236- let re = regex ! ( r"^\s*(<.+?>\s*(?:\(.*?\))?)\s*$ " ) ;
2272+ let re = regex ! ( r"^\s*< " ) ;
22372273
22382274 re. is_match ( content)
22392275}
@@ -2291,7 +2327,7 @@ fn is_step(content: &str) -> bool {
22912327}
22922328
22932329/// Detect patterns that look like steps but are invalid at the top-level
2294- fn is_invalid_step_pattern ( content : & str ) -> bool {
2330+ fn malformed_step_pattern ( content : & str ) -> bool {
22952331 let re = regex ! ( r"^\s*([a-zA-Z]|[ivxIVX]+)\.\s+" ) ;
22962332 re. is_match ( content)
22972333}
@@ -2336,7 +2372,7 @@ fn is_enum_response(content: &str) -> bool {
23362372}
23372373
23382374/// Detect response patterns with double quotes
2339- fn is_invalid_response_pattern ( content : & str ) -> bool {
2375+ fn malformed_response_pattern ( content : & str ) -> bool {
23402376 let re = regex ! ( r#"^\s*".+?"(\s*\|\s*".+?")*"# ) ;
23412377 re. is_match ( content)
23422378}
0 commit comments