diff --git a/Cargo.lock b/Cargo.lock index 49b8c7eefe..dc658ed906 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1192,7 +1192,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#550ce9b486b4f1e979ddc37a50d95f27afe37ff3" +source = "git+https://github.com/nushell/reedline?branch=main#1b244992981e392152b86ceed5c583c31150976c" dependencies = [ "chrono", "crossterm 0.22.1", diff --git a/crates/nu-command/src/core_commands/do_.rs b/crates/nu-command/src/core_commands/do_.rs index 482a25f841..92ac299f71 100644 --- a/crates/nu-command/src/core_commands/do_.rs +++ b/crates/nu-command/src/core_commands/do_.rs @@ -33,9 +33,9 @@ impl Command for Do { call: &Call, input: PipelineData, ) -> Result { - let block_id = call.positional[0] - .as_block() - .expect("internal error: expected block"); + let block: Value = call.req(engine_state, stack, 0)?; + let block_id = block.as_block()?; + let rest: Vec = call.rest(engine_state, stack, 1)?; let block = engine_state.get_block(block_id); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 3c30b20e71..c411a2d4fe 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -297,6 +297,20 @@ pub fn eval_expression( span: expr.span, }) } + Expr::Record(fields) => { + let mut cols = vec![]; + let mut vals = vec![]; + for (col, val) in fields { + cols.push(eval_expression(engine_state, stack, col)?.as_string()?); + vals.push(eval_expression(engine_state, stack, val)?); + } + + Ok(Value::Record { + cols, + vals, + span: expr.span, + }) + } Expr::Table(headers, vals) => { let mut output_headers = vec![]; for expr in headers { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index c244f4242a..59278857e5 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -164,6 +164,14 @@ pub fn flatten_expression( } output } + Expr::Record(list) => { + let mut output = vec![]; + for l in list { + output.extend(flatten_expression(working_set, &l.0)); + output.extend(flatten_expression(working_set, &l.1)); + } + output + } Expr::Keyword(_, span, expr) => { let mut output = vec![(*span, FlatShape::Operator)]; output.extend(flatten_expression(working_set, expr)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 628178a5d9..57847afd93 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1347,6 +1347,13 @@ pub fn parse_full_cell_path( tokens.next(); + (output, true) + } else if bytes.starts_with(b"{") { + let (output, err) = parse_record(working_set, head.span); + error = error.or(err); + + tokens.next(); + (output, true) } else if bytes.starts_with(b"$") { let (out, err) = parse_variable_expr(working_set, head.span); @@ -2669,6 +2676,11 @@ pub fn parse_value( return parse_full_cell_path(working_set, None, span); } } else if bytes.starts_with(b"{") { + if !matches!(shape, SyntaxShape::Block(..)) { + if let (expr, None) = parse_full_cell_path(working_set, None, span) { + return (expr, None); + } + } if matches!(shape, SyntaxShape::Block(_)) || matches!(shape, SyntaxShape::Any) { return parse_block_expression(working_set, shape, span); } else { @@ -3152,6 +3164,83 @@ pub fn parse_statement( } } +pub fn parse_record( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + + let mut error = None; + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"{") { + start += 1; + } else { + error = error.or_else(|| { + Some(ParseError::Expected( + "{".into(), + Span { + start, + end: start + 1, + }, + )) + }); + } + + if bytes.ends_with(b"}") { + end -= 1; + } else { + error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span { start: end, end }))); + } + + let span = Span { start, end }; + let source = working_set.get_span_contents(span); + + let (tokens, err) = lex(source, start, &[b'\n', b','], &[b':']); + error = error.or(err); + + let mut output = vec![]; + let mut idx = 0; + + while idx < tokens.len() { + let (field, err) = parse_value(working_set, tokens[idx].span, &SyntaxShape::Any); + error = error.or(err); + + idx += 1; + if idx == tokens.len() { + return ( + garbage(span), + Some(ParseError::Expected("record".into(), span)), + ); + } + let colon = working_set.get_span_contents(tokens[idx].span); + idx += 1; + if idx == tokens.len() || colon != b":" { + //FIXME: need better error + return ( + garbage(span), + Some(ParseError::Expected("record".into(), span)), + ); + } + let (value, err) = parse_value(working_set, tokens[idx].span, &SyntaxShape::Any); + error = error.or(err); + idx += 1; + + output.push((field, value)); + } + + ( + Expression { + expr: Expr::Record(output), + span, + ty: Type::Unknown, //FIXME: but we don't know the contents of the fields, do we? + custom_completion: None, + }, + error, + ) +} + pub fn parse_block( working_set: &mut StateWorkingSet, lite_block: &LiteBlock, @@ -3350,6 +3439,12 @@ pub fn find_captures_in_expr( output.extend(&result); } } + Expr::Record(fields) => { + for (field_name, field_value) in fields { + output.extend(&find_captures_in_expr(working_set, field_name, seen)); + output.extend(&find_captures_in_expr(working_set, field_value, seen)); + } + } Expr::RowCondition(var_id, expr) => { seen.push(*var_id); diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index f01c815fde..59a6fcd299 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -23,6 +23,7 @@ pub enum Expr { Block(BlockId), List(Vec), Table(Vec, Vec>), + Record(Vec<(Expression, Expression)>), Keyword(Vec, Span, Box), ValueWithUnit(Box, Spanned), Filepath(String), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 6b2d0084e3..94b580b31a 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -170,6 +170,17 @@ impl Expression { } false } + Expr::Record(fields) => { + for (field_name, field_value) in fields { + if field_name.has_in_variable(working_set) { + return true; + } + if field_value.has_in_variable(working_set) { + return true; + } + } + false + } Expr::RowCondition(_, expr) => expr.has_in_variable(working_set), Expr::Signature(_) => false, Expr::String(_) => false, @@ -292,6 +303,12 @@ impl Expression { right.replace_in_variable(working_set, new_var_id) } } + Expr::Record(fields) => { + for (field_name, field_value) in fields { + field_name.replace_in_variable(working_set, new_var_id); + field_value.replace_in_variable(working_set, new_var_id); + } + } Expr::RowCondition(_, expr) => expr.replace_in_variable(working_set, new_var_id), Expr::Signature(_) => {} Expr::String(_) => {} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 15765da81f..5cfc49282a 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -95,6 +95,17 @@ impl Value { } } + pub fn as_block(&self) -> Result { + match self { + Value::Block { val, .. } => Ok(*val), + x => Err(ShellError::CantConvert( + "block".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + /// Get the span for the current value pub fn span(&self) -> Result { match self { diff --git a/src/tests.rs b/src/tests.rs index 2895a78347..0bbcaa5f50 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -889,3 +889,13 @@ fn in_variable_5() -> TestResult { fn in_variable_6() -> TestResult { run_test(r#"3 | if $in > 6 { $in - 10 } else { $in * 10 }"#, "30") } + +#[test] +fn record_1() -> TestResult { + run_test(r#"{'a': 'b'} | get a"#, "b") +} + +#[test] +fn record_2() -> TestResult { + run_test(r#"{'b': 'c'}.b"#, "c") +}