summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-16 22:14:27 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-16 22:39:21 +0200
commit30f16bbf6431ca0c174ca0a1abaa6a13ef50ab06 (patch)
treef5a5c0adad15840ebe24b39e77ff467862067c91 /src
parent9f6137d8a829fe8f34554623495fa620252a0184 (diff)
Add Value type and replace dyn-nodes with call-exprs 🏗
- In addition to syntax trees there are now `Value`s, which syntax trees can be evaluated into (e.g. the tree is `5+5` and the value is `10`) - Parsing is completely pure, function calls are not parsed into nodes, but into simple call expressions, which are resolved later - Functions aren't dynamic nodes anymore, but simply functions which receive their arguments as a table and the layouting context - Functions may return any `Value` - Layouting is powered by functions which return the new `Commands` value, which informs the layouting engine what to do - When a function returns a non-`Commands` value, the layouter simply dumps the value into the document in monospace
Diffstat (limited to 'src')
-rw-r--r--src/color.rs131
-rw-r--r--src/compute/mod.rs5
-rw-r--r--src/compute/scope.rs50
-rw-r--r--src/compute/table.rs (renamed from src/table.rs)60
-rw-r--r--src/compute/value.rs474
-rw-r--r--src/diagnostic.rs2
-rw-r--r--src/layout/mod.rs27
-rw-r--r--src/layout/tree.rs105
-rw-r--r--src/lib.rs31
-rw-r--r--src/library/align.rs94
-rw-r--r--src/library/boxed.rs60
-rw-r--r--src/library/font.rs110
-rw-r--r--src/library/mod.rs62
-rw-r--r--src/library/page.rs94
-rw-r--r--src/library/spacing.rs46
-rw-r--r--src/library/val.rs28
-rw-r--r--src/prelude.rs (renamed from src/func.rs)28
-rw-r--r--src/syntax/decoration.rs14
-rw-r--r--src/syntax/expr.rs639
-rw-r--r--src/syntax/mod.rs47
-rw-r--r--src/syntax/parsing.rs201
-rw-r--r--src/syntax/scope.rs44
-rw-r--r--src/syntax/span.rs8
-rw-r--r--src/syntax/tree.rs191
24 files changed, 1231 insertions, 1320 deletions
diff --git a/src/color.rs b/src/color.rs
new file mode 100644
index 00000000..1feca8ef
--- /dev/null
+++ b/src/color.rs
@@ -0,0 +1,131 @@
+//! Color handling.
+
+use std::fmt::{self, Debug, Formatter};
+use std::str::FromStr;
+
+/// An 8-bit RGBA color.
+///
+/// # Example
+/// ```typst
+/// [page: background=#423abaff]
+/// ^^^^^^^^
+/// ```
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct RgbaColor {
+ /// Red channel.
+ pub r: u8,
+ /// Green channel.
+ pub g: u8,
+ /// Blue channel.
+ pub b: u8,
+ /// Alpha channel.
+ pub a: u8,
+ /// This is true if this value was provided as a fail-over by the parser
+ /// because the user-defined value was invalid. This color may be
+ /// overwritten if this property is true.
+ pub healed: bool,
+}
+
+impl RgbaColor {
+ /// Constructs a new color.
+ pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
+ Self { r, g, b, a, healed: false }
+ }
+
+ /// Constructs a new color with the healed property set to true.
+ pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> Self {
+ Self { r, g, b, a, healed: true }
+ }
+}
+
+impl FromStr for RgbaColor {
+ type Err = ParseColorError;
+
+ /// Constructs a new color from a hex string like `7a03c2`. Do not specify a
+ /// leading `#`.
+ fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
+ if !hex_str.is_ascii() {
+ return Err(ParseColorError);
+ }
+
+ let len = hex_str.len();
+ let long = len == 6 || len == 8;
+ let short = len == 3 || len == 4;
+ let alpha = len == 4 || len == 8;
+
+ if !long && !short {
+ return Err(ParseColorError);
+ }
+
+ let mut values: [u8; 4] = [255; 4];
+
+ for elem in if alpha { 0..4 } else { 0..3 } {
+ let item_len = if long { 2 } else { 1 };
+ let pos = elem * item_len;
+
+ let item = &hex_str[pos..(pos+item_len)];
+ values[elem] = u8::from_str_radix(item, 16)
+ .map_err(|_| ParseColorError)?;
+
+ if short {
+ // Duplicate number for shorthand notation, i.e. `a` -> `aa`
+ values[elem] += values[elem] * 16;
+ }
+ }
+
+ Ok(Self::new(values[0], values[1], values[2], values[3]))
+ }
+}
+
+impl Debug for RgbaColor {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if f.alternate() {
+ write!(
+ f, "rgba({:02}, {:02}, {:02}, {:02})",
+ self.r, self.g, self.b, self.a,
+ )?;
+ } else {
+ write!(
+ f, "#{:02x}{:02x}{:02x}{:02x}",
+ self.r, self.g, self.b, self.a,
+ )?;
+ }
+ if self.healed {
+ f.write_str(" [healed]")?;
+ }
+ Ok(())
+ }
+}
+
+/// The error when parsing an `RgbaColor` fails.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct ParseColorError;
+
+impl std::error::Error for ParseColorError {}
+
+impl fmt::Display for ParseColorError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("invalid color")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parse_color_strings() {
+ fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) {
+ assert_eq!(
+ RgbaColor::from_str(hex),
+ Ok(RgbaColor::new(r, g, b, a)),
+ );
+ }
+
+ test("f61243ff", 0xf6, 0x12, 0x43, 0xff);
+ test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff);
+ test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad);
+ test("233", 0x22, 0x33, 0x33, 0xff);
+ test("111b", 0x11, 0x11, 0x11, 0xbb);
+ }
+}
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
new file mode 100644
index 00000000..ac278243
--- /dev/null
+++ b/src/compute/mod.rs
@@ -0,0 +1,5 @@
+//! Building blocks for the computational part.
+
+pub mod scope;
+pub mod table;
+pub mod value;
diff --git a/src/compute/scope.rs b/src/compute/scope.rs
new file mode 100644
index 00000000..1fd4db0b
--- /dev/null
+++ b/src/compute/scope.rs
@@ -0,0 +1,50 @@
+//! Mapping from identifiers to functions.
+
+use std::collections::HashMap;
+use std::fmt::{self, Debug, Formatter};
+
+use super::value::FuncValue;
+
+/// A map from identifiers to functions.
+pub struct Scope {
+ functions: HashMap<String, FuncValue>,
+ fallback: FuncValue,
+}
+
+impl Scope {
+ // Create a new empty scope with a fallback function that is invoked when no
+ // match is found.
+ pub fn new(fallback: FuncValue) -> Self {
+ Self {
+ functions: HashMap::new(),
+ fallback,
+ }
+ }
+
+ /// Associate the given name with the function.
+ pub fn insert(&mut self, name: impl Into<String>, function: FuncValue) {
+ self.functions.insert(name.into(), function);
+ }
+
+ /// Return the function with the given name if there is one.
+ pub fn func(&self, name: &str) -> Option<&FuncValue> {
+ self.functions.get(name)
+ }
+
+ /// Return the function with the given name or the fallback if there is no
+ /// such function.
+ pub fn func_or_fallback(&self, name: &str) -> &FuncValue {
+ self.func(name).unwrap_or_else(|| self.fallback())
+ }
+
+ /// Return the fallback function.
+ pub fn fallback(&self) -> &FuncValue {
+ &self.fallback
+ }
+}
+
+impl Debug for Scope {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_set().entries(self.functions.keys()).finish()
+ }
+}
diff --git a/src/table.rs b/src/compute/table.rs
index 2d07ee5e..f11eacfc 100644
--- a/src/table.rs
+++ b/src/compute/table.rs
@@ -1,17 +1,17 @@
-//! A table data structure.
+//! A key-value map that can also model array-like structures.
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter};
use std::ops::Index;
-/// A table is a key-value map that can also model array-like structures.
-///
-/// An array-like table assigns value to successive indices from `0..n`. The
-/// table type offers special support for this pattern through the `push`
-/// method.
+use crate::syntax::span::{Span, Spanned};
+
+/// A table data structure, which maps from integers (`u64`) or strings to a
+/// generic value type.
///
-/// The keys of a table may be strings or integers (`u64`). The table is generic
-/// over the value type.
+/// The table can be used to model arrays by assigns values to successive
+/// indices from `0..n`. The table type offers special support for this pattern
+/// through the `push` method.
#[derive(Clone)]
pub struct Table<V> {
nums: BTreeMap<u64, V>,
@@ -224,8 +224,8 @@ impl From<String> for OwnedKey {
}
}
-impl From<&str> for OwnedKey {
- fn from(string: &str) -> Self {
+impl From<&'static str> for OwnedKey {
+ fn from(string: &'static str) -> Self {
Self::Str(string.to_string())
}
}
@@ -255,6 +255,46 @@ impl<'a> From<&'a str> for BorrowedKey<'a> {
}
}
+/// An table entry which tracks key and value span.
+#[derive(Clone, PartialEq)]
+pub struct SpannedEntry<V> {
+ pub key: Span,
+ pub val: Spanned<V>,
+}
+
+impl<V> SpannedEntry<V> {
+ /// Create a new entry.
+ pub fn new(key: Span, val: Spanned<V>) -> Self {
+ Self { key, val }
+ }
+
+ /// Create an entry with the same span for key and value.
+ pub fn val(val: Spanned<V>) -> Self {
+ Self { key: Span::ZERO, val }
+ }
+
+ /// Convert from `&SpannedEntry<T>` to `SpannedEntry<&T>`
+ pub fn as_ref(&self) -> SpannedEntry<&V> {
+ SpannedEntry { key: self.key, val: self.val.as_ref() }
+ }
+
+ /// Map the entry to a different value type.
+ pub fn map<U>(self, f: impl FnOnce(V) -> U) -> SpannedEntry<U> {
+ SpannedEntry { key: self.key, val: self.val.map(f) }
+ }
+}
+
+impl<V: Debug> Debug for SpannedEntry<V> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ if f.alternate() {
+ f.write_str("key")?;
+ self.key.fmt(f)?;
+ f.write_str(" ")?;
+ }
+ self.val.fmt(f)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::Table;
diff --git a/src/compute/value.rs b/src/compute/value.rs
new file mode 100644
index 00000000..daa3b17b
--- /dev/null
+++ b/src/compute/value.rs
@@ -0,0 +1,474 @@
+//! Computational values: Syntactical expressions can be evaluated into these.
+
+use std::fmt::{self, Debug, Formatter};
+use std::rc::Rc;
+
+use fontdock::{FontStyle, FontWeight, FontWidth};
+
+use crate::color::RgbaColor;
+use crate::layout::{Commands, Dir, LayoutContext, SpecAlign};
+use crate::length::{Length, ScaleLength};
+use crate::paper::Paper;
+use crate::syntax::span::{Span, Spanned};
+use crate::syntax::tree::SyntaxTree;
+use crate::syntax::Ident;
+use crate::{DynFuture, Feedback, Pass};
+use super::table::{BorrowedKey, SpannedEntry, Table};
+
+/// A computational value.
+#[derive(Clone)]
+pub enum Value {
+ /// An identifier: `ident`.
+ Ident(Ident),
+ /// A string: `"string"`.
+ Str(String),
+ /// A boolean: `true, false`.
+ Bool(bool),
+ /// A number: `1.2, 200%`.
+ Number(f64),
+ /// A length: `2cm, 5.2in`.
+ Length(Length),
+ /// A color value with alpha channel: `#f79143ff`.
+ Color(RgbaColor),
+ /// A table value: `(false, 12cm, greeting="hi")`.
+ Table(TableValue),
+ /// A syntax tree containing typesetting content.
+ Tree(SyntaxTree),
+ /// An executable function.
+ Func(FuncValue),
+ /// Layouting commands.
+ Commands(Commands),
+}
+
+impl Value {
+ /// A natural-language name of the type of this expression, e.g.
+ /// "identifier".
+ pub fn name(&self) -> &'static str {
+ use Value::*;
+ match self {
+ Ident(_) => "identifier",
+ Str(_) => "string",
+ Bool(_) => "bool",
+ Number(_) => "number",
+ Length(_) => "length",
+ Color(_) => "color",
+ Table(_) => "table",
+ Tree(_) => "syntax tree",
+ Func(_) => "function",
+ Commands(_) => "commands",
+ }
+ }
+}
+
+impl Debug for Value {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ use Value::*;
+ match self {
+ Ident(i) => i.fmt(f),
+ Str(s) => s.fmt(f),
+ Bool(b) => b.fmt(f),
+ Number(n) => n.fmt(f),
+ Length(s) => s.fmt(f),
+ Color(c) => c.fmt(f),
+ Table(t) => t.fmt(f),
+ Tree(t) => t.fmt(f),
+ Func(_) => f.pad("<function>"),
+ Commands(c) => c.fmt(f),
+ }
+ }
+}
+
+impl PartialEq for Value {
+ fn eq(&self, other: &Self) -> bool {
+ use Value::*;
+ match (self, other) {
+ (Ident(a), Ident(b)) => a == b,
+ (Str(a), Str(b)) => a == b,
+ (Bool(a), Bool(b)) => a == b,
+ (Number(a), Number(b)) => a == b,
+ (Length(a), Length(b)) => a == b,
+ (Color(a), Color(b)) => a == b,
+ (Table(a), Table(b)) => a == b,
+ (Tree(a), Tree(b)) => a == b,
+ (Func(a), Func(b)) => {
+ a.as_ref() as *const _ == b.as_ref() as *const _
+ }
+ (Commands(a), Commands(b)) => a == b,
+ _ => false,
+ }
+ }
+}
+
+/// An executable function value.
+///
+/// The first argument is a table containing the arguments passed to the
+/// function. The function may be asynchronous (as such it returns a dynamic
+/// future) and it may emit diagnostics, which are contained in the returned
+/// `Pass`. In the end, the function must evaluate to `Value`. Your typical
+/// typesetting function will return a `Commands` value which will instruct the
+/// layouting engine to do what the function pleases.
+///
+/// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable.
+pub type FuncValue = Rc<
+ dyn Fn(TableValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>
+>;
+
+/// A table of values.
+///
+/// # Example
+/// ```typst
+/// (false, 12cm, greeting="hi")
+/// ```
+pub type TableValue = Table<SpannedEntry<Value>>;
+
+impl TableValue {
+ /// Retrieve and remove the matching value with the lowest number key,
+ /// skipping and ignoring all non-matching entries with lower keys.
+ pub fn take<T: TryFromValue>(&mut self) -> Option<T> {
+ for (&key, entry) in self.nums() {
+ let expr = entry.val.as_ref();
+ if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
+ self.remove(key);
+ return Some(val);
+ }
+ }
+ None
+ }
+
+ /// Retrieve and remove the matching value with the lowest number key,
+ /// removing and generating errors for all non-matching entries with lower
+ /// keys.
+ pub fn expect<T: TryFromValue>(&mut self, f: &mut Feedback) -> Option<T> {
+ while let Some((num, _)) = self.first() {
+ let entry = self.remove(num).unwrap();
+ if let Some(val) = T::try_from_value(entry.val.as_ref(), f) {
+ return Some(val);
+ }
+ }
+ None
+ }
+
+ /// Retrieve and remove a matching value associated with the given key if
+ /// there is any.
+ ///
+ /// Generates an error if the key exists but the value does not match.
+ pub fn take_with_key<'a, K, T>(&mut self, key: K, f: &mut Feedback) -> Option<T>
+ where
+ K: Into<BorrowedKey<'a>>,
+ T: TryFromValue,
+ {
+ self.remove(key).and_then(|entry| {
+ let expr = entry.val.as_ref();
+ T::try_from_value(expr, f)
+ })
+ }
+
+ /// Retrieve and remove all matching pairs with number keys, skipping and
+ /// ignoring non-matching entries.
+ ///
+ /// The pairs are returned in order of increasing keys.
+ pub fn take_all_num<'a, T>(&'a mut self) -> impl Iterator<Item = (u64, T)> + 'a
+ where
+ T: TryFromValue,
+ {
+ let mut skip = 0;
+ std::iter::from_fn(move || {
+ for (&key, entry) in self.nums().skip(skip) {
+ let expr = entry.val.as_ref();
+ if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
+ self.remove(key);
+ return Some((key, val));
+ }
+ skip += 1;
+ }
+
+ None
+ })
+ }
+
+
+ /// Retrieve and remove all matching values with number keys, skipping and
+ /// ignoring non-matching entries.
+ ///
+ /// The values are returned in order of increasing keys.
+ pub fn take_all_num_vals<'a, T: 'a>(&'a mut self) -> impl Iterator<Item = T> + 'a
+ where
+ T: TryFromValue,
+ {
+ self.take_all_num::<T>().map(|(_, v)| v)
+ }
+
+ /// Retrieve and remove all matching pairs with string keys, skipping and
+ /// ignoring non-matching entries.
+ ///
+ /// The pairs are returned in order of increasing keys.
+ pub fn take_all_str<'a, T>(&'a mut self) -> impl Iterator<Item = (String, T)> + 'a
+ where
+ T: TryFromValue,
+ {
+ let mut skip = 0;
+ std::iter::from_fn(move || {
+ for (key, entry) in self.strs().skip(skip) {
+ let expr = entry.val.as_ref();
+ if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
+ let key = key.clone();
+ self.remove(&key);
+ return Some((key, val));
+ }
+ skip += 1;
+ }
+
+ None
+ })
+ }
+
+ /// Generated `"unexpected argument"` errors for all remaining entries.
+ pub fn unexpected(&self, f: &mut Feedback) {
+ for entry in self.values() {
+ let span = Span::merge(entry.key, entry.val.span);
+ error!(@f, span, "unexpected argument");
+ }
+ }
+}
+
+/// A trait for converting values into more specific types.
+pub trait TryFromValue: Sized {
+ // This trait takes references because we don't want to move the value
+ // out of its origin in case this returns `None`. This solution is not
+ // perfect because we need to do some cloning in the impls for this trait,
+ // but we haven't got a better solution, for now.
+
+ /// Try to convert a value to this type.
+ ///
+ /// Returns `None` and generates an appropriate error if the value is not
+ /// valid for this type.
+ fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self>;
+}
+
+macro_rules! impl_match {
+ ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
+ impl TryFromValue for $type {
+ fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
+ #[allow(unreachable_patterns)]
+ match value.v {
+ $($p => Some($r)),*,
+ other => {
+ error!(
+ @f, value.span,
+ "expected {}, found {}", $name, other.name()
+ );
+ None
+ }
+ }
+ }
+ }
+ };
+}
+
+macro_rules! impl_ident {
+ ($type:ty, $name:expr, $parse:expr) => {
+ impl TryFromValue for $type {
+ fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
+ if let Value::Ident(ident) = value.v {
+ let val = $parse(ident.as_str());
+ if val.is_none() {
+ error!(@f, value.span, "invalid {}", $name);
+ }
+ val
+ } else {
+ error!(
+ @f, value.span,
+ "expected {}, found {}", $name, value.v.name()
+ );
+ None
+ }
+ }
+ }
+ };
+}
+
+impl<T: TryFromValue> TryFromValue for Spanned<T> {
+ fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
+ let span = value.span;
+ T::try_from_value(value, f).map(|v| Spanned { v, span })
+ }
+}
+
+impl_match!(Value, "value", v => v.clone());
+impl_match!(Ident, "identifier", Value::Ident(i) => i.clone());
+impl_match!(String, "string", Value::Str(s) => s.clone());
+impl_match!(bool, "bool", &Value::Bool(b) => b);
+impl_match!(f64, "number", &Value::Number(n) => n);
+impl_match!(Length, "length", &Value::Length(l) => l);
+impl_match!(SyntaxTree, "tree", Value::Tree(t) => t.clone());
+impl_match!(TableValue, "table", Value::Table(t) => t.clone());
+impl_match!(FuncValue, "function", Value::Func(f) => f.clone());
+impl_match!(ScaleLength, "number or length",
+ &Value::Length(length) => ScaleLength::Absolute(length),
+ &Value::Number(scale) => ScaleLength::Scaled(scale),
+);
+
+/// A value type that matches identifiers and strings and implements
+/// `Into<String>`.
+pub struct StringLike(pub String);
+
+impl From<StringLike> for String {
+ fn from(like: StringLike) -> String {
+ like.0
+ }
+}
+
+impl_match!(StringLike, "identifier or string",
+ Value::Ident(Ident(s)) => StringLike(s.clone()),
+ Value::Str(s) => StringLike(s.clone()),
+);
+
+impl_ident!(Dir, "direction", |s| match s {
+ "ltr" => Some(Self::LTR),
+ "rtl" => Some(Self::RTL),
+ "ttb" => Some(Self::TTB),
+ "btt" => Some(Self::BTT),
+ _ => None,
+});
+
+impl_ident!(SpecAlign, "alignment", |s| match s {
+ "left" => Some(Self::Left),
+ "right" => Some(Self::Right),
+ "top" => Some(Self::Top),
+ "bottom" => Some(Self::Bottom),
+ "center" => Some(Self::Center),
+ _ => None,
+});
+
+impl_ident!(FontStyle, "font style", FontStyle::from_name);
+impl_ident!(Paper, "paper", Paper::from_name);
+
+impl TryFromValue for FontWeight {
+ fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
+ match value.v {
+ &Value::Number(weight) => {
+ const MIN: u16 = 100;
+ const MAX: u16 = 900;
+
+ Some(Self(if weight < MIN as f64 {
+ error!(@f, value.span, "the minimum font weight is {}", MIN);
+ MIN
+ } else if weight > MAX as f64 {
+ error!(@f, value.span, "the maximum font weight is {}", MAX);
+ MAX
+ } else {
+ weight.round() as u16
+ }))
+ }
+ Value::Ident(ident) => {
+ let weight = Self::from_name(ident.as_str());
+ if weight.is_none() {
+ error!(@f, value.span, "invalid font weight");
+ }
+ weight
+ }
+ other => {
+ error!(
+ @f, value.span,
+ "expected font weight (name or number), found {}",
+ other.name(),
+ );
+ None
+ }
+ }
+ }
+}
+
+impl TryFromValue for FontWidth {
+ fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
+ match value.v {
+ &Value::Number(width) => {
+ const MIN: u16 = 1;
+ const MAX: u16 = 9;
+
+ Self::new(if width < MIN as f64 {
+ error!(@f, value.span, "the minimum font width is {}", MIN);
+ MIN
+ } else if width > MAX as f64 {
+ error!(@f, value.span, "the maximum font width is {}", MAX);
+ MAX
+ } else {
+ width.round() as u16
+ })
+ }
+ Value::Ident(ident) => {
+ let width = Self::from_name(ident.as_str());
+ if width.is_none() {
+ error!(@f, value.span, "invalid font width");
+ }
+ width
+ }
+ other => {
+ error!(
+ @f, value.span,
+ "expected font width (name or number), found {}",
+ other.name(),
+ );
+ None
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn entry(value: Value) -> SpannedEntry<Value> {
+ SpannedEntry::val(Spanned::zero(value))
+ }
+
+ #[test]
+ fn test_table_take_removes_correct_entry() {
+ let mut table = Table::new();
+ table.insert(1, entry(Value::Bool(false)));
+ table.insert(2, entry(Value::Str("hi".to_string())));
+ assert_eq!(table.take::<String>(), Some("hi".to_string()));
+ assert_eq!(table.len(), 1);
+ assert_eq!(table.take::<bool>(), Some(false));
+ assert!(table.is_empty());
+ }
+
+ #[test]
+ fn test_table_expect_errors_about_previous_entries() {
+ let mut f = Feedback::new();
+ let mut table = Table::new();
+ table.insert(1, entry(Value::Bool(false)));
+ table.insert(3, entry(Value::Str("hi".to_string())));
+ table.insert(5, entry(Value::Bool(true)));
+ assert_eq!(table.expect::<String>(&mut f), Some("hi".to_string()));
+ assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected string, found bool")]);
+ assert_eq!(table.len(), 1);
+ }
+
+ #[test]
+ fn test_table_take_with_key_removes_the_entry() {
+ let mut f = Feedback::new();
+ let mut table = Table::new();
+ table.insert(1, entry(Value::Bool(false)));
+ table.insert("hi", entry(Value::Bool(true)));
+ assert_eq!(table.take_with_key::<_, bool>(1, &mut f), Some(false));
+ assert_eq!(table.take_with_key::<_, f64>("hi", &mut f), None);
+ assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected number, found bool")]);
+ assert!(table.is_empty());
+ }
+
+ #[test]
+ fn test_table_take_all_removes_the_correct_entries() {
+ let mut table = Table::new();
+ table.insert(1, entry(Value::Bool(false)));
+ table.insert(3, entry(Value::Number(0.0)));
+ table.insert(7, entry(Value::Bool(true)));
+ assert_eq!(
+ table.take_all_num::<bool>().collect::<Vec<_>>(),
+ [(1, false), (7, true)],
+ );
+ assert_eq!(table.len(), 1);
+ assert_eq!(table[3].val.v, Value::Number(0.0));
+ }
+}
diff --git a/src/diagnostic.rs b/src/diagnostic.rs
index 2c649dac..834cd928 100644
--- a/src/diagnostic.rs
+++ b/src/diagnostic.rs
@@ -13,7 +13,7 @@ use crate::syntax::span::SpanVec;
pub type Diagnostics = SpanVec<Diagnostic>;
/// A diagnostic that arose in parsing or layouting.
-#[derive(Debug, Clone, Eq, PartialEq)]
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Diagnostic {
/// How severe / important the diagnostic is.
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 5f5a4859..837c19ec 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -10,9 +10,7 @@ mod tree;
/// Basic types used across the layouting engine.
pub mod prelude {
pub use super::primitive::*;
- pub use super::{
- BoxLayout, layout, Layout, LayoutContext, LayoutSpace, MultiLayout,
- };
+ pub use super::{BoxLayout, layout, LayoutContext, LayoutSpace, MultiLayout};
pub use Dir::*;
pub use GenAlign::*;
pub use GenAxis::*;
@@ -23,13 +21,11 @@ pub mod prelude {
pub use primitive::*;
pub use tree::layout_tree as layout;
-use async_trait::async_trait;
-
+use crate::compute::scope::Scope;
use crate::font::SharedFontLoader;
use crate::geom::{Margins, Size};
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::tree::SyntaxTree;
-use crate::Pass;
use elements::LayoutElements;
use prelude::*;
@@ -48,18 +44,13 @@ pub struct BoxLayout {
pub elements: LayoutElements,
}
-/// Command-based layouting.
-#[async_trait(?Send)]
-pub trait Layout {
- /// Create a sequence of layouting commands to execute.
- async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
-}
-
/// The context for layouting.
-#[derive(Debug, Clone)]
+#[derive(Debug)]
pub struct LayoutContext<'a> {
/// The font loader to query fonts from when typesetting text.
pub loader: &'a SharedFontLoader,
+ /// The function scope.
+ pub scope: &'a Scope,
/// The style for pages and text.
pub style: &'a LayoutStyle,
/// The unpadded size of this container (the base 100% for relative sizes).
@@ -118,11 +109,11 @@ impl LayoutSpace {
}
/// A sequence of layouting commands.
-pub type Commands<'a> = Vec<Command<'a>>;
+pub type Commands = Vec<Command>;
/// Commands executable by the layouting engine.
-#[derive(Debug, Clone)]
-pub enum Command<'a> {
+#[derive(Debug, Clone, PartialEq)]
+pub enum Command {
/// Layout the given tree in the current context (i.e. not nested). The
/// content of the tree is not laid out into a separate box and then added,
/// but simply laid out flatly in the active layouting process.
@@ -130,7 +121,7 @@ pub enum Command<'a> {
/// This has the effect that the content fits nicely into the active line
/// layouting, enabling functions to e.g. change the style of some piece of
/// text while keeping it part of the current paragraph.
- LayoutSyntaxTree(&'a SyntaxTree),
+ LayoutSyntaxTree(SyntaxTree),
/// Add a finished layout.
Add(BoxLayout),
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index 3abdc934..39e111bd 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -1,9 +1,10 @@
//! Layouting of syntax trees.
+use crate::compute::value::Value;
use crate::style::LayoutStyle;
use crate::syntax::decoration::Decoration;
-use crate::syntax::span::{Span, Spanned};
-use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
+use crate::syntax::span::{Offset, Span, Spanned};
+use crate::syntax::tree::{CallExpr, SyntaxNode, SyntaxTree};
use crate::{DynFuture, Feedback, Pass};
use super::line::{LineContext, LineLayouter};
use super::text::{layout_text, TextContext};
@@ -66,60 +67,28 @@ impl<'a> TreeLayouter<'a> {
self.style.text.word_spacing(),
SpacingKind::WORD,
);
- },
-
+ }
SyntaxNode::Linebreak => self.layouter.finish_line(),
SyntaxNode::ToggleItalic => {
self.style.text.italic = !self.style.text.italic;
decorate(self, Decoration::Italic);
}
-
SyntaxNode::ToggleBolder => {
self.style.text.bolder = !self.style.text.bolder;
decorate(self, Decoration::Bold);
}
SyntaxNode::Text(text) => {
- if self.style.text.italic {
- decorate(self, Decoration::Italic);
- }
-
- if self.style.text.bolder {
- decorate(self, Decoration::Bold);
- }
-
+ if self.style.text.italic { decorate(self, Decoration::Italic); }
+ if self.style.text.bolder { decorate(self, Decoration::Bold); }
self.layout_text(text).await;
}
- SyntaxNode::Raw(lines) => {
- // TODO: Make this more efficient.
- let fallback = self.style.text.fallback.clone();
- self.style.text.fallback
- .list_mut()
- .insert(0, "monospace".to_string());
-
- self.style.text.fallback.flatten();
-
- // Layout the first line.
- let mut iter = lines.iter();
- if let Some(line) = iter.next() {
- self.layout_text(line).await;
- }
-
- // Put a newline before each following line.
- for line in iter {
- self.layouter.finish_line();
- self.layout_text(line).await;
- }
-
- self.style.text.fallback = fallback;
- }
-
+ SyntaxNode::Raw(lines) => self.layout_raw(lines).await,
SyntaxNode::Par(par) => self.layout_par(par).await,
-
- SyntaxNode::Dyn(dynamic) => {
- self.layout_dyn(Spanned::new(dynamic.as_ref(), node.span)).await;
+ SyntaxNode::Call(call) => {
+ self.layout_call(Spanned::new(call, node.span)).await;
}
}
}
@@ -133,19 +102,35 @@ impl<'a> TreeLayouter<'a> {
self.layout_tree(par).await;
}
- async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) {
- // Execute the dynamic node's command-generating layout function.
- let layouted = dynamic.v.layout(LayoutContext {
+ async fn layout_call(&mut self, call: Spanned<&CallExpr>) {
+ let name = call.v.name.v.as_str();
+ let span = call.v.name.span.offset(call.span.start);
+
+ let (func, deco) = if let Some(func) = self.ctx.scope.func(name) {
+ (func, Decoration::Resolved)
+ } else {
+ error!(@self.feedback, span, "unknown function");
+ (self.ctx.scope.fallback(), Decoration::Unresolved)
+ };
+
+ self.feedback.decorations.push(Spanned::new(deco, span));
+
+ let args = call.v.args.eval();
+ let pass = func(args, LayoutContext {
style: &self.style,
spaces: self.layouter.remaining(),
root: true,
..self.ctx
}).await;
- self.feedback.extend_offset(layouted.feedback, dynamic.span.start);
+ self.feedback.extend_offset(pass.feedback, call.span.start);
- for command in layouted.output {
- self.execute_command(command, dynamic.span).await;
+ if let Value::Commands(commands) = pass.output {
+ for command in commands {
+ self.execute_command(command, call.span).await;
+ }
+ } else {
+ self.layout_raw(&[format!("{:?}", pass.output)]).await;
}
}
@@ -163,11 +148,35 @@ impl<'a> TreeLayouter<'a> {
);
}
- async fn execute_command(&mut self, command: Command<'_>, span: Span) {
+ async fn layout_raw(&mut self, lines: &[String]) {
+ // TODO: Make this more efficient.
+ let fallback = self.style.text.fallback.clone();
+ self.style.text.fallback
+ .list_mut()
+ .insert(0, "monospace".to_string());
+
+ self.style.text.fallback.flatten();
+
+ // Layout the first line.
+ let mut iter = lines.iter();
+ if let Some(line) = iter.next() {
+ self.layout_text(line).await;
+ }
+
+ // Put a newline before each following line.
+ for line in iter {
+ self.layouter.finish_line();
+ self.layout_text(line).await;
+ }
+
+ self.style.text.fallback = fallback;
+ }
+
+ async fn execute_command(&mut self, command: Command, span: Span) {
use Command::*;
match command {
- LayoutSyntaxTree(tree) => self.layout_tree(tree).await,
+ LayoutSyntaxTree(tree) => self.layout_tree(&tree).await,
Add(layout) => self.layouter.add(layout),
AddMultiple(layouts) => self.layouter.add_multiple(layouts),
diff --git a/src/lib.rs b/src/lib.rs
index 21adad9e..e30e41b2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,9 +24,9 @@
mod macros;
#[macro_use]
pub mod diagnostic;
-#[macro_use]
-pub mod func;
+pub mod color;
+pub mod compute;
pub mod export;
pub mod font;
pub mod geom;
@@ -34,22 +34,24 @@ pub mod layout;
pub mod length;
pub mod library;
pub mod paper;
+pub mod prelude;
pub mod style;
pub mod syntax;
-pub mod table;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
+use crate::compute::scope::Scope;
+use crate::compute::value::Value;
use crate::diagnostic::Diagnostics;
use crate::font::SharedFontLoader;
-use crate::layout::MultiLayout;
+use crate::layout::{Commands, MultiLayout};
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::decoration::Decorations;
-use crate::syntax::parsing::{parse, ParseState};
+use crate::syntax::parsing::parse;
use crate::syntax::span::{Offset, Pos};
-use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
+use crate::syntax::tree::SyntaxTree;
/// Transforms source code into typesetted layouts.
///
@@ -57,10 +59,10 @@ use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
pub struct Typesetter {
/// The font loader shared by all typesetting processes.
loader: SharedFontLoader,
+ /// A scope that contains the standard library function definitions.
+ std: Scope,
/// The base layouting style.
style: LayoutStyle,
- /// The base parser state.
- parse_state: ParseState,
}
impl Typesetter {
@@ -68,8 +70,8 @@ impl Typesetter {
pub fn new(loader: SharedFontLoader) -> Self {
Self {
loader,
+ std: crate::library::_std(),
style: LayoutStyle::default(),
- parse_state: ParseState { scope: crate::library::_std() },
}
}
@@ -85,7 +87,7 @@ impl Typesetter {
/// Parse source code into a syntax tree.
pub fn parse(&self, src: &str) -> Pass<SyntaxTree> {
- parse(src, Pos::ZERO, &self.parse_state)
+ parse(src, Pos::ZERO)
}
/// Layout a syntax tree and return the produced layout.
@@ -97,6 +99,7 @@ impl Typesetter {
&tree,
LayoutContext {
loader: &self.loader,
+ scope: &self.std,
style: &self.style,
base: self.style.page.size.unpadded(margins),
spaces: vec![LayoutSpace {
@@ -155,10 +158,10 @@ impl<T> Pass<T> {
}
}
-impl Pass<SyntaxNode> {
- /// Create a new pass from an unboxed dynamic node and feedback data..
- pub fn node<T: DynamicNode + 'static>(node: T, feedback: Feedback) -> Self {
- Pass::new(SyntaxNode::boxed(node), feedback)
+impl Pass<Value> {
+ /// Create a new pass with a list of layouting commands.
+ pub fn commands(commands: Commands, feedback: Feedback) -> Self {
+ Pass::new(Value::Commands(commands), feedback)
}
}
diff --git a/src/library/align.rs b/src/library/align.rs
index b4cfd2e2..c716faef 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -10,67 +10,49 @@ use super::*;
/// - `vertical`: Any of `top`, `bottom` or `center`.
///
/// There may not be two alignment specifications for the same axis.
-pub fn align(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
+pub async fn align(mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
- let mut args = call.args;
- let node = AlignNode {
- content: args.take::<SyntaxTree>(),
- aligns: args.take_all_num_vals::<Spanned<SpecAlign>>().collect(),
- h: args.take_with_key::<_, Spanned<SpecAlign>>("horizontal", &mut f),
- v: args.take_with_key::<_, Spanned<SpecAlign>>("vertical", &mut f),
- };
- args.unexpected(&mut f);
- Pass::node(node, f)
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct AlignNode {
- content: Option<SyntaxTree>,
- aligns: SpanVec<SpecAlign>,
- h: Option<Spanned<SpecAlign>>,
- v: Option<Spanned<SpecAlign>>,
-}
-#[async_trait(?Send)]
-impl Layout for AlignNode {
- async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
- let mut f = Feedback::new();
+ let content = args.take::<SyntaxTree>();
+ let aligns: Vec<_> = args.take_all_num_vals::<Spanned<SpecAlign>>().collect();
+ let h = args.take_with_key::<_, Spanned<SpecAlign>>("horizontal", &mut f);
+ let v = args.take_with_key::<_, Spanned<SpecAlign>>("vertical", &mut f);
+ args.unexpected(&mut f);
- ctx.base = ctx.spaces[0].size;
+ ctx.base = ctx.spaces[0].size;
- let axes = ctx.axes;
- let all = self.aligns.iter()
- .map(|align| {
- let spec = align.v.axis().unwrap_or(axes.primary.axis());
- (spec, align)
- })
- .chain(self.h.iter().map(|align| (Horizontal, align)))
- .chain(self.v.iter().map(|align| (Vertical, align)));
+ let axes = ctx.axes;
+ let all = aligns.iter()
+ .map(|align| {
+ let spec = align.v.axis().unwrap_or(axes.primary.axis());
+ (spec, align)
+ })
+ .chain(h.iter().map(|align| (Horizontal, align)))
+ .chain(v.iter().map(|align| (Vertical, align)));
- let mut had = [false; 2];
- for (axis, align) in all {
- if align.v.axis().map(|a| a != axis).unwrap_or(false) {
- error!(
- @f, align.span,
- "invalid alignment {} for {} axis", align.v, axis,
- );
- } else if had[axis as usize] {
- error!(@f, align.span, "duplicate alignment for {} axis", axis);
- } else {
- had[axis as usize] = true;
- let gen_axis = axis.to_generic(ctx.axes);
- let gen_align = align.v.to_generic(ctx.axes);
- *ctx.align.get_mut(gen_axis) = gen_align;
- }
+ let mut had = [false; 2];
+ for (axis, align) in all {
+ if align.v.axis().map(|a| a != axis).unwrap_or(false) {
+ error!(
+ @f, align.span,
+ "invalid alignment {} for {} axis", align.v, axis,
+ );
+ } else if had[axis as usize] {
+ error!(@f, align.span, "duplicate alignment for {} axis", axis);
+ } else {
+ had[axis as usize] = true;
+ let gen_axis = axis.to_generic(ctx.axes);
+ let gen_align = align.v.to_generic(ctx.axes);
+ *ctx.align.get_mut(gen_axis) = gen_align;
}
-
- Pass::new(match &self.content {
- Some(tree) => {
- let layouted = layout(tree, ctx).await;
- f.extend(layouted.feedback);
- vec![AddMultiple(layouted.output)]
- }
- None => vec![SetAlignment(ctx.align)],
- }, f)
}
+
+ Pass::commands(match content {
+ Some(tree) => {
+ let layouted = layout(&tree, ctx).await;
+ f.extend(layouted.feedback);
+ vec![AddMultiple(layouted.output)]
+ }
+ None => vec![SetAlignment(ctx.align)],
+ }, f)
}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index 3637f072..c03043ae 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -6,48 +6,32 @@ use super::*;
/// # Keyword arguments
/// - `width`: The width of the box (length of relative to parent's width).
/// - `height`: The height of the box (length of relative to parent's height).
-pub fn boxed(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
+pub async fn boxed(mut args: TableValue, mut ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
- let mut args = call.args;
- let node = BoxNode {
- content: args.take::<SyntaxTree>().unwrap_or(SyntaxTree::new()),
- width: args.take_with_key::<_, ScaleLength>("width", &mut f),
- height: args.take_with_key::<_, ScaleLength>("height", &mut f),
- };
+ let content = args.take::<SyntaxTree>().unwrap_or(SyntaxTree::new());
+ let width = args.take_with_key::<_, ScaleLength>("width", &mut f);
+ let height = args.take_with_key::<_, ScaleLength>("height", &mut f);
args.unexpected(&mut f);
- Pass::node(node, f)
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct BoxNode {
- content: SyntaxTree,
- width: Option<ScaleLength>,
- height: Option<ScaleLength>,
-}
-#[async_trait(?Send)]
-impl Layout for BoxNode {
- async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
- ctx.spaces.truncate(1);
- ctx.repeat = false;
+ ctx.spaces.truncate(1);
+ ctx.repeat = false;
- self.width.with(|v| {
- let length = v.raw_scaled(ctx.base.x);
- ctx.base.x = length;
- ctx.spaces[0].size.x = length;
- ctx.spaces[0].expansion.horizontal = true;
- });
+ width.with(|v| {
+ let length = v.raw_scaled(ctx.base.x);
+ ctx.base.x = length;
+ ctx.spaces[0].size.x = length;
+ ctx.spaces[0].expansion.horizontal = true;
+ });
- self.height.with(|v| {
- let length = v.raw_scaled(ctx.base.y);
- ctx.base.y = length;
- ctx.spaces[0].size.y = length;
- ctx.spaces[0].expansion.vertical = true;
- });
+ height.with(|v| {
+ let length = v.raw_scaled(ctx.base.y);
+ ctx.base.y = length;
+ ctx.spaces[0].size.y = length;
+ ctx.spaces[0].expansion.vertical = true;
+ });
- layout(&self.content, ctx).await.map(|out| {
- let layout = out.into_iter().next().unwrap();
- vec![Add(layout)]
- })
- }
+ let layouted = layout(&content, ctx).await;
+ let layout = layouted.output.into_iter().next().unwrap();
+ f.extend(layouted.feedback);
+ Pass::commands(vec![Add(layout)], f)
}
diff --git a/src/library/font.rs b/src/library/font.rs
index 8787cc91..71e9552f 100644
--- a/src/library/font.rs
+++ b/src/library/font.rs
@@ -18,80 +18,60 @@ use super::*;
/// ```typst
/// serif = ("Source Serif Pro", "Noto Serif")
/// ```
-pub fn font(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
+pub async fn font(mut args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
- let mut args = call.args;
- let node = FontNode {
- content: args.take::<SyntaxTree>(),
- size: args.take::<ScaleLength>(),
- style: args.take_with_key::<_, FontStyle>("style", &mut f),
- weight: args.take_with_key::<_, FontWeight>("weight", &mut f),
- width: args.take_with_key::<_, FontWidth>("width", &mut f),
- list: args.take_all_num_vals::<StringLike>()
- .map(|s| s.0.to_lowercase())
- .collect(),
- classes: args.take_all_str::<TableExpr>()
- .map(|(class, mut table)| {
- let fallback = table.take_all_num_vals::<StringLike>()
- .map(|s| s.0.to_lowercase())
- .collect();
- (class, fallback)
- })
- .collect()
- };
+ let content = args.take::<SyntaxTree>();
+ let size = args.take::<ScaleLength>();
+ let style = args.take_with_key::<_, FontStyle>("style", &mut f);
+ let weight = args.take_with_key::<_, FontWeight>("weight", &mut f);
+ let width = args.take_with_key::<_, FontWidth>("width", &mut f);
+ let list: Vec<_> = args.take_all_num_vals::<StringLike>()
+ .map(|s| s.0.to_lowercase())
+ .collect();
+ let classes: Vec<(_, Vec<_>)> = args.take_all_str::<TableValue>()
+ .map(|(class, mut table)| {
+ let fallback = table.take_all_num_vals::<StringLike>()
+ .map(|s| s.0.to_lowercase())
+ .collect();
+ (class, fallback)
+ })
+ .collect();
args.unexpected(&mut f);
- Pass::node(node, f)
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct FontNode {
- content: Option<SyntaxTree>,
- size: Option<ScaleLength>,
- style: Option<FontStyle>,
- weight: Option<FontWeight>,
- width: Option<FontWidth>,
- list: Vec<String>,
- classes: Vec<(String, Vec<String>)>,
-}
-
-#[async_trait(?Send)]
-impl Layout for FontNode {
- async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
- let mut text = ctx.style.text.clone();
-
- self.size.with(|s| match s {
- ScaleLength::Absolute(length) => {
- text.base_font_size = length.as_raw();
- text.font_scale = 1.0;
- }
- ScaleLength::Scaled(scale) => text.font_scale = scale,
- });
- self.style.with(|s| text.variant.style = s);
- self.weight.with(|w| text.variant.weight = w);
- self.width.with(|w| text.variant.width = w);
+ let mut text = ctx.style.text.clone();
- if !self.list.is_empty() {
- *text.fallback.list_mut() = self.list.iter()
- .map(|s| s.to_lowercase())
- .collect();
+ size.with(|s| match s {
+ ScaleLength::Absolute(length) => {
+ text.base_font_size = length.as_raw();
+ text.font_scale = 1.0;
}
+ ScaleLength::Scaled(scale) => text.font_scale = scale,
+ });
- for (class, fallback) in &self.classes {
- text.fallback.set_class_list(class.clone(), fallback.clone());
- }
+ style.with(|s| text.variant.style = s);
+ weight.with(|w| text.variant.weight = w);
+ width.with(|w| text.variant.width = w);
- text.fallback.flatten();
+ if !list.is_empty() {
+ *text.fallback.list_mut() = list.iter()
+ .map(|s| s.to_lowercase())
+ .collect();
+ }
- Pass::okay(match &self.content {
- Some(tree) => vec![
- SetTextStyle(text),
- LayoutSyntaxTree(tree),
- SetTextStyle(ctx.style.text.clone()),
- ],
- None => vec![SetTextStyle(text)],
- })
+ for (class, fallback) in classes {
+ text.fallback.set_class_list(class.clone(), fallback.clone());
}
+
+ text.fallback.flatten();
+
+ Pass::commands(match content {
+ Some(tree) => vec![
+ SetTextStyle(text),
+ LayoutSyntaxTree(tree),
+ SetTextStyle(ctx.style.text.clone()),
+ ],
+ None => vec![SetTextStyle(text)],
+ }, f)
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index ef24d74f..1999ba31 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -5,30 +5,60 @@ mod boxed;
mod font;
mod page;
mod spacing;
-mod val;
pub use align::*;
pub use boxed::*;
pub use font::*;
pub use page::*;
pub use spacing::*;
-pub use val::*;
-use crate::func::prelude::*;
-use crate::syntax::scope::Scope;
+use std::rc::Rc;
-/// Create a scope with all standard library functions.
-pub fn _std() -> Scope {
- let mut std = Scope::new(Box::new(val));
+use crate::compute::scope::Scope;
+use crate::prelude::*;
- std.insert("val", Box::new(val));
- std.insert("font", Box::new(font));
- std.insert("page", Box::new(page));
- std.insert("align", Box::new(align));
- std.insert("box", Box::new(boxed));
- std.insert("pagebreak", Box::new(pagebreak));
- std.insert("h", Box::new(h));
- std.insert("v", Box::new(v));
+macro_rules! std {
+ (fallback: $fallback:expr $(, $name:literal => $func:expr)* $(,)?) => {
+ /// Create a scope with all standard library functions.
+ pub fn _std() -> Scope {
+ let mut std = Scope::new(wrap!(val));
+ $(std.insert($name, wrap!($func));)*
+ std
+ }
+ };
+}
+
+macro_rules! wrap {
+ ($func:expr) => {
+ Rc::new(|args, ctx| Box::pin($func(args, ctx)))
+ };
+}
+
+std! {
+ fallback: val,
+ "align" => align,
+ "box" => boxed,
+ "dump" => dump,
+ "font" => font,
+ "h" => h,
+ "page" => page,
+ "pagebreak" => pagebreak,
+ "v" => v,
+ "val" => val,
+}
+
+/// `val`: Layouts its body flatly, ignoring other arguments.
+///
+/// This is also the fallback function, which is used when a function name
+/// cannot be resolved.
+pub async fn val(mut args: TableValue, _: LayoutContext<'_>) -> Pass<Value> {
+ Pass::commands(match args.take::<SyntaxTree>() {
+ Some(tree) => vec![LayoutSyntaxTree(tree)],
+ None => vec![],
+ }, Feedback::new())
+}
- std
+/// `dump`: Dumps its arguments.
+pub async fn dump(args: TableValue, _: LayoutContext<'_>) -> Pass<Value> {
+ Pass::okay(Value::Table(args))
}
diff --git a/src/library/page.rs b/src/library/page.rs
index 7e4e6e54..42f29dbb 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -16,78 +16,46 @@ use super::*;
/// - `top`: The top margin (length or relative to height).
/// - `bottom`: The bottom margin (length or relative to height).
/// - `flip`: Flips custom or paper-defined width and height (boolean).
-pub fn page(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
+pub async fn page(mut args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
- let mut args = call.args;
- let node = PageNode {
- paper: args.take::<Paper>(),
- width: args.take_with_key::<_, Length>("width", &mut f),
- height: args.take_with_key::<_, Length>("height", &mut f),
- margins: args.take_with_key::<_, ScaleLength>("margins", &mut f),
- left: args.take_with_key::<_, ScaleLength>("left", &mut f),
- right: args.take_with_key::<_, ScaleLength>("right", &mut f),
- top: args.take_with_key::<_, ScaleLength>("top", &mut f),
- bottom: args.take_with_key::<_, ScaleLength>("bottom", &mut f),
- flip: args.take_with_key::<_, bool>("flip", &mut f).unwrap_or(false),
- };
+ let paper = args.take::<Paper>();
+ let width = args.take_with_key::<_, Length>("width", &mut f);
+ let height = args.take_with_key::<_, Length>("height", &mut f);
+ let margins = args.take_with_key::<_, ScaleLength>("margins", &mut f);
+ let left = args.take_with_key::<_, ScaleLength>("left", &mut f);
+ let right = args.take_with_key::<_, ScaleLength>("right", &mut f);
+ let top = args.take_with_key::<_, ScaleLength>("top", &mut f);
+ let bottom = args.take_with_key::<_, ScaleLength>("bottom", &mut f);
+ let flip = args.take_with_key::<_, bool>("flip", &mut f).unwrap_or(false);
args.unexpected(&mut f);
- Pass::node(node, f)
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct PageNode {
- paper: Option<Paper>,
- width: Option<Length>,
- height: Option<Length>,
- margins: Option<ScaleLength>,
- left: Option<ScaleLength>,
- right: Option<ScaleLength>,
- top: Option<ScaleLength>,
- bottom: Option<ScaleLength>,
- flip: bool,
-}
-#[async_trait(?Send)]
-impl Layout for PageNode {
- async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
- let mut style = ctx.style.page;
+ let mut style = ctx.style.page;
- if let Some(paper) = self.paper {
- style.class = paper.class;
- style.size = paper.size();
- } else if self.width.is_some() || self.height.is_some() {
- style.class = PaperClass::Custom;
- }
-
- self.width.with(|v| style.size.x = v.as_raw());
- self.height.with(|v| style.size.y = v.as_raw());
- self.margins.with(|v| style.margins.set_all(Some(v)));
- self.left.with(|v| style.margins.left = Some(v));
- self.right.with(|v| style.margins.right = Some(v));
- self.top.with(|v| style.margins.top = Some(v));
- self.bottom.with(|v| style.margins.bottom = Some(v));
+ if let Some(paper) = paper {
+ style.class = paper.class;
+ style.size = paper.size();
+ } else if width.is_some() || height.is_some() {
+ style.class = PaperClass::Custom;
+ }
- if self.flip {
- style.size.swap();
- }
+ width.with(|v| style.size.x = v.as_raw());
+ height.with(|v| style.size.y = v.as_raw());
+ margins.with(|v| style.margins.set_all(Some(v)));
+ left.with(|v| style.margins.left = Some(v));
+ right.with(|v| style.margins.right = Some(v));
+ top.with(|v| style.margins.top = Some(v));
+ bottom.with(|v| style.margins.bottom = Some(v));
- Pass::okay(vec![SetPageStyle(style)])
+ if flip {
+ style.size.swap();
}
+
+ Pass::commands(vec![SetPageStyle(style)], f)
}
/// `pagebreak`: Ends the current page.
-pub fn pagebreak(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
+pub async fn pagebreak(args: TableValue, _: LayoutContext<'_>) -> Pass<Value> {
let mut f = Feedback::new();
- call.args.unexpected(&mut f);
- Pass::node(PageBreakNode, f)
-}
-
-#[derive(Debug, Default, Clone, PartialEq)]
-struct PageBreakNode;
-
-#[async_trait(?Send)]
-impl Layout for PageBreakNode {
- async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
- Pass::okay(vec![BreakPage])
- }
+ args.unexpected(&mut f);
+ Pass::commands(vec![BreakPage], f)
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 81112cbd..3cd775c9 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -6,44 +6,32 @@ use super::*;
///
/// # Positional arguments
/// - The spacing (length or relative to font size).
-pub fn h(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
- spacing(call, Horizontal)
+pub async fn h(args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
+ spacing(args, ctx, Horizontal).await
}
/// `v`: Add vertical spacing.
///
/// # Positional arguments
/// - The spacing (length or relative to font size).
-pub fn v(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
- spacing(call, Vertical)
+pub async fn v(args: TableValue, ctx: LayoutContext<'_>) -> Pass<Value> {
+ spacing(args, ctx, Vertical).await
}
-fn spacing(call: FuncCall, axis: SpecAxis) -> Pass<SyntaxNode> {
+async fn spacing(
+ mut args: TableValue,
+ ctx: LayoutContext<'_>,
+ axis: SpecAxis,
+) -> Pass<Value> {
let mut f = Feedback::new();
- let mut args = call.args;
- let node = SpacingNode {
- spacing: args.expect::<ScaleLength>(&mut f)
- .map(|s| (axis, s))
- .or_missing(call.name.span, "spacing", &mut f),
- };
+ let spacing = args.expect::<ScaleLength>(&mut f).map(|s| (axis, s));
args.unexpected(&mut f);
- Pass::node(node, f)
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct SpacingNode {
- spacing: Option<(SpecAxis, ScaleLength)>,
-}
-#[async_trait(?Send)]
-impl Layout for SpacingNode {
- async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
- Pass::okay(if let Some((axis, spacing)) = self.spacing {
- let axis = axis.to_generic(ctx.axes);
- let spacing = spacing.raw_scaled(ctx.style.text.font_size());
- vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
- } else {
- vec![]
- })
- }
+ Pass::commands(if let Some((axis, spacing)) = spacing {
+ let axis = axis.to_generic(ctx.axes);
+ let spacing = spacing.raw_scaled(ctx.style.text.font_size());
+ vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
+ } else {
+ vec![]
+ }, f)
}
diff --git a/src/library/val.rs b/src/library/val.rs
deleted file mode 100644
index 9df55401..00000000
--- a/src/library/val.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use super::*;
-
-/// `val`: Ignores all arguments and layouts its body flatly.
-///
-/// This is also the fallback function, which is used when a function name
-/// cannot be resolved.
-pub fn val(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
- let mut args = call.args;
- let node = ValNode {
- content: args.take::<SyntaxTree>(),
- };
- Pass::node(node, Feedback::new())
-}
-
-#[derive(Debug, Clone, PartialEq)]
-struct ValNode {
- content: Option<SyntaxTree>,
-}
-
-#[async_trait(?Send)]
-impl Layout for ValNode {
- async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
- Pass::okay(match &self.content {
- Some(tree) => vec![LayoutSyntaxTree(tree)],
- None => vec![],
- })
- }
-}
diff --git a/src/func.rs b/src/prelude.rs
index bff4fdab..214b00c8 100644
--- a/src/func.rs
+++ b/src/prelude.rs
@@ -1,21 +1,15 @@
-//! Tools for building custom functions.
+//! A prelude for building custom functions.
-/// Useful things for creating functions.
-pub mod prelude {
- pub use async_trait::async_trait;
- pub use crate::layout::prelude::*;
- pub use crate::layout::Commands;
- pub use crate::layout::Command::{self, *};
- pub use crate::style::*;
- pub use crate::syntax::expr::*;
- pub use crate::syntax::parsing::{parse, FuncCall, ParseState};
- pub use crate::syntax::span::{Pos, Span, SpanVec, Spanned};
- pub use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
- pub use crate::{Pass, Feedback};
- pub use super::*;
-}
-
-use prelude::*;
+pub use crate::compute::value::*;
+pub use crate::layout::prelude::*;
+pub use crate::layout::Commands;
+pub use crate::layout::Command::{self, *};
+pub use crate::style::*;
+pub use crate::syntax::parsing::parse;
+pub use crate::syntax::span::{Pos, Span, SpanVec, Spanned};
+pub use crate::syntax::tree::*;
+pub use crate::{Pass, Feedback};
+pub use super::*;
/// Extra methods on `Option`s used for function argument parsing.
pub trait OptionExt<T>: Sized {
diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs
index a686596c..fe961be3 100644
--- a/src/syntax/decoration.rs
+++ b/src/syntax/decoration.rs
@@ -9,18 +9,18 @@ use super::span::SpanVec;
pub type Decorations = SpanVec<Decoration>;
/// Decorations for semantic syntax highlighting.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
pub enum Decoration {
- /// A valid, successfully resolved function name.
- ResolvedFunc,
- /// An invalid, unresolved function name.
- UnresolvedFunc,
- /// The key part of a key-value entry in a table.
- TableKey,
/// Text in italics.
Italic,
/// Text in bold.
Bold,
+ /// A valid, successfully resolved name.
+ Resolved,
+ /// An invalid, unresolved name.
+ Unresolved,
+ /// The key part of a key-value entry in a table.
+ TableKey,
}
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
deleted file mode 100644
index e92a75b0..00000000
--- a/src/syntax/expr.rs
+++ /dev/null
@@ -1,639 +0,0 @@
-//! Expressions in function headers.
-
-use std::fmt::{self, Debug, Formatter};
-use std::str::FromStr;
-use std::u8;
-
-use fontdock::{FontStyle, FontWeight, FontWidth};
-
-use crate::layout::{Dir, SpecAlign};
-use crate::length::{Length, ScaleLength};
-use crate::paper::Paper;
-use crate::table::{BorrowedKey, Table};
-use crate::Feedback;
-use super::parsing::FuncCall;
-use super::span::{Span, Spanned};
-use super::tokens::is_identifier;
-use super::tree::SyntaxTree;
-
-/// An expression.
-#[derive(Clone, PartialEq)]
-pub enum Expr {
- /// An identifier: `ident`.
- Ident(Ident),
- /// A string: `"string"`.
- Str(String),
- /// A boolean: `true, false`.
- Bool(bool),
- /// A number: `1.2, 200%`.
- Number(f64),
- /// A length: `2cm, 5.2in`.
- Length(Length),
- /// A color value with alpha channel: `#f79143ff`.
- Color(RgbaColor),
- /// A syntax tree containing typesetting content.
- Tree(SyntaxTree),
- /// A table: `(false, 12cm, greeting="hi")`.
- Table(TableExpr),
- /// An operation that negates the contained expression.
- Neg(Box<Spanned<Expr>>),
- /// An operation that adds the contained expressions.
- Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
- /// An operation that subtracts the contained expressions.
- Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
- /// An operation that multiplies the contained expressions.
- Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
- /// An operation that divides the contained expressions.
- Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
- /// A function call: `cmyk(37.7, 0, 3.9, 1.1)`.
- Call(FuncCall),
-}
-
-impl Expr {
- /// A natural-language name of the type of this expression, e.g.
- /// "identifier".
- pub fn name(&self) -> &'static str {
- use Expr::*;
- match self {
- Ident(_) => "identifier",
- Str(_) => "string",
- Bool(_) => "bool",
- Number(_) => "number",
- Length(_) => "length",
- Color(_) => "color",
- Tree(_) => "syntax tree",
- Table(_) => "table",
- Neg(_) => "negation",
- Add(_, _) => "addition",
- Sub(_, _) => "subtraction",
- Mul(_, _) => "multiplication",
- Div(_, _) => "division",
- Call(_) => "function call",
- }
- }
-}
-
-impl Debug for Expr {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- use Expr::*;
- match self {
- Ident(i) => i.fmt(f),
- Str(s) => s.fmt(f),
- Bool(b) => b.fmt(f),
- Number(n) => n.fmt(f),
- Length(s) => s.fmt(f),
- Color(c) => c.fmt(f),
- Tree(t) => t.fmt(f),
- Table(t) => t.fmt(f),
- Neg(e) => write!(f, "-{:?}", e),
- Add(a, b) => write!(f, "({:?} + {:?})", a, b),
- Sub(a, b) => write!(f, "({:?} - {:?})", a, b),
- Mul(a, b) => write!(f, "({:?} * {:?})", a, b),
- Div(a, b) => write!(f, "({:?} / {:?})", a, b),
- Call(c) => c.fmt(f),
- }
- }
-}
-
-/// An identifier as defined by unicode with a few extra permissible characters.
-#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
-pub struct Ident(pub String);
-
-impl Ident {
- /// Create a new identifier from a string checking that it is a valid.
- pub fn new(ident: impl AsRef<str> + Into<String>) -> Option<Self> {
- if is_identifier(ident.as_ref()) {
- Some(Self(ident.into()))
- } else {
- None
- }
- }
-
- /// Return a reference to the underlying string.
- pub fn as_str(&self) -> &str {
- self.0.as_str()
- }
-}
-
-impl Debug for Ident {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "`{}`", self.0)
- }
-}
-
-/// An 8-bit RGBA color.
-///
-/// # Example
-/// ```typst
-/// [page: background=#423abaff]
-/// ^^^^^^^^
-/// ```
-#[derive(Copy, Clone, Eq, PartialEq, Hash)]
-pub struct RgbaColor {
- /// Red channel.
- pub r: u8,
- /// Green channel.
- pub g: u8,
- /// Blue channel.
- pub b: u8,
- /// Alpha channel.
- pub a: u8,
- /// This is true if this value was provided as a fail-over by the parser
- /// because the user-defined value was invalid. This color may be
- /// overwritten if this property is true.
- pub healed: bool,
-}
-
-impl RgbaColor {
- /// Constructs a new color.
- pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
- Self { r, g, b, a, healed: false }
- }
-
- /// Constructs a new color with the healed property set to true.
- pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> Self {
- Self { r, g, b, a, healed: true }
- }
-}
-
-impl FromStr for RgbaColor {
- type Err = ParseColorError;
-
- /// Constructs a new color from a hex string like `7a03c2`. Do not specify a
- /// leading `#`.
- fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
- if !hex_str.is_ascii() {
- return Err(ParseColorError);
- }
-
- let len = hex_str.len();
- let long = len == 6 || len == 8;
- let short = len == 3 || len == 4;
- let alpha = len == 4 || len == 8;
-
- if !long && !short {
- return Err(ParseColorError);
- }
-
- let mut values: [u8; 4] = [255; 4];
-
- for elem in if alpha { 0..4 } else { 0..3 } {
- let item_len = if long { 2 } else { 1 };
- let pos = elem * item_len;
-
- let item = &hex_str[pos..(pos+item_len)];
- values[elem] = u8::from_str_radix(item, 16)
- .map_err(|_| ParseColorError)?;
-
- if short {
- // Duplicate number for shorthand notation, i.e. `a` -> `aa`
- values[elem] += values[elem] * 16;
- }
- }
-
- Ok(Self::new(values[0], values[1], values[2], values[3]))
- }
-}
-
-impl Debug for RgbaColor {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- if f.alternate() {
- write!(
- f, "rgba({:02}, {:02}, {:02}, {:02})",
- self.r, self.g, self.b, self.a,
- )?;
- } else {
- write!(
- f, "#{:02x}{:02x}{:02x}{:02x}",
- self.r, self.g, self.b, self.a,
- )?;
- }
- if self.healed {
- f.write_str(" [healed]")?;
- }
- Ok(())
- }
-}
-
-/// The error when parsing an `RgbaColor` fails.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-pub struct ParseColorError;
-
-impl std::error::Error for ParseColorError {}
-
-impl fmt::Display for ParseColorError {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad("invalid color")
- }
-}
-
-/// A table expression.
-///
-/// # Example
-/// ```typst
-/// (false, 12cm, greeting="hi")
-/// ```
-pub type TableExpr = Table<TableExprEntry>;
-
-impl TableExpr {
- /// Retrieve and remove the matching value with the lowest number key,
- /// skipping and ignoring all non-matching entries with lower keys.
- pub fn take<T: TryFromExpr>(&mut self) -> Option<T> {
- for (&key, entry) in self.nums() {
- let expr = entry.val.as_ref();
- if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
- self.remove(key);
- return Some(val);
- }
- }
- None
- }
-
- /// Retrieve and remove the matching value with the lowest number key,
- /// removing and generating errors for all non-matching entries with lower
- /// keys.
- pub fn expect<T: TryFromExpr>(&mut self, f: &mut Feedback) -> Option<T> {
- while let Some((num, _)) = self.first() {
- let entry = self.remove(num).unwrap();
- if let Some(val) = T::try_from_expr(entry.val.as_ref(), f) {
- return Some(val);
- }
- }
- None
- }
-
- /// Retrieve and remove a matching value associated with the given key if
- /// there is any.
- ///
- /// Generates an error if the key exists but the value does not match.
- pub fn take_with_key<'a, K, T>(&mut self, key: K, f: &mut Feedback) -> Option<T>
- where
- K: Into<BorrowedKey<'a>>,
- T: TryFromExpr,
- {
- self.remove(key).and_then(|entry| {
- let expr = entry.val.as_ref();
- T::try_from_expr(expr, f)
- })
- }
-
- /// Retrieve and remove all matching pairs with number keys, skipping and
- /// ignoring non-matching entries.
- ///
- /// The pairs are returned in order of increasing keys.
- pub fn take_all_num<'a, T>(&'a mut self) -> impl Iterator<Item = (u64, T)> + 'a
- where
- T: TryFromExpr,
- {
- let mut skip = 0;
- std::iter::from_fn(move || {
- for (&key, entry) in self.nums().skip(skip) {
- let expr = entry.val.as_ref();
- if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
- self.remove(key);
- return Some((key, val));
- }
- skip += 1;
- }
-
- None
- })
- }
-
-
- /// Retrieve and remove all matching values with number keys, skipping and
- /// ignoring non-matching entries.
- ///
- /// The values are returned in order of increasing keys.
- pub fn take_all_num_vals<'a, T: 'a>(&'a mut self) -> impl Iterator<Item = T> + 'a
- where
- T: TryFromExpr,
- {
- self.take_all_num::<T>().map(|(_, v)| v)
- }
-
- /// Retrieve and remove all matching pairs with string keys, skipping and
- /// ignoring non-matching entries.
- ///
- /// The pairs are returned in order of increasing keys.
- pub fn take_all_str<'a, T>(&'a mut self) -> impl Iterator<Item = (String, T)> + 'a
- where
- T: TryFromExpr,
- {
- let mut skip = 0;
- std::iter::from_fn(move || {
- for (key, entry) in self.strs().skip(skip) {
- let expr = entry.val.as_ref();
- if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
- let key = key.clone();
- self.remove(&key);
- return Some((key, val));
- }
- skip += 1;
- }
-
- None
- })
- }
-
- /// Generated `"unexpected argument"` errors for all remaining entries.
- pub fn unexpected(&self, f: &mut Feedback) {
- for entry in self.values() {
- let span = Span::merge(entry.key, entry.val.span);
- error!(@f, span, "unexpected argument");
- }
- }
-}
-
-/// An entry in a table expression.
-///
-/// Contains the key's span and the value.
-#[derive(Clone, PartialEq)]
-pub struct TableExprEntry {
- pub key: Span,
- pub val: Spanned<Expr>,
-}
-
-impl TableExprEntry {
- /// Create a new entry.
- pub fn new(key: Span, val: Spanned<Expr>) -> Self {
- Self { key, val }
- }
-
- /// Create an entry for a positional argument with the same span for key and
- /// value.
- pub fn val(val: Spanned<Expr>) -> Self {
- Self { key: Span::ZERO, val }
- }
-}
-
-impl Debug for TableExprEntry {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- if f.alternate() {
- f.write_str("key")?;
- self.key.fmt(f)?;
- f.write_str(" ")?;
- }
- self.val.fmt(f)
- }
-}
-
-/// A trait for converting expressions into specific types.
-pub trait TryFromExpr: Sized {
- // This trait takes references because we don't want to move the expression
- // out of its origin in case this returns `None`. This solution is not
- // perfect because we need to do some cloning in the impls for this trait,
- // but I haven't got a better solution, for now.
-
- /// Try to convert an expression into this type.
- ///
- /// Returns `None` and generates an appropriate error if the expression is
- /// not valid for this type.
- fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self>;
-}
-
-macro_rules! impl_match {
- ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
- impl TryFromExpr for $type {
- fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> {
- #[allow(unreachable_patterns)]
- match expr.v {
- $($p => Some($r)),*,
- other => {
- error!(
- @f, expr.span,
- "expected {}, found {}", $name, other.name()
- );
- None
- }
- }
- }
- }
- };
-}
-
-macro_rules! impl_ident {
- ($type:ty, $name:expr, $parse:expr) => {
- impl TryFromExpr for $type {
- fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> {
- if let Expr::Ident(ident) = expr.v {
- let val = $parse(ident.as_str());
- if val.is_none() {
- error!(@f, expr.span, "invalid {}", $name);
- }
- val
- } else {
- error!(
- @f, expr.span,
- "expected {}, found {}", $name, expr.v.name()
- );
- None
- }
- }
- }
- };
-}
-
-impl<T: TryFromExpr> TryFromExpr for Spanned<T> {
- fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> {
- let span = expr.span;
- T::try_from_expr(expr, f).map(|v| Spanned { v, span })
- }
-}
-
-impl_match!(Expr, "expression", e => e.clone());
-impl_match!(Ident, "identifier", Expr::Ident(i) => i.clone());
-impl_match!(String, "string", Expr::Str(s) => s.clone());
-impl_match!(bool, "bool", Expr::Bool(b) => b.clone());
-impl_match!(f64, "number", Expr::Number(n) => n.clone());
-impl_match!(Length, "length", Expr::Length(l) => l.clone());
-impl_match!(SyntaxTree, "tree", Expr::Tree(t) => t.clone());
-impl_match!(TableExpr, "table", Expr::Table(t) => t.clone());
-impl_match!(ScaleLength, "number or length",
- &Expr::Length(length) => ScaleLength::Absolute(length),
- &Expr::Number(scale) => ScaleLength::Scaled(scale),
-);
-
-/// A value type that matches identifiers and strings and implements
-/// `Into<String>`.
-pub struct StringLike(pub String);
-
-impl From<StringLike> for String {
- fn from(like: StringLike) -> String {
- like.0
- }
-}
-
-impl_match!(StringLike, "identifier or string",
- Expr::Ident(Ident(s)) => StringLike(s.clone()),
- Expr::Str(s) => StringLike(s.clone()),
-);
-
-impl_ident!(Dir, "direction", |s| match s {
- "ltr" => Some(Self::LTR),
- "rtl" => Some(Self::RTL),
- "ttb" => Some(Self::TTB),
- "btt" => Some(Self::BTT),
- _ => None,
-});
-
-impl_ident!(SpecAlign, "alignment", |s| match s {
- "left" => Some(Self::Left),
- "right" => Some(Self::Right),
- "top" => Some(Self::Top),
- "bottom" => Some(Self::Bottom),
- "center" => Some(Self::Center),
- _ => None,
-});
-
-impl_ident!(FontStyle, "font style", FontStyle::from_name);
-impl_ident!(Paper, "paper", Paper::from_name);
-
-impl TryFromExpr for FontWeight {
- fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> {
- match expr.v {
- &Expr::Number(weight) => {
- const MIN: u16 = 100;
- const MAX: u16 = 900;
-
- Some(Self(if weight < MIN as f64 {
- error!(@f, expr.span, "the minimum font weight is {}", MIN);
- MIN
- } else if weight > MAX as f64 {
- error!(@f, expr.span, "the maximum font weight is {}", MAX);
- MAX
- } else {
- weight.round() as u16
- }))
- }
- Expr::Ident(ident) => {
- let weight = Self::from_name(ident.as_str());
- if weight.is_none() {
- error!(@f, expr.span, "invalid font weight");
- }
- weight
- }
- other => {
- error!(
- @f, expr.span,
- "expected font weight (name or number), found {}",
- other.name(),
- );
- None
- }
- }
- }
-}
-
-impl TryFromExpr for FontWidth {
- fn try_from_expr(expr: Spanned<&Expr>, f: &mut Feedback) -> Option<Self> {
- match expr.v {
- &Expr::Number(width) => {
- const MIN: u16 = 1;
- const MAX: u16 = 9;
-
- Self::new(if width < MIN as f64 {
- error!(@f, expr.span, "the minimum font width is {}", MIN);
- MIN
- } else if width > MAX as f64 {
- error!(@f, expr.span, "the maximum font width is {}", MAX);
- MAX
- } else {
- width.round() as u16
- })
- }
- Expr::Ident(ident) => {
- let width = Self::from_name(ident.as_str());
- if width.is_none() {
- error!(@f, expr.span, "invalid font width");
- }
- width
- }
- other => {
- error!(
- @f, expr.span,
- "expected font width (name or number), found {}",
- other.name(),
- );
- None
- }
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn parse_color_strings() {
- fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) {
- assert_eq!(
- RgbaColor::from_str(hex),
- Ok(RgbaColor::new(r, g, b, a)),
- );
- }
-
- test("f61243ff", 0xf6, 0x12, 0x43, 0xff);
- test("b3d8b3", 0xb3, 0xd8, 0xb3, 0xff);
- test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad);
- test("233", 0x22, 0x33, 0x33, 0xff);
- test("111b", 0x11, 0x11, 0x11, 0xbb);
- }
-
- fn entry(expr: Expr) -> TableExprEntry {
- TableExprEntry {
- key: Span::ZERO,
- val: Spanned::zero(expr),
- }
- }
-
- #[test]
- fn test_table_take_removes_correct_entry() {
- let mut table = TableExpr::new();
- table.insert(1, entry(Expr::Bool(false)));
- table.insert(2, entry(Expr::Str("hi".to_string())));
- assert_eq!(table.take::<String>(), Some("hi".to_string()));
- assert_eq!(table.len(), 1);
- assert_eq!(table.take::<bool>(), Some(false));
- assert!(table.is_empty());
- }
-
- #[test]
- fn test_table_expect_errors_about_previous_entries() {
- let mut f = Feedback::new();
- let mut table = TableExpr::new();
- table.insert(1, entry(Expr::Bool(false)));
- table.insert(3, entry(Expr::Str("hi".to_string())));
- table.insert(5, entry(Expr::Bool(true)));
- assert_eq!(table.expect::<String>(&mut f), Some("hi".to_string()));
- assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected string, found bool")]);
- assert_eq!(table.len(), 1);
- }
-
- #[test]
- fn test_table_take_with_key_removes_the_entry() {
- let mut f = Feedback::new();
- let mut table = TableExpr::new();
- table.insert(1, entry(Expr::Bool(false)));
- table.insert("hi", entry(Expr::Bool(true)));
- assert_eq!(table.take_with_key::<_, bool>(1, &mut f), Some(false));
- assert_eq!(table.take_with_key::<_, f64>("hi", &mut f), None);
- assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected number, found bool")]);
- assert!(table.is_empty());
- }
-
- #[test]
- fn test_table_take_all_removes_the_correct_entries() {
- let mut table = TableExpr::new();
- table.insert(1, entry(Expr::Bool(false)));
- table.insert(3, entry(Expr::Number(0.0)));
- table.insert(7, entry(Expr::Bool(true)));
- assert_eq!(
- table.take_all_num::<bool>().collect::<Vec<_>>(),
- [(1, false), (7, true)],
- );
- assert_eq!(table.len(), 1);
- assert_eq!(table[3].val.v, Expr::Number(0.0));
- }
-}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index fdc105a3..596291a5 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -1,17 +1,44 @@
//! Syntax trees, parsing and tokenization.
pub mod decoration;
-pub mod expr;
pub mod parsing;
-pub mod scope;
pub mod span;
pub mod tokens;
pub mod tree;
+use std::fmt::{self, Debug, Formatter};
+use tokens::is_identifier;
+
+/// An identifier as defined by unicode with a few extra permissible characters.
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct Ident(pub String);
+
+impl Ident {
+ /// Create a new identifier from a string checking that it is a valid.
+ pub fn new(ident: impl AsRef<str> + Into<String>) -> Option<Self> {
+ if is_identifier(ident.as_ref()) {
+ Some(Self(ident.into()))
+ } else {
+ None
+ }
+ }
+
+ /// Return a reference to the underlying string.
+ pub fn as_str(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+impl Debug for Ident {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "`{}`", self.0)
+ }
+}
+
#[cfg(test)]
mod tests {
use std::fmt::Debug;
- use crate::func::prelude::*;
+ use crate::prelude::*;
use super::span;
/// Assert that expected and found are equal, printing both and panicking
@@ -49,18 +76,4 @@ mod tests {
Spanned::zero(t)
}
}
-
- pub fn debug_func(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
- Pass::node(DebugNode(call), Feedback::new())
- }
-
- #[derive(Debug, Clone, PartialEq)]
- pub struct DebugNode(pub FuncCall);
-
- #[async_trait(?Send)]
- impl Layout for DebugNode {
- async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
- unimplemented!()
- }
- }
}
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 88c930cb..8ed778e1 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -3,31 +3,13 @@
use std::str::FromStr;
use crate::{Feedback, Pass};
+use crate::color::RgbaColor;
+use crate::compute::table::SpannedEntry;
use super::decoration::Decoration;
-use super::expr::*;
-use super::scope::Scope;
use super::span::{Pos, Span, Spanned};
use super::tokens::{is_newline_char, Token, TokenMode, Tokens};
-use super::tree::{SyntaxNode, SyntaxTree};
-
-/// A function which parses a function call into a dynamic node.
-pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<SyntaxNode>;
-
-/// An invocation of a function.
-#[derive(Debug, Clone, PartialEq)]
-pub struct FuncCall {
- pub name: Spanned<Ident>,
- pub args: TableExpr,
-}
-
-/// The state which can influence how a string of source code is parsed.
-///
-/// Parsing is pure - when passed in the same state and source code, the output
-/// must be the same.
-pub struct ParseState {
- /// The scope containing all function definitions.
- pub scope: Scope,
-}
+use super::tree::{CallExpr, Expr, SyntaxNode, SyntaxTree, TableExpr};
+use super::Ident;
/// Parse a string of source code.
///
@@ -35,7 +17,7 @@ pub struct ParseState {
/// `offset` position. This is used to make spans of a function body relative to
/// the start of the function as a whole as opposed to the start of the
/// function's body.
-pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
+pub fn parse(src: &str, offset: Pos) -> Pass<SyntaxTree> {
let mut tree = SyntaxTree::new();
let mut par = SyntaxTree::new();
let mut feedback = Feedback::new();
@@ -58,12 +40,12 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
}
Token::Function { header, body, terminated } => {
- let parsed = FuncParser::new(header, body, state).parse();
+ let parsed = FuncParser::new(header, body).parse();
feedback.extend_offset(parsed.feedback, span.start);
if !terminated {
error!(@feedback, Span::at(span.end), "expected closing bracket");
}
- parsed.output
+ SyntaxNode::Call(parsed.output)
}
Token::Star => SyntaxNode::ToggleBolder,
@@ -97,8 +79,6 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
}
struct FuncParser<'s> {
- state: &'s ParseState,
- /// The tokens inside the header.
tokens: Tokens<'s>,
peeked: Option<Option<Spanned<Token<'s>>>>,
body: Option<Spanned<&'s str>>,
@@ -106,13 +86,8 @@ struct FuncParser<'s> {
}
impl<'s> FuncParser<'s> {
- fn new(
- header: &'s str,
- body: Option<Spanned<&'s str>>,
- state: &'s ParseState,
- ) -> Self {
+ fn new(header: &'s str, body: Option<Spanned<&'s str>>) -> Self {
Self {
- state,
// Start at column 1 because the opening bracket is also part of
// the function, but not part of the `header` string.
tokens: Tokens::new(header, Pos::new(0, 1), TokenMode::Header),
@@ -122,65 +97,17 @@ impl<'s> FuncParser<'s> {
}
}
- fn parse(mut self) -> Pass<SyntaxNode> {
- let (parser, mut call) = if let Some(call) = self.parse_func_header() {
- let name = call.name.v.as_str();
- let (parser, deco) = match self.state.scope.get_parser(name) {
- // The function exists in the scope.
- Some(parser) => (parser, Decoration::ResolvedFunc),
-
- // The function does not exist in the scope. The parser that is
- // returned here is a fallback parser which exists to make sure
- // the content of the function is not totally dropped (on a best
- // effort basis).
- None => {
- error!(@self.feedback, call.name.span, "unknown function");
- let parser = self.state.scope.get_fallback_parser();
- (parser, Decoration::UnresolvedFunc)
- }
- };
-
- self.feedback.decorations.push(Spanned::new(deco, call.name.span));
- (parser, call)
- } else {
- // Parse the call with the fallback parser even when the header is
- // completely unparsable.
- let parser = self.state.scope.get_fallback_parser();
- let call = FuncCall {
- name: Spanned::new(Ident(String::new()), Span::ZERO),
- args: TableExpr::new(),
- };
- (parser, call)
- };
-
- if let Some(body) = self.body {
- call.args.push(TableExprEntry {
- key: Span::ZERO,
- val: body.map(|src| {
- let parsed = parse(src, body.span.start, &self.state);
- self.feedback.extend(parsed.feedback);
- Expr::Tree(parsed.output)
- }),
- });
- }
-
- let parsed = parser(call, self.state);
- self.feedback.extend(parsed.feedback);
-
- Pass::new(parsed.output, self.feedback)
- }
-
- fn parse_func_header(&mut self) -> Option<FuncCall> {
+ fn parse(mut self) -> Pass<CallExpr> {
let after_bracket = self.pos();
self.skip_white();
- let name = try_opt_or!(self.parse_ident(), {
+ let name = self.parse_ident().unwrap_or_else(|| {
self.expected_found_or_at("function name", after_bracket);
- return None;
+ Spanned::zero(Ident(String::new()))
});
self.skip_white();
- let args = match self.eat().map(Spanned::value) {
+ let mut args = match self.eat().map(Spanned::value) {
Some(Token::Colon) => self.parse_table(false).0.v,
Some(_) => {
self.expected_at("colon", name.span.end);
@@ -189,7 +116,15 @@ impl<'s> FuncParser<'s> {
None => TableExpr::new(),
};
- Some(FuncCall { name, args })
+ if let Some(body) = self.body {
+ args.push(SpannedEntry::val(body.map(|src| {
+ let parsed = parse(src, body.span.start);
+ self.feedback.extend(parsed.feedback);
+ Expr::Tree(parsed.output)
+ })));
+ }
+
+ Pass::new(CallExpr { name, args }, self.feedback)
}
}
@@ -325,14 +260,20 @@ impl FuncParser<'_> {
}
}
- fn parse_func_call(&mut self, name: Spanned<Ident>) -> Spanned<FuncCall> {
+ fn parse_func_call(&mut self, name: Spanned<Ident>) -> Spanned<CallExpr> {
let args = self.parse_table(true).0;
let span = Span::merge(name.span, args.span);
- Spanned::new(FuncCall { name, args: args.v }, span)
+ Spanned::new(CallExpr { name, args: args.v }, span)
}
- /// The boolean tells you whether the table can be coerced into an expression
- /// (this is the case when it's length 1 and has no trailing comma).
+ /// Set `parens` to true, when this should expect an opening paren and stop
+ /// at the balanced closing paren (this is the case for normal tables and
+ /// round-paren function calls). Set it to false, when this is used to parse
+ /// the top-level function arguments.
+ ///
+ /// The returned boolean tells you whether the table can be coerced into an
+ /// expression (this is the case when it's length 1 and has no trailing
+ /// comma).
fn parse_table(&mut self, parens: bool) -> (Spanned<TableExpr>, bool) {
let start = self.pos();
if parens {
@@ -369,19 +310,19 @@ impl FuncParser<'_> {
coercable = false;
behind_arg = val.span.end;
- table.insert(key.v.0, TableExprEntry::new(key.span, val));
+ table.insert(key.v.0, SpannedEntry::new(key.span, val));
} else if self.check(Token::LeftParen) {
let call = self.parse_func_call(ident);
let expr = call.map(|call| Expr::Call(call));
behind_arg = expr.span.end;
- table.push(TableExprEntry::val(expr));
+ table.push(SpannedEntry::val(expr));
} else {
let expr = ident.map(|id| Expr::Ident(id));
behind_arg = expr.span.end;
- table.push(TableExprEntry::val(expr));
+ table.push(SpannedEntry::val(expr));
}
} else {
// It's a positional argument.
@@ -390,7 +331,7 @@ impl FuncParser<'_> {
continue;
});
behind_arg = expr.span.end;
- table.push(TableExprEntry::val(expr));
+ table.push(SpannedEntry::val(expr));
}
self.skip_white();
@@ -593,7 +534,7 @@ mod tests {
}
macro_rules! F {
- ($($tts:tt)*) => { SyntaxNode::boxed(DebugNode(Call!(@$($tts)*))) }
+ ($($tts:tt)*) => { SyntaxNode::Call(Call!(@$($tts)*)) }
}
// ------------------------ Construct Expressions ----------------------- //
@@ -603,24 +544,17 @@ mod tests {
fn Id(ident: &str) -> Expr { Expr::Ident(Ident(ident.to_string())) }
fn Str(string: &str) -> Expr { Expr::Str(string.to_string()) }
- macro_rules! Tree {
- (@$($node:expr),* $(,)?) => {
- vec![$(Into::<Spanned<SyntaxNode>>::into($node)),*]
- };
- ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) };
- }
-
macro_rules! Table {
(@table=$table:expr,) => {};
(@table=$table:expr, $key:expr => $value:expr $(, $($tts:tt)*)?) => {{
let key = Into::<Spanned<&str>>::into($key);
let val = Into::<Spanned<Expr>>::into($value);
- $table.insert(key.v, TableExprEntry::new(key.span, val));
+ $table.insert(key.v, SpannedEntry::new(key.span, val));
Table![@table=$table, $($($tts)*)?];
}};
(@table=$table:expr, $value:expr $(, $($tts:tt)*)?) => {
let val = Into::<Spanned<Expr>>::into($value);
- $table.push(TableExprEntry::val(val));
+ $table.push(SpannedEntry::val(val));
Table![@table=$table, $($($tts)*)?];
};
(@$($tts:tt)*) => {{
@@ -632,6 +566,24 @@ mod tests {
($($tts:tt)*) => { Expr::Table(Table![@$($tts)*]) };
}
+ macro_rules! Tree {
+ (@$($node:expr),* $(,)?) => {
+ vec![$(Into::<Spanned<SyntaxNode>>::into($node)),*]
+ };
+ ($($tts:tt)*) => { Expr::Tree(Tree![@$($tts)*]) };
+ }
+
+ macro_rules! Call {
+ (@$name:expr $(; $($tts:tt)*)?) => {{
+ let name = Into::<Spanned<&str>>::into($name);
+ CallExpr {
+ name: name.map(|n| Ident(n.to_string())),
+ args: Table![@$($($tts)*)?],
+ }
+ }};
+ ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) };
+ }
+
fn Neg<T: Into<Spanned<Expr>>>(e1: T) -> Expr {
Expr::Neg(Box::new(e1.into()))
}
@@ -648,17 +600,6 @@ mod tests {
Expr::Div(Box::new(e1.into()), Box::new(e2.into()))
}
- macro_rules! Call {
- (@$name:expr $(; $($tts:tt)*)?) => {{
- let name = Into::<Spanned<&str>>::into($name);
- FuncCall {
- name: name.map(|n| Ident(n.to_string())),
- args: Table![@$($($tts)*)?],
- }
- }};
- ($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) };
- }
-
// ----------------------------- Test Macros ---------------------------- //
// Test syntax trees with or without spans.
@@ -667,7 +608,7 @@ mod tests {
macro_rules! test {
(@spans=$spans:expr, $src:expr => $($tts:tt)*) => {
let exp = Tree![@$($tts)*];
- let pass = parse_default($src);
+ let pass = parse($src, Pos::ZERO);
check($src, exp, pass.output, $spans);
};
}
@@ -683,7 +624,7 @@ mod tests {
macro_rules! e {
($src:expr => $($tts:tt)*) => {
let exp = vec![$($tts)*];
- let pass = parse_default($src);
+ let pass = parse($src, Pos::ZERO);
let found = pass.feedback.diagnostics.iter()
.map(|s| s.as_ref().map(|e| e.message.as_str()))
.collect::<Vec<_>>();
@@ -695,20 +636,11 @@ mod tests {
macro_rules! d {
($src:expr => $($tts:tt)*) => {
let exp = vec![$($tts)*];
- let pass = parse_default($src);
+ let pass = parse($src, Pos::ZERO);
check($src, exp, pass.feedback.decorations, true);
};
}
- fn parse_default(src: &str) -> Pass<SyntaxTree> {
- let mut scope = Scope::new(Box::new(debug_func));
- scope.insert("box", Box::new(debug_func));
- scope.insert("val", Box::new(debug_func));
- scope.insert("f", Box::new(debug_func));
- let state = ParseState { scope };
- parse(src, Pos::ZERO, &state)
- }
-
// -------------------------------- Tests ------------------------------- //
#[test]
@@ -797,15 +729,9 @@ mod tests {
e!("[\"]" => s(0,1, 0,3, "expected function name, found string"),
s(0,3, 0,3, "expected closing bracket"));
- // An unknown name.
- t!("[hi]" => P![F!("hi")]);
- e!("[hi]" => s(0,1, 0,3, "unknown function"));
- d!("[hi]" => s(0,1, 0,3, UnresolvedFunc));
-
// A valid name.
- t!("[f]" => P![F!("f")]);
+ t!("[hi]" => P![F!("hi")]);
t!("[ f]" => P![F!("f")]);
- d!("[ f]" => s(0,3, 0,4, ResolvedFunc));
// An invalid name.
e!("[12]" => s(0,1, 0,3, "expected function name, found number"));
@@ -821,7 +747,6 @@ mod tests {
t!("[val=]" => P![F!("val")]);
e!("[val=]" => s(0,4, 0,4, "expected colon"));
e!("[val/🌎:$]" => s(0,4, 0,4, "expected colon"));
- d!("[val=]" => s(0,1, 0,4, ResolvedFunc));
// String in invalid header without colon still parsed as string
// Note: No "expected quote" error because not even the string was
@@ -939,9 +864,9 @@ mod tests {
v!("(1, key=\"value\")" => Table![Num(1.0), "key" => Str("value")]);
// Decorations.
- d!("[val: key=hi]" => s(0,6, 0,9, TableKey), s(0,1, 0,4, ResolvedFunc));
- d!("[val: (key=hi)]" => s(0,7, 0,10, TableKey), s(0,1, 0,4, ResolvedFunc));
- d!("[val: f(key=hi)]" => s(0,8, 0,11, TableKey), s(0,1, 0,4, ResolvedFunc));
+ d!("[val: key=hi]" => s(0,6, 0,9, TableKey));
+ d!("[val: (key=hi)]" => s(0,7, 0,10, TableKey));
+ d!("[val: f(key=hi)]" => s(0,8, 0,11, TableKey));
// Spanned with spacing around keyword arguments.
ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0,0, 4,2, P![
diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs
deleted file mode 100644
index c17ff64d..00000000
--- a/src/syntax/scope.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-//! Mapping of function names to function parsers.
-
-use std::collections::HashMap;
-use std::fmt::{self, Debug, Formatter};
-
-use super::parsing::CallParser;
-
-/// A map from identifiers to function parsers.
-pub struct Scope {
- parsers: HashMap<String, Box<CallParser>>,
- fallback: Box<CallParser>,
-}
-
-impl Scope {
- /// Create a new empty scope with a fallback parser that is invoked when no
- /// match is found.
- pub fn new(fallback: Box<CallParser>) -> Self {
- Self {
- parsers: HashMap::new(),
- fallback,
- }
- }
-
- /// Associate the given function name with a dynamic node type.
- pub fn insert(&mut self, name: impl Into<String>, parser: Box<CallParser>) {
- self.parsers.insert(name.into(), parser);
- }
-
- /// Return the parser with the given name if there is one.
- pub fn get_parser(&self, name: &str) -> Option<&CallParser> {
- self.parsers.get(name).map(AsRef::as_ref)
- }
-
- /// Return the fallback parser.
- pub fn get_fallback_parser(&self) -> &CallParser {
- &*self.fallback
- }
-}
-
-impl Debug for Scope {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_set().entries(self.parsers.keys()).finish()
- }
-}
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index 923d13c2..89f773c7 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -39,13 +39,11 @@ impl<T> Offset for SpanVec<T> {
}
/// A value with the span it corresponds to in the source code.
-#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Spanned<T> {
- /// The value.
- pub v: T,
- /// The corresponding span.
pub span: Span,
+ pub v: T,
}
impl<T> Spanned<T> {
@@ -99,7 +97,7 @@ impl<T: Debug> Debug for Spanned<T> {
}
/// Locates a slice of source code.
-#[derive(Copy, Clone, Hash)]
+#[derive(Copy, Clone, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Span {
/// The inclusive start position.
diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs
index 4ce39cd4..e7a1eaf1 100644
--- a/src/syntax/tree.rs
+++ b/src/syntax/tree.rs
@@ -1,17 +1,20 @@
//! The syntax tree.
-use std::any::Any;
-use std::fmt::Debug;
+use std::fmt::{self, Debug, Formatter};
-use crate::layout::Layout;
-use super::span::SpanVec;
+use crate::color::RgbaColor;
+use crate::compute::table::{SpannedEntry, Table};
+use crate::compute::value::{TableValue, Value};
+use crate::length::Length;
+use super::span::{Spanned, SpanVec};
+use super::Ident;
/// A collection of nodes which form a tree together with the nodes' children.
pub type SyntaxTree = SpanVec<SyntaxNode>;
/// A syntax node, which encompasses a single logical entity of parsed source
/// code.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub enum SyntaxNode {
/// Whitespace containing less than two newlines.
Spacing,
@@ -27,84 +30,138 @@ pub enum SyntaxNode {
Raw(Vec<String>),
/// A paragraph of child nodes.
Par(SyntaxTree),
- /// A dynamic node, created through function invocations in source code.
- Dyn(Box<dyn DynamicNode>),
+ /// A function call.
+ Call(CallExpr),
}
-impl SyntaxNode {
- /// Create a `Dyn` variant from an unboxed dynamic node.
- pub fn boxed<T: DynamicNode + 'static>(node: T) -> SyntaxNode {
- SyntaxNode::Dyn(Box::new(node))
- }
+/// An expression.
+#[derive(Clone, PartialEq)]
+pub enum Expr {
+ /// An identifier: `ident`.
+ Ident(Ident),
+ /// A string: `"string"`.
+ Str(String),
+ /// A boolean: `true, false`.
+ Bool(bool),
+ /// A number: `1.2, 200%`.
+ Number(f64),
+ /// A length: `2cm, 5.2in`.
+ Length(Length),
+ /// A color value with alpha channel: `#f79143ff`.
+ Color(RgbaColor),
+ /// A table expression: `(false, 12cm, greeting="hi")`.
+ Table(TableExpr),
+ /// A syntax tree containing typesetting content.
+ Tree(SyntaxTree),
+ /// A function call expression: `cmyk(37.7, 0, 3.9, 1.1)`.
+ Call(CallExpr),
+ /// An operation that negates the contained expression.
+ Neg(Box<Spanned<Expr>>),
+ /// An operation that adds the contained expressions.
+ Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
+ /// An operation that subtracts the contained expressions.
+ Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
+ /// An operation that multiplies the contained expressions.
+ Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
+ /// An operation that divides the contained expressions.
+ Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
}
-impl PartialEq for SyntaxNode {
- fn eq(&self, other: &SyntaxNode) -> bool {
- use SyntaxNode::*;
- match (self, other) {
- (Spacing, Spacing) => true,
- (Linebreak, Linebreak) => true,
- (ToggleItalic, ToggleItalic) => true,
- (ToggleBolder, ToggleBolder) => true,
- (Text(a), Text(b)) => a == b,
- (Raw(a), Raw(b)) => a == b,
- (Par(a), Par(b)) => a == b,
- (Dyn(a), Dyn(b)) => a == b,
- _ => false,
+impl Expr {
+ /// A natural-language name of the type of this expression, e.g.
+ /// "identifier".
+ pub fn name(&self) -> &'static str {
+ use Expr::*;
+ match self {
+ Ident(_) => "identifier",
+ Str(_) => "string",
+ Bool(_) => "bool",
+ Number(_) => "number",
+ Length(_) => "length",
+ Color(_) => "color",
+ Table(_) => "table",
+ Tree(_) => "syntax tree",
+ Call(_) => "function call",
+ Neg(_) => "negation",
+ Add(_, _) => "addition",
+ Sub(_, _) => "subtraction",
+ Mul(_, _) => "multiplication",
+ Div(_, _) => "division",
}
}
-}
-
-/// Dynamic syntax nodes.
-///
-/// *Note*: This is automatically implemented for all types which are
-/// `Debug + Clone + PartialEq`, `Layout` and `'static`.
-pub trait DynamicNode: Debug + Layout {
- /// Convert into a `dyn Any`.
- fn as_any(&self) -> &dyn Any;
-
- /// Check for equality with another dynamic node.
- fn dyn_eq(&self, other: &dyn DynamicNode) -> bool;
- /// Clone into a boxed node trait object.
- fn box_clone(&self) -> Box<dyn DynamicNode>;
-}
-
-impl dyn DynamicNode {
- /// Downcast this dynamic node to a concrete node.
- pub fn downcast<T: DynamicNode + 'static>(&self) -> Option<&T> {
- self.as_any().downcast_ref::<T>()
+ /// Evaluate the expression to a value.
+ pub fn eval(&self) -> Value {
+ use Expr::*;
+ match self {
+ Ident(i) => Value::Ident(i.clone()),
+ Str(s) => Value::Str(s.clone()),
+ &Bool(b) => Value::Bool(b),
+ &Number(n) => Value::Number(n),
+ &Length(s) => Value::Length(s),
+ &Color(c) => Value::Color(c),
+ Table(t) => Value::Table(t.eval()),
+ Tree(t) => Value::Tree(t.clone()),
+ Call(_) => todo!("eval call"),
+ Neg(_) => todo!("eval neg"),
+ Add(_, _) => todo!("eval add"),
+ Sub(_, _) => todo!("eval sub"),
+ Mul(_, _) => todo!("eval mul"),
+ Div(_, _) => todo!("eval div"),
+ }
}
}
-impl PartialEq for dyn DynamicNode {
- fn eq(&self, other: &Self) -> bool {
- self.dyn_eq(other)
+impl Debug for Expr {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ use Expr::*;
+ match self {
+ Ident(i) => i.fmt(f),
+ Str(s) => s.fmt(f),
+ Bool(b) => b.fmt(f),
+ Number(n) => n.fmt(f),
+ Length(s) => s.fmt(f),
+ Color(c) => c.fmt(f),
+ Table(t) => t.fmt(f),
+ Tree(t) => t.fmt(f),
+ Call(c) => c.fmt(f),
+ Neg(e) => write!(f, "-{:?}", e),
+ Add(a, b) => write!(f, "({:?} + {:?})", a, b),
+ Sub(a, b) => write!(f, "({:?} - {:?})", a, b),
+ Mul(a, b) => write!(f, "({:?} * {:?})", a, b),
+ Div(a, b) => write!(f, "({:?} / {:?})", a, b),
+ }
}
}
-impl Clone for Box<dyn DynamicNode> {
- fn clone(&self) -> Self {
- self.box_clone()
- }
-}
+/// A table of expressions.
+///
+/// # Example
+/// ```typst
+/// (false, 12cm, greeting="hi")
+/// ```
+pub type TableExpr = Table<SpannedEntry<Expr>>;
-impl<T> DynamicNode for T
-where
- T: Debug + PartialEq + Clone + Layout + 'static,
-{
- fn as_any(&self) -> &dyn Any {
- self
- }
+impl TableExpr {
+ /// Evaluate the table expression to a table value.
+ pub fn eval(&self) -> TableValue {
+ let mut table = TableValue::new();
- fn dyn_eq(&self, other: &dyn DynamicNode) -> bool {
- match other.as_any().downcast_ref::<Self>() {
- Some(other) => self == other,
- None => false,
+ for (&key, entry) in self.nums() {
+ table.insert(key, entry.as_ref().map(|val| val.eval()));
}
- }
- fn box_clone(&self) -> Box<dyn DynamicNode> {
- Box::new(self.clone())
+ for (key, entry) in self.strs() {
+ table.insert(key.clone(), entry.as_ref().map(|val| val.eval()));
+ }
+
+ table
}
}
+
+/// An invocation of a function.
+#[derive(Debug, Clone, PartialEq)]
+pub struct CallExpr {
+ pub name: Spanned<Ident>,
+ pub args: TableExpr,
+}