Skip to content

Commit

Permalink
revise: makes rule matching dynamic
Browse files Browse the repository at this point in the history
  • Loading branch information
claymcleod committed Nov 8, 2023
1 parent 388e86d commit 10e9fee
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 104 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = [ "wdl-grammar" ]
members = ["wdl-grammar"]
resolver = "2"

[workspace.package]
Expand All @@ -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"] }
1 change: 1 addition & 0 deletions wdl-grammar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ env_logger.workspace = true
log.workspace = true
pest = "2.7.5"
pest_derive = "2.7.5"
serde.workspace = true
86 changes: 33 additions & 53 deletions wdl-grammar/src/bin/wdl-grammar-create-test.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -12,7 +11,6 @@
#![deny(rustdoc::broken_intra_doc_links)]

use std::fs;
use std::path::Path;
use std::path::PathBuf;

use clap::Parser;
Expand All @@ -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 {
Expand All @@ -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<pest::error::Error<wdl::v1::Rule>>),
Expand All @@ -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}"),
}
Expand All @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -122,44 +140,6 @@ fn print_create_test_recursive(pair: Pair<'_, wdl::v1::Rule>, indent: usize) {
print!(")");
}

fn parse_from_path(
rule: impl AsRef<str>,
path: impl AsRef<Path>,
) -> 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<wdl::v1::Rule> {
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(_) => {}
Expand Down
11 changes: 11 additions & 0 deletions wdl-grammar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
78 changes: 29 additions & 49 deletions wdl-grammar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#![deny(rustdoc::broken_intra_doc_links)]

use std::fs;
use std::path::Path;
use std::path::PathBuf;

use clap::Parser;
Expand All @@ -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 {
Expand All @@ -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<pest::error::Error<wdl::v1::Rule>>),
Expand All @@ -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}"),
}
Expand All @@ -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,
Expand Down Expand Up @@ -92,62 +97,37 @@ 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);
};
}
}

Ok(())
}

fn parse_from_path(
rule: impl AsRef<str>,
path: impl AsRef<Path>,
) -> 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<wdl::v1::Rule> {
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(_) => {}
Expand Down
26 changes: 25 additions & 1 deletion wdl-grammar/src/v1.rs
Original file line number Diff line number Diff line change
@@ -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<Rule> {
for candidate in Rule::all_rules() {
if format!("{:?}", candidate) == rule {
return Some(*candidate)
}
}

None
}

0 comments on commit 10e9fee

Please sign in to comment.