From 10e9feeced1e71dbd332cdbdceabed1d4992261d Mon Sep 17 00:00:00 2001 From: Clay McLeod Date: Wed, 8 Nov 2023 14:44:54 -0600 Subject: [PATCH] revise: makes rule matching dynamic --- Cargo.lock | 21 +++++ Cargo.toml | 4 +- wdl-grammar/Cargo.toml | 1 + .../src/bin/wdl-grammar-create-test.rs | 86 +++++++------------ wdl-grammar/src/lib.rs | 11 +++ wdl-grammar/src/main.rs | 78 +++++++---------- wdl-grammar/src/v1.rs | 26 +++++- 7 files changed, 123 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efc869632..d8c39fb65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,6 +352,26 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.8" @@ -448,6 +468,7 @@ dependencies = [ "log", "pest", "pest_derive", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1cf0251ca..0fcfb5b6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [ "wdl-grammar" ] +members = ["wdl-grammar"] resolver = "2" [workspace.package] @@ -8,4 +8,6 @@ edition = "2021" [workspace.dependencies] env_logger = "0.10.0" +introspect = { git = "https://github.com/claymcleod/introspect.git", version = "0.1.1" } log = "0.4.20" +serde = { version = "1", features = ["derive"] } diff --git a/wdl-grammar/Cargo.toml b/wdl-grammar/Cargo.toml index 17c7e37e6..c398a05b2 100644 --- a/wdl-grammar/Cargo.toml +++ b/wdl-grammar/Cargo.toml @@ -10,3 +10,4 @@ env_logger.workspace = true log.workspace = true pest = "2.7.5" pest_derive = "2.7.5" +serde.workspace = true diff --git a/wdl-grammar/src/bin/wdl-grammar-create-test.rs b/wdl-grammar/src/bin/wdl-grammar-create-test.rs index 841564380..afa7f3dd7 100644 --- a/wdl-grammar/src/bin/wdl-grammar-create-test.rs +++ b/wdl-grammar/src/bin/wdl-grammar-create-test.rs @@ -1,9 +1,8 @@ //! A command-line tool to automatically generate tests for WDL syntax. //! //! This tool is only intended to be used in the development of the -//! `wdl-grammar` package. -//! -//! This tool is written very sloppily—please keep that in mind. +//! `wdl-grammar` package. It was written quickly and relatively sloppily in +//! contrast to the rest of this package—please keep that in mind! #![warn(missing_docs)] #![warn(rust_2018_idioms)] @@ -12,7 +11,6 @@ #![deny(rustdoc::broken_intra_doc_links)] use std::fs; -use std::path::Path; use std::path::PathBuf; use clap::Parser; @@ -23,6 +21,8 @@ use pest::Parser as _; use pest::iterators::Pair; use wdl_grammar as wdl; +use wdl::Version; + /// An error related to the `wdl` command-line tool. #[derive(Debug)] pub enum Error { @@ -32,8 +32,8 @@ pub enum Error { /// Attempted to access a file, but it was missing. FileDoesNotExist(PathBuf), - /// Not able to match the provided rule name to a defined rule. - RuleMismatch(PathBuf), + /// Unknown rule name. + UnknownRule(String), /// An error from Pest. PestError(Box>), @@ -44,8 +44,8 @@ impl std::fmt::Display for Error { match self { Error::IoError(err) => write!(f, "i/o error: {err}"), Error::FileDoesNotExist(path) => write!(f, "file does not exist: {}", path.display()), - Error::RuleMismatch(path) => { - write!(f, "cannot match rule from file: {}", path.display()) + Error::UnknownRule(rule) => { + write!(f, "unknown rule: {rule}") } Error::PestError(err) => write!(f, "pest error:\n{err}"), } @@ -62,6 +62,10 @@ pub struct Args { /// The path to the document. path: PathBuf, + /// The WDL specification version to use. + #[arg(short = 's', long, default_value_t, value_enum)] + specification_version: Version, + /// The rule to evaluate. #[arg(short = 'r', long, default_value = "document")] rule: String, @@ -74,15 +78,29 @@ fn inner() -> Result<()> { .filter_level(LevelFilter::Debug) .init(); - let (contents, rule) = parse_from_path(&args.rule, &args.path)?; - let parse_tree: pest::iterators::Pairs<'_, wdl::v1::Rule> = - wdl::v1::Parser::parse(rule, &contents).map_err(|err| Error::PestError(Box::new(err)))?; + let rule = match args.specification_version { + Version::V1 => { + wdl::v1::get_rule(&args.rule) + .map(Ok) + .unwrap_or_else(|| Err(Error::UnknownRule(args.rule.clone())))? + } + }; - for pair in parse_tree { - print_create_test_recursive(pair, 0); - } + let contents = fs::read_to_string(args.path).map_err(Error::IoError)?; - Ok(()) + match args.specification_version { + Version::V1 => { + let parse_tree: pest::iterators::Pairs<'_, wdl::v1::Rule> = + wdl::v1::Parser::parse(rule, &contents) + .map_err(|err| Error::PestError(Box::new(err)))?; + + for pair in parse_tree { + print_create_test_recursive(pair, 0); + } + + Ok(()) + } + } } fn print_create_test_recursive(pair: Pair<'_, wdl::v1::Rule>, indent: usize) { @@ -122,44 +140,6 @@ fn print_create_test_recursive(pair: Pair<'_, wdl::v1::Rule>, indent: usize) { print!(")"); } -fn parse_from_path( - rule: impl AsRef, - path: impl AsRef, -) -> Result<(String, wdl::v1::Rule)> { - let rule = rule.as_ref(); - let path = path.as_ref(); - - let rule = map_rule(rule) - .map(Ok) - .unwrap_or_else(|| Err(Error::RuleMismatch(path.to_path_buf())))?; - - let contents = fs::read_to_string(path).map_err(Error::IoError)?; - - Ok((contents, rule)) -} - -fn map_rule(rule: &str) -> Option { - match rule { - "document" => Some(wdl::v1::Rule::document), - "if" => Some(wdl::v1::Rule::r#if), - "task" => Some(wdl::v1::Rule::task), - "core" => Some(wdl::v1::Rule::core), - "expression" => Some(wdl::v1::Rule::expression), - "object_literal" => Some(wdl::v1::Rule::object_literal), - "task_metadata_object" => Some(wdl::v1::Rule::task_metadata_object), - "task_parameter_metadata" => Some(wdl::v1::Rule::task_parameter_metadata), - "workflow_metadata_kv" => Some(wdl::v1::Rule::workflow_metadata_kv), - "command_heredoc_interpolated_contents" => { - Some(wdl::v1::Rule::command_heredoc_interpolated_contents) - } - "workflow_scatter" => Some(wdl::v1::Rule::workflow_scatter), - "workflow_call" => Some(wdl::v1::Rule::workflow_call), - "workflow_conditional" => Some(wdl::v1::Rule::workflow_conditional), - "postfix" => Some(wdl::v1::Rule::postfix), - _ => todo!("must implement mapping for rule: {rule}"), - } -} - fn main() { match inner() { Ok(_) => {} diff --git a/wdl-grammar/src/lib.rs b/wdl-grammar/src/lib.rs index 10c36280a..4d0a427b5 100644 --- a/wdl-grammar/src/lib.rs +++ b/wdl-grammar/src/lib.rs @@ -6,4 +6,15 @@ #![warn(missing_debug_implementations)] #![deny(rustdoc::broken_intra_doc_links)] +use clap::ValueEnum; +use serde::Serialize; + pub mod v1; + +#[derive(Clone, Debug, Default, Serialize, ValueEnum)] +#[serde(rename_all = "lowercase")] +pub enum Version { + /// Version 1.x of the WDL specification. + #[default] + V1, +} \ No newline at end of file diff --git a/wdl-grammar/src/main.rs b/wdl-grammar/src/main.rs index e1cc4a67a..47f5fd64f 100644 --- a/wdl-grammar/src/main.rs +++ b/wdl-grammar/src/main.rs @@ -11,7 +11,6 @@ #![deny(rustdoc::broken_intra_doc_links)] use std::fs; -use std::path::Path; use std::path::PathBuf; use clap::Parser; @@ -22,6 +21,8 @@ use pest::Parser as _; use wdl_grammar as wdl; +use wdl::Version; + /// An error related to the `wdl` command-line tool. #[derive(Debug)] pub enum Error { @@ -31,8 +32,8 @@ pub enum Error { /// Attempted to access a file, but it was missing. FileDoesNotExist(PathBuf), - /// Not able to match the provided rule name to a defined rule. - RuleMismatch(PathBuf), + /// Unknown rule name. + UnknownRule(String), /// An error from Pest. PestError(Box>), @@ -43,8 +44,8 @@ impl std::fmt::Display for Error { match self { Error::IoError(err) => write!(f, "i/o error: {err}"), Error::FileDoesNotExist(path) => write!(f, "file does not exist: {}", path.display()), - Error::RuleMismatch(path) => { - write!(f, "cannot match rule from file: {}", path.display()) + Error::UnknownRule(rule) => { + write!(f, "unknown rule: {rule}") } Error::PestError(err) => write!(f, "pest error:\n{err}"), } @@ -61,6 +62,10 @@ pub struct ParseArgs { /// The path to the document. path: PathBuf, + /// The WDL specification version to use. + #[arg(short = 's', long, default_value_t, value_enum)] + specification_version: Version, + /// The rule to evaluate. #[arg(short = 'r', long, default_value = "document")] rule: String, @@ -92,22 +97,30 @@ fn inner() -> Result<()> { match args.command { Command::Parse(args) => { - let (contents, rule) = parse_from_path(&args.rule, &args.path)?; - let mut parse_tree = wdl::v1::Parser::parse(rule, &contents) - .map_err(|err| Error::PestError(Box::new(err)))?; + let rule = match args.specification_version { + Version::V1 => wdl::v1::get_rule(&args.rule) + .map(Ok) + .unwrap_or_else(|| Err(Error::UnknownRule(args.rule.clone())))?, + }; + + let contents = fs::read_to_string(args.path).map_err(Error::IoError)?; + + let mut parse_tree = match args.specification_version { + Version::V1 => { + wdl::v1::Parser::parse(rule, &contents) + .map_err(|err| Error::PestError(Box::new(err)))? + } + }; // For documents, we don't care about the parent element: it is much // more informative to see the children of the document split by // spaces. This is a stylistic choice. - match rule { - wdl::v1::Rule::document => { - for element in parse_tree.next().unwrap().into_inner() { - dbg!(element); - } - } - _ => { - dbg!(parse_tree); + if args.rule == "document" { + for element in parse_tree.next().unwrap().into_inner() { + dbg!(element); } + } else { + dbg!(parse_tree); }; } } @@ -115,39 +128,6 @@ fn inner() -> Result<()> { Ok(()) } -fn parse_from_path( - rule: impl AsRef, - path: impl AsRef, -) -> Result<(String, wdl::v1::Rule)> { - let rule = rule.as_ref(); - let path = path.as_ref(); - - let rule = map_rule(rule) - .map(Ok) - .unwrap_or_else(|| Err(Error::RuleMismatch(path.to_path_buf())))?; - - let contents = fs::read_to_string(path).map_err(Error::IoError)?; - - Ok((contents, rule)) -} - -fn map_rule(rule: &str) -> Option { - match rule { - "document" => Some(wdl::v1::Rule::document), - "task" => Some(wdl::v1::Rule::task), - "core" => Some(wdl::v1::Rule::core), - "expression" => Some(wdl::v1::Rule::expression), - "object_literal" => Some(wdl::v1::Rule::object_literal), - "task_metadata_object" => Some(wdl::v1::Rule::task_metadata_object), - "task_parameter_metadata" => Some(wdl::v1::Rule::task_parameter_metadata), - "workflow_metadata_kv" => Some(wdl::v1::Rule::workflow_metadata_kv), - "command_heredoc_interpolated_contents" => { - Some(wdl::v1::Rule::command_heredoc_interpolated_contents) - } - _ => todo!("must implement mapping for rule: {rule}"), - } -} - fn main() { match inner() { Ok(_) => {} diff --git a/wdl-grammar/src/v1.rs b/wdl-grammar/src/v1.rs index e132dd6a8..42a8dae66 100644 --- a/wdl-grammar/src/v1.rs +++ b/wdl-grammar/src/v1.rs @@ -1,8 +1,32 @@ +use introspect::Introspect; use pest_derive::Parser; #[cfg(test)] mod tests; -#[derive(Debug, Parser)] +#[derive(Debug, Introspect, Parser)] #[grammar = "v1/wdl.pest"] pub struct Parser; + +/// Gets a rule by name. +/// +/// # Examples +/// +/// ``` +/// use wdl_grammar as wdl; +/// +/// let rule = wdl::v1::get_rule("document"); +/// assert!(matches!(rule, Some(_))); +/// +/// let rule = wdl::v1::get_rule("foo-bar-baz-rule"); +/// assert!(!matches!(rule, Some(_))); +/// ``` +pub fn get_rule(rule: &str) -> Option { + for candidate in Rule::all_rules() { + if format!("{:?}", candidate) == rule { + return Some(*candidate) + } + } + + None +} \ No newline at end of file