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()); let node = ctx.make_text_node(text.clone());
ctx.push(node); ctx.push(node);
} }
Node::Space => { Node::Space => {
let em = ctx.state.font.font_size(); let em = ctx.state.font.font_size();
ctx.push(NodeSpacing { ctx.push(NodeSpacing {
@ -85,13 +84,10 @@ impl Eval for Spanned<&Node> {
} }
Node::Linebreak => ctx.apply_linebreak(), Node::Linebreak => ctx.apply_linebreak(),
Node::Parbreak => ctx.apply_parbreak(), Node::Parbreak => ctx.apply_parbreak(),
Node::Strong => ctx.state.font.strong ^= true, Node::Strong => ctx.state.font.strong ^= true,
Node::Emph => ctx.state.font.emph ^= true, Node::Emph => ctx.state.font.emph ^= true,
Node::Heading(heading) => heading.with_span(self.span).eval(ctx), Node::Heading(heading) => heading.with_span(self.span).eval(ctx),
Node::Raw(raw) => raw.with_span(self.span).eval(ctx), Node::Raw(raw) => raw.with_span(self.span).eval(ctx),
Node::Expr(expr) => { Node::Expr(expr) => {
let value = expr.with_span(self.span).eval(ctx); let value = expr.with_span(self.span).eval(ctx);
value.eval(ctx) value.eval(ctx)
@ -153,7 +149,21 @@ impl Eval for Spanned<&Expr> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match self.v { 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::Call(v) => v.with_span(self.span).eval(ctx),
Expr::Unary(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), 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> { impl Eval for Spanned<&ExprUnary> {
type Output = Value; 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. /// Compute the negation of a value.
fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value { fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
use Value::*; use Value::*;

View File

@ -487,6 +487,7 @@ macro_rules! impl_type {
mod tests { mod tests {
use super::*; use super::*;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::parse::parse;
use crate::pretty::pretty; use crate::pretty::pretty;
use crate::syntax::Node; use crate::syntax::Node;
@ -496,7 +497,7 @@ mod tests {
} }
#[test] #[test]
fn test_pretty_print_values() { fn test_pretty_print_simple_values() {
test_pretty(Value::None, "none"); test_pretty(Value::None, "none");
test_pretty(false, "false"); test_pretty(false, "false");
test_pretty(12.4, "12.4"); test_pretty(12.4, "12.4");
@ -521,8 +522,8 @@ mod tests {
// Dictionary. // Dictionary.
let mut dict = BTreeMap::new(); let mut dict = BTreeMap::new();
dict.insert("one".into(), Value::Int(1)); 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(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. /// A relative length.
/// ///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the /// _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)] #[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Relative(f64); 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> { fn argument(p: &mut Parser) -> Option<Argument> {
let first = p.span_if(expr)?; let first = p.span_if(expr)?;
if p.eat_if(Token::Colon) { 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 expr = p.span_if(expr)?;
let name = ident.with_span(first.span); let name = ident.with_span(first.span);
p.deco(Deco::Name.with_span(name.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> { fn take(expr: &mut Spanned<Expr>) -> Spanned<Expr> {
// Replace with anything, it's overwritten anyway. // 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>) { fn diag(p: &mut Parser, arg: Spanned<Argument>) {

View File

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

View File

@ -9,7 +9,8 @@ use crate::geom::Unit;
use crate::syntax::*; use crate::syntax::*;
use BinOp::*; 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::*; use UnOp::*;
macro_rules! t { macro_rules! t {
@ -99,39 +100,11 @@ fn Raw(lang: Option<&str>, lines: &[&str], inline: bool) -> Node {
} }
fn Id(ident: &str) -> Expr { fn Id(ident: &str) -> Expr {
Expr::Lit(Lit::Ident(Ident(ident.to_string()))) Expr::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))
} }
fn Str(string: &str) -> Expr { fn Str(string: &str) -> Expr {
Expr::Lit(Lit::Str(string.to_string())) Expr::Str(string.to_string())
}
fn Block(expr: Expr) -> Node {
Node::Expr(expr)
} }
fn Binary( fn Binary(
@ -614,7 +587,7 @@ fn test_parse_values() {
t!("{name}" Block(Id("name"))); t!("{name}" Block(Id("name")));
t!("{ke-bab}" Block(Id("ke-bab"))); t!("{ke-bab}" Block(Id("ke-bab")));
t!("{α}" Block(Id("α"))); t!("{α}" Block(Id("α")));
t!("{none}" Block(Expr::Lit(Lit::None))); t!("{none}" Block(Expr::None));
t!("{true}" Block(Bool(true))); t!("{true}" Block(Bool(true)));
t!("{false}" Block(Bool(false))); t!("{false}" Block(Bool(false)));
t!("{1.0e-4}" Block(Float(1e-4))); t!("{1.0e-4}" Block(Float(1e-4)));

View File

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

View File

@ -39,7 +39,7 @@ impl Printer {
Write::write_fmt(self, fmt) 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) pub fn join<T, I, F>(&mut self, items: I, joiner: &str, mut write_item: F)
where where
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,

View File

@ -5,8 +5,27 @@ use crate::geom::Unit;
/// An expression. /// An expression.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Expr { pub enum Expr {
/// A literal: `true`, `1cm`, `"hi"`. /// The none literal: `none`.
Lit(Lit), 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(...)`. /// An invocation of a function: `[foo ...]`, `foo(...)`.
Call(ExprCall), Call(ExprCall),
/// A unary operation: `-x`. /// A unary operation: `-x`.
@ -24,7 +43,15 @@ pub enum Expr {
impl Pretty for Expr { impl Pretty for Expr {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
match self { 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::Call(call) => call.pretty(p),
Self::Unary(unary) => unary.pretty(p), Self::Unary(unary) => unary.pretty(p),
Self::Binary(binary) => binary.pretty(p), Self::Binary(binary) => binary.pretty(p),
@ -264,48 +291,6 @@ impl Pretty for ExprDict {
/// A content expression: `{*Hello* there!}`. /// A content expression: `{*Hello* there!}`.
pub type ExprContent = Tree; 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)] #[cfg(test)]
mod tests { mod tests {
use super::super::tests::test_pretty; use super::super::tests::test_pretty;

View File

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

View File

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