2023-08-26 13:41:29 +00:00
|
|
|
use crate::{
|
2023-09-05 14:35:58 +00:00
|
|
|
ast::{
|
|
|
|
eval_operator, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
|
Spread operator in record literals (#11144)
Goes towards implementing #10598, which asks for a spread operator in
lists, in records, and when calling commands (continuation of #11006,
which only implements it in lists)
# Description
This PR is for adding a spread operator that can be used when building
records. Additional functionality can be added later.
Changes:
- Previously, the `Expr::Record` variant held `(Expression, Expression)`
pairs. It now holds instances of an enum `RecordItem` (the name isn't
amazing) that allows either a key-value mapping or a spread operator.
- `...` will be treated as the spread operator when it appears before
`$`, `{`, or `(` inside records (no whitespace allowed in between) (not
implemented yet)
- The error message for duplicate columns now includes the column name
itself, because if two spread records are involved in such an error, you
can't tell which field was duplicated from the spans alone
`...` will still be treated as a normal string outside records, and even
in records, it is not treated as a spread operator when not followed
immediately by a `$`, `{`, or `(`.
# User-Facing Changes
Users will be able to use `...` when building records.
```
> let rec = { x: 1, ...{ a: 2 } }
> $rec
╭───┬───╮
│ x │ 1 │
│ a │ 2 │
╰───┴───╯
> { foo: bar, ...$rec, baz: blah }
╭─────┬──────╮
│ foo │ bar │
│ x │ 1 │
│ a │ 2 │
│ baz │ blah │
╰─────┴──────╯
```
If you want to update a field of a record, you'll have to use `merge`
instead:
```
> { ...$rec, x: 5 }
Error: nu::shell::column_defined_twice
× Record field or table column used twice: x
╭─[entry #2:1:1]
1 │ { ...$rec, x: 5 }
· ──┬─ ┬
· │ ╰── field redefined here
· ╰── field first defined here
╰────
> $rec | merge { x: 5 }
╭───┬───╮
│ x │ 5 │
│ a │ 2 │
╰───┴───╯
```
# Tests + Formatting
# After Submitting
2023-11-29 17:31:31 +00:00
|
|
|
PipelineElement, RecordItem,
|
2023-09-05 14:35:58 +00:00
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
engine::{EngineState, StateWorkingSet},
|
2023-09-05 14:35:58 +00:00
|
|
|
record, HistoryFileFormat, PipelineData, Range, Record, ShellError, Span, Value,
|
2022-12-21 22:21:03 +00:00
|
|
|
};
|
2023-09-01 06:18:55 +00:00
|
|
|
use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
|
Spread operator in record literals (#11144)
Goes towards implementing #10598, which asks for a spread operator in
lists, in records, and when calling commands (continuation of #11006,
which only implements it in lists)
# Description
This PR is for adding a spread operator that can be used when building
records. Additional functionality can be added later.
Changes:
- Previously, the `Expr::Record` variant held `(Expression, Expression)`
pairs. It now holds instances of an enum `RecordItem` (the name isn't
amazing) that allows either a key-value mapping or a spread operator.
- `...` will be treated as the spread operator when it appears before
`$`, `{`, or `(` inside records (no whitespace allowed in between) (not
implemented yet)
- The error message for duplicate columns now includes the column name
itself, because if two spread records are involved in such an error, you
can't tell which field was duplicated from the spans alone
`...` will still be treated as a normal string outside records, and even
in records, it is not treated as a spread operator when not followed
immediately by a `$`, `{`, or `(`.
# User-Facing Changes
Users will be able to use `...` when building records.
```
> let rec = { x: 1, ...{ a: 2 } }
> $rec
╭───┬───╮
│ x │ 1 │
│ a │ 2 │
╰───┴───╯
> { foo: bar, ...$rec, baz: blah }
╭─────┬──────╮
│ foo │ bar │
│ x │ 1 │
│ a │ 2 │
│ baz │ blah │
╰─────┴──────╯
```
If you want to update a field of a record, you'll have to use `merge`
instead:
```
> { ...$rec, x: 5 }
Error: nu::shell::column_defined_twice
× Record field or table column used twice: x
╭─[entry #2:1:1]
1 │ { ...$rec, x: 5 }
· ──┬─ ┬
· │ ╰── field redefined here
· ╰── field first defined here
╰────
> $rec | merge { x: 5 }
╭───┬───╮
│ x │ 5 │
│ a │ 2 │
╰───┴───╯
```
# Tests + Formatting
# After Submitting
2023-11-29 17:31:31 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
2023-09-01 06:18:55 +00:00
|
|
|
|
|
|
|
pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result<Value, ShellError> {
|
2023-09-12 06:06:56 +00:00
|
|
|
fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
|
2023-09-01 06:18:55 +00:00
|
|
|
let cwd = engine_state.current_work_dir();
|
|
|
|
|
|
|
|
if path.exists() {
|
|
|
|
match nu_path::canonicalize_with(path, cwd) {
|
|
|
|
Ok(canon_path) => canon_path,
|
2023-09-12 06:06:56 +00:00
|
|
|
Err(_) => path.to_owned(),
|
2023-09-01 06:18:55 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-09-12 06:06:56 +00:00
|
|
|
path.to_owned()
|
2023-09-01 06:18:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut record = Record::new();
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"default-config-dir",
|
|
|
|
if let Some(mut path) = nu_path::config_dir() {
|
|
|
|
path.push("nushell");
|
|
|
|
Value::string(path.to_string_lossy(), span)
|
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not get config directory".into(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"config-path",
|
|
|
|
if let Some(path) = engine_state.get_config_path("config-path") {
|
|
|
|
let canon_config_path = canonicalize_path(engine_state, path);
|
|
|
|
Value::string(canon_config_path.to_string_lossy(), span)
|
|
|
|
} else if let Some(mut path) = nu_path::config_dir() {
|
|
|
|
path.push("nushell");
|
|
|
|
path.push("config.nu");
|
|
|
|
Value::string(path.to_string_lossy(), span)
|
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not get config directory".into(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"env-path",
|
|
|
|
if let Some(path) = engine_state.get_config_path("env-path") {
|
|
|
|
let canon_env_path = canonicalize_path(engine_state, path);
|
|
|
|
Value::string(canon_env_path.to_string_lossy(), span)
|
|
|
|
} else if let Some(mut path) = nu_path::config_dir() {
|
|
|
|
path.push("nushell");
|
|
|
|
path.push("env.nu");
|
|
|
|
Value::string(path.to_string_lossy(), span)
|
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not find environment path".into(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"history-path",
|
|
|
|
if let Some(mut path) = nu_path::config_dir() {
|
|
|
|
path.push("nushell");
|
|
|
|
match engine_state.config.history_file_format {
|
|
|
|
HistoryFileFormat::Sqlite => {
|
|
|
|
path.push("history.sqlite3");
|
|
|
|
}
|
|
|
|
HistoryFileFormat::PlainText => {
|
|
|
|
path.push("history.txt");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let canon_hist_path = canonicalize_path(engine_state, &path);
|
|
|
|
Value::string(canon_hist_path.to_string_lossy(), span)
|
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not find history path".into(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"loginshell-path",
|
|
|
|
if let Some(mut path) = nu_path::config_dir() {
|
|
|
|
path.push("nushell");
|
|
|
|
path.push("login.nu");
|
|
|
|
let canon_login_path = canonicalize_path(engine_state, &path);
|
|
|
|
Value::string(canon_login_path.to_string_lossy(), span)
|
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not find login shell path".into(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
{
|
|
|
|
record.push(
|
|
|
|
"plugin-path",
|
|
|
|
if let Some(path) = &engine_state.plugin_signatures {
|
|
|
|
let canon_plugin_path = canonicalize_path(engine_state, path);
|
|
|
|
Value::string(canon_plugin_path.to_string_lossy(), span)
|
2023-09-04 00:19:04 +00:00
|
|
|
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
|
|
|
// If there are no signatures, we should still populate the plugin path
|
|
|
|
plugin_path.push("nushell");
|
|
|
|
plugin_path.push("plugin.nu");
|
|
|
|
Value::string(plugin_path.to_string_lossy(), span)
|
2023-09-01 06:18:55 +00:00
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not get plugin signature location".into(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"home-path",
|
|
|
|
if let Some(path) = nu_path::home_dir() {
|
|
|
|
let canon_home_path = canonicalize_path(engine_state, &path);
|
|
|
|
Value::string(canon_home_path.to_string_lossy(), span)
|
|
|
|
} else {
|
2023-11-28 12:43:51 +00:00
|
|
|
Value::error(
|
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not get home path".into(),
|
|
|
|
},
|
|
|
|
span,
|
|
|
|
)
|
2023-09-01 06:18:55 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push("temp-path", {
|
|
|
|
let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
|
|
|
|
Value::string(canon_temp_path.to_string_lossy(), span)
|
|
|
|
});
|
|
|
|
|
|
|
|
record.push("pid", Value::int(std::process::id().into(), span));
|
|
|
|
|
|
|
|
record.push("os-info", {
|
|
|
|
let ver = get_kernel_version();
|
|
|
|
Value::record(
|
|
|
|
record! {
|
|
|
|
"name" => Value::string(get_os_name(), span),
|
|
|
|
"arch" => Value::string(get_os_arch(), span),
|
|
|
|
"family" => Value::string(get_os_family(), span),
|
|
|
|
"kernel_version" => Value::string(ver, span),
|
|
|
|
},
|
|
|
|
span,
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"startup-time",
|
|
|
|
Value::duration(engine_state.get_startup_time(), span),
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"is-interactive",
|
|
|
|
Value::bool(engine_state.is_interactive, span),
|
|
|
|
);
|
|
|
|
|
|
|
|
record.push("is-login", Value::bool(engine_state.is_login, span));
|
|
|
|
|
|
|
|
record.push(
|
|
|
|
"current-exe",
|
|
|
|
if let Ok(current_exe) = std::env::current_exe() {
|
|
|
|
Value::string(current_exe.to_string_lossy(), span)
|
|
|
|
} else {
|
|
|
|
Value::error(
|
2023-11-28 12:43:51 +00:00
|
|
|
ShellError::IOError {
|
|
|
|
msg: "Could not get current executable path".to_string(),
|
|
|
|
},
|
2023-09-01 06:18:55 +00:00
|
|
|
span,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(Value::record(record, span))
|
|
|
|
}
|
2022-12-21 22:21:03 +00:00
|
|
|
|
2023-08-26 13:41:29 +00:00
|
|
|
fn eval_const_call(
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
call: &Call,
|
|
|
|
input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
let decl = working_set.get_decl(call.decl_id);
|
|
|
|
|
|
|
|
if !decl.is_const() {
|
|
|
|
return Err(ShellError::NotAConstCommand(call.head));
|
|
|
|
}
|
|
|
|
|
|
|
|
if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
|
|
|
|
// It would require re-implementing get_full_help() for const evaluation. Assuming that
|
|
|
|
// getting help messages at parse-time is rare enough, we can simply disallow it.
|
|
|
|
return Err(ShellError::NotAConstHelp(call.head));
|
|
|
|
}
|
|
|
|
|
|
|
|
decl.run_const(working_set, call, input)
|
|
|
|
}
|
|
|
|
|
2023-09-12 18:35:47 +00:00
|
|
|
pub fn eval_const_subexpression(
|
2023-08-26 13:41:29 +00:00
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
block: &Block,
|
|
|
|
mut input: PipelineData,
|
2023-09-12 18:35:47 +00:00
|
|
|
span: Span,
|
2023-08-26 13:41:29 +00:00
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
for pipeline in block.pipelines.iter() {
|
|
|
|
for element in pipeline.elements.iter() {
|
|
|
|
let PipelineElement::Expression(_, expr) = element else {
|
2023-09-12 18:35:47 +00:00
|
|
|
return Err(ShellError::NotAConstant(span));
|
2023-08-26 13:41:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
input = eval_constant_with_input(working_set, expr, input)?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(input)
|
|
|
|
}
|
|
|
|
|
2023-09-12 18:35:47 +00:00
|
|
|
pub fn eval_constant_with_input(
|
2023-08-26 13:41:29 +00:00
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
expr: &Expression,
|
|
|
|
input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
match &expr.expr {
|
|
|
|
Expr::Call(call) => eval_const_call(working_set, call, input),
|
|
|
|
Expr::Subexpression(block_id) => {
|
|
|
|
let block = working_set.get_block(*block_id);
|
2023-09-12 18:35:47 +00:00
|
|
|
eval_const_subexpression(working_set, block, input, expr.span)
|
2023-08-26 13:41:29 +00:00
|
|
|
}
|
|
|
|
_ => eval_constant(working_set, expr).map(|v| PipelineData::Value(v, None)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
/// Evaluate a constant value at parse time
|
|
|
|
///
|
|
|
|
/// Based off eval_expression() in the engine
|
|
|
|
pub fn eval_constant(
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
expr: &Expression,
|
2023-08-26 13:41:29 +00:00
|
|
|
) -> Result<Value, ShellError> {
|
2022-12-21 22:21:03 +00:00
|
|
|
match &expr.expr {
|
2023-07-21 13:20:33 +00:00
|
|
|
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
2022-12-21 22:21:03 +00:00
|
|
|
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
|
|
|
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
2023-09-03 14:27:29 +00:00
|
|
|
Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)),
|
|
|
|
Expr::Filepath(path) => Ok(Value::string(path.clone(), expr.span)),
|
2023-07-13 21:02:05 +00:00
|
|
|
Expr::Var(var_id) => match working_set.get_variable(*var_id).const_val.as_ref() {
|
2022-12-21 22:21:03 +00:00
|
|
|
Some(val) => Ok(val.clone()),
|
2023-08-26 13:41:29 +00:00
|
|
|
None => Err(ShellError::NotAConstant(expr.span)),
|
2022-12-21 22:21:03 +00:00
|
|
|
},
|
2023-09-03 14:27:29 +00:00
|
|
|
Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)),
|
2022-12-21 22:21:03 +00:00
|
|
|
Expr::FullCellPath(cell_path) => {
|
|
|
|
let value = eval_constant(working_set, &cell_path.head)?;
|
|
|
|
|
2023-03-16 03:50:58 +00:00
|
|
|
match value.follow_cell_path(&cell_path.tail, false) {
|
2022-12-21 22:21:03 +00:00
|
|
|
Ok(val) => Ok(val),
|
|
|
|
// TODO: Better error conversion
|
2023-08-26 13:41:29 +00:00
|
|
|
Err(shell_error) => Err(ShellError::GenericError(
|
2022-12-21 22:21:03 +00:00
|
|
|
"Error when following cell path".to_string(),
|
2023-01-30 01:37:54 +00:00
|
|
|
format!("{shell_error:?}"),
|
2023-08-26 13:41:29 +00:00
|
|
|
Some(expr.span),
|
|
|
|
None,
|
|
|
|
vec![],
|
2022-12-21 22:21:03 +00:00
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
2023-09-03 14:27:29 +00:00
|
|
|
Expr::DateTime(dt) => Ok(Value::date(*dt, expr.span)),
|
2022-12-21 22:21:03 +00:00
|
|
|
Expr::List(x) => {
|
|
|
|
let mut output = vec![];
|
|
|
|
for expr in x {
|
2023-11-22 21:10:08 +00:00
|
|
|
match &expr.expr {
|
|
|
|
Expr::Spread(expr) => match eval_constant(working_set, expr)? {
|
|
|
|
Value::List { mut vals, .. } => output.append(&mut vals),
|
|
|
|
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
|
|
|
},
|
|
|
|
_ => output.push(eval_constant(working_set, expr)?),
|
|
|
|
}
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
2023-09-03 14:27:29 +00:00
|
|
|
Ok(Value::list(output, expr.span))
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
Spread operator in record literals (#11144)
Goes towards implementing #10598, which asks for a spread operator in
lists, in records, and when calling commands (continuation of #11006,
which only implements it in lists)
# Description
This PR is for adding a spread operator that can be used when building
records. Additional functionality can be added later.
Changes:
- Previously, the `Expr::Record` variant held `(Expression, Expression)`
pairs. It now holds instances of an enum `RecordItem` (the name isn't
amazing) that allows either a key-value mapping or a spread operator.
- `...` will be treated as the spread operator when it appears before
`$`, `{`, or `(` inside records (no whitespace allowed in between) (not
implemented yet)
- The error message for duplicate columns now includes the column name
itself, because if two spread records are involved in such an error, you
can't tell which field was duplicated from the spans alone
`...` will still be treated as a normal string outside records, and even
in records, it is not treated as a spread operator when not followed
immediately by a `$`, `{`, or `(`.
# User-Facing Changes
Users will be able to use `...` when building records.
```
> let rec = { x: 1, ...{ a: 2 } }
> $rec
╭───┬───╮
│ x │ 1 │
│ a │ 2 │
╰───┴───╯
> { foo: bar, ...$rec, baz: blah }
╭─────┬──────╮
│ foo │ bar │
│ x │ 1 │
│ a │ 2 │
│ baz │ blah │
╰─────┴──────╯
```
If you want to update a field of a record, you'll have to use `merge`
instead:
```
> { ...$rec, x: 5 }
Error: nu::shell::column_defined_twice
× Record field or table column used twice: x
╭─[entry #2:1:1]
1 │ { ...$rec, x: 5 }
· ──┬─ ┬
· │ ╰── field redefined here
· ╰── field first defined here
╰────
> $rec | merge { x: 5 }
╭───┬───╮
│ x │ 5 │
│ a │ 2 │
╰───┴───╯
```
# Tests + Formatting
# After Submitting
2023-11-29 17:31:31 +00:00
|
|
|
Expr::Record(items) => {
|
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
|
|
|
let mut record = Record::new();
|
Spread operator in record literals (#11144)
Goes towards implementing #10598, which asks for a spread operator in
lists, in records, and when calling commands (continuation of #11006,
which only implements it in lists)
# Description
This PR is for adding a spread operator that can be used when building
records. Additional functionality can be added later.
Changes:
- Previously, the `Expr::Record` variant held `(Expression, Expression)`
pairs. It now holds instances of an enum `RecordItem` (the name isn't
amazing) that allows either a key-value mapping or a spread operator.
- `...` will be treated as the spread operator when it appears before
`$`, `{`, or `(` inside records (no whitespace allowed in between) (not
implemented yet)
- The error message for duplicate columns now includes the column name
itself, because if two spread records are involved in such an error, you
can't tell which field was duplicated from the spans alone
`...` will still be treated as a normal string outside records, and even
in records, it is not treated as a spread operator when not followed
immediately by a `$`, `{`, or `(`.
# User-Facing Changes
Users will be able to use `...` when building records.
```
> let rec = { x: 1, ...{ a: 2 } }
> $rec
╭───┬───╮
│ x │ 1 │
│ a │ 2 │
╰───┴───╯
> { foo: bar, ...$rec, baz: blah }
╭─────┬──────╮
│ foo │ bar │
│ x │ 1 │
│ a │ 2 │
│ baz │ blah │
╰─────┴──────╯
```
If you want to update a field of a record, you'll have to use `merge`
instead:
```
> { ...$rec, x: 5 }
Error: nu::shell::column_defined_twice
× Record field or table column used twice: x
╭─[entry #2:1:1]
1 │ { ...$rec, x: 5 }
· ──┬─ ┬
· │ ╰── field redefined here
· ╰── field first defined here
╰────
> $rec | merge { x: 5 }
╭───┬───╮
│ x │ 5 │
│ a │ 2 │
╰───┴───╯
```
# Tests + Formatting
# After Submitting
2023-11-29 17:31:31 +00:00
|
|
|
let mut col_names = HashMap::new();
|
|
|
|
for item in items {
|
|
|
|
match item {
|
|
|
|
RecordItem::Pair(col, val) => {
|
|
|
|
// avoid duplicate cols
|
|
|
|
let col_name =
|
|
|
|
value_as_string(eval_constant(working_set, col)?, expr.span)?;
|
|
|
|
if let Some(orig_span) = col_names.get(&col_name) {
|
|
|
|
return Err(ShellError::ColumnDefinedTwice {
|
|
|
|
col_name,
|
|
|
|
second_use: col.span,
|
|
|
|
first_use: *orig_span,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
col_names.insert(col_name.clone(), col.span);
|
|
|
|
record.push(col_name, eval_constant(working_set, val)?);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
RecordItem::Spread(_, inner) => match eval_constant(working_set, inner)? {
|
|
|
|
Value::Record { val: inner_val, .. } => {
|
|
|
|
for (col_name, val) in inner_val {
|
|
|
|
if let Some(orig_span) = col_names.get(&col_name) {
|
|
|
|
return Err(ShellError::ColumnDefinedTwice {
|
|
|
|
col_name,
|
|
|
|
second_use: inner.span,
|
|
|
|
first_use: *orig_span,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
col_names.insert(col_name.clone(), inner.span);
|
|
|
|
record.push(col_name, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => return Err(ShellError::CannotSpreadAsRecord { span: inner.span }),
|
|
|
|
},
|
|
|
|
}
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
|
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
|
|
|
Ok(Value::record(record, expr.span))
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
Expr::Table(headers, vals) => {
|
|
|
|
let mut output_headers = vec![];
|
|
|
|
for expr in headers {
|
2023-11-01 20:25:35 +00:00
|
|
|
let header = value_as_string(eval_constant(working_set, expr)?, expr.span)?;
|
|
|
|
if let Some(idx) = output_headers
|
|
|
|
.iter()
|
|
|
|
.position(|existing| existing == &header)
|
|
|
|
{
|
|
|
|
return Err(ShellError::ColumnDefinedTwice {
|
Spread operator in record literals (#11144)
Goes towards implementing #10598, which asks for a spread operator in
lists, in records, and when calling commands (continuation of #11006,
which only implements it in lists)
# Description
This PR is for adding a spread operator that can be used when building
records. Additional functionality can be added later.
Changes:
- Previously, the `Expr::Record` variant held `(Expression, Expression)`
pairs. It now holds instances of an enum `RecordItem` (the name isn't
amazing) that allows either a key-value mapping or a spread operator.
- `...` will be treated as the spread operator when it appears before
`$`, `{`, or `(` inside records (no whitespace allowed in between) (not
implemented yet)
- The error message for duplicate columns now includes the column name
itself, because if two spread records are involved in such an error, you
can't tell which field was duplicated from the spans alone
`...` will still be treated as a normal string outside records, and even
in records, it is not treated as a spread operator when not followed
immediately by a `$`, `{`, or `(`.
# User-Facing Changes
Users will be able to use `...` when building records.
```
> let rec = { x: 1, ...{ a: 2 } }
> $rec
╭───┬───╮
│ x │ 1 │
│ a │ 2 │
╰───┴───╯
> { foo: bar, ...$rec, baz: blah }
╭─────┬──────╮
│ foo │ bar │
│ x │ 1 │
│ a │ 2 │
│ baz │ blah │
╰─────┴──────╯
```
If you want to update a field of a record, you'll have to use `merge`
instead:
```
> { ...$rec, x: 5 }
Error: nu::shell::column_defined_twice
× Record field or table column used twice: x
╭─[entry #2:1:1]
1 │ { ...$rec, x: 5 }
· ──┬─ ┬
· │ ╰── field redefined here
· ╰── field first defined here
╰────
> $rec | merge { x: 5 }
╭───┬───╮
│ x │ 5 │
│ a │ 2 │
╰───┴───╯
```
# Tests + Formatting
# After Submitting
2023-11-29 17:31:31 +00:00
|
|
|
col_name: header,
|
2023-11-01 20:25:35 +00:00
|
|
|
second_use: expr.span,
|
|
|
|
first_use: headers[idx].span,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
output_headers.push(header);
|
|
|
|
}
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut output_rows = vec![];
|
|
|
|
for val in vals {
|
|
|
|
let mut row = vec![];
|
|
|
|
for expr in val {
|
|
|
|
row.push(eval_constant(working_set, expr)?);
|
|
|
|
}
|
2023-11-01 22:19:58 +00:00
|
|
|
// length equality already ensured in parser
|
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
|
|
|
output_rows.push(Value::record(
|
2023-11-01 22:19:58 +00:00
|
|
|
Record::from_raw_cols_vals(output_headers.clone(), row),
|
Create `Record` type (#10103)
# Description
This PR creates a new `Record` type to reduce duplicate code and
possibly bugs as well. (This is an edited version of #9648.)
- `Record` implements `FromIterator` and `IntoIterator` and so can be
iterated over or collected into. For example, this helps with
conversions to and from (hash)maps. (Also, no more
`cols.iter().zip(vals)`!)
- `Record` has a `push(col, val)` function to help insure that the
number of columns is equal to the number of values. I caught a few
potential bugs thanks to this (e.g. in the `ls` command).
- Finally, this PR also adds a `record!` macro that helps simplify
record creation. It is used like so:
```rust
record! {
"key1" => some_value,
"key2" => Value::string("text", span),
"key3" => Value::int(optional_int.unwrap_or(0), span),
"key4" => Value::bool(config.setting, span),
}
```
Since macros hinder formatting, etc., the right hand side values should
be relatively short and sweet like the examples above.
Where possible, prefer `record!` or `.collect()` on an iterator instead
of multiple `Record::push`s, since the first two automatically set the
record capacity and do less work overall.
# User-Facing Changes
Besides the changes in `nu-protocol` the only other breaking changes are
to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
2023-08-24 19:50:29 +00:00
|
|
|
expr.span,
|
|
|
|
));
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
2023-09-03 14:27:29 +00:00
|
|
|
Ok(Value::list(output_rows, expr.span))
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
Expr::Keyword(_, _, expr) => eval_constant(working_set, expr),
|
2023-09-03 14:27:29 +00:00
|
|
|
Expr::String(s) => Ok(Value::string(s.clone(), expr.span)),
|
|
|
|
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
2023-05-20 13:23:25 +00:00
|
|
|
Expr::ValueWithUnit(expr, unit) => {
|
|
|
|
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) {
|
2023-08-26 13:41:29 +00:00
|
|
|
unit.item.to_value(val, unit.span)
|
2023-05-20 13:23:25 +00:00
|
|
|
} else {
|
2023-08-26 13:41:29 +00:00
|
|
|
Err(ShellError::NotAConstant(expr.span))
|
2023-05-20 13:23:25 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-26 13:41:29 +00:00
|
|
|
Expr::Call(call) => {
|
|
|
|
Ok(eval_const_call(working_set, call, PipelineData::empty())?.into_value(expr.span))
|
|
|
|
}
|
|
|
|
Expr::Subexpression(block_id) => {
|
|
|
|
let block = working_set.get_block(*block_id);
|
|
|
|
Ok(
|
2023-09-12 18:35:47 +00:00
|
|
|
eval_const_subexpression(working_set, block, PipelineData::empty(), expr.span)?
|
2023-08-26 13:41:29 +00:00
|
|
|
.into_value(expr.span),
|
|
|
|
)
|
|
|
|
}
|
2023-09-05 14:35:58 +00:00
|
|
|
Expr::Range(from, next, to, operator) => {
|
|
|
|
let from = if let Some(f) = from {
|
|
|
|
eval_constant(working_set, f)?
|
|
|
|
} else {
|
|
|
|
Value::Nothing {
|
|
|
|
internal_span: expr.span,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let next = if let Some(s) = next {
|
|
|
|
eval_constant(working_set, s)?
|
|
|
|
} else {
|
|
|
|
Value::Nothing {
|
|
|
|
internal_span: expr.span,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let to = if let Some(t) = to {
|
|
|
|
eval_constant(working_set, t)?
|
|
|
|
} else {
|
|
|
|
Value::Nothing {
|
|
|
|
internal_span: expr.span,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(Value::Range {
|
|
|
|
val: Box::new(Range::new(expr.span, from, next, to, operator)?),
|
|
|
|
internal_span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Expr::UnaryNot(expr) => {
|
|
|
|
let lhs = eval_constant(working_set, expr)?;
|
|
|
|
match lhs {
|
|
|
|
Value::Bool { val, .. } => Ok(Value::bool(!val, expr.span)),
|
|
|
|
_ => Err(ShellError::TypeMismatch {
|
|
|
|
err_message: "bool".to_string(),
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::BinaryOp(lhs, op, rhs) => {
|
|
|
|
let op_span = op.span;
|
|
|
|
let op = eval_operator(op)?;
|
|
|
|
|
|
|
|
match op {
|
|
|
|
Operator::Boolean(boolean) => {
|
|
|
|
let lhs = eval_constant(working_set, lhs)?;
|
|
|
|
match boolean {
|
|
|
|
Boolean::And => {
|
|
|
|
if lhs.is_false() {
|
|
|
|
Ok(Value::bool(false, expr.span))
|
|
|
|
} else {
|
|
|
|
let rhs = eval_constant(working_set, rhs)?;
|
|
|
|
lhs.and(op_span, &rhs, expr.span)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Boolean::Or => {
|
|
|
|
if lhs.is_true() {
|
|
|
|
Ok(Value::bool(true, expr.span))
|
|
|
|
} else {
|
|
|
|
let rhs = eval_constant(working_set, rhs)?;
|
|
|
|
lhs.or(op_span, &rhs, expr.span)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Boolean::Xor => {
|
|
|
|
let rhs = eval_constant(working_set, rhs)?;
|
|
|
|
lhs.xor(op_span, &rhs, expr.span)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Operator::Math(math) => {
|
|
|
|
let lhs = eval_constant(working_set, lhs)?;
|
|
|
|
let rhs = eval_constant(working_set, rhs)?;
|
|
|
|
|
|
|
|
match math {
|
|
|
|
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
|
|
|
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
|
|
|
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
|
|
|
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
|
|
|
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
|
|
|
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
|
|
|
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
|
|
|
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Operator::Comparison(comparison) => {
|
|
|
|
let lhs = eval_constant(working_set, lhs)?;
|
|
|
|
let rhs = eval_constant(working_set, rhs)?;
|
|
|
|
match comparison {
|
|
|
|
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
|
|
|
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
|
|
|
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
|
|
|
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
|
|
|
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
|
|
|
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
|
|
|
|
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
|
|
|
|
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
|
|
|
|
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
|
|
|
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
|
|
|
// RegEx comparison is not a constant
|
|
|
|
_ => Err(ShellError::NotAConstant(expr.span)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Operator::Bits(bits) => {
|
|
|
|
let lhs = eval_constant(working_set, lhs)?;
|
|
|
|
let rhs = eval_constant(working_set, rhs)?;
|
|
|
|
match bits {
|
|
|
|
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
|
|
|
|
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
|
|
|
|
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
|
|
|
|
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
|
|
|
|
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Operator::Assignment(_) => Err(ShellError::NotAConstant(expr.span)),
|
|
|
|
}
|
|
|
|
}
|
2023-09-12 18:35:47 +00:00
|
|
|
Expr::Block(block_id) => Ok(Value::block(*block_id, expr.span)),
|
2023-08-26 13:41:29 +00:00
|
|
|
_ => Err(ShellError::NotAConstant(expr.span)),
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the value as a string
|
2023-08-26 13:41:29 +00:00
|
|
|
pub fn value_as_string(value: Value, span: Span) -> Result<String, ShellError> {
|
2022-12-21 22:21:03 +00:00
|
|
|
match value {
|
|
|
|
Value::String { val, .. } => Ok(val),
|
2023-08-26 13:41:29 +00:00
|
|
|
_ => Err(ShellError::NotAConstant(span)),
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
}
|