diff --git a/crates/quake_engine/src/lib.rs b/crates/quake_engine/src/lib.rs index 601e1960d..f9d9c8e0a 100644 --- a/crates/quake_engine/src/lib.rs +++ b/crates/quake_engine/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::fs; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::Arc; use nu_parser::parse; @@ -53,9 +53,11 @@ impl Engine { let state = Arc::new(RwLock::new(State::new())); - let engine_state = create_engine_state(state.clone()); + let mut engine_state = create_engine_state(state.clone()); let stack = create_stack(project.project_root()); + engine_state.pipeline_externals_state = Arc::new((AtomicU32::new(1), AtomicU32::new(2))); + let mut engine = Self { project, _options: options, diff --git a/crates/quake_engine/src/nu/commands.rs b/crates/quake_engine/src/nu/commands.rs index 5453bbafa..5b1e8e636 100644 --- a/crates/quake_engine/src/nu/commands.rs +++ b/crates/quake_engine/src/nu/commands.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use nu_engine::CallExt; use nu_protocol::ast::Call; -use nu_protocol::engine::{Closure, Command, EngineState, Stack}; +use nu_protocol::engine::{Closure, Command, CommandType, EngineState, Stack, StateWorkingSet}; use nu_protocol::{ Category, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, }; @@ -21,28 +21,37 @@ impl Command for DefTask { } fn usage(&self) -> &str { - "Define a quake task" + "Define a quake task." + } + + fn extra_usage(&self) -> &str { + "\ +Must provide a declaration body (prefixed with 'where') and/or a run +body (prefixed with 'do'). " } fn signature(&self) -> Signature { Signature::build("def-task") .input_output_types(vec![(Type::Nothing, Type::Nothing)]) .required("name", SyntaxShape::String, "task name") + .required("params", SyntaxShape::Signature, "parameters") .switch( "concurrent", "allow this task to be run concurrently with others", Some('c'), ) - .switch( - "pure", - "make this a purely declarative task, with only a single declaration body and no run body", - None, + .optional( + "decl_body", + SyntaxShape::Keyword(b"where".to_vec(), Box::new(SyntaxShape::Closure(None))), + "body to be evaluated when this task is triggered", ) - .required("params", SyntaxShape::Signature, "parameters") - .required("first_body", SyntaxShape::Closure(None), "first body") - .required("second_body", SyntaxShape::Closure(None), "second body") + .optional( + "run_body", + SyntaxShape::Keyword(b"do".to_vec(), Box::new(SyntaxShape::Closure(None))), + "body to be evaluated when this task is triggered", + ) + .required("", SyntaxShape::Closure(None), "") // TODO hide from .creates_scope() - .category(Category::Custom(QUAKE_CATEGORY.to_owned())) } fn run( @@ -55,6 +64,24 @@ impl Command for DefTask { // (parser internal) Ok(PipelineData::Empty) } + + fn run_const( + &self, + _working_set: &StateWorkingSet, + _call: &Call, + _input: PipelineData, + ) -> Result { + // (parser internal) + Ok(PipelineData::Empty) + } + + fn is_const(&self) -> bool { + true + } + + fn command_type(&self) -> CommandType { + CommandType::Other + } } #[derive(Clone)] diff --git a/crates/quake_engine/src/nu/eval.rs b/crates/quake_engine/src/nu/eval.rs index e2e432094..ca387ff65 100644 --- a/crates/quake_engine/src/nu/eval.rs +++ b/crates/quake_engine/src/nu/eval.rs @@ -1,7 +1,11 @@ +use std::io::stdout; +use std::sync::atomic::AtomicU32; +use std::sync::Arc; + use nu_protocol::ast::{Argument, Block}; use nu_protocol::debugger::WithoutDebug; -use nu_protocol::engine::{EngineState, Stack}; -use nu_protocol::{PipelineData, Span, Value, VarId}; +use nu_protocol::engine::{EngineState, Redirection, Stack}; +use nu_protocol::{IoStream, PipelineData, RawStream, Span, Value, VarId}; use quake_core::metadata::TaskCallId; use quake_core::prelude::*; @@ -18,12 +22,8 @@ pub fn eval_block( return Ok(true); } - let result = nu_engine::eval_block_with_early_return::( - engine_state, - stack, - block, - PipelineData::Empty, - ); + let result = + nu_engine::eval_block::(engine_state, stack, block, PipelineData::Empty); // reset vt processing, aka ansi because illbehaved externals can break it #[cfg(windows)] diff --git a/crates/quake_engine/src/nu/mod.rs b/crates/quake_engine/src/nu/mod.rs index f0ff6db21..2c2a1408a 100644 --- a/crates/quake_engine/src/nu/mod.rs +++ b/crates/quake_engine/src/nu/mod.rs @@ -68,7 +68,7 @@ pub fn create_engine_state(state: Arc>) -> EngineState { macro_rules! bind_command { ($($command:expr),* $(,)?) => { - $(working_set.add_decl(Box::new($command));)* + $(working_set.add_decl(Box::new($command)));* }; } diff --git a/crates/quake_engine/src/nu/parse.rs b/crates/quake_engine/src/nu/parse.rs index 50241e6a9..53f348b3d 100644 --- a/crates/quake_engine/src/nu/parse.rs +++ b/crates/quake_engine/src/nu/parse.rs @@ -1,13 +1,14 @@ #![deny(clippy::wildcard_enum_match_arm)] +use std::collections::HashSet; use std::sync::Arc; -use nu_parser::{discover_captures_in_expr, parse_internal_call}; +use nu_parser::{discover_captures_in_expr, parse_internal_call, parse_value}; use nu_protocol::ast::{ Argument, Block, Call, Expr, Expression, ExternalArgument, MatchPattern, Pattern, RecordItem, }; -use nu_protocol::engine::StateWorkingSet; -use nu_protocol::{span, Category, DeclId, Spanned, Type}; +use nu_protocol::engine::{Closure, StateWorkingSet}; +use nu_protocol::{span, Category, DeclId, Span, Spanned, SyntaxShape, Type}; use quake_core::metadata::{Task, TaskFlags}; use quake_core::prelude::*; @@ -32,6 +33,10 @@ fn parse_def_task( working_set: &mut StateWorkingSet<'_>, state: &mut State, ) -> DiagResult<()> { + if call.has_flag_const(working_set, "help")? { + return Ok(()); + } + // extract name--must be const eval let name: Spanned = call.req_const(working_set, 0)?; @@ -39,7 +44,6 @@ fn parse_def_task( let flags = TaskFlags { concurrent: call.has_flag_const(working_set, "concurrent")?, }; - let is_pure = call.has_flag_const(working_set, "pure")?; // extract and update signature in place let Some(Expression { @@ -54,56 +58,46 @@ fn parse_def_task( let signature = signature.clone(); - let mut closures = call.arguments.iter().filter_map(|a| { - if let Some(Expression { - expr: Expr::Closure(block_id), - .. - }) = a.expression() - { - Some(*block_id) - } else { - None - } - }); + // extract closures by keyword + let (mut decl_body, mut run_body) = (None, None); + for expr in call.arguments.iter_mut().flat_map(|a| a.expression_mut()) { + match_expr!(Expr::Keyword(kw_name, _, kw_expr), expr, else { continue; }); + let block_id = match &kw_expr.expr { + Expr::Closure(block_id) => *block_id, + Expr::Garbage => { + *kw_expr = Box::new(parse_value( + working_set, + kw_expr.span, + &SyntaxShape::Closure(None), + )); + match_expr!(Expr::Closure(block_id), **kw_expr, else { + // indiciative of extra positional + break; + }); + block_id + } + _ => unreachable!(), + }; + + match kw_name.as_slice() { + b"where" => decl_body = Some(block_id), + b"do" => run_body = Some(block_id), + _ => unreachable!(), + }; + } - // extract block IDs - let (Some(first_block), second_block) = (closures.next(), closures.next()) else { + if decl_body.is_none() && run_body.is_none() { + state.error(errors::TaskMissingBlocks { span: call.span() }); return Ok(()); - }; - - // update signature for the first block - working_set - .get_block_mut(first_block) - .signature - .clone_from(&signature); - - // determine which blocks correspond to which bodies - let (run_body, decl_body) = match second_block { - Some(second_block) => { - if is_pure { - // too many blocks: add error and continue - state.error(errors::PureTaskHasExtraBody { - span: working_set.get_block(second_block).span.unwrap(), - }); + } - (Some(first_block), None) - } else { - // update the signature for the second block - working_set - .get_block_mut(second_block) - .signature - .clone_from(&signature); - (Some(second_block), Some(first_block)) - } - } - None => { - if is_pure { - (None, Some(first_block)) - } else { - (Some(first_block), None) - } - } - }; + // update signatures + for block_id in [decl_body, run_body].iter_mut().filter_map(Option::as_mut) { + working_set + .get_block_mut(*block_id) + .signature + .clone_from(&signature); + } // insert placeholder to be updated later with a `DependsTask` if successful let depends_decl_name = format!("depends {name}", name = &name.item); @@ -136,13 +130,22 @@ fn parse_def_task( } // remove errors indicating a missing argument when only one block is provided - if run_body.is_some() != decl_body.is_some() { - let call_span = call.span(); - working_set.parse_errors.retain(|e| { - !matches!(e, ParseError::MissingPositional(name, span, _) - if name == "second_body" && call_span.contains_span(*span)) - }); - } + // if run_body.is_some() != decl_body.is_some() { + let call_span = call.span(); + working_set.parse_errors.retain(|e| match e { + ParseError::ExpectedKeyword(kw, span) | ParseError::KeywordMissingArgument(_, kw, span) + if call_span.contains_span(*span) && (kw == "where" || kw == "do") => + { + false + } + ParseError::Expected(_, span) if call_span.contains_span(*span) => false, + ParseError::MissingPositional(arg, _, _) + if arg == "decl_body" || arg.starts_with("run_body") => + { + false + } + _ => true, + }); // note: errors when task has already been defined let name_span = name.span; diff --git a/crates/quake_errors/src/errors.rs b/crates/quake_errors/src/errors.rs index db4907f4d..4bda7ff22 100644 --- a/crates/quake_errors/src/errors.rs +++ b/crates/quake_errors/src/errors.rs @@ -62,13 +62,13 @@ make_errors! { pub span: Span, } - #[error("Pure task has extra body")] + #[error("Task is missing blocks")] #[diagnostic( - code(quake::decl_task_has_extra_body), - help("Remove the `--pure` flag or remove the extra block") + code(quake::task_missing_blocks), + help("Tasks must declare a decl body (a block prefixed with \"where\") and/or a run body (a block prefixed with \"run\")") )] - pub struct PureTaskHasExtraBody { - #[label("extra block")] + pub struct TaskMissingBlocks { + #[label("command used here")] pub span: Span, }