summaryrefslogtreecommitdiff
path: root/src/parse/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse/mod.rs')
-rw-r--r--src/parse/mod.rs919
1 files changed, 378 insertions, 541 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index cc0b6378..2f34357c 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -1,11 +1,13 @@
//! Parsing and tokenization.
mod lines;
+mod parser;
mod resolve;
mod scanner;
mod tokens;
pub use lines::*;
+pub use parser::*;
pub use resolve::*;
pub use scanner::*;
pub use tokens::*;
@@ -15,634 +17,469 @@ use std::str::FromStr;
use crate::color::RgbaColor;
use crate::eval::DictKey;
use crate::syntax::*;
-use crate::{Feedback, Pass};
+use crate::Pass;
/// Parse a string of source code.
pub fn parse(src: &str) -> Pass<SynTree> {
- Parser::new(src).parse()
+ let mut p = Parser::new(src);
+ Pass::new(tree(&mut p), p.finish())
}
-struct Parser<'s> {
- tokens: Tokens<'s>,
- peeked: Option<Option<Spanned<Token<'s>>>>,
- delimiters: Vec<(Pos, Token<'static>)>,
- at_block_or_line_start: bool,
- feedback: Feedback,
-}
-
-impl<'s> Parser<'s> {
- fn new(src: &'s str) -> Self {
- Self {
- tokens: Tokens::new(src, TokenMode::Body),
- peeked: None,
- delimiters: vec![],
- at_block_or_line_start: true,
- feedback: Feedback::new(),
+/// Parse a syntax tree.
+fn tree(p: &mut Parser) -> SynTree {
+ // We keep track of whether we are at the start of a block or paragraph
+ // to know whether headings are allowed.
+ let mut at_start = true;
+ let mut tree = vec![];
+ while !p.eof() {
+ if let Some(node) = node(p, at_start) {
+ if node.v == SynNode::Parbreak {
+ at_start = true;
+ } else if node.v != SynNode::Space {
+ at_start = false;
+ }
+ tree.push(node);
}
}
-
- fn parse(mut self) -> Pass<SynTree> {
- let tree = self.parse_body_contents();
- Pass::new(tree, self.feedback)
- }
+ tree
}
-// Typesetting content.
-impl Parser<'_> {
- fn parse_body_contents(&mut self) -> SynTree {
- let mut tree = SynTree::new();
-
- self.at_block_or_line_start = true;
- while !self.eof() {
- if let Some(node) = self.parse_node() {
- tree.push(node);
+/// Parse a syntax node.
+fn node(p: &mut Parser, at_start: bool) -> Option<Spanned<SynNode>> {
+ let token = p.eat()?;
+ let span = token.span;
+ Some(match token.v {
+ // Spaces.
+ Token::Space(newlines) => {
+ if newlines < 2 {
+ SynNode::Space.span_with(span)
+ } else {
+ SynNode::Parbreak.span_with(span)
}
}
-
- tree
- }
-
- fn parse_node(&mut self) -> Option<Spanned<SynNode>> {
- let token = self.peek()?;
- let end = Span::at(token.span.end);
-
- // Set block or line start to false because most nodes have that effect, but
- // remember the old value to actually check it for hashtags and because comments
- // and spaces want to retain it.
- let was_at_block_or_line_start = self.at_block_or_line_start;
- self.at_block_or_line_start = false;
-
- Some(match token.v {
- // Starting from two newlines counts as a paragraph break, a single
- // newline does not.
- Token::Space(n) => {
- if n == 0 {
- self.at_block_or_line_start = was_at_block_or_line_start;
- } else if n >= 1 {
- self.at_block_or_line_start = true;
- }
-
- self.with_span(if n >= 2 { SynNode::Parbreak } else { SynNode::Spacing })
- }
-
- Token::LineComment(_) | Token::BlockComment(_) => {
- self.at_block_or_line_start = was_at_block_or_line_start;
- self.eat();
- return None;
- }
-
- Token::LeftBracket => {
- let call = self.parse_bracket_call(false);
- self.at_block_or_line_start = false;
- call.map(|c| SynNode::Expr(Expr::Call(c)))
- }
-
- Token::Star => self.with_span(SynNode::ToggleBolder),
- Token::Underscore => self.with_span(SynNode::ToggleItalic),
- Token::Backslash => self.with_span(SynNode::Linebreak),
-
- Token::Hashtag if was_at_block_or_line_start => {
- self.parse_heading().map(SynNode::Heading)
- }
-
- Token::Raw { raw, backticks, terminated } => {
- if !terminated {
- error!(@self.feedback, end, "expected backtick(s)");
- }
-
- let raw = resolve::resolve_raw(raw, backticks);
- self.with_span(SynNode::Raw(raw))
- }
-
- Token::Text(text) => self.with_span(SynNode::Text(text.to_string())),
- Token::Hashtag => self.with_span(SynNode::Text("#".to_string())),
-
- Token::UnicodeEscape { sequence, terminated } => {
- if !terminated {
- error!(@self.feedback, end, "expected closing brace");
- }
-
- if let Some(c) = resolve::resolve_hex(sequence) {
- self.with_span(SynNode::Text(c.to_string()))
- } else {
- error!(@self.feedback, token.span, "invalid unicode escape sequence");
- // TODO: Decide whether to render the escape sequence.
- self.eat();
- return None;
- }
- }
-
- unexpected => {
- error!(@self.feedback, token.span, "unexpected {}", unexpected.name());
- self.eat();
- return None;
+ Token::Text(text) => SynNode::Text(text.into()).span_with(span),
+
+ // Comments.
+ Token::LineComment(_) | Token::BlockComment(_) => return None,
+
+ // Markup.
+ Token::Star => SynNode::ToggleBolder.span_with(span),
+ Token::Underscore => SynNode::ToggleItalic.span_with(span),
+ Token::Backslash => SynNode::Linebreak.span_with(span),
+ Token::Hashtag => {
+ if at_start {
+ heading(p, span.start).map(SynNode::Heading)
+ } else {
+ SynNode::Text(p.get(span).into()).span_with(span)
}
- })
- }
-
- fn parse_heading(&mut self) -> Spanned<NodeHeading> {
- let start = self.pos();
- self.assert(Token::Hashtag);
-
- let mut level = 0;
- while self.peekv() == Some(Token::Hashtag) {
- level += 1;
- self.eat();
}
+ Token::Raw(token) => raw(p, token, span).map(SynNode::Raw),
+ Token::UnicodeEscape(token) => unicode_escape(p, token, span).map(SynNode::Text),
- let span = Span::new(start, self.pos());
- let level = level.span_with(span);
-
- if level.v > 5 {
- warning!(
- @self.feedback, level.span,
- "section depth larger than 6 has no effect",
- );
+ // Functions.
+ Token::LeftBracket => {
+ p.jump(span.start);
+ bracket_call(p).map(Expr::Call).map(SynNode::Expr)
}
- self.skip_ws();
-
- let mut tree = SynTree::new();
- while !self.eof() && !matches!(self.peekv(), Some(Token::Space(n)) if n >= 1) {
- if let Some(node) = self.parse_node() {
- tree.push(node);
- }
+ // Bad tokens.
+ _ => {
+ p.diag_unexpected(token);
+ return None;
}
-
- let span = Span::new(start, self.pos());
- NodeHeading { level, contents: tree }.span_with(span)
- }
+ })
}
-// Function calls.
-impl Parser<'_> {
- fn parse_bracket_call(&mut self, chained: bool) -> Spanned<ExprCall> {
- let before_bracket = self.pos();
- if !chained {
- self.start_group(Group::Bracket);
- self.tokens.push_mode(TokenMode::Header);
+/// Parse a heading.
+fn heading(p: &mut Parser, start: Pos) -> Spanned<NodeHeading> {
+ // Parse the section depth.
+ let count = p.eat_while(|c| c == Token::Hashtag);
+ let span = (start, p.pos());
+ let level = (count.min(5) as u8).span_with(span);
+ if count > 5 {
+ p.diag(warning!(span, "section depth larger than 6 has no effect"));
+ }
+
+ // Parse the heading contents.
+ p.skip_white();
+ let mut contents = vec![];
+ while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) {
+ if let Some(node) = node(p, false) {
+ contents.push(node);
}
+ }
- let before_name = self.pos();
- self.start_group(Group::Subheader);
- self.skip_ws();
- let name = self.parse_ident().unwrap_or_else(|| {
- self.expected_found_or_at("function name", before_name);
- Ident(String::new()).span_with(Span::at(before_name))
- });
-
- self.skip_ws();
-
- let mut args = match self.eatv() {
- Some(Token::Colon) => self.parse_dict_contents().0,
- Some(_) => {
- self.expected_at("colon", name.span.end);
- while self.eat().is_some() {}
- LitDict::default()
- }
- None => LitDict::default(),
- };
-
- self.end_group();
- self.skip_ws();
- let (has_chained_child, end) = if self.peek().is_some() {
- let item = self.parse_bracket_call(true);
- let span = item.span;
- let tree = vec![item.map(|c| SynNode::Expr(Expr::Call(c)))];
- let expr = Expr::Lit(Lit::Content(tree));
- args.0.push(LitDictEntry { key: None, value: expr.span_with(span) });
- (true, span.end)
- } else {
- self.tokens.pop_mode();
- (false, self.end_group().end)
- };
-
- let start = if chained { before_name } else { before_bracket };
- let mut span = Span::new(start, end);
-
- if self.check(Token::LeftBracket) && !has_chained_child {
- self.start_group(Group::Bracket);
- self.tokens.push_mode(TokenMode::Body);
- let body = self.parse_body_contents();
- self.tokens.pop_mode();
- let body_span = self.end_group();
+ NodeHeading { level, contents }.span_with((start, p.pos()))
+}
- let expr = Expr::Lit(Lit::Content(body));
- args.0.push(LitDictEntry {
- key: None,
- value: expr.span_with(body_span),
- });
- span.expand(body_span);
- }
+/// Parse a raw block.
+fn raw(p: &mut Parser, token: TokenRaw, span: Span) -> Spanned<NodeRaw> {
+ let raw = resolve::resolve_raw(token.text, token.backticks);
- ExprCall { name, args }.span_with(span)
+ if !token.terminated {
+ p.diag(error!(span.end, "expected backtick(s)"));
}
- fn parse_paren_call(&mut self, name: Spanned<Ident>) -> Spanned<ExprCall> {
- self.start_group(Group::Paren);
- let args = self.parse_dict_contents().0;
- let args_span = self.end_group();
- let span = Span::merge(name.span, args_span);
- ExprCall { name, args }.span_with(span)
- }
+ raw.span_with(span)
}
-// Dicts.
-impl Parser<'_> {
- fn parse_dict_contents(&mut self) -> (LitDict, bool) {
- let mut dict = LitDict::default();
- let mut comma_and_keyless = true;
-
- while {
- self.skip_ws();
- !self.eof()
- } {
- let (key, value) = if let Some(ident) = self.parse_ident() {
- self.skip_ws();
-
- match self.peekv() {
- Some(Token::Equals) => {
- self.eat();
- self.skip_ws();
- if let Some(value) = self.parse_expr() {
- (Some(ident.map(|id| DictKey::Str(id.0))), value)
- } else {
- self.expected("value");
- continue;
- }
- }
-
- Some(Token::LeftParen) => {
- let call = self.parse_paren_call(ident);
- (None, call.map(Expr::Call))
- }
-
- _ => (None, ident.map(|id| Expr::Lit(Lit::Ident(id)))),
- }
- } else if let Some(value) = self.parse_expr() {
- (None, value)
- } else {
- self.expected("value");
- continue;
- };
-
- if let Some(key) = &key {
- comma_and_keyless = false;
- self.feedback
- .decorations
- .push(Decoration::DictKey.span_with(key.span));
- }
-
- let behind = value.span.end;
- dict.0.push(LitDictEntry { key, value });
+/// Parse a unicode escape sequence.
+fn unicode_escape(
+ p: &mut Parser,
+ token: TokenUnicodeEscape,
+ span: Span,
+) -> Spanned<String> {
+ let text = if let Some(c) = resolve::resolve_hex(token.sequence) {
+ c.to_string()
+ } else {
+ // Print out the escape sequence verbatim if it is
+ // invalid.
+ p.diag(error!(span, "invalid unicode escape sequence"));
+ p.get(span).into()
+ };
+
+ if !token.terminated {
+ p.diag(error!(span.end, "expected closing brace"));
+ }
+
+ text.span_with(span)
+}
- if {
- self.skip_ws();
- self.eof()
- } {
- break;
- }
+/// Parse a bracketed function call.
+fn bracket_call(p: &mut Parser) -> Spanned<ExprCall> {
+ let before_bracket = p.pos();
+ p.start_group(Group::Bracket);
+ p.push_mode(TokenMode::Header);
- self.expect_at(Token::Comma, behind);
- comma_and_keyless = false;
- }
+ // One header is guaranteed, but there may be more (through chaining).
+ let mut outer = vec![];
+ let mut inner = bracket_subheader(p);
- let coercable = comma_and_keyless && !dict.0.is_empty();
- (dict, coercable)
+ while p.eat_if(Token::Chain).is_some() {
+ outer.push(inner);
+ inner = bracket_subheader(p);
}
-}
-// Expressions and values.
-impl Parser<'_> {
- fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
- self.parse_binops("summand", Self::parse_term, |token| match token {
- Token::Plus => Some(BinOp::Add),
- Token::Hyphen => Some(BinOp::Sub),
- _ => None,
- })
- }
+ p.pop_mode();
+ p.end_group();
- fn parse_term(&mut self) -> Option<Spanned<Expr>> {
- self.parse_binops("factor", Self::parse_factor, |token| match token {
- Token::Star => Some(BinOp::Mul),
- Token::Slash => Some(BinOp::Div),
- _ => None,
- })
+ if p.peek() == Some(Token::LeftBracket) {
+ let expr = bracket_body(p).map(Lit::Content).map(Expr::Lit);
+ inner.span.expand(expr.span);
+ inner.v.args.0.push(LitDictEntry { key: None, expr });
}
- /// Parse expression of the form `<operand> (<op> <operand>)*`.
- fn parse_binops(
- &mut self,
- operand_name: &str,
- mut parse_operand: impl FnMut(&mut Self) -> Option<Spanned<Expr>>,
- mut parse_op: impl FnMut(Token) -> Option<BinOp>,
- ) -> Option<Spanned<Expr>> {
- let mut left = parse_operand(self)?;
-
- self.skip_ws();
- while let Some(token) = self.peek() {
- if let Some(op) = parse_op(token.v) {
- self.eat();
- self.skip_ws();
-
- if let Some(right) = parse_operand(self) {
- let span = Span::merge(left.span, right.span);
- let expr = Expr::Binary(ExprBinary {
- lhs: left.map(Box::new),
- op: op.span_with(token.span),
- rhs: right.map(Box::new),
- });
- left = expr.span_with(span);
- self.skip_ws();
- continue;
- }
+ while let Some(mut top) = outer.pop() {
+ let span = inner.span;
+ let node = inner.map(Expr::Call).map(SynNode::Expr);
+ let expr = Expr::Lit(Lit::Content(vec![node])).span_with(span);
+ top.v.args.0.push(LitDictEntry { key: None, expr });
+ inner = top;
+ }
- error!(
- @self.feedback, Span::merge(left.span, token.span),
- "missing right {}", operand_name,
- );
- }
- break;
- }
+ inner.v.span_with((before_bracket, p.pos()))
+}
- Some(left)
- }
+/// Parse one subheader of a bracketed function call.
+fn bracket_subheader(p: &mut Parser) -> Spanned<ExprCall> {
+ p.start_group(Group::Subheader);
+ let before_name = p.pos();
- fn parse_factor(&mut self) -> Option<Spanned<Expr>> {
- if let Some(hyph) = self.check_eat(Token::Hyphen) {
- self.skip_ws();
- if let Some(factor) = self.parse_factor() {
- let span = Span::merge(hyph.span, factor.span);
- let expr = Expr::Unary(ExprUnary {
- op: UnOp::Neg.span_with(hyph.span),
- expr: factor.map(Box::new),
- });
- Some(expr.span_with(span))
- } else {
- error!(@self.feedback, hyph.span, "dangling minus");
- None
- }
+ p.skip_white();
+ let name = ident(p).unwrap_or_else(|| {
+ if p.eof() {
+ p.diag_expected_at("function name", before_name);
} else {
- self.parse_value()
+ p.diag_expected("function name");
}
- }
-
- fn parse_value(&mut self) -> Option<Spanned<Expr>> {
- let Spanned { v: token, span } = self.peek()?;
- Some(match token {
- // This could be a function call or an identifier.
- Token::Ident(id) => {
- let name = Ident(id.to_string()).span_with(span);
- self.eat();
- self.skip_ws();
- if self.check(Token::LeftParen) {
- self.parse_paren_call(name).map(Expr::Call)
- } else {
- name.map(|n| Expr::Lit(Lit::Ident(n)))
- }
- }
-
- Token::Str { string, terminated } => {
- if !terminated {
- self.expected_at("quote", span.end);
- }
- self.with_span(Expr::Lit(Lit::Str(resolve::resolve_string(string))))
- }
-
- Token::Bool(b) => self.with_span(Expr::Lit(Lit::Bool(b))),
- Token::Number(n) => self.with_span(Expr::Lit(Lit::Float(n))),
- Token::Length(s) => self.with_span(Expr::Lit(Lit::Length(s))),
- Token::Hex(s) => {
- let color = RgbaColor::from_str(s).unwrap_or_else(|_| {
- // Heal color by assuming black.
- error!(@self.feedback, span, "invalid color");
- RgbaColor::new_healed(0, 0, 0, 255)
- });
- self.with_span(Expr::Lit(Lit::Color(color)))
- }
-
- // This could be a dictionary or a parenthesized expression. We
- // parse as a dictionary in any case and coerce into a value if
- // that's coercable (length 1 and no trailing comma).
- Token::LeftParen => {
- self.start_group(Group::Paren);
- let (dict, coercable) = self.parse_dict_contents();
- let span = self.end_group();
-
- let expr = if coercable {
- dict.0.into_iter().next().expect("dict is coercable").value.v
- } else {
- Expr::Lit(Lit::Dict(dict))
- };
-
- expr.span_with(span)
- }
-
- // This is a content expression.
- Token::LeftBrace => {
- self.start_group(Group::Brace);
- self.tokens.push_mode(TokenMode::Body);
- let tree = self.parse_body_contents();
- self.tokens.pop_mode();
- let span = self.end_group();
- Expr::Lit(Lit::Content(tree)).span_with(span)
- }
+ Ident(String::new()).span_with(before_name)
+ });
+
+ p.skip_white();
+ let args = if p.eat_if(Token::Colon).is_some() {
+ dict_contents(p).0
+ } else {
+ // Ignore the rest if there's no colon.
+ if !p.eof() {
+ p.diag_expected_at("colon", p.pos());
+ }
+ p.eat_while(|_| true);
+ LitDict::new()
+ };
- // This is a bracketed function call.
- Token::LeftBracket => {
- let call = self.parse_bracket_call(false);
- let tree = vec![call.map(|c| SynNode::Expr(Expr::Call(c)))];
- Expr::Lit(Lit::Content(tree)).span_with(span)
- }
+ ExprCall { name, args }.span_with(p.end_group())
+}
- _ => return None,
- })
- }
+/// Parse the body of a bracketed function call.
+fn bracket_body(p: &mut Parser) -> Spanned<SynTree> {
+ p.start_group(Group::Bracket);
+ p.push_mode(TokenMode::Body);
+ let tree = tree(p);
+ p.pop_mode();
+ tree.span_with(p.end_group())
+}
- fn parse_ident(&mut self) -> Option<Spanned<Ident>> {
- self.peek().and_then(|token| match token.v {
- Token::Ident(id) => Some(self.with_span(Ident(id.to_string()))),
- _ => None,
- })
- }
+/// Parse an expression: `term (+ term)*`.
+fn expr(p: &mut Parser) -> Option<Spanned<Expr>> {
+ binops(p, "summand", term, |token| match token {
+ Token::Plus => Some(BinOp::Add),
+ Token::Hyphen => Some(BinOp::Sub),
+ _ => None,
+ })
}
-// Error handling.
-impl Parser<'_> {
- fn expect_at(&mut self, token: Token<'_>, pos: Pos) -> bool {
- if self.check(token) {
- self.eat();
- true
- } else {
- self.expected_at(token.name(), pos);
- false
- }
- }
+/// Parse a term: `factor (* factor)*`.
+fn term(p: &mut Parser) -> Option<Spanned<Expr>> {
+ binops(p, "factor", factor, |token| match token {
+ Token::Star => Some(BinOp::Mul),
+ Token::Slash => Some(BinOp::Div),
+ _ => None,
+ })
+}
- fn expected(&mut self, thing: &str) {
- if let Some(found) = self.eat() {
- error!(
- @self.feedback, found.span,
- "expected {}, found {}", thing, found.v.name(),
- );
+/// Parse binary operations of the from `a (<op> b)*`.
+fn binops(
+ p: &mut Parser,
+ operand_name: &str,
+ operand: fn(&mut Parser) -> Option<Spanned<Expr>>,
+ op: fn(Token) -> Option<BinOp>,
+) -> Option<Spanned<Expr>> {
+ let mut lhs = operand(p)?;
+
+ loop {
+ p.skip_white();
+ if let Some(op) = p.eat_map(op) {
+ p.skip_white();
+
+ if let Some(rhs) = operand(p) {
+ let span = lhs.span.join(rhs.span);
+ let expr = Expr::Binary(ExprBinary {
+ lhs: lhs.map(Box::new),
+ op,
+ rhs: rhs.map(Box::new),
+ });
+ lhs = expr.span_with(span);
+ p.skip_white();
+ } else {
+ let span = lhs.span.join(op.span);
+ p.diag(error!(span, "missing right {}", operand_name));
+ break;
+ }
} else {
- error!(@self.feedback, Span::at(self.pos()), "expected {}", thing);
+ break;
}
}
- fn expected_at(&mut self, thing: &str, pos: Pos) {
- error!(@self.feedback, Span::at(pos), "expected {}", thing);
- }
+ Some(lhs)
+}
- fn expected_found_or_at(&mut self, thing: &str, pos: Pos) {
- if self.eof() {
- self.expected_at(thing, pos)
+/// Parse a factor of the form `-?value`.
+fn factor(p: &mut Parser) -> Option<Spanned<Expr>> {
+ if let Some(op) = p.eat_map(|token| match token {
+ Token::Hyphen => Some(UnOp::Neg),
+ _ => None,
+ }) {
+ p.skip_white();
+ if let Some(expr) = factor(p) {
+ let span = op.span.join(expr.span);
+ let expr = Expr::Unary(ExprUnary { op, expr: expr.map(Box::new) });
+ Some(expr.span_with(span))
} else {
- self.expected(thing);
+ p.diag(error!(op.span, "missing factor"));
+ None
}
+ } else {
+ value(p)
}
}
-// Parsing primitives.
-impl<'s> Parser<'s> {
- fn start_group(&mut self, group: Group) {
- let start = self.pos();
- if let Some(start_token) = group.start() {
- self.assert(start_token);
+/// Parse a value.
+fn value(p: &mut Parser) -> Option<Spanned<Expr>> {
+ let Spanned { v: token, span } = p.eat()?;
+ Some(match token {
+ // Bracketed function call.
+ Token::LeftBracket => {
+ p.jump(span.start);
+ let call = bracket_call(p);
+ let span = call.span;
+ let node = call.map(Expr::Call).map(SynNode::Expr);
+ Expr::Lit(Lit::Content(vec![node])).span_with(span)
}
- self.delimiters.push((start, group.end()));
- }
- fn end_group(&mut self) -> Span {
- let peeked = self.peek();
-
- let (start, end_token) = self.delimiters.pop().expect("group was not started");
+ // Content expression.
+ Token::LeftBrace => {
+ p.jump(span.start);
+ content(p).map(Lit::Content).map(Expr::Lit)
+ }
- if end_token != Token::Chain && peeked != None {
- self.delimiters.push((start, end_token));
- assert_eq!(peeked, None, "unfinished group");
+ // Dictionary or just a parenthesized expression.
+ Token::LeftParen => {
+ p.jump(span.start);
+ parenthesized(p)
}
- match self.peeked.unwrap() {
- Some(token) if token.v == end_token => {
- self.peeked = None;
- Span::new(start, token.span.end)
- }
- _ => {
- let end = self.pos();
- if end_token != Token::Chain {
- error!(
- @self.feedback, Span::at(end),
- "expected {}", end_token.name(),
- );
- }
- Span::new(start, end)
+ // Function or just ident.
+ Token::Ident(id) => {
+ let ident = Ident(id.into()).span_with(span);
+
+ p.skip_white();
+ if p.peek() == Some(Token::LeftParen) {
+ paren_call(p, ident).map(Expr::Call)
+ } else {
+ ident.map(Lit::Ident).map(Expr::Lit)
}
}
- }
- fn skip_ws(&mut self) {
- while matches!(
- self.peekv(),
- Some(Token::Space(_)) |
- Some(Token::LineComment(_)) |
- Some(Token::BlockComment(_))
- ) {
- self.eat();
+ // Atomic values.
+ Token::Bool(b) => Expr::Lit(Lit::Bool(b)).span_with(span),
+ Token::Number(f) => Expr::Lit(Lit::Float(f)).span_with(span),
+ Token::Length(l) => Expr::Lit(Lit::Length(l)).span_with(span),
+ Token::Hex(hex) => color(p, hex, span).map(Lit::Color).map(Expr::Lit),
+ Token::Str(token) => string(p, token, span).map(Lit::Str).map(Expr::Lit),
+
+ // No value.
+ _ => {
+ p.jump(span.start);
+ return None;
}
- }
+ })
+}
- fn eatv(&mut self) -> Option<Token<'s>> {
- self.eat().map(Spanned::value)
- }
+// Parse a content expression: `{...}`.
+fn content(p: &mut Parser) -> Spanned<SynTree> {
+ p.start_group(Group::Brace);
+ p.push_mode(TokenMode::Body);
+ let tree = tree(p);
+ p.pop_mode();
+ tree.span_with(p.end_group())
+}
- fn peekv(&mut self) -> Option<Token<'s>> {
- self.peek().map(Spanned::value)
- }
+/// Parse a parenthesized expression: `(a + b)`, `(1, key="value").
+fn parenthesized(p: &mut Parser) -> Spanned<Expr> {
+ p.start_group(Group::Paren);
+ let (dict, coercable) = dict_contents(p);
+ let expr = if coercable {
+ dict.0.into_iter().next().expect("dict is coercable").expr.v
+ } else {
+ Expr::Lit(Lit::Dict(dict))
+ };
+ expr.span_with(p.end_group())
+}
- fn assert(&mut self, token: Token<'_>) {
- assert!(self.check_eat(token).is_some());
- }
+/// Parse a parenthesized function call.
+fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Spanned<ExprCall> {
+ p.start_group(Group::Paren);
+ let args = dict_contents(p).0;
+ let span = name.span.join(p.end_group());
+ ExprCall { name, args }.span_with(span)
+}
- fn check_eat(&mut self, token: Token<'_>) -> Option<Spanned<Token<'s>>> {
- if self.check(token) { self.eat() } else { None }
- }
+/// Parse the contents of a dictionary.
+fn dict_contents(p: &mut Parser) -> (LitDict, bool) {
+ let mut dict = LitDict::new();
+ let mut comma_and_keyless = true;
- /// Checks if the next token is of some kind
- fn check(&mut self, token: Token<'_>) -> bool {
- self.peekv() == Some(token)
- }
+ loop {
+ p.skip_white();
+ if p.eof() {
+ break;
+ }
- fn with_span<T>(&mut self, v: T) -> Spanned<T> {
- let span = self.eat().expect("expected token").span;
- v.span_with(span)
- }
+ let entry = if let Some(entry) = dict_entry(p) {
+ entry
+ } else {
+ p.diag_expected("value");
+ continue;
+ };
- fn eof(&mut self) -> bool {
- self.peek().is_none()
- }
+ if let Some(key) = &entry.key {
+ comma_and_keyless = false;
+ p.deco(Decoration::DictKey.span_with(key.span));
+ }
- fn eat(&mut self) -> Option<Spanned<Token<'s>>> {
- let token = self.peek()?;
- self.peeked = None;
- Some(token)
- }
+ let behind = entry.expr.span.end;
+ dict.0.push(entry);
- fn peek(&mut self) -> Option<Spanned<Token<'s>>> {
- let tokens = &mut self.tokens;
- let token = (*self.peeked.get_or_insert_with(|| tokens.next()))?;
+ p.skip_white();
+ if p.eof() {
+ break;
+ }
- // Check for unclosed groups.
- if Group::is_delimiter(token.v) {
- if self.delimiters.iter().rev().any(|&(_, end)| token.v == end) {
- return None;
- }
+ if p.eat_if(Token::Comma).is_none() {
+ p.diag_expected_at("comma", behind);
}
- Some(token)
+ comma_and_keyless = false;
}
- fn pos(&self) -> Pos {
- self.peeked
- .flatten()
- .map(|s| s.span.start)
- .unwrap_or_else(|| self.tokens.pos())
- }
+ let coercable = comma_and_keyless && !dict.0.is_empty();
+ (dict, coercable)
}
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum Group {
- Paren,
- Bracket,
- Brace,
- Subheader,
-}
+/// Parse a single entry in a dictionary.
+fn dict_entry(p: &mut Parser) -> Option<LitDictEntry> {
+ if let Some(ident) = ident(p) {
+ p.skip_white();
+ match p.peek() {
+ // Key-value pair.
+ Some(Token::Equals) => {
+ p.eat_assert(Token::Equals);
+ p.skip_white();
+ if let Some(expr) = expr(p) {
+ Some(LitDictEntry {
+ key: Some(ident.map(|id| DictKey::Str(id.0))),
+ expr,
+ })
+ } else {
+ None
+ }
+ }
-impl Group {
- fn is_delimiter(token: Token<'_>) -> bool {
- matches!(
- token,
- Token::RightParen | Token::RightBracket | Token::RightBrace | Token::Chain
- )
- }
+ // Function call.
+ Some(Token::LeftParen) => Some(LitDictEntry {
+ key: None,
+ expr: paren_call(p, ident).map(Expr::Call),
+ }),
- fn start(self) -> Option<Token<'static>> {
- match self {
- Self::Paren => Some(Token::LeftParen),
- Self::Bracket => Some(Token::LeftBracket),
- Self::Brace => Some(Token::LeftBrace),
- Self::Subheader => None,
+ // Just an identifier.
+ _ => Some(LitDictEntry {
+ key: None,
+ expr: ident.map(|id| Expr::Lit(Lit::Ident(id))),
+ }),
}
+ } else if let Some(expr) = expr(p) {
+ Some(LitDictEntry { key: None, expr })
+ } else {
+ None
}
+}
- fn end(self) -> Token<'static> {
- match self {
- Self::Paren => Token::RightParen,
- Self::Bracket => Token::RightBracket,
- Self::Brace => Token::RightBrace,
- Self::Subheader => Token::Chain,
- }
+/// Parse an identifier.
+fn ident(p: &mut Parser) -> Option<Spanned<Ident>> {
+ p.eat_map(|token| match token {
+ Token::Ident(id) => Some(Ident(id.into())),
+ _ => None,
+ })
+}
+
+/// Parse a color.
+fn color(p: &mut Parser, hex: &str, span: Span) -> Spanned<RgbaColor> {
+ RgbaColor::from_str(hex)
+ .unwrap_or_else(|_| {
+ // Heal color by assuming black.
+ p.diag(error!(span, "invalid color"));
+ RgbaColor::new_healed(0, 0, 0, 255)
+ })
+ .span_with(span)
+}
+
+/// Parse a string.
+fn string(p: &mut Parser, token: TokenStr, span: Span) -> Spanned<String> {
+ if !token.terminated {
+ p.diag_expected_at("quote", span.end);
}
+
+ resolve::resolve_string(token.string).span_with(span)
}
#[cfg(test)]