Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Introduce where/do syntax #57

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions crates/quake_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
47 changes: 37 additions & 10 deletions crates/quake_engine/src/nu/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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(
Expand All @@ -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<PipelineData, ShellError> {
// (parser internal)
Ok(PipelineData::Empty)
}

fn is_const(&self) -> bool {
true
}

fn command_type(&self) -> CommandType {
CommandType::Other
}
}

#[derive(Clone)]
Expand Down
16 changes: 8 additions & 8 deletions crates/quake_engine/src/nu/eval.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -18,12 +22,8 @@ pub fn eval_block(
return Ok(true);
}

let result = nu_engine::eval_block_with_early_return::<WithoutDebug>(
engine_state,
stack,
block,
PipelineData::Empty,
);
let result =
nu_engine::eval_block::<WithoutDebug>(engine_state, stack, block, PipelineData::Empty);

// reset vt processing, aka ansi because illbehaved externals can break it
#[cfg(windows)]
Expand Down
2 changes: 1 addition & 1 deletion crates/quake_engine/src/nu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn create_engine_state(state: Arc<RwLock<State>>) -> EngineState {

macro_rules! bind_command {
($($command:expr),* $(,)?) => {
$(working_set.add_decl(Box::new($command));)*
$(working_set.add_decl(Box::new($command)));*
};
}

Expand Down
119 changes: 61 additions & 58 deletions crates/quake_engine/src/nu/parse.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -32,14 +33,17 @@ 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<String> = call.req_const(working_set, 0)?;

// try to extract flags--must be const eval
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 {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions crates/quake_errors/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down
Loading