diff --git a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs index 44ed06060b..1df6f01d23 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -61,14 +61,13 @@ impl Command for OverlayUse { call: &Call, input: PipelineData, ) -> Result { - let call = call.assert_ast_call()?; // FIXME let mut name_arg: Spanned = call.req(engine_state, caller_stack, 0)?; name_arg.item = trim_quotes_str(&name_arg.item).to_string(); let maybe_origin_module_id = - if let Some(overlay_expr) = call.get_parser_info("overlay_expr") { + if let Some(overlay_expr) = call.get_parser_info(caller_stack, "overlay_expr") { if let Expr::Overlay(module_id) = &overlay_expr.expr { - module_id + module_id.clone() } else { return Err(ShellError::NushellFailedSpanned { msg: "Not an overlay".to_string(), @@ -111,7 +110,7 @@ impl Command for OverlayUse { // a) adding a new overlay // b) refreshing an active overlay (the origin module changed) - let module = engine_state.get_module(*module_id); + let module = engine_state.get_module(module_id); // Evaluate the export-env block (if any) and keep its environment if let Some(block_id) = module.env_block { @@ -119,7 +118,7 @@ impl Command for OverlayUse { &name_arg.item, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )?; let block = engine_state.get_block(block_id); diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index 3df77b94a5..7f544fa5d4 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -54,11 +54,10 @@ This command is a parser keyword. For details, check: call: &Call, input: PipelineData, ) -> Result { - let call = call.assert_ast_call()?; // FIXME let Some(Expression { expr: Expr::ImportPattern(import_pattern), .. - }) = call.get_parser_info("import_pattern") + }) = call.get_parser_info(caller_stack, "import_pattern") else { return Err(ShellError::GenericError { error: "Unexpected import".into(), @@ -69,6 +68,9 @@ This command is a parser keyword. For details, check: }); }; + // Necessary so that we can modify the stack. + let import_pattern = import_pattern.clone(); + if let Some(module_id) = import_pattern.head.id { // Add constants for var_id in &import_pattern.constants { @@ -100,7 +102,7 @@ This command is a parser keyword. For details, check: &module_arg_str, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )?; let maybe_parent = maybe_file_path .as_ref() diff --git a/crates/nu-command/src/bytes/build_.rs b/crates/nu-command/src/bytes/build_.rs index c3855e4b85..9a3599a071 100644 --- a/crates/nu-command/src/bytes/build_.rs +++ b/crates/nu-command/src/bytes/build_.rs @@ -48,12 +48,9 @@ impl Command for BytesBuild { call: &Call, _input: PipelineData, ) -> Result { - let call = call.assert_ast_call()?; // FIXME let mut output = vec![]; - for val in call.rest_iter_flattened(0, |expr| { - let eval_expression = get_eval_expression(engine_state); - eval_expression(engine_state, stack, expr) - })? { + let eval_expression = get_eval_expression(engine_state); + for val in call.rest_iter_flattened(engine_state, stack, eval_expression, 0)? { let val_span = val.span(); match val { Value::Binary { mut val, .. } => output.append(&mut val), diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index c922494edf..1813a92f1f 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -45,7 +45,6 @@ impl Command for SourceEnv { call: &Call, input: PipelineData, ) -> Result { - let call = call.assert_ast_call()?; // FIXME let source_filename: Spanned = call.req(engine_state, caller_stack, 0)?; // Note: this hidden positional is the block_id that corresponded to the 0th position @@ -57,7 +56,7 @@ impl Command for SourceEnv { &source_filename.item, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )? { PathBuf::from(&path) } else { diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 93d918943a..400e25029a 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -87,7 +87,7 @@ impl Command for NuCheck { &path_str.item, engine_state, stack, - get_dirs_var_from_call(call.assert_ast_call()?), // FIXME + get_dirs_var_from_call(stack, call), // FIXME ) { Ok(path) => { if let Some(path) = path { diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index deaea0b6cd..1043a15604 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -293,7 +293,21 @@ impl CallExt for ir::Call { stack: &mut Stack, name: &str, ) -> Result { - todo!("req_parser_info is not yet implemented on ir::Call") + // FIXME: this depends on the AST evaluator. We can fix this by making the parser info an + // enum rather than using expressions. It's not clear that evaluation of this is ever really + // needed. + if let Some(expr) = self.get_parser_info(stack, name) { + let expr = expr.clone(); + let stack = &mut stack.use_call_arg_out_dest(); + let result = eval_expression::(engine_state, stack, &expr)?; + FromValue::from_value(result) + } else { + Err(ShellError::CantFindColumn { + col_name: name.into(), + span: None, + src_span: self.head, + }) + } } fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool { diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 73f1f361ae..ab3a4bc50c 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,8 +1,8 @@ use crate::ClosureEvalOnce; use nu_path::canonicalize_with; use nu_protocol::{ - ast::{Call, Expr}, - engine::{EngineState, Stack, StateWorkingSet}, + ast::Expr, + engine::{Call, EngineState, Stack, StateWorkingSet}, Config, ShellError, Span, Value, VarId, }; use std::{ @@ -244,15 +244,15 @@ pub fn path_str( } pub const DIR_VAR_PARSER_INFO: &str = "dirs_var"; -// FIXME: this should be possible on IR calls -pub fn get_dirs_var_from_call(call: &Call) -> Option { - call.get_parser_info(DIR_VAR_PARSER_INFO).and_then(|x| { - if let Expr::Var(id) = x.expr { - Some(id) - } else { - None - } - }) +pub fn get_dirs_var_from_call(stack: &Stack, call: &Call) -> Option { + call.get_parser_info(stack, DIR_VAR_PARSER_INFO) + .and_then(|x| { + if let Expr::Var(id) = x.expr { + Some(id) + } else { + None + } + }) } /// This helper function is used to find files during eval diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 56d9ecdac5..49082bdafa 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -662,7 +662,11 @@ fn eval_call( .argument_stack .get_args(*args_base, args_len) .into_iter() - .fold(head, |span, arg| span.append(arg.span())); + .fold(head, |span, arg| { + arg.span() + .map(|arg_span| span.append(arg_span)) + .unwrap_or(span) + }); let call = Call { decl_id, head, diff --git a/crates/nu-parser/src/known_external.rs b/crates/nu-parser/src/known_external.rs index 75d141c90c..defcef3703 100644 --- a/crates/nu-parser/src/known_external.rs +++ b/crates/nu-parser/src/known_external.rs @@ -189,7 +189,9 @@ fn ir_call_to_extern_call( extern_call.add_argument(stack, name_arg); extern_call.add_argument(stack, val_arg); } - a @ (engine::Argument::Positional { .. } | engine::Argument::Spread { .. }) => { + a @ (engine::Argument::Positional { .. } + | engine::Argument::Spread { .. } + | engine::Argument::ParserInfo { .. }) => { let argument = a.clone(); extern_call.add_argument(stack, argument); } diff --git a/crates/nu-protocol/src/ast/match_pattern.rs b/crates/nu-protocol/src/ast/match_pattern.rs index b8f87c3f63..7d9b2fc930 100644 --- a/crates/nu-protocol/src/ast/match_pattern.rs +++ b/crates/nu-protocol/src/ast/match_pattern.rs @@ -19,6 +19,8 @@ impl MatchPattern { pub enum Pattern { Record(Vec<(String, MatchPattern)>), List(Vec), + // TODO: it would be nice if this didn't depend on AST + // maybe const evaluation can get us to a Value instead? Value(Expression), Variable(VarId), Or(Vec), diff --git a/crates/nu-protocol/src/engine/argument.rs b/crates/nu-protocol/src/engine/argument.rs index 09e932bc18..c2c816d2e5 100644 --- a/crates/nu-protocol/src/engine/argument.rs +++ b/crates/nu-protocol/src/engine/argument.rs @@ -1,19 +1,14 @@ use std::sync::Arc; -use crate::{ir::DataSlice, Span, Value}; +use crate::{ast::Expression, ir::DataSlice, Span, Value}; /// Represents a fully evaluated argument to a call. #[derive(Debug, Clone)] pub enum Argument { /// A positional argument - Positional { - span: Span, - val: Value, - }, - Spread { - span: Span, - vals: Value, - }, + Positional { span: Span, val: Value }, + /// A spread argument, e.g. `...$args` + Spread { span: Span, vals: Value }, /// A named argument with no value, e.g. `--flag` Flag { data: Arc<[u8]>, @@ -27,17 +22,27 @@ pub enum Argument { span: Span, val: Value, }, + /// Information generated by the parser for use by certain keyword commands + ParserInfo { + data: Arc<[u8]>, + name: DataSlice, + // TODO: rather than `Expression`, this would probably be best served by a specific enum + // type for this purpose. + expr: Box, + }, } impl Argument { /// The span encompassing the argument's usage within the call, distinct from the span of the /// actual value of the argument. - pub fn span(&self) -> Span { + pub fn span(&self) -> Option { match self { - Argument::Positional { span, .. } => *span, - Argument::Spread { span, .. } => *span, - Argument::Flag { span, .. } => *span, - Argument::Named { span, .. } => *span, + Argument::Positional { span, .. } => Some(*span), + Argument::Spread { span, .. } => Some(*span), + Argument::Flag { span, .. } => Some(*span), + Argument::Named { span, .. } => Some(*span), + // Because `ParserInfo` is generated, its span shouldn't be used + Argument::ParserInfo { .. } => None, } } } diff --git a/crates/nu-protocol/src/engine/call.rs b/crates/nu-protocol/src/engine/call.rs index 86c5e0e1c0..4c7de6d77d 100644 --- a/crates/nu-protocol/src/engine/call.rs +++ b/crates/nu-protocol/src/engine/call.rs @@ -1,4 +1,7 @@ -use crate::{ast, ir, DeclId, FromValue, ShellError, Span, Value}; +use crate::{ + ast::{self, Expression}, + ir, DeclId, FromValue, ShellError, Span, Value, +}; use super::{EngineState, Stack, StateWorkingSet}; @@ -117,6 +120,16 @@ impl Call<'_> { } } + /// Get a parser info argument by name. + pub fn get_parser_info<'a>(&'a self, stack: &'a Stack, name: &str) -> Option<&'a Expression> { + match &self.inner { + CallImpl::AstRef(call) => call.get_parser_info(name), + CallImpl::AstBox(call) => call.get_parser_info(name), + CallImpl::IrRef(call) => call.get_parser_info(stack, name), + CallImpl::IrBox(call) => call.get_parser_info(stack, name), + } + } + /// Evaluator-agnostic implementation of `rest_iter_flattened()`. Evaluates or gets all of the /// positional and spread arguments, flattens spreads, and then returns one list of values. pub fn rest_iter_flattened( diff --git a/crates/nu-protocol/src/ir/call.rs b/crates/nu-protocol/src/ir/call.rs index 643d195868..90cc7009bb 100644 --- a/crates/nu-protocol/src/ir/call.rs +++ b/crates/nu-protocol/src/ir/call.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use crate::{ + ast::Expression, engine::{self, Argument, Stack}, DeclId, ShellError, Span, Spanned, Value, }; @@ -55,6 +56,7 @@ impl Call { Span::new(past.start, self.span.end) } + /// The number of named arguments, with or without values. pub fn named_len(&self, stack: &Stack) -> usize { self.arguments(stack) .iter() @@ -62,6 +64,7 @@ impl Call { .count() } + /// Iterate through named arguments, with or without values. pub fn named_iter<'a>( &'a self, stack: &'a Stack, @@ -97,6 +100,8 @@ impl Call { ) } + /// Get a named argument's value by name. Returns [`None`] for named arguments with no value as + /// well. pub fn get_named_arg<'a>(&self, stack: &'a Stack, flag_name: &str) -> Option<&'a Value> { // Optimized to avoid str::from_utf8() self.arguments(stack) @@ -115,6 +120,7 @@ impl Call { .flatten() } + /// The number of positional arguments, excluding spread arguments. pub fn positional_len(&self, stack: &Stack) -> usize { self.arguments(stack) .iter() @@ -122,6 +128,7 @@ impl Call { .count() } + /// Iterate through positional arguments. Does not include spread arguments. pub fn positional_iter<'a>(&self, stack: &'a Stack) -> impl Iterator { self.arguments(stack).iter().filter_map(|arg| match arg { Argument::Positional { val, .. } => Some(val), @@ -129,6 +136,7 @@ impl Call { }) } + /// Get a positional argument by index. Does not include spread arguments. pub fn positional_nth<'a>(&self, stack: &'a Stack, index: usize) -> Option<&'a Value> { self.positional_iter(stack).nth(index) } @@ -150,6 +158,8 @@ impl Call { .skip(start) } + /// Returns all of the positional arguments including and after `start`, with spread arguments + /// flattened into a single `Vec`. pub fn rest_iter_flattened( &self, stack: &Stack, @@ -174,6 +184,20 @@ impl Call { Ok(acc) } + /// Get a parser info argument by name. + pub fn get_parser_info<'a>(&self, stack: &'a Stack, name: &str) -> Option<&'a Expression> { + self.arguments(stack) + .iter() + .find_map(|argument| match argument { + Argument::ParserInfo { + data, + name: name_slice, + expr, + } if &data[*name_slice] == name.as_bytes() => Some(expr.as_ref()), + _ => None, + }) + } + /// Returns a span encompassing the entire call. pub fn span(&self) -> Span { self.span @@ -197,7 +221,9 @@ impl CallBuilder { self.inner.args_base = stack.argument_stack.get_base(); } self.inner.args_len += 1; - self.inner.span = self.inner.span.append(argument.span()); + if let Some(span) = argument.span() { + self.inner.span = self.inner.span.append(span); + } stack.argument_stack.push(argument); self }