2021-08-30 20:36:07 +02:00
|
|
|
use nu_parser::*;
|
2023-04-15 10:24:51 +02:00
|
|
|
use nu_protocol::ast::{Argument, Call, PathMember};
|
2023-03-16 04:50:58 +01:00
|
|
|
use nu_protocol::Span;
|
2021-09-02 20:21:37 +02:00
|
|
|
use nu_protocol::{
|
2022-11-18 22:46:48 +01:00
|
|
|
ast::{Expr, Expression, PipelineElement},
|
2021-10-25 08:31:39 +02:00
|
|
|
engine::{Command, EngineState, Stack, StateWorkingSet},
|
2023-04-07 02:35:45 +02:00
|
|
|
ParseError, PipelineData, ShellError, Signature, SyntaxShape,
|
2021-09-02 20:21:37 +02:00
|
|
|
};
|
2023-04-07 13:40:05 +02:00
|
|
|
use rstest::rstest;
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2021-09-13 10:19:05 +02:00
|
|
|
#[cfg(test)]
|
2021-10-25 06:01:02 +02:00
|
|
|
#[derive(Clone)]
|
2021-09-13 10:19:05 +02:00
|
|
|
pub struct Let;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
impl Command for Let {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"let"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
"Create a variable and give it a value."
|
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build("let")
|
|
|
|
.required("var_name", SyntaxShape::VarWithOptType, "variable name")
|
|
|
|
.required(
|
|
|
|
"initial_value",
|
2023-03-22 21:14:10 +01:00
|
|
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
2021-09-13 10:19:05 +02:00
|
|
|
"equals sign followed by value",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
2021-10-25 08:31:39 +02:00
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2021-09-13 10:19:05 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Syntax errors for string and int (#7952)
# Description
Added a few syntax errors in ints and strings, changed parser to stop
and show that error rather than continue trying to parse those tokens as
some other shape. However, I don't see how to push this direction much
further, and most of the classic confusing errors can't be changed.
Flagged as WIP for the moment, but passes all checks and works better
than current release:
1. I have yet to figure out how to make these errors refer back to the
book, as I see some other errors do.
2. How to give syntax error when malformed int is first token in line?
Currently parsed as external command, user gets confusing error message.
3. Would like to be more strict with *decimal* int literals (lacking,
e.g, `0x' prefix). Need to tinker more with the order of parse shape
calls, currently, float is tried after int, so '1.4' has to be passed.
_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_
```bash
〉"\z"
Error:
╭─[entry #3:1:1]
1 │ "\z"
· ─┬─
· ╰── Syntax error in string, unrecognized character after escape '\'.
╰────
```
Canonic presentation of a syntax error.
```bash
〉" \u{01ffbogus}"
Error:
× Invalid syntax
╭─[entry #2:1:1]
1 │ " \u{01ffbogus}"
· ───────┬──────
· ╰── Syntax error in string, expecting 1 to 6 hex digits in unicode escape '\u{X...}', max value 10FFFF.
╰────
```
Malformed unicode escape in string, flagged as error.
String parse can be opinionated, it's the last shape tried.
```bash
〉0x22bogus
Error: nu::shell::external_command (link)
× External command failed
╭─[entry #4:1:1]
1 │ 0x22bogus
· ────┬────
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
```
A *correct* number in first token would be evaluated, but an *incorrect*
one is treated as external command? Confusing to users.
```bash
〉0 + 0x22bogus
Error:
× Invalid syntax
╭─[entry #5:1:1]
1 │ 0 + 0x22bogus
· ────┬────
· ╰── Syntax error in int, invalid digits in radix 16 int.
╰────
```
Can give syntax error if token is unambiguously int literal. e.g has 0b
or 0x prefix, could not be a float.
```bash
〉0 + 098bogus
Error: nu::parser::unsupported_operation (link)
× Types mismatched for operation.
╭─[entry #6:1:1]
1 │ 0 + 098bogus
· ┬ ┬ ────┬───
· │ │ ╰── string
· │ ╰── doesn't support these values.
· ╰── int
╰────
help: Change int or string to be the right types and try again.
```
But *decimal* literal (no prefix) can't be too strict. Parser is going
to try float later. So '1.4' must be passed.
# User-Facing Changes
First and foremost, more specific error messages for typos in string and
int literals. Probably improves interactive user experience.
But a script that was causing and then checking for specific error might
notice a different error message.
_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_
# Tests + Formatting
Added (positive and negative unit tests in `cargo test -p nu-parser`.
Didn't add integration tests.
Make sure you've run and fixed any issues with these commands:
- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [x] `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.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-02-13 17:09:50 +01:00
|
|
|
fn test_int(
|
|
|
|
test_tag: &str, // name of sub-test
|
|
|
|
test: &[u8], // input expression
|
|
|
|
expected_val: Expr, // (usually Expr::{Int,String, Float}, not ::BinOp...
|
|
|
|
expected_err: Option<&str>,
|
|
|
|
) // substring in error text
|
|
|
|
{
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, test, true);
|
2023-04-07 02:35:45 +02:00
|
|
|
|
|
|
|
let err = working_set.parse_errors.first();
|
Syntax errors for string and int (#7952)
# Description
Added a few syntax errors in ints and strings, changed parser to stop
and show that error rather than continue trying to parse those tokens as
some other shape. However, I don't see how to push this direction much
further, and most of the classic confusing errors can't be changed.
Flagged as WIP for the moment, but passes all checks and works better
than current release:
1. I have yet to figure out how to make these errors refer back to the
book, as I see some other errors do.
2. How to give syntax error when malformed int is first token in line?
Currently parsed as external command, user gets confusing error message.
3. Would like to be more strict with *decimal* int literals (lacking,
e.g, `0x' prefix). Need to tinker more with the order of parse shape
calls, currently, float is tried after int, so '1.4' has to be passed.
_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_
```bash
〉"\z"
Error:
╭─[entry #3:1:1]
1 │ "\z"
· ─┬─
· ╰── Syntax error in string, unrecognized character after escape '\'.
╰────
```
Canonic presentation of a syntax error.
```bash
〉" \u{01ffbogus}"
Error:
× Invalid syntax
╭─[entry #2:1:1]
1 │ " \u{01ffbogus}"
· ───────┬──────
· ╰── Syntax error in string, expecting 1 to 6 hex digits in unicode escape '\u{X...}', max value 10FFFF.
╰────
```
Malformed unicode escape in string, flagged as error.
String parse can be opinionated, it's the last shape tried.
```bash
〉0x22bogus
Error: nu::shell::external_command (link)
× External command failed
╭─[entry #4:1:1]
1 │ 0x22bogus
· ────┬────
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
```
A *correct* number in first token would be evaluated, but an *incorrect*
one is treated as external command? Confusing to users.
```bash
〉0 + 0x22bogus
Error:
× Invalid syntax
╭─[entry #5:1:1]
1 │ 0 + 0x22bogus
· ────┬────
· ╰── Syntax error in int, invalid digits in radix 16 int.
╰────
```
Can give syntax error if token is unambiguously int literal. e.g has 0b
or 0x prefix, could not be a float.
```bash
〉0 + 098bogus
Error: nu::parser::unsupported_operation (link)
× Types mismatched for operation.
╭─[entry #6:1:1]
1 │ 0 + 098bogus
· ┬ ┬ ────┬───
· │ │ ╰── string
· │ ╰── doesn't support these values.
· ╰── int
╰────
help: Change int or string to be the right types and try again.
```
But *decimal* literal (no prefix) can't be too strict. Parser is going
to try float later. So '1.4' must be passed.
# User-Facing Changes
First and foremost, more specific error messages for typos in string and
int literals. Probably improves interactive user experience.
But a script that was causing and then checking for specific error might
notice a different error message.
_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_
# Tests + Formatting
Added (positive and negative unit tests in `cargo test -p nu-parser`.
Didn't add integration tests.
Make sure you've run and fixed any issues with these commands:
- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [x] `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.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-02-13 17:09:50 +01:00
|
|
|
|
|
|
|
if let Some(err_pat) = expected_err {
|
|
|
|
if let Some(parse_err) = err {
|
|
|
|
let act_err = format!("{:?}", parse_err);
|
|
|
|
assert!(
|
|
|
|
act_err.contains(err_pat),
|
|
|
|
"{test_tag}: expected err to contain {err_pat}, but actual error was {act_err}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
assert!(
|
|
|
|
err.is_some(),
|
|
|
|
"{test_tag}: expected err containing {err_pat}, but no error returned"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
assert!(err.is_none(), "{test_tag}: unexpected error {err:#?}");
|
|
|
|
assert_eq!(block.len(), 1, "{test_tag}: result block length > 1");
|
|
|
|
let expressions = &block[0];
|
|
|
|
assert_eq!(
|
|
|
|
expressions.len(),
|
|
|
|
1,
|
|
|
|
"{test_tag}: got multiple result expressions, expected 1"
|
|
|
|
);
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: observed_val, ..
|
|
|
|
},
|
|
|
|
) = &expressions[0]
|
|
|
|
{
|
Resolve Clippy warnings inside tests. (#8315)
# Description
Command: `cargo clippy --workspace --all-targets`
Resolve those warnings:
```
warning: this expression creates a reference which is immediately dereferenced by the compiler
--> crates/nu-parser/tests/test_parser.rs:86:59
|
86 | compare_rhs_binaryOp(test_tag, &expected_val, &observed_val);
| ^^^^^^^^^^^^^ help: change this to: `observed_val`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
= note: `#[warn(clippy::needless_borrow)]` on by default
warning: `assert!(false, ..)` should probably be replaced
--> crates/nu-cli/src/completions/command_completions.rs:319:17
|
319 | assert!(false, "Merge delta has failed: {}", err);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!(..)` or `unreachable!(..)`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
= note: `#[warn(clippy::assertions_on_constants)]` on by default
warning: 1 warning emitted
warning: `assert!(false, ..)` should probably be replaced
--> crates/nu-cli/src/completions/completer.rs:600:13
|
600 | assert!(false, "Error merging delta: {:?}", err);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: use `panic!(..)` or `unreachable!(..)`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
warning: length comparison to zero
--> crates/nu-cli/src/completions/completer.rs:620:24
|
620 | assert_eq!(result.len() > 0, has_result, "line: {}", line);
| ^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!result.is_empty()`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
= note: `#[warn(clippy::len_zero)]` on by default
warning: equality checks against true are unnecessary
--> crates/nu-cli/src/completions/completer.rs:632:33
|
632 | .filter(|x| *x == true)
| ^^^^^^^^^^ help: try simplifying it as shown: `*x`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
= note: `#[warn(clippy::bool_comparison)]` on by default
Checking nu v0.76.1 (/home/jaudiger/Development/git-repositories/jaudiger/nushell)
warning: 4 warnings emitted
warning: the borrowed expression implements the required traits
--> crates/nu-command/tests/commands/cp.rs:26:40
|
26 | let first_hash = get_file_hash(&test_file.display());
| ^^^^^^^^^^^^^^^^^^^^ help: change this to: `test_file.display()`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
= note: `#[warn(clippy::needless_borrow)]` on by default
warning: the borrowed expression implements the required traits
--> crates/nu-command/tests/commands/cp.rs:178:13
|
178 | &jonathans_expected_copied_dir
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `jonathans_expected_copied_dir`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
warning: the borrowed expression implements the required traits
--> crates/nu-command/tests/commands/cp.rs:182:13
|
182 | &andres_expected_copied_dir
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `andres_expected_copied_dir`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
warning: the borrowed expression implements the required traits
--> crates/nu-command/tests/commands/cp.rs:186:13
|
186 | &yehudas_expected_copied_dir
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change this to: `yehudas_expected_copied_dir`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
warning: 4 warnings emitted
```
# User-Facing Changes
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-03-04 14:58:20 +01:00
|
|
|
compare_rhs_binaryOp(test_tag, &expected_val, observed_val);
|
Syntax errors for string and int (#7952)
# Description
Added a few syntax errors in ints and strings, changed parser to stop
and show that error rather than continue trying to parse those tokens as
some other shape. However, I don't see how to push this direction much
further, and most of the classic confusing errors can't be changed.
Flagged as WIP for the moment, but passes all checks and works better
than current release:
1. I have yet to figure out how to make these errors refer back to the
book, as I see some other errors do.
2. How to give syntax error when malformed int is first token in line?
Currently parsed as external command, user gets confusing error message.
3. Would like to be more strict with *decimal* int literals (lacking,
e.g, `0x' prefix). Need to tinker more with the order of parse shape
calls, currently, float is tried after int, so '1.4' has to be passed.
_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_
```bash
〉"\z"
Error:
╭─[entry #3:1:1]
1 │ "\z"
· ─┬─
· ╰── Syntax error in string, unrecognized character after escape '\'.
╰────
```
Canonic presentation of a syntax error.
```bash
〉" \u{01ffbogus}"
Error:
× Invalid syntax
╭─[entry #2:1:1]
1 │ " \u{01ffbogus}"
· ───────┬──────
· ╰── Syntax error in string, expecting 1 to 6 hex digits in unicode escape '\u{X...}', max value 10FFFF.
╰────
```
Malformed unicode escape in string, flagged as error.
String parse can be opinionated, it's the last shape tried.
```bash
〉0x22bogus
Error: nu::shell::external_command (link)
× External command failed
╭─[entry #4:1:1]
1 │ 0x22bogus
· ────┬────
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
```
A *correct* number in first token would be evaluated, but an *incorrect*
one is treated as external command? Confusing to users.
```bash
〉0 + 0x22bogus
Error:
× Invalid syntax
╭─[entry #5:1:1]
1 │ 0 + 0x22bogus
· ────┬────
· ╰── Syntax error in int, invalid digits in radix 16 int.
╰────
```
Can give syntax error if token is unambiguously int literal. e.g has 0b
or 0x prefix, could not be a float.
```bash
〉0 + 098bogus
Error: nu::parser::unsupported_operation (link)
× Types mismatched for operation.
╭─[entry #6:1:1]
1 │ 0 + 098bogus
· ┬ ┬ ────┬───
· │ │ ╰── string
· │ ╰── doesn't support these values.
· ╰── int
╰────
help: Change int or string to be the right types and try again.
```
But *decimal* literal (no prefix) can't be too strict. Parser is going
to try float later. So '1.4' must be passed.
# User-Facing Changes
First and foremost, more specific error messages for typos in string and
int literals. Probably improves interactive user experience.
But a script that was causing and then checking for specific error might
notice a different error message.
_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_
# Tests + Formatting
Added (positive and negative unit tests in `cargo test -p nu-parser`.
Didn't add integration tests.
Make sure you've run and fixed any issues with these commands:
- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [x] `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.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-02-13 17:09:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
fn compare_rhs_binaryOp(
|
|
|
|
test_tag: &str,
|
|
|
|
expected: &Expr, // the rhs expr we hope to see (::Int, ::Float, not ::B)
|
|
|
|
observed: &Expr, // the Expr actually provided: can be ::Int, ::Float, ::String,
|
|
|
|
// or ::BinOp (in which case rhs is checked), or ::Call (in which case cmd is checked)
|
|
|
|
) {
|
|
|
|
match observed {
|
|
|
|
Expr::Int(..) | Expr::Float(..) | Expr::String(..) => {
|
|
|
|
assert_eq!(
|
|
|
|
expected, observed,
|
|
|
|
"{test_tag}: Expected: {expected:#?}, observed {observed:#?}"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Expr::BinaryOp(_, _, e) => {
|
|
|
|
let observed_expr = &e.expr;
|
|
|
|
// can't pattern match Box<Foo>, but can match the box, then deref in separate statement.
|
|
|
|
assert_eq!(
|
|
|
|
expected, observed_expr,
|
|
|
|
"{test_tag}: Expected: {expected:#?}, observed: {observed:#?}"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Expr::ExternalCall(e, _, _) => {
|
|
|
|
let observed_expr = &e.expr;
|
|
|
|
assert_eq!(
|
|
|
|
expected, observed_expr,
|
|
|
|
"{test_tag}: Expected: {expected:#?}, observed: {observed_expr:#?}"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!("{test_tag}: Unexpected Expr:: variant returned, observed {observed:#?}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn multi_test_parse_int() {
|
|
|
|
struct Test<'a>(&'a str, &'a [u8], Expr, Option<&'a str>);
|
|
|
|
|
|
|
|
// use test expression of form '0 + x' to force parse() to parse x as numeric.
|
|
|
|
// if expression were just 'x', parse() would try other items that would mask the error we're looking for.
|
|
|
|
let tests = vec![
|
|
|
|
Test("binary literal int", b"0 + 0b0", Expr::Int(0), None),
|
|
|
|
Test(
|
|
|
|
"binary literal invalid digits",
|
|
|
|
b"0 + 0b2",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("invalid digits for radix 2"),
|
|
|
|
),
|
|
|
|
Test("octal literal int", b"0 + 0o1", Expr::Int(1), None),
|
|
|
|
Test(
|
|
|
|
"octal literal int invalid digits",
|
|
|
|
b"0 + 0o8",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("invalid digits for radix 8"),
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"octal literal int truncated",
|
|
|
|
b"0 + 0o",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("invalid digits for radix 8"),
|
|
|
|
),
|
|
|
|
Test("hex literal int", b"0 + 0x2", Expr::Int(2), None),
|
|
|
|
Test(
|
|
|
|
"hex literal int invalid digits",
|
|
|
|
b"0 + 0x0aq",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("invalid digits for radix 16"),
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"hex literal with 'e' not mistaken for float",
|
|
|
|
b"0 + 0x00e0",
|
|
|
|
Expr::Int(0xe0),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
// decimal (rad10) literal is anything that starts with
|
|
|
|
// optional sign then a digit.
|
|
|
|
Test("rad10 literal int", b"0 + 42", Expr::Int(42), None),
|
|
|
|
Test(
|
|
|
|
"rad10 with leading + sign",
|
|
|
|
b"0 + -42",
|
|
|
|
Expr::Int(-42),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
Test("rad10 with leading - sign", b"0 + +42", Expr::Int(42), None),
|
|
|
|
Test(
|
|
|
|
"flag char is string, not (invalid) int",
|
|
|
|
b"-x",
|
|
|
|
Expr::String("-x".into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"keyword parameter is string",
|
|
|
|
b"--exact",
|
|
|
|
Expr::String("--exact".into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"ranges or relative paths not confused for int",
|
|
|
|
b"./a/b",
|
|
|
|
Expr::String("./a/b".into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"semver data not confused for int",
|
Require that values that look like numbers parse as numberlike (#8635)
# Description
Require that any value that looks like it might be a number (starts with
a digit, or a '-' + digit, or a '+' + digits, or a special form float
like `-inf`, `inf`, or `NaN`) must now be treated as a number-like
value. Number-like syntax can only parse into number-like values.
Number-like values include: durations, ints, floats, ranges, filesizes,
binary data, etc.
# User-Facing Changes
BREAKING CHANGE
BREAKING CHANGE
BREAKING CHANGE
BREAKING CHANGE
BREAKING CHANGE
BREAKING CHANGE
BREAKING CHANGE
BREAKING CHANGE
Just making sure we see this for release notes 😅
This breaks any and all numberlike values that were treated as strings
before. Example, we used to allow `3,` as a bare word. Anything like
this would now require quotes or backticks to be treated as a string or
bare word, respectively.
# 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
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# 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-03-28 08:31:38 +02:00
|
|
|
b"'1.0.1'",
|
Syntax errors for string and int (#7952)
# Description
Added a few syntax errors in ints and strings, changed parser to stop
and show that error rather than continue trying to parse those tokens as
some other shape. However, I don't see how to push this direction much
further, and most of the classic confusing errors can't be changed.
Flagged as WIP for the moment, but passes all checks and works better
than current release:
1. I have yet to figure out how to make these errors refer back to the
book, as I see some other errors do.
2. How to give syntax error when malformed int is first token in line?
Currently parsed as external command, user gets confusing error message.
3. Would like to be more strict with *decimal* int literals (lacking,
e.g, `0x' prefix). Need to tinker more with the order of parse shape
calls, currently, float is tried after int, so '1.4' has to be passed.
_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_
```bash
〉"\z"
Error:
╭─[entry #3:1:1]
1 │ "\z"
· ─┬─
· ╰── Syntax error in string, unrecognized character after escape '\'.
╰────
```
Canonic presentation of a syntax error.
```bash
〉" \u{01ffbogus}"
Error:
× Invalid syntax
╭─[entry #2:1:1]
1 │ " \u{01ffbogus}"
· ───────┬──────
· ╰── Syntax error in string, expecting 1 to 6 hex digits in unicode escape '\u{X...}', max value 10FFFF.
╰────
```
Malformed unicode escape in string, flagged as error.
String parse can be opinionated, it's the last shape tried.
```bash
〉0x22bogus
Error: nu::shell::external_command (link)
× External command failed
╭─[entry #4:1:1]
1 │ 0x22bogus
· ────┬────
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
```
A *correct* number in first token would be evaluated, but an *incorrect*
one is treated as external command? Confusing to users.
```bash
〉0 + 0x22bogus
Error:
× Invalid syntax
╭─[entry #5:1:1]
1 │ 0 + 0x22bogus
· ────┬────
· ╰── Syntax error in int, invalid digits in radix 16 int.
╰────
```
Can give syntax error if token is unambiguously int literal. e.g has 0b
or 0x prefix, could not be a float.
```bash
〉0 + 098bogus
Error: nu::parser::unsupported_operation (link)
× Types mismatched for operation.
╭─[entry #6:1:1]
1 │ 0 + 098bogus
· ┬ ┬ ────┬───
· │ │ ╰── string
· │ ╰── doesn't support these values.
· ╰── int
╰────
help: Change int or string to be the right types and try again.
```
But *decimal* literal (no prefix) can't be too strict. Parser is going
to try float later. So '1.4' must be passed.
# User-Facing Changes
First and foremost, more specific error messages for typos in string and
int literals. Probably improves interactive user experience.
But a script that was causing and then checking for specific error might
notice a different error message.
_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_
# Tests + Formatting
Added (positive and negative unit tests in `cargo test -p nu-parser`.
Didn't add integration tests.
Make sure you've run and fixed any issues with these commands:
- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [x] `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.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-02-13 17:09:50 +01:00
|
|
|
Expr::String("1.0.1".into()),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
for test in tests {
|
|
|
|
test_int(test.0, test.1, test.2, test.3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[ignore]
|
|
|
|
#[test]
|
|
|
|
pub fn multi_test_parse_number() {
|
|
|
|
struct Test<'a>(&'a str, &'a [u8], Expr, Option<&'a str>);
|
|
|
|
|
|
|
|
// use test expression of form '0 + x' to force parse() to parse x as numeric.
|
|
|
|
// if expression were just 'x', parse() would try other items that would mask the error we're looking for.
|
|
|
|
let tests = vec![
|
|
|
|
Test("float decimal", b"0 + 43.5", Expr::Float(43.5), None),
|
|
|
|
//Test("float with leading + sign", b"0 + +41.7", Expr::Float(-41.7), None),
|
|
|
|
Test(
|
|
|
|
"float with leading - sign",
|
|
|
|
b"0 + -41.7",
|
|
|
|
Expr::Float(-41.7),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"float scientific notation",
|
|
|
|
b"0 + 3e10",
|
|
|
|
Expr::Float(3.00e10),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"float decimal literal invalid digits",
|
|
|
|
b"0 + .3foo",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("invalid digits"),
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
"float scientific notation literal invalid digits",
|
|
|
|
b"0 + 3e0faa",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("invalid digits"),
|
|
|
|
),
|
|
|
|
Test(
|
|
|
|
// odd that error is unsupportedOperation, but it does fail.
|
|
|
|
"decimal literal int 2 leading signs",
|
|
|
|
b"0 + --9",
|
|
|
|
Expr::Int(0),
|
|
|
|
Some("UnsupportedOperation"),
|
|
|
|
),
|
|
|
|
//Test(
|
|
|
|
// ".<string> should not be taken as float",
|
|
|
|
// b"abc + .foo",
|
|
|
|
// Expr::String("..".into()),
|
|
|
|
// None,
|
|
|
|
//),
|
|
|
|
];
|
|
|
|
|
|
|
|
for test in tests {
|
|
|
|
test_int(test.0, test.1, test.2, test.3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#[ignore]
|
|
|
|
#[test]
|
|
|
|
fn test_parse_any() {
|
|
|
|
let test = b"1..10";
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, test, true);
|
Syntax errors for string and int (#7952)
# Description
Added a few syntax errors in ints and strings, changed parser to stop
and show that error rather than continue trying to parse those tokens as
some other shape. However, I don't see how to push this direction much
further, and most of the classic confusing errors can't be changed.
Flagged as WIP for the moment, but passes all checks and works better
than current release:
1. I have yet to figure out how to make these errors refer back to the
book, as I see some other errors do.
2. How to give syntax error when malformed int is first token in line?
Currently parsed as external command, user gets confusing error message.
3. Would like to be more strict with *decimal* int literals (lacking,
e.g, `0x' prefix). Need to tinker more with the order of parse shape
calls, currently, float is tried after int, so '1.4' has to be passed.
_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_
```bash
〉"\z"
Error:
╭─[entry #3:1:1]
1 │ "\z"
· ─┬─
· ╰── Syntax error in string, unrecognized character after escape '\'.
╰────
```
Canonic presentation of a syntax error.
```bash
〉" \u{01ffbogus}"
Error:
× Invalid syntax
╭─[entry #2:1:1]
1 │ " \u{01ffbogus}"
· ───────┬──────
· ╰── Syntax error in string, expecting 1 to 6 hex digits in unicode escape '\u{X...}', max value 10FFFF.
╰────
```
Malformed unicode escape in string, flagged as error.
String parse can be opinionated, it's the last shape tried.
```bash
〉0x22bogus
Error: nu::shell::external_command (link)
× External command failed
╭─[entry #4:1:1]
1 │ 0x22bogus
· ────┬────
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
```
A *correct* number in first token would be evaluated, but an *incorrect*
one is treated as external command? Confusing to users.
```bash
〉0 + 0x22bogus
Error:
× Invalid syntax
╭─[entry #5:1:1]
1 │ 0 + 0x22bogus
· ────┬────
· ╰── Syntax error in int, invalid digits in radix 16 int.
╰────
```
Can give syntax error if token is unambiguously int literal. e.g has 0b
or 0x prefix, could not be a float.
```bash
〉0 + 098bogus
Error: nu::parser::unsupported_operation (link)
× Types mismatched for operation.
╭─[entry #6:1:1]
1 │ 0 + 098bogus
· ┬ ┬ ────┬───
· │ │ ╰── string
· │ ╰── doesn't support these values.
· ╰── int
╰────
help: Change int or string to be the right types and try again.
```
But *decimal* literal (no prefix) can't be too strict. Parser is going
to try float later. So '1.4' must be passed.
# User-Facing Changes
First and foremost, more specific error messages for typos in string and
int literals. Probably improves interactive user experience.
But a script that was causing and then checking for specific error might
notice a different error message.
_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_
# Tests + Formatting
Added (positive and negative unit tests in `cargo test -p nu-parser`.
Didn't add integration tests.
Make sure you've run and fixed any issues with these commands:
- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [x] `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.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-02-13 17:09:50 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
match (block, working_set.parse_errors.first()) {
|
Syntax errors for string and int (#7952)
# Description
Added a few syntax errors in ints and strings, changed parser to stop
and show that error rather than continue trying to parse those tokens as
some other shape. However, I don't see how to push this direction much
further, and most of the classic confusing errors can't be changed.
Flagged as WIP for the moment, but passes all checks and works better
than current release:
1. I have yet to figure out how to make these errors refer back to the
book, as I see some other errors do.
2. How to give syntax error when malformed int is first token in line?
Currently parsed as external command, user gets confusing error message.
3. Would like to be more strict with *decimal* int literals (lacking,
e.g, `0x' prefix). Need to tinker more with the order of parse shape
calls, currently, float is tried after int, so '1.4' has to be passed.
_(Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.)_
```bash
〉"\z"
Error:
╭─[entry #3:1:1]
1 │ "\z"
· ─┬─
· ╰── Syntax error in string, unrecognized character after escape '\'.
╰────
```
Canonic presentation of a syntax error.
```bash
〉" \u{01ffbogus}"
Error:
× Invalid syntax
╭─[entry #2:1:1]
1 │ " \u{01ffbogus}"
· ───────┬──────
· ╰── Syntax error in string, expecting 1 to 6 hex digits in unicode escape '\u{X...}', max value 10FFFF.
╰────
```
Malformed unicode escape in string, flagged as error.
String parse can be opinionated, it's the last shape tried.
```bash
〉0x22bogus
Error: nu::shell::external_command (link)
× External command failed
╭─[entry #4:1:1]
1 │ 0x22bogus
· ────┬────
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
```
A *correct* number in first token would be evaluated, but an *incorrect*
one is treated as external command? Confusing to users.
```bash
〉0 + 0x22bogus
Error:
× Invalid syntax
╭─[entry #5:1:1]
1 │ 0 + 0x22bogus
· ────┬────
· ╰── Syntax error in int, invalid digits in radix 16 int.
╰────
```
Can give syntax error if token is unambiguously int literal. e.g has 0b
or 0x prefix, could not be a float.
```bash
〉0 + 098bogus
Error: nu::parser::unsupported_operation (link)
× Types mismatched for operation.
╭─[entry #6:1:1]
1 │ 0 + 098bogus
· ┬ ┬ ────┬───
· │ │ ╰── string
· │ ╰── doesn't support these values.
· ╰── int
╰────
help: Change int or string to be the right types and try again.
```
But *decimal* literal (no prefix) can't be too strict. Parser is going
to try float later. So '1.4' must be passed.
# User-Facing Changes
First and foremost, more specific error messages for typos in string and
int literals. Probably improves interactive user experience.
But a script that was causing and then checking for specific error might
notice a different error message.
_(List of all changes that impact the user experience here. This helps
us keep track of breaking changes.)_
# Tests + Formatting
Added (positive and negative unit tests in `cargo test -p nu-parser`.
Didn't add integration tests.
Make sure you've run and fixed any issues with these commands:
- [x] `cargo fmt --all -- --check` to check standard code formatting
(`cargo fmt --all` applies these changes)
- [x] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- [x] `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.
---------
Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2023-02-13 17:09:50 +01:00
|
|
|
(_, Some(e)) => {
|
|
|
|
println!("test: {test:?}, error: {e:#?}");
|
|
|
|
}
|
|
|
|
(b, None) => {
|
|
|
|
println!("test: {test:?}, parse: {b:#?}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-30 20:36:07 +02:00
|
|
|
#[test]
|
|
|
|
pub fn parse_int() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"3", true);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-02-15 20:31:14 +01:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-02-15 20:31:14 +01:00
|
|
|
assert!(matches!(
|
|
|
|
expressions[0],
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Int(3),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
)
|
2022-02-15 20:31:14 +01:00
|
|
|
))
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
|
2023-01-15 16:03:57 +01:00
|
|
|
#[test]
|
|
|
|
pub fn parse_int_with_underscores() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"420_69_2023", true);
|
2023-01-15 16:03:57 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 16:03:57 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
|
|
|
let expressions = &block[0];
|
|
|
|
assert_eq!(expressions.len(), 1);
|
|
|
|
assert!(matches!(
|
|
|
|
expressions[0],
|
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Int(420692023),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
)
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-03-16 04:50:58 +01:00
|
|
|
#[test]
|
|
|
|
pub fn parse_cell_path() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
working_set.add_variable(
|
|
|
|
"foo".to_string().into_bytes(),
|
|
|
|
Span::test_data(),
|
|
|
|
nu_protocol::Type::Record(vec![]),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"$foo.bar.baz", true);
|
2023-03-16 04:50:58 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-03-16 04:50:58 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
|
|
|
let expressions = &block[0];
|
|
|
|
assert_eq!(expressions.len(), 1);
|
|
|
|
|
|
|
|
// hoo boy this pattern matching is a pain
|
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
|
|
|
if let Expr::FullCellPath(b) = &expr.expr {
|
|
|
|
assert!(matches!(
|
|
|
|
b.head,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Var(_),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
));
|
|
|
|
if let [a, b] = &b.tail[..] {
|
|
|
|
if let PathMember::String { val, optional, .. } = a {
|
|
|
|
assert_eq!(val, "bar");
|
|
|
|
assert_eq!(optional, &false);
|
|
|
|
} else {
|
|
|
|
panic!("wrong type")
|
|
|
|
}
|
|
|
|
|
|
|
|
if let PathMember::String { val, optional, .. } = b {
|
|
|
|
assert_eq!(val, "baz");
|
|
|
|
assert_eq!(optional, &false);
|
|
|
|
} else {
|
|
|
|
panic!("wrong type")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("cell path tail is unexpected")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("Not a cell path");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_cell_path_optional() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
working_set.add_variable(
|
|
|
|
"foo".to_string().into_bytes(),
|
|
|
|
Span::test_data(),
|
|
|
|
nu_protocol::Type::Record(vec![]),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"$foo.bar?.baz", true);
|
2023-03-16 04:50:58 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-03-16 04:50:58 +01:00
|
|
|
|
|
|
|
assert_eq!(block.len(), 1);
|
|
|
|
let expressions = &block[0];
|
|
|
|
assert_eq!(expressions.len(), 1);
|
|
|
|
|
|
|
|
// hoo boy this pattern matching is a pain
|
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
|
|
|
if let Expr::FullCellPath(b) = &expr.expr {
|
|
|
|
assert!(matches!(
|
|
|
|
b.head,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Var(_),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
));
|
|
|
|
if let [a, b] = &b.tail[..] {
|
|
|
|
if let PathMember::String { val, optional, .. } = a {
|
|
|
|
assert_eq!(val, "bar");
|
|
|
|
assert_eq!(optional, &true);
|
|
|
|
} else {
|
|
|
|
panic!("wrong type")
|
|
|
|
}
|
|
|
|
|
|
|
|
if let PathMember::String { val, optional, .. } = b {
|
|
|
|
assert_eq!(val, "baz");
|
|
|
|
assert_eq!(optional, &false);
|
|
|
|
} else {
|
|
|
|
panic!("wrong type")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("cell path tail is unexpected")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("Not a cell path");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-11 09:58:57 +02:00
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_hex_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0x[13]", true);
|
2022-04-11 09:58:57 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-04-11 09:58:57 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert_eq!(expr.expr, Expr::Binary(vec![0x13]))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-04-11 09:58:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_incomplete_hex_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0x[3]", true);
|
2022-04-11 09:58:57 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-04-11 09:58:57 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert_eq!(expr.expr, Expr::Binary(vec![0x03]))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-04-11 09:58:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_binary_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0b[1010 1000]", true);
|
2022-04-11 09:58:57 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-04-11 09:58:57 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert_eq!(expr.expr, Expr::Binary(vec![0b10101000]))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-04-11 09:58:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_incomplete_binary_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0b[10]", true);
|
2022-04-11 09:58:57 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-04-11 09:58:57 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert_eq!(expr.expr, Expr::Binary(vec![0b00000010]))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-04-11 09:58:57 +02:00
|
|
|
}
|
|
|
|
|
2022-05-23 11:01:15 +02:00
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_octal_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0o[250]", true);
|
2022-05-23 11:01:15 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-05-23 11:01:15 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert_eq!(expr.expr, Expr::Binary(vec![0o250]))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-05-23 11:01:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_incomplete_octal_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0o[2]", true);
|
2022-05-23 11:01:15 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-05-23 11:01:15 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert_eq!(expr.expr, Expr::Binary(vec![0o2]))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-05-23 11:01:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_invalid_octal_format() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"0b[90]", true);
|
2022-05-23 11:01:15 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-05-23 11:01:15 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert!(!matches!(&expr.expr, Expr::Binary(_)))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-05-23 11:01:15 +02:00
|
|
|
}
|
|
|
|
|
2022-06-17 20:11:48 +02:00
|
|
|
#[test]
|
|
|
|
pub fn parse_binary_with_multi_byte_char() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
// found using fuzzing, Rust can panic if you slice into this string
|
|
|
|
let contents = b"0x[\xEF\xBF\xBD]";
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, contents, true);
|
2022-06-17 20:11:48 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-06-17 20:11:48 +02:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2022-11-18 22:46:48 +01:00
|
|
|
assert!(!matches!(&expr.expr, Expr::Binary(_)))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
2022-06-17 20:11:48 +02:00
|
|
|
}
|
|
|
|
|
2021-08-30 20:36:07 +02:00
|
|
|
#[test]
|
|
|
|
pub fn parse_call() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"foo", true);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
let expressions = &block[0];
|
|
|
|
assert_eq!(expressions.len(), 1);
|
2021-09-03 04:15:01 +02:00
|
|
|
|
2022-11-22 19:26:13 +01:00
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) = &expressions[0]
|
2022-02-15 20:31:14 +01:00
|
|
|
{
|
|
|
|
assert_eq!(call.decl_id, 0);
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_missing_flag_arg() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2021-10-13 19:53:27 +02:00
|
|
|
let sig = Signature::build("foo").named("jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
parse(&mut working_set, None, b"foo --jazz", true);
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::MissingFlagParam(..))
|
|
|
|
));
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_missing_short_flag_arg() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2021-08-30 20:36:07 +02:00
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
parse(&mut working_set, None, b"foo -j", true);
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::MissingFlagParam(..))
|
|
|
|
));
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2023-04-15 10:24:51 +02:00
|
|
|
pub fn parse_call_short_flag_batch_arg_allowed() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo")
|
|
|
|
.named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'))
|
2023-04-15 10:24:51 +02:00
|
|
|
.switch("--math", "math!!", Some('m'));
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2023-04-15 10:24:51 +02:00
|
|
|
|
|
|
|
let block = parse(&mut working_set, None, b"foo -mj 10", true);
|
|
|
|
|
|
|
|
assert!(working_set.parse_errors.is_empty());
|
|
|
|
assert_eq!(block.len(), 1);
|
|
|
|
let expressions = &block[0];
|
|
|
|
assert_eq!(expressions.len(), 1);
|
|
|
|
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) = &expressions[0]
|
|
|
|
{
|
|
|
|
assert_eq!(call.decl_id, 0);
|
|
|
|
assert_eq!(call.arguments.len(), 2);
|
|
|
|
matches!(call.arguments[0], Argument::Named((_, None, None)));
|
|
|
|
matches!(call.arguments[1], Argument::Named((_, None, Some(_))));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_short_flag_batch_arg_disallowed() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
let sig = Signature::build("foo")
|
|
|
|
.named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'))
|
|
|
|
.switch("--math", "math!!", Some('m'));
|
|
|
|
working_set.add_decl(sig.predeclare());
|
|
|
|
|
|
|
|
parse(&mut working_set, None, b"foo -jm 10", true);
|
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::OnlyLastFlagInBatchCanTakeArg(..))
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_short_flag_batch_disallow_multiple_args() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
let sig = Signature::build("foo")
|
|
|
|
.named("--math", SyntaxShape::Int, "math!!", Some('m'))
|
|
|
|
.named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
|
|
|
working_set.add_decl(sig.predeclare());
|
|
|
|
|
|
|
|
parse(&mut working_set, None, b"foo -mj 10 20", true);
|
2021-08-30 20:36:07 +02:00
|
|
|
assert!(matches!(
|
2023-04-07 02:35:45 +02:00
|
|
|
working_set.parse_errors.first(),
|
2023-04-15 10:24:51 +02:00
|
|
|
Some(ParseError::OnlyLastFlagInBatchCanTakeArg(..))
|
2021-08-30 20:36:07 +02:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_unknown_shorthand() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j'));
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2023-04-07 20:09:38 +02:00
|
|
|
parse(&mut working_set, None, b"foo -mj", true);
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::UnknownFlag(..))
|
|
|
|
));
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_extra_positional() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j'));
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2023-04-07 20:09:38 +02:00
|
|
|
parse(&mut working_set, None, b"foo -j 100", true);
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::ExtraPositional(..))
|
|
|
|
));
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_missing_req_positional() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!");
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2023-04-07 20:09:38 +02:00
|
|
|
parse(&mut working_set, None, b"foo", true);
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::MissingPositional(..))
|
|
|
|
));
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_call_missing_req_flag() {
|
2021-09-02 10:25:22 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2021-08-30 20:36:07 +02:00
|
|
|
|
|
|
|
let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None);
|
2021-09-02 20:21:37 +02:00
|
|
|
working_set.add_decl(sig.predeclare());
|
2023-04-07 20:09:38 +02:00
|
|
|
parse(&mut working_set, None, b"foo", true);
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
|
|
|
Some(ParseError::MissingRequiredFlag(..))
|
|
|
|
));
|
2021-08-30 20:36:07 +02:00
|
|
|
}
|
2021-09-05 00:25:31 +02:00
|
|
|
|
2021-12-20 02:05:33 +01:00
|
|
|
#[test]
|
2022-12-22 21:30:10 +01:00
|
|
|
fn test_nothing_comparison_eq() {
|
2021-12-20 02:05:33 +01:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"2 == null", true);
|
2021-12-20 02:05:33 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-02-15 20:31:14 +01:00
|
|
|
assert!(matches!(
|
|
|
|
&expressions[0],
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::BinaryOp(..),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
)
|
2022-02-15 20:31:14 +01:00
|
|
|
))
|
2021-12-20 02:05:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-12-22 21:30:10 +01:00
|
|
|
fn test_nothing_comparison_neq() {
|
2021-12-20 02:05:33 +01:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"2 != null", true);
|
2021-12-20 02:05:33 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-02-15 20:31:14 +01:00
|
|
|
assert!(matches!(
|
|
|
|
&expressions[0],
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::BinaryOp(..),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
)
|
2022-02-15 20:31:14 +01:00
|
|
|
))
|
2021-12-20 02:05:33 +01:00
|
|
|
}
|
|
|
|
|
2022-12-14 14:54:13 +01:00
|
|
|
mod string {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_string() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"\"hello nushell\"", true);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
|
|
|
assert_eq!(expr.expr, Expr::String("hello nushell".to_string()))
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mod interpolation {
|
|
|
|
use nu_protocol::Span;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_string_interpolation() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"$\"hello (39 + 3)\"", true);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2023-01-27 18:45:57 +01:00
|
|
|
let subexprs: Vec<&Expr> = match expr {
|
2022-12-14 14:54:13 +01:00
|
|
|
Expression {
|
|
|
|
expr: Expr::StringInterpolation(expressions),
|
|
|
|
..
|
2023-01-27 18:45:57 +01:00
|
|
|
} => expressions.iter().map(|e| &e.expr).collect(),
|
2022-12-14 14:54:13 +01:00
|
|
|
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
2023-01-27 18:45:57 +01:00
|
|
|
};
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
assert_eq!(subexprs.len(), 2);
|
|
|
|
|
|
|
|
assert_eq!(subexprs[0], &Expr::String("hello ".to_string()));
|
|
|
|
|
|
|
|
assert!(matches!(subexprs[1], &Expr::FullCellPath(..)));
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_string_interpolation_escaped_parenthesis() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"$\"hello \\(39 + 3)\"", true);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
let expressions = &block[0];
|
|
|
|
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2023-01-27 18:45:57 +01:00
|
|
|
let subexprs: Vec<&Expr> = match expr {
|
2022-12-14 14:54:13 +01:00
|
|
|
Expression {
|
|
|
|
expr: Expr::StringInterpolation(expressions),
|
|
|
|
..
|
2023-01-27 18:45:57 +01:00
|
|
|
} => expressions.iter().map(|e| &e.expr).collect(),
|
2022-12-14 14:54:13 +01:00
|
|
|
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
2023-01-27 18:45:57 +01:00
|
|
|
};
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
assert_eq!(subexprs.len(), 1);
|
|
|
|
|
|
|
|
assert_eq!(subexprs[0], &Expr::String("hello (39 + 3)".to_string()));
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_string_interpolation_escaped_backslash_before_parenthesis() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"$\"hello \\\\(39 + 3)\"", true);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
let expressions = &block[0];
|
|
|
|
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2023-01-27 18:45:57 +01:00
|
|
|
let subexprs: Vec<&Expr> = match expr {
|
2022-12-14 14:54:13 +01:00
|
|
|
Expression {
|
|
|
|
expr: Expr::StringInterpolation(expressions),
|
|
|
|
..
|
2023-01-27 18:45:57 +01:00
|
|
|
} => expressions.iter().map(|e| &e.expr).collect(),
|
2022-12-14 14:54:13 +01:00
|
|
|
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
2023-01-27 18:45:57 +01:00
|
|
|
};
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
assert_eq!(subexprs.len(), 2);
|
|
|
|
|
|
|
|
assert_eq!(subexprs[0], &Expr::String("hello \\".to_string()));
|
|
|
|
|
|
|
|
assert!(matches!(subexprs[1], &Expr::FullCellPath(..)));
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_string_interpolation_backslash_count_reset_by_expression() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, b"$\"\\(1 + 3)\\(7 - 5)\"", true);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2022-12-14 14:54:13 +01:00
|
|
|
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
let expressions = &block[0];
|
|
|
|
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 1);
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
if let PipelineElement::Expression(_, expr) = &expressions[0] {
|
2023-01-27 18:45:57 +01:00
|
|
|
let subexprs: Vec<&Expr> = match expr {
|
2022-12-14 14:54:13 +01:00
|
|
|
Expression {
|
|
|
|
expr: Expr::StringInterpolation(expressions),
|
|
|
|
..
|
2023-01-27 18:45:57 +01:00
|
|
|
} => expressions.iter().map(|e| &e.expr).collect(),
|
2022-12-14 14:54:13 +01:00
|
|
|
_ => panic!("Expected an `Expr::StringInterpolation`"),
|
2023-01-27 18:45:57 +01:00
|
|
|
};
|
2022-12-14 14:54:13 +01:00
|
|
|
|
|
|
|
assert_eq!(subexprs.len(), 1);
|
|
|
|
assert_eq!(subexprs[0], &Expr::String("(1 + 3)(7 - 5)".to_string()));
|
|
|
|
} else {
|
|
|
|
panic!("Not an expression")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_nested_expressions() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
working_set.add_variable(
|
|
|
|
"foo".to_string().into_bytes(),
|
|
|
|
Span::new(0, 0),
|
|
|
|
nu_protocol::Type::CellPath,
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
parse(
|
2022-12-14 14:54:13 +01:00
|
|
|
&mut working_set,
|
|
|
|
None,
|
|
|
|
br#"
|
|
|
|
$"(($foo))"
|
|
|
|
"#,
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2022-12-14 14:54:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn parse_path_expression() {
|
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
|
|
|
working_set.add_variable(
|
|
|
|
"foo".to_string().into_bytes(),
|
|
|
|
Span::new(0, 0),
|
|
|
|
nu_protocol::Type::CellPath,
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
parse(
|
2022-12-14 14:54:13 +01:00
|
|
|
&mut working_set,
|
|
|
|
None,
|
|
|
|
br#"
|
|
|
|
$"Hello ($foo.bar)"
|
|
|
|
"#,
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2022-12-14 14:54:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-05 00:25:31 +02:00
|
|
|
mod range {
|
|
|
|
use super::*;
|
|
|
|
use nu_protocol::ast::{RangeInclusion, RangeOperator};
|
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
#[rstest]
|
|
|
|
#[case(b"0..10", RangeInclusion::Inclusive, "inclusive")]
|
|
|
|
#[case(b"0..=10", RangeInclusion::Inclusive, "=inclusive")]
|
|
|
|
#[case(b"0..<10", RangeInclusion::RightExclusive, "exclusive")]
|
|
|
|
#[case(b"10..0", RangeInclusion::Inclusive, "reverse inclusive")]
|
|
|
|
#[case(b"10..=0", RangeInclusion::Inclusive, "reverse =inclusive")]
|
|
|
|
#[case(
|
|
|
|
b"(3 - 3)..<(8 + 2)",
|
|
|
|
RangeInclusion::RightExclusive,
|
|
|
|
"subexpression exclusive"
|
|
|
|
)]
|
|
|
|
#[case(
|
|
|
|
b"(3 - 3)..(8 + 2)",
|
|
|
|
RangeInclusion::Inclusive,
|
|
|
|
"subexpression inclusive"
|
|
|
|
)]
|
|
|
|
#[case(
|
|
|
|
b"(3 - 3)..=(8 + 2)",
|
|
|
|
RangeInclusion::Inclusive,
|
|
|
|
"subexpression =inclusive"
|
|
|
|
)]
|
|
|
|
#[case(b"-10..-3", RangeInclusion::Inclusive, "negative inclusive")]
|
|
|
|
#[case(b"-10..=-3", RangeInclusion::Inclusive, "negative =inclusive")]
|
|
|
|
#[case(b"-10..<-3", RangeInclusion::RightExclusive, "negative exclusive")]
|
|
|
|
|
|
|
|
fn parse_bounded_range(
|
|
|
|
#[case] phrase: &[u8],
|
|
|
|
#[case] inclusion: RangeInclusion,
|
|
|
|
#[case] tag: &str,
|
|
|
|
) {
|
2021-09-05 00:25:31 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, phrase, true);
|
2021-09-05 00:25:31 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(block.len(), 1, "{tag}: block length");
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(expressions.len(), 1, "{tag}: expression length");
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr:
|
|
|
|
Expr::Range(
|
2022-11-22 19:26:13 +01:00
|
|
|
Some(_),
|
|
|
|
None,
|
|
|
|
Some(_),
|
|
|
|
RangeOperator {
|
2023-04-07 13:40:05 +02:00
|
|
|
inclusion: the_inclusion,
|
2022-11-22 19:26:13 +01:00
|
|
|
..
|
2023-04-07 13:40:05 +02:00
|
|
|
},
|
2022-11-22 19:26:13 +01:00
|
|
|
),
|
2023-04-07 13:40:05 +02:00
|
|
|
..
|
|
|
|
},
|
|
|
|
) = expressions[0]
|
|
|
|
{
|
|
|
|
assert_eq!(
|
|
|
|
the_inclusion, inclusion,
|
|
|
|
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
panic!("{tag}: expression mismatch.")
|
|
|
|
};
|
2021-09-05 00:25:31 +02:00
|
|
|
}
|
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
#[rstest]
|
|
|
|
#[case(
|
|
|
|
b"let a = 2; $a..10",
|
|
|
|
RangeInclusion::Inclusive,
|
|
|
|
"variable start inclusive"
|
|
|
|
)]
|
|
|
|
#[case(
|
|
|
|
b"let a = 2; $a..=10",
|
|
|
|
RangeInclusion::Inclusive,
|
|
|
|
"variable start =inclusive"
|
|
|
|
)]
|
|
|
|
#[case(
|
|
|
|
b"let a = 2; $a..<($a + 10)",
|
|
|
|
RangeInclusion::RightExclusive,
|
|
|
|
"subexpression variable exclusive"
|
|
|
|
)]
|
|
|
|
fn parse_variable_range(
|
|
|
|
#[case] phrase: &[u8],
|
|
|
|
#[case] inclusion: RangeInclusion,
|
|
|
|
#[case] tag: &str,
|
|
|
|
) {
|
2021-09-05 00:25:31 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2021-09-13 10:19:05 +02:00
|
|
|
working_set.add_decl(Box::new(Let));
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, phrase, true);
|
2021-09-05 00:25:31 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(block.len(), 2, "{tag} block len 2");
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[1];
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr:
|
|
|
|
Expr::Range(
|
2022-11-22 19:26:13 +01:00
|
|
|
Some(_),
|
|
|
|
None,
|
|
|
|
Some(_),
|
|
|
|
RangeOperator {
|
2023-04-07 13:40:05 +02:00
|
|
|
inclusion: the_inclusion,
|
2022-11-22 19:26:13 +01:00
|
|
|
..
|
2023-04-07 13:40:05 +02:00
|
|
|
},
|
2022-11-22 19:26:13 +01:00
|
|
|
),
|
2023-04-07 13:40:05 +02:00
|
|
|
..
|
|
|
|
},
|
|
|
|
) = expressions[0]
|
|
|
|
{
|
|
|
|
assert_eq!(
|
|
|
|
the_inclusion, inclusion,
|
|
|
|
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
panic!("{tag}: expression mismatch.")
|
|
|
|
};
|
2021-09-05 00:25:31 +02:00
|
|
|
}
|
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
#[rstest]
|
|
|
|
#[case(b"0..", RangeInclusion::Inclusive, "right unbounded")]
|
|
|
|
#[case(b"0..=", RangeInclusion::Inclusive, "right unbounded =inclusive")]
|
|
|
|
#[case(b"0..<", RangeInclusion::RightExclusive, "right unbounded")]
|
2021-09-05 00:25:31 +02:00
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
fn parse_right_unbounded_range(
|
|
|
|
#[case] phrase: &[u8],
|
|
|
|
#[case] inclusion: RangeInclusion,
|
|
|
|
#[case] tag: &str,
|
|
|
|
) {
|
2021-09-05 00:25:31 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, phrase, true);
|
2021-09-05 00:25:31 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(block.len(), 1, "{tag}: block len 1");
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr:
|
|
|
|
Expr::Range(
|
2022-11-22 19:26:13 +01:00
|
|
|
Some(_),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
RangeOperator {
|
2023-04-07 13:40:05 +02:00
|
|
|
inclusion: the_inclusion,
|
2022-11-22 19:26:13 +01:00
|
|
|
..
|
2023-04-07 13:40:05 +02:00
|
|
|
},
|
2022-11-22 19:26:13 +01:00
|
|
|
),
|
2023-04-07 13:40:05 +02:00
|
|
|
..
|
|
|
|
},
|
|
|
|
) = expressions[0]
|
|
|
|
{
|
|
|
|
assert_eq!(
|
|
|
|
the_inclusion, inclusion,
|
|
|
|
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
panic!("{tag}: expression mismatch.")
|
|
|
|
};
|
2021-09-05 00:25:31 +02:00
|
|
|
}
|
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
#[rstest]
|
|
|
|
#[case(b"..10", RangeInclusion::Inclusive, "left unbounded inclusive")]
|
|
|
|
#[case(b"..=10", RangeInclusion::Inclusive, "left unbounded =inclusive")]
|
|
|
|
#[case(b"..<10", RangeInclusion::RightExclusive, "left unbounded exclusive")]
|
|
|
|
|
|
|
|
fn parse_left_unbounded_range(
|
|
|
|
#[case] phrase: &[u8],
|
|
|
|
#[case] inclusion: RangeInclusion,
|
|
|
|
#[case] tag: &str,
|
|
|
|
) {
|
2021-09-12 14:29:27 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, phrase, true);
|
2021-09-12 14:29:27 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(block.len(), 1, "{tag}: block len 1");
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr:
|
|
|
|
Expr::Range(
|
2022-11-22 19:26:13 +01:00
|
|
|
None,
|
|
|
|
None,
|
|
|
|
Some(_),
|
|
|
|
RangeOperator {
|
2023-04-07 13:40:05 +02:00
|
|
|
inclusion: the_inclusion,
|
2022-11-22 19:26:13 +01:00
|
|
|
..
|
2023-04-07 13:40:05 +02:00
|
|
|
},
|
2022-11-22 19:26:13 +01:00
|
|
|
),
|
2023-04-07 13:40:05 +02:00
|
|
|
..
|
|
|
|
},
|
|
|
|
) = expressions[0]
|
|
|
|
{
|
|
|
|
assert_eq!(
|
|
|
|
the_inclusion, inclusion,
|
|
|
|
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
panic!("{tag}: expression mismatch.")
|
|
|
|
};
|
2021-09-12 14:29:27 +02:00
|
|
|
}
|
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
#[rstest]
|
|
|
|
#[case(b"2.0..4.0..10.0", RangeInclusion::Inclusive, "float inclusive")]
|
|
|
|
#[case(b"2.0..4.0..=10.0", RangeInclusion::Inclusive, "float =inclusive")]
|
|
|
|
#[case(b"2.0..4.0..<10.0", RangeInclusion::RightExclusive, "float exclusive")]
|
2022-02-15 20:31:14 +01:00
|
|
|
|
2023-04-07 13:40:05 +02:00
|
|
|
fn parse_float_range(
|
|
|
|
#[case] phrase: &[u8],
|
|
|
|
#[case] inclusion: RangeInclusion,
|
|
|
|
#[case] tag: &str,
|
|
|
|
) {
|
2021-09-12 14:36:54 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, phrase, true);
|
2021-09-12 14:36:54 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(block.len(), 1, "{tag}: block length 1");
|
2022-02-15 20:31:14 +01:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-04-07 13:40:05 +02:00
|
|
|
assert_eq!(expressions.len(), 1, "{tag}: expression length 1");
|
|
|
|
if let PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr:
|
|
|
|
Expr::Range(
|
2022-11-22 19:26:13 +01:00
|
|
|
Some(_),
|
|
|
|
Some(_),
|
|
|
|
Some(_),
|
|
|
|
RangeOperator {
|
2023-04-07 13:40:05 +02:00
|
|
|
inclusion: the_inclusion,
|
2022-11-22 19:26:13 +01:00
|
|
|
..
|
2023-04-07 13:40:05 +02:00
|
|
|
},
|
2022-11-22 19:26:13 +01:00
|
|
|
),
|
2023-04-07 13:40:05 +02:00
|
|
|
..
|
|
|
|
},
|
|
|
|
) = expressions[0]
|
|
|
|
{
|
|
|
|
assert_eq!(
|
|
|
|
the_inclusion, inclusion,
|
|
|
|
"{tag}: wrong RangeInclusion {the_inclusion:?}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
panic!("{tag}: expression mismatch.")
|
|
|
|
};
|
2021-09-12 14:36:54 +02:00
|
|
|
}
|
|
|
|
|
2021-09-05 19:33:53 +02:00
|
|
|
#[test]
|
2021-09-05 20:44:18 +02:00
|
|
|
fn bad_parse_does_crash() {
|
2021-09-05 19:33:53 +02:00
|
|
|
let engine_state = EngineState::new();
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let _ = parse(&mut working_set, None, b"(0)..\"a\"", true);
|
2021-09-05 19:33:53 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(!working_set.parse_errors.is_empty());
|
2021-09-05 19:33:53 +02:00
|
|
|
}
|
2021-09-05 00:25:31 +02:00
|
|
|
}
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod input_types {
|
|
|
|
use super::*;
|
2023-02-05 22:17:46 +01:00
|
|
|
use nu_protocol::ast::Call;
|
|
|
|
use nu_protocol::{ast::Argument, Category, PipelineData, ShellError, Type};
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct LsTest;
|
|
|
|
|
|
|
|
impl Command for LsTest {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"ls"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock ls command."
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build(self.name()).category(Category::Default)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-10 17:59:35 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2022-06-12 21:18:00 +02:00
|
|
|
pub struct GroupBy;
|
2022-06-10 17:59:35 +02:00
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
impl Command for GroupBy {
|
2022-06-10 17:59:35 +02:00
|
|
|
fn name(&self) -> &str {
|
|
|
|
"group-by"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock group-by command."
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build(self.name())
|
|
|
|
.required("column", SyntaxShape::String, "column name")
|
|
|
|
.category(Category::Default)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-10 17:59:35 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct ToCustom;
|
|
|
|
|
|
|
|
impl Command for ToCustom {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"to-custom"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock converter command."
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
2022-06-25 23:23:56 +02:00
|
|
|
Signature::build(self.name())
|
|
|
|
.input_type(Type::Any)
|
|
|
|
.output_type(Type::Custom("custom".into()))
|
|
|
|
.category(Category::Custom("custom".into()))
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-10 17:59:35 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct GroupByCustom;
|
|
|
|
|
|
|
|
impl Command for GroupByCustom {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"group-by"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock custom group-by command."
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build(self.name())
|
|
|
|
.required("column", SyntaxShape::String, "column name")
|
|
|
|
.required("other", SyntaxShape::String, "other value")
|
2022-06-25 23:23:56 +02:00
|
|
|
.input_type(Type::Custom("custom".into()))
|
|
|
|
.output_type(Type::Custom("custom".into()))
|
2022-06-10 17:59:35 +02:00
|
|
|
.category(Category::Custom("custom".into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-10 17:59:35 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AggCustom;
|
|
|
|
|
|
|
|
impl Command for AggCustom {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"agg"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock custom agg command."
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build(self.name())
|
|
|
|
.required("operation", SyntaxShape::String, "operation")
|
2022-06-25 23:23:56 +02:00
|
|
|
.input_type(Type::Custom("custom".into()))
|
2022-06-10 17:59:35 +02:00
|
|
|
.category(Category::Custom("custom".into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-10 17:59:35 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AggMin;
|
|
|
|
|
|
|
|
impl Command for AggMin {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"min"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock custom min command."
|
2022-06-12 21:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build(self.name()).category(Category::Custom("custom".into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-12 21:18:00 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct WithColumn;
|
|
|
|
|
|
|
|
impl Command for WithColumn {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"with-column"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock custom with-column command."
|
2022-06-12 21:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build(self.name())
|
|
|
|
.rest("operation", SyntaxShape::Any, "operation")
|
2022-06-25 23:23:56 +02:00
|
|
|
.input_type(Type::Custom("custom".into()))
|
|
|
|
.output_type(Type::Custom("custom".into()))
|
2022-06-12 21:18:00 +02:00
|
|
|
.category(Category::Custom("custom".into()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-12 21:18:00 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Collect;
|
|
|
|
|
|
|
|
impl Command for Collect {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"collect"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock custom collect command."
|
2022-06-12 21:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
2022-06-25 23:23:56 +02:00
|
|
|
Signature::build(self.name())
|
|
|
|
.input_type(Type::Custom("custom".into()))
|
|
|
|
.output_type(Type::Custom("custom".into()))
|
|
|
|
.category(Category::Custom("custom".into()))
|
2022-06-12 21:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-12 21:18:00 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-15 03:31:14 +02:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct IfMocked;
|
|
|
|
|
|
|
|
impl Command for IfMocked {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"if"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2023-03-01 06:33:02 +01:00
|
|
|
"Mock if command."
|
2022-06-15 03:31:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
|
|
Signature::build("if")
|
2023-03-22 21:14:10 +01:00
|
|
|
.required("cond", SyntaxShape::MathExpression, "condition to check")
|
2022-06-15 03:31:14 +02:00
|
|
|
.required(
|
|
|
|
"then_block",
|
2022-11-10 09:21:49 +01:00
|
|
|
SyntaxShape::Block,
|
2022-06-15 03:31:14 +02:00
|
|
|
"block to run if check succeeds",
|
|
|
|
)
|
|
|
|
.optional(
|
|
|
|
"else_expression",
|
Restrict closure expression to be something like `{|| ...}` (#8290)
# Description
As title, closes: #7921 closes: #8273
# User-Facing Changes
when define a closure without pipe, nushell will raise error for now:
```
❯ let x = {ss ss}
Error: nu::parser::closure_missing_pipe
× Missing || inside closure
╭─[entry #2:1:1]
1 │ let x = {ss ss}
· ───┬───
· ╰── Parsing as a closure, but || is missing
╰────
help: Try add || to the beginning of closure
```
`any`, `each`, `all`, `where` command accepts closure, it forces user
input closure like `{||`, or parse error will returned.
```
❯ {major:2, minor:1, patch:4} | values | each { into string }
Error: nu::parser::closure_missing_pipe
× Missing || inside closure
╭─[entry #4:1:1]
1 │ {major:2, minor:1, patch:4} | values | each { into string }
· ───────┬───────
· ╰── Parsing as a closure, but || is missing
╰────
help: Try add || to the beginning of closure
```
`with-env`, `do`, `def`, `try` are special, they still remain the same,
although it says that it accepts a closure, but they don't need to be
written like `{||`, it's more likely a block but can capture variable
outside of scope:
```
❯ def test [input] { echo [0 1 2] | do { do { echo $input } } }; test aaa
aaa
```
Just realize that It's a big breaking change, we need to update config
and scripts...
# 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-03-17 13:36:28 +01:00
|
|
|
SyntaxShape::Keyword(
|
|
|
|
b"else".to_vec(),
|
|
|
|
Box::new(SyntaxShape::OneOf(vec![
|
|
|
|
SyntaxShape::Block,
|
|
|
|
SyntaxShape::Expression,
|
|
|
|
])),
|
|
|
|
),
|
2022-06-15 03:31:14 +02:00
|
|
|
"expression or block to run if check fails",
|
|
|
|
)
|
|
|
|
.category(Category::Core)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
_engine_state: &EngineState,
|
|
|
|
_stack: &mut Stack,
|
2023-02-05 22:17:46 +01:00
|
|
|
_call: &Call,
|
|
|
|
_input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-06-15 03:31:14 +02:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 12:43:46 +01:00
|
|
|
fn add_declarations(engine_state: &mut EngineState) {
|
2022-06-10 17:59:35 +02:00
|
|
|
let delta = {
|
2022-06-25 22:20:28 +02:00
|
|
|
let mut working_set = StateWorkingSet::new(engine_state);
|
2022-06-10 17:59:35 +02:00
|
|
|
working_set.add_decl(Box::new(Let));
|
|
|
|
working_set.add_decl(Box::new(AggCustom));
|
|
|
|
working_set.add_decl(Box::new(GroupByCustom));
|
2022-06-12 21:18:00 +02:00
|
|
|
working_set.add_decl(Box::new(GroupBy));
|
2022-06-10 17:59:35 +02:00
|
|
|
working_set.add_decl(Box::new(LsTest));
|
|
|
|
working_set.add_decl(Box::new(ToCustom));
|
2022-06-12 21:18:00 +02:00
|
|
|
working_set.add_decl(Box::new(AggMin));
|
|
|
|
working_set.add_decl(Box::new(Collect));
|
|
|
|
working_set.add_decl(Box::new(WithColumn));
|
2022-06-15 03:31:14 +02:00
|
|
|
working_set.add_decl(Box::new(IfMocked));
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
working_set.render()
|
|
|
|
};
|
|
|
|
|
2022-07-14 16:09:27 +02:00
|
|
|
engine_state
|
|
|
|
.merge_delta(delta)
|
|
|
|
.expect("Error merging delta");
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn call_types_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input = r#"ls | to-custom | group-by name other"#;
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 3);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[0] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-07-14 16:09:27 +02:00
|
|
|
let expected_id = working_set
|
|
|
|
.find_decl(b"ls", &Type::Any)
|
|
|
|
.expect("Error merging delta");
|
2022-06-10 17:59:35 +02:00
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-10 17:59:35 +02:00
|
|
|
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[2] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-10 17:59:35 +02:00
|
|
|
let expected_id = working_set
|
|
|
|
.find_decl(b"group-by", &Type::Custom("custom".into()))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn storing_variable_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input =
|
|
|
|
r#"let a = (ls | to-custom | group-by name other); let b = (1+3); $a | agg sum"#;
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 3);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
let expressions = &block[2];
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-10 17:59:35 +02:00
|
|
|
let expected_id = working_set
|
|
|
|
.find_decl(b"agg", &Type::Custom("custom".into()))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-12 21:18:00 +02:00
|
|
|
#[test]
|
|
|
|
fn stored_variable_operation_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input = r#"let a = (ls | to-custom | group-by name other); ($a + $a) | agg sum"#;
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 2);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let expressions = &block[1];
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let expected_id = working_set
|
|
|
|
.find_decl(b"agg", &Type::Custom("custom".into()))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multiple_stored_variable_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input = r#"
|
|
|
|
let a = (ls | to-custom | group-by name other); [1 2 3] | to-custom; [1 2 3] | to-custom"#;
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 3);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let expressions = &block[1];
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
|
|
|
|
let expressions = &block[2];
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-10 17:59:35 +02:00
|
|
|
#[test]
|
|
|
|
fn call_non_custom_types_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input = r#"ls | group-by name"#;
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 2);
|
2022-06-10 17:59:35 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[0] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-10 17:59:35 +02:00
|
|
|
let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-10 17:59:35 +02:00
|
|
|
let expected_id = working_set.find_decl(b"group-by", &Type::Any).unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nested_operations_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let (block, delta) = {
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input = r#"ls | to-custom | group-by name other | agg ("b" | min)"#;
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
(block, working_set.render())
|
|
|
|
};
|
|
|
|
|
2022-07-14 16:09:27 +02:00
|
|
|
engine_state.merge_delta(delta).unwrap();
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[3] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let arg = &call.arguments[0];
|
|
|
|
match arg {
|
|
|
|
Argument::Positional(a) => match &a.expr {
|
|
|
|
Expr::FullCellPath(path) => match &path.head.expr {
|
|
|
|
Expr::Subexpression(id) => {
|
|
|
|
let block = engine_state.get_block(*id);
|
|
|
|
|
|
|
|
let expressions = &block[0];
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(expressions.len(), 2);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[1] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let expected_id =
|
|
|
|
working_set.find_decl(b"min", &Type::Any).unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => panic!("Expected Subexpression not found"),
|
|
|
|
},
|
|
|
|
_ => panic!("Expected FullCellPath not found"),
|
|
|
|
},
|
|
|
|
_ => panic!("Expected Argument Positional not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn call_with_list_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let input = r#"[[a b]; [1 2] [3 4]] | to-custom | with-column [ ("a" | min) ("b" | min) ] | collect"#;
|
|
|
|
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-15 03:03:32 +01:00
|
|
|
assert_eq!(block.len(), 1);
|
2022-06-12 21:18:00 +02:00
|
|
|
|
|
|
|
let expressions = &block[0];
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[2] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let expected_id = working_set
|
|
|
|
.find_decl(b"with-column", &Type::Custom("custom".into()))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
match &expressions[3] {
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
2022-06-12 21:18:00 +02:00
|
|
|
let expected_id = working_set
|
|
|
|
.find_decl(b"collect", &Type::Custom("custom".into()))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(call.decl_id, expected_id)
|
|
|
|
}
|
|
|
|
_ => panic!("Expected expression Call not found"),
|
|
|
|
}
|
|
|
|
}
|
2022-06-15 03:31:14 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn operations_within_blocks_test() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-06-15 03:31:14 +02:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
|
|
|
let inputs = vec![
|
2022-12-08 00:02:11 +01:00
|
|
|
r#"let a = 'b'; ($a == 'b') or ($a == 'b')"#,
|
|
|
|
r#"let a = 'b'; ($a == 'b') or ($a == 'b') and ($a == 'b')"#,
|
|
|
|
r#"let a = 1; ($a == 1) or ($a == 2) and ($a == 3)"#,
|
|
|
|
r#"let a = 'b'; if ($a == 'b') or ($a == 'b') { true } else { false }"#,
|
|
|
|
r#"let a = 1; if ($a == 1) or ($a > 0) { true } else { false }"#,
|
2022-06-15 03:31:14 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
for input in inputs {
|
2023-04-07 20:09:38 +02:00
|
|
|
let block = parse(&mut working_set, None, input.as_bytes(), true);
|
2022-06-15 03:31:14 +02:00
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(working_set.parse_errors.is_empty());
|
2023-01-30 02:37:54 +01:00
|
|
|
assert_eq!(block.len(), 2, "testing: {input}");
|
2022-06-15 03:31:14 +02:00
|
|
|
}
|
|
|
|
}
|
2022-12-09 14:48:12 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn else_errors_correctly() {
|
|
|
|
let mut engine_state = EngineState::new();
|
2023-01-16 12:43:46 +01:00
|
|
|
add_declarations(&mut engine_state);
|
2022-12-09 14:48:12 +01:00
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2023-04-07 02:35:45 +02:00
|
|
|
parse(
|
2022-12-09 14:48:12 +01:00
|
|
|
&mut working_set,
|
|
|
|
None,
|
|
|
|
b"if false { 'a' } else { $foo }",
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
2023-05-02 17:17:14 +02:00
|
|
|
Some(ParseError::VariableNotFound(_, _))
|
2023-04-07 02:35:45 +02:00
|
|
|
));
|
2022-12-09 14:48:12 +01:00
|
|
|
}
|
2023-03-17 13:37:59 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn else_if_errors_correctly() {
|
|
|
|
let mut engine_state = EngineState::new();
|
|
|
|
add_declarations(&mut engine_state);
|
|
|
|
|
|
|
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
2023-04-07 02:35:45 +02:00
|
|
|
parse(
|
2023-03-17 13:37:59 +01:00
|
|
|
&mut working_set,
|
|
|
|
None,
|
|
|
|
b"if false { 'a' } else $foo { 'b' }",
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
|
2023-04-07 02:35:45 +02:00
|
|
|
assert!(matches!(
|
|
|
|
working_set.parse_errors.first(),
|
2023-05-02 17:17:14 +02:00
|
|
|
Some(ParseError::VariableNotFound(_, _))
|
2023-04-07 02:35:45 +02:00
|
|
|
));
|
2023-03-17 13:37:59 +01:00
|
|
|
}
|
2022-06-10 17:59:35 +02:00
|
|
|
}
|