use rnix::ast::{self, AstToken, HasEntry};
use rowan::ast::AstNode;
use crate::ir::{AttrName, Binding, ExprId, Ir, IrBound, IrDynamicBinding, IrGroupPlan, IrStaticBinding, Param, PathKind, PathPart, PatternEntry, PlanId, Program, Span, StrPart};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LowerError {
#[error("source failed to parse: {message}")]
ParseFailure { message: String },
#[error("parse-error node in the AST at bytes {start}..{end}")]
ParseErrorNode { start: u32, end: u32 },
#[error("`{construct}` node is missing its `{field}`")]
Missing {
construct: &'static str,
field: &'static str,
},
#[error("binding plan references an unlowered node at bytes {start}..{end}")]
PlanUnresolved { start: u32, end: u32 },
#[error("integer literal `{text}` does not fit i64")]
IntOutOfRange { text: String },
#[error("float literal `{text}` does not parse as f64")]
BadFloat { text: String },
}
pub fn lower_file(src: &str) -> Result<Program, LowerError> {
let parse = rnix::Root::parse(src);
if let Some(err) = parse.errors().first() {
return Err(LowerError::ParseFailure {
message: err.to_string(),
});
}
let root = parse.tree();
let expr = root.expr().ok_or(LowerError::Missing {
construct: "Root",
field: "expr",
})?;
lower(&expr)
}
pub fn lower(expr: &ast::Expr) -> Result<Program, LowerError> {
let mut lo = Lowerer {
exprs: Vec::new(),
spans: Vec::new(),
by_range: rustc_hash::FxHashMap::default(),
attr_by_range: rustc_hash::FxHashMap::default(),
plans: Vec::new(),
};
let root = lo.lower_expr(expr)?;
debug_assert_eq!(root.index() + 1, lo.exprs.len());
Ok(Program {
exprs: lo.exprs,
spans: lo.spans,
root,
plans: lo.plans,
})
}
struct Lowerer {
exprs: Vec<Ir>,
spans: Vec<Span>,
by_range: rustc_hash::FxHashMap<(u32, u32), ExprId>,
attr_by_range: rustc_hash::FxHashMap<(u32, u32), AttrName>,
plans: Vec<IrGroupPlan>,
}
fn span_of(node: &rnix::SyntaxNode) -> Span {
let r = node.text_range();
Span {
start: u32::from(r.start()),
end: u32::from(r.end()),
}
}
fn ident_text(ident: &ast::Ident) -> String {
match ident.ident_token() {
Some(tok) => tok.text().to_string(),
None => ident.syntax().text().to_string(),
}
}
impl Lowerer {
fn push(&mut self, ir: Ir, node: &rnix::SyntaxNode) -> ExprId {
let id = ExprId(u32::try_from(self.exprs.len()).expect("program exceeds u32 exprs"));
let sp = span_of(node);
self.exprs.push(ir);
self.spans.push(sp);
self.by_range.insert((sp.start, sp.end), id);
id
}
fn expr_id_of(&self, node: &ast::Expr) -> Result<ExprId, LowerError> {
let r = node.syntax().text_range();
let key = (u32::from(r.start()), u32::from(r.end()));
self.by_range
.get(&key)
.copied()
.ok_or(LowerError::PlanUnresolved {
start: key.0,
end: key.1,
})
}
fn lower_bound(&mut self, b: &sui_normalize::Binding) -> Result<IrBound, LowerError> {
Ok(match b {
sui_normalize::Binding::Leaf(e) => IrBound::Expr(self.expr_id_of(e)?),
sui_normalize::Binding::Group(sub) => IrBound::Group(self.lower_plan(sub)?),
sui_normalize::Binding::Inherit => IrBound::Inherit,
sui_normalize::Binding::InheritFrom { from } => IrBound::InheritFrom { from: *from },
})
}
fn lower_plan(&mut self, plan: &sui_normalize::GroupPlan) -> Result<PlanId, LowerError> {
let mut statics = Vec::with_capacity(plan.statics.len());
for b in &plan.statics {
statics.push(IrStaticBinding {
name: b.name,
value: self.lower_bound(&b.binding)?,
});
}
let mut dynamics = Vec::with_capacity(plan.dynamics.len());
for d in &plan.dynamics {
let kr = d.key.syntax().text_range();
let kk = (u32::from(kr.start()), u32::from(kr.end()));
let key = self
.attr_by_range
.get(&kk)
.cloned()
.or_else(|| {
d.key.syntax().parent().and_then(|par| {
let pr = par.text_range();
self.attr_by_range
.get(&(u32::from(pr.start()), u32::from(pr.end())))
.cloned()
})
})
.ok_or(LowerError::PlanUnresolved {
start: kk.0,
end: kk.1,
})?;
dynamics.push(IrDynamicBinding {
key,
value: self.lower_bound(&d.value)?,
});
}
let mut inherit_froms = Vec::with_capacity(plan.inherit_froms.len());
for e in &plan.inherit_froms {
inherit_froms.push(self.expr_id_of(e)?);
}
let id = PlanId(u32::try_from(self.plans.len()).expect("program exceeds u32 plans"));
self.plans.push(IrGroupPlan {
recursive: plan.recursive,
statics,
dynamics,
inherit_froms,
});
Ok(id)
}
fn record_plan<N: ast::HasEntry>(
&mut self,
node: &N,
recursive: bool,
id: ExprId,
) -> Result<(), LowerError> {
if let Ok(Some(plan)) = sui_normalize::plan_for_group(node, recursive) {
let pid = self.lower_plan(&plan)?;
match &mut self.exprs[id.index()] {
Ir::AttrSet { plan, .. } | Ir::LetIn { plan, .. } => *plan = pid,
other => unreachable!("record_plan on a non-binder: {other:?}"),
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
fn lower_expr(&mut self, expr: &ast::Expr) -> Result<ExprId, LowerError> {
match expr {
ast::Expr::Literal(lit) => self.lower_literal(lit),
ast::Expr::Ident(ident) => {
let sym = sui_intern::intern(&ident_text(ident));
Ok(self.push(Ir::Ident(sym), ident.syntax()))
}
ast::Expr::Str(st) => {
let parts = self.lower_str_parts(st)?;
Ok(self.push(Ir::Str(parts), st.syntax()))
}
ast::Expr::PathAbs(p) => {
let parts = self.lower_path_parts(&p.parts())?;
Ok(self.push(
Ir::Path {
kind: PathKind::Abs,
parts,
},
p.syntax(),
))
}
ast::Expr::PathRel(p) => {
let parts = self.lower_path_parts(&p.parts())?;
Ok(self.push(
Ir::Path {
kind: PathKind::Rel,
parts,
},
p.syntax(),
))
}
ast::Expr::PathHome(p) => {
let parts = self.lower_path_parts(&p.parts())?;
Ok(self.push(
Ir::Path {
kind: PathKind::Home,
parts,
},
p.syntax(),
))
}
ast::Expr::PathSearch(p) => {
let content = p.content().ok_or(LowerError::Missing {
construct: "PathSearch",
field: "content",
})?;
Ok(self.push(Ir::SearchPath(content.text().to_string()), p.syntax()))
}
ast::Expr::Select(sel) => {
let subject_ast = sel.expr().ok_or(LowerError::Missing {
construct: "Select",
field: "expr",
})?;
let subject = self.lower_expr(&subject_ast)?;
let attrpath = sel.attrpath().ok_or(LowerError::Missing {
construct: "Select",
field: "attrpath",
})?;
let path = self.lower_attrpath(&attrpath)?;
let or_default = match sel.default_expr() {
Some(d) => Some(self.lower_expr(&d)?),
None => None,
};
Ok(self.push(
Ir::Select {
subject,
path,
or_default,
},
sel.syntax(),
))
}
ast::Expr::HasAttr(ha) => {
let subject_ast = ha.expr().ok_or(LowerError::Missing {
construct: "HasAttr",
field: "expr",
})?;
let subject = self.lower_expr(&subject_ast)?;
let attrpath = ha.attrpath().ok_or(LowerError::Missing {
construct: "HasAttr",
field: "attrpath",
})?;
let path = self.lower_attrpath(&attrpath)?;
Ok(self.push(Ir::HasAttr { subject, path }, ha.syntax()))
}
ast::Expr::Apply(app) => {
let func_ast = app.lambda().ok_or(LowerError::Missing {
construct: "Apply",
field: "lambda",
})?;
let func = self.lower_expr(&func_ast)?;
let arg_ast = app.argument().ok_or(LowerError::Missing {
construct: "Apply",
field: "argument",
})?;
let arg = self.lower_expr(&arg_ast)?;
Ok(self.push(Ir::Apply { func, arg }, app.syntax()))
}
ast::Expr::Lambda(lam) => {
let param_ast = lam.param().ok_or(LowerError::Missing {
construct: "Lambda",
field: "param",
})?;
let param = self.lower_param(¶m_ast)?;
let body_ast = lam.body().ok_or(LowerError::Missing {
construct: "Lambda",
field: "body",
})?;
let body = self.lower_expr(&body_ast)?;
Ok(self.push(Ir::Lambda { param, body }, lam.syntax()))
}
ast::Expr::LetIn(li) => {
let bindings = self.lower_entries(li)?;
let body_ast = li.body().ok_or(LowerError::Missing {
construct: "LetIn",
field: "body",
})?;
let body = self.lower_expr(&body_ast)?;
let id = self.push(
Ir::LetIn {
bindings,
body,
plan: PlanId::NONE,
},
li.syntax(),
);
self.record_plan(li, true, id)?;
Ok(id)
}
ast::Expr::LegacyLet(ll) => {
let bindings = self.lower_entries(ll)?;
let id = self.push(Ir::LegacyLet { bindings }, ll.syntax());
self.record_plan(ll, true, id)?;
Ok(id)
}
ast::Expr::AttrSet(set) => {
let rec = set.rec_token().is_some();
let bindings = self.lower_entries(set)?;
let id = self.push(
Ir::AttrSet {
rec,
bindings,
plan: PlanId::NONE,
},
set.syntax(),
);
self.record_plan(set, rec, id)?;
Ok(id)
}
ast::Expr::List(list) => {
let mut items = Vec::new();
for item in list.items() {
items.push(self.lower_expr(&item)?);
}
Ok(self.push(Ir::List(items), list.syntax()))
}
ast::Expr::BinOp(bo) => {
let lhs_ast = bo.lhs().ok_or(LowerError::Missing {
construct: "BinOp",
field: "lhs",
})?;
let lhs = self.lower_expr(&lhs_ast)?;
let rhs_ast = bo.rhs().ok_or(LowerError::Missing {
construct: "BinOp",
field: "rhs",
})?;
let rhs = self.lower_expr(&rhs_ast)?;
let op = bo.operator().ok_or(LowerError::Missing {
construct: "BinOp",
field: "operator",
})?;
Ok(self.push(
Ir::BinOp {
op: op.into(),
lhs,
rhs,
},
bo.syntax(),
))
}
ast::Expr::UnaryOp(uo) => {
let op = uo.operator().ok_or(LowerError::Missing {
construct: "UnaryOp",
field: "operator",
})?;
let inner_ast = uo.expr().ok_or(LowerError::Missing {
construct: "UnaryOp",
field: "expr",
})?;
let inner = self.lower_expr(&inner_ast)?;
Ok(self.push(
Ir::UnaryOp {
op: op.into(),
expr: inner,
},
uo.syntax(),
))
}
ast::Expr::IfElse(ie) => {
let cond_ast = ie.condition().ok_or(LowerError::Missing {
construct: "IfElse",
field: "condition",
})?;
let condition = self.lower_expr(&cond_ast)?;
let then_ast = ie.body().ok_or(LowerError::Missing {
construct: "IfElse",
field: "body",
})?;
let then_body = self.lower_expr(&then_ast)?;
let else_ast = ie.else_body().ok_or(LowerError::Missing {
construct: "IfElse",
field: "else_body",
})?;
let else_body = self.lower_expr(&else_ast)?;
Ok(self.push(
Ir::IfElse {
condition,
then_body,
else_body,
},
ie.syntax(),
))
}
ast::Expr::With(w) => {
let ns_ast = w.namespace().ok_or(LowerError::Missing {
construct: "With",
field: "namespace",
})?;
let namespace = self.lower_expr(&ns_ast)?;
let body_ast = w.body().ok_or(LowerError::Missing {
construct: "With",
field: "body",
})?;
let body = self.lower_expr(&body_ast)?;
Ok(self.push(Ir::With { namespace, body }, w.syntax()))
}
ast::Expr::Assert(a) => {
let cond_ast = a.condition().ok_or(LowerError::Missing {
construct: "Assert",
field: "condition",
})?;
let condition = self.lower_expr(&cond_ast)?;
let body_ast = a.body().ok_or(LowerError::Missing {
construct: "Assert",
field: "body",
})?;
let body = self.lower_expr(&body_ast)?;
Ok(self.push(Ir::Assert { condition, body }, a.syntax()))
}
ast::Expr::Paren(p) => {
let inner_ast = p.expr().ok_or(LowerError::Missing {
construct: "Paren",
field: "expr",
})?;
let inner = self.lower_expr(&inner_ast)?;
Ok(self.push(Ir::Paren(inner), p.syntax()))
}
ast::Expr::Root(r) => {
let inner = r.expr().ok_or(LowerError::Missing {
construct: "Root",
field: "expr",
})?;
self.lower_expr(&inner)
}
ast::Expr::CurPos(cp) => Ok(self.push(Ir::CurPos, cp.syntax())),
ast::Expr::Error(e) => {
let s = span_of(e.syntax());
Err(LowerError::ParseErrorNode {
start: s.start,
end: s.end,
})
}
}
}
fn lower_literal(&mut self, lit: &ast::Literal) -> Result<ExprId, LowerError> {
match lit.kind() {
ast::LiteralKind::Integer(i) => match i.value() {
Ok(v) => Ok(self.push(Ir::Int(v), lit.syntax())),
Err(_) => Err(LowerError::IntOutOfRange {
text: i.syntax().text().to_string(),
}),
},
ast::LiteralKind::Float(f) => match f.value() {
Ok(v) => Ok(self.push(Ir::Float(v), lit.syntax())),
Err(_) => Err(LowerError::BadFloat {
text: f.syntax().text().to_string(),
}),
},
ast::LiteralKind::Uri(u) => {
Ok(self.push(Ir::Uri(u.syntax().text().to_string()), lit.syntax()))
}
}
}
fn lower_str_parts(&mut self, st: &ast::Str) -> Result<Vec<StrPart>, LowerError> {
let mut parts = Vec::new();
for part in st.normalized_parts() {
match part {
ast::InterpolPart::Literal(text) => parts.push(StrPart::Literal(text)),
ast::InterpolPart::Interpolation(interp) => {
let inner = interp.expr().ok_or(LowerError::Missing {
construct: "Interpol",
field: "expr",
})?;
parts.push(StrPart::Interp(self.lower_expr(&inner)?));
}
}
}
Ok(parts)
}
fn lower_path_parts(
&mut self,
raw: &[ast::InterpolPart<ast::PathContent>],
) -> Result<Vec<PathPart>, LowerError> {
let mut parts = Vec::new();
for part in raw {
match part {
ast::InterpolPart::Literal(content) => {
parts.push(PathPart::Literal(content.text().to_string()));
}
ast::InterpolPart::Interpolation(interp) => {
let inner = interp.expr().ok_or(LowerError::Missing {
construct: "Interpol",
field: "expr",
})?;
parts.push(PathPart::Interp(self.lower_expr(&inner)?));
}
}
}
Ok(parts)
}
fn lower_attr(&mut self, attr: &ast::Attr) -> Result<AttrName, LowerError> {
let name = self.lower_attr_inner(attr)?;
let r = attr.syntax().text_range();
self.attr_by_range
.insert((u32::from(r.start()), u32::from(r.end())), name.clone());
Ok(name)
}
fn lower_attr_inner(&mut self, attr: &ast::Attr) -> Result<AttrName, LowerError> {
match attr {
ast::Attr::Ident(ident) => {
Ok(AttrName::Ident(sui_intern::intern(&ident_text(ident))))
}
ast::Attr::Str(st) => Ok(AttrName::Str(self.lower_str_parts(st)?)),
ast::Attr::Dynamic(dy) => {
let inner = dy.expr().ok_or(LowerError::Missing {
construct: "Dynamic",
field: "expr",
})?;
Ok(AttrName::Dynamic(self.lower_expr(&inner)?))
}
}
}
fn lower_attrpath(&mut self, path: &ast::Attrpath) -> Result<Vec<AttrName>, LowerError> {
let mut out = Vec::new();
for attr in path.attrs() {
out.push(self.lower_attr(&attr)?);
}
if out.is_empty() {
return Err(LowerError::Missing {
construct: "Attrpath",
field: "attrs",
});
}
Ok(out)
}
fn lower_entries<N: HasEntry>(&mut self, node: &N) -> Result<Vec<Binding>, LowerError> {
let mut out = Vec::new();
for entry in node.entries() {
match entry {
ast::Entry::AttrpathValue(apv) => {
let attrpath = apv.attrpath().ok_or(LowerError::Missing {
construct: "AttrpathValue",
field: "attrpath",
})?;
let path = self.lower_attrpath(&attrpath)?;
let value_ast = apv.value().ok_or(LowerError::Missing {
construct: "AttrpathValue",
field: "value",
})?;
let value = self.lower_expr(&value_ast)?;
out.push(Binding::Path { path, value });
}
ast::Entry::Inherit(inh) => {
let from = match inh.from() {
Some(f) => {
let inner = f.expr().ok_or(LowerError::Missing {
construct: "InheritFrom",
field: "expr",
})?;
Some(self.lower_expr(&inner)?)
}
None => None,
};
let mut attrs = Vec::new();
for attr in inh.attrs() {
attrs.push(self.lower_attr(&attr)?);
}
out.push(Binding::Inherit { from, attrs });
}
}
}
Ok(out)
}
fn lower_param(&mut self, param: &ast::Param) -> Result<Param, LowerError> {
match param {
ast::Param::IdentParam(ip) => {
let ident = ip.ident().ok_or(LowerError::Missing {
construct: "IdentParam",
field: "ident",
})?;
Ok(Param::Ident(sui_intern::intern(&ident_text(&ident))))
}
ast::Param::Pattern(pat) => {
let mut entries = Vec::new();
for pe in pat.pat_entries() {
let ident = pe.ident().ok_or(LowerError::Missing {
construct: "PatEntry",
field: "ident",
})?;
let name = sui_intern::intern(&ident_text(&ident));
let default = match pe.default() {
Some(d) => Some(self.lower_expr(&d)?),
None => None,
};
entries.push(PatternEntry { name, default });
}
let ellipsis = pat.ellipsis_token().is_some();
let bind = match pat.pat_bind() {
Some(pb) => {
let ident = pb.ident().ok_or(LowerError::Missing {
construct: "PatBind",
field: "ident",
})?;
Some(sui_intern::intern(&ident_text(&ident)))
}
None => None,
};
Ok(Param::Pattern {
entries,
ellipsis,
bind,
})
}
}
}
}