Inline literal enum into expression enum 🔀

This commit is contained in:
Laurenz 2021-01-06 21:06:48 +01:00
parent 7b4d4d6002
commit 59d811aeba
11 changed files with 231 additions and 294 deletions

View File

@ -75,7 +75,6 @@ impl Eval for Spanned<&Node> {
let node = ctx.make_text_node(text.clone());
ctx.push(node);
}
Node::Space => {
let em = ctx.state.font.font_size();
ctx.push(NodeSpacing {
@ -85,13 +84,10 @@ impl Eval for Spanned<&Node> {
}
Node::Linebreak => ctx.apply_linebreak(),
Node::Parbreak => ctx.apply_parbreak(),
Node::Strong => ctx.state.font.strong ^= true,
Node::Emph => ctx.state.font.emph ^= true,
Node::Heading(heading) => heading.with_span(self.span).eval(ctx),
Node::Raw(raw) => raw.with_span(self.span).eval(ctx),
Node::Expr(expr) => {
let value = expr.with_span(self.span).eval(ctx);
value.eval(ctx)
@ -153,7 +149,21 @@ impl Eval for Spanned<&Expr> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self.v {
Expr::Lit(v) => v.with_span(self.span).eval(ctx),
Expr::None => Value::None,
Expr::Ident(v) => match ctx.state.scope.get(v) {
Some(value) => value.clone(),
None => {
ctx.diag(error!(self.span, "unknown variable"));
Value::Error
}
},
Expr::Bool(v) => Value::Bool(*v),
Expr::Int(v) => Value::Int(*v),
Expr::Float(v) => Value::Float(*v),
Expr::Length(v, unit) => Value::Length(Length::with_unit(*v, *unit)),
Expr::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Expr::Color(v) => Value::Color(Color::Rgba(*v)),
Expr::Str(v) => Value::Str(v.clone()),
Expr::Call(v) => v.with_span(self.span).eval(ctx),
Expr::Unary(v) => v.with_span(self.span).eval(ctx),
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
@ -164,49 +174,6 @@ impl Eval for Spanned<&Expr> {
}
}
impl Eval for Spanned<&Lit> {
type Output = Value;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match *self.v {
Lit::Ident(ref v) => match ctx.state.scope.get(&v) {
Some(value) => value.clone(),
None => {
ctx.diag(error!(self.span, "unknown variable"));
Value::Error
}
},
Lit::None => Value::None,
Lit::Bool(v) => Value::Bool(v),
Lit::Int(v) => Value::Int(v),
Lit::Float(v) => Value::Float(v),
Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(Color::Rgba(v)),
Lit::Str(ref v) => Value::Str(v.clone()),
}
}
}
impl Eval for Spanned<&ExprArray> {
type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v.iter().map(|expr| expr.as_ref().eval(ctx)).collect()
}
}
impl Eval for Spanned<&ExprDict> {
type Output = ValueDict;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v
.iter()
.map(|Named { name, expr }| (name.v.0.clone(), expr.as_ref().eval(ctx)))
.collect()
}
}
impl Eval for Spanned<&ExprUnary> {
type Output = Value;
@ -245,6 +212,25 @@ impl Eval for Spanned<&ExprBinary> {
}
}
impl Eval for Spanned<&ExprArray> {
type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v.iter().map(|expr| expr.as_ref().eval(ctx)).collect()
}
}
impl Eval for Spanned<&ExprDict> {
type Output = ValueDict;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v
.iter()
.map(|Named { name, expr }| (name.v.0.clone(), expr.as_ref().eval(ctx)))
.collect()
}
}
/// Compute the negation of a value.
fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
use Value::*;

View File

@ -487,6 +487,7 @@ macro_rules! impl_type {
mod tests {
use super::*;
use crate::color::RgbaColor;
use crate::parse::parse;
use crate::pretty::pretty;
use crate::syntax::Node;
@ -496,7 +497,7 @@ mod tests {
}
#[test]
fn test_pretty_print_values() {
fn test_pretty_print_simple_values() {
test_pretty(Value::None, "none");
test_pretty(false, "false");
test_pretty(12.4, "12.4");
@ -521,8 +522,8 @@ mod tests {
// Dictionary.
let mut dict = BTreeMap::new();
dict.insert("one".into(), Value::Int(1));
dict.insert("two".into(), Value::Int(2));
dict.insert("two".into(), Value::Content(parse("[f]").output));
test_pretty(BTreeMap::new(), "(:)");
test_pretty(dict, "(one: 1, two: 2)");
test_pretty(dict, "(one: 1, two: [f])");
}
}

View File

@ -3,7 +3,7 @@ use super::*;
/// A relative length.
///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
/// corresponding [literal](crate::syntax::Lit::Percent).
/// corresponding [literal](crate::syntax::Expr::Percent).
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Relative(f64);

View File

@ -53,7 +53,7 @@ fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
fn argument(p: &mut Parser) -> Option<Argument> {
let first = p.span_if(expr)?;
if p.eat_if(Token::Colon) {
if let Expr::Lit(Lit::Ident(ident)) = first.v {
if let Expr::Ident(ident) = first.v {
let expr = p.span_if(expr)?;
let name = ident.with_span(first.span);
p.deco(Deco::Name.with_span(name.span));
@ -131,7 +131,7 @@ impl Collection for State {
fn take(expr: &mut Spanned<Expr>) -> Spanned<Expr> {
// Replace with anything, it's overwritten anyway.
std::mem::replace(expr, Spanned::zero(Expr::Lit(Lit::Bool(false))))
std::mem::replace(expr, Spanned::zero(Expr::Bool(false)))
}
fn diag(p: &mut Parser, arg: Spanned<Argument>) {

View File

@ -49,6 +49,7 @@ fn tree(p: &mut Parser) -> Tree {
/// Parse a syntax node.
fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
let node = match p.peek()? {
Token::Text(text) => Node::Text(text.into()),
Token::Space(newlines) => {
if newlines < 2 {
Node::Space
@ -56,13 +57,20 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
Node::Parbreak
}
}
Token::Text(text) => Node::Text(text.into()),
Token::LineComment(_) | Token::BlockComment(_) => {
p.eat();
return None;
}
Token::LeftBracket => {
return Some(Node::Expr(Expr::Call(bracket_call(p))));
}
Token::LeftBrace => {
return Some(Node::Expr(block_expr(p)?));
}
Token::Star => Node::Strong,
Token::Underscore => Node::Emph,
Token::Tilde => Node::Text("\u{00A0}".into()),
@ -77,14 +85,6 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
Token::Raw(t) => Node::Raw(raw(p, t)),
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
Token::LeftBracket => {
return Some(Node::Expr(Expr::Call(bracket_call(p))));
}
Token::LeftBrace => {
return Some(Node::Expr(block_expr(p)?));
}
_ => {
p.diag_unexpected();
return None;
@ -321,19 +321,19 @@ fn value(p: &mut Parser) -> Option<Expr> {
let name = ident.with_span(p.peek_span());
return Some(Expr::Call(paren_call(p, name)));
} else {
return Some(Expr::Lit(Lit::Ident(ident)));
return Some(Expr::Ident(ident));
}
}
// Basic values.
Some(Token::None) => Expr::Lit(Lit::None),
Some(Token::Bool(b)) => Expr::Lit(Lit::Bool(b)),
Some(Token::Int(i)) => Expr::Lit(Lit::Int(i)),
Some(Token::Float(f)) => Expr::Lit(Lit::Float(f)),
Some(Token::Length(val, unit)) => Expr::Lit(Lit::Length(val, unit)),
Some(Token::Percent(p)) => Expr::Lit(Lit::Percent(p)),
Some(Token::Hex(hex)) => Expr::Lit(Lit::Color(color(p, hex))),
Some(Token::Str(token)) => Expr::Lit(Lit::Str(str(p, token))),
Some(Token::None) => Expr::None,
Some(Token::Bool(b)) => Expr::Bool(b),
Some(Token::Int(i)) => Expr::Int(i),
Some(Token::Float(f)) => Expr::Float(f),
Some(Token::Length(val, unit)) => Expr::Length(val, unit),
Some(Token::Percent(p)) => Expr::Percent(p),
Some(Token::Hex(hex)) => Expr::Color(color(p, hex)),
Some(Token::Str(token)) => Expr::Str(str(p, token)),
// No value.
_ => {

View File

@ -9,7 +9,8 @@ use crate::geom::Unit;
use crate::syntax::*;
use BinOp::*;
use Node::{Emph, Linebreak, Parbreak, Space, Strong};
use Expr::{Bool, Color, Float, Int, Length, Percent};
use Node::{Emph, Expr as Block, Linebreak, Parbreak, Space, Strong};
use UnOp::*;
macro_rules! t {
@ -99,39 +100,11 @@ fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node {
}
fn Id(ident: &str) -> Expr {
Expr::Lit(Lit::Ident(Ident(ident.to_string())))
}
fn Bool(b: bool) -> Expr {
Expr::Lit(Lit::Bool(b))
}
fn Int(int: i64) -> Expr {
Expr::Lit(Lit::Int(int))
}
fn Float(float: f64) -> Expr {
Expr::Lit(Lit::Float(float))
}
fn Percent(percent: f64) -> Expr {
Expr::Lit(Lit::Percent(percent))
}
fn Length(val: f64, unit: Unit) -> Expr {
Expr::Lit(Lit::Length(val, unit))
}
fn Color(color: RgbaColor) -> Expr {
Expr::Lit(Lit::Color(color))
Expr::Ident(Ident(ident.to_string()))
}
fn Str(string: &str) -> Expr {
Expr::Lit(Lit::Str(string.to_string()))
}
fn Block(expr: Expr) -> Node {
Node::Expr(expr)
Expr::Str(string.to_string())
}
fn Binary(
@ -614,7 +587,7 @@ fn test_parse_values() {
t!("{name}" Block(Id("name")));
t!("{ke-bab}" Block(Id("ke-bab")));
t!("{α}" Block(Id("α")));
t!("{none}" Block(Expr::Lit(Lit::None)));
t!("{none}" Block(Expr::None));
t!("{true}" Block(Bool(true)));
t!("{false}" Block(Bool(false)));
t!("{1.0e-4}" Block(Float(1e-4)));

View File

@ -379,25 +379,21 @@ mod tests {
use crate::parse::tests::check;
use Option::None;
use Token::{
BlockComment as BC, Ident as Id, LeftBrace as LB, LeftBracket as L,
LeftParen as LP, LineComment as LC, RightBrace as RB, RightBracket as R,
RightParen as RP, Space as S, Text as T, *,
};
use Token::{Ident, *};
use Unit::*;
fn Str(string: &str, terminated: bool) -> Token {
Token::Str(TokenStr { string, terminated })
}
fn Raw(text: &str, backticks: usize, terminated: bool) -> Token {
Token::Raw(TokenRaw { text, backticks, terminated })
}
fn UE(sequence: &str, terminated: bool) -> Token {
fn UnicodeEscape(sequence: &str, terminated: bool) -> Token {
Token::UnicodeEscape(TokenUnicodeEscape { sequence, terminated })
}
fn Str(string: &str, terminated: bool) -> Token {
Token::Str(TokenStr { string, terminated })
}
/// Building blocks for suffix testing.
///
/// We extend each test case with a collection of different suffixes to make
@ -421,27 +417,27 @@ mod tests {
/// - the resulting suffix token
const SUFFIXES: &[(char, Option<TokenMode>, &str, Token)] = &[
// Whitespace suffixes.
(' ', None, " ", S(0)),
(' ', None, "\n", S(1)),
(' ', None, "\r", S(1)),
(' ', None, "\r\n", S(1)),
(' ', None, " ", Space(0)),
(' ', None, "\n", Space(1)),
(' ', None, "\r", Space(1)),
(' ', None, "\r\n", Space(1)),
// Letter suffixes.
('a', Some(Body), "hello", T("hello")),
('a', Some(Body), "💚", T("💚")),
('a', Some(Header), "val", Id("val")),
('a', Some(Header), "α", Id("α")),
('a', Some(Header), "_", Id("_")),
('a', Some(Body), "hello", Text("hello")),
('a', Some(Body), "💚", Text("💚")),
('a', Some(Header), "val", Ident("val")),
('a', Some(Header), "α", Ident("α")),
('a', Some(Header), "_", Ident("_")),
// Number suffixes.
('1', Some(Header), "2", Int(2)),
('1', Some(Header), ".2", Float(0.2)),
// Symbol suffixes.
('/', None, "[", L),
('/', None, "//", LC("")),
('/', None, "/**/", BC("")),
('/', None, "[", LeftBracket),
('/', None, "//", LineComment("")),
('/', None, "/**/", BlockComment("")),
('/', Some(Body), "*", Star),
('/', Some(Body), "_", Underscore),
('/', Some(Body), r"\\", T(r"\")),
('/', Some(Header), "(", LP),
('/', Some(Body), r"\\", Text(r"\")),
('/', Some(Header), "(", LeftParen),
('/', Some(Header), ":", Colon),
('/', Some(Header), "+", Plus),
('/', Some(Header), "#123", Hex("123")),
@ -487,69 +483,69 @@ mod tests {
fn test_tokenize_whitespace() {
// Test basic whitespace.
t!(Both["a1/"]: "" => );
t!(Both["a1/"]: " " => S(0));
t!(Both["a1/"]: " " => S(0));
t!(Both["a1/"]: "\t" => S(0));
t!(Both["a1/"]: " \t" => S(0));
t!(Both["a1/"]: "\u{202F}" => S(0));
t!(Both["a1/"]: " " => Space(0));
t!(Both["a1/"]: " " => Space(0));
t!(Both["a1/"]: "\t" => Space(0));
t!(Both["a1/"]: " \t" => Space(0));
t!(Both["a1/"]: "\u{202F}" => Space(0));
// Test newline counting.
t!(Both["a1/"]: "\n" => S(1));
t!(Both["a1/"]: "\n " => S(1));
t!(Both["a1/"]: " \n" => S(1));
t!(Both["a1/"]: " \n " => S(1));
t!(Both["a1/"]: "\r\n" => S(1));
t!(Both["a1/"]: " \n\t \n " => S(2));
t!(Both["a1/"]: "\n\r" => S(2));
t!(Both["a1/"]: " \r\r\n \x0D" => S(3));
t!(Both["a1/"]: "\n" => Space(1));
t!(Both["a1/"]: "\n " => Space(1));
t!(Both["a1/"]: " \n" => Space(1));
t!(Both["a1/"]: " \n " => Space(1));
t!(Both["a1/"]: "\r\n" => Space(1));
t!(Both["a1/"]: " \n\t \n " => Space(2));
t!(Both["a1/"]: "\n\r" => Space(2));
t!(Both["a1/"]: " \r\r\n \x0D" => Space(3));
}
#[test]
fn test_tokenize_line_comments() {
// Test line comment with no trailing newline.
t!(Both[""]: "//" => LC(""));
t!(Both[""]: "//" => LineComment(""));
// Test line comment ends at newline.
t!(Both["a1/"]: "//bc\n" => LC("bc"), S(1));
t!(Both["a1/"]: "// bc \n" => LC(" bc "), S(1));
t!(Both["a1/"]: "//bc\r\n" => LC("bc"), S(1));
t!(Both["a1/"]: "//bc\n" => LineComment("bc"), Space(1));
t!(Both["a1/"]: "// bc \n" => LineComment(" bc "), Space(1));
t!(Both["a1/"]: "//bc\r\n" => LineComment("bc"), Space(1));
// Test nested line comments.
t!(Both["a1/"]: "//a//b\n" => LC("a//b"), S(1));
t!(Both["a1/"]: "//a//b\n" => LineComment("a//b"), Space(1));
}
#[test]
fn test_tokenize_block_comments() {
// Test basic block comments.
t!(Both[""]: "/*" => BC(""));
t!(Both: "/**/" => BC(""));
t!(Both: "/*🏞*/" => BC("🏞"));
t!(Both: "/*\n*/" => BC("\n"));
t!(Both[""]: "/*" => BlockComment(""));
t!(Both: "/**/" => BlockComment(""));
t!(Both: "/*🏞*/" => BlockComment("🏞"));
t!(Both: "/*\n*/" => BlockComment("\n"));
// Test depth 1 and 2 nested block comments.
t!(Both: "/* /* */ */" => BC(" /* */ "));
t!(Both: "/*/*/**/*/*/" => BC("/*/**/*/"));
t!(Both: "/* /* */ */" => BlockComment(" /* */ "));
t!(Both: "/*/*/**/*/*/" => BlockComment("/*/**/*/"));
// Test two nested, one unclosed block comments.
t!(Both[""]: "/*/*/**/*/" => BC("/*/**/*/"));
t!(Both[""]: "/*/*/**/*/" => BlockComment("/*/**/*/"));
// Test all combinations of up to two following slashes and stars.
t!(Both[""]: "/*" => BC(""));
t!(Both[""]: "/*/" => BC("/"));
t!(Both[""]: "/**" => BC("*"));
t!(Both[""]: "/*//" => BC("//"));
t!(Both[""]: "/*/*" => BC("/*"));
t!(Both[""]: "/**/" => BC(""));
t!(Both[""]: "/***" => BC("**"));
t!(Both[""]: "/*" => BlockComment(""));
t!(Both[""]: "/*/" => BlockComment("/"));
t!(Both[""]: "/**" => BlockComment("*"));
t!(Both[""]: "/*//" => BlockComment("//"));
t!(Both[""]: "/*/*" => BlockComment("/*"));
t!(Both[""]: "/**/" => BlockComment(""));
t!(Both[""]: "/***" => BlockComment("**"));
}
#[test]
fn test_tokenize_body_tokens() {
// Test parentheses.
t!(Body: "[" => L);
t!(Body: "]" => R);
t!(Body: "{" => LB);
t!(Body: "}" => RB);
t!(Body: "[" => LeftBracket);
t!(Body: "]" => RightBracket);
t!(Body: "{" => LeftBrace);
t!(Body: "}" => RightBrace);
// Test markup tokens.
t!(Body[" a1"]: "*" => Star);
@ -559,7 +555,7 @@ mod tests {
t!(Body[" "]: r"\" => Backslash);
// Test header symbols.
t!(Body[" /"]: ":,=|/+-" => T(":,=|/+-"));
t!(Body[" /"]: ":,=|/+-" => Text(":,=|/+-"));
}
#[test]
@ -584,62 +580,62 @@ mod tests {
#[test]
fn test_tokenize_escape_sequences() {
// Test escapable symbols.
t!(Body: r"\\" => T(r"\"));
t!(Body: r"\/" => T("/"));
t!(Body: r"\[" => T("["));
t!(Body: r"\]" => T("]"));
t!(Body: r"\{" => T("{"));
t!(Body: r"\}" => T("}"));
t!(Body: r"\*" => T("*"));
t!(Body: r"\_" => T("_"));
t!(Body: r"\#" => T("#"));
t!(Body: r"\~" => T("~"));
t!(Body: r"\`" => T("`"));
t!(Body: r"\\" => Text(r"\"));
t!(Body: r"\/" => Text("/"));
t!(Body: r"\[" => Text("["));
t!(Body: r"\]" => Text("]"));
t!(Body: r"\{" => Text("{"));
t!(Body: r"\}" => Text("}"));
t!(Body: r"\*" => Text("*"));
t!(Body: r"\_" => Text("_"));
t!(Body: r"\#" => Text("#"));
t!(Body: r"\~" => Text("~"));
t!(Body: r"\`" => Text("`"));
// Test unescapable symbols.
t!(Body[" /"]: r"\a" => T(r"\"), T("a"));
t!(Body[" /"]: r"\u" => T(r"\"), T("u"));
t!(Body[" /"]: r"\1" => T(r"\"), T("1"));
t!(Body[" /"]: r"\:" => T(r"\"), T(":"));
t!(Body[" /"]: r"\=" => T(r"\"), T("="));
t!(Body[" /"]: r#"\""# => T(r"\"), T("\""));
t!(Body[" /"]: r"\a" => Text(r"\"), Text("a"));
t!(Body[" /"]: r"\u" => Text(r"\"), Text("u"));
t!(Body[" /"]: r"\1" => Text(r"\"), Text("1"));
t!(Body[" /"]: r"\:" => Text(r"\"), Text(":"));
t!(Body[" /"]: r"\=" => Text(r"\"), Text("="));
t!(Body[" /"]: r#"\""# => Text(r"\"), Text("\""));
// Test basic unicode escapes.
t!(Body: r"\u{}" => UE("", true));
t!(Body: r"\u{2603}" => UE("2603", true));
t!(Body: r"\u{P}" => UE("P", true));
t!(Body: r"\u{}" => UnicodeEscape("", true));
t!(Body: r"\u{2603}" => UnicodeEscape("2603", true));
t!(Body: r"\u{P}" => UnicodeEscape("P", true));
// Test unclosed unicode escapes.
t!(Body[" /"]: r"\u{" => UE("", false));
t!(Body[" /"]: r"\u{1" => UE("1", false));
t!(Body[" /"]: r"\u{26A4" => UE("26A4", false));
t!(Body[" /"]: r"\u{1Q3P" => UE("1Q3P", false));
t!(Body: r"\u{1🏕}" => UE("1", false), T("🏕"), RB);
t!(Body[" /"]: r"\u{" => UnicodeEscape("", false));
t!(Body[" /"]: r"\u{1" => UnicodeEscape("1", false));
t!(Body[" /"]: r"\u{26A4" => UnicodeEscape("26A4", false));
t!(Body[" /"]: r"\u{1Q3P" => UnicodeEscape("1Q3P", false));
t!(Body: r"\u{1🏕}" => UnicodeEscape("1", false), Text("🏕"), RightBrace);
}
#[test]
fn test_tokenize_text() {
// Test basic text.
t!(Body[" /"]: "hello" => T("hello"));
t!(Body[" /"]: "hello-world" => T("hello-world"));
t!(Body[" /"]: "hello" => Text("hello"));
t!(Body[" /"]: "hello-world" => Text("hello-world"));
// Test header symbols in text.
t!(Body[" /"]: "a():\"b" => T("a():\"b"));
t!(Body[" /"]: "a():\"b" => Text("a():\"b"));
// Test text ends.
t!(Body[""]: "hello " => T("hello"), S(0));
t!(Body[""]: "hello~" => T("hello"), Tilde);
t!(Body[""]: "hello " => Text("hello"), Space(0));
t!(Body[""]: "hello~" => Text("hello"), Tilde);
}
#[test]
fn test_tokenize_header_tokens() {
// Test parentheses.
t!(Header: "[" => L);
t!(Header: "]" => R);
t!(Header: "{" => LB);
t!(Header: "}" => RB);
t!(Header: "(" => LP);
t!(Header: ")" => RP);
t!(Header: "[" => LeftBracket);
t!(Header: "]" => RightBracket);
t!(Header: "{" => LeftBrace);
t!(Header: "}" => RightBrace);
t!(Header: "(" => LeftParen);
t!(Header: ")" => RightParen);
// Test structural tokens.
t!(Header: ":" => Colon);
@ -652,10 +648,10 @@ mod tests {
// Test hyphen parsed as symbol.
t!(Header[" /"]: "-1" => Hyphen, Int(1));
t!(Header[" /"]: "-a" => Hyphen, Id("a"));
t!(Header[" /"]: "-a" => Hyphen, Ident("a"));
t!(Header[" /"]: "--1" => Hyphen, Hyphen, Int(1));
t!(Header[" /"]: "--_a" => Hyphen, Hyphen, Id("_a"));
t!(Header[" /"]: "a-b" => Id("a-b"));
t!(Header[" /"]: "--_a" => Hyphen, Hyphen, Ident("_a"));
t!(Header[" /"]: "a-b" => Ident("a-b"));
// Test some operations.
t!(Header[" /"]: "1+3" => Int(1), Plus, Int(3));
@ -666,33 +662,33 @@ mod tests {
#[test]
fn test_tokenize_idents() {
// Test valid identifiers.
t!(Header[" /"]: "x" => Id("x"));
t!(Header[" /"]: "value" => Id("value"));
t!(Header[" /"]: "__main__" => Id("__main__"));
t!(Header[" /"]: "_snake_case" => Id("_snake_case"));
t!(Header[" /"]: "x" => Ident("x"));
t!(Header[" /"]: "value" => Ident("value"));
t!(Header[" /"]: "__main__" => Ident("__main__"));
t!(Header[" /"]: "_snake_case" => Ident("_snake_case"));
// Test non-ascii.
t!(Header[" /"]: "α" => Id("α"));
t!(Header[" /"]: "ម្តាយ" => Id("ម្តាយ"));
t!(Header[" /"]: "α" => Ident("α"));
t!(Header[" /"]: "ម្តាយ" => Ident("ម្តាយ"));
// Test hyphen parsed as identifier.
t!(Header[" /"]: "kebab-case" => Id("kebab-case"));
t!(Header[" /"]: "one-10" => Id("one-10"));
t!(Header[" /"]: "kebab-case" => Ident("kebab-case"));
t!(Header[" /"]: "one-10" => Ident("one-10"));
}
#[test]
fn test_tokenize_keywords() {
// Test none.
t!(Header[" /"]: "none" => Token::None);
t!(Header[" /"]: "None" => Id("None"));
t!(Header[" /"]: "None" => Ident("None"));
// Test valid bools.
t!(Header[" /"]: "false" => Bool(false));
t!(Header[" /"]: "true" => Bool(true));
// Test invalid bools.
t!(Header[" /"]: "True" => Id("True"));
t!(Header[" /"]: "falser" => Id("falser"));
t!(Header[" /"]: "True" => Ident("True"));
t!(Header[" /"]: "falser" => Ident("falser"));
}
#[test]
@ -775,15 +771,15 @@ mod tests {
fn test_tokenize_invalid() {
// Test invalidly closed block comments.
t!(Both: "*/" => StarSlash);
t!(Both: "/**/*/" => BC(""), StarSlash);
t!(Both: "/**/*/" => BlockComment(""), StarSlash);
// Test invalid expressions.
t!(Header: r"\" => Invalid(r"\"));
t!(Header: "🌓" => Invalid("🌓"));
t!(Header: r"\:" => Invalid(r"\"), Colon);
t!(Header: "meal⌚" => Id("meal"), Invalid(""));
t!(Header[" /"]: r"\a" => Invalid(r"\"), Id("a"));
t!(Header[" /"]: ">main" => Invalid(">"), Id("main"));
t!(Header: "meal⌚" => Ident("meal"), Invalid(""));
t!(Header[" /"]: r"\a" => Invalid(r"\"), Ident("a"));
t!(Header[" /"]: ">main" => Invalid(">"), Ident("main"));
// Test invalid number suffixes.
t!(Header[" /"]: "1foo" => Invalid("1foo"));

View File

@ -39,7 +39,7 @@ impl Printer {
Write::write_fmt(self, fmt)
}
/// Write a comma-separated list of items.
/// Write a list of items joined by a joiner.
pub fn join<T, I, F>(&mut self, items: I, joiner: &str, mut write_item: F)
where
I: IntoIterator<Item = T>,

View File

@ -5,8 +5,27 @@ use crate::geom::Unit;
/// An expression.
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
/// A literal: `true`, `1cm`, `"hi"`.
Lit(Lit),
/// The none literal: `none`.
None,
/// A identifier literal: `left`.
Ident(Ident),
/// A boolean literal: `true`, `false`.
Bool(bool),
/// An integer literal: `120`.
Int(i64),
/// A floating-point literal: `1.2`, `10e-4`.
Float(f64),
/// A length literal: `12pt`, `3cm`.
Length(f64, Unit),
/// A percent literal: `50%`.
///
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
/// corresponding [value](crate::geom::Relative).
Percent(f64),
/// A color literal: `#ffccee`.
Color(RgbaColor),
/// A string literal: `"hello!"`.
Str(String),
/// An invocation of a function: `[foo ...]`, `foo(...)`.
Call(ExprCall),
/// A unary operation: `-x`.
@ -24,7 +43,15 @@ pub enum Expr {
impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Lit(lit) => lit.pretty(p),
Self::None => p.push_str("none"),
Self::Ident(v) => p.push_str(&v),
Self::Bool(v) => write!(p, "{}", v).unwrap(),
Self::Int(v) => write!(p, "{}", v).unwrap(),
Self::Float(v) => write!(p, "{}", v).unwrap(),
Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
Self::Color(v) => write!(p, "{}", v).unwrap(),
Self::Str(s) => write!(p, "{:?}", &s).unwrap(),
Self::Call(call) => call.pretty(p),
Self::Unary(unary) => unary.pretty(p),
Self::Binary(binary) => binary.pretty(p),
@ -264,48 +291,6 @@ impl Pretty for ExprDict {
/// A content expression: `{*Hello* there!}`.
pub type ExprContent = Tree;
/// A literal.
#[derive(Debug, Clone, PartialEq)]
pub enum Lit {
/// A identifier literal: `left`.
Ident(Ident),
/// The none literal: `none`.
None,
/// A boolean literal: `true`, `false`.
Bool(bool),
/// An integer literal: `120`.
Int(i64),
/// A floating-point literal: `1.2`, `10e-4`.
Float(f64),
/// A length literal: `12pt`, `3cm`.
Length(f64, Unit),
/// A percent literal: `50%`.
///
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
/// corresponding [value](crate::geom::Relative).
Percent(f64),
/// A color literal: `#ffccee`.
Color(RgbaColor),
/// A string literal: `"hello!"`.
Str(String),
}
impl Pretty for Lit {
fn pretty(&self, p: &mut Printer) {
match self {
Self::Ident(v) => p.push_str(&v),
Self::None => p.push_str("none"),
Self::Bool(v) => write!(p, "{}", v).unwrap(),
Self::Int(v) => write!(p, "{}", v).unwrap(),
Self::Float(v) => write!(p, "{}", v).unwrap(),
Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
Self::Percent(v) => write!(p, "{}%", v).unwrap(),
Self::Color(v) => write!(p, "{}", v).unwrap(),
Self::Str(s) => write!(p, "{:?}", &s).unwrap(),
}
}
}
#[cfg(test)]
mod tests {
use super::super::tests::test_pretty;

View File

@ -5,24 +5,20 @@ use super::*;
pub enum Node {
/// Plain text.
Text(String),
/// Whitespace containing less than two newlines.
Space,
/// A forced line break.
Linebreak,
/// A paragraph break.
Parbreak,
/// Strong text was enabled / disabled.
Strong,
/// Emphasized text was enabled / disabled.
Emph,
/// A section heading.
Heading(NodeHeading),
/// An optionally syntax-highlighted raw block.
Raw(NodeRaw),
/// An expression.
Expr(Expr),
}

View File

@ -3,13 +3,13 @@ use crate::geom::Unit;
/// A minimal semantic entity of source code.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Token<'s> {
/// A consecutive non-markup string.
Text(&'s str),
/// One or more whitespace characters.
///
/// The contained `usize` denotes the number of newlines that were contained
/// in the whitespace.
Space(usize),
/// A consecutive non-markup string.
Text(&'s str),
/// A line comment with inner string contents `//<str>\n`.
LineComment(&'s str),
@ -20,6 +20,19 @@ pub enum Token<'s> {
/// An end of a block comment that was not started.
StarSlash,
/// A left bracket: `[`.
LeftBracket,
/// A right bracket: `]`.
RightBracket,
/// A left brace: `{`.
LeftBrace,
/// A right brace: `}`.
RightBrace,
/// A left parenthesis: `(`.
LeftParen,
/// A right parenthesis: `)`.
RightParen,
/// A star: `*`.
Star,
/// An underscore: `_`.
@ -35,19 +48,6 @@ pub enum Token<'s> {
/// A unicode escape sequence: `\u{1F5FA}`.
UnicodeEscape(TokenUnicodeEscape<'s>),
/// A left bracket: `[`.
LeftBracket,
/// A right bracket: `]`.
RightBracket,
/// A left brace: `{`.
LeftBrace,
/// A right brace: `}`.
RightBrace,
/// A left parenthesis: `(`.
LeftParen,
/// A right parenthesis: `)`.
RightParen,
/// A colon: `:`.
Colon,
/// A comma: `,`.
@ -76,7 +76,7 @@ pub enum Token<'s> {
/// A percentage: `50%`.
///
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
/// [literal](super::Lit::Percent).
/// [literal](super::Expr::Percent).
Percent(f64),
/// A hex value: `#20d82a`.
Hex(&'s str),
@ -124,13 +124,20 @@ impl<'s> Token<'s> {
/// The natural-language name of this token for use in error messages.
pub fn name(self) -> &'static str {
match self {
Self::Space(_) => "space",
Self::Text(_) => "text",
Self::Space(_) => "space",
Self::LineComment(_) => "line comment",
Self::BlockComment(_) => "block comment",
Self::StarSlash => "end of block comment",
Self::LeftBracket => "opening bracket",
Self::RightBracket => "closing bracket",
Self::LeftBrace => "opening brace",
Self::RightBrace => "closing brace",
Self::LeftParen => "opening paren",
Self::RightParen => "closing paren",
Self::Star => "star",
Self::Underscore => "underscore",
Self::Backslash => "backslash",
@ -139,13 +146,6 @@ impl<'s> Token<'s> {
Self::Raw { .. } => "raw block",
Self::UnicodeEscape { .. } => "unicode escape sequence",
Self::LeftBracket => "opening bracket",
Self::RightBracket => "closing bracket",
Self::LeftBrace => "opening brace",
Self::RightBrace => "closing brace",
Self::LeftParen => "opening paren",
Self::RightParen => "closing paren",
Self::Colon => "colon",
Self::Comma => "comma",
Self::Pipe => "pipe",
@ -153,8 +153,8 @@ impl<'s> Token<'s> {
Self::Hyphen => "minus sign",
Self::Slash => "slash",
Self::Ident(_) => "identifier",
Self::None => "none",
Self::Ident(_) => "identifier",
Self::Bool(_) => "bool",
Self::Int(_) => "integer",
Self::Float(_) => "float",