summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/env.rs3
-rw-r--r--src/eval/call.rs2
-rw-r--r--src/eval/mod.rs47
-rw-r--r--src/eval/scope.rs2
-rw-r--r--src/layout/par.rs1
-rw-r--r--src/layout/stack.rs1
-rw-r--r--src/library/style.rs4
-rw-r--r--src/parse/collection.rs4
-rw-r--r--src/parse/mod.rs96
-rw-r--r--src/parse/parser.rs102
-rw-r--r--src/parse/tokens.rs5
-rw-r--r--src/syntax/expr.rs32
12 files changed, 207 insertions, 92 deletions
diff --git a/src/env.rs b/src/env.rs
index 5746dd2f..aaa04aa2 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -84,6 +84,9 @@ pub struct ImageResource {
}
impl ImageResource {
+ /// Parse an image resource from raw data in a supported format.
+ ///
+ /// The image format is determined automatically.
pub fn parse(data: Vec<u8>) -> Option<Self> {
let reader = ImageReader::new(Cursor::new(data)).with_guessed_format().ok()?;
let format = reader.format()?;
diff --git a/src/eval/call.rs b/src/eval/call.rs
index 5b0628a8..d57ed144 100644
--- a/src/eval/call.rs
+++ b/src/eval/call.rs
@@ -20,7 +20,7 @@ impl Eval for Spanned<&ExprCall> {
return returned;
} else {
let ty = value.type_name();
- ctx.diag(error!(span, "a value of type {} is not callable", ty));
+ ctx.diag(error!(span, "expected function, found {}", ty));
}
} else if !name.is_empty() {
ctx.diag(error!(span, "unknown function"));
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index c7b87aef..50043698 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -180,14 +180,8 @@ impl Eval for Spanned<&Expr> {
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
Expr::Group(v) => v.as_ref().eval(ctx),
Expr::Block(v) => v.as_ref().eval(ctx),
- Expr::Let(v) => {
- let value = match &v.expr {
- Some(expr) => expr.as_ref().eval(ctx),
- None => Value::None,
- };
- ctx.scopes.define(v.pat.v.as_str(), value);
- Value::None
- }
+ Expr::Let(v) => v.with_span(self.span).eval(ctx),
+ Expr::If(v) => v.with_span(self.span).eval(ctx),
}
}
}
@@ -249,3 +243,40 @@ impl Eval for Spanned<&ExprBinary> {
}
}
}
+
+impl Eval for Spanned<&ExprLet> {
+ type Output = Value;
+
+ fn eval(self, ctx: &mut EvalContext) -> Self::Output {
+ let value = match &self.v.expr {
+ Some(expr) => expr.as_ref().eval(ctx),
+ None => Value::None,
+ };
+ ctx.scopes.define(self.v.pat.v.as_str(), value);
+ Value::None
+ }
+}
+
+impl Eval for Spanned<&ExprIf> {
+ type Output = Value;
+
+ fn eval(self, ctx: &mut EvalContext) -> Self::Output {
+ let condition = self.v.condition.eval(ctx);
+ if let Value::Bool(boolean) = condition {
+ if boolean {
+ self.v.if_body.eval(ctx)
+ } else if let Some(expr) = &self.v.else_body {
+ expr.eval(ctx)
+ } else {
+ Value::None
+ }
+ } else {
+ ctx.diag(error!(
+ self.v.condition.span,
+ "expected boolean, found {}",
+ condition.type_name()
+ ));
+ Value::Error
+ }
+ }
+}
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index 62ee7e40..a93de269 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -42,7 +42,7 @@ pub struct Scope {
}
impl Scope {
- // Create a new empty scope.
+ /// Create a new empty scope.
pub fn new() -> Self {
Self::default()
}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 2a1ad941..3f971e12 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -95,7 +95,6 @@ impl<'a> ParLayouter<'a> {
while !self.areas.current.fits(frame.size) {
if self.areas.in_full_last() {
// TODO: Diagnose once the necessary spans exist.
- let _ = warning!("cannot fit frame into any area");
break;
} else {
self.finish_area();
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index bfb93a94..7d1d7a12 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -80,7 +80,6 @@ impl<'a> StackLayouter<'a> {
while !self.areas.current.fits(frame.size) {
if self.areas.in_full_last() {
// TODO: Diagnose once the necessary spans exist.
- let _ = warning!("cannot fit frame into any area");
break;
} else {
self.finish_area();
diff --git a/src/library/style.rs b/src/library/style.rs
index 2e348440..ee52c97c 100644
--- a/src/library/style.rs
+++ b/src/library/style.rs
@@ -158,7 +158,7 @@ impl_type! {
FontWeight: "font weight",
Value::Int(number) => {
let [min, max] = [Self::THIN, Self::BLACK];
- let message = || format!("must be between {:#?} and {:#?}", min, max);
+ let message = || format!("should be between {:#?} and {:#?}", min, max);
return if number < i64::from(min.to_number()) {
CastResult::Warn(min, message())
} else if number > i64::from(max.to_number()) {
@@ -189,7 +189,7 @@ pub fn rgb(ctx: &mut EvalContext, args: &mut Args) -> Value {
let mut clamp = |component: Option<Spanned<f64>>, default| {
component.map_or(default, |c| {
if c.v < 0.0 || c.v > 1.0 {
- ctx.diag(warning!(c.span, "must be between 0.0 and 1.0"));
+ ctx.diag(warning!(c.span, "should be between 0.0 and 1.0"));
}
(c.v.max(0.0).min(1.0) * 255.0).round() as u8
})
diff --git a/src/parse/collection.rs b/src/parse/collection.rs
index 9addcef0..58fd91ae 100644
--- a/src/parse/collection.rs
+++ b/src/parse/collection.rs
@@ -11,7 +11,7 @@ pub fn arguments(p: &mut Parser) -> ExprArgs {
/// - Dictionary literal
/// - Parenthesized expression
pub fn parenthesized(p: &mut Parser) -> Expr {
- p.start_group(Group::Paren);
+ p.start_group(Group::Paren, TokenMode::Code);
let state = if p.eat_if(Token::Colon) {
collection(p, State::Dict(vec![]))
} else {
@@ -30,7 +30,7 @@ fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
collection.push_arg(p, arg);
if let Some(pos) = missing_coma.take() {
- p.diag_expected_at("comma", pos);
+ p.expected_at("comma", pos);
}
if p.eof() {
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 0a656366..622223fa 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -78,9 +78,8 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
// Keywords.
- Token::Let => {
- return Some(Node::Expr(expr_let(p)?));
- }
+ Token::Let => return Some(Node::Expr(stmt_let(p)?)),
+ Token::If => return Some(Node::Expr(expr_if(p)?)),
// Comments.
Token::LineComment(_) | Token::BlockComment(_) => {
@@ -89,7 +88,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
}
_ => {
- p.diag_unexpected();
+ p.unexpected();
return None;
}
};
@@ -111,7 +110,7 @@ fn heading(p: &mut Parser) -> NodeHeading {
});
if level.v > 5 {
- p.diag(warning!(level.span, "section depth should not exceed 6"));
+ p.diag(warning!(level.span, "should not exceed depth 6"));
level.v = 5;
}
@@ -155,8 +154,7 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
/// Parse a bracketed function call.
fn bracket_call(p: &mut Parser) -> Expr {
- p.push_mode(TokenMode::Code);
- p.start_group(Group::Bracket);
+ p.start_group(Group::Bracket, TokenMode::Code);
// One header is guaranteed, but there may be more (through chaining).
let mut outer = vec![];
@@ -167,7 +165,6 @@ fn bracket_call(p: &mut Parser) -> Expr {
inner = p.span(bracket_subheader);
}
- p.pop_mode();
p.end_group();
if p.peek() == Some(Token::LeftBracket) {
@@ -189,15 +186,15 @@ fn bracket_call(p: &mut Parser) -> Expr {
/// Parse one subheader of a bracketed function call.
fn bracket_subheader(p: &mut Parser) -> ExprCall {
- p.start_group(Group::Subheader);
+ p.start_group(Group::Subheader, TokenMode::Code);
let start = p.next_start();
let name = p.span_if(ident).unwrap_or_else(|| {
let what = "function name";
if p.eof() {
- p.diag_expected_at(what, start);
+ p.expected_at(what, start);
} else {
- p.diag_expected(what);
+ p.expected(what);
}
Ident(String::new()).with_span(start)
});
@@ -210,23 +207,19 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall {
/// Parse the body of a bracketed function call.
fn bracket_body(p: &mut Parser) -> Tree {
- p.push_mode(TokenMode::Markup);
- p.start_group(Group::Bracket);
+ p.start_group(Group::Bracket, TokenMode::Markup);
let tree = tree(p);
- p.pop_mode();
p.end_group();
tree
}
/// Parse a block expression: `{...}`.
fn block(p: &mut Parser) -> Option<Expr> {
- p.push_mode(TokenMode::Code);
- p.start_group(Group::Brace);
+ p.start_group(Group::Brace, TokenMode::Code);
let expr = p.span_if(expr);
while !p.eof() {
- p.diag_unexpected();
+ p.unexpected();
}
- p.pop_mode();
p.end_group();
Some(Expr::Block(Box::new(expr?)))
}
@@ -333,7 +326,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
// No value.
_ => {
- p.diag_expected("expression");
+ p.expected("expression");
return None;
}
};
@@ -343,17 +336,15 @@ fn value(p: &mut Parser) -> Option<Expr> {
// Parse a template value: `[...]`.
fn template(p: &mut Parser) -> Expr {
- p.push_mode(TokenMode::Markup);
- p.start_group(Group::Bracket);
+ p.start_group(Group::Bracket, TokenMode::Markup);
let tree = tree(p);
- p.pop_mode();
p.end_group();
Expr::Template(tree)
}
/// Parse a parenthesized function call.
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
- p.start_group(Group::Paren);
+ p.start_group(Group::Paren, TokenMode::Code);
let args = p.span(arguments);
p.end_group();
Expr::Call(ExprCall { name, args })
@@ -379,36 +370,71 @@ fn color(p: &mut Parser, hex: &str) -> RgbaColor {
/// Parse a string.
fn string(p: &mut Parser, token: TokenStr) -> String {
if !token.terminated {
- p.diag_expected_at("quote", p.peek_span().end);
+ p.expected_at("quote", p.peek_span().end);
}
resolve::resolve_string(token.string)
}
-/// Parse a let expresion.
-fn expr_let(p: &mut Parser) -> Option<Expr> {
- p.push_mode(TokenMode::Code);
+/// Parse a let statement.
+fn stmt_let(p: &mut Parser) -> Option<Expr> {
+ p.start_group(Group::Stmt, TokenMode::Code);
p.eat_assert(Token::Let);
- p.start_group(Group::Expr);
let pat = p.span_if(ident);
let mut rhs = None;
if pat.is_some() {
if p.eat_if(Token::Eq) {
- if let Some(expr) = p.span_if(expr) {
- rhs = Some(Box::new(expr));
- }
+ rhs = p.span_if(expr);
}
} else {
- p.diag_expected("identifier");
+ p.expected("identifier");
}
- p.pop_mode();
if !p.eof() {
- p.diag_expected("semicolon or line break");
+ p.expected_at("semicolon or line break", p.last_end());
}
p.end_group();
- pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs }))
+
+ Some(Expr::Let(ExprLet { pat: pat?, expr: rhs.map(Box::new) }))
+}
+
+/// Parse an if expresion.
+fn expr_if(p: &mut Parser) -> Option<Expr> {
+ p.start_group(Group::Expr, TokenMode::Code);
+ p.eat_assert(Token::If);
+ let condition = p.span_if(expr);
+ p.end_group();
+
+ let condition = Box::new(condition?);
+ let if_body = Box::new(control_body(p)?);
+ let end = p.last_end();
+ p.skip_white();
+
+ let else_body = if p.eat_if(Token::Else) {
+ control_body(p).map(Box::new)
+ } else {
+ p.jump(end);
+ None
+ };
+
+ Some(Expr::If(ExprIf { condition, if_body, else_body }))
+}
+
+/// Parse a control flow body.
+fn control_body(p: &mut Parser) -> Option<Spanned<Expr>> {
+ let start = p.last_end();
+ p.skip_white();
+
+ match p.peek() {
+ Some(Token::LeftBracket) => Some(p.span(template)),
+ Some(Token::LeftBrace) => p.span_if(block),
+ _ => {
+ p.expected_at("body", start);
+ p.jump(start);
+ None
+ }
+ }
}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 8c27c8f7..f9ced34f 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -55,7 +55,7 @@ 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, what: &str) {
+ pub fn expected(&mut self, what: &str) {
let before = self.next_start;
if let Some(found) = self.eat() {
let after = self.last_end;
@@ -66,17 +66,17 @@ impl<'s> Parser<'s> {
found.name(),
));
} else {
- self.diag_expected_at(what, self.next_start);
+ self.expected_at(what, self.next_start);
}
}
- /// Add a diagnostic that the `thing` was expected at the given position.
- pub fn diag_expected_at(&mut self, what: &str, pos: Pos) {
+ /// Add a diagnostic that `what` was expected at the given position.
+ pub fn 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.
- pub fn diag_unexpected(&mut self) {
+ pub fn unexpected(&mut self) {
let before = self.next_start;
if let Some(found) = self.eat() {
let after = self.last_end;
@@ -89,21 +89,7 @@ impl<'s> Parser<'s> {
self.feedback.decos.push(deco);
}
- /// Update the token mode and push the previous mode onto a stack.
- pub fn push_mode(&mut self, mode: TokenMode) {
- self.modes.push(self.tokens.mode());
- self.tokens.set_mode(mode);
- }
-
- /// Pop the topmost token mode from the stack.
- ///
- /// # Panics
- /// This panics if there is no mode on the stack.
- pub fn pop_mode(&mut self) {
- self.tokens.set_mode(self.modes.pop().expect("no pushed mode"));
- }
-
- /// Continues parsing in a group.
+ /// Continue parsing in a group.
///
/// When the end delimiter of the group is reached, all subsequent calls to
/// `eat()` and `peek()` return `None`. Parsing can only continue with
@@ -111,37 +97,55 @@ impl<'s> Parser<'s> {
///
/// # Panics
/// This panics if the next token does not start the given group.
- pub fn start_group(&mut self, group: Group) {
+ pub fn start_group(&mut self, group: Group, mode: TokenMode) {
+ self.modes.push(self.tokens.mode());
+ self.tokens.set_mode(mode);
+
self.groups.push(group);
+ self.repeek();
match group {
Group::Paren => self.eat_assert(Token::LeftParen),
Group::Bracket => self.eat_assert(Token::LeftBracket),
Group::Brace => self.eat_assert(Token::LeftBrace),
- Group::Expr => self.repeek(),
- Group::Subheader => self.repeek(),
+ Group::Subheader => {}
+ Group::Stmt => {}
+ Group::Expr => {}
}
}
- /// Ends the parsing of a group and returns the span of the whole group.
+ /// End the parsing of a group.
///
/// # Panics
/// This panics if no group was started.
pub fn end_group(&mut self) {
+ let prev_mode = self.tokens.mode();
+ self.tokens.set_mode(self.modes.pop().expect("no pushed mode"));
+
let group = self.groups.pop().expect("no started group");
self.repeek();
- let (end, required) = match group {
- Group::Paren => (Token::RightParen, true),
- Group::Bracket => (Token::RightBracket, true),
- Group::Brace => (Token::RightBrace, true),
- Group::Expr => (Token::Semicolon, false),
- Group::Subheader => return,
- };
+ // Eat the end delimiter if there is one.
+ if let Some((end, required)) = match group {
+ Group::Paren => Some((Token::RightParen, true)),
+ Group::Bracket => Some((Token::RightBracket, true)),
+ Group::Brace => Some((Token::RightBrace, true)),
+ Group::Subheader => None,
+ Group::Stmt => Some((Token::Semicolon, false)),
+ Group::Expr => None,
+ } {
+ if self.next == Some(end) {
+ // Bump the delimeter and return. No need to rescan in this case.
+ self.bump();
+ return;
+ } else if required {
+ self.diag(error!(self.next_start, "expected {}", end.name()));
+ }
+ }
- if self.next == Some(end) {
+ // Rescan the peeked token if the mode changed.
+ if self.tokens.mode() != prev_mode {
+ self.tokens.jump(self.last_end);
self.bump();
- } else if required {
- self.diag(error!(self.next_start, "expected {}", end.name()));
}
}
@@ -201,6 +205,18 @@ impl<'s> Parser<'s> {
debug_assert_eq!(next, Some(t));
}
+ /// Skip whitespace and comment tokens.
+ pub fn skip_white(&mut self) {
+ while matches!(
+ self.peek(),
+ Some(Token::Space(_)) |
+ Some(Token::LineComment(_)) |
+ Some(Token::BlockComment(_))
+ ) {
+ self.eat();
+ }
+ }
+
/// Peek at the next token without consuming it.
pub fn peek(&self) -> Option<Token<'s>> {
self.peeked
@@ -243,6 +259,12 @@ impl<'s> Parser<'s> {
self.last_end
}
+ /// Jump to a position in the source string.
+ pub fn jump(&mut self, pos: Pos) {
+ self.tokens.jump(pos);
+ self.bump();
+ }
+
/// Slice a part out of the source string.
pub fn get(&self, span: impl Into<Span>) -> &'s str {
self.tokens.scanner().get(span.into().to_range())
@@ -265,7 +287,7 @@ impl<'s> Parser<'s> {
TokenMode::Code => loop {
match self.next {
Some(Token::Space(n)) => {
- if n >= 1 && self.groups.last() == Some(&Group::Expr) {
+ if n >= 1 && self.groups.last() == Some(&Group::Stmt) {
break;
}
}
@@ -293,8 +315,8 @@ impl<'s> Parser<'s> {
Token::RightParen if self.groups.contains(&Group::Paren) => {}
Token::RightBracket if self.groups.contains(&Group::Bracket) => {}
Token::RightBrace if self.groups.contains(&Group::Brace) => {}
- Token::Semicolon if self.groups.contains(&Group::Expr) => {}
- Token::Space(n) if n >= 1 && self.groups.last() == Some(&Group::Expr) => {}
+ Token::Semicolon if self.groups.contains(&Group::Stmt) => {}
+ Token::Space(n) if n >= 1 && self.groups.last() == Some(&Group::Stmt) => {}
Token::Pipe if self.groups.contains(&Group::Subheader) => {}
_ => return,
}
@@ -319,9 +341,11 @@ pub enum Group {
Bracket,
/// A curly-braced group: `{...}`.
Brace,
- /// A group ended by a semicolon or a line break: `;`, `\n`.
- Expr,
/// A group ended by a chained subheader or a closing bracket:
/// `... >>`, `...]`.
Subheader,
+ /// A group ended by a semicolon or a line break: `;`, `\n`.
+ Stmt,
+ /// A group for a single expression. Not ended by something specific.
+ Expr,
}
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index 68b31a87..312e941b 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -42,6 +42,11 @@ impl<'s> Tokens<'s> {
self.s.index().into()
}
+ /// Jump to the given position.
+ pub fn jump(&mut self, pos: Pos) {
+ self.s.jump(pos.to_usize());
+ }
+
/// The underlying scanner.
pub fn scanner(&self) -> &Scanner<'s> {
&self.s
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 79713cf1..29e143b2 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -46,6 +46,8 @@ pub enum Expr {
Block(ExprBlock),
/// A let expression: `let x = 1`.
Let(ExprLet),
+ /// An if expression: `if x { y } else { z }`.
+ If(ExprIf),
}
impl Pretty for Expr {
@@ -82,6 +84,7 @@ impl Pretty for Expr {
p.push_str("}");
}
Self::Let(v) => v.pretty(p),
+ Self::If(v) => v.pretty(p),
}
}
}
@@ -329,6 +332,30 @@ impl Pretty for ExprLet {
}
}
+/// An if expression: `if x { y } else { z }`.
+#[derive(Debug, Clone, PartialEq)]
+pub struct ExprIf {
+ /// The pattern to assign to.
+ pub condition: Box<Spanned<Expr>>,
+ /// The expression to evaluate if the condition is true.
+ pub if_body: Box<Spanned<Expr>>,
+ /// The expression to evaluate if the condition is false.
+ pub else_body: Option<Box<Spanned<Expr>>>,
+}
+
+impl Pretty for ExprIf {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str("#if ");
+ self.condition.v.pretty(p);
+ p.push_str(" ");
+ self.if_body.v.pretty(p);
+ if let Some(expr) = &self.else_body {
+ p.push_str(" #else ");
+ expr.v.pretty(p);
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::super::tests::test_pretty;
@@ -366,8 +393,9 @@ mod tests {
test_pretty("{(1)}", "{(1)}");
test_pretty("{{1}}", "{{1}}");
- // Let binding.
- test_pretty("#let x=1+2", "#let x = 1 + 2");
+ // Control flow.
+ test_pretty("#let x = 1+2", "#let x = 1 + 2");
+ test_pretty("#if x [y] #else [z]", "#if x [y] #else [z]");
}
#[test]