mirror of
https://github.com/nushell/nushell
synced 2024-11-10 15:14:14 +00:00
Add record literal syntax (#326)
This commit is contained in:
parent
586c6d9fa8
commit
568e566adf
9 changed files with 160 additions and 4 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(_) => {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
10
src/tests.rs
10
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")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue