diff --git a/src/parser.rs b/src/parser.rs index 52be52b..2908678 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,14 +1,14 @@ -use chumsky::{error::{EmptyErr, Rich, Simple}, prelude::{choice, just, none_of, one_of, recursive}, text, IterParser, Parser}; +use chumsky::{error::{EmptyErr, Rich, Simple}, prelude::{any, choice, end, just, none_of, one_of, recursive}, text, IterParser, Parser}; #[derive(Debug, Clone)] -struct Index { +pub struct Index { value: Box, index: Box } #[derive(Debug, Clone)] -enum Primitive { +pub enum Primitive { Number(f64), String(String), Variable(String), @@ -16,38 +16,38 @@ enum Primitive { } #[derive(Debug, Clone)] -enum Bindable { - Primitive(Primitive), +pub enum Bindable { + Primitive(Primitive) } #[derive(Debug, Clone)] -struct Command { +pub struct Command { name: Box, args: Vec } #[derive(Debug, Clone)] -struct Set { +pub struct Set { name: Box, value: Box } #[derive(Debug, Clone)] -struct If { +pub struct If { condition: Box, body: Vec, else_body: Option> } #[derive(Debug, Clone)] -struct While { +pub struct While { condition: Box, body: Vec, else_body: Option> } #[derive(Debug, Clone)] -struct For { +pub struct For { name: Box, iterable: Box, body: Vec, @@ -55,7 +55,14 @@ struct For { } #[derive(Debug, Clone)] -struct Loop { +pub struct Loop { + body: Vec +} + +#[derive(Debug, Clone)] +pub struct Function { + name: String, + args: Vec, body: Vec } @@ -67,11 +74,14 @@ pub enum Statement { While(While), If(If), Loop(Loop), - Break + Function(Function), + Return(Option), + Break, + Continue, } #[derive(Debug, Clone)] -enum Value { +pub enum Value { Primitive(Primitive), Group(Vec) } @@ -145,10 +155,11 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec, chumsky::extra::D direct_string.clone(), )); let eol = one_of("\n\r;"); - // let r#break = just("break").map(|_| Statement::Break); let variable = just('$').ignore_then(text::ident()); + let comment = just('#').then(any().and_is(just('\n').not()).repeated()); + recursive(|expr| { let primitive = choice(( number.map(Primitive::Number), @@ -156,36 +167,165 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec, chumsky::extra::D string.map(Primitive::String), )); - let bindable = primitive.clone(); + let bindable = primitive.clone().map(Bindable::Primitive); - let group = just('(') - .ignore_then(expr.clone()) - .then_ignore(just(')')) + let bindable_group = bindable + .clone() + .padded() + .separated_by(just(",")) + .collect() + .delimited_by(just('('), just(')')); + + let group = expr.clone() + .delimited_by(just('('), just(')')) .map(|v| Value::Group(v)); + let block = expr.clone() + .delimited_by(just('{'), just('}')); + let value = choice(( group, primitive.map(Value::Primitive), )); - let command = value - .separated_by(text::whitespace().at_least(1)) - .at_least(1) - .allow_trailing() - .allow_leading() - .collect() - .map(|args: Vec| { - let name = args[0].clone(); - let args = args[1..].to_vec(); + let cmdname = value.clone() + .and_is(choice(( + just("set"), + just("if"), + just("while"), + just("for"), + just("loop"), + just("break"), + just("continue"), + just("return"), + just("fn"), + )).then(end()).not()); + let args = value.clone() + .separated_by(text::inline_whitespace().at_least(1)) + .allow_leading() + .allow_trailing() + .collect(); + + let command = + cmdname + .padded_by(text::inline_whitespace()) + .then(args) + .map(|(name, args): (Value, Vec)| { Command { name: Box::new(name), args: args.into_iter().map(|v| v.clone()).collect() } }); - let statement = command.map(Statement::Command); + let set = just("set") + .then_ignore(text::inline_whitespace().at_least(1)) + .ignore_then(bindable.clone()) + .then_ignore(just('=').padded()) + .then(value.clone()) + .map(|(name, value): (Bindable, Value)| { + Set { + name: Box::new(name), + value: Box::new(value) + } + }); - statement.separated_by(eol).at_least(1).collect() + let else_ = just("else") + .then(text::inline_whitespace().at_least(1)) + .ignore_then(choice(( + block.clone(), + expr.clone() + ))); + + let if_ = just("if") + .then_ignore(text::inline_whitespace().at_least(1)) + .ignore_then(command.clone()) + .then(block.clone().padded()) + .then(else_.clone().or_not()) + .map(|((cond, body), else_body): ((Command, Vec), _)| { + If { + condition: Box::new(cond), + body, + else_body + } + }); + + let while_ = just("while") + .then_ignore(text::inline_whitespace().at_least(1)) + .ignore_then(command.clone()) + .then(block.clone().padded()) + .then(else_.clone().or_not()) + .map(|((cond, body), else_body): ((Command, Vec), _)| { + While { + condition: Box::new(cond), + body, + else_body + } + }); + + let for_ = just("for") + .then_ignore(text::inline_whitespace().at_least(1)) + .ignore_then(bindable.clone()) + .then_ignore(just("in").padded()) + .then(value.clone()) + .then(block.clone().padded()) + .then(else_.clone().or_not()) + .map(|(((name, iterable), body), else_body): (((Bindable, Value), Vec), _)| { + For { + name: Box::new(name), + iterable: Box::new(iterable), + body, + else_body + } + }); + + let loop_ = just("loop") + .ignore_then(block.clone().padded()) + .map(|body: Vec| { + Loop { + body + } + }); + + let return_ = just("return") + .then_ignore(text::inline_whitespace().at_least(1)) + .ignore_then(value.clone().or_not()) + .map(|v: Option| { + Statement::Return(v) + }); + + let function = just("fn") + .then_ignore(text::inline_whitespace().at_least(1)) + .ignore_then(text::ident()) + .then(bindable_group.clone()) + .then(block.clone().padded()) + .map(|((name, args), body): ((&str, Vec), Vec)| { + Function { + name: name.to_string(), + args: args, + body + } + }); + + let statement = choice(( + set.map(Statement::Set), + if_.map(Statement::If), + while_.map(Statement::While), + for_.map(Statement::For), + loop_.map(Statement::Loop), + function.map(Statement::Function), + return_, + just("break").to(Statement::Break), + just("continue").to(Statement::Continue), + command.map(Statement::Command), + )); + + statement + .padded_by(text::inline_whitespace().ignored().or(comment.ignored())) + .separated_by(eol.repeated().at_least(1)) + .at_least(1) + .allow_trailing() + .allow_leading() + .collect() }) } \ No newline at end of file diff --git a/test/parsetest.rush b/test/parsetest.rush index 2be6812..37bebef 100644 --- a/test/parsetest.rush +++ b/test/parsetest.rush @@ -1 +1 @@ - test ( test 2 ) \ No newline at end of file +fn test() {echo}