use std::fmt;
use super::Expr;
use crate::support;
use crate::support::child;
use crate::token;
use crate::AstNode;
use crate::AstToken;
use crate::Ident;
use crate::SyntaxKind;
use crate::SyntaxNode;
use crate::WorkflowDescriptionLanguage;
#[derive(Clone, Debug, Eq)]
pub struct MapType(SyntaxNode);
impl MapType {
pub fn types(&self) -> (PrimitiveType, Type) {
let mut children = self.0.children().filter_map(Type::cast);
let key = children
.next()
.expect("map should have a key type")
.unwrap_primitive_type();
let value = children.next().expect("map should have a value type");
(key, value)
}
pub fn is_optional(&self) -> bool {
matches!(
self.0.last_token().map(|t| t.kind()),
Some(SyntaxKind::QuestionMark)
)
}
}
impl PartialEq for MapType {
fn eq(&self, other: &Self) -> bool {
self.is_optional() == other.is_optional() && self.types() == other.types()
}
}
impl AstNode for MapType {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::MapTypeNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::MapTypeNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl fmt::Display for MapType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (key, value) = self.types();
write!(
f,
"Map[{key}, {value}]{o}",
o = if self.is_optional() { "?" } else { "" }
)
}
}
#[derive(Clone, Debug, Eq)]
pub struct ArrayType(SyntaxNode);
impl ArrayType {
pub fn element_type(&self) -> Type {
child(&self.0).expect("array should have an element type")
}
pub fn is_non_empty(&self) -> bool {
support::token(&self.0, SyntaxKind::Plus).is_some()
}
pub fn is_optional(&self) -> bool {
matches!(
self.0.last_token().map(|t| t.kind()),
Some(SyntaxKind::QuestionMark)
)
}
}
impl PartialEq for ArrayType {
fn eq(&self, other: &Self) -> bool {
self.is_optional() == other.is_optional()
&& self.is_non_empty() == other.is_non_empty()
&& self.element_type() == other.element_type()
}
}
impl AstNode for ArrayType {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::ArrayTypeNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::ArrayTypeNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl fmt::Display for ArrayType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Array[{ty}]{p}{o}",
ty = self.element_type(),
p = if self.is_non_empty() { "+" } else { "" },
o = if self.is_optional() { "?" } else { "" }
)
}
}
#[derive(Clone, Debug, Eq)]
pub struct PairType(SyntaxNode);
impl PairType {
pub fn types(&self) -> (Type, Type) {
let mut children = self.0.children().filter_map(Type::cast);
let first = children.next().expect("pair should have a first type");
let second = children.next().expect("pair should have a second type");
(first, second)
}
pub fn is_optional(&self) -> bool {
matches!(
self.0.last_token().map(|t| t.kind()),
Some(SyntaxKind::QuestionMark)
)
}
}
impl PartialEq for PairType {
fn eq(&self, other: &Self) -> bool {
self.is_optional() == other.is_optional() && self.types() == other.types()
}
}
impl AstNode for PairType {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::PairTypeNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::PairTypeNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl fmt::Display for PairType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (first, second) = self.types();
write!(
f,
"Pair[{first}, {second}]{o}",
o = if self.is_optional() { "?" } else { "" }
)
}
}
#[derive(Clone, Debug, Eq)]
pub struct ObjectType(SyntaxNode);
impl ObjectType {
pub fn is_optional(&self) -> bool {
matches!(
self.0.last_token().map(|t| t.kind()),
Some(SyntaxKind::QuestionMark)
)
}
}
impl PartialEq for ObjectType {
fn eq(&self, other: &Self) -> bool {
self.is_optional() == other.is_optional()
}
}
impl AstNode for ObjectType {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::ObjectTypeNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::ObjectTypeNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl fmt::Display for ObjectType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Object{o}",
o = if self.is_optional() { "?" } else { "" }
)
}
}
#[derive(Clone, Debug, Eq)]
pub struct TypeRef(SyntaxNode);
impl TypeRef {
pub fn name(&self) -> Ident {
token(&self.0).expect("type reference should have a name")
}
pub fn is_optional(&self) -> bool {
matches!(
self.0.last_token().map(|t| t.kind()),
Some(SyntaxKind::QuestionMark)
)
}
}
impl PartialEq for TypeRef {
fn eq(&self, other: &Self) -> bool {
self.is_optional() == other.is_optional() && self.name().as_str() == other.name().as_str()
}
}
impl AstNode for TypeRef {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::TypeRefNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::TypeRefNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl fmt::Display for TypeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{n}{o}",
n = self.name().as_str(),
o = if self.is_optional() { "?" } else { "" }
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PrimitiveTypeKind {
Boolean,
Integer,
Float,
String,
File,
Directory,
}
#[derive(Clone, Debug, Eq)]
pub struct PrimitiveType(SyntaxNode);
impl PrimitiveType {
pub fn kind(&self) -> PrimitiveTypeKind {
self.0
.children_with_tokens()
.find_map(|t| match t.kind() {
SyntaxKind::BooleanTypeKeyword => Some(PrimitiveTypeKind::Boolean),
SyntaxKind::IntTypeKeyword => Some(PrimitiveTypeKind::Integer),
SyntaxKind::FloatTypeKeyword => Some(PrimitiveTypeKind::Float),
SyntaxKind::StringTypeKeyword => Some(PrimitiveTypeKind::String),
SyntaxKind::FileTypeKeyword => Some(PrimitiveTypeKind::File),
SyntaxKind::DirectoryTypeKeyword => Some(PrimitiveTypeKind::Directory),
_ => None,
})
.expect("type should have a kind")
}
pub fn is_optional(&self) -> bool {
matches!(
self.0.last_token().map(|t| t.kind()),
Some(SyntaxKind::QuestionMark)
)
}
}
impl PartialEq for PrimitiveType {
fn eq(&self, other: &Self) -> bool {
self.kind() == other.kind()
}
}
impl AstNode for PrimitiveType {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::PrimitiveTypeNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::PrimitiveTypeNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind() {
PrimitiveTypeKind::Boolean => write!(f, "Boolean")?,
PrimitiveTypeKind::Integer => write!(f, "Int")?,
PrimitiveTypeKind::Float => write!(f, "Float")?,
PrimitiveTypeKind::String => write!(f, "String")?,
PrimitiveTypeKind::File => write!(f, "File")?,
PrimitiveTypeKind::Directory => write!(f, "Directory")?,
}
if self.is_optional() {
write!(f, "?")
} else {
Ok(())
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Type {
Map(MapType),
Array(ArrayType),
Pair(PairType),
Object(ObjectType),
Ref(TypeRef),
Primitive(PrimitiveType),
}
impl Type {
pub fn is_optional(&self) -> bool {
match self {
Self::Map(m) => m.is_optional(),
Self::Array(a) => a.is_optional(),
Self::Pair(p) => p.is_optional(),
Self::Object(o) => o.is_optional(),
Self::Ref(r) => r.is_optional(),
Self::Primitive(p) => p.is_optional(),
}
}
pub fn unwrap_map_type(self) -> MapType {
match self {
Self::Map(ty) => ty,
_ => panic!("not a map type"),
}
}
pub fn unwrap_array_type(self) -> ArrayType {
match self {
Self::Array(ty) => ty,
_ => panic!("not an array type"),
}
}
pub fn unwrap_pair_type(self) -> PairType {
match self {
Self::Pair(ty) => ty,
_ => panic!("not a pair type"),
}
}
pub fn unwrap_object_type(self) -> ObjectType {
match self {
Self::Object(ty) => ty,
_ => panic!("not an object type"),
}
}
pub fn unwrap_type_ref(self) -> TypeRef {
match self {
Self::Ref(r) => r,
_ => panic!("not a type reference"),
}
}
pub fn unwrap_primitive_type(self) -> PrimitiveType {
match self {
Self::Primitive(ty) => ty,
_ => panic!("not a primitive type"),
}
}
}
impl AstNode for Type {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
matches!(
kind,
SyntaxKind::MapTypeNode
| SyntaxKind::ArrayTypeNode
| SyntaxKind::PairTypeNode
| SyntaxKind::ObjectTypeNode
| SyntaxKind::TypeRefNode
| SyntaxKind::PrimitiveTypeNode
)
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::MapTypeNode => Some(Self::Map(MapType(syntax))),
SyntaxKind::ArrayTypeNode => Some(Self::Array(ArrayType(syntax))),
SyntaxKind::PairTypeNode => Some(Self::Pair(PairType(syntax))),
SyntaxKind::ObjectTypeNode => Some(Self::Object(ObjectType(syntax))),
SyntaxKind::TypeRefNode => Some(Self::Ref(TypeRef(syntax))),
SyntaxKind::PrimitiveTypeNode => Some(Self::Primitive(PrimitiveType(syntax))),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
match self {
Type::Map(m) => &m.0,
Type::Array(a) => &a.0,
Type::Pair(p) => &p.0,
Type::Object(o) => &o.0,
Type::Ref(r) => &r.0,
Type::Primitive(t) => &t.0,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Map(m) => m.fmt(f),
Type::Array(a) => a.fmt(f),
Type::Pair(p) => p.fmt(f),
Type::Object(o) => o.fmt(f),
Type::Ref(r) => r.fmt(f),
Type::Primitive(p) => p.fmt(f),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnboundDecl(pub(crate) SyntaxNode);
impl UnboundDecl {
pub fn ty(&self) -> Type {
child(&self.0).expect("unbound declaration should have a type")
}
pub fn name(&self) -> Ident {
token(&self.0).expect("unbound declaration should have a name")
}
}
impl AstNode for UnboundDecl {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::UnboundDeclNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::UnboundDeclNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BoundDecl(pub(crate) SyntaxNode);
impl BoundDecl {
pub fn ty(&self) -> Type {
child(&self.0).expect("bound declaration should have a type")
}
pub fn name(&self) -> Ident {
token(&self.0).expect("bound declaration should have a name")
}
pub fn expr(&self) -> Expr {
child(&self.0).expect("bound declaration should have an expression")
}
}
impl AstNode for BoundDecl {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::BoundDeclNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::BoundDeclNode => Some(Self(syntax)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Decl {
Bound(BoundDecl),
Unbound(UnboundDecl),
}
impl Decl {
pub fn ty(&self) -> Type {
match self {
Self::Bound(d) => d.ty(),
Self::Unbound(d) => d.ty(),
}
}
pub fn name(&self) -> Ident {
match self {
Self::Bound(d) => d.name(),
Self::Unbound(d) => d.name(),
}
}
pub fn expr(&self) -> Option<Expr> {
match self {
Self::Bound(d) => Some(d.expr()),
Self::Unbound(_) => None,
}
}
pub fn unwrap_bound_decl(self) -> BoundDecl {
match self {
Self::Bound(decl) => decl,
_ => panic!("not a bound declaration"),
}
}
pub fn unwrap_unbound_decl(self) -> UnboundDecl {
match self {
Self::Unbound(decl) => decl,
_ => panic!("not an unbound declaration"),
}
}
}
impl AstNode for Decl {
type Language = WorkflowDescriptionLanguage;
fn can_cast(kind: SyntaxKind) -> bool
where
Self: Sized,
{
kind == SyntaxKind::BoundDeclNode || kind == SyntaxKind::UnboundDeclNode
}
fn cast(syntax: SyntaxNode) -> Option<Self>
where
Self: Sized,
{
match syntax.kind() {
SyntaxKind::BoundDeclNode => Some(Self::Bound(BoundDecl(syntax))),
SyntaxKind::UnboundDeclNode => Some(Self::Unbound(UnboundDecl(syntax))),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode {
match self {
Self::Bound(b) => &b.0,
Self::Unbound(u) => &u.0,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Document;
use crate::SupportedVersion;
use crate::VisitReason;
use crate::Visitor;
#[test]
fn decls() {
let (document, diagnostics) = Document::parse(
r#"
version 1.1
task test {
input {
Boolean a
Int b = 42
Float? c = None
String d
File e = "foo.wdl"
Map[Int, Int] f
Array[String] g = []
Pair[Boolean, Int] h
Object i = object {}
MyStruct j
Directory k = "foo"
}
}
"#,
);
assert!(diagnostics.is_empty());
let ast = document.ast();
let ast = ast.as_v1().expect("should be a V1 AST");
let tasks: Vec<_> = ast.tasks().collect();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].name().as_str(), "test");
let input = tasks[0].input().expect("task should have an input section");
let decls: Vec<_> = input.declarations().collect();
assert_eq!(decls.len(), 11);
let decl = decls[0].clone().unwrap_unbound_decl();
assert_eq!(decl.ty().to_string(), "Boolean");
assert_eq!(decl.name().as_str(), "a");
let decl = decls[1].clone().unwrap_bound_decl();
assert_eq!(decl.ty().to_string(), "Int");
assert_eq!(decl.name().as_str(), "b");
assert_eq!(
decl.expr()
.unwrap_literal()
.unwrap_integer()
.value()
.unwrap(),
42
);
let decl = decls[2].clone().unwrap_bound_decl();
assert_eq!(decl.ty().to_string(), "Float?");
assert_eq!(decl.name().as_str(), "c");
decl.expr().unwrap_literal().unwrap_none();
let decl = decls[3].clone().unwrap_unbound_decl();
assert_eq!(decl.ty().to_string(), "String");
assert_eq!(decl.name().as_str(), "d");
let decl = decls[4].clone().unwrap_bound_decl();
assert_eq!(decl.ty().to_string(), "File");
assert_eq!(decl.name().as_str(), "e");
assert_eq!(
decl.expr()
.unwrap_literal()
.unwrap_string()
.text()
.unwrap()
.as_str(),
"foo.wdl"
);
let decl = decls[5].clone().unwrap_unbound_decl();
assert_eq!(decl.ty().to_string(), "Map[Int, Int]");
assert_eq!(decl.name().as_str(), "f");
let decl = decls[6].clone().unwrap_bound_decl();
assert_eq!(decl.ty().to_string(), "Array[String]");
assert_eq!(decl.name().as_str(), "g");
assert_eq!(
decl.expr()
.unwrap_literal()
.unwrap_array()
.elements()
.count(),
0
);
let decl = decls[7].clone().unwrap_unbound_decl();
assert_eq!(decl.ty().to_string(), "Pair[Boolean, Int]");
assert_eq!(decl.name().as_str(), "h");
let decl = decls[8].clone().unwrap_bound_decl();
assert_eq!(decl.ty().to_string(), "Object");
assert_eq!(decl.name().as_str(), "i");
assert_eq!(
decl.expr().unwrap_literal().unwrap_object().items().count(),
0
);
let decl = decls[9].clone().unwrap_unbound_decl();
assert_eq!(decl.ty().to_string(), "MyStruct");
assert_eq!(decl.name().as_str(), "j");
let decl = decls[10].clone().unwrap_bound_decl();
assert_eq!(decl.ty().to_string(), "Directory");
assert_eq!(decl.name().as_str(), "k");
assert_eq!(
decl.expr()
.unwrap_literal()
.unwrap_string()
.text()
.unwrap()
.as_str(),
"foo"
);
#[derive(Default)]
struct MyVisitor {
bound: usize,
unbound: usize,
}
impl Visitor for MyVisitor {
type State = ();
fn document(
&mut self,
_: &mut Self::State,
_: VisitReason,
_: &Document,
_: SupportedVersion,
) {
}
fn bound_decl(&mut self, _: &mut Self::State, reason: VisitReason, _: &BoundDecl) {
if reason == VisitReason::Enter {
self.bound += 1;
}
}
fn unbound_decl(&mut self, _: &mut Self::State, reason: VisitReason, _: &UnboundDecl) {
if reason == VisitReason::Enter {
self.unbound += 1;
}
}
}
let mut visitor = MyVisitor::default();
document.visit(&mut (), &mut visitor);
assert_eq!(visitor.bound, 6);
assert_eq!(visitor.unbound, 5);
}
}