diff --git a/src/main.rs b/src/main.rs index 3873cff..1be9130 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,9 @@ // - [x] do this to extract/simplify/DRY things like tuple patterns, fn clauses, etc. // * [x] Work around chumsky::Stream::from_iter().spanned disappearing in most recent version // * [x] investigate using labels (which is behind a compiler flag, somehow) +// * [ ] write parsing errors // * [ ] wire up Ariadne parsing errors +// * [ ] add stack traces and code locations to panics // * [ ] validation // * [x] break this out into multiple files // * [x] write a tree-walk VM @@ -24,7 +26,7 @@ // * [x] guards in match clauses // * [x] `as` patterns // * [x] splat patterns in tuples, lists, dicts -// * [ ] splats in list and dict literals +// * [x] splats in list and dict literals // * [ ] `loop` and `recur` // * [ ] string patterns // * [ ] string interpolation @@ -55,14 +57,13 @@ use crate::base::*; pub fn main() { let src = " -fn t () -> true -fn f () -> false - -fn id { - (x) if f () -> x - (x) -> :whoops +let foo = :foo +loop (foo) with { + (:foo) -> :done + (x as :bool) -> recur (:bar) + (x as :number) -> recur (:foo) + (x as :keyword) -> recur (:foo) } -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 9ab76b0..134c1b0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -32,17 +32,17 @@ impl<'src> fmt::Display for MatchClause<'src> { } } -#[derive(Clone, Debug, PartialEq)] -pub struct Pair<'src> { - pub key: &'src str, - pub value: Spanned>, -} +// #[derive(Clone, Debug, PartialEq)] +// pub struct Pair<'src> { +// pub key: &'src str, +// pub value: Spanned>, +// } -impl<'src> fmt::Display for Pair<'src> { - fn fmt(self: &Pair<'src>, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "pair: {}: {}", self.key, self.value.0) - } -} +// impl<'src> fmt::Display for Pair<'src> { +// fn fmt(self: &Pair<'src>, f: &mut fmt::Formatter) -> fmt::Result { +// write!(f, "pair: {}: {}", self.key, self.value.0) +// } +// } #[derive(Clone, Debug, PartialEq)] pub enum Ast<'src> { @@ -59,7 +59,7 @@ pub enum Ast<'src> { Tuple(Vec>), Arguments(Vec>), List(Vec>), - Dict(Vec>), + Dict(Vec>), Let(Box>>, Box>), Box(&'src str, Box>), Synthetic(Box>, Box>, Vec>), @@ -70,8 +70,10 @@ pub enum Ast<'src> { Panic(Box>), Do(Vec>), Repeat(Box>, Box>), - // Loop(Box>, Vec>), - // Recur(Vec>), + Splat(&'src str), + Pair(&'src str, Box>), + Loop(Box>, Vec>), + Recur(Vec>), } impl<'src> fmt::Display for Ast<'src> { @@ -105,7 +107,7 @@ impl<'src> fmt::Display for Ast<'src> { "#{{{}}}", entries .iter() - .map(|pair| pair.to_string()) + .map(|pair| (*pair).0.to_string()) .collect::>() .join(", ") ), @@ -184,8 +186,33 @@ impl<'src> fmt::Display for Ast<'src> { ) } Ast::Repeat(_times, _body) => todo!(), - // Ast::Loop(init, body) => todo!(), - // Ast::Recur(args) => todo!(), + Ast::Splat(word) => { + write!(f, "splat: {}", word) + } + Ast::Pair(k, v) => { + write!(f, "pair: {} {}", k, (*v).0.to_string()) + } + Ast::Loop(init, body) => { + write!( + f, + "loop: {} with {}", + (*init).0.to_string(), + body.iter() + .map(|clause| clause.to_string()) + .collect::>() + .join("\n") + ) + } + Ast::Recur(args) => { + write!( + f, + "recur: {}", + args.iter() + .map(|(arg, _)| arg.to_string()) + .collect::>() + .join(", ") + ) + } } } } @@ -428,8 +455,22 @@ where ) }); + let splat = just(Token::Punctuation("...")) + .ignore_then(word.clone()) + .map_with(|(w, _), e| { + ( + Ast::Splat(if let Ast::Word(w) = w { + w + } else { + unreachable!() + }), + e.span(), + ) + }); + let list = simple .clone() + .or(splat.clone()) .separated_by(separators.clone()) .allow_leading() .allow_trailing() @@ -439,15 +480,14 @@ where let pair = select! {Token::Keyword(k) => k} .then(simple.clone()) - .map_with(|(key, value), e| Pair { key, value }); + .map_with(|(key, value), e| (Ast::Pair(key, Box::new(value)), e.span())); - let shorthand = select! {Token::Word(w) => w}.map_with(|w, e| Pair { - key: w, - value: ((Ast::Word(w), e.span())), - }); + let shorthand = select! {Token::Word(w) => w} + .map_with(|w, e| (Ast::Pair(w, Box::new((Ast::Word(w), e.span()))), e.span())); let dict = pair .or(shorthand) + .or(splat.clone()) .separated_by(separators.clone()) .allow_leading() .allow_trailing() @@ -458,12 +498,22 @@ where ) .map_with(|dict, e| (Ast::Dict(dict), e.span())); + let recur = just(Token::Reserved("recur")) + .ignore_then(tuple.clone()) + .map_with(|args, e| { + let (Ast::Tuple(args), _) = args else { + unreachable!() + }; + (Ast::Recur(args), e.span()) + }); + simple.define( synthetic + .or(recur) .or(word) .or(keyword) .or(value) - .or(tuple) + .or(tuple.clone()) .or(list) .or(dict) .labelled("simple expression"), @@ -599,7 +649,7 @@ where }) .labelled("function clause"); - let fn_clause = tuple_pattern + let fn_unguarded = tuple_pattern .clone() .then_ignore(just(Token::Punctuation("->"))) .then(nonbinding.clone()) @@ -610,10 +660,28 @@ where }) .labelled("function clause"); + let fn_clause = fn_guarded.clone().or(fn_unguarded.clone()); + let lambda = just(Token::Reserved("fn")) - .ignore_then(fn_clause.clone()) + .ignore_then(fn_unguarded.clone()) .map_with(|clause, e| (Ast::Fn("anonymous", vec![clause]), e.span())); + let fn_multiclause = fn_clause + .clone() + .separated_by(terminators.clone()) + .allow_leading() + .allow_trailing() + .collect() + .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))); + + let fn_single_clause = fn_clause.clone().map_with(|c, e| vec![c]); + + let r#loop = just(Token::Reserved("loop")) + .ignore_then(tuple.clone()) + .then_ignore(just(Token::Reserved("with"))) + .then(fn_multiclause.clone().or(fn_single_clause.clone())) + .map_with(|(init, body), e| (Ast::Loop(Box::new(init), body), e.span())); + nonbinding.define( simple .clone() @@ -623,6 +691,7 @@ where .or(panic) .or(do_) .or(repeat) + .or(r#loop) .labelled("nonbinding expression"), ); @@ -658,18 +727,9 @@ where (Ast::FnDeclaration(name), e.span()) }); - // let tuple_pattern = pattern - // .clone() - // .separated_by(separators.clone()) - // .allow_leading() - // .allow_trailing() - // .collect() - // .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) - // .map_with(|tuple, e| (Pattern::Tuple(tuple), e.span())); - let fn_named = just(Token::Reserved("fn")) .ignore_then(word.clone()) - .then(fn_clause.clone().or(fn_guarded.clone())) + .then(fn_unguarded.clone()) .map_with(|(word, clause), e| { let name = if let Ast::Word(word) = word.0 { word @@ -681,16 +741,7 @@ where let fn_compound = just(Token::Reserved("fn")) .ignore_then(word.clone()) - .then( - fn_clause - .clone() - .or(fn_guarded.clone()) - .separated_by(terminators.clone()) - .allow_leading() - .allow_trailing() - .collect() - .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))), - ) + .then(fn_multiclause.clone()) .map_with(|(word, clauses), e| { let name = if let Ast::Word(word) = word.0 { word diff --git a/src/vm.rs b/src/vm.rs index 6cce52d..9a4b284 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -322,7 +322,19 @@ pub fn eval<'src, 'a>( Ast::List(members) => { let mut vect = Vector::new(); for member in members { - vect.push_back(eval(&member.0, ctx)?) + if let Ast::Splat(_) = member.0 { + let to_splat = eval(&member.0, ctx)?; + match to_splat { + Value::List(list) => vect.append(list), + _ => { + return Err(LudusError { + msg: "only lists may be splatted into lists".to_string(), + }) + } + } + } else { + vect.push_back(eval(&member.0, ctx)?) + } } Ok(Value::List(vect)) } @@ -333,7 +345,7 @@ pub fn eval<'src, 'a>( } Ok(Value::Tuple(Rc::new(vect))) } - Ast::Word(w) => { + Ast::Word(w) | Ast::Splat(w) => { let val = if let Some((_, value)) = ctx.iter().rev().find(|(name, _)| w == name) { value.clone() } else { @@ -372,11 +384,26 @@ pub fn eval<'src, 'a>( Ok(Value::Tuple(Rc::new(args))) } } - Ast::Dict(pairs) => { + Ast::Dict(terms) => { let mut dict = HashMap::new(); - for Pair { key, value } in pairs { - let value = eval(&value.0, ctx)?; - dict.insert(*key, value); + for term in terms { + let (term, _) = term; + match term { + Ast::Pair(key, value) => { + let value = eval(&value.0, ctx)?; + dict.insert(*key, value); + } + Ast::Splat(_) => { + let resolved = eval(term, ctx)?; + let Value::Dict(to_splat) = resolved else { + return Err(LudusError { + msg: "cannot splat non-dict into dict".to_string(), + }); + }; + dict = to_splat.union(dict); + } + _ => unreachable!(), + } } Ok(Value::Dict(dict)) } @@ -451,7 +478,11 @@ pub fn eval<'src, 'a>( result = apply(next, arg, ctx)?; } Ok(result) - } // Ast::Loop(_, _) => todo!(), - // Ast::Recur(_) => todo!(), + } + Ast::Pair(..) => { + unreachable!() + } + Ast::Loop(_, _) => todo!(), + Ast::Recur(_) => todo!(), } }