use crate::parser::*; use crate::spans::Span; use std::collections::{HashMap, HashSet}; #[derive(Clone, Debug, PartialEq)] pub struct VErr { msg: String, span: Span, } impl VErr { pub fn new(msg: String, span: Span) -> VErr { VErr { msg, span } } } #[derive(Clone, Debug, PartialEq)] struct VStatus { tail_position: bool, in_loop: bool, last_term: bool, has_placeholder: bool, used_bindings: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Arity { Fixed(u8), Splat(u8), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum FnInfo { Declared, Defined(HashSet, HashSet), Unknown, } #[derive(Debug, PartialEq)] pub struct Validator<'a> { // TODO: add another term here: FnStatus. See Issue #18. pub locals: Vec<(String, Span, FnInfo)>, pub prelude: &'a Vec, pub ast: &'a Ast, pub span: Span, pub errors: Vec, pub fn_info: HashMap<*const Ast, FnInfo>, status: VStatus, } impl<'a> Validator<'a> { pub fn new(ast: &'a Ast, span: Span, prelude: &'a Vec) -> Validator<'a> { Validator { locals: vec![], prelude, ast, span, fn_info: std::collections::HashMap::new(), errors: vec![], status: VStatus { tail_position: false, in_loop: false, last_term: false, has_placeholder: false, used_bindings: vec![], }, } } fn bind(&mut self, name: String) { self.locals.push((name, self.span, FnInfo::Unknown)); } fn declare_fn(&mut self, name: String) { self.locals.push((name, self.span, FnInfo::Declared)); } fn define_fn(&mut self, name: String, info: FnInfo) { self.locals.push((name, self.span, info)); } fn resolved(&self, name: &str) -> bool { self.locals.iter().any(|(bound, ..)| name == bound.as_str()) || self.prelude.iter().any(|bound| name == bound.as_str()) } fn bound(&self, name: &str) -> Option<&(String, Span, FnInfo)> { match self.locals.iter().rev().find(|(bound, ..)| name == bound) { Some(binding) => Some(binding), None => None, } } fn err(&mut self, msg: String) { self.errors.push(VErr::new(msg, self.span)) } fn use_name(&mut self, name: String) { self.status.used_bindings.push(name); } pub fn validate(&mut self) { let root = self.ast; match root { Ast::Error => unreachable!(), Ast::Word(name) | Ast::Splat(name) => { if !self.resolved(name) { self.err(format!("unbound name `{name}`")) } else { self.use_name(name.to_string()) } } Ast::Interpolated(parts) => { for part in parts { if let (StringPart::Word(name), span) = part { self.span = *span; if !self.resolved(name.as_str()) { self.err(format!("unbound name `{name}`")); } else { self.use_name(name.to_string()); } } } } // validate each line // ensure it's not empty // pass through tail position validation // check if there are any declared but undefined functions // pop all the bindings off the local stack Ast::Block(block) => { if block.is_empty() { self.err("blocks must have at least one expression".to_string()); return; } let to = self.locals.len(); let tailpos = self.status.tail_position; for (expr, span) in block.iter().take(block.len() - 1) { self.status.tail_position = false; self.ast = expr; self.span = *span; self.validate(); } let (expr, span) = block.last().unwrap(); self.ast = expr; self.span = *span; self.status.tail_position = tailpos; self.validate(); let block_bindings = self.locals.split_off(to); for binding in block_bindings { let (name, _, fn_info) = binding; if matches!(fn_info, FnInfo::Declared) { self.err(format!("fn `{name}` is declared but not defined")) } } } // if in tail position, pass through tail position validation // no unbound names Ast::If(cond, then, r#else) => { let tailpos = self.status.tail_position; self.status.tail_position = false; let (expr, span) = cond.as_ref(); self.ast = expr; self.span = *span; self.validate(); // pass through tailpos only to then/else self.status.tail_position = tailpos; let (expr, span) = then.as_ref(); self.ast = expr; self.span = *span; self.validate(); let (expr, span) = r#else.as_ref(); self.ast = expr; self.span = *span; self.validate(); } Ast::Tuple(members) => { if members.is_empty() { return; } let tailpos = self.status.tail_position; self.status.tail_position = false; for (expr, span) in members { self.ast = expr; self.span = *span; self.validate(); } self.status.tail_position = tailpos; } // no more than one placeholder Ast::Arguments(args) => { if args.is_empty() { return; } let tailpos = self.status.tail_position; self.status.tail_position = false; for (expr, span) in args { self.ast = expr; self.span = *span; self.validate(); } self.status.has_placeholder = false; self.status.tail_position = tailpos; } Ast::Placeholder => { if self.status.has_placeholder { self.err( "you may only use one placeholder when partially applying functions" .to_string(), ); } self.status.has_placeholder = true; } Ast::List(list) => { if list.is_empty() { return; } let tailpos = self.status.tail_position; self.status.tail_position = false; for (expr, span) in list { self.ast = expr; self.span = *span; self.validate(); } self.status.tail_position = tailpos; } Ast::Pair(_, value) => { let (expr, span) = value.as_ref(); self.ast = expr; self.span = *span; self.validate(); } Ast::Dict(dict) => { if dict.is_empty() { return; } let tailpos = self.status.tail_position; self.status.tail_position = false; for (expr, span) in dict { self.ast = expr; self.span = *span; self.validate(); } self.status.tail_position = tailpos; } // TODO! // first check all nodes // then... // check arity is 1 if first term is keyword // check arity against fn info if first term is word and second term is args Ast::Synthetic(first, second, rest) => { match (&first.0, &second.0) { (Ast::Word(_), Ast::Keyword(_)) => { let (expr, span) = first.as_ref(); self.ast = expr; self.span = *span; self.validate(); } (Ast::Keyword(_), Ast::Arguments(args)) => { if args.len() != 1 { self.err("called keywords may only take one argument".to_string()) } let (expr, span) = second.as_ref(); self.ast = expr; self.span = *span; self.validate(); } (Ast::Word(_), Ast::Arguments(_)) => { let (expr, span) = first.as_ref(); self.ast = expr; self.span = *span; self.validate(); let (expr, span) = second.as_ref(); self.ast = expr; self.span = *span; self.validate(); } _ => unreachable!(), } for term in rest { let (expr, span) = term; self.ast = expr; self.span = *span; self.validate(); } } Ast::When(clauses) => { // let tailpos = self.status.tail_position; // for (clause, _) in clauses { // self.status.tail_position = false; // let (expr, span) = clause.cond.clone(); // self.ast = &expr; // self.span = span; // self.validate(); // self.status.tail_position = tailpos; // let (expr, span) = clause.body; // self.ast = &expr; // self.span = span; // self.validate(); // } } // binding forms // TODO: set up errors to include original binding Ast::Box(name, boxed) => { if self.bound(name).is_some() { self.err(format!("box name `{name}` is already bound")); } else { self.bind(name.to_string()); } let (expr, span) = boxed.as_ref(); self.ast = expr; self.span = *span; self.validate(); } Ast::Let(lhs, rhs) => { let (expr, span) = rhs.as_ref(); self.ast = expr; self.span = *span; self.validate(); } Ast::Match(scrutinee, clauses) => { let (expr, span) = scrutinee.as_ref(); self.ast = expr; self.span = *span; self.validate(); } Ast::FnDeclaration(name) => { let tailpos = self.status.tail_position; self.status.tail_position = false; if self.bound(name).is_some() { self.err(format!("fn name `{name}` is already bound")); return; } self.declare_fn(name.to_string()); self.status.tail_position = tailpos; } Ast::Fn(name, clauses, ..) => { match self.bound(name) { Some((_, _, FnInfo::Declared)) => (), None => (), _ => { self.err(format!("name `{name}` is already bound")); } } let from = self.status.used_bindings.len(); let arities = HashSet::new(); for clause in clauses { // TODO: validate all parts of clauses // add clause arity to arities } // this should be right // we can't bind anything that's already bound, // even in arg names // so anything that is already bound and used // will, of necessity, be closed over // we don't want to try to close over locals in functions let mut closed_over = HashSet::new(); for binding in self.status.used_bindings.iter().skip(from) { if self.bound(binding.as_str()).is_some() { closed_over.insert(binding.clone()); } } let info = FnInfo::Defined(arities, closed_over); self.define_fn(name.to_string(), info) } Ast::Panic(msg) => { let tailpos = self.status.tail_position; self.status.tail_position = false; let (expr, span) = msg.as_ref(); self.ast = expr; self.span = *span; self.validate(); self.status.tail_position = tailpos; } Ast::Do(terms) => { for term in terms { let (expr, span) = term; self.ast = expr; self.span = *span; self.validate(); } } Ast::Repeat(times, body) => { self.status.tail_position = false; let (expr, span) = times.as_ref(); self.ast = expr; self.span = *span; self.validate(); let (expr, span) = body.as_ref(); self.ast = expr; self.span = *span; self.validate(); } Ast::Loop(with, body) => { let (expr, span) = with.as_ref(); self.span = *span; self.ast = expr; self.validate(); let in_loop = self.status.in_loop; self.status.in_loop = true; // let (expr, span) = body; // self.span = span; // self.expr = expr; // self.validate(); self.status.in_loop = in_loop; } Ast::Recur(args) => { if !self.status.in_loop { self.err("you may only use `recur` in a loop form".to_string()); } if !self.status.tail_position { self.err("you may only use `recur` in tail position".to_string()); } self.status.tail_position = false; for arg in args { let (expr, span) = arg; self.ast = expr; self.span = *span; self.validate(); } } // terminals can never be invalid Ast::Nil | Ast::Boolean(_) | Ast::Number(_) | Ast::Keyword(_) | Ast::String(_) => (), }; self.ast = root; } }