2021-09-02 10:25:22 +02:00
|
|
|
use crate::{
|
2022-12-21 23:21:03 +01:00
|
|
|
eval::{eval_constant, value_as_string},
|
2022-12-22 12:41:44 +01:00
|
|
|
lex,
|
|
|
|
lite_parser::{lite_parse, LiteCommand, LiteElement},
|
|
|
|
parse_mut,
|
2021-09-02 10:25:22 +02:00
|
|
|
type_check::{math_result_type, type_compatible},
|
2022-11-18 22:46:48 +01:00
|
|
|
ParseError, Token, TokenContents,
|
2021-08-17 01:00:00 +02:00
|
|
|
};
|
2021-07-02 03:42:25 +02:00
|
|
|
|
2021-09-02 03:29:43 +02:00
|
|
|
use nu_protocol::{
|
2021-09-04 23:52:57 +02:00
|
|
|
ast::{
|
2022-11-11 07:51:08 +01:00
|
|
|
Argument, Assignment, Bits, Block, Boolean, Call, CellPath, Comparison, Expr, Expression,
|
|
|
|
FullCellPath, ImportPattern, ImportPatternHead, ImportPatternMember, Math, Operator,
|
2022-12-22 12:41:44 +01:00
|
|
|
PathMember, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
|
2021-09-04 23:52:57 +02:00
|
|
|
},
|
2021-09-02 20:21:37 +02:00
|
|
|
engine::StateWorkingSet,
|
2022-02-11 00:15:15 +01:00
|
|
|
span, BlockId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId,
|
2022-04-19 00:28:01 +02:00
|
|
|
ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
2021-09-02 03:29:43 +02:00
|
|
|
};
|
2021-07-23 23:19:30 +02:00
|
|
|
|
2021-09-26 20:39:19 +02:00
|
|
|
use crate::parse_keywords::{
|
2022-08-23 09:45:17 +02:00
|
|
|
parse_alias, parse_def, parse_def_predecl, parse_export_in_block, parse_extern, parse_for,
|
2022-12-21 23:21:03 +01:00
|
|
|
parse_hide, parse_let_or_const, parse_module, parse_overlay, parse_source, parse_use,
|
|
|
|
parse_where, parse_where_expr,
|
2021-09-02 03:29:43 +02:00
|
|
|
};
|
2021-07-23 23:19:30 +02:00
|
|
|
|
2022-06-17 20:11:48 +02:00
|
|
|
use itertools::Itertools;
|
2022-01-01 22:42:50 +01:00
|
|
|
use log::trace;
|
2022-03-01 00:31:53 +01:00
|
|
|
use std::{
|
|
|
|
collections::{HashMap, HashSet},
|
|
|
|
num::ParseIntError,
|
|
|
|
};
|
2021-11-30 07:14:05 +01:00
|
|
|
|
2021-11-02 21:56:00 +01:00
|
|
|
#[cfg(feature = "plugin")]
|
2021-12-03 15:29:55 +01:00
|
|
|
use crate::parse_keywords::parse_register;
|
2021-11-02 21:56:00 +01:00
|
|
|
|
2021-09-26 20:39:19 +02:00
|
|
|
pub fn garbage(span: Span) -> Expression {
|
2021-07-01 02:01:04 +02:00
|
|
|
Expression::garbage(span)
|
|
|
|
}
|
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
pub fn garbage_pipeline(spans: &[Span]) -> Pipeline {
|
|
|
|
Pipeline::from_vec(vec![garbage(span(spans))])
|
2021-09-10 09:28:43 +02:00
|
|
|
}
|
|
|
|
|
2021-07-01 03:31:02 +02:00
|
|
|
fn is_identifier_byte(b: u8) -> bool {
|
2022-07-27 04:08:54 +02:00
|
|
|
b != b'.'
|
|
|
|
&& b != b'['
|
|
|
|
&& b != b'('
|
|
|
|
&& b != b'{'
|
|
|
|
&& b != b'+'
|
|
|
|
&& b != b'-'
|
|
|
|
&& b != b'*'
|
|
|
|
&& b != b'^'
|
|
|
|
&& b != b'/'
|
|
|
|
&& b != b'='
|
|
|
|
&& b != b'!'
|
|
|
|
&& b != b'<'
|
|
|
|
&& b != b'>'
|
|
|
|
&& b != b'&'
|
|
|
|
&& b != b'|'
|
2021-07-01 03:31:02 +02:00
|
|
|
}
|
|
|
|
|
2022-04-07 04:01:31 +02:00
|
|
|
pub fn is_math_expression_like(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> bool {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-02-22 16:55:28 +01:00
|
|
|
if bytes.is_empty() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-06 21:10:25 +02:00
|
|
|
if bytes == b"true" || bytes == b"false" || bytes == b"null" || bytes == b"not" {
|
2022-03-03 01:55:03 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-22 16:55:28 +01:00
|
|
|
let b = bytes[0];
|
|
|
|
|
2022-04-07 04:01:31 +02:00
|
|
|
if b == b'('
|
2021-10-27 23:52:59 +02:00
|
|
|
|| b == b'{'
|
|
|
|
|| b == b'['
|
|
|
|
|| b == b'$'
|
|
|
|
|| b == b'"'
|
|
|
|
|| b == b'\''
|
2022-04-04 22:42:26 +02:00
|
|
|
|| b == b'`'
|
2021-10-27 23:52:59 +02:00
|
|
|
|| b == b'-'
|
2022-04-07 04:01:31 +02:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if parse_number(bytes, span).1.is_none() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if parse_filesize(working_set, span).1.is_none() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if parse_duration(working_set, span).1.is_none() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-07 08:02:28 +02:00
|
|
|
if parse_datetime(working_set, span).1.is_none() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-04-07 04:01:31 +02:00
|
|
|
if parse_binary(working_set, span).1.is_none() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if parse_range(working_set, span, expand_aliases_denylist)
|
|
|
|
.1
|
|
|
|
.is_none()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
2021-10-27 23:52:59 +02:00
|
|
|
}
|
|
|
|
|
2021-07-01 03:31:02 +02:00
|
|
|
fn is_identifier(bytes: &[u8]) -> bool {
|
|
|
|
bytes.iter().all(|x| is_identifier_byte(*x))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_variable(bytes: &[u8]) -> bool {
|
|
|
|
if bytes.len() > 1 && bytes[0] == b'$' {
|
|
|
|
is_identifier(&bytes[1..])
|
|
|
|
} else {
|
|
|
|
is_identifier(bytes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-14 20:40:26 +01:00
|
|
|
pub fn trim_quotes(bytes: &[u8]) -> &[u8] {
|
|
|
|
if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|
|
|
|
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
|
2022-04-04 22:42:26 +02:00
|
|
|
|| (bytes.starts_with(b"`") && bytes.ends_with(b"`") && bytes.len() > 1)
|
2021-11-14 20:40:26 +01:00
|
|
|
{
|
|
|
|
&bytes[1..(bytes.len() - 1)]
|
|
|
|
} else {
|
|
|
|
bytes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 02:37:38 +02:00
|
|
|
pub fn trim_quotes_str(s: &str) -> &str {
|
|
|
|
if (s.starts_with('"') && s.ends_with('"') && s.len() > 1)
|
|
|
|
|| (s.starts_with('\'') && s.ends_with('\'') && s.len() > 1)
|
|
|
|
|| (s.starts_with('`') && s.ends_with('`') && s.len() > 1)
|
|
|
|
{
|
|
|
|
&s[1..(s.len() - 1)]
|
|
|
|
} else {
|
|
|
|
s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 12:50:35 +01:00
|
|
|
pub fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError> {
|
2021-10-13 19:53:27 +02:00
|
|
|
// Allow the call to pass if they pass in the help flag
|
2022-04-09 07:17:48 +02:00
|
|
|
if call.named_iter().any(|(n, _, _)| n.item == "help") {
|
2021-10-13 19:53:27 +02:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
if call.positional_len() < sig.required_positional.len() {
|
2021-12-27 20:13:52 +01:00
|
|
|
// Comparing the types of all signature positional arguments against the parsed
|
|
|
|
// expressions found in the call. If one type is not found then it could be assumed
|
|
|
|
// that that positional argument is missing from the parsed call
|
|
|
|
for argument in &sig.required_positional {
|
2022-04-09 04:55:02 +02:00
|
|
|
let found = call.positional_iter().fold(false, |ac, expr| {
|
2022-02-14 18:33:47 +01:00
|
|
|
if argument.shape.to_type() == expr.ty || argument.shape == SyntaxShape::Any {
|
2021-12-27 20:13:52 +01:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
ac
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if !found {
|
2022-04-09 04:55:02 +02:00
|
|
|
if let Some(last) = call.positional_iter().last() {
|
2022-01-04 00:14:33 +01:00
|
|
|
return Some(ParseError::MissingPositional(
|
|
|
|
argument.name.clone(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(last.span.end, last.span.end),
|
2022-01-04 00:14:33 +01:00
|
|
|
sig.call_signature(),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
return Some(ParseError::MissingPositional(
|
|
|
|
argument.name.clone(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(command.end, command.end),
|
2022-01-04 00:14:33 +01:00
|
|
|
sig.call_signature(),
|
|
|
|
));
|
|
|
|
}
|
2021-12-27 20:13:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
let missing = &sig.required_positional[call.positional_len()];
|
|
|
|
if let Some(last) = call.positional_iter().last() {
|
2022-01-04 00:14:33 +01:00
|
|
|
Some(ParseError::MissingPositional(
|
|
|
|
missing.name.clone(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(last.span.end, last.span.end),
|
2022-01-04 00:14:33 +01:00
|
|
|
sig.call_signature(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Some(ParseError::MissingPositional(
|
|
|
|
missing.name.clone(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(command.end, command.end),
|
2022-01-04 00:14:33 +01:00
|
|
|
sig.call_signature(),
|
|
|
|
))
|
|
|
|
}
|
2021-07-02 04:22:54 +02:00
|
|
|
} else {
|
|
|
|
for req_flag in sig.named.iter().filter(|x| x.required) {
|
2022-04-09 07:17:48 +02:00
|
|
|
if call.named_iter().all(|(n, _, _)| n.item != req_flag.long) {
|
2021-07-02 04:22:54 +02:00
|
|
|
return Some(ParseError::MissingRequiredFlag(
|
|
|
|
req_flag.long.clone(),
|
|
|
|
command,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-26 20:39:19 +02:00
|
|
|
pub fn check_name<'a>(
|
2021-09-02 10:25:22 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
2021-09-13 21:59:11 +02:00
|
|
|
spans: &'a [Span],
|
|
|
|
) -> Option<(&'a Span, ParseError)> {
|
2022-08-22 23:19:47 +02:00
|
|
|
let command_len = if !spans.is_empty() {
|
|
|
|
if working_set.get_span_contents(spans[0]) == b"export" {
|
|
|
|
2
|
|
|
|
} else {
|
|
|
|
1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
|
2021-09-13 21:59:11 +02:00
|
|
|
if spans.len() == 1 {
|
|
|
|
None
|
2022-08-22 23:19:47 +02:00
|
|
|
} else if spans.len() < command_len + 3 {
|
|
|
|
if working_set.get_span_contents(spans[command_len]) == b"=" {
|
|
|
|
let name =
|
|
|
|
String::from_utf8_lossy(working_set.get_span_contents(span(&spans[..command_len])));
|
2021-09-13 21:59:11 +02:00
|
|
|
Some((
|
2022-08-22 23:19:47 +02:00
|
|
|
&spans[command_len],
|
2021-09-13 21:59:11 +02:00
|
|
|
ParseError::AssignmentMismatch(
|
2023-01-30 02:37:54 +01:00
|
|
|
format!("{name} missing name"),
|
2021-09-13 21:59:11 +02:00
|
|
|
"missing name".into(),
|
2022-08-22 23:19:47 +02:00
|
|
|
spans[command_len],
|
2021-09-13 21:59:11 +02:00
|
|
|
),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2022-08-22 23:19:47 +02:00
|
|
|
} else if working_set.get_span_contents(spans[command_len + 1]) != b"=" {
|
|
|
|
let name =
|
|
|
|
String::from_utf8_lossy(working_set.get_span_contents(span(&spans[..command_len])));
|
2021-09-13 21:59:11 +02:00
|
|
|
Some((
|
2022-08-22 23:19:47 +02:00
|
|
|
&spans[command_len + 1],
|
2021-09-13 21:59:11 +02:00
|
|
|
ParseError::AssignmentMismatch(
|
2023-01-30 02:37:54 +01:00
|
|
|
format!("{name} missing sign"),
|
2021-09-13 21:59:11 +02:00
|
|
|
"missing equal sign".into(),
|
2022-08-22 23:19:47 +02:00
|
|
|
spans[command_len + 1],
|
2021-09-13 21:59:11 +02:00
|
|
|
),
|
2021-09-10 09:28:43 +02:00
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_external_call(
|
2021-10-08 23:51:47 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
2021-09-02 10:25:22 +02:00
|
|
|
spans: &[Span],
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression: bool,
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-03-21 23:57:48 +01:00
|
|
|
trace!("parse external");
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut args = vec![];
|
2022-01-13 09:17:45 +01:00
|
|
|
|
|
|
|
let head_contents = working_set.get_span_contents(spans[0]);
|
|
|
|
|
|
|
|
let head_span = if head_contents.starts_with(b"^") {
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(spans[0].start + 1, spans[0].end)
|
2022-01-06 11:20:31 +01:00
|
|
|
} else {
|
2022-01-13 09:17:45 +01:00
|
|
|
spans[0]
|
2022-01-06 11:20:31 +01:00
|
|
|
};
|
|
|
|
|
2022-03-03 20:05:55 +01:00
|
|
|
let head_contents = working_set.get_span_contents(head_span).to_vec();
|
|
|
|
|
2021-10-08 23:51:47 +02:00
|
|
|
let mut error = None;
|
|
|
|
|
2022-01-13 09:17:45 +01:00
|
|
|
let head = if head_contents.starts_with(b"$") || head_contents.starts_with(b"(") {
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
// the expression is inside external_call, so it's a subexpression
|
|
|
|
let (arg, err) = parse_expression(working_set, &[head_span], expand_aliases_denylist, true);
|
2022-01-13 09:17:45 +01:00
|
|
|
error = error.or(err);
|
|
|
|
Box::new(arg)
|
|
|
|
} else {
|
2022-05-01 21:26:29 +02:00
|
|
|
let (contents, err) = unescape_unquote_string(&head_contents, head_span);
|
|
|
|
error = error.or(err);
|
|
|
|
|
2022-01-13 09:17:45 +01:00
|
|
|
Box::new(Expression {
|
2022-05-01 21:26:29 +02:00
|
|
|
expr: Expr::String(contents),
|
2022-01-13 09:17:45 +01:00
|
|
|
span: head_span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
for span in &spans[1..] {
|
2021-10-08 23:51:47 +02:00
|
|
|
let contents = working_set.get_span_contents(*span);
|
|
|
|
|
|
|
|
if contents.starts_with(b"$") || contents.starts_with(b"(") {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (arg, err) = parse_dollar_expr(working_set, *span, expand_aliases_denylist);
|
2022-03-18 07:59:28 +01:00
|
|
|
error = error.or(err);
|
|
|
|
args.push(arg);
|
2022-06-06 12:19:06 +02:00
|
|
|
} else if contents.starts_with(b"[") {
|
|
|
|
let (arg, err) = parse_list_expression(
|
|
|
|
working_set,
|
|
|
|
*span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-10-08 23:51:47 +02:00
|
|
|
error = error.or(err);
|
|
|
|
args.push(arg);
|
|
|
|
} else {
|
2022-07-17 23:30:33 +02:00
|
|
|
// Eval stage trims the quotes, so we don't have to do the same thing when parsing.
|
2022-09-17 13:07:45 +02:00
|
|
|
let contents = if contents.starts_with(b"\"") {
|
|
|
|
let (contents, err) = unescape_string(contents, *span);
|
|
|
|
error = error.or(err);
|
|
|
|
String::from_utf8_lossy(&contents).to_string()
|
|
|
|
} else {
|
|
|
|
String::from_utf8_lossy(contents).to_string()
|
|
|
|
};
|
2022-05-01 21:26:29 +02:00
|
|
|
|
2021-10-08 23:51:47 +02:00
|
|
|
args.push(Expression {
|
2022-05-01 21:26:29 +02:00
|
|
|
expr: Expr::String(contents),
|
2021-10-08 23:51:47 +02:00
|
|
|
span: *span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
2022-12-03 10:44:12 +01:00
|
|
|
});
|
2021-10-08 23:51:47 +02:00
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
expr: Expr::ExternalCall(head, args, is_subexpression),
|
2021-09-02 10:25:22 +02:00
|
|
|
span: span(spans),
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
2021-10-08 23:51:47 +02:00
|
|
|
error,
|
2021-09-02 10:25:22 +02:00
|
|
|
)
|
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
fn parse_long_flag(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
spans_idx: &mut usize,
|
|
|
|
sig: &Signature,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-01-27 02:20:12 +01:00
|
|
|
) -> (
|
|
|
|
Option<Spanned<String>>,
|
|
|
|
Option<Expression>,
|
|
|
|
Option<ParseError>,
|
|
|
|
) {
|
2021-09-02 10:25:22 +02:00
|
|
|
let arg_span = spans[*spans_idx];
|
|
|
|
let arg_contents = working_set.get_span_contents(arg_span);
|
|
|
|
|
|
|
|
if arg_contents.starts_with(b"--") {
|
2021-10-12 19:44:23 +02:00
|
|
|
// FIXME: only use the first flag you find?
|
2021-09-02 10:25:22 +02:00
|
|
|
let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect();
|
|
|
|
let long_name = String::from_utf8(split[0].into());
|
|
|
|
if let Ok(long_name) = long_name {
|
2021-10-13 19:53:27 +02:00
|
|
|
let long_name = long_name[2..].to_string();
|
2021-09-02 10:25:22 +02:00
|
|
|
if let Some(flag) = sig.get_long_flag(&long_name) {
|
|
|
|
if let Some(arg_shape) = &flag.arg {
|
|
|
|
if split.len() > 1 {
|
|
|
|
// and we also have the argument
|
2022-01-27 02:20:12 +01:00
|
|
|
let long_name_len = long_name.len();
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut span = arg_span;
|
2022-01-27 02:20:12 +01:00
|
|
|
span.start += long_name_len + 3; //offset by long flag and '='
|
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (arg, err) =
|
|
|
|
parse_value(working_set, span, arg_shape, expand_aliases_denylist);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-01-27 02:20:12 +01:00
|
|
|
(
|
|
|
|
Some(Spanned {
|
|
|
|
item: long_name,
|
2022-12-03 10:44:12 +01:00
|
|
|
span: Span::new(arg_span.start, arg_span.start + long_name_len + 2),
|
2022-01-27 02:20:12 +01:00
|
|
|
}),
|
|
|
|
Some(arg),
|
|
|
|
err,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else if let Some(arg) = spans.get(*spans_idx + 1) {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (arg, err) =
|
|
|
|
parse_value(working_set, *arg, arg_shape, expand_aliases_denylist);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
|
|
|
*spans_idx += 1;
|
2022-01-27 02:20:12 +01:00
|
|
|
(
|
|
|
|
Some(Spanned {
|
|
|
|
item: long_name,
|
|
|
|
span: arg_span,
|
|
|
|
}),
|
|
|
|
Some(arg),
|
|
|
|
err,
|
|
|
|
)
|
2021-07-08 22:29:00 +02:00
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
2022-01-27 02:20:12 +01:00
|
|
|
Some(Spanned {
|
|
|
|
item: long_name,
|
|
|
|
span: arg_span,
|
|
|
|
}),
|
2021-09-02 10:25:22 +02:00
|
|
|
None,
|
2022-01-04 00:14:33 +01:00
|
|
|
Some(ParseError::MissingFlagParam(
|
|
|
|
arg_shape.to_string(),
|
|
|
|
arg_span,
|
|
|
|
)),
|
2021-09-02 10:25:22 +02:00
|
|
|
)
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
// A flag with no argument
|
2022-01-27 02:20:12 +01:00
|
|
|
(
|
|
|
|
Some(Spanned {
|
|
|
|
item: long_name,
|
|
|
|
span: arg_span,
|
|
|
|
}),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
2022-01-27 02:20:12 +01:00
|
|
|
Some(Spanned {
|
|
|
|
item: long_name.clone(),
|
|
|
|
span: arg_span,
|
|
|
|
}),
|
2021-09-02 10:25:22 +02:00
|
|
|
None,
|
2021-09-21 06:03:06 +02:00
|
|
|
Some(ParseError::UnknownFlag(
|
|
|
|
sig.name.clone(),
|
|
|
|
long_name.clone(),
|
|
|
|
arg_span,
|
2022-12-13 13:45:33 +01:00
|
|
|
sig.clone().formatted_flags(),
|
2021-09-21 06:03:06 +02:00
|
|
|
)),
|
2021-09-02 10:25:22 +02:00
|
|
|
)
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
|
|
|
} else {
|
2022-01-27 02:20:12 +01:00
|
|
|
(
|
|
|
|
Some(Spanned {
|
|
|
|
item: "--".into(),
|
|
|
|
span: arg_span,
|
|
|
|
}),
|
|
|
|
None,
|
|
|
|
Some(ParseError::NonUtf8(arg_span)),
|
|
|
|
)
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
(None, None, None)
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-08 22:29:00 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
fn parse_short_flags(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
spans_idx: &mut usize,
|
|
|
|
positional_idx: usize,
|
|
|
|
sig: &Signature,
|
|
|
|
) -> (Option<Vec<Flag>>, Option<ParseError>) {
|
|
|
|
let mut error = None;
|
|
|
|
let arg_span = spans[*spans_idx];
|
|
|
|
|
|
|
|
let arg_contents = working_set.get_span_contents(arg_span);
|
|
|
|
|
|
|
|
if arg_contents.starts_with(b"-") && arg_contents.len() > 1 {
|
|
|
|
let short_flags = &arg_contents[1..];
|
|
|
|
let mut found_short_flags = vec![];
|
|
|
|
let mut unmatched_short_flags = vec![];
|
|
|
|
for short_flag in short_flags.iter().enumerate() {
|
|
|
|
let short_flag_char = char::from(*short_flag.1);
|
|
|
|
let orig = arg_span;
|
2022-12-03 10:44:12 +01:00
|
|
|
let short_flag_span = Span::new(
|
|
|
|
orig.start + 1 + short_flag.0,
|
|
|
|
orig.start + 1 + short_flag.0 + 1,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
if let Some(flag) = sig.get_short_flag(short_flag_char) {
|
|
|
|
// If we require an arg and are in a batch of short flags, error
|
|
|
|
if !found_short_flags.is_empty() && flag.arg.is_some() {
|
|
|
|
error = error.or(Some(ParseError::ShortFlagBatchCantTakeArg(short_flag_span)))
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
found_short_flags.push(flag);
|
|
|
|
} else {
|
|
|
|
unmatched_short_flags.push(short_flag_span);
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-08 22:29:00 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if found_short_flags.is_empty() {
|
|
|
|
// check to see if we have a negative number
|
|
|
|
if let Some(positional) = sig.get_positional(positional_idx) {
|
|
|
|
if positional.shape == SyntaxShape::Int || positional.shape == SyntaxShape::Number {
|
|
|
|
if String::from_utf8_lossy(arg_contents).parse::<f64>().is_ok() {
|
|
|
|
return (None, None);
|
2021-07-08 22:29:00 +02:00
|
|
|
} else if let Some(first) = unmatched_short_flags.first() {
|
2021-09-21 06:03:06 +02:00
|
|
|
let contents = working_set.get_span_contents(*first);
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::UnknownFlag(
|
|
|
|
sig.name.clone(),
|
2021-11-13 01:42:13 +01:00
|
|
|
format!("-{}", String::from_utf8_lossy(contents)),
|
2021-09-21 06:03:06 +02:00
|
|
|
*first,
|
2022-12-13 13:45:33 +01:00
|
|
|
sig.clone().formatted_flags(),
|
2021-09-21 06:03:06 +02:00
|
|
|
))
|
|
|
|
});
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
|
|
|
} else if let Some(first) = unmatched_short_flags.first() {
|
2021-09-21 06:03:06 +02:00
|
|
|
let contents = working_set.get_span_contents(*first);
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::UnknownFlag(
|
|
|
|
sig.name.clone(),
|
2021-11-13 01:42:13 +01:00
|
|
|
format!("-{}", String::from_utf8_lossy(contents)),
|
2021-09-21 06:03:06 +02:00
|
|
|
*first,
|
2022-12-13 13:45:33 +01:00
|
|
|
sig.clone().formatted_flags(),
|
2021-09-21 06:03:06 +02:00
|
|
|
))
|
|
|
|
});
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
} else if let Some(first) = unmatched_short_flags.first() {
|
2021-09-21 06:03:06 +02:00
|
|
|
let contents = working_set.get_span_contents(*first);
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::UnknownFlag(
|
|
|
|
sig.name.clone(),
|
2021-11-13 01:42:13 +01:00
|
|
|
format!("-{}", String::from_utf8_lossy(contents)),
|
2021-09-21 06:03:06 +02:00
|
|
|
*first,
|
2022-12-13 13:45:33 +01:00
|
|
|
sig.clone().formatted_flags(),
|
2021-09-21 06:03:06 +02:00
|
|
|
))
|
|
|
|
});
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
} else if !unmatched_short_flags.is_empty() {
|
|
|
|
if let Some(first) = unmatched_short_flags.first() {
|
2021-09-21 06:03:06 +02:00
|
|
|
let contents = working_set.get_span_contents(*first);
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::UnknownFlag(
|
|
|
|
sig.name.clone(),
|
2021-11-13 01:42:13 +01:00
|
|
|
format!("-{}", String::from_utf8_lossy(contents)),
|
2021-09-21 06:03:06 +02:00
|
|
|
*first,
|
2022-12-13 13:45:33 +01:00
|
|
|
sig.clone().formatted_flags(),
|
2021-09-21 06:03:06 +02:00
|
|
|
))
|
|
|
|
});
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
|
|
|
|
(Some(found_short_flags), error)
|
|
|
|
} else {
|
|
|
|
(None, None)
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-08 22:29:00 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
fn first_kw_idx(
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
signature: &Signature,
|
|
|
|
spans: &[Span],
|
|
|
|
spans_idx: usize,
|
|
|
|
positional_idx: usize,
|
|
|
|
) -> (Option<usize>, usize) {
|
|
|
|
for idx in (positional_idx + 1)..signature.num_positionals() {
|
|
|
|
if let Some(PositionalArg {
|
|
|
|
shape: SyntaxShape::Keyword(kw, ..),
|
|
|
|
..
|
|
|
|
}) = signature.get_positional(idx)
|
|
|
|
{
|
|
|
|
#[allow(clippy::needless_range_loop)]
|
|
|
|
for span_idx in spans_idx..spans.len() {
|
|
|
|
let contents = working_set.get_span_contents(spans[span_idx]);
|
2021-08-26 23:48:27 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if contents == kw {
|
|
|
|
return (Some(idx), span_idx);
|
2021-08-26 23:48:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
(None, spans.len())
|
|
|
|
}
|
2021-08-26 23:48:27 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
fn calculate_end_span(
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
signature: &Signature,
|
|
|
|
spans: &[Span],
|
|
|
|
spans_idx: usize,
|
|
|
|
positional_idx: usize,
|
|
|
|
) -> usize {
|
|
|
|
if signature.rest_positional.is_some() {
|
|
|
|
spans.len()
|
|
|
|
} else {
|
|
|
|
let (kw_pos, kw_idx) =
|
|
|
|
first_kw_idx(working_set, signature, spans, spans_idx, positional_idx);
|
2021-08-26 23:48:27 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if let Some(kw_pos) = kw_pos {
|
|
|
|
// We found a keyword. Keywords, once found, create a guidepost to
|
|
|
|
// show us where the positionals will lay into the arguments. Because they're
|
|
|
|
// keywords, they get to set this by being present
|
2021-08-26 23:48:27 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let positionals_between = kw_pos - positional_idx - 1;
|
|
|
|
if positionals_between > (kw_idx - spans_idx) {
|
|
|
|
kw_idx
|
2021-08-26 23:48:27 +02:00
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
kw_idx - positionals_between
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Make space for the remaining require positionals, if we can
|
2022-02-14 18:33:47 +01:00
|
|
|
if signature.num_positionals_after(positional_idx) == 0 {
|
|
|
|
spans.len()
|
|
|
|
} else if positional_idx < signature.required_positional.len()
|
2021-09-02 10:25:22 +02:00
|
|
|
&& spans.len() > (signature.required_positional.len() - positional_idx)
|
|
|
|
{
|
|
|
|
spans.len() - (signature.required_positional.len() - positional_idx - 1)
|
|
|
|
} else {
|
2021-09-04 09:59:38 +02:00
|
|
|
spans_idx + 1
|
2021-07-24 07:57:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-24 07:57:17 +02:00
|
|
|
|
2021-12-15 23:56:12 +01:00
|
|
|
pub fn parse_multispan_value(
|
2021-09-02 10:25:22 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
spans_idx: &mut usize,
|
|
|
|
shape: &SyntaxShape,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
match shape {
|
|
|
|
SyntaxShape::VarWithOptType => {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: var with opt type");
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx, false);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-08 23:16:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(arg, error)
|
|
|
|
}
|
|
|
|
SyntaxShape::RowCondition => {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: row condition");
|
2022-03-18 20:03:57 +01:00
|
|
|
let (arg, err) =
|
|
|
|
parse_row_condition(working_set, &spans[*spans_idx..], expand_aliases_denylist);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
*spans_idx = spans.len() - 1;
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(arg, error)
|
|
|
|
}
|
2022-01-15 16:26:52 +01:00
|
|
|
SyntaxShape::MathExpression => {
|
|
|
|
trace!("parsing: math expression");
|
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (arg, err) = parse_math_expression(
|
|
|
|
working_set,
|
|
|
|
&spans[*spans_idx..],
|
|
|
|
None,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-01-15 16:26:52 +01:00
|
|
|
error = error.or(err);
|
|
|
|
*spans_idx = spans.len() - 1;
|
|
|
|
|
|
|
|
(arg, error)
|
|
|
|
}
|
2022-12-07 20:58:54 +01:00
|
|
|
SyntaxShape::OneOf(shapes) => {
|
2022-12-09 14:48:12 +01:00
|
|
|
let mut err = None;
|
2022-12-07 20:58:54 +01:00
|
|
|
for shape in shapes.iter() {
|
2022-12-09 14:48:12 +01:00
|
|
|
let (s, option_err) = parse_multispan_value(
|
2022-12-07 20:58:54 +01:00
|
|
|
working_set,
|
|
|
|
spans,
|
|
|
|
spans_idx,
|
|
|
|
shape,
|
|
|
|
expand_aliases_denylist,
|
2022-12-09 14:48:12 +01:00
|
|
|
);
|
|
|
|
match option_err {
|
|
|
|
None => return (s, None),
|
|
|
|
e => err = err.or(e),
|
2022-12-07 20:58:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let span = spans[*spans_idx];
|
2022-12-09 14:48:12 +01:00
|
|
|
|
|
|
|
if err.is_some() {
|
|
|
|
(Expression::garbage(span), err)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected(
|
2023-01-30 02:37:54 +01:00
|
|
|
format!("one of a list of accepted shapes: {shapes:?}"),
|
2022-12-09 14:48:12 +01:00
|
|
|
span,
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
2022-12-07 20:58:54 +01:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
SyntaxShape::Expression => {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: expression");
|
|
|
|
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
// is it subexpression?
|
|
|
|
// Not sure, but let's make it not, so the behavior is the same as previous version of nushell.
|
|
|
|
let (arg, err) = parse_expression(
|
|
|
|
working_set,
|
|
|
|
&spans[*spans_idx..],
|
|
|
|
expand_aliases_denylist,
|
|
|
|
false,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
*spans_idx = spans.len() - 1;
|
2021-07-08 23:16:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(arg, error)
|
|
|
|
}
|
|
|
|
SyntaxShape::Keyword(keyword, arg) => {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!(
|
|
|
|
"parsing: keyword({}) {:?}",
|
|
|
|
String::from_utf8_lossy(keyword),
|
|
|
|
arg
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
let arg_span = spans[*spans_idx];
|
2021-07-08 23:16:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let arg_contents = working_set.get_span_contents(arg_span);
|
2021-07-17 07:28:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if arg_contents != keyword {
|
|
|
|
// When keywords mismatch, this is a strong indicator of something going wrong.
|
|
|
|
// We won't often override the current error, but as this is a strong indicator
|
|
|
|
// go ahead and override the current error and tell the user about the missing
|
|
|
|
// keyword/literal.
|
|
|
|
error = Some(ParseError::ExpectedKeyword(
|
|
|
|
String::from_utf8_lossy(keyword).into(),
|
|
|
|
arg_span,
|
|
|
|
))
|
|
|
|
}
|
2021-07-17 07:28:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
*spans_idx += 1;
|
|
|
|
if *spans_idx >= spans.len() {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::KeywordMissingArgument(
|
2022-01-04 00:14:33 +01:00
|
|
|
arg.to_string(),
|
2021-07-30 00:56:51 +02:00
|
|
|
String::from_utf8_lossy(keyword).into(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(spans[*spans_idx - 1].end, spans[*spans_idx - 1].end),
|
2021-07-08 23:16:25 +02:00
|
|
|
))
|
2021-09-02 10:25:22 +02:00
|
|
|
});
|
|
|
|
return (
|
2021-07-08 23:16:25 +02:00
|
|
|
Expression {
|
2021-09-02 10:25:22 +02:00
|
|
|
expr: Expr::Keyword(
|
|
|
|
keyword.clone(),
|
|
|
|
spans[*spans_idx - 1],
|
|
|
|
Box::new(Expression::garbage(arg_span)),
|
|
|
|
),
|
2021-07-08 23:16:25 +02:00
|
|
|
span: arg_span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-07-08 23:16:25 +02:00
|
|
|
},
|
|
|
|
error,
|
2021-09-02 10:25:22 +02:00
|
|
|
);
|
2021-07-08 23:16:25 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
let keyword_span = spans[*spans_idx - 1];
|
2022-03-18 20:03:57 +01:00
|
|
|
let (expr, err) =
|
|
|
|
parse_multispan_value(working_set, spans, spans_idx, arg, expand_aliases_denylist);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
let ty = expr.ty.clone();
|
2021-07-17 07:28:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Keyword(keyword.clone(), keyword_span, Box::new(expr)),
|
|
|
|
span: arg_span,
|
|
|
|
ty,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
// All other cases are single-span values
|
|
|
|
let arg_span = spans[*spans_idx];
|
2021-07-08 23:16:25 +02:00
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (arg, err) = parse_value(working_set, arg_span, shape, expand_aliases_denylist);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
(arg, error)
|
2021-07-08 23:16:25 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-08 23:16:25 +02:00
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
pub struct ParsedInternalCall {
|
|
|
|
pub call: Box<Call>,
|
|
|
|
pub output: Type,
|
|
|
|
pub error: Option<ParseError>,
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_internal_call(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
command_span: Span,
|
|
|
|
spans: &[Span],
|
|
|
|
decl_id: usize,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-06-12 21:18:00 +02:00
|
|
|
) -> ParsedInternalCall {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: internal call (decl id: {})", decl_id);
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut error = None;
|
2021-07-02 00:40:08 +02:00
|
|
|
|
2022-02-04 03:01:45 +01:00
|
|
|
let mut call = Call::new(command_span);
|
2021-09-02 10:25:22 +02:00
|
|
|
call.decl_id = decl_id;
|
|
|
|
call.head = command_span;
|
2021-07-08 08:19:38 +02:00
|
|
|
|
2022-06-10 17:59:35 +02:00
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
let signature = decl.signature();
|
2022-06-25 23:23:56 +02:00
|
|
|
let output = signature.output_type.clone();
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
working_set.type_scope.add_type(output.clone());
|
2021-07-08 08:19:38 +02:00
|
|
|
|
2021-10-09 18:10:46 +02:00
|
|
|
if signature.creates_scope {
|
|
|
|
working_set.enter_scope();
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// The index into the positional parameter in the definition
|
|
|
|
let mut positional_idx = 0;
|
2021-07-08 08:19:38 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// The index into the spans of argument data given to parse
|
|
|
|
// Starting at the first argument
|
|
|
|
let mut spans_idx = 0;
|
2021-07-02 00:40:08 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
while spans_idx < spans.len() {
|
|
|
|
let arg_span = spans[spans_idx];
|
2021-07-08 23:16:25 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// Check if we're on a long flag, if so, parse
|
2022-03-18 20:03:57 +01:00
|
|
|
let (long_name, arg, err) = parse_long_flag(
|
|
|
|
working_set,
|
|
|
|
spans,
|
|
|
|
&mut spans_idx,
|
|
|
|
&signature,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-12-21 23:33:26 +01:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if let Some(long_name) = long_name {
|
|
|
|
// We found a long flag, like --bar
|
2022-12-21 23:33:26 +01:00
|
|
|
if matches!(err, Some(ParseError::UnknownFlag(_, _, _, _)))
|
|
|
|
&& signature.allows_unknown_args
|
|
|
|
{
|
|
|
|
let (arg, arg_err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
arg_span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
error = error.or(arg_err);
|
|
|
|
call.add_unknown(arg);
|
|
|
|
} else {
|
|
|
|
error = error.or(err);
|
|
|
|
call.add_named((long_name, None, arg));
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
spans_idx += 1;
|
|
|
|
continue;
|
|
|
|
}
|
2021-07-02 00:40:08 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// Check if we're on a short flag or group of short flags, if so, parse
|
|
|
|
let (short_flags, err) = parse_short_flags(
|
|
|
|
working_set,
|
|
|
|
spans,
|
|
|
|
&mut spans_idx,
|
|
|
|
positional_idx,
|
|
|
|
&signature,
|
|
|
|
);
|
2021-07-08 22:29:00 +02:00
|
|
|
|
2022-07-17 14:46:40 +02:00
|
|
|
if let Some(mut short_flags) = short_flags {
|
|
|
|
if short_flags.is_empty() {
|
2022-12-21 23:33:26 +01:00
|
|
|
// workaround for completions (PR #6067)
|
2022-07-17 14:46:40 +02:00
|
|
|
short_flags.push(Flag {
|
|
|
|
long: "".to_string(),
|
|
|
|
short: Some('a'),
|
|
|
|
arg: None,
|
|
|
|
required: false,
|
|
|
|
desc: "".to_string(),
|
|
|
|
var_id: None,
|
|
|
|
default_value: None,
|
|
|
|
})
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-12-21 23:33:26 +01:00
|
|
|
if matches!(err, Some(ParseError::UnknownFlag(_, _, _, _)))
|
|
|
|
&& signature.allows_unknown_args
|
|
|
|
{
|
|
|
|
let (arg, arg_err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
arg_span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
call.add_unknown(arg);
|
|
|
|
error = error.or(arg_err);
|
|
|
|
} else {
|
|
|
|
error = error.or(err);
|
|
|
|
for flag in short_flags {
|
|
|
|
if let Some(arg_shape) = flag.arg {
|
|
|
|
if let Some(arg) = spans.get(spans_idx + 1) {
|
|
|
|
let (arg, err) =
|
|
|
|
parse_value(working_set, *arg, &arg_shape, expand_aliases_denylist);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
if flag.long.is_empty() {
|
|
|
|
if let Some(short) = flag.short {
|
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
|
|
|
item: String::new(),
|
|
|
|
span: spans[spans_idx],
|
|
|
|
},
|
|
|
|
Some(Spanned {
|
|
|
|
item: short.to_string(),
|
|
|
|
span: spans[spans_idx],
|
|
|
|
}),
|
|
|
|
Some(arg),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
} else {
|
2022-04-09 07:17:48 +02:00
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
2022-12-21 23:33:26 +01:00
|
|
|
item: flag.long.clone(),
|
2022-04-09 07:17:48 +02:00
|
|
|
span: spans[spans_idx],
|
|
|
|
},
|
2022-12-21 23:33:26 +01:00
|
|
|
None,
|
2022-04-09 07:17:48 +02:00
|
|
|
Some(arg),
|
|
|
|
));
|
|
|
|
}
|
2022-12-21 23:33:26 +01:00
|
|
|
spans_idx += 1;
|
2022-04-09 07:17:48 +02:00
|
|
|
} else {
|
2022-12-21 23:33:26 +01:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::MissingFlagParam(
|
|
|
|
arg_shape.to_string(),
|
|
|
|
arg_span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else if flag.long.is_empty() {
|
|
|
|
if let Some(short) = flag.short {
|
2022-04-09 07:17:48 +02:00
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
2022-12-21 23:33:26 +01:00
|
|
|
item: String::new(),
|
2022-04-09 07:17:48 +02:00
|
|
|
span: spans[spans_idx],
|
|
|
|
},
|
2022-12-21 23:33:26 +01:00
|
|
|
Some(Spanned {
|
|
|
|
item: short.to_string(),
|
|
|
|
span: spans[spans_idx],
|
|
|
|
}),
|
2022-04-09 07:17:48 +02:00
|
|
|
None,
|
|
|
|
));
|
|
|
|
}
|
2021-07-08 08:19:38 +02:00
|
|
|
} else {
|
2022-04-09 07:17:48 +02:00
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
2022-12-21 23:33:26 +01:00
|
|
|
item: flag.long.clone(),
|
2022-04-09 07:17:48 +02:00
|
|
|
span: spans[spans_idx],
|
|
|
|
},
|
2022-12-21 23:33:26 +01:00
|
|
|
None,
|
2022-04-09 07:17:48 +02:00
|
|
|
None,
|
|
|
|
));
|
|
|
|
}
|
2021-07-08 08:19:38 +02:00
|
|
|
}
|
2021-07-08 22:29:00 +02:00
|
|
|
}
|
2022-12-21 23:33:26 +01:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
spans_idx += 1;
|
|
|
|
continue;
|
|
|
|
}
|
2021-07-08 22:29:00 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// Parse a positional arg if there is one
|
|
|
|
if let Some(positional) = signature.get_positional(positional_idx) {
|
|
|
|
let end = calculate_end_span(working_set, &signature, spans, spans_idx, positional_idx);
|
2021-07-17 01:22:01 +02:00
|
|
|
|
2022-02-14 18:33:47 +01:00
|
|
|
let end = if spans.len() > spans_idx && end == spans_idx {
|
|
|
|
end + 1
|
|
|
|
} else {
|
|
|
|
end
|
|
|
|
};
|
|
|
|
|
|
|
|
if spans[..end].is_empty() || spans_idx == end {
|
2021-12-27 21:04:48 +01:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::MissingPositional(
|
|
|
|
positional.name.clone(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(spans[spans_idx].end, spans[spans_idx].end),
|
2022-01-04 00:14:33 +01:00
|
|
|
signature.call_signature(),
|
2021-12-27 21:04:48 +01:00
|
|
|
))
|
|
|
|
});
|
|
|
|
positional_idx += 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let orig_idx = spans_idx;
|
|
|
|
let (arg, err) = parse_multispan_value(
|
|
|
|
working_set,
|
|
|
|
&spans[..end],
|
|
|
|
&mut spans_idx,
|
|
|
|
&positional.shape,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist,
|
2021-09-02 10:25:22 +02:00
|
|
|
);
|
|
|
|
error = error.or(err);
|
2021-07-23 23:46:55 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let arg = if !type_compatible(&positional.shape.to_type(), &arg.ty) {
|
|
|
|
let span = span(&spans[orig_idx..spans_idx]);
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::TypeMismatch(
|
|
|
|
positional.shape.to_type(),
|
|
|
|
arg.ty,
|
|
|
|
arg.span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
Expression::garbage(span)
|
2021-07-08 08:19:38 +02:00
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
arg
|
|
|
|
};
|
2022-04-09 04:55:02 +02:00
|
|
|
call.add_positional(arg);
|
2021-09-02 10:25:22 +02:00
|
|
|
positional_idx += 1;
|
2022-12-21 23:33:26 +01:00
|
|
|
} else if signature.allows_unknown_args {
|
|
|
|
let (arg, arg_err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
arg_span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
call.add_unknown(arg);
|
|
|
|
error = error.or(arg_err);
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2022-04-09 04:55:02 +02:00
|
|
|
call.add_positional(Expression::garbage(arg_span));
|
2022-01-04 00:14:33 +01:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::ExtraPositional(
|
|
|
|
signature.call_signature(),
|
|
|
|
arg_span,
|
|
|
|
))
|
|
|
|
})
|
2021-07-08 08:19:38 +02:00
|
|
|
}
|
2021-07-02 00:40:08 +02:00
|
|
|
|
2021-07-08 08:19:38 +02:00
|
|
|
error = error.or(err);
|
2021-09-02 10:25:22 +02:00
|
|
|
spans_idx += 1;
|
2021-07-08 08:19:38 +02:00
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let err = check_call(command_span, &signature, &call);
|
|
|
|
error = error.or(err);
|
2021-07-31 07:20:40 +02:00
|
|
|
|
2021-10-09 18:10:46 +02:00
|
|
|
if signature.creates_scope {
|
|
|
|
working_set.exit_scope();
|
|
|
|
}
|
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
ParsedInternalCall {
|
|
|
|
call: Box::new(call),
|
|
|
|
output,
|
|
|
|
error,
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-31 07:20:40 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_call(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2021-12-19 08:46:13 +01:00
|
|
|
head: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression: bool,
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: call");
|
|
|
|
|
2021-10-29 22:50:28 +02:00
|
|
|
if spans.is_empty() {
|
|
|
|
return (
|
2021-12-19 08:46:13 +01:00
|
|
|
garbage(head),
|
2021-10-27 23:52:59 +02:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"Encountered command with zero spans".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
2021-10-29 22:50:28 +02:00
|
|
|
);
|
2021-10-27 23:52:59 +02:00
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut pos = 0;
|
|
|
|
let cmd_start = pos;
|
2021-10-29 22:50:28 +02:00
|
|
|
let mut name_spans = vec![];
|
2022-01-10 03:52:01 +01:00
|
|
|
let mut name = vec![];
|
2021-08-17 01:00:00 +02:00
|
|
|
|
2021-10-29 22:50:28 +02:00
|
|
|
for word_span in spans[cmd_start..].iter() {
|
|
|
|
// Find the longest group of words that could form a command
|
2022-04-07 04:01:31 +02:00
|
|
|
|
|
|
|
if is_math_expression_like(working_set, *word_span, expand_aliases_denylist) {
|
|
|
|
let bytes = working_set.get_span_contents(*word_span);
|
|
|
|
if bytes != b"true" && bytes != b"false" && bytes != b"null" && bytes != b"not" {
|
|
|
|
break;
|
|
|
|
}
|
2021-08-09 02:19:07 +02:00
|
|
|
}
|
|
|
|
|
2021-10-29 22:50:28 +02:00
|
|
|
name_spans.push(*word_span);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-01-10 03:52:01 +01:00
|
|
|
let name_part = working_set.get_span_contents(*word_span);
|
|
|
|
if name.is_empty() {
|
|
|
|
name.extend(name_part);
|
|
|
|
} else {
|
|
|
|
name.push(b' ');
|
|
|
|
name.extend(name_part);
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
// If the word is an alias, expand it and re-parse the expression
|
|
|
|
if let Some(alias_id) = working_set.find_alias(&name) {
|
|
|
|
if !expand_aliases_denylist.contains(&alias_id) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("expanding alias");
|
|
|
|
|
2022-02-12 10:50:37 +01:00
|
|
|
let expansion = working_set.get_alias(alias_id);
|
|
|
|
|
2022-02-15 03:09:21 +01:00
|
|
|
let expansion_span = span(expansion);
|
|
|
|
|
|
|
|
let orig_span = span(&[spans[cmd_start], spans[pos]]);
|
2021-10-29 22:50:28 +02:00
|
|
|
let mut new_spans: Vec<Span> = vec![];
|
2022-02-15 03:09:21 +01:00
|
|
|
new_spans.extend(&spans[0..cmd_start]);
|
2021-10-29 22:50:28 +02:00
|
|
|
new_spans.extend(expansion);
|
2022-03-03 20:05:55 +01:00
|
|
|
// TODO: This seems like it should be `pos + 1`. `pos` starts as 0
|
2021-10-29 22:50:28 +02:00
|
|
|
if spans.len() > pos {
|
|
|
|
new_spans.extend(&spans[(pos + 1)..]);
|
|
|
|
}
|
2021-08-17 01:00:00 +02:00
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let mut expand_aliases_denylist = expand_aliases_denylist.to_vec();
|
|
|
|
expand_aliases_denylist.push(alias_id);
|
|
|
|
|
2022-03-17 23:35:50 +01:00
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: vec![],
|
|
|
|
parts: new_spans.clone(),
|
|
|
|
};
|
2022-03-21 23:57:48 +01:00
|
|
|
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
let (mut result, err) = parse_builtin_commands(
|
|
|
|
working_set,
|
|
|
|
&lite_command,
|
|
|
|
&expand_aliases_denylist,
|
|
|
|
is_subexpression,
|
|
|
|
);
|
2022-03-17 23:35:50 +01:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
let result = result.elements.remove(0);
|
2021-08-17 01:00:00 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
// If this is the first element in a pipeline, we know it has to be an expression
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, mut result) = result {
|
2022-11-18 22:46:48 +01:00
|
|
|
result.replace_span(working_set, expansion_span, orig_span);
|
2021-08-17 01:00:00 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
return (result, err);
|
|
|
|
} else {
|
|
|
|
panic!("Internal error: first element of pipeline not an expression")
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-10-29 22:50:28 +02:00
|
|
|
}
|
2021-08-17 01:00:00 +02:00
|
|
|
|
2021-10-29 22:50:28 +02:00
|
|
|
pos += 1;
|
|
|
|
}
|
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
let input = working_set.type_scope.get_previous();
|
2022-06-10 17:59:35 +02:00
|
|
|
let mut maybe_decl_id = working_set.find_decl(&name, input);
|
2021-10-29 22:50:28 +02:00
|
|
|
|
|
|
|
while maybe_decl_id.is_none() {
|
|
|
|
// Find the longest command match
|
|
|
|
if name_spans.len() <= 1 {
|
|
|
|
// Keep the first word even if it does not match -- could be external command
|
|
|
|
break;
|
2021-06-30 03:42:56 +02:00
|
|
|
}
|
2021-09-11 14:07:19 +02:00
|
|
|
|
2021-10-29 22:50:28 +02:00
|
|
|
name_spans.pop();
|
|
|
|
pos -= 1;
|
|
|
|
|
2022-01-10 03:52:01 +01:00
|
|
|
let mut name = vec![];
|
|
|
|
for name_span in &name_spans {
|
|
|
|
let name_part = working_set.get_span_contents(*name_span);
|
|
|
|
if name.is_empty() {
|
|
|
|
name.extend(name_part);
|
|
|
|
} else {
|
|
|
|
name.push(b' ');
|
|
|
|
name.extend(name_part);
|
|
|
|
}
|
|
|
|
}
|
2022-06-10 17:59:35 +02:00
|
|
|
maybe_decl_id = working_set.find_decl(&name, input);
|
2021-10-29 22:50:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(decl_id) = maybe_decl_id {
|
2021-09-11 14:07:19 +02:00
|
|
|
// Before the internal parsing we check if there is no let or alias declarations
|
|
|
|
// that are missing their name, e.g.: let = 1 or alias = 2
|
|
|
|
if spans.len() > 1 {
|
|
|
|
let test_equal = working_set.get_span_contents(spans[1]);
|
|
|
|
|
2021-09-11 14:16:40 +02:00
|
|
|
if test_equal == [b'='] {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("incomplete statement");
|
|
|
|
|
2021-09-11 14:07:19 +02:00
|
|
|
return (
|
2021-12-19 08:46:13 +01:00
|
|
|
garbage(span(spans)),
|
2021-09-11 14:07:19 +02:00
|
|
|
Some(ParseError::UnknownState(
|
2021-09-12 17:36:16 +02:00
|
|
|
"Incomplete statement".into(),
|
2021-09-11 14:07:19 +02:00
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 19:37:15 +02:00
|
|
|
trace!("parsing: internal call");
|
2022-01-01 22:42:50 +01:00
|
|
|
|
2022-07-25 19:37:15 +02:00
|
|
|
// parse internal command
|
|
|
|
let parsed_call = parse_internal_call(
|
|
|
|
working_set,
|
|
|
|
span(&spans[cmd_start..pos]),
|
|
|
|
&spans[pos..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
2022-07-25 19:37:15 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(parsed_call.call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: parsed_call.output,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
parsed_call.error,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2021-09-12 14:48:19 +02:00
|
|
|
// We might be parsing left-unbounded range ("..10")
|
|
|
|
let bytes = working_set.get_span_contents(spans[0]);
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: range {:?} ", bytes);
|
2022-08-11 18:54:54 +02:00
|
|
|
if let (Some(b'.'), Some(b'.')) = (bytes.first(), bytes.get(1)) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- found leading range indicator");
|
2022-03-18 20:03:57 +01:00
|
|
|
let (range_expr, range_err) =
|
|
|
|
parse_range(working_set, spans[0], expand_aliases_denylist);
|
2021-09-12 14:48:19 +02:00
|
|
|
if range_err.is_none() {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- successfully parsed range");
|
2021-09-12 14:48:19 +02:00
|
|
|
return (range_expr, range_err);
|
|
|
|
}
|
|
|
|
}
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: external call");
|
2021-10-29 22:50:28 +02:00
|
|
|
|
|
|
|
// Otherwise, try external command
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
parse_external_call(
|
|
|
|
working_set,
|
|
|
|
spans,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
is_subexpression,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-01 00:31:53 +01:00
|
|
|
pub fn parse_binary(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-04-11 09:58:57 +02:00
|
|
|
let (hex_value, err) = parse_binary_with_base(working_set, span, 16, 2, b"0x[", b"]");
|
|
|
|
if err.is_some() {
|
2022-05-23 11:01:15 +02:00
|
|
|
let (octal_value, err) = parse_binary_with_base(working_set, span, 8, 3, b"0o[", b"]");
|
|
|
|
if err.is_some() {
|
|
|
|
return parse_binary_with_base(working_set, span, 2, 8, b"0b[", b"]");
|
|
|
|
}
|
|
|
|
return (octal_value, err);
|
2022-03-01 00:31:53 +01:00
|
|
|
}
|
2022-04-11 09:58:57 +02:00
|
|
|
(hex_value, err)
|
|
|
|
}
|
2022-03-01 00:31:53 +01:00
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
fn parse_binary_with_base(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
base: u32,
|
|
|
|
min_digits_per_byte: usize,
|
|
|
|
prefix: &[u8],
|
|
|
|
suffix: &[u8],
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-03-01 00:31:53 +01:00
|
|
|
let token = working_set.get_span_contents(span);
|
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
if let Some(token) = token.strip_prefix(prefix) {
|
|
|
|
if let Some(token) = token.strip_suffix(suffix) {
|
|
|
|
let (lexed, err) = lex(
|
|
|
|
token,
|
|
|
|
span.start + prefix.len(),
|
|
|
|
&[b',', b'\r', b'\n'],
|
|
|
|
&[],
|
|
|
|
true,
|
|
|
|
);
|
2022-03-01 00:31:53 +01:00
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
let mut binary_value = vec![];
|
2022-03-01 00:31:53 +01:00
|
|
|
for token in lexed {
|
|
|
|
match token.contents {
|
|
|
|
TokenContents::Item => {
|
|
|
|
let contents = working_set.get_span_contents(token.span);
|
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
binary_value.extend_from_slice(contents);
|
2022-03-01 00:31:53 +01:00
|
|
|
}
|
2022-12-08 00:02:11 +01:00
|
|
|
TokenContents::Pipe
|
|
|
|
| TokenContents::PipePipe
|
|
|
|
| TokenContents::OutGreaterThan
|
|
|
|
| TokenContents::ErrGreaterThan
|
|
|
|
| TokenContents::OutErrGreaterThan => {
|
2022-03-01 00:31:53 +01:00
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("binary".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
2022-12-08 00:02:11 +01:00
|
|
|
TokenContents::Comment | TokenContents::Semicolon | TokenContents::Eol => {}
|
2022-03-01 00:31:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
let required_padding = (min_digits_per_byte - binary_value.len() % min_digits_per_byte)
|
|
|
|
% min_digits_per_byte;
|
|
|
|
|
|
|
|
if required_padding != 0 {
|
|
|
|
binary_value = {
|
|
|
|
let mut tail = binary_value;
|
|
|
|
let mut binary_value: Vec<u8> = vec![b'0'; required_padding];
|
|
|
|
binary_value.append(&mut tail);
|
|
|
|
binary_value
|
|
|
|
};
|
2022-03-01 00:31:53 +01:00
|
|
|
}
|
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
let str = String::from_utf8_lossy(&binary_value).to_string();
|
2022-03-01 00:31:53 +01:00
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
match decode_with_base(&str, base, min_digits_per_byte) {
|
2022-03-01 00:31:53 +01:00
|
|
|
Ok(v) => {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Binary(v),
|
|
|
|
span,
|
|
|
|
ty: Type::Binary,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Err(x) => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::IncorrectValue(
|
|
|
|
"not a binary value".into(),
|
|
|
|
span,
|
|
|
|
x.to_string(),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("binary".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
fn decode_with_base(s: &str, base: u32, digits_per_byte: usize) -> Result<Vec<u8>, ParseIntError> {
|
2022-06-17 20:11:48 +02:00
|
|
|
s.chars()
|
|
|
|
.chunks(digits_per_byte)
|
|
|
|
.into_iter()
|
|
|
|
.map(|chunk| {
|
|
|
|
let str: String = chunk.collect();
|
|
|
|
u8::from_str_radix(&str, base)
|
|
|
|
})
|
2022-04-11 09:58:57 +02:00
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2023-01-15 16:03:57 +01:00
|
|
|
fn strip_underscores(token: &[u8]) -> String {
|
|
|
|
String::from_utf8_lossy(token)
|
|
|
|
.chars()
|
|
|
|
.filter(|c| *c != '_')
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
|
2023-01-15 16:03:57 +01:00
|
|
|
fn extract_int(token: &str, span: Span, radix: u32) -> (Expression, Option<ParseError>) {
|
|
|
|
if let Ok(num) = i64::from_str_radix(token, radix) {
|
2021-08-08 22:21:21 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2023-01-15 16:03:57 +01:00
|
|
|
expr: Expr::Int(num),
|
2021-08-08 22:21:21 +02:00
|
|
|
span,
|
2021-09-02 10:25:22 +02:00
|
|
|
ty: Type::Int,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-08-08 22:21:21 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
2021-09-02 10:25:22 +02:00
|
|
|
Some(ParseError::Mismatch(
|
|
|
|
"int".into(),
|
|
|
|
"incompatible int".into(),
|
|
|
|
span,
|
|
|
|
)),
|
2021-08-08 22:21:21 +02:00
|
|
|
)
|
|
|
|
}
|
2023-01-15 16:03:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let token = strip_underscores(token);
|
|
|
|
|
|
|
|
if let Some(num) = token.strip_prefix("0b") {
|
|
|
|
extract_int(num, span, 2)
|
|
|
|
} else if let Some(num) = token.strip_prefix("0o") {
|
|
|
|
extract_int(num, span, 8)
|
|
|
|
} else if let Some(num) = token.strip_prefix("0x") {
|
|
|
|
extract_int(num, span, 16)
|
|
|
|
} else if let Ok(num) = token.parse::<i64>() {
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2023-01-15 16:03:57 +01:00
|
|
|
expr: Expr::Int(num),
|
2021-09-02 10:25:22 +02:00
|
|
|
span,
|
|
|
|
ty: Type::Int,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("int".into(), span)),
|
|
|
|
)
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
pub fn parse_float(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
|
2023-01-15 16:03:57 +01:00
|
|
|
let token = strip_underscores(token);
|
|
|
|
|
|
|
|
if let Ok(x) = token.parse::<f64>() {
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Float(x),
|
|
|
|
span,
|
|
|
|
ty: Type::Float,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("float".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
pub fn parse_number(token: &[u8], span: Span) -> (Expression, Option<ParseError>) {
|
2021-09-03 09:35:29 +02:00
|
|
|
if let (x, None) = parse_int(token, span) {
|
2021-09-02 10:25:22 +02:00
|
|
|
(x, None)
|
2021-09-03 09:35:29 +02:00
|
|
|
} else if let (x, None) = parse_float(token, span) {
|
2021-09-02 10:25:22 +02:00
|
|
|
(x, None)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("number".into(), span)),
|
|
|
|
)
|
2021-07-30 05:26:06 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2021-09-04 23:52:57 +02:00
|
|
|
pub fn parse_range(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-04 23:52:57 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: range");
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
// Range follows the following syntax: [<from>][<next_operator><next>]<range_operator>[<to>]
|
|
|
|
// where <next_operator> is ".."
|
2021-09-04 23:52:57 +02:00
|
|
|
// and <range_operator> is ".." or "..<"
|
|
|
|
// and one of the <from> or <to> bounds must be present (just '..' is not allowed since it
|
|
|
|
// looks like parent directory)
|
|
|
|
|
|
|
|
let contents = working_set.get_span_contents(span);
|
2022-02-24 13:58:53 +01:00
|
|
|
|
2021-09-04 23:52:57 +02:00
|
|
|
let token = if let Ok(s) = String::from_utf8(contents.into()) {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
return (garbage(span), Some(ParseError::NonUtf8(span)));
|
|
|
|
};
|
|
|
|
|
2022-02-24 13:58:53 +01:00
|
|
|
if !token.contains("..") {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"at least one range bound set".into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-04 23:52:57 +02:00
|
|
|
// First, figure out what exact operators are used and determine their positions
|
|
|
|
let dotdot_pos: Vec<_> = token.match_indices("..").map(|(pos, _)| pos).collect();
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
let (next_op_pos, range_op_pos) =
|
2021-09-04 23:52:57 +02:00
|
|
|
match dotdot_pos.len() {
|
|
|
|
1 => (None, dotdot_pos[0]),
|
|
|
|
2 => (Some(dotdot_pos[0]), dotdot_pos[1]),
|
|
|
|
_ => return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected(
|
2021-09-11 13:13:04 +02:00
|
|
|
"one range operator ('..' or '..<') and optionally one next operator ('..')"
|
2021-09-04 23:52:57 +02:00
|
|
|
.into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
let (inclusion, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") {
|
2021-09-04 23:52:57 +02:00
|
|
|
if pos == range_op_pos {
|
|
|
|
let op_str = "..<";
|
|
|
|
let op_span = Span::new(
|
|
|
|
span.start + range_op_pos,
|
|
|
|
span.start + range_op_pos + op_str.len(),
|
|
|
|
);
|
2021-09-11 13:13:04 +02:00
|
|
|
(RangeInclusion::RightExclusive, "..<", op_span)
|
2021-09-04 23:52:57 +02:00
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"inclusive operator preceding second range bound".into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let op_str = "..";
|
|
|
|
let op_span = Span::new(
|
|
|
|
span.start + range_op_pos,
|
|
|
|
span.start + range_op_pos + op_str.len(),
|
|
|
|
);
|
2021-09-11 13:13:04 +02:00
|
|
|
(RangeInclusion::Inclusive, "..", op_span)
|
2021-09-04 23:52:57 +02:00
|
|
|
};
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
// Now, based on the operator positions, figure out where the bounds & next are located and
|
2021-09-04 23:52:57 +02:00
|
|
|
// parse them
|
2021-10-12 19:44:23 +02:00
|
|
|
// TODO: Actually parse the next number in the range
|
2021-09-04 23:52:57 +02:00
|
|
|
let from = if token.starts_with("..") {
|
2021-09-11 13:13:04 +02:00
|
|
|
// token starts with either next operator, or range operator -- we don't care which one
|
2021-09-04 23:52:57 +02:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let from_span = Span::new(span.start, span.start + dotdot_pos[0]);
|
2022-03-18 20:03:57 +01:00
|
|
|
match parse_value(
|
|
|
|
working_set,
|
|
|
|
from_span,
|
|
|
|
&SyntaxShape::Number,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
) {
|
2021-09-04 23:52:57 +02:00
|
|
|
(expression, None) => Some(Box::new(expression)),
|
|
|
|
_ => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("number".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let to = if token.ends_with(range_op_str) {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let to_span = Span::new(range_op_span.end, span.end);
|
2022-03-18 20:03:57 +01:00
|
|
|
match parse_value(
|
|
|
|
working_set,
|
|
|
|
to_span,
|
|
|
|
&SyntaxShape::Number,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
) {
|
2021-09-04 23:52:57 +02:00
|
|
|
(expression, None) => Some(Box::new(expression)),
|
|
|
|
_ => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("number".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- from: {:?} to: {:?}", from, to);
|
|
|
|
|
2021-09-04 23:52:57 +02:00
|
|
|
if let (None, None) = (&from, &to) {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"at least one range bound set".into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
let (next, next_op_span) = if let Some(pos) = next_op_pos {
|
|
|
|
let next_op_span = Span::new(span.start + pos, span.start + pos + "..".len());
|
|
|
|
let next_span = Span::new(next_op_span.end, range_op_span.start);
|
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
match parse_value(
|
|
|
|
working_set,
|
|
|
|
next_span,
|
|
|
|
&SyntaxShape::Number,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
) {
|
2021-09-11 13:13:04 +02:00
|
|
|
(expression, None) => (Some(Box::new(expression)), next_op_span),
|
|
|
|
_ => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("number".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2021-12-19 08:46:13 +01:00
|
|
|
(None, span)
|
2021-09-11 13:13:04 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let range_op = RangeOperator {
|
|
|
|
inclusion,
|
|
|
|
span: range_op_span,
|
|
|
|
next_op_span,
|
|
|
|
};
|
|
|
|
|
2021-09-04 23:52:57 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2021-09-11 13:13:04 +02:00
|
|
|
expr: Expr::Range(from, next, to, range_op),
|
2021-09-04 23:52:57 +02:00
|
|
|
span,
|
|
|
|
ty: Type::Range,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-04 23:52:57 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub(crate) fn parse_dollar_expr(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-02-24 13:58:53 +01:00
|
|
|
trace!("parsing: dollar expression");
|
2021-09-02 10:25:22 +02:00
|
|
|
let contents = working_set.get_span_contents(span);
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2021-12-25 21:50:02 +01:00
|
|
|
if contents.starts_with(b"$\"") || contents.starts_with(b"$'") {
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_string_interpolation(working_set, span, expand_aliases_denylist)
|
|
|
|
} else if let (expr, None) = parse_range(working_set, span, expand_aliases_denylist) {
|
2021-09-05 00:40:15 +02:00
|
|
|
(expr, None)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_full_cell_path(working_set, None, span, expand_aliases_denylist)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_string_interpolation(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
enum InterpolationMode {
|
|
|
|
String,
|
|
|
|
Expression,
|
|
|
|
}
|
|
|
|
let mut error = None;
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let contents = working_set.get_span_contents(span);
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2022-03-03 19:14:03 +01:00
|
|
|
let mut double_quote = false;
|
|
|
|
|
2021-12-25 21:50:02 +01:00
|
|
|
let (start, end) = if contents.starts_with(b"$\"") {
|
2022-03-03 19:14:03 +01:00
|
|
|
double_quote = true;
|
2021-12-25 21:50:02 +01:00
|
|
|
let end = if contents.ends_with(b"\"") && contents.len() > 2 {
|
|
|
|
span.end - 1
|
|
|
|
} else {
|
|
|
|
span.end
|
|
|
|
};
|
|
|
|
(span.start + 2, end)
|
|
|
|
} else if contents.starts_with(b"$'") {
|
|
|
|
let end = if contents.ends_with(b"'") && contents.len() > 2 {
|
|
|
|
span.end - 1
|
|
|
|
} else {
|
|
|
|
span.end
|
|
|
|
};
|
|
|
|
(span.start + 2, end)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2021-12-25 21:50:02 +01:00
|
|
|
(span.start, span.end)
|
2021-09-02 10:25:22 +02:00
|
|
|
};
|
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let inner_span = Span::new(start, end);
|
2021-09-02 10:25:22 +02:00
|
|
|
let contents = working_set.get_span_contents(inner_span).to_vec();
|
|
|
|
|
|
|
|
let mut output = vec![];
|
|
|
|
let mut mode = InterpolationMode::String;
|
|
|
|
let mut token_start = start;
|
2022-02-10 17:09:08 +01:00
|
|
|
let mut delimiter_stack = vec![];
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-12-14 14:54:13 +01:00
|
|
|
let mut consecutive_backslashes: usize = 0;
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut b = start;
|
|
|
|
|
|
|
|
while b != end {
|
2022-12-14 14:54:13 +01:00
|
|
|
let current_byte = contents[b - start];
|
|
|
|
|
|
|
|
if mode == InterpolationMode::String {
|
|
|
|
let preceding_consecutive_backslashes = consecutive_backslashes;
|
|
|
|
|
|
|
|
let is_backslash = current_byte == b'\\';
|
|
|
|
consecutive_backslashes = if is_backslash {
|
|
|
|
preceding_consecutive_backslashes + 1
|
2022-11-06 23:17:00 +01:00
|
|
|
} else {
|
2022-12-14 14:54:13 +01:00
|
|
|
0
|
|
|
|
};
|
2022-03-03 19:14:03 +01:00
|
|
|
|
2022-12-14 14:54:13 +01:00
|
|
|
if current_byte == b'(' && (!double_quote || preceding_consecutive_backslashes % 2 == 0)
|
|
|
|
{
|
|
|
|
mode = InterpolationMode::Expression;
|
|
|
|
if token_start < b {
|
|
|
|
let span = Span::new(token_start, b);
|
|
|
|
let str_contents = working_set.get_span_contents(span);
|
2022-03-03 19:14:03 +01:00
|
|
|
|
2022-12-14 14:54:13 +01:00
|
|
|
let str_contents = if double_quote {
|
|
|
|
let (str_contents, err) = unescape_string(str_contents, span);
|
|
|
|
error = error.or(err);
|
2022-03-03 19:14:03 +01:00
|
|
|
|
2022-12-14 14:54:13 +01:00
|
|
|
str_contents
|
|
|
|
} else {
|
|
|
|
str_contents.to_vec()
|
|
|
|
};
|
|
|
|
|
|
|
|
output.push(Expression {
|
|
|
|
expr: Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
});
|
|
|
|
token_start = b;
|
|
|
|
}
|
2022-11-06 23:17:00 +01:00
|
|
|
}
|
|
|
|
}
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2022-11-06 23:17:00 +01:00
|
|
|
if mode == InterpolationMode::Expression {
|
2022-12-14 14:54:13 +01:00
|
|
|
let byte = current_byte;
|
2022-11-06 23:17:00 +01:00
|
|
|
if let Some(b'\'') = delimiter_stack.last() {
|
|
|
|
if byte == b'\'' {
|
|
|
|
delimiter_stack.pop();
|
|
|
|
}
|
|
|
|
} else if let Some(b'"') = delimiter_stack.last() {
|
|
|
|
if byte == b'"' {
|
|
|
|
delimiter_stack.pop();
|
|
|
|
}
|
|
|
|
} else if let Some(b'`') = delimiter_stack.last() {
|
|
|
|
if byte == b'`' {
|
|
|
|
delimiter_stack.pop();
|
|
|
|
}
|
|
|
|
} else if byte == b'\'' {
|
|
|
|
delimiter_stack.push(b'\'')
|
|
|
|
} else if byte == b'"' {
|
|
|
|
delimiter_stack.push(b'"');
|
|
|
|
} else if byte == b'`' {
|
|
|
|
delimiter_stack.push(b'`')
|
|
|
|
} else if byte == b'(' {
|
|
|
|
delimiter_stack.push(b')');
|
|
|
|
} else if byte == b')' {
|
|
|
|
if let Some(b')') = delimiter_stack.last() {
|
|
|
|
delimiter_stack.pop();
|
|
|
|
}
|
|
|
|
if delimiter_stack.is_empty() {
|
|
|
|
mode = InterpolationMode::String;
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if token_start < b {
|
2022-12-03 10:44:12 +01:00
|
|
|
let span = Span::new(token_start, b + 1);
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2022-11-06 23:17:00 +01:00
|
|
|
let (expr, err) =
|
|
|
|
parse_full_cell_path(working_set, None, span, expand_aliases_denylist);
|
|
|
|
error = error.or(err);
|
|
|
|
output.push(expr);
|
2022-11-06 20:57:28 +01:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-11-06 23:17:00 +01:00
|
|
|
token_start = b + 1;
|
|
|
|
continue;
|
2021-07-30 05:26:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
b += 1;
|
|
|
|
}
|
2021-07-30 05:26:06 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
match mode {
|
|
|
|
InterpolationMode::String => {
|
|
|
|
if token_start < end {
|
2022-12-03 10:44:12 +01:00
|
|
|
let span = Span::new(token_start, end);
|
2021-09-02 10:25:22 +02:00
|
|
|
let str_contents = working_set.get_span_contents(span);
|
2022-03-15 17:09:30 +01:00
|
|
|
|
|
|
|
let str_contents = if double_quote {
|
|
|
|
let (str_contents, err) = unescape_string(str_contents, span);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
str_contents
|
|
|
|
} else {
|
|
|
|
str_contents.to_vec()
|
|
|
|
};
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
output.push(Expression {
|
2022-03-15 17:09:30 +01:00
|
|
|
expr: Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
|
2021-07-30 05:26:06 +02:00
|
|
|
span,
|
|
|
|
ty: Type::String,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
InterpolationMode::Expression => {
|
|
|
|
if token_start < end {
|
2022-12-03 10:44:12 +01:00
|
|
|
let span = Span::new(token_start, end);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (expr, err) =
|
|
|
|
parse_full_cell_path(working_set, None, span, expand_aliases_denylist);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
output.push(expr);
|
|
|
|
}
|
2021-07-30 05:26:06 +02:00
|
|
|
}
|
2021-07-24 07:57:17 +02:00
|
|
|
}
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2021-12-25 21:50:02 +01:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::StringInterpolation(output),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-24 07:57:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_variable_expr(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let contents = working_set.get_span_contents(span);
|
|
|
|
|
2022-03-19 20:12:10 +01:00
|
|
|
if contents == b"$nothing" {
|
2021-12-20 02:05:33 +01:00
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Nothing,
|
|
|
|
span,
|
|
|
|
ty: Type::Nothing,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
2021-10-29 20:15:17 +02:00
|
|
|
} else if contents == b"$nu" {
|
|
|
|
return (
|
|
|
|
Expression {
|
2021-11-02 04:08:05 +01:00
|
|
|
expr: Expr::Var(nu_protocol::NU_VARIABLE_ID),
|
|
|
|
span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-11-02 04:08:05 +01:00
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
2021-11-08 07:21:24 +01:00
|
|
|
} else if contents == b"$in" {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Var(nu_protocol::IN_VARIABLE_ID),
|
|
|
|
span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-11-08 07:21:24 +01:00
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
2022-01-04 22:34:42 +01:00
|
|
|
} else if contents == b"$env" {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Var(nu_protocol::ENV_VARIABLE_ID),
|
|
|
|
span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2022-01-04 22:34:42 +01:00
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let (id, err) = parse_variable(working_set, span);
|
|
|
|
|
|
|
|
if err.is_none() {
|
|
|
|
if let Some(id) = id {
|
|
|
|
(
|
2021-07-02 09:15:30 +02:00
|
|
|
Expression {
|
2021-09-02 10:25:22 +02:00
|
|
|
expr: Expr::Var(id),
|
2021-07-02 09:15:30 +02:00
|
|
|
span,
|
2022-03-09 10:42:19 +01:00
|
|
|
ty: working_set.get_variable(id).ty.clone(),
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-07-02 09:15:30 +02:00
|
|
|
},
|
|
|
|
None,
|
2021-09-02 10:25:22 +02:00
|
|
|
)
|
|
|
|
} else {
|
2021-09-13 09:54:13 +02:00
|
|
|
(garbage(span), Some(ParseError::VariableNotFound(span)))
|
2021-07-02 09:15:30 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
(garbage(span), err)
|
|
|
|
}
|
|
|
|
}
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2021-10-02 04:59:11 +02:00
|
|
|
pub fn parse_cell_path(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
tokens: impl Iterator<Item = Token>,
|
|
|
|
mut expect_dot: bool,
|
2022-04-26 01:44:44 +02:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-10-02 04:59:11 +02:00
|
|
|
span: Span,
|
|
|
|
) -> (Vec<PathMember>, Option<ParseError>) {
|
|
|
|
let mut error = None;
|
|
|
|
let mut tail = vec![];
|
|
|
|
|
|
|
|
for path_element in tokens {
|
|
|
|
let bytes = working_set.get_span_contents(path_element.span);
|
|
|
|
|
|
|
|
if expect_dot {
|
|
|
|
expect_dot = false;
|
|
|
|
if bytes.len() != 1 || bytes[0] != b'.' {
|
|
|
|
error = error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span)));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
expect_dot = true;
|
|
|
|
|
|
|
|
match parse_int(bytes, path_element.span) {
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Int(val),
|
|
|
|
span,
|
|
|
|
..
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
) => tail.push(PathMember::Int {
|
|
|
|
val: val as usize,
|
|
|
|
span,
|
|
|
|
}),
|
|
|
|
_ => {
|
2022-04-26 01:44:44 +02:00
|
|
|
let (result, err) =
|
|
|
|
parse_string(working_set, path_element.span, expand_aliases_denylist);
|
2021-10-02 04:59:11 +02:00
|
|
|
error = error.or(err);
|
|
|
|
match result {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::String(string),
|
|
|
|
span,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
tail.push(PathMember::String { val: string, span });
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
error =
|
|
|
|
error.or_else(|| Some(ParseError::Expected("string".into(), span)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(tail, error)
|
|
|
|
}
|
|
|
|
|
2021-09-26 20:39:19 +02:00
|
|
|
pub fn parse_full_cell_path(
|
2021-09-02 10:25:22 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
2021-09-09 23:47:20 +02:00
|
|
|
implicit_head: Option<VarId>,
|
2021-09-02 10:25:22 +02:00
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: full cell path");
|
2021-09-26 20:39:19 +02:00
|
|
|
let full_cell_span = span;
|
2021-09-07 00:02:24 +02:00
|
|
|
let source = working_set.get_span_contents(span);
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut error = None;
|
2021-07-08 08:19:38 +02:00
|
|
|
|
2021-11-21 19:13:09 +01:00
|
|
|
let (tokens, err) = lex(source, span.start, &[b'\n', b'\r'], &[b'.'], true);
|
2021-09-07 00:02:24 +02:00
|
|
|
error = error.or(err);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2021-09-09 23:47:20 +02:00
|
|
|
let mut tokens = tokens.into_iter().peekable();
|
|
|
|
if let Some(head) = tokens.peek() {
|
2021-09-07 00:02:24 +02:00
|
|
|
let bytes = working_set.get_span_contents(head.span);
|
2021-10-02 04:59:11 +02:00
|
|
|
let (head, expect_dot) = if bytes.starts_with(b"(") {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: paren-head of full cell path");
|
|
|
|
|
2021-11-06 06:50:33 +01:00
|
|
|
let head_span = head.span;
|
2021-09-07 00:02:24 +02:00
|
|
|
let mut start = head.span.start;
|
|
|
|
let mut end = head.span.end;
|
2021-07-08 08:19:38 +02:00
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
if bytes.starts_with(b"(") {
|
|
|
|
start += 1;
|
|
|
|
}
|
|
|
|
if bytes.ends_with(b")") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error =
|
|
|
|
error.or_else(|| Some(ParseError::Unclosed(")".into(), Span::new(end, end))));
|
2021-09-07 00:02:24 +02:00
|
|
|
}
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let span = Span::new(start, end);
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
let source = working_set.get_span_contents(span);
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2021-11-21 19:13:09 +01:00
|
|
|
let (output, err) = lex(source, span.start, &[b'\n', b'\r'], &[], true);
|
2021-09-07 00:02:24 +02:00
|
|
|
error = error.or(err);
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
// Creating a Type scope to parse the new block. This will keep track of
|
|
|
|
// the previous input type found in that block
|
2022-04-08 23:41:05 +02:00
|
|
|
let (output, err) =
|
|
|
|
parse_block(working_set, &output, true, expand_aliases_denylist, true);
|
2022-06-12 21:18:00 +02:00
|
|
|
working_set
|
|
|
|
.type_scope
|
|
|
|
.add_type(working_set.type_scope.get_last_output());
|
|
|
|
|
2022-06-15 03:31:14 +02:00
|
|
|
let ty = output
|
|
|
|
.pipelines
|
|
|
|
.last()
|
2022-11-18 22:46:48 +01:00
|
|
|
.and_then(|Pipeline { elements, .. }| elements.last())
|
|
|
|
.map(|element| match element {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(_, expr)
|
2022-11-18 22:46:48 +01:00
|
|
|
if matches!(
|
|
|
|
expr,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::BinaryOp(..),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
) =>
|
|
|
|
{
|
|
|
|
expr.ty.clone()
|
|
|
|
}
|
2022-06-15 03:31:14 +02:00
|
|
|
_ => working_set.type_scope.get_last_output(),
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| working_set.type_scope.get_last_output());
|
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
error = error.or(err);
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
let block_id = working_set.add_block(output);
|
2021-09-09 23:47:20 +02:00
|
|
|
tokens.next();
|
2021-09-07 00:02:24 +02:00
|
|
|
|
2021-09-09 23:47:20 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Subexpression(block_id),
|
2021-11-06 06:50:33 +01:00
|
|
|
span: head_span,
|
2022-06-15 03:31:14 +02:00
|
|
|
ty,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-09 23:47:20 +02:00
|
|
|
},
|
|
|
|
true,
|
|
|
|
)
|
2021-11-08 00:18:00 +01:00
|
|
|
} else if bytes.starts_with(b"[") {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: table head of full cell path");
|
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (output, err) =
|
|
|
|
parse_table_expression(working_set, head.span, expand_aliases_denylist);
|
2021-11-08 00:18:00 +01:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
tokens.next();
|
|
|
|
|
2021-11-11 00:14:00 +01:00
|
|
|
(output, true)
|
|
|
|
} else if bytes.starts_with(b"{") {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: record head of full cell path");
|
2022-03-18 20:03:57 +01:00
|
|
|
let (output, err) = parse_record(working_set, head.span, expand_aliases_denylist);
|
2021-11-11 00:14:00 +01:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
tokens.next();
|
|
|
|
|
2021-11-08 00:18:00 +01:00
|
|
|
(output, true)
|
2021-09-07 00:02:24 +02:00
|
|
|
} else if bytes.starts_with(b"$") {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: $variable head of full cell path");
|
|
|
|
|
2021-09-07 00:02:24 +02:00
|
|
|
let (out, err) = parse_variable_expr(working_set, head.span);
|
|
|
|
error = error.or(err);
|
|
|
|
|
2021-09-09 23:47:20 +02:00
|
|
|
tokens.next();
|
|
|
|
|
|
|
|
(out, true)
|
|
|
|
} else if let Some(var_id) = implicit_head {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: implicit head of full cell path");
|
2021-09-09 23:47:20 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Var(var_id),
|
2022-12-10 18:23:24 +01:00
|
|
|
span: head.span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-09 23:47:20 +02:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
)
|
2021-09-07 00:02:24 +02:00
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Mismatch(
|
|
|
|
"variable or subexpression".into(),
|
|
|
|
String::from_utf8_lossy(bytes).to_string(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-04-26 01:44:44 +02:00
|
|
|
let (tail, err) = parse_cell_path(
|
|
|
|
working_set,
|
|
|
|
tokens,
|
|
|
|
expect_dot,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
span,
|
|
|
|
);
|
2021-10-02 04:59:11 +02:00
|
|
|
error = error.or(err);
|
2021-09-07 00:02:24 +02:00
|
|
|
|
2022-12-06 18:51:55 +01:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
// FIXME: Get the type of the data at the tail using follow_cell_path() (or something)
|
|
|
|
ty: if !tail.is_empty() {
|
|
|
|
// Until the aforementioned fix is implemented, this is necessary to allow mutable list upserts
|
|
|
|
// such as $a.1 = 2 to work correctly.
|
|
|
|
Type::Any
|
|
|
|
} else {
|
|
|
|
head.ty.clone()
|
2021-11-08 00:18:00 +01:00
|
|
|
},
|
2022-12-06 18:51:55 +01:00
|
|
|
expr: Expr::FullCellPath(Box::new(FullCellPath { head, tail })),
|
|
|
|
span: full_cell_span,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
2021-09-07 00:02:24 +02:00
|
|
|
} else {
|
|
|
|
(garbage(span), error)
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2022-04-22 22:18:51 +02:00
|
|
|
pub fn parse_directory(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-05-01 20:37:20 +02:00
|
|
|
let (token, err) = unescape_unquote_string(bytes, span);
|
2022-04-22 22:18:51 +02:00
|
|
|
trace!("parsing: directory");
|
|
|
|
|
2022-05-01 20:37:20 +02:00
|
|
|
if err.is_none() {
|
2022-04-22 22:18:51 +02:00
|
|
|
trace!("-- found {}", token);
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Directory(token),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("directory".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-04 21:21:31 +02:00
|
|
|
pub fn parse_filepath(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-05-01 20:37:20 +02:00
|
|
|
let (token, err) = unescape_unquote_string(bytes, span);
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: filepath");
|
2021-10-04 21:21:31 +02:00
|
|
|
|
2022-05-01 20:37:20 +02:00
|
|
|
if err.is_none() {
|
2022-01-16 14:55:56 +01:00
|
|
|
trace!("-- found {}", token);
|
2021-10-04 21:21:31 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2022-01-16 14:55:56 +01:00
|
|
|
expr: Expr::Filepath(token),
|
2021-10-04 21:21:31 +02:00
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
2021-11-18 20:32:27 +01:00
|
|
|
Some(ParseError::Expected("filepath".into(), span)),
|
2021-10-04 21:21:31 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 03:02:48 +01:00
|
|
|
/// Parse a datetime type, eg '2022-02-02'
|
|
|
|
pub fn parse_datetime(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
trace!("parsing: datetime");
|
|
|
|
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-02-24 13:58:53 +01:00
|
|
|
|
|
|
|
if bytes.is_empty() || !bytes[0].is_ascii_digit() {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Mismatch(
|
|
|
|
"datetime".into(),
|
|
|
|
"non-datetime".into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-24 03:02:48 +01:00
|
|
|
let token = String::from_utf8_lossy(bytes).to_string();
|
|
|
|
|
|
|
|
if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&token) {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::DateTime(datetime),
|
|
|
|
span,
|
|
|
|
ty: Type::Date,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Just the date
|
2022-02-24 13:58:53 +01:00
|
|
|
let just_date = token.clone() + "T00:00:00+00:00";
|
|
|
|
if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&just_date) {
|
2022-02-24 03:02:48 +01:00
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::DateTime(datetime),
|
|
|
|
span,
|
|
|
|
ty: Type::Date,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Date and time, assume UTC
|
2022-02-24 13:58:53 +01:00
|
|
|
let datetime = token + "+00:00";
|
|
|
|
if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&datetime) {
|
2022-02-24 03:02:48 +01:00
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::DateTime(datetime),
|
|
|
|
span,
|
|
|
|
ty: Type::Date,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Mismatch(
|
|
|
|
"datetime".into(),
|
|
|
|
"non-datetime".into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-10-05 04:27:39 +02:00
|
|
|
/// Parse a duration type, eg '10day'
|
|
|
|
pub fn parse_duration(
|
2022-04-07 04:01:31 +02:00
|
|
|
working_set: &StateWorkingSet,
|
2021-10-05 04:27:39 +02:00
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: duration");
|
|
|
|
|
2022-03-03 14:16:04 +01:00
|
|
|
let bytes = working_set.get_span_contents(span);
|
|
|
|
|
|
|
|
match parse_duration_bytes(bytes, span) {
|
|
|
|
Some(expression) => (expression, None),
|
|
|
|
None => (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Mismatch(
|
|
|
|
"duration".into(),
|
|
|
|
"non-duration unit".into(),
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
|
|
|
|
pub fn modf(x: f64) -> (f64, f64) {
|
|
|
|
let rv2: f64;
|
|
|
|
let mut u = x.to_bits();
|
|
|
|
let e = ((u >> 52 & 0x7ff) as i32) - 0x3ff;
|
|
|
|
|
|
|
|
/* no fractional part */
|
|
|
|
if e >= 52 {
|
|
|
|
rv2 = x;
|
|
|
|
if e == 0x400 && (u << 12) != 0 {
|
|
|
|
/* nan */
|
|
|
|
return (x, rv2);
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
2022-09-29 20:24:17 +02:00
|
|
|
u &= 1 << 63;
|
|
|
|
return (f64::from_bits(u), rv2);
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
/* no integral part*/
|
|
|
|
if e < 0 {
|
|
|
|
u &= 1 << 63;
|
|
|
|
rv2 = f64::from_bits(u);
|
|
|
|
return (x, rv2);
|
2022-02-24 13:58:53 +01:00
|
|
|
}
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
let mask = ((!0) >> 12) >> e;
|
|
|
|
if (u & mask) == 0 {
|
|
|
|
rv2 = x;
|
|
|
|
u &= 1 << 63;
|
|
|
|
return (f64::from_bits(u), rv2);
|
|
|
|
}
|
|
|
|
u &= !mask;
|
|
|
|
rv2 = f64::from_bits(u);
|
|
|
|
(x - rv2, rv2)
|
|
|
|
}
|
2021-10-05 04:27:39 +02:00
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
pub fn parse_duration_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
|
|
|
|
if num_with_unit_bytes.is_empty()
|
|
|
|
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
|
|
|
|
{
|
|
|
|
return None;
|
|
|
|
}
|
2022-02-22 10:50:49 +01:00
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
|
|
|
|
let uppercase_num_with_unit = num_with_unit.to_uppercase();
|
2021-10-05 04:27:39 +02:00
|
|
|
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))),
|
|
|
|
];
|
2022-09-29 20:24:17 +02:00
|
|
|
|
|
|
|
if let Some(unit) = unit_groups
|
|
|
|
.iter()
|
|
|
|
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
|
|
|
|
{
|
|
|
|
let mut lhs = num_with_unit;
|
2021-10-05 04:27:39 +02:00
|
|
|
for _ in 0..unit.1.len() {
|
|
|
|
lhs.pop();
|
|
|
|
}
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(_) => return None,
|
|
|
|
});
|
|
|
|
|
|
|
|
let (num, unit_to_use) = match unit.2 {
|
|
|
|
Some(unit_to_convert_to) => (
|
|
|
|
Some(
|
|
|
|
((number_part * unit_to_convert_to.1 as f64)
|
|
|
|
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
|
|
|
|
),
|
|
|
|
unit_to_convert_to.0,
|
|
|
|
),
|
|
|
|
None => (Some(number_part as i64), unit.0),
|
2021-10-05 04:27:39 +02:00
|
|
|
};
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
if let Some(x) = num {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- found {} {:?}", x, unit_to_use);
|
|
|
|
|
2021-10-05 04:27:39 +02:00
|
|
|
let lhs_span = Span::new(span.start, span.start + lhs.len());
|
|
|
|
let unit_span = Span::new(span.start + lhs.len(), span.end);
|
2022-03-03 14:16:04 +01:00
|
|
|
return Some(Expression {
|
|
|
|
expr: Expr::ValueWithUnit(
|
|
|
|
Box::new(Expression {
|
|
|
|
expr: Expr::Int(x),
|
|
|
|
span: lhs_span,
|
|
|
|
ty: Type::Number,
|
|
|
|
custom_completion: None,
|
|
|
|
}),
|
|
|
|
Spanned {
|
|
|
|
item: unit_to_use,
|
|
|
|
span: unit_span,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
span,
|
|
|
|
ty: Type::Duration,
|
|
|
|
custom_completion: None,
|
|
|
|
});
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 14:16:04 +01:00
|
|
|
None
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse a unit type, eg '10kb'
|
|
|
|
pub fn parse_filesize(
|
2022-04-07 04:01:31 +02:00
|
|
|
working_set: &StateWorkingSet,
|
2021-10-05 04:27:39 +02:00
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-09-29 20:24:17 +02:00
|
|
|
trace!("parsing: filesize");
|
2021-10-05 04:27:39 +02:00
|
|
|
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-02-24 13:58:53 +01:00
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
match parse_filesize_bytes(bytes, span) {
|
|
|
|
Some(expression) => (expression, None),
|
|
|
|
None => (
|
2022-02-24 13:58:53 +01:00
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Mismatch(
|
|
|
|
"filesize".into(),
|
|
|
|
"non-filesize unit".into(),
|
|
|
|
span,
|
|
|
|
)),
|
2022-09-29 20:24:17 +02:00
|
|
|
),
|
2022-02-24 13:58:53 +01:00
|
|
|
}
|
2022-09-29 20:24:17 +02:00
|
|
|
}
|
2022-02-24 13:58:53 +01:00
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
pub fn parse_filesize_bytes(num_with_unit_bytes: &[u8], span: Span) -> Option<Expression> {
|
|
|
|
if num_with_unit_bytes.is_empty()
|
|
|
|
|| (!num_with_unit_bytes[0].is_ascii_digit() && num_with_unit_bytes[0] != b'-')
|
|
|
|
{
|
|
|
|
return None;
|
|
|
|
}
|
2022-02-22 10:50:49 +01:00
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
let num_with_unit = String::from_utf8_lossy(num_with_unit_bytes).to_string();
|
|
|
|
let uppercase_num_with_unit = num_with_unit.to_uppercase();
|
2021-10-05 04:27:39 +02:00
|
|
|
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))),
|
2022-07-26 15:05:37 +02:00
|
|
|
(Unit::Exabyte, "EB", Some((Unit::Petabyte, 1000))),
|
|
|
|
(Unit::Zettabyte, "ZB", Some((Unit::Exabyte, 1000))),
|
2021-10-05 04:27:39 +02:00
|
|
|
(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))),
|
2022-07-26 15:05:37 +02:00
|
|
|
(Unit::Exbibyte, "EIB", Some((Unit::Pebibyte, 1024))),
|
|
|
|
(Unit::Zebibyte, "ZIB", Some((Unit::Exbibyte, 1024))),
|
2021-10-05 04:27:39 +02:00
|
|
|
(Unit::Byte, "B", None),
|
|
|
|
];
|
2022-09-29 20:24:17 +02:00
|
|
|
|
|
|
|
if let Some(unit) = unit_groups
|
|
|
|
.iter()
|
|
|
|
.find(|&x| uppercase_num_with_unit.ends_with(x.1))
|
|
|
|
{
|
|
|
|
let mut lhs = num_with_unit;
|
2021-10-05 04:27:39 +02:00
|
|
|
for _ in 0..unit.1.len() {
|
|
|
|
lhs.pop();
|
|
|
|
}
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(_) => return None,
|
|
|
|
});
|
|
|
|
|
|
|
|
let (num, unit_to_use) = match unit.2 {
|
|
|
|
Some(unit_to_convert_to) => (
|
|
|
|
Some(
|
|
|
|
((number_part * unit_to_convert_to.1 as f64)
|
|
|
|
+ (decimal_part * unit_to_convert_to.1 as f64)) as i64,
|
|
|
|
),
|
|
|
|
unit_to_convert_to.0,
|
|
|
|
),
|
|
|
|
None => (Some(number_part as i64), unit.0),
|
2021-10-05 04:27:39 +02:00
|
|
|
};
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
if let Some(x) = num {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- found {} {:?}", x, unit_to_use);
|
|
|
|
|
2021-10-05 04:27:39 +02:00
|
|
|
let lhs_span = Span::new(span.start, span.start + lhs.len());
|
|
|
|
let unit_span = Span::new(span.start + lhs.len(), span.end);
|
2022-09-29 20:24:17 +02:00
|
|
|
return Some(Expression {
|
|
|
|
expr: Expr::ValueWithUnit(
|
|
|
|
Box::new(Expression {
|
|
|
|
expr: Expr::Int(x),
|
|
|
|
span: lhs_span,
|
|
|
|
ty: Type::Number,
|
|
|
|
custom_completion: None,
|
|
|
|
}),
|
|
|
|
Spanned {
|
|
|
|
item: unit_to_use,
|
|
|
|
span: unit_span,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
span,
|
|
|
|
ty: Type::Filesize,
|
|
|
|
custom_completion: None,
|
|
|
|
});
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-29 20:24:17 +02:00
|
|
|
None
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
|
|
|
|
2021-10-04 21:21:31 +02:00
|
|
|
pub fn parse_glob_pattern(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-05-01 20:37:20 +02:00
|
|
|
let (token, err) = unescape_unquote_string(bytes, span);
|
|
|
|
trace!("parsing: glob pattern");
|
2021-10-04 21:21:31 +02:00
|
|
|
|
2022-05-01 20:37:20 +02:00
|
|
|
if err.is_none() {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- found {}", token);
|
2021-10-04 21:21:31 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2022-01-16 14:55:56 +01:00
|
|
|
expr: Expr::GlobPattern(token),
|
2021-10-04 21:21:31 +02:00
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("string".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 19:14:03 +01:00
|
|
|
pub fn unescape_string(bytes: &[u8], span: Span) -> (Vec<u8>, Option<ParseError>) {
|
|
|
|
let mut output = Vec::new();
|
|
|
|
|
|
|
|
let mut idx = 0;
|
|
|
|
let mut err = None;
|
|
|
|
|
2023-01-28 21:25:53 +01:00
|
|
|
'us_loop: while idx < bytes.len() {
|
2022-03-03 19:14:03 +01:00
|
|
|
if bytes[idx] == b'\\' {
|
|
|
|
// We're in an escape
|
|
|
|
idx += 1;
|
|
|
|
|
|
|
|
match bytes.get(idx) {
|
|
|
|
Some(b'"') => {
|
|
|
|
output.push(b'"');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'\'') => {
|
|
|
|
output.push(b'\'');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'\\') => {
|
|
|
|
output.push(b'\\');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'/') => {
|
|
|
|
output.push(b'/');
|
|
|
|
idx += 1;
|
|
|
|
}
|
2022-03-07 23:39:16 +01:00
|
|
|
Some(b'(') => {
|
|
|
|
output.push(b'(');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b')') => {
|
|
|
|
output.push(b')');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'{') => {
|
|
|
|
output.push(b'{');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'}') => {
|
|
|
|
output.push(b'}');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'$') => {
|
|
|
|
output.push(b'$');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'^') => {
|
|
|
|
output.push(b'^');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'#') => {
|
|
|
|
output.push(b'#');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'|') => {
|
|
|
|
output.push(b'|');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'~') => {
|
|
|
|
output.push(b'~');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'a') => {
|
|
|
|
output.push(0x7);
|
|
|
|
idx += 1;
|
|
|
|
}
|
2022-03-03 19:14:03 +01:00
|
|
|
Some(b'b') => {
|
|
|
|
output.push(0x8);
|
|
|
|
idx += 1;
|
|
|
|
}
|
2022-03-07 23:39:16 +01:00
|
|
|
Some(b'e') => {
|
|
|
|
output.push(0x1b);
|
|
|
|
idx += 1;
|
|
|
|
}
|
2022-03-03 19:14:03 +01:00
|
|
|
Some(b'f') => {
|
|
|
|
output.push(0xc);
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'n') => {
|
|
|
|
output.push(b'\n');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'r') => {
|
|
|
|
output.push(b'\r');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b't') => {
|
|
|
|
output.push(b'\t');
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
Some(b'u') => {
|
2023-01-28 21:25:53 +01:00
|
|
|
let mut digits = String::with_capacity(10);
|
|
|
|
let mut cur_idx = idx + 1; // index of first beyond current end of token
|
|
|
|
|
|
|
|
if let Some(b'{') = bytes.get(idx + 1) {
|
|
|
|
cur_idx = idx + 2;
|
|
|
|
loop {
|
|
|
|
match bytes.get(cur_idx) {
|
|
|
|
Some(b'}') => {
|
|
|
|
cur_idx += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Some(c) => {
|
|
|
|
digits.push(*c as char);
|
|
|
|
cur_idx += 1;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
err = Some(ParseError::Expected(
|
|
|
|
"closing '}' in unicode escape `\\u{n..}`".into(),
|
|
|
|
Span::new(span.start + idx, span.end),
|
|
|
|
));
|
|
|
|
break 'us_loop;
|
2022-03-03 19:14:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-28 21:25:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (1..=6).contains(&digits.len()) {
|
|
|
|
let int = u32::from_str_radix(&digits, 16);
|
|
|
|
|
|
|
|
if let Ok(int) = int {
|
|
|
|
if int <= 0x10ffff {
|
|
|
|
let result = char::from_u32(int);
|
|
|
|
|
|
|
|
if let Some(result) = result {
|
|
|
|
let mut buffer = vec![0; 4];
|
|
|
|
let result = result.encode_utf8(&mut buffer);
|
|
|
|
|
|
|
|
for elem in result.bytes() {
|
|
|
|
output.push(elem);
|
|
|
|
}
|
|
|
|
|
|
|
|
idx = cur_idx;
|
|
|
|
continue 'us_loop;
|
|
|
|
}
|
|
|
|
}
|
2022-03-03 19:14:03 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-28 21:25:53 +01:00
|
|
|
// fall through -- escape not accepted above, must be error.
|
|
|
|
err = Some(ParseError::Expected(
|
|
|
|
"unicode escape \\u{n..}".into(),
|
|
|
|
Span::new(span.start + idx, span.end),
|
|
|
|
));
|
|
|
|
break 'us_loop;
|
2022-03-03 19:14:03 +01:00
|
|
|
}
|
2023-01-28 21:25:53 +01:00
|
|
|
|
2022-03-03 19:14:03 +01:00
|
|
|
_ => {
|
|
|
|
err = Some(ParseError::Expected(
|
|
|
|
"supported escape character".into(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(span.start + idx, span.end),
|
2022-03-03 19:14:03 +01:00
|
|
|
));
|
2023-01-28 21:25:53 +01:00
|
|
|
break 'us_loop;
|
2022-03-03 19:14:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
output.push(bytes[idx]);
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(output, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn unescape_unquote_string(bytes: &[u8], span: Span) -> (String, Option<ParseError>) {
|
|
|
|
if bytes.starts_with(b"\"") {
|
|
|
|
// Needs unescaping
|
|
|
|
let bytes = trim_quotes(bytes);
|
|
|
|
|
|
|
|
let (bytes, err) = unescape_string(bytes, span);
|
|
|
|
|
|
|
|
if let Ok(token) = String::from_utf8(bytes) {
|
|
|
|
(token, err)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
String::new(),
|
|
|
|
Some(ParseError::Expected("string".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let bytes = trim_quotes(bytes);
|
|
|
|
|
|
|
|
if let Ok(token) = String::from_utf8(bytes.into()) {
|
|
|
|
(token, None)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
String::new(),
|
|
|
|
Some(ParseError::Expected("string".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_string(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-04-26 01:44:44 +02:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: string");
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-01-19 15:58:12 +01:00
|
|
|
|
2022-04-26 01:44:44 +02:00
|
|
|
// Check for bare word interpolation
|
|
|
|
if bytes[0] != b'\'' && bytes[0] != b'"' && bytes[0] != b'`' && bytes.contains(&b'(') {
|
|
|
|
return parse_string_interpolation(working_set, span, expand_aliases_denylist);
|
|
|
|
}
|
|
|
|
|
2022-03-03 19:14:03 +01:00
|
|
|
let (s, err) = unescape_unquote_string(bytes, span);
|
2021-07-16 22:26:40 +02:00
|
|
|
|
2022-03-03 19:14:03 +01:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::String(s),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
err,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-02 09:15:30 +02:00
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
pub fn parse_string_strict(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: string, with required delimiters");
|
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
let bytes = working_set.get_span_contents(span);
|
2022-01-19 15:58:12 +01:00
|
|
|
|
|
|
|
// Check for unbalanced quotes:
|
2022-03-24 17:57:03 +01:00
|
|
|
{
|
|
|
|
let bytes = if bytes.starts_with(b"$") {
|
|
|
|
&bytes[1..]
|
|
|
|
} else {
|
|
|
|
bytes
|
|
|
|
};
|
|
|
|
if bytes.starts_with(b"\"") && (bytes.len() == 1 || !bytes.ends_with(b"\"")) {
|
|
|
|
return (garbage(span), Some(ParseError::Unclosed("\"".into(), span)));
|
|
|
|
}
|
|
|
|
if bytes.starts_with(b"\'") && (bytes.len() == 1 || !bytes.ends_with(b"\'")) {
|
|
|
|
return (garbage(span), Some(ParseError::Unclosed("\'".into(), span)));
|
|
|
|
}
|
2022-01-19 15:58:12 +01:00
|
|
|
}
|
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
|
|
|
|
|| (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
|
|
|
|
{
|
|
|
|
(&bytes[1..(bytes.len() - 1)], true)
|
2022-03-24 17:57:03 +01:00
|
|
|
} else if (bytes.starts_with(b"$\"") && bytes.ends_with(b"\"") && bytes.len() > 2)
|
|
|
|
|| (bytes.starts_with(b"$\'") && bytes.ends_with(b"\'") && bytes.len() > 2)
|
|
|
|
{
|
|
|
|
(&bytes[2..(bytes.len() - 1)], true)
|
2021-11-04 03:32:35 +01:00
|
|
|
} else {
|
|
|
|
(bytes, false)
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(token) = String::from_utf8(bytes.into()) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("-- found {}", token);
|
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
if quoted {
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::String(token),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else if token.contains(' ') {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("string".into(), span)),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::String(token),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("string".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-12 19:44:23 +02:00
|
|
|
//TODO: Handle error case for unknown shapes
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_shape_name(
|
2022-02-11 19:38:10 +01:00
|
|
|
working_set: &StateWorkingSet,
|
2021-09-02 10:25:22 +02:00
|
|
|
bytes: &[u8],
|
|
|
|
span: Span,
|
|
|
|
) -> (SyntaxShape, Option<ParseError>) {
|
|
|
|
let result = match bytes {
|
|
|
|
b"any" => SyntaxShape::Any,
|
2022-03-01 00:31:53 +01:00
|
|
|
b"binary" => SyntaxShape::Binary,
|
2022-11-10 09:21:49 +01:00
|
|
|
b"block" => SyntaxShape::Block, //FIXME: Blocks should have known output types
|
2022-12-13 17:46:22 +01:00
|
|
|
b"bool" => SyntaxShape::Boolean,
|
2021-09-07 00:02:24 +02:00
|
|
|
b"cell-path" => SyntaxShape::CellPath,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"closure" => SyntaxShape::Closure(None), //FIXME: Blocks should have known output types
|
|
|
|
b"cond" => SyntaxShape::RowCondition,
|
|
|
|
// b"custom" => SyntaxShape::Custom(Box::new(SyntaxShape::Any), SyntaxShape::Int),
|
|
|
|
b"datetime" => SyntaxShape::DateTime,
|
2022-04-22 22:18:51 +02:00
|
|
|
b"directory" => SyntaxShape::Directory,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"duration" => SyntaxShape::Duration,
|
|
|
|
b"error" => SyntaxShape::Error,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"expr" => SyntaxShape::Expression,
|
2023-01-25 13:43:22 +01:00
|
|
|
b"float" | b"decimal" => SyntaxShape::Decimal,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"filesize" => SyntaxShape::Filesize,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"full-cell-path" => SyntaxShape::FullCellPath,
|
2021-09-02 10:25:22 +02:00
|
|
|
b"glob" => SyntaxShape::GlobPattern,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"int" => SyntaxShape::Int,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"import-pattern" => SyntaxShape::ImportPattern,
|
|
|
|
b"keyword" => SyntaxShape::Keyword(vec![], Box::new(SyntaxShape::Any)),
|
|
|
|
b"list" => SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
2021-09-02 10:25:22 +02:00
|
|
|
b"math" => SyntaxShape::MathExpression,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"nothing" => SyntaxShape::Nothing,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"number" => SyntaxShape::Number,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"one-of" => SyntaxShape::OneOf(vec![]),
|
2022-01-12 16:59:07 +01:00
|
|
|
b"operator" => SyntaxShape::Operator,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"path" => SyntaxShape::Filepath,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"range" => SyntaxShape::Range,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"record" => SyntaxShape::Record,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"signature" => SyntaxShape::Signature,
|
|
|
|
b"string" => SyntaxShape::String,
|
2022-03-31 10:11:03 +02:00
|
|
|
b"table" => SyntaxShape::Table,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"variable" => SyntaxShape::Variable,
|
|
|
|
b"var-with-opt-type" => SyntaxShape::VarWithOptType,
|
2022-02-11 19:38:10 +01:00
|
|
|
_ => {
|
|
|
|
if bytes.contains(&b'@') {
|
|
|
|
let str = String::from_utf8_lossy(bytes);
|
|
|
|
let split: Vec<_> = str.split('@').collect();
|
|
|
|
let (shape, err) = parse_shape_name(
|
|
|
|
working_set,
|
|
|
|
split[0].as_bytes(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(span.start, span.start + split[0].len()),
|
2022-02-11 19:38:10 +01:00
|
|
|
);
|
|
|
|
let command_name = trim_quotes(split[1].as_bytes());
|
|
|
|
|
2022-06-10 17:59:35 +02:00
|
|
|
let decl_id = working_set.find_decl(command_name, &Type::Any);
|
2022-03-10 08:49:02 +01:00
|
|
|
|
|
|
|
if let Some(decl_id) = decl_id {
|
|
|
|
return (SyntaxShape::Custom(Box::new(shape), decl_id), err);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
shape,
|
2022-12-03 10:44:12 +01:00
|
|
|
Some(ParseError::UnknownCommand(Span::new(
|
|
|
|
span.start + split[0].len() + 1,
|
|
|
|
span.end,
|
|
|
|
))),
|
2022-03-10 08:49:02 +01:00
|
|
|
);
|
|
|
|
}
|
2022-02-11 19:38:10 +01:00
|
|
|
} else {
|
|
|
|
return (SyntaxShape::Any, Some(ParseError::UnknownType(span)));
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
(result, None)
|
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2021-09-04 09:59:38 +02:00
|
|
|
pub fn parse_type(_working_set: &StateWorkingSet, bytes: &[u8]) -> Type {
|
2021-10-12 06:49:17 +02:00
|
|
|
match bytes {
|
2022-12-13 17:46:22 +01:00
|
|
|
b"binary" => Type::Binary,
|
2021-10-12 06:49:17 +02:00
|
|
|
b"block" => Type::Block,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"bool" => Type::Bool,
|
|
|
|
b"cellpath" => Type::CellPath,
|
|
|
|
b"closure" => Type::Closure,
|
2022-01-12 16:59:07 +01:00
|
|
|
b"date" => Type::Date,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"duration" => Type::Duration,
|
|
|
|
b"error" => Type::Error,
|
2021-10-12 06:49:17 +02:00
|
|
|
b"filesize" => Type::Filesize,
|
2023-01-25 13:43:22 +01:00
|
|
|
b"float" | b"decimal" => Type::Float,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"int" => Type::Int,
|
|
|
|
b"list" => Type::List(Box::new(Type::Any)),
|
2022-01-12 16:59:07 +01:00
|
|
|
b"number" => Type::Number,
|
2022-12-13 17:46:22 +01:00
|
|
|
b"range" => Type::Range,
|
|
|
|
b"record" => Type::Record(vec![]),
|
|
|
|
b"string" => Type::String,
|
2022-04-07 06:34:09 +02:00
|
|
|
b"table" => Type::Table(vec![]), //FIXME
|
2021-10-12 06:49:17 +02:00
|
|
|
|
2022-04-07 06:34:09 +02:00
|
|
|
_ => Type::Any,
|
2021-07-16 08:24:46 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2021-09-26 20:39:19 +02:00
|
|
|
pub fn parse_import_pattern(
|
2021-09-27 02:23:22 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
2021-10-26 23:06:08 +02:00
|
|
|
spans: &[Span],
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-01-10 02:39:25 +01:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2021-09-26 20:39:19 +02:00
|
|
|
let mut error = None;
|
|
|
|
|
2022-12-21 23:21:03 +01:00
|
|
|
let head_span = if let Some(head_span) = spans.get(0) {
|
|
|
|
head_span
|
2021-10-19 22:38:49 +02:00
|
|
|
} else {
|
2021-09-26 20:39:19 +02:00
|
|
|
return (
|
2022-01-10 02:39:25 +01:00
|
|
|
garbage(span(spans)),
|
2021-10-26 23:06:08 +02:00
|
|
|
Some(ParseError::WrongImportPattern(span(spans))),
|
2021-09-26 20:39:19 +02:00
|
|
|
);
|
2021-10-26 23:06:08 +02:00
|
|
|
};
|
2021-09-26 20:39:19 +02:00
|
|
|
|
2022-12-21 23:21:03 +01:00
|
|
|
let (head_expr, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
*head_span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let (maybe_module_id, head_name) = match eval_constant(working_set, &head_expr) {
|
|
|
|
Ok(val) => match value_as_string(val, head_expr.span) {
|
|
|
|
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
|
|
|
Err(err) => {
|
|
|
|
return (garbage(span(spans)), error.or(Some(err)));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(err) => {
|
|
|
|
return (garbage(span(spans)), error.or(Some(err)));
|
|
|
|
}
|
|
|
|
};
|
2022-02-18 02:58:24 +01:00
|
|
|
|
2022-01-10 02:39:25 +01:00
|
|
|
let (import_pattern, err) = if let Some(tail_span) = spans.get(1) {
|
2021-09-26 20:39:19 +02:00
|
|
|
// FIXME: expand this to handle deeper imports once we support module imports
|
2021-10-26 23:06:08 +02:00
|
|
|
let tail = working_set.get_span_contents(*tail_span);
|
2021-09-26 20:39:19 +02:00
|
|
|
if tail == b"*" {
|
|
|
|
(
|
|
|
|
ImportPattern {
|
2021-11-16 00:16:06 +01:00
|
|
|
head: ImportPatternHead {
|
2022-12-21 23:21:03 +01:00
|
|
|
name: head_name,
|
2022-05-07 21:39:22 +02:00
|
|
|
id: maybe_module_id,
|
2021-11-16 00:16:06 +01:00
|
|
|
span: *head_span,
|
|
|
|
},
|
2021-10-26 23:06:08 +02:00
|
|
|
members: vec![ImportPatternMember::Glob { span: *tail_span }],
|
2021-11-30 07:14:05 +01:00
|
|
|
hidden: HashSet::new(),
|
2021-09-26 20:39:19 +02:00
|
|
|
},
|
2022-01-10 02:39:25 +01:00
|
|
|
None,
|
2021-09-26 20:39:19 +02:00
|
|
|
)
|
2021-09-27 02:23:22 +02:00
|
|
|
} else if tail.starts_with(b"[") {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (result, err) = parse_list_expression(
|
|
|
|
working_set,
|
|
|
|
*tail_span,
|
|
|
|
&SyntaxShape::String,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-09-27 02:23:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let mut output = vec![];
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::List(list),
|
|
|
|
..
|
|
|
|
} => {
|
2022-07-29 10:57:10 +02:00
|
|
|
for expr in list {
|
|
|
|
let contents = working_set.get_span_contents(expr.span);
|
|
|
|
output.push((trim_quotes(contents).to_vec(), expr.span));
|
2021-09-27 02:23:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
ImportPattern {
|
2021-11-16 00:16:06 +01:00
|
|
|
head: ImportPatternHead {
|
2022-12-21 23:21:03 +01:00
|
|
|
name: head_name,
|
2022-05-07 21:39:22 +02:00
|
|
|
id: maybe_module_id,
|
2021-11-16 00:16:06 +01:00
|
|
|
span: *head_span,
|
|
|
|
},
|
2021-09-27 02:23:22 +02:00
|
|
|
members: vec![ImportPatternMember::List { names: output }],
|
2021-11-30 07:14:05 +01:00
|
|
|
hidden: HashSet::new(),
|
2021-09-27 02:23:22 +02:00
|
|
|
},
|
2022-01-10 02:39:25 +01:00
|
|
|
None,
|
2021-09-27 02:23:22 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
_ => (
|
|
|
|
ImportPattern {
|
2021-11-16 00:16:06 +01:00
|
|
|
head: ImportPatternHead {
|
2022-12-21 23:21:03 +01:00
|
|
|
name: head_name,
|
2022-05-07 21:39:22 +02:00
|
|
|
id: maybe_module_id,
|
2021-11-16 00:16:06 +01:00
|
|
|
span: *head_span,
|
|
|
|
},
|
2021-09-27 02:23:22 +02:00
|
|
|
members: vec![],
|
2021-11-30 07:14:05 +01:00
|
|
|
hidden: HashSet::new(),
|
2021-09-27 02:23:22 +02:00
|
|
|
},
|
|
|
|
Some(ParseError::ExportNotFound(result.span)),
|
|
|
|
),
|
|
|
|
}
|
2021-09-26 20:39:19 +02:00
|
|
|
} else {
|
2021-11-14 20:40:26 +01:00
|
|
|
let tail = trim_quotes(tail);
|
2021-09-26 20:39:19 +02:00
|
|
|
(
|
|
|
|
ImportPattern {
|
2021-11-16 00:16:06 +01:00
|
|
|
head: ImportPatternHead {
|
2022-12-21 23:21:03 +01:00
|
|
|
name: head_name,
|
2022-05-07 21:39:22 +02:00
|
|
|
id: maybe_module_id,
|
2021-11-16 00:16:06 +01:00
|
|
|
span: *head_span,
|
|
|
|
},
|
2021-09-26 20:39:19 +02:00
|
|
|
members: vec![ImportPatternMember::Name {
|
|
|
|
name: tail.to_vec(),
|
2021-10-26 23:06:08 +02:00
|
|
|
span: *tail_span,
|
2021-09-26 20:39:19 +02:00
|
|
|
}],
|
2021-11-30 07:14:05 +01:00
|
|
|
hidden: HashSet::new(),
|
2021-09-26 20:39:19 +02:00
|
|
|
},
|
2022-01-10 02:39:25 +01:00
|
|
|
None,
|
2021-09-26 20:39:19 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
ImportPattern {
|
2021-11-16 00:16:06 +01:00
|
|
|
head: ImportPatternHead {
|
2022-12-21 23:21:03 +01:00
|
|
|
name: head_name,
|
2022-05-07 21:39:22 +02:00
|
|
|
id: maybe_module_id,
|
2021-11-16 00:16:06 +01:00
|
|
|
span: *head_span,
|
|
|
|
},
|
2021-09-26 20:39:19 +02:00
|
|
|
members: vec![],
|
2021-11-30 07:14:05 +01:00
|
|
|
hidden: HashSet::new(),
|
2021-09-26 20:39:19 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
2022-01-10 02:39:25 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::ImportPattern(import_pattern),
|
|
|
|
span: span(&spans[1..]),
|
|
|
|
ty: Type::List(Box::new(Type::String)),
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error.or(err),
|
|
|
|
)
|
2021-09-26 20:39:19 +02:00
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_var_with_opt_type(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
spans_idx: &mut usize,
|
2022-11-11 07:51:08 +01:00
|
|
|
mutable: bool,
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec();
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2022-04-04 22:42:26 +02:00
|
|
|
if bytes.contains(&b' ')
|
|
|
|
|| bytes.contains(&b'"')
|
|
|
|
|| bytes.contains(&b'\'')
|
|
|
|
|| bytes.contains(&b'`')
|
|
|
|
{
|
2021-10-12 07:08:55 +02:00
|
|
|
return (
|
|
|
|
garbage(spans[*spans_idx]),
|
|
|
|
Some(ParseError::VariableNotValid(spans[*spans_idx])),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if bytes.ends_with(b":") {
|
|
|
|
// We end with colon, so the next span should be the type
|
|
|
|
if *spans_idx + 1 < spans.len() {
|
|
|
|
*spans_idx += 1;
|
|
|
|
let type_bytes = working_set.get_span_contents(spans[*spans_idx]);
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let ty = parse_type(working_set, type_bytes);
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2022-07-27 04:08:54 +02:00
|
|
|
let var_name = bytes[0..(bytes.len() - 1)].to_vec();
|
|
|
|
|
|
|
|
if !is_variable(&var_name) {
|
|
|
|
return (
|
|
|
|
garbage(spans[*spans_idx]),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"valid variable name".into(),
|
|
|
|
spans[*spans_idx],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone(), mutable);
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2021-10-25 22:04:23 +02:00
|
|
|
expr: Expr::VarDecl(id),
|
2021-09-02 10:25:22 +02:00
|
|
|
span: span(&spans[*spans_idx - 1..*spans_idx + 1]),
|
|
|
|
ty,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
2021-07-16 08:24:46 +02:00
|
|
|
} else {
|
2022-07-27 04:08:54 +02:00
|
|
|
let var_name = bytes[0..(bytes.len() - 1)].to_vec();
|
|
|
|
|
|
|
|
if !is_variable(&var_name) {
|
|
|
|
return (
|
|
|
|
garbage(spans[*spans_idx]),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"valid variable name".into(),
|
|
|
|
spans[*spans_idx],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any, mutable);
|
2021-07-16 08:24:46 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2021-10-25 22:04:23 +02:00
|
|
|
expr: Expr::VarDecl(id),
|
2021-09-02 10:25:22 +02:00
|
|
|
span: spans[*spans_idx],
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-07-16 08:24:46 +02:00
|
|
|
},
|
2021-09-02 10:25:22 +02:00
|
|
|
Some(ParseError::MissingType(spans[*spans_idx])),
|
2021-07-16 08:24:46 +02:00
|
|
|
)
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2022-07-27 04:08:54 +02:00
|
|
|
let var_name = bytes;
|
|
|
|
|
|
|
|
if !is_variable(&var_name) {
|
|
|
|
return (
|
|
|
|
garbage(spans[*spans_idx]),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"valid variable name".into(),
|
|
|
|
spans[*spans_idx],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let id = working_set.add_variable(
|
|
|
|
var_name,
|
|
|
|
span(&spans[*spans_idx..*spans_idx + 1]),
|
|
|
|
Type::Any,
|
2022-11-11 07:51:08 +01:00
|
|
|
mutable,
|
2022-07-27 04:08:54 +02:00
|
|
|
);
|
2021-07-08 00:55:46 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2021-10-25 22:04:23 +02:00
|
|
|
expr: Expr::VarDecl(id),
|
2021-09-02 10:25:22 +02:00
|
|
|
span: span(&spans[*spans_idx..*spans_idx + 1]),
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-09-09 23:47:20 +02:00
|
|
|
|
|
|
|
pub fn expand_to_cell_path(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
expression: &mut Expression,
|
|
|
|
var_id: VarId,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-09 23:47:20 +02:00
|
|
|
) {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: expanding to cell path");
|
2021-09-09 23:47:20 +02:00
|
|
|
if let Expression {
|
|
|
|
expr: Expr::String(_),
|
|
|
|
span,
|
|
|
|
..
|
|
|
|
} = expression
|
|
|
|
{
|
|
|
|
// Re-parse the string as if it were a cell-path
|
2022-03-18 20:03:57 +01:00
|
|
|
let (new_expression, _err) =
|
|
|
|
parse_full_cell_path(working_set, Some(var_id), *span, expand_aliases_denylist);
|
2021-09-09 23:47:20 +02:00
|
|
|
|
|
|
|
*expression = new_expression;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_row_condition(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-11-11 07:51:08 +01:00
|
|
|
let var_id = working_set.add_variable(b"$it".to_vec(), span(spans), Type::Any, false);
|
2022-03-18 20:03:57 +01:00
|
|
|
let (expression, err) =
|
|
|
|
parse_math_expression(working_set, spans, Some(var_id), expand_aliases_denylist);
|
2021-09-09 23:47:20 +02:00
|
|
|
let span = span(spans);
|
2021-11-26 04:49:03 +01:00
|
|
|
|
|
|
|
let block_id = match expression.expr {
|
|
|
|
Expr::Block(block_id) => block_id,
|
2022-11-10 09:21:49 +01:00
|
|
|
Expr::Closure(block_id) => block_id,
|
2021-11-26 04:49:03 +01:00
|
|
|
_ => {
|
|
|
|
// We have an expression, so let's convert this into a block.
|
|
|
|
let mut block = Block::new();
|
|
|
|
let mut pipeline = Pipeline::new();
|
2022-11-18 22:46:48 +01:00
|
|
|
pipeline
|
|
|
|
.elements
|
2022-11-22 19:26:13 +01:00
|
|
|
.push(PipelineElement::Expression(None, expression));
|
2021-11-26 04:49:03 +01:00
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
block.pipelines.push(pipeline);
|
2021-11-26 04:49:03 +01:00
|
|
|
|
|
|
|
block.signature.required_positional.push(PositionalArg {
|
|
|
|
name: "$it".into(),
|
|
|
|
desc: "row condition".into(),
|
|
|
|
shape: SyntaxShape::Any,
|
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-11-26 04:49:03 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
working_set.add_block(block)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-09-09 23:47:20 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
ty: Type::Bool,
|
|
|
|
span,
|
2021-11-26 04:49:03 +01:00
|
|
|
expr: Expr::RowCondition(block_id),
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-09 23:47:20 +02:00
|
|
|
},
|
|
|
|
err,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_signature(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut error = None;
|
|
|
|
let mut start = span.start;
|
|
|
|
let mut end = span.end;
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2022-08-30 06:17:10 +02:00
|
|
|
let mut has_paren = false;
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if bytes.starts_with(b"[") {
|
|
|
|
start += 1;
|
2022-08-30 06:17:10 +02:00
|
|
|
} else if bytes.starts_with(b"(") {
|
|
|
|
has_paren = true;
|
|
|
|
start += 1;
|
2021-10-11 22:58:38 +02:00
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-08-30 06:17:10 +02:00
|
|
|
"[ or (".into(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(start, start + 1),
|
2021-10-11 22:58:38 +02:00
|
|
|
))
|
|
|
|
});
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-10-11 22:58:38 +02:00
|
|
|
|
2022-08-30 06:17:10 +02:00
|
|
|
if (has_paren && bytes.ends_with(b")")) || (!has_paren && bytes.ends_with(b"]")) {
|
2021-09-02 10:25:22 +02:00
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("] or )".into(), Span::new(end, end))));
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (sig, err) =
|
2022-12-03 10:44:12 +01:00
|
|
|
parse_signature_helper(working_set, Span::new(start, end), expand_aliases_denylist);
|
2021-09-06 01:16:27 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Signature(sig),
|
|
|
|
span,
|
2021-12-27 20:13:52 +01:00
|
|
|
ty: Type::Signature,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-06 01:16:27 +02:00
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_signature_helper(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-06 01:16:27 +02:00
|
|
|
) -> (Box<Signature>, Option<ParseError>) {
|
2022-03-07 21:08:56 +01:00
|
|
|
#[allow(clippy::enum_variant_names)]
|
2021-09-06 01:16:27 +02:00
|
|
|
enum ParseMode {
|
|
|
|
ArgMode,
|
2022-12-31 12:18:53 +01:00
|
|
|
AfterCommaArgMode,
|
2021-09-06 01:16:27 +02:00
|
|
|
TypeMode,
|
2022-03-07 21:08:56 +01:00
|
|
|
DefaultValueMode,
|
2021-09-06 01:16:27 +02:00
|
|
|
}
|
|
|
|
|
2022-03-07 21:08:56 +01:00
|
|
|
#[derive(Debug)]
|
2021-09-06 01:16:27 +02:00
|
|
|
enum Arg {
|
|
|
|
Positional(PositionalArg, bool), // bool - required
|
2022-03-07 17:44:27 +01:00
|
|
|
RestPositional(PositionalArg),
|
2021-09-06 01:16:27 +02:00
|
|
|
Flag(Flag),
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut error = None;
|
2021-09-02 10:25:22 +02:00
|
|
|
let source = working_set.get_span_contents(span);
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2022-03-07 21:08:56 +01:00
|
|
|
let (output, err) = lex(
|
|
|
|
source,
|
|
|
|
span.start,
|
2022-12-31 12:18:53 +01:00
|
|
|
&[b'\n', b'\r'],
|
|
|
|
&[b':', b'=', b','],
|
2022-03-07 21:08:56 +01:00
|
|
|
false,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut args: Vec<Arg> = vec![];
|
|
|
|
let mut parse_mode = ParseMode::ArgMode;
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
for token in &output {
|
|
|
|
match token {
|
|
|
|
Token {
|
|
|
|
contents: crate::TokenContents::Item,
|
|
|
|
span,
|
|
|
|
} => {
|
|
|
|
let span = *span;
|
|
|
|
let contents = working_set.get_span_contents(span);
|
|
|
|
|
2022-12-28 00:00:44 +01:00
|
|
|
// The : symbol separates types
|
2021-09-02 10:25:22 +02:00
|
|
|
if contents == b":" {
|
|
|
|
match parse_mode {
|
|
|
|
ParseMode::ArgMode => {
|
|
|
|
parse_mode = ParseMode::TypeMode;
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2022-12-31 12:18:53 +01:00
|
|
|
ParseMode::AfterCommaArgMode => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("parameter or flag".into(), span))
|
|
|
|
});
|
|
|
|
}
|
2022-03-07 21:08:56 +01:00
|
|
|
ParseMode::TypeMode | ParseMode::DefaultValueMode => {
|
2021-09-02 10:25:22 +02:00
|
|
|
// We're seeing two types for the same thing for some reason, error
|
|
|
|
error =
|
|
|
|
error.or_else(|| Some(ParseError::Expected("type".into(), span)));
|
|
|
|
}
|
|
|
|
}
|
2022-12-28 00:00:44 +01:00
|
|
|
}
|
|
|
|
// The = symbol separates a variable from its default value
|
|
|
|
else if contents == b"=" {
|
2022-03-07 21:08:56 +01:00
|
|
|
match parse_mode {
|
|
|
|
ParseMode::ArgMode | ParseMode::TypeMode => {
|
|
|
|
parse_mode = ParseMode::DefaultValueMode;
|
|
|
|
}
|
2022-12-31 12:18:53 +01:00
|
|
|
ParseMode::AfterCommaArgMode => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("parameter or flag".into(), span))
|
|
|
|
});
|
|
|
|
}
|
2022-03-07 21:08:56 +01:00
|
|
|
ParseMode::DefaultValueMode => {
|
|
|
|
// We're seeing two default values for some reason, error
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("default value".into(), span))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-12-31 12:18:53 +01:00
|
|
|
}
|
|
|
|
// The , symbol separates params only
|
|
|
|
else if contents == b"," {
|
|
|
|
match parse_mode {
|
|
|
|
ParseMode::ArgMode => parse_mode = ParseMode::AfterCommaArgMode,
|
|
|
|
ParseMode::AfterCommaArgMode => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("parameter or flag".into(), span))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
ParseMode::TypeMode => {
|
|
|
|
error =
|
|
|
|
error.or_else(|| Some(ParseError::Expected("type".into(), span)));
|
|
|
|
}
|
|
|
|
ParseMode::DefaultValueMode => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("default value".into(), span))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
match parse_mode {
|
2022-12-31 12:18:53 +01:00
|
|
|
ParseMode::ArgMode | ParseMode::AfterCommaArgMode => {
|
|
|
|
// Long flag with optional short form following with no whitespace, e.g. --output, --age(-a)
|
2021-09-02 10:25:22 +02:00
|
|
|
if contents.starts_with(b"--") && contents.len() > 2 {
|
2022-12-28 00:00:44 +01:00
|
|
|
// Split the long flag from the short flag with the ( character as delimiter.
|
|
|
|
// The trailing ) is removed further down.
|
2021-09-02 10:25:22 +02:00
|
|
|
let flags: Vec<_> =
|
|
|
|
contents.split(|x| x == &b'(').map(|x| x.to_vec()).collect();
|
|
|
|
|
2021-10-13 19:53:27 +02:00
|
|
|
let long = String::from_utf8_lossy(&flags[0][2..]).to_string();
|
2022-07-27 04:08:54 +02:00
|
|
|
let mut variable_name = flags[0][2..].to_vec();
|
|
|
|
// Replace the '-' in a variable name with '_'
|
|
|
|
(0..variable_name.len()).for_each(|idx| {
|
|
|
|
if variable_name[idx] == b'-' {
|
|
|
|
variable_name[idx] = b'_';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if !is_variable(&variable_name) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-12-31 12:18:53 +01:00
|
|
|
"valid variable name for this long flag".into(),
|
2022-07-27 04:08:54 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-09 10:42:19 +01:00
|
|
|
let var_id =
|
2022-11-11 07:51:08 +01:00
|
|
|
working_set.add_variable(variable_name, span, Type::Any, false);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-12-28 00:00:44 +01:00
|
|
|
// If there's no short flag, exit now. Otherwise, parse it.
|
2021-09-02 10:25:22 +02:00
|
|
|
if flags.len() == 1 {
|
|
|
|
args.push(Arg::Flag(Flag {
|
|
|
|
arg: None,
|
|
|
|
desc: String::new(),
|
|
|
|
long,
|
|
|
|
short: None,
|
|
|
|
required: false,
|
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
}));
|
2022-07-10 10:32:52 +02:00
|
|
|
} else if flags.len() >= 3 {
|
|
|
|
error = error.or_else(|| {
|
2022-12-28 00:00:44 +01:00
|
|
|
Some(ParseError::Expected(
|
|
|
|
"only one short flag alternative".into(),
|
|
|
|
span,
|
|
|
|
))
|
2022-07-10 10:32:52 +02:00
|
|
|
});
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
let short_flag = &flags[1];
|
|
|
|
let short_flag = if !short_flag.starts_with(b"-")
|
|
|
|
|| !short_flag.ends_with(b")")
|
|
|
|
{
|
|
|
|
error = error.or_else(|| {
|
2022-12-28 00:00:44 +01:00
|
|
|
Some(ParseError::Expected(
|
|
|
|
"short flag alternative for the long flag".into(),
|
|
|
|
span,
|
|
|
|
))
|
2021-09-02 10:25:22 +02:00
|
|
|
});
|
|
|
|
short_flag
|
2021-07-17 20:52:50 +02:00
|
|
|
} else {
|
2022-12-28 00:00:44 +01:00
|
|
|
// Obtain the flag's name by removing the starting - and trailing )
|
2021-09-02 10:25:22 +02:00
|
|
|
&short_flag[1..(short_flag.len() - 1)]
|
|
|
|
};
|
2022-12-28 00:00:44 +01:00
|
|
|
// Note that it is currently possible to make a short flag with non-alphanumeric characters,
|
|
|
|
// like -).
|
2021-07-17 00:39:30 +02:00
|
|
|
|
|
|
|
let short_flag =
|
|
|
|
String::from_utf8_lossy(short_flag).to_string();
|
|
|
|
let chars: Vec<char> = short_flag.chars().collect();
|
2021-10-13 19:53:27 +02:00
|
|
|
let long = String::from_utf8_lossy(&flags[0][2..]).to_string();
|
2022-07-27 09:27:28 +02:00
|
|
|
let mut variable_name = flags[0][2..].to_vec();
|
|
|
|
|
|
|
|
(0..variable_name.len()).for_each(|idx| {
|
|
|
|
if variable_name[idx] == b'-' {
|
|
|
|
variable_name[idx] = b'_';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-07-27 04:08:54 +02:00
|
|
|
if !is_variable(&variable_name) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-12-31 12:18:53 +01:00
|
|
|
"valid variable name for this short flag".into(),
|
2022-07-27 04:08:54 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
let var_id = working_set.add_variable(
|
|
|
|
variable_name,
|
|
|
|
span,
|
|
|
|
Type::Any,
|
|
|
|
false,
|
|
|
|
);
|
2021-07-17 00:39:30 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if chars.len() == 1 {
|
2021-07-17 00:39:30 +02:00
|
|
|
args.push(Arg::Flag(Flag {
|
|
|
|
arg: None,
|
|
|
|
desc: String::new(),
|
2021-09-02 10:25:22 +02:00
|
|
|
long,
|
2021-07-17 00:39:30 +02:00
|
|
|
short: Some(chars[0]),
|
|
|
|
required: false,
|
2021-07-23 23:19:30 +02:00
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-07-17 00:39:30 +02:00
|
|
|
}));
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2021-07-30 00:56:51 +02:00
|
|
|
error = error.or_else(|| {
|
2021-08-17 01:00:00 +02:00
|
|
|
Some(ParseError::Expected("short flag".into(), span))
|
2021-07-30 00:56:51 +02:00
|
|
|
});
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
}
|
2022-12-31 12:18:53 +01:00
|
|
|
parse_mode = ParseMode::ArgMode;
|
2022-12-28 00:00:44 +01:00
|
|
|
}
|
|
|
|
// Mandatory short flag, e.g. -e (must be one character)
|
|
|
|
else if contents.starts_with(b"-") && contents.len() > 1 {
|
2021-09-02 10:25:22 +02:00
|
|
|
let short_flag = &contents[1..];
|
|
|
|
let short_flag = String::from_utf8_lossy(short_flag).to_string();
|
|
|
|
let chars: Vec<char> = short_flag.chars().collect();
|
|
|
|
|
|
|
|
if chars.len() > 1 {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("short flag".into(), span))
|
|
|
|
});
|
2022-01-06 22:06:54 +01:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-01-06 22:06:54 +01:00
|
|
|
let mut encoded_var_name = vec![0u8; 4];
|
|
|
|
let len = chars[0].encode_utf8(&mut encoded_var_name).len();
|
|
|
|
let variable_name = encoded_var_name[0..len].to_vec();
|
2022-12-28 00:00:44 +01:00
|
|
|
|
2022-07-27 04:08:54 +02:00
|
|
|
if !is_variable(&variable_name) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-12-31 12:18:53 +01:00
|
|
|
"valid variable name for this short flag".into(),
|
2022-07-27 04:08:54 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-09 10:42:19 +01:00
|
|
|
let var_id =
|
2022-11-11 07:51:08 +01:00
|
|
|
working_set.add_variable(variable_name, span, Type::Any, false);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-01-06 22:06:54 +01:00
|
|
|
args.push(Arg::Flag(Flag {
|
|
|
|
arg: None,
|
|
|
|
desc: String::new(),
|
|
|
|
long: String::new(),
|
|
|
|
short: Some(chars[0]),
|
|
|
|
required: false,
|
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2022-01-06 22:06:54 +01:00
|
|
|
}));
|
2022-12-31 12:18:53 +01:00
|
|
|
parse_mode = ParseMode::ArgMode;
|
2022-12-28 00:00:44 +01:00
|
|
|
}
|
2022-12-31 12:18:53 +01:00
|
|
|
// Short flag alias for long flag, e.g. --b (-a)
|
|
|
|
// This is the same as the short flag in --b(-a)
|
2022-12-28 00:00:44 +01:00
|
|
|
else if contents.starts_with(b"(-") {
|
2022-12-31 12:18:53 +01:00
|
|
|
if matches!(parse_mode, ParseMode::AfterCommaArgMode) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("parameter or flag".into(), span))
|
|
|
|
});
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
let short_flag = &contents[2..];
|
|
|
|
|
|
|
|
let short_flag = if !short_flag.ends_with(b")") {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("short flag".into(), span))
|
|
|
|
});
|
|
|
|
short_flag
|
|
|
|
} else {
|
|
|
|
&short_flag[..(short_flag.len() - 1)]
|
|
|
|
};
|
2021-07-17 20:52:50 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let short_flag = String::from_utf8_lossy(short_flag).to_string();
|
|
|
|
let chars: Vec<char> = short_flag.chars().collect();
|
2021-07-17 20:52:50 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if chars.len() == 1 {
|
|
|
|
match args.last_mut() {
|
|
|
|
Some(Arg::Flag(flag)) => {
|
|
|
|
if flag.short.is_some() {
|
2021-07-30 00:56:51 +02:00
|
|
|
error = error.or_else(|| {
|
2021-08-17 01:00:00 +02:00
|
|
|
Some(ParseError::Expected(
|
2021-09-02 10:25:22 +02:00
|
|
|
"one short flag".into(),
|
2021-07-30 00:56:51 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
});
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
flag.short = Some(chars[0]);
|
2021-07-17 20:52:50 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
_ => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"unknown flag".into(),
|
|
|
|
span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
2021-07-17 20:52:50 +02:00
|
|
|
}
|
2021-07-30 00:56:51 +02:00
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected("short flag".into(), span))
|
|
|
|
});
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2022-12-28 00:00:44 +01:00
|
|
|
}
|
|
|
|
// Positional arg, optional
|
|
|
|
else if contents.ends_with(b"?") {
|
2021-09-02 10:25:22 +02:00
|
|
|
let contents: Vec<_> = contents[..(contents.len() - 1)].into();
|
|
|
|
let name = String::from_utf8_lossy(&contents).to_string();
|
|
|
|
|
2022-07-27 04:08:54 +02:00
|
|
|
if !is_variable(&contents) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-12-31 12:18:53 +01:00
|
|
|
"valid variable name for this optional parameter"
|
|
|
|
.into(),
|
2022-07-27 04:08:54 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
let var_id =
|
|
|
|
working_set.add_variable(contents, span, Type::Any, false);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
|
|
|
args.push(Arg::Positional(
|
|
|
|
PositionalArg {
|
|
|
|
desc: String::new(),
|
|
|
|
name,
|
|
|
|
shape: SyntaxShape::Any,
|
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
false,
|
2022-12-31 12:18:53 +01:00
|
|
|
));
|
|
|
|
parse_mode = ParseMode::ArgMode;
|
2022-12-28 00:00:44 +01:00
|
|
|
}
|
|
|
|
// Rest param
|
|
|
|
else if let Some(contents) = contents.strip_prefix(b"...") {
|
2021-09-07 05:37:02 +02:00
|
|
|
let name = String::from_utf8_lossy(contents).to_string();
|
|
|
|
let contents_vec: Vec<u8> = contents.to_vec();
|
2022-12-28 00:00:44 +01:00
|
|
|
|
2022-07-27 04:08:54 +02:00
|
|
|
if !is_variable(&contents_vec) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-12-31 12:18:53 +01:00
|
|
|
"valid variable name for this rest parameter".into(),
|
2022-07-27 04:08:54 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
2021-09-07 05:37:02 +02:00
|
|
|
|
2022-03-09 10:42:19 +01:00
|
|
|
let var_id =
|
2022-11-11 07:51:08 +01:00
|
|
|
working_set.add_variable(contents_vec, span, Type::Any, false);
|
2021-09-07 05:37:02 +02:00
|
|
|
|
2022-03-07 17:44:27 +01:00
|
|
|
args.push(Arg::RestPositional(PositionalArg {
|
|
|
|
desc: String::new(),
|
|
|
|
name,
|
|
|
|
shape: SyntaxShape::Any,
|
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2022-03-07 17:44:27 +01:00
|
|
|
}));
|
2022-12-31 12:18:53 +01:00
|
|
|
parse_mode = ParseMode::ArgMode;
|
2022-12-28 00:00:44 +01:00
|
|
|
}
|
|
|
|
// Normal param
|
|
|
|
else {
|
2021-09-02 10:25:22 +02:00
|
|
|
let name = String::from_utf8_lossy(contents).to_string();
|
|
|
|
let contents_vec = contents.to_vec();
|
|
|
|
|
2022-07-27 04:08:54 +02:00
|
|
|
if !is_variable(&contents_vec) {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
2022-12-31 12:18:53 +01:00
|
|
|
"valid variable name for this parameter".into(),
|
2022-07-27 04:08:54 +02:00
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-09 10:42:19 +01:00
|
|
|
let var_id =
|
2022-11-11 07:51:08 +01:00
|
|
|
working_set.add_variable(contents_vec, span, Type::Any, false);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
|
|
|
// Positional arg, required
|
|
|
|
args.push(Arg::Positional(
|
|
|
|
PositionalArg {
|
|
|
|
desc: String::new(),
|
|
|
|
name,
|
|
|
|
shape: SyntaxShape::Any,
|
|
|
|
var_id: Some(var_id),
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
true,
|
2022-12-31 12:18:53 +01:00
|
|
|
));
|
|
|
|
parse_mode = ParseMode::ArgMode;
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
ParseMode::TypeMode => {
|
|
|
|
if let Some(last) = args.last_mut() {
|
|
|
|
let (syntax_shape, err) =
|
|
|
|
parse_shape_name(working_set, contents, span);
|
|
|
|
error = error.or(err);
|
2021-10-12 19:44:23 +02:00
|
|
|
//TODO check if we're replacing a custom parameter already
|
2021-09-02 10:25:22 +02:00
|
|
|
match last {
|
|
|
|
Arg::Positional(PositionalArg { shape, var_id, .. }, ..) => {
|
|
|
|
working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type());
|
|
|
|
*shape = syntax_shape;
|
|
|
|
}
|
2022-03-07 17:44:27 +01:00
|
|
|
Arg::RestPositional(PositionalArg {
|
|
|
|
shape, var_id, ..
|
|
|
|
}) => {
|
|
|
|
working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type());
|
|
|
|
*shape = syntax_shape;
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
Arg::Flag(Flag { arg, var_id, .. }) => {
|
2021-10-12 06:49:17 +02:00
|
|
|
// Flags with a boolean type are just present/not-present switches
|
|
|
|
if syntax_shape != SyntaxShape::Boolean {
|
|
|
|
working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type());
|
|
|
|
*arg = Some(syntax_shape)
|
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
parse_mode = ParseMode::ArgMode;
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2022-03-07 21:08:56 +01:00
|
|
|
ParseMode::DefaultValueMode => {
|
|
|
|
if let Some(last) = args.last_mut() {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (expression, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-03-07 21:08:56 +01:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
//TODO check if we're replacing a custom parameter already
|
|
|
|
match last {
|
|
|
|
Arg::Positional(
|
|
|
|
PositionalArg {
|
|
|
|
shape,
|
|
|
|
var_id,
|
|
|
|
default_value,
|
|
|
|
..
|
|
|
|
},
|
|
|
|
required,
|
|
|
|
) => {
|
|
|
|
let var_id = var_id.expect("internal error: all custom parameters must have var_ids");
|
2022-03-09 10:42:19 +01:00
|
|
|
let var_type = &working_set.get_variable(var_id).ty;
|
2022-03-07 21:08:56 +01:00
|
|
|
match var_type {
|
2022-04-07 06:34:09 +02:00
|
|
|
Type::Any => {
|
2022-03-07 21:08:56 +01:00
|
|
|
working_set.set_variable_type(
|
|
|
|
var_id,
|
|
|
|
expression.ty.clone(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
t => {
|
|
|
|
if t != &expression.ty {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::AssignmentMismatch(
|
|
|
|
"Default value wrong type".into(),
|
2023-01-30 02:37:54 +01:00
|
|
|
format!("default value not {t}"),
|
2022-03-07 21:08:56 +01:00
|
|
|
expression.span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*shape = expression.ty.to_shape();
|
|
|
|
*default_value = Some(expression);
|
|
|
|
*required = false;
|
|
|
|
}
|
|
|
|
Arg::RestPositional(..) => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::AssignmentMismatch(
|
2022-12-31 12:18:53 +01:00
|
|
|
"Rest parameter was given a default value".into(),
|
2022-03-07 21:08:56 +01:00
|
|
|
"can't have default value".into(),
|
|
|
|
expression.span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Arg::Flag(Flag {
|
|
|
|
arg,
|
|
|
|
var_id,
|
|
|
|
default_value,
|
|
|
|
..
|
|
|
|
}) => {
|
|
|
|
let var_id = var_id.expect("internal error: all custom parameters must have var_ids");
|
2022-03-09 10:42:19 +01:00
|
|
|
let var_type = &working_set.get_variable(var_id).ty;
|
2022-03-07 21:08:56 +01:00
|
|
|
|
|
|
|
let expression_ty = expression.ty.clone();
|
|
|
|
let expression_span = expression.span;
|
|
|
|
|
|
|
|
*default_value = Some(expression);
|
|
|
|
|
|
|
|
// Flags with a boolean type are just present/not-present switches
|
|
|
|
if var_type != &Type::Bool {
|
|
|
|
match var_type {
|
2022-04-07 06:34:09 +02:00
|
|
|
Type::Any => {
|
2022-03-07 21:08:56 +01:00
|
|
|
*arg = Some(expression_ty.to_shape());
|
|
|
|
working_set
|
|
|
|
.set_variable_type(var_id, expression_ty);
|
|
|
|
}
|
|
|
|
t => {
|
|
|
|
if t != &expression_ty {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::AssignmentMismatch(
|
2022-12-31 12:18:53 +01:00
|
|
|
"Default value is the wrong type"
|
|
|
|
.into(),
|
|
|
|
format!(
|
2023-01-30 02:37:54 +01:00
|
|
|
"default value should be {t}"
|
2022-12-31 12:18:53 +01:00
|
|
|
),
|
2022-03-07 21:08:56 +01:00
|
|
|
expression_span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
parse_mode = ParseMode::ArgMode;
|
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
Token {
|
|
|
|
contents: crate::TokenContents::Comment,
|
|
|
|
span,
|
|
|
|
} => {
|
2022-12-03 10:44:12 +01:00
|
|
|
let contents = working_set.get_span_contents(Span::new(span.start + 1, span.end));
|
2021-09-02 10:25:22 +02:00
|
|
|
|
|
|
|
let mut contents = String::from_utf8_lossy(contents).to_string();
|
|
|
|
contents = contents.trim().into();
|
|
|
|
|
|
|
|
if let Some(last) = args.last_mut() {
|
|
|
|
match last {
|
|
|
|
Arg::Flag(flag) => {
|
|
|
|
if !flag.desc.is_empty() {
|
|
|
|
flag.desc.push('\n');
|
2021-07-17 00:31:36 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
flag.desc.push_str(&contents);
|
|
|
|
}
|
|
|
|
Arg::Positional(positional, ..) => {
|
|
|
|
if !positional.desc.is_empty() {
|
|
|
|
positional.desc.push('\n');
|
2021-07-17 00:31:36 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
positional.desc.push_str(&contents);
|
2021-07-17 00:31:36 +02:00
|
|
|
}
|
2022-03-07 17:44:27 +01:00
|
|
|
Arg::RestPositional(positional) => {
|
|
|
|
if !positional.desc.is_empty() {
|
|
|
|
positional.desc.push('\n');
|
|
|
|
}
|
|
|
|
positional.desc.push_str(&contents);
|
|
|
|
}
|
2021-07-17 00:31:36 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
_ => {}
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut sig = Signature::new(String::new());
|
|
|
|
|
|
|
|
for arg in args {
|
|
|
|
match arg {
|
|
|
|
Arg::Positional(positional, required) => {
|
2021-09-07 05:37:02 +02:00
|
|
|
if required {
|
2022-03-07 21:08:56 +01:00
|
|
|
if !sig.optional_positional.is_empty() {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::RequiredAfterOptional(
|
|
|
|
positional.name.clone(),
|
|
|
|
span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
sig.required_positional.push(positional)
|
|
|
|
} else {
|
|
|
|
sig.optional_positional.push(positional)
|
2021-07-17 00:53:45 +02:00
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
Arg::Flag(flag) => sig.named.push(flag),
|
2022-03-07 17:44:27 +01:00
|
|
|
Arg::RestPositional(positional) => {
|
|
|
|
if positional.name.is_empty() {
|
|
|
|
error = error.or(Some(ParseError::RestNeedsName(span)))
|
|
|
|
} else if sig.rest_positional.is_none() {
|
|
|
|
sig.rest_positional = Some(PositionalArg {
|
|
|
|
name: positional.name,
|
|
|
|
..positional
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// Too many rest params
|
|
|
|
error = error.or(Some(ParseError::MultipleRestParams(span)))
|
|
|
|
}
|
|
|
|
}
|
2021-07-16 23:55:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-06 01:16:27 +02:00
|
|
|
(Box::new(sig), error)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_list_expression(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
element_shape: &SyntaxShape,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut error = None;
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut start = span.start;
|
|
|
|
let mut end = span.end;
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if bytes.starts_with(b"[") {
|
|
|
|
start += 1;
|
|
|
|
}
|
|
|
|
if bytes.ends_with(b"]") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("]".into(), Span::new(end, end))));
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let inner_span = Span::new(start, end);
|
2022-01-03 04:18:23 +01:00
|
|
|
let source = working_set.get_span_contents(inner_span);
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2022-01-03 04:18:23 +01:00
|
|
|
let (output, err) = lex(source, inner_span.start, &[b'\n', b'\r', b','], &[], true);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let (output, err) = lite_parse(&output);
|
|
|
|
error = error.or(err);
|
2021-07-08 23:45:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut args = vec![];
|
2021-08-17 02:26:05 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut contained_type: Option<Type> = None;
|
2021-07-08 09:49:17 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if !output.block.is_empty() {
|
|
|
|
for arg in &output.block[0].commands {
|
|
|
|
let mut spans_idx = 0;
|
2021-07-08 23:45:56 +02:00
|
|
|
|
2022-11-22 19:26:13 +01:00
|
|
|
if let LiteElement::Command(_, command) = arg {
|
2022-11-18 22:46:48 +01:00
|
|
|
while spans_idx < command.parts.len() {
|
|
|
|
let (arg, err) = parse_multispan_value(
|
|
|
|
working_set,
|
|
|
|
&command.parts,
|
|
|
|
&mut spans_idx,
|
|
|
|
element_shape,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
if let Some(ref ctype) = contained_type {
|
|
|
|
if *ctype != arg.ty {
|
|
|
|
contained_type = Some(Type::Any);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
contained_type = Some(arg.ty.clone());
|
2021-08-17 02:26:05 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
args.push(arg);
|
2021-07-16 08:24:46 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
spans_idx += 1;
|
|
|
|
}
|
2021-07-08 09:49:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::List(args),
|
|
|
|
span,
|
|
|
|
ty: Type::List(Box::new(if let Some(ty) = contained_type {
|
2021-09-04 09:59:38 +02:00
|
|
|
ty
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2022-04-07 06:34:09 +02:00
|
|
|
Type::Any
|
2021-09-02 10:25:22 +02:00
|
|
|
})),
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_table_expression(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2021-09-08 20:54:27 +02:00
|
|
|
original_span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2021-09-08 20:54:27 +02:00
|
|
|
let bytes = working_set.get_span_contents(original_span);
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut error = None;
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2021-09-08 20:54:27 +02:00
|
|
|
let mut start = original_span.start;
|
|
|
|
let mut end = original_span.end;
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if bytes.starts_with(b"[") {
|
|
|
|
start += 1;
|
|
|
|
}
|
|
|
|
if bytes.ends_with(b"]") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("]".into(), Span::new(end, end))));
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let inner_span = Span::new(start, end);
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2022-01-03 04:18:23 +01:00
|
|
|
let source = working_set.get_span_contents(inner_span);
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2021-11-21 19:13:09 +01:00
|
|
|
let (output, err) = lex(source, start, &[b'\n', b'\r', b','], &[], true);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let (output, err) = lite_parse(&output);
|
|
|
|
error = error.or(err);
|
2021-07-06 00:58:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
match output.block.len() {
|
|
|
|
0 => (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::List(vec![]),
|
2022-01-03 04:18:23 +01:00
|
|
|
span: original_span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::List(Box::new(Type::Any)),
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
1 => {
|
|
|
|
// List
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_list_expression(
|
|
|
|
working_set,
|
|
|
|
original_span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
_ => {
|
2022-11-18 22:46:48 +01:00
|
|
|
match &output.block[0].commands[0] {
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
LiteElement::Command(_, command)
|
|
|
|
| LiteElement::Redirection(_, _, command)
|
|
|
|
| LiteElement::SeparateRedirection {
|
|
|
|
out: (_, command), ..
|
|
|
|
} => {
|
2022-11-18 22:46:48 +01:00
|
|
|
let mut table_headers = vec![];
|
|
|
|
|
|
|
|
let (headers, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
command.parts[0],
|
|
|
|
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
if let Expression {
|
|
|
|
expr: Expr::List(headers),
|
|
|
|
..
|
|
|
|
} = headers
|
|
|
|
{
|
|
|
|
table_headers = headers;
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &output.block[1].commands[0] {
|
2022-11-22 19:26:13 +01:00
|
|
|
LiteElement::Command(_, command)
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
| LiteElement::Redirection(_, _, command)
|
|
|
|
| LiteElement::SeparateRedirection {
|
|
|
|
out: (_, command), ..
|
|
|
|
} => {
|
2022-11-18 22:46:48 +01:00
|
|
|
let mut rows = vec![];
|
|
|
|
for part in &command.parts {
|
|
|
|
let (values, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
*part,
|
|
|
|
&SyntaxShape::List(Box::new(SyntaxShape::Any)),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
|
|
|
if let Expression {
|
|
|
|
expr: Expr::List(values),
|
|
|
|
span,
|
|
|
|
..
|
|
|
|
} = values
|
|
|
|
{
|
|
|
|
match values.len().cmp(&table_headers.len()) {
|
|
|
|
std::cmp::Ordering::Less => {
|
|
|
|
error = error.or(Some(ParseError::MissingColumns(
|
|
|
|
table_headers.len(),
|
|
|
|
span,
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
std::cmp::Ordering::Equal => {}
|
|
|
|
std::cmp::Ordering::Greater => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::ExtraColumns(
|
|
|
|
table_headers.len(),
|
|
|
|
values[table_headers.len()].span,
|
|
|
|
))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-07-06 03:48:45 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
rows.push(values);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Table(table_headers, rows),
|
|
|
|
span: original_span,
|
|
|
|
ty: Type::Table(vec![]), //FIXME
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
2021-09-08 20:54:27 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-06 03:48:45 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-02 09:32:30 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-02 09:32:30 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_block_expression(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2021-09-13 09:54:13 +02:00
|
|
|
shape: &SyntaxShape,
|
2021-09-02 10:25:22 +02:00
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: block expression");
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let bytes = working_set.get_span_contents(span);
|
|
|
|
let mut error = None;
|
2021-07-02 09:32:30 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut start = span.start;
|
|
|
|
let mut end = span.end;
|
2021-07-02 09:32:30 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if bytes.starts_with(b"{") {
|
|
|
|
start += 1;
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("block".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if bytes.ends_with(b"}") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span::new(end, end))));
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-02 09:32:30 +02:00
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let inner_span = Span::new(start, end);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-01-03 06:21:26 +01:00
|
|
|
let source = working_set.get_span_contents(inner_span);
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2022-01-22 19:24:47 +01:00
|
|
|
let (output, err) = lex(source, start, &[], &[], false);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
2021-09-06 04:20:02 +02:00
|
|
|
working_set.enter_scope();
|
2021-09-08 00:00:20 +02:00
|
|
|
|
2022-11-10 09:21:49 +01:00
|
|
|
// Check to see if we have parameters
|
|
|
|
let (signature, amt_to_skip): (Option<(Box<Signature>, Span)>, usize) = match output.first() {
|
|
|
|
Some(Token {
|
|
|
|
contents: TokenContents::Pipe,
|
|
|
|
span,
|
|
|
|
}) => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"block but found closure".into(),
|
|
|
|
*span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
(None, 0)
|
|
|
|
}
|
|
|
|
_ => (None, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: Finish this
|
|
|
|
if let SyntaxShape::Closure(Some(v)) = shape {
|
|
|
|
if let Some((sig, sig_span)) = &signature {
|
|
|
|
if sig.num_positionals() > v.len() {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
format!(
|
|
|
|
"{} block parameter{}",
|
|
|
|
v.len(),
|
|
|
|
if v.len() > 1 { "s" } else { "" }
|
|
|
|
),
|
|
|
|
*sig_span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (expected, PositionalArg { name, shape, .. }) in
|
|
|
|
v.iter().zip(sig.required_positional.iter())
|
|
|
|
{
|
|
|
|
if expected != shape && *shape != SyntaxShape::Any {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::ParameterMismatchType(
|
|
|
|
name.to_owned(),
|
|
|
|
expected.to_string(),
|
|
|
|
shape.to_string(),
|
|
|
|
*sig_span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
let (mut output, err) = parse_block(
|
|
|
|
working_set,
|
|
|
|
&output[amt_to_skip..],
|
|
|
|
false,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
false,
|
|
|
|
);
|
2022-11-10 09:21:49 +01:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
if let Some(signature) = signature {
|
|
|
|
output.signature = signature.0;
|
|
|
|
} else if let Some(last) = working_set.delta.scope.last() {
|
|
|
|
// FIXME: this only supports the top $it. Is this sufficient?
|
|
|
|
|
|
|
|
if let Some(var_id) = last.get_var(b"$it") {
|
|
|
|
let mut signature = Signature::new("");
|
|
|
|
signature.required_positional.push(PositionalArg {
|
|
|
|
var_id: Some(*var_id),
|
|
|
|
name: "$it".into(),
|
|
|
|
desc: String::new(),
|
|
|
|
shape: SyntaxShape::Any,
|
|
|
|
default_value: None,
|
|
|
|
});
|
|
|
|
output.signature = Box::new(signature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
output.span = Some(span);
|
|
|
|
|
|
|
|
working_set.exit_scope();
|
|
|
|
|
|
|
|
let block_id = working_set.add_block(output);
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Block(block_id),
|
|
|
|
span,
|
|
|
|
ty: Type::Block,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_closure_expression(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
shape: &SyntaxShape,
|
|
|
|
span: Span,
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
trace!("parsing: closure expression");
|
|
|
|
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let mut start = span.start;
|
|
|
|
let mut end = span.end;
|
|
|
|
|
|
|
|
if bytes.starts_with(b"{") {
|
|
|
|
start += 1;
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
2023-01-01 11:26:51 +01:00
|
|
|
Some(ParseError::Expected("closure".into(), span)),
|
2022-11-10 09:21:49 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if bytes.ends_with(b"}") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span::new(end, end))));
|
2022-11-10 09:21:49 +01:00
|
|
|
}
|
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let inner_span = Span::new(start, end);
|
2022-11-10 09:21:49 +01:00
|
|
|
|
|
|
|
let source = working_set.get_span_contents(inner_span);
|
|
|
|
|
|
|
|
let (output, err) = lex(source, start, &[], &[], false);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
working_set.enter_scope();
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// Check to see if we have parameters
|
2022-02-17 12:40:24 +01:00
|
|
|
let (signature, amt_to_skip): (Option<(Box<Signature>, Span)>, usize) = match output.first() {
|
2021-09-02 10:25:22 +02:00
|
|
|
Some(Token {
|
|
|
|
contents: TokenContents::Pipe,
|
2021-09-06 01:16:27 +02:00
|
|
|
span,
|
|
|
|
}) => {
|
|
|
|
// We've found a parameter list
|
|
|
|
let start_point = span.start;
|
|
|
|
let mut token_iter = output.iter().enumerate().skip(1);
|
|
|
|
let mut end_span = None;
|
|
|
|
let mut amt_to_skip = 1;
|
|
|
|
|
|
|
|
for token in &mut token_iter {
|
|
|
|
if let Token {
|
2021-09-02 10:25:22 +02:00
|
|
|
contents: TokenContents::Pipe,
|
2021-09-06 01:16:27 +02:00
|
|
|
span,
|
|
|
|
} = token.1
|
|
|
|
{
|
|
|
|
end_span = Some(span);
|
|
|
|
amt_to_skip = token.0;
|
|
|
|
break;
|
2021-08-25 21:29:36 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-06 01:16:27 +02:00
|
|
|
|
|
|
|
let end_point = if let Some(span) = end_span {
|
|
|
|
span.end
|
|
|
|
} else {
|
|
|
|
end
|
|
|
|
};
|
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let signature_span = Span::new(start_point, end_point);
|
2022-03-18 20:03:57 +01:00
|
|
|
let (signature, err) =
|
|
|
|
parse_signature_helper(working_set, signature_span, expand_aliases_denylist);
|
2021-09-06 01:16:27 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-02-17 12:40:24 +01:00
|
|
|
(Some((signature, signature_span)), amt_to_skip)
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2022-11-10 09:21:49 +01:00
|
|
|
Some(Token {
|
2022-12-08 00:02:11 +01:00
|
|
|
contents: TokenContents::PipePipe,
|
2022-11-10 09:21:49 +01:00
|
|
|
span,
|
2022-12-08 00:02:11 +01:00
|
|
|
}) => (
|
|
|
|
Some((Box::new(Signature::new("closure".to_string())), *span)),
|
|
|
|
1,
|
|
|
|
),
|
2021-09-06 01:16:27 +02:00
|
|
|
_ => (None, 0),
|
2021-09-02 10:25:22 +02:00
|
|
|
};
|
2021-08-25 21:29:36 +02:00
|
|
|
|
2022-01-08 01:40:40 +01:00
|
|
|
// TODO: Finish this
|
2022-11-10 09:21:49 +01:00
|
|
|
if let SyntaxShape::Closure(Some(v)) = shape {
|
2022-02-17 12:40:24 +01:00
|
|
|
if let Some((sig, sig_span)) = &signature {
|
2022-04-08 21:57:27 +02:00
|
|
|
if sig.num_positionals() > v.len() {
|
2022-02-17 12:40:24 +01:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
format!(
|
2023-01-01 11:26:51 +01:00
|
|
|
"{} closure parameter{}",
|
2022-02-17 12:40:24 +01:00
|
|
|
v.len(),
|
|
|
|
if v.len() > 1 { "s" } else { "" }
|
|
|
|
),
|
|
|
|
*sig_span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
2021-09-13 09:54:13 +02:00
|
|
|
|
2022-02-17 12:40:24 +01:00
|
|
|
for (expected, PositionalArg { name, shape, .. }) in
|
|
|
|
v.iter().zip(sig.required_positional.iter())
|
|
|
|
{
|
|
|
|
if expected != shape && *shape != SyntaxShape::Any {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::ParameterMismatchType(
|
|
|
|
name.to_owned(),
|
|
|
|
expected.to_string(),
|
|
|
|
shape.to_string(),
|
|
|
|
*sig_span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-09-13 09:54:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
let (mut output, err) = parse_block(
|
|
|
|
working_set,
|
|
|
|
&output[amt_to_skip..],
|
|
|
|
false,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
false,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-02 09:32:30 +02:00
|
|
|
|
2021-09-06 01:16:27 +02:00
|
|
|
if let Some(signature) = signature {
|
2022-02-17 12:40:24 +01:00
|
|
|
output.signature = signature.0;
|
2021-09-06 04:20:02 +02:00
|
|
|
} else if let Some(last) = working_set.delta.scope.last() {
|
2021-10-12 19:44:23 +02:00
|
|
|
// FIXME: this only supports the top $it. Is this sufficient?
|
2021-09-13 09:31:11 +02:00
|
|
|
|
2021-09-06 04:20:02 +02:00
|
|
|
if let Some(var_id) = last.get_var(b"$it") {
|
|
|
|
let mut signature = Signature::new("");
|
|
|
|
signature.required_positional.push(PositionalArg {
|
|
|
|
var_id: Some(*var_id),
|
|
|
|
name: "$it".into(),
|
|
|
|
desc: String::new(),
|
|
|
|
shape: SyntaxShape::Any,
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-09-06 04:20:02 +02:00
|
|
|
});
|
|
|
|
output.signature = Box::new(signature);
|
|
|
|
}
|
2021-09-06 01:16:27 +02:00
|
|
|
}
|
|
|
|
|
2022-01-30 23:05:25 +01:00
|
|
|
output.span = Some(span);
|
2021-10-25 22:04:23 +02:00
|
|
|
|
2021-09-06 04:20:02 +02:00
|
|
|
working_set.exit_scope();
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let block_id = working_set.add_block(output);
|
2021-07-16 22:26:40 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
2022-11-10 09:21:49 +01:00
|
|
|
expr: Expr::Closure(block_id),
|
2021-09-02 10:25:22 +02:00
|
|
|
span,
|
2022-11-10 09:21:49 +01:00
|
|
|
ty: Type::Closure,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
2021-07-02 09:32:30 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_value(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
shape: &SyntaxShape,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
|
|
|
|
2022-02-24 13:58:53 +01:00
|
|
|
if bytes.is_empty() {
|
|
|
|
return (garbage(span), Some(ParseError::IncompleteParser(span)));
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// First, check the special-cases. These will likely represent specific values as expressions
|
|
|
|
// and may fit a variety of shapes.
|
|
|
|
//
|
2021-09-07 09:07:11 +02:00
|
|
|
// We check variable first because immediately following we check for variables with cell paths
|
2021-09-02 10:25:22 +02:00
|
|
|
// which might result in a value that fits other shapes (and require the variable to already be
|
|
|
|
// declared)
|
|
|
|
if shape == &SyntaxShape::Variable {
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing: variable");
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
return parse_variable_expr(working_set, span);
|
2022-02-24 13:58:53 +01:00
|
|
|
}
|
2022-01-01 22:42:50 +01:00
|
|
|
|
2022-03-03 19:14:03 +01:00
|
|
|
// Check for reserved keyword values
|
|
|
|
match bytes {
|
|
|
|
b"true" => {
|
|
|
|
if matches!(shape, SyntaxShape::Boolean) || matches!(shape, SyntaxShape::Any) {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Bool(true),
|
|
|
|
span,
|
|
|
|
ty: Type::Bool,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected("non-boolean value".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b"false" => {
|
|
|
|
if matches!(shape, SyntaxShape::Boolean) || matches!(shape, SyntaxShape::Any) {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Bool(false),
|
|
|
|
span,
|
|
|
|
ty: Type::Bool,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected("non-boolean value".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
2022-03-03 01:55:03 +01:00
|
|
|
}
|
2022-03-03 19:14:03 +01:00
|
|
|
b"null" => {
|
2022-03-03 01:55:03 +01:00
|
|
|
return (
|
|
|
|
Expression {
|
2022-03-03 19:14:03 +01:00
|
|
|
expr: Expr::Nothing,
|
2022-03-03 01:55:03 +01:00
|
|
|
span,
|
2022-03-03 19:14:03 +01:00
|
|
|
ty: Type::Nothing,
|
2022-03-03 01:55:03 +01:00
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
2022-03-03 19:14:03 +01:00
|
|
|
|
|
|
|
_ => {}
|
2022-03-03 01:55:03 +01:00
|
|
|
}
|
2022-03-03 19:14:03 +01:00
|
|
|
|
2022-02-24 13:58:53 +01:00
|
|
|
match bytes[0] {
|
2022-03-18 20:03:57 +01:00
|
|
|
b'$' => return parse_dollar_expr(working_set, span, expand_aliases_denylist),
|
2022-02-24 13:58:53 +01:00
|
|
|
b'(' => {
|
2022-03-18 20:03:57 +01:00
|
|
|
if let (expr, None) = parse_range(working_set, span, expand_aliases_denylist) {
|
2021-11-11 00:14:00 +01:00
|
|
|
return (expr, None);
|
2022-08-30 06:17:10 +02:00
|
|
|
} else if matches!(shape, SyntaxShape::Signature) {
|
|
|
|
return parse_signature(working_set, span, expand_aliases_denylist);
|
2022-02-24 13:58:53 +01:00
|
|
|
} else {
|
2022-03-18 20:03:57 +01:00
|
|
|
return parse_full_cell_path(working_set, None, span, expand_aliases_denylist);
|
2021-11-11 00:14:00 +01:00
|
|
|
}
|
|
|
|
}
|
2022-02-24 13:58:53 +01:00
|
|
|
b'{' => {
|
2022-11-10 09:21:49 +01:00
|
|
|
if !matches!(shape, SyntaxShape::Closure(..)) && !matches!(shape, SyntaxShape::Block) {
|
2023-01-24 09:05:46 +01:00
|
|
|
let (expr, err) =
|
|
|
|
parse_full_cell_path(working_set, None, span, expand_aliases_denylist);
|
|
|
|
match err {
|
|
|
|
Some(err) => {
|
|
|
|
if let ParseError::Unbalanced(_, _, _) = err {
|
|
|
|
return (expr, Some(err));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => return (expr, None),
|
2022-02-24 13:58:53 +01:00
|
|
|
}
|
|
|
|
}
|
2022-11-10 09:21:49 +01:00
|
|
|
if matches!(shape, SyntaxShape::Closure(_)) || matches!(shape, SyntaxShape::Any) {
|
|
|
|
return parse_closure_expression(working_set, shape, span, expand_aliases_denylist);
|
|
|
|
} else if matches!(shape, SyntaxShape::Block) {
|
2022-03-18 20:03:57 +01:00
|
|
|
return parse_block_expression(working_set, shape, span, expand_aliases_denylist);
|
2022-02-24 13:58:53 +01:00
|
|
|
} else if matches!(shape, SyntaxShape::Record) {
|
2022-03-18 20:03:57 +01:00
|
|
|
return parse_record(working_set, span, expand_aliases_denylist);
|
2022-02-24 13:58:53 +01:00
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
Expression::garbage(span),
|
2022-12-07 20:58:54 +01:00
|
|
|
Some(ParseError::Expected(
|
2023-01-30 02:37:54 +01:00
|
|
|
format!("non-block value: {shape}"),
|
2022-12-07 20:58:54 +01:00
|
|
|
span,
|
|
|
|
)),
|
2022-02-24 13:58:53 +01:00
|
|
|
);
|
|
|
|
}
|
2021-09-04 08:52:28 +02:00
|
|
|
}
|
2022-02-24 13:58:53 +01:00
|
|
|
b'[' => match shape {
|
2021-09-02 10:25:22 +02:00
|
|
|
SyntaxShape::Any
|
|
|
|
| SyntaxShape::List(_)
|
|
|
|
| SyntaxShape::Table
|
|
|
|
| SyntaxShape::Signature => {}
|
|
|
|
_ => {
|
|
|
|
return (
|
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected("non-[] value".into(), span)),
|
|
|
|
);
|
2021-07-08 23:45:56 +02:00
|
|
|
}
|
2022-02-24 13:58:53 +01:00
|
|
|
},
|
|
|
|
_ => {}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-01 03:31:02 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
match shape {
|
2021-09-14 06:59:46 +02:00
|
|
|
SyntaxShape::Custom(shape, custom_completion) => {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (mut expression, err) =
|
|
|
|
parse_value(working_set, span, shape, expand_aliases_denylist);
|
2022-03-10 08:49:02 +01:00
|
|
|
expression.custom_completion = Some(*custom_completion);
|
2021-09-14 06:59:46 +02:00
|
|
|
(expression, err)
|
|
|
|
}
|
2021-09-07 00:02:24 +02:00
|
|
|
SyntaxShape::Number => parse_number(bytes, span),
|
2023-01-25 13:43:22 +01:00
|
|
|
SyntaxShape::Decimal => parse_float(bytes, span),
|
2021-09-07 00:02:24 +02:00
|
|
|
SyntaxShape::Int => parse_int(bytes, span),
|
2021-10-05 04:27:39 +02:00
|
|
|
SyntaxShape::Duration => parse_duration(working_set, span),
|
2022-02-24 03:02:48 +01:00
|
|
|
SyntaxShape::DateTime => parse_datetime(working_set, span),
|
2021-10-05 04:27:39 +02:00
|
|
|
SyntaxShape::Filesize => parse_filesize(working_set, span),
|
2022-03-18 20:03:57 +01:00
|
|
|
SyntaxShape::Range => parse_range(working_set, span, expand_aliases_denylist),
|
2021-10-04 21:21:31 +02:00
|
|
|
SyntaxShape::Filepath => parse_filepath(working_set, span),
|
2022-04-22 22:18:51 +02:00
|
|
|
SyntaxShape::Directory => parse_directory(working_set, span),
|
2021-10-04 21:21:31 +02:00
|
|
|
SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
|
2022-04-26 01:44:44 +02:00
|
|
|
SyntaxShape::String => parse_string(working_set, span, expand_aliases_denylist),
|
2022-03-01 00:31:53 +01:00
|
|
|
SyntaxShape::Binary => parse_binary(working_set, span),
|
2021-09-02 10:25:22 +02:00
|
|
|
SyntaxShape::Signature => {
|
|
|
|
if bytes.starts_with(b"[") {
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_signature(working_set, span, expand_aliases_denylist)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
(
|
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected("signature".into(), span)),
|
|
|
|
)
|
2021-07-09 08:23:20 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
SyntaxShape::List(elem) => {
|
|
|
|
if bytes.starts_with(b"[") {
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_list_expression(working_set, span, elem, expand_aliases_denylist)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
|
|
|
(
|
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected("list".into(), span)),
|
|
|
|
)
|
2021-07-08 09:49:17 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
SyntaxShape::Table => {
|
|
|
|
if bytes.starts_with(b"[") {
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_table_expression(working_set, span, expand_aliases_denylist)
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2021-07-02 08:44:37 +02:00
|
|
|
(
|
2021-09-02 10:25:22 +02:00
|
|
|
Expression::garbage(span),
|
|
|
|
Some(ParseError::Expected("table".into(), span)),
|
2021-07-02 08:44:37 +02:00
|
|
|
)
|
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
2021-10-02 04:59:11 +02:00
|
|
|
SyntaxShape::CellPath => {
|
|
|
|
let source = working_set.get_span_contents(span);
|
|
|
|
let mut error = None;
|
|
|
|
|
2021-11-21 19:13:09 +01:00
|
|
|
let (tokens, err) = lex(source, span.start, &[b'\n', b'\r'], &[b'.'], true);
|
2021-10-02 04:59:11 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let tokens = tokens.into_iter().peekable();
|
|
|
|
|
2022-04-26 01:44:44 +02:00
|
|
|
let (cell_path, err) =
|
|
|
|
parse_cell_path(working_set, tokens, false, expand_aliases_denylist, span);
|
2021-10-02 04:59:11 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::CellPath(CellPath { members: cell_path }),
|
|
|
|
span,
|
|
|
|
ty: Type::CellPath,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
2021-10-12 06:49:17 +02:00
|
|
|
SyntaxShape::Boolean => {
|
|
|
|
// Redundant, though we catch bad boolean parses here
|
2022-03-03 01:55:03 +01:00
|
|
|
if bytes == b"true" || bytes == b"false" {
|
2021-10-12 06:49:17 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Bool(true),
|
|
|
|
span,
|
|
|
|
ty: Type::Bool,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("bool".into(), span)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
SyntaxShape::Any => {
|
2021-09-07 05:56:30 +02:00
|
|
|
if bytes.starts_with(b"[") {
|
2021-11-08 00:18:00 +01:00
|
|
|
//parse_value(working_set, span, &SyntaxShape::Table)
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_full_cell_path(working_set, None, span, expand_aliases_denylist)
|
2021-09-07 05:56:30 +02:00
|
|
|
} else {
|
|
|
|
let shapes = [
|
2022-03-01 00:31:53 +01:00
|
|
|
SyntaxShape::Binary,
|
2021-09-07 05:56:30 +02:00
|
|
|
SyntaxShape::Int,
|
|
|
|
SyntaxShape::Number,
|
|
|
|
SyntaxShape::Range,
|
2022-02-24 03:02:48 +01:00
|
|
|
SyntaxShape::DateTime,
|
2021-09-07 05:56:30 +02:00
|
|
|
SyntaxShape::Filesize,
|
|
|
|
SyntaxShape::Duration,
|
2022-03-03 19:14:03 +01:00
|
|
|
SyntaxShape::Record,
|
2022-11-10 09:21:49 +01:00
|
|
|
SyntaxShape::Closure(None),
|
|
|
|
SyntaxShape::Block,
|
2021-09-07 05:56:30 +02:00
|
|
|
SyntaxShape::String,
|
|
|
|
];
|
|
|
|
for shape in shapes.iter() {
|
2022-03-18 20:03:57 +01:00
|
|
|
if let (s, None) =
|
|
|
|
parse_value(working_set, span, shape, expand_aliases_denylist)
|
|
|
|
{
|
2021-09-07 05:56:30 +02:00
|
|
|
return (s, None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("any shape".into(), span)),
|
|
|
|
)
|
2021-07-02 08:44:37 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
_ => (garbage(span), Some(ParseError::IncompleteParser(span))),
|
2021-07-02 08:44:37 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_operator(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let contents = working_set.get_span_contents(span);
|
|
|
|
|
|
|
|
let operator = match contents {
|
2022-11-11 07:51:08 +01:00
|
|
|
b"=" => Operator::Assignment(Assignment::Assign),
|
2022-11-11 19:50:43 +01:00
|
|
|
b"+=" => Operator::Assignment(Assignment::PlusAssign),
|
2022-12-09 17:20:58 +01:00
|
|
|
b"++=" => Operator::Assignment(Assignment::AppendAssign),
|
2022-11-11 19:50:43 +01:00
|
|
|
b"-=" => Operator::Assignment(Assignment::MinusAssign),
|
|
|
|
b"*=" => Operator::Assignment(Assignment::MultiplyAssign),
|
|
|
|
b"/=" => Operator::Assignment(Assignment::DivideAssign),
|
2022-11-11 07:51:08 +01:00
|
|
|
b"==" => Operator::Comparison(Comparison::Equal),
|
|
|
|
b"!=" => Operator::Comparison(Comparison::NotEqual),
|
|
|
|
b"<" => Operator::Comparison(Comparison::LessThan),
|
|
|
|
b"<=" => Operator::Comparison(Comparison::LessThanOrEqual),
|
|
|
|
b">" => Operator::Comparison(Comparison::GreaterThan),
|
|
|
|
b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual),
|
|
|
|
b"=~" => Operator::Comparison(Comparison::RegexMatch),
|
|
|
|
b"!~" => Operator::Comparison(Comparison::NotRegexMatch),
|
|
|
|
b"+" => Operator::Math(Math::Plus),
|
|
|
|
b"++" => Operator::Math(Math::Append),
|
|
|
|
b"-" => Operator::Math(Math::Minus),
|
|
|
|
b"*" => Operator::Math(Math::Multiply),
|
|
|
|
b"/" => Operator::Math(Math::Divide),
|
|
|
|
b"//" => Operator::Math(Math::FloorDivision),
|
|
|
|
b"in" => Operator::Comparison(Comparison::In),
|
|
|
|
b"not-in" => Operator::Comparison(Comparison::NotIn),
|
|
|
|
b"mod" => Operator::Math(Math::Modulo),
|
|
|
|
b"bit-or" => Operator::Bits(Bits::BitOr),
|
|
|
|
b"bit-xor" => Operator::Bits(Bits::BitXor),
|
|
|
|
b"bit-and" => Operator::Bits(Bits::BitAnd),
|
|
|
|
b"bit-shl" => Operator::Bits(Bits::ShiftLeft),
|
|
|
|
b"bit-shr" => Operator::Bits(Bits::ShiftRight),
|
|
|
|
b"starts-with" => Operator::Comparison(Comparison::StartsWith),
|
|
|
|
b"ends-with" => Operator::Comparison(Comparison::EndsWith),
|
2022-12-08 00:02:11 +01:00
|
|
|
b"and" => Operator::Boolean(Boolean::And),
|
|
|
|
b"or" => Operator::Boolean(Boolean::Or),
|
2022-11-26 17:02:37 +01:00
|
|
|
b"xor" => Operator::Boolean(Boolean::Xor),
|
2022-11-11 07:51:08 +01:00
|
|
|
b"**" => Operator::Math(Math::Pow),
|
2022-11-26 22:59:43 +01:00
|
|
|
// WARNING: not actual operators below! Error handling only
|
|
|
|
pow @ (b"^" | b"pow") => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
match pow {
|
|
|
|
b"^" => "^",
|
|
|
|
b"pow" => "pow",
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
2022-12-01 11:34:41 +01:00
|
|
|
"Use '**' for exponentiation or 'bit-xor' for bitwise XOR.",
|
2022-11-26 22:59:43 +01:00
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
equality @ (b"is" | b"===") => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
match equality {
|
|
|
|
b"is" => "is",
|
|
|
|
b"===" => "===",
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
"Did you mean '=='?",
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
b"contains" => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
"contains",
|
|
|
|
"Did you mean '$string =~ $pattern' or '$element in $container'?",
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
b"%" => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
"%",
|
|
|
|
"Did you mean 'mod'?",
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
2022-12-01 11:34:41 +01:00
|
|
|
b"&" => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
"&",
|
|
|
|
"Did you mean 'bit-and'?",
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
b"<<" => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
"<<",
|
|
|
|
"Did you mean 'bit-shl'?",
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
b">>" => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
">>",
|
|
|
|
"Did you mean 'bit-shr'?",
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
bits @ (b"bits-and" | b"bits-xor" | b"bits-or" | b"bits-shl" | b"bits-shr") => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::UnknownOperator(
|
|
|
|
match bits {
|
|
|
|
b"bits-and" => "bits-and",
|
|
|
|
b"bits-xor" => "bits-xor",
|
|
|
|
b"bits-or" => "bits-or",
|
|
|
|
b"bits-shl" => "bits-shl",
|
|
|
|
b"bits-shr" => "bits-shr",
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
match bits {
|
|
|
|
b"bits-and" => "Did you mean 'bit-and'?",
|
|
|
|
b"bits-xor" => "Did you mean 'bit-xor'?",
|
|
|
|
b"bits-or" => "Did you mean 'bit-or'?",
|
|
|
|
b"bits-shl" => "Did you mean 'bit-shl'?",
|
|
|
|
b"bits-shr" => "Did you mean 'bit-shr'?",
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
_ => {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("operator".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Operator(operator),
|
|
|
|
span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_math_expression(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2021-09-09 23:47:20 +02:00
|
|
|
lhs_row_var_id: Option<VarId>,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: math expression");
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// As the expr_stack grows, we increase the required precedence to grow larger
|
|
|
|
// If, at any time, the operator we're looking at is the same or lower precedence
|
|
|
|
// of what is in the expression stack, we collapse the expression stack.
|
|
|
|
//
|
|
|
|
// This leads to an expression stack that grows under increasing precedence and collapses
|
|
|
|
// under decreasing/sustained precedence
|
|
|
|
//
|
|
|
|
// The end result is a stack that we can fold into binary operations as right associations
|
|
|
|
// safely.
|
|
|
|
|
|
|
|
let mut expr_stack: Vec<Expression> = vec![];
|
|
|
|
|
|
|
|
let mut idx = 0;
|
|
|
|
let mut last_prec = 1000000;
|
|
|
|
|
|
|
|
let mut error = None;
|
2022-04-06 21:10:25 +02:00
|
|
|
|
|
|
|
let first_span = working_set.get_span_contents(spans[0]);
|
|
|
|
|
|
|
|
if first_span == b"not" {
|
|
|
|
if spans.len() > 1 {
|
|
|
|
let (remainder, err) = parse_math_expression(
|
|
|
|
working_set,
|
|
|
|
&spans[1..],
|
|
|
|
lhs_row_var_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::UnaryNot(Box::new(remainder)),
|
|
|
|
span: span(spans),
|
|
|
|
ty: Type::Bool,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
err,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage(spans[0]),
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"expression".into(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(spans[0].end, spans[0].end),
|
2022-04-06 21:10:25 +02:00
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-03 00:41:36 +02:00
|
|
|
let (mut lhs, err) = parse_value(
|
2022-03-18 20:03:57 +01:00
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
idx += 1;
|
|
|
|
|
2022-04-03 00:41:36 +02:00
|
|
|
if idx >= spans.len() {
|
|
|
|
// We already found the one part of our expression, so let's expand
|
|
|
|
if let Some(row_var_id) = lhs_row_var_id {
|
|
|
|
expand_to_cell_path(working_set, &mut lhs, row_var_id, expand_aliases_denylist);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
expr_stack.push(lhs);
|
|
|
|
|
|
|
|
while idx < spans.len() {
|
|
|
|
let (op, err) = parse_operator(working_set, spans[idx]);
|
2021-07-02 08:44:37 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let op_prec = op.precedence();
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
idx += 1;
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if idx == spans.len() {
|
|
|
|
// Handle broken math expr `1 +` etc
|
|
|
|
error = error.or(Some(ParseError::IncompleteMathExpression(spans[idx - 1])));
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
expr_stack.push(Expression::garbage(spans[idx - 1]));
|
|
|
|
expr_stack.push(Expression::garbage(spans[idx - 1]));
|
2021-07-22 21:50:59 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
break;
|
|
|
|
}
|
2021-07-22 21:50:59 +02:00
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
let (rhs, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
spans[idx],
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2022-03-25 04:23:08 +01:00
|
|
|
while op_prec <= last_prec && expr_stack.len() > 1 {
|
2021-11-06 08:31:28 +01:00
|
|
|
// Collapse the right associated operations first
|
|
|
|
// so that we can get back to a stack with a lower precedence
|
|
|
|
let mut rhs = expr_stack
|
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
|
|
|
let mut op = expr_stack
|
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
|
|
|
|
2022-03-25 04:23:08 +01:00
|
|
|
last_prec = op.precedence();
|
|
|
|
|
|
|
|
if last_prec < op_prec {
|
|
|
|
expr_stack.push(op);
|
|
|
|
expr_stack.push(rhs);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-11-06 08:31:28 +01:00
|
|
|
let mut lhs = expr_stack
|
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
|
|
|
|
|
|
|
if let Some(row_var_id) = lhs_row_var_id {
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_to_cell_path(working_set, &mut lhs, row_var_id, expand_aliases_denylist);
|
2021-11-06 08:31:28 +01:00
|
|
|
}
|
2021-09-09 23:47:20 +02:00
|
|
|
|
2021-11-06 08:31:28 +01:00
|
|
|
let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
|
|
|
|
error = error.or(err);
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-11-06 08:31:28 +01:00
|
|
|
let op_span = span(&[lhs.span, rhs.span]);
|
|
|
|
expr_stack.push(Expression {
|
|
|
|
expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)),
|
|
|
|
span: op_span,
|
|
|
|
ty: result_ty,
|
|
|
|
custom_completion: None,
|
|
|
|
});
|
2021-07-02 08:44:37 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
expr_stack.push(op);
|
|
|
|
expr_stack.push(rhs);
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
last_prec = op_prec;
|
2021-07-23 23:19:30 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
idx += 1;
|
|
|
|
}
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
while expr_stack.len() != 1 {
|
|
|
|
let mut rhs = expr_stack
|
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
|
|
|
let mut op = expr_stack
|
2021-07-02 08:44:37 +02:00
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
2021-09-02 10:25:22 +02:00
|
|
|
let mut lhs = expr_stack
|
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
|
|
|
|
2021-09-09 23:47:20 +02:00
|
|
|
if let Some(row_var_id) = lhs_row_var_id {
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_to_cell_path(working_set, &mut lhs, row_var_id, expand_aliases_denylist);
|
2021-09-09 23:47:20 +02:00
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
|
|
|
|
error = error.or(err);
|
2021-07-02 08:44:37 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let binary_op_span = span(&[lhs.span, rhs.span]);
|
|
|
|
expr_stack.push(Expression {
|
|
|
|
expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)),
|
|
|
|
span: binary_op_span,
|
|
|
|
ty: result_ty,
|
2021-09-14 06:59:46 +02:00
|
|
|
custom_completion: None,
|
2021-09-02 10:25:22 +02:00
|
|
|
});
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let output = expr_stack
|
|
|
|
.pop()
|
|
|
|
.expect("internal error: expression stack empty");
|
2021-07-02 00:40:08 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(output, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_expression(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression: bool,
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: expression");
|
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
let mut pos = 0;
|
|
|
|
let mut shorthand = vec![];
|
2021-09-02 10:25:22 +02:00
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
while pos < spans.len() {
|
|
|
|
// Check if there is any environment shorthand
|
|
|
|
let name = working_set.get_span_contents(spans[pos]);
|
2022-03-03 01:55:03 +01:00
|
|
|
|
2022-03-29 22:56:55 +02:00
|
|
|
let split = name.splitn(2, |x| *x == b'=');
|
2021-11-04 03:32:35 +01:00
|
|
|
let split: Vec<_> = split.collect();
|
|
|
|
if split.len() == 2 && !split[0].is_empty() {
|
|
|
|
let point = split[0].len() + 1;
|
|
|
|
|
|
|
|
let lhs = parse_string_strict(
|
|
|
|
working_set,
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(spans[pos].start, spans[pos].start + point - 1),
|
2021-11-04 03:32:35 +01:00
|
|
|
);
|
|
|
|
let rhs = if spans[pos].start + point < spans[pos].end {
|
2022-12-03 10:44:12 +01:00
|
|
|
let rhs_span = Span::new(spans[pos].start + point, spans[pos].end);
|
2022-05-07 13:21:29 +02:00
|
|
|
|
|
|
|
if working_set.get_span_contents(rhs_span).starts_with(b"$") {
|
|
|
|
parse_dollar_expr(working_set, rhs_span, expand_aliases_denylist)
|
|
|
|
} else {
|
|
|
|
parse_string_strict(working_set, rhs_span)
|
|
|
|
}
|
2021-11-04 03:32:35 +01:00
|
|
|
} else {
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::String(String::new()),
|
2022-12-03 10:44:12 +01:00
|
|
|
span: Span::unknown(),
|
2021-11-04 03:32:35 +01:00
|
|
|
ty: Type::Nothing,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
if lhs.1.is_none() && rhs.1.is_none() {
|
|
|
|
shorthand.push((lhs.0, rhs.0));
|
|
|
|
pos += 1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if pos == spans.len() {
|
|
|
|
return (
|
|
|
|
garbage(span(spans)),
|
|
|
|
Some(ParseError::UnknownCommand(spans[0])),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-04-07 04:01:31 +02:00
|
|
|
let (output, err) = if is_math_expression_like(working_set, spans[pos], expand_aliases_denylist)
|
2022-02-22 16:55:28 +01:00
|
|
|
{
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_math_expression(working_set, &spans[pos..], None, expand_aliases_denylist)
|
2021-11-04 03:32:35 +01:00
|
|
|
} else {
|
2022-04-07 04:01:31 +02:00
|
|
|
let bytes = working_set.get_span_contents(spans[pos]);
|
|
|
|
|
2022-01-15 16:26:52 +01:00
|
|
|
// For now, check for special parses of certain keywords
|
|
|
|
match bytes {
|
|
|
|
b"def" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline("def".into(), spans[0])),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
2022-02-11 19:38:10 +01:00
|
|
|
b"extern" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"extern".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
2022-02-11 19:38:10 +01:00
|
|
|
),
|
2022-11-10 21:05:34 +01:00
|
|
|
b"for" => (
|
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-11-10 21:05:34 +01:00
|
|
|
)
|
|
|
|
.0,
|
|
|
|
Some(ParseError::BuiltinCommandInPipeline("for".into(), spans[0])),
|
|
|
|
),
|
2023-01-20 00:11:48 +01:00
|
|
|
b"let" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-05-26 02:13:14 +02:00
|
|
|
Some(ParseError::LetInPipeline(
|
2022-05-29 22:16:41 +02:00
|
|
|
String::from_utf8_lossy(match spans.len() {
|
2023-01-20 00:11:48 +01:00
|
|
|
1 | 2 | 3 => b"value",
|
|
|
|
_ => working_set.get_span_contents(spans[3]),
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
String::from_utf8_lossy(match spans.len() {
|
|
|
|
1 => b"variable",
|
|
|
|
_ => working_set.get_span_contents(spans[1]),
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
b"const" => (
|
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
|
|
|
is_subexpression,
|
|
|
|
)
|
|
|
|
.0,
|
|
|
|
Some(ParseError::ConstInPipeline(
|
|
|
|
String::from_utf8_lossy(match spans.len() {
|
2022-05-29 22:16:41 +02:00
|
|
|
1 | 2 | 3 => b"value",
|
|
|
|
_ => working_set.get_span_contents(spans[3]),
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
String::from_utf8_lossy(match spans.len() {
|
|
|
|
1 => b"variable",
|
|
|
|
_ => working_set.get_span_contents(spans[1]),
|
|
|
|
})
|
|
|
|
.to_string(),
|
2022-05-26 02:13:14 +02:00
|
|
|
spans[0],
|
|
|
|
)),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
2022-11-11 07:51:08 +01:00
|
|
|
b"mut" => (
|
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-11-11 07:51:08 +01:00
|
|
|
)
|
|
|
|
.0,
|
|
|
|
Some(ParseError::MutInPipeline(
|
|
|
|
String::from_utf8_lossy(match spans.len() {
|
|
|
|
1 | 2 | 3 => b"value",
|
|
|
|
_ => working_set.get_span_contents(spans[3]),
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
String::from_utf8_lossy(match spans.len() {
|
|
|
|
1 => b"variable",
|
|
|
|
_ => working_set.get_span_contents(spans[1]),
|
|
|
|
})
|
|
|
|
.to_string(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
|
|
|
),
|
2022-01-15 16:26:52 +01:00
|
|
|
b"alias" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"alias".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
|
|
|
b"module" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"module".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
|
|
|
b"use" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline("use".into(), spans[0])),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
2022-05-07 21:39:22 +02:00
|
|
|
b"overlay" => {
|
|
|
|
if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"list" {
|
|
|
|
// whitelist 'overlay list'
|
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-05-07 21:39:22 +02:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-05-07 21:39:22 +02:00
|
|
|
)
|
|
|
|
.0,
|
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"overlay".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-01-15 16:26:52 +01:00
|
|
|
b"source" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"source".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
|
|
|
b"export" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-01-15 16:26:52 +01:00
|
|
|
Some(ParseError::UnexpectedKeyword("export".into(), spans[0])),
|
|
|
|
),
|
|
|
|
b"hide" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"hide".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
2022-12-10 18:23:24 +01:00
|
|
|
b"where" => parse_where_expr(working_set, &spans[pos..], expand_aliases_denylist),
|
2022-01-15 16:26:52 +01:00
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
b"register" => (
|
2022-03-18 20:03:57 +01:00
|
|
|
parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
)
|
|
|
|
.0,
|
2022-02-15 20:31:14 +01:00
|
|
|
Some(ParseError::BuiltinCommandInPipeline(
|
|
|
|
"plugin".into(),
|
|
|
|
spans[0],
|
|
|
|
)),
|
2022-01-15 16:26:52 +01:00
|
|
|
),
|
|
|
|
|
2022-03-18 20:03:57 +01:00
|
|
|
_ => parse_call(
|
|
|
|
working_set,
|
|
|
|
&spans[pos..],
|
|
|
|
spans[0],
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-03-18 20:03:57 +01:00
|
|
|
),
|
2022-01-15 16:26:52 +01:00
|
|
|
}
|
2021-11-04 03:32:35 +01:00
|
|
|
};
|
|
|
|
|
2022-06-10 17:59:35 +02:00
|
|
|
let with_env = working_set.find_decl(b"with-env", &Type::Any);
|
2021-11-04 03:32:35 +01:00
|
|
|
|
|
|
|
if !shorthand.is_empty() {
|
|
|
|
if let Some(decl_id) = with_env {
|
|
|
|
let mut block = Block::default();
|
|
|
|
let ty = output.ty.clone();
|
2022-11-18 22:46:48 +01:00
|
|
|
block.pipelines = vec![Pipeline::from_vec(vec![output])];
|
2021-11-04 03:32:35 +01:00
|
|
|
|
|
|
|
let block_id = working_set.add_block(block);
|
|
|
|
|
|
|
|
let mut env_vars = vec![];
|
|
|
|
for sh in shorthand {
|
|
|
|
env_vars.push(sh.0);
|
|
|
|
env_vars.push(sh.1);
|
|
|
|
}
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
let arguments = vec![
|
|
|
|
Argument::Positional(Expression {
|
2021-11-04 03:32:35 +01:00
|
|
|
expr: Expr::List(env_vars),
|
|
|
|
span: span(&spans[..pos]),
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-11-04 03:32:35 +01:00
|
|
|
custom_completion: None,
|
2022-04-09 04:55:02 +02:00
|
|
|
}),
|
|
|
|
Argument::Positional(Expression {
|
2022-11-10 09:21:49 +01:00
|
|
|
expr: Expr::Closure(block_id),
|
2021-11-04 03:32:35 +01:00
|
|
|
span: span(&spans[pos..]),
|
2022-11-10 09:21:49 +01:00
|
|
|
ty: Type::Closure,
|
2021-11-04 03:32:35 +01:00
|
|
|
custom_completion: None,
|
2022-04-09 04:55:02 +02:00
|
|
|
}),
|
2021-11-04 03:32:35 +01:00
|
|
|
];
|
|
|
|
|
2022-02-21 18:58:04 +01:00
|
|
|
let expr = Expr::Call(Box::new(Call {
|
2022-12-03 10:44:12 +01:00
|
|
|
head: Span::unknown(),
|
2022-02-21 18:58:04 +01:00
|
|
|
decl_id,
|
2022-04-09 04:55:02 +02:00
|
|
|
arguments,
|
2022-02-21 23:22:21 +01:00
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 15:36:13 +01:00
|
|
|
parser_info: vec![],
|
2022-02-21 18:58:04 +01:00
|
|
|
}));
|
|
|
|
|
2021-11-04 03:32:35 +01:00
|
|
|
(
|
|
|
|
Expression {
|
2022-02-21 18:58:04 +01:00
|
|
|
expr,
|
2021-11-04 03:32:35 +01:00
|
|
|
custom_completion: None,
|
|
|
|
span: span(spans),
|
2022-11-10 09:21:49 +01:00
|
|
|
ty,
|
2021-11-04 03:32:35 +01:00
|
|
|
},
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(output, err)
|
|
|
|
}
|
2021-10-27 23:52:59 +02:00
|
|
|
} else {
|
2021-11-04 03:32:35 +01:00
|
|
|
(output, err)
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_variable(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
|
|
|
) -> (Option<VarId>, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if is_variable(bytes) {
|
|
|
|
if let Some(var_id) = working_set.find_variable(bytes) {
|
2022-06-12 21:18:00 +02:00
|
|
|
let input = working_set.get_variable(var_id).ty.clone();
|
|
|
|
working_set.type_scope.add_type(input);
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(Some(var_id), None)
|
2021-07-01 02:01:04 +02:00
|
|
|
} else {
|
2021-09-02 10:25:22 +02:00
|
|
|
(None, None)
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
} else {
|
2022-07-27 04:08:54 +02:00
|
|
|
(
|
|
|
|
None,
|
|
|
|
Some(ParseError::Expected("valid variable name".into(), span)),
|
|
|
|
)
|
2021-07-01 02:01:04 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
pub fn parse_builtin_commands(
|
2021-09-27 14:10:18 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
2022-01-22 19:24:47 +01:00
|
|
|
lite_command: &LiteCommand,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression: bool,
|
2022-02-15 20:31:14 +01:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2022-01-22 19:24:47 +01:00
|
|
|
let name = working_set.get_span_contents(lite_command.parts[0]);
|
2021-09-27 14:10:18 +02:00
|
|
|
|
2021-09-13 21:59:11 +02:00
|
|
|
match name {
|
2023-01-22 20:34:15 +01:00
|
|
|
b"def" | b"def-env" => parse_def(working_set, lite_command, None, expand_aliases_denylist),
|
|
|
|
b"extern" => parse_extern(working_set, lite_command, None, expand_aliases_denylist),
|
2022-12-21 23:21:03 +01:00
|
|
|
b"let" | b"const" => {
|
|
|
|
parse_let_or_const(working_set, &lite_command.parts, expand_aliases_denylist)
|
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
b"mut" => parse_mut(working_set, &lite_command.parts, expand_aliases_denylist),
|
2022-01-15 16:26:52 +01:00
|
|
|
b"for" => {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (expr, err) = parse_for(working_set, &lite_command.parts, expand_aliases_denylist);
|
2022-02-15 20:31:14 +01:00
|
|
|
(Pipeline::from_vec(vec![expr]), err)
|
2022-01-15 16:26:52 +01:00
|
|
|
}
|
2023-01-22 20:34:15 +01:00
|
|
|
b"alias" => parse_alias(working_set, lite_command, None, expand_aliases_denylist),
|
2022-12-30 16:44:37 +01:00
|
|
|
b"module" => parse_module(working_set, lite_command, expand_aliases_denylist),
|
2022-07-29 10:57:10 +02:00
|
|
|
b"use" => {
|
|
|
|
let (pipeline, _, err) =
|
|
|
|
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
|
|
|
(pipeline, err)
|
|
|
|
}
|
2022-05-07 21:39:22 +02:00
|
|
|
b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
|
2022-09-08 22:41:49 +02:00
|
|
|
b"source" | b"source-env" => {
|
|
|
|
parse_source(working_set, &lite_command.parts, expand_aliases_denylist)
|
|
|
|
}
|
2022-08-22 23:19:47 +02:00
|
|
|
b"export" => parse_export_in_block(working_set, lite_command, expand_aliases_denylist),
|
2022-03-18 20:03:57 +01:00
|
|
|
b"hide" => parse_hide(working_set, &lite_command.parts, expand_aliases_denylist),
|
2022-12-10 18:23:24 +01:00
|
|
|
b"where" => parse_where(working_set, &lite_command.parts, expand_aliases_denylist),
|
2021-11-02 21:56:00 +01:00
|
|
|
#[cfg(feature = "plugin")]
|
2022-03-18 20:03:57 +01:00
|
|
|
b"register" => parse_register(working_set, &lite_command.parts, expand_aliases_denylist),
|
2021-09-13 21:59:11 +02:00
|
|
|
_ => {
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
let (expr, err) = parse_expression(
|
|
|
|
working_set,
|
|
|
|
&lite_command.parts,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
is_subexpression,
|
|
|
|
);
|
2022-02-15 20:31:14 +01:00
|
|
|
(Pipeline::from_vec(vec![expr]), err)
|
2021-09-27 14:10:18 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
2021-06-30 03:42:56 +02:00
|
|
|
|
2021-11-11 00:14:00 +01:00
|
|
|
pub fn parse_record(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
span: Span,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-11-11 00:14:00 +01:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
let bytes = working_set.get_span_contents(span);
|
|
|
|
|
|
|
|
let mut error = None;
|
|
|
|
let mut start = span.start;
|
|
|
|
let mut end = span.end;
|
|
|
|
|
|
|
|
if bytes.starts_with(b"{") {
|
|
|
|
start += 1;
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
"{".into(),
|
2022-12-03 10:44:12 +01:00
|
|
|
Span::new(start, start + 1),
|
2021-11-11 00:14:00 +01:00
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if bytes.ends_with(b"}") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 10:44:12 +01:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span::new(end, end))));
|
2021-11-11 00:14:00 +01:00
|
|
|
}
|
|
|
|
|
2022-12-03 10:44:12 +01:00
|
|
|
let inner_span = Span::new(start, end);
|
2022-01-03 06:21:26 +01:00
|
|
|
let source = working_set.get_span_contents(inner_span);
|
2021-11-11 00:14:00 +01:00
|
|
|
|
2021-11-21 19:13:09 +01:00
|
|
|
let (tokens, err) = lex(source, start, &[b'\n', b'\r', b','], &[b':'], true);
|
2021-11-11 00:14:00 +01:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let mut output = vec![];
|
|
|
|
let mut idx = 0;
|
|
|
|
|
|
|
|
while idx < tokens.len() {
|
2022-03-18 20:03:57 +01:00
|
|
|
let (field, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
tokens[idx].span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-11-11 00:14:00 +01:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
idx += 1;
|
|
|
|
if idx == tokens.len() {
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("record".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let colon = working_set.get_span_contents(tokens[idx].span);
|
|
|
|
idx += 1;
|
|
|
|
if idx == tokens.len() || colon != b":" {
|
|
|
|
//FIXME: need better error
|
|
|
|
return (
|
|
|
|
garbage(span),
|
|
|
|
Some(ParseError::Expected("record".into(), span)),
|
|
|
|
);
|
|
|
|
}
|
2022-03-18 20:03:57 +01:00
|
|
|
let (value, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
tokens[idx].span,
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-11-11 00:14:00 +01:00
|
|
|
error = error.or(err);
|
|
|
|
idx += 1;
|
|
|
|
|
|
|
|
output.push((field, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Record(output),
|
|
|
|
span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any, //FIXME: but we don't know the contents of the fields, do we?
|
2021-11-11 00:14:00 +01:00
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
pub fn parse_block(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2022-11-18 22:46:48 +01:00
|
|
|
tokens: &[Token],
|
2021-09-02 10:25:22 +02:00
|
|
|
scoped: bool,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-04-08 23:41:05 +02:00
|
|
|
is_subexpression: bool,
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Block, Option<ParseError>) {
|
2022-11-18 22:46:48 +01:00
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let (lite_block, err) = lite_parse(tokens);
|
|
|
|
error = error.or(err);
|
|
|
|
|
2022-01-01 22:42:50 +01:00
|
|
|
trace!("parsing block: {:?}", lite_block);
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if scoped {
|
|
|
|
working_set.enter_scope();
|
|
|
|
}
|
2022-06-12 21:18:00 +02:00
|
|
|
working_set.type_scope.enter_scope();
|
2021-06-30 03:42:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
// Pre-declare any definition so that definitions
|
|
|
|
// that share the same block can see each other
|
|
|
|
for pipeline in &lite_block.block {
|
|
|
|
if pipeline.commands.len() == 1 {
|
2022-11-18 22:46:48 +01:00
|
|
|
match &pipeline.commands[0] {
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
LiteElement::Command(_, command)
|
|
|
|
| LiteElement::Redirection(_, _, command)
|
|
|
|
| LiteElement::SeparateRedirection {
|
|
|
|
out: (_, command), ..
|
|
|
|
} => {
|
2022-11-18 22:46:48 +01:00
|
|
|
if let Some(err) =
|
|
|
|
parse_def_predecl(working_set, &command.parts, expand_aliases_denylist)
|
|
|
|
{
|
|
|
|
error = error.or(Some(err));
|
|
|
|
}
|
|
|
|
}
|
2021-10-01 22:16:27 +02:00
|
|
|
}
|
2021-09-02 10:25:22 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2021-09-10 09:28:43 +02:00
|
|
|
let block: Block = lite_block
|
|
|
|
.block
|
|
|
|
.iter()
|
2022-01-27 00:46:13 +01:00
|
|
|
.enumerate()
|
|
|
|
.map(|(idx, pipeline)| {
|
2021-09-10 09:28:43 +02:00
|
|
|
if pipeline.commands.len() > 1 {
|
2021-11-08 07:21:24 +01:00
|
|
|
let mut output = pipeline
|
2021-09-10 09:28:43 +02:00
|
|
|
.commands
|
|
|
|
.iter()
|
2022-11-18 22:46:48 +01:00
|
|
|
.map(|command| match command {
|
2022-11-22 19:26:13 +01:00
|
|
|
LiteElement::Command(span, command) => {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: pipeline element: command");
|
2022-11-18 22:46:48 +01:00
|
|
|
let (expr, err) = parse_expression(
|
|
|
|
working_set,
|
|
|
|
&command.parts,
|
|
|
|
expand_aliases_denylist,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression,
|
2022-11-18 22:46:48 +01:00
|
|
|
);
|
|
|
|
working_set.type_scope.add_type(expr.ty.clone());
|
2022-06-12 21:18:00 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
if error.is_none() {
|
|
|
|
error = err;
|
|
|
|
}
|
|
|
|
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(*span, expr)
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
2022-11-22 19:26:13 +01:00
|
|
|
LiteElement::Redirection(span, redirection, command) => {
|
2022-12-10 18:23:24 +01:00
|
|
|
trace!("parsing: pipeline element: redirection");
|
2022-11-22 19:26:13 +01:00
|
|
|
let (expr, err) = parse_string(
|
2022-11-18 22:46:48 +01:00
|
|
|
working_set,
|
2022-11-22 19:26:13 +01:00
|
|
|
command.parts[0],
|
2022-11-18 22:46:48 +01:00
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
working_set.type_scope.add_type(expr.ty.clone());
|
|
|
|
|
|
|
|
if error.is_none() {
|
|
|
|
error = err;
|
|
|
|
}
|
|
|
|
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Redirection(*span, redirection.clone(), expr)
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
LiteElement::SeparateRedirection {
|
|
|
|
out: (out_span, out_command),
|
|
|
|
err: (err_span, err_command),
|
|
|
|
} => {
|
|
|
|
trace!("parsing: pipeline element: separate redirection");
|
|
|
|
let (out_expr, out_err) = parse_string(
|
|
|
|
working_set,
|
|
|
|
out_command.parts[0],
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
working_set.type_scope.add_type(out_expr.ty.clone());
|
|
|
|
|
|
|
|
if error.is_none() {
|
|
|
|
error = out_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
let (err_expr, err_err) = parse_string(
|
|
|
|
working_set,
|
|
|
|
err_command.parts[0],
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
working_set.type_scope.add_type(err_expr.ty.clone());
|
|
|
|
|
|
|
|
if error.is_none() {
|
|
|
|
error = err_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
PipelineElement::SeparateRedirection {
|
|
|
|
out: (*out_span, out_expr),
|
|
|
|
err: (*err_span, err_expr),
|
|
|
|
}
|
|
|
|
}
|
2021-09-10 09:28:43 +02:00
|
|
|
})
|
2022-11-18 22:46:48 +01:00
|
|
|
.collect::<Vec<PipelineElement>>();
|
2021-09-10 09:28:43 +02:00
|
|
|
|
2022-04-08 23:41:05 +02:00
|
|
|
if is_subexpression {
|
2022-11-18 22:46:48 +01:00
|
|
|
for element in output.iter_mut().skip(1) {
|
|
|
|
if element.has_in_variable(working_set) {
|
|
|
|
*element = wrap_element_with_collect(working_set, element);
|
2022-04-08 23:41:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-11-18 22:46:48 +01:00
|
|
|
for element in output.iter_mut() {
|
|
|
|
if element.has_in_variable(working_set) {
|
|
|
|
*element = wrap_element_with_collect(working_set, element);
|
2022-04-08 23:41:05 +02:00
|
|
|
}
|
2021-11-08 07:21:24 +01:00
|
|
|
}
|
|
|
|
}
|
2021-11-08 08:13:55 +01:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
Pipeline { elements: output }
|
2021-09-10 09:28:43 +02:00
|
|
|
} else {
|
2022-12-13 04:36:13 +01:00
|
|
|
match &pipeline.commands[0] {
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
LiteElement::Command(_, command)
|
|
|
|
| LiteElement::Redirection(_, _, command)
|
|
|
|
| LiteElement::SeparateRedirection {
|
|
|
|
out: (_, command), ..
|
|
|
|
} => {
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
let (mut pipeline, err) = parse_builtin_commands(
|
|
|
|
working_set,
|
|
|
|
command,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
is_subexpression,
|
|
|
|
);
|
2022-11-18 22:46:48 +01:00
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
if idx == 0 {
|
|
|
|
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Any) {
|
|
|
|
if let Some(let_env_decl_id) =
|
|
|
|
working_set.find_decl(b"let-env", &Type::Any)
|
2022-02-15 20:31:14 +01:00
|
|
|
{
|
2022-12-13 04:36:13 +01:00
|
|
|
for element in pipeline.elements.iter_mut() {
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) = element
|
2022-01-27 00:46:13 +01:00
|
|
|
{
|
2022-12-13 04:36:13 +01:00
|
|
|
if call.decl_id == let_decl_id
|
|
|
|
|| call.decl_id == let_env_decl_id
|
|
|
|
{
|
|
|
|
// Do an expansion
|
|
|
|
if let Some(Expression {
|
|
|
|
expr: Expr::Keyword(_, _, expr),
|
|
|
|
..
|
|
|
|
}) = call.positional_iter_mut().nth(1)
|
|
|
|
{
|
|
|
|
if expr.has_in_variable(working_set) {
|
|
|
|
*expr = Box::new(wrap_expr_with_collect(
|
|
|
|
working_set,
|
|
|
|
expr,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
} else if element.has_in_variable(working_set)
|
|
|
|
&& !is_subexpression
|
|
|
|
{
|
|
|
|
*element =
|
|
|
|
wrap_element_with_collect(working_set, element);
|
2022-01-27 00:46:13 +01:00
|
|
|
}
|
2022-12-13 04:36:13 +01:00
|
|
|
} else if element.has_in_variable(working_set)
|
|
|
|
&& !is_subexpression
|
|
|
|
{
|
|
|
|
*element =
|
|
|
|
wrap_element_with_collect(working_set, element);
|
2022-01-27 00:46:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-12 21:53:46 +01:00
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
if error.is_none() {
|
|
|
|
error = err;
|
|
|
|
}
|
2022-12-12 21:53:46 +01:00
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
pipeline
|
|
|
|
}
|
|
|
|
}
|
2021-09-10 09:28:43 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.into();
|
2021-06-30 03:42:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
if scoped {
|
|
|
|
working_set.exit_scope();
|
2021-06-30 03:42:56 +02:00
|
|
|
}
|
2022-06-12 21:18:00 +02:00
|
|
|
working_set.type_scope.exit_scope();
|
2021-06-30 03:42:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(block, error)
|
|
|
|
}
|
2021-06-30 03:42:56 +02:00
|
|
|
|
2022-11-10 09:21:49 +01:00
|
|
|
pub fn discover_captures_in_closure(
|
2021-10-25 22:04:23 +02:00
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
block: &Block,
|
|
|
|
seen: &mut Vec<VarId>,
|
2022-11-11 07:51:08 +01:00
|
|
|
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
|
|
|
|
) -> Result<Vec<(VarId, Span)>, ParseError> {
|
2021-10-25 22:04:23 +02:00
|
|
|
let mut output = vec![];
|
|
|
|
|
|
|
|
for flag in &block.signature.named {
|
|
|
|
if let Some(var_id) = flag.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for positional in &block.signature.required_positional {
|
|
|
|
if let Some(var_id) = positional.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for positional in &block.signature.optional_positional {
|
|
|
|
if let Some(var_id) = positional.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for positional in &block.signature.rest_positional {
|
|
|
|
if let Some(var_id) = positional.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
for pipeline in &block.pipelines {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks)?;
|
2022-02-15 20:31:14 +01:00
|
|
|
output.extend(&result);
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
Ok(output)
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-02-11 00:15:15 +01:00
|
|
|
fn discover_captures_in_pipeline(
|
2021-10-25 22:04:23 +02:00
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
pipeline: &Pipeline,
|
|
|
|
seen: &mut Vec<VarId>,
|
2022-11-11 07:51:08 +01:00
|
|
|
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
|
|
|
|
) -> Result<Vec<(VarId, Span)>, ParseError> {
|
2021-10-25 22:04:23 +02:00
|
|
|
let mut output = vec![];
|
2022-11-18 22:46:48 +01:00
|
|
|
for element in &pipeline.elements {
|
|
|
|
let result =
|
|
|
|
discover_captures_in_pipeline_element(working_set, element, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
Ok(output)
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
// Closes over captured variables
|
|
|
|
pub fn discover_captures_in_pipeline_element(
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
element: &PipelineElement,
|
|
|
|
seen: &mut Vec<VarId>,
|
|
|
|
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
|
|
|
|
) -> Result<Vec<(VarId, Span)>, ParseError> {
|
|
|
|
match element {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(_, expression)
|
|
|
|
| PipelineElement::Redirection(_, _, expression)
|
2022-12-13 04:36:13 +01:00
|
|
|
| PipelineElement::And(_, expression)
|
2022-11-22 19:26:13 +01:00
|
|
|
| PipelineElement::Or(_, expression) => {
|
2022-11-18 22:46:48 +01:00
|
|
|
discover_captures_in_expr(working_set, expression, seen, seen_blocks)
|
|
|
|
}
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
PipelineElement::SeparateRedirection {
|
|
|
|
out: (_, out_expr),
|
|
|
|
err: (_, err_expr),
|
|
|
|
} => {
|
|
|
|
let mut result = discover_captures_in_expr(working_set, out_expr, seen, seen_blocks)?;
|
|
|
|
result.append(&mut discover_captures_in_expr(
|
|
|
|
working_set,
|
|
|
|
err_expr,
|
|
|
|
seen,
|
|
|
|
seen_blocks,
|
|
|
|
)?);
|
|
|
|
Ok(result)
|
|
|
|
}
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
// Closes over captured variables
|
2022-02-11 00:15:15 +01:00
|
|
|
pub fn discover_captures_in_expr(
|
2021-10-25 22:04:23 +02:00
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
expr: &Expression,
|
|
|
|
seen: &mut Vec<VarId>,
|
2022-11-11 07:51:08 +01:00
|
|
|
seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
|
|
|
|
) -> Result<Vec<(VarId, Span)>, ParseError> {
|
|
|
|
let mut output: Vec<(VarId, Span)> = vec![];
|
2021-10-25 22:04:23 +02:00
|
|
|
match &expr.expr {
|
|
|
|
Expr::BinaryOp(lhs, _, rhs) => {
|
2022-11-11 07:51:08 +01:00
|
|
|
let lhs_result = discover_captures_in_expr(working_set, lhs, seen, seen_blocks)?;
|
|
|
|
let rhs_result = discover_captures_in_expr(working_set, rhs, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
|
|
|
|
output.extend(&lhs_result);
|
|
|
|
output.extend(&rhs_result);
|
|
|
|
}
|
2022-04-06 21:10:25 +02:00
|
|
|
Expr::UnaryNot(expr) => {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2022-04-06 21:10:25 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
2022-11-10 09:21:49 +01:00
|
|
|
Expr::Closure(block_id) => {
|
|
|
|
let block = working_set.get_block(*block_id);
|
|
|
|
let results = {
|
|
|
|
let mut seen = vec![];
|
2022-11-11 07:51:08 +01:00
|
|
|
let results =
|
|
|
|
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?;
|
|
|
|
|
|
|
|
for (var_id, span) in results.iter() {
|
|
|
|
if !seen.contains(var_id) {
|
|
|
|
if let Some(variable) = working_set.get_variable_if_possible(*var_id) {
|
|
|
|
if variable.mutable {
|
|
|
|
return Err(ParseError::CaptureOfMutableVar(*span));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
results
|
2022-11-10 09:21:49 +01:00
|
|
|
};
|
|
|
|
seen_blocks.insert(*block_id, results.clone());
|
2022-11-11 07:51:08 +01:00
|
|
|
for (var_id, span) in results.into_iter() {
|
2022-11-10 09:21:49 +01:00
|
|
|
if !seen.contains(&var_id) {
|
2022-11-11 07:51:08 +01:00
|
|
|
output.push((var_id, span))
|
2022-11-10 09:21:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::Block(block_id) => {
|
2022-05-17 00:49:59 +02:00
|
|
|
let block = working_set.get_block(*block_id);
|
2022-11-10 09:21:49 +01:00
|
|
|
// FIXME: is this correct?
|
2022-05-17 00:49:59 +02:00
|
|
|
let results = {
|
|
|
|
let mut seen = vec![];
|
2022-11-11 07:51:08 +01:00
|
|
|
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?
|
2022-05-17 00:49:59 +02:00
|
|
|
};
|
|
|
|
seen_blocks.insert(*block_id, results.clone());
|
2022-11-11 07:51:08 +01:00
|
|
|
for (var_id, span) in results.into_iter() {
|
2022-05-17 00:49:59 +02:00
|
|
|
if !seen.contains(&var_id) {
|
2022-11-11 07:51:08 +01:00
|
|
|
output.push((var_id, span))
|
2022-02-11 00:15:15 +01:00
|
|
|
}
|
|
|
|
}
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
2022-03-01 00:31:53 +01:00
|
|
|
Expr::Binary(_) => {}
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::Bool(_) => {}
|
|
|
|
Expr::Call(call) => {
|
2022-01-12 05:06:56 +01:00
|
|
|
let decl = working_set.get_decl(call.decl_id);
|
2022-02-11 00:15:15 +01:00
|
|
|
if let Some(block_id) = decl.get_block_id() {
|
|
|
|
match seen_blocks.get(&block_id) {
|
|
|
|
Some(capture_list) => {
|
|
|
|
output.extend(capture_list);
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let block = working_set.get_block(block_id);
|
|
|
|
if !block.captures.is_empty() {
|
2022-11-11 07:51:08 +01:00
|
|
|
output.extend(block.captures.iter().map(|var_id| (*var_id, call.head)));
|
2022-02-11 00:15:15 +01:00
|
|
|
} else {
|
|
|
|
let mut seen = vec![];
|
|
|
|
seen_blocks.insert(block_id, output.clone());
|
|
|
|
|
2022-11-10 09:21:49 +01:00
|
|
|
let result = discover_captures_in_closure(
|
2022-02-11 00:15:15 +01:00
|
|
|
working_set,
|
|
|
|
block,
|
|
|
|
&mut seen,
|
|
|
|
seen_blocks,
|
2022-11-11 07:51:08 +01:00
|
|
|
)?;
|
2022-02-11 00:15:15 +01:00
|
|
|
output.extend(&result);
|
|
|
|
seen_blocks.insert(block_id, result);
|
|
|
|
}
|
2022-01-21 17:39:55 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-12 05:06:56 +01:00
|
|
|
}
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
for named in call.named_iter() {
|
2022-04-09 07:17:48 +02:00
|
|
|
if let Some(arg) = &named.2 {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, arg, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
for positional in call.positional_iter() {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, positional, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::CellPath(_) => {}
|
2022-02-24 03:02:48 +01:00
|
|
|
Expr::DateTime(_) => {}
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
Expr::ExternalCall(head, exprs, _) => {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, head, seen, seen_blocks)?;
|
2022-01-13 09:17:45 +01:00
|
|
|
output.extend(&result);
|
|
|
|
|
2021-10-25 22:04:23 +02:00
|
|
|
for expr in exprs {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::Filepath(_) => {}
|
2022-04-22 22:18:51 +02:00
|
|
|
Expr::Directory(_) => {}
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::Float(_) => {}
|
|
|
|
Expr::FullCellPath(cell_path) => {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result =
|
|
|
|
discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
2021-11-16 00:16:06 +01:00
|
|
|
Expr::ImportPattern(_) => {}
|
2022-09-04 17:36:42 +02:00
|
|
|
Expr::Overlay(_) => {}
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::Garbage => {}
|
2021-12-20 02:05:33 +01:00
|
|
|
Expr::Nothing => {}
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::GlobPattern(_) => {}
|
|
|
|
Expr::Int(_) => {}
|
|
|
|
Expr::Keyword(_, _, expr) => {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
Expr::List(exprs) => {
|
|
|
|
for expr in exprs {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::Operator(_) => {}
|
|
|
|
Expr::Range(expr1, expr2, expr3, _) => {
|
|
|
|
if let Some(expr) = expr1 {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
if let Some(expr) = expr2 {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
if let Some(expr) = expr3 {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
2021-11-11 00:14:00 +01:00
|
|
|
Expr::Record(fields) => {
|
|
|
|
for (field_name, field_value) in fields {
|
2022-02-11 00:15:15 +01:00
|
|
|
output.extend(&discover_captures_in_expr(
|
2022-01-21 17:39:55 +01:00
|
|
|
working_set,
|
|
|
|
field_name,
|
|
|
|
seen,
|
2022-02-11 00:15:15 +01:00
|
|
|
seen_blocks,
|
2022-11-11 07:51:08 +01:00
|
|
|
)?);
|
2022-02-11 00:15:15 +01:00
|
|
|
output.extend(&discover_captures_in_expr(
|
2022-01-21 17:39:55 +01:00
|
|
|
working_set,
|
|
|
|
field_value,
|
|
|
|
seen,
|
2022-02-11 00:15:15 +01:00
|
|
|
seen_blocks,
|
2022-11-11 07:51:08 +01:00
|
|
|
)?);
|
2021-11-11 00:14:00 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-12 05:06:56 +01:00
|
|
|
Expr::Signature(sig) => {
|
|
|
|
// Something with a declaration, similar to a var decl, will introduce more VarIds into the stack at eval
|
|
|
|
for pos in &sig.required_positional {
|
|
|
|
if let Some(var_id) = pos.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for pos in &sig.optional_positional {
|
|
|
|
if let Some(var_id) = pos.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(rest) = &sig.rest_positional {
|
|
|
|
if let Some(var_id) = rest.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for named in &sig.named {
|
|
|
|
if let Some(var_id) = named.var_id {
|
|
|
|
seen.push(var_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::String(_) => {}
|
2021-12-25 21:50:02 +01:00
|
|
|
Expr::StringInterpolation(exprs) => {
|
|
|
|
for expr in exprs {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-12-25 21:50:02 +01:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
2021-11-26 04:49:03 +01:00
|
|
|
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
|
2022-05-17 00:49:59 +02:00
|
|
|
let block = working_set.get_block(*block_id);
|
|
|
|
let results = {
|
|
|
|
let mut seen = vec![];
|
2022-11-11 07:51:08 +01:00
|
|
|
discover_captures_in_closure(working_set, block, &mut seen, seen_blocks)?
|
2022-05-17 00:49:59 +02:00
|
|
|
};
|
|
|
|
seen_blocks.insert(*block_id, results.clone());
|
2022-11-11 07:51:08 +01:00
|
|
|
for (var_id, span) in results.into_iter() {
|
2022-05-17 00:49:59 +02:00
|
|
|
if !seen.contains(&var_id) {
|
2022-11-11 07:51:08 +01:00
|
|
|
output.push((var_id, span))
|
2022-02-11 13:37:10 +01:00
|
|
|
}
|
|
|
|
}
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
|
|
|
Expr::Table(headers, values) => {
|
|
|
|
for header in headers {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, header, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
for row in values {
|
|
|
|
for cell in row {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, cell, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::ValueWithUnit(expr, _) => {
|
2022-11-11 07:51:08 +01:00
|
|
|
let result = discover_captures_in_expr(working_set, expr, seen, seen_blocks)?;
|
2021-10-25 22:04:23 +02:00
|
|
|
output.extend(&result);
|
|
|
|
}
|
|
|
|
Expr::Var(var_id) => {
|
2022-01-12 05:06:56 +01:00
|
|
|
if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) {
|
2022-11-11 07:51:08 +01:00
|
|
|
output.push((*var_id, expr.span));
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::VarDecl(var_id) => {
|
|
|
|
seen.push(*var_id);
|
|
|
|
}
|
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
Ok(output)
|
2021-10-25 22:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
fn wrap_element_with_collect(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
element: &PipelineElement,
|
|
|
|
) -> PipelineElement {
|
|
|
|
match element {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(span, expression) => {
|
|
|
|
PipelineElement::Expression(*span, wrap_expr_with_collect(working_set, expression))
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Redirection(span, redirection, expression) => {
|
|
|
|
PipelineElement::Redirection(
|
|
|
|
*span,
|
|
|
|
redirection.clone(),
|
|
|
|
wrap_expr_with_collect(working_set, expression),
|
|
|
|
)
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
PipelineElement::SeparateRedirection {
|
|
|
|
out: (out_span, out_exp),
|
|
|
|
err: (err_span, err_exp),
|
|
|
|
} => PipelineElement::SeparateRedirection {
|
|
|
|
out: (*out_span, wrap_expr_with_collect(working_set, out_exp)),
|
|
|
|
err: (*err_span, wrap_expr_with_collect(working_set, err_exp)),
|
|
|
|
},
|
2022-12-13 04:36:13 +01:00
|
|
|
PipelineElement::And(span, expression) => {
|
|
|
|
PipelineElement::And(*span, wrap_expr_with_collect(working_set, expression))
|
|
|
|
}
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Or(span, expression) => {
|
|
|
|
PipelineElement::Or(*span, wrap_expr_with_collect(working_set, expression))
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 07:21:24 +01:00
|
|
|
fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression {
|
|
|
|
let span = expr.span;
|
|
|
|
|
2022-06-10 17:59:35 +02:00
|
|
|
if let Some(decl_id) = working_set.find_decl(b"collect", &Type::Any) {
|
2021-11-08 07:21:24 +01:00
|
|
|
let mut output = vec![];
|
|
|
|
|
2023-01-28 18:55:29 +01:00
|
|
|
let var_id = IN_VARIABLE_ID;
|
2021-11-08 07:21:24 +01:00
|
|
|
let mut signature = Signature::new("");
|
|
|
|
signature.required_positional.push(PositionalArg {
|
|
|
|
var_id: Some(var_id),
|
2021-12-30 04:26:40 +01:00
|
|
|
name: "$in".into(),
|
2021-11-08 07:21:24 +01:00
|
|
|
desc: String::new(),
|
|
|
|
shape: SyntaxShape::Any,
|
2022-03-07 21:08:56 +01:00
|
|
|
default_value: None,
|
2021-11-08 07:21:24 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
let mut expr = expr.clone();
|
|
|
|
expr.replace_in_variable(working_set, var_id);
|
|
|
|
|
2022-02-11 00:15:15 +01:00
|
|
|
let block = Block {
|
2022-11-18 22:46:48 +01:00
|
|
|
pipelines: vec![Pipeline::from_vec(vec![expr])],
|
2021-11-08 07:21:24 +01:00
|
|
|
signature: Box::new(signature),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let block_id = working_set.add_block(block);
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
output.push(Argument::Positional(Expression {
|
2022-11-10 09:21:49 +01:00
|
|
|
expr: Expr::Closure(block_id),
|
2021-11-08 07:21:24 +01:00
|
|
|
span,
|
2022-04-07 06:34:09 +02:00
|
|
|
ty: Type::Any,
|
2021-11-08 07:21:24 +01:00
|
|
|
custom_completion: None,
|
2022-04-09 04:55:02 +02:00
|
|
|
}));
|
2021-11-08 07:21:24 +01:00
|
|
|
|
2022-10-13 11:04:34 +02:00
|
|
|
output.push(Argument::Named((
|
|
|
|
Spanned {
|
|
|
|
item: "keep-env".to_string(),
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)));
|
|
|
|
|
2021-12-30 04:26:40 +01:00
|
|
|
// The containing, synthetic call to `collect`.
|
|
|
|
// We don't want to have a real span as it will confuse flattening
|
|
|
|
// The args are where we'll get the real info
|
2021-11-08 07:21:24 +01:00
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(Box::new(Call {
|
2021-12-30 04:26:40 +01:00
|
|
|
head: Span::new(0, 0),
|
2022-04-09 04:55:02 +02:00
|
|
|
arguments: output,
|
2021-11-08 07:21:24 +01:00
|
|
|
decl_id,
|
2022-02-21 23:22:21 +01:00
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 15:36:13 +01:00
|
|
|
parser_info: vec![],
|
2021-11-08 07:21:24 +01:00
|
|
|
})),
|
|
|
|
span,
|
|
|
|
ty: Type::String,
|
|
|
|
custom_completion: None,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Expression::garbage(span)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-06 22:41:30 +02:00
|
|
|
// Parses a vector of u8 to create an AST Block. If a file name is given, then
|
|
|
|
// the name is stored in the working set. When parsing a source without a file
|
|
|
|
// name, the source of bytes is stored as "source"
|
|
|
|
pub fn parse(
|
2021-09-02 10:25:22 +02:00
|
|
|
working_set: &mut StateWorkingSet,
|
2021-09-06 22:41:30 +02:00
|
|
|
fname: Option<&str>,
|
2021-09-02 10:25:22 +02:00
|
|
|
contents: &[u8],
|
|
|
|
scoped: bool,
|
2022-03-18 20:03:57 +01:00
|
|
|
expand_aliases_denylist: &[usize],
|
2021-09-02 10:25:22 +02:00
|
|
|
) -> (Block, Option<ParseError>) {
|
|
|
|
let mut error = None;
|
2021-07-03 03:29:56 +02:00
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
let span_offset = working_set.next_span_start();
|
2021-07-03 05:35:15 +02:00
|
|
|
|
2021-09-06 22:41:30 +02:00
|
|
|
let name = match fname {
|
|
|
|
Some(fname) => fname.to_string(),
|
|
|
|
None => "source".to_string(),
|
|
|
|
};
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2021-09-06 22:41:30 +02:00
|
|
|
working_set.add_file(name, contents);
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2022-01-22 19:24:47 +01:00
|
|
|
let (output, err) = lex(contents, span_offset, &[], &[], false);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
2021-07-01 02:01:04 +02:00
|
|
|
|
2022-04-08 23:41:05 +02:00
|
|
|
let (mut output, err) =
|
|
|
|
parse_block(working_set, &output, scoped, expand_aliases_denylist, false);
|
2021-09-02 10:25:22 +02:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-02-11 00:15:15 +01:00
|
|
|
let mut seen = vec![];
|
|
|
|
let mut seen_blocks = HashMap::new();
|
|
|
|
|
2022-11-10 09:21:49 +01:00
|
|
|
let captures = discover_captures_in_closure(working_set, &output, &mut seen, &mut seen_blocks);
|
2022-11-11 07:51:08 +01:00
|
|
|
match captures {
|
|
|
|
Ok(captures) => output.captures = captures.into_iter().map(|(var_id, _)| var_id).collect(),
|
|
|
|
Err(err) => error = Some(err),
|
|
|
|
}
|
2022-02-11 00:15:15 +01:00
|
|
|
|
2022-02-11 13:37:10 +01:00
|
|
|
// Also check other blocks that might have been imported
|
|
|
|
for (block_idx, block) in working_set.delta.blocks.iter().enumerate() {
|
|
|
|
let block_id = block_idx + working_set.permanent_state.num_blocks();
|
|
|
|
|
|
|
|
if !seen_blocks.contains_key(&block_id) {
|
|
|
|
let captures =
|
2022-11-10 09:21:49 +01:00
|
|
|
discover_captures_in_closure(working_set, block, &mut seen, &mut seen_blocks);
|
2022-11-11 07:51:08 +01:00
|
|
|
match captures {
|
|
|
|
Ok(captures) => {
|
|
|
|
seen_blocks.insert(block_id, captures);
|
|
|
|
}
|
|
|
|
Err(err) => error = Some(err),
|
|
|
|
}
|
2022-02-11 13:37:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-11 00:15:15 +01:00
|
|
|
for (block_id, captures) in seen_blocks.into_iter() {
|
|
|
|
// In theory, we should only be updating captures where we have new information
|
|
|
|
// the only place where this is possible would be blocks that are newly created
|
|
|
|
// by our working set delta. If we ever tried to modify the permanent state, we'd
|
|
|
|
// panic (again, in theory, this shouldn't be possible)
|
|
|
|
let block = working_set.get_block(block_id);
|
|
|
|
let block_captures_empty = block.captures.is_empty();
|
|
|
|
if !captures.is_empty() && block_captures_empty {
|
|
|
|
let block = working_set.get_block_mut(block_id);
|
2022-11-11 07:51:08 +01:00
|
|
|
block.captures = captures.into_iter().map(|(var_id, _)| var_id).collect();
|
2022-02-11 00:15:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-02 10:25:22 +02:00
|
|
|
(output, error)
|
2021-06-30 03:42:56 +02:00
|
|
|
}
|