diff --git a/src/main.rs b/src/main.rs index ea8c73d..3873cff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,9 +21,9 @@ // - [x] with stack mechanics and refcounting // - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM) // - [ ] with all the necessary forms for current Ludus -// * [ ] guards in match clauses +// * [x] guards in match clauses // * [x] `as` patterns -// * [ ] splat patterns in tuples, lists, dicts +// * [x] splat patterns in tuples, lists, dicts // * [ ] splats in list and dict literals // * [ ] `loop` and `recur` // * [ ] string patterns @@ -55,8 +55,14 @@ use crate::base::*; pub fn main() { let src = " -let [x, y, ...z] = [1, 2, 3, 4] -z +fn t () -> true +fn f () -> false + +fn id { + (x) if f () -> x + (x) -> :whoops +} +id (:foo) "; let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if lex_errs.len() > 0 { diff --git a/src/parser.rs b/src/parser.rs index faf326d..9ab76b0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -215,9 +215,8 @@ pub enum Pattern<'src> { Placeholder, Tuple(Vec>), List(Vec>), - // is this the right representation for Dicts? - // Could/should this also be a Vec? - Dict(Vec>>), + Pair(&'src str, Box>), + Dict(Vec>), } impl<'src> fmt::Display for Pattern<'src> { @@ -257,6 +256,7 @@ impl<'src> fmt::Display for Pattern<'src> { .collect::>() .join(", ") ), + Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0), } } } @@ -340,20 +340,18 @@ where let pair_pattern = select! {Token::Keyword(k) => k} .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| { ( - PairPattern { - key: w, - patt: ((Pattern::Word(w), e.span())), - }, + Pattern::Pair(w, Box::new((Pattern::Word(w), e.span()))), e.span(), ) }); let dict_pattern = pair_pattern .or(shorthand_pattern) + .or(splattern.clone()) .separated_by(separators.clone()) .allow_leading() .allow_trailing() @@ -523,6 +521,18 @@ where ) .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 .clone() .then_ignore(just(Token::Punctuation("->"))) @@ -539,6 +549,7 @@ where .then( match_clause .clone() + .or(guarded_clause) .separated_by(terminators.clone()) .allow_leading() .allow_trailing() @@ -575,6 +586,19 @@ where .then(block.clone()) .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 .clone() .then_ignore(just(Token::Punctuation("->"))) @@ -645,7 +669,7 @@ where let fn_named = just(Token::Reserved("fn")) .ignore_then(word.clone()) - .then(fn_clause.clone()) + .then(fn_clause.clone().or(fn_guarded.clone())) .map_with(|(word, clause), e| { let name = if let Ast::Word(word) = word.0 { word @@ -660,6 +684,7 @@ where .then( fn_clause .clone() + .or(fn_guarded.clone()) .separated_by(terminators.clone()) .allow_leading() .allow_trailing() diff --git a/src/vm.rs b/src/vm.rs index cd51e18..6cce52d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -136,22 +136,55 @@ pub fn match_pattern<'src, 'a>( } 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)) => { - 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; }; let to = ctx.len(); - for (PairPattern { key, patt }, _) in x { - if let Some(val) = y.get(key) { - if let None = match_pattern(&patt.0, val, ctx) { - while ctx.len() > to { - ctx.pop(); + 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 None = match_pattern(&patt.0, val, ctx) { + while ctx.len() > to { + ctx.pop(); + return None; + } + } else { + matched.push(key); + } + } else { return None; - } + }; } - } else { - 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) } @@ -165,8 +198,24 @@ pub fn match_clauses<'src, 'a>( ctx: &'a mut Vec<(&'src str, Value<'src>)>, ) -> Result, LudusError> { 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) { + 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); while ctx.len() > to { ctx.pop();