Merge pull request #46 from nushell/block_param_types

Block param types
This commit is contained in:
JT 2021-09-13 20:22:44 +12:00 committed by GitHub
commit c0bad7ab23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 183 additions and 34 deletions

View File

@ -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)

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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);
}
}
}

View File

@ -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(

View File

@ -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)),

View File

@ -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<ParseError>) {
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<Box<Signature>>, usize) = match output.first() {
let (mut signature, amt_to_skip): (Option<Box<Signature>>, 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();

View File

@ -20,6 +20,7 @@ pub fn math_result_type(
op: &mut Expression,
rhs: &mut Expression,
) -> (Type, Option<ParseError>) {
//println!("checking: {:?} {:?} {:?}", lhs, op, rhs);
match &op.expr {
Expr::Operator(operator) => match operator {
Operator::Plus => match (&lhs.ty, &rhs.ty) {

View File

@ -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<nu_protocol::Value, nu_protocol::ShellError> {
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());

View File

@ -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
}
}

View File

@ -34,7 +34,7 @@ pub enum SyntaxShape {
GlobPattern,
/// A block is allowed, eg `{start this thing}`
Block,
Block(Option<Vec<SyntaxShape>>),
/// 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,

View File

@ -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",
)
}