#![allow(clippy::type_complexity)] use crate::parse::{call_node::*, comment::*, flag::*, number::*, operator::*, pipeline::*}; use derive_new::new; use getset::Getters; use nu_errors::{ParseError, ShellError}; use nu_protocol::{ShellTypeName, SpannedTypeName}; use nu_source::{ b, DebugDocBuilder, HasSpan, PrettyDebugWithSource, Span, Spanned, SpannedItem, Text, }; use std::borrow::Cow; use std::ops::Deref; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Token { Number(RawNumber), CompareOperator(CompareOperator), EvaluationOperator(EvaluationOperator), String(Span), Variable(Span), ItVariable(Span), ExternalCommand(Span), ExternalWord, GlobPattern, Bare, Garbage, Call(CallNode), Delimited(DelimitedNode), Pipeline(Pipeline), Flag(Flag), Comment(Comment), Whitespace, Separator, } macro_rules! token_type { (struct $name:tt (desc: $desc:tt) -> $out:ty { |$span:ident, $pat:pat| => $do:expr }) => { pub struct $name; impl TokenType for $name { type Output = $out; fn desc(&self) -> Cow<'static, str> { Cow::Borrowed($desc) } fn extract_token_value( &self, token: &SpannedToken, err: ParseErrorFn<$out>, ) -> Result<$out, ParseError> { let $span = token.span(); match *token.unspanned() { $pat => Ok($do), _ => err(), } } } }; (struct $name:tt (desc: $desc:tt) -> $out:ty { $pat:pat => $do:expr }) => { pub struct $name; impl TokenType for $name { type Output = $out; fn desc(&self) -> Cow<'static, str> { Cow::Borrowed($desc) } fn extract_token_value( &self, token: &SpannedToken, err: ParseErrorFn<$out>, ) -> Result<$out, ParseError> { match token.unspanned().clone() { $pat => Ok($do), _ => err(), } } } }; } pub type ParseErrorFn<'a, T> = &'a dyn Fn() -> Result; token_type!(struct IntType (desc: "integer") -> RawNumber { Token::Number(number @ RawNumber::Int(_)) => number }); token_type!(struct DecimalType (desc: "decimal") -> RawNumber { Token::Number(number @ RawNumber::Decimal(_)) => number }); token_type!(struct StringType (desc: "string") -> (Span, Span) { |outer, Token::String(inner)| => (inner, outer) }); token_type!(struct BareType (desc: "word") -> Span { |span, Token::Bare| => span }); token_type!(struct DotType (desc: "dot") -> Span { |span, Token::EvaluationOperator(EvaluationOperator::Dot)| => span }); token_type!(struct DotDotType (desc: "dotdot") -> Span { |span, Token::EvaluationOperator(EvaluationOperator::DotDot)| => span }); token_type!(struct CompareOperatorType (desc: "compare operator") -> (Span, CompareOperator) { |span, Token::CompareOperator(operator)| => (span, operator) }); token_type!(struct ExternalWordType (desc: "external word") -> Span { |span, Token::ExternalWord| => span }); token_type!(struct ExternalCommandType (desc: "external command") -> (Span, Span) { |outer, Token::ExternalCommand(inner)| => (inner, outer) }); token_type!(struct CommentType (desc: "comment") -> (Comment, Span) { |outer, Token::Comment(comment)| => (comment, outer) }); token_type!(struct SeparatorType (desc: "separator") -> Span { |span, Token::Separator| => span }); token_type!(struct WhitespaceType (desc: "whitespace") -> Span { |span, Token::Whitespace| => span }); token_type!(struct WordType (desc: "word") -> Span { |span, Token::Bare| => span }); token_type!(struct ItVarType (desc: "$it") -> (Span, Span) { |outer, Token::ItVariable(inner)| => (inner, outer) }); token_type!(struct VarType (desc: "variable") -> (Span, Span) { |outer, Token::Variable(inner)| => (inner, outer) }); token_type!(struct PipelineType (desc: "pipeline") -> Pipeline { Token::Pipeline(pipeline) => pipeline }); token_type!(struct BlockType (desc: "block") -> DelimitedNode { Token::Delimited(block @ DelimitedNode { delimiter: Delimiter::Brace, .. }) => block }); token_type!(struct SquareType (desc: "square") -> DelimitedNode { Token::Delimited(square @ DelimitedNode { delimiter: Delimiter::Square, .. }) => square }); pub trait TokenType { type Output; fn desc(&self) -> Cow<'static, str>; fn extract_token_value( &self, token: &SpannedToken, err: ParseErrorFn, ) -> Result; } impl Token { pub fn into_spanned(self, span: impl Into) -> SpannedToken { SpannedToken { unspanned: self, span: span.into(), } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters)] pub struct SpannedToken { #[get = "pub"] unspanned: Token, span: Span, } impl Deref for SpannedToken { type Target = Token; fn deref(&self) -> &Self::Target { &self.unspanned } } impl HasSpan for SpannedToken { fn span(&self) -> Span { self.span } } impl ShellTypeName for SpannedToken { fn type_name(&self) -> &'static str { self.unspanned.type_name() } } impl PrettyDebugWithSource for SpannedToken { fn pretty_debug(&self, source: &str) -> DebugDocBuilder { match self.unspanned() { Token::Number(number) => number.pretty_debug(source), Token::CompareOperator(operator) => operator.pretty_debug(source), Token::EvaluationOperator(operator) => operator.pretty_debug(source), Token::String(_) | Token::GlobPattern | Token::Bare => { b::primitive(self.span.slice(source)) } Token::Variable(_) => b::var(self.span.slice(source)), Token::ItVariable(_) => b::keyword(self.span.slice(source)), Token::ExternalCommand(_) => b::description(self.span.slice(source)), Token::ExternalWord => b::description(self.span.slice(source)), Token::Call(call) => call.pretty_debug(source), Token::Delimited(delimited) => delimited.pretty_debug(source), Token::Pipeline(pipeline) => pipeline.pretty_debug(source), Token::Flag(flag) => flag.pretty_debug(source), Token::Garbage => b::error(self.span.slice(source)), Token::Whitespace => b::typed( "whitespace", b::description(format!("{:?}", self.span.slice(source))), ), Token::Separator => b::typed( "separator", b::description(format!("{:?}", self.span.slice(source))), ), Token::Comment(comment) => { b::typed("comment", b::description(comment.text.slice(source))) } } } } impl ShellTypeName for Token { fn type_name(&self) -> &'static str { match self { Token::Number(_) => "number", Token::CompareOperator(_) => "comparison operator", Token::EvaluationOperator(EvaluationOperator::Dot) => "dot", Token::EvaluationOperator(EvaluationOperator::DotDot) => "dot dot", Token::String(_) => "string", Token::Variable(_) => "variable", Token::ItVariable(_) => "it variable", Token::ExternalCommand(_) => "external command", Token::ExternalWord => "external word", Token::GlobPattern => "glob pattern", Token::Bare => "word", Token::Call(_) => "command", Token::Delimited(d) => d.type_name(), Token::Pipeline(_) => "pipeline", Token::Flag(_) => "flag", Token::Garbage => "garbage", Token::Whitespace => "whitespace", Token::Separator => "separator", Token::Comment(_) => "comment", } } } impl From<&SpannedToken> for Span { fn from(token: &SpannedToken) -> Span { token.span } } impl SpannedToken { pub fn as_external_arg(&self, source: &Text) -> String { self.span().slice(source).to_string() } pub fn source<'a>(&self, source: &'a Text) -> &'a str { self.span().slice(source) } pub fn get_variable(&self) -> Result<(Span, Span), ShellError> { match self.unspanned() { Token::Variable(inner_span) => Ok((self.span(), *inner_span)), _ => Err(ShellError::type_error("variable", self.spanned_type_name())), } } pub fn is_bare(&self) -> bool { match self.unspanned() { Token::Bare => true, _ => false, } } pub fn is_string(&self) -> bool { match self.unspanned() { Token::String(_) => true, _ => false, } } pub fn is_number(&self) -> bool { match self.unspanned() { Token::Number(_) => true, _ => false, } } pub fn as_string(&self) -> Option<(Span, Span)> { match self.unspanned() { Token::String(inner_span) => Some((self.span(), *inner_span)), _ => None, } } pub fn is_pattern(&self) -> bool { match self.unspanned() { Token::GlobPattern => true, _ => false, } } pub fn is_word(&self) -> bool { match self.unspanned() { Token::Bare => true, _ => false, } } pub fn is_int(&self) -> bool { match self.unspanned() { Token::Number(RawNumber::Int(_)) => true, _ => false, } } pub fn is_dot(&self) -> bool { match self.unspanned() { Token::EvaluationOperator(EvaluationOperator::Dot) => true, _ => false, } } pub fn as_block(&self) -> Option<(Spanned<&[SpannedToken]>, (Span, Span))> { match self.unspanned() { Token::Delimited(DelimitedNode { delimiter, children, spans, }) if *delimiter == Delimiter::Brace => { Some(((&children[..]).spanned(self.span()), *spans)) } _ => None, } } pub fn is_external(&self) -> bool { match self.unspanned() { Token::ExternalCommand(..) => true, _ => false, } } pub(crate) fn as_flag(&self, value: &str, short: Option, source: &Text) -> Option { match self.unspanned() { Token::Flag(flag) => { let name = flag.name().slice(source); match flag.kind { FlagKind::Longhand if value == name => Some(*flag), FlagKind::Shorthand => { if let Some(short_hand) = short { if short_hand.to_string() == name { return Some(*flag); } } None } _ => None, } } _ => None, } } pub fn as_pipeline(&self) -> Result { match self.unspanned() { Token::Pipeline(pipeline) => Ok(pipeline.clone()), _ => Err(ParseError::mismatch("pipeline", self.spanned_type_name())), } } pub fn is_whitespace(&self) -> bool { match self.unspanned() { Token::Whitespace => true, _ => false, } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters, new)] #[get = "pub(crate)"] pub struct DelimitedNode { pub(crate) delimiter: Delimiter, pub(crate) spans: (Span, Span), pub(crate) children: Vec, } impl HasSpan for DelimitedNode { fn span(&self) -> Span { self.spans.0.until(self.spans.1) } } impl PrettyDebugWithSource for DelimitedNode { fn pretty_debug(&self, source: &str) -> DebugDocBuilder { b::delimit( self.delimiter.open(), b::intersperse( self.children.iter().map(|child| child.pretty_debug(source)), b::space(), ), self.delimiter.close(), ) } } impl DelimitedNode { pub fn type_name(&self) -> &'static str { match self.delimiter { Delimiter::Brace => "braced expression", Delimiter::Paren => "parenthesized expression", Delimiter::Square => "array literal or index operator", } } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Delimiter { Paren, Brace, Square, } impl Delimiter { pub(crate) fn open(self) -> &'static str { match self { Delimiter::Paren => "(", Delimiter::Brace => "{", Delimiter::Square => "[", } } pub(crate) fn close(self) -> &'static str { match self { Delimiter::Paren => ")", Delimiter::Brace => "}", Delimiter::Square => "]", } } } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters, new)] #[get = "pub(crate)"] pub struct PathNode { head: Box, tail: Vec, } #[cfg(test)] impl SpannedToken { pub fn expect_external(&self) -> Span { match self.unspanned() { Token::ExternalCommand(span) => *span, _ => panic!( "Only call expect_external if you checked is_external first, found {:?}", self ), } } pub fn expect_string(&self) -> (Span, Span) { match self.unspanned() { Token::String(inner_span) => (self.span(), *inner_span), other => panic!("Expected string, found {:?}", other), } } pub fn expect_list(&self) -> Spanned> { match self.unspanned() { Token::Pipeline(pipeline) => pipeline .parts() .iter() .flat_map(|part| part.tokens()) .cloned() .collect::>() .spanned(self.span()), _ => panic!("Expected list, found {:?}", self), } } pub fn expect_pattern(&self) -> Span { match self.unspanned() { Token::GlobPattern => self.span(), _ => panic!("Expected pattern, found {:?}", self), } } pub fn expect_var(&self) -> (Span, Span) { match self.unspanned() { Token::Variable(inner_span) => (self.span(), *inner_span), Token::ItVariable(inner_span) => (self.span(), *inner_span), other => panic!("Expected var, found {:?}", other), } } pub fn expect_dot(&self) -> Span { match self.unspanned() { Token::EvaluationOperator(EvaluationOperator::Dot) => self.span(), other => panic!("Expected dot, found {:?}", other), } } pub fn expect_bare(&self) -> Span { match self.unspanned() { Token::Bare => self.span(), _ => panic!("Expected bare, found {:?}", self), } } }