Skip to content

Commit

Permalink
Merge pull request #1278 from Barsik-sus/wca_parser_re_work
Browse files Browse the repository at this point in the history
READY : (wca): Extend Parser Functionality to Handle Command Line Arguments
  • Loading branch information
Wandalen authored Apr 3, 2024
2 parents 99a5427 + bce653a commit a30e97d
Show file tree
Hide file tree
Showing 19 changed files with 384 additions and 695 deletions.
2 changes: 1 addition & 1 deletion module/move/unitore/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
1 change: 0 additions & 1 deletion module/move/wca/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 4 additions & 6 deletions module/move/wca/src/ca/aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -101,7 +100,7 @@ pub( crate ) mod private
#[ default( Dictionary::default() ) ]
dictionary : Dictionary,

#[ default( Parser::former().form() ) ]
#[ default( Parser ) ]
parser : Parser,

#[ setter( false ) ]
Expand Down Expand Up @@ -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 ) )
Expand Down
10 changes: 5 additions & 5 deletions module/move/wca/src/ca/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand Down Expand Up @@ -51,23 +51,23 @@ pub( crate ) mod private

fn into_input( self ) -> Input
{
Input( self.to_string() )
Input( self.split( ' ' ).map( ToString::to_string ).collect() )
}
}

impl IntoInput for String
{
fn into_input( self ) -> Input
{
Input( self )
Input( self.split( ' ' ).map( ToString::to_string ).collect() )
}
}

impl IntoInput for Vec< String >
{
fn into_input( self ) -> Input
{
Input( self.join( " " ) )
Input( self )
}
}

Expand Down
288 changes: 51 additions & 237 deletions module/move/wca/src/ca/parser/command.rs
Original file line number Diff line number Diff line change
@@ -1,247 +1,61 @@
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 >
}
}

//

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
Loading

0 comments on commit a30e97d

Please sign in to comment.