summaryrefslogtreecommitdiff
path: root/src/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax')
-rw-r--r--src/syntax/mod.rs4
-rw-r--r--src/syntax/parsing.rs117
-rw-r--r--src/syntax/tokens.rs135
-rw-r--r--src/syntax/tree.rs12
4 files changed, 135 insertions, 133 deletions
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index 596291a5..a9fe7c2e 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -37,9 +37,9 @@ impl Debug for Ident {
#[cfg(test)]
mod tests {
- use std::fmt::Debug;
- use crate::prelude::*;
use super::span;
+ use crate::prelude::*;
+ use std::fmt::Debug;
/// Assert that expected and found are equal, printing both and panicking
/// and the source of their test case if they aren't.
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index e35835c8..ae9cfdb1 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -2,16 +2,14 @@
use std::str::FromStr;
-use crate::{Feedback, Pass};
-use crate::color::RgbaColor;
-use crate::compute::table::SpannedEntry;
use super::decoration::Decoration;
use super::span::{Pos, Span, Spanned};
use super::tokens::{is_newline_char, Token, TokenMode, Tokens};
-use super::tree::{
- CallExpr, Expr, SyntaxNode, SyntaxTree, TableExpr, Code,
-};
+use super::tree::{CallExpr, Code, Expr, SyntaxNode, SyntaxTree, TableExpr};
use super::Ident;
+use crate::color::RgbaColor;
+use crate::compute::table::SpannedEntry;
+use crate::{Feedback, Pass};
/// Parse a string of source code.
pub fn parse(src: &str) -> Pass<SyntaxTree> {
@@ -106,9 +104,7 @@ impl Parser<'_> {
self.with_span(SyntaxNode::Code(Code { lang, lines, block }))
}
- Token::Text(text) => {
- self.with_span(SyntaxNode::Text(text.to_string()))
- }
+ Token::Text(text) => self.with_span(SyntaxNode::Text(text.to_string())),
Token::UnicodeEscape { sequence, terminated } => {
if !terminated {
@@ -222,7 +218,10 @@ impl Parser<'_> {
let mut table = TableExpr::new();
let mut comma_and_keyless = true;
- while { self.skip_white(); !self.eof() } {
+ while {
+ self.skip_white();
+ !self.eof()
+ } {
let (key, val) = if let Some(ident) = self.parse_ident() {
self.skip_white();
@@ -230,11 +229,12 @@ impl Parser<'_> {
Some(Token::Equals) => {
self.eat();
self.skip_white();
-
- (Some(ident), try_or!(self.parse_expr(), {
+ if let Some(value) = self.parse_expr() {
+ (Some(ident), value)
+ } else {
self.expected("value");
continue;
- }))
+ }
}
Some(Token::LeftParen) => {
@@ -242,26 +242,30 @@ impl Parser<'_> {
(None, call.map(Expr::Call))
}
- _ => (None, ident.map(Expr::Ident))
+ _ => (None, ident.map(Expr::Ident)),
}
+ } else if let Some(value) = self.parse_expr() {
+ (None, value)
} else {
- (None, try_or!(self.parse_expr(), {
- self.expected("value");
- continue;
- }))
+ self.expected("value");
+ continue;
};
let behind = val.span.end;
if let Some(key) = key {
comma_and_keyless = false;
table.insert(key.v.0, SpannedEntry::new(key.span, val));
- self.feedback.decorations
+ self.feedback
+ .decorations
.push(Spanned::new(Decoration::TableKey, key.span));
} else {
table.push(SpannedEntry::val(val));
}
- if { self.skip_white(); self.eof() } {
+ if {
+ self.skip_white();
+ self.eof()
+ } {
break;
}
@@ -274,6 +278,8 @@ impl Parser<'_> {
}
}
+type Binop = fn(Box<Spanned<Expr>>, Box<Spanned<Expr>>) -> Expr;
+
// Expressions and values.
impl Parser<'_> {
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
@@ -297,9 +303,7 @@ impl Parser<'_> {
&mut self,
operand_name: &str,
mut parse_operand: impl FnMut(&mut Self) -> Option<Spanned<Expr>>,
- mut parse_op: impl FnMut(Token) -> Option<
- fn(Box<Spanned<Expr>>, Box<Spanned<Expr>>) -> Expr
- >,
+ mut parse_op: impl FnMut(Token) -> Option<Binop>,
) -> Option<Spanned<Expr>> {
let mut left = parse_operand(self)?;
@@ -388,9 +392,7 @@ impl Parser<'_> {
let span = self.end_group();
let expr = if coercable {
- table.into_values()
- .next()
- .expect("table is coercable").val.v
+ table.into_values().next().expect("table is coercable").val.v
} else {
Expr::Table(table)
};
@@ -478,8 +480,7 @@ impl<'s> Parser<'s> {
fn end_group(&mut self) -> Span {
let peeked = self.peek();
- let (start, end_token) = self.delimiters.pop()
- .expect("group was not started");
+ let (start, end_token) = self.delimiters.pop().expect("group was not started");
if end_token != Token::Chain && peeked != None {
self.delimiters.push((start, end_token));
@@ -528,11 +529,7 @@ impl<'s> Parser<'s> {
}
fn check_eat(&mut self, token: Token<'_>) -> Option<Spanned<Token<'s>>> {
- if self.check(token) {
- self.eat()
- } else {
- None
- }
+ if self.check(token) { self.eat() } else { None }
}
/// Checks if the next token is of some kind
@@ -589,8 +586,7 @@ impl Group {
fn is_delimiter(token: Token<'_>) -> bool {
matches!(
token,
- Token::RightParen | Token::RightBracket
- | Token::RightBrace | Token::Chain
+ Token::RightParen | Token::RightBracket | Token::RightBrace | Token::Chain
)
}
@@ -654,7 +650,10 @@ fn unescape_string(string: &str) -> String {
}
Some('n') => out.push('\n'),
Some('t') => out.push('\t'),
- Some(c) => { out.push('\\'); out.push(c); }
+ Some(c) => {
+ out.push('\\');
+ out.push(c);
+ }
None => out.push('\\'),
}
} else {
@@ -784,22 +783,20 @@ fn split_lines(text: &str) -> Vec<String> {
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
- use crate::syntax::tests::*;
- use crate::length::Length;
use super::*;
+ use crate::length::Length;
+ use crate::syntax::tests::*;
use Decoration::*;
// ----------------------- Construct Syntax Nodes ----------------------- //
use SyntaxNode::{
- Spacing as S,
- Linebreak as L,
- Parbreak as P,
- ToggleItalic as I,
- ToggleBolder as B,
+ Linebreak as L, Parbreak as P, Spacing as S, ToggleBolder as B, ToggleItalic as I,
};
- fn T(text: &str) -> SyntaxNode { SyntaxNode::Text(text.to_string()) }
+ fn T(text: &str) -> SyntaxNode {
+ SyntaxNode::Text(text.to_string())
+ }
macro_rules! R {
($($line:expr),* $(,)?) => {
@@ -832,10 +829,14 @@ mod tests {
// ------------------------ Construct Expressions ----------------------- //
- use Expr::{Bool, Number as Num, Length as Len, Color};
+ use Expr::{Bool, Color, Length as Len, Number as Num};
- fn Id(ident: &str) -> Expr { Expr::Ident(Ident(ident.to_string())) }
- fn Str(string: &str) -> Expr { Expr::Str(string.to_string()) }
+ fn Id(ident: &str) -> Expr {
+ Expr::Ident(Ident(ident.to_string()))
+ }
+ fn Str(string: &str) -> Expr {
+ Expr::Str(string.to_string())
+ }
macro_rules! Table {
(@table=$table:expr,) => {};
@@ -937,6 +938,7 @@ mod tests {
// -------------------------------- Tests ------------------------------- //
#[test]
+ #[rustfmt::skip]
fn test_unescape_strings() {
fn test(string: &str, expected: &str) {
assert_eq!(unescape_string(string), expected.to_string());
@@ -957,6 +959,7 @@ mod tests {
}
#[test]
+ #[rustfmt::skip]
fn test_unescape_raws() {
fn test(raw: &str, expected: Vec<&str>) {
assert_eq!(unescape_raw(raw), expected);
@@ -974,6 +977,7 @@ mod tests {
}
#[test]
+ #[rustfmt::skip]
fn test_unescape_code() {
fn test(raw: &str, expected: Vec<&str>) {
assert_eq!(unescape_code(raw), expected);
@@ -1060,7 +1064,7 @@ mod tests {
s(0,1, 0,1, "expected closing bracket"));
// No name.
- e!("[]" => s(0,1, 0,1, "expected function name"));
+ e!("[]" => s(0,1, 0,1, "expected function name"));
e!("[\"]" => s(0,1, 0,3, "expected function name, found string"),
s(0,3, 0,3, "expected closing bracket"));
@@ -1077,8 +1081,8 @@ mod tests {
fn test_parse_chaining() {
// Things the parser has to make sense of
t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Table![Num(5.0), Num(2.1)], Tree![F!("you")]));
- t!("[box >>][Hi]" => F!("box"; Tree![T("Hi")]));
- t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
+ t!("[box >>][Hi]" => F!("box"; Tree![T("Hi")]));
+ t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi")))
]));
t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Num(400.0), Tree![
@@ -1110,7 +1114,7 @@ mod tests {
#[test]
fn test_parse_function_bodies() {
t!("[val: 1][*Hi*]" => F!("val"; Num(1.0), Tree![B, T("Hi"), B]));
- e!(" [val][ */ ]" => s(0,8, 0,10, "unexpected end of block comment"));
+ e!(" [val][ */ ]" => s(0,8, 0,10, "unexpected end of block comment"));
// Raw in body.
t!("[val][`Hi]`" => F!("val"; Tree![R!["Hi]"]]));
@@ -1148,10 +1152,10 @@ mod tests {
v!("\"a\n[]\\\"string\"" => Str("a\n[]\"string"));
// Content.
- v!("{_hi_}" => Tree![I, T("hi"), I]);
- e!("[val: {_hi_}]" => );
- v!("[hi]" => Tree![F!["hi"]]);
- e!("[val: [hi]]" => );
+ v!("{_hi_}" => Tree![I, T("hi"), I]);
+ e!("[val: {_hi_}]" => );
+ v!("[hi]" => Tree![F!("hi")]);
+ e!("[val: [hi]]" => );
// Healed colors.
v!("#12345" => Color(RgbaColor::new_healed(0, 0, 0, 0xff)));
@@ -1232,8 +1236,7 @@ mod tests {
// Spanned with spacing around keyword arguments.
ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0,0, 4,2, F!(
- s(0,1, 0,4, "val");
- s(1,1, 1,3, "hi") => s(3,4, 4,1, Str("s\n"))
+ s(0,1, 0,4, "val"); s(1,1, 1,3, "hi") => s(3,4, 4,1, Str("s\n"))
)));
e!("[val: \n hi \n = /* //\n */ \"s\n\"]" => );
}
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index fe20d11a..d566363c 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -4,8 +4,8 @@ use std::iter::Peekable;
use std::str::Chars;
use unicode_xid::UnicodeXID;
-use crate::length::Length;
use super::span::{Pos, Span, Spanned};
+use crate::length::Length;
use Token::*;
use TokenMode::*;
@@ -224,7 +224,10 @@ impl<'s> Iterator for Tokens<'s> {
// Comments.
'/' if self.peek() == Some('/') => self.read_line_comment(),
'/' if self.peek() == Some('*') => self.read_block_comment(),
- '*' if self.peek() == Some('/') => { self.eat(); Invalid("*/") }
+ '*' if self.peek() == Some('/') => {
+ self.eat();
+ Invalid("*/")
+ }
// Whitespace.
c if c.is_whitespace() => self.read_whitespace(start),
@@ -241,8 +244,7 @@ impl<'s> Iterator for Tokens<'s> {
':' if self.mode == Header => Colon,
',' if self.mode == Header => Comma,
'=' if self.mode == Header => Equals,
- '>' if self.mode == Header && self.peek() == Some('>') =>
- self.read_chain(),
+ '>' if self.mode == Header && self.peek() == Some('>') => self.read_chain(),
// Expression operators.
'+' if self.mode == Header => Plus,
@@ -270,20 +272,22 @@ impl<'s> Iterator for Tokens<'s> {
c => {
let body = self.mode == Body;
+ let start_offset = -(c.len_utf8() as isize);
let mut last_was_e = false;
- let text = self.read_string_until(|n| {
+
+ let (text, _) = self.read_string_until(false, start_offset, 0, |n| {
let val = match n {
c if c.is_whitespace() => true,
- '[' | ']' | '{' | '}' | '/' | '*' => true,
+ '[' | ']' | '{' | '}' | '/' | '*' => true,
'\\' | '_' | '`' if body => true,
- ':' | '=' | ',' | '"' | '(' | ')' if !body => true,
- '+' | '-' if !body && !last_was_e => true,
+ ':' | '=' | ',' | '"' | '(' | ')' if !body => true,
+ '+' | '-' if !body && !last_was_e => true,
_ => false,
};
last_was_e = n == 'e' || n == 'E';
val
- }, false, -(c.len_utf8() as isize), 0).0;
+ });
if self.mode == Header {
self.read_expr(text)
@@ -302,35 +306,41 @@ impl<'s> Iterator for Tokens<'s> {
impl<'s> Tokens<'s> {
fn read_line_comment(&mut self) -> Token<'s> {
- LineComment(self.read_string_until(is_newline_char, false, 1, 0).0)
+ self.eat();
+ LineComment(self.read_string_until(false, 0, 0, is_newline_char).0)
}
fn read_block_comment(&mut self) -> Token<'s> {
- enum Last { Slash, Star, Other }
-
- self.eat();
+ enum Last {
+ Slash,
+ Star,
+ Other,
+ }
let mut depth = 0;
let mut last = Last::Other;
// Find the first `*/` that does not correspond to a nested `/*`.
// Remove the last two bytes to obtain the raw inner text without `*/`.
- BlockComment(self.read_string_until(|n| {
- match n {
+ self.eat();
+ let (content, _) = self.read_string_until(true, 0, -2, |c| {
+ match c {
'/' => match last {
Last::Star if depth == 0 => return true,
Last::Star => depth -= 1,
- _ => last = Last::Slash
- }
+ _ => last = Last::Slash,
+ },
'*' => match last {
Last::Slash => depth += 1,
_ => last = Last::Star,
- }
+ },
_ => last = Last::Other,
}
false
- }, true, 0, -2).0)
+ });
+
+ BlockComment(content)
}
fn read_chain(&mut self) -> Token<'s> {
@@ -339,7 +349,7 @@ impl<'s> Tokens<'s> {
}
fn read_whitespace(&mut self, start: Pos) -> Token<'s> {
- self.read_string_until(|n| !n.is_whitespace(), false, 0, 0);
+ self.read_string_until(false, 0, 0, |n| !n.is_whitespace());
let end = self.pos();
Space(end.line - start.line)
@@ -358,11 +368,11 @@ impl<'s> Tokens<'s> {
// Reads the lang tag (until newline or whitespace).
let start = self.pos();
- let lang = self.read_string_until(
- |c| c == '`' || c.is_whitespace() || is_newline_char(c),
- false, 0, 0,
- ).0;
+ let (lang, _) = self.read_string_until(false, 0, 0, |c| {
+ c == '`' || c.is_whitespace() || is_newline_char(c)
+ });
let end = self.pos();
+
let lang = if !lang.is_empty() {
Some(Spanned::new(lang, Span::new(start, end)))
} else {
@@ -405,25 +415,25 @@ impl<'s> Tokens<'s> {
Code {
lang,
- raw: &self.src[start..end],
- terminated
+ raw: &self.src[start .. end],
+ terminated,
}
} else {
Raw { raw, terminated }
}
}
- fn read_until_unescaped(&mut self, c: char) -> (&'s str, bool) {
+ fn read_until_unescaped(&mut self, end: char) -> (&'s str, bool) {
let mut escaped = false;
- self.read_string_until(|n| {
- match n {
- n if n == c && !escaped => return true,
+ self.read_string_until(true, 0, -1, |c| {
+ match c {
+ c if c == end && !escaped => return true,
'\\' => escaped = !escaped,
_ => escaped = false,
}
false
- }, true, 0, -1)
+ })
}
fn read_escaped(&mut self) -> Token<'s> {
@@ -439,10 +449,8 @@ impl<'s> Tokens<'s> {
self.eat();
if self.peek() == Some('{') {
self.eat();
- let sequence = self.read_string_until(
- |c| !c.is_ascii_hexdigit(),
- false, 0, 0,
- ).0;
+ let (sequence, _) =
+ self.read_string_until(false, 0, 0, |c| !c.is_ascii_hexdigit());
let terminated = self.peek() == Some('}');
if terminated {
@@ -457,7 +465,7 @@ impl<'s> Tokens<'s> {
Some(c) if is_escapable(c) => {
let index = self.index();
self.eat();
- Text(&self.src[index..index + c.len_utf8()])
+ Text(&self.src[index .. index + c.len_utf8()])
}
Some(c) if c.is_whitespace() => Backslash,
Some(_) => Text("\\"),
@@ -468,10 +476,7 @@ impl<'s> Tokens<'s> {
fn read_hex(&mut self) -> Token<'s> {
// This will parse more than the permissable 0-9, a-f, A-F character
// ranges to provide nicer error messages later.
- Hex(self.read_string_until(
- |n| !n.is_ascii_alphanumeric(),
- false, 0, 0
- ).0)
+ Hex(self.read_string_until(false, 0, 0, |n| !n.is_ascii_alphanumeric()).0)
}
fn read_expr(&mut self, text: &'s str) -> Token<'s> {
@@ -497,10 +502,10 @@ impl<'s> Tokens<'s> {
/// after the match depending on `eat_match`.
fn read_string_until(
&mut self,
- mut f: impl FnMut(char) -> bool,
eat_match: bool,
offset_start: isize,
offset_end: isize,
+ mut f: impl FnMut(char) -> bool,
) -> (&'s str, bool) {
let start = ((self.index() as isize) + offset_start) as usize;
let mut matched = false;
@@ -522,7 +527,7 @@ impl<'s> Tokens<'s> {
end = ((end as isize) + offset_end) as usize;
}
- (&self.src[start..end], matched)
+ (&self.src[start .. end], matched)
}
fn eat(&mut self) -> Option<char> {
@@ -546,7 +551,7 @@ impl<'s> Tokens<'s> {
fn parse_percentage(text: &str) -> Option<f64> {
if text.ends_with('%') {
- text[..text.len() - 1].parse::<f64>().ok()
+ text[.. text.len() - 1].parse::<f64>().ok()
} else {
None
}
@@ -556,7 +561,7 @@ fn parse_percentage(text: &str) -> Option<f64> {
pub fn is_newline_char(character: char) -> bool {
match character {
// Line Feed, Vertical Tab, Form Feed, Carriage Return.
- '\x0A'..='\x0D' => true,
+ '\x0A' ..= '\x0D' => true,
// Next Line, Line Separator, Paragraph Separator.
'\u{0085}' | '\u{2028}' | '\u{2029}' => true,
_ => false,
@@ -588,35 +593,33 @@ pub fn is_identifier(string: &str) -> bool {
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
+ use super::super::span::Spanned;
+ use super::*;
use crate::length::Length;
use crate::syntax::tests::*;
- use super::*;
- use super::super::span::Spanned;
use Token::{
- Space as S,
- LineComment as LC, BlockComment as BC,
- LeftBracket as L, RightBracket as R,
- LeftParen as LP, RightParen as RP,
- LeftBrace as LB, RightBrace as RB,
- Chain,
- Ident as Id,
- Bool,
- Number as Num,
- Length as Len,
- Hex,
- Plus,
- Hyphen as Min,
- Slash,
- Star,
- Text as T,
+ BlockComment as BC, Bool, Chain, Hex, Hyphen as Min, Ident as Id,
+ LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len,
+ LineComment as LC, Number as Num, Plus, RightBrace as RB, RightBracket as R,
+ RightParen as RP, Slash, Space as S, Star, Text as T,
};
- fn Str(string: &str, terminated: bool) -> Token { Token::Str { string, terminated } }
- fn Raw(raw: &str, terminated: bool) -> Token { Token::Raw { raw, terminated } }
+ fn Str(string: &str, terminated: bool) -> Token {
+ Token::Str { string, terminated }
+ }
+ fn Raw(raw: &str, terminated: bool) -> Token {
+ Token::Raw { raw, terminated }
+ }
fn Code<'a>(lang: Option<&'a str>, raw: &'a str, terminated: bool) -> Token<'a> {
- Token::Code { lang: lang.map(Spanned::zero), raw, terminated }
+ Token::Code {
+ lang: lang.map(Spanned::zero),
+ raw,
+ terminated,
+ }
+ }
+ fn UE(sequence: &str, terminated: bool) -> Token {
+ Token::UnicodeEscape { sequence, terminated }
}
- fn UE(sequence: &str, terminated: bool) -> Token { Token::UnicodeEscape { sequence, terminated } }
macro_rules! t { ($($tts:tt)*) => {test!(@spans=false, $($tts)*)} }
macro_rules! ts { ($($tts:tt)*) => {test!(@spans=true, $($tts)*)} }
diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs
index 44acd023..7295ec04 100644
--- a/src/syntax/tree.rs
+++ b/src/syntax/tree.rs
@@ -2,15 +2,15 @@
use std::fmt::{self, Debug, Formatter};
+use super::decoration::Decoration;
+use super::span::{SpanVec, Spanned};
+use super::Ident;
use crate::color::RgbaColor;
use crate::compute::table::{SpannedEntry, Table};
use crate::compute::value::{TableValue, Value};
use crate::layout::LayoutContext;
use crate::length::Length;
use crate::{DynFuture, Feedback};
-use super::decoration::Decoration;
-use super::span::{Spanned, SpanVec};
-use super::Ident;
/// A collection of nodes which form a tree together with the nodes' children.
pub type SyntaxTree = SpanVec<SyntaxNode>;
@@ -96,11 +96,7 @@ impl Expr {
}
/// Evaluate the expression to a value.
- pub async fn eval(
- &self,
- ctx: &LayoutContext<'_>,
- f: &mut Feedback,
- ) -> Value {
+ pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value {
use Expr::*;
match self {
Ident(i) => Value::Ident(i.clone()),