diff --git a/src/parse/collection.rs b/src/parse/collection.rs deleted file mode 100644 index ab358f76a..000000000 --- a/src/parse/collection.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::*; - -/// Parse the arguments to a function call. -pub fn args(p: &mut Parser) -> ExprArgs { - let start = p.start(); - let items = collection(p, vec![]); - ExprArgs { span: p.span(start), items } -} - -/// Parse a parenthesized group, which can be either of: -/// - Array literal -/// - Dictionary literal -/// - Parenthesized expression -pub fn parenthesized(p: &mut Parser) -> Expr { - p.start_group(Group::Paren, TokenMode::Code); - let state = if p.eat_if(Token::Colon) { - collection(p, State::Dict(vec![])) - } else { - collection(p, State::Unknown) - }; - let span = p.end_group(); - state.into_expr(span) -} - -/// Parse a collection. -fn collection(p: &mut Parser, mut collection: T) -> T { - let mut missing_coma = None; - - while !p.eof() { - if let Some(arg) = argument(p) { - collection.push_arg(p, arg); - - if let Some(pos) = missing_coma.take() { - p.expected_at("comma", pos); - } - - if p.eof() { - break; - } - - let behind = p.end(); - if p.eat_if(Token::Comma) { - collection.push_comma(); - } else { - missing_coma = Some(behind); - } - } - } - - collection -} - -/// Parse an expression or a named pair. -fn argument(p: &mut Parser) -> Option { - let first = expr(p)?; - if p.eat_if(Token::Colon) { - if let Expr::Ident(name) = first { - Some(ExprArg::Named(Named { name, expr: expr(p)? })) - } else { - p.diag(error!(first.span(), "expected identifier")); - expr(p); - None - } - } else { - Some(ExprArg::Pos(first)) - } -} - -/// Abstraction for comma-separated list of expression / named pairs. -trait Collection { - fn push_arg(&mut self, p: &mut Parser, arg: ExprArg); - fn push_comma(&mut self) {} -} - -impl Collection for Vec { - fn push_arg(&mut self, _: &mut Parser, arg: ExprArg) { - self.push(arg); - } -} - -/// State of collection parsing. -#[derive(Debug)] -enum State { - Unknown, - Expr(Expr), - Array(Vec), - Dict(Vec), -} - -impl State { - fn into_expr(self, span: Span) -> Expr { - match self { - Self::Unknown => Expr::Array(ExprArray { span, items: vec![] }), - Self::Expr(expr) => Expr::Group(ExprGroup { span, expr: Box::new(expr) }), - Self::Array(items) => Expr::Array(ExprArray { span, items }), - Self::Dict(items) => Expr::Dict(ExprDict { span, items }), - } - } -} - -impl Collection for State { - fn push_arg(&mut self, p: &mut Parser, arg: ExprArg) { - match self { - Self::Unknown => match arg { - ExprArg::Pos(expr) => *self = Self::Expr(expr), - ExprArg::Named(named) => *self = Self::Dict(vec![named]), - }, - Self::Expr(prev) => match arg { - ExprArg::Pos(expr) => *self = Self::Array(vec![take(prev), expr]), - ExprArg::Named(_) => diag(p, arg), - }, - Self::Array(array) => match arg { - ExprArg::Pos(expr) => array.push(expr), - ExprArg::Named(_) => diag(p, arg), - }, - Self::Dict(dict) => match arg { - ExprArg::Pos(_) => diag(p, arg), - ExprArg::Named(named) => dict.push(named), - }, - } - } - - fn push_comma(&mut self) { - if let Self::Expr(expr) = self { - *self = Self::Array(vec![take(expr)]); - } - } -} - -fn take(expr: &mut Expr) -> Expr { - // Replace with anything, it's overwritten anyway. - std::mem::replace( - expr, - Expr::Lit(Lit { span: Span::ZERO, kind: LitKind::None }), - ) -} - -fn diag(p: &mut Parser, arg: ExprArg) { - p.diag(error!(arg.span(), "{}", match arg { - ExprArg::Pos(_) => "expected named pair, found expression", - ExprArg::Named(_) => "expected expression, found named pair", - })); -} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index d4cf90c75..327a99f33 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,6 +1,5 @@ //! Parsing and tokenization. -mod collection; mod lines; mod parser; mod resolve; @@ -17,7 +16,6 @@ use std::rc::Rc; use crate::diag::Pass; use crate::syntax::*; -use collection::{args, parenthesized}; /// Parse a string of source code. pub fn parse(src: &str) -> Pass { @@ -188,17 +186,17 @@ fn primary(p: &mut Parser) -> Option { } } + // Structures. + Some(Token::LeftParen) => Some(parenthesized(p)), + Some(Token::LeftBracket) => Some(template(p)), + Some(Token::LeftBrace) => Some(block(p, true)), + // Keywords. Some(Token::Let) => expr_let(p), Some(Token::If) => expr_if(p), Some(Token::While) => expr_while(p), Some(Token::For) => expr_for(p), - // Structures. - Some(Token::LeftBrace) => Some(block(p, true)), - Some(Token::LeftBracket) => Some(template(p)), - Some(Token::LeftParen) => Some(parenthesized(p)), - // Nothing. _ => { p.expected("expression"); @@ -230,6 +228,109 @@ fn literal(p: &mut Parser) -> Option { Some(Expr::Lit(Lit { span: p.eat_span(), kind })) } +/// Parse a parenthesized expression, which can be either of: +/// - Array literal +/// - Dictionary literal +/// - Parenthesized expression +pub fn parenthesized(p: &mut Parser) -> Expr { + p.start_group(Group::Paren, TokenMode::Code); + let colon = p.eat_if(Token::Colon); + let (items, has_comma) = collection(p); + let span = p.end_group(); + + if colon { + // Leading colon makes this a dictionary. + return dict(p, items, span); + } + + // Find out which kind of collection this is. + match items.as_slice() { + [] => array(p, items, span), + [ExprArg::Pos(_)] if !has_comma => match items.into_iter().next() { + Some(ExprArg::Pos(expr)) => { + Expr::Group(ExprGroup { span, expr: Box::new(expr) }) + } + _ => unreachable!(), + }, + [ExprArg::Pos(_), ..] => array(p, items, span), + [ExprArg::Named(_), ..] => dict(p, items, span), + } +} + +/// Parse a collection. +/// +/// Returns whether the literal contained any commas. +fn collection(p: &mut Parser) -> (Vec, bool) { + let mut items = vec![]; + let mut has_comma = false; + let mut missing_coma = None; + + while !p.eof() { + if let Some(arg) = item(p) { + items.push(arg); + + if let Some(pos) = missing_coma.take() { + p.expected_at("comma", pos); + } + + if p.eof() { + break; + } + + let behind = p.end(); + if p.eat_if(Token::Comma) { + has_comma = true; + } else { + missing_coma = Some(behind); + } + } + } + + (items, has_comma) +} + +/// Parse an expression or a named pair. +fn item(p: &mut Parser) -> Option { + let first = expr(p)?; + if p.eat_if(Token::Colon) { + if let Expr::Ident(name) = first { + Some(ExprArg::Named(Named { name, expr: expr(p)? })) + } else { + p.diag(error!(first.span(), "expected identifier")); + expr(p); + None + } + } else { + Some(ExprArg::Pos(first)) + } +} + +/// Convert a collection into an array, producing errors for named items. +fn array(p: &mut Parser, items: Vec, span: Span) -> Expr { + let items = items.into_iter().filter_map(|item| match item { + ExprArg::Pos(expr) => Some(expr), + ExprArg::Named(_) => { + p.diag(error!(item.span(), "expected expression, found named pair")); + None + } + }); + + Expr::Array(ExprArray { span, items: items.collect() }) +} + +/// Convert a collection into a dictionary, producing errors for expressions. +fn dict(p: &mut Parser, items: Vec, span: Span) -> Expr { + let items = items.into_iter().filter_map(|item| match item { + ExprArg::Named(named) => Some(named), + ExprArg::Pos(_) => { + p.diag(error!(item.span(), "expected named pair, found expression")); + None + } + }); + + Expr::Dict(ExprDict { span, items: items.collect() }) +} + // Parse a template value: `[...]`. fn template(p: &mut Parser) -> Expr { p.start_group(Group::Bracket, TokenMode::Markup); @@ -330,6 +431,13 @@ fn call(p: &mut Parser, name: Ident) -> Expr { }) } +/// Parse the arguments to a function call. +fn args(p: &mut Parser) -> ExprArgs { + let start = p.start(); + let items = collection(p).0; + ExprArgs { span: p.span(start), items } +} + /// Parse a let expression. fn expr_let(p: &mut Parser) -> Option { let start = p.start();