From 540e9ce570fa5e0ddc36faa26212e51ab3aa3a52 Mon Sep 17 00:00:00 2001 From: Barsik Date: Mon, 1 Apr 2024 21:18:46 +0300 Subject: [PATCH 1/4] Refactor Parser to handle command line arguments This commit refactors the Parser module of the wca library, enabling it to parse command-line arguments as a vector of Strings. This optimises functionality by enabling commands to be parsed directly from arguments passed to a program's main function. Additional improvements include further structuring the parse_command function and removal of unnecessary dependencies on the nom crate and methods from Parser. --- module/move/wca/Cargo.toml | 1 - module/move/wca/src/ca/aggregator.rs | 10 +- module/move/wca/src/ca/input.rs | 10 +- module/move/wca/src/ca/parser/command.rs | 288 ++++------------------ module/move/wca/src/ca/parser/entities.rs | 61 ----- module/move/wca/src/ca/parser/mod.rs | 11 +- module/move/wca/src/ca/parser/parser.rs | 187 +++++++++----- module/move/wca/src/ca/parser/program.rs | 73 ------ 8 files changed, 196 insertions(+), 445 deletions(-) delete mode 100644 module/move/wca/src/ca/parser/entities.rs delete mode 100644 module/move/wca/src/ca/parser/program.rs diff --git a/module/move/wca/Cargo.toml b/module/move/wca/Cargo.toml index b072febd84..781a98da4a 100644 --- a/module/move/wca/Cargo.toml +++ b/module/move/wca/Cargo.toml @@ -48,7 +48,6 @@ former = { workspace = true, features = [ "default" ] } ## external log = "0.4" -nom = "7.1" #closure = "0.3" eddie = { version = "0.4", optional = true } # fuzzy commands search diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index 63854b1bfb..9c287326a7 100644 --- a/module/move/wca/src/ca/aggregator.rs +++ b/module/move/wca/src/ca/aggregator.rs @@ -3,9 +3,8 @@ pub( crate ) mod private use crate::*; use ca:: { - Parser, Verifier, + Verifier, Executor, - ProgramParser, Command, grammar::command::private::CommandFormer, help::{ HelpGeneratorFn, HelpGeneratorOptions, HelpVariants }, @@ -101,7 +100,7 @@ pub( crate ) mod private #[ default( Dictionary::default() ) ] dictionary : Dictionary, - #[ default( Parser::former().form() ) ] + #[ default( Parser ) ] parser : Parser, #[ setter( false ) ] @@ -260,13 +259,12 @@ pub( crate ) mod private { let Input( ref program ) = program.into_input(); - let raw_program = self.parser.program( program ).map_err( | e | Error::Validation( ValidationError::Parser { input : program.to_string(), error : e } ) )?; + let raw_program = self.parser.parse( program ).map_err( | e | Error::Validation( ValidationError::Parser { input : format!( "{:?}", program ), error : e } ) )?; let grammar_program = self.verifier.to_program( &self.dictionary, raw_program ).map_err( | e | Error::Validation( ValidationError::Verifier( e ) ) )?; - // let exec_program = self.executor_converter.to_program( grammar_program ).map_err( | e | Error::Validation( ValidationError::ExecutorConverter( e ) ) )?; if let Some( callback ) = &self.callback_fn { - callback.0( program, &grammar_program ) + callback.0( &program.join( " " ), &grammar_program ) } self.executor.program( &self.dictionary, grammar_program ).map_err( | e | Error::Execution( e ) ) diff --git a/module/move/wca/src/ca/input.rs b/module/move/wca/src/ca/input.rs index 4deac4264c..c8ef39a28b 100644 --- a/module/move/wca/src/ca/input.rs +++ b/module/move/wca/src/ca/input.rs @@ -15,11 +15,11 @@ pub( crate ) mod private /// A structure representing an input with a single string value. /// - /// This struct is designed to encapsulate a single piece of input data as a `String`. + /// This struct is designed to encapsulate a single piece of input data as a `Vec< String >`. /// It provides a simple wrapper that can be used to convert various types of string /// representations into a uniform `Input` struct. #[ derive( Debug ) ] - pub struct Input( pub String ); + pub struct Input( pub Vec< String > ); /// A trait for converting various types into `Input`. /// @@ -51,7 +51,7 @@ pub( crate ) mod private fn into_input( self ) -> Input { - Input( self.to_string() ) + Input( self.split( ' ' ).map( ToString::to_string ).collect() ) } } @@ -59,7 +59,7 @@ pub( crate ) mod private { fn into_input( self ) -> Input { - Input( self ) + Input( self.split( ' ' ).map( ToString::to_string ).collect() ) } } @@ -67,7 +67,7 @@ pub( crate ) mod private { fn into_input( self ) -> Input { - Input( self.join( " " ) ) + Input( self ) } } diff --git a/module/move/wca/src/ca/parser/command.rs b/module/move/wca/src/ca/parser/command.rs index 13a9be8230..1d39e9bf34 100644 --- a/module/move/wca/src/ca/parser/command.rs +++ b/module/move/wca/src/ca/parser/command.rs @@ -1,238 +1,54 @@ pub( crate ) mod private { - use crate:: - { - ca:: - { - Parser, - ParsedCommand as Command, - parser::parser::any_word, - }, - wtools - }; - use wtools::{ error:: Result, err }; - use nom:: - { - branch::alt, - bytes::complete::{ tag, take_while, escaped }, - character::complete::{ alpha1, multispace0, multispace1, none_of, one_of }, - combinator::{ map, map_opt, recognize, eof }, - multi::many0, - sequence::{ tuple, pair, delimited }, - IResult, - }; - - /// Can parse Commands - pub trait CommandParser - { - /// Parses first command from string - /// - /// Command name must starts with letter or underscore - fn command( &self, input : &str ) -> Result< Command >; - } - - type CommandParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, Command > + 'a >; - - /// Can be used as function to parse a Command - pub trait CommandParserFn : GetCommandPrefix + CommandNameParserFn + CommandSubjectParserFn + CommandPropertyParserFn - { - /// Returns function that can parse a Command - fn command_fn( &self ) -> CommandParserFunction< '_ > - { - let command_prefix = self.get_command_prefix(); - Box::new( move | input : &str | - map - ( - tuple - (( - multispace0, - tag( &*format!( "{command_prefix}" ) ), - alt - (( - // it is a command - map( tuple - (( - Self::command_name_fn(), - many0( tuple(( multispace1, self.command_subject_fn() )) ), - //? why multispace0 - many0( tuple(( multispace0, self.command_property_fn() )) ) - )), |( name, subjects, props )| - { - Command - { - name : name.to_owned(), - subjects : subjects.into_iter().filter_map( |( _, subject )| if subject.is_empty() { None } else { Some( subject ) } ).collect(), - properties : props.into_iter().map( |( _, prop )| prop ).collect() - } - }), - // spacial cases - map( tag( "?" ), | _ | Command { name : format!( "{}?", command_prefix ), ..Default::default() } ), - map - ( - eof, - | _ | - Command - { - name : command_prefix.to_string(), - ..Default::default() - } - ) - )), - )), - |( _, _, command )| command - )( input ) ) - } - } - - pub trait GetCommandPrefix - { - fn get_command_prefix( &self ) -> char; - } - - impl GetCommandPrefix for Parser - { - fn get_command_prefix( &self ) -> char { self.command_prefix } - } - - type CommandNameParserFunction = Box< dyn Fn( &str ) -> IResult< &str, &str > >; - - /// Can be used as function to parse a Command name - pub trait CommandNameParserFn - { - /// Returns function that can parse a Command name - fn command_name_fn() -> CommandNameParserFunction; - } - - type CommandSubjectParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, String > + 'a >; - - /// Can be used as function to parse a Command subject - pub trait CommandSubjectParserFn - { - /// Returns function that can parse a Command subject - fn command_subject_fn( &self ) -> CommandSubjectParserFunction< '_ >; - } - - type CommandPropertyParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, ( String, String ) > + 'a >; - - /// Can be used as function to parse a Command property - pub trait CommandPropertyParserFn - { - /// Returns function that can parse a Command property - fn command_property_fn( &self ) -> CommandPropertyParserFunction< '_ >; - } - - impl CommandNameParserFn for Parser - { - fn command_name_fn() -> CommandNameParserFunction - { - Box::new - ( - move | input : &str | - recognize( pair - ( - alt(( alpha1, tag( "_" ) )), - any_word - ))( input ) - ) - } - } - - impl CommandSubjectParserFn for Parser - { - fn command_subject_fn( &self ) -> CommandSubjectParserFunction< '_ > - { - // ? looks not good - // reason - all words can be `subject` - let prop_delimeter = self.prop_delimeter; - let namespace_delimeter = self.namespace_delimeter.clone(); - - Box::new - ( - move | input : &str | - { - alt - (( - // quoted subject - map( delimited( tag( "\"" ), escaped( none_of( "\\\"" ), '\\', one_of( "\\\"" ) ), tag( "\"" ) ), | s : &str | s.replace( "\\\"", "\"" ).replace( "\\\\", "\\" ) ), - map( delimited( tag( "'" ), escaped( none_of( "\\'" ), '\\', one_of( "\\'" ) ), tag( "'" ) ), | s : &str | s.replace( "\\\'", "'" ).replace( "\\\\", "\\" ) ), - // single word subject - map_opt - ( - any_word, - | word | - { - // if next word - is a command => it isn't subject - // if you want pass command as subject - take it in quotes - let not_a_command = self.command_fn()( word ).is_err(); - if not_a_command && !word.contains( prop_delimeter ) && !word.contains( &*namespace_delimeter ) - { - Some( word.to_owned() ) - } - else - { - None - } - } - ) - ))( input ) - } - ) - } - } - - impl Parser - { - fn propery_name( &self ) -> impl Fn( &str ) -> IResult< &str, &str > - { - let property_delimeter = self.prop_delimeter; - move | input : &str | - recognize( pair - ( - alt(( alpha1, tag( "_" ) )), - take_while( | c : char | !c.is_whitespace() && c != property_delimeter ) - ))( input ) - } - } - - impl CommandPropertyParserFn for Parser - { - fn command_property_fn( &self ) -> CommandPropertyParserFunction< '_ > - { - let property_delimeter = self.prop_delimeter; - Box::new - ( - move | input : &str | - map - ( - tuple - (( - self.propery_name(), - tag( &*format!( "{property_delimeter}" ) ), - alt - (( - // quoted value - map( delimited( tag( "\"" ), escaped( none_of( "\\\"" ), '\\', one_of( "\\\"" ) ), tag( "\"" ) ), | s : &str | s.replace( "\\\"", "\"" ).replace( "\\\\", "\\" ) ), - map( delimited( tag( "'" ), escaped( none_of( "\\'" ), '\\', one_of( "\\'" ) ), tag( "'" ) ), | s : &str | s.replace( "\\\'", "'" ).replace( "\\\\", "\\" ) ), - // single word - map( any_word, | s : &str | s.to_owned() ), - )) - )), - |( name, _, value ) : ( &str, _, _ ) | ( name.to_owned(), value ) - )( input ) - ) - } - } - - impl CommandParserFn for Parser {} - - impl CommandParser for Parser - { - fn command< 'a >( &'a self, input : &'a str ) -> Result< Command > - { - self.command_fn()( input ) - .map( |( _, command )| command ) - .map_err( | _ | err!( "Fail to parse `Command`" ) ) - } + use std::collections::HashMap; + + /// Represents a program that contains one or more namespaces, where each namespace contains a list of commands. + /// + /// A `Program` consists of one or more commannd + /// + /// The program can be executed by iterating over each commands and executing it + // qqq : xxx : for Bohdan : Commands should be here instead of Namespace + // qqq : remove concept Namespace + // qqq : introduce concept Dictionary for grammar + #[ derive( Debug, Clone, PartialEq, Eq ) ] + pub struct Program< Command > + { + /// list of namespaces with commands + pub commands : Vec< Command >, + } + + /// Represents a parsed command that has been extracted from an input string by a `Parser`. + /// + /// The `ParsedCommand` struct is designed to be flexible and allow for a wide variety of commands to be parsed and represented. However, this flexibility also means that a `ParsedCommand` may contain invalid or unexpected data. + /// + /// # Example: + /// + /// ``` + /// # use wca::ParsedCommand; + /// # use std::collections::HashMap; + /// ParsedCommand + /// { + /// name : "command".to_string(), + /// subjects : vec![ "subject_value".to_string(), /* ... */ ], + /// properties : HashMap::from_iter( + /// [ + /// ( "prop_name".to_string(), "raw_prop_value".to_string() ), + /// /* ... */ + /// ]) + /// }; + /// ``` + /// + /// In the above example, a `ParsedCommand` instance is created with the name "command", a single subject "subject_value", and one property "prop_name" with a raw value of "raw_prop_value". + /// + #[ derive( Default, Debug, Clone, PartialEq, Eq ) ] + pub struct ParsedCommand + { + /// name of command without delimiter + pub name : String, + /// list of all subjects for the command + pub subjects : Vec< String >, + /// dictionary of properties. Each property has a name and a raw value + pub properties : HashMap< String, String > } } @@ -240,8 +56,6 @@ pub( crate ) mod private crate::mod_interface! { - exposed use CommandParser; - protected use CommandParserFn; + exposed use Program; + exposed use ParsedCommand; } - -// qqq : use orphan instead of exposed for ALL files in the folder, dont use prelude for structs \ No newline at end of file diff --git a/module/move/wca/src/ca/parser/entities.rs b/module/move/wca/src/ca/parser/entities.rs deleted file mode 100644 index 03a9b1dc28..0000000000 --- a/module/move/wca/src/ca/parser/entities.rs +++ /dev/null @@ -1,61 +0,0 @@ -pub( crate ) mod private -{ - use std::collections::HashMap; - - /// Represents a program that contains one or more namespaces, where each namespace contains a list of commands. - /// - /// A `Program` consists of one or more commannd - /// - /// The program can be executed by iterating over each commands and executing it - // qqq : xxx : for Bohdan : Commands should be here instead of Namespace - // qqq : remove concept Namespace - // qqq : introduce concept Dictionary for grammar - #[ derive( Debug, Clone, PartialEq, Eq ) ] - pub struct Program< Command > - { - /// list of namespaces with commands - pub commands : Vec< Command >, - } - - /// Represents a parsed command that has been extracted from an input string by a `Parser`. - /// - /// The `ParsedCommand` struct is designed to be flexible and allow for a wide variety of commands to be parsed and represented. However, this flexibility also means that a `ParsedCommand` may contain invalid or unexpected data. - /// - /// # Example: - /// - /// ``` - /// # use wca::ParsedCommand; - /// # use std::collections::HashMap; - /// ParsedCommand - /// { - /// name : "command".to_string(), - /// subjects : vec![ "subject_value".to_string(), /* ... */ ], - /// properties : HashMap::from_iter( - /// [ - /// ( "prop_name".to_string(), "raw_prop_value".to_string() ), - /// /* ... */ - /// ]) - /// }; - /// ``` - /// - /// In the above example, a `ParsedCommand` instance is created with the name "command", a single subject "subject_value", and one property "prop_name" with a raw value of "raw_prop_value". - /// - #[ derive( Default, Debug, Clone, PartialEq, Eq ) ] - pub struct ParsedCommand - { - /// name of command without delimiter - pub name : String, - /// list of all subjects for the command - pub subjects : Vec< String >, - /// dictionary of properties. Each property has a name and a raw value - pub properties : HashMap< String, String > - } -} - -// - -crate::mod_interface! -{ - exposed use Program; - exposed use ParsedCommand; -} diff --git a/module/move/wca/src/ca/parser/mod.rs b/module/move/wca/src/ca/parser/mod.rs index 3360410874..47a8d4c325 100644 --- a/module/move/wca/src/ca/parser/mod.rs +++ b/module/move/wca/src/ca/parser/mod.rs @@ -1,11 +1,8 @@ crate::mod_interface! { - /// Parser configuration - layer parser; - /// Implementation for parsing command + /// Parsed command layer command; - /// Implementation for parsing program - layer program; - /// Entities representation to interact with - layer entities; + + /// Parser. + layer parser; } diff --git a/module/move/wca/src/ca/parser/parser.rs b/module/move/wca/src/ca/parser/parser.rs index 58da2393fe..db4768b068 100644 --- a/module/move/wca/src/ca/parser/parser.rs +++ b/module/move/wca/src/ca/parser/parser.rs @@ -1,66 +1,144 @@ -pub( crate ) mod private +mod private { - use std::borrow::Cow; - use former::Former; - use nom:: - { - bytes::complete::take_while, - IResult, - }; + use crate::*; - /// `Parser` provides parsing command strings into `ParsedCommand` objects. - /// It allows you to specify the symbols that will be used to interpret the command string, such as the command delimiter, property delimiter, and namespace delimiter. - /// - /// ``` - /// use wca::{ Parser, CommandParser }; - /// # fn main() -> Result< (), Box< dyn std::error::Error > > { - /// /// Configure the parser - /// let parser = Parser::former() - /// .command_prefix( '.' ) - /// .prop_delimeter( ':' ) - /// .form(); - /// - /// /// Parse a command from a` string - /// let raw_command = parser.command( ".command subject_value prop_name:prop_value" )?; - /// # Ok( () ) } - /// ``` - /// - /// In the above example, a `Parser` object is created and configured to accept commands with a `.` prefix and `:` delimiters for properties. - /// - /// Note that `Parser` uses `CommandParser` trait to parse commands from user input( You can also use `NamespaceParser` to parse namespaces, or `ProgramParser` to parse programs from string ). - /// + use std::collections::HashMap; + + use error_tools::{ Result, return_err }; + + /// `Parser` is a struct used for parsing data. #[ derive( Debug ) ] - #[ derive( Former ) ] - pub struct Parser + pub struct Parser; + + impl Parser { - /// Symbol that will be interpreted as the beginning of a command - /// - /// command_delimiter = `.` + /// Parses a vector of command line arguments and returns a `Program` containing the parsed commands. /// - /// ".command" -> Command( "command" ) - #[ default( '.' ) ] - pub command_prefix : char, - /// Symbol that will be interpreted as a separator for the name and value of the property + /// # Arguments /// - /// prop_delimiter = `:` + /// * `args` - A vector of strings representing the command line arguments. /// - /// "prop:value" -> ( "prop", "value" ) - #[ default( ':' ) ] - pub prop_delimeter : char, - /// String that will be interpreted as a separator for namespaces + /// # Returns /// - /// namespace_delimiter = ".also" - /// - /// "< commands1 > .also < commands2 >" -> Namespace( < commands1 > ), Namespace( < commands2 > ) - #[ default( ".also" ) ] - pub namespace_delimeter : Cow< 'static, str >, - } + /// Returns a `Result` with a `Program` containing the parsed commands if successful, or an error if parsing fails. + pub fn parse< As, A >( &self, args : As ) -> Result< Program< ParsedCommand > > + where + As : IntoIterator< Item = A >, + A : Into< String >, + { + let args = args.into_iter().map( Into::into ).collect::< Vec< _ > >(); + let mut commands = vec![]; + let mut i = 0; + while i < args.len() + { + let ( command, relative_pos ) = Self::parse_command( &args[ i.. ] )?; + i += relative_pos; + commands.push( command ); + } - /// Parses first word from string. All characters before first space - pub fn any_word( input : &str ) -> IResult< &str, &str > - { - let s = take_while( | c : char | !c.is_whitespace() )( input ); - s + Ok( Program { commands } ) + } + + // with dot at the beginning + fn valid_command_name( input : &str ) -> bool + { + if let Some( name ) = input.strip_prefix( '.' ) + { + name.is_empty() || name.starts_with( '?' ) || name.chars().next().is_some_and( | c | c.is_alphanumeric() ) + } + else + { + false + } + } + + // returns ParsedCommand and position of last parsed item + fn parse_command( args : &[ String ] ) -> Result< ( ParsedCommand, usize ) > + { + if args.is_empty() { + return_err!( "Unexpected behaviour: Try to parse command without input" ); + } + + let mut i = 0; + if Self::valid_command_name( &args[ i ] ) + { + let name = args[ i ].strip_prefix( '.' ).unwrap(); + i += 1; + // special situation(internal commands) + let name = if name.is_empty() { "." } else if name == "?" { ".?" } else { name }; + let mut subjects = vec![]; + let mut properties = HashMap::new(); + + let mut properties_turn = false; + while i < args.len() + { + let item = &args[ i ]; + + if Self::valid_command_name( item ) { break; } + + if item.contains( ':' ) + { + properties_turn = true; + let ( name, value ) = item.split_once( ':' ).unwrap(); + // prop:value + if !value.is_empty() + { + properties.insert( name.to_string(), value.to_string() ); + } + // prop: value + else if args.len() > i + 1 + { + properties.insert( name.to_string(), args[ i + 1 ].to_string() ); + i += 1; + } + } + // prop : value | prop :value + else if args.len() > i + 1 && args[ i + 1 ].starts_with( ':' ) + { + // :value + if args[ i + 1 ].len() > 1 + { + properties.insert( args[ i ].clone(), args[ i + 1 ].strip_prefix( ':' ).unwrap().to_string() ); + i += 1; + } + // : value + else if args.len() > i + 2 + { + properties.insert( args[ i ].clone(), args[ i + 2 ].clone() ); + i += 2; + } + else + { + return_err!( "Unexpected input: Expected property value, found EOL" ); + } + } + else if !properties_turn + { + subjects.push( item.to_string() ); + } + else + { + return_err!( "Unexpected input: Expected `subject` or `property`, found: `{}`", item ); + } + i += 1; + } + + return Ok( + ( + ParsedCommand + { + name : name.to_string(), + subjects, + properties, + }, + i, + )) + } + else + { + return_err!( "Unexpected input: Expected a command, found: `{}`", args[ i ] ) + } + } } } @@ -69,5 +147,4 @@ pub( crate ) mod private crate::mod_interface! { exposed use Parser; - protected use any_word; } diff --git a/module/move/wca/src/ca/parser/program.rs b/module/move/wca/src/ca/parser/program.rs deleted file mode 100644 index 5ecdf05a71..0000000000 --- a/module/move/wca/src/ca/parser/program.rs +++ /dev/null @@ -1,73 +0,0 @@ -pub( crate ) mod private -{ - use crate::*; - use { - Program, ParsedCommand, - Parser, - ca::parser::command::CommandParserFn, - wtools::{ error::Result, err }, - }; - use nom:: - { - character::complete::anychar, - combinator::{ map, not }, - multi::many_till, - IResult, - }; - - /// Can parser Programs - pub trait ProgramParser - { - /// Parses program from string - fn program( &self, input : &str ) -> Result< Program< ParsedCommand > >; - } - - type ProgramParserFunction< 'a > = Box< dyn Fn( &str ) -> IResult< &str, Program< ParsedCommand > > + 'a >; - - /// Can be used as function to parse a Namespace - pub( crate ) trait ProgramParserFn : CommandParserFn - { - /// Returns function that can parse a Namespace - fn program_fn( &self ) -> ProgramParserFunction< '_ > - { - Box::new - ( - move | input : &str | - map( many_till - ( - self.command_fn(), - not( anychar ) - ), |( commands, _ )| Program { commands } - )( input ) - ) - } - } - - impl ProgramParserFn for Parser {} - - impl ProgramParser for Parser - { - fn program< 'a >( &'a self, input : &'a str ) -> Result< Program< ParsedCommand > > - { - self.program_fn()( input.trim() ) - .map( |( _, program )| program ) - .map_err - ( - | e | - { - match e - { - nom::Err::Incomplete( _ ) => { err!( "Program has incomplete sentences" ) }, - nom::Err::Error( nom::error::Error { input, .. } ) | nom::Err::Failure( nom::error::Error { input, .. } ) => { err!( "It is a sentence that can not be parsed: `{}`", input ) } - } - } ) - } - } -} - -// - -crate::mod_interface! -{ - exposed use ProgramParser; -} From 8a89a5d06ce09c7c04c5ccce55611aa10cc41505 Mon Sep 17 00:00:00 2001 From: Barsik Date: Mon, 1 Apr 2024 21:22:01 +0300 Subject: [PATCH 2/4] Refactor parser and update tests Major updates have been made to the parser implementation in the testing files. This included the removal of unnecessary parser functions and making adjustments to reflect the new parsing structure. The changes mainly revolve around parsing commands and properties with spaces. Tests were updated to ensure compatibility with these changes. --- .../tests/inc/commands_aggregator/basic.rs | 81 ++----- module/move/wca/tests/inc/executor/command.rs | 26 +-- module/move/wca/tests/inc/executor/mod.rs | 1 - module/move/wca/tests/inc/executor/program.rs | 10 +- .../wca/tests/inc/grammar/from_command.rs | 74 +++--- .../wca/tests/inc/grammar/from_program.rs | 6 +- module/move/wca/tests/inc/grammar/mod.rs | 1 - module/move/wca/tests/inc/parser/command.rs | 214 +++++++----------- module/move/wca/tests/inc/parser/mod.rs | 1 - module/move/wca/tests/inc/parser/program.rs | 6 +- 10 files changed, 171 insertions(+), 249 deletions(-) diff --git a/module/move/wca/tests/inc/commands_aggregator/basic.rs b/module/move/wca/tests/inc/commands_aggregator/basic.rs index 1b93ea7b79..f7019bebf6 100644 --- a/module/move/wca/tests/inc/commands_aggregator/basic.rs +++ b/module/move/wca/tests/inc/commands_aggregator/basic.rs @@ -1,4 +1,5 @@ use super::*; +use the_module::VerifiedCommand; // @@ -35,24 +36,6 @@ tests_impls! a_true!( ca.perform( ".help.command" ).is_err() ); } - fn custom_parser() - { - let parser = Parser::former() - .command_prefix( '-' ) - .form(); - - let ca = CommandsAggregator::former() - .parser( parser ) - .command( "command" ) - .hint( "hint" ) - .long_hint( "long_hint" ) - .routine( || println!( "command" ) ) - .end() - .perform(); - - a_id!( (), ca.perform( "-command" ).unwrap() ); - } - fn dot_command() { let ca = CommandsAggregator::former() @@ -134,7 +117,7 @@ tests_impls! .end() .perform(); - let command = r#".command "./path:to_dir" "#; + let command = vec![ ".command".into(), "./path:to_dir".into() ]; a_id!( (), ca.perform( command ).unwrap() ); @@ -166,14 +149,11 @@ tests_impls! .form() ) .perform(); - let parser = Parser::former().form(); - use the_module::CommandParser; + let parser = Parser; let grammar = the_module::Verifier; let executor = the_module::Executor::former().form(); - let command = r#".command qwe:rty nightly:true "#; - - let raw_command = parser.command( command ).unwrap(); + let raw_command = parser.parse( [ ".command", "qwe:rty", "nightly:true" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); a_id!( grammar_command.args.0, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); @@ -196,14 +176,11 @@ tests_impls! ) .form(); - let command = r#".command qwe:rty"#; - - let parser = Parser::former().form(); - use the_module::CommandParser; + let parser = Parser; let grammar = the_module::Verifier; let executor = the_module::Executor::former().form(); - let raw_command = parser.command( command ).unwrap(); + let raw_command = parser.parse( [ ".command", "qwe:rty" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); a_id!( grammar_command.args.0, vec![ the_module::Value::String( "qwe:rty".into() ) ] ); @@ -227,14 +204,11 @@ tests_impls! ) .form(); - let command = r#".command qwe:rty"#; - - let parser = Parser::former().form(); - use the_module::CommandParser; + let parser = Parser; let grammar = the_module::Verifier; let executor = the_module::Executor::former().form(); - let raw_command = parser.command( command ).unwrap(); + let raw_command = parser.parse( [ ".command", "qwe:rty" ] ).unwrap().commands.remove( 0 ); let grammar_command = grammar.to_command( dictionary, raw_command ).unwrap(); a_id!( grammar_command.args.0, vec![ the_module::Value::String("qwe:rty".into()) ] ); @@ -243,27 +217,21 @@ tests_impls! } // qqq : make the following test work - // fn subject_with_spaces() - // { - // let query = "SELECT title, links, MIN( published ) FROM Frames"; - // let ca = CommandsAggregator::former() - // .grammar( - // [ - // wca::Command::former() - // .hint( "hint" ) - // .long_hint( "long_hint" ) - // .phrase( "query.execute" ) - // .subject( "SQL query", Type::String, false ) - // .form(), - // ]) - // .executor( - // [ - // ( "query.execute".to_owned(), Routine::new( move |( args, _ )| { assert_eq!( query, args.get_owned::< &str >( 0 ).unwrap() ); Ok( () ) } ) ), - // ]) - // .build(); - - // a_id!( (), ca.perform( vec![ ".query.execute".to_string(), query.into() ] ).unwrap() ); - // } + fn subject_with_spaces() + { + let query = "SELECT title, links, MIN( published ) FROM Frames"; + + let ca = CommandsAggregator::former() + .command( "query.execute" ) + .hint( "hint" ) + .long_hint( "long_hint" ) + .subject().hint( "SQL query" ).kind( Type::String ).optional( false ).end() + .routine( move | o : VerifiedCommand | assert_eq!( query, o.args.get_owned::< &str >( 0 ).unwrap() ) ) + .end() + .perform(); + + a_id!( (), ca.perform( vec![ ".query.execute".to_string(), query.into() ] ).unwrap() ); + } } // @@ -272,12 +240,11 @@ tests_index! { simple, with_only_general_help, - custom_parser, dot_command, error_types, path_subject_with_colon, string_subject_with_colon, no_prop_subject_with_colon, optional_prop_subject_with_colon, - // subject_with_spaces, + subject_with_spaces, } diff --git a/module/move/wca/tests/inc/executor/command.rs b/module/move/wca/tests/inc/executor/command.rs index 095a0f5a13..b1dcf7ac12 100644 --- a/module/move/wca/tests/inc/executor/command.rs +++ b/module/move/wca/tests/inc/executor/command.rs @@ -8,7 +8,7 @@ tests_impls! fn basic() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -25,7 +25,7 @@ tests_impls! let verifier = Verifier; // init executor - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); let executor = Executor::former().form(); @@ -36,7 +36,7 @@ tests_impls! fn with_subject() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -57,14 +57,14 @@ tests_impls! let executor = Executor::former().form(); // with subject - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // execute the command a_true!( executor.command( dictionary, grammar_command ).is_ok() ); // without subject - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); } @@ -72,7 +72,7 @@ tests_impls! fn with_property() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -93,19 +93,19 @@ tests_impls! let executor = Executor::former().form(); // with property - let raw_command = parser.command( ".command prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // execute the command a_true!( executor.command( dictionary, grammar_command ).is_ok() ); // with subject and without property - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // with subject and with property - let raw_command = parser.command( ".command subject prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); } @@ -115,7 +115,7 @@ tests_impls! use std::sync::{ Arc, Mutex }; // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -143,7 +143,7 @@ tests_impls! .context( ctx ) .form(); - let raw_command = parser.command( ".check" ).unwrap(); + let raw_command = parser.parse( [ ".check" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // execute the command @@ -154,7 +154,7 @@ tests_impls! fn without_routine() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -172,7 +172,7 @@ tests_impls! // init executor let executor = Executor::former().form(); - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( executor.command( dictionary, grammar_command ).is_err() ); diff --git a/module/move/wca/tests/inc/executor/mod.rs b/module/move/wca/tests/inc/executor/mod.rs index 162ddf8904..6b2906031a 100644 --- a/module/move/wca/tests/inc/executor/mod.rs +++ b/module/move/wca/tests/inc/executor/mod.rs @@ -2,7 +2,6 @@ use super::*; use the_module:: { Parser, - ProgramParser, CommandParser, Context, Type, Dictionary, diff --git a/module/move/wca/tests/inc/executor/program.rs b/module/move/wca/tests/inc/executor/program.rs index dfbccf227b..885d2e017e 100644 --- a/module/move/wca/tests/inc/executor/program.rs +++ b/module/move/wca/tests/inc/executor/program.rs @@ -8,7 +8,7 @@ tests_impls! fn basic() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -28,7 +28,7 @@ tests_impls! let executor = Executor::former().form(); // existed command | unknown command will fail on converter - let raw_program = parser.program( ".command" ).unwrap(); + let raw_program = parser.parse( [ ".command" ] ).unwrap(); let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); // execute the command @@ -41,7 +41,7 @@ tests_impls! use wtools::error::for_app::Error; // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -98,13 +98,13 @@ tests_impls! .form(); // value in context = 0 - let raw_program = parser.program( ".eq 1" ).unwrap(); + let raw_program = parser.parse( [ ".eq", "1" ] ).unwrap(); let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); a_true!( executor.program( dictionary, grammar_program ).is_err() ); // value in context = 1 + 1 + 1 = 3 - let raw_program = parser.program( ".eq 0 .inc .inc .eq 2" ).unwrap(); + let raw_program = parser.parse( [ ".eq", "0", ".inc", ".inc", ".eq", "2" ] ).unwrap(); let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); a_true!( executor.program( dictionary, grammar_program ).is_ok() ); diff --git a/module/move/wca/tests/inc/grammar/from_command.rs b/module/move/wca/tests/inc/grammar/from_command.rs index 5ac6ec20cc..9823236c0c 100644 --- a/module/move/wca/tests/inc/grammar/from_command.rs +++ b/module/move/wca/tests/inc/grammar/from_command.rs @@ -7,7 +7,7 @@ tests_impls! fn command_validation() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -23,25 +23,25 @@ tests_impls! let verifier = Verifier; // existed command - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // not existed command - let raw_command = parser.command( ".invalid_command" ).unwrap(); + let raw_command = parser.parse( [ ".invalid_command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // invalid command syntax - let raw_command = parser.command( "invalid_command" ); + let raw_command = parser.parse( [ "invalid_command" ] ); a_true!( raw_command.is_err() ); } fn subjects() { // init parser - let parser = Parser::former().form(); + let parser = Parser; let dictionary = &Dictionary::former() .command ( @@ -58,25 +58,25 @@ tests_impls! let verifier = Verifier; // with only one subject - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_id!( vec![ Value::String( "subject".to_string() ) ], grammar_command.args.0 ); a_true!( grammar_command.props.is_empty() ); // with more subjects that it is set - let raw_command = parser.command( ".command subject1 subject2" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject1", "subject2" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // with subject and property that isn't declared - let raw_command = parser.command( ".command subject prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands.remove( 0 ); a_true!( verifier.to_command( dictionary, raw_command ).is_err() ); // subject with colon when property not declared - let raw_command = parser.command( ".command prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_id!( vec![ Value::String( "prop:value".to_string() ) ], grammar_command.args.0 ); @@ -86,7 +86,7 @@ tests_impls! fn subject_type_check() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -103,19 +103,19 @@ tests_impls! let verifier = Verifier; // string when number expected - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // valid negative float number when number expected - let raw_command = parser.command( ".command -3.14" ).unwrap(); + let raw_command = parser.parse( [ ".command", "-3.14" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); } fn subject_with_list() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -132,7 +132,7 @@ tests_impls! let verifier = Verifier; // with only one subject - let raw_command = parser.command( ".command first_subject,second_subject,third_subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "first_subject,second_subject,third_subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( &dictionary, raw_command ).unwrap(); a_id!( vec! @@ -150,7 +150,7 @@ tests_impls! fn subject_is_optional_basic() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -167,18 +167,18 @@ tests_impls! let verifier = Verifier; // with subject - let raw_command = parser.command( ".command subject" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // without subject - let raw_command = parser.command( ".command" ).unwrap(); + let raw_command = parser.parse( [ ".command" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); } fn preferred_non_optional_first_order() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -196,20 +196,20 @@ tests_impls! let verifier = Verifier; // second subject is required, but missing - let raw_command = parser.command( ".command 42" ).unwrap(); + let raw_command = parser.parse( [ ".command", "42" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err(), "subject identifies as first subject" ); // first subject is missing - let raw_command = parser.command( ".command valid_string" ).unwrap(); + let raw_command = parser.parse( [ ".command", "valid_string" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // both subjects exists - let raw_command = parser.command( ".command 42 string" ).unwrap(); + let raw_command = parser.parse( [ ".command", "42", "string" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); // first subject not a number, but both arguments exists - let raw_command = parser.command( ".command not_a_number string" ).unwrap(); + let raw_command = parser.parse( [ ".command", "not_a_number", "string" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err(), "first subject not a number" ); } @@ -217,7 +217,7 @@ tests_impls! fn properties() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -234,26 +234,26 @@ tests_impls! let verifier = Verifier; // with only one property - let raw_command = parser.command( ".command prop1:value1" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop1:value1" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "value1".to_string() ) ) ]), grammar_command.props.0 ); // with property re-write - let raw_command = parser.command( ".command prop1:value prop1:another_value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop1:value", "prop1:another_value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); a_id!( HashMap::from_iter([ ( "prop1".to_string(), Value::String( "another_value".to_string() ) ) ]), grammar_command.props.0 ); // with undeclareted property - let raw_command = parser.command( ".command undeclareted_prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "undeclareted_prop:value" ] ).unwrap().commands.remove( 0 ); a_true!( verifier.to_command( dictionary, raw_command ).is_err() ); // with undeclareted subject - let raw_command = parser.command( ".command subject prop1:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "subject", "prop1:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); @@ -262,7 +262,7 @@ tests_impls! fn property_type_check() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -279,19 +279,19 @@ tests_impls! let verifier = Verifier; // string when number expected - let raw_command = parser.command( ".command prop:Property" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:Property" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ); a_true!( grammar_command.is_err() ); // valid negative float number when number expected - let raw_command = parser.command( ".command prop:-3.14" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:-3.14" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); } fn property_with_list() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -308,7 +308,7 @@ tests_impls! let verifier = Verifier; // with only one subject - let raw_command = parser.command( ".command prop:1,2,3" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:1,2,3" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); @@ -322,7 +322,7 @@ tests_impls! fn alias_property() { // init parser - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -345,21 +345,21 @@ tests_impls! let verifier = Verifier; // basic - let raw_command = parser.command( ".command property:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "property:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); // first alias - let raw_command = parser.command( ".command prop:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "prop:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); a_id!( HashMap::from_iter([ ( "property".to_string(), Value::String( "value".to_string() ) ) ]), grammar_command.props.0 ); // second alias - let raw_command = parser.command( ".command p:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "p:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); @@ -380,7 +380,7 @@ tests_impls! .form(); let verifier = Verifier; - let raw_command = parser.command( ".command p:value" ).unwrap(); + let raw_command = parser.parse( [ ".command", "p:value" ] ).unwrap().commands.remove( 0 ); let grammar_command = verifier.to_command( dictionary, raw_command ).unwrap(); a_true!( grammar_command.args.0.is_empty() ); diff --git a/module/move/wca/tests/inc/grammar/from_program.rs b/module/move/wca/tests/inc/grammar/from_program.rs index 7e478faf14..670eaf178c 100644 --- a/module/move/wca/tests/inc/grammar/from_program.rs +++ b/module/move/wca/tests/inc/grammar/from_program.rs @@ -6,7 +6,7 @@ tests_impls! { fn basic() { - let parser = Parser::former().form(); + let parser = Parser; // init converter let dictionary = &Dictionary::former() @@ -32,7 +32,7 @@ tests_impls! let verifier = Verifier; // parse program with only one command - let raw_program = parser.program( ".command1 subject" ).unwrap(); + let raw_program = parser.parse( [ ".command1", "subject" ] ).unwrap(); // convert program let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); @@ -40,7 +40,7 @@ tests_impls! a_id!( vec![ Value::String( "subject".to_string() ) ], grammar_program.commands[ 0 ].args.0 ); // parse program several commands - let raw_program = parser.program( ".command1 first_subj .command2 second_subj" ).unwrap(); + let raw_program = parser.parse( [ ".command1", "first_subj", ".command2", "second_subj" ] ).unwrap(); // convert program let grammar_program = verifier.to_program( dictionary, raw_program ).unwrap(); diff --git a/module/move/wca/tests/inc/grammar/mod.rs b/module/move/wca/tests/inc/grammar/mod.rs index 679a096afe..38c94dc114 100644 --- a/module/move/wca/tests/inc/grammar/mod.rs +++ b/module/move/wca/tests/inc/grammar/mod.rs @@ -2,7 +2,6 @@ use super::*; use the_module:: { Parser, - ProgramParser, CommandParser, Type, Value, Dictionary, diff --git a/module/move/wca/tests/inc/parser/command.rs b/module/move/wca/tests/inc/parser/command.rs index ff75539190..986ab1d0c0 100644 --- a/module/move/wca/tests/inc/parser/command.rs +++ b/module/move/wca/tests/inc/parser/command.rs @@ -6,7 +6,7 @@ tests_impls! { fn basic() { - let parser = Parser::former().form(); + let parser = Parser; // only command a_id! @@ -17,7 +17,7 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( ".command" ).unwrap() + parser.parse( [ ".command" ] ).unwrap().commands[ 0 ] ); // command with one subject @@ -29,7 +29,7 @@ tests_impls! subjects : vec![ "subject".into() ], properties : HashMap::new(), }, - parser.command( ".command subject" ).unwrap() + parser.parse( [ ".command", "subject" ] ).unwrap().commands[ 0 ] ); // command with many subjects @@ -41,7 +41,7 @@ tests_impls! subjects : vec![ "subject1".into(), "subject2".into(), "subject3".into() ], properties : HashMap::new(), }, - parser.command( ".command subject1 subject2 subject3" ).unwrap() + parser.parse( [ ".command", "subject1", "subject2", "subject3" ] ).unwrap().commands[ 0 ] ); // command with one property @@ -53,7 +53,7 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), }, - parser.command( ".command prop:value" ).unwrap() + parser.parse( [ ".command", "prop:value" ] ).unwrap().commands[ 0 ] ); // command with many properties @@ -70,7 +70,7 @@ tests_impls! ( "prop3".into(), "value3".into() ) ]), }, - parser.command( ".command prop1:value1 prop2:value2 prop3:value3" ).unwrap() + parser.parse( [ ".command", "prop1:value1", "prop2:value2", "prop3:value3" ] ).unwrap().commands[ 0 ] ); // command with one subject and one property @@ -82,7 +82,7 @@ tests_impls! subjects : vec![ "subject".into() ], properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), }, - parser.command( ".command subject prop:value" ).unwrap() + parser.parse( [ ".command", "subject", "prop:value" ] ).unwrap().commands[ 0 ] ); // command with many subjects and many properties @@ -104,23 +104,25 @@ tests_impls! ( "prop3".into(), "value3".into() ), ]), }, - parser.command( ".command subject1 subject2 subject3 prop1:value1 prop2:value2 prop3:value3" ).unwrap() + parser.parse( [ ".command", "subject1", "subject2", "subject3", "prop1:value1", "prop2:value2", "prop3:value3" ] ).unwrap().commands[ 0 ] ); } - fn with_spaces() + // aaa : the parser must be able to accept a list of arguments(std::env::args()) + // aaa : yep + fn with_spaces_in_value() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( ParsedCommand { name : "command".into(), - subjects : vec![], + subjects : vec![ "value with spaces".into() ], properties : HashMap::new(), }, - parser.command( " .command " ).unwrap() + parser.parse( [ ".command", "value with spaces" ] ).unwrap().commands[ 0 ] ); a_id! @@ -128,10 +130,10 @@ tests_impls! ParsedCommand { name : "command".into(), - subjects : vec![ "subject".into() ], - properties : HashMap::new(), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), }, - parser.command( " .command subject " ).unwrap() + parser.parse( [ ".command", "prop:value with spaces" ] ).unwrap().commands[ 0 ] ); a_id! @@ -139,66 +141,38 @@ tests_impls! ParsedCommand { name : "command".into(), - subjects : vec![ "subject".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), }, - parser.command( " .command subject prop:value " ).unwrap() + parser.parse( [ ".command", "prop:", "value with spaces" ] ).unwrap().commands[ 0 ] + ); + + a_id! + ( + ParsedCommand + { + name : "command".into(), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + }, + parser.parse( [ ".command", "prop", ":value with spaces" ] ).unwrap().commands[ 0 ] ); - } - // qqq : the parser must be able to accept a list of arguments(std::env::args()) - // fn with_spaces_in_value() - // { - // let parser = Parser::former().form(); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![ "value with spaces".into() ], - // properties : HashMap::new(), - // }, - // parser.command( vec![ ".command".to_string(), "value with spaces".into() ] ).unwrap() - // ); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![], - // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), - // }, - // parser.command( vec![ ".command".to_string(), "prop:value with spaces".into() ] ).unwrap() - // ); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![], - // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), - // }, - // parser.command( vec![ ".command".to_string(), "prop:".into(), "value with spaces".into() ] ).unwrap() - // ); - - // a_id! - // ( - // ParsedCommand - // { - // name : "command".into(), - // subjects : vec![], - // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), - // }, - // parser.command( vec![ ".command".to_string(), "prop".into(), ":".into(), "value with spaces".into() ] ).unwrap() - // ); - // } + a_id! + ( + ParsedCommand + { + name : "command".into(), + subjects : vec![], + properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + }, + parser.parse( [ ".command", "prop", ":", "value with spaces" ] ).unwrap().commands[ 0 ] + ); + } fn not_only_alphanumeric_symbols() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -208,7 +182,7 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( ".additional_command" ).unwrap() + parser.parse( [ ".additional_command" ] ).unwrap().commands[ 0 ] ); a_id! @@ -219,7 +193,7 @@ tests_impls! subjects : vec![ "subj_ect".into() ], properties : HashMap::new(), }, - parser.command( ".command.sub_command subj_ect" ).unwrap() + parser.parse( [ ".command.sub_command", "subj_ect" ] ).unwrap().commands[ 0 ] ); a_id! @@ -230,32 +204,13 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "long_prop".into(), "some-value".into() ) ]), }, - parser.command( ".command long_prop:some-value" ).unwrap() - ); - } - - fn same_command_and_prop_delimeter() - { - let parser = Parser::former() - .command_prefix( '-' ) - .prop_delimeter( '-' ) - .form(); - - a_id! - ( - ParsedCommand - { - name : "command".into(), - subjects : vec![ "subject".into() ], - properties : HashMap::from_iter([ ( "prop".into(), "value".into() ) ]), - }, - parser.command( "-command subject prop-value" ).unwrap() + parser.parse( [ ".command", "long_prop:some-value" ] ).unwrap().commands[ 0 ] ); } fn path_in_subject() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -265,7 +220,7 @@ tests_impls! subjects : vec![ "/absolute/path/to/something".into() ], properties : HashMap::new(), }, - parser.command( ".command /absolute/path/to/something" ).unwrap() + parser.parse( [ ".command", "/absolute/path/to/something" ] ).unwrap().commands[ 0 ] ); a_id! @@ -276,13 +231,13 @@ tests_impls! subjects : vec![ "./path/to/something".into() ], properties : HashMap::new(), }, - parser.command( ".command ./path/to/something" ).unwrap() + parser.parse( [ ".command", "./path/to/something" ] ).unwrap().commands[ 0 ] ); } fn path_in_property() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -292,7 +247,7 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "path".into(), "/absolute/path/to/something".into() ) ]), }, - parser.command( ".command path:/absolute/path/to/something" ).unwrap() + parser.parse( [ ".command", "path:/absolute/path/to/something" ] ).unwrap().commands[ 0 ] ); a_id! @@ -303,7 +258,7 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "path".into(), "./path/to/something".into() ) ]), }, - parser.command( ".command path:./path/to/something" ).unwrap() + parser.parse( [ ".command", "path:./path/to/something" ] ).unwrap().commands[ 0 ] ); a_id! @@ -314,28 +269,13 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "path".into(), "../path/to/something".into() ) ]), }, - parser.command( ".command path:../path/to/something" ).unwrap() - ); - - let parser = Parser::former() - .command_prefix( '/' ) - .form(); - - a_id! - ( - ParsedCommand - { - name : "command".into(), - subjects : vec![], - properties : HashMap::from_iter([ ( "path".into(), "/absolute/path/to/something".into() ) ]), - }, - parser.command( "/command path:/absolute/path/to/something" ).unwrap() + parser.parse( [ ".command", "path:../path/to/something" ] ).unwrap().commands[ 0 ] ); } fn list_in_property() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -345,13 +285,13 @@ tests_impls! subjects : vec![], properties : HashMap::from_iter([ ( "list".into(), "[1,2,3]".into() ) ]), }, - parser.command( ".command list:[1,2,3]" ).unwrap() + parser.parse( [ ".command", "list:[1,2,3]" ] ).unwrap().commands[ 0 ] ); } fn string_value() { - let parser = Parser::former().form(); + let parser = Parser; a_id! ( @@ -361,7 +301,7 @@ tests_impls! subjects : vec![ "subject with spaces".into() ], properties : HashMap::from_iter([ ( "prop".into(), "property with spaces".into() ) ]), }, - parser.command( r#".command "subject with spaces" prop:"property with spaces""# ).unwrap() + parser.parse( [ ".command", "subject with spaces", "prop:property with spaces" ] ).unwrap().commands[ 0 ] ); // command in subject and property @@ -370,10 +310,10 @@ tests_impls! ParsedCommand { name : "command".into(), - subjects : vec![ ".command".into() ], + subjects : vec![ "\\.command".into() ], properties : HashMap::from_iter([ ( "prop".into(), ".command".into() ) ]), }, - parser.command( r#".command ".command" prop:".command""# ).unwrap() + parser.parse( [ ".command", "\\.command", "prop:.command" ] ).unwrap().commands[ 0 ] ); // with escaped quetes @@ -385,15 +325,14 @@ tests_impls! subjects : vec![ "' queted ' \\ value".into() ], properties : HashMap::from_iter([ ( "prop".into(), "some \"quetes\" ' \\ in string".into() ) ]), }, - parser.command( r#".command '\' queted \' \\ value' prop:"some \"quetes\" ' \\ in string""# ).unwrap() + parser.parse( [ ".command", "\' queted \' \\ value", "prop:some \"quetes\" ' \\ in string" ] ).unwrap().commands[ 0 ] ); } fn dot_command() { - let parser = Parser::former().form(); + let parser = Parser; - // single dot a_id! ( ParsedCommand @@ -402,10 +341,9 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( "." ).unwrap() + parser.parse( [ "." ] ).unwrap().commands[ 0 ] ); - // command . a_id! ( ParsedCommand @@ -414,7 +352,29 @@ tests_impls! subjects : vec![], properties : HashMap::new(), }, - parser.command( ".command." ).unwrap() + parser.parse( [ ".command." ] ).unwrap().commands[ 0 ] + ); + + a_id! + ( + ParsedCommand + { + name : ".?".into(), + subjects : vec![], + properties : HashMap::new(), + }, + parser.parse( [ ".?" ] ).unwrap().commands[ 0 ] + ); + + a_id! + ( + ParsedCommand + { + name : "command.?".into(), + subjects : vec![], + properties : HashMap::new(), + }, + parser.parse( [ ".command.?" ] ).unwrap().commands[ 0 ] ); } } @@ -424,10 +384,8 @@ tests_impls! tests_index! { basic, - with_spaces, - // with_spaces_in_value, + with_spaces_in_value, not_only_alphanumeric_symbols, - same_command_and_prop_delimeter, path_in_subject, path_in_property, list_in_property, diff --git a/module/move/wca/tests/inc/parser/mod.rs b/module/move/wca/tests/inc/parser/mod.rs index 103789b48e..456679d11a 100644 --- a/module/move/wca/tests/inc/parser/mod.rs +++ b/module/move/wca/tests/inc/parser/mod.rs @@ -4,7 +4,6 @@ use wca:: Program, ParsedCommand, Parser, - ProgramParser, CommandParser, }; mod command; diff --git a/module/move/wca/tests/inc/parser/program.rs b/module/move/wca/tests/inc/parser/program.rs index 39dbb64d20..081f8cc3e8 100644 --- a/module/move/wca/tests/inc/parser/program.rs +++ b/module/move/wca/tests/inc/parser/program.rs @@ -6,7 +6,7 @@ tests_impls! { fn basic() { - let parser = Parser::former().form(); + let parser = Parser; // only one command a_id! @@ -20,7 +20,7 @@ tests_impls! properties : HashMap::new(), }, ]}, - parser.program( ".command" ).unwrap() + parser.parse( [ ".command" ] ).unwrap() ); a_id! @@ -46,7 +46,7 @@ tests_impls! properties : HashMap::new(), } ]}, - parser.program( ".command1 .command2 .command3" ).unwrap() + parser.parse( [ ".command1", ".command2", ".command3" ] ).unwrap() ); } } From b43c7f0a020925c65e41eb862993adab46b46b99 Mon Sep 17 00:00:00 2001 From: Barsik Date: Mon, 1 Apr 2024 22:32:38 +0300 Subject: [PATCH 3/4] Refactor implementation of parser in 'parser.rs' The parser code in 'parser.rs' has been updated to enhance error handling and streamline the logic for parsing commands and their arguments. This includes better categorization of inputs as either commands, subjects, or properties, improved clarity of error messages, and the refactoring of the code structure itself for maintainability and increased efficiency. --- module/move/wca/src/ca/parser/parser.rs | 146 +++++++++++++----------- 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/module/move/wca/src/ca/parser/parser.rs b/module/move/wca/src/ca/parser/parser.rs index db4768b068..a952e6427e 100644 --- a/module/move/wca/src/ca/parser/parser.rs +++ b/module/move/wca/src/ca/parser/parser.rs @@ -52,7 +52,7 @@ mod private } } - // returns ParsedCommand and position of last parsed item + // returns ParsedCommand and relative position of the last parsed item fn parse_command( args : &[ String ] ) -> Result< ( ParsedCommand, usize ) > { if args.is_empty() { @@ -60,84 +60,100 @@ mod private } let mut i = 0; - if Self::valid_command_name( &args[ i ] ) + + if !Self::valid_command_name( &args[ i ] ) { - let name = args[ i ].strip_prefix( '.' ).unwrap(); - i += 1; - // special situation(internal commands) - let name = if name.is_empty() { "." } else if name == "?" { ".?" } else { name }; - let mut subjects = vec![]; - let mut properties = HashMap::new(); + return_err!( "Unexpected input: Expected a command, found: `{}`", args[ i ] ); + } + let name = match args[ i ].strip_prefix( '.' ).unwrap() + { + "" => ".", + "?" => ".?", + other => other, + }; + i += 1; + let ( subjects, properties, relative_pos ) = Self::parse_command_args( &args[ i .. ] )?; + + i += relative_pos; - let mut properties_turn = false; - while i < args.len() + return Ok( + ( + ParsedCommand { - let item = &args[ i ]; - - if Self::valid_command_name( item ) { break; } - - if item.contains( ':' ) + name : name.to_string(), + subjects, + properties, + }, + i, + )) + } + + // returns ( subjects, properties, relative_end_pos ) + fn parse_command_args( args : &[ String ] ) -> Result< ( Vec< String >, HashMap< String, String >, usize ) > + { + let mut i = 0; + + let mut subjects = vec![]; + let mut properties = HashMap::new(); + + let mut properties_turn = false; + while i < args.len() + { + let item = &args[ i ]; + + if Self::valid_command_name( item ) { break; } + + if item.contains( ':' ) + { + properties_turn = true; + let ( name, value ) = item.split_once( ':' ).unwrap(); + // prop:value + if !value.is_empty() { - properties_turn = true; - let ( name, value ) = item.split_once( ':' ).unwrap(); - // prop:value - if !value.is_empty() - { - properties.insert( name.to_string(), value.to_string() ); - } - // prop: value - else if args.len() > i + 1 - { - properties.insert( name.to_string(), args[ i + 1 ].to_string() ); - i += 1; - } + properties.insert( name.to_string(), value.to_string() ); } - // prop : value | prop :value - else if args.len() > i + 1 && args[ i + 1 ].starts_with( ':' ) + // prop: value + else if args.len() > i + 1 { - // :value - if args[ i + 1 ].len() > 1 - { - properties.insert( args[ i ].clone(), args[ i + 1 ].strip_prefix( ':' ).unwrap().to_string() ); - i += 1; - } - // : value - else if args.len() > i + 2 - { - properties.insert( args[ i ].clone(), args[ i + 2 ].clone() ); - i += 2; - } - else - { - return_err!( "Unexpected input: Expected property value, found EOL" ); - } + properties.insert( name.to_string(), args[ i + 1 ].to_string() ); + i += 1; } - else if !properties_turn + // we can identify this as a subject, can't we? + // prop: + else + { + return_err!( "Unexpected input '{}': Detected a possible property key preceding the ':' character. However, no corresponding value was found.", item ); + } + } + // prop : value | prop :value + else if args.len() > i + 1 && args[ i + 1 ].starts_with( ':' ) + { + // :value + if args[ i + 1 ].len() > 1 + { + properties.insert( args[ i ].clone(), args[ i + 1 ].strip_prefix( ':' ).unwrap().to_string() ); + i += 1; + } + // : value + else if args.len() > i + 2 { - subjects.push( item.to_string() ); + properties.insert( args[ i ].clone(), args[ i + 2 ].clone() ); + i += 2; } + // : else { - return_err!( "Unexpected input: Expected `subject` or `property`, found: `{}`", item ); + return_err!( "Unexpected input '{} :': Detected a possible property key preceding the ':' character. However, no corresponding value was found.", item ); } - i += 1; } - - return Ok( - ( - ParsedCommand - { - name : name.to_string(), - subjects, - properties, - }, - i, - )) - } - else - { - return_err!( "Unexpected input: Expected a command, found: `{}`", args[ i ] ) + + else if !properties_turn { subjects.push( item.to_string() ); } + + else { return_err!( "Unexpected input: Expected `command` or `property`, found: `{}`", item ); } + i += 1; } + + Ok(( subjects, properties, i )) } } } From bce653aa2bdfc738e6f6872b7989b12e33336fae Mon Sep 17 00:00:00 2001 From: Barsik Date: Mon, 1 Apr 2024 22:41:43 +0300 Subject: [PATCH 4/4] Updated unitore module to accept single SQL query The change in the module/move/unitore/src/executor/mod.rs allows for a single SQL query string as an argument, rather than a list of strings. Now, the 'subject().hint( "Query" )' method call supports Type::String. --- module/move/unitore/src/executor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/move/unitore/src/executor/mod.rs b/module/move/unitore/src/executor/mod.rs index 9acbf0446b..97c5601448 100644 --- a/module/move/unitore/src/executor/mod.rs +++ b/module/move/unitore/src/executor/mod.rs @@ -197,7 +197,7 @@ pub fn execute() -> Result< (), Box< dyn std::error::Error + Send + Sync > > r#" .query.execute \'SELECT title, links, MIN\(published\) FROM frame\'"#, "\n\n", )) - .subject().hint( "Query" ).kind( Type::List( Type::String.into(), ' ' ) ).optional( false ).end() + .subject().hint( "Query" ).kind( Type::String ).optional( false ).end() .routine( | o : VerifiedCommand | { match action( execute_query, &o.args )