mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refresh function call and dictionary syntax
- No colon between function name and arguments, just whitespace - "Named" arguments (previously "keyword" arguments) use colon instead of equals sign
This commit is contained in:
parent
750d220bb0
commit
ba3d43f7b2
10
src/color.rs
10
src/color.rs
@ -6,7 +6,7 @@ use std::str::FromStr;
|
||||
/// A color in a dynamic format.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Color {
|
||||
/// An 8-bit RGBA color.
|
||||
/// An 8-bit RGBA color: `#423abaff`.
|
||||
Rgba(RgbaColor),
|
||||
}
|
||||
|
||||
@ -18,13 +18,7 @@ impl Debug for Color {
|
||||
}
|
||||
}
|
||||
|
||||
/// An 8-bit RGBA color.
|
||||
///
|
||||
/// # Example
|
||||
/// ```typst
|
||||
/// [page: background=#423abaff]
|
||||
/// ^^^^^^^^
|
||||
/// ```
|
||||
/// An 8-bit RGBA color: `#423abaff`.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct RgbaColor {
|
||||
/// Red channel.
|
||||
|
@ -86,7 +86,7 @@ impl Args {
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve and remove all matching keyword arguments.
|
||||
/// Retrieve and remove all matching named arguments.
|
||||
pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_
|
||||
where
|
||||
T: TryFromValue,
|
||||
|
@ -160,11 +160,9 @@ impl<V: Debug> Debug for Dict<V> {
|
||||
if self.0 {
|
||||
f.write_str("\"")?;
|
||||
}
|
||||
if f.alternate() {
|
||||
f.write_str(" = ")?;
|
||||
} else {
|
||||
f.write_str("=")?;
|
||||
}
|
||||
|
||||
f.write_str(": ")?;
|
||||
|
||||
self.2.fmt(f)
|
||||
}
|
||||
}
|
||||
@ -511,13 +509,13 @@ mod tests {
|
||||
dict.insert("sp ace", "quotes");
|
||||
assert_eq!(
|
||||
format!("{:?}", dict),
|
||||
r#"(10="hello", "sp ace"="quotes", twenty="there")"#,
|
||||
r#"(10: "hello", "sp ace": "quotes", twenty: "there")"#,
|
||||
);
|
||||
assert_eq!(format!("{:#?}", dict).lines().collect::<Vec<_>>(), [
|
||||
"(",
|
||||
r#" 10 = "hello","#,
|
||||
r#" "sp ace" = "quotes","#,
|
||||
r#" twenty = "there","#,
|
||||
r#" 10: "hello","#,
|
||||
r#" "sp ace": "quotes","#,
|
||||
r#" twenty: "there","#,
|
||||
")",
|
||||
]);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ pub enum Value {
|
||||
Color(Color),
|
||||
/// A string: `"string"`.
|
||||
Str(String),
|
||||
/// A dictionary value: `(false, 12cm, greeting="hi")`.
|
||||
/// A dictionary value: `(false, 12cm, greeting: "hi")`.
|
||||
Dict(ValueDict),
|
||||
/// A content value: `{*Hi* there}`.
|
||||
Content(SynTree),
|
||||
@ -125,7 +125,7 @@ impl Debug for Value {
|
||||
///
|
||||
/// # Example
|
||||
/// ```typst
|
||||
/// (false, 12cm, greeting="hi")
|
||||
/// (false, 12cm, greeting: "hi")
|
||||
/// ```
|
||||
pub type ValueDict = Dict<SpannedEntry<Value>>;
|
||||
|
||||
|
@ -12,7 +12,7 @@ use crate::prelude::*;
|
||||
/// - first (optional, `Alignment`): An alignment for any of the two axes.
|
||||
/// - second (optional, `Alignment`): An alignment for the other axis.
|
||||
///
|
||||
/// # Keyword arguments
|
||||
/// # Named arguments
|
||||
/// - `horizontal` (`Alignment`): An alignment for the horizontal axis.
|
||||
/// - `vertical` (`Alignment`): An alignment for the vertical axis.
|
||||
///
|
||||
@ -188,7 +188,7 @@ impl Display for SpecAlign {
|
||||
|
||||
/// `box`: Layout content into a box.
|
||||
///
|
||||
/// # Keyword arguments
|
||||
/// # Named arguments
|
||||
/// - `width` (`linear` relative to parent width): The width of the box.
|
||||
/// - `height` (`linear` relative to parent height): The height of the box.
|
||||
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||
@ -268,7 +268,7 @@ fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
|
||||
/// # Positional arguments
|
||||
/// - Paper name (optional, `Paper`).
|
||||
///
|
||||
/// # Keyword arguments
|
||||
/// # Named arguments
|
||||
/// - `width` (`length`): The width of pages.
|
||||
/// - `height` (`length`): The height of pages.
|
||||
/// - `margins` (`linear` relative to sides): The margins for all sides.
|
||||
|
@ -2,9 +2,9 @@ use std::rc::Rc;
|
||||
|
||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::eval::StringLike;
|
||||
use crate::geom::Linear;
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// `font`: Configure the font.
|
||||
@ -13,7 +13,7 @@ use crate::prelude::*;
|
||||
/// - Font size (optional, `linear` relative to current font size).
|
||||
/// - Font families ... (optional, variadic, `Family`)
|
||||
///
|
||||
/// # Keyword arguments
|
||||
/// # Named arguments
|
||||
/// - `style` (`Style`): The font style.
|
||||
/// - `weight` (`Weight`): The font weight.
|
||||
/// - `stretch` (`Stretch`): The font stretch.
|
||||
@ -26,17 +26,17 @@ use crate::prelude::*;
|
||||
/// # Examples
|
||||
/// Set font size and font families.
|
||||
/// ```typst
|
||||
/// [font: 12pt, "Arial", "Noto Sans", sans-serif]
|
||||
/// [font 12pt, "Arial", "Noto Sans", sans-serif]
|
||||
/// ```
|
||||
///
|
||||
/// Redefine the default sans-serif family to a single font family.
|
||||
/// ```typst
|
||||
/// [font: sans-serif="Source Sans Pro"]
|
||||
/// [font sans-serif: "Source Sans Pro"]
|
||||
/// ```
|
||||
///
|
||||
/// Redefine the default emoji family with a fallback.
|
||||
/// ```typst
|
||||
/// [font: emoji=("Segoe UI Emoji", "Noto Emoji")]
|
||||
/// [font emoji: ("Segoe UI Emoji", "Noto Emoji")]
|
||||
/// ```
|
||||
///
|
||||
/// # Enumerations
|
||||
|
@ -154,7 +154,7 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
|
||||
let mut outer = vec![];
|
||||
let mut inner = p.span(|p| bracket_subheader(p));
|
||||
|
||||
while p.eat_if(Token::Chain) {
|
||||
while p.eat_if(Token::Pipe) {
|
||||
outer.push(inner);
|
||||
inner = p.span(|p| bracket_subheader(p));
|
||||
}
|
||||
@ -186,28 +186,17 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall {
|
||||
|
||||
p.skip_white();
|
||||
let name = p.span(|p| ident(p)).transpose().unwrap_or_else(|| {
|
||||
let what = "function name";
|
||||
if p.eof() {
|
||||
p.diag_expected_at("function name", start);
|
||||
p.diag_expected_at(what, start);
|
||||
} else {
|
||||
p.diag_expected("function name");
|
||||
p.diag_expected(what);
|
||||
}
|
||||
Ident(String::new()).span_with(start)
|
||||
});
|
||||
|
||||
p.skip_white();
|
||||
let args = if p.eat_if(Token::Colon) {
|
||||
p.skip_white();
|
||||
p.span(|p| dict_contents(p).0)
|
||||
} else {
|
||||
// Ignore the rest if there's no colon.
|
||||
p.span(|p| {
|
||||
if !p.eof() {
|
||||
p.diag_expected_at("colon", p.pos());
|
||||
}
|
||||
p.eat_while(|_| true);
|
||||
LitDict::new()
|
||||
})
|
||||
};
|
||||
let args = p.span(|p| dict_contents(p).0);
|
||||
|
||||
p.end_group();
|
||||
ExprCall { name, args }
|
||||
@ -285,8 +274,8 @@ fn dict_entry(p: &mut Parser) -> Option<LitDictEntry> {
|
||||
p.skip_white();
|
||||
match p.peek() {
|
||||
// Key-value pair.
|
||||
Some(Token::Equals) => {
|
||||
p.eat_assert(Token::Equals);
|
||||
Some(Token::Colon) => {
|
||||
p.eat_assert(Token::Colon);
|
||||
p.skip_white();
|
||||
if let Some(expr) = expr(p) {
|
||||
Some(LitDictEntry {
|
||||
@ -463,7 +452,7 @@ fn content(p: &mut Parser) -> SynTree {
|
||||
tree
|
||||
}
|
||||
|
||||
/// Parse a parenthesized expression: `(a + b)`, `(1, key="value").
|
||||
/// Parse a parenthesized expression: `(a + b)`, `(1, name: "value").
|
||||
fn parenthesized(p: &mut Parser) -> Expr {
|
||||
p.start_group(Group::Paren);
|
||||
let (dict, coercible) = dict_contents(p);
|
||||
|
@ -40,24 +40,24 @@ impl<'s> Parser<'s> {
|
||||
|
||||
/// Eat the next token and add a diagnostic that it is not the expected
|
||||
/// `thing`.
|
||||
pub fn diag_expected(&mut self, thing: &str) {
|
||||
pub fn diag_expected(&mut self, what: &str) {
|
||||
let before = self.pos();
|
||||
if let Some(found) = self.eat() {
|
||||
let after = self.pos();
|
||||
self.diag(error!(
|
||||
before .. after,
|
||||
"expected {}, found {}",
|
||||
thing,
|
||||
what,
|
||||
found.name(),
|
||||
));
|
||||
} else {
|
||||
self.diag_expected_at(thing, self.pos());
|
||||
self.diag_expected_at(what, self.pos());
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a diagnostic that the `thing` was expected at the given position.
|
||||
pub fn diag_expected_at(&mut self, thing: &str, pos: Pos) {
|
||||
self.diag(error!(pos, "expected {}", thing));
|
||||
pub fn diag_expected_at(&mut self, what: &str, pos: Pos) {
|
||||
self.diag(error!(pos, "expected {}", what));
|
||||
}
|
||||
|
||||
/// Eat the next token and add a diagnostic that it is unexpected.
|
||||
@ -220,7 +220,7 @@ impl<'s> Parser<'s> {
|
||||
Token::RightParen => Group::Paren,
|
||||
Token::RightBracket => Group::Bracket,
|
||||
Token::RightBrace => Group::Brace,
|
||||
Token::Chain => Group::Subheader,
|
||||
Token::Pipe => Group::Subheader,
|
||||
_ => return Some(token),
|
||||
};
|
||||
|
||||
|
@ -164,7 +164,7 @@ macro_rules! test {
|
||||
// Test expressions.
|
||||
macro_rules! v {
|
||||
($src:expr => $($tts:tt)*) => {
|
||||
t!(concat!("[val: ", $src, "]") => F!("val"; $($tts)*));
|
||||
t!(concat!("[val ", $src, "]") => F!("val"; $($tts)*));
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ fn test_parse_groups() {
|
||||
e!("[)" => s(1, 2, "expected function name, found closing paren"),
|
||||
s(2, 2, "expected closing bracket"));
|
||||
|
||||
e!("[v:{]}" => s(4, 4, "expected closing brace"),
|
||||
e!("[v {]}" => s(4, 4, "expected closing brace"),
|
||||
s(5, 6, "unexpected closing brace"));
|
||||
}
|
||||
|
||||
@ -250,9 +250,9 @@ fn test_parse_simple_nodes() {
|
||||
t!("\n\n\nhello" => P, T("hello"));
|
||||
t!(r"a\ b" => T("a"), L, S, T("b"));
|
||||
|
||||
e!("\\u{d421c809}" => s(0, 12, "invalid unicode escape sequence"));
|
||||
e!("\\u{abc" => s(6, 6, "expected closing brace"));
|
||||
t!("💜\n\n 🌍" => T("💜"), P, T("🌍"));
|
||||
e!("\\u{d421c809}" => s(0, 12, "invalid unicode escape sequence"));
|
||||
e!("\\u{abc" => s(6, 6, "expected closing brace"));
|
||||
t!("💜\n\n 🌍" => T("💜"), P, T("🌍"));
|
||||
|
||||
ts!("hi" => s(0, 2, T("hi")));
|
||||
ts!("*Hi*" => s(0, 1, B), s(1, 3, T("Hi")), s(3, 4, B));
|
||||
@ -296,10 +296,10 @@ fn test_parse_comments() {
|
||||
e!("🌎\n*/n" => s(5, 7, "unexpected end of block comment"));
|
||||
|
||||
// In header.
|
||||
t!("[val:/*12pt*/]" => F!("val"));
|
||||
t!("[val \n /* \n */:]" => F!("val"));
|
||||
e!("[val \n /* \n */:]" => );
|
||||
e!("[val : 12, /* \n */ 14]" => );
|
||||
t!("[val /*12pt*/]" => F!("val"));
|
||||
t!("[val \n /* \n */]" => F!("val"));
|
||||
e!("[val \n /* \n */]" => );
|
||||
e!("[val 12, /* \n */ 14]" => );
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -357,40 +357,23 @@ fn test_parse_function_names() {
|
||||
#[test]
|
||||
fn test_parse_chaining() {
|
||||
// Things the parser has to make sense of
|
||||
t!("[hi: (5.0, 2.1 >> you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")]));
|
||||
t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
|
||||
t!("[hi (5.0, 2.1 | you]" => F!("hi"; Dict![Float(5.0), Float(2.1)], Tree![F!("you")]));
|
||||
t!("[box | pad: 1pt][Hi]" => F!("box"; Tree![
|
||||
F!("pad"; Length(1.0, Pt), Tree!(T("Hi")))
|
||||
]));
|
||||
t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Int(400), Tree![
|
||||
t!("[bold 400, | emph | sub: 1cm]" => F!("bold"; Int(400), Tree![
|
||||
F!("emph"; Tree!(F!("sub"; Length(1.0, Cm))))
|
||||
]));
|
||||
|
||||
// Errors for unclosed / empty predecessor groups
|
||||
e!("[hi: (5.0, 2.1 >> you]" => s(15, 15, "expected closing paren"));
|
||||
e!("[>> abc]" => s(1, 1, "expected function name"));
|
||||
e!("[box >>][Hi]" => s(7, 7, "expected function name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_colon_starting_func_args() {
|
||||
// Just colon without args.
|
||||
e!("[val:]" => );
|
||||
|
||||
// Wrong token.
|
||||
t!("[val=]" => F!("val"));
|
||||
e!("[val=]" => s(4, 4, "expected colon"));
|
||||
e!("[val/🌎:$]" => s(4, 4, "expected colon"));
|
||||
|
||||
// String in invalid header without colon still parsed as string
|
||||
// _Note_: No "expected quote" error because not even the string was
|
||||
// expected.
|
||||
e!("[val/\"]" => s(4, 4, "expected colon"),
|
||||
s(7, 7, "expected closing bracket"));
|
||||
e!("[hi (5.0, 2.1 | you]" => s(14, 14, "expected closing paren"));
|
||||
e!("[| abc]" => s(1, 1, "expected function name"));
|
||||
e!("[box |][Hi]" => s(6, 6, "expected function name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_function_bodies() {
|
||||
t!("[val: 1][*Hi*]" => F!("val"; Int(1), Tree![B, T("Hi"), B]));
|
||||
t!("[val 1][*Hi*]" => F!("val"; Int(1), Tree![B, T("Hi"), B]));
|
||||
e!(" [val][ */]" => s(8, 10, "unexpected end of block comment"));
|
||||
|
||||
// Raw in body.
|
||||
@ -431,24 +414,24 @@ fn test_parse_values() {
|
||||
|
||||
// Content.
|
||||
v!("{_hi_}" => Tree![E, T("hi"), E]);
|
||||
e!("[val: {_hi_}]" => );
|
||||
e!("[val {_hi_}]" => );
|
||||
v!("[hi]" => Tree![F!("hi")]);
|
||||
e!("[val: [hi]]" => );
|
||||
e!("[val [hi]]" => );
|
||||
|
||||
// Healed colors.
|
||||
v!("#12345" => Color(RgbaColor::new(0, 0, 0, 0xff)));
|
||||
e!("[val: #12345]" => s(6, 12, "invalid color"));
|
||||
e!("[val: #a5]" => s(6, 9, "invalid color"));
|
||||
e!("[val: #14b2ah]" => s(6, 13, "invalid color"));
|
||||
e!("[val: #f075ff011]" => s(6, 16, "invalid color"));
|
||||
e!("[val #12345]" => s(5, 11, "invalid color"));
|
||||
e!("[val #a5]" => s(5, 8, "invalid color"));
|
||||
e!("[val #14b2ah]" => s(5, 12, "invalid color"));
|
||||
e!("[val #f075ff011]" => s(5, 15, "invalid color"));
|
||||
|
||||
// Unclosed string.
|
||||
v!("\"hello" => Str("hello]"));
|
||||
e!("[val: \"hello]" => s(13, 13, "expected quote"),
|
||||
s(13, 13, "expected closing bracket"));
|
||||
e!("[val \"hello]" => s(12, 12, "expected quote"),
|
||||
s(12, 12, "expected closing bracket"));
|
||||
|
||||
// Spanned.
|
||||
ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"), 6 .. 9; s(6, 9, Float(1.4)))));
|
||||
ts!("[val 1.4]" => s(0, 9, F!(s(1, 4, "val"), 5 .. 8; s(5, 8, Float(1.4)))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -485,24 +468,24 @@ fn test_parse_expressions() {
|
||||
v!("3/4*5" => Binary(Mul, Binary(Div, Int(3), Int(4)), Int(5)));
|
||||
|
||||
// Spanned.
|
||||
ts!("[val: 1 + 3]" => s(0, 12, F!(
|
||||
s(1, 4, "val"), 6 .. 11; s(6, 11, Binary(
|
||||
s(8, 9, Add),
|
||||
s(6, 7, Int(1)),
|
||||
s(10, 11, Int(3))
|
||||
ts!("[val 1 + 3]" => s(0, 11, F!(
|
||||
s(1, 4, "val"), 5 .. 10; s(5, 10, Binary(
|
||||
s(7, 8, Add),
|
||||
s(5, 6, Int(1)),
|
||||
s(9, 10, Int(3))
|
||||
))
|
||||
)));
|
||||
|
||||
// Span of parenthesized expression contains parens.
|
||||
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 6 .. 9; s(6, 9, Int(1)))));
|
||||
ts!("[val (1)]" => s(0, 9, F!(s(1, 4, "val"), 5 .. 8; s(5, 8, Int(1)))));
|
||||
|
||||
// Invalid expressions.
|
||||
v!("4pt--" => Length(4.0, Pt));
|
||||
e!("[val: 4pt--]" => s(10, 11, "missing factor"),
|
||||
s(6, 10, "missing right summand"));
|
||||
v!("4pt--" => Length(4.0, Pt));
|
||||
e!("[val 4pt--]" => s(9, 10, "missing factor"),
|
||||
s(5, 9, "missing right summand"));
|
||||
|
||||
v!("3mm+4pt*" => Binary(Add, Length(3.0, Mm), Length(4.0, Pt)));
|
||||
e!("[val: 3mm+4pt*]" => s(10, 14, "missing right factor"));
|
||||
v!("3mm+4pt*" => Binary(Add, Length(3.0, Mm), Length(4.0, Pt)));
|
||||
e!("[val 3mm+4pt*]" => s(9, 13, "missing right factor"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -511,25 +494,25 @@ fn test_parse_dicts() {
|
||||
v!("()" => Dict![]);
|
||||
v!("(false)" => Bool(false));
|
||||
v!("(true,)" => Dict![Bool(true)]);
|
||||
v!("(key=val)" => Dict!["key" => Id("val")]);
|
||||
v!("(key: val)" => Dict!["key" => Id("val")]);
|
||||
v!("(1, 2)" => Dict![Int(1), Int(2)]);
|
||||
v!("(1, key=\"value\")" => Dict![Int(1), "key" => Str("value")]);
|
||||
v!("(1, key: \"value\")" => Dict![Int(1), "key" => Str("value")]);
|
||||
|
||||
// Decorations.
|
||||
d!("[val: key=hi]" => s(6, 9, DictKey));
|
||||
d!("[val: (key=hi)]" => s(7, 10, DictKey));
|
||||
d!("[val: f(key=hi)]" => s(8, 11, DictKey));
|
||||
d!("[val key: hi]" => s(5, 8, DictKey));
|
||||
d!("[val (key: hi)]" => s(6, 9, DictKey));
|
||||
d!("[val f(key: hi)]" => s(7, 10, DictKey));
|
||||
|
||||
// Spanned with spacing around keyword arguments.
|
||||
ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0, 30, F!(
|
||||
// Spanned with spacing around named arguments.
|
||||
ts!("[val \n hi \n : /* //\n */ \"s\n\"]" => s(0, 30, F!(
|
||||
s(1, 4, "val"),
|
||||
8 .. 29; s(8, 10, "hi") => s(25, 29, Str("s\n"))
|
||||
)));
|
||||
e!("[val: \n hi \n = /* //\n */ \"s\n\"]" => );
|
||||
e!("[val \n hi \n : /* //\n */ \"s\n\"]" => );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_dicts_compute_func_calls() {
|
||||
fn test_parse_dicts_paren_func_calls() {
|
||||
v!("empty()" => Call!("empty"));
|
||||
v!("add ( 1 , 2 )" => Call!("add"; Int(1), Int(2)));
|
||||
v!("items(\"fire\", #f93a6d)" => Call!("items";
|
||||
@ -537,25 +520,25 @@ fn test_parse_dicts_compute_func_calls() {
|
||||
));
|
||||
|
||||
// More complex.
|
||||
v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!(
|
||||
v!(r#"css(1pt, color: rgb(90, 102, 254), stroke: "solid")"# => Call!(
|
||||
"css";
|
||||
Length(1.0, Pt),
|
||||
Call!("rgb"; Int(90), Int(102), Int(254)),
|
||||
Str("solid"),
|
||||
"color" => Call!("rgb"; Int(90), Int(102), Int(254)),
|
||||
"stroke" => Str("solid"),
|
||||
));
|
||||
|
||||
// Unclosed.
|
||||
v!("lang(中文]" => Call!("lang"; Id("中文")));
|
||||
e!("[val: lang(中文]" => s(17, 17, "expected closing paren"));
|
||||
v!("lang(中文]" => Call!("lang"; Id("中文")));
|
||||
e!("[val lang(中文]" => s(16, 16, "expected closing paren"));
|
||||
|
||||
// Invalid name.
|
||||
v!("👠(\"abc\", 13e-5)" => Dict!(Str("abc"), Float(13.0e-5)));
|
||||
e!("[val: 👠(\"abc\", 13e-5)]" => s(6, 10, "invalid token"));
|
||||
v!("👠(\"abc\", 13e-5)" => Dict!(Str("abc"), Float(13.0e-5)));
|
||||
e!("[val 👠(\"abc\", 13e-5)]" => s(5, 9, "invalid token"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_dicts_nested() {
|
||||
v!("(1, ( ab=(), d = (3, 14pt) )), false" =>
|
||||
v!("(1, ( ab:(), d : (3, 14pt) )), false" =>
|
||||
Dict![
|
||||
Int(1),
|
||||
Dict!(
|
||||
@ -570,28 +553,28 @@ fn test_parse_dicts_nested() {
|
||||
#[test]
|
||||
fn test_parse_dicts_errors() {
|
||||
// Expected value.
|
||||
e!("[val: (=)]" => s(7, 8, "unexpected equals sign"));
|
||||
e!("[val: (,)]" => s(7, 8, "unexpected comma"));
|
||||
v!("(\x07 abc,)" => Dict![Id("abc")]);
|
||||
e!("[val: (\x07 abc,)]" => s(7, 8, "invalid token"));
|
||||
e!("[val: (key=,)]" => s(11, 12, "expected value, found comma"));
|
||||
e!("[val: hi,)]" => s(9, 10, "unexpected closing paren"));
|
||||
e!("[val (:)]" => s(6, 7, "unexpected colon"));
|
||||
e!("[val (,)]" => s(6, 7, "unexpected comma"));
|
||||
v!("(\x07 abc,)" => Dict![Id("abc")]);
|
||||
e!("[val (\x07 abc,)]" => s(6, 7, "invalid token"));
|
||||
e!("[val (key:,)]" => s(10, 11, "expected value, found comma"));
|
||||
e!("[val hi,)]" => s(8, 9, "unexpected closing paren"));
|
||||
|
||||
// Expected comma.
|
||||
v!("(true false)" => Dict![Bool(true), Bool(false)]);
|
||||
e!("[val: (true false)]" => s(11, 11, "expected comma"));
|
||||
v!("(true false)" => Dict![Bool(true), Bool(false)]);
|
||||
e!("[val (true false)]" => s(10, 10, "expected comma"));
|
||||
|
||||
// Expected closing paren.
|
||||
e!("[val: (#000]" => s(11, 11, "expected closing paren"));
|
||||
e!("[val: (key]" => s(10, 10, "expected closing paren"));
|
||||
e!("[val: (key=]" => s(11, 11, "expected value"),
|
||||
s(11, 11, "expected closing paren"));
|
||||
e!("[val (#000]" => s(10, 10, "expected closing paren"));
|
||||
e!("[val (key]" => s(9, 9, "expected closing paren"));
|
||||
e!("[val (key:]" => s(10, 10, "expected value"),
|
||||
s(10, 10, "expected closing paren"));
|
||||
|
||||
// Bad key.
|
||||
v!("true=you" => Bool(true), Id("you"));
|
||||
e!("[val: true=you]" => s(10, 11, "unexpected equals sign"));
|
||||
v!("true:you" => Bool(true), Id("you"));
|
||||
e!("[val true:you]" => s(9, 10, "unexpected colon"));
|
||||
|
||||
// Unexpected equals sign.
|
||||
v!("z=y=4" => "z" => Id("y"), Int(4));
|
||||
e!("[val: z=y=4]" => s(9, 10, "unexpected equals sign"));
|
||||
// Unexpected colon.
|
||||
v!("z:y:4" => "z" => Id("y"), Int(4));
|
||||
e!("[val z:y:4]" => s(8, 9, "unexpected colon"));
|
||||
}
|
||||
|
@ -109,8 +109,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
')' => Token::RightParen,
|
||||
':' => Token::Colon,
|
||||
',' => Token::Comma,
|
||||
'=' => Token::Equals,
|
||||
'>' if self.s.eat_if('>') => Token::Chain,
|
||||
'|' => Token::Pipe,
|
||||
'+' => Token::Plus,
|
||||
'-' => Token::Hyphen,
|
||||
'*' => Token::Star,
|
||||
@ -567,7 +566,7 @@ mod tests {
|
||||
t!(Body[" "]: r"\" => Backslash);
|
||||
|
||||
// Test header symbols.
|
||||
t!(Body[" /"]: ":,=>>/+-" => T(":,=>>/+-"));
|
||||
t!(Body[" /"]: ":,=|/+-" => T(":,=|/+-"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -652,8 +651,7 @@ mod tests {
|
||||
// Test structural tokens.
|
||||
t!(Header: ":" => Colon);
|
||||
t!(Header: "," => Comma);
|
||||
t!(Header: "=" => Equals);
|
||||
t!(Header: ">>" => Chain);
|
||||
t!(Header: "|" => Pipe);
|
||||
t!(Header: "+" => Plus);
|
||||
t!(Header: "-" => Hyphen);
|
||||
t!(Header[" a1"]: "*" => Star);
|
||||
|
@ -7,7 +7,7 @@ use super::*;
|
||||
pub enum Expr {
|
||||
/// A literal: `true`, `1cm`, `"hi"`, `{_Hey!_}`.
|
||||
Lit(Lit),
|
||||
/// An invocation of a function: `[foo: ...]`, `foo(...)`.
|
||||
/// An invocation of a function: `[foo ...]`, `foo(...)`.
|
||||
Call(ExprCall),
|
||||
/// A unary operation: `-x`.
|
||||
Unary(ExprUnary),
|
||||
@ -15,7 +15,7 @@ pub enum Expr {
|
||||
Binary(ExprBinary),
|
||||
}
|
||||
|
||||
/// An invocation of a function: `[foo: ...]`, `foo(...)`.
|
||||
/// An invocation of a function: `[foo ...]`, `foo(...)`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExprCall {
|
||||
/// The name of the function.
|
||||
|
@ -27,17 +27,17 @@ pub enum Lit {
|
||||
Color(RgbaColor),
|
||||
/// A string literal: `"hello!"`.
|
||||
Str(String),
|
||||
/// A dictionary literal: `(false, 12cm, greeting = "hi")`.
|
||||
/// A dictionary literal: `(false, 12cm, greeting: "hi")`.
|
||||
Dict(LitDict),
|
||||
/// A content literal: `{*Hello* there!}`.
|
||||
Content(SynTree),
|
||||
}
|
||||
|
||||
/// A dictionary literal: `(false, 12cm, greeting = "hi")`.
|
||||
/// A dictionary literal: `(false, 12cm, greeting: "hi")`.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct LitDict(pub Vec<LitDictEntry>);
|
||||
|
||||
/// An entry in a dictionary literal: `false` or `greeting = "hi"`.
|
||||
/// An entry in a dictionary literal: `false` or `greeting: "hi"`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LitDictEntry {
|
||||
/// The key of the entry if there was one: `greeting`.
|
||||
|
@ -54,10 +54,8 @@ pub enum Token<'s> {
|
||||
Colon,
|
||||
/// A comma: `,`.
|
||||
Comma,
|
||||
/// An equals sign: `=`.
|
||||
Equals,
|
||||
/// A double forward chevron: `>>`.
|
||||
Chain,
|
||||
/// A pipe: `|`.
|
||||
Pipe,
|
||||
/// A plus: `+`.
|
||||
Plus,
|
||||
/// A hyphen: `-`.
|
||||
@ -150,8 +148,7 @@ impl<'s> Token<'s> {
|
||||
|
||||
Self::Colon => "colon",
|
||||
Self::Comma => "comma",
|
||||
Self::Equals => "equals sign",
|
||||
Self::Chain => "function chaining operator",
|
||||
Self::Pipe => "pipe",
|
||||
Self::Plus => "plus sign",
|
||||
Self::Hyphen => "minus sign",
|
||||
Self::Slash => "slash",
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Test integration of syntax, page setup, box layout and alignment.
|
||||
|
||||
[page: width=450pt, height=300pt, margins=1cm]
|
||||
[page width: 450pt, height: 300pt, margins: 1cm]
|
||||
|
||||
[box][
|
||||
*Technische Universität Berlin* \
|
||||
@ -9,17 +9,17 @@
|
||||
Dr. Max Mustermann \
|
||||
Ola Nordmann, John Doe
|
||||
]
|
||||
[align: right >> box][*WiSe 2019/2020* \ Woche 3]
|
||||
[align right | box][*WiSe 2019/2020* \ Woche 3]
|
||||
|
||||
[v: 6mm]
|
||||
[v 6mm]
|
||||
|
||||
[align: center][
|
||||
#### 3. Übungsblatt Computerorientierte Mathematik II [v: 2mm]
|
||||
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 2mm]
|
||||
[align center][
|
||||
#### 3. Übungsblatt Computerorientierte Mathematik II [v 2mm]
|
||||
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v 2mm]
|
||||
*Alle Antworten sind zu beweisen.*
|
||||
]
|
||||
|
||||
*1. Aufgabe* [align: right][(1 + 1 + 2 Punkte)]
|
||||
*1. Aufgabe* [align right][(1 + 1 + 2 Punkte)]
|
||||
|
||||
Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten ≤ 2 Kinder hat.
|
||||
Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
|
||||
|
@ -1,17 +1,17 @@
|
||||
// Test error cases of the `font` function.
|
||||
|
||||
// Not one of the valid things for positional arguments.
|
||||
[font: false]
|
||||
[font false]
|
||||
|
||||
// Wrong types.
|
||||
[font: style=bold, weight="thin", emoji=0]
|
||||
[font style: bold, weight: "thin", serif: 0]
|
||||
|
||||
// Non-existing argument.
|
||||
[font: something="invalid"]
|
||||
[font something: "invalid"]
|
||||
|
||||
// compare-ref: false
|
||||
// error: 4:8-4:13 unexpected argument
|
||||
// error: 4:7-4:12 unexpected argument
|
||||
// error: 7:14-7:18 invalid font style
|
||||
// error: 7:27-7:33 expected font weight, found string
|
||||
// error: 7:41-7:42 expected family or list of families, found integer
|
||||
// error: 10:8-10:27 unexpected argument
|
||||
// error: 7:28-7:34 expected font weight, found string
|
||||
// error: 7:43-7:44 expected family or list of families, found integer
|
||||
// error: 10:7-10:27 unexpected argument
|
||||
|
@ -4,15 +4,15 @@
|
||||
Emoji: 🏀
|
||||
|
||||
// CMU Serif + Noto Emoji.
|
||||
[font: "CMU Serif", "Noto Emoji"][Emoji: 🏀]
|
||||
[font "CMU Serif", "Noto Emoji"][Emoji: 🏀]
|
||||
|
||||
// Class definitions.
|
||||
[font: math=("CMU Serif", "Latin Modern Math", "Noto Emoji")]
|
||||
[font: math][Math: ∫ α + β ➗ 3]
|
||||
[font math: ("CMU Serif", "Latin Modern Math", "Noto Emoji")]
|
||||
[font math][Math: ∫ α + β ➗ 3]
|
||||
|
||||
// Class redefinition.
|
||||
[font: sans-serif="Noto Emoji"]
|
||||
[font: sans-serif=("Archivo", sans-serif)]
|
||||
[font sans-serif: "Noto Emoji"]
|
||||
[font sans-serif: ("Archivo", sans-serif)]
|
||||
New sans-serif. 🚀
|
||||
|
||||
// TODO: Add tests for other scripts.
|
||||
|
@ -1,20 +1,20 @@
|
||||
// Test configuring font properties.
|
||||
|
||||
[font: "PT Sans", 10pt]
|
||||
[font "PT Sans", 10pt]
|
||||
|
||||
// Set same font size in three different ways.
|
||||
[font: 20pt][A]
|
||||
[font: 200%][A]
|
||||
[font: 15pt + 50%][A]
|
||||
[font 20pt][A]
|
||||
[font 200%][A]
|
||||
[font 15pt + 50%][A]
|
||||
|
||||
// Do nothing.
|
||||
[font][Normal]
|
||||
|
||||
// Set style (is available).
|
||||
[font: style=italic][Italic]
|
||||
[font style: italic][Italic]
|
||||
|
||||
// Set weight (is available).
|
||||
[font: weight=bold][Bold]
|
||||
[font weight: bold][Bold]
|
||||
|
||||
// Set stretch (not available, matching closest).
|
||||
[font: stretch=ultra-condensed][Condensed]
|
||||
[font stretch: ultra-condensed][Condensed]
|
||||
|
@ -1,25 +1,25 @@
|
||||
// Test the `h` and `v` functions.
|
||||
|
||||
// Ends paragraphs.
|
||||
Tightly [v: -5pt] packed
|
||||
Tightly [v -5pt] packed
|
||||
|
||||
// Eating up soft spacing.
|
||||
Inv [h: 0pt] isible
|
||||
Inv [h 0pt] isible
|
||||
|
||||
// Multiple spacings in a row.
|
||||
Add [h: 10pt] [h: 10pt] up
|
||||
Add [h 10pt] [h 10pt] up
|
||||
|
||||
// Relative to font size.
|
||||
Relative [h: 100%] spacing
|
||||
Relative [h 100%] spacing
|
||||
|
||||
// Missing spacing.
|
||||
Totally [h] ignored
|
||||
|
||||
// Swapped axes.
|
||||
[page: main-dir=rtl, cross-dir=ttb][
|
||||
1 [h: 1cm] 2
|
||||
[page main-dir: rtl, cross-dir: ttb][
|
||||
1 [h 1cm] 2
|
||||
|
||||
3 [v: 1cm] 4 [v: -1cm] 5
|
||||
3 [v 1cm] 4 [v -1cm] 5
|
||||
]
|
||||
|
||||
// error: 16:11-16:11 missing argument: spacing
|
||||
|
@ -1,11 +1,11 @@
|
||||
// Test error cases of the `image` function.
|
||||
|
||||
// File does not exist.
|
||||
[image: "path/does/not/exist"]
|
||||
[image "path/does/not/exist"]
|
||||
|
||||
// File exists, but is no image.
|
||||
[image: "typ/image-error.typ"]
|
||||
[image "typ/image-error.typ"]
|
||||
|
||||
// compare-ref: false
|
||||
// error: 4:9-4:30 failed to load image
|
||||
// error: 7:9-7:30 failed to load image
|
||||
// error: 4:8-4:29 failed to load image
|
||||
// error: 7:8-7:29 failed to load image
|
||||
|
@ -1,23 +1,23 @@
|
||||
// Test configuring the size and fitting behaviour of images.
|
||||
|
||||
// Fit to width of page.
|
||||
[image: "res/rhino.png"]
|
||||
[image "res/rhino.png"]
|
||||
|
||||
// Fit to height of page.
|
||||
[page: width=270pt][
|
||||
[image: "res/rhino.png"]
|
||||
[page width: 270pt][
|
||||
[image "res/rhino.png"]
|
||||
]
|
||||
|
||||
// Set width explicitly.
|
||||
[image: "res/rhino.png", width=50pt]
|
||||
[image "res/rhino.png", width: 50pt]
|
||||
|
||||
// Set height explicitly.
|
||||
[image: "res/rhino.png", height=50pt]
|
||||
[image "res/rhino.png", height: 50pt]
|
||||
|
||||
// Set width and height explicitly and force stretching.
|
||||
[image: "res/rhino.png", width=25pt, height=50pt]
|
||||
[image "res/rhino.png", width: 25pt, height: 50pt]
|
||||
|
||||
// Make sure the bounding-box of the image is correct.
|
||||
[align: bottom, right][
|
||||
[image: "res/tiger.jpg"]
|
||||
[align bottom, right][
|
||||
[image "res/tiger.jpg"]
|
||||
]
|
||||
|
@ -1,8 +1,8 @@
|
||||
// Test loading different image formats.
|
||||
|
||||
// Load an RGBA PNG image.
|
||||
[image: "res/rhino.png"]
|
||||
[image "res/rhino.png"]
|
||||
[pagebreak]
|
||||
|
||||
// Load an RGB JPEG image.
|
||||
[image: "res/tiger.jpg"]
|
||||
[image "res/tiger.jpg"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Test a combination of pages with bodies and normal content.
|
||||
|
||||
[page: height=50pt]
|
||||
[page height: 50pt]
|
||||
|
||||
[page][First]
|
||||
[page][Second]
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Test changing the layouting directions of pages.
|
||||
|
||||
[page: main-dir=btt, cross-dir=rtl]
|
||||
[page main-dir: btt, cross-dir: rtl]
|
||||
|
||||
Right to left!
|
||||
|
@ -1,11 +1,11 @@
|
||||
// Test error cases of the `page` function.
|
||||
|
||||
// Invalid paper.
|
||||
[page: nonexistant]
|
||||
[page nonexistant]
|
||||
|
||||
// Aligned axes.
|
||||
[page: main-dir=ltr]
|
||||
[page main-dir: ltr]
|
||||
|
||||
// compare-ref: false
|
||||
// error: 4:8-4:19 invalid paper
|
||||
// error: 4:7-4:18 invalid paper
|
||||
// error: 7:17-7:20 aligned axis
|
||||
|
@ -1,25 +1,25 @@
|
||||
// Test configuring page sizes and margins.
|
||||
|
||||
// Set width.
|
||||
[page: width=50pt][High]
|
||||
[page width: 50pt][High]
|
||||
|
||||
// Set height.
|
||||
[page: height=50pt][Wide]
|
||||
[page height: 50pt][Wide]
|
||||
|
||||
// Set all margins at once.
|
||||
[page: margins=40pt][
|
||||
[align: top, left][TL]
|
||||
[align: bottom, right][BR]
|
||||
[page margins: 40pt][
|
||||
[align top, left][TL]
|
||||
[align bottom, right][BR]
|
||||
]
|
||||
|
||||
// Set individual margins.
|
||||
[page: left=0pt >> align: left][Left]
|
||||
[page: right=0pt >> align: right][Right]
|
||||
[page: top=0pt >> align: top][Top]
|
||||
[page: bottom=0pt >> align: bottom][Bottom]
|
||||
[page left: 0pt | align left][Left]
|
||||
[page right: 0pt | align right][Right]
|
||||
[page top: 0pt | align top][Top]
|
||||
[page bottom: 0pt | align bottom][Bottom]
|
||||
|
||||
// Ensure that specific margins override general margins.
|
||||
[page: margins=0pt, left=40pt][Overriden]
|
||||
[page margins: 0pt, left: 40pt][Overriden]
|
||||
|
||||
// Flip the page.
|
||||
[page: a10, flip=true][Flipped]
|
||||
[page a10, flip: true][Flipped]
|
||||
|
@ -2,4 +2,4 @@
|
||||
|
||||
First of two
|
||||
[pagebreak]
|
||||
[page: height=40pt]
|
||||
[page height: 40pt]
|
||||
|
@ -1,24 +1,24 @@
|
||||
// Test the `rgb` function.
|
||||
|
||||
// Check the output.
|
||||
[rgb: 0.0, 0.3, 0.7] [val: #004db3]
|
||||
[rgb 0.0, 0.3, 0.7] [val #004db3]
|
||||
|
||||
// Alpha channel.
|
||||
[rgb: 1.0, 0.0, 0.0, 0.5]
|
||||
[rgb 1.0, 0.0, 0.0, 0.5]
|
||||
|
||||
// Value smaller than 0.0 and larger than 1.0
|
||||
[rgb: -30, 15.5, 0.5]
|
||||
[rgb -30, 15.5, 0.5]
|
||||
|
||||
// Missing blue component.
|
||||
[rgb: 0, 1]
|
||||
[rgb 0, 1]
|
||||
|
||||
// Missing all components.
|
||||
[rgb]
|
||||
|
||||
// error: 4:23-4:26 unknown function
|
||||
// error: 10:7-10:10 should be between 0.0 and 1.0
|
||||
// error: 10:12-10:16 should be between 0.0 and 1.0
|
||||
// error: 13:7-13:11 missing argument: blue component
|
||||
// error: 4:22-4:25 unknown function
|
||||
// error: 10:6-10:9 should be between 0.0 and 1.0
|
||||
// error: 10:11-10:15 should be between 0.0 and 1.0
|
||||
// error: 13:6-13:10 missing argument: blue component
|
||||
// error: 16:5-16:5 missing argument: red component
|
||||
// error: 16:5-16:5 missing argument: green component
|
||||
// error: 16:5-16:5 missing argument: blue component
|
||||
|
Loading…
x
Reference in New Issue
Block a user