From b15bb2c667c122b161a01131c2143e96b39430e3 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Tue, 10 Sep 2019 08:31:21 -0700 Subject: [PATCH] Added glob patterns to the syntax shapes Bare words now represent literal file names, and globs are a different syntax shape called "Pattern". This allows commands like `cp` to ask for a pattern as a source and a literal file as a target. This also means that attempting to pass a glob to a command that expects a literal path will produce an error. --- src/commands/cp.rs | 2 +- src/commands/to_bson.rs | 1 + src/commands/to_json.rs | 1 + src/commands/to_sqlite.rs | 1 + src/commands/to_toml.rs | 1 + src/commands/to_yaml.rs | 1 + src/data/base.rs | 8 ++++ src/evaluate/evaluator.rs | 1 + src/parser/hir.rs | 10 ++++- src/parser/hir/baseline_parse.rs | 49 +++++++++++++++++++++++++ src/parser/hir/baseline_parse_tokens.rs | 16 +++++++- src/parser/parse/parser.rs | 35 +++++++++++++++++- src/parser/parse/token_tree_builder.rs | 18 +++++++++ src/parser/parse/tokens.rs | 2 + src/shell/helper.rs | 4 ++ 15 files changed, 146 insertions(+), 4 deletions(-) diff --git a/src/commands/cp.rs b/src/commands/cp.rs index 8160fc9d2d..491e18b1aa 100644 --- a/src/commands/cp.rs +++ b/src/commands/cp.rs @@ -21,7 +21,7 @@ impl PerItemCommand for Cpy { fn signature(&self) -> Signature { Signature::build("cp") - .required("src", SyntaxType::Path) + .required("src", SyntaxType::Pattern) .required("dst", SyntaxType::Path) .named("file", SyntaxType::Any) .switch("recursive") diff --git a/src/commands/to_bson.rs b/src/commands/to_bson.rs index bb0355a5e9..a77bebeacc 100644 --- a/src/commands/to_bson.rs +++ b/src/commands/to_bson.rs @@ -50,6 +50,7 @@ pub fn value_to_bson_value(v: &Tagged) -> Result { } Value::Primitive(Primitive::Nothing) => Bson::Null, Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()), + Value::Primitive(Primitive::Pattern(p)) => Bson::String(p.clone()), Value::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()), Value::Table(l) => Bson::Array( l.iter() diff --git a/src/commands/to_json.rs b/src/commands/to_json.rs index f53fbd8d28..35c03af32d 100644 --- a/src/commands/to_json.rs +++ b/src/commands/to_json.rs @@ -45,6 +45,7 @@ pub fn value_to_json_value(v: &Tagged) -> Result::coerce_into(i.tagged(v.tag), "converting to JSON number")?, )), Value::Primitive(Primitive::Nothing) => serde_json::Value::Null, + Value::Primitive(Primitive::Pattern(s)) => serde_json::Value::String(s.clone()), Value::Primitive(Primitive::String(s)) => serde_json::Value::String(s.clone()), Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()), diff --git a/src/commands/to_sqlite.rs b/src/commands/to_sqlite.rs index 7580c3f4b7..0fd392f345 100644 --- a/src/commands/to_sqlite.rs +++ b/src/commands/to_sqlite.rs @@ -91,6 +91,7 @@ fn nu_value_to_sqlite_string(v: Value) -> String { Primitive::Int(i) => format!("{}", i), Primitive::Decimal(f) => format!("{}", f), Primitive::Bytes(u) => format!("{}", u), + Primitive::Pattern(s) => format!("'{}'", s.replace("'", "''")), Primitive::String(s) => format!("'{}'", s.replace("'", "''")), Primitive::Boolean(true) => "1".into(), Primitive::Boolean(_) => "0".into(), diff --git a/src/commands/to_toml.rs b/src/commands/to_toml.rs index 7bca9840e9..e18e152363 100644 --- a/src/commands/to_toml.rs +++ b/src/commands/to_toml.rs @@ -44,6 +44,7 @@ pub fn value_to_toml_value(v: &Tagged) -> Result toml::Value::Integer(i.tagged(v.tag).coerce_into("converting to TOML integer")?) } Value::Primitive(Primitive::Nothing) => toml::Value::String("".to_string()), + Value::Primitive(Primitive::Pattern(s)) => toml::Value::String(s.clone()), Value::Primitive(Primitive::String(s)) => toml::Value::String(s.clone()), Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()), diff --git a/src/commands/to_yaml.rs b/src/commands/to_yaml.rs index 129deebdf6..915827252d 100644 --- a/src/commands/to_yaml.rs +++ b/src/commands/to_yaml.rs @@ -42,6 +42,7 @@ pub fn value_to_yaml_value(v: &Tagged) -> Result::coerce_into(i.tagged(v.tag), "converting to YAML number")?, )), Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null, + Value::Primitive(Primitive::Pattern(s)) => serde_yaml::Value::String(s.clone()), Value::Primitive(Primitive::String(s)) => serde_yaml::Value::String(s.clone()), Value::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()), diff --git a/src/data/base.rs b/src/data/base.rs index b48d692123..6707d64020 100644 --- a/src/data/base.rs +++ b/src/data/base.rs @@ -20,6 +20,7 @@ pub enum Primitive { Decimal(BigDecimal), Bytes(u64), String(String), + Pattern(String), Boolean(bool), Date(DateTime), Path(PathBuf), @@ -53,6 +54,7 @@ impl Primitive { Int(_) => "int", Decimal(_) => "decimal", Bytes(_) => "bytes", + Pattern(_) => "pattern", String(_) => "string", Boolean(_) => "boolean", Date(_) => "date", @@ -71,6 +73,7 @@ impl Primitive { Path(path) => write!(f, "{}", path.display()), Decimal(decimal) => write!(f, "{}", decimal), Bytes(bytes) => write!(f, "{}", bytes), + Pattern(string) => write!(f, "{:?}", string), String(string) => write!(f, "{:?}", string), Boolean(boolean) => write!(f, "{}", boolean), Date(date) => write!(f, "{}", date), @@ -108,6 +111,7 @@ impl Primitive { } Primitive::Int(i) => format!("{}", i), Primitive::Decimal(decimal) => format!("{}", decimal), + Primitive::Pattern(s) => format!("{}", s), Primitive::String(s) => format!("{}", s), Primitive::Boolean(b) => match (b, field_name) { (true, None) => format!("Yes"), @@ -577,6 +581,10 @@ impl Value { Value::Primitive(Primitive::String(s.into())) } + pub fn pattern(s: impl Into) -> Value { + Value::Primitive(Primitive::String(s.into())) + } + pub fn path(s: impl Into) -> Value { Value::Primitive(Primitive::Path(s.into())) } diff --git a/src/evaluate/evaluator.rs b/src/evaluate/evaluator.rs index 6419ab73a6..52edf69818 100644 --- a/src/evaluate/evaluator.rs +++ b/src/evaluate/evaluator.rs @@ -114,6 +114,7 @@ fn evaluate_literal(literal: Tagged<&hir::Literal>, source: &Text) -> Tagged int.into(), hir::Literal::Size(int, unit) => unit.compute(int), hir::Literal::String(span) => Value::string(span.slice(source)), + hir::Literal::GlobPattern => Value::pattern(literal.span().slice(source)), hir::Literal::Bare => Value::string(literal.span().slice(source)), }; diff --git a/src/parser/hir.rs b/src/parser/hir.rs index aaf5bb7711..90bb38796a 100644 --- a/src/parser/hir.rs +++ b/src/parser/hir.rs @@ -17,7 +17,7 @@ use crate::evaluate::Scope; pub(crate) use self::baseline_parse::{ baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, - baseline_parse_token_as_string, + baseline_parse_token_as_pattern, baseline_parse_token_as_string, }; pub(crate) use self::baseline_parse_tokens::{baseline_parse_next_expr, TokensIterator}; pub(crate) use self::binary::Binary; @@ -90,6 +90,7 @@ pub enum RawExpression { Block(Vec), List(Vec), Path(Box), + FilePath(PathBuf), ExternalCommand(ExternalCommand), @@ -164,6 +165,10 @@ impl Expression { Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into()) } + pub(crate) fn pattern(tag: impl Into) -> Expression { + RawExpression::Literal(Literal::GlobPattern).tagged(tag.into()) + } + pub(crate) fn variable(inner: impl Into, outer: impl Into) -> Expression { Tagged::from_simple_spanned_item( RawExpression::Variable(Variable::Other(inner.into())), @@ -238,6 +243,7 @@ pub enum Literal { Number(Number), Size(Number, Unit), String(Span), + GlobPattern, Bare, } @@ -247,6 +253,7 @@ impl ToDebug for Tagged<&Literal> { Literal::Number(number) => write!(f, "{:?}", *number), Literal::Size(number, unit) => write!(f, "{:?}{:?}", *number, unit), Literal::String(span) => write!(f, "{}", span.slice(source)), + Literal::GlobPattern => write!(f, "{}", self.span().slice(source)), Literal::Bare => write!(f, "{}", self.span().slice(source)), } } @@ -259,6 +266,7 @@ impl Literal { Literal::Size(..) => "size", Literal::String(..) => "string", Literal::Bare => "string", + Literal::GlobPattern => "pattern", } } } diff --git a/src/parser/hir/baseline_parse.rs b/src/parser/hir/baseline_parse.rs index 4437a6d38b..5248bde5f9 100644 --- a/src/parser/hir/baseline_parse.rs +++ b/src/parser/hir/baseline_parse.rs @@ -1,6 +1,7 @@ use crate::context::Context; use crate::errors::ShellError; use crate::parser::{hir, RawToken, Token}; +use crate::TaggedItem; use crate::Text; use std::path::PathBuf; @@ -20,6 +21,7 @@ pub fn baseline_parse_single_token( RawToken::Variable(span) => hir::Expression::variable(span, token.span()), RawToken::ExternalCommand(span) => hir::Expression::external_command(span, token.span()), RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())), + RawToken::GlobPattern => hir::Expression::pattern(token.span()), RawToken::Bare => hir::Expression::bare(token.span()), }) } @@ -40,6 +42,12 @@ pub fn baseline_parse_token_as_number( hir::Expression::size(number.to_number(source), unit, token.span()) } RawToken::Bare => hir::Expression::bare(token.span()), + RawToken::GlobPattern => { + return Err(ShellError::type_error( + "Number", + "glob pattern".to_string().tagged(token.tag()), + )) + } RawToken::String(span) => hir::Expression::string(span, token.span()), }) } @@ -58,6 +66,12 @@ pub fn baseline_parse_token_as_string( RawToken::Number(_) => hir::Expression::bare(token.span()), RawToken::Size(_, _) => hir::Expression::bare(token.span()), RawToken::Bare => hir::Expression::bare(token.span()), + RawToken::GlobPattern => { + return Err(ShellError::type_error( + "String", + "glob pattern".tagged(token.tag()), + )) + } RawToken::String(span) => hir::Expression::string(span, token.span()), }) } @@ -80,6 +94,41 @@ pub fn baseline_parse_token_as_path( expand_path(token.span().slice(source), context), token.span(), ), + RawToken::GlobPattern => { + return Err(ShellError::type_error( + "Path", + "glob pattern".tagged(token.tag()), + )) + } + RawToken::String(span) => { + hir::Expression::file_path(expand_path(span.slice(source), context), token.span()) + } + }) +} + +pub fn baseline_parse_token_as_pattern( + token: &Token, + context: &Context, + source: &Text, +) -> Result { + Ok(match *token.item() { + RawToken::Variable(span) if span.slice(source) == "it" => { + hir::Expression::it_variable(span, token.span()) + } + RawToken::ExternalCommand(_) => { + return Err(ShellError::syntax_error( + "Invalid external command".to_string().tagged(token.tag()), + )) + } + RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())), + RawToken::Variable(span) => hir::Expression::variable(span, token.span()), + RawToken::Number(_) => hir::Expression::bare(token.span()), + RawToken::Size(_, _) => hir::Expression::bare(token.span()), + RawToken::GlobPattern => hir::Expression::pattern(token.span()), + RawToken::Bare => hir::Expression::file_path( + expand_path(token.span().slice(source), context), + token.span(), + ), RawToken::String(span) => { hir::Expression::file_path(expand_path(span.slice(source), context), token.span()) } diff --git a/src/parser/hir/baseline_parse_tokens.rs b/src/parser/hir/baseline_parse_tokens.rs index 13c7630fe1..ac2c703d3a 100644 --- a/src/parser/hir/baseline_parse_tokens.rs +++ b/src/parser/hir/baseline_parse_tokens.rs @@ -4,7 +4,7 @@ use crate::parser::{ hir, hir::{ baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, - baseline_parse_token_as_string, + baseline_parse_token_as_pattern, baseline_parse_token_as_string, }, DelimitedNode, Delimiter, PathNode, RawToken, TokenNode, }; @@ -43,6 +43,7 @@ pub enum SyntaxType { Variable, Number, Path, + Pattern, Binary, Block, Boolean, @@ -59,6 +60,7 @@ impl std::fmt::Display for SyntaxType { SyntaxType::Variable => write!(f, "Variable"), SyntaxType::Number => write!(f, "Number"), SyntaxType::Path => write!(f, "Path"), + SyntaxType::Pattern => write!(f, "Pattern"), SyntaxType::Binary => write!(f, "Binary"), SyntaxType::Block => write!(f, "Block"), SyntaxType::Boolean => write!(f, "Boolean"), @@ -90,6 +92,17 @@ pub fn baseline_parse_next_expr( )) } + (SyntaxType::Pattern, TokenNode::Token(token)) => { + return baseline_parse_token_as_pattern(token, context, source) + } + + (SyntaxType::Pattern, token) => { + return Err(ShellError::type_error( + "Path", + token.type_name().simple_spanned(token.span()), + )) + } + (SyntaxType::String, TokenNode::Token(token)) => { return baseline_parse_token_as_string(token, source); } @@ -315,6 +328,7 @@ pub fn baseline_parse_path( | RawToken::Size(..) | RawToken::Variable(_) | RawToken::ExternalCommand(_) + | RawToken::GlobPattern | RawToken::ExternalWord => { return Err(ShellError::type_error( "String", diff --git a/src/parser/parse/parser.rs b/src/parser/parse/parser.rs index a691fb2444..66656619d9 100644 --- a/src/parser/parse/parser.rs +++ b/src/parser/parse/parser.rs @@ -231,6 +231,29 @@ pub fn external(input: NomSpan) -> IResult { }) } +pub fn pattern(input: NomSpan) -> IResult { + trace_step(input, "bare", move |input| { + let start = input.offset; + let (input, _) = take_while1(is_start_glob_char)(input)?; + let (input, _) = take_while(is_glob_char)(input)?; + + let next_char = &input.fragment.chars().nth(0); + + if let Some(next_char) = next_char { + if is_external_word_char(*next_char) { + return Err(nom::Err::Error(nom::error::make_error( + input, + nom::error::ErrorKind::TakeWhile1, + ))); + } + } + + let end = input.offset; + + Ok((input, TokenTreeBuilder::spanned_pattern((start, end)))) + }) +} + pub fn bare(input: NomSpan) -> IResult { trace_step(input, "bare", move |input| { let start = input.offset; @@ -240,7 +263,7 @@ pub fn bare(input: NomSpan) -> IResult { let next_char = &input.fragment.chars().nth(0); if let Some(next_char) = next_char { - if is_external_word_char(*next_char) { + if is_external_word_char(*next_char) || *next_char == '*' { return Err(nom::Err::Error(nom::error::make_error( input, nom::error::ErrorKind::TakeWhile1, @@ -395,6 +418,7 @@ pub fn leaf(input: NomSpan) -> IResult { var, external, bare, + pattern, external_word, ))(input)?; @@ -655,6 +679,14 @@ fn is_external_word_char(c: char) -> bool { } } +fn is_start_glob_char(c: char) -> bool { + is_start_bare_char(c) || c == '*' +} + +fn is_glob_char(c: char) -> bool { + is_bare_char(c) || c == '*' +} + fn is_start_bare_char(c: char) -> bool { match c { '+' => false, @@ -680,6 +712,7 @@ fn is_bare_char(c: char) -> bool { '-' => true, '=' => true, '~' => true, + ':' => true, _ => false, } } diff --git a/src/parser/parse/token_tree_builder.rs b/src/parser/parse/token_tree_builder.rs index 8034e8b0c7..ae1b344c44 100644 --- a/src/parser/parse/token_tree_builder.rs +++ b/src/parser/parse/token_tree_builder.rs @@ -152,6 +152,24 @@ impl TokenTreeBuilder { )) } + pub fn pattern(input: impl Into) -> CurriedToken { + let input = input.into(); + + Box::new(move |b| { + let (start, end) = b.consume(&input); + b.pos = end; + + TokenTreeBuilder::spanned_pattern((start, end)) + }) + } + + pub fn spanned_pattern(input: impl Into) -> TokenNode { + TokenNode::Token(Tagged::from_simple_spanned_item( + RawToken::Bare, + input.into(), + )) + } + pub fn external_word(input: impl Into) -> CurriedToken { let input = input.into(); diff --git a/src/parser/parse/tokens.rs b/src/parser/parse/tokens.rs index 0bb2e3f17d..b599852499 100644 --- a/src/parser/parse/tokens.rs +++ b/src/parser/parse/tokens.rs @@ -12,6 +12,7 @@ pub enum RawToken { Variable(Span), ExternalCommand(Span), ExternalWord, + GlobPattern, Bare, } @@ -53,6 +54,7 @@ impl RawToken { RawToken::Variable(_) => "Variable", RawToken::ExternalCommand(_) => "ExternalCommand", RawToken::ExternalWord => "ExternalWord", + RawToken::GlobPattern => "GlobPattern", RawToken::Bare => "String", } } diff --git a/src/shell/helper.rs b/src/shell/helper.rs index 462f375291..16802657db 100644 --- a/src/shell/helper.rs +++ b/src/shell/helper.rs @@ -123,6 +123,10 @@ fn paint_token_node(token_node: &TokenNode, line: &str) -> String { item: RawToken::Size(..), .. }) => Color::Purple.bold().paint(token_node.span().slice(line)), + TokenNode::Token(Tagged { + item: RawToken::GlobPattern, + .. + }) => Color::Cyan.normal().paint(token_node.span().slice(line)), TokenNode::Token(Tagged { item: RawToken::String(..), ..