mirror of
https://github.com/oxalica/nil.git
synced 2025-12-23 09:19:49 +00:00
Impl Attrset lowering with desugaring
This commit is contained in:
parent
d31c62a5ad
commit
0b643ddc0d
4 changed files with 477 additions and 6 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -226,6 +226,7 @@ name = "nil"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"expect-test",
|
||||
"indexmap",
|
||||
"la-arena",
|
||||
"ordered-float",
|
||||
"rowan",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ version = "0.0.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
indexmap = "1.9.1"
|
||||
la-arena = "0.2.1"
|
||||
ordered-float = "3.0.0"
|
||||
rowan = "0.15.6"
|
||||
|
|
|
|||
435
src/def/lower.rs
435
src/def/lower.rs
|
|
@ -1,13 +1,14 @@
|
|||
use super::{
|
||||
AstPtr, Attrpath, Expr, ExprId, Literal, Module, ModuleSourceMap, NameDef, NameDefId, Pat,
|
||||
Path, PathAnchor,
|
||||
AstPtr, Attrpath, BindingKey, BindingValue, Bindings, Expr, ExprId, Literal, Module,
|
||||
ModuleSourceMap, NameDef, NameDefId, Pat, Path, PathAnchor,
|
||||
};
|
||||
use crate::base::{FileId, InFile};
|
||||
use indexmap::IndexMap;
|
||||
use la_arena::Arena;
|
||||
use rowan::ast::AstNode;
|
||||
use smol_str::SmolStr;
|
||||
use std::str;
|
||||
use syntax::ast::{self, HasStringParts, LiteralKind};
|
||||
use syntax::ast::{self, HasBindings, HasStringParts, LiteralKind};
|
||||
|
||||
pub(super) fn lower(root: InFile<ast::SourceFile>) -> (Module, ModuleSourceMap) {
|
||||
let mut ctx = LowerCtx {
|
||||
|
|
@ -146,8 +147,26 @@ impl LowerCtx {
|
|||
let elements = e.elements().map(|e| self.lower_expr(e)).collect();
|
||||
self.alloc_expr(Expr::List(elements), ptr)
|
||||
}
|
||||
ast::Expr::AttrSet(_) => todo!(),
|
||||
ast::Expr::LetIn(_) => todo!(),
|
||||
ast::Expr::LetIn(e) => {
|
||||
let mut set = MergingSet::new(true, ptr.clone());
|
||||
set.merge_bindings(self, &e);
|
||||
let bindings = set.finish(self);
|
||||
let body = self.lower_expr_opt(e.body());
|
||||
self.alloc_expr(Expr::LetIn(bindings, body), ptr)
|
||||
}
|
||||
ast::Expr::AttrSet(e) => {
|
||||
let (is_rec, ctor): (bool, fn(_) -> _) = if e.rec_token().is_some() {
|
||||
(true, Expr::Attrset)
|
||||
} else if e.let_token().is_some() {
|
||||
(true, Expr::LetAttrset)
|
||||
} else {
|
||||
(false, Expr::Attrset)
|
||||
};
|
||||
let mut set = MergingSet::new(is_rec, ptr.clone());
|
||||
set.merge_bindings(self, &e);
|
||||
let bindings = set.finish(self);
|
||||
self.alloc_expr(ctor(bindings), ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +265,246 @@ impl LowerCtx {
|
|||
.collect();
|
||||
self.alloc_expr(Expr::StringInterpolation(parts), ptr)
|
||||
}
|
||||
|
||||
fn lower_key(&mut self, is_rec: bool, attr: ast::Attr) -> BindingKey {
|
||||
let ast_string = match attr {
|
||||
ast::Attr::Name(n) if is_rec => return BindingKey::NameDef(self.lower_name(n)),
|
||||
ast::Attr::Name(n) => {
|
||||
return BindingKey::Name(
|
||||
n.token()
|
||||
.map_or_else(Default::default, |tok| tok.text().into()),
|
||||
)
|
||||
}
|
||||
ast::Attr::String(s) => s,
|
||||
ast::Attr::Dynamic(d) => {
|
||||
let mut e = d.expr();
|
||||
loop {
|
||||
match e {
|
||||
Some(ast::Expr::String(s)) => break s,
|
||||
Some(ast::Expr::Paren(p)) => e = p.expr(),
|
||||
_ => return BindingKey::Dynamic(self.lower_expr_opt(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ast_string
|
||||
.string_parts()
|
||||
.all(|part| !matches!(part, ast::StringPart::Dynamic(_)))
|
||||
{
|
||||
let ptr = AstPtr::new(ast_string.syntax());
|
||||
let content = ast_string
|
||||
.string_parts()
|
||||
.fold(String::new(), |prev, part| match part {
|
||||
ast::StringPart::Dynamic(_) => unreachable!(),
|
||||
ast::StringPart::Fragment(tok) => prev + tok.text(),
|
||||
ast::StringPart::Escape(tok) => match tok.text().as_bytes() {
|
||||
b"\\n" => prev + "\n",
|
||||
b"\\r" => prev + "\r",
|
||||
b"\\t" => prev + "\t",
|
||||
[b'\\', bytes @ ..] => {
|
||||
prev + str::from_utf8(bytes).expect("Verified by the lexer")
|
||||
}
|
||||
_ => unreachable!("Verified by the lexer"),
|
||||
},
|
||||
});
|
||||
if is_rec {
|
||||
return BindingKey::NameDef(self.alloc_name_def(content.into(), ptr));
|
||||
} else {
|
||||
return BindingKey::Name(content.into());
|
||||
}
|
||||
}
|
||||
|
||||
BindingKey::Dynamic(self.lower_string(&ast_string))
|
||||
}
|
||||
}
|
||||
|
||||
struct MergingSet {
|
||||
is_rec: bool,
|
||||
ptr: AstPtr,
|
||||
entries: IndexMap<BindingKey, MergingValue>,
|
||||
inherit_froms: Vec<ExprId>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum MergingValue {
|
||||
#[default]
|
||||
Placeholder,
|
||||
Attrset(MergingSet),
|
||||
Final(BindingValue),
|
||||
}
|
||||
|
||||
impl MergingSet {
|
||||
fn new(is_rec: bool, ptr: AstPtr) -> Self {
|
||||
Self {
|
||||
is_rec,
|
||||
ptr,
|
||||
entries: Default::default(),
|
||||
inherit_froms: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Place an orphaned Expr as dynamic-attrs for error recovery.
|
||||
// TODO: Report errors.
|
||||
fn error(&mut self, ctx: &mut LowerCtx, expr: ExprId, ptr: AstPtr) {
|
||||
let k = BindingKey::Dynamic(ctx.alloc_expr(Expr::Missing, ptr));
|
||||
let v = MergingValue::Final(BindingValue::Expr(expr));
|
||||
self.entries.insert(k, v);
|
||||
}
|
||||
|
||||
fn merge_bindings(&mut self, ctx: &mut LowerCtx, n: &impl HasBindings) {
|
||||
for b in n.bindings() {
|
||||
match b {
|
||||
ast::Binding::Inherit(i) => self.merge_inherit(ctx, i),
|
||||
ast::Binding::AttrpathValue(entry) => self.merge_path_value(ctx, entry),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_inherit(&mut self, ctx: &mut LowerCtx, i: ast::Inherit) {
|
||||
let from_expr = i.from_expr().map(|e| {
|
||||
let expr = ctx.lower_expr_opt(e.expr());
|
||||
let from_id = self.inherit_froms.len() as u32;
|
||||
self.inherit_froms.push(expr);
|
||||
from_id
|
||||
});
|
||||
|
||||
for attr in i.attrs() {
|
||||
let ptr = AstPtr::new(attr.syntax());
|
||||
let key = match ctx.lower_key(self.is_rec, attr) {
|
||||
// `inherit ${expr}` or `inherit (expr) ${expr}` is invalid.
|
||||
BindingKey::Dynamic(expr) => {
|
||||
self.error(ctx, expr, ptr.clone());
|
||||
continue;
|
||||
}
|
||||
key => key,
|
||||
};
|
||||
|
||||
// Inherited names never merge other values. It must be an error.
|
||||
if self.entries.contains_key(&key) {
|
||||
// Report error.
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = match from_expr {
|
||||
Some(id) => MergingValue::Final(BindingValue::InheritFrom(id)),
|
||||
None => {
|
||||
let name = match &key {
|
||||
BindingKey::NameDef(def) => ctx.module[*def].name.clone(),
|
||||
BindingKey::Name(s) => s.clone(),
|
||||
BindingKey::Dynamic(_) => unreachable!(),
|
||||
};
|
||||
let ref_expr = ctx.alloc_expr(Expr::Reference(name), ptr);
|
||||
MergingValue::Final(BindingValue::Inherit(ref_expr))
|
||||
}
|
||||
};
|
||||
self.entries.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_path_value(
|
||||
mut self: &mut Self,
|
||||
// &mut self,
|
||||
ctx: &mut LowerCtx,
|
||||
entry: ast::AttrpathValue,
|
||||
) {
|
||||
let mut attrs = entry.attrpath().into_iter().flat_map(|path| path.attrs());
|
||||
let mut next_attr = match attrs.next() {
|
||||
Some(first_attr) => first_attr,
|
||||
// Recover from missing Attrpath.
|
||||
None => {
|
||||
let value_expr = ctx.lower_expr_opt(entry.value());
|
||||
self.error(ctx, value_expr, AstPtr::new(entry.syntax()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let key = ctx.lower_key(self.is_rec, next_attr);
|
||||
let deep = self.entries.entry(key).or_default();
|
||||
match attrs.next() {
|
||||
Some(attr) => {
|
||||
self = deep.make_attrset(ctx, false, AstPtr::new(attr.syntax()));
|
||||
next_attr = attr;
|
||||
}
|
||||
None => {
|
||||
deep.merge_ast(ctx, entry.value());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(self, ctx: &mut LowerCtx) -> Bindings {
|
||||
Bindings {
|
||||
entries: self
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.finish(ctx)))
|
||||
.collect(),
|
||||
inherit_froms: self.inherit_froms.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MergingValue {
|
||||
fn make_attrset(&mut self, ctx: &mut LowerCtx, is_rec: bool, ptr: AstPtr) -> &mut MergingSet {
|
||||
match self {
|
||||
// TOOD: Report warnings if set.is_rec
|
||||
Self::Attrset(set) => return set,
|
||||
Self::Placeholder => *self = Self::Attrset(MergingSet::new(is_rec, ptr)),
|
||||
// We prefer to become a Attrset as a guess, which supports further merging.
|
||||
Self::Final(v) => {
|
||||
// TOOD: Report errors.
|
||||
let mut set = MergingSet::new(is_rec, ptr);
|
||||
if let BindingValue::Expr(expr) = *v {
|
||||
// If the previous one is Final, it must be from `inherit` or `key = final_expr`,
|
||||
// where the source map must already be store.
|
||||
// Value merging can only make Final -> Attrset but not the reverse.
|
||||
let prev_ptr = ctx.source_map.expr_map_rev[&expr].clone();
|
||||
set.error(ctx, expr, prev_ptr);
|
||||
}
|
||||
*self = Self::Attrset(set);
|
||||
}
|
||||
}
|
||||
match self {
|
||||
Self::Attrset(set) => set,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_ast(&mut self, ctx: &mut LowerCtx, mut e: Option<ast::Expr>) {
|
||||
loop {
|
||||
match e {
|
||||
Some(ast::Expr::Paren(p)) => e = p.expr(),
|
||||
Some(ast::Expr::AttrSet(e)) if e.let_token().is_none() => {
|
||||
return self
|
||||
.make_attrset(ctx, e.rec_token().is_some(), AstPtr::new(e.syntax()))
|
||||
.merge_bindings(ctx, &e);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
// Here we got an unmergable non-Attrset Expr.
|
||||
match self {
|
||||
Self::Placeholder => *self = Self::Final(BindingValue::Expr(ctx.lower_expr_opt(e))),
|
||||
// TODO: Report errors.
|
||||
Self::Attrset(..) | Self::Final(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(self, ctx: &mut LowerCtx) -> BindingValue {
|
||||
match self {
|
||||
Self::Placeholder => unreachable!("Should be processed"),
|
||||
Self::Final(k) => k,
|
||||
Self::Attrset(set) => {
|
||||
let ptr = set.ptr.clone();
|
||||
let bindings = set.finish(ctx);
|
||||
let expr = ctx.alloc_expr(Expr::Attrset(bindings), ptr);
|
||||
BindingValue::Expr(expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -263,6 +522,12 @@ mod tests {
|
|||
for (i, e) in module.exprs.iter() {
|
||||
writeln!(got, "{}: {:?}", i.into_raw(), e).unwrap();
|
||||
}
|
||||
if !module.name_defs.is_empty() {
|
||||
writeln!(got).unwrap();
|
||||
}
|
||||
for (i, def) in module.name_defs.iter() {
|
||||
writeln!(got, "{}: {:?}", i.into_raw(), def).unwrap();
|
||||
}
|
||||
expect.assert_eq(&got);
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +600,8 @@ mod tests {
|
|||
expect![[r#"
|
||||
0: Literal(Int(0))
|
||||
1: Lambda(Some(Idx::<NameDef>(0)), None, Idx::<Expr>(0))
|
||||
|
||||
0: NameDef { name: "a" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
|
|
@ -349,6 +616,8 @@ mod tests {
|
|||
expect![[r#"
|
||||
0: Literal(Int(0))
|
||||
1: Lambda(Some(Idx::<NameDef>(0)), Some(Pat { fields: [], ellipsis: true }), Idx::<Expr>(0))
|
||||
|
||||
0: NameDef { name: "a" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
|
|
@ -356,6 +625,8 @@ mod tests {
|
|||
expect![[r#"
|
||||
0: Literal(Int(0))
|
||||
1: Lambda(Some(Idx::<NameDef>(0)), Some(Pat { fields: [], ellipsis: false }), Idx::<Expr>(0))
|
||||
|
||||
0: NameDef { name: "a" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
|
|
@ -364,6 +635,9 @@ mod tests {
|
|||
0: Literal(Int(0))
|
||||
1: Literal(Int(0))
|
||||
2: Lambda(None, Some(Pat { fields: [(Some(Idx::<NameDef>(0)), None), (Some(Idx::<NameDef>(1)), Some(Idx::<Expr>(0)))], ellipsis: true }), Idx::<Expr>(1))
|
||||
|
||||
0: NameDef { name: "a" }
|
||||
1: NameDef { name: "b" }
|
||||
"#]],
|
||||
);
|
||||
check_lower(
|
||||
|
|
@ -372,6 +646,10 @@ mod tests {
|
|||
0: Literal(Int(0))
|
||||
1: Literal(Int(0))
|
||||
2: Lambda(Some(Idx::<NameDef>(0)), Some(Pat { fields: [(Some(Idx::<NameDef>(1)), Some(Idx::<Expr>(0))), (Some(Idx::<NameDef>(2)), None)], ellipsis: false }), Idx::<Expr>(1))
|
||||
|
||||
0: NameDef { name: "c" }
|
||||
1: NameDef { name: "a" }
|
||||
2: NameDef { name: "b" }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
@ -483,4 +761,151 @@ mod tests {
|
|||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_key_kind() {
|
||||
check_lower(
|
||||
r#"{ a = 1; ${b} = 2; "c" = 3; ${(("\n"))} = 4; }"#,
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Reference("b")
|
||||
2: Literal(Int(2))
|
||||
3: Literal(Int(3))
|
||||
4: Literal(Int(4))
|
||||
5: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(0))), (Dynamic(Idx::<Expr>(1)), Expr(Idx::<Expr>(2))), (Name("c"), Expr(Idx::<Expr>(3))), (Name("\n"), Expr(Idx::<Expr>(4)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_inherit() {
|
||||
check_lower(
|
||||
r#"{ inherit; inherit a "b" ${("c")}; inherit (d); inherit (e) f g; }"#,
|
||||
expect![[r#"
|
||||
0: Reference("a")
|
||||
1: Reference("b")
|
||||
2: Reference("c")
|
||||
3: Reference("d")
|
||||
4: Reference("e")
|
||||
5: Attrset(Bindings { entries: [(Name("a"), Inherit(Idx::<Expr>(0))), (Name("b"), Inherit(Idx::<Expr>(1))), (Name("c"), Inherit(Idx::<Expr>(2))), (Name("f"), InheritFrom(1)), (Name("g"), InheritFrom(1))], inherit_froms: [Idx::<Expr>(3), Idx::<Expr>(4)] })
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_merge_non_rec() {
|
||||
// Path and path.
|
||||
check_lower(
|
||||
"{ a.b = 1; a.c = 2; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Literal(Int(2))
|
||||
2: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(0))), (Name("c"), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
// Path and attrset.
|
||||
check_lower(
|
||||
"{ a.b = 1; a = { c = 2; }; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Literal(Int(2))
|
||||
2: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(0))), (Name("c"), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
// Attrset and path.
|
||||
check_lower(
|
||||
"{ a = { b = 1; }; a.c = 2; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Literal(Int(2))
|
||||
2: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(0))), (Name("c"), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
// Attrset and attrset.
|
||||
check_lower(
|
||||
"{ a = { b = 1; }; a = { c = 2; }; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Literal(Int(2))
|
||||
2: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(0))), (Name("c"), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_merge_rec() {
|
||||
// Rec and non-rec.
|
||||
check_lower(
|
||||
"{ a = rec { b = 1; }; a.c = 2; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Literal(Int(2))
|
||||
2: Attrset(Bindings { entries: [(NameDef(Idx::<NameDef>(0)), Expr(Idx::<Expr>(0))), (NameDef(Idx::<NameDef>(1)), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
|
||||
0: NameDef { name: "b" }
|
||||
1: NameDef { name: "c" }
|
||||
"#]],
|
||||
);
|
||||
// Non-rec and rec.
|
||||
check_lower(
|
||||
"{ a = { b = 1; }; a = rec { c = 2; }; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Literal(Int(2))
|
||||
2: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(0))), (Name("c"), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_rec_deep() {
|
||||
check_lower(
|
||||
"rec { a.b = 1; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(0)))], inherit_froms: [] })
|
||||
2: Attrset(Bindings { entries: [(NameDef(Idx::<NameDef>(0)), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
|
||||
0: NameDef { name: "a" }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_let() {
|
||||
check_lower(
|
||||
"{ a.b = let { c.d = 1; }; }",
|
||||
expect![[r#"
|
||||
0: Literal(Int(1))
|
||||
1: Attrset(Bindings { entries: [(Name("d"), Expr(Idx::<Expr>(0)))], inherit_froms: [] })
|
||||
2: LetAttrset(Bindings { entries: [(NameDef(Idx::<NameDef>(0)), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
3: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(2)))], inherit_froms: [] })
|
||||
4: Attrset(Bindings { entries: [(Name("a"), Expr(Idx::<Expr>(3)))], inherit_froms: [] })
|
||||
|
||||
0: NameDef { name: "c" }
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attrset_dynamic_no_merge() {
|
||||
check_lower(
|
||||
"{ ${a}.b = 1; ${a}.b = 2; }",
|
||||
expect![[r#"
|
||||
0: Reference("a")
|
||||
1: Literal(Int(1))
|
||||
2: Reference("a")
|
||||
3: Literal(Int(2))
|
||||
4: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(1)))], inherit_froms: [] })
|
||||
5: Attrset(Bindings { entries: [(Name("b"), Expr(Idx::<Expr>(3)))], inherit_froms: [] })
|
||||
6: Attrset(Bindings { entries: [(Dynamic(Idx::<Expr>(0)), Expr(Idx::<Expr>(4))), (Dynamic(Idx::<Expr>(2)), Expr(Idx::<Expr>(5)))], inherit_froms: [] })
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ pub enum Expr {
|
|||
Select(ExprId, Attrpath, Option<ExprId>),
|
||||
StringInterpolation(Box<[ExprId]>),
|
||||
List(Box<[ExprId]>),
|
||||
// TODO
|
||||
LetIn(Bindings, ExprId),
|
||||
Attrset(Bindings),
|
||||
LetAttrset(Bindings),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
|
|
@ -147,6 +149,13 @@ impl Expr {
|
|||
}
|
||||
}
|
||||
Self::StringInterpolation(xs) | Self::List(xs) => xs.iter().copied().for_each(f),
|
||||
Self::LetIn(bindings, body) => {
|
||||
bindings.walk_child_exprs(&mut f);
|
||||
f(*body);
|
||||
}
|
||||
Self::Attrset(bindings) | Self::LetAttrset(bindings) => {
|
||||
bindings.walk_child_exprs(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -193,3 +202,38 @@ pub struct Pat {
|
|||
}
|
||||
|
||||
pub type Attrpath = Box<[ExprId]>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Bindings {
|
||||
pub entries: Box<[(BindingKey, BindingValue)]>,
|
||||
pub inherit_froms: Box<[ExprId]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum BindingKey {
|
||||
NameDef(NameDefId),
|
||||
Name(SmolStr),
|
||||
Dynamic(ExprId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BindingValue {
|
||||
Inherit(ExprId),
|
||||
InheritFrom(u32),
|
||||
Expr(ExprId),
|
||||
}
|
||||
|
||||
impl Bindings {
|
||||
pub(crate) fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
||||
for (key, kind) in self.entries.iter() {
|
||||
match key {
|
||||
BindingKey::NameDef(_) | BindingKey::Name(_) => {}
|
||||
&BindingKey::Dynamic(expr) => f(expr),
|
||||
}
|
||||
match *kind {
|
||||
BindingValue::Inherit(e) | BindingValue::Expr(e) => f(e),
|
||||
BindingValue::InheritFrom(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue