diff --git a/TODO.md b/TODO.md index 92049f2ac..4ea8afd78 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,8 @@ - [x] Iteration (`each`) over tables - [x] Row conditions - [x] Simple completions +- [ ] Detecting `$it` currently only looks at top scope but should find any free `$it` in the expression (including subexprs) +- [ ] Signature needs to make parameters visible in scope before block is parsed - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) diff --git a/crates/nu-command/src/benchmark.rs b/crates/nu-command/src/benchmark.rs index 5bd5a6afc..1590e8af8 100644 --- a/crates/nu-command/src/benchmark.rs +++ b/crates/nu-command/src/benchmark.rs @@ -17,7 +17,11 @@ impl Command for Benchmark { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run") + Signature::build("benchmark").required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) } fn run( diff --git a/crates/nu-command/src/def.rs b/crates/nu-command/src/def.rs index 25004d800..0f6ef1b56 100644 --- a/crates/nu-command/src/def.rs +++ b/crates/nu-command/src/def.rs @@ -17,7 +17,11 @@ impl Command for Def { Signature::build("def") .required("def_name", SyntaxShape::String, "definition name") .required("params", SyntaxShape::Signature, "parameters") - .required("block", SyntaxShape::Block, "body of the definition") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the definition", + ) } fn run( diff --git a/crates/nu-command/src/do_.rs b/crates/nu-command/src/do_.rs index 3630e84ff..20bd0eb7d 100644 --- a/crates/nu-command/src/do_.rs +++ b/crates/nu-command/src/do_.rs @@ -15,7 +15,11 @@ impl Command for Do { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("do").required("block", SyntaxShape::Block, "the block to run") + Signature::build("do").required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) } fn run( diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index cd2894e9b..e48cfb719 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -15,7 +15,13 @@ impl Command for Each { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("each").required("block", SyntaxShape::Block, "the block to run") + Signature::build("each") + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run", + ) + .switch("numbered", "iterate with an index", Some('n')) } fn run( @@ -27,20 +33,42 @@ impl Command for Each { let block_id = call.positional[0] .as_block() .expect("internal error: expected block"); + + let numbered = call.has_flag("numbered"); let context = context.clone(); + let span = call.head; match input { Value::Range { val, .. } => Ok(Value::Stream { stream: val .into_iter() - .map(move |x| { + .enumerate() + .map(move |(idx, x)| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block_id); let state = context.enter_scope(); + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } } } @@ -55,14 +83,32 @@ impl Command for Each { Value::List { vals: val, .. } => Ok(Value::Stream { stream: val .into_iter() - .map(move |x| { + .enumerate() + .map(move |(idx, x)| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block_id); let state = context.enter_scope(); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } } } @@ -76,14 +122,32 @@ impl Command for Each { }), Value::Stream { stream, .. } => Ok(Value::Stream { stream: stream - .map(move |x| { + .enumerate() + .map(move |(idx, x)| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block_id); let state = context.enter_scope(); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } } } diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 35d806dd5..158cd6b6c 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -29,7 +29,11 @@ impl Command for For { ), "range of the loop", ) - .required("block", SyntaxShape::Block, "the block to run") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) } fn run( diff --git a/crates/nu-command/src/if_.rs b/crates/nu-command/src/if_.rs index 2b8df4867..a9b1489a6 100644 --- a/crates/nu-command/src/if_.rs +++ b/crates/nu-command/src/if_.rs @@ -17,7 +17,7 @@ impl Command for If { fn signature(&self) -> nu_protocol::Signature { Signature::build("if") .required("cond", SyntaxShape::Expression, "condition") - .required("then_block", SyntaxShape::Block, "then block") + .required("then_block", SyntaxShape::Block(Some(vec![])), "then block") .optional( "else", SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 8c0da6220..1c44c6bba 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1075,17 +1075,7 @@ pub fn parse_variable_expr( None, ) } else { - let name = working_set.get_span_contents(span).to_vec(); - // this seems okay to set it to unknown here, but we should double-check - let id = working_set.add_variable(name, Type::Unknown); - ( - Expression { - expr: Expr::Var(id), - span, - ty: Type::Unknown, - }, - None, - ) + (garbage(span), Some(ParseError::VariableNotFound(span))) } } else { (garbage(span), err) @@ -1285,7 +1275,7 @@ pub fn parse_shape_name( b"int" => SyntaxShape::Int, b"path" => SyntaxShape::FilePath, b"glob" => SyntaxShape::GlobPattern, - b"block" => SyntaxShape::Block, + b"block" => SyntaxShape::Block(None), //FIXME b"cond" => SyntaxShape::RowCondition, b"operator" => SyntaxShape::Operator, b"math" => SyntaxShape::MathExpression, @@ -1947,6 +1937,7 @@ pub fn parse_table_expression( pub fn parse_block_expression( working_set: &mut StateWorkingSet, + shape: &SyntaxShape, span: Span, ) -> (Expression, Option) { let bytes = working_set.get_span_contents(span); @@ -1987,7 +1978,7 @@ pub fn parse_block_expression( working_set.enter_scope(); // Check to see if we have parameters - let (signature, amt_to_skip): (Option>, usize) = match output.first() { + let (mut signature, amt_to_skip): (Option>, usize) = match output.first() { Some(Token { contents: TokenContents::Pipe, span, @@ -2033,12 +2024,31 @@ pub fn parse_block_expression( let (output, err) = lite_parse(&output[amt_to_skip..]); error = error.or(err); + if let SyntaxShape::Block(Some(v)) = shape { + if signature.is_none() && v.len() == 1 { + // We'll assume there's an `$it` present + let var_id = working_set.add_variable(b"$it".to_vec(), Type::Unknown); + + let mut new_sigature = Signature::new(""); + new_sigature.required_positional.push(PositionalArg { + var_id: Some(var_id), + name: "$it".into(), + desc: String::new(), + shape: SyntaxShape::Any, + }); + + signature = Some(Box::new(new_sigature)); + } + } + let (mut output, err) = parse_block(working_set, &output, false); error = error.or(err); if let Some(signature) = signature { output.signature = signature; } else if let Some(last) = working_set.delta.scope.last() { + // FIXME: this only supports the top $it. Instead, we should look for a free $it in the expression. + if let Some(var_id) = last.get_var(b"$it") { let mut signature = Signature::new(""); signature.required_positional.push(PositionalArg { @@ -2089,8 +2099,8 @@ pub fn parse_value( return parse_full_column_path(working_set, None, span); } } else if bytes.starts_with(b"{") { - if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) { - return parse_block_expression(working_set, span); + if matches!(shape, SyntaxShape::Block(_)) || matches!(shape, SyntaxShape::Any) { + return parse_block_expression(working_set, shape, span); } else { return ( Expression::garbage(span), @@ -2119,9 +2129,9 @@ pub fn parse_value( SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { parse_string(working_set, span) } - SyntaxShape::Block => { + SyntaxShape::Block(_) => { if bytes.starts_with(b"{") { - parse_block_expression(working_set, span) + parse_block_expression(working_set, shape, span) } else { ( Expression::garbage(span), @@ -2169,7 +2179,7 @@ pub fn parse_value( SyntaxShape::Range, SyntaxShape::Filesize, SyntaxShape::Duration, - SyntaxShape::Block, + SyntaxShape::Block(None), SyntaxShape::String, ]; for shape in shapes.iter() { @@ -2421,7 +2431,8 @@ pub fn parse_def( let (sig, err) = parse_signature(working_set, spans[2]); error = error.or(err); - let (block, err) = parse_block_expression(working_set, spans[3]); + let (block, err) = + parse_block_expression(working_set, &SyntaxShape::Block(Some(vec![])), spans[3]); error = error.or(err); working_set.exit_scope(); diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 67a082c67..58167b47f 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -20,6 +20,7 @@ pub fn math_result_type( op: &mut Expression, rhs: &mut Expression, ) -> (Type, Option) { + //println!("checking: {:?} {:?} {:?}", lhs, op, rhs); match &op.expr { Expr::Operator(operator) => match operator { Operator::Plus => match (&lhs.ty, &rhs.ty) { diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index cdc6f5a8d..26393f37e 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -2,10 +2,43 @@ use nu_parser::ParseError; use nu_parser::*; use nu_protocol::{ ast::{Expr, Expression, Pipeline, Statement}, - engine::{EngineState, StateWorkingSet}, + engine::{Command, EngineState, StateWorkingSet}, Signature, SyntaxShape, }; +#[cfg(test)] +pub struct Let; + +#[cfg(test)] +impl Command for Let { + fn name(&self) -> &str { + "let" + } + + fn usage(&self) -> &str { + "Create a variable and give it a value." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("let") + .required("var_name", SyntaxShape::VarWithOptType, "variable name") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", + ) + } + + fn run( + &self, + _context: &nu_protocol::engine::EvaluationContext, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::Value, + ) -> Result { + todo!() + } +} + #[test] pub fn parse_int() { let engine_state = EngineState::new(); @@ -280,6 +313,8 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(Let)); + let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..10", true); assert!(err.is_none()); @@ -312,6 +347,8 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(Let)); + let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..<($a + 10)", true); assert!(err.is_none()); diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index efe5d7b48..7ee6d6c16 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -25,4 +25,14 @@ impl Call { named: vec![], } } + + pub fn has_flag(&self, flag_name: &str) -> bool { + for name in &self.named { + if flag_name == name.0 { + return true; + } + } + + false + } } diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 3e3337cd0..b5b81fe9a 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -34,7 +34,7 @@ pub enum SyntaxShape { GlobPattern, /// A block is allowed, eg `{start this thing}` - Block, + Block(Option>), /// A table is allowed, eg `[[first, second]; [1, 2]]` Table, @@ -75,7 +75,7 @@ impl SyntaxShape { pub fn to_type(&self) -> Type { match self { SyntaxShape::Any => Type::Unknown, - SyntaxShape::Block => Type::Block, + SyntaxShape::Block(_) => Type::Block, SyntaxShape::CellPath => Type::Unknown, SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, diff --git a/src/tests.rs b/src/tests.rs index df619f2b9..8684e817b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -308,3 +308,11 @@ fn row_condition2() -> TestResult { "1", ) } + +#[test] +fn better_block_types() -> TestResult { + run_test( + r#"([1, 2, 3] | each -n { $"($it.index) is ($it.item)" }).1"#, + "1 is 2", + ) +}