nushell/crates/nu-engine/src/eval.rs

1207 lines
43 KiB
Rust
Raw Normal View History

use crate::{current_dir_str, get_full_help, scope::create_scope};
use nu_path::expand_path_with;
2021-10-25 04:01:02 +00:00
use nu_protocol::{
ast::{
Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
PathMember,
},
engine::{EngineState, Stack},
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
PipelineData, Range, RawStream, ShellError, Span, Spanned, Unit, Value, VarId, ENV_VARIABLE_ID,
2021-10-25 04:01:02 +00:00
};
use nu_utils::stdout_write_all_and_flush;
use std::collections::HashMap;
use sysinfo::SystemExt;
2021-10-13 17:53:27 +00:00
2021-08-15 22:33:34 +00:00
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
2021-07-23 05:14:49 +00:00
match op {
Expression {
expr: Expr::Operator(operator),
..
} => Ok(operator.clone()),
2021-09-05 23:16:27 +00:00
Expression { span, expr, .. } => {
Err(ShellError::UnknownOperator(format!("{:?}", expr), *span))
}
}
2021-07-23 05:14:49 +00:00
}
pub fn eval_call(
2021-10-25 06:31:39 +00:00
engine_state: &EngineState,
caller_stack: &mut Stack,
2021-10-25 04:01:02 +00:00
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if let Some(ctrlc) = &engine_state.ctrlc {
if ctrlc.load(core::sync::atomic::Ordering::SeqCst) {
return Ok(Value::Nothing { span: call.head }.into_pipeline_data());
}
}
let decl = engine_state.get_decl(call.decl_id);
if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
let mut signature = decl.signature();
signature.usage = decl.usage().to_string();
signature.extra_usage = decl.extra_usage().to_string();
let full_help = get_full_help(&signature, &decl.examples(), engine_state, caller_stack);
2021-10-13 17:53:27 +00:00
Ok(Value::String {
val: full_help,
span: call.head,
2021-10-25 04:01:02 +00:00
}
.into_pipeline_data())
2021-10-13 17:53:27 +00:00
} else if let Some(block_id) = decl.get_block_id() {
2021-10-25 20:04:23 +00:00
let block = engine_state.get_block(block_id);
let mut callee_stack = caller_stack.gather_captures(&block.captures);
for (param_idx, param) in decl
.signature()
.required_positional
.iter()
.chain(decl.signature().optional_positional.iter())
.enumerate()
{
2021-07-23 21:19:30 +00:00
let var_id = param
.var_id
.expect("internal error: all custom parameters must have var_ids");
if let Some(arg) = call.positional_nth(param_idx) {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
2022-03-07 20:08:56 +00:00
} else if let Some(arg) = &param.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(var_id, Value::nothing(call.head));
}
2021-07-23 21:19:30 +00:00
}
2021-09-07 03:37:02 +00:00
if let Some(rest_positional) = decl.signature().rest_positional {
let mut rest_items = vec![];
for arg in call.positional_iter().skip(
2021-09-07 03:37:02 +00:00
decl.signature().required_positional.len()
+ decl.signature().optional_positional.len(),
) {
let result = eval_expression(engine_state, caller_stack, arg)?;
2021-09-07 03:37:02 +00:00
rest_items.push(result);
}
let span = if let Some(rest_item) = rest_items.first() {
2021-10-11 18:45:31 +00:00
rest_item.span()?
2021-09-07 03:37:02 +00:00
} else {
2021-12-19 07:46:13 +00:00
call.head
2021-09-07 03:37:02 +00:00
};
callee_stack.add_var(
2021-09-07 03:37:02 +00:00
rest_positional
.var_id
.expect("Internal error: rest positional parameter lacks var_id"),
Value::List {
vals: rest_items,
2021-09-07 03:37:02 +00:00
span,
},
)
}
2021-10-11 21:17:45 +00:00
for named in decl.signature().named {
2021-10-13 17:53:27 +00:00
if let Some(var_id) = named.var_id {
let mut found = false;
for call_named in call.named_iter() {
2021-10-13 17:53:27 +00:00
if call_named.0.item == named.long {
if let Some(arg) = &call_named.2 {
let result = eval_expression(engine_state, caller_stack, arg)?;
2021-10-12 04:49:17 +00:00
2022-03-07 20:08:56 +00:00
callee_stack.add_var(var_id, result);
} else if let Some(arg) = &named.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
2021-10-13 17:53:27 +00:00
} else {
callee_stack.add_var(
2021-10-13 17:53:27 +00:00
var_id,
Value::Bool {
val: true,
span: call.head,
},
)
}
found = true;
2021-10-11 21:17:45 +00:00
}
}
2021-10-12 04:49:17 +00:00
if !found {
if named.arg.is_none() {
callee_stack.add_var(
var_id,
Value::Bool {
val: false,
span: call.head,
},
)
2022-03-07 20:08:56 +00:00
} else if let Some(arg) = &named.default_value {
let result = eval_expression(engine_state, caller_stack, arg)?;
callee_stack.add_var(var_id, result);
} else {
callee_stack.add_var(var_id, Value::Nothing { span: call.head })
}
2021-10-13 17:53:27 +00:00
}
2021-10-12 04:49:17 +00:00
}
2021-10-11 21:17:45 +00:00
}
let result = eval_block(
engine_state,
&mut callee_stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
);
if block.redirect_env {
redirect_env(engine_state, caller_stack, &callee_stack);
}
result
2021-07-29 22:56:51 +00:00
} else {
// We pass caller_stack here with the knowledge that internal commands
// are going to be specifically looking for global state in the stack
// rather than any local state.
decl.run(engine_state, caller_stack, call, input)
}
2021-07-23 05:14:49 +00:00
}
/// Redirect the environment from callee to the caller.
pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
// Grab all environment variables from the callee
let caller_env_vars = caller_stack.get_env_var_names(engine_state);
// remove env vars that are present in the caller but not in the callee
// (the callee hid them)
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
caller_stack.remove_env_var(engine_state, var);
}
}
// add new env vars from callee to caller
for (var, value) in callee_stack.get_stack_env_vars() {
caller_stack.add_env_var(var, value);
}
}
/// Eval extarnal expression
///
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
2021-09-19 21:48:33 +00:00
fn eval_external(
2021-10-25 06:31:39 +00:00
engine_state: &EngineState,
stack: &mut Stack,
head: &Expression,
2021-10-08 21:51:47 +00:00
args: &[Expression],
2021-10-25 04:01:02 +00:00
input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> {
2021-10-25 06:31:39 +00:00
let decl_id = engine_state
Overlays (#5375) * WIP: Start laying overlays * Rename Overlay->Module; Start adding overlay * Revamp adding overlay * Add overlay add tests; Disable debug print * Fix overlay add; Add overlay remove * Add overlay remove tests * Add missing overlay remove file * Add overlay list command * (WIP?) Enable overlays for env vars * Move OverlayFrames to ScopeFrames * (WIP) Move everything to overlays only ScopeFrame contains nothing but overlays now * Fix predecls * Fix wrong overlay id translation and aliases * Fix broken env lookup logic * Remove TODOs * Add overlay add + remove for environment * Add a few overlay tests; Fix overlay add name * Some cleanup; Fix overlay add/remove names * Clippy * Fmt * Remove walls of comments * List overlays from stack; Add debugging flag Currently, the engine state ordering is somehow broken. * Fix (?) overlay list test * Fix tests on Windows * Fix activated overlay ordering * Check for active overlays equality in overlay list This removes the -p flag: Either both parser and engine will have the same overlays, or the command will fail. * Add merging on overlay remove * Change help message and comment * Add some remove-merge/discard tests * (WIP) Track removed overlays properly * Clippy; Fmt * Fix getting last overlay; Fix predecls in overlays * Remove merging; Fix re-add overwriting stuff Also some error message tweaks. * Fix overlay error in the engine * Update variable_completions.rs * Adds flags and optional arguments to view-source (#5446) * added flags and optional arguments to view-source * removed redundant code * removed redundant code * fmt * fix bug in shell_integration (#5450) * fix bug in shell_integration * add some comments * enable cd to work with directory abbreviations (#5452) * enable cd to work with abbreviations * add abbreviation example * fix tests * make it configurable * make cd recornize symblic link (#5454) * implement seq char command to generate single character sequence (#5453) * add tmp code * add seq char command * Add split number flag in `split row` (#5434) Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com> * Add two more overlay tests * Add ModuleId to OverlayFrame * Fix env conversion accidentally activating overlay It activated overlay from permanent state prematurely which would cause `overlay add` to misbehave. * Remove unused parameter; Add overlay list test * Remove added traces * Add overlay commands examples * Modify TODO * Fix $nu.scope iteration * Disallow removing default overlay * Refactor some parser errors * Remove last overlay if no argument * Diversify overlay examples * Make it possible to update overlay's module In case the origin module updates, the overlay add loads the new module, makes it overlay's origin and applies the changes. Before, it was impossible to update the overlay if the module changed. Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com> Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> Co-authored-by: WindSoilder <WindSoilder@outlook.com> Co-authored-by: Yuheng Su <gipsyh.icu@gmail.com>
2022-05-07 19:39:22 +00:00
.find_decl("run-external".as_bytes(), &[])
.ok_or(ShellError::ExternalNotSupported(head.span))?;
2021-09-19 21:48:33 +00:00
2021-10-25 06:31:39 +00:00
let command = engine_state.get_decl(decl_id);
2021-09-19 21:48:33 +00:00
let mut call = Call::new(head.span);
2021-10-08 21:51:47 +00:00
call.add_positional(head.clone());
2021-10-08 21:51:47 +00:00
for arg in args {
call.add_positional(arg.clone())
2021-10-08 21:51:47 +00:00
}
2021-09-19 21:48:33 +00:00
if redirect_stdout {
call.add_named((
2021-10-11 21:17:45 +00:00
Spanned {
item: "redirect-stdout".into(),
span: head.span,
},
None,
None,
))
}
if redirect_stderr {
call.add_named((
Spanned {
item: "redirect-stderr".into(),
span: head.span,
2021-10-11 21:17:45 +00:00
},
None,
None,
2021-10-11 21:17:45 +00:00
))
2021-09-23 16:42:03 +00:00
}
command.run(engine_state, stack, &call, input)
2021-09-19 21:48:33 +00:00
}
2021-09-02 22:58:15 +00:00
pub fn eval_expression(
2021-10-25 06:31:39 +00:00
engine_state: &EngineState,
stack: &mut Stack,
2021-09-02 22:58:15 +00:00
expr: &Expression,
) -> Result<Value, ShellError> {
2021-07-23 05:14:49 +00:00
match &expr.expr {
2021-07-24 05:57:17 +00:00
Expr::Bool(b) => Ok(Value::Bool {
val: *b,
span: expr.span,
}),
2021-07-23 05:14:49 +00:00
Expr::Int(i) => Ok(Value::Int {
val: *i,
span: expr.span,
}),
2021-08-08 20:21:21 +00:00
Expr::Float(f) => Ok(Value::Float {
val: *f,
span: expr.span,
}),
2022-02-28 23:31:53 +00:00
Expr::Binary(b) => Ok(Value::Binary {
val: b.clone(),
span: expr.span,
}),
2021-10-25 06:31:39 +00:00
Expr::ValueWithUnit(e, unit) => match eval_expression(engine_state, stack, e)? {
2021-10-05 02:27:39 +00:00
Value::Int { val, .. } => Ok(compute(val, unit.item, unit.span)),
x => Err(ShellError::CantConvert(
"unit value".into(),
x.get_type().to_string(),
e.span,
None,
)),
2021-10-05 02:27:39 +00:00
},
Expr::Range(from, next, to, operator) => {
let from = if let Some(f) = from {
2021-10-25 06:31:39 +00:00
eval_expression(engine_state, stack, f)?
} else {
2021-12-19 07:46:13 +00:00
Value::Nothing { span: expr.span }
};
let next = if let Some(s) = next {
2021-10-25 06:31:39 +00:00
eval_expression(engine_state, stack, s)?
} else {
2021-12-19 07:46:13 +00:00
Value::Nothing { span: expr.span }
};
let to = if let Some(t) = to {
2021-10-25 06:31:39 +00:00
eval_expression(engine_state, stack, t)?
} else {
2021-12-19 07:46:13 +00:00
Value::Nothing { span: expr.span }
};
Ok(Value::Range {
val: Box::new(Range::new(expr.span, from, next, to, operator)?),
span: expr.span,
})
}
2021-10-29 18:15:17 +00:00
Expr::Var(var_id) => eval_variable(engine_state, stack, *var_id, expr.span),
2021-10-25 20:04:23 +00:00
Expr::VarDecl(_) => Ok(Value::Nothing { span: expr.span }),
2021-10-02 02:59:11 +00:00
Expr::CellPath(cell_path) => Ok(Value::CellPath {
val: cell_path.clone(),
span: expr.span,
}),
2021-09-26 18:39:19 +00:00
Expr::FullCellPath(cell_path) => {
2021-10-25 06:31:39 +00:00
let value = eval_expression(engine_state, stack, &cell_path.head)?;
2021-09-06 22:02:24 +00:00
value.follow_cell_path(&cell_path.tail, false)
2021-09-06 22:02:24 +00:00
}
2021-12-19 07:46:13 +00:00
Expr::ImportPattern(_) => Ok(Value::Nothing { span: expr.span }),
Expr::Overlay(_) => {
let name =
String::from_utf8_lossy(engine_state.get_span_contents(&expr.span)).to_string();
Ok(Value::String {
val: name,
span: expr.span,
})
}
2021-10-25 04:01:02 +00:00
Expr::Call(call) => {
// FIXME: protect this collect with ctrl-c
Ok(
eval_call(engine_state, stack, call, PipelineData::new(call.head))?
.into_value(call.head),
)
2021-10-25 04:01:02 +00:00
}
Expr::ExternalCall(head, args) => {
let span = head.span;
2021-10-25 04:01:02 +00:00
// FIXME: protect this collect with ctrl-c
2021-10-25 21:14:21 +00:00
Ok(eval_external(
engine_state,
stack,
head,
2021-10-25 21:14:21 +00:00
args,
PipelineData::new(span),
false,
false,
2021-10-25 21:14:21 +00:00
)?
.into_value(span))
2021-09-23 16:42:03 +00:00
}
Expr::DateTime(dt) => Ok(Value::Date {
val: *dt,
span: expr.span,
}),
2021-08-08 21:02:47 +00:00
Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }),
2022-04-06 19:10:25 +00:00
Expr::UnaryNot(expr) => {
let lhs = eval_expression(engine_state, stack, expr)?;
match lhs {
Value::Bool { val, .. } => Ok(Value::Bool {
val: !val,
span: expr.span,
}),
_ => Err(ShellError::TypeMismatch("bool".to_string(), expr.span)),
}
}
2021-07-23 05:14:49 +00:00
Expr::BinaryOp(lhs, op, rhs) => {
2021-08-10 06:31:34 +00:00
let op_span = op.span;
2021-08-15 22:33:34 +00:00
let op = eval_operator(op)?;
2021-07-23 05:14:49 +00:00
match op {
Operator::Boolean(boolean) => {
let lhs = eval_expression(engine_state, stack, lhs)?;
match boolean {
Boolean::And => {
if lhs.is_false() {
Ok(Value::Bool {
val: false,
span: expr.span,
})
} else {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.and(op_span, &rhs, expr.span)
}
}
Boolean::Or => {
if lhs.is_true() {
Ok(Value::Bool {
val: true,
span: expr.span,
})
} else {
let rhs = eval_expression(engine_state, stack, rhs)?;
lhs.or(op_span, &rhs, expr.span)
}
}
}
}
Operator::Math(math) => {
let lhs = eval_expression(engine_state, stack, lhs)?;
let rhs = eval_expression(engine_state, stack, 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),
}
2022-07-03 11:45:20 +00:00
}
Operator::Comparison(comparison) => {
let lhs = eval_expression(engine_state, stack, lhs)?;
let rhs = eval_expression(engine_state, stack, 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::RegexMatch => lhs.regex_match(op_span, &rhs, false, expr.span),
Comparison::NotRegexMatch => {
lhs.regex_match(op_span, &rhs, true, expr.span)
}
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
}
}
Operator::Bits(bits) => {
let lhs = eval_expression(engine_state, stack, lhs)?;
let rhs = eval_expression(engine_state, stack, 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(assignment) => {
let rhs = eval_expression(engine_state, stack, rhs)?;
let rhs = match assignment {
Assignment::Assign => rhs,
Assignment::PlusAssign => {
let lhs = eval_expression(engine_state, stack, lhs)?;
lhs.add(op_span, &rhs, op_span)?
}
Assignment::MinusAssign => {
let lhs = eval_expression(engine_state, stack, lhs)?;
lhs.sub(op_span, &rhs, op_span)?
}
Assignment::MultiplyAssign => {
let lhs = eval_expression(engine_state, stack, lhs)?;
lhs.mul(op_span, &rhs, op_span)?
}
Assignment::DivideAssign => {
let lhs = eval_expression(engine_state, stack, lhs)?;
lhs.div(op_span, &rhs, op_span)?
}
};
match &lhs.expr {
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
let var_info = engine_state.get_var(*var_id);
if var_info.mutable {
stack.vars.insert(*var_id, rhs);
Ok(Value::nothing(lhs.span))
} else {
Err(ShellError::AssignmentRequiresMutableVar(lhs.span))
}
}
Expr::FullCellPath(cell_path) => match &cell_path.head.expr {
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
if var_id == &ENV_VARIABLE_ID {
// let mut lhs =
// eval_expression(engine_state, stack, &cell_path.head)?;
//lhs.update_data_at_cell_path(&cell_path.tail, rhs)?;
match &cell_path.tail[0] {
PathMember::String { val, .. } => {
stack.add_env_var(val.to_string(), rhs);
}
PathMember::Int { val, .. } => {
stack.add_env_var(val.to_string(), rhs);
}
}
Ok(Value::nothing(cell_path.head.span))
} else {
let var_info = engine_state.get_var(*var_id);
if var_info.mutable {
let mut lhs =
eval_expression(engine_state, stack, &cell_path.head)?;
lhs.update_data_at_cell_path(&cell_path.tail, rhs)?;
stack.vars.insert(*var_id, lhs);
Ok(Value::nothing(cell_path.head.span))
} else {
Err(ShellError::AssignmentRequiresMutableVar(lhs.span))
}
}
}
_ => Err(ShellError::AssignmentRequiresVar(lhs.span)),
},
_ => Err(ShellError::AssignmentRequiresVar(lhs.span)),
}
}
2021-07-23 05:14:49 +00:00
}
}
Expr::Subexpression(block_id) => {
2021-10-25 06:31:39 +00:00
let block = engine_state.get_block(*block_id);
2021-07-23 05:14:49 +00:00
2021-10-25 04:01:02 +00:00
// FIXME: protect this collect with ctrl-c
Ok(
eval_subexpression(engine_state, stack, block, PipelineData::new(expr.span))?
.into_value(expr.span),
)
2021-07-23 05:14:49 +00:00
}
Expr::RowCondition(block_id) | Expr::Closure(block_id) => {
let mut captures = HashMap::new();
let block = engine_state.get_block(*block_id);
for var_id in &block.captures {
captures.insert(*var_id, stack.get_var(*var_id, expr.span)?);
}
Ok(Value::Closure {
val: *block_id,
captures,
span: expr.span,
})
}
Expr::Block(block_id) => Ok(Value::Block {
val: *block_id,
span: expr.span,
}),
2021-07-23 21:19:30 +00:00
Expr::List(x) => {
let mut output = vec![];
for expr in x {
2021-10-25 06:31:39 +00:00
output.push(eval_expression(engine_state, stack, expr)?);
2021-07-23 21:19:30 +00:00
}
2021-08-08 21:02:47 +00:00
Ok(Value::List {
vals: output,
2021-08-08 21:02:47 +00:00
span: expr.span,
})
2021-07-23 21:19:30 +00:00
}
2021-11-10 23:14:00 +00:00
Expr::Record(fields) => {
let mut cols = vec![];
let mut vals = vec![];
for (col, val) in fields {
// avoid duplicate cols.
let col_name = eval_expression(engine_state, stack, col)?.as_string()?;
let pos = cols.iter().position(|c| c == &col_name);
match pos {
Some(index) => {
vals[index] = eval_expression(engine_state, stack, val)?;
}
None => {
cols.push(col_name);
vals.push(eval_expression(engine_state, stack, val)?);
}
}
2021-11-10 23:14:00 +00:00
}
Ok(Value::Record {
cols,
vals,
span: expr.span,
})
}
2021-08-28 19:17:30 +00:00
Expr::Table(headers, vals) => {
let mut output_headers = vec![];
for expr in headers {
2021-10-25 06:31:39 +00:00
output_headers.push(eval_expression(engine_state, stack, expr)?.as_string()?);
2021-08-28 19:17:30 +00:00
}
let mut output_rows = vec![];
for val in vals {
let mut row = vec![];
for expr in val {
2021-10-25 06:31:39 +00:00
row.push(eval_expression(engine_state, stack, expr)?);
2021-08-28 19:17:30 +00:00
}
output_rows.push(Value::Record {
cols: output_headers.clone(),
vals: row,
span: expr.span,
});
2021-08-28 19:17:30 +00:00
}
Ok(Value::List {
vals: output_rows,
2021-08-28 19:17:30 +00:00
span: expr.span,
})
}
2021-10-25 06:31:39 +00:00
Expr::Keyword(_, _, expr) => eval_expression(engine_state, stack, expr),
Expr::StringInterpolation(exprs) => {
let mut parts = vec![];
for expr in exprs {
parts.push(eval_expression(engine_state, stack, expr)?);
}
let config = engine_state.get_config();
parts
.into_iter()
.into_pipeline_data(None)
.collect_string("", config)
.map(|x| Value::String {
val: x,
span: expr.span,
})
}
2021-07-23 21:19:30 +00:00
Expr::String(s) => Ok(Value::String {
val: s.clone(),
span: expr.span,
}),
Expr::Filepath(s) => {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
Expr::Directory(s) => {
2022-04-22 23:48:10 +00:00
if s == "-" {
Ok(Value::String {
val: "-".to_string(),
span: expr.span,
})
} else {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
2022-04-22 23:48:10 +00:00
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
}
Expr::GlobPattern(s) => {
let cwd = current_dir_str(engine_state, stack)?;
let path = expand_path_with(s, cwd);
Ok(Value::String {
val: path.to_string_lossy().to_string(),
span: expr.span,
})
}
2021-08-08 21:02:47 +00:00
Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }),
Expr::Garbage => Ok(Value::Nothing { span: expr.span }),
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
}
2021-07-23 05:14:49 +00:00
}
/// Checks the expression to see if it's a internal or external call. If so, passes the input
/// into the call and gets out the result
/// Otherwise, invokes the expression
///
/// It returns PipelineData with a boolean flag, indicate that if the external runs to failed.
/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
pub fn eval_expression_with_input(
engine_state: &EngineState,
stack: &mut Stack,
expr: &Expression,
mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> {
match expr {
Expression {
expr: Expr::Call(call),
..
} => {
if !redirect_stdout || redirect_stderr {
// we're doing something different than the defaults
let mut call = call.clone();
call.redirect_stdout = redirect_stdout;
call.redirect_stderr = redirect_stderr;
input = eval_call(engine_state, stack, &call, input)?;
} else {
input = eval_call(engine_state, stack, call, input)?;
}
}
Expression {
expr: Expr::ExternalCall(head, args),
..
} => {
input = eval_external(
engine_state,
stack,
head,
args,
input,
redirect_stdout,
redirect_stderr,
)?;
}
Expression {
expr: Expr::Subexpression(block_id),
..
} => {
let block = engine_state.get_block(*block_id);
// FIXME: protect this collect with ctrl-c
input = eval_subexpression(engine_state, stack, block, input)?;
}
elem => {
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
}
};
Ok(might_consume_external_result(input))
}
// if the result is ExternalStream without redirecting output.
// that indicates we have no more commands to execute currently.
// we can try to catch and detect if external command runs to failed.
//
// This is useful to commands with semicolon, we can detect errors early to avoid
// commands after semicolon running.
fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) {
let mut runs_to_failed = false;
if let PipelineData::ExternalStream {
stdout: None,
stderr,
mut exit_code,
span,
metadata,
} = input
{
let exit_code = exit_code.take();
// Note:
// In run-external's implementation detail, the result sender thread
// send out stderr message first, then stdout message, then exit_code.
//
// In this clause, we already make sure that `stdout` is None
// But not the case of `stderr`, so if `stderr` is not None
// We need to consume stderr message before reading external commands' exit code.
//
// Or we'll never have a chance to read exit_code if stderr producer produce too much stderr message.
// So we consume stderr stream and rebuild it.
let stderr = stderr.map(|stderr_stream| {
let stderr_ctrlc = stderr_stream.ctrlc.clone();
let stderr_span = stderr_stream.span;
let stderr_bytes = match stderr_stream.into_bytes() {
Err(_) => vec![],
Ok(bytes) => bytes.item,
};
RawStream::new(
Box::new(vec![Ok(stderr_bytes)].into_iter()),
stderr_ctrlc,
stderr_span,
)
});
match exit_code {
Some(exit_code_stream) => {
let ctrlc = exit_code_stream.ctrlc.clone();
let exit_code: Vec<Value> = exit_code_stream.into_iter().collect();
if let Some(Value::Int { val: code, .. }) = exit_code.last() {
// if exit_code is not 0, it indicates error occured, return back Err.
if *code != 0 {
runs_to_failed = true;
}
}
(
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: Some(ListStream::from_stream(exit_code.into_iter(), ctrlc)),
span,
metadata,
},
runs_to_failed,
)
}
None => (
PipelineData::ExternalStream {
stdout: None,
stderr,
exit_code: None,
span,
metadata,
},
runs_to_failed,
),
}
} else {
(input, false)
}
}
2021-09-03 02:15:01 +00:00
pub fn eval_block(
2021-10-25 06:31:39 +00:00
engine_state: &EngineState,
stack: &mut Stack,
2021-09-03 02:15:01 +00:00
block: &Block,
2021-10-25 04:01:02 +00:00
mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
2021-10-25 04:01:02 +00:00
) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
for (i, elem) in pipeline.expressions.iter().enumerate() {
// if eval internal command failed, it can just make early return with `Err(ShellError)`.
let eval_result = eval_expression_with_input(
engine_state,
stack,
elem,
input,
redirect_stdout || (i != pipeline.expressions.len() - 1),
redirect_stderr,
)?;
input = eval_result.0;
// external command may runs to failed
// make early return so remaining commands will not be executed.
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
if eval_result.1 {
return Ok(input);
}
}
if pipeline_idx < (num_pipelines) - 1 {
match input {
PipelineData::Value(Value::Nothing { .. }, ..) => {}
2022-03-04 22:46:18 +00:00
PipelineData::ExternalStream {
ref mut exit_code, ..
} => {
let exit_code = exit_code.take();
// Drain the input to the screen via tabular output
let config = engine_state.get_config();
2022-03-04 22:46:18 +00:00
Overlays (#5375) * WIP: Start laying overlays * Rename Overlay->Module; Start adding overlay * Revamp adding overlay * Add overlay add tests; Disable debug print * Fix overlay add; Add overlay remove * Add overlay remove tests * Add missing overlay remove file * Add overlay list command * (WIP?) Enable overlays for env vars * Move OverlayFrames to ScopeFrames * (WIP) Move everything to overlays only ScopeFrame contains nothing but overlays now * Fix predecls * Fix wrong overlay id translation and aliases * Fix broken env lookup logic * Remove TODOs * Add overlay add + remove for environment * Add a few overlay tests; Fix overlay add name * Some cleanup; Fix overlay add/remove names * Clippy * Fmt * Remove walls of comments * List overlays from stack; Add debugging flag Currently, the engine state ordering is somehow broken. * Fix (?) overlay list test * Fix tests on Windows * Fix activated overlay ordering * Check for active overlays equality in overlay list This removes the -p flag: Either both parser and engine will have the same overlays, or the command will fail. * Add merging on overlay remove * Change help message and comment * Add some remove-merge/discard tests * (WIP) Track removed overlays properly * Clippy; Fmt * Fix getting last overlay; Fix predecls in overlays * Remove merging; Fix re-add overwriting stuff Also some error message tweaks. * Fix overlay error in the engine * Update variable_completions.rs * Adds flags and optional arguments to view-source (#5446) * added flags and optional arguments to view-source * removed redundant code * removed redundant code * fmt * fix bug in shell_integration (#5450) * fix bug in shell_integration * add some comments * enable cd to work with directory abbreviations (#5452) * enable cd to work with abbreviations * add abbreviation example * fix tests * make it configurable * make cd recornize symblic link (#5454) * implement seq char command to generate single character sequence (#5453) * add tmp code * add seq char command * Add split number flag in `split row` (#5434) Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com> * Add two more overlay tests * Add ModuleId to OverlayFrame * Fix env conversion accidentally activating overlay It activated overlay from permanent state prematurely which would cause `overlay add` to misbehave. * Remove unused parameter; Add overlay list test * Remove added traces * Add overlay commands examples * Modify TODO * Fix $nu.scope iteration * Disallow removing default overlay * Refactor some parser errors * Remove last overlay if no argument * Diversify overlay examples * Make it possible to update overlay's module In case the origin module updates, the overlay add loads the new module, makes it overlay's origin and applies the changes. Before, it was impossible to update the overlay if the module changed. Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com> Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> Co-authored-by: WindSoilder <WindSoilder@outlook.com> Co-authored-by: Yuheng Su <gipsyh.icu@gmail.com>
2022-05-07 19:39:22 +00:00
match engine_state.find_decl("table".as_bytes(), &[]) {
2022-03-04 22:46:18 +00:00
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
input,
)?;
print_or_return(table, config)?;
2022-03-04 22:46:18 +00:00
}
None => {
print_or_return(input, config)?;
2022-03-04 22:46:18 +00:00
}
};
if let Some(exit_code) = exit_code {
let mut v: Vec<_> = exit_code.collect();
2022-03-04 22:46:18 +00:00
if let Some(v) = v.pop() {
stack.add_env_var("LAST_EXIT_CODE".into(), v);
}
}
}
_ => {
// Drain the input to the screen via tabular output
let config = engine_state.get_config();
Overlays (#5375) * WIP: Start laying overlays * Rename Overlay->Module; Start adding overlay * Revamp adding overlay * Add overlay add tests; Disable debug print * Fix overlay add; Add overlay remove * Add overlay remove tests * Add missing overlay remove file * Add overlay list command * (WIP?) Enable overlays for env vars * Move OverlayFrames to ScopeFrames * (WIP) Move everything to overlays only ScopeFrame contains nothing but overlays now * Fix predecls * Fix wrong overlay id translation and aliases * Fix broken env lookup logic * Remove TODOs * Add overlay add + remove for environment * Add a few overlay tests; Fix overlay add name * Some cleanup; Fix overlay add/remove names * Clippy * Fmt * Remove walls of comments * List overlays from stack; Add debugging flag Currently, the engine state ordering is somehow broken. * Fix (?) overlay list test * Fix tests on Windows * Fix activated overlay ordering * Check for active overlays equality in overlay list This removes the -p flag: Either both parser and engine will have the same overlays, or the command will fail. * Add merging on overlay remove * Change help message and comment * Add some remove-merge/discard tests * (WIP) Track removed overlays properly * Clippy; Fmt * Fix getting last overlay; Fix predecls in overlays * Remove merging; Fix re-add overwriting stuff Also some error message tweaks. * Fix overlay error in the engine * Update variable_completions.rs * Adds flags and optional arguments to view-source (#5446) * added flags and optional arguments to view-source * removed redundant code * removed redundant code * fmt * fix bug in shell_integration (#5450) * fix bug in shell_integration * add some comments * enable cd to work with directory abbreviations (#5452) * enable cd to work with abbreviations * add abbreviation example * fix tests * make it configurable * make cd recornize symblic link (#5454) * implement seq char command to generate single character sequence (#5453) * add tmp code * add seq char command * Add split number flag in `split row` (#5434) Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com> * Add two more overlay tests * Add ModuleId to OverlayFrame * Fix env conversion accidentally activating overlay It activated overlay from permanent state prematurely which would cause `overlay add` to misbehave. * Remove unused parameter; Add overlay list test * Remove added traces * Add overlay commands examples * Modify TODO * Fix $nu.scope iteration * Disallow removing default overlay * Refactor some parser errors * Remove last overlay if no argument * Diversify overlay examples * Make it possible to update overlay's module In case the origin module updates, the overlay add loads the new module, makes it overlay's origin and applies the changes. Before, it was impossible to update the overlay if the module changed. Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com> Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> Co-authored-by: WindSoilder <WindSoilder@outlook.com> Co-authored-by: Yuheng Su <gipsyh.icu@gmail.com>
2022-05-07 19:39:22 +00:00
match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
input,
)?;
print_or_return(table, config)?;
}
None => {
print_or_return(input, config)?;
}
};
}
}
input = PipelineData::new(Span { start: 0, end: 0 })
}
}
Ok(input)
}
fn print_or_return(pipeline_data: PipelineData, config: &Config) -> Result<(), ShellError> {
for item in pipeline_data {
if let Value::Error { error } = item {
return Err(error);
}
let mut out = item.into_string("\n", config);
out.push('\n');
stdout_write_all_and_flush(out)?;
}
Ok(())
}
pub fn eval_subexpression(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
for pipeline in block.pipelines.iter() {
for expr in pipeline.expressions.iter() {
input = eval_expression_with_input(engine_state, stack, expr, input, true, false)?.0
}
}
2021-07-23 05:14:49 +00:00
2021-09-03 02:15:01 +00:00
Ok(input)
}
2021-10-05 02:27:39 +00:00
2021-10-29 18:15:17 +00:00
pub fn eval_variable(
2021-11-02 03:08:05 +00:00
engine_state: &EngineState,
2021-10-29 18:15:17 +00:00
stack: &Stack,
var_id: VarId,
span: Span,
) -> Result<Value, ShellError> {
match var_id {
nu_protocol::NU_VARIABLE_ID => {
// $nu
let mut output_cols = vec![];
let mut output_vals = vec![];
2021-10-29 18:15:17 +00:00
if let Some(path) = engine_state.get_config_path("config-path") {
output_cols.push("config-path".into());
output_vals.push(Value::String {
val: path.to_string_lossy().to_string(),
span,
});
}
if let Some(path) = engine_state.get_config_path("env-path") {
output_cols.push("env-path".into());
output_vals.push(Value::String {
val: path.to_string_lossy().to_string(),
span,
});
}
if let Some(mut config_path) = nu_path::config_dir() {
config_path.push("nushell");
let mut env_config_path = config_path.clone();
let mut loginshell_path = config_path.clone();
2021-10-29 18:15:17 +00:00
let mut history_path = config_path.clone();
2021-10-29 18:15:17 +00:00
match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => {
history_path.push("history.sqlite3");
}
HistoryFileFormat::PlainText => {
history_path.push("history.txt");
}
}
// let mut history_path = config_files::get_history_path(); // todo: this should use the get_history_path method but idk where to put that function
2021-10-29 18:15:17 +00:00
output_cols.push("history-path".into());
output_vals.push(Value::String {
val: history_path.to_string_lossy().to_string(),
span,
});
2021-10-29 18:15:17 +00:00
if engine_state.get_config_path("config-path").is_none() {
config_path.push("config.nu");
2021-12-11 19:29:56 +00:00
output_cols.push("config-path".into());
output_vals.push(Value::String {
val: config_path.to_string_lossy().to_string(),
span,
});
}
if engine_state.get_config_path("env-path").is_none() {
env_config_path.push("env.nu");
output_cols.push("env-path".into());
output_vals.push(Value::String {
val: env_config_path.to_string_lossy().to_string(),
span,
});
}
loginshell_path.push("login.nu");
output_cols.push("loginshell-path".into());
output_vals.push(Value::String {
val: loginshell_path.to_string_lossy().to_string(),
span,
});
}
#[cfg(feature = "plugin")]
if let Some(path) = &engine_state.plugin_signatures {
if let Some(path_str) = path.to_str() {
output_cols.push("plugin-path".into());
output_vals.push(Value::String {
val: path_str.into(),
span,
});
}
2021-12-02 06:35:32 +00:00
}
output_cols.push("scope".into());
output_vals.push(create_scope(engine_state, stack, span)?);
if let Some(home_path) = nu_path::home_dir() {
if let Some(home_path_str) = home_path.to_str() {
output_cols.push("home-path".into());
output_vals.push(Value::String {
val: home_path_str.into(),
span,
})
}
}
let temp = std::env::temp_dir();
if let Some(temp_path) = temp.to_str() {
output_cols.push("temp-path".into());
2021-12-11 19:12:30 +00:00
output_vals.push(Value::String {
val: temp_path.into(),
2021-12-11 19:12:30 +00:00
span,
})
}
2022-03-12 16:54:59 +00:00
let pid = std::process::id();
output_cols.push("pid".into());
output_vals.push(Value::int(pid as i64, span));
let sys = sysinfo::System::new();
let ver = match sys.kernel_version() {
Some(v) => v,
None => "unknown".into(),
};
let os_record = Value::Record {
cols: vec![
"name".into(),
"arch".into(),
"family".into(),
"kernel_version".into(),
],
vals: vec![
Value::string(std::env::consts::OS, span),
Value::string(std::env::consts::ARCH, span),
Value::string(std::env::consts::FAMILY, span),
Value::string(ver, span),
],
span,
};
output_cols.push("os-info".into());
output_vals.push(os_record);
Ok(Value::Record {
cols: output_cols,
vals: output_vals,
2021-12-11 20:00:29 +00:00
span,
})
}
ENV_VARIABLE_ID => {
let env_vars = stack.get_env_vars(engine_state);
let env_columns = env_vars.keys();
let env_values = env_vars.values();
let mut pairs = env_columns
.map(|x| x.to_string())
.zip(env_values.cloned())
.collect::<Vec<(String, Value)>>();
pairs.sort_by(|a, b| a.0.cmp(&b.0));
let (env_columns, env_values) = pairs.into_iter().unzip();
Ok(Value::Record {
cols: env_columns,
vals: env_values,
span,
})
}
var_id => stack.get_var(var_id, span),
2021-10-29 18:15:17 +00:00
}
}
fn compute(size: i64, unit: Unit, span: Span) -> Value {
2021-10-05 02:27:39 +00:00
match unit {
Unit::Byte => Value::Filesize { val: size, span },
Unit::Kilobyte => Value::Filesize {
val: size * 1000,
span,
},
Unit::Megabyte => Value::Filesize {
val: size * 1000 * 1000,
span,
},
Unit::Gigabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000,
span,
},
Unit::Terabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Petabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Exabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
span,
},
Unit::Zettabyte => Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
span,
},
2021-10-05 02:27:39 +00:00
Unit::Kibibyte => Value::Filesize {
val: size * 1024,
span,
},
Unit::Mebibyte => Value::Filesize {
val: size * 1024 * 1024,
span,
},
Unit::Gibibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024,
span,
},
Unit::Tebibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Pebibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Exbibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
span,
},
Unit::Zebibyte => Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
span,
},
2021-10-05 02:27:39 +00:00
Unit::Nanosecond => Value::Duration { val: size, span },
Unit::Microsecond => Value::Duration {
val: size * 1000,
span,
},
Unit::Millisecond => Value::Duration {
val: size * 1000 * 1000,
span,
},
Unit::Second => Value::Duration {
val: size * 1000 * 1000 * 1000,
span,
},
Unit::Minute => match size.checked_mul(1000 * 1000 * 1000 * 60) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
2021-10-05 02:27:39 +00:00
},
Unit::Hour => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
2021-10-05 02:27:39 +00:00
},
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
2021-10-05 02:27:39 +00:00
},
Unit::Week => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 7) {
Some(val) => Value::Duration { val, span },
None => Value::Error {
error: ShellError::GenericError(
"duration too large".into(),
"duration too large".into(),
Some(span),
None,
Vec::new(),
),
},
},
2021-10-05 02:27:39 +00:00
}
}