more instructions for read/write variables, cell path, env var

This commit is contained in:
Devyn Cairns 2024-06-10 21:39:47 -07:00
parent 20a214a4b9
commit 6eb035f071
3 changed files with 191 additions and 35 deletions

View File

@ -1,11 +1,11 @@
use nu_protocol::{
ast::{
Argument, Block, Call, CellPath, Expr, Expression, Operator, Pipeline, PipelineRedirection,
RedirectionSource, RedirectionTarget,
Argument, Block, Call, CellPath, Expr, Expression, Operator, PathMember, Pipeline,
PipelineRedirection, RedirectionSource, RedirectionTarget,
},
engine::StateWorkingSet,
ir::{Instruction, IrBlock, Literal, RedirectMode},
IntoSpanned, OutDest, RegId, ShellError, Span, Spanned,
IntoSpanned, OutDest, RegId, ShellError, Span, Spanned, ENV_VARIABLE_ID,
};
const BLOCK_INPUT: RegId = RegId(0);
@ -264,7 +264,13 @@ fn compile_expression(
Expr::Float(f) => lit(builder, Literal::Float(*f)),
Expr::Binary(bin) => lit(builder, Literal::Binary(bin.as_slice().into())),
Expr::Range(_) => Err(CompileError::Todo("Range")),
Expr::Var(_) => Err(CompileError::Todo("Var")),
Expr::Var(var_id) => builder.push(
Instruction::LoadVariable {
dst: out_reg,
var_id: *var_id,
}
.into_spanned(expr.span),
),
Expr::VarDecl(_) => Err(CompileError::Todo("VarDecl")),
Expr::Call(call) => {
// Ensure that out_reg contains the input value, because a call only uses one register
@ -332,38 +338,42 @@ fn compile_expression(
Expr::RawString(rs) => lit(builder, Literal::RawString(rs.as_str().into())),
Expr::CellPath(path) => lit(builder, Literal::CellPath(Box::new(path.clone()))),
Expr::FullCellPath(full_cell_path) => {
compile_expression(
working_set,
builder,
&full_cell_path.head,
RedirectModes::capture_out(expr.span),
in_reg,
out_reg,
)?;
// Only do the follow if this is actually needed
if !full_cell_path.tail.is_empty() {
let cell_path_reg = builder.literal(
Literal::CellPath(Box::new(CellPath {
members: full_cell_path.tail.clone(),
}))
.into_spanned(expr.span),
)?;
builder.push(
Instruction::FollowCellPath {
src_dst: out_reg,
path: cell_path_reg,
}
.into_spanned(expr.span),
if matches!(full_cell_path.head.expr, Expr::Var(ENV_VARIABLE_ID)) {
compile_load_env(builder, expr.span, &full_cell_path.tail, out_reg)
} else {
compile_expression(
working_set,
builder,
&full_cell_path.head,
RedirectModes::capture_out(expr.span),
in_reg,
out_reg,
)?;
// Only do the follow if this is actually needed
if !full_cell_path.tail.is_empty() {
let cell_path_reg = builder.literal(
Literal::CellPath(Box::new(CellPath {
members: full_cell_path.tail.clone(),
}))
.into_spanned(expr.span),
)?;
builder.push(
Instruction::FollowCellPath {
src_dst: out_reg,
path: cell_path_reg,
}
.into_spanned(expr.span),
)?;
}
Ok(())
}
Ok(())
}
Expr::ImportPattern(_) => Err(CompileError::Todo("ImportPattern")),
Expr::Overlay(_) => Err(CompileError::Todo("Overlay")),
Expr::Signature(_) => Err(CompileError::Todo("Signature")),
Expr::StringInterpolation(_) => Err(CompileError::Todo("StringInterpolation")),
Expr::Nothing => Err(CompileError::Todo("Nothing ")),
Expr::Garbage => Err(CompileError::Todo("Garbage ")),
Expr::Nothing => Err(CompileError::Todo("Nothing")),
Expr::Garbage => Err(CompileError::Todo("Garbage")),
}
}
@ -519,6 +529,53 @@ fn compile_binary_op(
Ok(())
}
fn compile_load_env(
builder: &mut BlockBuilder,
span: Span,
path: &[PathMember],
out_reg: RegId,
) -> Result<(), CompileError> {
if path.is_empty() {
builder.push(
Instruction::LoadVariable {
dst: out_reg,
var_id: ENV_VARIABLE_ID,
}
.into_spanned(span),
)
} else {
let (key, optional) = match &path[0] {
PathMember::String { val, optional, .. } => (val.as_str().into(), *optional),
PathMember::Int { span, .. } => return Err(CompileError::AccessEnvByInt(*span)),
};
let tail = &path[1..];
if optional {
builder.push(Instruction::LoadEnvOpt { dst: out_reg, key }.into_spanned(span))?;
} else {
builder.push(Instruction::LoadEnv { dst: out_reg, key }.into_spanned(span))?;
}
if !tail.is_empty() {
let path = builder.literal(
Literal::CellPath(Box::new(CellPath {
members: tail.to_vec(),
}))
.into_spanned(span),
)?;
builder.push(
Instruction::FollowCellPath {
src_dst: out_reg,
path,
}
.into_spanned(span),
)?;
}
Ok(())
}
}
/// An internal compiler error, generally means a Nushell bug rather than an issue with user error
/// since parsing and typechecking has already passed.
#[derive(Debug)]
@ -528,11 +585,12 @@ enum CompileError {
InvalidRedirectMode,
Garbage,
UnsupportedOperatorExpression,
AccessEnvByInt(Span),
Todo(&'static str),
}
impl CompileError {
fn to_shell_error(self, span: Option<Span>) -> ShellError {
fn to_shell_error(self, mut span: Option<Span>) -> ShellError {
let ice = "internal compiler error: ";
let message = match self {
CompileError::RegisterOverflow => format!("{ice}register overflow"),
@ -548,6 +606,10 @@ impl CompileError {
CompileError::UnsupportedOperatorExpression => {
format!("{ice}unsupported operator expression")
}
CompileError::AccessEnvByInt(local_span) => {
span = Some(local_span);
format!("{ice}attempted access of $env by integer path")
}
CompileError::Todo(msg) => {
format!("{ice}TODO: {msg}")
}
@ -631,8 +693,8 @@ impl BlockBuilder {
}
}
/// Insert an instruction into the block, automatically freeing any registers consumed by the
/// instruction.
/// Insert an instruction into the block, automatically marking any registers populated by
/// the instruction, and freeing any registers consumed by the instruction.
fn push(&mut self, instruction: Spanned<Instruction>) -> Result<(), CompileError> {
match &instruction.item {
Instruction::LoadLiteral { dst, lit: _ } => self.mark_register(*dst)?,
@ -643,6 +705,11 @@ impl BlockBuilder {
Instruction::Clone { dst, src: _ } => self.mark_register(*dst)?,
Instruction::Collect { src_dst: _ } => (),
Instruction::Drain { src } => self.free_register(*src)?,
Instruction::LoadVariable { dst, var_id: _ } => self.mark_register(*dst)?,
Instruction::StoreVariable { var_id: _, src } => self.free_register(*src)?,
Instruction::LoadEnv { dst, key: _ } => self.mark_register(*dst)?,
Instruction::LoadEnvOpt { dst, key: _ } => self.mark_register(*dst)?,
Instruction::StoreEnv { key: _, src } => self.free_register(*src)?,
Instruction::PushPositional { src } => self.free_register(*src)?,
Instruction::AppendRest { src } => self.free_register(*src)?,
Instruction::PushFlag { name: _ } => (),
@ -661,6 +728,18 @@ impl BlockBuilder {
rhs,
} => self.free_register(*rhs)?,
Instruction::FollowCellPath { src_dst: _, path } => self.free_register(*path)?,
Instruction::CloneCellPath { dst, src: _, path } => {
self.mark_register(*dst)?;
self.free_register(*path)?;
}
Instruction::UpsertCellPath {
src_dst: _,
path,
new_value,
} => {
self.free_register(*path)?;
self.free_register(*new_value)?;
}
Instruction::Jump { index: _ } => (),
Instruction::BranchIf { cond, index: _ } => self.free_register(*cond)?,
Instruction::Return { src } => self.free_register(*src)?,

View File

@ -1,6 +1,6 @@
use std::fmt;
use crate::{engine::EngineState, DeclId};
use crate::{engine::EngineState, DeclId, VarId};
use super::{Instruction, IrBlock, RedirectMode};
@ -60,6 +60,23 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
Instruction::Drain { src } => {
write!(f, "{:WIDTH$} {src}", "drain")
}
Instruction::LoadVariable { dst, var_id } => {
let var = FmtVar::new(self.engine_state, *var_id);
write!(f, "{:WIDTH$} {dst}, {var}", "load-variable")
}
Instruction::StoreVariable { var_id, src } => {
let var = FmtVar::new(self.engine_state, *var_id);
write!(f, "{:WIDTH$} {var}, {src}", "store-variable")
}
Instruction::LoadEnv { dst, key } => {
write!(f, "{:WIDTH$} {dst}, {key:?}", "load-env")
}
Instruction::LoadEnvOpt { dst, key } => {
write!(f, "{:WIDTH$} {dst}, {key:?}", "load-env-opt")
}
Instruction::StoreEnv { key, src } => {
write!(f, "{:WIDTH$} {key:?}, {src}", "store-env")
}
Instruction::PushPositional { src } => {
write!(f, "{:WIDTH$} {src}", "push-positional")
}
@ -88,6 +105,20 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
Instruction::FollowCellPath { src_dst, path } => {
write!(f, "{:WIDTH$} {src_dst}, {path}", "follow-cell-path")
}
Instruction::CloneCellPath { dst, src, path } => {
write!(f, "{:WIDTH$} {dst}, {src}, {path}", "clone-cell-path")
}
Instruction::UpsertCellPath {
src_dst,
path,
new_value,
} => {
write!(
f,
"{:WIDTH$} {src_dst}, {path}, {new_value}",
"upsert-cell-path"
)
}
Instruction::Jump { index } => {
write!(f, "{:WIDTH$} {index}", "jump")
}
@ -115,6 +146,30 @@ impl fmt::Display for FmtDecl<'_> {
}
}
struct FmtVar<'a>(DeclId, Option<&'a str>);
impl<'a> FmtVar<'a> {
fn new(engine_state: &'a EngineState, var_id: VarId) -> Self {
// Search for the name of the variable
let name: Option<&str> = engine_state
.active_overlays(&[])
.flat_map(|overlay| overlay.vars.iter())
.find(|(_, v)| **v == var_id)
.map(|(k, _)| std::str::from_utf8(k).unwrap_or("<utf-8 error>"));
FmtVar(var_id, name)
}
}
impl fmt::Display for FmtVar<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(name) = self.1 {
write!(f, "var {} {:?}", self.0, name)
} else {
write!(f, "var {}", self.0)
}
}
}
impl std::fmt::Display for RedirectMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {

View File

@ -1,7 +1,7 @@
use crate::{
ast::{CellPath, Operator},
engine::EngineState,
BlockId, DeclId, RegId, Span,
BlockId, DeclId, RegId, Span, VarId,
};
use serde::{Deserialize, Serialize};
@ -39,6 +39,17 @@ pub enum Instruction {
Collect { src_dst: RegId },
/// Drain the value/stream in a register and discard (e.g. semicolon)
Drain { src: RegId },
/// Load the value of a variable into the `dst` register
LoadVariable { dst: RegId, var_id: VarId },
/// Store the value of a variable from the `src` register
StoreVariable { var_id: VarId, src: RegId },
/// Load the value of an environment variable into the `dst` register
LoadEnv { dst: RegId, key: Box<str> },
/// Load the value of an environment variable into the `dst` register, or `Nothing` if it
/// doesn't exist
LoadEnvOpt { dst: RegId, key: Box<str> },
/// Store the value of an environment variable from the `src` register
StoreEnv { key: Box<str>, src: RegId },
/// Add a positional arg to the next call
PushPositional { src: RegId },
/// Add a list of args to the next call (spread/rest)
@ -61,8 +72,19 @@ pub enum Instruction {
op: Operator,
rhs: RegId,
},
/// Follow a cell path on the `path`
/// Follow a cell path on the value in `src_dst`, storing the result back to `src_dst`
FollowCellPath { src_dst: RegId, path: RegId },
/// Clone the value at a cell path in `src`, storing the result to `dst`. The original value
/// remains in `src`. Must be a collected value.
CloneCellPath { dst: RegId, src: RegId, path: RegId },
/// Update/insert a cell path to `new_value` on the value in `src_dst`, storing the modified
/// value back to `src_dst`
UpsertCellPath {
src_dst: RegId,
path: RegId,
new_value: RegId,
},
/// Update a cell path
/// Jump to an offset in this block
Jump { index: usize },
/// Branch to an offset in this block if the value of the `cond` register is a true boolean,