diff --git a/crates/nu-engine/src/compile/mod.rs b/crates/nu-engine/src/compile/mod.rs index 4c94e8f1ff..da29c90c39 100644 --- a/crates/nu-engine/src/compile/mod.rs +++ b/crates/nu-engine/src/compile/mod.rs @@ -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>, err: Option>, @@ -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::, 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 { + 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 { diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index b0a00d9ec5..453bb3f8f4 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -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(), diff --git a/crates/nu-protocol/src/ir/display.rs b/crates/nu-protocol/src/ir/display.rs index 0c4e8e7961..dd57528203 100644 --- a/crates/nu-protocol/src/ir/display.rs +++ b/crates/nu-protocol/src/ir/display.rs @@ -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 } => { diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index 77f604e2d5..41c76c49bc 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -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