list, record, table expression support

This commit is contained in:
Devyn Cairns 2024-06-26 02:10:00 -07:00
parent 7890b3f27a
commit ed59f80b35
No known key found for this signature in database
4 changed files with 189 additions and 11 deletions

View File

@ -1,7 +1,7 @@
use nu_protocol::{
ast::{
Argument, Block, Call, CellPath, Expr, Expression, Operator, PathMember, Pipeline,
PipelineRedirection, RedirectionSource, RedirectionTarget,
Argument, Block, Call, CellPath, Expr, Expression, ListItem, Math, Operator, PathMember,
Pipeline, PipelineRedirection, RecordItem, RedirectionSource, RedirectionTarget,
},
engine::StateWorkingSet,
ir::{DataSlice, Instruction, IrBlock, Literal, RedirectMode},
@ -36,7 +36,7 @@ pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result<IrBlock,
Ok(builder.finish())
}
#[derive(Default)]
#[derive(Default, Clone)]
struct RedirectModes {
out: Option<Spanned<RedirectMode>>,
err: Option<Spanned<RedirectMode>>,
@ -49,6 +49,13 @@ impl RedirectModes {
err: None,
}
}
fn with_capture_out(&self, span: Span) -> Self {
RedirectModes {
out: Some(RedirectMode::Capture.into_spanned(span)),
err: self.err.clone(),
}
}
}
fn compile_block(
@ -328,9 +335,170 @@ fn compile_expression(
Expr::Block(block_id) => lit(builder, Literal::Block(*block_id)),
Expr::Closure(block_id) => lit(builder, Literal::Closure(*block_id)),
Expr::MatchBlock(_) => Err(CompileError::Todo("MatchBlock")),
Expr::List(_) => Err(CompileError::Todo("List")),
Expr::Table(_) => Err(CompileError::Todo("Table")),
Expr::Record(_) => Err(CompileError::Todo("Record")),
Expr::List(items) => {
// Guess capacity based on items (does not consider spread as more than 1)
lit(
builder,
Literal::List {
capacity: items.len(),
},
)?;
for item in items {
match item {
ListItem::Item(expr) => {
// Add each item using push-list
let item_reg = builder.next_register()?;
compile_expression(
working_set,
builder,
expr,
redirect_modes.with_capture_out(expr.span),
None,
item_reg,
)?;
builder.push(
Instruction::ListPush {
src_dst: out_reg,
item: item_reg,
}
.into_spanned(expr.span),
)?;
}
ListItem::Spread(spread_span, expr) => {
// Implement a spread as a ++ binary operation
let rhs_reg = builder.next_register()?;
compile_expression(
working_set,
builder,
expr,
redirect_modes.with_capture_out(expr.span),
None,
rhs_reg,
)?;
builder.push(
Instruction::BinaryOp {
lhs_dst: out_reg,
op: Operator::Math(Math::Append),
rhs: rhs_reg,
}
.into_spanned(*spread_span),
)?;
}
}
}
Ok(())
}
Expr::Table(table) => {
lit(
builder,
Literal::List {
capacity: table.rows.len(),
},
)?;
// Evaluate the columns
let column_registers = table
.columns
.iter()
.map(|column| {
let reg = builder.next_register()?;
compile_expression(
working_set,
builder,
column,
redirect_modes.with_capture_out(column.span),
None,
reg,
)?;
Ok(reg)
})
.collect::<Result<Vec<RegId>, CompileError>>()?;
// Build records for each row
for row in table.rows.iter() {
let row_reg = builder.next_register()?;
builder.load_literal(
row_reg,
Literal::Record {
capacity: table.columns.len(),
}
.into_spanned(expr.span),
)?;
for (column_reg, item) in column_registers.iter().zip(row.iter()) {
let column_reg = builder.clone_reg(*column_reg, item.span)?;
let item_reg = builder.next_register()?;
compile_expression(
working_set,
builder,
item,
redirect_modes.with_capture_out(item.span),
None,
item_reg,
)?;
builder.push(
Instruction::RecordInsert {
src_dst: out_reg,
key: column_reg,
val: item_reg,
}
.into_spanned(item.span),
)?;
}
}
// Free the column registers, since they aren't needed anymore
for reg in column_registers {
builder.free_register(reg)?;
}
Ok(())
}
Expr::Record(items) => {
lit(
builder,
Literal::Record {
capacity: items.len(),
},
)?;
for item in items {
match item {
RecordItem::Pair(key, val) => {
// Add each item using record-insert
let key_reg = builder.next_register()?;
let val_reg = builder.next_register()?;
compile_expression(
working_set,
builder,
key,
redirect_modes.with_capture_out(expr.span),
None,
key_reg,
)?;
compile_expression(
working_set,
builder,
val,
redirect_modes.with_capture_out(expr.span),
None,
val_reg,
)?;
builder.push(
Instruction::RecordInsert {
src_dst: out_reg,
key: key_reg,
val: val_reg,
}
.into_spanned(expr.span),
)?;
}
RecordItem::Spread(spread_span, expr) => {
return Err(CompileError::Todo("Record with spread"))
}
}
}
Ok(())
}
Expr::Keyword(_) => Err(CompileError::Todo("Keyword")),
Expr::ValueWithUnit(_) => Err(CompileError::Todo("ValueWithUnit")),
Expr::DateTime(_) => Err(CompileError::Todo("DateTime")),
@ -757,9 +925,12 @@ impl BlockBuilder {
Instruction::ListPush { src_dst: _, item } => self.free_register(*item)?,
Instruction::RecordInsert {
src_dst: _,
key: _,
key,
val,
} => self.free_register(*val)?,
} => {
self.free_register(*key)?;
self.free_register(*val)?;
}
Instruction::BinaryOp {
lhs_dst: _,
op: _,
@ -829,6 +1000,13 @@ impl BlockBuilder {
}
}
/// Clone a register with a `clone` instruction.
fn clone_reg(&mut self, src: RegId, span: Span) -> Result<RegId, CompileError> {
let dst = self.next_register()?;
self.push(Instruction::Clone { dst, src }.into_spanned(span))?;
Ok(dst)
}
/// Consume the builder and produce the final [`IrBlock`].
fn finish(self) -> IrBlock {
IrBlock {

View File

@ -306,10 +306,11 @@ fn eval_instruction(
}
Instruction::RecordInsert { src_dst, key, val } => {
let record_value = ctx.collect_reg(*src_dst, *span)?;
let key = ctx.collect_reg(*key, *span)?;
let val = ctx.collect_reg(*val, *span)?;
let record_span = record_value.span();
let mut record = record_value.into_record()?;
record.insert(ctx.get_str(*key, *span)?, val);
record.insert(key.coerce_into_string()?, val);
ctx.put_reg(
*src_dst,
Value::record(record, record_span).into_pipeline_data(),

View File

@ -116,7 +116,6 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
write!(f, "{:WIDTH$} {src_dst}, {item}", "list-push")
}
Instruction::RecordInsert { src_dst, key, val } => {
let key = FmtData(self.data, *key);
write!(f, "{:WIDTH$} {src_dst}, {key}, {val}", "record-insert")
}
Instruction::BinaryOp { lhs_dst, op, rhs } => {

View File

@ -93,7 +93,7 @@ pub enum Instruction {
/// Insert a key-value pair into a record. Any existing value for the key is overwritten.
RecordInsert {
src_dst: RegId,
key: DataSlice,
key: RegId,
val: RegId,
},
/// Do a binary operation on `lhs_dst` (left) and `rhs` (right) and write the result to