string, glob interpolation

This commit is contained in:
Devyn Cairns 2024-06-28 22:07:39 -07:00
parent 37255bef3c
commit b63b0217bd
No known key found for this signature in database
6 changed files with 109 additions and 7 deletions

View File

@ -157,6 +157,11 @@ impl BlockBuilder {
decl_id: _,
src_dst: _,
} => (),
Instruction::StringAppend { src_dst: _, val } => self.free_register(*val)?,
Instruction::GlobFrom {
src_dst: _,
no_expand: _,
} => (),
Instruction::ListPush { src_dst: _, item } => self.free_register(*item)?,
Instruction::ListSpread { src_dst: _, items } => self.free_register(*items)?,
Instruction::RecordInsert {

View File

@ -21,6 +21,7 @@ pub enum CompileError {
RunExternalNotFound,
InvalidLhsForAssignment(Span),
ModifyImmutableVariable(Span),
UnexpectedExpr(&'static str),
Todo(&'static str),
}
@ -56,6 +57,9 @@ impl CompileError {
CompileError::ModifyImmutableVariable(_) => {
"attempted to modify immutable variable".into()
}
CompileError::UnexpectedExpr(msg) => {
format!("unexpected expression in this context: {msg}")
}
CompileError::Todo(msg) => {
format!("TODO: {msg}")
}

View File

@ -6,8 +6,8 @@ use super::{
use nu_protocol::{
ast::{CellPath, Expr, Expression, ListItem, RecordItem},
engine::StateWorkingSet,
ir::{Instruction, Literal},
IntoSpanned, RegId, ENV_VARIABLE_ID,
ir::{DataSlice, Instruction, Literal},
IntoSpanned, RegId, Type, ENV_VARIABLE_ID,
};
pub(crate) fn compile_expression(
@ -124,7 +124,7 @@ pub(crate) fn compile_expression(
)?;
Ok(())
}
Expr::VarDecl(_) => Err(CompileError::Todo("VarDecl")),
Expr::VarDecl(_) => Err(CompileError::UnexpectedExpr("VarDecl")),
Expr::Call(call) => {
move_in_reg_to_out_reg(builder)?;
@ -135,7 +135,7 @@ pub(crate) fn compile_expression(
compile_external_call(working_set, builder, head, args, redirect_modes, out_reg)
}
Expr::Operator(_) => Err(CompileError::Todo("Operator")),
Expr::Operator(_) => Err(CompileError::UnexpectedExpr("Operator")),
Expr::RowCondition(_) => Err(CompileError::Todo("RowCondition")),
Expr::UnaryNot(subexpr) => {
drop_input(builder)?;
@ -354,7 +354,7 @@ pub(crate) fn compile_expression(
}
Ok(())
}
Expr::Keyword(_) => Err(CompileError::Todo("Keyword")),
Expr::Keyword(_) => Err(CompileError::UnexpectedExpr("Keyword")),
Expr::ValueWithUnit(_) => Err(CompileError::Todo("ValueWithUnit")),
Expr::DateTime(_) => Err(CompileError::Todo("DateTime")),
Expr::Filepath(path, no_expand) => {
@ -430,8 +430,57 @@ pub(crate) fn compile_expression(
Expr::ImportPattern(_) => Err(CompileError::Todo("ImportPattern")),
Expr::Overlay(_) => Err(CompileError::Todo("Overlay")),
Expr::Signature(_) => ignore(builder), // no effect
Expr::StringInterpolation(_) => Err(CompileError::Todo("StringInterpolation")),
Expr::GlobInterpolation(_, _) => Err(CompileError::Todo("GlobInterpolation")),
Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
let mut exprs_iter = exprs.iter().peekable();
if exprs_iter.peek().is_some_and(|e| e.ty == Type::String) {
// If the first expression is typed as a string, just take it and build from that
compile_expression(
working_set,
builder,
exprs_iter.next().expect("peek() was Some"),
redirect_modes.with_capture_out(expr.span),
None,
out_reg,
)?;
} else {
// Start with an empty string
lit(builder, Literal::String(DataSlice::empty()))?;
}
// Compile each expression and append to out_reg
for expr in exprs_iter {
let scratch_reg = builder.next_register()?;
compile_expression(
working_set,
builder,
expr,
redirect_modes.with_capture_out(expr.span),
None,
scratch_reg,
)?;
builder.push(
Instruction::StringAppend {
src_dst: out_reg,
val: scratch_reg,
}
.into_spanned(expr.span),
)?;
}
// If it's a glob interpolation, change it to a glob
if let Expr::GlobInterpolation(_, no_expand) = expr.expr {
builder.push(
Instruction::GlobFrom {
src_dst: out_reg,
no_expand,
}
.into_spanned(expr.span),
)?;
}
Ok(())
}
Expr::Nothing => lit(builder, Literal::Nothing),
Expr::Garbage => Err(CompileError::Garbage),
}

View File

@ -299,6 +299,31 @@ fn eval_instruction(
ctx.put_reg(*src_dst, result);
Ok(Continue)
}
Instruction::StringAppend { src_dst, val } => {
let string_value = ctx.collect_reg(*src_dst, *span)?;
let operand_value = ctx.collect_reg(*val, *span)?;
let string_span = string_value.span();
let mut string = string_value.into_string()?;
let operand = if let Value::String { val, .. } = operand_value {
// Small optimization, so we don't have to copy the string *again*
val
} else {
operand_value.to_expanded_string(", ", ctx.engine_state.get_config())
};
string.push_str(&operand);
let new_string_value = Value::string(string, string_span);
ctx.put_reg(*src_dst, new_string_value.into_pipeline_data());
Ok(Continue)
}
Instruction::GlobFrom { src_dst, no_expand } => {
let string_value = ctx.collect_reg(*src_dst, *span)?;
let string = string_value.into_string()?;
let glob_value = Value::glob(string, *no_expand, *span);
ctx.put_reg(*src_dst, glob_value.into_pipeline_data());
Ok(Continue)
}
Instruction::ListPush { src_dst, item } => {
let list_value = ctx.collect_reg(*src_dst, *span)?;
let item = ctx.collect_reg(*item, *span)?;

View File

@ -115,6 +115,13 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
let decl = FmtDecl::new(self.engine_state, *decl_id);
write!(f, "{:WIDTH$} {decl}, {src_dst}", "call")
}
Instruction::StringAppend { src_dst, val } => {
write!(f, "{:WIDTH$} {src_dst}, {val}", "string-append")
}
Instruction::GlobFrom { src_dst, no_expand } => {
let no_expand = if *no_expand { "no-expand" } else { "expand" };
write!(f, "{:WIDTH$} {src_dst}, {no_expand}", "glob-from",)
}
Instruction::ListPush { src_dst, item } => {
write!(f, "{:WIDTH$} {src_dst}, {item}", "list-push")
}

View File

@ -42,6 +42,13 @@ pub struct DataSlice {
pub len: u32,
}
impl DataSlice {
/// A data slice that contains no data. This slice is always valid.
pub const fn empty() -> DataSlice {
DataSlice { start: 0, len: 0 }
}
}
impl std::ops::Index<DataSlice> for [u8] {
type Output = [u8];
@ -90,6 +97,11 @@ pub enum Instruction {
/// Make a call. The input is taken from `src_dst`, and the output is placed in `src_dst`,
/// overwriting it. The argument stack is used implicitly and cleared when the call ends.
Call { decl_id: DeclId, src_dst: RegId },
/// Append a value onto the end of a string. Uses `to_expanded_string(", ", ...)` on the value.
/// Used for string interpolation literals. Not the same thing as the `++` operator.
StringAppend { src_dst: RegId, val: RegId },
/// Convert a string into a glob. Used for glob interpolation.
GlobFrom { src_dst: RegId, no_expand: bool },
/// Push a value onto the end of a list. Used to construct list literals.
ListPush { src_dst: RegId, item: RegId },
/// Spread a value onto the end of a list. Used to construct list literals.