mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Basic let bindings 🎞
This commit is contained in:
parent
d2ba1b705e
commit
539735e668
@ -46,6 +46,17 @@ pub trait Eval {
|
|||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output;
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Eval for &'a Spanned<T>
|
||||||
|
where
|
||||||
|
Spanned<&'a T>: Eval,
|
||||||
|
{
|
||||||
|
type Output = <Spanned<&'a T> as Eval>::Output;
|
||||||
|
|
||||||
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
|
self.as_ref().eval(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Eval for &[Spanned<Node>] {
|
impl Eval for &[Spanned<Node>] {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
@ -163,6 +174,14 @@ impl Eval for Spanned<&Expr> {
|
|||||||
Expr::Template(v) => Value::Template(v.clone()),
|
Expr::Template(v) => Value::Template(v.clone()),
|
||||||
Expr::Group(v) => v.as_ref().with_span(self.span).eval(ctx),
|
Expr::Group(v) => v.as_ref().with_span(self.span).eval(ctx),
|
||||||
Expr::Block(v) => v.as_ref().with_span(self.span).eval(ctx),
|
Expr::Block(v) => v.as_ref().with_span(self.span).eval(ctx),
|
||||||
|
Expr::Let(v) => {
|
||||||
|
let value = match &v.expr {
|
||||||
|
Some(expr) => expr.as_ref().eval(ctx),
|
||||||
|
None => Value::None,
|
||||||
|
};
|
||||||
|
Rc::make_mut(&mut ctx.state.scope).set(v.pat.v.as_str(), value);
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,7 +190,7 @@ impl Eval for Spanned<&ExprUnary> {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let value = (*self.v.expr).as_ref().eval(ctx);
|
let value = self.v.expr.as_ref().eval(ctx);
|
||||||
|
|
||||||
if let Value::Error = value {
|
if let Value::Error = value {
|
||||||
return Value::Error;
|
return Value::Error;
|
||||||
@ -189,8 +208,8 @@ impl Eval for Spanned<&ExprBinary> {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let lhs = (*self.v.lhs).as_ref().eval(ctx);
|
let lhs = self.v.lhs.as_ref().eval(ctx);
|
||||||
let rhs = (*self.v.rhs).as_ref().eval(ctx);
|
let rhs = self.v.rhs.as_ref().eval(ctx);
|
||||||
|
|
||||||
if lhs == Value::Error || rhs == Value::Error {
|
if lhs == Value::Error || rhs == Value::Error {
|
||||||
return Value::Error;
|
return Value::Error;
|
||||||
|
@ -82,6 +82,11 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Node> {
|
|||||||
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.
|
||||||
|
Token::Let => {
|
||||||
|
return Some(Node::Expr(expr_let(p)?));
|
||||||
|
}
|
||||||
|
|
||||||
// Comments.
|
// Comments.
|
||||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
Token::LineComment(_) | Token::BlockComment(_) => {
|
||||||
p.eat();
|
p.eat();
|
||||||
@ -329,7 +334,7 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
|||||||
Some(Token::Angle(val, unit)) => Expr::Angle(val, unit),
|
Some(Token::Angle(val, unit)) => Expr::Angle(val, unit),
|
||||||
Some(Token::Percent(p)) => Expr::Percent(p),
|
Some(Token::Percent(p)) => Expr::Percent(p),
|
||||||
Some(Token::Hex(hex)) => Expr::Color(color(p, hex)),
|
Some(Token::Hex(hex)) => Expr::Color(color(p, hex)),
|
||||||
Some(Token::Str(token)) => Expr::Str(str(p, token)),
|
Some(Token::Str(token)) => Expr::Str(string(p, token)),
|
||||||
|
|
||||||
// No value.
|
// No value.
|
||||||
_ => {
|
_ => {
|
||||||
@ -377,7 +382,7 @@ fn color(p: &mut Parser, hex: &str) -> RgbaColor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a string.
|
/// Parse a string.
|
||||||
fn str(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.diag_expected_at("quote", p.peek_span().end);
|
||||||
}
|
}
|
||||||
@ -385,5 +390,33 @@ fn str(p: &mut Parser, token: TokenStr) -> String {
|
|||||||
resolve::resolve_string(token.string)
|
resolve::resolve_string(token.string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a let expresion.
|
||||||
|
fn expr_let(p: &mut Parser) -> Option<Expr> {
|
||||||
|
p.push_mode(TokenMode::Code);
|
||||||
|
p.start_group(Group::Terminated);
|
||||||
|
p.eat_assert(Token::Let);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.diag_expected("identifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
while !p.eof() {
|
||||||
|
p.diag_unexpected();
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pop_mode();
|
||||||
|
p.end_group();
|
||||||
|
pat.map(|pat| Expr::Let(ExprLet { pat, expr: rhs }))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -117,6 +117,7 @@ impl<'s> Parser<'s> {
|
|||||||
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::Subheader => {}
|
Group::Subheader => {}
|
||||||
|
Group::Terminated => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.groups.push(group);
|
self.groups.push(group);
|
||||||
@ -139,6 +140,7 @@ impl<'s> Parser<'s> {
|
|||||||
Group::Bracket => Some(Token::RightBracket),
|
Group::Bracket => Some(Token::RightBracket),
|
||||||
Group::Brace => Some(Token::RightBrace),
|
Group::Brace => Some(Token::RightBrace),
|
||||||
Group::Subheader => None,
|
Group::Subheader => None,
|
||||||
|
Group::Terminated => Some(Token::Semicolon),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(token) = end {
|
if let Some(token) = end {
|
||||||
@ -290,6 +292,7 @@ impl<'s> Parser<'s> {
|
|||||||
Some(Token::RightBracket) => Group::Bracket,
|
Some(Token::RightBracket) => Group::Bracket,
|
||||||
Some(Token::RightBrace) => Group::Brace,
|
Some(Token::RightBrace) => Group::Brace,
|
||||||
Some(Token::Pipe) => Group::Subheader,
|
Some(Token::Pipe) => Group::Subheader,
|
||||||
|
Some(Token::Semicolon) => Group::Terminated,
|
||||||
_ => return,
|
_ => return,
|
||||||
}) {
|
}) {
|
||||||
self.peeked = None;
|
self.peeked = None;
|
||||||
@ -316,4 +319,6 @@ pub enum Group {
|
|||||||
/// 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: `;`.
|
||||||
|
Terminated,
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,21 @@ macro_rules! Block {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! Let {
|
||||||
|
(@$pat:expr $(=> $expr:expr)?) => {{
|
||||||
|
#[allow(unused)]
|
||||||
|
let mut expr = None;
|
||||||
|
$(expr = Some(Box::new(into!($expr)));)?
|
||||||
|
Expr::Let(ExprLet {
|
||||||
|
pat: into!($pat).map(|s: &str| Ident(s.into())),
|
||||||
|
expr
|
||||||
|
})
|
||||||
|
}};
|
||||||
|
($($tts:tt)*) => {
|
||||||
|
Node::Expr(Let!(@$($tts)*))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_comments() {
|
fn test_parse_comments() {
|
||||||
// In markup.
|
// In markup.
|
||||||
@ -651,3 +666,26 @@ fn test_parse_values() {
|
|||||||
nodes: [],
|
nodes: [],
|
||||||
errors: [S(1..3, "expected expression, found invalid token")]);
|
errors: [S(1..3, "expected expression, found invalid token")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_let_bindings() {
|
||||||
|
// Basic let.
|
||||||
|
t!("#let x;" Let!("x"));
|
||||||
|
t!("#let _y=1;" Let!("_y" => Int(1)));
|
||||||
|
|
||||||
|
// Followed by text.
|
||||||
|
t!("#let x = 1\n+\n2;\nHi there"
|
||||||
|
Let!("x" => Binary(Int(1), Add, Int(2))),
|
||||||
|
Space, Text("Hi"), Space, Text("there"));
|
||||||
|
|
||||||
|
// Missing semicolon.
|
||||||
|
t!("#let x = a\nHi"
|
||||||
|
nodes: [Let!("x" => Id("a"))],
|
||||||
|
errors: [S(11..13, "unexpected identifier"),
|
||||||
|
S(13..13, "expected semicolon")]);
|
||||||
|
|
||||||
|
// Missing identifier.
|
||||||
|
t!("#let 1;"
|
||||||
|
nodes: [],
|
||||||
|
errors: [S(5..6, "expected identifier, found integer")])
|
||||||
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
loop {
|
loop {
|
||||||
// Common elements.
|
// Common elements.
|
||||||
return Some(match c {
|
return Some(match c {
|
||||||
// Functions and blocks.
|
// Functions, blocks and terminators.
|
||||||
'[' => Token::LeftBracket,
|
'[' => Token::LeftBracket,
|
||||||
']' => Token::RightBracket,
|
']' => Token::RightBracket,
|
||||||
'{' => Token::LeftBrace,
|
'{' => Token::LeftBrace,
|
||||||
@ -112,6 +112,7 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
|
|
||||||
// Length one.
|
// Length one.
|
||||||
',' => Token::Comma,
|
',' => Token::Comma,
|
||||||
|
';' => Token::Semicolon,
|
||||||
':' => Token::Colon,
|
':' => Token::Colon,
|
||||||
'|' => Token::Pipe,
|
'|' => Token::Pipe,
|
||||||
'+' => Token::Plus,
|
'+' => Token::Plus,
|
||||||
@ -575,6 +576,7 @@ mod tests {
|
|||||||
fn test_tokenize_code_symbols() {
|
fn test_tokenize_code_symbols() {
|
||||||
// Test all symbols.
|
// Test all symbols.
|
||||||
t!(Code: "," => Comma);
|
t!(Code: "," => Comma);
|
||||||
|
t!(Code: ";" => Semicolon);
|
||||||
t!(Code: ":" => Colon);
|
t!(Code: ":" => Colon);
|
||||||
t!(Code: "|" => Pipe);
|
t!(Code: "|" => Pipe);
|
||||||
t!(Code: "+" => Plus);
|
t!(Code: "+" => Plus);
|
||||||
@ -682,7 +684,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));
|
||||||
|
@ -44,6 +44,8 @@ pub enum Expr {
|
|||||||
Group(Box<Expr>),
|
Group(Box<Expr>),
|
||||||
/// A block expression: `{1 + 2}`.
|
/// A block expression: `{1 + 2}`.
|
||||||
Block(Box<Expr>),
|
Block(Box<Expr>),
|
||||||
|
/// A let expression: `let x = 1`.
|
||||||
|
Let(ExprLet),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for Expr {
|
impl Pretty for Expr {
|
||||||
@ -79,6 +81,7 @@ impl Pretty for Expr {
|
|||||||
v.pretty(p);
|
v.pretty(p);
|
||||||
p.push_str("}");
|
p.push_str("}");
|
||||||
}
|
}
|
||||||
|
Self::Let(v) => v.pretty(p),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,6 +303,26 @@ impl Pretty for ExprDict {
|
|||||||
/// A template expression: `[*Hi* there!]`.
|
/// A template expression: `[*Hi* there!]`.
|
||||||
pub type ExprTemplate = Tree;
|
pub type ExprTemplate = Tree;
|
||||||
|
|
||||||
|
/// A let expression: `let x = 1`.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ExprLet {
|
||||||
|
/// The pattern to assign to.
|
||||||
|
pub pat: Spanned<Ident>,
|
||||||
|
/// The expression to assign to the pattern.
|
||||||
|
pub expr: Option<Box<Spanned<Expr>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pretty for ExprLet {
|
||||||
|
fn pretty(&self, p: &mut Printer) {
|
||||||
|
p.push_str("#let ");
|
||||||
|
p.push_str(&self.pat.v);
|
||||||
|
if let Some(expr) = &self.expr {
|
||||||
|
p.push_str(" = ");
|
||||||
|
expr.v.pretty(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::super::tests::test_pretty;
|
use super::super::tests::test_pretty;
|
||||||
@ -336,6 +359,9 @@ mod tests {
|
|||||||
// Parens and blocks.
|
// Parens and blocks.
|
||||||
test_pretty("{(1)}", "{(1)}");
|
test_pretty("{(1)}", "{(1)}");
|
||||||
test_pretty("{{1}}", "{{1}}");
|
test_pretty("{{1}}", "{{1}}");
|
||||||
|
|
||||||
|
// Let binding.
|
||||||
|
test_pretty("#let x=1+2", "#let x = 1 + 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -27,6 +27,8 @@ pub enum Token<'s> {
|
|||||||
Backslash,
|
Backslash,
|
||||||
/// A comma: `,`.
|
/// A comma: `,`.
|
||||||
Comma,
|
Comma,
|
||||||
|
/// A semicolon: `;`.
|
||||||
|
Semicolon,
|
||||||
/// A colon: `:`.
|
/// A colon: `:`.
|
||||||
Colon,
|
Colon,
|
||||||
/// A pipe: `|`.
|
/// A pipe: `|`.
|
||||||
@ -201,6 +203,7 @@ impl<'s> Token<'s> {
|
|||||||
Self::Tilde => "tilde",
|
Self::Tilde => "tilde",
|
||||||
Self::Backslash => "backslash",
|
Self::Backslash => "backslash",
|
||||||
Self::Comma => "comma",
|
Self::Comma => "comma",
|
||||||
|
Self::Semicolon => "semicolon",
|
||||||
Self::Colon => "colon",
|
Self::Colon => "colon",
|
||||||
Self::Pipe => "pipe",
|
Self::Pipe => "pipe",
|
||||||
Self::Plus => "plus",
|
Self::Plus => "plus",
|
||||||
|
@ -137,7 +137,7 @@ fn test(
|
|||||||
output: frames,
|
output: frames,
|
||||||
feedback: Feedback { mut diags, .. },
|
feedback: Feedback { mut diags, .. },
|
||||||
} = typeset(&src, Rc::clone(env), state);
|
} = typeset(&src, Rc::clone(env), state);
|
||||||
diags.sort();
|
diags.sort_by_key(|d| d.span);
|
||||||
|
|
||||||
let env = env.borrow();
|
let env = env.borrow();
|
||||||
let canvas = draw(&frames, &env, 2.0);
|
let canvas = draw(&frames, &env, 2.0);
|
||||||
@ -215,7 +215,7 @@ fn parse_metadata(src: &str, map: &LineMap) -> (SpanVec<Diag>, bool) {
|
|||||||
diags.push(Diag::new(level, s.rest().trim()).with_span(start .. end));
|
diags.push(Diag::new(level, s.rest().trim()).with_span(start .. end));
|
||||||
}
|
}
|
||||||
|
|
||||||
diags.sort();
|
diags.sort_by_key(|d| d.span);
|
||||||
|
|
||||||
(diags, compare_ref)
|
(diags, compare_ref)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user