@@ 0,0 1,1049 @@
+//! High-level intermediate representation, basically what you get after immediate parsing and
+//! where all the typechecking and other kinds of checking happen. Slightly simplified over the
+//! AST -- basically the result of a lowering/desugaring pass. This might give us a nice place to
+//! do other simple optimization-like things like constant folding, dead code detection, etc.
+//!
+//! Currently it's not really lowered by much! If's and maybe loops or something.
+//! It's mostly a layer of indirection for further stuff to happen to, so we can change
+//! the parser without changing the typechecking and codegen.
+//!
+//! This is an EXPERIMENT for having our IR all shoved down into a single vec
+//! with everything in it just arrays.
+
+use std::fmt;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use crate::ast;
+pub use crate::ast::{BOp, IfCase, Literal, Signature, UOp};
+use crate::*;
+
+/// Expression ID. Used for associating individual expressions to types, and whatever
+/// other metadata we feel like. That way we can keep that data in tables instead of
+/// having to mutate the HIR tree.
+/// The only guarantees the number offers is it will be unique.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Eid(usize);
+
+/// Slightly less ugly horrible atomic global for storing index values for
+/// expression ID's, so each expression gets a unique ID.
+static NEXT_EID: AtomicUsize = AtomicUsize::new(0);
+
+impl Eid {
+ fn new() -> Self {
+ // We may be able to use Ordering::Relaxed here since we don't
+ // care what the value actually is, just that it increments
+ // atomically. Eh, it's fine.
+ let current = NEXT_EID.fetch_add(1, Ordering::AcqRel);
+ // Check for overflow, since fetch_add can't do it.
+ // It returns the previous value, so if the next value
+ // is < than it, we've overflowed.
+ if (current.wrapping_add(1)) < current {
+ panic!("Integer overflow incrementing Eid; is your program absolutely enormous?");
+ }
+ Eid(current)
+ }
+}
+
+/// Expression index to an expr node in our flat list thing.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct EIdx(usize);
+
+/// An expression node with a unique ID attached.
+/// Expressions are assigned types by the typechecker
+/// via an `Eid`->type map.
+#[derive(Debug, Clone)]
+pub struct ExprNode {
+ /// expression
+ pub e: Expr,
+ /// Expression ID
+ pub id: Eid,
+ /// Whether or not the expression is constant,
+ /// ie can be entirely evaluated at compile time.
+ pub is_const: bool,
+}
+
+impl PartialEq for ExprNode {
+ /// ExprNode's must ignore the Eid for equality purposes,
+ /// otherwise they will just *never* be equal.
+ ///
+ /// If you need to compare ExprNode including Eid's, then
+ /// write a new function. (And then you might as well *only*
+ /// compare the Eid's unless you are purposefully testing for
+ /// ill-constructed nodes.)
+ fn eq(&self, other: &Self) -> bool {
+ self.e == other.e
+ }
+}
+
+impl ExprNode {
+ pub fn write(&self, indent: usize, f: &mut dyn fmt::Write) -> fmt::Result {
+ // TODO: We can make this take a callback that gets called for each
+ // expr node, which prints out types for each node. Will make the
+ // formatting even more horrible though.
+ self.e.write(indent, f)?;
+ Ok(())
+ }
+}
+
+/*
+impl fmt::Display for Decl {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use crate::hir2::Decl as D;
+ match self {
+ D::Function {
+ name,
+ signature,
+ body,
+ } => {
+ writeln!(f, "fn {}{} =", name, signature.to_name())?;
+ for e in body {
+ e.write(1, f)?;
+ }
+ writeln!(f, "\nend")?;
+ }
+
+ D::Const {
+ name,
+ typ: typename,
+ init,
+ } => {
+ write!(f, "const {}: {} = ", name, typename.get_name())?;
+ init.write(1, f)?;
+ writeln!(f)?;
+ }
+ D::TypeDef {
+ name,
+ typedecl,
+ params,
+ } => {
+ writeln!(f, "type {}({:?}) = {}", name, params, typedecl.get_name())?;
+ }
+ D::Import { name, localname } => {
+ writeln!(f, "import {} as {}", name, localname)?;
+ }
+ }
+ Ok(())
+ }
+}
+ */
+
+impl ExprNode {
+ pub fn new(e: Expr) -> Self {
+ ExprNode {
+ e: e,
+ id: Eid::new(),
+ is_const: false,
+ }
+ }
+
+ /// Call the function on the contents
+ /// and turn the result into a new ExprNode *without*
+ /// changing our Eid.
+ ///
+ /// Consumes self to preserve the invariant that no
+ /// Eid's are ever duplicated.
+ pub fn map(self, f: &mut dyn FnMut(Expr) -> Expr) -> Self {
+ ExprNode {
+ e: f(self.e),
+ ..self
+ }
+ }
+
+ /*
+ /// Apply a function with extra state to the given node.
+ /// Unneeded so far, see passes::expr_fold()
+ pub fn fold<S>(self, state: S, f: &dyn Fn(Expr, S) -> (Expr, S)) -> (Self, S) {
+ let (new_e, new_s) = f(*self.e, state);
+ (
+ ExprNode {
+ e: Box::new(new_e),
+ id: self.id,
+ },
+ new_s,
+ )
+ }
+ */
+}
+
+/// An expression.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Expr {
+ Lit {
+ val: Literal,
+ },
+ Var {
+ name: Sym,
+ },
+ BinOp {
+ op: BOp,
+ lhs: EIdx,
+ rhs: EIdx,
+ },
+ UniOp {
+ op: UOp,
+ rhs: EIdx,
+ },
+ Block {
+ body: Vec<EIdx>,
+ },
+ Let {
+ varname: Sym,
+ typename: Option<Type>,
+ init: EIdx,
+ mutable: bool,
+ },
+ If {
+ cases: Vec<(EIdx, Vec<EIdx>)>,
+ },
+ Loop {
+ body: Vec<EIdx>,
+ },
+ Lambda {
+ signature: Signature,
+ body: Vec<EIdx>,
+ },
+ Funcall {
+ func: EIdx,
+ params: Vec<EIdx>,
+ type_params: Vec<Type>,
+ },
+ Break,
+ Return {
+ retval: EIdx,
+ },
+ TupleCtor {
+ body: Vec<EIdx>,
+ },
+ StructCtor {
+ body: Vec<(Sym, EIdx)>,
+ },
+ ArrayCtor {
+ body: Vec<EIdx>,
+ },
+ EnumCtor {
+ name: Sym,
+ variant: Sym,
+ value: i32,
+ },
+ // Create a new sum type.
+ // Like EnumCtor, this is generated for you by the
+ // compiler.
+ SumCtor {
+ name: Sym,
+ variant: Sym,
+ body: EIdx,
+ },
+ // Wrap a new type.
+ // Like EnumCtor, this is generated by the compiler.
+ TypeCtor {
+ name: Sym,
+ type_params: Vec<Type>,
+ body: EIdx,
+ },
+ // Opposite of TypeCtor
+ TypeUnwrap {
+ expr: EIdx,
+ },
+ TupleRef {
+ expr: EIdx,
+ elt: usize,
+ },
+ StructRef {
+ expr: EIdx,
+ elt: Sym,
+ },
+ ArrayRef {
+ expr: EIdx,
+ idx: EIdx,
+ },
+ Assign {
+ lhs: EIdx,
+ rhs: EIdx,
+ },
+ Typecast {
+ e: EIdx,
+ to: Type,
+ },
+ Deref {
+ expr: EIdx,
+ },
+ Ref {
+ expr: EIdx,
+ },
+}
+
+impl Expr {
+ /// Shortcut function for making literal bools
+ pub fn bool(ir: &mut Ir, b: bool) -> EIdx {
+ ir.insert(Self::Lit {
+ val: Literal::Bool(b),
+ })
+ }
+
+ /// Shortcut function for making literal integers
+ pub fn int(ir: &mut Ir, i: i128) -> EIdx {
+ ir.insert(Self::Lit {
+ val: Literal::Integer(i),
+ })
+ }
+
+ /// Shortcut function for making literal unit
+ pub fn unit(ir: &mut Ir) -> EIdx {
+ ir.insert(Self::TupleCtor { body: vec![] })
+ }
+
+ pub fn write(&self, indent: usize, f: &mut dyn fmt::Write) -> fmt::Result {
+ todo!()
+ /*
+ use Expr::*;
+ for _ in 0..(indent * 2) {
+ write!(f, " ")?;
+ }
+ match self {
+ Lit { val } => {
+ write!(f, "{}", val)?;
+ }
+ Var { name } => write!(f, "{}", name)?,
+ BinOp { op, lhs, rhs } => {
+ write!(f, "({:?} ", op)?;
+ lhs.write(indent, f)?;
+ rhs.write(indent, f)?;
+ writeln!(f, ")")?;
+ }
+ UniOp { op, rhs } => {
+ write!(f, "({:?} ", op)?;
+ rhs.write(indent, f)?;
+ writeln!(f, ")")?;
+ }
+ Block { body } => {
+ writeln!(f, "(block")?;
+ for b in body {
+ b.write(indent + 1, f)?;
+ }
+ writeln!(f, ")")?;
+ }
+ Let {
+ varname,
+ typename,
+ init,
+ mutable,
+ } => {
+ let m = if *mutable { " mut" } else { "" };
+ let type_str = typename
+ .as_ref()
+ .map(|inner| inner.get_name())
+ .unwrap_or(Cow::from(""));
+ write!(f, "(let {}{} {} = ", &*varname.val(), m, type_str)?;
+ init.write(0, f)?;
+ writeln!(f, ")")?;
+ }
+ If { cases } => {
+ writeln!(f, "(if")?;
+ for (case, arm) in cases {
+ case.write(indent + 1, f)?;
+ for expr in arm {
+ expr.write(indent + 2, f)?;
+ }
+ }
+ writeln!(f, ")")?;
+ }
+ Loop { body } => {
+ writeln!(f, "(loop")?;
+ for b in body {
+ b.write(indent + 1, f)?;
+ }
+ writeln!(f, ")")?;
+ }
+ Lambda { signature, body } => {
+ writeln!(f, "(lambda {}", signature.to_name())?;
+ for b in body {
+ b.write(indent + 1, f)?;
+ }
+ writeln!(f, ")")?;
+ }
+ Funcall {
+ func,
+ params,
+ type_params,
+ } => {
+ write!(f, "(funcall ")?;
+ func.write(0, f)?;
+ for b in params {
+ b.write(indent + 1, f)?;
+ write!(f, " ")?;
+ }
+ write!(f, "|")?;
+ for ty in type_params {
+ write!(f, "{},", ty.get_name())?;
+ }
+ write!(f, ")")?;
+ }
+ Break => {
+ writeln!(f, "(break)")?;
+ }
+ Return { retval } => {
+ write!(f, "(return ")?;
+ retval.write(0, f)?;
+ writeln!(f)?;
+ }
+ EnumCtor {
+ name,
+ variant,
+ value,
+ } => {
+ write!(f, "(enumval {} {} {})", name, &*variant.val(), value)?;
+ }
+ TupleCtor { body } => {
+ write!(f, "(tuple ")?;
+ for b in body {
+ b.write(0, f)?;
+ write!(f, " ")?;
+ }
+ write!(f, ")")?;
+ }
+ StructCtor { body } => {
+ writeln!(f, "(struct")?;
+ for (nm, expr) in body {
+ write!(f, "{} = ", &*nm.val())?;
+ expr.write(0, f)?;
+ writeln!(f)?;
+ }
+ writeln!(f, ")")?;
+ }
+ ArrayCtor { body } => {
+ writeln!(f, "(array")?;
+ for b in body {
+ b.write(indent + 1, f)?;
+ }
+ write!(f, ")")?;
+ }
+ SumCtor {
+ name,
+ variant,
+ body,
+ } => {
+ write!(f, "(sum {} {} ", name, &*variant.val())?;
+ body.write(0, f)?;
+ write!(f, ")")?;
+ }
+ TypeCtor {
+ name,
+ type_params: _,
+ body,
+ } => {
+ // TODO: type_params
+ write!(f, "(typector {} ", name)?;
+ body.write(0, f)?;
+ write!(f, ")")?;
+ }
+ TypeUnwrap { expr } => {
+ write!(f, "(typeunwrap ")?;
+ expr.write(0, f)?;
+ write!(f, ")")?;
+ }
+ TupleRef { expr, elt } => {
+ write!(f, "(tupleref ")?;
+ expr.write(0, f)?;
+ write!(f, " {})", elt)?;
+ }
+ StructRef { expr, elt } => {
+ write!(f, "(structref ")?;
+ expr.write(0, f)?;
+ write!(f, " {})", elt)?;
+ }
+ ArrayRef { expr: e, idx } => {
+ write!(f, "(arrayref ")?;
+ e.write(0, f)?;
+ write!(f, " ")?;
+ idx.write(0, f)?;
+ write!(f, ")")?;
+ }
+ Assign { lhs, rhs } => {
+ write!(f, "(assign ")?;
+ lhs.write(0, f)?;
+ write!(f, " ")?;
+ rhs.write(0, f)?;
+ write!(f, ")")?;
+ }
+ Typecast { e, to } => {
+ write!(f, "(cast {} ", to.get_name())?;
+ e.write(0, f)?;
+ write!(f, ")")?;
+ }
+ Deref { expr } => {
+ write!(f, "(ref ")?;
+ expr.write(0, f)?;
+ write!(f, ")")?;
+ }
+ Ref { expr } => {
+ write!(f, "(ref ")?;
+ expr.write(0, f)?;
+ write!(f, ")")?;
+ }
+ }
+ Ok(())
+ */
+ }
+}
+
+impl ExprNode {
+ /// Shortcut function for making literal bools
+ pub fn bool(b: bool) -> Self {
+ Self::new(Expr::Lit {
+ val: Literal::Bool(b),
+ })
+ }
+
+ /// Shortcut function for making literal integers
+ pub fn int(i: i128) -> Self {
+ Self::new(Expr::Lit {
+ val: Literal::Integer(i),
+ })
+ }
+
+ /// Shortcut function for making literal unit
+ pub fn unit() -> Self {
+ Self::new(Expr::TupleCtor { body: vec![] })
+ }
+}
+/// A top-level declaration in the source file.
+/// Like ExprNode, contains a type annotation.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Decl {
+ Function {
+ name: Sym,
+ signature: Signature,
+ body: Vec<EIdx>,
+ },
+ Const {
+ name: Sym,
+ typ: Type,
+ init: EIdx,
+ },
+ TypeDef {
+ name: Sym,
+ params: Vec<Sym>,
+ typedecl: Type,
+ },
+ Import {
+ name: Sym,
+ localname: Sym,
+ },
+}
+
+/// A compilable chunk of IR.
+///
+/// Currently, basically a compilation unit.
+#[derive(Debug, Clone, Default)]
+pub struct Ir {
+ pub decls: Vec<Decl>,
+ pub exprs: Vec<ExprNode>,
+ pub filename: String,
+ pub modulename: String,
+}
+
+impl Ir {
+ fn new(filename: &str, modulename: &str) -> Self {
+ Ir {
+ decls: vec![],
+ exprs: vec![],
+ filename: filename.to_owned(),
+ modulename: modulename.to_owned(),
+ }
+ }
+ fn insert(&mut self, expr: Expr) -> EIdx {
+ let en = ExprNode::new(expr);
+ self.exprs.push(en);
+ EIdx(self.exprs.len() - 1)
+ }
+
+ fn get(&self, eidx: EIdx) -> &ExprNode {
+ self.exprs
+ .get(eidx.0)
+ .expect("Invalid expr idx, should never happen!")
+ }
+}
+
+impl fmt::Display for Ir {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ todo!()
+ /*
+ for decl in &self.decls {
+ write!(f, "{}", decl)?;
+ }
+ Ok(())
+ */
+ }
+}
+
+/// Transforms AST into HIR
+pub fn lower(ast: &ast::Ast) -> Ir {
+ let mut ir = Ir {
+ decls: vec![],
+ exprs: vec![],
+ filename: ast.filename.clone(),
+ modulename: ast.modulename.clone(),
+ };
+ for d in ast.decls.iter() {
+ lower_decl(&mut ir, d)
+ }
+
+ ir
+}
+
+fn lower_lit(lit: &ast::Literal) -> Literal {
+ lit.clone()
+}
+
+fn lower_bop(bop: &ast::BOp) -> BOp {
+ *bop
+}
+
+fn lower_uop(uop: &ast::UOp) -> UOp {
+ *uop
+}
+
+fn lower_signature(sig: &ast::Signature) -> Signature {
+ sig.clone()
+}
+
+/// This is the biggie currently
+fn lower_expr(ir: &mut Ir, expr: &ast::Expr) -> EIdx {
+ use ast::Expr as E;
+ use Expr::*;
+ let new_exp = match expr {
+ E::Lit { val } => Lit {
+ val: lower_lit(val),
+ },
+ E::Var { name } => Var { name: *name },
+ E::BinOp { op, lhs, rhs } => {
+ let nop = lower_bop(op);
+ let nlhs = lower_expr(ir, lhs);
+ let nrhs = lower_expr(ir, rhs);
+ BinOp {
+ op: nop,
+ lhs: nlhs,
+ rhs: nrhs,
+ }
+ }
+ E::UniOp { op, rhs } => {
+ let nop = lower_uop(op);
+ let nrhs = lower_expr(ir, rhs);
+ UniOp { op: nop, rhs: nrhs }
+ }
+ E::Block { body } => {
+ let nbody = lower_exprs(ir, body);
+ Block { body: nbody }
+ }
+ E::Let {
+ varname,
+ typename,
+ init,
+ mutable,
+ } => {
+ let ninit = lower_expr(ir, init);
+ Let {
+ varname: *varname,
+ typename: typename.clone(),
+ init: ninit,
+ mutable: *mutable,
+ }
+ }
+ E::If { cases, falseblock } => {
+ // One of the actual transformations, this makes all if/else statements
+ // into essentially a switch: `if ... else if ... else if ... else if true ... end`
+ // This is more consistent and easier to handle for typechecking.
+ assert!(!cases.is_empty(), "Should never happen");
+ let mut cases: Vec<_> = cases
+ .iter()
+ .map(|case| (lower_expr(ir, &case.condition), lower_exprs(ir, &case.body)))
+ .collect();
+ // Add the "else" case, which we can just make `else if true then...`
+ // No idea if this is a good idea, but it makes life easier right
+ // this instant, so. Hasn't bit me yet, so it's not a *bad* idea.
+ let nelse_case = Expr::bool(ir, true);
+ // Empty false block becomes a false block that returns unit
+ let false_exprs = if falseblock.is_empty() {
+ lower_exprs(ir, &[ast::Expr::TupleCtor { body: vec![] }])
+ } else {
+ lower_exprs(ir, falseblock)
+ };
+ cases.push((nelse_case, false_exprs));
+ If { cases }
+ }
+ E::Loop { body } => {
+ let nbody = lower_exprs(ir, body);
+ Loop { body: nbody }
+ }
+ E::While { cond, body } => {
+ // While loops just get turned into a Loop containing
+ // if not cond then break end
+ let inverted_cond = E::UniOp {
+ op: UOp::Not,
+ rhs: cond.clone(),
+ };
+ let test = lower_expr(ir, &inverted_cond);
+ let brk = vec![ir.insert(Break)];
+ // As per above, we need to always have an "else" end case
+ let else_case = Expr::bool(ir, true);
+ let else_exprs = lower_exprs(ir, &[ast::Expr::TupleCtor { body: vec![] }]);
+ let if_expr = ir.insert(If {
+ cases: vec![(test, brk), (else_case, else_exprs)],
+ });
+ let mut nbody = lower_exprs(ir, body);
+ nbody.insert(0, if_expr);
+ Loop { body: nbody }
+ }
+ E::Lambda { signature, body } => {
+ let nsig = lower_signature(signature);
+ let nbody = lower_exprs(ir, body);
+ Lambda {
+ signature: nsig,
+ body: nbody,
+ }
+ }
+ E::Funcall {
+ func,
+ params,
+ typeparams,
+ } => {
+ let nfunc = lower_expr(ir, func);
+ let nparams = lower_exprs(ir, params);
+ Funcall {
+ func: nfunc,
+ params: nparams,
+ type_params: typeparams.clone(),
+ }
+ }
+ E::Break => Break,
+ E::Return { retval: e } => Return {
+ retval: lower_expr(ir, e),
+ },
+ E::TupleCtor { body } => {
+ /*
+ // Turn {foo, bar} into {_0: foo, _1: bar}
+ let body_pairs = body
+ .into_iter()
+ .enumerate()
+ .map(|(i, vl)| (Sym::new(format!("_{}", i)), lower_expr(ir, vl)))
+ .collect();
+ Expr::StructCtor { body: body_pairs }
+ */
+ let body = lower_exprs(ir, body);
+ Expr::TupleCtor { body }
+ }
+ E::StructCtor { body } => {
+ let lowered_body = body
+ .iter()
+ .map(|(nm, expr)| (*nm, lower_expr(ir, expr)))
+ .collect();
+ Expr::StructCtor { body: lowered_body }
+ }
+ E::ArrayRef { expr, idx } => Expr::ArrayRef {
+ expr: lower_expr(ir, expr),
+ idx: lower_expr(ir, idx),
+ },
+ E::TupleRef { expr, elt } => Expr::TupleRef {
+ expr: lower_expr(ir, expr),
+ elt: *elt,
+ },
+ E::StructRef { expr, elt } => Expr::StructRef {
+ expr: lower_expr(ir, expr),
+ elt: *elt,
+ },
+ E::TypeUnwrap { expr } => Expr::TypeUnwrap {
+ expr: lower_expr(ir, expr),
+ },
+ E::Deref { expr } => Expr::Deref {
+ expr: lower_expr(ir, expr),
+ },
+ E::Ref { expr } => Expr::Ref {
+ expr: lower_expr(ir, expr),
+ },
+ E::Assign { lhs, rhs } => Expr::Assign {
+ lhs: lower_expr(ir, lhs),
+ rhs: lower_expr(ir, rhs),
+ },
+ E::ArrayCtor { body } => Expr::ArrayCtor {
+ body: lower_exprs(ir, body.as_slice()),
+ },
+ };
+ ir.insert(new_exp)
+}
+
+/// handy shortcut to lower Vec<ast::Expr>
+fn lower_exprs(ir: &mut Ir, exprs: &[ast::Expr]) -> Vec<EIdx> {
+ exprs.iter().map(|e| lower_expr(ir, e)).collect()
+}
+fn lower_typedef(ir: &mut Ir, name: Sym, ty: &Type, params: &[Sym]) {
+ use Decl::*;
+ match ty {
+ // For `type Foo = enum Foo, Bar, Baz end`
+ // synthesize
+ // const Foo = {
+ // .Foo = <magic unprintable thing>
+ // .Bar = <magic unprintable thing>
+ // .Baz = <magic unprintable thing>
+ // }
+ Type::Enum(ts) => {
+ let struct_body: Vec<_> = ts
+ .iter()
+ .map(|(enumname, enumval)| {
+ // TODO: Enum vals?
+ let e = ir.insert(Expr::EnumCtor {
+ name,
+ variant: *enumname,
+ value: *enumval,
+ });
+ let e2 = ir.insert(Expr::TypeCtor {
+ name,
+ type_params: vec![],
+ body: e,
+ });
+ (*enumname, e2)
+ })
+ .collect();
+ let init_val = ir.insert(Expr::StructCtor { body: struct_body });
+ let struct_signature = ts
+ .iter()
+ //.map(|(enumname, _enumval)| (*enumname, ty.clone()))
+ .map(|(enumname, _enumval)| (*enumname, Type::Named(name, vec![])))
+ .collect();
+ // Enums cannot have type parameters, so this works.
+ let init_type = Type::Struct(struct_signature, vec![]);
+ let new_constdef = Const {
+ name,
+ typ: init_type,
+ init: init_val,
+ };
+ ir.decls.push(new_constdef);
+ }
+ // For `type Foo = sum X {}, Y Thing end`
+ // synthesize
+ // const Foo = {
+ // .X = fn({}) Foo = <magic unprintable thing> end
+ // .Y = fn(Thing) Foo = <magic unprintable thing> end
+ // }
+ //
+ // Maybe also something like this???
+ // type X = {}
+ // type Y = Thing
+ // TODO: What do we do with the generics... Right now they just
+ // get stuffed into the constructor functions verbatim.
+ Type::Sum(body, generics) => {
+ trace!("Lowering sum type {}", name);
+ let struct_body: Vec<_> = body
+ .iter()
+ .map(|(variant_name, variant_type)| {
+ let paramname = Sym::new("x");
+ let signature = ast::Signature {
+ params: vec![(paramname, variant_type.clone())],
+ rettype: Type::Named(name, generics.clone()),
+ typeparams: generics.clone(),
+ };
+ // Just return the value passed to it wrapped
+ // in a constructor of some kind...?
+ let contents = ir.insert(Expr::Var { name: paramname });
+ let body = vec![ir.insert(Expr::SumCtor {
+ name,
+ variant: *variant_name,
+ body: contents,
+ })];
+ let e = ir.insert(Expr::Lambda { signature, body });
+ //println!("{} is {:#?}", variant_name, e);
+ (*variant_name, e)
+ })
+ .collect();
+ let init_val = ir.insert(Expr::StructCtor { body: struct_body });
+ let struct_typebody = body
+ .iter()
+ .map(|(variant_name, variant_type)| {
+ // The function inside the struct has the signature
+ // `fn(variant_type) name`
+ (
+ *variant_name,
+ Type::Func(
+ vec![variant_type.clone()],
+ Box::new(Type::Named(name, generics.clone())),
+ generics.clone(),
+ ),
+ )
+ })
+ .collect();
+ let struct_type = Type::Struct(struct_typebody, generics.clone());
+ let new_constdef = Const {
+ name: name.to_owned(),
+ typ: struct_type,
+ init: init_val,
+ };
+ //println!("Lowered to {:#?}", &new_constdef);
+ ir.decls.push(new_constdef);
+ }
+ // For other types, we create a constructor function to build them.
+ other => {
+ let s = Sym::new("x");
+ trace!("Lowering params {:?}", params);
+ let type_params: Vec<_> = params.iter().map(|s| Type::Generic(*s)).collect();
+ let signature = ast::Signature {
+ params: vec![(s, other.clone())],
+ rettype: Type::Named(name.to_owned(), type_params.clone()),
+ typeparams: type_params.clone(),
+ };
+ // The generated function just returns the value passed to it wrapped
+ // in a type constructor
+ let contents = ir.insert(Expr::Var { name: s });
+ let body = vec![ir.insert(Expr::TypeCtor {
+ name,
+ type_params,
+ body: contents,
+ })];
+ //println!("{} is {:#?}", variant_name, e);
+ let new_fundecl = Function {
+ name: name.to_owned(),
+ signature,
+ body,
+ };
+ ir.decls.push(new_fundecl);
+ }
+ }
+}
+
+/// Lower an AST decl to IR.
+///
+/// Is there a more elegant way of doing this than passing an accumulator?
+/// Returning a vec is lame. Return an iterator? Sounds like work.
+fn lower_decl(ir: &mut Ir, decl: &ast::Decl) {
+ use ast::Decl as D;
+ match decl {
+ D::Function {
+ name,
+ signature,
+ body,
+ ..
+ } => {
+ let body = lower_exprs(ir, body);
+ ir.decls.push(Decl::Function {
+ name: *name,
+ signature: lower_signature(signature),
+ body,
+ })
+ }
+ D::Const {
+ name,
+ typename,
+ init,
+ ..
+ } => {
+ let init = lower_expr(ir, init);
+ ir.decls.push(Decl::Const {
+ name: *name,
+ typ: typename.clone(),
+ init,
+ })
+ }
+ // this needs to generate the typedef AND the type constructor
+ // declaration. Plus the type deconstructor.
+ D::TypeDef {
+ name,
+ typedecl,
+ doc_comment: _,
+ params,
+ } => {
+ lower_typedef(ir, *name, typedecl, params);
+ ir.decls.push(Decl::TypeDef {
+ name: *name,
+ params: params.clone(),
+ typedecl: typedecl.clone(),
+ });
+ }
+ D::Import { name, rename } => ir.decls.push(Decl::Import {
+ name: *name,
+ localname: rename.unwrap_or(*name),
+ }),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::ast::Expr as A;
+ use crate::hir2::Expr as I;
+ use crate::hir2::ExprNode as EN;
+
+ /// Does `return;` turn into `return ();`?
+ /// Not doing that to make parsing simpler, since we don't
+ /// actually have semicolons.
+ #[test]
+ fn test_return_none() {
+ let ir = &mut Ir::new("", "");
+ let input = A::Return {
+ retval: Box::new(A::unit()),
+ };
+ let retval = Expr::unit(ir);
+ let output = ir.insert(I::Return { retval });
+ let res = lower_expr(ir, &input);
+ assert_eq!(&res, &output);
+ }
+
+ /*
+ /// Do we turn chained if's properly into nested ones?
+ /// Single if.
+ #[test]
+ fn test_if_lowering_single() {
+ let input = A::If {
+ cases: vec![ast::IfCase {
+ condition: Box::new(A::bool(false)),
+ body: vec![A::int(1)],
+ }],
+ falseblock: vec![A::int(2)],
+ };
+ let output = EN::new(I::If {
+ cases: vec![
+ (EN::bool(false), vec![EN::int(1)]),
+ (EN::bool(true), vec![EN::int(2)]),
+ ],
+ });
+ let res = lower_expr(ir, &input);
+ assert_eq!(&res, &output);
+ }
+
+ /// Do we turn chained if's properly into nested ones?
+ /// Chained if/else's
+ #[test]
+ fn test_if_lowering_chained() {
+ let input = A::If {
+ cases: vec![
+ ast::IfCase {
+ condition: Box::new(A::bool(false)),
+ body: vec![A::int(1)],
+ },
+ ast::IfCase {
+ condition: Box::new(A::bool(true)),
+ body: vec![A::int(2)],
+ },
+ ],
+ falseblock: vec![A::int(3)],
+ };
+ let output = EN::new(I::If {
+ cases: vec![
+ (EN::bool(false), vec![EN::int(1)]),
+ (EN::bool(true), vec![EN::int(2)]),
+ (EN::bool(true), vec![EN::int(3)]),
+ ],
+ });
+ let res = lower_expr(ir, &input);
+ assert_eq!(&res, &output);
+ }
+
+ /// Do we panic if we get an impossible if with no cases?
+ #[test]
+ #[should_panic]
+ fn test_if_nothing() {
+ let input = A::If {
+ cases: vec![],
+ falseblock: vec![],
+ };
+ let _ = lower_expr(ir, &input);
+ }
+ */
+}
@@ 0,0 1,574 @@
+//! Transformation/optimization passes that function on the IR.
+//! May happen before or after typechecking, either way.
+//! For now each is a function from `IR -> IR`, with some recursion
+//! scheme helper functions. There's two types of passes, one that
+//! happens before type checking and one that happens after.
+//!
+//! It could probably be more efficient, especially with
+//! IR nodes flattened into a vec, but that doesn't matter for now.
+//! We just allocate our way to happiness.
+//!
+//! TODO: A pass that might be useful would be "alpha renaming".
+//! Essentially you walk through your entire compilation unit and
+//! rename everything to a globally unique name. This means that
+//! scope vanishes, for example, and anything potentially
+//! ambiguous becomes unambiguous. It can come after typechecking tho.
+//!
+//!
+//! TODO: Passes that need to be done still:
+//! * Closure conversion
+//! * Monomorphization
+//! * Pointerification, turning large struct values into pointers? Maybe.
+//! * Turning all enums into ints? Maybe.
+
+// Not 100% convinced that having a submodule for each pass is
+// necessary, since many of the passes are pretty small, but
+// oh well.
+
+mod lambda_lift;
+use crate::hir2::{Decl as D, EIdx, Expr as E, ExprNode, Ir};
+use crate::*;
+
+type Pass = fn(Ir) -> Ir;
+
+/// Tck has to be mutable because we may change the return type
+/// of expr nodes.
+type TckPass = fn(Ir, &mut typeck::Tck) -> Ir;
+
+pub fn run_passes(ir: Ir) -> Ir {
+ // TODO: It may be more efficient to compose passes rather than fold them?
+ // That way they don't need to build the full intermediate IR each time.
+ // You can use the recursion scheme pattern for this.
+ // That will take some nontrivial restructuring of expr_map though, will also need
+ // a decl_map or something that can compose multiple passes together.
+ // Probably not *difficult*, but tricksy.
+ let passes: &[Pass] = &[lambda_lift::lambda_lift];
+ passes.iter().fold(ir, |prev_ir, f| f(prev_ir))
+}
+
+pub fn run_typechecked_passes(ir: Ir, tck: &mut typeck::Tck) -> Ir {
+ // let passes: &[TckPass] = &[nameify, enum_to_int];
+ //let passes: &[TckPass] = &[nameify, struct_to_tuple];
+ let passes: &[TckPass] = &[];
+ let res = passes.iter().fold(ir, |prev_ir, f| f(prev_ir, tck));
+ res
+}
+
+/// Handy do-nothing combinator
+fn id<T>(thing: T) -> T {
+ thing
+}
+
+/// This is the core recusion scheme that takes a function and applies
+/// it to an expression, and all its subexpressions.
+/// The function is `&mut FnMut` so if it needs to have access to more
+/// data between calls besides the expression itself, just smuggle it
+/// into the closure. Trying to make this generic over different funcs
+/// signatures is probably not worth the effort
+///
+/// To handle multiple expressions this will turn more into a fold()
+/// than a map(), it will take an accumulator that gets threaded through
+/// everything... That gets *very weird* though and I manage to pour
+/// my coffee on my cat this morning so let's give that a miss for now.
+/// As it is, this can only transform subtrees into other subtrees.
+///
+/// We don't really need a version of this for decls, 'cause decls
+/// can't be nested.
+///
+/// The function has to take and return an ExprNode, not an expr,
+/// because they may have to look up the type of the expression.
+///
+/// This can take a function to call to transform the current node
+/// before descending into sub-expressions
+/// sub-nodes, and one to call after. Use expr_map_pre()
+/// and expr_map_post() to just do a pre-traversal or a post-traversal
+/// specifically.
+fn expr_map(
+ ir: &Ir,
+ out: &mut Ir,
+ expr: ExprNode,
+ f_pre: &mut dyn FnMut(ExprNode) -> EIdx,
+ f_post: &mut dyn FnMut(ExprNode) -> EIdx,
+) -> hir2::EIdx {
+ let thing = f_pre(expr);
+ let exprfn = &mut |e| match e {
+ // Nodes with no recursive expressions.
+ // I list all these out explicitly instead of
+ // using a catchall so it doesn't fuck up if we change
+ // the type of Expr.
+ E::Lit { .. } => e,
+ E::Var { .. } => e,
+ E::Break => e,
+ E::EnumCtor { .. } => e,
+ E::TupleCtor { body } => E::TupleCtor {
+ body: exprs_map(out, body, f_pre, f_post),
+ },
+ E::StructCtor { body } => {
+ let new_body = body
+ .into_iter()
+ .map(|(sym, vl)| (sym, expr_map(ir, out, vl, f_pre, f_post)))
+ .collect();
+ E::StructCtor { body: new_body }
+ }
+ E::TypeCtor {
+ name,
+ type_params,
+ body,
+ } => E::TypeCtor {
+ name,
+ type_params,
+ body: expr_map(ir, out, body, f_pre, f_post),
+ },
+ E::SumCtor {
+ name,
+ variant,
+ body,
+ } => E::SumCtor {
+ name,
+ variant,
+ body: expr_map(ir, out, body, f_pre, f_post),
+ },
+ E::ArrayCtor { body } => E::ArrayCtor {
+ body: exprs_map(out, body, f_pre, f_post),
+ },
+ E::TypeUnwrap { expr } => E::TypeUnwrap {
+ expr: expr_map(ir, out, expr, f_pre, f_post),
+ },
+ E::TupleRef { expr, elt } => E::TupleRef {
+ expr: expr_map(ir, out, expr, f_pre, f_post),
+ elt,
+ },
+ E::StructRef { expr, elt } => E::StructRef {
+ expr: expr_map(ir, out, expr, f_pre, f_post),
+ elt,
+ },
+ E::ArrayRef { expr, idx } => E::ArrayRef {
+ expr: expr_map(ir, out, expr, f_pre, f_post),
+ idx,
+ },
+ E::Assign { lhs, rhs } => E::Assign {
+ lhs: expr_map(ir, out, lhs, f_pre, f_post), // TODO: Think real hard about lvalues
+ rhs: expr_map(ir, out, rhs, f_pre, f_post),
+ },
+ E::BinOp { op, lhs, rhs } => E::BinOp {
+ op,
+ lhs: expr_map(ir, out, lhs, f_pre, f_post),
+ rhs: expr_map(ir, out, rhs, f_pre, f_post),
+ },
+ E::UniOp { op, rhs } => E::UniOp {
+ op,
+ rhs: expr_map(ir, out, rhs, f_pre, f_post),
+ },
+ E::Block { body } => E::Block {
+ body: exprs_map(out, body, f_pre, f_post),
+ },
+ E::Let {
+ varname,
+ typename,
+ init,
+ mutable,
+ } => E::Let {
+ varname,
+ typename,
+ init: expr_map(ir, out, init, f_pre, f_post),
+ mutable,
+ },
+ E::If { cases } => {
+ let new_cases = cases
+ .into_iter()
+ .map(|(test, case)| {
+ let new_test = expr_map(ir, out, test, f_pre, f_post);
+ let new_cases = exprs_map(out, case, f_pre, f_post);
+ (new_test, new_cases)
+ })
+ .collect();
+ E::If { cases: new_cases }
+ }
+ E::Loop { body } => E::Loop {
+ body: exprs_map(out, body, f_pre, f_post),
+ },
+ E::Return { retval } => E::Return {
+ retval: expr_map(ir, out, retval, f_pre, f_post),
+ },
+ E::Funcall {
+ func,
+ params,
+ type_params,
+ } => {
+ let new_func = expr_map(ir, out, func, f_pre, f_post);
+ let new_params = exprs_map(out, params, f_pre, f_post);
+ E::Funcall {
+ func: new_func,
+ params: new_params,
+ type_params,
+ }
+ }
+ E::Lambda { signature, body } => E::Lambda {
+ signature,
+ body: exprs_map(out, body, f_pre, f_post),
+ },
+ E::Typecast { e, to } => E::Typecast {
+ e: expr_map(ir, out, e, f_pre, f_post),
+ to,
+ },
+ E::Ref { expr } => E::Ref {
+ expr: expr_map(ir, out, expr, f_pre, f_post),
+ },
+ E::Deref { expr } => E::Deref {
+ expr: expr_map(ir, out, expr, f_pre, f_post),
+ },
+ };
+ let post_thing = thing.map(exprfn);
+ f_post(post_thing)
+}
+
+pub fn expr_map_pre(expr: ExprNode, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> ExprNode {
+ expr_map(ir, out, expr, f, &mut id)
+}
+
+pub fn expr_map_post(expr: ExprNode, f: &mut dyn FnMut(ExprNode) -> ExprNode) -> ExprNode {
+ expr_map(ir, out, expr, &mut id, f)
+}
+
+/// Map functions over a list of exprs.
+fn exprs_map(
+ in: &Ir,
+ out: &mut Ir,
+ exprs: Vec<ExprNode>,
+ f_pre: &mut dyn FnMut(ExprNode) -> EIdx,
+ f_post: &mut dyn FnMut(ExprNode) -> EIdx,
+) -> Vec<EIdx> {
+ exprs
+ .into_iter()
+ .map(|e| expr_map(ir, out, e, f_pre, f_post))
+ .collect()
+}
+
+fn exprs_map_pre(
+ out: &mut Ir,
+ exprs: Vec<ExprNode>,
+ f: &mut dyn FnMut(ExprNode) -> ExprNode,
+) -> Vec<EIdx> {
+ exprs_map(out, exprs, f, &mut id)
+}
+
+fn _exprs_map_post(
+ out: &mut Ir,
+ exprs: Vec<ExprNode>,
+ f: &mut dyn FnMut(ExprNode) -> ExprNode,
+) -> Vec<EIdx> {
+ exprs_map(out, exprs, &mut id, f)
+}
+
+fn decl_map_pre(
+ out: &mut Ir,
+ decl: D,
+ fe: &mut dyn FnMut(ExprNode) -> ExprNode,
+ ft: &mut dyn FnMut(Type) -> Type,
+) {
+ decl_map(out, decl, fe, &mut id, ft)
+}
+
+fn decl_map_post(
+ out: &mut Ir,
+ decl: D,
+ fe: &mut dyn FnMut(ExprNode) -> ExprNode,
+ ft: &mut dyn FnMut(Type) -> Type,
+) {
+ decl_map(out, decl, &mut id, fe, ft)
+}
+
+/// Takes a decl and applies the given expr and type transformers to any
+/// expression in it.
+///
+/// Technically not very necessary, since decls can't contain other decls,
+/// but happens often enough that it's worth having.
+fn decl_map(
+ out: &mut Ir,
+ decl: D,
+ fe_pre: &mut dyn FnMut(ExprNode) -> ExprNode,
+ fe_post: &mut dyn FnMut(ExprNode) -> ExprNode,
+ ft: &mut dyn FnMut(Type) -> Type,
+) {
+ let d = match decl {
+ D::Function {
+ name,
+ signature,
+ body,
+ } => D::Function {
+ name,
+ signature: signature_map(signature, ft),
+ body: exprs_map(out, body, fe_pre, fe_post),
+ },
+ D::Const { name, typ, init } => D::Const {
+ name,
+ typ: type_map(typ, ft),
+ init: expr_map(ir, out, init, fe_pre, fe_post),
+ },
+ D::TypeDef {
+ name,
+ params,
+ typedecl,
+ } => D::TypeDef {
+ name,
+ params,
+ typedecl: type_map(typedecl, ft),
+ },
+ D::Import { .. } => decl,
+ };
+ out.decls.push(d)
+}
+
+fn types_map(typs: Vec<Type>, f: &mut dyn FnMut(Type) -> Type) -> Vec<Type> {
+ typs.into_iter().map(|t| type_map(t, f)).collect()
+}
+
+/// Recursion scheme to turn one type into another.
+fn type_map(typ: Type, f: &mut dyn FnMut(Type) -> Type) -> Type {
+ /// Really this is only here to be cute, it's used a grand total of twice.
+ /// There's probably some horrible combinator chain we could use to make
+ /// it generic over any iterator, if we want to make life even harder for
+ /// ourself.
+ fn types_map_btree<K>(
+ typs: BTreeMap<K, Type>,
+ f: &mut dyn FnMut(Type) -> Type,
+ ) -> BTreeMap<K, Type>
+ where
+ K: Ord,
+ {
+ typs.into_iter()
+ .map(|(key, ty)| (key, type_map(ty, f)))
+ .collect()
+ }
+ let res = match typ {
+ Type::Struct(fields, generics) => {
+ let fields = types_map_btree(fields, f);
+ Type::Struct(fields, generics)
+ }
+ Type::Sum(fields, generics) => {
+ let new_fields = types_map_btree(fields, f);
+ Type::Sum(new_fields, generics)
+ }
+ Type::Array(ty, len) => Type::Array(Box::new(type_map(*ty, f)), len),
+ Type::Func(args, rettype, typeparams) => {
+ let new_args = types_map(args, f);
+ let new_rettype = type_map(*rettype, f);
+ let new_typeparams = types_map(typeparams, f);
+ Type::Func(new_args, Box::new(new_rettype), new_typeparams)
+ }
+ // Not super sure whether this is necessary, but can't hurt.
+ Type::Named(nm, tys) => Type::Named(nm, types_map(tys, f)),
+ Type::Prim(_) => typ,
+ Type::Never => typ,
+ Type::Enum(_) => typ,
+ Type::Generic(_) => typ,
+ Type::Uniq(t) => {
+ let new_t = type_map(*t, f);
+ Type::Uniq(Box::new(new_t))
+ }
+ };
+ f(res)
+}
+
+/// Produce a new signature by transforming the types
+fn signature_map(sig: hir::Signature, f: &mut dyn FnMut(Type) -> Type) -> hir::Signature {
+ let new_params = sig
+ .params
+ .into_iter()
+ .map(|(sym, ty)| (sym, type_map(ty, f)))
+ .collect();
+ hir::Signature {
+ params: new_params,
+ rettype: type_map(sig.rettype, f),
+ typeparams: types_map(sig.typeparams, f),
+ }
+}
+
+pub fn generate_type_name(typ: &Type) -> String {
+ match typ {
+ Type::Enum(fields) => {
+ let fieldnames: Vec<_> = fields
+ .iter()
+ .map(|(nm, vl)| format!("F{}_{}", nm, vl))
+ .collect();
+ let fieldstr = fieldnames.join("_");
+ format!("__Enum{}", fieldstr)
+ }
+ Type::Func(params, rettype, typeparams) => {
+ let paramnames: Vec<String> = params.iter().map(generate_type_name).collect();
+ let paramstr = paramnames.join("_");
+ let retname = generate_type_name(rettype);
+ let tparamnames: Vec<String> = typeparams.iter().map(generate_type_name).collect();
+ let tparamstr = tparamnames.join("_");
+ format!("__Func__{}__{}_{}", paramstr, retname, tparamstr)
+ }
+ Type::Struct(body, _) => {
+ let fieldnames: Vec<_> = body
+ .iter()
+ .map(|(nm, ty)| format!("{}_{}", nm, generate_type_name(ty)))
+ .collect();
+ let fieldstr = fieldnames.join("_");
+ format!("__Struct__{}", fieldstr)
+ }
+ Type::Sum(body, _) => {
+ let fieldnames: Vec<_> = body
+ .iter()
+ .map(|(nm, ty)| format!("{}_{}", nm, generate_type_name(ty)))
+ .collect();
+ let fieldstr = fieldnames.join("_");
+ format!("__Sum__{}", fieldstr)
+ }
+ Type::Generic(name) => {
+ format!("__G{}", name)
+ }
+ Type::Named(name, fields) => {
+ let field_names: Vec<_> = fields.iter().map(generate_type_name).collect();
+ let field_str = field_names.join("_");
+ format!("__Named{}__{}", name, field_str)
+ }
+ Type::Prim(p) => p.get_name().into_owned(),
+ Type::Never => format!("!"),
+ Type::Array(t, len) => {
+ format!("__Arr{}__{}", generate_type_name(t), len)
+ }
+ Type::Uniq(t) => {
+ format!("__Uniq__{}", generate_type_name(t))
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn test_expr_map(ir, )) {
+ fn swap_binop_args(expr: ExprNode) -> ExprNode {
+ trace!("Swapping {:?}", expr);
+ expr.map(&mut |e| match e {
+ E::BinOp { op, lhs, rhs } => E::BinOp {
+ op,
+ lhs: rhs,
+ rhs: lhs,
+ },
+ other => other,
+ })
+ }
+ // Make sure this works for trivial exppressions
+ let inp = ExprNode::new(E::BinOp {
+ op: hir::BOp::Add,
+ lhs: ExprNode::int(3),
+ rhs: ExprNode::int(4),
+ });
+ let desired = ExprNode::new(E::BinOp {
+ op: hir::BOp::Add,
+ lhs: ExprNode::int(4),
+ rhs: ExprNode::int(3),
+ });
+ let out = expr_map_pre(inp, &mut swap_binop_args);
+ assert_eq!(out, desired);
+
+ // Make sure it recurses properly for deeper trees.
+ let inp2 = ExprNode::new(E::BinOp {
+ op: hir::BOp::Add,
+ lhs: ExprNode::new(E::BinOp {
+ op: hir::BOp::Sub,
+ lhs: ExprNode::int(100),
+ rhs: ExprNode::int(200),
+ }),
+ rhs: ExprNode::int(4),
+ });
+ let desired2 = ExprNode::new(E::BinOp {
+ op: hir::BOp::Add,
+ lhs: ExprNode::int(4),
+ rhs: ExprNode::new(E::BinOp {
+ op: hir::BOp::Sub,
+ lhs: ExprNode::int(200),
+ rhs: ExprNode::int(100),
+ }),
+ });
+ let out2 = expr_map_pre(inp2, &mut swap_binop_args);
+ assert_eq!(out2, desired2);
+ }
+
+ /// Test whether our pre-traversal expr map works properly.
+ #[test]
+ fn test_expr_pretraverse() {
+ let inp = ExprNode::new(E::Block {
+ body: vec![ExprNode::new(E::Var {
+ name: Sym::new("foo"),
+ })],
+ });
+ // Make a transformer that renames vars, and check in the Block
+ // body whether the inner var has been transformed.
+ let f = &mut |e: ExprNode| {
+ let helper = &mut |e| match e {
+ E::Var { .. } => E::Var {
+ name: Sym::new("bar!"),
+ },
+ E::Block { body } => {
+ // Has the name within the body been transformed yet?
+ {
+ let inner = &*body[0].e;
+ assert_eq!(
+ inner,
+ &E::Var {
+ name: Sym::new("foo")
+ }
+ );
+ }
+ E::Block { body }
+ }
+ _other => _other,
+ };
+ e.map(helper)
+ };
+ let outp = expr_map_pre(inp, f);
+ // Make sure that the actual transformation has done what we expected.
+ let expected = ExprNode::new(E::Block {
+ body: vec![ExprNode::new(E::Var {
+ name: Sym::new("bar!"),
+ })],
+ });
+ assert_eq!(outp, expected);
+ }
+
+ /// Similar to above with the case in helper() reversed
+ #[test]
+ fn test_expr_posttraverse() {
+ let inp = ExprNode::new(E::Block {
+ body: vec![ExprNode::new(E::Var {
+ name: Sym::new("foo"),
+ })],
+ });
+ let f = &mut |e: ExprNode| {
+ let helper = &mut |e| match e {
+ E::Var { .. } => E::Var {
+ name: Sym::new("bar!"),
+ },
+ E::Block { body } => {
+ // Has the name within the body been transformed yet?
+ {
+ let inner = &*body[0].e;
+ assert_eq!(
+ inner,
+ &E::Var {
+ name: Sym::new("bar!")
+ }
+ );
+ }
+ E::Block { body }
+ }
+ _other => _other,
+ };
+ e.map(helper)
+ };
+ let outp = expr_map_post(inp, f);
+ // Make sure that the actual transformation has done what we expected.
+ let expected = ExprNode::new(E::Block {
+ body: vec![ExprNode::new(E::Var {
+ name: Sym::new("bar!"),
+ })],
+ });
+ assert_eq!(outp, expected);
+ }
+}