diff --git a/src/main.rs b/src/main.rs index f6c1e6d..5a48bae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ // * [x] `loop` and `recur` // * [ ] string patterns // * [ ] string interpolation -// * [ ] docstrings +// * [x] docstrings // * [~] write `base` in Rust // * [ ] turn this into a library function // * [ ] compile this into WASM @@ -58,7 +58,12 @@ use crate::base::*; pub fn main() { let src = " -true +fn foo { + \"this is a docstring\" + () -> :foo + (_) -> :bar +} +foo () "; let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { diff --git a/src/parser.rs b/src/parser.rs index b064a77..6d1cd8a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -54,7 +54,7 @@ pub enum Ast<'src> { Synthetic(Box>, Box>, Vec>), When(Vec>>), Match(Box>, Vec>), - Fn(&'src str, Vec>), + Fn(&'src str, Vec>, Option<&'src str>), FnDeclaration(&'src str), Panic(Box>), Do(Vec>), @@ -149,7 +149,7 @@ impl fmt::Display for Ast<'_> { .join("\n") ) } - Ast::Fn(name, clauses) => { + Ast::Fn(name, clauses, _) => { write!( f, "fn: {}\n{}", @@ -407,10 +407,11 @@ where Token::Nil => Ast::Nil, Token::Boolean(b) => Ast::Boolean(b), Token::Number(n) => Ast::Number(n), - Token::String(s) => Ast::String(s), } .map_with(|v, e| (v, e.span())); + let string = select! {Token::String(s) => Ast::String(s)}.map_with(|s, e| (s, e.span())); + let tuple = simple .clone() .separated_by(separators.clone()) @@ -505,6 +506,7 @@ where .or(tuple.clone()) .or(list) .or(dict) + .or(string) .labelled("simple expression"), ); @@ -599,12 +601,6 @@ where let conditional = when.or(if_).or(match_); - //todo: - // * [x] do - // * [ ] loop - // * [x] repeat - // * [x] panic! - let panic = just(Token::Reserved("panic!")) .ignore_then(nonbinding.clone()) .map_with(|expr, e| (Ast::Panic(Box::new(expr)), e.span())); @@ -653,14 +649,17 @@ where let lambda = just(Token::Reserved("fn")) .ignore_then(fn_unguarded.clone()) - .map_with(|clause, e| (Ast::Fn("anonymous", vec![clause]), e.span())); + .map_with(|clause, e| (Ast::Fn("anonymous", vec![clause], None), e.span())); - let fn_multiclause = fn_clause + let fn_clauses = fn_clause .clone() .separated_by(terminators.clone()) .allow_leading() .allow_trailing() - .collect() + .collect(); + + let loop_multiclause = fn_clauses + .clone() .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))); let fn_single_clause = fn_clause.clone().map_with(|c, _| vec![c]); @@ -668,7 +667,7 @@ where 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())) + .then(loop_multiclause.clone().or(fn_single_clause.clone())) .map_with(|(init, body), e| (Ast::Loop(Box::new(init), body), e.span())); nonbinding.define( @@ -725,19 +724,29 @@ where } else { unreachable!() }; - (Ast::Fn(name, vec![clause]), e.span()) + (Ast::Fn(name, vec![clause], None), e.span()) }); + let docstr = select! {Token::String(s) => s}; + + let fn_multiclause = separators + .clone() + .or_not() + .ignore_then(docstr.or_not()) + .then(fn_clauses.clone()) + .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))) + .map_with(|(docstr, clauses), e| (docstr, clauses, e.span())); + let fn_compound = just(Token::Reserved("fn")) .ignore_then(word) - .then(fn_multiclause.clone()) - .map_with(|(word, clauses), e| { + .then(fn_multiclause) + .map_with(|(word, (docstr, clauses, _)), e| { let name = if let Ast::Word(word) = word.0 { word } else { unreachable!() }; - (Ast::Fn(name, clauses), e.span()) + (Ast::Fn(name, clauses, docstr), e.span()) }); let fn_ = fn_named.or(fn_compound).or(fn_decl); diff --git a/src/vm.rs b/src/vm.rs index f2073c7..df08f28 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -414,7 +414,7 @@ pub fn eval<'src, 'a>( let value = eval(&value.0, ctx)?; match_clauses(&value, clauses, ctx) } - Ast::Fn(name, clauses) => { + Ast::Fn(name, clauses, ..) => { let the_fn = Value::Fn::<'src>(Rc::new(Fn::<'src> { name, body: clauses,