mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
If expressions 🔀
This commit is contained in:
parent
dd246e5bc9
commit
84ba547c7c
@ -84,6 +84,9 @@ pub struct ImageResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn parse(data: Vec<u8>) -> Option<Self> {
|
||||||
let reader = ImageReader::new(Cursor::new(data)).with_guessed_format().ok()?;
|
let reader = ImageReader::new(Cursor::new(data)).with_guessed_format().ok()?;
|
||||||
let format = reader.format()?;
|
let format = reader.format()?;
|
||||||
|
@ -20,7 +20,7 @@ impl Eval for Spanned<&ExprCall> {
|
|||||||
return returned;
|
return returned;
|
||||||
} else {
|
} else {
|
||||||
let ty = value.type_name();
|
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() {
|
} else if !name.is_empty() {
|
||||||
ctx.diag(error!(span, "unknown function"));
|
ctx.diag(error!(span, "unknown function"));
|
||||||
|
@ -180,14 +180,8 @@ impl Eval for Spanned<&Expr> {
|
|||||||
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
|
Expr::Binary(v) => v.with_span(self.span).eval(ctx),
|
||||||
Expr::Group(v) => v.as_ref().eval(ctx),
|
Expr::Group(v) => v.as_ref().eval(ctx),
|
||||||
Expr::Block(v) => v.as_ref().eval(ctx),
|
Expr::Block(v) => v.as_ref().eval(ctx),
|
||||||
Expr::Let(v) => {
|
Expr::Let(v) => v.with_span(self.span).eval(ctx),
|
||||||
let value = match &v.expr {
|
Expr::If(v) => v.with_span(self.span).eval(ctx),
|
||||||
Some(expr) => expr.as_ref().eval(ctx),
|
|
||||||
None => Value::None,
|
|
||||||
};
|
|
||||||
ctx.scopes.define(v.pat.v.as_str(), value);
|
|
||||||
Value::None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -42,7 +42,7 @@ pub struct Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
// Create a new empty scope.
|
/// Create a new empty scope.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,6 @@ impl<'a> ParLayouter<'a> {
|
|||||||
while !self.areas.current.fits(frame.size) {
|
while !self.areas.current.fits(frame.size) {
|
||||||
if self.areas.in_full_last() {
|
if self.areas.in_full_last() {
|
||||||
// TODO: Diagnose once the necessary spans exist.
|
// TODO: Diagnose once the necessary spans exist.
|
||||||
let _ = warning!("cannot fit frame into any area");
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
self.finish_area();
|
self.finish_area();
|
||||||
|
@ -80,7 +80,6 @@ impl<'a> StackLayouter<'a> {
|
|||||||
while !self.areas.current.fits(frame.size) {
|
while !self.areas.current.fits(frame.size) {
|
||||||
if self.areas.in_full_last() {
|
if self.areas.in_full_last() {
|
||||||
// TODO: Diagnose once the necessary spans exist.
|
// TODO: Diagnose once the necessary spans exist.
|
||||||
let _ = warning!("cannot fit frame into any area");
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
self.finish_area();
|
self.finish_area();
|
||||||
|
@ -158,7 +158,7 @@ impl_type! {
|
|||||||
FontWeight: "font weight",
|
FontWeight: "font weight",
|
||||||
Value::Int(number) => {
|
Value::Int(number) => {
|
||||||
let [min, max] = [Self::THIN, Self::BLACK];
|
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()) {
|
return if number < i64::from(min.to_number()) {
|
||||||
CastResult::Warn(min, message())
|
CastResult::Warn(min, message())
|
||||||
} else if number > i64::from(max.to_number()) {
|
} 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| {
|
let mut clamp = |component: Option<Spanned<f64>>, default| {
|
||||||
component.map_or(default, |c| {
|
component.map_or(default, |c| {
|
||||||
if c.v < 0.0 || c.v > 1.0 {
|
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
|
(c.v.max(0.0).min(1.0) * 255.0).round() as u8
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ pub fn arguments(p: &mut Parser) -> ExprArgs {
|
|||||||
/// - Dictionary literal
|
/// - Dictionary literal
|
||||||
/// - Parenthesized expression
|
/// - Parenthesized expression
|
||||||
pub fn parenthesized(p: &mut Parser) -> Expr {
|
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) {
|
let state = if p.eat_if(Token::Colon) {
|
||||||
collection(p, State::Dict(vec![]))
|
collection(p, State::Dict(vec![]))
|
||||||
} else {
|
} else {
|
||||||
@ -30,7 +30,7 @@ fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
|
|||||||
collection.push_arg(p, arg);
|
collection.push_arg(p, arg);
|
||||||
|
|
||||||
if let Some(pos) = missing_coma.take() {
|
if let Some(pos) = missing_coma.take() {
|
||||||
p.diag_expected_at("comma", pos);
|
p.expected_at("comma", pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.eof() {
|
if p.eof() {
|
||||||
|
@ -78,9 +78,8 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
|||||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||||
|
|
||||||
// Keywords.
|
// Keywords.
|
||||||
Token::Let => {
|
Token::Let => return Some(Node::Expr(stmt_let(p)?)),
|
||||||
return Some(Node::Expr(expr_let(p)?));
|
Token::If => return Some(Node::Expr(expr_if(p)?)),
|
||||||
}
|
|
||||||
|
|
||||||
// Comments.
|
// Comments.
|
||||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -111,7 +110,7 @@ fn heading(p: &mut Parser) -> NodeHeading {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if level.v > 5 {
|
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;
|
level.v = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,8 +154,7 @@ fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape) -> String {
|
|||||||
|
|
||||||
/// Parse a bracketed function call.
|
/// Parse a bracketed function call.
|
||||||
fn bracket_call(p: &mut Parser) -> Expr {
|
fn bracket_call(p: &mut Parser) -> Expr {
|
||||||
p.push_mode(TokenMode::Code);
|
p.start_group(Group::Bracket, TokenMode::Code);
|
||||||
p.start_group(Group::Bracket);
|
|
||||||
|
|
||||||
// One header is guaranteed, but there may be more (through chaining).
|
// One header is guaranteed, but there may be more (through chaining).
|
||||||
let mut outer = vec![];
|
let mut outer = vec![];
|
||||||
@ -167,7 +165,6 @@ fn bracket_call(p: &mut Parser) -> Expr {
|
|||||||
inner = p.span(bracket_subheader);
|
inner = p.span(bracket_subheader);
|
||||||
}
|
}
|
||||||
|
|
||||||
p.pop_mode();
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
|
|
||||||
if p.peek() == Some(Token::LeftBracket) {
|
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.
|
/// Parse one subheader of a bracketed function call.
|
||||||
fn bracket_subheader(p: &mut Parser) -> ExprCall {
|
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 start = p.next_start();
|
||||||
let name = p.span_if(ident).unwrap_or_else(|| {
|
let name = p.span_if(ident).unwrap_or_else(|| {
|
||||||
let what = "function name";
|
let what = "function name";
|
||||||
if p.eof() {
|
if p.eof() {
|
||||||
p.diag_expected_at(what, start);
|
p.expected_at(what, start);
|
||||||
} else {
|
} else {
|
||||||
p.diag_expected(what);
|
p.expected(what);
|
||||||
}
|
}
|
||||||
Ident(String::new()).with_span(start)
|
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.
|
/// Parse the body of a bracketed function call.
|
||||||
fn bracket_body(p: &mut Parser) -> Tree {
|
fn bracket_body(p: &mut Parser) -> Tree {
|
||||||
p.push_mode(TokenMode::Markup);
|
p.start_group(Group::Bracket, TokenMode::Markup);
|
||||||
p.start_group(Group::Bracket);
|
|
||||||
let tree = tree(p);
|
let tree = tree(p);
|
||||||
p.pop_mode();
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
tree
|
tree
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a block expression: `{...}`.
|
/// Parse a block expression: `{...}`.
|
||||||
fn block(p: &mut Parser) -> Option<Expr> {
|
fn block(p: &mut Parser) -> Option<Expr> {
|
||||||
p.push_mode(TokenMode::Code);
|
p.start_group(Group::Brace, TokenMode::Code);
|
||||||
p.start_group(Group::Brace);
|
|
||||||
let expr = p.span_if(expr);
|
let expr = p.span_if(expr);
|
||||||
while !p.eof() {
|
while !p.eof() {
|
||||||
p.diag_unexpected();
|
p.unexpected();
|
||||||
}
|
}
|
||||||
p.pop_mode();
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
Some(Expr::Block(Box::new(expr?)))
|
Some(Expr::Block(Box::new(expr?)))
|
||||||
}
|
}
|
||||||
@ -333,7 +326,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
|||||||
|
|
||||||
// No value.
|
// No value.
|
||||||
_ => {
|
_ => {
|
||||||
p.diag_expected("expression");
|
p.expected("expression");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -343,17 +336,15 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
|||||||
|
|
||||||
// Parse a template value: `[...]`.
|
// Parse a template value: `[...]`.
|
||||||
fn template(p: &mut Parser) -> Expr {
|
fn template(p: &mut Parser) -> Expr {
|
||||||
p.push_mode(TokenMode::Markup);
|
p.start_group(Group::Bracket, TokenMode::Markup);
|
||||||
p.start_group(Group::Bracket);
|
|
||||||
let tree = tree(p);
|
let tree = tree(p);
|
||||||
p.pop_mode();
|
|
||||||
p.end_group();
|
p.end_group();
|
||||||
Expr::Template(tree)
|
Expr::Template(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a parenthesized function call.
|
/// Parse a parenthesized function call.
|
||||||
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Expr {
|
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);
|
let args = p.span(arguments);
|
||||||
p.end_group();
|
p.end_group();
|
||||||
Expr::Call(ExprCall { name, args })
|
Expr::Call(ExprCall { name, args })
|
||||||
@ -379,36 +370,71 @@ fn color(p: &mut Parser, hex: &str) -> RgbaColor {
|
|||||||
/// Parse a string.
|
/// Parse a string.
|
||||||
fn string(p: &mut Parser, token: TokenStr) -> String {
|
fn string(p: &mut Parser, token: TokenStr) -> String {
|
||||||
if !token.terminated {
|
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)
|
resolve::resolve_string(token.string)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a let expresion.
|
/// Parse a let statement.
|
||||||
fn expr_let(p: &mut Parser) -> Option<Expr> {
|
fn stmt_let(p: &mut Parser) -> Option<Expr> {
|
||||||
p.push_mode(TokenMode::Code);
|
p.start_group(Group::Stmt, TokenMode::Code);
|
||||||
p.eat_assert(Token::Let);
|
p.eat_assert(Token::Let);
|
||||||
p.start_group(Group::Expr);
|
|
||||||
|
|
||||||
let pat = p.span_if(ident);
|
let pat = p.span_if(ident);
|
||||||
let mut rhs = None;
|
let mut rhs = None;
|
||||||
|
|
||||||
if pat.is_some() {
|
if pat.is_some() {
|
||||||
if p.eat_if(Token::Eq) {
|
if p.eat_if(Token::Eq) {
|
||||||
if let Some(expr) = p.span_if(expr) {
|
rhs = p.span_if(expr);
|
||||||
rhs = Some(Box::new(expr));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.diag_expected("identifier");
|
p.expected("identifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
p.pop_mode();
|
|
||||||
if !p.eof() {
|
if !p.eof() {
|
||||||
p.diag_expected("semicolon or line break");
|
p.expected_at("semicolon or line break", p.last_end());
|
||||||
}
|
}
|
||||||
|
|
||||||
p.end_group();
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
/// Eat the next token and add a diagnostic that it is not the expected
|
/// Eat the next token and add a diagnostic that it is not the expected
|
||||||
/// `thing`.
|
/// `thing`.
|
||||||
pub fn diag_expected(&mut self, what: &str) {
|
pub fn expected(&mut self, what: &str) {
|
||||||
let before = self.next_start;
|
let before = self.next_start;
|
||||||
if let Some(found) = self.eat() {
|
if let Some(found) = self.eat() {
|
||||||
let after = self.last_end;
|
let after = self.last_end;
|
||||||
@ -66,17 +66,17 @@ impl<'s> Parser<'s> {
|
|||||||
found.name(),
|
found.name(),
|
||||||
));
|
));
|
||||||
} else {
|
} 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.
|
/// Add a diagnostic that `what` was expected at the given position.
|
||||||
pub fn diag_expected_at(&mut self, what: &str, pos: Pos) {
|
pub fn expected_at(&mut self, what: &str, pos: Pos) {
|
||||||
self.diag(error!(pos, "expected {}", what));
|
self.diag(error!(pos, "expected {}", what));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Eat the next token and add a diagnostic that it is unexpected.
|
/// 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;
|
let before = self.next_start;
|
||||||
if let Some(found) = self.eat() {
|
if let Some(found) = self.eat() {
|
||||||
let after = self.last_end;
|
let after = self.last_end;
|
||||||
@ -89,21 +89,7 @@ impl<'s> Parser<'s> {
|
|||||||
self.feedback.decos.push(deco);
|
self.feedback.decos.push(deco);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the token mode and push the previous mode onto a stack.
|
/// Continue parsing in a group.
|
||||||
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.
|
|
||||||
///
|
///
|
||||||
/// When the end delimiter of the group is reached, all subsequent calls to
|
/// When the end delimiter of the group is reached, all subsequent calls to
|
||||||
/// `eat()` and `peek()` return `None`. Parsing can only continue with
|
/// `eat()` and `peek()` return `None`. Parsing can only continue with
|
||||||
@ -111,37 +97,55 @@ impl<'s> Parser<'s> {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// This panics if the next token does not start the given group.
|
/// 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.groups.push(group);
|
||||||
|
self.repeek();
|
||||||
match group {
|
match group {
|
||||||
Group::Paren => self.eat_assert(Token::LeftParen),
|
Group::Paren => self.eat_assert(Token::LeftParen),
|
||||||
Group::Bracket => self.eat_assert(Token::LeftBracket),
|
Group::Bracket => self.eat_assert(Token::LeftBracket),
|
||||||
Group::Brace => self.eat_assert(Token::LeftBrace),
|
Group::Brace => self.eat_assert(Token::LeftBrace),
|
||||||
Group::Expr => self.repeek(),
|
Group::Subheader => {}
|
||||||
Group::Subheader => self.repeek(),
|
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
|
/// # Panics
|
||||||
/// This panics if no group was started.
|
/// This panics if no group was started.
|
||||||
pub fn end_group(&mut self) {
|
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");
|
let group = self.groups.pop().expect("no started group");
|
||||||
self.repeek();
|
self.repeek();
|
||||||
|
|
||||||
let (end, required) = match group {
|
// Eat the end delimiter if there is one.
|
||||||
Group::Paren => (Token::RightParen, true),
|
if let Some((end, required)) = match group {
|
||||||
Group::Bracket => (Token::RightBracket, true),
|
Group::Paren => Some((Token::RightParen, true)),
|
||||||
Group::Brace => (Token::RightBrace, true),
|
Group::Bracket => Some((Token::RightBracket, true)),
|
||||||
Group::Expr => (Token::Semicolon, false),
|
Group::Brace => Some((Token::RightBrace, true)),
|
||||||
Group::Subheader => return,
|
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();
|
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));
|
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.
|
/// Peek at the next token without consuming it.
|
||||||
pub fn peek(&self) -> Option<Token<'s>> {
|
pub fn peek(&self) -> Option<Token<'s>> {
|
||||||
self.peeked
|
self.peeked
|
||||||
@ -243,6 +259,12 @@ impl<'s> Parser<'s> {
|
|||||||
self.last_end
|
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.
|
/// Slice a part out of the source string.
|
||||||
pub fn get(&self, span: impl Into<Span>) -> &'s str {
|
pub fn get(&self, span: impl Into<Span>) -> &'s str {
|
||||||
self.tokens.scanner().get(span.into().to_range())
|
self.tokens.scanner().get(span.into().to_range())
|
||||||
@ -265,7 +287,7 @@ impl<'s> Parser<'s> {
|
|||||||
TokenMode::Code => loop {
|
TokenMode::Code => loop {
|
||||||
match self.next {
|
match self.next {
|
||||||
Some(Token::Space(n)) => {
|
Some(Token::Space(n)) => {
|
||||||
if n >= 1 && self.groups.last() == Some(&Group::Expr) {
|
if n >= 1 && self.groups.last() == Some(&Group::Stmt) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,8 +315,8 @@ impl<'s> Parser<'s> {
|
|||||||
Token::RightParen if self.groups.contains(&Group::Paren) => {}
|
Token::RightParen if self.groups.contains(&Group::Paren) => {}
|
||||||
Token::RightBracket if self.groups.contains(&Group::Bracket) => {}
|
Token::RightBracket if self.groups.contains(&Group::Bracket) => {}
|
||||||
Token::RightBrace if self.groups.contains(&Group::Brace) => {}
|
Token::RightBrace if self.groups.contains(&Group::Brace) => {}
|
||||||
Token::Semicolon if self.groups.contains(&Group::Expr) => {}
|
Token::Semicolon if self.groups.contains(&Group::Stmt) => {}
|
||||||
Token::Space(n) if n >= 1 && self.groups.last() == Some(&Group::Expr) => {}
|
Token::Space(n) if n >= 1 && self.groups.last() == Some(&Group::Stmt) => {}
|
||||||
Token::Pipe if self.groups.contains(&Group::Subheader) => {}
|
Token::Pipe if self.groups.contains(&Group::Subheader) => {}
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
@ -319,9 +341,11 @@ pub enum Group {
|
|||||||
Bracket,
|
Bracket,
|
||||||
/// A curly-braced group: `{...}`.
|
/// A curly-braced group: `{...}`.
|
||||||
Brace,
|
Brace,
|
||||||
/// A group ended by a semicolon or a line break: `;`, `\n`.
|
|
||||||
Expr,
|
|
||||||
/// A group ended by a chained subheader or a closing bracket:
|
/// A group ended by a chained subheader or a closing bracket:
|
||||||
/// `... >>`, `...]`.
|
/// `... >>`, `...]`.
|
||||||
Subheader,
|
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,
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,11 @@ impl<'s> Tokens<'s> {
|
|||||||
self.s.index().into()
|
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.
|
/// The underlying scanner.
|
||||||
pub fn scanner(&self) -> &Scanner<'s> {
|
pub fn scanner(&self) -> &Scanner<'s> {
|
||||||
&self.s
|
&self.s
|
||||||
|
@ -46,6 +46,8 @@ pub enum Expr {
|
|||||||
Block(ExprBlock),
|
Block(ExprBlock),
|
||||||
/// A let expression: `let x = 1`.
|
/// A let expression: `let x = 1`.
|
||||||
Let(ExprLet),
|
Let(ExprLet),
|
||||||
|
/// An if expression: `if x { y } else { z }`.
|
||||||
|
If(ExprIf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for Expr {
|
impl Pretty for Expr {
|
||||||
@ -82,6 +84,7 @@ impl Pretty for Expr {
|
|||||||
p.push_str("}");
|
p.push_str("}");
|
||||||
}
|
}
|
||||||
Self::Let(v) => v.pretty(p),
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::super::tests::test_pretty;
|
use super::super::tests::test_pretty;
|
||||||
@ -366,8 +393,9 @@ mod tests {
|
|||||||
test_pretty("{(1)}", "{(1)}");
|
test_pretty("{(1)}", "{(1)}");
|
||||||
test_pretty("{{1}}", "{{1}}");
|
test_pretty("{{1}}", "{{1}}");
|
||||||
|
|
||||||
// Let binding.
|
// Control flow.
|
||||||
test_pretty("#let x=1+2", "#let x = 1 + 2");
|
test_pretty("#let x = 1+2", "#let x = 1 + 2");
|
||||||
|
test_pretty("#if x [y] #else [z]", "#if x [y] #else [z]");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
BIN
tests/lang/ref/if.png
Normal file
BIN
tests/lang/ref/if.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.3 KiB |
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Ref: false
|
// Ref: false
|
||||||
// Error: 2:2-2:3 a value of type string is not callable
|
// Error: 2:2-2:3 expected function, found string
|
||||||
#let x = "string"
|
#let x = "string"
|
||||||
[x]
|
[x]
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
###### Six
|
###### Six
|
||||||
|
|
||||||
// Too many hashtags.
|
// Too many hashtags.
|
||||||
// Warning: 1:1-1:8 section depth should not exceed 6
|
// Warning: 1:1-1:8 should not exceed depth 6
|
||||||
####### Seven
|
####### Seven
|
||||||
|
|
||||||
---
|
---
|
||||||
|
66
tests/lang/typ/if.typ
Normal file
66
tests/lang/typ/if.typ
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#let x = true
|
||||||
|
|
||||||
|
// The two different bodies.
|
||||||
|
#if true [_1_,] #if x {"2"}
|
||||||
|
|
||||||
|
// Braced condition is fine.
|
||||||
|
#if {true} {"3"}
|
||||||
|
|
||||||
|
// Newline between body and else-clause.
|
||||||
|
#if false []
|
||||||
|
#else [4]
|
||||||
|
|
||||||
|
// Multiline (condition needs parens because it's terminated by the line break,
|
||||||
|
// just like the right-hand side of a let-binding).
|
||||||
|
#if
|
||||||
|
x
|
||||||
|
{
|
||||||
|
"Fi" + "ve"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacing is somewhat delicated. We only want to have spacing in the output if
|
||||||
|
// there was whitespace before/after the full if-else statement. In particular,
|
||||||
|
// spacing after a simple if should be retained, but spacing between the first
|
||||||
|
// body and the else should be ignored.
|
||||||
|
a#if true[b]c \
|
||||||
|
a#if true[b] c \
|
||||||
|
a #if true{"b"}c \
|
||||||
|
a #if true{"b"} c \
|
||||||
|
a#if false [?] #else [b]c \
|
||||||
|
a#if true {"b"} #else {"?"} c \
|
||||||
|
|
||||||
|
// Body not evaluated at all if condition is false.
|
||||||
|
#if false { dont-care-about-undefined-variables }
|
||||||
|
|
||||||
|
---
|
||||||
|
#let x = true
|
||||||
|
|
||||||
|
// Needs condition.
|
||||||
|
// Error: 1:6-1:7 expected expression, found closing brace
|
||||||
|
a#if }
|
||||||
|
|
||||||
|
// Needs if-body.
|
||||||
|
// Error: 2:7-2:7 expected body
|
||||||
|
// Error: 1:16-1:16 expected body
|
||||||
|
a#if x b#if (x)c
|
||||||
|
|
||||||
|
// Needs if-body expression.
|
||||||
|
// Error: 1:12-1:12 expected expression
|
||||||
|
a#if true {}
|
||||||
|
|
||||||
|
// Needs else-body.
|
||||||
|
// Error: 1:20-1:20 expected body
|
||||||
|
a#if true [b] #else c
|
||||||
|
|
||||||
|
// Lone else.
|
||||||
|
// Error: 2:1-2:6 unexpected else keyword
|
||||||
|
// Error: 1:8-1:8 expected function name
|
||||||
|
#else []
|
||||||
|
|
||||||
|
// Condition must be boolean. If it isn't, neither branch is evaluated.
|
||||||
|
// Error: 1:5-1:14 expected boolean, found string
|
||||||
|
#if "a" + "b" { "nope" } #else { "nope" }
|
||||||
|
|
||||||
|
// No coercing from empty array or or stuff like that.
|
||||||
|
// Error: 1:5-1:7 expected boolean, found array
|
||||||
|
#if () { "nope" } #else { "nope" }
|
@ -1,5 +1,3 @@
|
|||||||
// Ref: false
|
|
||||||
|
|
||||||
// Automatically initialized with `none`.
|
// Automatically initialized with `none`.
|
||||||
#let x
|
#let x
|
||||||
[eq x, none]
|
[eq x, none]
|
||||||
@ -8,40 +6,7 @@
|
|||||||
#let y = 1
|
#let y = 1
|
||||||
[eq y, 1]
|
[eq y, 1]
|
||||||
|
|
||||||
// Multiple bindings in one line.
|
// Initialize with template, not terminated by semicolon in template.
|
||||||
#let x = "a"; #let y = "b"; [eq x + y, "ab"]
|
|
||||||
|
|
||||||
// No name.
|
|
||||||
// Error: 1:6-1:7 expected identifier, found integer
|
|
||||||
#let 1
|
|
||||||
|
|
||||||
---
|
|
||||||
// Terminated with just a line break.
|
|
||||||
#let v = "a"
|
|
||||||
First
|
|
||||||
[eq v, "a"]
|
|
||||||
|
|
||||||
// Terminated with just a semicolon.
|
|
||||||
#let v = "a"; Second
|
|
||||||
[eq v, "a"]
|
|
||||||
|
|
||||||
// Terminated with semicolon + line break.
|
|
||||||
#let v = "a";
|
|
||||||
Third
|
|
||||||
[eq v, "a"]
|
|
||||||
|
|
||||||
// Terminated by semicolon even though we are in a paren group.
|
|
||||||
// Error: 2:22-2:22 expected expression
|
|
||||||
// Error: 1:22-1:22 expected closing paren
|
|
||||||
#let array = (1, 2 + ;Fourth
|
|
||||||
[eq array, (1, 2)]
|
|
||||||
|
|
||||||
// Not terminated.
|
|
||||||
// Error: 1:14-1:20 expected semicolon or line break, found identifier
|
|
||||||
#let v = "a" Unseen Fifth
|
|
||||||
[eq v, "a"]
|
|
||||||
|
|
||||||
// Not terminated by semicolon in template.
|
|
||||||
#let v = [Hello; there]
|
#let v = [Hello; there]
|
||||||
|
|
||||||
// Not terminated by line break due to parens.
|
// Not terminated by line break due to parens.
|
||||||
@ -51,3 +16,43 @@ Third
|
|||||||
3,
|
3,
|
||||||
)
|
)
|
||||||
[eq x, (1, 2, 3)]
|
[eq x, (1, 2, 3)]
|
||||||
|
|
||||||
|
// Multiple bindings in one line.
|
||||||
|
#let x = "a"; #let y = "b"; [eq x + y, "ab"]
|
||||||
|
|
||||||
|
// Invalid name.
|
||||||
|
// Error: 1:6-1:7 expected identifier, found integer
|
||||||
|
#let 1
|
||||||
|
|
||||||
|
// Terminated by end of line before binding name.
|
||||||
|
// Error: 1:5-1:5 expected identifier
|
||||||
|
#let
|
||||||
|
x = 5
|
||||||
|
|
||||||
|
// No name at all.
|
||||||
|
// Error: 1:11-1:11 expected identifier
|
||||||
|
The Fi#let;rst
|
||||||
|
|
||||||
|
// Terminated with just a line break.
|
||||||
|
#let v = "a"
|
||||||
|
The Second [eq v, "a"]
|
||||||
|
|
||||||
|
// Terminated with semicolon + line break.
|
||||||
|
#let v = "a";
|
||||||
|
The Third [eq v, "a"]
|
||||||
|
|
||||||
|
// Terminated with just a semicolon.
|
||||||
|
The#let v = "a"; Fourth [eq 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 [eq array, (1, 2)]
|
||||||
|
|
||||||
|
// Not terminated.
|
||||||
|
// Error: 1:16-1:16 expected semicolon or line break
|
||||||
|
The#let v = "a"Sixth [eq v, "a"]
|
||||||
|
|
||||||
|
// Not terminated.
|
||||||
|
// Error: 1:16-1:16 expected semicolon or line break
|
||||||
|
The#let v = "a" [eq v, "a"] Seventh
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
// Error: 1:43-1:44 expected font family or array of font families, found integer
|
// Error: 1:43-1:44 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 must be between 100 and 900
|
// Warning: 1:15-1:19 should be between 100 and 900
|
||||||
[font weight: 2700]
|
[font weight: 2700]
|
||||||
|
|
||||||
// Error: 1:7-1:27 unexpected argument
|
// Error: 1:7-1:27 unexpected argument
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
// 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 must be between 0.0 and 1.0
|
// Warning: 2:6-2:9 should be between 0.0 and 1.0
|
||||||
// Warning: 1:11-1:15 must 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]
|
[rgb -30, 15.5, 0.5]
|
||||||
|
|
||||||
// Error: 1:6-1:10 missing argument: blue component
|
// Error: 1:6-1:10 missing argument: blue component
|
||||||
|
Loading…
x
Reference in New Issue
Block a user