forked from extern/nushell
Not all lite command's first part denotes a real command (for cases like sub commands that it's second lite part accounts for the command name). This is important so that we can refer to it's span correctly. Here we fix to include the command's name span correctly whether it's a command or sub command when reporting missing flag errors.
2340 lines
78 KiB
Rust
2340 lines
78 KiB
Rust
use std::{path::Path, sync::Arc};
|
|
|
|
use bigdecimal::BigDecimal;
|
|
use indexmap::IndexMap;
|
|
use log::trace;
|
|
use nu_errors::{ArgumentError, ParseError};
|
|
use nu_protocol::hir::{
|
|
self, Binary, Block, ClassifiedCommand, Expression, ExternalRedirection, Flag, FlagKind, Group,
|
|
InternalCommand, Member, NamedArguments, Operator, Pipeline, RangeOperator, SpannedExpression,
|
|
Unit,
|
|
};
|
|
use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember};
|
|
use nu_source::{HasSpan, Span, Spanned, SpannedItem};
|
|
use num_bigint::BigInt;
|
|
|
|
use crate::{
|
|
lex::lexer::{lex, parse_block},
|
|
ParserScope,
|
|
};
|
|
use crate::{
|
|
lex::{
|
|
lexer::Token,
|
|
tokens::{LiteBlock, LiteCommand, LitePipeline, TokenContents},
|
|
},
|
|
parse::def::lex_split_baseline_tokens_on,
|
|
};
|
|
use crate::{parse::def::parse_parameter, path::expand_path};
|
|
|
|
use self::{
|
|
def::{parse_definition, parse_definition_prototype},
|
|
util::trim_quotes,
|
|
util::verify_and_strip,
|
|
};
|
|
|
|
mod def;
|
|
mod util;
|
|
|
|
pub use self::util::garbage;
|
|
|
|
/// Parses a simple column path, one without a variable (implied or explicit) at the head
|
|
pub fn parse_simple_column_path(
|
|
lite_arg: &Spanned<String>,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
let mut delimiter = '.';
|
|
let mut inside_delimiter = false;
|
|
let mut output = vec![];
|
|
let mut current_part = String::new();
|
|
let mut start_index = 0;
|
|
let mut last_index = 0;
|
|
|
|
for (idx, c) in lite_arg.item.char_indices() {
|
|
last_index = idx;
|
|
if inside_delimiter {
|
|
if c == delimiter {
|
|
inside_delimiter = false;
|
|
}
|
|
} else if c == '\'' || c == '"' || c == '`' {
|
|
inside_delimiter = true;
|
|
delimiter = c;
|
|
} else if c == '.' {
|
|
let part_span = Span::new(
|
|
lite_arg.span.start() + start_index,
|
|
lite_arg.span.start() + idx,
|
|
);
|
|
|
|
if let Ok(row_number) = current_part.parse::<i64>() {
|
|
output.push(Member::Int(row_number, part_span));
|
|
} else {
|
|
let trimmed = trim_quotes(¤t_part);
|
|
output.push(Member::Bare(trimmed.clone().spanned(part_span)));
|
|
}
|
|
current_part.clear();
|
|
// Note: I believe this is safe because of the delimiter we're using,
|
|
// but if we get fancy with Unicode we'll need to change this.
|
|
start_index = idx + '.'.len_utf8();
|
|
continue;
|
|
}
|
|
current_part.push(c);
|
|
}
|
|
|
|
if !current_part.is_empty() {
|
|
let part_span = Span::new(
|
|
lite_arg.span.start() + start_index,
|
|
lite_arg.span.start() + last_index + 1,
|
|
);
|
|
if let Ok(row_number) = current_part.parse::<i64>() {
|
|
output.push(Member::Int(row_number, part_span));
|
|
} else {
|
|
let current_part = trim_quotes(¤t_part);
|
|
output.push(Member::Bare(current_part.spanned(part_span)));
|
|
}
|
|
}
|
|
|
|
(
|
|
SpannedExpression::new(Expression::simple_column_path(output), lite_arg.span),
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// Parses a column path, adding in the preceding reference to $it if it's elided
|
|
pub fn parse_full_column_path(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
let mut inside_delimiter = vec![];
|
|
let mut output = vec![];
|
|
let mut current_part = String::new();
|
|
let mut start_index = 0;
|
|
let mut last_index = 0;
|
|
let mut error = None;
|
|
|
|
let mut head = None;
|
|
|
|
for (idx, c) in lite_arg.item.char_indices() {
|
|
last_index = idx;
|
|
if c == '(' {
|
|
inside_delimiter.push(')');
|
|
} else if let Some(delimiter) = inside_delimiter.last() {
|
|
if c == *delimiter {
|
|
inside_delimiter.pop();
|
|
}
|
|
} else if c == '\'' || c == '"' {
|
|
inside_delimiter.push(c);
|
|
} else if c == '.' {
|
|
let part_span = Span::new(
|
|
lite_arg.span.start() + start_index,
|
|
lite_arg.span.start() + idx,
|
|
);
|
|
|
|
if head.is_none() && current_part.starts_with('(') && current_part.ends_with(')') {
|
|
let (invoc, err) =
|
|
parse_invocation(¤t_part.clone().spanned(part_span), scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
head = Some(invoc.expr);
|
|
} else if head.is_none() && current_part.starts_with('$') {
|
|
// We have the variable head
|
|
head = Some(Expression::variable(current_part.clone(), part_span))
|
|
} else if let Ok(row_number) = current_part.parse::<i64>() {
|
|
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
|
|
} else {
|
|
let current_part = trim_quotes(¤t_part);
|
|
output.push(
|
|
UnspannedPathMember::String(current_part.clone()).into_path_member(part_span),
|
|
);
|
|
}
|
|
current_part.clear();
|
|
// Note: I believe this is safe because of the delimiter we're using,
|
|
// but if we get fancy with Unicode we'll need to change this.
|
|
start_index = idx + '.'.len_utf8();
|
|
continue;
|
|
}
|
|
current_part.push(c);
|
|
}
|
|
|
|
if !current_part.is_empty() {
|
|
let part_span = Span::new(
|
|
lite_arg.span.start() + start_index,
|
|
lite_arg.span.start() + last_index + 1,
|
|
);
|
|
|
|
if head.is_none() {
|
|
if current_part.starts_with('(') && current_part.ends_with(')') {
|
|
let (invoc, err) = parse_invocation(¤t_part.spanned(part_span), scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
head = Some(invoc.expr);
|
|
} else if current_part.starts_with('$') {
|
|
head = Some(Expression::variable(current_part, lite_arg.span));
|
|
} else if let Ok(row_number) = current_part.parse::<i64>() {
|
|
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
|
|
} else {
|
|
let current_part = trim_quotes(¤t_part);
|
|
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
|
|
}
|
|
} else if let Ok(row_number) = current_part.parse::<i64>() {
|
|
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
|
|
} else {
|
|
let current_part = trim_quotes(¤t_part);
|
|
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
|
|
}
|
|
}
|
|
|
|
if let Some(head) = head {
|
|
(
|
|
SpannedExpression::new(
|
|
Expression::path(SpannedExpression::new(head, lite_arg.span), output),
|
|
lite_arg.span,
|
|
),
|
|
error,
|
|
)
|
|
} else {
|
|
(
|
|
SpannedExpression::new(
|
|
Expression::path(
|
|
SpannedExpression::new(
|
|
Expression::variable("$it".into(), lite_arg.span),
|
|
lite_arg.span,
|
|
),
|
|
output,
|
|
),
|
|
lite_arg.span,
|
|
),
|
|
error,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Parse a numeric range
|
|
fn parse_range(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
let lite_arg_span_start = lite_arg.span.start();
|
|
let lite_arg_len = lite_arg.item.len();
|
|
let (dotdot_pos, operator_str, operator) = if let Some(pos) = lite_arg.item.find("..<") {
|
|
(pos, "..<", RangeOperator::RightExclusive)
|
|
} else if let Some(pos) = lite_arg.item.find("..") {
|
|
(pos, "..", RangeOperator::Inclusive)
|
|
} else {
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("range", lite_arg.clone())),
|
|
);
|
|
};
|
|
|
|
if lite_arg.item[0..dotdot_pos].is_empty()
|
|
&& lite_arg.item[(dotdot_pos + operator_str.len())..].is_empty()
|
|
{
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("range", lite_arg.clone())),
|
|
);
|
|
}
|
|
|
|
let numbers: Vec<_> = lite_arg.item.split(operator_str).collect();
|
|
|
|
if numbers.len() != 2 {
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("range", lite_arg.clone())),
|
|
);
|
|
}
|
|
|
|
let right_number_offset = operator_str.len();
|
|
|
|
let lhs = numbers[0].to_string().spanned(Span::new(
|
|
lite_arg_span_start,
|
|
lite_arg_span_start + dotdot_pos,
|
|
));
|
|
let rhs = numbers[1].to_string().spanned(Span::new(
|
|
lite_arg_span_start + dotdot_pos + right_number_offset,
|
|
lite_arg_span_start + lite_arg_len,
|
|
));
|
|
|
|
let left_hand_open = dotdot_pos == 0;
|
|
let right_hand_open = dotdot_pos == lite_arg_len - right_number_offset;
|
|
|
|
let left = if left_hand_open {
|
|
None
|
|
} else if let (left, None) = parse_arg(SyntaxShape::Number, scope, &lhs) {
|
|
Some(left)
|
|
} else {
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("range", lhs)),
|
|
);
|
|
};
|
|
|
|
let right = if right_hand_open {
|
|
None
|
|
} else if let (right, None) = parse_arg(SyntaxShape::Number, scope, &rhs) {
|
|
Some(right)
|
|
} else {
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("range", rhs)),
|
|
);
|
|
};
|
|
|
|
(
|
|
SpannedExpression::new(
|
|
Expression::range(
|
|
left,
|
|
operator.spanned(Span::new(
|
|
lite_arg_span_start + dotdot_pos,
|
|
lite_arg_span_start + dotdot_pos + right_number_offset,
|
|
)),
|
|
right,
|
|
),
|
|
lite_arg.span,
|
|
),
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// Parse any allowed operator, including word-based operators
|
|
fn parse_operator(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
|
let operator = match &lite_arg.item[..] {
|
|
"==" => Operator::Equal,
|
|
"!=" => Operator::NotEqual,
|
|
"<" => Operator::LessThan,
|
|
"<=" => Operator::LessThanOrEqual,
|
|
">" => Operator::GreaterThan,
|
|
">=" => Operator::GreaterThanOrEqual,
|
|
"=~" => Operator::Contains,
|
|
"!~" => Operator::NotContains,
|
|
"+" => Operator::Plus,
|
|
"-" => Operator::Minus,
|
|
"*" => Operator::Multiply,
|
|
"/" => Operator::Divide,
|
|
"in" => Operator::In,
|
|
"not-in" => Operator::NotIn,
|
|
"mod" => Operator::Modulo,
|
|
"&&" => Operator::And,
|
|
"||" => Operator::Or,
|
|
"**" => Operator::Pow,
|
|
_ => {
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("operator", lite_arg.clone())),
|
|
);
|
|
}
|
|
};
|
|
|
|
(
|
|
SpannedExpression::new(Expression::operator(operator), lite_arg.span),
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// Parse a duration type, eg '10day'
|
|
fn parse_duration(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
|
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
|
|
let string_to_parse = format!("0.{}", decimal);
|
|
if let Ok(x) = string_to_parse.parse::<f64>() {
|
|
return Some((1_f64 / x) as i64);
|
|
}
|
|
None
|
|
}
|
|
let unit_groups = [
|
|
(Unit::Nanosecond, "NS", None),
|
|
(Unit::Microsecond, "US", Some((Unit::Nanosecond, 1000))),
|
|
(Unit::Millisecond, "MS", Some((Unit::Microsecond, 1000))),
|
|
(Unit::Second, "SEC", Some((Unit::Millisecond, 1000))),
|
|
(Unit::Minute, "MIN", Some((Unit::Second, 60))),
|
|
(Unit::Hour, "HR", Some((Unit::Minute, 60))),
|
|
(Unit::Day, "DAY", Some((Unit::Minute, 1440))),
|
|
(Unit::Week, "WK", Some((Unit::Day, 7))),
|
|
];
|
|
if let Some(unit) = unit_groups
|
|
.iter()
|
|
.find(|&x| lite_arg.to_uppercase().ends_with(x.1))
|
|
{
|
|
let mut lhs = lite_arg.item.clone();
|
|
for _ in 0..unit.1.len() {
|
|
lhs.pop();
|
|
}
|
|
|
|
let input: Vec<&str> = lhs.split('.').collect();
|
|
let (value, unit_to_use) = match &input[..] {
|
|
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
|
|
[number_str, decimal_part_str] => match unit.2 {
|
|
Some(unit_to_convert_to) => match (
|
|
number_str.parse::<i64>(),
|
|
parse_decimal_str_to_number(decimal_part_str),
|
|
) {
|
|
(Ok(number), Some(decimal_part)) => (
|
|
Some(
|
|
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
|
|
),
|
|
unit_to_convert_to.0,
|
|
),
|
|
_ => (None, unit.0),
|
|
},
|
|
None => (None, unit.0),
|
|
},
|
|
_ => (None, unit.0),
|
|
};
|
|
|
|
if let Some(x) = value {
|
|
let lhs_span = Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
|
|
let unit_span = Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
|
|
return (
|
|
SpannedExpression::new(
|
|
Expression::unit(x.spanned(lhs_span), unit_to_use.spanned(unit_span)),
|
|
lite_arg.span,
|
|
),
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("duration", lite_arg.clone())),
|
|
)
|
|
}
|
|
|
|
/// Parse a unit type, eg '10kb'
|
|
fn parse_filesize(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
|
|
fn parse_decimal_str_to_number(decimal: &str) -> Option<i64> {
|
|
let string_to_parse = format!("0.{}", decimal);
|
|
if let Ok(x) = string_to_parse.parse::<f64>() {
|
|
return Some((1_f64 / x) as i64);
|
|
}
|
|
None
|
|
}
|
|
let unit_groups = [
|
|
(Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))),
|
|
(Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))),
|
|
(Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))),
|
|
(Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))),
|
|
(Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))),
|
|
(Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))),
|
|
(Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))),
|
|
(Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))),
|
|
(Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))),
|
|
(Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))),
|
|
(Unit::Byte, "B", None),
|
|
];
|
|
if let Some(unit) = unit_groups
|
|
.iter()
|
|
.find(|&x| lite_arg.to_uppercase().ends_with(x.1))
|
|
{
|
|
let mut lhs = lite_arg.item.clone();
|
|
for _ in 0..unit.1.len() {
|
|
lhs.pop();
|
|
}
|
|
|
|
let input: Vec<&str> = lhs.split('.').collect();
|
|
let (value, unit_to_use) = match &input[..] {
|
|
[number_str] => (number_str.parse::<i64>().ok(), unit.0),
|
|
[number_str, decimal_part_str] => match unit.2 {
|
|
Some(unit_to_convert_to) => match (
|
|
number_str.parse::<i64>(),
|
|
parse_decimal_str_to_number(decimal_part_str),
|
|
) {
|
|
(Ok(number), Some(decimal_part)) => (
|
|
Some(
|
|
(number * unit_to_convert_to.1) + (unit_to_convert_to.1 / decimal_part),
|
|
),
|
|
unit_to_convert_to.0,
|
|
),
|
|
_ => (None, unit.0),
|
|
},
|
|
None => (None, unit.0),
|
|
},
|
|
_ => (None, unit.0),
|
|
};
|
|
|
|
if let Some(x) = value {
|
|
let lhs_span = Span::new(lite_arg.span.start(), lite_arg.span.start() + lhs.len());
|
|
let unit_span = Span::new(lite_arg.span.start() + lhs.len(), lite_arg.span.end());
|
|
return (
|
|
SpannedExpression::new(
|
|
Expression::unit(x.spanned(lhs_span), unit_to_use.spanned(unit_span)),
|
|
lite_arg.span,
|
|
),
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("unit", lite_arg.clone())),
|
|
)
|
|
}
|
|
|
|
fn parse_invocation(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
// We have a command invocation
|
|
let string: String = lite_arg
|
|
.item
|
|
.chars()
|
|
.skip(1)
|
|
.take(lite_arg.item.chars().count() - 2)
|
|
.collect();
|
|
|
|
// We haven't done much with the inner string, so let's go ahead and work with it
|
|
let (tokens, err) = lex(&string, lite_arg.span.start() + 1);
|
|
if err.is_some() {
|
|
return (garbage(lite_arg.span), err);
|
|
};
|
|
let (lite_block, err) = parse_block(tokens);
|
|
if err.is_some() {
|
|
return (garbage(lite_arg.span), err);
|
|
};
|
|
|
|
scope.enter_scope();
|
|
let (classified_block, err) = classify_block(&lite_block, scope);
|
|
scope.exit_scope();
|
|
|
|
(
|
|
SpannedExpression::new(Expression::Invocation(classified_block), lite_arg.span),
|
|
err,
|
|
)
|
|
}
|
|
|
|
fn parse_variable(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
if lite_arg.item == "$it" {
|
|
trace!("parsing $it");
|
|
parse_full_column_path(lite_arg, scope)
|
|
} else {
|
|
(
|
|
SpannedExpression::new(
|
|
Expression::variable(lite_arg.item.clone(), lite_arg.span),
|
|
lite_arg.span,
|
|
),
|
|
None,
|
|
)
|
|
}
|
|
}
|
|
/// Parses the given lite_arg starting with dollar returning
|
|
/// a expression starting with $
|
|
/// Currently either Variable, Invocation, FullColumnPath
|
|
fn parse_dollar_expr(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
trace!("Parsing dollar expression: {:?}", lite_arg.item);
|
|
if (lite_arg.item.starts_with("$\"") && lite_arg.item.len() > 1 && lite_arg.item.ends_with('"'))
|
|
|| (lite_arg.item.starts_with("$'")
|
|
&& lite_arg.item.len() > 1
|
|
&& lite_arg.item.ends_with('\''))
|
|
{
|
|
// This is an interpolated string
|
|
parse_interpolated_string(&lite_arg, scope)
|
|
} else if let (expr, None) = parse_range(lite_arg, scope) {
|
|
(expr, None)
|
|
} else if let (expr, None) = parse_full_column_path(lite_arg, scope) {
|
|
(expr, None)
|
|
} else {
|
|
parse_variable(lite_arg, scope)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum FormatCommand {
|
|
Text(Spanned<String>),
|
|
Column(Spanned<String>),
|
|
}
|
|
|
|
fn format(input: &str, start: usize) -> (Vec<FormatCommand>, Option<ParseError>) {
|
|
let original_start = start;
|
|
let mut output = vec![];
|
|
let mut error = None;
|
|
|
|
let mut loop_input = input.chars().peekable();
|
|
let mut start = start;
|
|
let mut end = start;
|
|
loop {
|
|
let mut before = String::new();
|
|
|
|
loop {
|
|
end += 1;
|
|
if let Some(c) = loop_input.next() {
|
|
if c == '(' {
|
|
break;
|
|
}
|
|
before.push(c);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !before.is_empty() {
|
|
output.push(FormatCommand::Text(
|
|
before.to_string().spanned(Span::new(start, end - 1)),
|
|
));
|
|
}
|
|
// Look for column as we're now at one
|
|
let mut column = String::new();
|
|
start = end;
|
|
|
|
let mut found_end = false;
|
|
let mut delimiter_stack = vec![')'];
|
|
|
|
while let Some(c) = loop_input.next() {
|
|
end += 1;
|
|
if let Some('\'') = delimiter_stack.last() {
|
|
if c == '\'' {
|
|
delimiter_stack.pop();
|
|
}
|
|
} else if let Some('"') = delimiter_stack.last() {
|
|
if c == '"' {
|
|
delimiter_stack.pop();
|
|
}
|
|
} else if c == '\'' {
|
|
delimiter_stack.push('\'');
|
|
} else if c == '"' {
|
|
delimiter_stack.push('"');
|
|
} else if c == '(' {
|
|
delimiter_stack.push(')');
|
|
} else if c == ')' {
|
|
if let Some(')') = delimiter_stack.last() {
|
|
delimiter_stack.pop();
|
|
}
|
|
if delimiter_stack.is_empty() {
|
|
found_end = true;
|
|
break;
|
|
}
|
|
}
|
|
column.push(c);
|
|
}
|
|
|
|
if !column.is_empty() {
|
|
output.push(FormatCommand::Column(
|
|
column.to_string().spanned(Span::new(start, end)),
|
|
));
|
|
}
|
|
|
|
if column.is_empty() {
|
|
break;
|
|
}
|
|
|
|
if !found_end {
|
|
error = Some(ParseError::argument_error(
|
|
input.spanned(Span::new(original_start, end)),
|
|
ArgumentError::MissingValueForName("unclosed { }".to_string()),
|
|
));
|
|
}
|
|
|
|
start = end;
|
|
}
|
|
|
|
(output, error)
|
|
}
|
|
|
|
/// Parses an interpolated string, one that has expressions inside of it
|
|
fn parse_interpolated_string(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
trace!("Parse_interpolated_string");
|
|
let string_len = lite_arg.item.len();
|
|
let inner_string = lite_arg
|
|
.item
|
|
.chars()
|
|
.skip(2)
|
|
.take(string_len - 3)
|
|
.collect::<String>();
|
|
let mut error = None;
|
|
|
|
let (format_result, err) = format(&inner_string, lite_arg.span.start() + 2);
|
|
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let mut output = vec![];
|
|
|
|
for f in format_result {
|
|
match f {
|
|
FormatCommand::Text(t) => {
|
|
output.push(SpannedExpression {
|
|
expr: Expression::Literal(hir::Literal::String(t.item)),
|
|
span: t.span,
|
|
});
|
|
}
|
|
FormatCommand::Column(c) => {
|
|
let result = parse(&c, c.span.start(), scope);
|
|
match result {
|
|
(classified_block, None) => {
|
|
output.push(SpannedExpression {
|
|
expr: Expression::Invocation(classified_block),
|
|
span: c.span,
|
|
});
|
|
}
|
|
(_, Some(err)) => {
|
|
return (garbage(c.span), Some(err));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let pipelines = vec![Pipeline {
|
|
span: lite_arg.span,
|
|
list: vec![ClassifiedCommand::Internal(InternalCommand {
|
|
name: "build-string".to_owned(),
|
|
name_span: lite_arg.span,
|
|
args: hir::Call {
|
|
head: Box::new(SpannedExpression {
|
|
expr: Expression::Synthetic(hir::Synthetic::String("build-string".to_owned())),
|
|
span: lite_arg.span,
|
|
}),
|
|
external_redirection: ExternalRedirection::Stdout,
|
|
named: None,
|
|
positional: Some(output),
|
|
span: lite_arg.span,
|
|
},
|
|
})],
|
|
}];
|
|
|
|
let group = Group::new(pipelines, lite_arg.span);
|
|
|
|
let call = SpannedExpression {
|
|
expr: Expression::Invocation(Arc::new(Block::new(
|
|
Signature::new("<invocation>"),
|
|
vec![group],
|
|
IndexMap::new(),
|
|
lite_arg.span,
|
|
))),
|
|
span: lite_arg.span,
|
|
};
|
|
|
|
(call, error)
|
|
}
|
|
|
|
/// Parses the given argument using the shape as a guide for how to correctly parse the argument
|
|
fn parse_external_arg(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
if lite_arg.item.starts_with('$') {
|
|
parse_dollar_expr(&lite_arg, scope)
|
|
} else if lite_arg.item.starts_with('(') {
|
|
parse_invocation(&lite_arg, scope)
|
|
} else {
|
|
(
|
|
SpannedExpression::new(Expression::string(lite_arg.item.clone()), lite_arg.span),
|
|
None,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn parse_list(
|
|
lite_block: &LiteBlock,
|
|
scope: &dyn ParserScope,
|
|
) -> (Vec<SpannedExpression>, Option<ParseError>) {
|
|
let mut error = None;
|
|
|
|
if lite_block.block.is_empty() {
|
|
return (vec![], None);
|
|
}
|
|
let lite_pipeline = &lite_block.block[0];
|
|
let mut output = vec![];
|
|
for lite_pipeline in &lite_pipeline.pipelines {
|
|
for lite_inner in &lite_pipeline.commands {
|
|
for part in &lite_inner.parts {
|
|
let item = if part.ends_with(',') {
|
|
let mut str: String = part.item.clone();
|
|
str.pop();
|
|
str.spanned(Span::new(part.span.start(), part.span.end() - 1))
|
|
} else {
|
|
part.clone()
|
|
};
|
|
let (part, err) = parse_arg(SyntaxShape::Any, scope, &item);
|
|
output.push(part);
|
|
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
(output, error)
|
|
}
|
|
|
|
fn parse_table(
|
|
lite_block: &LiteBlock,
|
|
scope: &dyn ParserScope,
|
|
span: Span,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
let mut error = None;
|
|
let mut output = vec![];
|
|
|
|
// Header
|
|
let lite_group = &lite_block.block[0];
|
|
let lite_pipeline = &lite_group.pipelines[0];
|
|
let lite_inner = &lite_pipeline.commands[0];
|
|
|
|
let (string, err) = verify_and_strip(&lite_inner.parts[0], '[', ']');
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let (tokens, err) = lex(&string, lite_inner.parts[0].span.start() + 1);
|
|
if err.is_some() {
|
|
return (garbage(lite_inner.span()), err);
|
|
}
|
|
|
|
let (lite_header, err) = parse_block(tokens);
|
|
if err.is_some() {
|
|
return (garbage(lite_inner.span()), err);
|
|
}
|
|
|
|
let (headers, err) = parse_list(&lite_header, scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
// Cells
|
|
let lite_rows = &lite_group.pipelines[1];
|
|
let lite_cells = &lite_rows.commands[0];
|
|
|
|
for arg in &lite_cells.parts {
|
|
let (string, err) = verify_and_strip(&arg, '[', ']');
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
let (tokens, err) = lex(&string, arg.span.start() + 1);
|
|
if err.is_some() {
|
|
return (garbage(arg.span), err);
|
|
}
|
|
let (lite_cell, err) = parse_block(tokens);
|
|
if err.is_some() {
|
|
return (garbage(arg.span), err);
|
|
}
|
|
let (inner_cell, err) = parse_list(&lite_cell, scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
output.push(inner_cell);
|
|
}
|
|
|
|
(
|
|
SpannedExpression::new(Expression::Table(headers, output), span),
|
|
error,
|
|
)
|
|
}
|
|
|
|
/// Parses the given argument using the shape as a guide for how to correctly parse the argument
|
|
fn parse_arg(
|
|
expected_type: SyntaxShape,
|
|
scope: &dyn ParserScope,
|
|
lite_arg: &Spanned<String>,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
if lite_arg.item.starts_with('$') {
|
|
return parse_dollar_expr(&lite_arg, scope);
|
|
}
|
|
|
|
// before anything else, try to see if this is a number in paranthesis
|
|
if lite_arg.item.starts_with('(') {
|
|
let (expr, err) = parse_full_column_path(&lite_arg, scope);
|
|
if err.is_none() {
|
|
return (expr, None);
|
|
}
|
|
}
|
|
|
|
match expected_type {
|
|
SyntaxShape::Number => {
|
|
if let Ok(x) = lite_arg.item.parse::<i64>() {
|
|
(
|
|
SpannedExpression::new(Expression::integer(x), lite_arg.span),
|
|
None,
|
|
)
|
|
} else if let Ok(x) = lite_arg.item.parse::<BigInt>() {
|
|
(
|
|
SpannedExpression::new(Expression::big_integer(x), lite_arg.span),
|
|
None,
|
|
)
|
|
} else if let Ok(x) = lite_arg.item.parse::<BigDecimal>() {
|
|
(
|
|
SpannedExpression::new(Expression::decimal(x), lite_arg.span),
|
|
None,
|
|
)
|
|
} else {
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("number", lite_arg.clone())),
|
|
)
|
|
}
|
|
}
|
|
SyntaxShape::Int => {
|
|
if let Ok(x) = lite_arg.item.parse::<i64>() {
|
|
(
|
|
SpannedExpression::new(Expression::integer(x), lite_arg.span),
|
|
None,
|
|
)
|
|
} else if let Ok(x) = lite_arg.item.parse::<BigInt>() {
|
|
(
|
|
SpannedExpression::new(Expression::big_integer(x), lite_arg.span),
|
|
None,
|
|
)
|
|
} else {
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("int", lite_arg.clone())),
|
|
)
|
|
}
|
|
}
|
|
SyntaxShape::String => {
|
|
let trimmed = trim_quotes(&lite_arg.item);
|
|
(
|
|
SpannedExpression::new(Expression::string(trimmed), lite_arg.span),
|
|
None,
|
|
)
|
|
}
|
|
SyntaxShape::GlobPattern => {
|
|
let trimmed = trim_quotes(&lite_arg.item);
|
|
let expanded = expand_path(&trimmed).to_string();
|
|
(
|
|
SpannedExpression::new(Expression::glob_pattern(expanded), lite_arg.span),
|
|
None,
|
|
)
|
|
}
|
|
|
|
SyntaxShape::Range => parse_range(&lite_arg, scope),
|
|
SyntaxShape::Operator => parse_operator(&lite_arg),
|
|
SyntaxShape::Filesize => parse_filesize(&lite_arg),
|
|
SyntaxShape::Duration => parse_duration(&lite_arg),
|
|
SyntaxShape::FilePath => {
|
|
let trimmed = trim_quotes(&lite_arg.item);
|
|
let expanded = expand_path(&trimmed).to_string();
|
|
let path = Path::new(&expanded);
|
|
(
|
|
SpannedExpression::new(Expression::FilePath(path.to_path_buf()), lite_arg.span),
|
|
None,
|
|
)
|
|
}
|
|
SyntaxShape::ColumnPath => parse_simple_column_path(lite_arg),
|
|
SyntaxShape::FullColumnPath => parse_full_column_path(lite_arg, scope),
|
|
SyntaxShape::Any => {
|
|
let shapes = vec![
|
|
SyntaxShape::Int,
|
|
SyntaxShape::Number,
|
|
SyntaxShape::Range,
|
|
SyntaxShape::Filesize,
|
|
SyntaxShape::Duration,
|
|
SyntaxShape::Block,
|
|
SyntaxShape::Table,
|
|
SyntaxShape::String,
|
|
];
|
|
for shape in shapes.iter() {
|
|
if let (s, None) = parse_arg(*shape, scope, lite_arg) {
|
|
return (s, None);
|
|
}
|
|
}
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("any shape", lite_arg.clone())),
|
|
)
|
|
}
|
|
SyntaxShape::Table => {
|
|
let mut chars = lite_arg.item.chars();
|
|
|
|
match (chars.next(), chars.next_back()) {
|
|
(Some('['), Some(']')) => {
|
|
// We have a literal row
|
|
let string: String = chars.collect();
|
|
|
|
// We haven't done much with the inner string, so let's go ahead and work with it
|
|
let (tokens, err) = lex(&string, lite_arg.span.start() + 1);
|
|
if err.is_some() {
|
|
return (garbage(lite_arg.span), err);
|
|
}
|
|
|
|
let (lite_block, err) = parse_block(tokens);
|
|
if err.is_some() {
|
|
return (garbage(lite_arg.span), err);
|
|
}
|
|
|
|
let lite_groups = &lite_block.block;
|
|
|
|
if lite_groups.is_empty() {
|
|
return (
|
|
SpannedExpression::new(Expression::List(vec![]), lite_arg.span),
|
|
None,
|
|
);
|
|
}
|
|
if lite_groups[0].pipelines.len() == 1 {
|
|
let (items, err) = parse_list(&lite_block, scope);
|
|
(
|
|
SpannedExpression::new(Expression::List(items), lite_arg.span),
|
|
err,
|
|
)
|
|
} else if lite_groups[0].pipelines.len() == 2 {
|
|
parse_table(&lite_block, scope, lite_arg.span)
|
|
} else {
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch(
|
|
"list or table",
|
|
"unknown".to_string().spanned(lite_arg.span),
|
|
)),
|
|
)
|
|
}
|
|
}
|
|
_ => (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("table", lite_arg.clone())),
|
|
),
|
|
}
|
|
}
|
|
SyntaxShape::MathExpression => parse_arg(SyntaxShape::Any, scope, lite_arg),
|
|
|
|
SyntaxShape::Block | SyntaxShape::RowCondition => {
|
|
// Blocks have one of two forms: the literal block and the implied block
|
|
// To parse a literal block, we need to detect that what we have is itself a block
|
|
let mut chars = lite_arg.item.chars();
|
|
|
|
match (chars.next(), chars.next_back()) {
|
|
(Some('{'), Some('}')) => {
|
|
// We have a literal block
|
|
let string: String = chars.collect();
|
|
|
|
// We haven't done much with the inner string, so let's go ahead and work with it
|
|
let (mut tokens, err) = lex(&string, lite_arg.span.start() + 1);
|
|
if err.is_some() {
|
|
return (garbage(lite_arg.span), err);
|
|
}
|
|
|
|
// Check to see if we have parameters
|
|
let params = if matches!(
|
|
tokens.first(),
|
|
Some(Token {
|
|
contents: TokenContents::Pipe,
|
|
..
|
|
})
|
|
) {
|
|
// We've found a parameter list
|
|
let mut param_tokens = vec![];
|
|
let mut token_iter = tokens.into_iter().skip(1);
|
|
while let Some(token) = token_iter.next() {
|
|
if matches!(
|
|
token,
|
|
Token {
|
|
contents: TokenContents::Pipe,
|
|
..
|
|
}
|
|
) {
|
|
break;
|
|
} else {
|
|
param_tokens.push(token);
|
|
}
|
|
}
|
|
let split_tokens =
|
|
lex_split_baseline_tokens_on(param_tokens, &[',', ':', '?']);
|
|
|
|
let mut i = 0;
|
|
let mut params = vec![];
|
|
|
|
while i < split_tokens.len() {
|
|
let (parameter, advance_by, error) =
|
|
parse_parameter(&split_tokens[i..], split_tokens[i].span);
|
|
|
|
if error.is_some() {
|
|
return (garbage(lite_arg.span), error);
|
|
}
|
|
i += advance_by;
|
|
params.push(parameter);
|
|
}
|
|
|
|
tokens = token_iter.collect();
|
|
if tokens.is_empty() {
|
|
return (
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch(
|
|
"block with parameters",
|
|
lite_arg.clone(),
|
|
)),
|
|
);
|
|
}
|
|
params
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
let (lite_block, err) = parse_block(tokens);
|
|
if err.is_some() {
|
|
return (garbage(lite_arg.span), err);
|
|
}
|
|
|
|
scope.enter_scope();
|
|
let (mut classified_block, err) = classify_block(&lite_block, scope);
|
|
scope.exit_scope();
|
|
|
|
if let Some(classified_block) = Arc::get_mut(&mut classified_block) {
|
|
classified_block.span = lite_arg.span;
|
|
if !params.is_empty() {
|
|
classified_block.params.positional.clear();
|
|
for param in params {
|
|
classified_block
|
|
.params
|
|
.positional
|
|
.push((param.pos_type, param.desc.unwrap_or_default()));
|
|
}
|
|
}
|
|
}
|
|
|
|
(
|
|
SpannedExpression::new(Expression::Block(classified_block), lite_arg.span),
|
|
err,
|
|
)
|
|
}
|
|
_ => {
|
|
// We have an implied block, but we can't parse this here
|
|
// it needed to have been parsed up higher where we have control over more than one arg
|
|
(
|
|
garbage(lite_arg.span),
|
|
Some(ParseError::mismatch("block", lite_arg.clone())),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Match the available flags in a signature with what the user provided. This will check both long-form flags (--long) and shorthand flags (-l)
|
|
/// This also allows users to provide a group of shorthand flags (-la) that correspond to multiple shorthand flags at once.
|
|
fn get_flags_from_flag(
|
|
signature: &nu_protocol::Signature,
|
|
cmd: &InternalCommand,
|
|
arg: &Spanned<String>,
|
|
) -> (Vec<(String, NamedType)>, Option<ParseError>) {
|
|
if arg.item.starts_with('-') {
|
|
// It's a flag (or set of flags)
|
|
let mut output = vec![];
|
|
let mut error = None;
|
|
|
|
let remainder: String = arg.item.chars().skip(1).collect();
|
|
|
|
if remainder.starts_with('-') {
|
|
// Long flag expected
|
|
let remainder: String = remainder.chars().skip(1).collect();
|
|
if let Some((named_type, _)) = signature.named.get(&remainder) {
|
|
output.push((remainder.clone(), named_type.clone()));
|
|
} else {
|
|
error = Some(ParseError::argument_error(
|
|
cmd.name.to_string().spanned(cmd.name_span),
|
|
ArgumentError::UnexpectedFlag(arg.clone()),
|
|
));
|
|
}
|
|
} else {
|
|
// Short flag(s) expected
|
|
let mut starting_pos = arg.span.start() + 1;
|
|
for c in remainder.chars() {
|
|
let mut found = false;
|
|
for (full_name, named_arg) in signature.named.iter() {
|
|
if Some(c) == named_arg.0.get_short() {
|
|
found = true;
|
|
output.push((full_name.clone(), named_arg.0.clone()));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
error = Some(ParseError::argument_error(
|
|
cmd.name.to_string().spanned(cmd.name_span),
|
|
ArgumentError::UnexpectedFlag(
|
|
arg.item
|
|
.clone()
|
|
.spanned(Span::new(starting_pos, starting_pos + c.len_utf8())),
|
|
),
|
|
));
|
|
}
|
|
|
|
starting_pos += c.len_utf8();
|
|
}
|
|
}
|
|
|
|
(output, error)
|
|
} else {
|
|
// It's not a flag, so don't bother with it
|
|
(vec![], None)
|
|
}
|
|
}
|
|
|
|
/// This is a bit of a "fix-up" of previously parsed areas. In cases where we're in shorthand mode (eg in the `where` command), we need
|
|
/// to use the original source to parse a column path. Without it, we'll lose a little too much information to parse it correctly. As we'll
|
|
/// only know we were on the left-hand side of an expression after we do the full math parse, we need to do this step after rather than during
|
|
/// the initial parse.
|
|
fn shorthand_reparse(
|
|
left: SpannedExpression,
|
|
orig_left: Option<Spanned<String>>,
|
|
scope: &dyn ParserScope,
|
|
shorthand_mode: bool,
|
|
) -> (SpannedExpression, Option<ParseError>) {
|
|
// If we're in shorthand mode, we need to reparse the left-hand side if possible
|
|
if shorthand_mode {
|
|
if let Some(orig_left) = orig_left {
|
|
parse_arg(SyntaxShape::FullColumnPath, scope, &orig_left)
|
|
} else {
|
|
(left, None)
|
|
}
|
|
} else {
|
|
(left, None)
|
|
}
|
|
}
|
|
|
|
fn parse_possibly_parenthesized(
|
|
lite_arg: &Spanned<String>,
|
|
scope: &dyn ParserScope,
|
|
) -> (
|
|
(Option<Spanned<String>>, SpannedExpression),
|
|
Option<ParseError>,
|
|
) {
|
|
let (lhs, err) = parse_arg(SyntaxShape::Any, scope, lite_arg);
|
|
((Some(lite_arg.clone()), lhs), err)
|
|
}
|
|
|
|
/// Handle parsing math expressions, complete with working with the precedence of the operators
|
|
pub fn parse_math_expression(
|
|
incoming_idx: usize,
|
|
lite_args: &[Spanned<String>],
|
|
scope: &dyn ParserScope,
|
|
shorthand_mode: bool,
|
|
) -> (usize, SpannedExpression, Option<ParseError>) {
|
|
// Precedence parsing is included
|
|
// shorthand_mode means that the left-hand side of an expression can point to a column-path.
|
|
// To make this possible, we parse as normal, but then go back and when we detect a
|
|
// left-hand side, reparse that value if it's a string
|
|
|
|
let mut idx = 0;
|
|
let mut error = None;
|
|
|
|
let mut working_exprs = vec![];
|
|
let mut prec = vec![];
|
|
|
|
let (lhs_working_expr, err) = parse_possibly_parenthesized(&lite_args[idx], scope);
|
|
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
working_exprs.push(lhs_working_expr);
|
|
|
|
idx += 1;
|
|
|
|
prec.push(0);
|
|
|
|
while idx < lite_args.len() {
|
|
let (op, err) = parse_arg(SyntaxShape::Operator, scope, &lite_args[idx]);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
idx += 1;
|
|
|
|
if idx == lite_args.len() {
|
|
if error.is_none() {
|
|
error = Some(ParseError::argument_error(
|
|
lite_args[idx - 1].clone(),
|
|
ArgumentError::MissingMandatoryPositional("right hand side".into()),
|
|
));
|
|
}
|
|
working_exprs.push((None, garbage(op.span)));
|
|
working_exprs.push((None, garbage(op.span)));
|
|
prec.push(0);
|
|
break;
|
|
}
|
|
|
|
trace!(
|
|
"idx: {} working_exprs: {:#?} prec: {:?}",
|
|
idx,
|
|
working_exprs,
|
|
prec
|
|
);
|
|
|
|
let (rhs_working_expr, err) = parse_possibly_parenthesized(&lite_args[idx], scope);
|
|
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let next_prec = op.precedence();
|
|
|
|
if !prec.is_empty() && next_prec > *prec.last().expect("this shouldn't happen") {
|
|
prec.push(next_prec);
|
|
working_exprs.push((None, op));
|
|
working_exprs.push(rhs_working_expr);
|
|
idx += 1;
|
|
continue;
|
|
}
|
|
while !prec.is_empty()
|
|
&& *prec.last().expect("This shouldn't happen") >= next_prec
|
|
&& next_prec > 0 // Not garbage
|
|
&& working_exprs.len() >= 3
|
|
{
|
|
// Pop 3 and create and expression, push and repeat
|
|
trace!(
|
|
"idx: {} working_exprs: {:#?} prec: {:?}",
|
|
idx,
|
|
working_exprs,
|
|
prec
|
|
);
|
|
let (_, right) = working_exprs.pop().expect("This shouldn't be possible");
|
|
let (_, op) = working_exprs.pop().expect("This shouldn't be possible");
|
|
let (orig_left, left) = working_exprs.pop().expect("This shouldn't be possible");
|
|
|
|
// If we're in shorthand mode, we need to reparse the left-hand side if possible
|
|
let (left, err) = shorthand_reparse(left, orig_left, scope, shorthand_mode);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let span = Span::new(left.span.start(), right.span.end());
|
|
working_exprs.push((
|
|
None,
|
|
SpannedExpression {
|
|
expr: Expression::Binary(Box::new(Binary { left, op, right })),
|
|
span,
|
|
},
|
|
));
|
|
prec.pop();
|
|
}
|
|
working_exprs.push((None, op));
|
|
working_exprs.push(rhs_working_expr);
|
|
prec.push(next_prec);
|
|
|
|
idx += 1;
|
|
}
|
|
|
|
while working_exprs.len() >= 3 {
|
|
// Pop 3 and create and expression, push and repeat
|
|
let (_, right) = working_exprs.pop().expect("This shouldn't be possible");
|
|
let (_, op) = working_exprs.pop().expect("This shouldn't be possible");
|
|
let (orig_left, left) = working_exprs.pop().expect("This shouldn't be possible");
|
|
|
|
let (left, err) = shorthand_reparse(left, orig_left, scope, shorthand_mode);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let span = Span::new(left.span.start(), right.span.end());
|
|
working_exprs.push((
|
|
None,
|
|
SpannedExpression {
|
|
expr: Expression::Binary(Box::new(Binary { left, op, right })),
|
|
span,
|
|
},
|
|
));
|
|
}
|
|
|
|
let (orig_left, left) = working_exprs.pop().expect("This shouldn't be possible");
|
|
let (left, err) = shorthand_reparse(left, orig_left, scope, shorthand_mode);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
(incoming_idx + idx, left, error)
|
|
}
|
|
|
|
/// Handles parsing the positional arguments as a batch
|
|
/// This allows us to check for times where multiple arguments are treated as one shape, as is the case with SyntaxShape::Math
|
|
fn parse_positional_argument(
|
|
idx: usize,
|
|
lite_cmd: &LiteCommand,
|
|
positional_type: &PositionalType,
|
|
remaining_positionals: usize,
|
|
scope: &dyn ParserScope,
|
|
) -> (usize, SpannedExpression, Option<ParseError>) {
|
|
let mut idx = idx;
|
|
let mut error = None;
|
|
let arg = match positional_type {
|
|
PositionalType::Mandatory(_, SyntaxShape::MathExpression)
|
|
| PositionalType::Optional(_, SyntaxShape::MathExpression) => {
|
|
let end_idx = if (lite_cmd.parts.len() - 1) > remaining_positionals {
|
|
lite_cmd.parts.len() - remaining_positionals
|
|
} else {
|
|
lite_cmd.parts.len()
|
|
};
|
|
|
|
let (new_idx, arg, err) =
|
|
parse_math_expression(idx, &lite_cmd.parts[idx..end_idx], scope, false);
|
|
|
|
let span = arg.span;
|
|
let mut commands = hir::Pipeline::new(span);
|
|
commands.push(ClassifiedCommand::Expr(Box::new(arg)));
|
|
|
|
let block = hir::Block::new(
|
|
Signature::new("<initializer>"),
|
|
vec![Group::new(vec![commands], lite_cmd.span())],
|
|
IndexMap::new(),
|
|
span,
|
|
);
|
|
|
|
let arg = SpannedExpression::new(Expression::Block(Arc::new(block)), span);
|
|
|
|
idx = new_idx - 1;
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
arg
|
|
}
|
|
PositionalType::Mandatory(_, SyntaxShape::RowCondition)
|
|
| PositionalType::Optional(_, SyntaxShape::RowCondition) => {
|
|
// A condition can take up multiple arguments, as we build the operation as <arg> <operator> <arg>
|
|
// We need to do this here because in parse_arg, we have access to only one arg at a time
|
|
|
|
if idx < lite_cmd.parts.len() {
|
|
if lite_cmd.parts[idx].item.starts_with('{') {
|
|
// It's an explicit math expression, so parse it deeper in
|
|
let (arg, err) =
|
|
parse_arg(SyntaxShape::RowCondition, scope, &lite_cmd.parts[idx]);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
arg
|
|
} else {
|
|
let end_idx = if (lite_cmd.parts.len() - 1) > remaining_positionals {
|
|
lite_cmd.parts.len() - remaining_positionals
|
|
} else {
|
|
lite_cmd.parts.len()
|
|
};
|
|
|
|
let (new_idx, arg, err) =
|
|
parse_math_expression(idx, &lite_cmd.parts[idx..end_idx], scope, true);
|
|
|
|
let span = arg.span;
|
|
let mut commands = hir::Pipeline::new(span);
|
|
commands.push(ClassifiedCommand::Expr(Box::new(arg)));
|
|
|
|
let mut block = hir::Block::new(
|
|
Signature::new("<cond>"),
|
|
vec![Group::new(vec![commands], lite_cmd.span())],
|
|
IndexMap::new(),
|
|
span,
|
|
);
|
|
|
|
block.infer_params();
|
|
|
|
let arg = SpannedExpression::new(Expression::Block(Arc::new(block)), span);
|
|
|
|
idx = new_idx - 1;
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
arg
|
|
}
|
|
} else {
|
|
if error.is_none() {
|
|
error = Some(ParseError::argument_error(
|
|
lite_cmd.parts[0].clone(),
|
|
ArgumentError::MissingMandatoryPositional("condition".into()),
|
|
))
|
|
}
|
|
garbage(lite_cmd.span())
|
|
}
|
|
}
|
|
PositionalType::Mandatory(_, shape) | PositionalType::Optional(_, shape) => {
|
|
let (arg, err) = parse_arg(*shape, scope, &lite_cmd.parts[idx]);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
arg
|
|
}
|
|
};
|
|
|
|
(idx, arg, error)
|
|
}
|
|
|
|
/// Does a full parse of an internal command using the lite-ly parse command as a starting point
|
|
/// This main focus at this level is to understand what flags were passed in, what positional arguments were passed in, what rest arguments were passed in
|
|
/// and to ensure that the basic requirements in terms of number of each were met.
|
|
fn parse_internal_command(
|
|
lite_cmd: &LiteCommand,
|
|
scope: &dyn ParserScope,
|
|
signature: &Signature,
|
|
mut idx: usize,
|
|
) -> (InternalCommand, Option<ParseError>) {
|
|
// This is a known internal command, so we need to work with the arguments and parse them according to the expected types
|
|
|
|
let (name, name_span) = (
|
|
lite_cmd.parts[0..(idx + 1)]
|
|
.iter()
|
|
.map(|x| x.item.clone())
|
|
.collect::<Vec<String>>()
|
|
.join(" "),
|
|
Span::new(
|
|
lite_cmd.parts[0].span.start(),
|
|
lite_cmd.parts[idx].span.end(),
|
|
),
|
|
);
|
|
|
|
let mut internal_command = InternalCommand::new(name, name_span, lite_cmd.span());
|
|
internal_command.args.set_initial_flags(&signature);
|
|
|
|
let mut current_positional = 0;
|
|
let mut named = NamedArguments::new();
|
|
let mut positional = vec![];
|
|
let mut error = None;
|
|
idx += 1; // Start where the arguments begin
|
|
|
|
while idx < lite_cmd.parts.len() {
|
|
if lite_cmd.parts[idx].item.starts_with('-') && lite_cmd.parts[idx].item.len() > 1 {
|
|
let (named_types, err) =
|
|
get_flags_from_flag(&signature, &internal_command, &lite_cmd.parts[idx]);
|
|
|
|
if err.is_none() {
|
|
for (full_name, named_type) in &named_types {
|
|
match named_type {
|
|
NamedType::Mandatory(_, shape) | NamedType::Optional(_, shape) => {
|
|
if idx == lite_cmd.parts.len() {
|
|
// Oops, we're missing the argument to our named argument
|
|
if error.is_none() {
|
|
error = Some(ParseError::argument_error(
|
|
lite_cmd.parts[0].clone(),
|
|
ArgumentError::MissingValueForName(format!("{:?}", shape)),
|
|
));
|
|
}
|
|
} else {
|
|
idx += 1;
|
|
if lite_cmd.parts.len() > idx {
|
|
let (arg, err) = parse_arg(*shape, scope, &lite_cmd.parts[idx]);
|
|
named.insert_mandatory(
|
|
full_name.clone(),
|
|
lite_cmd.parts[idx - 1].span,
|
|
arg,
|
|
);
|
|
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
} else if error.is_none() {
|
|
error = Some(ParseError::argument_error(
|
|
lite_cmd.parts[0].clone(),
|
|
ArgumentError::MissingValueForName(full_name.to_owned()),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
NamedType::Switch(_) => {
|
|
named.insert_switch(
|
|
full_name.clone(),
|
|
Some(Flag::new(FlagKind::Longhand, lite_cmd.parts[idx].span)),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
positional.push(garbage(lite_cmd.parts[idx].span));
|
|
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
}
|
|
} else if signature.positional.len() > current_positional {
|
|
let arg = {
|
|
let (new_idx, expr, err) = parse_positional_argument(
|
|
idx,
|
|
&lite_cmd,
|
|
&signature.positional[current_positional].0,
|
|
signature.positional.len() - current_positional - 1,
|
|
scope,
|
|
);
|
|
idx = new_idx;
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
expr
|
|
};
|
|
|
|
positional.push(arg);
|
|
current_positional += 1;
|
|
} else if let Some((rest_type, _)) = &signature.rest_positional {
|
|
let (arg, err) = parse_arg(*rest_type, scope, &lite_cmd.parts[idx]);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
positional.push(arg);
|
|
current_positional += 1;
|
|
} else {
|
|
positional.push(garbage(lite_cmd.parts[idx].span));
|
|
|
|
if error.is_none() {
|
|
error = Some(ParseError::argument_error(
|
|
lite_cmd.parts[0].clone(),
|
|
ArgumentError::UnexpectedArgument(lite_cmd.parts[idx].clone()),
|
|
));
|
|
}
|
|
}
|
|
|
|
idx += 1;
|
|
}
|
|
|
|
// Count the required positional arguments and ensure these have been met
|
|
let mut required_arg_count = 0;
|
|
for positional_arg in &signature.positional {
|
|
if let PositionalType::Mandatory(_, _) = positional_arg.0 {
|
|
required_arg_count += 1;
|
|
}
|
|
}
|
|
if positional.len() < required_arg_count && error.is_none() {
|
|
// to make "command -h" work even if required arguments are missing
|
|
if !named.named.contains_key("help") {
|
|
let (_, name) = &signature.positional[positional.len()];
|
|
error = Some(ParseError::argument_error(
|
|
lite_cmd.parts[0].clone(),
|
|
ArgumentError::MissingMandatoryPositional(name.to_owned()),
|
|
));
|
|
}
|
|
}
|
|
|
|
if !named.is_empty() {
|
|
internal_command.args.named = Some(named);
|
|
}
|
|
|
|
if !positional.is_empty() {
|
|
internal_command.args.positional = Some(positional);
|
|
}
|
|
|
|
(internal_command, error)
|
|
}
|
|
|
|
fn parse_external_call(
|
|
lite_cmd: &LiteCommand,
|
|
end_of_pipeline: bool,
|
|
scope: &dyn ParserScope,
|
|
) -> (Option<ClassifiedCommand>, Option<ParseError>) {
|
|
let mut error = None;
|
|
let name = lite_cmd.parts[0].clone().map(|v| {
|
|
let trimmed = trim_quotes(&v);
|
|
expand_path(&trimmed).to_string()
|
|
});
|
|
|
|
let mut args = vec![];
|
|
|
|
let (name, err) = parse_arg(SyntaxShape::String, scope, &name);
|
|
let name_span = name.span;
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
args.push(name);
|
|
|
|
for lite_arg in &lite_cmd.parts[1..] {
|
|
let (expr, err) = parse_external_arg(lite_arg, scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
args.push(expr);
|
|
}
|
|
|
|
(
|
|
Some(ClassifiedCommand::Internal(InternalCommand {
|
|
name: "run_external".to_string(),
|
|
name_span,
|
|
args: hir::Call {
|
|
head: Box::new(SpannedExpression {
|
|
expr: Expression::string("run_external".to_string()),
|
|
span: name_span,
|
|
}),
|
|
positional: Some(args),
|
|
named: None,
|
|
span: name_span,
|
|
external_redirection: if end_of_pipeline {
|
|
ExternalRedirection::None
|
|
} else {
|
|
ExternalRedirection::Stdout
|
|
},
|
|
},
|
|
})),
|
|
error,
|
|
)
|
|
}
|
|
|
|
fn parse_value_call(
|
|
call: LiteCommand,
|
|
scope: &dyn ParserScope,
|
|
) -> (Option<ClassifiedCommand>, Option<ParseError>) {
|
|
let mut err = None;
|
|
|
|
let (head, error) = parse_arg(SyntaxShape::Block, scope, &call.parts[0]);
|
|
let mut span = head.span;
|
|
if err.is_none() {
|
|
err = error;
|
|
}
|
|
|
|
let mut args = vec![];
|
|
for arg in call.parts.iter().skip(1) {
|
|
let (arg, error) = parse_arg(SyntaxShape::Any, scope, arg);
|
|
if err.is_none() {
|
|
err = error;
|
|
}
|
|
span = span.until(arg.span);
|
|
args.push(arg);
|
|
}
|
|
|
|
(
|
|
Some(ClassifiedCommand::Dynamic(hir::Call {
|
|
head: Box::new(head),
|
|
positional: Some(args),
|
|
named: None,
|
|
span,
|
|
external_redirection: ExternalRedirection::None,
|
|
})),
|
|
err,
|
|
)
|
|
}
|
|
|
|
fn expand_aliases_in_call(call: &mut LiteCommand, scope: &dyn ParserScope) {
|
|
if let Some(name) = call.parts.get(0) {
|
|
if let Some(mut expansion) = scope.get_alias(name) {
|
|
// set the expansion's spans to point to the alias itself
|
|
for item in expansion.iter_mut() {
|
|
item.span = name.span;
|
|
}
|
|
|
|
// replace the alias with the expansion
|
|
call.parts.remove(0);
|
|
expansion.append(&mut call.parts);
|
|
call.parts = expansion;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_call(
|
|
mut lite_cmd: LiteCommand,
|
|
end_of_pipeline: bool,
|
|
scope: &dyn ParserScope,
|
|
) -> (Option<ClassifiedCommand>, Option<ParseError>) {
|
|
expand_aliases_in_call(&mut lite_cmd, scope);
|
|
|
|
let mut error = None;
|
|
if lite_cmd.parts.is_empty() {
|
|
return (None, None);
|
|
} else if lite_cmd.parts[0].item.starts_with('^') {
|
|
let name = lite_cmd.parts[0]
|
|
.clone()
|
|
.map(|v| v.chars().skip(1).collect::<String>());
|
|
// TODO this is the same as the `else` branch below, only the name differs. Find a way
|
|
// to share this functionality.
|
|
let mut args = vec![];
|
|
|
|
let (name, err) = parse_arg(SyntaxShape::String, scope, &name);
|
|
let name_span = name.span;
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
args.push(name);
|
|
|
|
for lite_arg in &lite_cmd.parts[1..] {
|
|
let (expr, err) = parse_external_arg(lite_arg, scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
args.push(expr);
|
|
}
|
|
|
|
return (
|
|
Some(ClassifiedCommand::Internal(InternalCommand {
|
|
name: "run_external".to_string(),
|
|
name_span,
|
|
args: hir::Call {
|
|
head: Box::new(SpannedExpression {
|
|
expr: Expression::string("run_external".to_string()),
|
|
span: name_span,
|
|
}),
|
|
positional: Some(args),
|
|
named: None,
|
|
span: name_span,
|
|
external_redirection: if end_of_pipeline {
|
|
ExternalRedirection::None
|
|
} else {
|
|
ExternalRedirection::Stdout
|
|
},
|
|
},
|
|
})),
|
|
error,
|
|
);
|
|
} else if lite_cmd.parts[0].item.starts_with('{') {
|
|
return parse_value_call(lite_cmd, scope);
|
|
} else if lite_cmd.parts[0].item.starts_with('$')
|
|
|| lite_cmd.parts[0].item.starts_with('\"')
|
|
|| lite_cmd.parts[0].item.starts_with('\'')
|
|
|| lite_cmd.parts[0].item.starts_with('-')
|
|
|| lite_cmd.parts[0].item.starts_with('0')
|
|
|| lite_cmd.parts[0].item.starts_with('1')
|
|
|| lite_cmd.parts[0].item.starts_with('2')
|
|
|| lite_cmd.parts[0].item.starts_with('3')
|
|
|| lite_cmd.parts[0].item.starts_with('4')
|
|
|| lite_cmd.parts[0].item.starts_with('5')
|
|
|| lite_cmd.parts[0].item.starts_with('6')
|
|
|| lite_cmd.parts[0].item.starts_with('7')
|
|
|| lite_cmd.parts[0].item.starts_with('8')
|
|
|| lite_cmd.parts[0].item.starts_with('9')
|
|
|| lite_cmd.parts[0].item.starts_with('[')
|
|
|| lite_cmd.parts[0].item.starts_with('(')
|
|
{
|
|
let (_, expr, err) = parse_math_expression(0, &lite_cmd.parts[..], scope, false);
|
|
error = error.or(err);
|
|
return (Some(ClassifiedCommand::Expr(Box::new(expr))), error);
|
|
} else if lite_cmd.parts[0].item == "alias" {
|
|
let error = parse_alias(&lite_cmd, scope);
|
|
if error.is_none() {
|
|
return (None, None);
|
|
} else {
|
|
return (
|
|
Some(ClassifiedCommand::Expr(Box::new(garbage(lite_cmd.span())))),
|
|
error,
|
|
);
|
|
}
|
|
} else if lite_cmd.parts[0].item == "source" {
|
|
if lite_cmd.parts.len() != 2 {
|
|
return (
|
|
None,
|
|
Some(ParseError::argument_error(
|
|
lite_cmd.parts[0].clone(),
|
|
ArgumentError::MissingMandatoryPositional("a path for sourcing".into()),
|
|
)),
|
|
);
|
|
}
|
|
if lite_cmd.parts[1].item.starts_with('$') {
|
|
return (
|
|
None,
|
|
Some(ParseError::mismatch(
|
|
"a filepath constant",
|
|
lite_cmd.parts[1].clone(),
|
|
)),
|
|
);
|
|
}
|
|
if let Ok(contents) =
|
|
std::fs::read_to_string(expand_path(&lite_cmd.parts[1].item).into_owned())
|
|
{
|
|
let _ = parse(&contents, 0, scope);
|
|
} else {
|
|
return (
|
|
None,
|
|
Some(ParseError::mismatch(
|
|
"a filepath to a source file",
|
|
lite_cmd.parts[1].clone(),
|
|
)),
|
|
);
|
|
}
|
|
} else if lite_cmd.parts.len() > 1 {
|
|
// Check if it's a sub-command
|
|
if let Some(signature) = scope.get_signature(&format!(
|
|
"{} {}",
|
|
lite_cmd.parts[0].item, lite_cmd.parts[1].item
|
|
)) {
|
|
let (mut internal_command, err) =
|
|
parse_internal_command(&lite_cmd, scope, &signature, 1);
|
|
|
|
error = error.or(err);
|
|
internal_command.args.external_redirection = if end_of_pipeline {
|
|
ExternalRedirection::None
|
|
} else {
|
|
ExternalRedirection::Stdout
|
|
};
|
|
return (Some(ClassifiedCommand::Internal(internal_command)), error);
|
|
}
|
|
}
|
|
// Check if it's an internal command
|
|
if let Some(signature) = scope.get_signature(&lite_cmd.parts[0].item) {
|
|
if lite_cmd.parts[0].item == "def" {
|
|
let error = parse_definition(&lite_cmd, scope);
|
|
if error.is_some() {
|
|
return (
|
|
Some(ClassifiedCommand::Expr(Box::new(garbage(lite_cmd.span())))),
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
let (mut internal_command, err) = parse_internal_command(&lite_cmd, scope, &signature, 0);
|
|
|
|
error = error.or(err);
|
|
internal_command.args.external_redirection = if end_of_pipeline {
|
|
ExternalRedirection::None
|
|
} else {
|
|
ExternalRedirection::Stdout
|
|
};
|
|
(Some(ClassifiedCommand::Internal(internal_command)), error)
|
|
} else {
|
|
parse_external_call(&lite_cmd, end_of_pipeline, scope)
|
|
}
|
|
}
|
|
|
|
/// Convert a lite-ly parsed pipeline into a fully classified pipeline, ready to be evaluated.
|
|
/// This conversion does error-recovery, so the result is allowed to be lossy. A lossy unit is designated as garbage.
|
|
/// Errors are returned as part of a side-car error rather than a Result to allow both error and lossy result simultaneously.
|
|
fn parse_pipeline(
|
|
lite_pipeline: LitePipeline,
|
|
scope: &dyn ParserScope,
|
|
) -> (Pipeline, Option<ParseError>) {
|
|
let mut commands = Pipeline::new(lite_pipeline.span());
|
|
let mut error = None;
|
|
|
|
let mut iter = lite_pipeline.commands.into_iter().peekable();
|
|
while let Some(lite_cmd) = iter.next() {
|
|
let (call, err) = parse_call(lite_cmd, iter.peek().is_none(), scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
if let Some(call) = call {
|
|
commands.push(call);
|
|
}
|
|
}
|
|
|
|
(commands, error)
|
|
}
|
|
|
|
type SpannedKeyValue = (Spanned<String>, Spanned<String>);
|
|
|
|
fn expand_shorthand_forms(
|
|
lite_pipeline: &LitePipeline,
|
|
) -> (LitePipeline, Option<SpannedKeyValue>, Option<ParseError>) {
|
|
if !lite_pipeline.commands.is_empty() {
|
|
if lite_pipeline.commands[0].parts[0].contains('=')
|
|
&& !lite_pipeline.commands[0].parts[0].starts_with('$')
|
|
{
|
|
let assignment: Vec<_> = lite_pipeline.commands[0].parts[0].split('=').collect();
|
|
if assignment.len() != 2 {
|
|
(
|
|
lite_pipeline.clone(),
|
|
None,
|
|
Some(ParseError::mismatch(
|
|
"environment variable assignment",
|
|
lite_pipeline.commands[0].parts[0].clone(),
|
|
)),
|
|
)
|
|
} else {
|
|
let original_span = lite_pipeline.commands[0].parts[0].span;
|
|
let env_value = trim_quotes(assignment[1]);
|
|
|
|
let (variable_name, value) = (assignment[0], env_value);
|
|
let mut lite_pipeline = lite_pipeline.clone();
|
|
|
|
if !lite_pipeline.commands[0].parts.len() > 1 {
|
|
let mut new_lite_command_parts = lite_pipeline.commands[0].parts.clone();
|
|
new_lite_command_parts.remove(0);
|
|
|
|
lite_pipeline.commands[0].parts = new_lite_command_parts;
|
|
|
|
(
|
|
lite_pipeline,
|
|
Some((
|
|
variable_name.to_string().spanned(original_span),
|
|
value.spanned(original_span),
|
|
)),
|
|
None,
|
|
)
|
|
} else {
|
|
(
|
|
lite_pipeline.clone(),
|
|
None,
|
|
Some(ParseError::mismatch(
|
|
"a command following variable",
|
|
lite_pipeline.commands[0].parts[0].clone(),
|
|
)),
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
(lite_pipeline.clone(), None, None)
|
|
}
|
|
} else {
|
|
(lite_pipeline.clone(), None, None)
|
|
}
|
|
}
|
|
|
|
fn parse_alias(call: &LiteCommand, scope: &dyn ParserScope) -> Option<ParseError> {
|
|
if call.parts.len() < 4 {
|
|
return Some(ParseError::mismatch("alias", call.parts[0].clone()));
|
|
}
|
|
|
|
if call.parts[0].item != "alias" {
|
|
return Some(ParseError::mismatch("alias", call.parts[0].clone()));
|
|
}
|
|
|
|
if call.parts[2].item != "=" {
|
|
return Some(ParseError::mismatch("=", call.parts[2].clone()));
|
|
}
|
|
|
|
let name = call.parts[1].item.clone();
|
|
let args: Vec<_> = call.parts.iter().skip(3).cloned().collect();
|
|
|
|
scope.add_alias(&name, args);
|
|
|
|
None
|
|
}
|
|
|
|
pub fn classify_block(
|
|
lite_block: &LiteBlock,
|
|
scope: &dyn ParserScope,
|
|
) -> (Arc<Block>, Option<ParseError>) {
|
|
let mut output = Block::basic();
|
|
let mut error = None;
|
|
|
|
// Check for custom commands first
|
|
for group in lite_block.block.iter() {
|
|
for pipeline in &group.pipelines {
|
|
for call in &pipeline.commands {
|
|
if let Some(first) = call.parts.first() {
|
|
if first.item == "def" {
|
|
if pipeline.commands.len() > 1 && error.is_none() {
|
|
error = Some(ParseError::mismatch("definition", first.clone()));
|
|
}
|
|
parse_definition_prototype(call, scope);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then the rest of the code
|
|
for group in &lite_block.block {
|
|
let mut out_group = Group::basic();
|
|
for pipeline in &group.pipelines {
|
|
let (pipeline, vars, err) = expand_shorthand_forms(pipeline);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let (out_pipe, err) = parse_pipeline(pipeline.clone(), scope);
|
|
if error.is_none() {
|
|
error = err;
|
|
}
|
|
|
|
let pipeline = if let Some(vars) = vars {
|
|
let span = pipeline.span();
|
|
let block = hir::Block::new(
|
|
Signature::new("<block>"),
|
|
vec![Group::new(vec![out_pipe.clone()], span)],
|
|
IndexMap::new(),
|
|
span,
|
|
);
|
|
let mut call = hir::Call::new(
|
|
Box::new(SpannedExpression {
|
|
expr: Expression::string("with-env".to_string()),
|
|
span,
|
|
}),
|
|
span,
|
|
);
|
|
call.positional = Some(vec![
|
|
SpannedExpression {
|
|
expr: Expression::List(vec![
|
|
SpannedExpression {
|
|
expr: Expression::string(vars.0.item),
|
|
span: vars.0.span,
|
|
},
|
|
SpannedExpression {
|
|
expr: Expression::string(vars.1.item),
|
|
span: vars.1.span,
|
|
},
|
|
]),
|
|
span: Span::new(vars.0.span.start(), vars.1.span.end()),
|
|
},
|
|
SpannedExpression {
|
|
expr: Expression::Block(Arc::new(block)),
|
|
span,
|
|
},
|
|
]);
|
|
let classified_with_env = ClassifiedCommand::Internal(InternalCommand {
|
|
name: "with-env".to_string(),
|
|
name_span: Span::unknown(),
|
|
args: call,
|
|
});
|
|
|
|
Pipeline {
|
|
list: vec![classified_with_env],
|
|
span,
|
|
}
|
|
} else {
|
|
out_pipe
|
|
};
|
|
|
|
if !pipeline.list.is_empty() {
|
|
out_group.push(pipeline);
|
|
}
|
|
}
|
|
if !out_group.pipelines.is_empty() {
|
|
output.push(out_group);
|
|
}
|
|
}
|
|
|
|
let definitions = scope.get_definitions();
|
|
for definition in definitions.into_iter() {
|
|
let name = definition.params.name.clone();
|
|
if !output.definitions.contains_key(&name) {
|
|
output.definitions.insert(name, definition.clone());
|
|
}
|
|
}
|
|
output.infer_params();
|
|
|
|
(Arc::new(output), error)
|
|
}
|
|
|
|
pub fn parse(
|
|
input: &str,
|
|
span_offset: usize,
|
|
scope: &dyn ParserScope,
|
|
) -> (Arc<Block>, Option<ParseError>) {
|
|
let (output, error) = lex(input, span_offset);
|
|
if error.is_some() {
|
|
return (Arc::new(Block::basic()), error);
|
|
}
|
|
let (lite_block, error) = parse_block(output);
|
|
if error.is_some() {
|
|
return (Arc::new(Block::basic()), error);
|
|
}
|
|
|
|
classify_block(&lite_block, scope)
|
|
}
|
|
|
|
#[test]
|
|
fn unit_parse_byte_units() {
|
|
struct TestCase {
|
|
string: String,
|
|
value: i64,
|
|
unit: Unit,
|
|
}
|
|
|
|
let cases = [
|
|
TestCase {
|
|
string: String::from("108b"),
|
|
value: 108,
|
|
unit: Unit::Byte,
|
|
},
|
|
TestCase {
|
|
string: String::from("0B"),
|
|
value: 0,
|
|
unit: Unit::Byte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10kb"),
|
|
value: 10,
|
|
unit: Unit::Kilobyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("16KB"),
|
|
value: 16,
|
|
unit: Unit::Kilobyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("99kB"),
|
|
value: 99,
|
|
unit: Unit::Kilobyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("27Kb"),
|
|
value: 27,
|
|
unit: Unit::Kilobyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("11Mb"),
|
|
value: 11,
|
|
unit: Unit::Megabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("27mB"),
|
|
value: 27,
|
|
unit: Unit::Megabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("811Gb"),
|
|
value: 811,
|
|
unit: Unit::Gigabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("27gB"),
|
|
value: 27,
|
|
unit: Unit::Gigabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("11Tb"),
|
|
value: 11,
|
|
unit: Unit::Terabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("1027tB"),
|
|
value: 1027,
|
|
unit: Unit::Terabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("11Pb"),
|
|
value: 11,
|
|
unit: Unit::Petabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("27pB"),
|
|
value: 27,
|
|
unit: Unit::Petabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10kib"),
|
|
value: 10,
|
|
unit: Unit::Kibibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("123KiB"),
|
|
value: 123,
|
|
unit: Unit::Kibibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("24kiB"),
|
|
value: 24,
|
|
unit: Unit::Kibibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10mib"),
|
|
value: 10,
|
|
unit: Unit::Mebibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("123MiB"),
|
|
value: 123,
|
|
unit: Unit::Mebibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10gib"),
|
|
value: 10,
|
|
unit: Unit::Gibibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("123GiB"),
|
|
value: 123,
|
|
unit: Unit::Gibibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10tib"),
|
|
value: 10,
|
|
unit: Unit::Tebibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("123TiB"),
|
|
value: 123,
|
|
unit: Unit::Tebibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10pib"),
|
|
value: 10,
|
|
unit: Unit::Pebibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("123PiB"),
|
|
value: 123,
|
|
unit: Unit::Pebibyte,
|
|
},
|
|
];
|
|
|
|
for case in cases.iter() {
|
|
let input_len = case.string.len();
|
|
let value_len = case.value.to_string().len();
|
|
let input = case.string.clone().spanned(Span::new(0, input_len));
|
|
let result = parse_filesize(&input);
|
|
assert_eq!(result.1, None);
|
|
assert_eq!(
|
|
result.0.expr,
|
|
Expression::unit(
|
|
Spanned {
|
|
span: Span::new(0, value_len),
|
|
item: case.value
|
|
},
|
|
Spanned {
|
|
span: Span::new(value_len, input_len),
|
|
item: case.unit
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unit_parse_byte_units_decimal() {
|
|
struct TestCase {
|
|
string: String,
|
|
value: i64,
|
|
value_str: String,
|
|
unit: Unit,
|
|
}
|
|
|
|
let cases = [
|
|
TestCase {
|
|
string: String::from("0.25KB"),
|
|
value: 250,
|
|
value_str: String::from("0.25"),
|
|
unit: Unit::Byte,
|
|
},
|
|
TestCase {
|
|
string: String::from("2.5Mb"),
|
|
value: 2500,
|
|
value_str: String::from("2.5"),
|
|
unit: Unit::Kilobyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("0.5Gb"),
|
|
value: 500,
|
|
value_str: String::from("0.5"),
|
|
unit: Unit::Megabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("811.5Gb"),
|
|
value: 811500,
|
|
value_str: String::from("811.5"),
|
|
unit: Unit::Megabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("11.5Tb"),
|
|
value: 11500,
|
|
value_str: String::from("11.5"),
|
|
unit: Unit::Gigabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("12.5Pb"),
|
|
value: 12500,
|
|
value_str: String::from("12.5"),
|
|
unit: Unit::Terabyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("10.5kib"),
|
|
value: 10752,
|
|
value_str: String::from("10.5"),
|
|
unit: Unit::Byte,
|
|
},
|
|
TestCase {
|
|
string: String::from("0.5mib"),
|
|
value: 512,
|
|
value_str: String::from("0.5"),
|
|
unit: Unit::Kibibyte,
|
|
},
|
|
TestCase {
|
|
string: String::from("3.25gib"),
|
|
value: 3328,
|
|
value_str: String::from("3.25"),
|
|
unit: Unit::Mebibyte,
|
|
},
|
|
];
|
|
|
|
for case in cases.iter() {
|
|
let input_len = case.string.len();
|
|
let value_len = case.value_str.to_string().len();
|
|
let input = case.string.clone().spanned(Span::new(0, input_len));
|
|
let result = parse_filesize(&input);
|
|
assert_eq!(result.1, None);
|
|
assert_eq!(
|
|
result.0.expr,
|
|
Expression::unit(
|
|
Spanned {
|
|
span: Span::new(0, value_len),
|
|
item: case.value
|
|
},
|
|
Spanned {
|
|
span: Span::new(value_len, input_len),
|
|
item: case.unit
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|