feat: stash

This commit is contained in:
2025-05-10 16:29:55 +08:00
parent 14045f7924
commit f86c088e97
21 changed files with 222 additions and 219 deletions

737
src/ir.rs Normal file
View File

@@ -0,0 +1,737 @@
use std::collections::HashMap;
use ecow::EcoString;
use rnix::ast::{self, Expr};
use crate::bytecode::{ConstIdx, Consts, ThunkIdx};
use crate::compile::*;
use crate::error::*;
use crate::ty::internal as i;
pub fn downgrade(expr: Expr) -> Result<Downgraded> {
let mut state = DowngradeState::new();
let ir = expr.downgrade(&mut state)?;
Ok(Downgraded {
top_level: ir,
consts: state.consts.into(),
thunks: state.thunks.into(),
})
}
trait Downcast<T: Sized>
where
Self: Sized,
{
fn downcast_ref(&self) -> Option<&T>;
fn downcast_mut(&mut self) -> Option<&mut T>;
fn downcast(self) -> core::result::Result<T, Self>;
}
macro_rules! ir {
(
$(
$(#[$($x:tt)*])*
$ty:ident
=>
{$($name:ident : $elemtype:ty),*$(,)?}
)
,*$(,)?
) => {
#[derive(Clone, Debug)]
pub enum Ir {
$(
$ty($ty),
)*
}
impl Ir {
fn boxed(self) -> Box<Self> {
Box::new(self)
}
fn ok(self) -> Result<Self> {
Ok(self)
}
}
impl Compile for Ir {
fn compile(self, state: &mut Compiler) {
match self {
$(Ir::$ty(ir) => ir.compile(state),)*
}
}
}
$(
$(
#[$($x)*]
)*
#[derive(Clone, Debug)]
pub struct $ty {
$(
pub $name : $elemtype,
)*
}
impl $ty {
pub fn ir(self) -> Ir {
Ir::$ty(self)
}
}
impl Downcast<$ty> for Ir {
fn downcast_ref(&self) -> Option<&$ty> {
match self {
Ir::$ty(value) => Some(value),
_ => None,
}
}
fn downcast_mut(&mut self) -> Option<&mut $ty> {
match self {
Ir::$ty(value) => Some(value),
_ => None,
}
}
fn downcast(self) -> core::result::Result<$ty, Self> {
match self {
Ir::$ty(value) => Ok(value),
_ => Err(self),
}
}
}
)*
}
}
ir! {
Attrs => { stcs: HashMap<EcoString, Ir>, dyns: Vec<DynamicAttrPair>, rec: bool },
List => { items: Vec<Ir> },
HasAttr => { lhs: Box<Ir>, rhs: Vec<Attr> },
BinOp => { lhs: Box<Ir>, rhs: Box<Ir>, kind: BinOpKind },
UnOp => { rhs: Box<Ir>, kind: UnOpKind },
Select => { expr: Box<Ir>, attrpath: Vec<Attr>, default: Option<Box<Ir>> },
If => { cond: Box<Ir>, consq: Box<Ir>, alter: Box<Ir> },
Func => { param: Param, body: Thunk },
Call => { func: Box<Ir>, args: Vec<Ir> },
Let => { attrs: Attrs, expr: Box<Ir> },
With => { namespace: Box<Ir>, expr: Box<Ir> },
Assert => { assertion: Box<Ir>, expr: Box<Ir> },
ConcatStrings => { parts: Vec<Ir> },
Const => { value: i::Const },
Var => { sym: EcoString },
#[derive(Copy)]
Thunk => { idx: ThunkIdx },
Path => { expr: Box<Ir> },
}
#[derive(Clone, Debug)]
pub struct DynamicAttrPair(pub Ir, pub Ir);
pub struct DowngradeState {
thunks: Vec<Ir>,
consts: Vec<i::Const>,
consts_table: HashMap<i::Const, ConstIdx>,
}
pub struct Downgraded {
pub top_level: Ir,
pub consts: Consts,
pub thunks: Box<[Ir]>,
}
impl DowngradeState {
fn new() -> DowngradeState {
DowngradeState {
thunks: Vec::new(),
consts: Vec::new(),
consts_table: HashMap::new(),
}
}
fn new_thunk(&mut self, thunk: Ir) -> Thunk {
let idx = self.thunks.len();
self.thunks.push(thunk);
Thunk { idx }
}
fn lookup_thunk(&self, idx: ThunkIdx) -> &Ir {
self.thunks.get(idx).unwrap()
}
}
impl Attrs {
fn _insert(&mut self, mut path: std::vec::IntoIter<Attr>, name: Attr, value: Ir) -> Result<()> {
if let Some(attr) = path.next() {
match attr {
Attr::Str(ident) => {
if self.stcs.get(&ident).is_some() {
self.stcs
.get_mut(&ident)
.unwrap()
.downcast_mut()
.ok_or(Error::DowngradeError(format!(
r#""{ident}" already exsists in this set"#
)))
.and_then(|attrs: &mut Attrs| attrs._insert(path, name, value))
} else {
let mut attrs = Attrs {
rec: false,
stcs: HashMap::new(),
dyns: Vec::new(),
};
attrs._insert(path, name, value)?;
assert!(self.stcs.insert(ident, attrs.ir()).is_none());
Ok(())
}
}
Attr::Strs(string) => {
let mut attrs = Attrs {
rec: false,
stcs: HashMap::new(),
dyns: Vec::new(),
};
attrs._insert(path, name, value)?;
self.dyns.push(DynamicAttrPair(string.ir(), attrs.ir()));
Ok(())
}
Attr::Dynamic(dynamic) => {
let mut attrs = Attrs {
rec: false,
stcs: HashMap::new(),
dyns: Vec::new(),
};
attrs._insert(path, name, value)?;
self.dyns.push(DynamicAttrPair(dynamic, attrs.ir()));
Ok(())
}
}
} else {
match name {
Attr::Str(ident) => {
if self.stcs.get(&ident).is_some() {
return Err(Error::DowngradeError(format!(
r#""{ident}" already exsists in this set"#
)));
}
self.stcs.insert(ident, value);
}
Attr::Strs(string) => {
self.dyns.push(DynamicAttrPair(string.ir(), value));
}
Attr::Dynamic(dynamic) => {
self.dyns.push(DynamicAttrPair(dynamic, value));
}
}
Ok(())
}
}
pub fn insert(&mut self, path: Vec<Attr>, value: Ir) -> Result<()> {
let mut path = path.into_iter();
let name = path.next_back().unwrap();
self._insert(path, name, value)
}
fn _has_attr(&self, mut path: std::slice::Iter<Attr>, name: Attr) -> Option<bool> {
match path.next() {
Some(Attr::Str(ident)) => self
.stcs
.get(ident.as_str())
.and_then(|attrs| attrs.downcast_ref())
.map_or(Some(false), |attrs: &Attrs| attrs._has_attr(path, name)),
None => match name {
Attr::Str(ident) => Some(self.stcs.get(&ident).is_some()),
_ => None,
},
_ => None,
}
}
pub fn has_attr(&self, path: &[Attr]) -> Option<bool> {
let mut path = path.iter();
let name = path.next_back().unwrap().clone();
self._has_attr(path, name)
}
}
#[derive(Clone, Debug)]
pub enum Attr {
Dynamic(Ir),
Strs(ConcatStrings),
Str(EcoString),
}
#[derive(Clone, Debug)]
pub enum BinOpKind {
Add,
Sub,
Div,
Mul,
Eq,
Neq,
Lt,
Gt,
Leq,
Geq,
And,
Or,
Impl,
Con,
Upd,
}
impl From<ast::BinOpKind> for BinOpKind {
fn from(op: ast::BinOpKind) -> Self {
use BinOpKind::*;
use ast::BinOpKind as astkind;
match op {
astkind::Concat => Con,
astkind::Update => Upd,
astkind::Add => Add,
astkind::Sub => Sub,
astkind::Mul => Mul,
astkind::Div => Div,
astkind::And => And,
astkind::Equal => Eq,
astkind::Implication => Impl,
astkind::Less => Lt,
astkind::LessOrEq => Leq,
astkind::More => Gt,
astkind::MoreOrEq => Geq,
astkind::NotEqual => Neq,
astkind::Or => Or,
}
}
}
#[derive(Clone, Debug)]
pub enum UnOpKind {
Neg,
Not,
}
impl From<ast::UnaryOpKind> for UnOpKind {
fn from(value: ast::UnaryOpKind) -> Self {
match value {
ast::UnaryOpKind::Invert => UnOpKind::Not,
ast::UnaryOpKind::Negate => UnOpKind::Neg,
}
}
}
#[derive(Clone, Debug)]
pub enum Param {
Ident(EcoString),
Formals {
formals: Vec<(EcoString, Option<Thunk>)>,
ellipsis: bool,
alias: Option<EcoString>,
},
}
trait Downgrade
where
Self: Sized,
{
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir>;
}
impl Downgrade for Expr {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
match self {
Expr::Apply(apply) => apply.downgrade(state),
Expr::Assert(assert) => assert.downgrade(state),
Expr::Error(error) => return Err(Error::DowngradeError(error.to_string())),
Expr::IfElse(ifelse) => ifelse.downgrade(state),
Expr::Select(select) => select.downgrade(state),
Expr::Str(str) => str.downgrade(state),
Expr::Path(path) => path.downgrade(state),
Expr::Literal(lit) => lit.downgrade(state),
Expr::Lambda(lambda) => lambda.downgrade(state),
Expr::LegacyLet(let_) => let_.downgrade(state),
Expr::LetIn(letin) => letin.downgrade(state),
Expr::List(list) => list.downgrade(state),
Expr::BinOp(op) => op.downgrade(state),
Expr::Paren(paren) => paren.expr().unwrap().downgrade(state),
Expr::Root(root) => root.expr().unwrap().downgrade(state),
Expr::AttrSet(attrs) => attrs.downgrade(state),
Expr::UnaryOp(op) => op.downgrade(state),
Expr::Ident(ident) => ident.downgrade(state),
Expr::With(with) => with.downgrade(state),
Expr::HasAttr(has) => has.downgrade(state),
}
}
}
impl Downgrade for ast::Assert {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
Assert {
assertion: self.condition().unwrap().downgrade(state)?.boxed(),
expr: self.body().unwrap().downgrade(state)?.boxed(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::IfElse {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
If {
cond: self.condition().unwrap().downgrade(state)?.boxed(),
consq: self.body().unwrap().downgrade(state)?.boxed(),
alter: self.else_body().unwrap().downgrade(state)?.boxed(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::Path {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let parts = self
.parts()
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Const {
value: lit.to_string().into(),
}
.ir()
.ok(),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(state)
}
})
.collect::<Result<Vec<_>>>()?;
if parts.len() == 1 {
Path {
expr: parts.into_iter().next().unwrap().boxed(),
}
} else {
Path {
expr: ConcatStrings { parts }.ir().boxed(),
}
}
.ir()
.ok()
}
}
impl Downgrade for ast::Str {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let parts = self
.normalized_parts()
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Const { value: lit.into() }.ir().ok(),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(state)
}
})
.collect::<Result<Vec<_>>>()?;
if parts.len() == 1 {
Ok(parts.into_iter().next().unwrap())
} else {
ConcatStrings { parts }.ir().ok()
}
}
}
impl Downgrade for ast::Literal {
fn downgrade(self, _state: &mut DowngradeState) -> Result<Ir> {
match self.kind() {
ast::LiteralKind::Integer(int) => Const {
value: int.value().unwrap().into(),
},
ast::LiteralKind::Float(float) => Const {
value: float.value().unwrap().into(),
},
ast::LiteralKind::Uri(uri) => Const {
value: uri.to_string().into(),
},
}
.ir()
.ok()
}
}
impl Downgrade for ast::Ident {
fn downgrade(self, _state: &mut DowngradeState) -> Result<Ir> {
Var {
sym: self.to_string().into(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::AttrSet {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let rec = self.rec_token().is_some();
downgrade_has_entry(self, rec, state).map(|attrs| attrs.ir())
}
}
impl Downgrade for ast::List {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let mut items = Vec::with_capacity(self.items().size_hint().0);
for item in self.items() {
items.push(item.downgrade(state)?)
}
List { items }.ir().ok()
}
}
impl Downgrade for ast::BinOp {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
BinOp {
lhs: self.lhs().unwrap().downgrade(state)?.boxed(),
rhs: self.rhs().unwrap().downgrade(state)?.boxed(),
kind: self.operator().unwrap().into(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::HasAttr {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let attrs = self.expr().unwrap().downgrade(state)?;
let path = downgrade_attrpath(self.attrpath().unwrap(), state)?;
if let Some(attrs) = Downcast::<Attrs>::downcast_ref(&attrs) {
if let Some(res) = attrs.has_attr(&path) {
return Const { value: res.into() }.ir().ok();
}
}
HasAttr {
lhs: attrs.boxed(),
rhs: path,
}
.ir()
.ok()
}
}
impl Downgrade for ast::UnaryOp {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
UnOp {
rhs: self.expr().unwrap().downgrade(state)?.boxed(),
kind: self.operator().unwrap().into(),
}
.ir()
.ok()
}
}
impl Downgrade for ast::Select {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
Select {
expr: self.expr().unwrap().downgrade(state)?.boxed(),
attrpath: downgrade_attrpath(self.attrpath().unwrap(), state)?,
default: match self.default_expr() {
Some(default) => Some(default.downgrade(state)?.boxed()),
None => None,
},
}
.ir()
.ok()
}
}
impl Downgrade for ast::LegacyLet {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let attrs = downgrade_has_entry(self, true, state)?;
Select {
expr: attrs.ir().boxed(),
attrpath: vec![Attr::Str("body".to_string().into())],
default: None,
}
.ir()
.ok()
}
}
impl Downgrade for ast::LetIn {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let body = self.body().unwrap();
let attrs = downgrade_has_entry(self, true, state)?;
let expr = body.downgrade(state)?.boxed();
Let { attrs, expr }.ir().ok()
}
}
impl Downgrade for ast::With {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let namespace = self.namespace().unwrap().downgrade(state)?;
if let Ir::Attrs(attrs) = namespace {
let expr = self.body().unwrap().downgrade(state)?.boxed();
Let { attrs, expr }.ir().ok()
} else {
let namespace = namespace.boxed();
let expr = self.body().unwrap().downgrade(state)?.boxed();
With { namespace, expr }.ir().ok()
}
}
}
impl Downgrade for ast::Lambda {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let body = self.body().unwrap();
let param = downgrade_param(self.param().unwrap(), state)?;
let body = body.downgrade(state)?;
let body = state.new_thunk(body);
Func { param, body }.ir().ok()
}
}
impl Downgrade for ast::Apply {
fn downgrade(self, state: &mut DowngradeState) -> Result<Ir> {
let mut args = vec![self.argument().unwrap().downgrade(state)?];
let mut func = self.lambda().unwrap();
while let ast::Expr::Apply(call) = func {
func = call.lambda().unwrap();
args.push(call.argument().unwrap().downgrade(state)?);
}
let func = func.downgrade(state)?.boxed();
args.reverse();
Call { func, args }.ir().ok()
}
}
fn downgrade_param(param: ast::Param, state: &mut DowngradeState) -> Result<Param> {
match param {
ast::Param::IdentParam(ident) => Ok(Param::Ident(ident.to_string().into())),
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, state),
}
}
fn downgrade_pattern(pattern: ast::Pattern, state: &mut DowngradeState) -> Result<Param> {
let formals = pattern
.pat_entries()
.map(|entry| {
let ident = entry.ident().unwrap().to_string().into();
if entry.default().is_none() {
Ok((ident, None))
} else {
entry
.default()
.unwrap()
.downgrade(state)
.map(|ok| (ident, Some(state.new_thunk(ok))))
}
})
.collect::<Result<Vec<_>>>()?;
let ellipsis = pattern.ellipsis_token().is_some();
let alias = pattern
.pat_bind()
.map(|alias| alias.ident().unwrap().to_string().into());
Ok(Param::Formals {
formals,
ellipsis,
alias,
})
}
fn downgrade_has_entry(
has_entry: impl ast::HasEntry,
rec: bool,
state: &mut DowngradeState,
) -> Result<Attrs> {
let entires = has_entry.entries();
let mut attrs = Attrs {
rec,
stcs: HashMap::new(),
dyns: Vec::new(),
};
for entry in entires {
match entry {
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, state)?,
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, state)?,
}
}
Ok(attrs)
}
fn downgrade_inherit(
inherit: ast::Inherit,
stcs: &mut HashMap<EcoString, Ir>,
state: &mut DowngradeState,
) -> Result<()> {
let from = if let Some(from) = inherit.from() {
let from = from.expr().unwrap().downgrade(state)?;
Some(state.new_thunk(from))
} else {
None
};
for attr in inherit.attrs() {
let ident: EcoString = match downgrade_attr(attr, state)? {
Attr::Str(ident) => ident.to_string().into(),
_ => {
return Err(Error::DowngradeError(
"dynamic attributes not allowed in inherit".to_string(),
));
}
};
let expr = from.map_or_else(
|| Var { sym: ident.clone() }.ir().ok(),
|from| {
Ok(Select {
expr: from.ir().boxed(),
attrpath: vec![Attr::Str(ident.clone())],
default: None,
}
.ir())
},
)?;
stcs.insert(ident, expr).unwrap();
}
Ok(())
}
fn downgrade_attr(attr: ast::Attr, state: &mut DowngradeState) -> Result<Attr> {
use ast::Attr::*;
use ast::InterpolPart::*;
match attr {
Ident(ident) => Ok(Attr::Str(ident.to_string().into())),
Str(string) => {
let parts = string.normalized_parts();
if parts.len() == 1 {
match parts.into_iter().next().unwrap() {
Literal(ident) => Ok(Attr::Str(ident.into())),
Interpolation(interpol) => {
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(state)?))
}
}
} else {
let parts = parts
.into_iter()
.map(|part| match part {
Literal(lit) => Const { value: lit.into() }.ir().ok(),
Interpolation(interpol) => interpol.expr().unwrap().downgrade(state),
})
.collect::<Result<Vec<_>>>()?;
Ok(Attr::Strs(ConcatStrings { parts }))
}
}
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(state)?)),
}
}
fn downgrade_attrpath(attrpath: ast::Attrpath, state: &mut DowngradeState) -> Result<Vec<Attr>> {
attrpath
.attrs()
.map(|attr| downgrade_attr(attr, state))
.collect::<Result<Vec<_>>>()
}
fn downgrade_attrpathvalue(
value: ast::AttrpathValue,
attrs: &mut Attrs,
state: &mut DowngradeState,
) -> Result<()> {
let path = downgrade_attrpath(value.attrpath().unwrap(), state)?;
let value = value.value().unwrap().downgrade(state)?;
attrs.insert(path, state.new_thunk(value).ir())
}