Compare commits

...

2 Commits

Author SHA1 Message Date
Scott Richmond
eafe7a7fa9 complete first draft of validator 2024-12-10 23:42:05 -05:00
Scott Richmond
9542dcf5e2 cover all Ast branches 2024-12-10 22:40:57 -05:00
2 changed files with 207 additions and 48 deletions

View File

@ -148,11 +148,11 @@ pub fn run(src: &'static str) {
let dummy_prelude = vec![]; let dummy_prelude = vec![];
let mut valxor = validator::Validator::new(&ast, span, &dummy_prelude); let mut v6or = validator::Validator::new(&ast, span, &dummy_prelude);
// valxor.validate(); v6or.validate();
// dbg!(valxor); dbg!(v6or);
let mut ctx = prelude(); let mut ctx = prelude();
ctx.ast = * ctx.ast = *
@ -167,8 +167,10 @@ pub fn run(src: &'static str) {
pub fn main() { pub fn main() {
let src = " let src = "
let #{:a (x, y), :b [1, 2, (a, b)]} = #{:a (1, 2), :b [1, 2, (7, 8)]} let bar = :bar
(x, y, a, b) match :foo with {
:foo -> bar
}
"; ";
run(src); run(src);
// struct_scalpel::print_dissection_info::<value::Value>() // struct_scalpel::print_dissection_info::<value::Value>()

View File

@ -101,18 +101,34 @@ impl<'a> Validator<'a> {
self.status.used_bindings.push(name); self.status.used_bindings.push(name);
} }
fn arity(&mut self) -> Arity {
let Ast::MatchClause(pattern, ..) = self.ast else {
unreachable!("internal Ludus error")
};
let (Ast::TuplePattern(members), _) = pattern.as_ref() else {
unreachable!("internal Ludus error");
};
let last_member = members.last();
match last_member {
None => Arity::Fixed(0),
Some((Ast::Splattern(..), _)) => Arity::Splat(members.len() as u8),
Some(_) => Arity::Fixed(members.len() as u8),
}
}
pub fn validate(&mut self) { pub fn validate(&mut self) {
use Ast::*;
let root = self.ast; let root = self.ast;
match root { match root {
Ast::Error => unreachable!(), Error => unreachable!(),
Ast::Word(name) | Ast::Splat(name) => { Word(name) | Ast::Splat(name) => {
if !self.resolved(name) { if !self.resolved(name) {
self.err(format!("unbound name `{name}`")) self.err(format!("unbound name `{name}`"))
} else { } else {
self.use_name(name.to_string()) self.use_name(name.to_string())
} }
} }
Ast::Interpolated(parts) => { Interpolated(parts) => {
for part in parts { for part in parts {
if let (StringPart::Word(name), span) = part { if let (StringPart::Word(name), span) = part {
self.span = *span; self.span = *span;
@ -129,7 +145,7 @@ impl<'a> Validator<'a> {
// pass through tail position validation // pass through tail position validation
// check if there are any declared but undefined functions // check if there are any declared but undefined functions
// pop all the bindings off the local stack // pop all the bindings off the local stack
Ast::Block(block) => { Block(block) => {
if block.is_empty() { if block.is_empty() {
self.err("blocks must have at least one expression".to_string()); self.err("blocks must have at least one expression".to_string());
return; return;
@ -160,7 +176,7 @@ impl<'a> Validator<'a> {
} }
// if in tail position, pass through tail position validation // if in tail position, pass through tail position validation
// no unbound names // no unbound names
Ast::If(cond, then, r#else) => { If(cond, then, r#else) => {
let tailpos = self.status.tail_position; let tailpos = self.status.tail_position;
self.status.tail_position = false; self.status.tail_position = false;
@ -181,7 +197,7 @@ impl<'a> Validator<'a> {
self.span = *span; self.span = *span;
self.validate(); self.validate();
} }
Ast::Tuple(members) => { Tuple(members) => {
if members.is_empty() { if members.is_empty() {
return; return;
} }
@ -195,7 +211,7 @@ impl<'a> Validator<'a> {
self.status.tail_position = tailpos; self.status.tail_position = tailpos;
} }
// no more than one placeholder // no more than one placeholder
Ast::Arguments(args) => { Arguments(args) => {
if args.is_empty() { if args.is_empty() {
return; return;
} }
@ -209,7 +225,7 @@ impl<'a> Validator<'a> {
self.status.has_placeholder = false; self.status.has_placeholder = false;
self.status.tail_position = tailpos; self.status.tail_position = tailpos;
} }
Ast::Placeholder => { Placeholder => {
if self.status.has_placeholder { if self.status.has_placeholder {
self.err( self.err(
"you may only use one placeholder when partially applying functions" "you may only use one placeholder when partially applying functions"
@ -218,7 +234,7 @@ impl<'a> Validator<'a> {
} }
self.status.has_placeholder = true; self.status.has_placeholder = true;
} }
Ast::List(list) => { List(list) => {
if list.is_empty() { if list.is_empty() {
return; return;
} }
@ -232,13 +248,13 @@ impl<'a> Validator<'a> {
self.status.tail_position = tailpos; self.status.tail_position = tailpos;
} }
Ast::Pair(_, value) => { Pair(_, value) => {
let (expr, span) = value.as_ref(); let (expr, span) = value.as_ref();
self.ast = expr; self.ast = expr;
self.span = *span; self.span = *span;
self.validate(); self.validate();
} }
Ast::Dict(dict) => { Dict(dict) => {
if dict.is_empty() { if dict.is_empty() {
return; return;
} }
@ -257,7 +273,7 @@ impl<'a> Validator<'a> {
// then... // then...
// check arity is 1 if first term is keyword // check arity is 1 if first term is keyword
// check arity against fn info if first term is word and second term is args // check arity against fn info if first term is word and second term is args
Ast::Synthetic(first, second, rest) => { Synthetic(first, second, rest) => {
match (&first.0, &second.0) { match (&first.0, &second.0) {
(Ast::Word(_), Ast::Keyword(_)) => { (Ast::Word(_), Ast::Keyword(_)) => {
let (expr, span) = first.as_ref(); let (expr, span) = first.as_ref();
@ -296,26 +312,32 @@ impl<'a> Validator<'a> {
self.validate(); self.validate();
} }
} }
Ast::When(clauses) => { WhenClause(cond, body) => {
// let tailpos = self.status.tail_position; let tailpos = self.status.tail_position;
// for (clause, _) in clauses { self.status.tail_position = false;
// self.status.tail_position = false; let (expr, span) = cond.as_ref();
// let (expr, span) = clause.cond.clone(); self.ast = expr;
// self.ast = &expr; self.span = *span;
// self.span = span; self.validate();
// self.validate();
// self.status.tail_position = tailpos; self.status.tail_position = tailpos;
// let (expr, span) = clause.body; let (expr, span) = body.as_ref();
// self.ast = &expr; self.ast = expr;
// self.span = span; self.span = *span;
// self.validate(); self.validate();
// } }
When(clauses) => {
for clause in clauses {
let (expr, span) = clause;
self.ast = expr;
self.span = *span;
self.validate();
}
} }
// binding forms // binding forms
// TODO: set up errors to include original binding // TODO: set up errors to include original binding
Ast::LBox(name, boxed) => { LBox(name, boxed) => {
if self.bound(name).is_some() { if self.bound(name).is_some() {
self.err(format!("box name `{name}` is already bound")); self.err(format!("box name `{name}` is already bound"));
} else { } else {
@ -326,20 +348,52 @@ impl<'a> Validator<'a> {
self.span = *span; self.span = *span;
self.validate(); self.validate();
} }
Ast::Let(lhs, rhs) => { Let(lhs, rhs) => {
let (expr, span) = rhs.as_ref(); let (expr, span) = rhs.as_ref();
self.ast = expr; self.ast = expr;
self.span = *span; self.span = *span;
self.validate(); self.validate();
}
Ast::Match(scrutinee, clauses) => { let (expr, span) = lhs.as_ref();
let (expr, span) = scrutinee.as_ref();
self.ast = expr; self.ast = expr;
self.span = *span; self.span = *span;
self.validate(); self.validate();
} }
MatchClause(pattern, guard, body) => {
let to = self.locals.len();
Ast::FnDeclaration(name) => { let (patt, span) = pattern.as_ref();
self.ast = patt;
self.span = *span;
self.validate();
if let Some((expr, span)) = guard.as_ref() {
self.ast = expr;
self.span = *span;
self.validate();
}
let (expr, span) = body.as_ref();
self.ast = expr;
self.span = *span;
self.validate();
self.locals.truncate(to);
}
Match(scrutinee, clauses) => {
let (expr, span) = scrutinee.as_ref();
self.ast = expr;
self.span = *span;
self.validate();
for clause in clauses {
let (expr, span) = clause;
self.ast = expr;
self.span = *span;
self.validate();
}
}
FnDeclaration(name) => {
let tailpos = self.status.tail_position; let tailpos = self.status.tail_position;
self.status.tail_position = false; self.status.tail_position = false;
if self.bound(name).is_some() { if self.bound(name).is_some() {
@ -349,7 +403,7 @@ impl<'a> Validator<'a> {
self.declare_fn(name.to_string()); self.declare_fn(name.to_string());
self.status.tail_position = tailpos; self.status.tail_position = tailpos;
} }
Ast::Fn(name, clauses, ..) => { Fn(name, clauses, ..) => {
match self.bound(name) { match self.bound(name) {
Some((_, _, FnInfo::Declared)) => (), Some((_, _, FnInfo::Declared)) => (),
None => (), None => (),
@ -359,11 +413,16 @@ impl<'a> Validator<'a> {
} }
let from = self.status.used_bindings.len(); let from = self.status.used_bindings.len();
let arities = HashSet::new(); let mut arities = HashSet::new();
for clause in clauses { for clause in clauses {
// TODO: validate all parts of clauses // TODO: validate all parts of clauses
let (expr, span) = clause;
self.ast = expr;
self.span = *span;
// add clause arity to arities // add clause arity to arities
arities.insert(self.arity());
self.validate();
} }
// this should be right // this should be right
@ -381,10 +440,14 @@ impl<'a> Validator<'a> {
let info = FnInfo::Defined(arities, closed_over); let info = FnInfo::Defined(arities, closed_over);
self.define_fn(name.to_string(), info) let root_ptr: *const Ast = root;
self.fn_info.insert(root_ptr, info.clone());
self.define_fn(name.to_string(), info);
} }
Ast::Panic(msg) => { Panic(msg) => {
let tailpos = self.status.tail_position; let tailpos = self.status.tail_position;
self.status.tail_position = false; self.status.tail_position = false;
let (expr, span) = msg.as_ref(); let (expr, span) = msg.as_ref();
@ -394,7 +457,7 @@ impl<'a> Validator<'a> {
self.status.tail_position = tailpos; self.status.tail_position = tailpos;
} }
// TODO: fix the tail call here? // TODO: fix the tail call here?
Ast::Do(terms) => { Do(terms) => {
if terms.len() < 2 { if terms.len() < 2 {
return self.err("do expressions must have at least two terms".to_string()); return self.err("do expressions must have at least two terms".to_string());
} }
@ -413,7 +476,7 @@ impl<'a> Validator<'a> {
} }
self.validate(); self.validate();
} }
Ast::Repeat(times, body) => { Repeat(times, body) => {
self.status.tail_position = false; self.status.tail_position = false;
let (expr, span) = times.as_ref(); let (expr, span) = times.as_ref();
self.ast = expr; self.ast = expr;
@ -425,7 +488,7 @@ impl<'a> Validator<'a> {
self.span = *span; self.span = *span;
self.validate(); self.validate();
} }
Ast::Loop(with, body) => { Loop(with, body) => {
let (expr, span) = with.as_ref(); let (expr, span) = with.as_ref();
self.span = *span; self.span = *span;
self.ast = expr; self.ast = expr;
@ -440,12 +503,29 @@ impl<'a> Validator<'a> {
self.status.in_loop = true; self.status.in_loop = true;
self.status.loop_arity = input.len() as u8; self.status.loop_arity = input.len() as u8;
// for clause in body {} for clause in body {
let (expr, span) = clause;
self.ast = expr;
self.span = *span;
match self.arity() {
Arity::Fixed(clause_arity) => {
if clause_arity != arity {
self.err(format!("mismatched arity: expected {arity} arguments in `loop` clause; got {clause_arity}"))
}
}
Arity::Splat(clause_arity) => {
if clause_arity > arity {
self.err(format!("mismathced arity: expected {arity} arguments in `loop` clause; this clause takes {clause_arity} or more"))
}
}
};
self.validate();
}
self.status.in_loop = in_loop; self.status.in_loop = in_loop;
self.status.loop_arity = arity; self.status.loop_arity = arity;
} }
Ast::Recur(args) => { Recur(args) => {
if !self.status.in_loop { if !self.status.in_loop {
self.err("you may only use `recur` in a `loop` form".to_string()); self.err("you may only use `recur` in a `loop` form".to_string());
return; return;
@ -468,9 +548,86 @@ impl<'a> Validator<'a> {
self.validate(); self.validate();
} }
} }
WordPattern(name) => match self.bound(name) {
Some((name, _span, _)) => {
self.err(format!("name `{name}` is already bound"));
}
None => {
self.bind(name.to_string());
}
},
InterpolatedPattern(parts, _) => {
for (part, span) in parts {
if let StringPart::Word(name) = part {
self.span = *span;
match self.bound(name) {
Some(_) => self.err(format!("name `{name}` is already bound")),
None => self.bind(name.to_string()),
}
}
}
}
AsPattern(name, r#type) => {
match self.bound(name) {
Some((name, _span, _)) => {
self.err(format!("name `{name}` is already bound"));
}
None => {
self.bind(name.to_string());
}
}
let as_type = *r#type;
match as_type {
"nil" | "bool" | "number" | "keyword" | "string" | "tuple" | "dict"
| "list" | "fn" | "box" => (),
_ => self.err(format!("unknown type `:{as_type}`")),
}
}
Splattern(splatted) => {
if !self.status.last_term {
self.err("splats in patterns must come last".to_string());
}
match splatted.as_ref() {
(Ast::Placeholder, _) => (),
(Ast::Word(name), span) => match self.bound(name) {
Some(_) => {
self.span = *span;
self.err(format!("name `{name}` is already bound"))
}
None => self.bind(name.to_string()),
},
_ => unreachable!(),
}
}
TuplePattern(terms) | ListPattern(terms) | DictPattern(terms) => {
if terms.is_empty() {
return;
}
for term in terms.iter().take(terms.len() - 1) {
let (patt, span) = term;
self.ast = patt;
self.span = *span;
self.validate();
}
self.status.last_term = true;
let (patt, span) = terms.last().unwrap();
self.ast = patt;
self.span = *span;
self.validate();
self.status.last_term = false;
}
PairPattern(_, patt) => {
let (patt, span) = patt.as_ref();
self.ast = patt;
self.span = *span;
self.validate();
}
// terminals can never be invalid // terminals can never be invalid
Ast::Nil | Ast::Boolean(_) | Ast::Number(_) | Ast::Keyword(_) | Ast::String(_) => (), Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) => (),
_ => todo!(), // terminal patterns can never be invalid
NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..)
| KeywordPattern(..) | PlaceholderPattern => (),
}; };
self.ast = root; self.ast = root;
} }