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

Commit

Permalink
Introduce where/do syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
cassaundra committed May 1, 2024
1 parent 96133cf commit 2c85bd2
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 84 deletions.
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

0 comments on commit 2c85bd2

Please sign in to comment.