Add table literals (#2453)

* Add table literals

* clippy
This commit is contained in:
Jonathan Turner 2020-08-30 16:55:33 +12:00 committed by GitHub
parent 84a6010f71
commit 6f69ae8707
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 251 additions and 31 deletions

View File

@ -27,6 +27,15 @@ impl<'s> Flatten<'s> {
Expression::Block(block) => self.completion_locations(block),
Expression::Invocation(block) => self.completion_locations(block),
Expression::List(exprs) => exprs.iter().flat_map(|v| self.expression(v)).collect(),
Expression::Table(headers, cells) => headers
.iter()
.flat_map(|v| self.expression(v))
.chain(
cells
.iter()
.flat_map(|v| v.iter().flat_map(|v| self.expression(v))),
)
.collect(),
Expression::Command => vec![LocationType::Command.spanned(e.span)],
Expression::Path(path) => self.expression(&path.head),
Expression::Variable(_) => vec![LocationType::Variable.spanned(e.span)],

View File

@ -80,6 +80,46 @@ pub(crate) async fn evaluate_baseline_expr(
Ok(UntaggedValue::range(left, right).into_value(tag))
}
Expression::Table(headers, cells) => {
let mut output_headers = vec![];
for expr in headers {
let val = evaluate_baseline_expr(&expr, registry, it, vars, env).await?;
let header = val.as_string()?;
output_headers.push(header);
}
let mut output_table = vec![];
for row in cells {
if row.len() != headers.len() {
match (row.first(), row.last()) {
(Some(first), Some(last)) => {
return Err(ShellError::labeled_error(
"Cell count doesn't match header count",
format!("expected {} columns", headers.len()),
Span::new(first.span.start(), last.span.end()),
));
}
_ => {
return Err(ShellError::untagged_runtime_error(
"Cell count doesn't match header count",
));
}
}
}
let mut row_output = IndexMap::new();
for cell in output_headers.iter().zip(row.iter()) {
let val = evaluate_baseline_expr(&cell.1, registry, it, vars, env).await?;
row_output.insert(cell.0.clone(), val);
}
output_table.push(UntaggedValue::row(row_output).into_value(tag.clone()));
}
Ok(UntaggedValue::Table(output_table).into_value(tag))
}
Expression::List(list) => {
let mut exprs = vec![];

View File

@ -537,6 +537,129 @@ fn parse_external_arg(
}
}
fn parse_list(
lite_block: &LiteBlock,
registry: &dyn SignatureRegistry,
) -> (Vec<SpannedExpression>, Option<ParseError>) {
let mut error = None;
if lite_block.block.is_empty() {
return (vec![], None);
}
let lite_pipeline = &lite_block.block[0];
let mut output = vec![];
for lite_inner in &lite_pipeline.commands {
let (arg, err) = parse_arg(SyntaxShape::Any, registry, &lite_inner.name);
output.push(arg);
if error.is_none() {
error = err;
}
for arg in &lite_inner.args {
let (arg, err) = parse_arg(SyntaxShape::Any, registry, &arg);
output.push(arg);
if error.is_none() {
error = err;
}
}
}
(output, error)
}
fn verify_and_strip(
contents: &Spanned<String>,
left: char,
right: char,
) -> (String, Option<ParseError>) {
let mut chars = contents.item.chars();
match (chars.next(), chars.next_back()) {
(Some(l), Some(r)) if l == left && r == right => {
let output: String = chars.collect();
(output, None)
}
_ => (
String::new(),
Some(ParseError::mismatch(
format!("value in {} {}", left, right),
contents.clone(),
)),
),
}
}
fn parse_table(
lite_block: &LiteBlock,
registry: &dyn SignatureRegistry,
span: Span,
) -> (SpannedExpression, Option<ParseError>) {
let mut error = None;
let mut output = vec![];
// Header
let lite_pipeline = &lite_block.block[0];
let lite_inner = &lite_pipeline.commands[0];
let (string, err) = verify_and_strip(&lite_inner.name, '[', ']');
if error.is_none() {
error = err;
}
let lite_header = match lite_parse(&string, lite_inner.name.span.start() + 1) {
Ok(lb) => lb,
Err(e) => return (garbage(lite_inner.name.span), Some(e.cause)),
};
let (headers, err) = parse_list(&lite_header, registry);
if error.is_none() {
error = err;
}
// Cells
let lite_rows = &lite_block.block[1];
let lite_cells = &lite_rows.commands[0];
let (string, err) = verify_and_strip(&lite_cells.name, '[', ']');
if error.is_none() {
error = err;
}
let lite_cell = match lite_parse(&string, lite_cells.name.span.start() + 1) {
Ok(lb) => lb,
Err(e) => return (garbage(lite_cells.name.span), Some(e.cause)),
};
let (inner_cell, err) = parse_list(&lite_cell, registry);
if error.is_none() {
error = err;
}
output.push(inner_cell);
for arg in &lite_cells.args {
let (string, err) = verify_and_strip(&arg, '[', ']');
if error.is_none() {
error = err;
}
let lite_cell = match lite_parse(&string, arg.span.start() + 1) {
Ok(lb) => lb,
Err(e) => return (garbage(arg.span), Some(e.cause)),
};
let (inner_cell, err) = parse_list(&lite_cell, registry);
if error.is_none() {
error = err;
}
output.push(inner_cell);
}
(
SpannedExpression::new(Expression::Table(headers, output), span),
error,
)
}
/// Parses the given argument using the shape as a guide for how to correctly parse the argument
fn parse_arg(
expected_type: SyntaxShape,
@ -644,7 +767,6 @@ fn parse_arg(
(Some('['), Some(']')) => {
// We have a literal row
let string: String = chars.collect();
let mut error = None;
// We haven't done much with the inner string, so let's go ahead and work with it
let lite_block = match lite_parse(&string, lite_arg.span.start() + 1) {
@ -655,40 +777,26 @@ fn parse_arg(
if lite_block.block.is_empty() {
return (
SpannedExpression::new(Expression::List(vec![]), lite_arg.span),
error,
None,
);
}
if lite_block.block.len() > 1 {
return (
if lite_block.block.len() == 1 {
let (items, err) = parse_list(&lite_block, registry);
(
SpannedExpression::new(Expression::List(items), lite_arg.span),
err,
)
} else if lite_block.block.len() == 2 {
parse_table(&lite_block, registry, lite_arg.span)
} else {
(
garbage(lite_arg.span),
Some(ParseError::mismatch("table", lite_arg.clone())),
);
Some(ParseError::mismatch(
"list or table",
"unknown".to_string().spanned(lite_arg.span),
)),
)
}
let lite_pipeline = lite_block.block[0].clone();
let mut output = vec![];
for lite_inner in &lite_pipeline.commands {
let (arg, err) = parse_arg(SyntaxShape::Any, registry, &lite_inner.name);
output.push(arg);
if error.is_none() {
error = err;
}
for arg in &lite_inner.args {
let (arg, err) = parse_arg(SyntaxShape::Any, registry, &arg);
output.push(arg);
if error.is_none() {
error = err;
}
}
}
(
SpannedExpression::new(Expression::List(output), lite_arg.span),
error,
)
}
_ => (
garbage(lite_arg.span),

View File

@ -16,6 +16,18 @@ pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec<Spanned<FlatShape>
}
output
}
Expression::Table(headers, cells) => {
let mut output = vec![];
for header in headers.iter() {
output.append(&mut expression_to_flat_shape(header));
}
for row in cells {
for cell in row {
output.append(&mut expression_to_flat_shape(&cell));
}
}
output
}
Expression::Path(exprs) => {
let mut output = vec![];
output.append(&mut expression_to_flat_shape(&exprs.head));

View File

@ -716,6 +716,20 @@ impl PrettyDebugWithSource for SpannedExpression {
),
"]",
),
Expression::Table(_headers, cells) => b::delimit(
"[",
b::intersperse(
cells
.iter()
.map(|row| {
row.iter()
.map(|item| item.refined_pretty_debug(refine, source))
})
.flatten(),
b::space(),
),
"]",
),
Expression::Path(path) => path.pretty_debug(source),
Expression::FilePath(path) => b::typed("path", b::primitive(path.display())),
Expression::ExternalCommand(external) => {
@ -756,6 +770,17 @@ impl PrettyDebugWithSource for SpannedExpression {
),
"]",
),
Expression::Table(_headers, cells) => b::delimit(
"[",
b::intersperse(
cells
.iter()
.map(|row| row.iter().map(|item| item.pretty_debug(source)))
.flatten(),
b::space(),
),
"]",
),
Expression::Path(path) => path.pretty_debug(source),
Expression::FilePath(path) => b::typed("path", b::primitive(path.display())),
Expression::ExternalCommand(external) => b::typed(
@ -960,6 +985,7 @@ pub enum Expression {
Range(Box<Range>),
Block(hir::Block),
List(Vec<SpannedExpression>),
Table(Vec<SpannedExpression>, Vec<Vec<SpannedExpression>>),
Path(Box<Path>),
FilePath(PathBuf),
@ -985,6 +1011,7 @@ impl ShellTypeName for Expression {
Expression::FilePath(..) => "file path",
Expression::Variable(..) => "variable",
Expression::List(..) => "list",
Expression::Table(..) => "table",
Expression::Binary(..) => "binary",
Expression::Range(..) => "range",
Expression::Block(..) => "block",

View File

@ -418,6 +418,30 @@ fn echoing_ranges() {
assert_eq!(actual.out, "6");
}
#[test]
fn table_literals1() {
let actual = nu!(
cwd: ".",
r#"
echo [[name age]; [foo 13]] | get age
"#
);
assert_eq!(actual.out, "13");
}
#[test]
fn table_literals2() {
let actual = nu!(
cwd: ".",
r#"
echo [[name age] ; [bob 13] [sally 20]] | get age | math sum
"#
);
assert_eq!(actual.out, "33");
}
mod parse {
use nu_test_support::nu;