@@ -765,7 +765,7 @@ impl<'i> Parser<'i> {
765765 } else if is_code_block ( content) {
766766 let expression = outer. read_code_block ( ) ?;
767767 elements. push ( Element :: CodeBlock ( expression) ) ;
768- } else if is_role_assignment ( content) {
768+ } else if is_attribute_assignment ( content) {
769769 let attribute_block = outer. read_attribute_scope ( ) ?;
770770 elements. push ( Element :: Steps ( vec ! [ attribute_block] ) ) ;
771771 } else if is_step ( content) {
@@ -796,14 +796,14 @@ impl<'i> Parser<'i> {
796796 && !is_procedure_title ( line)
797797 && !is_code_block ( line)
798798 && !malformed_step_pattern ( line)
799- && !is_role_assignment ( line)
799+ && !is_attribute_assignment ( line)
800800 } ,
801801 |line| {
802802 is_step ( line)
803803 || is_procedure_title ( line)
804804 || is_code_block ( line)
805805 || malformed_step_pattern ( line)
806- || is_role_assignment ( line)
806+ || is_attribute_assignment ( line)
807807 } ,
808808 |inner| {
809809 let content = inner. source ;
@@ -1672,7 +1672,7 @@ impl<'i> Parser<'i> {
16721672 || is_substep_dependent ( line)
16731673 || is_substep_parallel ( line)
16741674 || is_subsubstep_dependent ( line)
1675- || is_role_assignment ( line)
1675+ || is_attribute_assignment ( line)
16761676 || is_enum_response ( line)
16771677 || malformed_step_pattern ( line)
16781678 || malformed_response_pattern ( line)
@@ -1927,32 +1927,43 @@ impl<'i> Parser<'i> {
19271927 self . offset += i;
19281928 }
19291929
1930- /// Parse role assignments like @surgeon, @nurse , or @marketing + @sales
1931- fn read_role_assignments ( & mut self ) -> Result < Vec < Attribute < ' i > > , ParsingError < ' i > > {
1930+ /// Parse attributes (roles and/or places) like @surgeon, ^kitchen , or @chef + ^bathroom
1931+ fn read_attributes ( & mut self ) -> Result < Vec < Attribute < ' i > > , ParsingError < ' i > > {
19321932 self . take_line ( |inner| {
19331933 let mut attributes = Vec :: new ( ) ;
19341934
19351935 let line = inner. source ;
19361936
1937- // Handle multiple roles separated by +
1938- let role_parts : Vec < & str > = line
1937+ // Handle multiple attributes separated by +
1938+ let parts : Vec < & str > = line
19391939 . split ( '+' )
19401940 . collect ( ) ;
19411941
1942- for part in role_parts {
1943- let re = regex ! ( r"^\s*@([a-z][a-z0-9_]*)\s*$" ) ;
1944- let cap = re
1945- . captures ( part. trim_ascii ( ) )
1946- . ok_or ( ParsingError :: InvalidStep ( inner. offset ) ) ?;
1947-
1948- let role_name = cap
1949- . get ( 1 )
1950- . ok_or ( ParsingError :: Expected ( inner. offset , "role name after @" ) ) ?
1951- . as_str ( ) ;
1952-
1953- let identifier = validate_identifier ( role_name)
1954- . ok_or ( ParsingError :: InvalidIdentifier ( inner. offset , role_name) ) ?;
1955- attributes. push ( Attribute :: Role ( identifier) ) ;
1942+ for part in parts {
1943+ let trimmed = part. trim_ascii ( ) ;
1944+
1945+ // Check if it's a role '@'
1946+ if let Some ( captures) = regex ! ( r"^@([a-z][a-z0-9_]*)$" ) . captures ( trimmed) {
1947+ let role_name = captures
1948+ . get ( 1 )
1949+ . ok_or ( ParsingError :: Expected ( inner. offset , "role name after @" ) ) ?
1950+ . as_str ( ) ;
1951+ let identifier = validate_identifier ( role_name)
1952+ . ok_or ( ParsingError :: InvalidIdentifier ( inner. offset , role_name) ) ?;
1953+ attributes. push ( Attribute :: Role ( identifier) ) ;
1954+ }
1955+ // Check if it's a place '^'
1956+ else if let Some ( captures) = regex ! ( r"^\^([a-z][a-z0-9_]*)$" ) . captures ( trimmed) {
1957+ let place_name = captures
1958+ . get ( 1 )
1959+ . ok_or ( ParsingError :: Expected ( inner. offset , "place name after ^" ) ) ?
1960+ . as_str ( ) ;
1961+ let identifier = validate_identifier ( place_name)
1962+ . ok_or ( ParsingError :: InvalidIdentifier ( inner. offset , place_name) ) ?;
1963+ attributes. push ( Attribute :: Place ( identifier) ) ;
1964+ } else {
1965+ return Err ( ParsingError :: InvalidStep ( inner. offset ) ) ;
1966+ }
19561967 }
19571968
19581969 Ok ( attributes)
@@ -1972,7 +1983,7 @@ impl<'i> Parser<'i> {
19721983
19731984 let content = self . source ;
19741985
1975- if is_role_assignment ( content) {
1986+ if is_attribute_assignment ( content) {
19761987 let block = self . read_attribute_scope ( ) ?;
19771988 scopes. push ( block) ;
19781989 } else if is_substep_dependent ( content) {
@@ -2004,10 +2015,10 @@ impl<'i> Parser<'i> {
20042015 Ok ( scopes)
20052016 }
20062017
2007- /// Parse an attribute block (role assignment) with its subscopes
2018+ /// Parse an attribute block (role or place assignment) with its subscopes
20082019 fn read_attribute_scope ( & mut self ) -> Result < Scope < ' i > , ParsingError < ' i > > {
2009- self . take_block_lines ( is_role_assignment , is_role_assignment , |outer| {
2010- let attributes = outer. read_role_assignments ( ) ?;
2020+ self . take_block_lines ( is_attribute_assignment , is_attribute_assignment , |outer| {
2021+ let attributes = outer. read_attributes ( ) ?;
20112022 let subscopes = outer. read_scopes ( ) ?;
20122023
20132024 Ok ( Scope :: AttributeBlock {
@@ -2266,7 +2277,7 @@ fn is_procedure_body(content: &str) -> bool {
22662277 // Check for procedure body indicators. At the end, if it doesn't look like signature, it's body.
22672278 is_procedure_title ( content)
22682279 || is_step ( content)
2269- || is_role_assignment ( content)
2280+ || is_attribute_assignment ( content)
22702281 || is_code_block ( content)
22712282 || is_enum_response ( content)
22722283 || ( !is_signature_part ( content) )
@@ -2390,11 +2401,6 @@ fn is_subsubstep_dependent(content: &str) -> bool {
23902401 re. is_match ( content)
23912402}
23922403
2393- fn is_role_assignment ( content : & str ) -> bool {
2394- let re = regex ! ( r"^\s*@[a-z][a-z0-9_]*(\s*\+\s*@[a-z][a-z0-9_]*)*" ) ;
2395- re. is_match ( content)
2396- }
2397-
23982404fn is_enum_response ( content : & str ) -> bool {
23992405 let re = regex ! ( r"^\s*'.+?'" ) ;
24002406 re. is_match ( content)
@@ -2427,6 +2433,12 @@ fn is_string_literal(content: &str) -> bool {
24272433 re. is_match ( content)
24282434}
24292435
2436+ fn is_attribute_assignment ( input : & str ) -> bool {
2437+ // Matches any combination of @ and ^ attributes separated by +
2438+ let re = regex ! ( r"^\s*[@^][a-z][a-z0-9_]*(\s*\+\s*[@^][a-z][a-z0-9_]*)*" ) ;
2439+ re. is_match ( input)
2440+ }
2441+
24302442#[ cfg( test) ]
24312443mod check {
24322444 use super :: * ;
@@ -2955,11 +2967,16 @@ making_coffee(b, m) :
29552967 assert ! ( is_subsubstep_dependent( "xi. Eleven" ) ) ;
29562968 assert ! ( is_subsubstep_dependent( "xxxix. Thirty-nine" ) ) ;
29572969
2958- // Test role assignments
2959- assert ! ( is_role_assignment( "@surgeon" ) ) ;
2960- assert ! ( is_role_assignment( " @nursing_team" ) ) ;
2961- assert ! ( !is_role_assignment( "surgeon" ) ) ;
2962- assert ! ( !is_role_assignment( "@123invalid" ) ) ;
2970+ // Test attribute assignments
2971+ assert ! ( is_attribute_assignment( "@surgeon" ) ) ;
2972+ assert ! ( is_attribute_assignment( " @nursing_team" ) ) ;
2973+ assert ! ( is_attribute_assignment( "^kitchen" ) ) ;
2974+ assert ! ( is_attribute_assignment( " ^garden " ) ) ;
2975+ assert ! ( is_attribute_assignment( "@chef + ^kitchen" ) ) ;
2976+ assert ! ( is_attribute_assignment( "^room1 + @barista" ) ) ;
2977+ assert ! ( !is_attribute_assignment( "surgeon" ) ) ;
2978+ assert ! ( !is_attribute_assignment( "@123invalid" ) ) ;
2979+ assert ! ( !is_attribute_assignment( "^InvalidPlace" ) ) ;
29632980
29642981 // Test enum responses
29652982 assert ! ( is_enum_response( "'Yes'" ) ) ;
@@ -4126,68 +4143,84 @@ echo test
41264143 }
41274144
41284145 #[ test]
4129- fn reading_role_assignments ( ) {
4146+ fn reading_attributes ( ) {
41304147 let mut input = Parser :: new ( ) ;
41314148
4132- // Test simple role assignment
4133- input. initialize ( "@surgeon " ) ;
4134- let result = input. read_role_assignments ( ) ;
4135- assert_eq ! ( result, Ok ( vec![ Attribute :: Role ( Identifier ( "surgeon " ) ) ] ) ) ;
4149+ // Test simple role
4150+ input. initialize ( "@chef " ) ;
4151+ let result = input. read_attributes ( ) ;
4152+ assert_eq ! ( result, Ok ( vec![ Attribute :: Role ( Identifier ( "chef " ) ) ] ) ) ;
41364153
4137- // Test role assignment with whitespace
4138- input. initialize ( " @nurse " ) ;
4139- let result = input. read_role_assignments ( ) ;
4140- assert_eq ! ( result, Ok ( vec![ Attribute :: Role ( Identifier ( "nurse " ) ) ] ) ) ;
4154+ // Test simple place
4155+ input. initialize ( "^kitchen " ) ;
4156+ let result = input. read_attributes ( ) ;
4157+ assert_eq ! ( result, Ok ( vec![ Attribute :: Place ( Identifier ( "kitchen " ) ) ] ) ) ;
41414158
4142- // Test role assignment with underscores
4143- input. initialize ( "@nursing_team " ) ;
4144- let result = input. read_role_assignments ( ) ;
4159+ // Test multiple roles
4160+ input. initialize ( "@master_chef + @barista " ) ;
4161+ let result = input. read_attributes ( ) ;
41454162 assert_eq ! (
41464163 result,
4147- Ok ( vec![ Attribute :: Role ( Identifier ( "nursing_team" ) ) ] )
4164+ Ok ( vec![
4165+ Attribute :: Role ( Identifier ( "master_chef" ) ) ,
4166+ Attribute :: Role ( Identifier ( "barista" ) )
4167+ ] )
41484168 ) ;
41494169
4150- // Test role assignment with numbers
4151- input. initialize ( "@team1" ) ;
4152- let result = input. read_role_assignments ( ) ;
4153- assert_eq ! ( result, Ok ( vec![ Attribute :: Role ( Identifier ( "team1" ) ) ] ) ) ;
4170+ // Test multiple places
4171+ input. initialize ( "^kitchen + ^bath_room" ) ;
4172+ let result = input. read_attributes ( ) ;
4173+ assert_eq ! (
4174+ result,
4175+ Ok ( vec![
4176+ Attribute :: Place ( Identifier ( "kitchen" ) ) ,
4177+ Attribute :: Place ( Identifier ( "bath_room" ) )
4178+ ] )
4179+ ) ;
41544180
4155- // Test multiple roles with +
4156- input. initialize ( "@marketing + @sales " ) ;
4157- let result = input. read_role_assignments ( ) ;
4181+ // Test mixed roles and places
4182+ input. initialize ( "@chef + ^bathroom " ) ;
4183+ let result = input. read_attributes ( ) ;
41584184 assert_eq ! (
41594185 result,
41604186 Ok ( vec![
4161- Attribute :: Role ( Identifier ( "marketing " ) ) ,
4162- Attribute :: Role ( Identifier ( "sales " ) )
4187+ Attribute :: Role ( Identifier ( "chef " ) ) ,
4188+ Attribute :: Place ( Identifier ( "bathroom " ) )
41634189 ] )
41644190 ) ;
41654191
4166- // Test multiple roles with + and extra whitespace
4167- input. initialize ( "@operators + @users + @management " ) ;
4168- let result = input. read_role_assignments ( ) ;
4192+ // Test mixed places and roles
4193+ input. initialize ( "^kitchen + @barista " ) ;
4194+ let result = input. read_attributes ( ) ;
41694195 assert_eq ! (
41704196 result,
41714197 Ok ( vec![
4172- Attribute :: Role ( Identifier ( "operators" ) ) ,
4173- Attribute :: Role ( Identifier ( "users" ) ) ,
4174- Attribute :: Role ( Identifier ( "management" ) )
4198+ Attribute :: Place ( Identifier ( "kitchen" ) ) ,
4199+ Attribute :: Role ( Identifier ( "barista" ) )
41754200 ] )
41764201 ) ;
41774202
4178- // Test invalid role assignment - uppercase
4179- input. initialize ( "@Surgeon" ) ;
4180- let result = input. read_role_assignments ( ) ;
4181- assert ! ( result. is_err( ) ) ;
4203+ // Test complex mixed attributes
4204+ input. initialize ( "@chef + ^kitchen + @barista + ^dining_room" ) ;
4205+ let result = input. read_attributes ( ) ;
4206+ assert_eq ! (
4207+ result,
4208+ Ok ( vec![
4209+ Attribute :: Role ( Identifier ( "chef" ) ) ,
4210+ Attribute :: Place ( Identifier ( "kitchen" ) ) ,
4211+ Attribute :: Role ( Identifier ( "barista" ) ) ,
4212+ Attribute :: Place ( Identifier ( "dining_room" ) )
4213+ ] )
4214+ ) ;
41824215
4183- // Test invalid role assignment - missing @
4184- input. initialize ( "surgeon " ) ;
4185- let result = input. read_role_assignments ( ) ;
4216+ // Test invalid - uppercase
4217+ input. initialize ( "^Kitchen " ) ;
4218+ let result = input. read_attributes ( ) ;
41864219 assert ! ( result. is_err( ) ) ;
41874220
4188- // Test invalid role assignment - empty
4189- input. initialize ( "@ " ) ;
4190- let result = input. read_role_assignments ( ) ;
4221+ // Test invalid - no marker
4222+ input. initialize ( "kitchen " ) ;
4223+ let result = input. read_attributes ( ) ;
41914224 assert ! ( result. is_err( ) ) ;
41924225 }
41934226
0 commit comments