diff --git a/src/eval/value.rs b/src/eval/value.rs index 6e838f6ca..860c06348 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -8,7 +8,7 @@ use super::{Args, Eval, EvalContext}; use crate::color::Color; use crate::geom::{Angle, Length, Linear, Relative}; use crate::pretty::{pretty, Pretty, Printer}; -use crate::syntax::{Spanned, Tree, WithSpan}; +use crate::syntax::{pretty_template, Spanned, Tree, WithSpan}; /// A computational value. #[derive(Debug, Clone, PartialEq)] @@ -121,11 +121,7 @@ impl Pretty for Value { Value::Str(v) => write!(p, "{:?}", v).unwrap(), Value::Array(v) => v.pretty(p), Value::Dict(v) => v.pretty(p), - Value::Template(v) => { - p.push_str("["); - v.pretty(p); - p.push_str("]"); - } + Value::Template(v) => pretty_template(v, p), Value::Func(v) => v.pretty(p), Value::Any(v) => v.pretty(p), Value::Error => p.push_str("(error)"), @@ -537,8 +533,8 @@ mod tests { // Dictionary. let mut dict = BTreeMap::new(); dict.insert("one".into(), Value::Int(1)); - dict.insert("two".into(), Value::Template(parse("[f]").output)); + dict.insert("two".into(), Value::Template(parse("#[f]").output)); test_pretty(BTreeMap::new(), "(:)"); - test_pretty(dict, "(one: 1, two: [[f]])"); + test_pretty(dict, "(one: 1, two: #[f])"); } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index ff75a563d..3fc7d483a 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -45,38 +45,33 @@ fn tree(p: &mut Parser) -> Tree { fn node(p: &mut Parser, at_start: &mut bool) -> Option { let token = p.peek()?; let node = match token { - // Bracket call. - Token::LeftBracket => { - return Some(Node::Expr(bracket_call(p)?)); + // Whitespace. + Token::Space(newlines) => { + *at_start |= newlines > 0; + if newlines < 2 { Node::Space } else { Node::Parbreak } } - // Code block. - Token::LeftBrace => { - return Some(Node::Expr(block(p, false)?)); - } + // Text. + Token::Text(text) => Node::Text(text.into()), // Markup. Token::Star => Node::Strong, Token::Underscore => Node::Emph, - Token::Tilde => Node::Text("\u{00A0}".into()), - Token::Hash => { + Token::Eq => { if *at_start { return Some(Node::Heading(heading(p))); } else { Node::Text(p.get(p.peek_span()).into()) } } + Token::Tilde => Node::Text("\u{00A0}".into()), Token::Backslash => Node::Linebreak, - Token::Space(newlines) => { - *at_start |= newlines > 0; - if newlines < 2 { Node::Space } else { Node::Parbreak } - } - Token::Text(text) => Node::Text(text.into()), Token::Raw(t) => Node::Raw(raw(p, t)), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), // Keywords. Token::Let | Token::If | Token::For => { + *at_start = false; let stmt = token == Token::Let; let group = if stmt { Group::Stmt } else { Group::Expr }; @@ -92,6 +87,24 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { return expr.map(Node::Expr); } + // Block. + Token::LeftBrace => { + *at_start = false; + return Some(Node::Expr(block(p, false)?)); + } + + // Template. + Token::LeftBracket => { + *at_start = false; + return Some(Node::Expr(template(p))); + } + + // Function template. + Token::HashBracket => { + *at_start = false; + return Some(Node::Expr(bracket_call(p)?)); + } + // Comments. Token::LineComment(_) | Token::BlockComment(_) => { p.eat(); @@ -99,6 +112,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { } _ => { + *at_start = false; p.unexpected(); return None; } @@ -109,12 +123,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option { /// Parse a heading. fn heading(p: &mut Parser) -> NodeHeading { - // Count hashtags. + // Count depth. let mut level = p.span(|p| { - p.assert(Token::Hash); + p.assert(&[Token::Eq]); let mut level = 0u8; - while p.eat_if(Token::Hash) { + while p.eat_if(Token::Eq) { level = level.saturating_add(1); } level @@ -278,33 +292,6 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option { /// Parse a primary expression. fn primary(p: &mut Parser) -> Option { let expr = match p.peek() { - // Template. - Some(Token::LeftBracket) => { - return Some(template(p)); - } - - // Nested block. - Some(Token::LeftBrace) => { - return block(p, true); - } - - // Dictionary or just a parenthesized expression. - Some(Token::LeftParen) => { - return Some(parenthesized(p)); - } - - // Function or just ident. - Some(Token::Ident(id)) => { - p.eat(); - let ident = Ident(id.into()); - if p.peek() == Some(Token::LeftParen) { - let name = ident.with_span(p.peek_span()); - return Some(paren_call(p, name)); - } else { - return Some(Expr::Ident(ident)); - } - } - // Basic values. Some(Token::None) => Expr::None, Some(Token::Bool(b)) => Expr::Bool(b), @@ -316,12 +303,45 @@ fn primary(p: &mut Parser) -> Option { Some(Token::Color(color)) => Expr::Color(color), Some(Token::Str(token)) => Expr::Str(string(p, token)), + // Function or identifier. + Some(Token::Ident(id)) => { + p.eat(); + let ident = Ident(id.into()); + if p.peek() == Some(Token::LeftParen) { + let name = ident.with_span(p.peek_span()); + return Some(paren_call(p, name)); + } else { + return Some(Expr::Ident(ident)); + } + } + // Keywords. Some(Token::Let) => return expr_let(p), Some(Token::If) => return expr_if(p), Some(Token::For) => return expr_for(p), - // No value. + // Block. + Some(Token::LeftBrace) => { + return block(p, true); + } + + // Template. + Some(Token::LeftBracket) => { + return Some(template(p)); + } + + // Function template. + Some(Token::HashBracket) => { + let call = p.span_if(bracket_call)?.map(Node::Expr); + return Some(Expr::Template(vec![call])); + } + + // Array, dictionary or parenthesized expression. + Some(Token::LeftParen) => { + return Some(parenthesized(p)); + } + + // Nothing. _ => { p.expected("expression"); return None; @@ -380,7 +400,7 @@ fn string(p: &mut Parser, token: TokenStr) -> String { /// Parse a let expression. fn expr_let(p: &mut Parser) -> Option { - p.assert(Token::Let); + p.assert(&[Token::Let]); let mut expr_let = None; if let Some(pat) = p.span_if(ident) { @@ -397,7 +417,7 @@ fn expr_let(p: &mut Parser) -> Option { /// Parse an if expresion. fn expr_if(p: &mut Parser) -> Option { - p.assert(Token::If); + p.assert(&[Token::If]); let mut expr_if = None; if let Some(condition) = p.span_if(expr) { @@ -420,7 +440,7 @@ fn expr_if(p: &mut Parser) -> Option { /// Parse a for expression. fn expr_for(p: &mut Parser) -> Option { - p.assert(Token::For); + p.assert(&[Token::For]); let mut expr_for = None; if let Some(pat) = p.span_if(for_pattern) { diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 906d9e623..2ca8eb102 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -105,9 +105,9 @@ impl<'s> Parser<'s> { self.repeek(); match group { - Group::Paren => self.assert(Token::LeftParen), - Group::Bracket => self.assert(Token::LeftBracket), - Group::Brace => self.assert(Token::LeftBrace), + Group::Paren => self.assert(&[Token::LeftParen]), + Group::Bracket => self.assert(&[Token::HashBracket, Token::LeftBracket]), + Group::Brace => self.assert(&[Token::LeftBrace]), Group::Subheader => {} Group::Stmt => {} Group::Expr => {} @@ -210,10 +210,10 @@ impl<'s> Parser<'s> { eaten } - /// Consume the next token, debug-asserting that it is the given one. - pub fn assert(&mut self, t: Token) { + /// Consume the next token, debug-asserting that it is one of the given ones. + pub fn assert(&mut self, ts: &[Token]) { let next = self.eat(); - debug_assert_eq!(next, Some(t)); + debug_assert!(next.map_or(false, |n| ts.contains(&n))); } /// Skip whitespace and comment tokens. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 056bbbbb1..4f5d8ab44 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -67,12 +67,15 @@ impl<'s> Iterator for Tokens<'s> { loop { // Common elements. return Some(match c { - // Functions, blocks and terminators. + // Blocks and templates. '[' => Token::LeftBracket, ']' => Token::RightBracket, '{' => Token::LeftBrace, '}' => Token::RightBrace, + // Keywords, function templates, colors. + '#' => self.hash(start), + // Whitespace. c if c.is_whitespace() => self.whitespace(c), @@ -90,8 +93,8 @@ impl<'s> Iterator for Tokens<'s> { // Markup. '*' => Token::Star, '_' => Token::Underscore, + '=' => Token::Eq, '~' => Token::Tilde, - '#' => self.hash(start), '`' => self.raw(), '$' => self.math(), '\\' => self.backslash(), @@ -140,8 +143,7 @@ impl<'s> Iterator for Tokens<'s> { self.number(start, c) } - // Hex values and strings. - '#' => self.hex(start), + // Strings. '"' => self.string(), _ => Token::Invalid(self.s.eaten_from(start)), @@ -151,6 +153,27 @@ impl<'s> Iterator for Tokens<'s> { } impl<'s> Tokens<'s> { + fn hash(&mut self, start: usize) -> Token<'s> { + if self.s.eat_if('[') { + return Token::HashBracket; + } + + self.s.eat_while(is_id_continue); + let read = self.s.eaten_from(start); + + if let Some(keyword) = keyword(read) { + return keyword; + } + + if self.mode == TokenMode::Code { + if let Ok(color) = RgbaColor::from_str(read) { + return Token::Color(color); + } + } + + Token::Invalid(read) + } + fn whitespace(&mut self, first: char) -> Token<'s> { // Fast path for just a single space if first == ' ' && !self.s.check(|c| c.is_whitespace()) { @@ -182,10 +205,10 @@ impl<'s> Tokens<'s> { c if c.is_whitespace() => true, // Comments. '/' if self.s.check(|c| c == '/' || c == '*') => true, - // Parenthesis. - '[' | ']' | '{' | '}' => true, + // Parenthesis and hashtag. + '[' | ']' | '{' | '}' | '#' => true, // Markup. - '*' | '_' | '#' | '~' | '`' | '$' => true, + '*' | '_' | '=' | '~' | '`' | '$' => true, // Escaping. '\\' => true, _ => false, @@ -198,21 +221,6 @@ impl<'s> Tokens<'s> { Token::Text(self.s.eaten_from(start)) } - fn hash(&mut self, start: usize) -> Token<'s> { - if self.s.check(is_id_start) { - self.s.eat(); - self.s.eat_while(is_id_continue); - let read = self.s.eaten_from(start); - if let Some(keyword) = keyword(read) { - keyword - } else { - Token::Invalid(read) - } - } else { - Token::Hash - } - } - fn raw(&mut self) -> Token<'s> { let mut backticks = 1; while self.s.eat_if('`') { @@ -276,10 +284,10 @@ impl<'s> Tokens<'s> { match c { // Backslash and comments. '\\' | '/' | - // Parenthesis. - '[' | ']' | '{' | '}' | + // Parenthesis and hashtag. + '[' | ']' | '{' | '}' | '#' | // Markup. - '*' | '_' | '#' | '~' | '`' | '$' => { + '*' | '_' | '=' | '~' | '`' | '$' => { let start = self.s.index(); self.s.eat_assert(c); Token::Text(&self.s.eaten_from(start)) @@ -367,18 +375,6 @@ impl<'s> Tokens<'s> { } } - fn hex(&mut self, start: usize) -> Token<'s> { - self.s.eat_while(is_id_continue); - let read = self.s.eaten_from(start); - if let Some(keyword) = keyword(read) { - keyword - } else if let Ok(color) = RgbaColor::from_str(read) { - Token::Color(color) - } else { - Token::Invalid(read) - } - } - fn string(&mut self) -> Token<'s> { let mut escaped = false; Token::Str(TokenStr { @@ -596,8 +592,8 @@ mod tests { // Test markup tokens. t!(Markup[" a1"]: "*" => Star); t!(Markup: "_" => Underscore); - t!(Markup[""]: "###" => Hash, Hash, Hash); - t!(Markup["a1/"]: "# " => Hash, Space(0)); + t!(Markup[""]: "===" => Eq, Eq, Eq); + t!(Markup["a1/"]: "= " => Eq, Space(0)); t!(Markup: "~" => Tilde); t!(Markup[" "]: r"\" => Backslash); } @@ -655,10 +651,9 @@ mod tests { ]; for &(s, t) in &both { - t!(Code[" "]: format!("#{}", s) => t); - t!(Markup[" "]: format!("#{}", s) => t); - t!(Markup[" "]: format!("#{0}#{0}", s) => t, t); - t!(Markup[" /"]: format!("# {}", s) => Hash, Space(0), Text(s)); + t!(Both[" "]: format!("#{}", s) => t); + t!(Both[" "]: format!("#{0}#{0}", s) => t, t); + t!(Markup[" /"]: format!("# {}", s) => Token::Invalid("#"), Space(0), Text(s)); } let code = [ @@ -713,7 +708,7 @@ mod tests { // Test code symbols in text. t!(Markup[" /"]: "a():\"b" => Text("a():\"b")); - t!(Markup[" /"]: ";:,=|/+-" => Text(";:,=|/+-")); + t!(Markup[" /"]: ";:,|/+-" => Text(";:,|/+-")); // Test text ends. t!(Markup[""]: "hello " => Text("hello"), Space(0)); @@ -765,17 +760,17 @@ mod tests { t!(Markup: r"\}" => Text("}")); t!(Markup: r"\*" => Text("*")); t!(Markup: r"\_" => Text("_")); - t!(Markup: r"\#" => Text("#")); + t!(Markup: r"\=" => Text("=")); t!(Markup: r"\~" => Text("~")); t!(Markup: r"\`" => Text("`")); t!(Markup: r"\$" => Text("$")); + t!(Markup: r"\#" => Text("#")); // Test unescapable symbols. t!(Markup[" /"]: r"\a" => Text(r"\"), Text("a")); t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u")); t!(Markup[" /"]: r"\1" => Text(r"\"), Text("1")); t!(Markup[" /"]: r"\:" => Text(r"\"), Text(":")); - t!(Markup[" /"]: r"\=" => Text(r"\"), Text("=")); t!(Markup[" /"]: r#"\""# => Text(r"\"), Text("\"")); // Test basic unicode escapes. @@ -947,7 +942,7 @@ mod tests { t!(Code: "1%%" => Percent(1.0), Invalid("%")); // Test invalid keyword. - t!(Markup[" /"]: "#-" => Hash, Text("-")); + t!(Markup[" /"]: "#-" => Invalid("#-")); t!(Markup[" /"]: "#do" => Invalid("#do")); t!(Code[" /"]: r"#letter" => Invalid(r"#letter")); } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index ca55bdd0a..7a055cc7a 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -43,7 +43,7 @@ pub enum Expr { Unary(ExprUnary), /// A binary operation: `a + b`. Binary(ExprBinary), - /// An invocation of a function: `foo(...)`, `[foo ...]`. + /// An invocation of a function: `foo(...)`, `#[foo ...]`. Call(ExprCall), /// A let expression: `#let x = 1`. Let(ExprLet), @@ -75,11 +75,7 @@ impl Pretty for Expr { Self::Str(v) => write!(p, "{:?}", &v).unwrap(), Self::Array(v) => v.pretty(p), Self::Dict(v) => v.pretty(p), - Self::Template(v) => { - p.push_str("["); - v.pretty(p); - p.push_str("]"); - } + Self::Template(v) => pretty_template(v, p), Self::Group(v) => { p.push_str("("); v.v.pretty(p); @@ -146,6 +142,17 @@ impl Pretty for Named { /// A template expression: `[*Hi* there!]`. pub type ExprTemplate = Tree; +/// Pretty print a template. +pub fn pretty_template(template: &ExprTemplate, p: &mut Printer) { + if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { + pretty_func_template(call, p, false) + } else { + p.push_str("["); + template.pretty(p); + p.push_str("]"); + } +} + /// A grouped expression: `(1 + 2)`. pub type ExprGroup = SpanBox; @@ -400,7 +407,7 @@ pub enum Associativity { Right, } -/// An invocation of a function: `foo(...)`, `[foo ...]`. +/// An invocation of a function: `foo(...)`, `#[foo ...]`. #[derive(Debug, Clone, PartialEq)] pub struct ExprCall { /// The callee of the function. @@ -418,12 +425,12 @@ impl Pretty for ExprCall { } } -/// Pretty print a bracketed function call, with body or chaining when possible. -pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { +/// Pretty print a function template, with body or chaining when possible. +pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) { if chained { p.push_str(" | "); } else { - p.push_str("["); + p.push_str("#["); } // Function name. @@ -431,7 +438,7 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { // Find out whether this can be written with a body or as a chain. // - // Example: Transforms "[v [Hi]]" => "[v][Hi]". + // Example: Transforms "#[v [Hi]]" => "#[v][Hi]". if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] = call.args.v.as_slice() { @@ -443,9 +450,9 @@ pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { // Find out whether this can written as a chain. // - // Example: Transforms "[v][[f]]" => "[v | f]". + // Example: Transforms "#[v][[f]]" => "#[v | f]". if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = template.as_slice() { - return pretty_bracket_call(call, p, true); + return pretty_func_template(call, p, true); } else { p.push_str("]["); template.pretty(p); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index d1fc6b779..41fba1345 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -60,7 +60,7 @@ mod tests { roundtrip("hi"); // Heading. - roundtrip("# *Ok*"); + roundtrip("= *Ok*"); // Raw. roundtrip("`lang 1`"); @@ -94,9 +94,9 @@ mod tests { roundtrip("{(a: 1, b: 2)}"); // Templates. - roundtrip("{[]}"); - roundtrip("{[*Ok*]}"); - roundtrip("{[[f]]}"); + roundtrip("[]"); + roundtrip("[*Ok*]"); + roundtrip("{[f]}"); // Groups. roundtrip("{(1)}"); @@ -105,6 +105,7 @@ mod tests { roundtrip("{}"); roundtrip("{1}"); roundtrip("{ #let x = 1; x += 2; x + 1 }"); + roundtrip("[{}]"); // Operators. roundtrip("{-x}"); @@ -116,14 +117,14 @@ mod tests { roundtrip("{v(1)}"); roundtrip("{v(a: 1, b)}"); - // Bracket calls. - roundtrip("[v]"); - roundtrip("[v 1]"); - roundtrip("[v 1, 2][*Ok*]"); - roundtrip("[v 1 | f 2]"); - roundtrip("{[[v]]}"); - test("[v 1, [[f 2]]]", "[v 1 | f 2]"); - test("[v 1, 2][[f 3]]", "[v 1, 2 | f 3]"); + // Function templates. + roundtrip("#[v]"); + roundtrip("#[v 1]"); + roundtrip("#[v 1, 2][*Ok*]"); + roundtrip("#[v 1 | f 2]"); + roundtrip("{#[v]}"); + test("#[v 1, #[f 2]]", "#[v 1 | f 2]"); + test("#[v 1, 2][#[f 3]]", "#[v 1, 2 | f 3]"); // Keywords. roundtrip("#let x = 1 + 2"); diff --git a/src/syntax/node.rs b/src/syntax/node.rs index db3b65108..d45e59528 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -36,8 +36,8 @@ impl Pretty for Node { Self::Raw(raw) => raw.pretty(p), Self::Expr(expr) => { if let Expr::Call(call) = expr { - // Format bracket calls appropriately. - pretty_bracket_call(call, p, false) + // Format function templates appropriately. + pretty_func_template(call, p, false) } else { expr.pretty(p); } @@ -58,7 +58,7 @@ pub struct NodeHeading { impl Pretty for NodeHeading { fn pretty(&self, p: &mut Printer) { for _ in 0 ..= self.level.v { - p.push_str("#"); + p.push_str("="); } self.contents.pretty(p); } diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 43797f75d..c4b9ec8fd 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -6,6 +6,8 @@ use crate::geom::{AngularUnit, LengthUnit}; pub enum Token<'s> { /// A left square bracket: `[`. LeftBracket, + /// A hashtag followed by a left square bracket: `#[`. + HashBracket, /// A right square bracket: `]`. RightBracket, /// A left curly brace: `{`. @@ -20,8 +22,8 @@ pub enum Token<'s> { Star, /// An underscore: `_`. Underscore, - /// A hashtag: `#`. - Hash, + /// A single equals sign: `=`. + Eq, /// A tilde: `~`. Tilde, /// A backslash followed by nothing or whitespace: `\`. @@ -40,8 +42,6 @@ pub enum Token<'s> { Hyph, /// A slash: `/`. Slash, - /// A single equals sign: `=`. - Eq, /// Two equals signs: `==`. EqEq, /// An exclamation mark followed by an equals sign: `!=`. @@ -191,6 +191,7 @@ impl<'s> Token<'s> { pub fn name(self) -> &'static str { match self { Self::LeftBracket => "opening bracket", + Self::HashBracket => "start of function template", Self::RightBracket => "closing bracket", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", @@ -198,7 +199,6 @@ impl<'s> Token<'s> { Self::RightParen => "closing paren", Self::Star => "star", Self::Underscore => "underscore", - Self::Hash => "hashtag", Self::Tilde => "tilde", Self::Backslash => "backslash", Self::Comma => "comma", diff --git a/tests/full/typ/coma.typ b/tests/full/typ/coma.typ index b7863fce8..32990997c 100644 --- a/tests/full/typ/coma.typ +++ b/tests/full/typ/coma.typ @@ -1,23 +1,23 @@ -[page width: 450pt, height: 300pt, margins: 1cm] +#[page width: 450pt, height: 300pt, margins: 1cm] -[box][ +#[box][ *Technische Universität Berlin* \ *Fakultät II, Institut for Mathematik* \ Sekretariat MA \ 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 diff --git a/tests/lang/ref/bracket-call.png b/tests/lang/ref/bracket-call.png index e89244842..fe7f24346 100644 Binary files a/tests/lang/ref/bracket-call.png and b/tests/lang/ref/bracket-call.png differ diff --git a/tests/lang/ref/headings.png b/tests/lang/ref/headings.png index c8d831650..b1ad33666 100644 Binary files a/tests/lang/ref/headings.png and b/tests/lang/ref/headings.png differ diff --git a/tests/lang/typ/bracket-call.typ b/tests/lang/typ/bracket-call.typ index 3d1fceb04..46757c45b 100644 --- a/tests/lang/typ/bracket-call.typ +++ b/tests/lang/typ/bracket-call.typ @@ -1,25 +1,25 @@ // Basic call, whitespace insignificant. -[f], [ f ], [ +#[f], #[ f ], #[ f ] -[f bold] +#[f bold] -[f 1,] +#[f 1,] -[f a:2] +#[f a:2] -[f 1, a: (3, 4), 2, b: "5"] +#[f 1, a: (3, 4), 2, b: "5"] --- // Body and no body. -[f][[f]] +#[f][#[f]] // Lots of potential bodies. -[f][f][f] +#[f][f]#[f] // Multi-paragraph body. -[box][ +#[box][ First Second @@ -27,81 +27,81 @@ --- // Chained. -[f | f] +#[f | f] // Multi-chain. -[f|f|f] +#[f|f|f] // With body. -// Error: 1:6-1:7 expected identifier, found integer -[f | 1 | box][💕] +// Error: 1:7-1:8 expected identifier, found integer +#[f | 1 | box][💕] -// Error: 2:2-2:2 expected identifier -// Error: 1:3-1:3 expected identifier -[||f true] +// Error: 2:3-2:3 expected identifier +// Error: 1:4-1:4 expected identifier +#[||f true] -// Error: 1:6-1:6 expected identifier -[f 1|] +// Error: 1:7-1:7 expected identifier +#[f 1|] -// Error: 2:2-2:2 expected identifier -// Error: 1:3-1:3 expected identifier -[|][Nope] +// Error: 2:3-2:3 expected identifier +// Error: 1:4-1:4 expected identifier +#[|][Nope] -// Error: 2:5-2:5 expected closing paren -// Error: 1:8-1:9 expected expression, found closing paren -[f (|f )] +// Error: 2:6-2:6 expected closing paren +// Error: 1:9-1:10 expected expression, found closing paren +#[f (|f )] // With actual functions. -[box width: 1cm | image "res/rhino.png"] +#[box width: 1cm | image "res/rhino.png"] --- -// Error: 1:4-1:6 expected expression, found end of block comment -[f */] +// Error: 1:5-1:7 expected expression, found end of block comment +#[f */] -// Error: 1:7-1:8 expected expression, found colon -[f a:1:] +// Error: 1:8-1:9 expected expression, found colon +#[f a:1:] -// Error: 1:5-1:5 expected comma -[f 1 2] +// Error: 1:6-1:6 expected comma +#[f 1 2] -// Error: 2:4-2:5 expected identifier -// Error: 1:6-1:6 expected expression -[f 1:] +// Error: 2:5-2:6 expected identifier +// Error: 1:7-1:7 expected expression +#[f 1:] -// Error: 1:4-1:5 expected identifier -[f 1:2] +// Error: 1:5-1:6 expected identifier +#[f 1:2] -// Error: 1:4-1:7 expected identifier -[f (x):1] +// Error: 1:5-1:8 expected identifier +#[f (x):1] --- // Ref: false -// Error: 2:2-2:3 expected function, found string +// Error: 2:3-2:4 expected function, found string #let x = "string" -[x] +#[x] -// Error: 1:2-1:3 expected identifier, found invalid token -[# 1] +// Error: 1:3-1:4 expected identifier, found invalid token +#[# 1] // Error: 4:1-4:1 expected identifier // Error: 3:1-3:1 expected closing bracket -[ +#[ --- // Ref: false -// Error: 2:2-2:3 expected identifier, found closing paren +// Error: 2:3-2:4 expected identifier, found closing paren // Error: 3:1-3:1 expected closing bracket -[) +#[) --- // Error: 3:1-3:1 expected closing bracket -[f [*] +#[f [*] --- // Error: 3:1-3:1 expected closing bracket -[f][`a]` +#[f][`a]` --- // Error: 3:1-3:1 expected quote // Error: 2:1-2:1 expected closing bracket -[f "] +#[f "] diff --git a/tests/lang/typ/comments.typ b/tests/lang/typ/comments.typ index 5118a05e3..880e8c2b5 100644 --- a/tests/lang/typ/comments.typ +++ b/tests/lang/typ/comments.typ @@ -8,7 +8,7 @@ C/* */D // Works in headers. -[f /*1*/ a: "b" // +#[f /*1*/ a: "b" // , 1] // End should not appear without start. diff --git a/tests/lang/typ/emph-strong.typ b/tests/lang/typ/emph-strong.typ index dcd173376..2f77c7bbe 100644 --- a/tests/lang/typ/emph-strong.typ +++ b/tests/lang/typ/emph-strong.typ @@ -5,7 +5,7 @@ _Emph_ and *strong*! Pa_rtl_y emphasized or str*ength*ened. // Scoped to body. -[box][*Sco_ped] to body. +#[box][*Sco_ped] to body. // Unterminated is fine. _The End diff --git a/tests/lang/typ/expressions.typ b/tests/lang/typ/expressions.typ index da6c6f4f8..eee400854 100644 --- a/tests/lang/typ/expressions.typ +++ b/tests/lang/typ/expressions.typ @@ -7,75 +7,75 @@ #let error = +"" // Paren call. -[test f(1), "f(1)"] -[test type(1), "integer"] +#[test f(1), "f(1)"] +#[test type(1), "integer"] // Unary operations. -[test +1, 1] -[test -1, 1-2] -[test --1, 1] +#[test +1, 1] +#[test -1, 1-2] +#[test --1, 1] // Math operations. -[test "a" + "b", "ab"] -[test 1-4, 3*-1] -[test a * b, 8] -[test 12pt/.4, 30pt] -[test 1e+2-1e-2, 99.99] +#[test "a" + "b", "ab"] +#[test 1-4, 3*-1] +#[test a * b, 8] +#[test 12pt/.4, 30pt] +#[test 1e+2-1e-2, 99.99] // Associativity. -[test 1+2+3, 6] -[test 1/2*3, 1.5] +#[test 1+2+3, 6] +#[test 1/2*3, 1.5] // Precedence. -[test 1+2*-3, -5] +#[test 1+2*-3, -5] // Short-circuiting logical operators. -[test not "a" == "b", true] -[test not 7 < 4 and 10 == 10, true] -[test 3 < 2 or 4 < 5, true] -[test false and false or true, true] +#[test not "a" == "b", true] +#[test not 7 < 4 and 10 == 10, true] +#[test 3 < 2 or 4 < 5, true] +#[test false and false or true, true] // Right-hand side not even evaluated. -[test false and dont-care, false] -[test true or dont-care, true] +#[test false and dont-care, false] +#[test true or dont-care, true] // Equality and inequality. -[test "ab" == "a" + "b", true] -[test [*Hi*] == [*Hi*], true] -[test "a" != "a", false] -[test [*] != [_], true] -[test (1, 2, 3) == (1, 2) + (3,), true] -[test () == (1,), false] -[test (a: 1, b: 2) == (b: 2, a: 1), true] -[test (:) == (a: 1), false] -[test 1 == "hi", false] -[test 1 == 1.0, true] -[test 30% == 30% + 0cm, true] -[test 1in == 0% + 72pt, true] -[test 30% == 30% + 1cm, false] +#[test "ab" == "a" + "b", true] +#[test [*Hi*] == [*Hi*], true] +#[test "a" != "a", false] +#[test [*] != [_], true] +#[test (1, 2, 3) == (1, 2) + (3,), true] +#[test () == (1,), false] +#[test (a: 1, b: 2) == (b: 2, a: 1), true] +#[test (:) == (a: 1), false] +#[test 1 == "hi", false] +#[test 1 == 1.0, true] +#[test 30% == 30% + 0cm, true] +#[test 1in == 0% + 72pt, true] +#[test 30% == 30% + 1cm, false] // Comparisons. -[test 13 * 3 < 14 * 4, true] -[test 5 < 10, true] -[test 5 > 5, false] -[test 5 <= 5, true] -[test 5 <= 4, false] -[test 45deg < 1rad, true] +#[test 13 * 3 < 14 * 4, true] +#[test 5 < 10, true] +#[test 5 > 5, false] +#[test 5 <= 5, true] +#[test 5 <= 4, false] +#[test 45deg < 1rad, true] // Assignment. #let x = "some" #let y = "some" -[test (x = y = "") == none and x == none and y == "", true] +#[test (x = y = "") == none and x == none and y == "", true] // Modify-assign operators. #let x = 0 -{ x = 10 } [test x, 10] -{ x -= 5 } [test x, 5] -{ x += 1 } [test x, 6] -{ x *= x } [test x, 36] -{ x /= 2.0 } [test x, 18.0] -{ x = "some" } [test x, "some"] -{ x += "thing" } [test x, "something"] +{ x = 10 } #[test x, 10] +{ x -= 5 } #[test x, 5] +{ x += 1 } #[test x, 6] +{ x *= x } #[test x, 36] +{ x /= 2.0 } #[test x, 18.0] +{ x = "some" } #[test x, "some"] +{ x += "thing" } #[test x, "something"] // Error: 1:3-1:4 unknown variable { z = 1 } @@ -94,21 +94,21 @@ #let box = ""; { box = "hi" } // Parentheses. -[test (a), 2] -[test (2), 2] -[test (1+2)*3, 9] +#[test (a), 2] +#[test (2), 2] +#[test (1+2)*3, 9] // Error: 1:3-1:3 expected expression {-} -// Error: 1:10-1:10 expected expression -[test {1+}, 1] +// Error: 1:11-1:11 expected expression +#[test {1+}, 1] -// Error: 1:10-1:10 expected expression -[test {2*}, 2] +// Error: 1:11-1:11 expected expression +#[test {2*}, 2] -// Error: 1:7-1:16 cannot apply '-' to boolean -[test -not true, error] +// Error: 1:8-1:17 cannot apply '-' to boolean +#[test -not true, error] // Error: 1:2-1:8 cannot apply 'not' to array {not ()} diff --git a/tests/lang/typ/headings.typ b/tests/lang/typ/headings.typ index e63574ca3..a1bb37537 100644 --- a/tests/lang/typ/headings.typ +++ b/tests/lang/typ/headings.typ @@ -1,48 +1,41 @@ // Test different numbers of hashtags. // Valid levels. -# One -### Three -###### Six += One +=== Three +====== Six // Too many hashtags. // Warning: 1:1-1:8 should not exceed depth 6 -####### Seven +======= Seven --- // Test heading vs. no heading. // Parsed as headings if at start of the context. -/**/ # Heading -{[## Heading]} -[box][### Heading] +/**/ = Heading +{[== Heading]} +#[box][=== Heading] // Not at the start of the context. -Text with # hashtag +Text = with=sign // Escaped. -\# No heading - -// Keyword. -// Error: 1:1-1:6 unexpected invalid token -#nope - -// Not parsed as a keyword, but neither as a heading. -Nr#1 +\= No heading --- // Heading continuation over linebreak. // Code blocks continue heading. -# This { += This { "continues" } // Function call continues heading. -# [box][ += #[box][ This, ] too // Without some kind of block, headings end at a line break. -# This += This not diff --git a/tests/lang/typ/if.typ b/tests/lang/typ/if.typ index ee466a8d9..5fb46320b 100644 --- a/tests/lang/typ/if.typ +++ b/tests/lang/typ/if.typ @@ -52,8 +52,7 @@ a#if x b#if (x)c a#if true [b] #else c // Lone else. -// Error: 2:1-2:6 unexpected keyword `#else` -// Error: 1:8-1:8 expected identifier +// Error: 1:1-1:6 unexpected keyword `#else` #else [] // Condition must be boolean. If it isn't, neither branch is evaluated. diff --git a/tests/lang/typ/let.typ b/tests/lang/typ/let.typ index b3eadeaa2..52545b2f0 100644 --- a/tests/lang/typ/let.typ +++ b/tests/lang/typ/let.typ @@ -1,10 +1,10 @@ // Automatically initialized with `none`. #let x -[test x, none] +#[test x, none] // Initialized with `1`. #let y = 1 -[test y, 1] +#[test y, 1] // Initialize with template, not terminated by semicolon in template. #let v = [Hello; there] @@ -15,10 +15,10 @@ 2, 3, ) -[test x, (1, 2, 3)] +#[test x, (1, 2, 3)] // Multiple bindings in one line. -#let x = "a"; #let y = "b"; [test x + y, "ab"] +#let x = "a"; #let y = "b"; #[test x + y, "ab"] // Invalid name. // Error: 1:6-1:7 expected identifier, found integer @@ -43,24 +43,24 @@ The Fi#let;rst // Terminated with just a line break. #let v = "a" -The Second [test v, "a"] +The Second #[test v, "a"] // Terminated with semicolon + line break. #let v = "a"; -The Third [test v, "a"] +The Third #[test v, "a"] // Terminated with just a semicolon. -The#let v = "a"; Fourth [test v, "a"] +The#let v = "a"; Fourth #[test v, "a"] // Terminated by semicolon even though we are in a paren group. // Error: 2:25-2:25 expected expression // Error: 1:25-1:25 expected closing paren -The#let array = (1, 2 + ;Fifth [test array, (1, 2)] +The#let array = (1, 2 + ;Fifth #[test array, (1, 2)] // Not terminated. // Error: 1:16-1:16 expected semicolon or line break -The#let v = "a"Sixth [test v, "a"] +The#let v = "a"Sixth #[test v, "a"] // Not terminated. // Error: 1:16-1:16 expected semicolon or line break -The#let v = "a" [test v, "a"] Seventh +The#let v = "a" #[test v, "a"] Seventh diff --git a/tests/lang/typ/raw.typ b/tests/lang/typ/raw.typ index 2c0890ba2..a1f796b5c 100644 --- a/tests/lang/typ/raw.typ +++ b/tests/lang/typ/raw.typ @@ -1,4 +1,4 @@ -[font 8pt] +#[font 8pt] // Typst syntax inside. `#let x = 1``[f 1]` diff --git a/tests/lang/typ/values.typ b/tests/lang/typ/values.typ index 55712c4db..53c2ea68c 100644 --- a/tests/lang/typ/values.typ +++ b/tests/lang/typ/values.typ @@ -37,4 +37,4 @@ {"a\n[]\"\u{1F680}string"} \ // Templates. -{[*{"Hi"} [f 1]*]} +{[*{"Hi"} #[f 1]*]} diff --git a/tests/library/typ/font.typ b/tests/library/typ/font.typ index 4be98941c..57761a8fb 100644 --- a/tests/library/typ/font.typ +++ b/tests/library/typ/font.typ @@ -1,37 +1,37 @@ // 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] +#[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] -// Error: 1:7-1:12 unexpected argument -[font false] +// Error: 1:8-1:13 unexpected argument +#[font false] -// Error: 3:14-3:18 expected font style, found font weight -// Error: 2:28-2:34 expected font weight, found string -// Error: 1:43-1:44 expected font family or array of font families, found integer -[font style: bold, weight: "thin", serif: 0] +// Error: 3:15-3:19 expected font style, found font weight +// Error: 2:29-2:35 expected font weight, found string +// Error: 1:44-1:45 expected font family or array of font families, found integer +#[font style: bold, weight: "thin", serif: 0] -// Warning: 1:15-1:19 should be between 100 and 900 -[font weight: 2700] +// Warning: 1:16-1:20 should be between 100 and 900 +#[font weight: 2700] -// Error: 1:7-1:27 unexpected argument -[font something: "invalid"] +// Error: 1:8-1:28 unexpected argument +#[font something: "invalid"] --- // Test font fallback and class definitions. @@ -40,17 +40,17 @@ Emoji: 🏀 // CMU Serif + Noto Emoji. -[font "CMU Serif", "Noto Emoji"][ +#[font "CMU Serif", "Noto Emoji"][ Emoji: 🏀 ] // Class definitions. -[font serif: ("CMU Serif", "Latin Modern Math", "Noto Emoji")] -[font serif][ +#[font serif: ("CMU Serif", "Latin Modern Math", "Noto Emoji")] +#[font serif][ Math: ∫ α + β ➗ 3 ] // Class definition reused. -[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. 🚀 diff --git a/tests/library/typ/hv.typ b/tests/library/typ/hv.typ index 36a6d242d..67c04a12f 100644 --- a/tests/library/typ/hv.typ +++ b/tests/library/typ/hv.typ @@ -1,22 +1,22 @@ // 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. -// Error: 1:11-1:11 missing argument: spacing -Totally [h] ignored +// Error: 1:12-1:12 missing argument: spacing +Totally #[h] ignored // Swapped axes. -[page main-dir: rtl, cross-dir: ttb, height: 80pt][ - 1 [h 1cm] 2 +#[page main-dir: rtl, cross-dir: ttb, height: 80pt][ + 1 #[h 1cm] 2 - 3 [v 1cm] 4 [v -1cm] 5 + 3 #[v 1cm] 4 #[v -1cm] 5 ] diff --git a/tests/library/typ/image.typ b/tests/library/typ/image.typ index 2e7b1eb52..a1e886dd9 100644 --- a/tests/library/typ/image.typ +++ b/tests/library/typ/image.typ @@ -1,39 +1,39 @@ // Test loading different image formats. // Load an RGBA PNG image. -[image "res/rhino.png"] -[pagebreak] +#[image "res/rhino.png"] +#[pagebreak] // Load an RGB JPEG image. -[image "res/tiger.jpg"] +#[image "res/tiger.jpg"] -// Error: 1:8-1:29 failed to load image -[image "path/does/not/exist"] +// Error: 1:9-1:30 failed to load image +#[image "path/does/not/exist"] -// Error: 1:8-1:29 failed to load image -[image "typ/image-error.typ"] +// Error: 1:9-1:30 failed to load image +#[image "typ/image-error.typ"] --- // 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 height: 40pt][ - [image "res/rhino.png"] +#[page height: 40pt][ + #[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", width: 60pt] +#[align bottom, right][ + #[image "res/tiger.jpg", width: 60pt] ] diff --git a/tests/library/typ/page.typ b/tests/library/typ/page.typ index 9eeddc93c..97100ae99 100644 --- a/tests/library/typ/page.typ +++ b/tests/library/typ/page.typ @@ -1,54 +1,53 @@ // Test configuring page sizes and margins. // Set width and height. -[page width: 120pt, height: 120pt] -[page width: 40pt][High] -[page height: 40pt][Wide] +#[page width: 120pt, height: 120pt] +#[page width: 40pt][High] +#[page height: 40pt][Wide] // Set all margins at once. -[page margins: 30pt][ - [align top, left][TL] - [align bottom, right][BR] +#[page margins: 30pt][ + #[align top, left][TL] + #[align bottom, right][BR] ] // Set individual margins. -[page height: 40pt] -[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 height: 40pt] +#[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: 20pt][Overriden] +#[page margins: 0pt, left: 20pt][Overriden] -// Error: 1:7-1:18 unknown variable -[page nonexistant] +// Error: 1:8-1:19 unknown variable +#[page nonexistant] -// Error: 1:17-1:20 aligned axis -[page main-dir: ltr] +// Error: 1:18-1:21 aligned axis +#[page main-dir: ltr] // Flipped predefined paper. -[page "a11", flip: true][Flipped A11] +#[page "a11", flip: true][Flipped A11] // Flipped custom page size. -[page width: 40pt, height: 120pt] -[page flip: true] +#[page width: 40pt, height: 120pt] +#[page flip: true] Wide // Test changing the layouting directions of pages. - -[page height: 50pt, main-dir: btt, cross-dir: rtl] +#[page height: 50pt, main-dir: btt, cross-dir: rtl] Right to left! --- // Test a combination of pages with bodies and normal content. -[page height: 50pt] +#[page height: 50pt] -[page][First] -[page][Second] -[pagebreak] +#[page][First] +#[page][Second] +#[pagebreak] Fourth -[page][] +#[page][] Sixth -[page][Seventh and last] +#[page][Seventh and last] diff --git a/tests/library/typ/pagebreak.typ b/tests/library/typ/pagebreak.typ index 2f539ce00..08dd9c7b7 100644 --- a/tests/library/typ/pagebreak.typ +++ b/tests/library/typ/pagebreak.typ @@ -1,3 +1,3 @@ First of two -[pagebreak] -[page height: 40pt] +#[pagebreak] +#[page height: 40pt] diff --git a/tests/library/typ/rgb.typ b/tests/library/typ/rgb.typ index f778b2d1f..e7b405184 100644 --- a/tests/library/typ/rgb.typ +++ b/tests/library/typ/rgb.typ @@ -1,17 +1,17 @@ // Check the output. -[rgb 0.0, 0.3, 0.7] +#[rgb 0.0, 0.3, 0.7] // Alpha channel. -[rgb 1.0, 0.0, 0.0, 0.5] +#[rgb 1.0, 0.0, 0.0, 0.5] -// Warning: 2:6-2:9 should be between 0.0 and 1.0 -// Warning: 1:11-1:15 should be between 0.0 and 1.0 -[rgb -30, 15.5, 0.5] +// Warning: 2:7-2:10 should be between 0.0 and 1.0 +// Warning: 1:12-1:16 should be between 0.0 and 1.0 +#[rgb -30, 15.5, 0.5] -// Error: 1:6-1:10 missing argument: blue component -[rgb 0, 1] +// Error: 1:7-1:11 missing argument: blue component +#[rgb 0, 1] -// Error: 3:5-3:5 missing argument: red component -// Error: 2:5-2:5 missing argument: green component -// Error: 1:5-1:5 missing argument: blue component -[rgb] +// Error: 3:6-3:6 missing argument: red component +// Error: 2:6-2:6 missing argument: green component +// Error: 1:6-1:6 missing argument: blue component +#[rgb]