mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 07:55:59 +02:00
Allow parse-time evaluation of calls, pipelines and subexpressions (#9499)
Co-authored-by: Antoine Stevan <44101798+amtoine@users.noreply.github.com>
This commit is contained in:
@ -1,136 +0,0 @@
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::StateWorkingSet,
|
||||
ParseError, Record, Span, Value,
|
||||
};
|
||||
|
||||
/// Evaluate a constant value at parse time
|
||||
///
|
||||
/// Based off eval_expression() in the engine
|
||||
pub fn eval_constant(
|
||||
working_set: &StateWorkingSet,
|
||||
expr: &Expression,
|
||||
) -> Result<Value, ParseError> {
|
||||
match &expr.expr {
|
||||
Expr::Bool(b) => Ok(Value::bool(*b, expr.span)),
|
||||
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
||||
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
||||
Expr::Binary(b) => Ok(Value::Binary {
|
||||
val: b.clone(),
|
||||
span: expr.span,
|
||||
}),
|
||||
Expr::Filepath(path) => Ok(Value::String {
|
||||
val: path.clone(),
|
||||
span: expr.span,
|
||||
}),
|
||||
Expr::Var(var_id) => match working_set.get_variable(*var_id).const_val.as_ref() {
|
||||
Some(val) => Ok(val.clone()),
|
||||
None => Err(ParseError::NotAConstant(expr.span)),
|
||||
},
|
||||
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
||||
val: cell_path.clone(),
|
||||
span: expr.span,
|
||||
}),
|
||||
Expr::FullCellPath(cell_path) => {
|
||||
let value = eval_constant(working_set, &cell_path.head)?;
|
||||
|
||||
match value.follow_cell_path(&cell_path.tail, false) {
|
||||
Ok(val) => Ok(val),
|
||||
// TODO: Better error conversion
|
||||
Err(shell_error) => Err(ParseError::LabeledError(
|
||||
"Error when following cell path".to_string(),
|
||||
format!("{shell_error:?}"),
|
||||
expr.span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
Expr::DateTime(dt) => Ok(Value::Date {
|
||||
val: *dt,
|
||||
span: expr.span,
|
||||
}),
|
||||
Expr::List(x) => {
|
||||
let mut output = vec![];
|
||||
for expr in x {
|
||||
output.push(eval_constant(working_set, expr)?);
|
||||
}
|
||||
Ok(Value::List {
|
||||
vals: output,
|
||||
span: expr.span,
|
||||
})
|
||||
}
|
||||
Expr::Record(fields) => {
|
||||
let mut record = Record::new();
|
||||
for (col, val) in fields {
|
||||
// avoid duplicate cols.
|
||||
let col_name = value_as_string(eval_constant(working_set, col)?, expr.span)?;
|
||||
let pos = record.cols.iter().position(|c| c == &col_name);
|
||||
match pos {
|
||||
Some(index) => {
|
||||
record.vals[index] = eval_constant(working_set, val)?;
|
||||
}
|
||||
None => {
|
||||
record.push(col_name, eval_constant(working_set, val)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::record(record, expr.span))
|
||||
}
|
||||
Expr::Table(headers, vals) => {
|
||||
let mut output_headers = vec![];
|
||||
for expr in headers {
|
||||
output_headers.push(value_as_string(
|
||||
eval_constant(working_set, expr)?,
|
||||
expr.span,
|
||||
)?);
|
||||
}
|
||||
|
||||
let mut output_rows = vec![];
|
||||
for val in vals {
|
||||
let mut row = vec![];
|
||||
for expr in val {
|
||||
row.push(eval_constant(working_set, expr)?);
|
||||
}
|
||||
output_rows.push(Value::record(
|
||||
Record {
|
||||
cols: output_headers.clone(),
|
||||
vals: row,
|
||||
},
|
||||
expr.span,
|
||||
));
|
||||
}
|
||||
Ok(Value::List {
|
||||
vals: output_rows,
|
||||
span: expr.span,
|
||||
})
|
||||
}
|
||||
Expr::Keyword(_, _, expr) => eval_constant(working_set, expr),
|
||||
Expr::String(s) => Ok(Value::String {
|
||||
val: s.clone(),
|
||||
span: expr.span,
|
||||
}),
|
||||
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
|
||||
Expr::ValueWithUnit(expr, unit) => {
|
||||
if let Ok(Value::Int { val, .. }) = eval_constant(working_set, expr) {
|
||||
unit.item.to_value(val, unit.span).map_err(|_| {
|
||||
ParseError::InvalidLiteral(
|
||||
"literal can not fit in unit".into(),
|
||||
"literal can not fit in unit".into(),
|
||||
unit.span,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Err(ParseError::NotAConstant(expr.span))
|
||||
}
|
||||
}
|
||||
_ => Err(ParseError::NotAConstant(expr.span)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value as a string
|
||||
pub fn value_as_string(value: Value, span: Span) -> Result<String, ParseError> {
|
||||
match value {
|
||||
Value::String { val, .. } => Ok(val),
|
||||
_ => Err(ParseError::NotAConstant(span)),
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
mod deparse;
|
||||
mod eval;
|
||||
mod flatten;
|
||||
mod known_external;
|
||||
mod lex;
|
||||
|
@ -12,6 +12,7 @@ use nu_protocol::{
|
||||
ImportPatternMember, Pipeline, PipelineElement,
|
||||
},
|
||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||
eval_const::{eval_constant, value_as_string},
|
||||
span, Alias, BlockId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
||||
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, VarId,
|
||||
};
|
||||
@ -24,7 +25,6 @@ pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
|
||||
pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS";
|
||||
|
||||
use crate::{
|
||||
eval::{eval_constant, value_as_string},
|
||||
is_math_expression_like,
|
||||
known_external::KnownExternal,
|
||||
lex,
|
||||
@ -2585,12 +2585,12 @@ pub fn parse_overlay_new(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
}
|
||||
@ -2634,12 +2634,12 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
}
|
||||
@ -2660,12 +2660,12 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
span: new_name_expression.span,
|
||||
}),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
}
|
||||
@ -2851,12 +2851,12 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
Ok(val) => match value_as_string(val, expr.span) {
|
||||
Ok(s) => (s, expr.span),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, call_span));
|
||||
return garbage_pipeline(&[call_span]);
|
||||
}
|
||||
}
|
||||
@ -3107,7 +3107,7 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
|
||||
// Assign the constant value to the variable
|
||||
working_set.set_variable_const_val(var_id, val);
|
||||
}
|
||||
Err(err) => working_set.error(err),
|
||||
Err(err) => working_set.error(err.wrap(working_set, rvalue.span)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -3300,7 +3300,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
||||
let val = match eval_constant(working_set, &expr) {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||
return Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: span(&spans[1..]),
|
||||
@ -3313,7 +3313,7 @@ pub fn parse_source(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeli
|
||||
let filename = match value_as_string(val, spans[1]) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(&spans[1..])));
|
||||
return Pipeline::from_vec(vec![Expression {
|
||||
expr: Expr::Call(call),
|
||||
span: span(&spans[1..]),
|
||||
@ -3504,8 +3504,10 @@ pub fn parse_register(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipe
|
||||
let arguments = call
|
||||
.positional_nth(0)
|
||||
.map(|expr| {
|
||||
let val = eval_constant(working_set, expr)?;
|
||||
let filename = value_as_string(val, expr.span)?;
|
||||
let val =
|
||||
eval_constant(working_set, expr).map_err(|err| err.wrap(working_set, call.head))?;
|
||||
let filename =
|
||||
value_as_string(val, expr.span).map_err(|err| err.wrap(working_set, call.head))?;
|
||||
|
||||
let Some(path) = find_in_dirs(&filename, working_set, &cwd, PLUGIN_DIRS_VAR) else {
|
||||
return Err(ParseError::RegisteredFileNotFound(filename, expr.span))
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::{
|
||||
eval::{eval_constant, value_as_string},
|
||||
lex::{lex, lex_signature},
|
||||
lite_parser::{lite_parse, LiteCommand, LiteElement, LitePipeline},
|
||||
parse_mut,
|
||||
@ -16,6 +15,7 @@ use nu_protocol::{
|
||||
Operator, PathMember, Pattern, Pipeline, PipelineElement, RangeInclusion, RangeOperator,
|
||||
},
|
||||
engine::StateWorkingSet,
|
||||
eval_const::{eval_constant, value_as_string},
|
||||
span, BlockId, DidYouMean, Flag, ParseError, PositionalArg, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Unit, VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID,
|
||||
};
|
||||
@ -2959,12 +2959,12 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
|
||||
Ok(val) => match value_as_string(val, head_expr.span) {
|
||||
Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(spans)));
|
||||
return garbage(span(spans));
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
working_set.error(err);
|
||||
working_set.error(err.wrap(working_set, span(spans)));
|
||||
return garbage(span(spans));
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user