Merge pull request #632 from nushell/improve-external-words

Close a bunch of holes in external command args
This commit is contained in:
Andrés N. Robalino 2019-09-10 12:37:43 -05:00 committed by GitHub
commit f47349c1a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 447 additions and 77 deletions

View File

@ -21,7 +21,7 @@ impl PerItemCommand for Cpy {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("cp") Signature::build("cp")
.required("src", SyntaxType::Path) .required("src", SyntaxType::Pattern)
.required("dst", SyntaxType::Path) .required("dst", SyntaxType::Path)
.named("file", SyntaxType::Any) .named("file", SyntaxType::Any)
.switch("recursive") .switch("recursive")

View File

@ -10,7 +10,7 @@ impl WholeStreamCommand for LS {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("ls").optional("path", SyntaxType::Path) Signature::build("ls").optional("path", SyntaxType::Pattern)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {

View File

@ -50,6 +50,7 @@ pub fn value_to_bson_value(v: &Tagged<Value>) -> Result<Bson, ShellError> {
} }
Value::Primitive(Primitive::Nothing) => Bson::Null, Value::Primitive(Primitive::Nothing) => Bson::Null,
Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()), 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::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()),
Value::Table(l) => Bson::Array( Value::Table(l) => Bson::Array(
l.iter() l.iter()

View File

@ -45,6 +45,7 @@ pub fn value_to_json_value(v: &Tagged<Value>) -> Result<serde_json::Value, Shell
CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to JSON number")?, CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to JSON number")?,
)), )),
Value::Primitive(Primitive::Nothing) => serde_json::Value::Null, 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::String(s)) => serde_json::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()), Value::Primitive(Primitive::Path(s)) => serde_json::Value::String(s.display().to_string()),

View File

@ -91,6 +91,7 @@ fn nu_value_to_sqlite_string(v: Value) -> String {
Primitive::Int(i) => format!("{}", i), Primitive::Int(i) => format!("{}", i),
Primitive::Decimal(f) => format!("{}", f), Primitive::Decimal(f) => format!("{}", f),
Primitive::Bytes(u) => format!("{}", u), Primitive::Bytes(u) => format!("{}", u),
Primitive::Pattern(s) => format!("'{}'", s.replace("'", "''")),
Primitive::String(s) => format!("'{}'", s.replace("'", "''")), Primitive::String(s) => format!("'{}'", s.replace("'", "''")),
Primitive::Boolean(true) => "1".into(), Primitive::Boolean(true) => "1".into(),
Primitive::Boolean(_) => "0".into(), Primitive::Boolean(_) => "0".into(),

View File

@ -44,6 +44,7 @@ pub fn value_to_toml_value(v: &Tagged<Value>) -> Result<toml::Value, ShellError>
toml::Value::Integer(i.tagged(v.tag).coerce_into("converting to TOML integer")?) toml::Value::Integer(i.tagged(v.tag).coerce_into("converting to TOML integer")?)
} }
Value::Primitive(Primitive::Nothing) => toml::Value::String("<Nothing>".to_string()), Value::Primitive(Primitive::Nothing) => toml::Value::String("<Nothing>".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::String(s)) => toml::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()), Value::Primitive(Primitive::Path(s)) => toml::Value::String(s.display().to_string()),

View File

@ -42,6 +42,7 @@ pub fn value_to_yaml_value(v: &Tagged<Value>) -> Result<serde_yaml::Value, Shell
CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to YAML number")?, CoerceInto::<i64>::coerce_into(i.tagged(v.tag), "converting to YAML number")?,
)), )),
Value::Primitive(Primitive::Nothing) => serde_yaml::Value::Null, 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::String(s)) => serde_yaml::Value::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()), Value::Primitive(Primitive::Path(s)) => serde_yaml::Value::String(s.display().to_string()),

View File

@ -20,6 +20,7 @@ pub enum Primitive {
Decimal(BigDecimal), Decimal(BigDecimal),
Bytes(u64), Bytes(u64),
String(String), String(String),
Pattern(String),
Boolean(bool), Boolean(bool),
Date(DateTime<Utc>), Date(DateTime<Utc>),
Path(PathBuf), Path(PathBuf),
@ -53,6 +54,7 @@ impl Primitive {
Int(_) => "int", Int(_) => "int",
Decimal(_) => "decimal", Decimal(_) => "decimal",
Bytes(_) => "bytes", Bytes(_) => "bytes",
Pattern(_) => "pattern",
String(_) => "string", String(_) => "string",
Boolean(_) => "boolean", Boolean(_) => "boolean",
Date(_) => "date", Date(_) => "date",
@ -71,6 +73,7 @@ impl Primitive {
Path(path) => write!(f, "{}", path.display()), Path(path) => write!(f, "{}", path.display()),
Decimal(decimal) => write!(f, "{}", decimal), Decimal(decimal) => write!(f, "{}", decimal),
Bytes(bytes) => write!(f, "{}", bytes), Bytes(bytes) => write!(f, "{}", bytes),
Pattern(string) => write!(f, "{:?}", string),
String(string) => write!(f, "{:?}", string), String(string) => write!(f, "{:?}", string),
Boolean(boolean) => write!(f, "{}", boolean), Boolean(boolean) => write!(f, "{}", boolean),
Date(date) => write!(f, "{}", date), Date(date) => write!(f, "{}", date),
@ -108,6 +111,7 @@ impl Primitive {
} }
Primitive::Int(i) => format!("{}", i), Primitive::Int(i) => format!("{}", i),
Primitive::Decimal(decimal) => format!("{}", decimal), Primitive::Decimal(decimal) => format!("{}", decimal),
Primitive::Pattern(s) => format!("{}", s),
Primitive::String(s) => format!("{}", s), Primitive::String(s) => format!("{}", s),
Primitive::Boolean(b) => match (b, field_name) { Primitive::Boolean(b) => match (b, field_name) {
(true, None) => format!("Yes"), (true, None) => format!("Yes"),
@ -578,6 +582,10 @@ impl Value {
Value::Primitive(Primitive::String(s.into())) Value::Primitive(Primitive::String(s.into()))
} }
pub fn pattern(s: impl Into<String>) -> Value {
Value::Primitive(Primitive::String(s.into()))
}
pub fn path(s: impl Into<PathBuf>) -> Value { pub fn path(s: impl Into<PathBuf>) -> Value {
Value::Primitive(Primitive::Path(s.into())) Value::Primitive(Primitive::Path(s.into()))
} }

View File

@ -38,6 +38,7 @@ pub enum ArgumentError {
MissingMandatoryFlag(String), MissingMandatoryFlag(String),
MissingMandatoryPositional(String), MissingMandatoryPositional(String),
MissingValueForName(String), MissingValueForName(String),
InvalidExternalWord,
} }
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
@ -136,6 +137,16 @@ impl ShellError {
.start() .start()
} }
pub(crate) fn invalid_external_word(span: Span) -> ShellError {
ProximateShellError::ArgumentError {
command: "Invalid argument to Nu command (did you mean to call an external command?)"
.into(),
error: ArgumentError::InvalidExternalWord,
span,
}
.start()
}
pub(crate) fn parse_error( pub(crate) fn parse_error(
error: nom::Err<(nom5_locate::LocatedSpan<&str>, nom::error::ErrorKind)>, error: nom::Err<(nom5_locate::LocatedSpan<&str>, nom::error::ErrorKind)>,
) -> ShellError { ) -> ShellError {
@ -190,6 +201,10 @@ impl ShellError {
error, error,
span, span,
} => match error { } => match error {
ArgumentError::InvalidExternalWord => Diagnostic::new(
Severity::Error,
format!("Invalid bare word for Nu command (did you intend to invoke an external command?)"))
.with_label(Label::new_primary(span)),
ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new( ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new(
Severity::Error, Severity::Error,
format!( format!(

View File

@ -1,5 +1,5 @@
use crate::data::base::Block; use crate::data::base::Block;
use crate::errors::Description; use crate::errors::{ArgumentError, Description};
use crate::parser::{ use crate::parser::{
hir::{self, Expression, RawExpression}, hir::{self, Expression, RawExpression},
CommandRegistry, Text, CommandRegistry, Text,
@ -39,6 +39,11 @@ pub(crate) fn evaluate_baseline_expr(
) -> Result<Tagged<Value>, ShellError> { ) -> Result<Tagged<Value>, ShellError> {
match &expr.item { match &expr.item {
RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(literal), source)), RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(literal), source)),
RawExpression::ExternalWord => Err(ShellError::argument_error(
"Invalid external word",
ArgumentError::InvalidExternalWord,
expr.span(),
)),
RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())), RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())),
RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()), RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()),
RawExpression::Variable(var) => evaluate_reference(var, scope, source), RawExpression::Variable(var) => evaluate_reference(var, scope, source),
@ -109,6 +114,7 @@ fn evaluate_literal(literal: Tagged<&hir::Literal>, source: &Text) -> Tagged<Val
hir::Literal::Number(int) => int.into(), hir::Literal::Number(int) => int.into(),
hir::Literal::Size(int, unit) => unit.compute(int), hir::Literal::Size(int, unit) => unit.compute(int),
hir::Literal::String(span) => Value::string(span.slice(source)), 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)), hir::Literal::Bare => Value::string(literal.span().slice(source)),
}; };

View File

@ -17,7 +17,7 @@ use crate::evaluate::Scope;
pub(crate) use self::baseline_parse::{ pub(crate) use self::baseline_parse::{
baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, 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::baseline_parse_tokens::{baseline_parse_next_expr, TokensIterator};
pub(crate) use self::binary::Binary; pub(crate) use self::binary::Binary;
@ -83,12 +83,14 @@ impl ToDebug for Call {
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum RawExpression { pub enum RawExpression {
Literal(Literal), Literal(Literal),
ExternalWord,
Synthetic(Synthetic), Synthetic(Synthetic),
Variable(Variable), Variable(Variable),
Binary(Box<Binary>), Binary(Box<Binary>),
Block(Vec<Expression>), Block(Vec<Expression>),
List(Vec<Expression>), List(Vec<Expression>),
Path(Box<Path>), Path(Box<Path>),
FilePath(PathBuf), FilePath(PathBuf),
ExternalCommand(ExternalCommand), ExternalCommand(ExternalCommand),
@ -113,6 +115,7 @@ impl RawExpression {
match self { match self {
RawExpression::Literal(literal) => literal.type_name(), RawExpression::Literal(literal) => literal.type_name(),
RawExpression::Synthetic(synthetic) => synthetic.type_name(), RawExpression::Synthetic(synthetic) => synthetic.type_name(),
RawExpression::ExternalWord => "externalword",
RawExpression::FilePath(..) => "filepath", RawExpression::FilePath(..) => "filepath",
RawExpression::Variable(..) => "variable", RawExpression::Variable(..) => "variable",
RawExpression::List(..) => "list", RawExpression::List(..) => "list",
@ -162,6 +165,10 @@ impl Expression {
Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into()) Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into())
} }
pub(crate) fn pattern(tag: impl Into<Tag>) -> Expression {
RawExpression::Literal(Literal::GlobPattern).tagged(tag.into())
}
pub(crate) fn variable(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression { pub(crate) fn variable(inner: impl Into<Span>, outer: impl Into<Span>) -> Expression {
Tagged::from_simple_spanned_item( Tagged::from_simple_spanned_item(
RawExpression::Variable(Variable::Other(inner.into())), RawExpression::Variable(Variable::Other(inner.into())),
@ -189,6 +196,7 @@ impl ToDebug for Expression {
match self.item() { match self.item() {
RawExpression::Literal(l) => l.tagged(self.span()).fmt_debug(f, source), RawExpression::Literal(l) => l.tagged(self.span()).fmt_debug(f, source),
RawExpression::FilePath(p) => write!(f, "{}", p.display()), RawExpression::FilePath(p) => write!(f, "{}", p.display()),
RawExpression::ExternalWord => write!(f, "{}", self.span().slice(source)),
RawExpression::Synthetic(Synthetic::String(s)) => write!(f, "{:?}", s), RawExpression::Synthetic(Synthetic::String(s)) => write!(f, "{:?}", s),
RawExpression::Variable(Variable::It(_)) => write!(f, "$it"), RawExpression::Variable(Variable::It(_)) => write!(f, "$it"),
RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)), RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)),
@ -225,11 +233,17 @@ impl From<Tagged<Path>> for Expression {
} }
} }
/// Literals are expressions that are:
///
/// 1. Copy
/// 2. Can be evaluated without additional context
/// 3. Evaluation cannot produce an error
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Literal { pub enum Literal {
Number(Number), Number(Number),
Size(Number, Unit), Size(Number, Unit),
String(Span), String(Span),
GlobPattern,
Bare, Bare,
} }
@ -239,6 +253,7 @@ impl ToDebug for Tagged<&Literal> {
Literal::Number(number) => write!(f, "{:?}", *number), Literal::Number(number) => write!(f, "{:?}", *number),
Literal::Size(number, unit) => write!(f, "{:?}{:?}", *number, unit), Literal::Size(number, unit) => write!(f, "{:?}{:?}", *number, unit),
Literal::String(span) => write!(f, "{}", span.slice(source)), Literal::String(span) => write!(f, "{}", span.slice(source)),
Literal::GlobPattern => write!(f, "{}", self.span().slice(source)),
Literal::Bare => write!(f, "{}", self.span().slice(source)), Literal::Bare => write!(f, "{}", self.span().slice(source)),
} }
} }
@ -251,6 +266,7 @@ impl Literal {
Literal::Size(..) => "size", Literal::Size(..) => "size",
Literal::String(..) => "string", Literal::String(..) => "string",
Literal::Bare => "string", Literal::Bare => "string",
Literal::GlobPattern => "pattern",
} }
} }
} }

View File

@ -1,10 +1,15 @@
use crate::context::Context; use crate::context::Context;
use crate::errors::ShellError;
use crate::parser::{hir, RawToken, Token}; use crate::parser::{hir, RawToken, Token};
use crate::TaggedItem;
use crate::Text; use crate::Text;
use std::path::PathBuf; use std::path::PathBuf;
pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression { pub fn baseline_parse_single_token(
match *token.item() { token: &Token,
source: &Text,
) -> Result<hir::Expression, ShellError> {
Ok(match *token.item() {
RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()), RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()),
RawToken::Size(int, unit) => { RawToken::Size(int, unit) => {
hir::Expression::size(int.to_number(source), unit, token.span()) hir::Expression::size(int.to_number(source), unit, token.span())
@ -14,51 +19,74 @@ pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Express
hir::Expression::it_variable(span, token.span()) hir::Expression::it_variable(span, token.span())
} }
RawToken::Variable(span) => hir::Expression::variable(span, token.span()), RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
RawToken::External(span) => hir::Expression::external_command(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()), RawToken::Bare => hir::Expression::bare(token.span()),
} })
} }
pub fn baseline_parse_token_as_number(token: &Token, source: &Text) -> hir::Expression { pub fn baseline_parse_token_as_number(
match *token.item() { token: &Token,
source: &Text,
) -> Result<hir::Expression, ShellError> {
Ok(match *token.item() {
RawToken::Variable(span) if span.slice(source) == "it" => { RawToken::Variable(span) if span.slice(source) == "it" => {
hir::Expression::it_variable(span, token.span()) hir::Expression::it_variable(span, token.span())
} }
RawToken::External(span) => hir::Expression::external_command(span, token.span()), RawToken::ExternalCommand(span) => hir::Expression::external_command(span, token.span()),
RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())),
RawToken::Variable(span) => hir::Expression::variable(span, token.span()), RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()), RawToken::Number(number) => hir::Expression::number(number.to_number(source), token.span()),
RawToken::Size(number, unit) => { RawToken::Size(number, unit) => {
hir::Expression::size(number.to_number(source), unit, token.span()) hir::Expression::size(number.to_number(source), unit, token.span())
} }
RawToken::Bare => hir::Expression::bare(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()), RawToken::String(span) => hir::Expression::string(span, token.span()),
} })
} }
pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expression { pub fn baseline_parse_token_as_string(
match *token.item() { token: &Token,
source: &Text,
) -> Result<hir::Expression, ShellError> {
Ok(match *token.item() {
RawToken::Variable(span) if span.slice(source) == "it" => { RawToken::Variable(span) if span.slice(source) == "it" => {
hir::Expression::it_variable(span, token.span()) hir::Expression::it_variable(span, token.span())
} }
RawToken::External(span) => hir::Expression::external_command(span, token.span()), RawToken::ExternalCommand(span) => hir::Expression::external_command(span, token.span()),
RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())),
RawToken::Variable(span) => hir::Expression::variable(span, token.span()), RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
RawToken::Number(_) => hir::Expression::bare(token.span()), RawToken::Number(_) => hir::Expression::bare(token.span()),
RawToken::Size(_, _) => hir::Expression::bare(token.span()), RawToken::Size(_, _) => hir::Expression::bare(token.span()),
RawToken::Bare => 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()), RawToken::String(span) => hir::Expression::string(span, token.span()),
} })
} }
pub fn baseline_parse_token_as_path( pub fn baseline_parse_token_as_path(
token: &Token, token: &Token,
context: &Context, context: &Context,
source: &Text, source: &Text,
) -> hir::Expression { ) -> Result<hir::Expression, ShellError> {
match *token.item() { Ok(match *token.item() {
RawToken::Variable(span) if span.slice(source) == "it" => { RawToken::Variable(span) if span.slice(source) == "it" => {
hir::Expression::it_variable(span, token.span()) hir::Expression::it_variable(span, token.span())
} }
RawToken::External(span) => hir::Expression::external_command(span, token.span()), RawToken::ExternalCommand(span) => hir::Expression::external_command(span, token.span()),
RawToken::ExternalWord => return Err(ShellError::invalid_external_word(token.span())),
RawToken::Variable(span) => hir::Expression::variable(span, token.span()), RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
RawToken::Number(_) => hir::Expression::bare(token.span()), RawToken::Number(_) => hir::Expression::bare(token.span()),
RawToken::Size(_, _) => hir::Expression::bare(token.span()), RawToken::Size(_, _) => hir::Expression::bare(token.span()),
@ -66,10 +94,45 @@ pub fn baseline_parse_token_as_path(
expand_path(token.span().slice(source), context), expand_path(token.span().slice(source), context),
token.span(), token.span(),
), ),
RawToken::GlobPattern => {
return Err(ShellError::type_error(
"Path",
"glob pattern".tagged(token.tag()),
))
}
RawToken::String(span) => { RawToken::String(span) => {
hir::Expression::file_path(expand_path(span.slice(source), context), token.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<hir::Expression, ShellError> {
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())
}
})
} }
pub fn expand_path(string: &str, context: &Context) -> PathBuf { pub fn expand_path(string: &str, context: &Context) -> PathBuf {

View File

@ -4,7 +4,7 @@ use crate::parser::{
hir, hir,
hir::{ hir::{
baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path, 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, DelimitedNode, Delimiter, PathNode, RawToken, TokenNode,
}; };
@ -33,7 +33,6 @@ pub fn baseline_parse_tokens(
Ok(exprs) Ok(exprs)
} }
#[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum SyntaxType { pub enum SyntaxType {
Any, Any,
@ -44,6 +43,7 @@ pub enum SyntaxType {
Variable, Variable,
Number, Number,
Path, Path,
Pattern,
Binary, Binary,
Block, Block,
Boolean, Boolean,
@ -60,9 +60,10 @@ impl std::fmt::Display for SyntaxType {
SyntaxType::Variable => write!(f, "Variable"), SyntaxType::Variable => write!(f, "Variable"),
SyntaxType::Number => write!(f, "Number"), SyntaxType::Number => write!(f, "Number"),
SyntaxType::Path => write!(f, "Path"), SyntaxType::Path => write!(f, "Path"),
SyntaxType::Pattern => write!(f, "Pattern"),
SyntaxType::Binary => write!(f, "Binary"), SyntaxType::Binary => write!(f, "Binary"),
SyntaxType::Block => write!(f, "Block"), SyntaxType::Block => write!(f, "Block"),
SyntaxType::Boolean => write!(f, "Boolean") SyntaxType::Boolean => write!(f, "Boolean"),
} }
} }
} }
@ -81,7 +82,7 @@ pub fn baseline_parse_next_expr(
match (syntax_type, next) { match (syntax_type, next) {
(SyntaxType::Path, TokenNode::Token(token)) => { (SyntaxType::Path, TokenNode::Token(token)) => {
return Ok(baseline_parse_token_as_path(token, context, source)) return baseline_parse_token_as_path(token, context, source)
} }
(SyntaxType::Path, token) => { (SyntaxType::Path, token) => {
@ -91,8 +92,19 @@ 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)) => { (SyntaxType::String, TokenNode::Token(token)) => {
return Ok(baseline_parse_token_as_string(token, source)); return baseline_parse_token_as_string(token, source);
} }
(SyntaxType::String, token) => { (SyntaxType::String, token) => {
@ -103,7 +115,7 @@ pub fn baseline_parse_next_expr(
} }
(SyntaxType::Number, TokenNode::Token(token)) => { (SyntaxType::Number, TokenNode::Token(token)) => {
return Ok(baseline_parse_token_as_number(token, source)); return Ok(baseline_parse_token_as_number(token, source)?);
} }
(SyntaxType::Number, token) => { (SyntaxType::Number, token) => {
@ -115,7 +127,7 @@ pub fn baseline_parse_next_expr(
// TODO: More legit member processing // TODO: More legit member processing
(SyntaxType::Member, TokenNode::Token(token)) => { (SyntaxType::Member, TokenNode::Token(token)) => {
return Ok(baseline_parse_token_as_string(token, source)); return baseline_parse_token_as_string(token, source);
} }
(SyntaxType::Member, token) => { (SyntaxType::Member, token) => {
@ -245,7 +257,7 @@ pub fn baseline_parse_semantic_token(
source: &Text, source: &Text,
) -> Result<hir::Expression, ShellError> { ) -> Result<hir::Expression, ShellError> {
match token { match token {
TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)), TokenNode::Token(token) => baseline_parse_single_token(token, source),
TokenNode::Call(_call) => unimplemented!(), TokenNode::Call(_call) => unimplemented!(),
TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, context, source), TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, context, source),
TokenNode::Pipeline(_pipeline) => unimplemented!(), TokenNode::Pipeline(_pipeline) => unimplemented!(),
@ -315,7 +327,9 @@ pub fn baseline_parse_path(
RawToken::Number(_) RawToken::Number(_)
| RawToken::Size(..) | RawToken::Size(..)
| RawToken::Variable(_) | RawToken::Variable(_)
| RawToken::External(_) => { | RawToken::ExternalCommand(_)
| RawToken::GlobPattern
| RawToken::ExternalWord => {
return Err(ShellError::type_error( return Err(ShellError::type_error(
"String", "String",
token.type_name().simple_spanned(part), token.type_name().simple_spanned(part),

View File

@ -1,5 +1,7 @@
use crate::parser::TokenNode; use crate::parser::TokenNode;
use crate::traits::ToDebug;
use getset::Getters; use getset::Getters;
use std::fmt;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters)]
pub struct CallNode { pub struct CallNode {
@ -24,3 +26,17 @@ impl CallNode {
} }
} }
} }
impl ToDebug for CallNode {
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
write!(f, "{}", self.head.debug(source))?;
if let Some(children) = &self.children {
for child in children {
write!(f, "{}", child.debug(source))?
}
}
Ok(())
}
}

View File

@ -231,17 +231,62 @@ pub fn external(input: NomSpan) -> IResult<NomSpan, TokenNode> {
}) })
} }
pub fn pattern(input: NomSpan) -> IResult<NomSpan, TokenNode> {
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<NomSpan, TokenNode> { pub fn bare(input: NomSpan) -> IResult<NomSpan, TokenNode> {
trace_step(input, "bare", move |input| { trace_step(input, "bare", move |input| {
let start = input.offset; let start = input.offset;
let (input, _) = take_while1(is_start_bare_char)(input)?; let (input, _) = take_while1(is_start_bare_char)(input)?;
let (input, _) = take_while(is_bare_char)(input)?; let (input, _) = take_while(is_bare_char)(input)?;
let next_char = &input.fragment.chars().nth(0);
if let Some(next_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,
)));
}
}
let end = input.offset; let end = input.offset;
Ok((input, TokenTreeBuilder::spanned_bare((start, end)))) Ok((input, TokenTreeBuilder::spanned_bare((start, end))))
}) })
} }
pub fn external_word(input: NomSpan) -> IResult<NomSpan, TokenNode> {
trace_step(input, "bare", move |input| {
let start = input.offset;
let (input, _) = take_while1(is_external_word_char)(input)?;
let end = input.offset;
Ok((input, TokenTreeBuilder::spanned_external_word((start, end))))
})
}
pub fn var(input: NomSpan) -> IResult<NomSpan, TokenNode> { pub fn var(input: NomSpan) -> IResult<NomSpan, TokenNode> {
trace_step(input, "var", move |input| { trace_step(input, "var", move |input| {
let start = input.offset; let start = input.offset;
@ -364,8 +409,18 @@ pub fn size(input: NomSpan) -> IResult<NomSpan, TokenNode> {
pub fn leaf(input: NomSpan) -> IResult<NomSpan, TokenNode> { pub fn leaf(input: NomSpan) -> IResult<NomSpan, TokenNode> {
trace_step(input, "leaf", move |input| { trace_step(input, "leaf", move |input| {
let (input, node) = let (input, node) = alt((
alt((size, string, operator, flag, shorthand, var, external, bare))(input)?; size,
string,
operator,
flag,
shorthand,
var,
external,
bare,
pattern,
external_word,
))(input)?;
Ok((input, node)) Ok((input, node))
}) })
@ -582,26 +637,13 @@ pub fn pipeline(input: NomSpan) -> IResult<NomSpan, TokenNode> {
} }
fn make_call_list( fn make_call_list(
head: Option<( head: Option<(Option<NomSpan>, Tagged<CallNode>, Option<NomSpan>)>,
Option<NomSpan>, items: Vec<(NomSpan, Option<NomSpan>, Tagged<CallNode>, Option<NomSpan>)>,
Tagged<CallNode>,
Option<NomSpan>
)>,
items: Vec<(
NomSpan,
Option<NomSpan>,
Tagged<CallNode>,
Option<NomSpan>,
)>,
) -> Vec<PipelineElement> { ) -> Vec<PipelineElement> {
let mut out = vec![]; let mut out = vec![];
if let Some(head) = head { if let Some(head) = head {
let el = PipelineElement::new( let el = PipelineElement::new(None, head.0.map(Span::from), head.1, head.2.map(Span::from));
None,
head.0.map(Span::from),
head.1,
head.2.map(Span::from));
out.push(el); out.push(el);
} }
@ -611,7 +653,8 @@ fn make_call_list(
Some(pipe).map(Span::from), Some(pipe).map(Span::from),
ws1.map(Span::from), ws1.map(Span::from),
call, call,
ws2.map(Span::from)); ws2.map(Span::from),
);
out.push(el); out.push(el);
} }
@ -628,40 +671,49 @@ fn int<T>(frag: &str, neg: Option<T>) -> i64 {
} }
} }
fn is_external_word_char(c: char) -> bool {
match c {
';' | '|' | '#' | '-' | '"' | '\'' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '`' => false,
other if other.is_whitespace() => false,
_ => true,
}
}
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 { fn is_start_bare_char(c: char) -> bool {
match c { match c {
'+' => false,
_ if c.is_alphabetic() => true, _ if c.is_alphabetic() => true,
_ if c.is_numeric() => true,
'.' => true, '.' => true,
'\\' => true, '\\' => true,
'/' => true, '/' => true,
'_' => true, '_' => true,
'-' => true, '-' => true,
'@' => true,
'*' => true,
'?' => true,
'~' => true, '~' => true,
'+' => true,
_ => false, _ => false,
} }
} }
fn is_bare_char(c: char) -> bool { fn is_bare_char(c: char) -> bool {
match c { match c {
'+' => false,
_ if c.is_alphanumeric() => true, _ if c.is_alphanumeric() => true,
':' => true,
'.' => true, '.' => true,
'\\' => true, '\\' => true,
'/' => true, '/' => true,
'_' => true, '_' => true,
'-' => true, '-' => true,
'@' => true,
'*' => true,
'?' => true,
'=' => true, '=' => true,
'~' => true, '~' => true,
'+' => true, ':' => true,
'%' => true, '?' => true,
_ => false, _ => false,
} }
} }
@ -724,6 +776,44 @@ mod tests {
} }
} }
macro_rules! equal_tokens {
($source:tt -> $tokens:expr) => {
let result = apply(pipeline, "pipeline", $source);
let (expected_tree, expected_source) = TokenTreeBuilder::build($tokens);
if result != expected_tree {
let debug_result = format!("{}", result.debug($source));
let debug_expected = format!("{}", expected_tree.debug(&expected_source));
if debug_result == debug_expected {
assert_eq!(
result, expected_tree,
"NOTE: actual and expected had equivalent debug serializations, source={:?}, debug_expected={:?}",
$source,
debug_expected
)
} else {
assert_eq!(debug_result, debug_expected)
}
}
// apply(pipeline, "pipeline", r#"cargo +nightly run"#),
// build_token(b::pipeline(vec![(
// None,
// b::call(
// b::bare("cargo"),
// vec![
// b::sp(),
// b::external_word("+nightly"),
// b::sp(),
// b::bare("run")
// ]
// ),
// None
// )]))
};
}
#[test] #[test]
fn test_integer() { fn test_integer() {
assert_leaf! { assert_leaf! {
@ -854,7 +944,7 @@ mod tests {
fn test_external() { fn test_external() {
assert_leaf! { assert_leaf! {
parsers [ external ] parsers [ external ]
"^ls" -> 0..3 { External(span(1, 3)) } "^ls" -> 0..3 { ExternalCommand(span(1, 3)) }
} }
} }
@ -1058,6 +1148,46 @@ mod tests {
); );
} }
#[test]
fn test_external_word() {
let _ = pretty_env_logger::try_init();
equal_tokens!(
"cargo +nightly run" ->
b::pipeline(vec![(
None,
b::call(
b::bare("cargo"),
vec![
b::sp(),
b::external_word("+nightly"),
b::sp(),
b::bare("run")
]
),
None
)])
);
equal_tokens!(
"rm foo%bar" ->
b::pipeline(vec![(
None,
b::call(b::bare("rm"), vec![b::sp(), b::external_word("foo%bar"),]),
None
)])
);
equal_tokens!(
"rm foo%bar" ->
b::pipeline(vec![(
None,
b::call(b::bare("rm"), vec![b::sp(), b::external_word("foo%bar"),]),
None
)])
);
}
#[test] #[test]
fn test_smoke_pipeline() { fn test_smoke_pipeline() {
let _ = pretty_env_logger::try_init(); let _ = pretty_env_logger::try_init();
@ -1178,7 +1308,6 @@ mod tests {
} }
fn build_token(block: CurriedToken) -> TokenNode { fn build_token(block: CurriedToken) -> TokenNode {
let mut builder = TokenTreeBuilder::new(); TokenTreeBuilder::build(block).0
block(&mut builder)
} }
} }

View File

@ -1,7 +1,9 @@
use crate::parser::CallNode; use crate::parser::CallNode;
use crate::traits::ToDebug;
use crate::{Span, Tagged}; use crate::{Span, Tagged};
use derive_new::new; use derive_new::new;
use getset::Getters; use getset::Getters;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, new)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, new)]
pub struct Pipeline { pub struct Pipeline {
@ -9,6 +11,20 @@ pub struct Pipeline {
pub(crate) post_ws: Option<Span>, pub(crate) post_ws: Option<Span>,
} }
impl ToDebug for Pipeline {
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
for part in &self.parts {
write!(f, "{}", part.debug(source))?;
}
if let Some(post_ws) = self.post_ws {
write!(f, "{}", post_ws.slice(source))?
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters, new)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Getters, new)]
pub struct PipelineElement { pub struct PipelineElement {
pub pipe: Option<Span>, pub pipe: Option<Span>,
@ -17,3 +33,23 @@ pub struct PipelineElement {
call: Tagged<CallNode>, call: Tagged<CallNode>,
pub post_ws: Option<Span>, pub post_ws: Option<Span>,
} }
impl ToDebug for PipelineElement {
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
if let Some(pipe) = self.pipe {
write!(f, "{}", pipe.slice(source))?;
}
if let Some(pre_ws) = self.pre_ws {
write!(f, "{}", pre_ws.slice(source))?;
}
write!(f, "{}", self.call.debug(source))?;
if let Some(post_ws) = self.post_ws {
write!(f, "{}", post_ws.slice(source))?;
}
Ok(())
}
}

View File

@ -1,5 +1,6 @@
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::parser::parse::{call_node::*, flag::*, operator::*, pipeline::*, tokens::*}; use crate::parser::parse::{call_node::*, flag::*, operator::*, pipeline::*, tokens::*};
use crate::traits::ToDebug;
use crate::{Span, Tagged, Text}; use crate::{Span, Tagged, Text};
use derive_new::new; use derive_new::new;
use enum_utils::FromStr; use enum_utils::FromStr;
@ -22,6 +23,12 @@ pub enum TokenNode {
Path(Tagged<PathNode>), Path(Tagged<PathNode>),
} }
impl ToDebug for TokenNode {
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
write!(f, "{:?}", self.old_debug(&Text::from(source)))
}
}
pub struct DebugTokenNode<'a> { pub struct DebugTokenNode<'a> {
node: &'a TokenNode, node: &'a TokenNode,
source: &'a Text, source: &'a Text,
@ -34,11 +41,11 @@ impl fmt::Debug for DebugTokenNode<'_> {
TokenNode::Call(s) => { TokenNode::Call(s) => {
write!(f, "(")?; write!(f, "(")?;
write!(f, "{:?}", s.head().debug(self.source))?; write!(f, "{}", s.head().debug(self.source))?;
if let Some(children) = s.children() { if let Some(children) = s.children() {
for child in children { for child in children {
write!(f, "{:?}", child.debug(self.source))?; write!(f, "{}", child.debug(self.source))?;
} }
} }
@ -57,7 +64,7 @@ impl fmt::Debug for DebugTokenNode<'_> {
)?; )?;
for child in d.children() { for child in d.children() {
write!(f, "{:?}", child.debug(self.source))?; write!(f, "{:?}", child.old_debug(self.source))?;
} }
write!( write!(
@ -70,7 +77,7 @@ impl fmt::Debug for DebugTokenNode<'_> {
} }
) )
} }
TokenNode::Pipeline(_) => write!(f, "<todo:pipeline>"), TokenNode::Pipeline(pipeline) => write!(f, "{}", pipeline.debug(self.source)),
TokenNode::Error(s) => write!(f, "<error> for {:?}", s.span().slice(self.source)), TokenNode::Error(s) => write!(f, "<error> for {:?}", s.span().slice(self.source)),
rest => write!(f, "{}", rest.span().slice(self.source)), rest => write!(f, "{}", rest.span().slice(self.source)),
} }
@ -115,7 +122,7 @@ impl TokenNode {
.to_string() .to_string()
} }
pub fn debug<'a>(&'a self, source: &'a Text) -> DebugTokenNode<'a> { pub fn old_debug<'a>(&'a self, source: &'a Text) -> DebugTokenNode<'a> {
DebugTokenNode { node: self, source } DebugTokenNode { node: self, source }
} }
@ -140,7 +147,7 @@ impl TokenNode {
pub fn is_external(&self) -> bool { pub fn is_external(&self) -> bool {
match self { match self {
TokenNode::Token(Tagged { TokenNode::Token(Tagged {
item: RawToken::External(..), item: RawToken::ExternalCommand(..),
.. ..
}) => true, }) => true,
_ => false, _ => false,
@ -150,7 +157,7 @@ impl TokenNode {
pub fn expect_external(&self) -> Span { pub fn expect_external(&self) -> Span {
match self { match self {
TokenNode::Token(Tagged { TokenNode::Token(Tagged {
item: RawToken::External(span), item: RawToken::ExternalCommand(span),
.. ..
}) => *span, }) => *span,
_ => panic!("Only call expect_external if you checked is_external first"), _ => panic!("Only call expect_external if you checked is_external first"),

View File

@ -14,15 +14,19 @@ use derive_new::new;
pub struct TokenTreeBuilder { pub struct TokenTreeBuilder {
#[new(default)] #[new(default)]
pos: usize, pos: usize,
#[new(default)]
output: String,
} }
pub type CurriedToken = Box<dyn FnOnce(&mut TokenTreeBuilder) -> TokenNode + 'static>; pub type CurriedToken = Box<dyn FnOnce(&mut TokenTreeBuilder) -> TokenNode + 'static>;
pub type CurriedCall = Box<dyn FnOnce(&mut TokenTreeBuilder) -> Tagged<CallNode> + 'static>; pub type CurriedCall = Box<dyn FnOnce(&mut TokenTreeBuilder) -> Tagged<CallNode> + 'static>;
impl TokenTreeBuilder { impl TokenTreeBuilder {
pub fn build(block: impl FnOnce(&mut Self) -> TokenNode) -> TokenNode { pub fn build(block: impl FnOnce(&mut Self) -> TokenNode) -> (TokenNode, String) {
let mut builder = TokenTreeBuilder::new(); let mut builder = TokenTreeBuilder::new();
block(&mut builder) let node = block(&mut builder);
(node, builder.output)
} }
pub fn pipeline(input: Vec<(Option<&str>, CurriedCall, Option<&str>)>) -> CurriedToken { pub fn pipeline(input: Vec<(Option<&str>, CurriedCall, Option<&str>)>) -> CurriedToken {
@ -56,7 +60,8 @@ impl TokenTreeBuilder {
pipe, pipe,
pre_span.map(Span::from), pre_span.map(Span::from),
call, call,
post_span.map(Span::from))); post_span.map(Span::from),
));
loop { loop {
match input.next() { match input.next() {
@ -147,9 +152,45 @@ impl TokenTreeBuilder {
)) ))
} }
pub fn pattern(input: impl Into<String>) -> 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<Span>) -> TokenNode {
TokenNode::Token(Tagged::from_simple_spanned_item(
RawToken::Bare,
input.into(),
))
}
pub fn external_word(input: impl Into<String>) -> CurriedToken {
let input = input.into();
Box::new(move |b| {
let (start, end) = b.consume(&input);
b.pos = end;
TokenTreeBuilder::spanned_external_word((start, end))
})
}
pub fn spanned_external_word(input: impl Into<Span>) -> TokenNode {
TokenNode::Token(Tagged::from_simple_spanned_item(
RawToken::ExternalWord,
input.into(),
))
}
pub fn spanned_external(input: impl Into<Span>, span: impl Into<Span>) -> TokenNode { pub fn spanned_external(input: impl Into<Span>, span: impl Into<Span>) -> TokenNode {
TokenNode::Token(Tagged::from_simple_spanned_item( TokenNode::Token(Tagged::from_simple_spanned_item(
RawToken::External(input.into()), RawToken::ExternalCommand(input.into()),
span.into(), span.into(),
)) ))
} }
@ -422,6 +463,7 @@ impl TokenTreeBuilder {
fn consume(&mut self, input: &str) -> (usize, usize) { fn consume(&mut self, input: &str) -> (usize, usize) {
let start = self.pos; let start = self.pos;
self.pos += input.len(); self.pos += input.len();
self.output.push_str(input);
(start, self.pos) (start, self.pos)
} }
} }

View File

@ -10,7 +10,9 @@ pub enum RawToken {
Size(RawNumber, Unit), Size(RawNumber, Unit),
String(Span), String(Span),
Variable(Span), Variable(Span),
External(Span), ExternalCommand(Span),
ExternalWord,
GlobPattern,
Bare, Bare,
} }
@ -50,7 +52,9 @@ impl RawToken {
RawToken::Size(..) => "Size", RawToken::Size(..) => "Size",
RawToken::String(_) => "String", RawToken::String(_) => "String",
RawToken::Variable(_) => "Variable", RawToken::Variable(_) => "Variable",
RawToken::External(_) => "External", RawToken::ExternalCommand(_) => "ExternalCommand",
RawToken::ExternalWord => "ExternalWord",
RawToken::GlobPattern => "GlobPattern",
RawToken::Bare => "String", RawToken::Bare => "String",
} }
} }

View File

@ -6,6 +6,7 @@ use crate::parser::{
hir::{self, NamedArguments}, hir::{self, NamedArguments},
Flag, RawToken, TokenNode, Flag, RawToken, TokenNode,
}; };
use crate::traits::ToDebug;
use crate::{Span, Tag, Tagged, Text}; use crate::{Span, Tag, Tagged, Text};
use log::trace; use log::trace;
@ -248,7 +249,7 @@ pub fn trace_remaining(desc: &'static str, tail: hir::TokensIterator<'_>, source
itertools::join( itertools::join(
tail.debug_remaining() tail.debug_remaining()
.iter() .iter()
.map(|i| format!("%{:?}%", i.debug(source))), .map(|i| format!("%{}%", i.debug(&source))),
" " " "
) )
); );

View File

@ -123,6 +123,10 @@ fn paint_token_node(token_node: &TokenNode, line: &str) -> String {
item: RawToken::Size(..), item: RawToken::Size(..),
.. ..
}) => Color::Purple.bold().paint(token_node.span().slice(line)), }) => 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 { TokenNode::Token(Tagged {
item: RawToken::String(..), item: RawToken::String(..),
.. ..
@ -136,9 +140,13 @@ fn paint_token_node(token_node: &TokenNode, line: &str) -> String {
.. ..
}) => Color::Green.normal().paint(token_node.span().slice(line)), }) => Color::Green.normal().paint(token_node.span().slice(line)),
TokenNode::Token(Tagged { TokenNode::Token(Tagged {
item: RawToken::External(..), item: RawToken::ExternalCommand(..),
.. ..
}) => Color::Cyan.bold().paint(token_node.span().slice(line)), }) => Color::Cyan.bold().paint(token_node.span().slice(line)),
TokenNode::Token(Tagged {
item: RawToken::ExternalWord,
..
}) => Color::Black.bold().paint(token_node.span().slice(line)),
}; };
styled.to_string() styled.to_string()