New syntax 💎

- Everything everywhere!
  - Blocks with curly braces: {}
  - Templates with brackets: []
  - Function templates with hashtag: `#[f]`
- Headings with equals sign: `= Introduction`
This commit is contained in:
Laurenz 2021-01-30 12:09:26 +01:00
parent ac24075469
commit 89eb8bae49
26 changed files with 384 additions and 374 deletions

View File

@ -8,7 +8,7 @@ use super::{Args, Eval, EvalContext};
use crate::color::Color; use crate::color::Color;
use crate::geom::{Angle, Length, Linear, Relative}; use crate::geom::{Angle, Length, Linear, Relative};
use crate::pretty::{pretty, Pretty, Printer}; use crate::pretty::{pretty, Pretty, Printer};
use crate::syntax::{Spanned, Tree, WithSpan}; use crate::syntax::{pretty_template, Spanned, Tree, WithSpan};
/// A computational value. /// A computational value.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -121,11 +121,7 @@ impl Pretty for Value {
Value::Str(v) => write!(p, "{:?}", v).unwrap(), Value::Str(v) => write!(p, "{:?}", v).unwrap(),
Value::Array(v) => v.pretty(p), Value::Array(v) => v.pretty(p),
Value::Dict(v) => v.pretty(p), Value::Dict(v) => v.pretty(p),
Value::Template(v) => { Value::Template(v) => pretty_template(v, p),
p.push_str("[");
v.pretty(p);
p.push_str("]");
}
Value::Func(v) => v.pretty(p), Value::Func(v) => v.pretty(p),
Value::Any(v) => v.pretty(p), Value::Any(v) => v.pretty(p),
Value::Error => p.push_str("(error)"), Value::Error => p.push_str("(error)"),
@ -537,8 +533,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::Template(parse("[f]").output)); dict.insert("two".into(), Value::Template(parse("#[f]").output));
test_pretty(BTreeMap::new(), "(:)"); test_pretty(BTreeMap::new(), "(:)");
test_pretty(dict, "(one: 1, two: [[f]])"); test_pretty(dict, "(one: 1, two: #[f])");
} }
} }

View File

@ -45,38 +45,33 @@ fn tree(p: &mut Parser) -> Tree {
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
let token = p.peek()?; let token = p.peek()?;
let node = match token { let node = match token {
// Bracket call. // Whitespace.
Token::LeftBracket => { Token::Space(newlines) => {
return Some(Node::Expr(bracket_call(p)?)); *at_start |= newlines > 0;
if newlines < 2 { Node::Space } else { Node::Parbreak }
} }
// Code block. // Text.
Token::LeftBrace => { Token::Text(text) => Node::Text(text.into()),
return Some(Node::Expr(block(p, false)?));
}
// Markup. // Markup.
Token::Star => Node::Strong, Token::Star => Node::Strong,
Token::Underscore => Node::Emph, Token::Underscore => Node::Emph,
Token::Tilde => Node::Text("\u{00A0}".into()), Token::Eq => {
Token::Hash => {
if *at_start { if *at_start {
return Some(Node::Heading(heading(p))); return Some(Node::Heading(heading(p)));
} else { } else {
Node::Text(p.get(p.peek_span()).into()) Node::Text(p.get(p.peek_span()).into())
} }
} }
Token::Tilde => Node::Text("\u{00A0}".into()),
Token::Backslash => Node::Linebreak, 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::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)),
// Keywords. // Keywords.
Token::Let | Token::If | Token::For => { Token::Let | Token::If | Token::For => {
*at_start = false;
let stmt = token == Token::Let; let stmt = token == Token::Let;
let group = if stmt { Group::Stmt } else { Group::Expr }; let group = if stmt { Group::Stmt } else { Group::Expr };
@ -92,6 +87,24 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
return expr.map(Node::Expr); 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. // Comments.
Token::LineComment(_) | Token::BlockComment(_) => { Token::LineComment(_) | Token::BlockComment(_) => {
p.eat(); p.eat();
@ -99,6 +112,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
} }
_ => { _ => {
*at_start = false;
p.unexpected(); p.unexpected();
return None; return None;
} }
@ -109,12 +123,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
/// Parse a heading. /// Parse a heading.
fn heading(p: &mut Parser) -> NodeHeading { fn heading(p: &mut Parser) -> NodeHeading {
// Count hashtags. // Count depth.
let mut level = p.span(|p| { let mut level = p.span(|p| {
p.assert(Token::Hash); p.assert(&[Token::Eq]);
let mut level = 0u8; let mut level = 0u8;
while p.eat_if(Token::Hash) { while p.eat_if(Token::Eq) {
level = level.saturating_add(1); level = level.saturating_add(1);
} }
level level
@ -278,33 +292,6 @@ fn expr_with(p: &mut Parser, min_prec: usize) -> Option<Expr> {
/// Parse a primary expression. /// Parse a primary expression.
fn primary(p: &mut Parser) -> Option<Expr> { fn primary(p: &mut Parser) -> Option<Expr> {
let expr = match p.peek() { 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. // Basic values.
Some(Token::None) => Expr::None, Some(Token::None) => Expr::None,
Some(Token::Bool(b)) => Expr::Bool(b), Some(Token::Bool(b)) => Expr::Bool(b),
@ -316,12 +303,45 @@ fn primary(p: &mut Parser) -> Option<Expr> {
Some(Token::Color(color)) => Expr::Color(color), Some(Token::Color(color)) => Expr::Color(color),
Some(Token::Str(token)) => Expr::Str(string(p, token)), 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. // Keywords.
Some(Token::Let) => return expr_let(p), Some(Token::Let) => return expr_let(p),
Some(Token::If) => return expr_if(p), Some(Token::If) => return expr_if(p),
Some(Token::For) => return expr_for(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"); p.expected("expression");
return None; return None;
@ -380,7 +400,7 @@ fn string(p: &mut Parser, token: TokenStr) -> String {
/// Parse a let expression. /// Parse a let expression.
fn expr_let(p: &mut Parser) -> Option<Expr> { fn expr_let(p: &mut Parser) -> Option<Expr> {
p.assert(Token::Let); p.assert(&[Token::Let]);
let mut expr_let = None; let mut expr_let = None;
if let Some(pat) = p.span_if(ident) { if let Some(pat) = p.span_if(ident) {
@ -397,7 +417,7 @@ fn expr_let(p: &mut Parser) -> Option<Expr> {
/// Parse an if expresion. /// Parse an if expresion.
fn expr_if(p: &mut Parser) -> Option<Expr> { fn expr_if(p: &mut Parser) -> Option<Expr> {
p.assert(Token::If); p.assert(&[Token::If]);
let mut expr_if = None; let mut expr_if = None;
if let Some(condition) = p.span_if(expr) { if let Some(condition) = p.span_if(expr) {
@ -420,7 +440,7 @@ fn expr_if(p: &mut Parser) -> Option<Expr> {
/// Parse a for expression. /// Parse a for expression.
fn expr_for(p: &mut Parser) -> Option<Expr> { fn expr_for(p: &mut Parser) -> Option<Expr> {
p.assert(Token::For); p.assert(&[Token::For]);
let mut expr_for = None; let mut expr_for = None;
if let Some(pat) = p.span_if(for_pattern) { if let Some(pat) = p.span_if(for_pattern) {

View File

@ -105,9 +105,9 @@ impl<'s> Parser<'s> {
self.repeek(); self.repeek();
match group { match group {
Group::Paren => self.assert(Token::LeftParen), Group::Paren => self.assert(&[Token::LeftParen]),
Group::Bracket => self.assert(Token::LeftBracket), Group::Bracket => self.assert(&[Token::HashBracket, Token::LeftBracket]),
Group::Brace => self.assert(Token::LeftBrace), Group::Brace => self.assert(&[Token::LeftBrace]),
Group::Subheader => {} Group::Subheader => {}
Group::Stmt => {} Group::Stmt => {}
Group::Expr => {} Group::Expr => {}
@ -210,10 +210,10 @@ impl<'s> Parser<'s> {
eaten eaten
} }
/// Consume the next token, debug-asserting that it is the given one. /// Consume the next token, debug-asserting that it is one of the given ones.
pub fn assert(&mut self, t: Token) { pub fn assert(&mut self, ts: &[Token]) {
let next = self.eat(); 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. /// Skip whitespace and comment tokens.

View File

@ -67,12 +67,15 @@ impl<'s> Iterator for Tokens<'s> {
loop { loop {
// Common elements. // Common elements.
return Some(match c { return Some(match c {
// Functions, blocks and terminators. // Blocks and templates.
'[' => Token::LeftBracket, '[' => Token::LeftBracket,
']' => Token::RightBracket, ']' => Token::RightBracket,
'{' => Token::LeftBrace, '{' => Token::LeftBrace,
'}' => Token::RightBrace, '}' => Token::RightBrace,
// Keywords, function templates, colors.
'#' => self.hash(start),
// Whitespace. // Whitespace.
c if c.is_whitespace() => self.whitespace(c), c if c.is_whitespace() => self.whitespace(c),
@ -90,8 +93,8 @@ impl<'s> Iterator for Tokens<'s> {
// Markup. // Markup.
'*' => Token::Star, '*' => Token::Star,
'_' => Token::Underscore, '_' => Token::Underscore,
'=' => Token::Eq,
'~' => Token::Tilde, '~' => Token::Tilde,
'#' => self.hash(start),
'`' => self.raw(), '`' => self.raw(),
'$' => self.math(), '$' => self.math(),
'\\' => self.backslash(), '\\' => self.backslash(),
@ -140,8 +143,7 @@ impl<'s> Iterator for Tokens<'s> {
self.number(start, c) self.number(start, c)
} }
// Hex values and strings. // Strings.
'#' => self.hex(start),
'"' => self.string(), '"' => self.string(),
_ => Token::Invalid(self.s.eaten_from(start)), _ => Token::Invalid(self.s.eaten_from(start)),
@ -151,6 +153,27 @@ impl<'s> Iterator for Tokens<'s> {
} }
impl<'s> 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> { fn whitespace(&mut self, first: char) -> Token<'s> {
// Fast path for just a single space // Fast path for just a single space
if first == ' ' && !self.s.check(|c| c.is_whitespace()) { if first == ' ' && !self.s.check(|c| c.is_whitespace()) {
@ -182,10 +205,10 @@ impl<'s> Tokens<'s> {
c if c.is_whitespace() => true, c if c.is_whitespace() => true,
// Comments. // Comments.
'/' if self.s.check(|c| c == '/' || c == '*') => true, '/' if self.s.check(|c| c == '/' || c == '*') => true,
// Parenthesis. // Parenthesis and hashtag.
'[' | ']' | '{' | '}' => true, '[' | ']' | '{' | '}' | '#' => true,
// Markup. // Markup.
'*' | '_' | '#' | '~' | '`' | '$' => true, '*' | '_' | '=' | '~' | '`' | '$' => true,
// Escaping. // Escaping.
'\\' => true, '\\' => true,
_ => false, _ => false,
@ -198,21 +221,6 @@ impl<'s> Tokens<'s> {
Token::Text(self.s.eaten_from(start)) 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> { fn raw(&mut self) -> Token<'s> {
let mut backticks = 1; let mut backticks = 1;
while self.s.eat_if('`') { while self.s.eat_if('`') {
@ -276,10 +284,10 @@ impl<'s> Tokens<'s> {
match c { match c {
// Backslash and comments. // Backslash and comments.
'\\' | '/' | '\\' | '/' |
// Parenthesis. // Parenthesis and hashtag.
'[' | ']' | '{' | '}' | '[' | ']' | '{' | '}' | '#' |
// Markup. // Markup.
'*' | '_' | '#' | '~' | '`' | '$' => { '*' | '_' | '=' | '~' | '`' | '$' => {
let start = self.s.index(); let start = self.s.index();
self.s.eat_assert(c); self.s.eat_assert(c);
Token::Text(&self.s.eaten_from(start)) 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> { fn string(&mut self) -> Token<'s> {
let mut escaped = false; let mut escaped = false;
Token::Str(TokenStr { Token::Str(TokenStr {
@ -596,8 +592,8 @@ mod tests {
// Test markup tokens. // Test markup tokens.
t!(Markup[" a1"]: "*" => Star); t!(Markup[" a1"]: "*" => Star);
t!(Markup: "_" => Underscore); t!(Markup: "_" => Underscore);
t!(Markup[""]: "###" => Hash, Hash, Hash); t!(Markup[""]: "===" => Eq, Eq, Eq);
t!(Markup["a1/"]: "# " => Hash, Space(0)); t!(Markup["a1/"]: "= " => Eq, Space(0));
t!(Markup: "~" => Tilde); t!(Markup: "~" => Tilde);
t!(Markup[" "]: r"\" => Backslash); t!(Markup[" "]: r"\" => Backslash);
} }
@ -655,10 +651,9 @@ mod tests {
]; ];
for &(s, t) in &both { for &(s, t) in &both {
t!(Code[" "]: format!("#{}", s) => t); t!(Both[" "]: format!("#{}", s) => t);
t!(Markup[" "]: format!("#{}", s) => t); t!(Both[" "]: format!("#{0}#{0}", s) => t, t);
t!(Markup[" "]: format!("#{0}#{0}", s) => t, t); t!(Markup[" /"]: format!("# {}", s) => Token::Invalid("#"), Space(0), Text(s));
t!(Markup[" /"]: format!("# {}", s) => Hash, Space(0), Text(s));
} }
let code = [ let code = [
@ -713,7 +708,7 @@ mod tests {
// Test code symbols in text. // Test code symbols in text.
t!(Markup[" /"]: "a():\"b" => Text("a():\"b")); t!(Markup[" /"]: "a():\"b" => Text("a():\"b"));
t!(Markup[" /"]: ";:,=|/+-" => Text(";:,=|/+-")); t!(Markup[" /"]: ";:,|/+-" => Text(";:,|/+-"));
// Test text ends. // Test text ends.
t!(Markup[""]: "hello " => Text("hello"), Space(0)); 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("~")); 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. // Test unescapable symbols.
t!(Markup[" /"]: r"\a" => Text(r"\"), Text("a")); t!(Markup[" /"]: r"\a" => Text(r"\"), Text("a"));
t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u")); t!(Markup[" /"]: r"\u" => Text(r"\"), Text("u"));
t!(Markup[" /"]: r"\1" => Text(r"\"), Text("1")); 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("="));
t!(Markup[" /"]: r#"\""# => Text(r"\"), Text("\"")); t!(Markup[" /"]: r#"\""# => Text(r"\"), Text("\""));
// Test basic unicode escapes. // Test basic unicode escapes.
@ -947,7 +942,7 @@ mod tests {
t!(Code: "1%%" => Percent(1.0), Invalid("%")); t!(Code: "1%%" => Percent(1.0), Invalid("%"));
// Test invalid keyword. // Test invalid keyword.
t!(Markup[" /"]: "#-" => Hash, Text("-")); t!(Markup[" /"]: "#-" => Invalid("#-"));
t!(Markup[" /"]: "#do" => Invalid("#do")); t!(Markup[" /"]: "#do" => Invalid("#do"));
t!(Code[" /"]: r"#letter" => Invalid(r"#letter")); t!(Code[" /"]: r"#letter" => Invalid(r"#letter"));
} }

View File

@ -43,7 +43,7 @@ pub enum Expr {
Unary(ExprUnary), Unary(ExprUnary),
/// A binary operation: `a + b`. /// A binary operation: `a + b`.
Binary(ExprBinary), Binary(ExprBinary),
/// An invocation of a function: `foo(...)`, `[foo ...]`. /// An invocation of a function: `foo(...)`, `#[foo ...]`.
Call(ExprCall), Call(ExprCall),
/// A let expression: `#let x = 1`. /// A let expression: `#let x = 1`.
Let(ExprLet), Let(ExprLet),
@ -75,11 +75,7 @@ impl Pretty for Expr {
Self::Str(v) => write!(p, "{:?}", &v).unwrap(), Self::Str(v) => write!(p, "{:?}", &v).unwrap(),
Self::Array(v) => v.pretty(p), Self::Array(v) => v.pretty(p),
Self::Dict(v) => v.pretty(p), Self::Dict(v) => v.pretty(p),
Self::Template(v) => { Self::Template(v) => pretty_template(v, p),
p.push_str("[");
v.pretty(p);
p.push_str("]");
}
Self::Group(v) => { Self::Group(v) => {
p.push_str("("); p.push_str("(");
v.v.pretty(p); v.v.pretty(p);
@ -146,6 +142,17 @@ impl Pretty for Named {
/// A template expression: `[*Hi* there!]`. /// A template expression: `[*Hi* there!]`.
pub type ExprTemplate = Tree; 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)`. /// A grouped expression: `(1 + 2)`.
pub type ExprGroup = SpanBox<Expr>; pub type ExprGroup = SpanBox<Expr>;
@ -400,7 +407,7 @@ pub enum Associativity {
Right, Right,
} }
/// An invocation of a function: `foo(...)`, `[foo ...]`. /// An invocation of a function: `foo(...)`, `#[foo ...]`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ExprCall { pub struct ExprCall {
/// The callee of the function. /// 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. /// Pretty print a function template, with body or chaining when possible.
pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) { pub fn pretty_func_template(call: &ExprCall, p: &mut Printer, chained: bool) {
if chained { if chained {
p.push_str(" | "); p.push_str(" | ");
} else { } else {
p.push_str("["); p.push_str("#[");
} }
// Function name. // 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. // 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), .. })] = if let [head @ .., Argument::Pos(Spanned { v: Expr::Template(template), .. })] =
call.args.v.as_slice() 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. // 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() { 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 { } else {
p.push_str("]["); p.push_str("][");
template.pretty(p); template.pretty(p);

View File

@ -60,7 +60,7 @@ mod tests {
roundtrip("hi"); roundtrip("hi");
// Heading. // Heading.
roundtrip("# *Ok*"); roundtrip("= *Ok*");
// Raw. // Raw.
roundtrip("`lang 1`"); roundtrip("`lang 1`");
@ -94,9 +94,9 @@ mod tests {
roundtrip("{(a: 1, b: 2)}"); roundtrip("{(a: 1, b: 2)}");
// Templates. // Templates.
roundtrip("{[]}"); roundtrip("[]");
roundtrip("{[*Ok*]}"); roundtrip("[*Ok*]");
roundtrip("{[[f]]}"); roundtrip("{[f]}");
// Groups. // Groups.
roundtrip("{(1)}"); roundtrip("{(1)}");
@ -105,6 +105,7 @@ mod tests {
roundtrip("{}"); roundtrip("{}");
roundtrip("{1}"); roundtrip("{1}");
roundtrip("{ #let x = 1; x += 2; x + 1 }"); roundtrip("{ #let x = 1; x += 2; x + 1 }");
roundtrip("[{}]");
// Operators. // Operators.
roundtrip("{-x}"); roundtrip("{-x}");
@ -116,14 +117,14 @@ mod tests {
roundtrip("{v(1)}"); roundtrip("{v(1)}");
roundtrip("{v(a: 1, b)}"); roundtrip("{v(a: 1, b)}");
// Bracket calls. // Function templates.
roundtrip("[v]"); roundtrip("#[v]");
roundtrip("[v 1]"); roundtrip("#[v 1]");
roundtrip("[v 1, 2][*Ok*]"); roundtrip("#[v 1, 2][*Ok*]");
roundtrip("[v 1 | f 2]"); roundtrip("#[v 1 | f 2]");
roundtrip("{[[v]]}"); roundtrip("{#[v]}");
test("[v 1, [[f 2]]]", "[v 1 | f 2]"); test("#[v 1, #[f 2]]", "#[v 1 | f 2]");
test("[v 1, 2][[f 3]]", "[v 1, 2 | f 3]"); test("#[v 1, 2][#[f 3]]", "#[v 1, 2 | f 3]");
// Keywords. // Keywords.
roundtrip("#let x = 1 + 2"); roundtrip("#let x = 1 + 2");

View File

@ -36,8 +36,8 @@ impl Pretty for Node {
Self::Raw(raw) => raw.pretty(p), Self::Raw(raw) => raw.pretty(p),
Self::Expr(expr) => { Self::Expr(expr) => {
if let Expr::Call(call) = expr { if let Expr::Call(call) = expr {
// Format bracket calls appropriately. // Format function templates appropriately.
pretty_bracket_call(call, p, false) pretty_func_template(call, p, false)
} else { } else {
expr.pretty(p); expr.pretty(p);
} }
@ -58,7 +58,7 @@ pub struct NodeHeading {
impl Pretty for NodeHeading { impl Pretty for NodeHeading {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
for _ in 0 ..= self.level.v { for _ in 0 ..= self.level.v {
p.push_str("#"); p.push_str("=");
} }
self.contents.pretty(p); self.contents.pretty(p);
} }

View File

@ -6,6 +6,8 @@ use crate::geom::{AngularUnit, LengthUnit};
pub enum Token<'s> { pub enum Token<'s> {
/// A left square bracket: `[`. /// A left square bracket: `[`.
LeftBracket, LeftBracket,
/// A hashtag followed by a left square bracket: `#[`.
HashBracket,
/// A right square bracket: `]`. /// A right square bracket: `]`.
RightBracket, RightBracket,
/// A left curly brace: `{`. /// A left curly brace: `{`.
@ -20,8 +22,8 @@ pub enum Token<'s> {
Star, Star,
/// An underscore: `_`. /// An underscore: `_`.
Underscore, Underscore,
/// A hashtag: `#`. /// A single equals sign: `=`.
Hash, Eq,
/// A tilde: `~`. /// A tilde: `~`.
Tilde, Tilde,
/// A backslash followed by nothing or whitespace: `\`. /// A backslash followed by nothing or whitespace: `\`.
@ -40,8 +42,6 @@ pub enum Token<'s> {
Hyph, Hyph,
/// A slash: `/`. /// A slash: `/`.
Slash, Slash,
/// A single equals sign: `=`.
Eq,
/// Two equals signs: `==`. /// Two equals signs: `==`.
EqEq, EqEq,
/// An exclamation mark followed by an equals sign: `!=`. /// An exclamation mark followed by an equals sign: `!=`.
@ -191,6 +191,7 @@ impl<'s> Token<'s> {
pub fn name(self) -> &'static str { pub fn name(self) -> &'static str {
match self { match self {
Self::LeftBracket => "opening bracket", Self::LeftBracket => "opening bracket",
Self::HashBracket => "start of function template",
Self::RightBracket => "closing bracket", Self::RightBracket => "closing bracket",
Self::LeftBrace => "opening brace", Self::LeftBrace => "opening brace",
Self::RightBrace => "closing brace", Self::RightBrace => "closing brace",
@ -198,7 +199,6 @@ impl<'s> Token<'s> {
Self::RightParen => "closing paren", Self::RightParen => "closing paren",
Self::Star => "star", Self::Star => "star",
Self::Underscore => "underscore", Self::Underscore => "underscore",
Self::Hash => "hashtag",
Self::Tilde => "tilde", Self::Tilde => "tilde",
Self::Backslash => "backslash", Self::Backslash => "backslash",
Self::Comma => "comma", Self::Comma => "comma",

View File

@ -1,23 +1,23 @@
[page width: 450pt, height: 300pt, margins: 1cm] #[page width: 450pt, height: 300pt, margins: 1cm]
[box][ #[box][
*Technische Universität Berlin* \ *Technische Universität Berlin* \
*Fakultät II, Institut for Mathematik* \ *Fakultät II, Institut for Mathematik* \
Sekretariat MA \ Sekretariat MA \
Dr. Max Mustermann \ Dr. Max Mustermann \
Ola Nordmann, John Doe 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][ #[align center][
#### 3. Übungsblatt Computerorientierte Mathematik II [v 2mm] ==== 3. Übungsblatt Computerorientierte Mathematik II #[v 2mm]
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v 2mm] *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #[v 2mm]
*Alle Antworten sind zu beweisen.* *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. 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 Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -1,25 +1,25 @@
// Basic call, whitespace insignificant. // Basic call, whitespace insignificant.
[f], [ 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. // Body and no body.
[f][[f]] #[f][#[f]]
// Lots of potential bodies. // Lots of potential bodies.
[f][f][f] #[f][f]#[f]
// Multi-paragraph body. // Multi-paragraph body.
[box][ #[box][
First First
Second Second
@ -27,81 +27,81 @@
--- ---
// Chained. // Chained.
[f | f] #[f | f]
// Multi-chain. // Multi-chain.
[f|f|f] #[f|f|f]
// With body. // With body.
// Error: 1:6-1:7 expected identifier, found integer // Error: 1:7-1:8 expected identifier, found integer
[f | 1 | box][💕] #[f | 1 | box][💕]
// Error: 2:2-2:2 expected identifier // Error: 2:3-2:3 expected identifier
// Error: 1:3-1:3 expected identifier // Error: 1:4-1:4 expected identifier
[||f true] #[||f true]
// Error: 1:6-1:6 expected identifier // Error: 1:7-1:7 expected identifier
[f 1|] #[f 1|]
// Error: 2:2-2:2 expected identifier // Error: 2:3-2:3 expected identifier
// Error: 1:3-1:3 expected identifier // Error: 1:4-1:4 expected identifier
[|][Nope] #[|][Nope]
// Error: 2:5-2:5 expected closing paren // Error: 2:6-2:6 expected closing paren
// Error: 1:8-1:9 expected expression, found closing paren // Error: 1:9-1:10 expected expression, found closing paren
[f (|f )] #[f (|f )]
// With actual functions. // 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 // Error: 1:5-1:7 expected expression, found end of block comment
[f */] #[f */]
// Error: 1:7-1:8 expected expression, found colon // Error: 1:8-1:9 expected expression, found colon
[f a:1:] #[f a:1:]
// Error: 1:5-1:5 expected comma // Error: 1:6-1:6 expected comma
[f 1 2] #[f 1 2]
// Error: 2:4-2:5 expected identifier // Error: 2:5-2:6 expected identifier
// Error: 1:6-1:6 expected expression // Error: 1:7-1:7 expected expression
[f 1:] #[f 1:]
// Error: 1:4-1:5 expected identifier // Error: 1:5-1:6 expected identifier
[f 1:2] #[f 1:2]
// Error: 1:4-1:7 expected identifier // Error: 1:5-1:8 expected identifier
[f (x):1] #[f (x):1]
--- ---
// Ref: false // Ref: false
// Error: 2:2-2:3 expected function, found string // Error: 2:3-2:4 expected function, found string
#let x = "string" #let x = "string"
[x] #[x]
// Error: 1:2-1:3 expected identifier, found invalid token // Error: 1:3-1:4 expected identifier, found invalid token
[# 1] #[# 1]
// Error: 4:1-4:1 expected identifier // Error: 4:1-4:1 expected identifier
// Error: 3:1-3:1 expected closing bracket // Error: 3:1-3:1 expected closing bracket
[ #[
--- ---
// Ref: false // 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
[) #[)
--- ---
// 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 // Error: 3:1-3:1 expected closing bracket
[f][`a]` #[f][`a]`
--- ---
// Error: 3:1-3:1 expected quote // Error: 3:1-3:1 expected quote
// Error: 2:1-2:1 expected closing bracket // Error: 2:1-2:1 expected closing bracket
[f "] #[f "]

View File

@ -8,7 +8,7 @@ C/*
*/D */D
// Works in headers. // Works in headers.
[f /*1*/ a: "b" // #[f /*1*/ a: "b" //
, 1] , 1]
// End should not appear without start. // End should not appear without start.

View File

@ -5,7 +5,7 @@ _Emph_ and *strong*!
Pa_rtl_y emphasized or str*ength*ened. Pa_rtl_y emphasized or str*ength*ened.
// Scoped to body. // Scoped to body.
[box][*Sco_ped] to body. #[box][*Sco_ped] to body.
// Unterminated is fine. // Unterminated is fine.
_The End _The End

View File

@ -7,75 +7,75 @@
#let error = +"" #let error = +""
// Paren call. // Paren call.
[test f(1), "f(1)"] #[test f(1), "f(1)"]
[test type(1), "integer"] #[test type(1), "integer"]
// Unary operations. // Unary operations.
[test +1, 1] #[test +1, 1]
[test -1, 1-2] #[test -1, 1-2]
[test --1, 1] #[test --1, 1]
// Math operations. // Math operations.
[test "a" + "b", "ab"] #[test "a" + "b", "ab"]
[test 1-4, 3*-1] #[test 1-4, 3*-1]
[test a * b, 8] #[test a * b, 8]
[test 12pt/.4, 30pt] #[test 12pt/.4, 30pt]
[test 1e+2-1e-2, 99.99] #[test 1e+2-1e-2, 99.99]
// Associativity. // Associativity.
[test 1+2+3, 6] #[test 1+2+3, 6]
[test 1/2*3, 1.5] #[test 1/2*3, 1.5]
// Precedence. // Precedence.
[test 1+2*-3, -5] #[test 1+2*-3, -5]
// Short-circuiting logical operators. // Short-circuiting logical operators.
[test not "a" == "b", true] #[test not "a" == "b", true]
[test not 7 < 4 and 10 == 10, true] #[test not 7 < 4 and 10 == 10, true]
[test 3 < 2 or 4 < 5, true] #[test 3 < 2 or 4 < 5, true]
[test false and false or true, true] #[test false and false or true, true]
// Right-hand side not even evaluated. // Right-hand side not even evaluated.
[test false and dont-care, false] #[test false and dont-care, false]
[test true or dont-care, true] #[test true or dont-care, true]
// Equality and inequality. // Equality and inequality.
[test "ab" == "a" + "b", true] #[test "ab" == "a" + "b", true]
[test [*Hi*] == [*Hi*], true] #[test [*Hi*] == [*Hi*], true]
[test "a" != "a", false] #[test "a" != "a", false]
[test [*] != [_], true] #[test [*] != [_], true]
[test (1, 2, 3) == (1, 2) + (3,), true] #[test (1, 2, 3) == (1, 2) + (3,), true]
[test () == (1,), false] #[test () == (1,), false]
[test (a: 1, b: 2) == (b: 2, a: 1), true] #[test (a: 1, b: 2) == (b: 2, a: 1), true]
[test (:) == (a: 1), false] #[test (:) == (a: 1), false]
[test 1 == "hi", false] #[test 1 == "hi", false]
[test 1 == 1.0, true] #[test 1 == 1.0, true]
[test 30% == 30% + 0cm, true] #[test 30% == 30% + 0cm, true]
[test 1in == 0% + 72pt, true] #[test 1in == 0% + 72pt, true]
[test 30% == 30% + 1cm, false] #[test 30% == 30% + 1cm, false]
// Comparisons. // Comparisons.
[test 13 * 3 < 14 * 4, true] #[test 13 * 3 < 14 * 4, true]
[test 5 < 10, true] #[test 5 < 10, true]
[test 5 > 5, false] #[test 5 > 5, false]
[test 5 <= 5, true] #[test 5 <= 5, true]
[test 5 <= 4, false] #[test 5 <= 4, false]
[test 45deg < 1rad, true] #[test 45deg < 1rad, true]
// Assignment. // Assignment.
#let x = "some" #let x = "some"
#let y = "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. // Modify-assign operators.
#let x = 0 #let x = 0
{ x = 10 } [test x, 10] { x = 10 } #[test x, 10]
{ x -= 5 } [test x, 5] { x -= 5 } #[test x, 5]
{ x += 1 } [test x, 6] { x += 1 } #[test x, 6]
{ x *= x } [test x, 36] { x *= x } #[test x, 36]
{ x /= 2.0 } [test x, 18.0] { x /= 2.0 } #[test x, 18.0]
{ x = "some" } [test x, "some"] { x = "some" } #[test x, "some"]
{ x += "thing" } [test x, "something"] { x += "thing" } #[test x, "something"]
// Error: 1:3-1:4 unknown variable // Error: 1:3-1:4 unknown variable
{ z = 1 } { z = 1 }
@ -94,21 +94,21 @@
#let box = ""; { box = "hi" } #let box = ""; { box = "hi" }
// Parentheses. // Parentheses.
[test (a), 2] #[test (a), 2]
[test (2), 2] #[test (2), 2]
[test (1+2)*3, 9] #[test (1+2)*3, 9]
// Error: 1:3-1:3 expected expression // Error: 1:3-1:3 expected expression
{-} {-}
// Error: 1:10-1:10 expected expression // Error: 1:11-1:11 expected expression
[test {1+}, 1] #[test {1+}, 1]
// Error: 1:10-1:10 expected expression // Error: 1:11-1:11 expected expression
[test {2*}, 2] #[test {2*}, 2]
// Error: 1:7-1:16 cannot apply '-' to boolean // Error: 1:8-1:17 cannot apply '-' to boolean
[test -not true, error] #[test -not true, error]
// Error: 1:2-1:8 cannot apply 'not' to array // Error: 1:2-1:8 cannot apply 'not' to array
{not ()} {not ()}

View File

@ -1,48 +1,41 @@
// Test different numbers of hashtags. // Test different numbers of hashtags.
// Valid levels. // Valid levels.
# One = One
### Three === Three
###### Six ====== Six
// Too many hashtags. // Too many hashtags.
// Warning: 1:1-1:8 should not exceed depth 6 // Warning: 1:1-1:8 should not exceed depth 6
####### Seven ======= Seven
--- ---
// Test heading vs. no heading. // Test heading vs. no heading.
// Parsed as headings if at start of the context. // Parsed as headings if at start of the context.
/**/ # Heading /**/ = Heading
{[## Heading]} {[== Heading]}
[box][### Heading] #[box][=== Heading]
// Not at the start of the context. // Not at the start of the context.
Text with # hashtag Text = with=sign
// Escaped. // Escaped.
\# No heading \= No heading
// Keyword.
// Error: 1:1-1:6 unexpected invalid token
#nope
// Not parsed as a keyword, but neither as a heading.
Nr#1
--- ---
// Heading continuation over linebreak. // Heading continuation over linebreak.
// Code blocks continue heading. // Code blocks continue heading.
# This { = This {
"continues" "continues"
} }
// Function call continues heading. // Function call continues heading.
# [box][ = #[box][
This, This,
] too ] too
// Without some kind of block, headings end at a line break. // Without some kind of block, headings end at a line break.
# This = This
not not

View File

@ -52,8 +52,7 @@ a#if x b#if (x)c
a#if true [b] #else c a#if true [b] #else c
// Lone else. // Lone else.
// Error: 2:1-2:6 unexpected keyword `#else` // Error: 1:1-1:6 unexpected keyword `#else`
// Error: 1:8-1:8 expected identifier
#else [] #else []
// Condition must be boolean. If it isn't, neither branch is evaluated. // Condition must be boolean. If it isn't, neither branch is evaluated.

View File

@ -1,10 +1,10 @@
// Automatically initialized with `none`. // Automatically initialized with `none`.
#let x #let x
[test x, none] #[test x, none]
// Initialized with `1`. // Initialized with `1`.
#let y = 1 #let y = 1
[test y, 1] #[test y, 1]
// Initialize with template, not terminated by semicolon in template. // Initialize with template, not terminated by semicolon in template.
#let v = [Hello; there] #let v = [Hello; there]
@ -15,10 +15,10 @@
2, 2,
3, 3,
) )
[test x, (1, 2, 3)] #[test x, (1, 2, 3)]
// Multiple bindings in one line. // 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. // Invalid name.
// Error: 1:6-1:7 expected identifier, found integer // Error: 1:6-1:7 expected identifier, found integer
@ -43,24 +43,24 @@ The Fi#let;rst
// Terminated with just a line break. // Terminated with just a line break.
#let v = "a" #let v = "a"
The Second [test v, "a"] The Second #[test v, "a"]
// Terminated with semicolon + line break. // Terminated with semicolon + line break.
#let v = "a"; #let v = "a";
The Third [test v, "a"] The Third #[test v, "a"]
// Terminated with just a semicolon. // 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. // Terminated by semicolon even though we are in a paren group.
// Error: 2:25-2:25 expected expression // Error: 2:25-2:25 expected expression
// Error: 1:25-1:25 expected closing paren // 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. // Not terminated.
// Error: 1:16-1:16 expected semicolon or line break // 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. // Not terminated.
// Error: 1:16-1:16 expected semicolon or line break // 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

View File

@ -1,4 +1,4 @@
[font 8pt] #[font 8pt]
// Typst syntax inside. // Typst syntax inside.
`#let x = 1``[f 1]` `#let x = 1``[f 1]`

View File

@ -37,4 +37,4 @@
{"a\n[]\"\u{1F680}string"} \ {"a\n[]\"\u{1F680}string"} \
// Templates. // Templates.
{[*{"Hi"} [f 1]*]} {[*{"Hi"} #[f 1]*]}

View File

@ -1,37 +1,37 @@
// Test configuring font properties. // Test configuring font properties.
[font "PT Sans", 10pt] #[font "PT Sans", 10pt]
// Set same font size in three different ways. // Set same font size in three different ways.
[font 20pt][A] #[font 20pt][A]
[font 200%][A] #[font 200%][A]
[font 15pt + 50%][A] #[font 15pt + 50%][A]
// Do nothing. // Do nothing.
[font][Normal] #[font][Normal]
// Set style (is available). // Set style (is available).
[font style: italic][Italic] #[font style: italic][Italic]
// Set weight (is available). // Set weight (is available).
[font weight: bold][Bold] #[font weight: bold][Bold]
// Set stretch (not available, matching closest). // Set stretch (not available, matching closest).
[font stretch: ultra-condensed][Condensed] #[font stretch: ultra-condensed][Condensed]
// Error: 1:7-1:12 unexpected argument // Error: 1:8-1:13 unexpected argument
[font false] #[font false]
// Error: 3:14-3:18 expected font style, found font weight // Error: 3:15-3:19 expected font style, found font weight
// Error: 2:28-2:34 expected font weight, found string // Error: 2:29-2:35 expected font weight, found string
// Error: 1:43-1:44 expected font family or array of font families, found integer // Error: 1:44-1:45 expected font family or array of font families, found integer
[font style: bold, weight: "thin", serif: 0] #[font style: bold, weight: "thin", serif: 0]
// Warning: 1:15-1:19 should be between 100 and 900 // Warning: 1:16-1:20 should be between 100 and 900
[font weight: 2700] #[font weight: 2700]
// Error: 1:7-1:27 unexpected argument // Error: 1:8-1:28 unexpected argument
[font something: "invalid"] #[font something: "invalid"]
--- ---
// Test font fallback and class definitions. // Test font fallback and class definitions.
@ -40,17 +40,17 @@
Emoji: 🏀 Emoji: 🏀
// CMU Serif + Noto Emoji. // CMU Serif + Noto Emoji.
[font "CMU Serif", "Noto Emoji"][ #[font "CMU Serif", "Noto Emoji"][
Emoji: 🏀 Emoji: 🏀
] ]
// Class definitions. // Class definitions.
[font serif: ("CMU Serif", "Latin Modern Math", "Noto Emoji")] #[font serif: ("CMU Serif", "Latin Modern Math", "Noto Emoji")]
[font serif][ #[font serif][
Math: ∫ α + β ➗ 3 Math: ∫ α + β ➗ 3
] ]
// Class definition reused. // Class definition reused.
[font sans-serif: "Noto Emoji"] #[font sans-serif: "Noto Emoji"]
[font sans-serif: ("Archivo", sans-serif)] #[font sans-serif: ("Archivo", sans-serif)]
New sans-serif. 🚀 New sans-serif. 🚀

View File

@ -1,22 +1,22 @@
// Ends paragraphs. // Ends paragraphs.
Tightly [v -5pt] packed Tightly #[v -5pt] packed
// Eating up soft spacing. // Eating up soft spacing.
Inv [h 0pt] isible Inv #[h 0pt] isible
// Multiple spacings in a row. // Multiple spacings in a row.
Add [h 10pt] [h 10pt] up Add #[h 10pt] #[h 10pt] up
// Relative to font size. // Relative to font size.
Relative [h 100%] spacing Relative #[h 100%] spacing
// Missing spacing. // Missing spacing.
// Error: 1:11-1:11 missing argument: spacing // Error: 1:12-1:12 missing argument: spacing
Totally [h] ignored Totally #[h] ignored
// Swapped axes. // Swapped axes.
[page main-dir: rtl, cross-dir: ttb, height: 80pt][ #[page main-dir: rtl, cross-dir: ttb, height: 80pt][
1 [h 1cm] 2 1 #[h 1cm] 2
3 [v 1cm] 4 [v -1cm] 5 3 #[v 1cm] 4 #[v -1cm] 5
] ]

View File

@ -1,39 +1,39 @@
// Test loading different image formats. // Test loading different image formats.
// Load an RGBA PNG image. // Load an RGBA PNG image.
[image "res/rhino.png"] #[image "res/rhino.png"]
[pagebreak] #[pagebreak]
// Load an RGB JPEG image. // Load an RGB JPEG image.
[image "res/tiger.jpg"] #[image "res/tiger.jpg"]
// Error: 1:8-1:29 failed to load image // Error: 1:9-1:30 failed to load image
[image "path/does/not/exist"] #[image "path/does/not/exist"]
// Error: 1:8-1:29 failed to load image // Error: 1:9-1:30 failed to load image
[image "typ/image-error.typ"] #[image "typ/image-error.typ"]
--- ---
// Test configuring the size and fitting behaviour of images. // Test configuring the size and fitting behaviour of images.
// Fit to width of page. // Fit to width of page.
[image "res/rhino.png"] #[image "res/rhino.png"]
// Fit to height of page. // Fit to height of page.
[page height: 40pt][ #[page height: 40pt][
[image "res/rhino.png"] #[image "res/rhino.png"]
] ]
// Set width explicitly. // Set width explicitly.
[image "res/rhino.png", width: 50pt] #[image "res/rhino.png", width: 50pt]
// Set height explicitly. // Set height explicitly.
[image "res/rhino.png", height: 50pt] #[image "res/rhino.png", height: 50pt]
// Set width and height explicitly and force stretching. // 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. // Make sure the bounding-box of the image is correct.
[align bottom, right][ #[align bottom, right][
[image "res/tiger.jpg", width: 60pt] #[image "res/tiger.jpg", width: 60pt]
] ]

View File

@ -1,54 +1,53 @@
// Test configuring page sizes and margins. // Test configuring page sizes and margins.
// Set width and height. // Set width and height.
[page width: 120pt, height: 120pt] #[page width: 120pt, height: 120pt]
[page width: 40pt][High] #[page width: 40pt][High]
[page height: 40pt][Wide] #[page height: 40pt][Wide]
// Set all margins at once. // Set all margins at once.
[page margins: 30pt][ #[page margins: 30pt][
[align top, left][TL] #[align top, left][TL]
[align bottom, right][BR] #[align bottom, right][BR]
] ]
// Set individual margins. // Set individual margins.
[page height: 40pt] #[page height: 40pt]
[page left: 0pt | align left][Left] #[page left: 0pt | align left][Left]
[page right: 0pt | align right][Right] #[page right: 0pt | align right][Right]
[page top: 0pt | align top][Top] #[page top: 0pt | align top][Top]
[page bottom: 0pt | align bottom][Bottom] #[page bottom: 0pt | align bottom][Bottom]
// Ensure that specific margins override general margins. // 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 // Error: 1:8-1:19 unknown variable
[page nonexistant] #[page nonexistant]
// Error: 1:17-1:20 aligned axis // Error: 1:18-1:21 aligned axis
[page main-dir: ltr] #[page main-dir: ltr]
// Flipped predefined paper. // Flipped predefined paper.
[page "a11", flip: true][Flipped A11] #[page "a11", flip: true][Flipped A11]
// Flipped custom page size. // Flipped custom page size.
[page width: 40pt, height: 120pt] #[page width: 40pt, height: 120pt]
[page flip: true] #[page flip: true]
Wide Wide
// Test changing the layouting directions of pages. // 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! Right to left!
--- ---
// Test a combination of pages with bodies and normal content. // Test a combination of pages with bodies and normal content.
[page height: 50pt] #[page height: 50pt]
[page][First] #[page][First]
[page][Second] #[page][Second]
[pagebreak] #[pagebreak]
Fourth Fourth
[page][] #[page][]
Sixth Sixth
[page][Seventh and last] #[page][Seventh and last]

View File

@ -1,3 +1,3 @@
First of two First of two
[pagebreak] #[pagebreak]
[page height: 40pt] #[page height: 40pt]

View File

@ -1,17 +1,17 @@
// Check the output. // Check the output.
[rgb 0.0, 0.3, 0.7] #[rgb 0.0, 0.3, 0.7]
// Alpha channel. // 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: 2:7-2:10 should be between 0.0 and 1.0
// Warning: 1:11-1:15 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] #[rgb -30, 15.5, 0.5]
// Error: 1:6-1:10 missing argument: blue component // Error: 1:7-1:11 missing argument: blue component
[rgb 0, 1] #[rgb 0, 1]
// Error: 3:5-3:5 missing argument: red component // Error: 3:6-3:6 missing argument: red component
// Error: 2:5-2:5 missing argument: green component // Error: 2:6-2:6 missing argument: green component
// Error: 1:5-1:5 missing argument: blue component // Error: 1:6-1:6 missing argument: blue component
[rgb] #[rgb]