@@ -48,6 +48,7 @@ pub enum ParsingError {
4848 InvalidCodeBlock ( usize , usize ) ,
4949 InvalidStep ( usize , usize ) ,
5050 InvalidSubstep ( usize , usize ) ,
51+ InvalidAttribute ( usize , usize ) ,
5152 InvalidResponse ( usize , usize ) ,
5253 InvalidMultiline ( usize , usize ) ,
5354 InvalidForeach ( usize , usize ) ,
@@ -87,6 +88,7 @@ impl ParsingError {
8788 ParsingError :: InvalidMultiline ( offset, _) => * offset,
8889 ParsingError :: InvalidStep ( offset, _) => * offset,
8990 ParsingError :: InvalidSubstep ( offset, _) => * offset,
91+ ParsingError :: InvalidAttribute ( offset, _) => * offset,
9092 ParsingError :: InvalidForeach ( offset, _) => * offset,
9193 ParsingError :: InvalidResponse ( offset, _) => * offset,
9294 ParsingError :: InvalidIntegral ( offset, _) => * offset,
@@ -123,6 +125,7 @@ impl ParsingError {
123125 ParsingError :: InvalidMultiline ( _, width) => * width,
124126 ParsingError :: InvalidStep ( _, width) => * width,
125127 ParsingError :: InvalidSubstep ( _, width) => * width,
128+ ParsingError :: InvalidAttribute ( _, width) => * width,
126129 ParsingError :: InvalidForeach ( _, width) => * width,
127130 ParsingError :: InvalidResponse ( _, width) => * width,
128131 ParsingError :: InvalidIntegral ( _, width) => * width,
@@ -1072,14 +1075,20 @@ impl<'i> Parser<'i> {
10721075 parser. skip_to_next_line ( ) ;
10731076 }
10741077 }
1075- } else if is_attribute_assignment ( content) {
1076- match parser. read_attribute_scope ( ) {
1077- Ok ( attribute_block) => elements. push ( Element :: Steps ( vec ! [ attribute_block] ) ) ,
1078- Err ( error) => {
1079- self . problems
1080- . push ( error) ;
1081- parser. skip_to_next_line ( ) ;
1078+ } else if is_attribute_pattern ( content) {
1079+ if is_attribute_assignment ( content) {
1080+ match parser. read_attribute_scope ( ) {
1081+ Ok ( attribute_block) => elements. push ( Element :: Steps ( vec ! [ attribute_block] ) ) ,
1082+ Err ( error) => {
1083+ self . problems
1084+ . push ( error) ;
1085+ parser. skip_to_next_line ( ) ;
1086+ }
10821087 }
1088+ } else {
1089+ self . problems
1090+ . push ( ParsingError :: InvalidAttribute ( parser. offset , content. len ( ) ) ) ;
1091+ parser. skip_to_next_line ( ) ;
10831092 }
10841093 } else if is_step ( content) {
10851094 let mut steps = vec ! [ ] ;
@@ -1124,14 +1133,14 @@ impl<'i> Parser<'i> {
11241133 && !is_procedure_title ( line)
11251134 && !is_code_block ( line)
11261135 && !malformed_step_pattern ( line)
1127- && !is_attribute_assignment ( line)
1136+ && !is_attribute_pattern ( line)
11281137 } ,
11291138 |line| {
11301139 is_step ( line)
11311140 || is_procedure_title ( line)
11321141 || is_code_block ( line)
11331142 || malformed_step_pattern ( line)
1134- || is_attribute_assignment ( line)
1143+ || is_attribute_pattern ( line)
11351144 } ,
11361145 |inner| {
11371146 let content = inner. source ;
@@ -2161,7 +2170,7 @@ impl<'i> Parser<'i> {
21612170 || is_substep_dependent ( line)
21622171 || is_substep_parallel ( line)
21632172 || is_subsubstep_dependent ( line)
2164- || is_attribute_assignment ( line)
2173+ || is_attribute_pattern ( line)
21652174 || is_enum_response ( line)
21662175 || malformed_step_pattern ( line)
21672176 || malformed_response_pattern ( line)
@@ -2494,7 +2503,13 @@ impl<'i> Parser<'i> {
24942503 ) ) ?;
24952504 attributes. push ( Attribute :: Place ( identifier) ) ;
24962505 } else {
2497- return Err ( ParsingError :: InvalidStep ( inner. offset , 0 ) ) ;
2506+ // Check if this looks like a malformed attribute (starts with @ or ^)
2507+ if is_attribute_pattern ( trimmed) {
2508+ // This might be multiple attributes without proper + joiners
2509+ return Err ( ParsingError :: InvalidAttribute ( inner. offset , line. len ( ) ) ) ;
2510+ } else {
2511+ return Err ( ParsingError :: InvalidStep ( inner. offset , 0 ) ) ;
2512+ }
24982513 }
24992514 }
25002515
@@ -2515,9 +2530,13 @@ impl<'i> Parser<'i> {
25152530
25162531 let content = self . source ;
25172532
2518- if is_attribute_assignment ( content) {
2519- let block = self . read_attribute_scope ( ) ?;
2520- scopes. push ( block) ;
2533+ if is_attribute_pattern ( content) {
2534+ if is_attribute_assignment ( content) {
2535+ let block = self . read_attribute_scope ( ) ?;
2536+ scopes. push ( block) ;
2537+ } else {
2538+ return Err ( ParsingError :: InvalidAttribute ( self . offset , content. len ( ) ) ) ;
2539+ }
25212540 } else if is_substep_dependent ( content) {
25222541 let block = self . read_substep_dependent ( ) ?;
25232542 scopes. push ( block) ;
@@ -3037,6 +3056,12 @@ fn is_attribute_assignment(input: &str) -> bool {
30373056 re. is_match ( input)
30383057}
30393058
3059+ /// Detect the beginning of an attribute (role or place) assignment
3060+ fn is_attribute_pattern ( input : & str ) -> bool {
3061+ let trimmed = input. trim_ascii_start ( ) ;
3062+ trimmed. starts_with ( '@' ) || trimmed. starts_with ( '^' )
3063+ }
3064+
30403065// This is a rather monsterous test battery, so we move it into a separate
30413066// file. We use the path directive to avoid the need to put it into a
30423067// subdirectory with the same name as this module.
0 commit comments