mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Better argument parsing 🥙
This commit is contained in:
parent
ef8aa763fa
commit
605ab104c5
134
src/eval/args.rs
Normal file
134
src/eval/args.rs
Normal file
@ -0,0 +1,134 @@
|
||||
//! Simplifies argument parsing.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A wrapper around a dictionary value that simplifies argument parsing in
|
||||
/// functions.
|
||||
pub struct Args(pub Spanned<ValueDict>);
|
||||
|
||||
impl Args {
|
||||
/// Retrieve and remove the argument associated with the given key if there
|
||||
/// is any.
|
||||
///
|
||||
/// Generates an error if the key exists, but the value can't be converted
|
||||
/// into the type `T`.
|
||||
pub fn get<'a, K, T>(&mut self, ctx: &mut LayoutContext, key: K) -> Option<T>
|
||||
where
|
||||
K: Into<RefKey<'a>>,
|
||||
T: Convert,
|
||||
{
|
||||
self.0.v.remove(key).and_then(|entry| {
|
||||
let span = entry.value.span;
|
||||
let (t, diag) = T::convert(entry.value);
|
||||
if let Some(diag) = diag {
|
||||
ctx.f.diags.push(diag.span_with(span))
|
||||
}
|
||||
t.ok()
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve and remove the first matching positional argument.
|
||||
pub fn find<T>(&mut self) -> Option<T>
|
||||
where
|
||||
T: Convert,
|
||||
{
|
||||
for (&key, entry) in self.0.v.nums_mut() {
|
||||
let span = entry.value.span;
|
||||
match T::convert(mem::take(&mut entry.value)).0 {
|
||||
Ok(t) => {
|
||||
self.0.v.remove(key);
|
||||
return Some(t);
|
||||
}
|
||||
Err(v) => entry.value = v.span_with(span),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Retrieve and remove all matching positional arguments.
|
||||
pub fn find_all<T>(&mut self) -> impl Iterator<Item = T> + '_
|
||||
where
|
||||
T: Convert,
|
||||
{
|
||||
let mut skip = 0;
|
||||
std::iter::from_fn(move || {
|
||||
for (&key, entry) in self.0.v.nums_mut().skip(skip) {
|
||||
let span = entry.value.span;
|
||||
match T::convert(mem::take(&mut entry.value)).0 {
|
||||
Ok(t) => {
|
||||
self.0.v.remove(key);
|
||||
return Some(t);
|
||||
}
|
||||
Err(v) => entry.value = v.span_with(span),
|
||||
}
|
||||
skip += 1;
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve and remove all matching keyword arguments.
|
||||
pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_
|
||||
where
|
||||
T: Convert,
|
||||
{
|
||||
let mut skip = 0;
|
||||
std::iter::from_fn(move || {
|
||||
for (key, entry) in self.0.v.strs_mut().skip(skip) {
|
||||
let span = entry.value.span;
|
||||
match T::convert(mem::take(&mut entry.value)).0 {
|
||||
Ok(t) => {
|
||||
let key = key.clone();
|
||||
self.0.v.remove(&key);
|
||||
return Some((key, t));
|
||||
}
|
||||
Err(v) => entry.value = v.span_with(span),
|
||||
}
|
||||
skip += 1;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Generated _unexpected argument_ errors for all remaining entries.
|
||||
pub fn done(&self, ctx: &mut LayoutContext) {
|
||||
for entry in self.0.v.values() {
|
||||
let span = entry.key_span.join(entry.value.span);
|
||||
error!(@ctx.f, span, "unexpected argument");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(value: Value) -> SpannedEntry<Value> {
|
||||
SpannedEntry::value(Spanned::zero(value))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_args_find() {
|
||||
let mut args = Args(Spanned::zero(Dict::new()));
|
||||
args.0.v.insert(1, entry(Value::Bool(false)));
|
||||
args.0.v.insert(2, entry(Value::Str("hi".to_string())));
|
||||
assert_eq!(args.find::<String>(), Some("hi".to_string()));
|
||||
assert_eq!(args.0.v.len(), 1);
|
||||
assert_eq!(args.find::<bool>(), Some(false));
|
||||
assert!(args.0.v.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_args_find_all() {
|
||||
let mut args = Args(Spanned::zero(Dict::new()));
|
||||
args.0.v.insert(1, entry(Value::Bool(false)));
|
||||
args.0.v.insert(3, entry(Value::Float(0.0)));
|
||||
args.0.v.insert(7, entry(Value::Bool(true)));
|
||||
assert_eq!(args.find_all::<bool>().collect::<Vec<_>>(), [false, true]);
|
||||
assert_eq!(args.0.v.len(), 1);
|
||||
assert_eq!(args.0.v[3].value.v, Value::Float(0.0));
|
||||
}
|
||||
}
|
194
src/eval/convert.rs
Normal file
194
src/eval/convert.rs
Normal file
@ -0,0 +1,194 @@
|
||||
//! Conversion from values into other types.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use super::*;
|
||||
use crate::diag::Diag;
|
||||
use crate::geom::Linear;
|
||||
use crate::layout::{Dir, SpecAlign};
|
||||
use crate::paper::Paper;
|
||||
|
||||
/// Types that values can be converted into.
|
||||
pub trait Convert: Sized {
|
||||
/// Convert a value into `Self`.
|
||||
///
|
||||
/// If the conversion works out, this should return `Ok(...)` with an
|
||||
/// instance of `Self`. If it doesn't, it should return `Err(...)` giving
|
||||
/// back the original value.
|
||||
///
|
||||
/// In addition to the result, the method can return an optional diagnostic
|
||||
/// to warn even when the conversion suceeded or to explain the problem when
|
||||
/// the conversion failed.
|
||||
///
|
||||
/// The function takes a `Spanned<Value>` instead of just a `Value` so that
|
||||
/// this trait can be blanket implemented for `Spanned<T>` where `T:
|
||||
/// Convert`.
|
||||
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>);
|
||||
}
|
||||
|
||||
impl<T: Convert> Convert for Spanned<T> {
|
||||
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
|
||||
let span = value.span;
|
||||
let (result, diag) = T::convert(value);
|
||||
(result.map(|v| v.span_with(span)), diag)
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [length] values.
|
||||
///
|
||||
/// [length]: enum.Value.html#variant.Length
|
||||
pub struct Absolute(pub f64);
|
||||
|
||||
impl From<Absolute> for f64 {
|
||||
fn from(abs: Absolute) -> f64 {
|
||||
abs.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [relative] values.
|
||||
///
|
||||
/// [relative]: enum.Value.html#variant.Relative
|
||||
pub struct Relative(pub f64);
|
||||
|
||||
impl From<Relative> for f64 {
|
||||
fn from(rel: Relative) -> f64 {
|
||||
rel.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [identifier] and [string] values.
|
||||
///
|
||||
/// [identifier]: enum.Value.html#variant.Ident
|
||||
/// [string]: enum.Value.html#variant.Str
|
||||
pub struct StringLike(pub String);
|
||||
|
||||
impl From<StringLike> for String {
|
||||
fn from(like: StringLike) -> String {
|
||||
like.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StringLike {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_match {
|
||||
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
|
||||
impl Convert for $type {
|
||||
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
|
||||
#[allow(unreachable_patterns)]
|
||||
match value.v {
|
||||
$($p => (Ok($r), None)),*,
|
||||
v => {
|
||||
let err = error!("expected {}, found {}", $name, v.ty());
|
||||
(Err(v), Some(err))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_match!(Value, "value", v => v);
|
||||
impl_match!(Ident, "ident", Value::Ident(v) => v);
|
||||
impl_match!(bool, "bool", Value::Bool(v) => v);
|
||||
impl_match!(i64, "int", Value::Int(v) => v);
|
||||
impl_match!(f64, "float",
|
||||
Value::Int(v) => v as f64,
|
||||
Value::Float(v) => v,
|
||||
);
|
||||
impl_match!(Absolute, "length", Value::Length(v) => Absolute(v));
|
||||
impl_match!(Relative, "relative", Value::Relative(v) => Relative(v));
|
||||
impl_match!(Linear, "linear",
|
||||
Value::Linear(v) => v,
|
||||
Value::Length(v) => Linear::abs(v),
|
||||
Value::Relative(v) => Linear::rel(v),
|
||||
);
|
||||
impl_match!(String, "string", Value::Str(v) => v);
|
||||
impl_match!(SynTree, "tree", Value::Content(v) => v);
|
||||
impl_match!(ValueDict, "dict", Value::Dict(v) => v);
|
||||
impl_match!(ValueFunc, "function", Value::Func(v) => v);
|
||||
impl_match!(StringLike, "ident or string",
|
||||
Value::Ident(Ident(v)) => StringLike(v),
|
||||
Value::Str(v) => StringLike(v),
|
||||
);
|
||||
|
||||
macro_rules! impl_ident {
|
||||
($type:ty, $name:expr, $parse:expr) => {
|
||||
impl Convert for $type {
|
||||
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
|
||||
match value.v {
|
||||
Value::Ident(id) => {
|
||||
if let Some(thing) = $parse(&id) {
|
||||
(Ok(thing), None)
|
||||
} else {
|
||||
(Err(Value::Ident(id)), Some(error!("invalid {}", $name)))
|
||||
}
|
||||
}
|
||||
v => {
|
||||
let err = error!("expected {}, found {}", $name, v.ty());
|
||||
(Err(v), Some(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ident!(Dir, "direction", |v| match v {
|
||||
"ltr" => Some(Self::LTR),
|
||||
"rtl" => Some(Self::RTL),
|
||||
"ttb" => Some(Self::TTB),
|
||||
"btt" => Some(Self::BTT),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
impl_ident!(SpecAlign, "alignment", |v| match v {
|
||||
"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", Self::from_str);
|
||||
impl_ident!(FontStretch, "font stretch", Self::from_str);
|
||||
impl_ident!(Paper, "paper", Self::from_name);
|
||||
|
||||
impl Convert for FontWeight {
|
||||
fn convert(value: Spanned<Value>) -> (Result<Self, Value>, Option<Diag>) {
|
||||
match value.v {
|
||||
Value::Int(number) => {
|
||||
let [min, max] = [100, 900];
|
||||
let warning = if number < min {
|
||||
Some(warning!("the minimum font weight is {}", min))
|
||||
} else if number > max {
|
||||
Some(warning!("the maximum font weight is {}", max))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let weight = Self::from_number(number.min(max).max(min) as u16);
|
||||
(Ok(weight), warning)
|
||||
}
|
||||
Value::Ident(id) => {
|
||||
if let Some(thing) = FontWeight::from_str(&id) {
|
||||
(Ok(thing), None)
|
||||
} else {
|
||||
(Err(Value::Ident(id)), Some(error!("invalid font weight")))
|
||||
}
|
||||
}
|
||||
v => {
|
||||
let err =
|
||||
error!("expected font weight (name or number), found {}", v.ty());
|
||||
(Err(v), Some(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
259
src/eval/dict.rs
259
src/eval/dict.rs
@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::iter::Extend;
|
||||
use std::iter::{Extend, FromIterator};
|
||||
use std::ops::Index;
|
||||
|
||||
use crate::syntax::{Span, Spanned};
|
||||
@ -115,49 +115,16 @@ impl<V> Dict<V> {
|
||||
self.nums.insert(self.lowest_free, value);
|
||||
self.lowest_free += 1;
|
||||
}
|
||||
|
||||
/// Iterator over all borrowed keys and values.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
/// Iterate over all values in the dictionary.
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
|
||||
}
|
||||
|
||||
/// Iterate over the number key-value pairs.
|
||||
pub fn nums(&self) -> std::collections::btree_map::Iter<u64, V> {
|
||||
self.nums.iter()
|
||||
}
|
||||
|
||||
/// Iterate over the string key-value pairs.
|
||||
pub fn strs(&self) -> std::collections::btree_map::Iter<String, V> {
|
||||
self.strs.iter()
|
||||
}
|
||||
|
||||
/// Move into an owned iterator over all values in the dictionary.
|
||||
pub fn into_values(self) -> impl Iterator<Item = V> {
|
||||
self.nums
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.chain(self.strs.into_iter().map(|(_, v)| v))
|
||||
}
|
||||
|
||||
/// Iterate over the number key-value pairs.
|
||||
pub fn into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
|
||||
self.nums.into_iter()
|
||||
}
|
||||
|
||||
/// Iterate over the string key-value pairs.
|
||||
pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
|
||||
self.strs.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for Dict<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
impl<'a, K, V> Index<K> for Dict<V>
|
||||
where
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, index: K) -> &Self::Output {
|
||||
self.get(index).expect("key not in dict")
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,65 +136,9 @@ impl<V: PartialEq> PartialEq for Dict<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> IntoIterator for Dict<V> {
|
||||
type Item = (DictKey, V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IntoIter<u64, V>,
|
||||
fn((u64, V)) -> (DictKey, V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IntoIter<String, V>,
|
||||
fn((String, V)) -> (DictKey, V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let nums = self.nums.into_iter().map((|(k, v)| (DictKey::Num(k), v)) as _);
|
||||
let strs = self.strs.into_iter().map((|(k, v)| (DictKey::Str(k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, V> IntoIterator for &'a Dict<V> {
|
||||
type Item = (RefKey<'a>, &'a V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::Iter<'a, u64, V>,
|
||||
fn((&'a u64, &'a V)) -> (RefKey<'a>, &'a V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::Iter<'a, String, V>,
|
||||
fn((&'a String, &'a V)) -> (RefKey<'a>, &'a V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
|
||||
let nums = self.nums().map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Extend<(DictKey, V)> for Dict<V> {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = (DictKey, V)>,
|
||||
{
|
||||
for (key, value) in iter.into_iter() {
|
||||
self.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V> Index<K> for Dict<V>
|
||||
where
|
||||
K: Into<RefKey<'a>>,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, index: K) -> &Self::Output {
|
||||
self.get(index).expect("key not in dict")
|
||||
impl<V> Default for Dict<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,6 +181,152 @@ impl<V: Debug> Debug for Dict<V> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iteration.
|
||||
impl<V> Dict<V> {
|
||||
/// Iterator over all borrowed keys and values.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (RefKey, &V)> {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
/// Iterator over all borrowed keys and values.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (RefKey, &mut V)> {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
/// Iterate over all values in the dictionary.
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
|
||||
}
|
||||
|
||||
/// Iterate over all values in the dictionary.
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.nums
|
||||
.iter_mut()
|
||||
.map(|(_, v)| v)
|
||||
.chain(self.strs.iter_mut().map(|(_, v)| v))
|
||||
}
|
||||
|
||||
/// Move into an owned iterator over all values in the dictionary.
|
||||
pub fn into_values(self) -> impl Iterator<Item = V> {
|
||||
self.nums
|
||||
.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.chain(self.strs.into_iter().map(|(_, v)| v))
|
||||
}
|
||||
|
||||
/// Iterate over the number key-value pairs.
|
||||
pub fn nums(&self) -> std::collections::btree_map::Iter<u64, V> {
|
||||
self.nums.iter()
|
||||
}
|
||||
|
||||
/// Iterate mutably over the number key-value pairs.
|
||||
pub fn nums_mut(&mut self) -> std::collections::btree_map::IterMut<u64, V> {
|
||||
self.nums.iter_mut()
|
||||
}
|
||||
|
||||
/// Iterate over the number key-value pairs.
|
||||
pub fn into_nums(self) -> std::collections::btree_map::IntoIter<u64, V> {
|
||||
self.nums.into_iter()
|
||||
}
|
||||
|
||||
/// Iterate over the string key-value pairs.
|
||||
pub fn strs(&self) -> std::collections::btree_map::Iter<String, V> {
|
||||
self.strs.iter()
|
||||
}
|
||||
|
||||
/// Iterate mutably over the string key-value pairs.
|
||||
pub fn strs_mut(&mut self) -> std::collections::btree_map::IterMut<String, V> {
|
||||
self.strs.iter_mut()
|
||||
}
|
||||
|
||||
/// Iterate over the string key-value pairs.
|
||||
pub fn into_strs(self) -> std::collections::btree_map::IntoIter<String, V> {
|
||||
self.strs.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Extend<(DictKey, V)> for Dict<V> {
|
||||
fn extend<T: IntoIterator<Item = (DictKey, V)>>(&mut self, iter: T) {
|
||||
for (key, value) in iter.into_iter() {
|
||||
self.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> FromIterator<(DictKey, V)> for Dict<V> {
|
||||
fn from_iter<T: IntoIterator<Item = (DictKey, V)>>(iter: T) -> Self {
|
||||
let mut v = Self::new();
|
||||
v.extend(iter);
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> IntoIterator for Dict<V> {
|
||||
type Item = (DictKey, V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IntoIter<u64, V>,
|
||||
fn((u64, V)) -> (DictKey, V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IntoIter<String, V>,
|
||||
fn((String, V)) -> (DictKey, V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let nums = self.nums.into_iter().map((|(k, v)| (DictKey::Num(k), v)) as _);
|
||||
let strs = self.strs.into_iter().map((|(k, v)| (DictKey::Str(k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, V> IntoIterator for &'a Dict<V> {
|
||||
type Item = (RefKey<'a>, &'a V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::Iter<'a, u64, V>,
|
||||
fn((&'a u64, &'a V)) -> (RefKey<'a>, &'a V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::Iter<'a, String, V>,
|
||||
fn((&'a String, &'a V)) -> (RefKey<'a>, &'a V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let nums = self.nums().map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
|
||||
let strs = self.strs().map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, V> IntoIterator for &'a mut Dict<V> {
|
||||
type Item = (RefKey<'a>, &'a mut V);
|
||||
type IntoIter = std::iter::Chain<
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IterMut<'a, u64, V>,
|
||||
fn((&'a u64, &'a mut V)) -> (RefKey<'a>, &'a mut V),
|
||||
>,
|
||||
std::iter::Map<
|
||||
std::collections::btree_map::IterMut<'a, String, V>,
|
||||
fn((&'a String, &'a mut V)) -> (RefKey<'a>, &'a mut V),
|
||||
>,
|
||||
>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let nums = self
|
||||
.nums
|
||||
.iter_mut()
|
||||
.map((|(k, v): (&u64, _)| (RefKey::Num(*k), v)) as _);
|
||||
let strs = self
|
||||
.strs
|
||||
.iter_mut()
|
||||
.map((|(k, v): (&'a String, _)| (RefKey::Str(k), v)) as _);
|
||||
nums.chain(strs)
|
||||
}
|
||||
}
|
||||
|
||||
/// The owned variant of a dictionary key.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum DictKey {
|
||||
|
@ -1,10 +1,14 @@
|
||||
//! Evaluation of syntax trees.
|
||||
|
||||
mod args;
|
||||
mod convert;
|
||||
mod dict;
|
||||
mod scope;
|
||||
mod state;
|
||||
mod value;
|
||||
|
||||
pub use args::*;
|
||||
pub use convert::*;
|
||||
pub use dict::*;
|
||||
pub use scope::*;
|
||||
pub use state::*;
|
||||
@ -88,9 +92,10 @@ impl Eval for ExprCall {
|
||||
async fn eval(&self, ctx: &mut LayoutContext) -> Self::Output {
|
||||
let name = &self.name.v;
|
||||
let span = self.name.span;
|
||||
let args = self.args.eval(ctx).await;
|
||||
let dict = self.args.v.eval(ctx).await;
|
||||
|
||||
if let Some(func) = ctx.state.scope.get(name) {
|
||||
let args = Args(dict.span_with(self.args.span));
|
||||
ctx.f.decos.push(Deco::Resolved.span_with(span));
|
||||
(func.clone())(args, ctx).await
|
||||
} else {
|
||||
@ -98,7 +103,7 @@ impl Eval for ExprCall {
|
||||
error!(@ctx.f, span, "unknown function");
|
||||
ctx.f.decos.push(Deco::Unresolved.span_with(span));
|
||||
}
|
||||
Value::Dict(args)
|
||||
Value::Dict(dict)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,12 @@ use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use super::dict::{Dict, SpannedEntry};
|
||||
use super::{Args, Dict, SpannedEntry};
|
||||
use crate::color::RgbaColor;
|
||||
use crate::geom::Linear;
|
||||
use crate::layout::{Command, Dir, LayoutContext, SpecAlign};
|
||||
use crate::paper::Paper;
|
||||
use crate::layout::{Command, LayoutContext};
|
||||
use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree};
|
||||
use crate::{DynFuture, Feedback};
|
||||
use crate::DynFuture;
|
||||
|
||||
/// A computational value.
|
||||
#[derive(Clone, PartialEq)]
|
||||
@ -78,6 +75,12 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned<Value> {
|
||||
/// Transform this value into something layoutable.
|
||||
///
|
||||
@ -156,13 +159,13 @@ impl Debug for Value {
|
||||
pub struct ValueFunc(pub Rc<Func>);
|
||||
|
||||
/// The signature of executable functions.
|
||||
pub type Func = dyn Fn(ValueDict, &mut LayoutContext) -> DynFuture<Value>;
|
||||
pub type Func = dyn Fn(Args, &mut LayoutContext) -> DynFuture<Value>;
|
||||
|
||||
impl ValueFunc {
|
||||
/// Create a new function value from a rust function or closure.
|
||||
pub fn new<F: 'static>(f: F) -> Self
|
||||
where
|
||||
F: Fn(ValueDict, &mut LayoutContext) -> DynFuture<Value>,
|
||||
F: Fn(Args, &mut LayoutContext) -> DynFuture<Value>,
|
||||
{
|
||||
Self(Rc::new(f))
|
||||
}
|
||||
@ -197,366 +200,3 @@ impl Debug for ValueFunc {
|
||||
/// (false, 12cm, greeting="hi")
|
||||
/// ```
|
||||
pub type ValueDict = Dict<SpannedEntry<Value>>;
|
||||
|
||||
impl ValueDict {
|
||||
/// 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.value.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.
|
||||
///
|
||||
/// Generates an error at `err_span` when no matching value was found.
|
||||
pub fn expect<T: TryFromValue>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
span: Span,
|
||||
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.value.as_ref(), f) {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
error!(@f, span, "missing argument: {}", name);
|
||||
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_key<T>(&mut self, key: &str, f: &mut Feedback) -> Option<T>
|
||||
where
|
||||
T: TryFromValue,
|
||||
{
|
||||
self.remove(key).and_then(|entry| {
|
||||
let expr = entry.value.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.value.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.value.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() {
|
||||
error!(@f, entry.key_span.join(entry.value.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.ty()
|
||||
);
|
||||
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);
|
||||
if val.is_none() {
|
||||
error!(@f, value.span, "invalid {}", $name);
|
||||
}
|
||||
val
|
||||
} else {
|
||||
error!(
|
||||
@f, value.span,
|
||||
"expected {}, found {}", $name, value.v.ty()
|
||||
);
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [length] values.
|
||||
///
|
||||
/// [length]: enum.Value.html#variant.Length
|
||||
pub struct Absolute(pub f64);
|
||||
|
||||
impl From<Absolute> for f64 {
|
||||
fn from(abs: Absolute) -> f64 {
|
||||
abs.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [relative] values.
|
||||
///
|
||||
/// [relative]: enum.Value.html#variant.Relative
|
||||
pub struct Relative(pub f64);
|
||||
|
||||
impl From<Relative> for f64 {
|
||||
fn from(rel: Relative) -> f64 {
|
||||
rel.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [identifier] and [string] values.
|
||||
///
|
||||
/// [identifier]: enum.Value.html#variant.Ident
|
||||
/// [string]: enum.Value.html#variant.Str
|
||||
pub struct StringLike(pub String);
|
||||
|
||||
impl From<StringLike> for String {
|
||||
fn from(like: StringLike) -> String {
|
||||
like.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StringLike {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl_match!(Value, "value", v => v.clone());
|
||||
impl_match!(Ident, "identifier", Value::Ident(v) => v.clone());
|
||||
impl_match!(bool, "bool", &Value::Bool(v) => v);
|
||||
impl_match!(i64, "integer", &Value::Int(v) => v);
|
||||
impl_match!(f64, "float",
|
||||
&Value::Int(v) => v as f64,
|
||||
&Value::Float(v) => v,
|
||||
);
|
||||
impl_match!(Absolute, "length", &Value::Length(v) => Absolute(v));
|
||||
impl_match!(Relative, "relative", &Value::Relative(v) => Relative(v));
|
||||
impl_match!(Linear, "linear",
|
||||
&Value::Linear(v) => v,
|
||||
&Value::Length(v) => Linear::abs(v),
|
||||
&Value::Relative(v) => Linear::rel(v),
|
||||
);
|
||||
impl_match!(String, "string", Value::Str(v) => v.clone());
|
||||
impl_match!(SynTree, "tree", Value::Content(v) => v.clone());
|
||||
impl_match!(ValueDict, "dict", Value::Dict(v) => v.clone());
|
||||
impl_match!(ValueFunc, "function", Value::Func(v) => v.clone());
|
||||
impl_match!(StringLike, "identifier or string",
|
||||
Value::Ident(Ident(v)) => StringLike(v.clone()),
|
||||
Value::Str(v) => StringLike(v.clone()),
|
||||
);
|
||||
|
||||
impl_ident!(Dir, "direction", |v| match v {
|
||||
"ltr" => Some(Self::LTR),
|
||||
"rtl" => Some(Self::RTL),
|
||||
"ttb" => Some(Self::TTB),
|
||||
"btt" => Some(Self::BTT),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
impl_ident!(SpecAlign, "alignment", |v| match v {
|
||||
"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", Self::from_str);
|
||||
impl_ident!(FontStretch, "font stretch", Self::from_str);
|
||||
impl_ident!(Paper, "paper", Self::from_name);
|
||||
|
||||
impl TryFromValue for FontWeight {
|
||||
fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
|
||||
match value.v {
|
||||
&Value::Int(weight) => {
|
||||
const MIN: i64 = 100;
|
||||
const MAX: i64 = 900;
|
||||
let weight = if weight < MIN {
|
||||
error!(@f, value.span, "the minimum font weight is {}", MIN);
|
||||
MIN
|
||||
} else if weight > MAX {
|
||||
error!(@f, value.span, "the maximum font weight is {}", MAX);
|
||||
MAX
|
||||
} else {
|
||||
weight
|
||||
};
|
||||
Self::from_number(weight as u16)
|
||||
}
|
||||
Value::Ident(ident) => {
|
||||
let weight = Self::from_str(ident);
|
||||
if weight.is_none() {
|
||||
error!(@f, value.span, "invalid font weight");
|
||||
}
|
||||
weight
|
||||
}
|
||||
other => {
|
||||
error!(
|
||||
@f, value.span,
|
||||
"expected font weight (name or integer), found {}",
|
||||
other.ty(),
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(value: Value) -> SpannedEntry<Value> {
|
||||
SpannedEntry::value(Spanned::zero(value))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dict_take_removes_correct_entry() {
|
||||
let mut dict = Dict::new();
|
||||
dict.insert(1, entry(Value::Bool(false)));
|
||||
dict.insert(2, entry(Value::Str("hi".to_string())));
|
||||
assert_eq!(dict.take::<String>(), Some("hi".to_string()));
|
||||
assert_eq!(dict.len(), 1);
|
||||
assert_eq!(dict.take::<bool>(), Some(false));
|
||||
assert!(dict.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dict_expect_errors_about_previous_entries() {
|
||||
let mut f = Feedback::new();
|
||||
let mut dict = Dict::new();
|
||||
dict.insert(1, entry(Value::Bool(false)));
|
||||
dict.insert(3, entry(Value::Str("hi".to_string())));
|
||||
dict.insert(5, entry(Value::Bool(true)));
|
||||
assert_eq!(
|
||||
dict.expect::<String>("", Span::ZERO, &mut f),
|
||||
Some("hi".to_string())
|
||||
);
|
||||
assert_eq!(f.diags, [error!(Span::ZERO, "expected string, found bool")]);
|
||||
assert_eq!(dict.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dict_take_with_key_removes_the_entry() {
|
||||
let mut f = Feedback::new();
|
||||
let mut dict = Dict::new();
|
||||
dict.insert(1, entry(Value::Bool(false)));
|
||||
dict.insert("hi", entry(Value::Bool(true)));
|
||||
assert_eq!(dict.take::<bool>(), Some(false));
|
||||
assert_eq!(dict.take_key::<f64>("hi", &mut f), None);
|
||||
assert_eq!(f.diags, [error!(Span::ZERO, "expected float, found bool")]);
|
||||
assert!(dict.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dict_take_all_removes_the_correct_entries() {
|
||||
let mut dict = Dict::new();
|
||||
dict.insert(1, entry(Value::Bool(false)));
|
||||
dict.insert(3, entry(Value::Float(0.0)));
|
||||
dict.insert(7, entry(Value::Bool(true)));
|
||||
assert_eq!(dict.take_all_num::<bool>().collect::<Vec<_>>(), [
|
||||
(1, false),
|
||||
(7, true)
|
||||
],);
|
||||
assert_eq!(dict.len(), 1);
|
||||
assert_eq!(dict[3].value.v, Value::Float(0.0));
|
||||
}
|
||||
}
|
||||
|
@ -14,21 +14,41 @@ use super::*;
|
||||
/// - `vertical`: Any of `top`, `bottom` or `center`.
|
||||
///
|
||||
/// There may not be two alignment specifications for the same axis.
|
||||
pub async fn align(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
let content = args.take::<SynTree>();
|
||||
let h = args.take_key::<Spanned<SpecAlign>>("horizontal", &mut ctx.f);
|
||||
let v = args.take_key::<Spanned<SpecAlign>>("vertical", &mut ctx.f);
|
||||
let all = args
|
||||
.take_all_num_vals::<Spanned<SpecAlign>>()
|
||||
pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let body = args.find::<SynTree>();
|
||||
|
||||
let h = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal");
|
||||
let v = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical");
|
||||
let pos = args.find_all::<Spanned<SpecAlign>>();
|
||||
|
||||
let iter = pos
|
||||
.map(|align| (align.v.axis(), align))
|
||||
.chain(h.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
|
||||
.chain(v.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));
|
||||
|
||||
let aligns = parse_aligns(ctx, iter);
|
||||
|
||||
args.done(ctx);
|
||||
Value::Commands(match body {
|
||||
Some(tree) => vec![
|
||||
SetAlignment(aligns),
|
||||
LayoutSyntaxTree(tree),
|
||||
SetAlignment(ctx.state.align),
|
||||
],
|
||||
None => vec![SetAlignment(aligns)],
|
||||
})
|
||||
}
|
||||
|
||||
/// Deduplicate alignments and deduce to which axes they apply.
|
||||
fn parse_aligns(
|
||||
ctx: &mut LayoutContext,
|
||||
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>,
|
||||
) -> LayoutAlign {
|
||||
let mut aligns = ctx.state.align;
|
||||
let mut had = [false; 2];
|
||||
let mut deferred_center = false;
|
||||
|
||||
for (axis, align) in all {
|
||||
for (axis, align) in iter {
|
||||
// Check whether we know which axis this alignment belongs to. We don't
|
||||
// if the alignment is `center` for a positional argument. Then we set
|
||||
// `deferred_center` to true and handle the situation once we know more.
|
||||
@ -80,13 +100,5 @@ pub async fn align(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
aligns.primary = GenAlign::Center;
|
||||
}
|
||||
|
||||
args.unexpected(&mut ctx.f);
|
||||
Value::Commands(match content {
|
||||
Some(tree) => vec![
|
||||
SetAlignment(aligns),
|
||||
LayoutSyntaxTree(tree),
|
||||
SetAlignment(ctx.state.align),
|
||||
],
|
||||
None => vec![SetAlignment(aligns)],
|
||||
})
|
||||
aligns
|
||||
}
|
||||
|
@ -6,31 +6,32 @@ use crate::geom::Linear;
|
||||
/// # Keyword arguments
|
||||
/// - `width`: The width of the box (length or relative to parent's width).
|
||||
/// - `height`: The height of the box (length or relative to parent's height).
|
||||
pub async fn boxed(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
let content = args.take::<SynTree>().unwrap_or_default();
|
||||
pub async fn boxed(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let body = args.find::<SynTree>().unwrap_or_default();
|
||||
let width = args.get::<_, Linear>(ctx, "width");
|
||||
let height = args.get::<_, Linear>(ctx, "height");
|
||||
args.done(ctx);
|
||||
|
||||
let constraints = &mut ctx.constraints;
|
||||
constraints.base = constraints.spaces[0].size;
|
||||
constraints.spaces.truncate(1);
|
||||
constraints.repeat = false;
|
||||
|
||||
if let Some(width) = args.take_key::<Linear>("width", &mut ctx.f) {
|
||||
if let Some(width) = width {
|
||||
let abs = width.eval(constraints.base.width);
|
||||
constraints.base.width = abs;
|
||||
constraints.spaces[0].size.width = abs;
|
||||
constraints.spaces[0].expansion.horizontal = true;
|
||||
}
|
||||
|
||||
if let Some(height) = args.take_key::<Linear>("height", &mut ctx.f) {
|
||||
if let Some(height) = height {
|
||||
let abs = height.eval(constraints.base.height);
|
||||
constraints.base.height = abs;
|
||||
constraints.spaces[0].size.height = abs;
|
||||
constraints.spaces[0].expansion.vertical = true;
|
||||
}
|
||||
|
||||
args.unexpected(&mut ctx.f);
|
||||
|
||||
let layouted = layout_tree(&content, ctx).await;
|
||||
let layouted = layout_tree(&body, ctx).await;
|
||||
let layout = layouted.into_iter().next().unwrap();
|
||||
|
||||
Value::Commands(vec![Add(layout)])
|
||||
|
@ -2,24 +2,22 @@ use super::*;
|
||||
use crate::color::RgbaColor;
|
||||
|
||||
/// `rgb`: Create an RGB(A) color.
|
||||
pub async fn rgb(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
let mut f = Feedback::new();
|
||||
|
||||
let r = args.expect::<Spanned<i64>>("red value", Span::ZERO, &mut f);
|
||||
let g = args.expect::<Spanned<i64>>("green value", Span::ZERO, &mut f);
|
||||
let b = args.expect::<Spanned<i64>>("blue value", Span::ZERO, &mut f);
|
||||
let a = args.take::<Spanned<i64>>();
|
||||
pub async fn rgb(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let r = args.get::<_, Spanned<i64>>(ctx, 0);
|
||||
let g = args.get::<_, Spanned<i64>>(ctx, 1);
|
||||
let b = args.get::<_, Spanned<i64>>(ctx, 2);
|
||||
let a = args.get::<_, Spanned<i64>>(ctx, 3);
|
||||
args.done(ctx);
|
||||
|
||||
let mut clamp = |component: Option<Spanned<i64>>, default| {
|
||||
component.map_or(default, |c| {
|
||||
if c.v < 0 || c.v > 255 {
|
||||
error!(@f, c.span, "should be between 0 and 255")
|
||||
error!(@ctx.f, c.span, "should be between 0 and 255")
|
||||
}
|
||||
c.v.max(0).min(255) as u8
|
||||
})
|
||||
};
|
||||
|
||||
args.unexpected(&mut ctx.f);
|
||||
Value::Color(RgbaColor::new(
|
||||
clamp(r, 0),
|
||||
clamp(g, 0),
|
||||
|
@ -49,13 +49,13 @@ use crate::geom::Linear;
|
||||
/// ```typst
|
||||
/// [font: "My Serif", serif]
|
||||
/// ```
|
||||
pub async fn font(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
pub async fn font(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let mut text = ctx.state.text.clone();
|
||||
let mut updated_fallback = false;
|
||||
let mut needs_flatten = false;
|
||||
|
||||
let content = args.take::<SynTree>();
|
||||
let body = args.find::<SynTree>();
|
||||
|
||||
if let Some(linear) = args.take::<Linear>() {
|
||||
if let Some(linear) = args.find::<Linear>() {
|
||||
if linear.rel == 0.0 {
|
||||
text.font_size.base = linear.abs;
|
||||
text.font_size.scale = Linear::rel(1.0);
|
||||
@ -64,44 +64,41 @@ pub async fn font(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
}
|
||||
}
|
||||
|
||||
let list: Vec<_> = args
|
||||
.take_all_num_vals::<StringLike>()
|
||||
.map(|s| s.to_lowercase())
|
||||
.collect();
|
||||
|
||||
let list: Vec<_> = args.find_all::<StringLike>().map(|s| s.to_lowercase()).collect();
|
||||
if !list.is_empty() {
|
||||
text.fallback.list = list;
|
||||
updated_fallback = true;
|
||||
needs_flatten = true;
|
||||
}
|
||||
|
||||
if let Some(style) = args.take_key::<FontStyle>("style", &mut ctx.f) {
|
||||
if let Some(style) = args.get::<_, FontStyle>(ctx, "style") {
|
||||
text.variant.style = style;
|
||||
}
|
||||
|
||||
if let Some(weight) = args.take_key::<FontWeight>("weight", &mut ctx.f) {
|
||||
if let Some(weight) = args.get::<_, FontWeight>(ctx, "weight") {
|
||||
text.variant.weight = weight;
|
||||
}
|
||||
|
||||
if let Some(stretch) = args.take_key::<FontStretch>("stretch", &mut ctx.f) {
|
||||
if let Some(stretch) = args.get::<_, FontStretch>(ctx, "stretch") {
|
||||
text.variant.stretch = stretch;
|
||||
}
|
||||
|
||||
for (class, mut dict) in args.take_all_str::<ValueDict>() {
|
||||
let fallback = dict
|
||||
.take_all_num_vals::<StringLike>()
|
||||
for (class, dict) in args.find_all_str::<Spanned<ValueDict>>() {
|
||||
let fallback = Args(dict)
|
||||
.find_all::<StringLike>()
|
||||
.map(|s| s.to_lowercase())
|
||||
.collect();
|
||||
|
||||
text.fallback.update_class_list(class, fallback);
|
||||
updated_fallback = true;
|
||||
needs_flatten = true;
|
||||
}
|
||||
|
||||
if updated_fallback {
|
||||
args.done(ctx);
|
||||
|
||||
if needs_flatten {
|
||||
text.fallback.flatten();
|
||||
}
|
||||
|
||||
args.unexpected(&mut ctx.f);
|
||||
Value::Commands(match content {
|
||||
Value::Commands(match body {
|
||||
Some(tree) => vec![
|
||||
SetTextState(text),
|
||||
LayoutSyntaxTree(tree),
|
||||
|
@ -19,54 +19,54 @@ use crate::paper::{Paper, PaperClass};
|
||||
/// - `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 async fn page(mut args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
pub async fn page(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let mut page = ctx.state.page.clone();
|
||||
|
||||
if let Some(paper) = args.take::<Paper>() {
|
||||
if let Some(paper) = args.find::<Paper>() {
|
||||
page.class = paper.class;
|
||||
page.size = paper.size();
|
||||
}
|
||||
|
||||
if let Some(Absolute(width)) = args.take_key::<Absolute>("width", &mut ctx.f) {
|
||||
if let Some(Absolute(width)) = args.get::<_, Absolute>(ctx, "width") {
|
||||
page.class = PaperClass::Custom;
|
||||
page.size.width = width;
|
||||
}
|
||||
|
||||
if let Some(Absolute(height)) = args.take_key::<Absolute>("height", &mut ctx.f) {
|
||||
if let Some(Absolute(height)) = args.get::<_, Absolute>(ctx, "height") {
|
||||
page.class = PaperClass::Custom;
|
||||
page.size.height = height;
|
||||
}
|
||||
|
||||
if let Some(margins) = args.take_key::<Linear>("margins", &mut ctx.f) {
|
||||
if let Some(margins) = args.get::<_, Linear>(ctx, "margins") {
|
||||
page.margins = Sides::uniform(Some(margins));
|
||||
}
|
||||
|
||||
if let Some(left) = args.take_key::<Linear>("left", &mut ctx.f) {
|
||||
if let Some(left) = args.get::<_, Linear>(ctx, "left") {
|
||||
page.margins.left = Some(left);
|
||||
}
|
||||
|
||||
if let Some(top) = args.take_key::<Linear>("top", &mut ctx.f) {
|
||||
if let Some(top) = args.get::<_, Linear>(ctx, "top") {
|
||||
page.margins.top = Some(top);
|
||||
}
|
||||
|
||||
if let Some(right) = args.take_key::<Linear>("right", &mut ctx.f) {
|
||||
if let Some(right) = args.get::<_, Linear>(ctx, "right") {
|
||||
page.margins.right = Some(right);
|
||||
}
|
||||
|
||||
if let Some(bottom) = args.take_key::<Linear>("bottom", &mut ctx.f) {
|
||||
if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") {
|
||||
page.margins.bottom = Some(bottom);
|
||||
}
|
||||
|
||||
if args.take_key::<bool>("flip", &mut ctx.f).unwrap_or(false) {
|
||||
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) {
|
||||
mem::swap(&mut page.size.width, &mut page.size.height);
|
||||
}
|
||||
|
||||
args.unexpected(&mut ctx.f);
|
||||
args.done(ctx);
|
||||
Value::Commands(vec![SetPageState(page)])
|
||||
}
|
||||
|
||||
/// `pagebreak`: Ends the current page.
|
||||
pub async fn pagebreak(args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
args.unexpected(&mut ctx.f);
|
||||
pub async fn pagebreak(args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
args.done(ctx);
|
||||
Value::Commands(vec![BreakPage])
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use crate::layout::SpacingKind;
|
||||
///
|
||||
/// # Positional arguments
|
||||
/// - The spacing (length or relative to font size).
|
||||
pub async fn h(args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
pub async fn h(args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
spacing(args, ctx, SpecAxis::Horizontal)
|
||||
}
|
||||
|
||||
@ -14,16 +14,17 @@ pub async fn h(args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
///
|
||||
/// # Positional arguments
|
||||
/// - The spacing (length or relative to font size).
|
||||
pub async fn v(args: ValueDict, ctx: &mut LayoutContext) -> Value {
|
||||
pub async fn v(args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
spacing(args, ctx, SpecAxis::Vertical)
|
||||
}
|
||||
|
||||
fn spacing(mut args: ValueDict, ctx: &mut LayoutContext, axis: SpecAxis) -> Value {
|
||||
let spacing = args.expect::<Linear>("spacing", Span::ZERO, &mut ctx.f);
|
||||
args.unexpected(&mut ctx.f);
|
||||
fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value {
|
||||
let spacing = args.get::<_, Linear>(ctx, 0);
|
||||
args.done(ctx);
|
||||
|
||||
Value::Commands(if let Some(spacing) = spacing {
|
||||
let axis = axis.to_gen(ctx.state.sys);
|
||||
let spacing = spacing.eval(ctx.state.text.font_size());
|
||||
let axis = axis.to_gen(ctx.state.sys);
|
||||
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
||||
} else {
|
||||
vec![]
|
||||
|
@ -163,14 +163,14 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
|
||||
if p.peek() == Some(Token::LeftBracket) {
|
||||
let expr = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p))));
|
||||
inner.span.expand(expr.span);
|
||||
inner.v.args.0.push(LitDictEntry { key: None, expr });
|
||||
inner.v.args.v.0.push(LitDictEntry { key: None, expr });
|
||||
}
|
||||
|
||||
while let Some(mut top) = outer.pop() {
|
||||
let span = inner.span;
|
||||
let node = inner.map(Expr::Call).map(SynNode::Expr);
|
||||
let expr = Expr::Lit(Lit::Content(vec![node])).span_with(span);
|
||||
top.v.args.0.push(LitDictEntry { key: None, expr });
|
||||
top.v.args.v.0.push(LitDictEntry { key: None, expr });
|
||||
inner = top;
|
||||
}
|
||||
|
||||
@ -194,14 +194,16 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall {
|
||||
|
||||
p.skip_white();
|
||||
let args = if p.eat_if(Token::Colon) {
|
||||
dict_contents(p).0
|
||||
p.span(|p| dict_contents(p).0)
|
||||
} else {
|
||||
// Ignore the rest if there's no colon.
|
||||
if !p.eof() {
|
||||
p.diag_expected_at("colon", p.pos());
|
||||
}
|
||||
p.eat_while(|_| true);
|
||||
LitDict::new()
|
||||
p.span(|p| {
|
||||
if !p.eof() {
|
||||
p.diag_expected_at("colon", p.pos());
|
||||
}
|
||||
p.eat_while(|_| true);
|
||||
LitDict::new()
|
||||
})
|
||||
};
|
||||
|
||||
p.end_group();
|
||||
@ -221,7 +223,7 @@ fn bracket_body(p: &mut Parser) -> SynTree {
|
||||
/// Parse a parenthesized function call.
|
||||
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall {
|
||||
p.start_group(Group::Paren);
|
||||
let args = dict_contents(p).0;
|
||||
let args = p.span(|p| dict_contents(p).0);
|
||||
p.end_group();
|
||||
ExprCall { name, args }
|
||||
}
|
||||
|
@ -76,47 +76,19 @@ fn Str(string: &str) -> Expr {
|
||||
Expr::Lit(Lit::Str(string.to_string()))
|
||||
}
|
||||
|
||||
macro_rules! Dict {
|
||||
(@dict=$dict:expr,) => {};
|
||||
(@dict=$dict:expr, $key:expr => $expr:expr $(, $($tts:tt)*)?) => {{
|
||||
let key = Into::<Spanned<&str>>::into($key);
|
||||
let key = key.map(Into::<DictKey>::into);
|
||||
let expr = Into::<Spanned<Expr>>::into($expr);
|
||||
$dict.0.push(LitDictEntry { key: Some(key), expr });
|
||||
Dict![@dict=$dict, $($($tts)*)?];
|
||||
}};
|
||||
(@dict=$dict:expr, $expr:expr $(, $($tts:tt)*)?) => {
|
||||
let expr = Into::<Spanned<Expr>>::into($expr);
|
||||
$dict.0.push(LitDictEntry { key: None, expr });
|
||||
Dict![@dict=$dict, $($($tts)*)?];
|
||||
};
|
||||
(@$($tts:tt)*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut dict = LitDict::new();
|
||||
Dict![@dict=dict, $($tts)*];
|
||||
dict
|
||||
}};
|
||||
($($tts:tt)*) => { Expr::Lit(Lit::Dict(Dict![@$($tts)*])) };
|
||||
}
|
||||
|
||||
macro_rules! Tree {
|
||||
(@$($node:expr),* $(,)?) => {
|
||||
vec![$(Into::<Spanned<SynNode>>::into($node)),*]
|
||||
};
|
||||
($($tts:tt)*) => { Expr::Lit(Lit::Content(Tree![@$($tts)*])) };
|
||||
}
|
||||
|
||||
macro_rules! Call {
|
||||
(@$name:expr $(; $($tts:tt)*)?) => {{
|
||||
(@$name:expr $(, $span:expr)? $(; $($tts:tt)*)?) => {{
|
||||
let name = Into::<Spanned<&str>>::into($name);
|
||||
#[allow(unused)]
|
||||
let mut span = Span::ZERO;
|
||||
$(span = $span.into();)?
|
||||
ExprCall {
|
||||
name: name.map(|n| Ident(n.to_string())),
|
||||
args: Dict![@$($($tts)*)?],
|
||||
args: Dict![@$($($tts)*)?].span_with(span),
|
||||
}
|
||||
}};
|
||||
($($tts:tt)*) => { Expr::Call(Call![@$($tts)*]) };
|
||||
}
|
||||
|
||||
fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
|
||||
Expr::Unary(ExprUnary {
|
||||
op: op.into(),
|
||||
@ -135,6 +107,36 @@ fn Binary(
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! Dict {
|
||||
(@dict=$dict:expr,) => {};
|
||||
(@dict=$dict:expr, $key:expr => $expr:expr $(, $($tts:tt)*)?) => {{
|
||||
let key = Into::<Spanned<&str>>::into($key);
|
||||
let key = key.map(Into::<DictKey>::into);
|
||||
let expr = Into::<Spanned<Expr>>::into($expr);
|
||||
$dict.0.push(LitDictEntry { key: Some(key), expr });
|
||||
Dict![@dict=$dict, $($($tts)*)?];
|
||||
}};
|
||||
(@dict=$dict:expr, $expr:expr $(, $($tts:tt)*)?) => {
|
||||
let expr = Into::<Spanned<Expr>>::into($expr);
|
||||
$dict.0.push(LitDictEntry { key: None, expr });
|
||||
Dict![@dict=$dict, $($($tts)*)?];
|
||||
};
|
||||
(@$($tts:tt)*) => {{
|
||||
#[allow(unused)]
|
||||
let mut dict = LitDict::new();
|
||||
Dict![@dict=dict, $($tts)*];
|
||||
dict
|
||||
}};
|
||||
($($tts:tt)*) => { Expr::Lit(Lit::Dict(Dict![@$($tts)*])) };
|
||||
}
|
||||
|
||||
macro_rules! Tree {
|
||||
(@$($node:expr),* $(,)?) => {
|
||||
vec![$(Into::<Spanned<SynNode>>::into($node)),*]
|
||||
};
|
||||
($($tts:tt)*) => { Expr::Lit(Lit::Content(Tree![@$($tts)*])) };
|
||||
}
|
||||
|
||||
// ------------------------------------ Test Macros ----------------------------------- //
|
||||
|
||||
// Test syntax trees with or without spans.
|
||||
@ -387,7 +389,7 @@ fn test_parse_function_bodies() {
|
||||
// Spanned.
|
||||
ts!(" [box][Oh my]" =>
|
||||
s(0, 1, S),
|
||||
s(1, 13, F!(s(2, 5, "box");
|
||||
s(1, 13, F!(s(2, 5, "box"), 5 .. 5;
|
||||
s(6, 13, Tree![
|
||||
s(7, 9, T("Oh")), s(9, 10, S), s(10, 12, T("my")),
|
||||
])
|
||||
@ -431,7 +433,7 @@ fn test_parse_values() {
|
||||
s(13, 13, "expected closing bracket"));
|
||||
|
||||
// Spanned.
|
||||
ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Float(1.4)))));
|
||||
ts!("[val: 1.4]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Float(1.4)))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -468,7 +470,7 @@ fn test_parse_expressions() {
|
||||
|
||||
// Spanned.
|
||||
ts!("[val: 1 + 3]" => s(0, 12, F!(
|
||||
s(1, 4, "val"); s(6, 11, Binary(
|
||||
s(1, 4, "val"), 5 .. 11; s(6, 11, Binary(
|
||||
s(8, 9, Add),
|
||||
s(6, 7, Int(1)),
|
||||
s(10, 11, Int(3))
|
||||
@ -476,7 +478,7 @@ fn test_parse_expressions() {
|
||||
)));
|
||||
|
||||
// Span of parenthesized expression contains parens.
|
||||
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Int(1)))));
|
||||
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"), 5 .. 9; s(6, 9, Int(1)))));
|
||||
|
||||
// Invalid expressions.
|
||||
v!("4pt--" => Len(Length::pt(4.0)));
|
||||
@ -504,8 +506,8 @@ fn test_parse_dicts() {
|
||||
|
||||
// Spanned with spacing around keyword arguments.
|
||||
ts!("[val: \n hi \n = /* //\n */ \"s\n\"]" => s(0, 30, F!(
|
||||
s(1, 4, "val");
|
||||
s(8, 10, "hi") => s(25, 29, Str("s\n"))
|
||||
s(1, 4, "val"),
|
||||
5 .. 29; s(8, 10, "hi") => s(25, 29, Str("s\n"))
|
||||
)));
|
||||
e!("[val: \n hi \n = /* //\n */ \"s\n\"]" => );
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! A prelude for building custom functions.
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use crate::eval::{Dict, Value, ValueDict};
|
||||
pub use crate::eval::{Args, Dict, Value, ValueDict};
|
||||
#[doc(no_inline)]
|
||||
pub use crate::layout::{layout_tree, primitive::*, Command, LayoutContext};
|
||||
#[doc(no_inline)]
|
||||
|
@ -21,7 +21,7 @@ pub struct ExprCall {
|
||||
/// The name of the function.
|
||||
pub name: Spanned<Ident>,
|
||||
/// The arguments to the function.
|
||||
pub args: LitDict,
|
||||
pub args: Spanned<LitDict>,
|
||||
}
|
||||
|
||||
/// A unary operation: `-x`.
|
||||
|
@ -40,7 +40,7 @@ impl<T> Offset for SpanVec<T> {
|
||||
}
|
||||
|
||||
/// A value with the span it corresponds to in the source code.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
pub struct Spanned<T> {
|
||||
/// The spanned value.
|
||||
@ -178,6 +178,12 @@ impl PartialEq for Span {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Span {
|
||||
fn default() -> Self {
|
||||
Span::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Span
|
||||
where
|
||||
T: Into<Pos> + Copy,
|
||||
@ -229,6 +235,12 @@ impl From<u32> for Pos {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Pos {
|
||||
fn from(index: i32) -> Self {
|
||||
Self(index as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Pos {
|
||||
fn from(index: usize) -> Self {
|
||||
Self(index as u32)
|
||||
|
Loading…
x
Reference in New Issue
Block a user