Add record literal syntax (#326)

This commit is contained in:
JT 2021-11-11 12:14:00 +13:00 committed by GitHub
parent 586c6d9fa8
commit 568e566adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 160 additions and 4 deletions

2
Cargo.lock generated
View File

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

View File

@ -33,9 +33,9 @@ impl Command for Do {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
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<Value> = call.rest(engine_state, stack, 1)?;
let block = engine_state.get_block(block_id);

View File

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

View File

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

View File

@ -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<ParseError>) {
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);

View File

@ -23,6 +23,7 @@ pub enum Expr {
Block(BlockId),
List(Vec<Expression>),
Table(Vec<Expression>, Vec<Vec<Expression>>),
Record(Vec<(Expression, Expression)>),
Keyword(Vec<u8>, Span, Box<Expression>),
ValueWithUnit(Box<Expression>, Spanned<Unit>),
Filepath(String),

View File

@ -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(_) => {}

View File

@ -95,6 +95,17 @@ impl Value {
}
}
pub fn as_block(&self) -> Result<BlockId, ShellError> {
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<Span, ShellError> {
match self {

View File

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