Skip to content

Commit

Permalink
revise: adds extension trait and AST registry
Browse files Browse the repository at this point in the history
  • Loading branch information
claymcleod committed Aug 7, 2024
1 parent 5660c1e commit 8aee829
Show file tree
Hide file tree
Showing 7 changed files with 675 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ futures = "0.3.30"
glob = "0.3.1"
path-clean = "1.0.1"
indicatif = "0.17.8"
itertools = "0.13.0"
2 changes: 2 additions & 0 deletions wdl-ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub use wdl_grammar::WorkflowDescriptionLanguage;

pub mod v1;

#[cfg(test)]
mod registry;
mod validation;
mod visitor;

Expand Down
241 changes: 241 additions & 0 deletions wdl-ast/src/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::LazyLock;

use wdl_grammar::WorkflowDescriptionLanguage;
use wdl_grammar::ALL_SYNTAX_KIND;

use crate::v1;
use crate::AstNode;
use crate::AstToken;
use crate::Comment;
use crate::Document;
use crate::Ident;
use crate::SyntaxKind;
use crate::Version;
use crate::VersionStatement;
use crate::Whitespace;

/// A private module for sealed traits.
mod private {
/// The sealed trait for [`AstNodeRegistrant`](super::AstNodeRegistrant).
pub trait SealedNode {}

/// The sealed trait for [`AstTokenRegistrant`](super::AstTokenRegistrant).
pub trait SealedToken {}
}

/// A registry of all known mappings between types that implement [`AstNode`]
/// and the [`SyntaxKind`] they can map to.
static REGISTRY: LazyLock<HashMap<TypeId, Box<[SyntaxKind]>>> = LazyLock::new(|| {
let types = vec![
Comment::register(),
Document::register(),
Ident::register(),
v1::AccessExpr::register(),
v1::AdditionExpr::register(),
v1::ArrayType::register(),
v1::Ast::register(),
v1::BoundDecl::register(),
v1::CallAfter::register(),
v1::CallAlias::register(),
v1::CallExpr::register(),
v1::CallInputItem::register(),
v1::CallTarget::register(),
v1::CommandSection::register(),
v1::CommandText::register(),
v1::ConditionalStatement::register(),
v1::Decl::register(),
v1::DefaultOption::register(),
v1::DivisionExpr::register(),
v1::DocumentItem::register(),
v1::EqualityExpr::register(),
v1::ExponentiationExpr::register(),
v1::Expr::register(),
v1::Float::register(),
v1::GreaterEqualExpr::register(),
v1::GreaterExpr::register(),
v1::HintsItem::register(),
v1::HintsSection::register(),
v1::IfExpr::register(),
v1::ImportAlias::register(),
v1::ImportStatement::register(),
v1::IndexExpr::register(),
v1::InequalityExpr::register(),
v1::InputSection::register(),
v1::Integer::register(),
v1::LessEqualExpr::register(),
v1::LessExpr::register(),
v1::LiteralArray::register(),
v1::LiteralBoolean::register(),
v1::LiteralExpr::register(),
v1::LiteralFloat::register(),
v1::LiteralHints::register(),
v1::LiteralHintsItem::register(),
v1::LiteralInput::register(),
v1::LiteralInputItem::register(),
v1::LiteralInteger::register(),
v1::LiteralMap::register(),
v1::LiteralMapItem::register(),
v1::LiteralNone::register(),
v1::LiteralObject::register(),
v1::LiteralObjectItem::register(),
v1::LiteralOutput::register(),
v1::LiteralOutputItem::register(),
v1::LiteralPair::register(),
v1::LiteralString::register(),
v1::LiteralStruct::register(),
v1::LiteralStructItem::register(),
v1::LogicalAndExpr::register(),
v1::LogicalNotExpr::register(),
v1::LogicalOrExpr::register(),
v1::MapType::register(),
v1::MetadataArray::register(),
v1::MetadataObjectItem::register(),
v1::MetadataSection::register(),
v1::ModuloExpr::register(),
v1::MultiplicationExpr::register(),
v1::NameRef::register(),
v1::NegationExpr::register(),
v1::ObjectType::register(),
v1::OutputSection::register(),
v1::PairType::register(),
v1::ParameterMetadataSection::register(),
v1::ParenthesizedExpr::register(),
v1::Placeholder::register(),
v1::PlaceholderOption::register(),
v1::PrimitiveType::register(),
v1::RequirementsItem::register(),
v1::RequirementsSection::register(),
v1::RuntimeItem::register(),
v1::RuntimeSection::register(),
v1::ScatterStatement::register(),
v1::SectionParent::register(),
v1::SepOption::register(),
v1::StringText::register(),
v1::StructDefinition::register(),
v1::StructItem::register(),
v1::SubtractionExpr::register(),
v1::TaskDefinition::register(),
v1::TaskItem::register(),
v1::TrueFalseOption::register(),
v1::Type::register(),
v1::TypeRef::register(),
v1::UnboundDecl::register(),
v1::WorkflowDefinition::register(),
v1::WorkflowItem::register(),
v1::WorkflowStatement::register(),
Version::register(),
VersionStatement::register(),
Whitespace::register(),
];

let mut result = HashMap::new();

// NOTE: this is done this way instead of collecting to check on the fly to
// make sure that no keys are duplicated.
for (r#type, kinds) in types {
if result.contains_key(&r#type) {
panic!("the `{:?}` key is duplicated", r#type);
}

result.insert(r#type, kinds);
}

result
});

/// Computes the inverse of the registry (maps [`SyntaxKind`]s to every type
/// that can cast from them).
fn inverse_registry() -> HashMap<SyntaxKind, Box<[TypeId]>> {
let mut result = HashMap::<SyntaxKind, Vec<TypeId>>::new();

for (key, values) in REGISTRY.iter() {
for value in values.into_iter() {
result.entry(value.to_owned()).or_default().push(*key);
}
}

result
.into_iter()
.map(|(key, values)| (key, values.into_boxed_slice()))
.collect()
}

trait AstNodeRegistrant: private::SealedNode {
/// Registers the AST element.
fn register() -> (TypeId, Box<[SyntaxKind]>);
}

impl<T: AstNode<Language = WorkflowDescriptionLanguage> + 'static> private::SealedNode for T {}

impl<T: AstNode<Language = WorkflowDescriptionLanguage> + 'static> AstNodeRegistrant for T {
fn register() -> (TypeId, Box<[SyntaxKind]>) {
(
TypeId::of::<T>(),
ALL_SYNTAX_KIND
.iter()
.filter(|kind| T::can_cast(**kind))
.cloned()
.collect::<Vec<_>>()
.into_boxed_slice(),
)
}
}

trait AstTokenRegistrant: private::SealedToken {
/// Registers a type implementing `AstToken` that can be .
fn register() -> (TypeId, Box<[SyntaxKind]>);
}

impl<T: AstToken + 'static> private::SealedToken for T {}

impl<T: AstToken + 'static> AstTokenRegistrant for T {
fn register() -> (TypeId, Box<[SyntaxKind]>) {
(
TypeId::of::<T>(),
ALL_SYNTAX_KIND
.iter()
.filter(|kind| T::can_cast(**kind))
.cloned()
.collect::<Vec<_>>()
.into_boxed_slice(),
)
}
}

mod tests {
use super::*;

#[test]
fn ensure_each_syntax_element_has_an_ast_node() {
let mut missing = Vec::new();
let inverse_registry = inverse_registry();

for kind in ALL_SYNTAX_KIND {
// NOTE: these are pseudo elements and should not be reported.
if *kind == SyntaxKind::Abandoned || *kind == SyntaxKind::MAX {
continue;
}

if !inverse_registry.contains_key(kind) {
missing.push(kind);
}
}

if !missing.is_empty() {
let mut missing = missing
.into_iter()
.map(|kind| format!("{:?}", kind))
.collect::<Vec<_>>();
missing.sort();

panic!(
"detected `SyntaxKind`s without an associated `AstNode` (n={}): {}",
missing.len(),
missing.join(", ")
)
}
}
}
2 changes: 1 addition & 1 deletion wdl-ast/src/v1/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3698,7 +3698,7 @@ task test {
r#"
version 1.1
task test {
task test {
Foo a = Foo { foo: "bar" }
Bar b = Bar { bar: 1, baz: [1, 2, 3] }
}
Expand Down
127 changes: 127 additions & 0 deletions wdl-config/src/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use std::collections::VecDeque;
use std::convert::Infallible;
use std::path::PathBuf;

use config::ConfigError;
use config::Environment;
use config::File;

use crate::providers::EnvProvider;
use crate::providers::FileProvider;
use crate::BoxedProvider;
use crate::Config;
use crate::Provider;
use crate::CONFIG_SEARCH_PATHS;

#[derive(Debug)]
pub enum Error {
/// An error from the `config` crate.
Config(ConfigError),
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Config(err) => write!(f, "`config` error: {err}"),
}
}
}

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

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

pub struct Loader(VecDeque<BoxedProvider>);

impl Loader {
/// Creates an empty [`Loader`].
pub fn empty() -> Self {
Self(VecDeque::new())
}

/// Adds the default configuration to the front of the provider stack.
pub fn with_default_configuration(mut self) -> Self {
// NOTE: default configuration should always be the first provider evaluated.
self.0.push_front(Config::default().into());
self
}

/// Adds a file to the search path of the [`Loader`].
///
/// Note that the file is not required to be present.
pub fn add_optional_file(mut self, path: PathBuf) -> Self {
self.0.push_back(FileProvider::optional(path).into());
self
}

/// Adds a file to the search path of the [`Loader`].
///
/// Note that the file is required to be present.
pub fn add_required_file(mut self, path: PathBuf) -> Self {
self.0.push_back(FileProvider::required(path).into());
self
}

/// Adds the default search paths to the [`Loader`].
pub fn with_default_search_paths(mut self) -> Self {
for path in CONFIG_SEARCH_PATHS.clone().into_iter() {
self = self.add_optional_file(path);
}

self
}

/// Adds a new environment prefix to the [`Loader`].
pub fn add_env_prefix(mut self, prefix: &str) -> Self {
self.0.push_back(EnvProvider::new(prefix).into());
self
}

/// Adds the default environment prefix to the [`Loader`].
pub fn with_default_env_prefix(mut self) -> Self {
self.0.push_back(EnvProvider::default().into());
self
}

/// Gets a reference to the inner [`ConfigBuilder`].
pub fn inner(&self) -> &VecDeque<BoxedProvider> {
&self.0
}

/// Consumes `self` and returns the inner [`ConfigBuilder`].
pub fn into_inner(self) -> VecDeque<BoxedProvider> {
self.0
}

/// Consumes `self` and attempts to load the [`Config`].
pub fn try_load(self) -> std::result::Result<Config, Box<dyn std::error::Error>> {
for provider in self.0 {
let config = provider.provide().map_err(|e| );
}

self.0
.build()
.map_err(Error::Config)?
.try_deserialize()
.map_err(Error::Config)
}
}

impl Default for Loader {
fn default() -> Self {
Self::empty()
.with_default_search_paths()
.with_default_env_prefix()
}
}

#[cfg(test)]
mod tests {
use crate::Loader;

#[test]
fn an_empty_loader_unwraps() {
Loader::empty();
}
}
Loading

0 comments on commit 8aee829

Please sign in to comment.