add guard clauses to match and fn

This commit is contained in:
Scott Richmond 2024-11-20 20:10:17 -05:00
parent 7a4bf5ff29
commit 56e6712154
3 changed files with 104 additions and 24 deletions

View File

@ -21,9 +21,9 @@
// - [x] with stack mechanics and refcounting // - [x] with stack mechanics and refcounting
// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM) // - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM)
// - [ ] with all the necessary forms for current Ludus // - [ ] with all the necessary forms for current Ludus
// * [ ] guards in match clauses // * [x] guards in match clauses
// * [x] `as` patterns // * [x] `as` patterns
// * [ ] splat patterns in tuples, lists, dicts // * [x] splat patterns in tuples, lists, dicts
// * [ ] splats in list and dict literals // * [ ] splats in list and dict literals
// * [ ] `loop` and `recur` // * [ ] `loop` and `recur`
// * [ ] string patterns // * [ ] string patterns
@ -55,8 +55,14 @@ use crate::base::*;
pub fn main() { pub fn main() {
let src = " let src = "
let [x, y, ...z] = [1, 2, 3, 4] fn t () -> true
z fn f () -> false
fn id {
(x) if f () -> x
(x) -> :whoops
}
id (:foo)
"; ";
let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
if lex_errs.len() > 0 { if lex_errs.len() > 0 {

View File

@ -215,9 +215,8 @@ pub enum Pattern<'src> {
Placeholder, Placeholder,
Tuple(Vec<Spanned<Self>>), Tuple(Vec<Spanned<Self>>),
List(Vec<Spanned<Self>>), List(Vec<Spanned<Self>>),
// is this the right representation for Dicts? Pair(&'src str, Box<Spanned<Self>>),
// Could/should this also be a Vec? Dict(Vec<Spanned<Self>>),
Dict(Vec<Spanned<PairPattern<'src>>>),
} }
impl<'src> fmt::Display for Pattern<'src> { impl<'src> fmt::Display for Pattern<'src> {
@ -257,6 +256,7 @@ impl<'src> fmt::Display for Pattern<'src> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", ") .join(", ")
), ),
Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0),
} }
} }
} }
@ -340,20 +340,18 @@ where
let pair_pattern = select! {Token::Keyword(k) => k} let pair_pattern = select! {Token::Keyword(k) => k}
.then(pattern.clone()) .then(pattern.clone())
.map_with(|(key, patt), e| (PairPattern { key, patt }, e.span())); .map_with(|(key, patt), e| (Pattern::Pair(key, Box::new(patt)), e.span()));
let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| { let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| {
( (
PairPattern { Pattern::Pair(w, Box::new((Pattern::Word(w), e.span()))),
key: w,
patt: ((Pattern::Word(w), e.span())),
},
e.span(), e.span(),
) )
}); });
let dict_pattern = pair_pattern let dict_pattern = pair_pattern
.or(shorthand_pattern) .or(shorthand_pattern)
.or(splattern.clone())
.separated_by(separators.clone()) .separated_by(separators.clone())
.allow_leading() .allow_leading()
.allow_trailing() .allow_trailing()
@ -523,6 +521,18 @@ where
) )
.map_with(|clauses, e| (Ast::When(clauses), e.span())); .map_with(|clauses, e| (Ast::When(clauses), e.span()));
let guarded_clause = pattern
.clone()
.then_ignore(just(Token::Reserved("if")))
.then(simple.clone())
.then_ignore(just(Token::Punctuation("->")))
.then(expr.clone())
.map_with(|((patt, guard), body), _| MatchClause {
patt,
guard: Some(guard),
body,
});
let match_clause = pattern let match_clause = pattern
.clone() .clone()
.then_ignore(just(Token::Punctuation("->"))) .then_ignore(just(Token::Punctuation("->")))
@ -539,6 +549,7 @@ where
.then( .then(
match_clause match_clause
.clone() .clone()
.or(guarded_clause)
.separated_by(terminators.clone()) .separated_by(terminators.clone())
.allow_leading() .allow_leading()
.allow_trailing() .allow_trailing()
@ -575,6 +586,19 @@ where
.then(block.clone()) .then(block.clone())
.map_with(|(count, body), e| (Ast::Repeat(Box::new(count), Box::new(body)), e.span())); .map_with(|(count, body), e| (Ast::Repeat(Box::new(count), Box::new(body)), e.span()));
let fn_guarded = tuple_pattern
.clone()
.then_ignore(just(Token::Reserved("if")))
.then(simple.clone())
.then_ignore(just(Token::Punctuation("->")))
.then(nonbinding.clone())
.map_with(|((patt, guard), body), _| MatchClause {
patt,
body,
guard: Some(guard),
})
.labelled("function clause");
let fn_clause = tuple_pattern let fn_clause = tuple_pattern
.clone() .clone()
.then_ignore(just(Token::Punctuation("->"))) .then_ignore(just(Token::Punctuation("->")))
@ -645,7 +669,7 @@ where
let fn_named = just(Token::Reserved("fn")) let fn_named = just(Token::Reserved("fn"))
.ignore_then(word.clone()) .ignore_then(word.clone())
.then(fn_clause.clone()) .then(fn_clause.clone().or(fn_guarded.clone()))
.map_with(|(word, clause), e| { .map_with(|(word, clause), e| {
let name = if let Ast::Word(word) = word.0 { let name = if let Ast::Word(word) = word.0 {
word word
@ -660,6 +684,7 @@ where
.then( .then(
fn_clause fn_clause
.clone() .clone()
.or(fn_guarded.clone())
.separated_by(terminators.clone()) .separated_by(terminators.clone())
.allow_leading() .allow_leading()
.allow_trailing() .allow_trailing()

View File

@ -136,23 +136,56 @@ pub fn match_pattern<'src, 'a>(
} }
Some(ctx) Some(ctx)
} }
// TODO: optimize this on several levels
// - [ ] opportunistic mutation
// - [ ] get rid of all the pointer indirection in word splats
(Pattern::Dict(x), Value::Dict(y)) => { (Pattern::Dict(x), Value::Dict(y)) => {
if x.len() != y.len() { let has_splat = x.iter().any(|patt| {
if let (Pattern::Splattern(_), _) = patt {
true
} else {
false
}
});
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
return None; return None;
}; };
let to = ctx.len(); let to = ctx.len();
for (PairPattern { key, patt }, _) in x { let mut matched = vec![];
for i in 0..x.len() {
let (pattern, _) = &x[i];
match pattern {
Pattern::Pair(key, patt) => {
if let Some(val) = y.get(key) { if let Some(val) = y.get(key) {
if let None = match_pattern(&patt.0, val, ctx) { if let None = match_pattern(&patt.0, val, ctx) {
while ctx.len() > to { while ctx.len() > to {
ctx.pop(); ctx.pop();
return None; return None;
} }
} else {
matched.push(key);
} }
} else { } else {
return None; return None;
}; };
} }
Pattern::Splattern(pattern) => match &(*pattern).0 {
Pattern::Word(w) => {
// TODO: find a way to take ownership
// this will ALWAYS make structural changes, because of this clone
// we want opportunistic mutation if possible
let mut unmatched = y.clone();
for key in matched.iter() {
unmatched.remove(*key);
}
ctx.push((*w, Value::Dict(unmatched)));
}
Pattern::Placeholder => (),
_ => unreachable!(),
},
_ => unreachable!(),
}
}
Some(ctx) Some(ctx)
} }
_ => None, _ => None,
@ -165,8 +198,24 @@ pub fn match_clauses<'src, 'a>(
ctx: &'a mut Vec<(&'src str, Value<'src>)>, ctx: &'a mut Vec<(&'src str, Value<'src>)>,
) -> Result<Value<'src>, LudusError> { ) -> Result<Value<'src>, LudusError> {
let to = ctx.len(); let to = ctx.len();
for MatchClause { patt, body, .. } in clauses.iter() { for MatchClause { patt, body, guard } in clauses.iter() {
if let Some(ctx) = match_pattern(&patt.0, value, ctx) { if let Some(ctx) = match_pattern(&patt.0, value, ctx) {
let pass_guard = match guard {
None => true,
Some((ast, _)) => {
let guard_res = eval(&ast, ctx);
match &guard_res {
Err(_) => return guard_res,
Ok(val) => val.bool(),
}
}
};
if !pass_guard {
while ctx.len() > to {
ctx.pop();
}
continue;
}
let res = eval(&body.0, ctx); let res = eval(&body.0, ctx);
while ctx.len() > to { while ctx.len() > to {
ctx.pop(); ctx.pop();