diff --git a/crates/nu-cmd-lang/src/core_commands/extern_.rs b/crates/nu-cmd-lang/src/core_commands/extern_.rs index 8b6846606c..d75cb095e1 100644 --- a/crates/nu-cmd-lang/src/core_commands/extern_.rs +++ b/crates/nu-cmd-lang/src/core_commands/extern_.rs @@ -19,6 +19,7 @@ impl Command for Extern { .input_output_types(vec![(Type::Nothing, Type::Nothing)]) .required("def_name", SyntaxShape::String, "definition name") .required("params", SyntaxShape::Signature, "parameters") + .optional("body", SyntaxShape::Block, "wrapper function block") .category(Category::Core) } diff --git a/crates/nu-command/tests/commands/def.rs b/crates/nu-command/tests/commands/def.rs index a9f44e9d04..25bc392bef 100644 --- a/crates/nu-command/tests/commands/def.rs +++ b/crates/nu-command/tests/commands/def.rs @@ -141,3 +141,11 @@ fn def_with_paren_params() { assert_eq!(actual.out, "3"); } + +#[test] +fn extern_with_block() { + let actual = + nu!("extern foo [...rest] { print ($rest | str join ',' ) }; foo --bar baz -- -q -u -x"); + + assert_eq!(actual.out, "--bar,baz,--,-q,-u,-x"); +} diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index e945e5f723..444edfc9ea 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -159,7 +159,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { working_set.error(ParseError::DuplicateCommandDef(spans[1])); } } - } else if name == b"extern" && spans.len() == 3 { + } else if name == b"extern" && spans.len() >= 3 { let name_expr = parse_string(working_set, spans[1]); let name = name_expr.as_string(); @@ -430,40 +430,7 @@ pub fn parse_def( *declaration = signature.clone().into_block_command(block_id); let mut block = working_set.get_block_mut(block_id); - let calls_itself = block.pipelines.iter().any(|pipeline| { - pipeline - .elements - .iter() - .any(|pipe_element| match pipe_element { - PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call_expr), - .. - }, - ) => { - if call_expr.decl_id == decl_id { - return true; - } - call_expr.arguments.iter().any(|arg| match arg { - Argument::Positional(Expression { expr, .. }) => match expr { - Expr::Keyword(.., expr) => { - let expr = expr.as_ref(); - let Expression { expr, .. } = expr; - match expr { - Expr::Call(call_expr2) => call_expr2.decl_id == decl_id, - _ => false, - } - } - Expr::Call(call_expr2) => call_expr2.decl_id == decl_id, - _ => false, - }, - _ => false, - }) - } - _ => false, - }) - }); + let calls_itself = block_calls_itself(block, decl_id); block.recursive = Some(calls_itself); block.signature = signature; block.redirect_env = def_call == b"def-env"; @@ -543,6 +510,7 @@ pub fn parse_extern( }; let name_expr = call.positional_nth(0); let sig = call.positional_nth(1); + let body = call.positional_nth(2); if let (Some(name_expr), Some(sig)) = (name_expr, sig) { if let (Some(name), Some(mut signature)) = (&name_expr.as_string(), sig.as_signature()) { @@ -581,13 +549,29 @@ pub fn parse_extern( signature.extra_usage = extra_usage.clone(); signature.allows_unknown_args = true; - let decl = KnownExternal { - name: external_name, - usage: [usage, extra_usage].join("\n"), - signature, - }; + if let Some(block_id) = body.and_then(|x| x.as_block()) { + if signature.rest_positional.is_none() { + working_set.error(ParseError::InternalError( + "Extern block must have a rest positional argument".into(), + name_expr.span, + )); + } else { + *declaration = signature.clone().into_block_command(block_id); - *declaration = Box::new(decl); + let block = working_set.get_block_mut(block_id); + let calls_itself = block_calls_itself(block, decl_id); + block.recursive = Some(calls_itself); + block.signature = signature; + } + } else { + let decl = KnownExternal { + name: external_name, + usage: [usage, extra_usage].join("\n"), + signature, + }; + + *declaration = Box::new(decl); + } } else { working_set.error(ParseError::InternalError( "Predeclaration failed to add declaration".into(), @@ -614,6 +598,43 @@ pub fn parse_extern( }]) } +fn block_calls_itself(block: &Block, decl_id: usize) -> bool { + block.pipelines.iter().any(|pipeline| { + pipeline + .elements + .iter() + .any(|pipe_element| match pipe_element { + PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call_expr), + .. + }, + ) => { + if call_expr.decl_id == decl_id { + return true; + } + call_expr.arguments.iter().any(|arg| match arg { + Argument::Positional(Expression { expr, .. }) => match expr { + Expr::Keyword(.., expr) => { + let expr = expr.as_ref(); + let Expression { expr, .. } = expr; + match expr { + Expr::Call(call_expr2) => call_expr2.decl_id == decl_id, + _ => false, + } + } + Expr::Call(call_expr2) => call_expr2.decl_id == decl_id, + _ => false, + }, + _ => false, + }) + } + _ => false, + }) + }) +} + pub fn parse_alias( working_set: &mut StateWorkingSet, lite_command: &LiteCommand,