//! # Reduced AST //! The reduced AST is a minimal AST designed to be built from the full AST after all possible //! static checks have been done. Consequently, the AST reduction phase does very little error //! checking itself - any errors should ideally be caught either by an earlier phase, or are //! runtime errors that the evaluator should handle. That said, becuase it does do table lookups //! that can in principle fail [especially at the moment with most static analysis not yet complete], //! there is an Expr variant `ReductionError` to handle these cases. //! //! A design decision to make - should the ReducedAST types contain all information about //! type/layout necessary for the evaluator to work? If so, then the evaluator should not //! have access to the symbol table at all and ReducedAST should carry that information. If not, //! then ReducedAST shouldn't be duplicating information that can be queried at runtime from the //! symbol table. But I think the former might make sense since ultimately the bytecode will be //! built from the ReducedAST. use std::rc::Rc; use std::str::FromStr; use crate::ast::*; use crate::symbol_table::{Symbol, SymbolSpec, SymbolTable, FullyQualifiedSymbolName}; use crate::builtin::Builtin; #[derive(Debug)] pub struct ReducedAST(pub Vec); #[derive(Debug, Clone)] pub enum Stmt { PreBinding { name: Rc, func: Func, }, Binding { name: Rc, constant: bool, expr: Expr, }, Expr(Expr), Noop, } #[derive(Debug, Clone)] pub enum Expr { Unit, Lit(Lit), Tuple(Vec), Func(Func), Sym(Rc), Constructor { type_name: Rc, name: Rc, tag: usize, arity: usize, // n.b. arity here is always the value from the symbol table - if it doesn't match what it's being called with, that's an eval error, eval will handle it }, Call { f: Box, args: Vec, }, Assign { val: Box, expr: Box, }, Conditional { cond: Box, then_clause: Vec, else_clause: Vec, }, ConditionalTargetSigilValue, CaseMatch { cond: Box, alternatives: Vec }, UnimplementedSigilValue, ReductionError(String), } pub type BoundVars = Vec>>; //remember that order matters here #[derive(Debug, Clone)] pub struct Alternative { pub matchable: Subpattern, pub item: Vec, } #[derive(Debug, Clone)] pub struct Subpattern { pub tag: Option, pub subpatterns: Vec>, pub bound_vars: BoundVars, pub guard: Option, } #[derive(Debug, Clone)] pub enum Lit { Nat(u64), Int(i64), Float(f64), Bool(bool), StringLit(Rc), } #[derive(Debug, Clone)] pub enum Func { BuiltIn(Builtin), UserDefined { name: Option>, params: Vec>, body: Vec, } } pub fn reduce(ast: &AST, symbol_table: &SymbolTable) -> ReducedAST { let mut reducer = Reducer { symbol_table }; reducer.ast(ast) } struct Reducer<'a> { symbol_table: &'a SymbolTable } impl<'a> Reducer<'a> { fn ast(&mut self, ast: &AST) -> ReducedAST { let mut output = vec![]; for statement in ast.statements.iter() { output.push(self.statement(statement)); } ReducedAST(output) } fn statement(&mut self, stmt: &Meta) -> Stmt { match &stmt.node().kind { StatementKind::Expression(expr) => Stmt::Expr(self.expression(&expr)), StatementKind::Declaration(decl) => self.declaration(&decl), } } fn block(&mut self, block: &Block) -> Vec { block.iter().map(|stmt| self.statement(stmt)).collect() } fn invocation_argument(&mut self, invoc: &InvocationArgument) -> Expr { use crate::ast::InvocationArgument::*; match invoc { Positional(ex) => self.expression(ex), Keyword { .. } => Expr::UnimplementedSigilValue, Ignored => Expr::UnimplementedSigilValue, } } fn expression(&mut self, expr: &Meta) -> Expr { use crate::ast::ExpressionKind::*; let symbol_table = self.symbol_table; let ref node = expr.node(); let ref input = node.kind; match input { NatLiteral(n) => Expr::Lit(Lit::Nat(*n)), FloatLiteral(f) => Expr::Lit(Lit::Float(*f)), StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())), BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)), BinExp(binop, lhs, rhs) => self.binop(binop, lhs, rhs), PrefixExp(op, arg) => self.prefix(op, arg), Value(qualified_name) => { let ref sym_name = match expr.fqsn { Some(ref fqsn) => fqsn, None => return Expr::ReductionError(format!("FQSN lookup for Value {:?} failed", qualified_name)), }; //TODO this probably needs to change let FullyQualifiedSymbolName(ref v) = sym_name; let name = v.last().unwrap().name.clone(); match symbol_table.lookup_by_fqsn(&sym_name) { Some(Symbol { spec: SymbolSpec::DataConstructor { index, type_args, type_name}, .. }) => Expr::Constructor { type_name: type_name.clone(), name: name.clone(), tag: index.clone(), arity: type_args.len(), }, _ => Expr::Sym(name.clone()), } }, Call { f, arguments } => self.reduce_call_expression(f, arguments), TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| self.expression(e)).collect()), IfExpression { discriminator, body } => self.reduce_if_expression(discriminator, body), Lambda { params, body, .. } => self.reduce_lambda(params, body), NamedStruct { name, fields } => self.reduce_named_struct(expr.fqsn.as_ref(), name.node(), fields), Index { .. } => Expr::UnimplementedSigilValue, WhileExpression { .. } => Expr::UnimplementedSigilValue, ForExpression { .. } => Expr::UnimplementedSigilValue, ListLiteral { .. } => Expr::UnimplementedSigilValue, } } fn reduce_lambda(&mut self, params: &Vec, body: &Block) -> Expr { Expr::Func(Func::UserDefined { name: None, params: params.iter().map(|param| param.name.clone()).collect(), body: self.block(body), }) } fn reduce_named_struct(&mut self, fqsn: Option<&FullyQualifiedSymbolName>, _name: &QualifiedName, fields: &Vec<(Rc, Meta)>) -> Expr { let symbol_table = self.symbol_table; let sym_name = match fqsn { Some(fqsn) => fqsn, None => return Expr::ReductionError(format!("FQSN lookup for value B failed")), }; let FullyQualifiedSymbolName(ref v) = sym_name; let ref name = v.last().unwrap().name; let (type_name, index, members_from_table) = match symbol_table.lookup_by_fqsn(&sym_name) { Some(Symbol { spec: SymbolSpec::RecordConstructor { members, type_name, index }, .. }) => (type_name.clone(), index, members), _ => return Expr::ReductionError("Not a record constructor".to_string()), }; let arity = members_from_table.len(); let mut args: Vec<(Rc, Expr)> = fields.iter() .map(|(name, expr)| (name.clone(), self.expression(expr))) .collect(); args.as_mut_slice() .sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); //arbitrary - sorting by alphabetical order let args = args.into_iter().map(|(_, expr)| expr).collect(); //TODO make sure this sorting actually works let f = box Expr::Constructor { type_name, name: name.clone(), tag: *index, arity, }; Expr::Call { f, args } } fn reduce_call_expression(&mut self, func: &Meta, arguments: &Vec) -> Expr { Expr::Call { f: Box::new(self.expression(func)), args: arguments.iter().map(|arg| self.invocation_argument(arg)).collect(), } } fn reduce_if_expression(&mut self, discriminator: &Discriminator, body: &IfExpressionBody) -> Expr { let symbol_table = self.symbol_table; let cond = Box::new(match *discriminator { Discriminator::Simple(ref expr) => self.expression(expr), Discriminator::BinOp(ref _expr, ref _binop) => panic!("Can't yet handle binop discriminators") }); match *body { IfExpressionBody::SimpleConditional(ref then_clause, ref else_clause) => { let then_clause = self.block(then_clause); let else_clause = match else_clause { None => vec![], Some(stmts) => self.block(stmts), }; Expr::Conditional { cond, then_clause, else_clause } }, IfExpressionBody::SimplePatternMatch(ref pat, ref then_clause, ref else_clause) => { let then_clause = self.block(then_clause); let else_clause = match else_clause { None => vec![], Some(stmts) => self.block(stmts), }; let alternatives = vec![ pat.to_alternative(then_clause, symbol_table), Alternative { matchable: Subpattern { tag: None, subpatterns: vec![], bound_vars: vec![], guard: None, }, item: else_clause }, ]; Expr::CaseMatch { cond, alternatives, } }, IfExpressionBody::GuardList(ref guard_arms) => { let mut alternatives = vec![]; for arm in guard_arms { match arm.guard { Guard::Pat(ref p) => { let item = self.block(&arm.body); let alt = p.to_alternative(item, symbol_table); alternatives.push(alt); }, Guard::HalfExpr(HalfExpr { op: _, expr: _ }) => { return Expr::UnimplementedSigilValue } } } Expr::CaseMatch { cond, alternatives } } } } fn binop(&mut self, binop: &BinOp, lhs: &Box>, rhs: &Box>) -> Expr { let operation = Builtin::from_str(binop.sigil()).ok(); match operation { Some(Builtin::Assignment) => Expr::Assign { val: Box::new(self.expression(&*lhs)), expr: Box::new(self.expression(&*rhs)), }, Some(op) => { let f = Box::new(Expr::Func(Func::BuiltIn(op))); Expr::Call { f, args: vec![self.expression(&*lhs), self.expression(&*rhs)] } }, None => { //TODO handle a user-defined operation Expr::UnimplementedSigilValue } } } fn prefix(&mut self, prefix: &PrefixOp, arg: &Box>) -> Expr { match prefix.builtin { Some(op) => { let f = Box::new(Expr::Func(Func::BuiltIn(op))); Expr::Call { f, args: vec![self.expression(arg)] } }, None => { //TODO need this for custom prefix ops Expr::UnimplementedSigilValue } } } fn declaration(&mut self, declaration: &Declaration) -> Stmt { use self::Declaration::*; match declaration { Binding {name, constant, expr, .. } => Stmt::Binding { name: name.clone(), constant: *constant, expr: self.expression(expr) }, FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding { name: name.clone(), func: Func::UserDefined { name: Some(name.clone()), params: params.iter().map(|param| param.name.clone()).collect(), body: self.block(&statements), } }, TypeDecl { .. } => Stmt::Noop, TypeAlias(_, _) => Stmt::Noop, Interface { .. } => Stmt::Noop, Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue), _ => Stmt::Expr(Expr::UnimplementedSigilValue) } } } /* ig var pat * x is SomeBigOldEnum(_, x, Some(t)) */ fn handle_symbol(symbol: Option<&Symbol>, inner_patterns: &Vec, symbol_table: &SymbolTable) -> Subpattern { use self::Pattern::*; let tag = symbol.map(|symbol| match symbol.spec { SymbolSpec::DataConstructor { index, .. } => index.clone(), _ => panic!("Symbol is not a data constructor - this should've been caught in type-checking"), }); let bound_vars = inner_patterns.iter().map(|p| match p { VarOrName(meta_name) => { let symbol_exists = meta_name.fqsn.as_ref().and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)).is_some(); if symbol_exists { None } else { let QualifiedName(name_elems) = meta_name.node(); if name_elems.len() == 1 { Some(name_elems[0].clone()) } else { panic!("Bad variable name in pattern"); } } }, _ => None, }).collect(); let subpatterns = inner_patterns.iter().map(|p| match p { Ignored => None, VarOrName(_) => None, Literal(other) => Some(other.to_subpattern(symbol_table)), tp @ TuplePattern(_) => Some(tp.to_subpattern(symbol_table)), ts @ TupleStruct(_, _) => Some(ts.to_subpattern(symbol_table)), Record(..) => unimplemented!(), }).collect(); let guard = None; /* let guard_equality_exprs: Vec = subpatterns.iter().map(|p| match p { Literal(lit) => match lit { _ => unimplemented!() }, _ => unimplemented!() }).collect(); */ Subpattern { tag, subpatterns, guard, bound_vars, } } impl Pattern { fn to_alternative(&self, item: Vec, symbol_table: &SymbolTable) -> Alternative { let s = self.to_subpattern(symbol_table); Alternative { matchable: Subpattern { tag: s.tag, subpatterns: s.subpatterns, bound_vars: s.bound_vars, guard: s.guard, }, item } } fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern { use self::Pattern::*; match self { TupleStruct( Meta { n: QualifiedName(vec), fqsn, .. }, inner_patterns) => { match fqsn.as_ref().and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)) { Some(symbol) => handle_symbol(Some(symbol), inner_patterns, symbol_table), None => { panic!("Symbol {:?} not found", QualifiedName(vec.to_vec())); } } }, TuplePattern(inner_patterns) => handle_symbol(None, inner_patterns, symbol_table), Record(_name, _pairs) => { unimplemented!() }, Ignored => Subpattern { tag: None, subpatterns: vec![], guard: None, bound_vars: vec![] }, Literal(lit) => lit.to_subpattern(symbol_table), VarOrName(Meta { n: QualifiedName(vec), fqsn, .. }) => { // if fqsn is Some, treat this as a symbol pattern. If it's None, treat it // as a variable. println!("Calling VarOrName reduction with : {:?}", vec); match fqsn.as_ref().and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)) { Some(symbol) => handle_symbol(Some(symbol), &vec![], symbol_table), None => { let name = if vec.len() == 1 { vec[0].clone() } else { panic!("check this line of code yo"); }; Subpattern { tag: None, subpatterns: vec![], guard: None, bound_vars: vec![Some(name.clone())], } } } }, } } } impl PatternLiteral { fn to_subpattern(&self, _symbol_table: &SymbolTable) -> Subpattern { use self::PatternLiteral::*; match self { NumPattern { neg, num } => { let comparison = Expr::Lit(match (neg, num) { (false, ExpressionKind::NatLiteral(n)) => Lit::Nat(*n), (false, ExpressionKind::FloatLiteral(f)) => Lit::Float(*f), (true, ExpressionKind::NatLiteral(n)) => Lit::Int(-1*(*n as i64)), (true, ExpressionKind::FloatLiteral(f)) => Lit::Float(-1.0*f), _ => panic!("This should never happen") }); let guard = Some(Expr::Call { f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))), args: vec![comparison, Expr::ConditionalTargetSigilValue], }); Subpattern { tag: None, subpatterns: vec![], guard, bound_vars: vec![], } }, StringPattern(s) => { let guard = Some(Expr::Call { f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))), args: vec![Expr::Lit(Lit::StringLit(s.clone())), Expr::ConditionalTargetSigilValue] }); Subpattern { tag: None, subpatterns: vec![], guard, bound_vars: vec![], } }, BoolPattern(b) => { let guard = Some(if *b { Expr::ConditionalTargetSigilValue } else { Expr::Call { f: Box::new(Expr::Func(Func::BuiltIn(Builtin::BooleanNot))), args: vec![Expr::ConditionalTargetSigilValue] } }); Subpattern { tag: None, subpatterns: vec![], guard, bound_vars: vec![], } }, } } }