use std::borrow::Cow; use std::str; use nom::branch::alt; use nom::bytes::complete::{tag, take_until}; use nom::character::complete::char; use nom::combinator::{ complete, consumed, cut, eof, map, map_res, not, opt, peek, recognize, value, }; use nom::error::{Error, ErrorKind}; use nom::error_position; use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use crate::{ErrorContext, ParseResult}; use super::{ bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, str_lit, ws, Expr, PathOrIdentifier, State, }; #[derive(Debug, PartialEq)] pub enum Node<'a> { Lit(Lit<'a>), Comment(Comment<'a>), Expr(Ws, Expr<'a>), Call(Call<'a>), Let(Let<'a>), If(If<'a>), Match(Match<'a>), Loop(Box>), Extends(Extends<'a>), BlockDef(BlockDef<'a>), Include(Include<'a>), Import(Import<'a>), Macro(Macro<'a>), Raw(Raw<'a>), Break(Ws), Continue(Ws), } impl<'a> Node<'a> { pub(super) fn many(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec> { complete(many0(alt(( map(|i| Lit::parse(i, s), Self::Lit), map(|i| Comment::parse(i, s), Self::Comment), |i| Self::expr(i, s), |i| Self::parse(i, s), ))))(i) } fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = delimited( |i| s.tag_block_start(i), alt(( map(|i| Call::parse(i, s), Self::Call), map(|i| Let::parse(i, s), Self::Let), map(|i| If::parse(i, s), Self::If), map(|i| Loop::parse(i, s), |l| Self::Loop(Box::new(l))), map(|i| Match::parse(i, s), Self::Match), map(Extends::parse, Self::Extends), map(Include::parse, Self::Include), map(Import::parse, Self::Import), map(|i| BlockDef::parse(i, s), Self::BlockDef), map(|i| Macro::parse(i, s), Self::Macro), map(|i| Raw::parse(i, s), Self::Raw), |i| Self::r#break(i, s), |i| Self::r#continue(i, s), )), cut(|i| s.tag_block_end(i)), ); s.nest(i)?; let result = p(i); s.leave(); result } fn r#break(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("break")), opt(Whitespace::parse), )); let (j, (pws, _, nws)) = p(i)?; if !s.is_in_loop() { return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag))); } Ok((j, Self::Break(Ws(pws, nws)))) } fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("continue")), opt(Whitespace::parse), )); let (j, (pws, _, nws)) = p(i)?; if !s.is_in_loop() { return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag))); } Ok((j, Self::Continue(Ws(pws, nws)))) } fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_expr_start(i), cut(tuple(( opt(Whitespace::parse), ws(|i| Expr::parse(i, s.level.get())), opt(Whitespace::parse), |i| s.tag_expr_end(i), ))), )); let (i, (_, (pws, expr, nws, _))) = p(i)?; Ok((i, Self::Expr(Ws(pws, nws), expr))) } } #[derive(Clone, Debug, PartialEq)] pub enum Target<'a> { Name(&'a str), Tuple(Vec<&'a str>, Vec>), Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>), NumLit(&'a str), StrLit(&'a str), CharLit(&'a str), BoolLit(&'a str), Path(Vec<&'a str>), OrChain(Vec>), } impl<'a> Target<'a> { /// Parses multiple targets with `or` separating them pub(super) fn parse(i: &'a str) -> ParseResult<'a, Self> { map( separated_list1(ws(tag("or")), Self::parse_one), |mut opts| match opts.len() { 1 => opts.pop().unwrap(), _ => Self::OrChain(opts), }, )(i) } /// Parses a single target without an `or`, unless it is wrapped in parentheses. fn parse_one(i: &'a str) -> ParseResult<'a, Self> { let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some()); let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some()); let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some()); let (i, lit) = opt(Self::lit)(i)?; if let Some(lit) = lit { return Ok((i, lit)); } // match tuples and unused parentheses let (i, target_is_tuple) = opt_opening_paren(i)?; if target_is_tuple { let (i, is_empty_tuple) = opt_closing_paren(i)?; if is_empty_tuple { return Ok((i, Self::Tuple(Vec::new(), Vec::new()))); } let (i, first_target) = Self::parse(i)?; let (i, is_unused_paren) = opt_closing_paren(i)?; if is_unused_paren { return Ok((i, first_target)); } let mut targets = vec![first_target]; let (i, _) = cut(tuple(( fold_many0( preceded(ws(char(',')), Self::parse), || (), |_, target| { targets.push(target); }, ), opt(ws(char(','))), ws(cut(char(')'))), )))(i)?; return Ok((i, Self::Tuple(Vec::new(), targets))); } let path = |i| { map_res(path_or_identifier, |v| match v { PathOrIdentifier::Path(v) => Ok(v), PathOrIdentifier::Identifier(v) => Err(v), })(i) }; // match structs let (i, path) = opt(path)(i)?; if let Some(path) = path { let i_before_matching_with = i; let (i, _) = opt(ws(keyword("with")))(i)?; let (i, is_unnamed_struct) = opt_opening_paren(i)?; if is_unnamed_struct { let (i, targets) = alt(( map(char(')'), |_| Vec::new()), terminated( cut(separated_list1(ws(char(',')), Self::parse)), pair(opt(ws(char(','))), ws(cut(char(')')))), ), ))(i)?; return Ok((i, Self::Tuple(path, targets))); } let (i, is_named_struct) = opt_opening_brace(i)?; if is_named_struct { let (i, targets) = alt(( map(char('}'), |_| Vec::new()), terminated( cut(separated_list1(ws(char(',')), Self::named)), pair(opt(ws(char(','))), ws(cut(char('}')))), ), ))(i)?; return Ok((i, Self::Struct(path, targets))); } return Ok((i_before_matching_with, Self::Path(path))); } // neither literal nor struct nor path let (new_i, name) = identifier(i)?; Ok((new_i, Self::verify_name(i, name)?)) } fn lit(i: &'a str) -> ParseResult<'a, Self> { alt(( map(str_lit, Self::StrLit), map(char_lit, Self::CharLit), map(num_lit, Self::NumLit), map(bool_lit, Self::BoolLit), ))(i) } fn named(init_i: &'a str) -> ParseResult<'a, (&str, Self)> { let (i, (src, target)) = pair(identifier, opt(preceded(ws(char(':')), Self::parse)))(init_i)?; let target = match target { Some(target) => target, None => Self::verify_name(init_i, src)?, }; Ok((i, (src, target))) } fn verify_name(input: &'a str, name: &'a str) -> Result>> { match name { "self" | "writer" => Err(nom::Err::Failure(ErrorContext { input, message: Some(Cow::Owned(format!("Cannot use `{name}` as a name"))), })), _ => Ok(Self::Name(name)), } } } #[derive(Debug, PartialEq)] pub struct When<'a> { pub ws: Ws, pub target: Target<'a>, pub nodes: Vec>, } impl<'a> When<'a> { fn r#match(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("else")), cut(tuple(( opt(Whitespace::parse), |i| s.tag_block_end(i), cut(|i| Node::many(i, s)), ))), )); let (i, (_, pws, _, (nws, _, nodes))) = p(i)?; Ok(( i, Self { ws: Ws(pws, nws), target: Target::Name("_"), nodes, }, )) } #[allow(clippy::self_named_constructors)] fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("when")), cut(tuple(( ws(Target::parse), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(|i| Node::many(i, s)), ))), )); let (i, (_, pws, _, (target, nws, _, nodes))) = p(i)?; Ok(( i, Self { ws: Ws(pws, nws), target, nodes, }, )) } } #[derive(Debug, PartialEq)] pub struct Cond<'a> { pub ws: Ws, pub cond: Option>, pub nodes: Vec>, } impl<'a> Cond<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(alt((keyword("else"), |i| { let _ = keyword("elif")(i)?; Err(nom::Err::Failure(ErrorContext { input: i, message: Some(Cow::Borrowed( "unknown `elif` keyword; did you mean `else if`?", )), })) }))), cut(tuple(( opt(|i| CondTest::parse(i, s)), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(|i| Node::many(i, s)), ))), )); let (i, (_, pws, _, (cond, nws, _, nodes))) = p(i)?; Ok(( i, Self { ws: Ws(pws, nws), cond, nodes, }, )) } } #[derive(Debug, PartialEq)] pub struct CondTest<'a> { pub target: Option>, pub expr: Expr<'a>, } impl<'a> CondTest<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = preceded( ws(keyword("if")), cut(tuple(( opt(delimited( ws(alt((keyword("let"), keyword("set")))), ws(Target::parse), ws(char('=')), )), ws(|i| Expr::parse(i, s.level.get())), ))), ); let (i, (target, expr)) = p(i)?; Ok((i, Self { target, expr })) } } #[derive(Clone, Copy, Debug, PartialEq)] pub enum Whitespace { Preserve, Suppress, Minimize, } impl Whitespace { fn parse(i: &str) -> ParseResult<'_, Self> { alt(( value(Self::Preserve, char('+')), value(Self::Suppress, char('-')), value(Self::Minimize, char('~')), ))(i) } } #[derive(Debug, PartialEq)] pub struct Loop<'a> { pub ws1: Ws, pub var: Target<'a>, pub iter: Expr<'a>, pub cond: Option>, pub body: Vec>, pub ws2: Ws, pub else_nodes: Vec>, pub ws3: Ws, } impl<'a> Loop<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec>> { s.enter_loop(); let result = Node::many(i, s); s.leave_loop(); result } let if_cond = preceded( ws(keyword("if")), cut(ws(|i| Expr::parse(i, s.level.get()))), ); let else_block = |i| { let mut p = preceded( ws(keyword("else")), cut(tuple(( opt(Whitespace::parse), delimited( |i| s.tag_block_end(i), |i| Node::many(i, s), |i| s.tag_block_start(i), ), opt(Whitespace::parse), ))), ); let (i, (pws, nodes, nws)) = p(i)?; Ok((i, (pws, nodes, nws))) }; let mut p = tuple(( opt(Whitespace::parse), ws(keyword("for")), cut(tuple(( ws(Target::parse), ws(keyword("in")), cut(tuple(( ws(|i| Expr::parse(i, s.level.get())), opt(if_cond), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(tuple(( |i| content(i, s), cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), opt(else_block), ws(keyword("endfor")), opt(Whitespace::parse), ))), ))), ))), ))), )); let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) = p(i)?; let (nws3, else_block, pws3) = else_block.unwrap_or_default(); Ok(( i, Self { ws1: Ws(pws1, nws1), var, iter, cond, body, ws2: Ws(pws2, nws3), else_nodes: else_block, ws3: Ws(pws3, nws2), }, )) } } #[derive(Debug, PartialEq)] pub struct Macro<'a> { pub ws1: Ws, pub name: &'a str, pub args: Vec<&'a str>, pub nodes: Vec>, pub ws2: Ws, } impl<'a> Macro<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> { delimited( ws(char('(')), separated_list0(char(','), ws(identifier)), ws(char(')')), )(i) } let mut start = tuple(( opt(Whitespace::parse), ws(keyword("macro")), cut(tuple(( ws(identifier), opt(ws(parameters)), opt(Whitespace::parse), |i| s.tag_block_end(i), ))), )); let (i, (pws1, _, (name, params, nws1, _))) = start(i)?; let mut end = cut(tuple(( |i| Node::many(i, s), cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endmacro")), cut(preceded( opt(|before| { let (after, end_name) = ws(identifier)(before)?; check_end_name(before, after, name, end_name, "macro") }), opt(Whitespace::parse), )), ))), ))); let (i, (contents, (_, pws2, _, nws2))) = end(i)?; if name == "super" { // TODO: yield a a better error message here return Err(ErrorContext::from_err(nom::Err::Failure(Error::new( i, ErrorKind::Fail, )))); } Ok(( i, Self { ws1: Ws(pws1, nws1), name, args: params.unwrap_or_default(), nodes: contents, ws2: Ws(pws2, nws2), }, )) } } #[derive(Debug, PartialEq)] pub struct Import<'a> { pub ws: Ws, pub path: &'a str, pub scope: &'a str, } impl<'a> Import<'a> { fn parse(i: &'a str) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("import")), cut(tuple(( ws(str_lit), ws(keyword("as")), cut(pair(ws(identifier), opt(Whitespace::parse))), ))), )); let (i, (pws, _, (path, _, (scope, nws)))) = p(i)?; Ok(( i, Self { ws: Ws(pws, nws), path, scope, }, )) } } #[derive(Debug, PartialEq)] pub struct Call<'a> { pub ws: Ws, pub scope: Option<&'a str>, pub name: &'a str, pub args: Vec>, } impl<'a> Call<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("call")), cut(tuple(( opt(tuple((ws(identifier), ws(tag("::"))))), ws(identifier), opt(ws(|nested| Expr::arguments(nested, s.level.get(), true))), opt(Whitespace::parse), ))), )); let (i, (pws, _, (scope, name, args, nws))) = p(i)?; let scope = scope.map(|(scope, _)| scope); let args = args.unwrap_or_default(); Ok(( i, Self { ws: Ws(pws, nws), scope, name, args, }, )) } } #[derive(Debug, PartialEq)] pub struct Match<'a> { pub ws1: Ws, pub expr: Expr<'a>, pub arms: Vec>, pub ws2: Ws, } impl<'a> Match<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("match")), cut(tuple(( ws(|i| Expr::parse(i, s.level.get())), opt(Whitespace::parse), |i| s.tag_block_end(i), cut(tuple(( ws(many0(ws(value((), |i| Comment::parse(i, s))))), many1(|i| When::when(i, s)), cut(tuple(( opt(|i| When::r#match(i, s)), cut(tuple(( ws(|i| s.tag_block_start(i)), opt(Whitespace::parse), ws(keyword("endmatch")), opt(Whitespace::parse), ))), ))), ))), ))), )); let (i, (pws1, _, (expr, nws1, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?; let mut arms = arms; if let Some(arm) = else_arm { arms.push(arm); } Ok(( i, Self { ws1: Ws(pws1, nws1), expr, arms, ws2: Ws(pws2, nws2), }, )) } } #[derive(Debug, PartialEq)] pub struct BlockDef<'a> { pub ws1: Ws, pub name: &'a str, pub nodes: Vec>, pub ws2: Ws, } impl<'a> BlockDef<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut start = tuple(( opt(Whitespace::parse), ws(keyword("block")), cut(tuple((ws(identifier), opt(Whitespace::parse), |i| { s.tag_block_end(i) }))), )); let (i, (pws1, _, (name, nws1, _))) = start(i)?; let mut end = cut(tuple(( |i| Node::many(i, s), cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endblock")), cut(tuple(( opt(|before| { let (after, end_name) = ws(identifier)(before)?; check_end_name(before, after, name, end_name, "block") }), opt(Whitespace::parse), ))), ))), ))); let (i, (nodes, (_, pws2, _, (_, nws2)))) = end(i)?; Ok(( i, BlockDef { ws1: Ws(pws1, nws1), name, nodes, ws2: Ws(pws2, nws2), }, )) } } fn check_end_name<'a>( before: &'a str, after: &'a str, name: &'a str, end_name: &'a str, kind: &str, ) -> ParseResult<'a> { if name == end_name { return Ok((after, end_name)); } let message = if name.is_empty() && !end_name.is_empty() { format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`") } else { format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`") }; Err(nom::Err::Failure(ErrorContext { input: before, message: Some(Cow::Owned(message)), })) } #[derive(Debug, PartialEq)] pub struct Lit<'a> { pub lws: &'a str, pub val: &'a str, pub rws: &'a str, } impl<'a> Lit<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let p_start = alt(( tag(s.syntax.block_start), tag(s.syntax.comment_start), tag(s.syntax.expr_start), )); let (i, _) = not(eof)(i)?; let (i, content) = opt(recognize(skip_till(p_start)))(i)?; let (i, content) = match content { Some("") => { // {block,comment,expr}_start follows immediately. return Err(nom::Err::Error(error_position!(i, ErrorKind::TakeUntil))); } Some(content) => (i, content), None => ("", i), // there is no {block,comment,expr}_start: take everything }; Ok((i, Self::split_ws_parts(content))) } pub(crate) fn split_ws_parts(s: &'a str) -> Self { let trimmed_start = s.trim_start_matches(is_ws); let len_start = s.len() - trimmed_start.len(); let trimmed = trimmed_start.trim_end_matches(is_ws); Self { lws: &s[..len_start], val: trimmed, rws: &trimmed_start[trimmed.len()..], } } } #[derive(Debug, PartialEq)] pub struct Raw<'a> { pub ws1: Ws, pub lit: Lit<'a>, pub ws2: Ws, } impl<'a> Raw<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let endraw = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endraw")), opt(Whitespace::parse), peek(|i| s.tag_block_end(i)), )); let mut p = tuple(( opt(Whitespace::parse), ws(keyword("raw")), cut(tuple(( opt(Whitespace::parse), |i| s.tag_block_end(i), consumed(skip_till(endraw)), ))), )); let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?; let lit = Lit::split_ws_parts(contents); let ws1 = Ws(pws1, nws1); let ws2 = Ws(pws2, nws2); Ok((i, Self { ws1, lit, ws2 })) } } #[derive(Debug, PartialEq)] pub struct Let<'a> { pub ws: Ws, pub var: Target<'a>, pub val: Option>, } impl<'a> Let<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), cut(tuple(( ws(Target::parse), opt(preceded( ws(char('=')), ws(|i| Expr::parse(i, s.level.get())), )), opt(Whitespace::parse), ))), )); let (i, (pws, _, (var, val, nws))) = p(i)?; Ok(( i, Let { ws: Ws(pws, nws), var, val, }, )) } } #[derive(Debug, PartialEq)] pub struct If<'a> { pub ws: Ws, pub branches: Vec>, } impl<'a> If<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), |i| CondTest::parse(i, s), cut(tuple(( opt(Whitespace::parse), |i| s.tag_block_end(i), cut(tuple(( |i| Node::many(i, s), many0(|i| Cond::parse(i, s)), cut(tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endif")), opt(Whitespace::parse), ))), ))), ))), )); let (i, (pws1, cond, (nws1, _, (nodes, elifs, (_, pws2, _, nws2))))) = p(i)?; let mut branches = vec![Cond { ws: Ws(pws1, nws1), cond: Some(cond), nodes, }]; branches.extend(elifs); Ok(( i, Self { ws: Ws(pws2, nws2), branches, }, )) } } #[derive(Debug, PartialEq)] pub struct Include<'a> { pub ws: Ws, pub path: &'a str, } impl<'a> Include<'a> { fn parse(i: &'a str) -> ParseResult<'a, Self> { let mut p = tuple(( opt(Whitespace::parse), ws(keyword("include")), cut(pair(ws(str_lit), opt(Whitespace::parse))), )); let (i, (pws, _, (path, nws))) = p(i)?; Ok(( i, Self { ws: Ws(pws, nws), path, }, )) } } #[derive(Debug, PartialEq)] pub struct Extends<'a> { pub path: &'a str, } impl<'a> Extends<'a> { fn parse(i: &'a str) -> ParseResult<'a, Self> { let (i, path) = preceded(ws(keyword("extends")), cut(ws(str_lit)))(i)?; Ok((i, Self { path })) } } #[derive(Debug, PartialEq)] pub struct Comment<'a> { pub ws: Ws, pub content: &'a str, } impl<'a> Comment<'a> { fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { fn body<'a>(mut i: &'a str, s: &State<'_>) -> ParseResult<'a> { let mut level = 0; loop { let (end, tail) = take_until(s.syntax.comment_end)(i)?; match take_until::<_, _, ErrorContext<'_>>(s.syntax.comment_start)(i) { Ok((start, _)) if start.as_ptr() < end.as_ptr() => { level += 1; i = &start[2..]; } _ if level > 0 => { level -= 1; i = &end[2..]; } _ => return Ok((end, tail)), } } } let mut p = tuple(( |i| s.tag_comment_start(i), cut(tuple(( opt(Whitespace::parse), |i| body(i, s), |i| s.tag_comment_end(i), ))), )); let (i, (content, (pws, tail, _))) = p(i)?; let nws = if tail.ends_with('-') { Some(Whitespace::Suppress) } else if tail.ends_with('+') { Some(Whitespace::Preserve) } else if tail.ends_with('~') { Some(Whitespace::Minimize) } else { None }; Ok(( i, Self { ws: Ws(pws, nws), content, }, )) } } /// First field is "minus/plus sign was used on the left part of the item". /// /// Second field is "minus/plus sign was used on the right part of the item". #[derive(Clone, Copy, Debug, PartialEq)] pub struct Ws(pub Option, pub Option);