Skip to content

Commit

Permalink
feat: adds the abstract syntax tree package, wdl-ast
Browse files Browse the repository at this point in the history
  • Loading branch information
claymcleod committed Dec 9, 2023
1 parent 66da811 commit b3e9e73
Show file tree
Hide file tree
Showing 155 changed files with 14,278 additions and 1,634 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target/
.vscode/

rustc-ice*
68 changes: 65 additions & 3 deletions Cargo.lock

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

10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["wdl", "wdl-grammar"]
members = ["wdl", "wdl-ast", "wdl-core", "wdl-gauntlet", "wdl-grammar"]
resolver = "2"

[workspace.package]
Expand All @@ -9,11 +9,17 @@ version = "0.1.0"

[workspace.dependencies]
clap = { version = "4.4.7", features = ["derive"] }
colored = "2.0.4"
env_logger = "0.10.0"
indexmap = { version = "2.1.0", features = ["serde"] }
lazy_static = "1.4.0"
log = "0.4.20"
nonempty = "0.9.0"
ordered-float = "4.2.0"
pest = { version = "2.7.5", features = ["pretty-print"] }
pest_derive = "2.7.5"
regex = "1.10.2"
serde = { version = "1", features = ["derive"] }
serde_with = { version = "3.4.0" }
serde_with = "3.4.0"
tokio = { version = "1.33.0", features = ["full"] }
toml = "0.8.8"
20 changes: 10 additions & 10 deletions Gauntlet.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
version = "v1"

[[repositories]]
organization = "stjudecloud"
name = "workflows"

[[repositories]]
organization = "PacificBiosciences"
name = "HiFi-human-WGS-WDL"

[[repositories]]
organization = "biowdl"
name = "tasks"

[[repositories]]
organization = "chanzuckerberg"
name = "czid-workflows"

[[repositories]]
organization = "biowdl"
name = "tasks"
organization = "stjudecloud"
name = "workflows"

[[ignored_errors]]
document = "biowdl/tasks:bcftools.wdl"
Expand Down Expand Up @@ -86,16 +86,16 @@ parse error:

[[ignored_errors]]
document = "stjudecloud/workflows:tools/bwa.wdl"
error = '''validation error: [v1::001] invalid escape character '\.' in string at line 34:17'''
error = '''validation error: [v1::001] invalid escape character '\.' in string at line 41:17'''

[[ignored_errors]]
document = "stjudecloud/workflows:tools/fq.wdl"
error = '''validation error: [v1::001] invalid escape character '\.' in string at line 123:17'''
error = '''validation error: [v1::001] invalid escape character '\.' in string at line 128:17'''

[[ignored_errors]]
document = "stjudecloud/workflows:tools/kraken2.wdl"
error = '''validation error: [v1::001] invalid escape character '\.' in string at line 335:17'''
error = '''validation error: [v1::001] invalid escape character '\.' in string at line 336:17'''

[[ignored_errors]]
document = "stjudecloud/workflows:workflows/rnaseq/rnaseq-standard-fastq.wdl"
error = '''validation error: [v1::001] invalid escape character '\*' in string at line 55:241'''
error = '''validation error: [v1::001] invalid escape character '\*' in string at line 56:241'''
32 changes: 32 additions & 0 deletions wdl-ast/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "wdl-ast"
version = "0.1.0"
authors = ["Clay McLeod <clay.l.mcleod@gmail.com>"]
edition.workspace = true
license.workspace = true
description = "Abstract syntax tree for Workflow Description Language (WDL) documents"
homepage = "https://github.com/stjude-rust-labs/wdl"
repository = "https://github.com/stjude-rust-labs/wdl"
documentation = "https://docs.rs/wdl-ast"

[dependencies]
clap = { workspace = true, optional = true }
env_logger = { workspace = true, optional = true }
indexmap.workspace = true
lazy_static.workspace = true
log = { workspace = true, optional = true }
nonempty.workspace = true
ordered-float.workspace = true
pest.workspace = true
regex.workspace = true
tokio = { workspace = true, optional = true }
wdl-core = { path = "../wdl-core", version = "0.1.0" }
wdl-grammar = { path = "../wdl-grammar", version = "0.1.0" }

[features]
binaries = ["dep:clap", "dep:env_logger", "dep:log", "dep:tokio"]

[[bin]]
name = "wdl-ast"
path = "src/main.rs"
required-features = ["binaries"]
3 changes: 3 additions & 0 deletions wdl-ast/src/commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Subcommands for the `wdl-ast` command-line tool.
pub mod parse;
79 changes: 79 additions & 0 deletions wdl-ast/src/commands/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! `wdl-ast parse`
use core::lint::warning::display;
use std::path::PathBuf;

use clap::Parser;

use log::warn;
use wdl_ast as ast;
use wdl_core as core;
use wdl_grammar as grammar;

/// An error related to the `wdl-ast parse` subcommand.
#[derive(Debug)]
pub enum Error {
/// An abstract syntax tree error.
Ast(ast::Error),

/// An input/output error.
InputOutput(std::io::Error),

/// A grammar error.
Grammar(grammar::Error<grammar::v1::Rule>),
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Ast(err) => write!(f, "ast error: {err}"),
Error::InputOutput(err) => write!(f, "i/o error: {err}"),
Error::Grammar(err) => write!(f, "grammar error: {err}"),
}
}
}

impl std::error::Error for Error {}

/// A [`Result`](std::result::Result) with an [`Error`].
type Result<T> = std::result::Result<T, Error>;

/// Arguments for the `wdl-ast parse` subcommand.
#[derive(Debug, Parser)]
pub struct Args {
/// Path to the WDL document.
#[clap(value_name = "PATH")]
path: PathBuf,

/// The Workflow Description Language (WDL) specification version to use.
#[arg(value_name = "VERSION", short = 's', long, default_value_t, value_enum)]
specification_version: core::Version,
}

/// Main function for this subcommand.
pub fn parse(args: Args) -> Result<()> {
let contents = std::fs::read_to_string(args.path).map_err(Error::InputOutput)?;

let document = match args.specification_version {
core::Version::V1 => {
let pt = grammar::v1::parse(grammar::v1::Rule::document, &contents)
.map_err(Error::Grammar)?;

ast::v1::parse(pt).map_err(Error::Ast)?
}
};

if let Some(warnings) = document.warnings() {
for warning in warnings {
let mut buffer = String::new();
warning.display(&mut buffer, display::Mode::OneLine).unwrap();
warn!("{}", buffer);
}
}

for (_, task) in document.tasks() {
dbg!(task.runtime());
}

Ok(())
}
9 changes: 9 additions & 0 deletions wdl-ast/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Common functionality used across all WDL abstract syntax trees.
mod linter;
mod tree;
mod validator;

pub use linter::Linter;
pub use tree::Tree;
pub use validator::Validator;
35 changes: 35 additions & 0 deletions wdl-ast/src/common/linter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! An abstract syntax tree linter.
use wdl_core as core;

use core::lint;
use core::lint::Warning;

use crate::v1::Document;

/// A [`Result`](std::result::Result) for [`Linter::lint()`].
pub type Result = std::result::Result<Option<Vec<Warning>>, Box<dyn std::error::Error>>;

/// An abstract syntax tree linter.
#[derive(Debug)]
pub struct Linter;

impl Linter {
/// Lints a WDL abstract syntax tree according to a set of lint rules and
/// returns a set of lint warnings (if any are detected).
pub fn lint(tree: &Document) -> Result {
let warnings = crate::v1::lint::RULES
.iter()
.map(|rule| rule.check(tree))
.collect::<std::result::Result<Vec<Option<Vec<lint::Warning>>>, Box<dyn std::error::Error>>>()?
.into_iter()
.flatten()
.flatten()
.collect::<Vec<lint::Warning>>();

match warnings.is_empty() {
true => Ok(None),
false => Ok(Some(warnings)),
}
}
}
51 changes: 51 additions & 0 deletions wdl-ast/src/common/tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! An abstract syntax tree.
use wdl_core as core;

use core::lint;

use crate::v1::Document;

/// An abstract syntax tree with a set of lint [`Warning`](lint::Warning)s.
///
/// **Note:** this struct implements [`std::ops::Deref`] for a parsed WDL
/// [`Document`], so you can treat this exactly as if you were workings with a
/// [`Document`] directly.
#[derive(Debug)]
pub struct Tree {
/// The inner document.
inner: Document,

/// The lint warnings associated with the parse tree.
warnings: Option<Vec<lint::Warning>>,
}

impl Tree {
/// Creates a new [`Tree`].
pub fn new(inner: Document, warnings: Option<Vec<lint::Warning>>) -> Self {
Self { inner, warnings }
}

/// Gets the inner [`Document`] for the [`Tree`] by reference.
pub fn inner(&self) -> &Document {
&self.inner
}

/// Consumes `self` to return the inner [`Document`] from the [`Tree`].
pub fn into_inner(self) -> Document {
self.inner
}

/// Gets the [`Warning`](lint::Warning)s from the [`Tree`] by reference.
pub fn warnings(&self) -> Option<&Vec<lint::Warning>> {
self.warnings.as_ref()
}
}

impl std::ops::Deref for Tree {
type Target = Document;

fn deref(&self) -> &Self::Target {
&self.inner
}
}
Loading

0 comments on commit b3e9e73

Please sign in to comment.