use crate::func::Scope; use super::*; use Token::*; /// Parses source code into a syntax tree given a context. pub fn parse(src: &str, ctx: ParseContext) -> (SyntaxTree, Colorization, ErrorMap) { Parser::new(src, ctx).parse() } /// The context for parsing. #[derive(Debug, Copy, Clone)] pub struct ParseContext<'a> { /// The scope containing function definitions. pub scope: &'a Scope, } struct Parser<'s> { src: &'s str, ctx: ParseContext<'s>, colorization: Colorization, error_map: ErrorMap, tokens: Tokens<'s>, peeked: Option>>>, position: Position, last_position: Position, } impl<'s> Parser<'s> { fn new(src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> { Parser { src, ctx, error_map: ErrorMap::new(), colorization: Colorization::new(), tokens: Tokens::new(src), peeked: None, position: Position::ZERO, last_position: Position::ZERO, } } /// The main parsing entrypoint. fn parse(mut self) -> (SyntaxTree, Colorization, ErrorMap) { let mut tree = SyntaxTree::new(); loop { if let Some(spanned) = self.eat() { match spanned.v { LineComment(_) | BlockComment(_) => {} Whitespace(newlines) => { tree.add(spanned.map_v(if newlines >= 2 { Node::Newline } else { Node::Space })); } LeftBracket => { if let Some(func) = self.parse_func() { tree.add(func); } } Star => tree.add(spanned.map_v(Node::ToggleBolder)), Underscore => tree.add(spanned.map_v(Node::ToggleItalic)), Backtick => tree.add(spanned.map_v(Node::ToggleMonospace)), Text(text) => tree.add(spanned.map_v(Node::Text(text.to_owned()))), _ => self.unexpected(spanned), } } else { break; } } (tree, self.colorization, self.error_map) } /// Parses a function including header and body with the cursor starting /// right behind the first opening bracket. fn parse_func(&mut self) -> Option> { let start = self.last_pos(); let header = self.parse_func_header(); self.eat_until(|t| t == RightBracket, false); if self.eat().map(Spanned::value) != Some(RightBracket) { self.expected_at("closing bracket", self.pos()); } let call = self.parse_func_call(header)?; let end = self.pos(); let span = Span { start, end }; Some(Spanned { v: Node::Func(call), span }) } /// Parses a function header including the closing bracket. fn parse_func_header(&mut self) -> Option { self.skip_whitespace(); let name = self.parse_func_name()?; self.skip_whitespace(); let args = match self.eat() { Some(Spanned { v: Colon, .. }) => self.parse_func_args(), Some(Spanned { v: RightBracket, .. }) => FuncArgs::new(), other => { self.expected_at("colon or closing bracket", name.span.end); FuncArgs::new() } }; Some(FuncHeader { name, args }) } /// Parses the function name if is the next token. Otherwise, it adds an /// error and returns `None`. fn parse_func_name(&mut self) -> Option> { match self.eat() { Some(Spanned { v: ExprIdent(ident), span }) => { self.colorization.replace_last(ColorToken::FuncName); return Some(Spanned { v: Ident(ident.to_string()), span }); } other => self.expected_found_or_at("identifier", other, self.pos()), } None } /// Parses the function arguments and stops right before the final closing /// bracket. fn parse_func_args(&mut self) -> FuncArgs { let mut args = FuncArgs::new(); loop { self.skip_whitespace(); match self.peekv() { Some(RightBracket) | None => break, _ => match self.parse_arg() { Some(Arg::Pos(item)) => args.add_pos(item), Some(Arg::Key(pair)) => args.add_key_pair(pair), None => {} } } } args } /// Parse a positional or keyword argument. fn parse_arg(&mut self) -> Option { let first = self.peek()?; let span = first.span; let arg = if let ExprIdent(ident) = first.v { self.eat(); self.skip_whitespace(); let ident = Ident(ident.to_string()); if let Some(Equals) = self.peekv() { self.colorization.replace_last(ColorToken::Key); self.eat(); self.skip_whitespace(); self.parse_expr().map(|value| { Arg::Key(Pair { key: Spanned { v: ident, span }, value, }) }) } else { Some(Arg::Pos(Spanned::new(Expression::Ident(ident), span))) } } else { self.parse_expr().map(|expr| Arg::Pos(expr)) }; if let Some(arg) = &arg { self.skip_whitespace(); match self.peekv() { Some(RightBracket) => {} Some(Comma) => { self.eat(); } Some(_) => self.expected_at("comma", arg.span().end), _ => {} } } else { let found = self.eat(); self.expected_found_or_at("value", found, self.pos()); } arg } /// Parse a atomic or compound (tuple / object) expression. fn parse_expr(&mut self) -> Option> { let first = self.peek()?; let mut expr = |v| { self.eat(); Spanned { v, span: first.span } }; Some(match first.v { ExprIdent(i) => expr(Expression::Ident(Ident(i.to_string()))), ExprStr(s) => expr(Expression::Str(s.to_string())), ExprNumber(n) => expr(Expression::Number(n)), ExprSize(s) => expr(Expression::Size(s)), ExprBool(b) => expr(Expression::Bool(b)), LeftParen => self.parse_tuple(), LeftBrace => self.parse_object(), _ => return None, }) } /// Parse a tuple expression. fn parse_tuple(&mut self) -> Spanned { let start = self.pos(); // TODO: Do the thing. self.eat_until(|t| matches!(t, RightParen | RightBracket), false); if self.peekv() == Some(RightParen) { self.eat(); } let end = self.pos(); let span = Span { start, end }; Spanned { v: Expression::Tuple(Tuple::new()), span } } /// Parse an object expression. fn parse_object(&mut self) -> Spanned { let start = self.pos(); // TODO: Do the thing. self.eat_until(|t| matches!(t, RightBrace | RightBracket), false); if self.peekv() == Some(RightBrace) { self.eat(); } let end = self.pos(); let span = Span { start, end }; Spanned { v: Expression::Object(Object::new()), span } } /// Parse the body of a function invocation. fn parse_func_call(&mut self, header: Option) -> Option { let body = if self.peekv() == Some(LeftBracket) { self.eat(); let start = self.tokens.index(); let found = self.tokens.move_to_closing_bracket(); let end = self.tokens.index(); self.last_position = self.position; self.position = self.tokens.pos(); let body = &self.src[start .. end]; if found { let next = self.eat().map(Spanned::value); debug_assert_eq!(next, Some(RightBracket)); } else { self.expected_at("closing bracket", self.pos()); } Some(body) } else { None }; let header = header?; let parser = self.ctx.scope.get_parser(header.name.v.as_str()).or_else(|| { let message = format!("unknown function: `{}`", header.name.v); self.error_map.add(message, header.name.span); None })?; Some(FuncCall(parser(header, body, self.ctx).unwrap())) } /// Skip all whitespace/comment tokens. fn skip_whitespace(&mut self) { self.eat_until(|t| !matches!(t, Whitespace(_) | LineComment(_) | BlockComment(_)), false) } /// Add an error about an `thing` which was expected but not found at the /// given position. fn expected_at(&mut self, thing: &str, pos: Position) { self.error_map.add_at(format!("expected {}", thing), pos); } /// Add an error about an expected `thing` which was not found, showing /// what was found instead. fn expected_found(&mut self, thing: &str, found: Spanned) { let message = format!("expected {}, found {}", thing, name(found.v)); self.error_map.add(message, found.span); } /// Add a found-error if `found` is some and a positional error, otherwise. fn expected_found_or_at( &mut self, thing: &str, found: Option>, pos: Position ) { match found { Some(found) => self.expected_found(thing, found), None => self.expected_at(thing, pos), } } /// Add an error about an unexpected token `found`. fn unexpected(&mut self, found: Spanned) { self.error_map.add(format!("unexpected {}", name(found.v)), found.span); } /// Consume tokens until the function returns true and only consume the last /// token if instructed to. fn eat_until(&mut self, mut f: F, eat_match: bool) where F: FnMut(Token<'s>) -> bool { while let Some(token) = self.peek() { if f(token.v) { if eat_match { self.eat(); } break; } self.eat(); } } /// Consume and return the next token, update positions and colorize the /// token. All colorable tokens are per default colorized here, to override /// a colorization use `Colorization::replace_last`. fn eat(&mut self) -> Option>> { let token = self.peeked.take() .unwrap_or_else(|| self.tokens.next()); if let Some(token) = token { if let Some(color) = color(token.v) { self.colorization.add(color, token.span); } self.last_position = self.position; self.position = token.span.end; } token } /// Peek at the next token without consuming it. fn peek(&mut self) -> Option>> { let iter = &mut self.tokens; *self.peeked.get_or_insert_with(|| iter.next()) } fn peekv(&mut self) -> Option> { self.peek().map(Spanned::value) } /// The position at the end of the last eat token / start of the peekable /// token. fn pos(&self) -> Position { self.position } /// The position at the start of the last eaten token. fn last_pos(&self) -> Position { self.last_position } } /// The name of a token in an `expected <...>` error. fn name(token: Token) -> &'static str { match token { Whitespace(_) => "whitespace", LineComment(_) | BlockComment(_) => "comment", StarSlash => "end of block comment", LeftBracket => "opening bracket", RightBracket => "closing bracket", LeftParen => "opening paren", RightParen => "closing paren", LeftBrace => "opening brace", RightBrace => "closing brace", Colon => "colon", Comma => "comma", Equals => "equals sign", ExprIdent(_) => "identifier", ExprStr(_) => "string", ExprNumber(_) => "number", ExprSize(_) => "size", ExprBool(_) => "bool", Star => "star", Underscore => "underscore", Backtick => "backtick", Text(_) => "invalid identifier", } } /// The color token corresponding to a token. fn color(token: Token) -> Option { Some(match token { LineComment(_) | BlockComment(_) => ColorToken::Comment, LeftBracket | RightBracket => ColorToken::Bracket, LeftParen | RightParen => ColorToken::Paren, LeftBrace | RightBrace => ColorToken::Brace, Colon => ColorToken::Colon, Comma => ColorToken::Comma, Equals => ColorToken::Equals, ExprIdent(_) => ColorToken::ExprIdent, ExprStr(_) => ColorToken::ExprStr, ExprNumber(_) => ColorToken::ExprNumber, ExprSize(_) => ColorToken::ExprSize, ExprBool(_) => ColorToken::ExprBool, StarSlash => ColorToken::Invalid, _ => return None, }) }