use std::fmt;
use rowan::NodeOrToken;
use wdl_grammar::SupportedVersion;
use wdl_grammar::SyntaxTokenExt;
use wdl_grammar::version::V1;
use super::BoundDecl;
use super::Expr;
use super::InputSection;
use super::LiteralBoolean;
use super::LiteralFloat;
use super::LiteralInteger;
use super::LiteralString;
use super::MetadataSection;
use super::MetadataValue;
use super::OutputSection;
use super::ParameterMetadataSection;
use super::WorkflowKeyword;
use crate::AstNode;
use crate::AstToken;
use crate::Comment;
use crate::Documented;
use crate::Ident;
use crate::SyntaxKind;
use crate::SyntaxNode;
use crate::TreeNode;
use crate::TreeToken;
use crate::v1::CallKeyword;
use crate::v1::ScatterKeyword;
pub const WORKFLOW_HINT_ALLOW_NESTED_INPUTS: &str = "allow_nested_inputs";
pub const WORKFLOW_HINT_ALLOW_NESTED_INPUTS_ALIAS: &str = "allowNestedInputs";
pub const WORKFLOW_HINT_KEYS: &[(&str, &str)] = &[(
WORKFLOW_HINT_ALLOW_NESTED_INPUTS,
"If `true`, allows nested input objects for the workflow.",
)];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkflowDefinition<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> WorkflowDefinition<N> {
pub fn name(&self) -> Ident<N::Token> {
self.token().expect("workflow should have a name")
}
pub fn keyword(&self) -> WorkflowKeyword<N::Token> {
self.token().expect("workflow should have a keyword")
}
pub fn items(&self) -> impl Iterator<Item = WorkflowItem<N>> + use<'_, N> {
WorkflowItem::children(&self.0)
}
pub fn input(&self) -> Option<InputSection<N>> {
self.child()
}
pub fn output(&self) -> Option<OutputSection<N>> {
self.child()
}
pub fn statements(&self) -> impl Iterator<Item = WorkflowStatement<N>> + use<'_, N> {
WorkflowStatement::children(&self.0)
}
pub fn metadata(&self) -> Option<MetadataSection<N>> {
self.child()
}
pub fn parameter_metadata(&self) -> Option<ParameterMetadataSection<N>> {
self.child()
}
pub fn hints(&self) -> Option<WorkflowHintsSection<N>> {
self.child()
}
pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
self.children()
}
pub fn allows_nested_inputs(&self, version: SupportedVersion) -> bool {
match version {
SupportedVersion::V1(V1::Zero) => return true,
SupportedVersion::V1(V1::One) => {
}
SupportedVersion::V1(V1::Two | V1::Three) => {
let allow = self.hints().and_then(|s| {
s.items().find_map(|i| {
let name = i.name();
if name.text() == WORKFLOW_HINT_ALLOW_NESTED_INPUTS
|| name.text() == WORKFLOW_HINT_ALLOW_NESTED_INPUTS_ALIAS
{
match i.value() {
WorkflowHintsItemValue::Boolean(v) => Some(v.value()),
_ => None,
}
} else {
None
}
})
});
if let Some(allow) = allow {
return allow;
}
}
_ => return false,
}
self.metadata()
.and_then(|s| {
s.items().find_map(|i| {
if i.name().text() == WORKFLOW_HINT_ALLOW_NESTED_INPUTS_ALIAS {
match i.value() {
MetadataValue::Boolean(v) => Some(v.value()),
_ => None,
}
} else {
None
}
})
})
.unwrap_or(false)
}
}
impl<N: TreeNode> AstNode<N> for WorkflowDefinition<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::WorkflowDefinitionNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::WorkflowDefinitionNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
impl Documented<SyntaxNode> for WorkflowDefinition<SyntaxNode> {
fn doc_comments(&self) -> Option<Vec<Comment<<SyntaxNode as TreeNode>::Token>>> {
Some(crate::doc_comments::<SyntaxNode>(self.keyword().inner().preceding_trivia()).collect())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WorkflowItem<N: TreeNode = SyntaxNode> {
Input(InputSection<N>),
Output(OutputSection<N>),
Conditional(ConditionalStatement<N>),
Scatter(ScatterStatement<N>),
Call(CallStatement<N>),
Metadata(MetadataSection<N>),
ParameterMetadata(ParameterMetadataSection<N>),
Hints(WorkflowHintsSection<N>),
Declaration(BoundDecl<N>),
}
impl<N: TreeNode> WorkflowItem<N> {
pub fn can_cast(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::InputSectionNode
| SyntaxKind::OutputSectionNode
| SyntaxKind::ConditionalStatementNode
| SyntaxKind::ScatterStatementNode
| SyntaxKind::CallStatementNode
| SyntaxKind::MetadataSectionNode
| SyntaxKind::ParameterMetadataSectionNode
| SyntaxKind::WorkflowHintsSectionNode
| SyntaxKind::BoundDeclNode
)
}
pub fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::InputSectionNode => Some(Self::Input(
InputSection::cast(inner).expect("input section to cast"),
)),
SyntaxKind::OutputSectionNode => Some(Self::Output(
OutputSection::cast(inner).expect("output section to cast"),
)),
SyntaxKind::ConditionalStatementNode => Some(Self::Conditional(
ConditionalStatement::cast(inner).expect("conditional statement to cast"),
)),
SyntaxKind::ScatterStatementNode => Some(Self::Scatter(
ScatterStatement::cast(inner).expect("scatter statement to cast"),
)),
SyntaxKind::CallStatementNode => Some(Self::Call(
CallStatement::cast(inner).expect("call statement to cast"),
)),
SyntaxKind::MetadataSectionNode => Some(Self::Metadata(
MetadataSection::cast(inner).expect("metadata section to cast"),
)),
SyntaxKind::ParameterMetadataSectionNode => Some(Self::ParameterMetadata(
ParameterMetadataSection::cast(inner).expect("parameter metadata section to cast"),
)),
SyntaxKind::WorkflowHintsSectionNode => Some(Self::Hints(
WorkflowHintsSection::cast(inner).expect("workflow hints section to cast"),
)),
SyntaxKind::BoundDeclNode => Some(Self::Declaration(
BoundDecl::cast(inner).expect("bound decl to cast"),
)),
_ => None,
}
}
pub fn inner(&self) -> &N {
match self {
Self::Input(element) => element.inner(),
Self::Output(element) => element.inner(),
Self::Conditional(element) => element.inner(),
Self::Scatter(element) => element.inner(),
Self::Call(element) => element.inner(),
Self::Metadata(element) => element.inner(),
Self::ParameterMetadata(element) => element.inner(),
Self::Hints(element) => element.inner(),
Self::Declaration(element) => element.inner(),
}
}
pub fn as_input_section(&self) -> Option<&InputSection<N>> {
match self {
Self::Input(s) => Some(s),
_ => None,
}
}
pub fn into_input_section(self) -> Option<InputSection<N>> {
match self {
Self::Input(s) => Some(s),
_ => None,
}
}
pub fn as_output_section(&self) -> Option<&OutputSection<N>> {
match self {
Self::Output(s) => Some(s),
_ => None,
}
}
pub fn into_output_section(self) -> Option<OutputSection<N>> {
match self {
Self::Output(s) => Some(s),
_ => None,
}
}
pub fn as_conditional(&self) -> Option<&ConditionalStatement<N>> {
match self {
Self::Conditional(s) => Some(s),
_ => None,
}
}
pub fn into_conditional(self) -> Option<ConditionalStatement<N>> {
match self {
Self::Conditional(s) => Some(s),
_ => None,
}
}
pub fn as_scatter(&self) -> Option<&ScatterStatement<N>> {
match self {
Self::Scatter(s) => Some(s),
_ => None,
}
}
pub fn into_scatter(self) -> Option<ScatterStatement<N>> {
match self {
Self::Scatter(s) => Some(s),
_ => None,
}
}
pub fn as_call(&self) -> Option<&CallStatement<N>> {
match self {
Self::Call(s) => Some(s),
_ => None,
}
}
pub fn into_call(self) -> Option<CallStatement<N>> {
match self {
Self::Call(s) => Some(s),
_ => None,
}
}
pub fn as_metadata_section(&self) -> Option<&MetadataSection<N>> {
match self {
Self::Metadata(s) => Some(s),
_ => None,
}
}
pub fn into_metadata_section(self) -> Option<MetadataSection<N>> {
match self {
Self::Metadata(s) => Some(s),
_ => None,
}
}
pub fn as_parameter_metadata_section(&self) -> Option<&ParameterMetadataSection<N>> {
match self {
Self::ParameterMetadata(s) => Some(s),
_ => None,
}
}
pub fn into_parameter_metadata_section(self) -> Option<ParameterMetadataSection<N>> {
match self {
Self::ParameterMetadata(s) => Some(s),
_ => None,
}
}
pub fn as_hints_section(&self) -> Option<&WorkflowHintsSection<N>> {
match self {
Self::Hints(s) => Some(s),
_ => None,
}
}
pub fn into_hints_section(self) -> Option<WorkflowHintsSection<N>> {
match self {
Self::Hints(s) => Some(s),
_ => None,
}
}
pub fn as_declaration(&self) -> Option<&BoundDecl<N>> {
match self {
Self::Declaration(d) => Some(d),
_ => None,
}
}
pub fn into_declaration(self) -> Option<BoundDecl<N>> {
match self {
Self::Declaration(d) => Some(d),
_ => None,
}
}
pub fn child(node: &N) -> Option<Self> {
node.children().find_map(Self::cast)
}
pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
node.children().filter_map(Self::cast)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WorkflowStatement<N: TreeNode = SyntaxNode> {
Conditional(ConditionalStatement<N>),
Scatter(ScatterStatement<N>),
Call(CallStatement<N>),
Declaration(BoundDecl<N>),
}
impl<N: TreeNode> WorkflowStatement<N> {
pub fn can_cast(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::ConditionalStatementNode
| SyntaxKind::ScatterStatementNode
| SyntaxKind::CallStatementNode
| SyntaxKind::BoundDeclNode
)
}
pub fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::ConditionalStatementNode => Some(Self::Conditional(
ConditionalStatement::cast(inner).expect("conditional statement to cast"),
)),
SyntaxKind::ScatterStatementNode => Some(Self::Scatter(
ScatterStatement::cast(inner).expect("scatter statement to cast"),
)),
SyntaxKind::CallStatementNode => Some(Self::Call(
CallStatement::cast(inner).expect("call statement to cast"),
)),
SyntaxKind::BoundDeclNode => Some(Self::Declaration(
BoundDecl::cast(inner).expect("bound decl to cast"),
)),
_ => None,
}
}
pub fn inner(&self) -> &N {
match self {
Self::Conditional(s) => s.inner(),
Self::Scatter(s) => s.inner(),
Self::Call(s) => s.inner(),
Self::Declaration(s) => s.inner(),
}
}
pub fn as_conditional(&self) -> Option<&ConditionalStatement<N>> {
match self {
Self::Conditional(s) => Some(s),
_ => None,
}
}
pub fn into_conditional(self) -> Option<ConditionalStatement<N>> {
match self {
Self::Conditional(s) => Some(s),
_ => None,
}
}
pub fn unwrap_conditional(self) -> ConditionalStatement<N> {
match self {
Self::Conditional(s) => s,
_ => panic!("not a conditional statement"),
}
}
pub fn as_scatter(&self) -> Option<&ScatterStatement<N>> {
match self {
Self::Scatter(s) => Some(s),
_ => None,
}
}
pub fn into_scatter(self) -> Option<ScatterStatement<N>> {
match self {
Self::Scatter(s) => Some(s),
_ => None,
}
}
pub fn unwrap_scatter(self) -> ScatterStatement<N> {
match self {
Self::Scatter(s) => s,
_ => panic!("not a scatter statement"),
}
}
pub fn as_call(&self) -> Option<&CallStatement<N>> {
match self {
Self::Call(s) => Some(s),
_ => None,
}
}
pub fn into_call(self) -> Option<CallStatement<N>> {
match self {
Self::Call(s) => Some(s),
_ => None,
}
}
pub fn unwrap_call(self) -> CallStatement<N> {
match self {
Self::Call(s) => s,
_ => panic!("not a call statement"),
}
}
pub fn as_declaration(&self) -> Option<&BoundDecl<N>> {
match self {
Self::Declaration(d) => Some(d),
_ => None,
}
}
pub fn into_declaration(self) -> Option<BoundDecl<N>> {
match self {
Self::Declaration(d) => Some(d),
_ => None,
}
}
pub fn unwrap_declaration(self) -> BoundDecl<N> {
match self {
Self::Declaration(d) => d,
_ => panic!("not a bound declaration"),
}
}
pub fn child(node: &N) -> Option<Self> {
node.children().find_map(Self::cast)
}
pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
node.children().filter_map(Self::cast)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ConditionalStatementClauseKind {
If,
ElseIf,
Else,
}
impl std::fmt::Display for ConditionalStatementClauseKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConditionalStatementClauseKind::If => write!(f, "`if` clause"),
ConditionalStatementClauseKind::ElseIf => write!(f, "`else if` clause"),
ConditionalStatementClauseKind::Else => write!(f, "`else` clause"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConditionalStatementClause<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> AstNode<N> for ConditionalStatementClause<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::ConditionalStatementClauseNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::ConditionalStatementClauseNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
impl<N: TreeNode> ConditionalStatementClause<N> {
pub fn expr(&self) -> Option<Expr<N>> {
Expr::child(&self.0)
}
pub fn statements(&self) -> impl Iterator<Item = WorkflowStatement<N>> + use<'_, N> {
WorkflowStatement::children(&self.0)
}
pub fn kind(&self) -> ConditionalStatementClauseKind {
let has_else = self.else_keyword().is_some();
let has_if = self.if_keyword().is_some();
debug_assert!(has_if || has_else);
if has_if && has_else {
return ConditionalStatementClauseKind::ElseIf;
} else if has_if {
return ConditionalStatementClauseKind::If;
} else if has_else {
return ConditionalStatementClauseKind::Else;
}
unreachable!("conditional clause should have an `if` or `else` keyword");
}
pub fn else_keyword(&self) -> Option<N::Token> {
self.0
.children_with_tokens()
.find_map(|node_or_token| match node_or_token {
NodeOrToken::Token(t) if t.kind() == SyntaxKind::ElseKeyword => Some(t),
_ => None,
})
}
pub fn if_keyword(&self) -> Option<N::Token> {
self.0
.children_with_tokens()
.find_map(|node_or_token| match node_or_token {
NodeOrToken::Token(t) if t.kind() == SyntaxKind::IfKeyword => Some(t),
_ => None,
})
}
pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
node.children().filter_map(Self::cast)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConditionalStatement<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> ConditionalStatement<N> {
pub fn clauses(&self) -> impl Iterator<Item = ConditionalStatementClause<N>> {
ConditionalStatementClause::children(&self.0)
}
pub fn if_clause(&self) -> ConditionalStatementClause<N> {
self.clauses()
.find(|clause| clause.kind() == ConditionalStatementClauseKind::If)
.expect("missing required conditional statement `if` clause")
}
pub fn else_if_clauses(&self) -> impl Iterator<Item = ConditionalStatementClause<N>> {
self.clauses()
.filter(|clause| clause.kind() == ConditionalStatementClauseKind::ElseIf)
}
pub fn else_clause(&self) -> Option<ConditionalStatementClause<N>> {
self.clauses()
.find(|clause| clause.kind() == ConditionalStatementClauseKind::Else)
}
}
impl<N: TreeNode> AstNode<N> for ConditionalStatement<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::ConditionalStatementNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::ConditionalStatementNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScatterStatement<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> ScatterStatement<N> {
pub fn variable(&self) -> Ident<N::Token> {
self.token()
.expect("expected a scatter variable identifier")
}
pub fn expr(&self) -> Expr<N> {
Expr::child(&self.0).expect("expected a scatter expression")
}
pub fn statements(&self) -> impl Iterator<Item = WorkflowStatement<N>> + use<'_, N> {
WorkflowStatement::children(&self.0)
}
pub fn keyword(&self) -> ScatterKeyword<N::Token> {
self.token()
.expect("ScatterStatement must have ScatterKeyword")
}
}
impl<N: TreeNode> AstNode<N> for ScatterStatement<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::ScatterStatementNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::ScatterStatementNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallStatement<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> CallStatement<N> {
pub fn target(&self) -> CallTarget<N> {
self.child().expect("expected a call target")
}
pub fn alias(&self) -> Option<CallAlias<N>> {
self.child()
}
pub fn after(&self) -> impl Iterator<Item = CallAfter<N>> + use<'_, N> {
self.children()
}
pub fn inputs(&self) -> impl Iterator<Item = CallInputItem<N>> + use<'_, N> {
self.children()
}
pub fn keyword(&self) -> CallKeyword<N::Token> {
self.token().expect("CallStatement must have CallKeyword")
}
}
impl<N: TreeNode> AstNode<N> for CallStatement<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::CallStatementNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::CallStatementNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallTarget<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> CallTarget<N> {
pub fn names(&self) -> impl Iterator<Item = Ident<N::Token>> + use<'_, N> {
self.0
.children_with_tokens()
.filter_map(NodeOrToken::into_token)
.filter_map(Ident::cast)
}
}
impl<N: TreeNode> AstNode<N> for CallTarget<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::CallTargetNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::CallTargetNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallAlias<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> CallAlias<N> {
pub fn name(&self) -> Ident<N::Token> {
self.token().expect("expected an alias identifier")
}
}
impl<N: TreeNode> AstNode<N> for CallAlias<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::CallAliasNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::CallAliasNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallAfter<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> CallAfter<N> {
pub fn name(&self) -> Ident<N::Token> {
self.token().expect("expected an after identifier")
}
}
impl<N: TreeNode> AstNode<N> for CallAfter<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::CallAfterNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::CallAfterNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallInputItem<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> CallInputItem<N> {
pub fn name(&self) -> Ident<N::Token> {
self.token().expect("expected an input name")
}
pub fn expr(&self) -> Option<Expr<N>> {
Expr::child(&self.0)
}
pub fn parent(&self) -> CallStatement<N> {
<Self as AstNode<N>>::parent(self).expect("should have parent")
}
pub fn is_implicit_bind(&self) -> bool {
self.expr().is_none()
}
}
impl<N: TreeNode> AstNode<N> for CallInputItem<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::CallInputItemNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::CallInputItemNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkflowHintsSection<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> WorkflowHintsSection<N> {
pub fn items(&self) -> impl Iterator<Item = WorkflowHintsItem<N>> + use<'_, N> {
self.children()
}
pub fn parent(&self) -> WorkflowDefinition<N> {
<Self as AstNode<N>>::parent(self).expect("should have parent")
}
}
impl<N: TreeNode> AstNode<N> for WorkflowHintsSection<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::WorkflowHintsSectionNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::WorkflowHintsSectionNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkflowHintsItem<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> WorkflowHintsItem<N> {
pub fn name(&self) -> Ident<N::Token> {
self.token().expect("expected an item name")
}
pub fn value(&self) -> WorkflowHintsItemValue<N> {
self.child().expect("expected an item value")
}
}
impl<N: TreeNode> AstNode<N> for WorkflowHintsItem<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::WorkflowHintsItemNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::WorkflowHintsItemNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WorkflowHintsItemValue<N: TreeNode = SyntaxNode> {
Boolean(LiteralBoolean<N>),
Integer(LiteralInteger<N>),
Float(LiteralFloat<N>),
String(LiteralString<N>),
Object(WorkflowHintsObject<N>),
Array(WorkflowHintsArray<N>),
}
impl<N: TreeNode> WorkflowHintsItemValue<N> {
pub fn unwrap_boolean(self) -> LiteralBoolean<N> {
match self {
Self::Boolean(b) => b,
_ => panic!("not a boolean"),
}
}
pub fn unwrap_integer(self) -> LiteralInteger<N> {
match self {
Self::Integer(i) => i,
_ => panic!("not an integer"),
}
}
pub fn unwrap_float(self) -> LiteralFloat<N> {
match self {
Self::Float(f) => f,
_ => panic!("not a float"),
}
}
pub fn unwrap_string(self) -> LiteralString<N> {
match self {
Self::String(s) => s,
_ => panic!("not a string"),
}
}
pub fn unwrap_object(self) -> WorkflowHintsObject<N> {
match self {
Self::Object(o) => o,
_ => panic!("not an object"),
}
}
pub fn unwrap_array(self) -> WorkflowHintsArray<N> {
match self {
Self::Array(a) => a,
_ => panic!("not an array"),
}
}
}
impl<N: TreeNode> AstNode<N> for WorkflowHintsItemValue<N> {
fn can_cast(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::LiteralBooleanNode
| SyntaxKind::LiteralIntegerNode
| SyntaxKind::LiteralFloatNode
| SyntaxKind::LiteralStringNode
| SyntaxKind::WorkflowHintsObjectNode
| SyntaxKind::WorkflowHintsArrayNode
)
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(inner))),
SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(inner))),
SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(inner))),
SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(inner))),
SyntaxKind::WorkflowHintsObjectNode => Some(Self::Object(WorkflowHintsObject(inner))),
SyntaxKind::WorkflowHintsArrayNode => Some(Self::Array(WorkflowHintsArray(inner))),
_ => None,
}
}
fn inner(&self) -> &N {
match self {
Self::Boolean(b) => &b.0,
Self::Integer(i) => &i.0,
Self::Float(f) => &f.0,
Self::String(s) => &s.0,
Self::Object(o) => &o.0,
Self::Array(a) => &a.0,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkflowHintsObject<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> WorkflowHintsObject<N> {
pub fn items(&self) -> impl Iterator<Item = WorkflowHintsObjectItem<N>> + use<'_, N> {
self.children()
}
}
impl<N: TreeNode> AstNode<N> for WorkflowHintsObject<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::WorkflowHintsObjectNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::WorkflowHintsObjectNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkflowHintsObjectItem<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> WorkflowHintsObjectItem<N> {
pub fn name(&self) -> Ident<N::Token> {
self.token().expect("expected a name")
}
pub fn value(&self) -> WorkflowHintsItemValue<N> {
self.child().expect("expected a value")
}
}
impl<N: TreeNode> AstNode<N> for WorkflowHintsObjectItem<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::WorkflowHintsObjectItemNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::WorkflowHintsObjectItemNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkflowHintsArray<N: TreeNode = SyntaxNode>(N);
impl<N: TreeNode> WorkflowHintsArray<N> {
pub fn elements(&self) -> impl Iterator<Item = WorkflowHintsItemValue<N>> + use<'_, N> {
self.children()
}
}
impl<N: TreeNode> AstNode<N> for WorkflowHintsArray<N> {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::WorkflowHintsArrayNode
}
fn cast(inner: N) -> Option<Self> {
match inner.kind() {
SyntaxKind::WorkflowHintsArrayNode => Some(Self(inner)),
_ => None,
}
}
fn inner(&self) -> &N {
&self.0
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Document;
#[test]
fn workflows() {
let (document, diagnostics) = Document::parse(
r#"
version 1.1
workflow test {
input {
String name
Boolean do_thing
}
output {
String greeting = "hello, ~{name}!"
}
if (do_thing) {
call foo.my_task
scatter (a in [1, 2, 3]) {
call my_task as my_task2 { input: a }
}
}
call my_task as my_task3 after my_task2 after my_task { input: a = 1 }
scatter (a in ["1", "2", "3"]) {
# Do nothing
}
meta {
description: "a test"
foo: null
}
parameter_meta {
name: {
help: "a name to greet"
}
}
hints {
foo: "bar"
}
String x = "private"
}
"#,
None,
);
assert!(diagnostics.is_empty());
let ast = document.ast();
let ast = ast.as_v1().expect("should be a V1 AST");
let workflows: Vec<_> = ast.workflows().collect();
assert_eq!(workflows.len(), 1);
assert_eq!(workflows[0].name().text(), "test");
let input = workflows[0]
.input()
.expect("workflow should have an input section");
assert_eq!(input.parent().unwrap_workflow().name().text(), "test");
let decls: Vec<_> = input.declarations().collect();
assert_eq!(decls.len(), 2);
assert_eq!(
decls[0].clone().unwrap_unbound_decl().ty().to_string(),
"String"
);
assert_eq!(decls[0].clone().unwrap_unbound_decl().name().text(), "name");
assert_eq!(
decls[1].clone().unwrap_unbound_decl().ty().to_string(),
"Boolean"
);
assert_eq!(
decls[1].clone().unwrap_unbound_decl().name().text(),
"do_thing"
);
let output = workflows[0]
.output()
.expect("workflow should have an output section");
assert_eq!(output.parent().unwrap_workflow().name().text(), "test");
let decls: Vec<_> = output.declarations().collect();
assert_eq!(decls.len(), 1);
assert_eq!(decls[0].ty().to_string(), "String");
assert_eq!(decls[0].name().text(), "greeting");
let parts: Vec<_> = decls[0]
.expr()
.unwrap_literal()
.unwrap_string()
.parts()
.collect();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].clone().unwrap_text().text(), "hello, ");
assert_eq!(
parts[1]
.clone()
.unwrap_placeholder()
.expr()
.unwrap_name_ref()
.name()
.text(),
"name"
);
assert_eq!(parts[2].clone().unwrap_text().text(), "!");
let statements: Vec<_> = workflows[0].statements().collect();
assert_eq!(statements.len(), 4);
let conditional = statements[0].clone().unwrap_conditional();
assert_eq!(
conditional
.if_clause()
.expr()
.expect("expression to exist for `if` clause")
.unwrap_name_ref()
.name()
.text(),
"do_thing"
);
let inner: Vec<_> = conditional.if_clause().statements().collect();
assert_eq!(inner.len(), 2);
let call = inner[0].clone().unwrap_call();
let names = call.target().names().collect::<Vec<_>>();
assert_eq!(names.len(), 2);
assert_eq!(names[0].text(), "foo");
assert_eq!(names[1].text(), "my_task");
assert!(call.alias().is_none());
assert_eq!(call.after().count(), 0);
assert_eq!(call.inputs().count(), 0);
let scatter = inner[1].clone().unwrap_scatter();
assert_eq!(scatter.variable().text(), "a");
let elements: Vec<_> = scatter
.expr()
.unwrap_literal()
.unwrap_array()
.elements()
.collect();
assert_eq!(elements.len(), 3);
assert_eq!(
elements[0]
.clone()
.unwrap_literal()
.unwrap_integer()
.value()
.unwrap(),
1
);
assert_eq!(
elements[1]
.clone()
.unwrap_literal()
.unwrap_integer()
.value()
.unwrap(),
2
);
assert_eq!(
elements[2]
.clone()
.unwrap_literal()
.unwrap_integer()
.value()
.unwrap(),
3
);
let inner: Vec<_> = scatter.statements().collect();
assert_eq!(inner.len(), 1);
let call = inner[0].clone().unwrap_call();
let names = call.target().names().collect::<Vec<_>>();
assert_eq!(names.len(), 1);
assert_eq!(names[0].text(), "my_task");
assert_eq!(call.alias().unwrap().name().text(), "my_task2");
assert_eq!(call.after().count(), 0);
let inputs: Vec<_> = call.inputs().collect();
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].name().text(), "a");
assert!(inputs[0].expr().is_none());
let call = statements[1].clone().unwrap_call();
assert_eq!(names.len(), 1);
assert_eq!(names[0].text(), "my_task");
assert_eq!(call.alias().unwrap().name().text(), "my_task3");
let after: Vec<_> = call.after().collect();
assert_eq!(after.len(), 2);
assert_eq!(after[0].name().text(), "my_task2");
assert_eq!(after[1].name().text(), "my_task");
let inputs: Vec<_> = call.inputs().collect();
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].name().text(), "a");
assert_eq!(
inputs[0]
.expr()
.unwrap()
.unwrap_literal()
.unwrap_integer()
.value()
.unwrap(),
1
);
let scatter = statements[2].clone().unwrap_scatter();
assert_eq!(scatter.variable().text(), "a");
let elements: Vec<_> = scatter
.expr()
.unwrap_literal()
.unwrap_array()
.elements()
.collect();
assert_eq!(elements.len(), 3);
assert_eq!(
elements[0]
.clone()
.unwrap_literal()
.unwrap_string()
.text()
.unwrap()
.text(),
"1"
);
assert_eq!(
elements[1]
.clone()
.unwrap_literal()
.unwrap_string()
.text()
.unwrap()
.text(),
"2"
);
assert_eq!(
elements[2]
.clone()
.unwrap_literal()
.unwrap_string()
.text()
.unwrap()
.text(),
"3"
);
let inner: Vec<_> = scatter.statements().collect();
assert_eq!(inner.len(), 0);
let metadata = workflows[0]
.metadata()
.expect("workflow should have a metadata section");
assert_eq!(metadata.parent().unwrap_workflow().name().text(), "test");
let items: Vec<_> = metadata.items().collect();
assert_eq!(items.len(), 2);
assert_eq!(items[0].name().text(), "description");
assert_eq!(
items[0].value().unwrap_string().text().unwrap().text(),
"a test"
);
assert_eq!(items[1].name().text(), "foo");
items[1].value().unwrap_null();
let param_meta = workflows[0]
.parameter_metadata()
.expect("workflow should have a parameter metadata section");
assert_eq!(param_meta.parent().unwrap_workflow().name().text(), "test");
let items: Vec<_> = param_meta.items().collect();
assert_eq!(items.len(), 1);
assert_eq!(items[0].name().text(), "name");
let items: Vec<_> = items[0].value().unwrap_object().items().collect();
assert_eq!(items.len(), 1);
assert_eq!(items[0].name().text(), "help");
assert_eq!(
items[0].value().unwrap_string().text().unwrap().text(),
"a name to greet"
);
let hints = workflows[0]
.hints()
.expect("workflow should have a hints section");
assert_eq!(hints.parent().name().text(), "test");
let items: Vec<_> = hints.items().collect();
assert_eq!(items.len(), 1);
assert_eq!(items[0].name().text(), "foo");
assert_eq!(
items[0].value().unwrap_string().text().unwrap().text(),
"bar"
);
let decls: Vec<_> = workflows[0].declarations().collect();
assert_eq!(decls.len(), 1);
assert_eq!(decls[0].ty().to_string(), "String");
assert_eq!(decls[0].name().text(), "x");
assert_eq!(
decls[0]
.expr()
.unwrap_literal()
.unwrap_string()
.text()
.unwrap()
.text(),
"private"
);
}
}