-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
revise: adds extension trait and AST registry
- Loading branch information
1 parent
5660c1e
commit 8aee829
Showing
7 changed files
with
675 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(", ") | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.