mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Evaluate expressions 🧮
This commit is contained in:
parent
95bae5725c
commit
91d14d2a22
@ -2,6 +2,7 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::iter::Extend;
|
||||
use std::ops::Index;
|
||||
|
||||
use crate::syntax::{Span, Spanned};
|
||||
@ -117,9 +118,7 @@ impl<V> Dict<V> {
|
||||
|
||||
/// Iterator over all borrowed keys and values.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
|
||||
self.nums()
|
||||
.map(|(&k, v)| (RefKey::Num(k), v))
|
||||
.chain(self.strs().map(|(k, v)| (RefKey::Str(k), v)))
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
/// Iterate over all values in the dictionary.
|
||||
@ -137,14 +136,6 @@ impl<V> Dict<V> {
|
||||
self.strs.iter()
|
||||
}
|
||||
|
||||
/// Move into an owned iterator over owned keys and values.
|
||||
pub fn into_iter(self) -> impl Iterator<Item = (DictKey, V)> {
|
||||
self.nums
|
||||
.into_iter()
|
||||
.map(|(k, v)| (DictKey::Num(k), v))
|
||||
.chain(self.strs.into_iter().map(|(k, v)| (DictKey::Str(k), v)))
|
||||
}
|
||||
|
||||
/// Move into an owned iterator over all values in the dictionary.
|
||||
pub fn into_values(self) -> impl Iterator<Item = V> {
|
||||
self.nums
|
||||
@ -164,17 +155,6 @@ impl<V> Dict<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> Index<K> for Dict<V>
|
||||
where
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, index: K) -> &Self::Output {
|
||||
self.get(index).expect("key not in dict")
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for Dict<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@ -189,6 +169,68 @@ impl<V: PartialEq> PartialEq for Dict<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> IntoIterator for Dict<V> {
|
||||
type Item = (DictKey, V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IntoIter<u64, V>,
|
||||
fn((u64, V)) -> (DictKey, V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IntoIter<String, V>,
|
||||
fn((String, V)) -> (DictKey, V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let nums = self.nums.into_iter().map((|(k, v)| (DictKey::Num(k), v)) as _);
|
||||
let strs = self.strs.into_iter().map((|(k, v)| (DictKey::Str(k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, V> IntoIterator for &'a Dict<V> {
|
||||
type Item = (RefKey<'a>, &'a V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::Iter<'a, u64, V>,
|
||||
fn((&'a u64, &'a V)) -> (RefKey<'a>, &'a V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::Iter<'a, String, V>,
|
||||
fn((&'a String, &'a V)) -> (RefKey<'a>, &'a V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
|
||||
let nums = self.nums().map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Extend<(DictKey, V)> for Dict<V> {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = (DictKey, V)>,
|
||||
{
|
||||
for (key, value) in iter.into_iter() {
|
||||
self.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> Index<K> for Dict<V>
|
||||
where
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, index: K) -> &Self::Output {
|
||||
self.get(index).expect("key not in dict")
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Debug> Debug for Dict<V> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if self.is_empty() {
|
||||
|
@ -17,6 +17,8 @@ use crate::{DynFuture, Feedback, Pass};
|
||||
/// A computational value.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
/// The result of invalid operations.
|
||||
Error,
|
||||
/// An identifier: `ident`.
|
||||
Ident(Ident),
|
||||
/// A boolean: `true, false`.
|
||||
@ -54,6 +56,7 @@ impl Value {
|
||||
/// The natural-language name of this value for use in error messages.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Error => "error",
|
||||
Self::Ident(_) => "identifier",
|
||||
Self::Bool(_) => "bool",
|
||||
Self::Int(_) => "integer",
|
||||
@ -111,6 +114,7 @@ impl Spanned<Value> {
|
||||
impl Debug for Value {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Error => f.pad("<error>"),
|
||||
Self::Ident(i) => i.fmt(f),
|
||||
Self::Bool(b) => b.fmt(f),
|
||||
Self::Int(i) => i.fmt(f),
|
||||
|
@ -299,6 +299,14 @@ impl DivAssign<f64> for Linear {
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { rel: -self.rel, abs: -self.abs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Linear {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}x + {}", self.rel, self.abs)
|
||||
|
@ -245,9 +245,9 @@ impl<'s> Tokens<'s> {
|
||||
self.s.eat_until(|c| {
|
||||
let end = match c {
|
||||
c if c.is_whitespace() => true,
|
||||
'[' | ']' | '*' | '/' => true,
|
||||
'[' | ']' | '{' | '}' | '*' | '/' | '#' => true,
|
||||
'_' | '`' | '~' | '\\' if body => true,
|
||||
'(' | ')' | '{' | '}' | ':' | ',' | '=' | '"' | '#' if header => true,
|
||||
'(' | ')' | ':' | ',' | '=' | '"' if header => true,
|
||||
'+' | '-' if header && !last_was_e => true,
|
||||
_ => false,
|
||||
};
|
||||
@ -360,19 +360,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tokenize_body_tokens() {
|
||||
t!(Body, "_*" => Underscore, Star);
|
||||
t!(Body, "***" => Star, Star, Star);
|
||||
t!(Body, "a_*" => T("a"), Underscore, Star);
|
||||
t!(Body, "a***" => T("a"), Star, Star, Star);
|
||||
t!(Body, "[func]*bold*" => L, T("func"), R, Star, T("bold"), Star);
|
||||
t!(Body, "hi_you_ there" => T("hi"), Underscore, T("you"), Underscore, S(0), T("there"));
|
||||
t!(Body, "# hi" => Hashtag, S(0), T("hi"));
|
||||
t!(Body, "#()" => Hashtag, T("()"));
|
||||
t!(Body, "ab# hi" => T("ab"), Hashtag, S(0), T("hi"));
|
||||
t!(Body, "#{}" => Hashtag, LB, RB);
|
||||
t!(Body, "{text}" => LB, Text("text"), RB);
|
||||
t!(Header, "_`" => Invalid("_`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize_raw() {
|
||||
// Basics.
|
||||
t!(Body, "`raw`" => Raw("raw", 1, true));
|
||||
t!(Body, "a`raw`" => T("a"), Raw("raw", 1, true));
|
||||
t!(Body, "`[func]`" => Raw("[func]", 1, true));
|
||||
t!(Body, "`]" => Raw("]", 1, false));
|
||||
t!(Body, r"`\`` " => Raw(r"\", 1, true), Raw(" ", 1, false));
|
||||
|
@ -3,7 +3,7 @@
|
||||
use crate::eval::Value;
|
||||
use crate::layout::LayoutContext;
|
||||
use crate::syntax::{Decoration, Ident, Lit, LitDict, SpanWith, Spanned};
|
||||
use crate::Feedback;
|
||||
use crate::{DynFuture, Feedback};
|
||||
|
||||
/// An expression.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -20,13 +20,19 @@ pub enum Expr {
|
||||
|
||||
impl Expr {
|
||||
/// Evaluate the expression to a value.
|
||||
pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value {
|
||||
match self {
|
||||
Self::Lit(lit) => lit.eval(ctx, f).await,
|
||||
Self::Unary(unary) => unary.eval(ctx, f).await,
|
||||
Self::Binary(binary) => binary.eval(ctx, f).await,
|
||||
Self::Call(call) => call.eval(ctx, f).await,
|
||||
}
|
||||
pub fn eval<'a>(
|
||||
&'a self,
|
||||
ctx: &'a LayoutContext<'a>,
|
||||
f: &'a mut Feedback,
|
||||
) -> DynFuture<'a, Value> {
|
||||
Box::pin(async move {
|
||||
match self {
|
||||
Self::Lit(lit) => lit.eval(ctx, f).await,
|
||||
Self::Unary(unary) => unary.eval(ctx, f).await,
|
||||
Self::Binary(binary) => binary.eval(ctx, f).await,
|
||||
Self::Call(call) => call.eval(ctx, f).await,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,9 +47,27 @@ pub struct ExprUnary {
|
||||
|
||||
impl ExprUnary {
|
||||
/// Evaluate the expression to a value.
|
||||
pub async fn eval(&self, _: &LayoutContext<'_>, _: &mut Feedback) -> Value {
|
||||
pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value {
|
||||
use Value::*;
|
||||
|
||||
let value = self.expr.v.eval(ctx, f).await;
|
||||
if value == Error {
|
||||
return Error;
|
||||
}
|
||||
|
||||
let span = self.op.span.join(self.expr.span);
|
||||
match self.op.v {
|
||||
UnOp::Neg => todo!("eval neg"),
|
||||
UnOp::Neg => match value {
|
||||
Int(x) => Int(-x),
|
||||
Float(x) => Float(-x),
|
||||
Length(x) => Length(-x),
|
||||
Relative(x) => Relative(-x),
|
||||
Linear(x) => Linear(-x),
|
||||
v => {
|
||||
error!(@f, span, "cannot negate {}", v.name());
|
||||
Value::Error
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,16 +92,138 @@ pub struct ExprBinary {
|
||||
|
||||
impl ExprBinary {
|
||||
/// Evaluate the expression to a value.
|
||||
pub async fn eval(&self, _: &LayoutContext<'_>, _: &mut Feedback) -> Value {
|
||||
pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value {
|
||||
use crate::geom::Linear as Lin;
|
||||
use Value::*;
|
||||
|
||||
let lhs = self.lhs.v.eval(ctx, f).await;
|
||||
let rhs = self.rhs.v.eval(ctx, f).await;
|
||||
|
||||
if lhs == Error || rhs == Error {
|
||||
return Error;
|
||||
}
|
||||
|
||||
let span = self.lhs.span.join(self.rhs.span);
|
||||
match self.op.v {
|
||||
BinOp::Add => todo!("eval add"),
|
||||
BinOp::Sub => todo!("eval sub"),
|
||||
BinOp::Mul => todo!("eval mul"),
|
||||
BinOp::Div => todo!("eval div"),
|
||||
BinOp::Add => match (lhs, rhs) {
|
||||
// Numbers to themselves.
|
||||
(Int(a), Int(b)) => Int(a + b),
|
||||
(Int(i), Float(f)) | (Float(f), Int(i)) => Float(i as f64 + f),
|
||||
(Float(a), Float(b)) => Float(a + b),
|
||||
|
||||
// Lengths, relatives and linears to themselves.
|
||||
(Length(a), Length(b)) => Length(a + b),
|
||||
(Length(a), Relative(b)) => Linear(Lin::abs(a) + Lin::rel(b)),
|
||||
(Length(a), Linear(b)) => Linear(Lin::abs(a) + b),
|
||||
|
||||
(Relative(a), Length(b)) => Linear(Lin::rel(a) + Lin::abs(b)),
|
||||
(Relative(a), Relative(b)) => Relative(a + b),
|
||||
(Relative(a), Linear(b)) => Linear(Lin::rel(a) + b),
|
||||
|
||||
(Linear(a), Length(b)) => Linear(a + Lin::abs(b)),
|
||||
(Linear(a), Relative(b)) => Linear(a + Lin::rel(b)),
|
||||
(Linear(a), Linear(b)) => Linear(a + b),
|
||||
|
||||
// Complex data types to themselves.
|
||||
(Str(a), Str(b)) => Str(a + &b),
|
||||
(Dict(a), Dict(b)) => Dict(concat(a, b)),
|
||||
(Tree(a), Tree(b)) => Tree(concat(a, b)),
|
||||
(Commands(a), Commands(b)) => Commands(concat(a, b)),
|
||||
|
||||
(a, b) => {
|
||||
error!(@f, span, "cannot add {} and {}", a.name(), b.name());
|
||||
Value::Error
|
||||
}
|
||||
},
|
||||
|
||||
BinOp::Sub => match (lhs, rhs) {
|
||||
// Numbers from themselves.
|
||||
(Int(a), Int(b)) => Int(a - b),
|
||||
(Int(a), Float(b)) => Float(a as f64 - b),
|
||||
(Float(a), Int(b)) => Float(a - b as f64),
|
||||
(Float(a), Float(b)) => Float(a - b),
|
||||
|
||||
// Lengths, relatives and linears from themselves.
|
||||
(Length(a), Length(b)) => Length(a - b),
|
||||
(Length(a), Relative(b)) => Linear(Lin::abs(a) - Lin::rel(b)),
|
||||
(Length(a), Linear(b)) => Linear(Lin::abs(a) - b),
|
||||
(Relative(a), Length(b)) => Linear(Lin::rel(a) - Lin::abs(b)),
|
||||
(Relative(a), Relative(b)) => Relative(a - b),
|
||||
(Relative(a), Linear(b)) => Linear(Lin::rel(a) - b),
|
||||
(Linear(a), Length(b)) => Linear(a - Lin::abs(b)),
|
||||
(Linear(a), Relative(b)) => Linear(a - Lin::rel(b)),
|
||||
(Linear(a), Linear(b)) => Linear(a - b),
|
||||
|
||||
(a, b) => {
|
||||
error!(@f, span, "cannot subtract {1} from {0}", a.name(), b.name());
|
||||
Value::Error
|
||||
}
|
||||
},
|
||||
|
||||
BinOp::Mul => match (lhs, rhs) {
|
||||
// Numbers with themselves.
|
||||
(Int(a), Int(b)) => Int(a * b),
|
||||
(Int(a), Float(b)) => Float(a as f64 * b),
|
||||
(Float(a), Int(b)) => Float(a * b as f64),
|
||||
(Float(a), Float(b)) => Float(a * b),
|
||||
|
||||
// Lengths, relatives and linears with numbers.
|
||||
(Length(a), Int(b)) => Length(a * b as f64),
|
||||
(Length(a), Float(b)) => Length(a * b),
|
||||
(Int(a), Length(b)) => Length(a as f64 * b),
|
||||
(Float(a), Length(b)) => Length(a * b),
|
||||
(Relative(a), Int(b)) => Relative(a * b as f64),
|
||||
(Relative(a), Float(b)) => Relative(a * b),
|
||||
(Int(a), Relative(b)) => Relative(a as f64 * b),
|
||||
(Float(a), Relative(b)) => Relative(a * b),
|
||||
(Linear(a), Int(b)) => Linear(a * b as f64),
|
||||
(Linear(a), Float(b)) => Linear(a * b),
|
||||
(Int(a), Linear(b)) => Linear(a as f64 * b),
|
||||
(Float(a), Linear(b)) => Linear(a * b),
|
||||
|
||||
// Integers with strings.
|
||||
(Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)),
|
||||
(Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)),
|
||||
|
||||
(a, b) => {
|
||||
error!(@f, span, "cannot multiply {} with {}", a.name(), b.name());
|
||||
Value::Error
|
||||
}
|
||||
},
|
||||
|
||||
BinOp::Div => match (lhs, rhs) {
|
||||
// Numbers by themselves.
|
||||
(Int(a), Int(b)) => Float(a as f64 / b as f64),
|
||||
(Int(a), Float(b)) => Float(a as f64 / b),
|
||||
(Float(a), Int(b)) => Float(a / b as f64),
|
||||
(Float(a), Float(b)) => Float(a / b),
|
||||
|
||||
// Lengths by numbers.
|
||||
(Length(a), Int(b)) => Length(a / b as f64),
|
||||
(Length(a), Float(b)) => Length(a / b),
|
||||
(Relative(a), Int(b)) => Relative(a / b as f64),
|
||||
(Relative(a), Float(b)) => Relative(a / b),
|
||||
(Linear(a), Int(b)) => Linear(a / b as f64),
|
||||
(Linear(a), Float(b)) => Linear(a / b),
|
||||
|
||||
(a, b) => {
|
||||
error!(@f, span, "cannot divide {} by {}", a.name(), b.name());
|
||||
Value::Error
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenate two collections.
|
||||
fn concat<T, A>(mut a: T, b: T) -> T
|
||||
where
|
||||
T: Extend<A> + IntoIterator<Item = A>,
|
||||
{
|
||||
a.extend(b);
|
||||
a
|
||||
}
|
||||
|
||||
/// A binary operator.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum BinOp {
|
||||
|
@ -39,11 +39,7 @@ pub enum Lit {
|
||||
|
||||
impl Lit {
|
||||
/// Evaluate the dictionary literal to a dictionary value.
|
||||
pub async fn eval<'a>(
|
||||
&'a self,
|
||||
ctx: &'a LayoutContext<'a>,
|
||||
f: &'a mut Feedback,
|
||||
) -> Value {
|
||||
pub async fn eval(&self, ctx: &LayoutContext<'_>, f: &mut Feedback) -> Value {
|
||||
match *self {
|
||||
Lit::Ident(ref i) => Value::Ident(i.clone()),
|
||||
Lit::Bool(b) => Value::Bool(b),
|
||||
|
Loading…
x
Reference in New Issue
Block a user