Move column paths to support broader value types.

This commit is contained in:
Andrés N. Robalino 2019-11-01 16:19:46 -05:00
parent 1b784cb77a
commit 6ea8e42331
13 changed files with 201 additions and 123 deletions

View File

@ -44,7 +44,7 @@ impl WholeStreamCommand for Get {
} }
} }
pub type ColumnPath = Vec<Tagged<String>>; pub type ColumnPath = Vec<Tagged<Value>>;
pub fn get_column_path( pub fn get_column_path(
path: &ColumnPath, path: &ColumnPath,
@ -67,7 +67,13 @@ pub fn get_column_path(
return ShellError::labeled_error_with_secondary( return ShellError::labeled_error_with_secondary(
"Row not found", "Row not found",
format!("There isn't a row indexed at '{}'", **column_path_tried), format!(
"There isn't a row indexed at '{}'",
match &*column_path_tried {
Value::Primitive(primitive) => primitive.format(None),
_ => String::from(""),
}
),
column_path_tried.tag(), column_path_tried.tag(),
format!("The table only has {} rows (0..{})", total, total - 1), format!("The table only has {} rows (0..{})", total, total - 1),
end_tag, end_tag,

View File

@ -409,27 +409,19 @@ impl Tagged<Value> {
ValueDebug { value: self } ValueDebug { value: self }
} }
pub fn as_column_path(&self) -> Result<Tagged<Vec<Tagged<String>>>, ShellError> { pub fn as_column_path(&self) -> Result<Tagged<Vec<Tagged<Value>>>, ShellError> {
let mut out: Vec<Tagged<String>> = vec![];
match &self.item { match &self.item {
Value::Table(table) => { Value::Primitive(Primitive::String(s)) => {
for item in table { Ok(vec![Value::string(s).tagged(&self.tag)].tagged(&self.tag))
out.push(item.as_string()?.tagged(&item.tag));
} }
} Value::Table(table) => Ok(table.to_vec().tagged(&self.tag)),
other => Err(ShellError::type_error(
other => {
return Err(ShellError::type_error(
"column name", "column name",
other.type_name().tagged(&self.tag), other.type_name().tagged(&self.tag),
)) )),
} }
} }
Ok(out.tagged(&self.tag))
}
pub(crate) fn as_string(&self) -> Result<String, ShellError> { pub(crate) fn as_string(&self) -> Result<String, ShellError> {
match &self.item { match &self.item {
Value::Primitive(Primitive::String(s)) => Ok(s.clone()), Value::Primitive(Primitive::String(s)) => Ok(s.clone()),
@ -528,30 +520,32 @@ impl Value {
pub fn get_data_by_column_path( pub fn get_data_by_column_path(
&self, &self,
tag: Tag, tag: Tag,
path: &Vec<Tagged<String>>, path: &Vec<Tagged<Value>>,
callback: Box<dyn FnOnce((&Value, &Tagged<String>)) -> ShellError>, callback: Box<dyn FnOnce((Value, Tagged<Value>)) -> ShellError>,
) -> Result<Option<Tagged<&Value>>, ShellError> { ) -> Result<Option<Tagged<&Value>>, ShellError> {
let mut current = self; let mut current = self;
for p in path { for p in path {
// note: let value = match p.item() {
// This will eventually be refactored once we are able Value::Primitive(Primitive::String(s)) => {
// to parse correctly column_paths and get them deserialized if let Value::Row(_) = current {
// to values for us. current.get_data_by_key(s)
let value = match p.item().parse::<usize>() { } else {
Ok(number) => match current { None
Value::Table(_) => current.get_data_by_index(number), }
Value::Row(_) => current.get_data_by_key(p), }
Value::Primitive(Primitive::Int(n)) => {
if let Value::Table(_) = current {
current.get_data_by_index(n.to_usize().unwrap())
} else {
None
}
}
_ => None, _ => None,
}, };
Err(_) => match self {
Value::Table(_) | Value::Row(_) => current.get_data_by_key(p),
_ => None,
},
}; // end
match value { match value {
Some(v) => current = v, Some(v) => current = v,
None => return Err(callback((&current.clone(), &p.clone()))), None => return Err(callback((current.clone(), p.clone()))),
} }
} }
@ -614,9 +608,21 @@ impl Value {
pub fn insert_data_at_column_path( pub fn insert_data_at_column_path(
&self, &self,
tag: Tag, tag: Tag,
split_path: &Vec<Tagged<String>>, split_path: &Vec<Tagged<Value>>,
new_value: Value, new_value: Value,
) -> Option<Tagged<Value>> { ) -> Option<Tagged<Value>> {
let split_path = split_path
.into_iter()
.map(|p| match p {
Tagged {
item: Value::Primitive(Primitive::String(s)),
tag,
} => Ok(s.clone().tagged(tag)),
o => Err(o),
})
.filter_map(Result::ok)
.collect::<Vec<Tagged<String>>>();
let mut new_obj = self.clone(); let mut new_obj = self.clone();
if let Value::Row(ref mut o) = new_obj { if let Value::Row(ref mut o) = new_obj {
@ -665,14 +671,14 @@ impl Value {
pub fn replace_data_at_column_path( pub fn replace_data_at_column_path(
&self, &self,
tag: Tag, tag: Tag,
split_path: &Vec<Tagged<String>>, split_path: &Vec<Tagged<Value>>,
replaced_value: Value, replaced_value: Value,
) -> Option<Tagged<Value>> { ) -> Option<Tagged<Value>> {
let mut new_obj = self.clone(); let mut new_obj = self.clone();
let mut current = &mut new_obj; let mut current = &mut new_obj;
for idx in 0..split_path.len() { for idx in 0..split_path.len() {
match current.get_mut_data_by_key(&split_path[idx].item) { match current.get_mut_data_by_key(&split_path[idx].as_string().unwrap()) {
Some(next) => { Some(next) => {
if idx == (split_path.len() - 1) { if idx == (split_path.len() - 1) {
*next = replaced_value.tagged(&tag); *next = replaced_value.tagged(&tag);
@ -943,6 +949,10 @@ mod tests {
Value::string(input.into()).tagged_unknown() Value::string(input.into()).tagged_unknown()
} }
fn number(n: i64) -> Tagged<Value> {
Value::number(n).tagged_unknown()
}
fn row(entries: IndexMap<String, Tagged<Value>>) -> Tagged<Value> { fn row(entries: IndexMap<String, Tagged<Value>>) -> Tagged<Value> {
Value::row(entries).tagged_unknown() Value::row(entries).tagged_unknown()
} }
@ -951,19 +961,12 @@ mod tests {
Value::table(list).tagged_unknown() Value::table(list).tagged_unknown()
} }
fn error_callback() -> impl FnOnce((&Value, &Tagged<String>)) -> ShellError { fn error_callback() -> impl FnOnce((Value, Tagged<Value>)) -> ShellError {
move |(_obj_source, _column_path_tried)| ShellError::unimplemented("will never be called.") move |(_obj_source, _column_path_tried)| ShellError::unimplemented("will never be called.")
} }
fn column_path(paths: &Vec<Tagged<Value>>) -> Tagged<Vec<Tagged<String>>> { fn column_path(paths: &Vec<Tagged<Value>>) -> Vec<Tagged<Value>> {
table( table(paths).as_column_path().unwrap().item
&paths
.iter()
.map(|p| string(p.as_string().unwrap()))
.collect(),
)
.as_column_path()
.unwrap()
} }
#[test] #[test]
@ -1005,36 +1008,9 @@ mod tests {
) )
} }
#[test]
fn gets_first_matching_field_from_rows_with_same_field_inside_a_table() {
let field_path = column_path(&vec![string("package"), string("authors"), string("name")]);
let (name, tag) = string("Andrés N. Robalino").into_parts();
let value = Value::row(indexmap! {
"package".into() => row(indexmap! {
"name".into() => string("nu"),
"version".into() => string("0.4.0"),
"authors".into() => table(&vec![
row(indexmap!{"name".into() => string("Andrés N. Robalino")}),
row(indexmap!{"name".into() => string("Jonathan Turner")}),
row(indexmap!{"name".into() => string("Yehuda Katz")})
])
})
});
assert_eq!(
**value
.get_data_by_column_path(tag, &field_path, Box::new(error_callback()))
.unwrap()
.unwrap(),
name
)
}
#[test] #[test]
fn column_path_that_contains_just_a_number_gets_a_row_from_a_table() { fn column_path_that_contains_just_a_number_gets_a_row_from_a_table() {
let field_path = column_path(&vec![string("package"), string("authors"), string("0")]); let field_path = column_path(&vec![string("package"), string("authors"), number(0)]);
let (_, tag) = string("Andrés N. Robalino").into_parts(); let (_, tag) = string("Andrés N. Robalino").into_parts();

View File

@ -61,7 +61,7 @@ impl<'de> ConfigDeserializer<'de> {
pub fn top(&mut self) -> &DeserializerItem { pub fn top(&mut self) -> &DeserializerItem {
let value = self.stack.last(); let value = self.stack.last();
trace!("inspecting top value :: {:?}", value); trace!("inspecting top value :: {:?}", value);
value.expect("Can't get top elemant of an empty stack") value.expect("Can't get top element of an empty stack")
} }
pub fn pop(&mut self) -> DeserializerItem { pub fn pop(&mut self) -> DeserializerItem {
@ -486,8 +486,8 @@ mod tests {
// is unspecified and change is likely. // is unspecified and change is likely.
// This test makes sure that such change is detected // This test makes sure that such change is detected
// by this test failing, and not things silently breaking. // by this test failing, and not things silently breaking.
// Specifically, we rely on this behaviour further above // Specifically, we rely on this behavior further above
// in the file to special case Tagged<Value> parsing. // in the file for the Tagged<Value> special case parsing.
let tuple = type_name::<()>(); let tuple = type_name::<()>();
let tagged_tuple = type_name::<Tagged<()>>(); let tagged_tuple = type_name::<Tagged<()>>();
let tagged_value = type_name::<Tagged<Value>>(); let tagged_value = type_name::<Tagged<Value>>();

View File

@ -1,7 +1,7 @@
use crate::parser::hir::syntax_shape::{ use crate::parser::hir::syntax_shape::{
expand_atom, parse_single_node, ExpandContext, ExpandExpression, ExpansionRule, expand_atom, parse_single_node, ExpandContext, ExpandExpression, ExpansionRule,
FallibleColorSyntax, FlatShape, ParseError, FallibleColorSyntax, FlatShape, TestSyntax, ParseError};
}; use crate::parser::hir::tokens_iterator::Peeked;
use crate::parser::{ use crate::parser::{
hir, hir,
hir::{RawNumber, TokensIterator}, hir::{RawNumber, TokensIterator},
@ -212,3 +212,18 @@ impl FallibleColorSyntax for IntShape {
Ok(()) Ok(())
} }
} }
impl TestSyntax for NumberShape {
fn test<'a, 'b>(
&self,
token_nodes: &'b mut TokensIterator<'a>,
_context: &ExpandContext,
) -> Option<Peeked<'a, 'b>> {
let peeked = token_nodes.peek_any();
match peeked.node {
Some(token) if token.is_number() => Some(peeked),
_ => None,
}
}
}

View File

@ -906,6 +906,16 @@ impl ExpandSyntax for MemberShape {
return Ok(Member::Bare(node.span())); return Ok(Member::Bare(node.span()));
} }
/* KATZ */
/* let number = NumberShape.test(token_nodes, context);
if let Some(peeked) = number {
let node = peeked.not_eof("column")?.commit();
let (n, span) = node.as_number().unwrap();
return Ok(Member::Number(n, span))
}*/
let string = StringShape.test(token_nodes, context); let string = StringShape.test(token_nodes, context);
if let Some(peeked) = string { if let Some(peeked) = string {

View File

@ -0,0 +1,16 @@
use crate::parser::hir::TokensIterator;
use crate::parser::parse::token_tree_builder::TokenTreeBuilder as b;
use crate::Span;
#[test]
fn supplies_tokens() {
let tokens = b::token_list(vec![b::var("it"), b::op("."), b::bare("cpu")]);
let (tokens, _) = b::build(tokens);
let tokens = tokens.expect_list();
let mut iterator = TokensIterator::all(tokens, Span::unknown());
iterator.next().unwrap().expect_var();
iterator.next().unwrap().expect_dot();
iterator.next().unwrap().expect_bare();
}

View File

@ -171,6 +171,16 @@ impl TokenNode {
} }
} }
pub fn is_number(&self) -> bool {
match self {
TokenNode::Token(Spanned {
item: RawToken::Number(_),
..
}) => true,
_ => false,
}
}
pub fn as_string(&self) -> Option<(Span, Span)> { pub fn as_string(&self) -> Option<(Span, Span)> {
match self { match self {
TokenNode::Token(Spanned { TokenNode::Token(Spanned {

View File

@ -3,7 +3,7 @@ use nu::{
Tagged, Value, Tagged, Value,
}; };
pub type ColumnPath = Tagged<Vec<Tagged<String>>>; pub type ColumnPath = Tagged<Vec<Tagged<Value>>>;
struct Edit { struct Edit {
field: Option<ColumnPath>, field: Option<ColumnPath>,

View File

@ -14,7 +14,7 @@ pub enum SemVerAction {
Patch, Patch,
} }
pub type ColumnPath = Vec<Tagged<String>>; pub type ColumnPath = Tagged<Vec<Tagged<Value>>>;
struct Inc { struct Inc {
field: Option<ColumnPath>, field: Option<ColumnPath>,
@ -100,7 +100,7 @@ impl Inc {
let replace_for = value.item.get_data_by_column_path( let replace_for = value.item.get_data_by_column_path(
value.tag(), value.tag(),
&f, f,
Box::new(move |(obj_source, column_path_tried)| { Box::new(move |(obj_source, column_path_tried)| {
match did_you_mean(&obj_source, &column_path_tried) { match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => { Some(suggestions) => {
@ -191,7 +191,7 @@ impl Plugin for Inc {
item: Value::Table(_), item: Value::Table(_),
.. ..
} => { } => {
self.field = Some(table.as_column_path()?.item().to_vec()); self.field = Some(table.as_column_path()?);
} }
value => return Err(ShellError::type_error("table", value.tagged_type_name())), value => return Err(ShellError::type_error("table", value.tagged_type_name())),
} }
@ -229,8 +229,8 @@ mod tests {
use super::{Inc, SemVerAction}; use super::{Inc, SemVerAction};
use indexmap::IndexMap; use indexmap::IndexMap;
use nu::{ use nu::{
CallInfo, EvaluatedArgs, Plugin, ReturnSuccess, Tag, Tagged, TaggedDictBuilder, TaggedItem, CallInfo, EvaluatedArgs, Plugin, Primitive, ReturnSuccess, Tag, Tagged, TaggedDictBuilder,
Value, TaggedItem, Value,
}; };
struct CallStub { struct CallStub {
@ -344,9 +344,13 @@ mod tests {
.is_ok()); .is_ok());
assert_eq!( assert_eq!(
plugin plugin.field.map(|f| f
.field .iter()
.map(|f| f.iter().map(|f| f.item.clone()).collect()), .map(|f| match &f.item {
Value::Primitive(Primitive::String(s)) => s.clone(),
_ => panic!(""),
})
.collect()),
Some(vec!["package".to_string(), "version".to_string()]) Some(vec!["package".to_string(), "version".to_string()])
); );
} }

View File

@ -4,7 +4,7 @@ use nu::{
Tagged, TaggedItem, Value, Tagged, TaggedItem, Value,
}; };
pub type ColumnPath = Vec<Tagged<String>>; pub type ColumnPath = Vec<Tagged<Value>>;
struct Insert { struct Insert {
field: Option<ColumnPath>, field: Option<ColumnPath>,
@ -22,14 +22,21 @@ impl Insert {
let value_tag = value.tag(); let value_tag = value.tag();
match (value.item, self.value.clone()) { match (value.item, self.value.clone()) {
(obj @ Value::Row(_), Some(v)) => match &self.field { (obj @ Value::Row(_), Some(v)) => match &self.field {
Some(f) => match obj.insert_data_at_column_path(value_tag.clone(), &f, v) { Some(f) => match obj.insert_data_at_column_path(value_tag.clone(), f, v) {
Some(v) => return Ok(v), Some(v) => return Ok(v),
None => { None => {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
format!( format!(
"add could not find place to insert field {:?} {}", "add could not find place to insert field {:?} {}",
obj, obj,
f.iter().map(|i| &i.item).join(".") f.iter()
.map(|i| {
match &i.item {
Value::Primitive(primitive) => primitive.format(None),
_ => String::from(""),
}
})
.join(".")
), ),
"column name", "column name",
&value_tag, &value_tag,

View File

@ -12,7 +12,7 @@ enum Action {
Substring(usize, usize), Substring(usize, usize),
} }
pub type ColumnPath = Vec<Tagged<String>>; pub type ColumnPath = Tagged<Vec<Tagged<Value>>>;
struct Str { struct Str {
field: Option<ColumnPath>, field: Option<ColumnPath>,
@ -132,7 +132,7 @@ impl Str {
let replace_for = value.item.get_data_by_column_path( let replace_for = value.item.get_data_by_column_path(
value.tag(), value.tag(),
&f, f,
Box::new(move |(obj_source, column_path_tried)| { Box::new(move |(obj_source, column_path_tried)| {
match did_you_mean(&obj_source, &column_path_tried) { match did_you_mean(&obj_source, &column_path_tried) {
Some(suggestions) => { Some(suggestions) => {
@ -169,7 +169,7 @@ impl Str {
match value.item.replace_data_at_column_path( match value.item.replace_data_at_column_path(
value.tag(), value.tag(),
&f, f,
replacement.item.clone(), replacement.item.clone(),
) { ) {
Some(v) => return Ok(v), Some(v) => return Ok(v),
@ -246,17 +246,22 @@ impl Plugin for Str {
if let Some(possible_field) = args.nth(0) { if let Some(possible_field) = args.nth(0) {
match possible_field { match possible_field {
Tagged { string @ Tagged {
item: Value::Primitive(Primitive::String(s)), item: Value::Primitive(Primitive::String(_)),
tag, ..
} => { } => match self.action {
self.for_field(vec![s.clone().tagged(tag)]); Some(Action::Downcase)
| Some(Action::Upcase)
| Some(Action::ToInteger)
| None => {
self.for_field(string.as_column_path()?);
} }
},
table @ Tagged { table @ Tagged {
item: Value::Table(_), item: Value::Table(_),
.. ..
} => { } => {
self.field = Some(table.as_column_path()?.item); self.field = Some(table.as_column_path()?);
} }
_ => { _ => {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
@ -419,9 +424,13 @@ mod tests {
.is_ok()); .is_ok());
assert_eq!( assert_eq!(
plugin plugin.field.map(|f| f
.field .iter()
.map(|f| f.into_iter().map(|f| f.item).collect()), .map(|f| match &f.item {
Value::Primitive(Primitive::String(s)) => s.clone(),
_ => panic!(""),
})
.collect()),
Some(vec!["package".to_string(), "description".to_string()]) Some(vec!["package".to_string(), "description".to_string()])
) )
} }

View File

@ -7,8 +7,10 @@ use std::path::{Component, Path, PathBuf};
pub fn did_you_mean( pub fn did_you_mean(
obj_source: &Value, obj_source: &Value,
field_tried: &Tagged<String>, field_tried: &Tagged<Value>,
) -> Option<Vec<(usize, String)>> { ) -> Option<Vec<(usize, String)>> {
let field_tried = field_tried.as_string().unwrap();
let possibilities = obj_source.data_descriptors(); let possibilities = obj_source.data_descriptors();
let mut possible_matches: Vec<_> = possibilities let mut possible_matches: Vec<_> = possibilities

View File

@ -27,7 +27,7 @@ fn get() {
} }
#[test] #[test]
fn fetches_by_index_from_a_given_table() { fn fetches_by_index() {
Playground::setup("get_test_2", |dirs, sandbox| { Playground::setup("get_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent( sandbox.with_files(vec![FileWithContent(
"sample.toml", "sample.toml",
@ -53,14 +53,13 @@ fn fetches_by_index_from_a_given_table() {
}) })
} }
#[test] #[test]
fn supports_fetching_rows_from_tables_using_columns_named_as_numbers() { fn fetches_by_column_path() {
Playground::setup("get_test_3", |dirs, sandbox| { Playground::setup("get_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent( sandbox.with_files(vec![FileWithContent(
"sample.toml", "sample.toml",
r#" r#"
[package] [package]
0 = "nu" name = "nu"
1 = "0.4.1"
"#, "#,
)]); )]);
@ -68,25 +67,23 @@ fn supports_fetching_rows_from_tables_using_columns_named_as_numbers() {
cwd: dirs.test(), h::pipeline( cwd: dirs.test(), h::pipeline(
r#" r#"
open sample.toml open sample.toml
| get package.1 | get package.name
| echo $it | echo $it
"# "#
)); ));
assert_eq!(actual, "0.4.1"); assert_eq!(actual, "nu");
}) })
} }
#[test] #[test]
fn can_fetch_tables_or_rows_using_numbers_in_column_path() { fn column_paths_are_either_double_quoted_or_regular_unquoted_words_separated_by_dot() {
Playground::setup("get_test_4", |dirs, sandbox| { Playground::setup("get_test_4", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent( sandbox.with_files(vec![FileWithContent(
"sample.toml", "sample.toml",
r#" r#"
[package] [package]
0 = "nu" 9999 = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
1 = "0.4.1"
2 = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
description = "When arepas shells are tasty and fun." description = "When arepas shells are tasty and fun."
"#, "#,
)]); )]);
@ -95,17 +92,18 @@ fn can_fetch_tables_or_rows_using_numbers_in_column_path() {
cwd: dirs.test(), h::pipeline( cwd: dirs.test(), h::pipeline(
r#" r#"
open sample.toml open sample.toml
| get package.2.1 | get package."9999"
| count
| echo $it | echo $it
"# "#
)); ));
assert_eq!(actual, "Jonathan Turner <jonathan.d.turner@gmail.com>"); assert_eq!(actual, "3");
}) })
} }
#[test] #[test]
fn fetches_more_than_one_column_member_path() { fn fetches_more_than_one_column_path() {
Playground::setup("get_test_5", |dirs, sandbox| { Playground::setup("get_test_5", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent( sandbox.with_files(vec![FileWithContent(
"sample.toml", "sample.toml",
@ -161,9 +159,34 @@ fn errors_fetching_by_column_not_present() {
assert!(actual.contains("did you mean 'taconushell'?")); assert!(actual.contains("did you mean 'taconushell'?"));
}) })
} }
#[test] #[test]
fn errors_fetching_by_index_out_of_bounds_from_table() { fn errors_fetching_by_column_using_a_number() {
Playground::setup("get_test_7", |dirs, sandbox| { Playground::setup("get_test_7", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"sample.toml",
r#"
[spanish_lesson]
0 = "can only be fetched with 0 double quoted."
"#,
)]);
let actual = nu_error!(
cwd: dirs.test(), h::pipeline(
r#"
open sample.toml
| get spanish_lesson.0
"#
));
assert!(actual.contains("No rows available"));
assert!(actual.contains("Tried getting a row indexed at '0'"));
assert!(actual.contains(r#"Not a table. Perhaps you meant to get the column "0" instead?"#))
})
}
#[test]
fn errors_fetching_by_index_out_of_bounds() {
Playground::setup("get_test_8", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent( sandbox.with_files(vec![FileWithContent(
"sample.toml", "sample.toml",
r#" r#"
@ -188,7 +211,7 @@ fn errors_fetching_by_index_out_of_bounds_from_table() {
#[test] #[test]
fn requires_at_least_one_column_member_path() { fn requires_at_least_one_column_member_path() {
Playground::setup("get_test_8", |dirs, sandbox| { Playground::setup("get_test_9", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("andres.txt")]); sandbox.with_files(vec![EmptyFile("andres.txt")]);
let actual = nu_error!( let actual = nu_error!(