Dynamic values, Types, Arrays, and Dictionaries 🚀

- Identifiers are now evaluated as variables instead of being plain values
- Constants like `left` or `bold` are stored as dynamic values containing the respective rust types
- We now distinguish between arrays and dictionaries to make things more intuitive (at the cost of a bit more complex parsing)
- Spans were removed from collections (arrays, dictionaries), function arguments still have spans for the top-level values to enable good diagnostics
This commit is contained in:
Laurenz 2021-01-02 19:37:10 +01:00
parent 8cad78481c
commit 1c40dc42e7
24 changed files with 1110 additions and 1332 deletions

View File

@ -111,7 +111,7 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn parse_color_strings() { fn test_parse_color_strings() {
fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) { 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))); assert_eq!(RgbaColor::from_str(hex), Ok(RgbaColor::new(r, g, b, a)));
} }
@ -124,7 +124,7 @@ mod tests {
} }
#[test] #[test]
fn parse_invalid_colors() { fn test_parse_invalid_colors() {
fn test(hex: &str) { fn test(hex: &str) {
assert_eq!(RgbaColor::from_str(hex), Err(ParseRgbaError)); assert_eq!(RgbaColor::from_str(hex), Err(ParseRgbaError));
} }

View File

@ -1,184 +1,138 @@
//! Simplifies argument parsing.
use std::mem;
use super::{Conv, EvalContext, RefKey, TryFromValue, Value, ValueDict};
use crate::diag::Diag;
use crate::syntax::{Span, SpanVec, Spanned, WithSpan};
/// 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 EvalContext, key: K) -> Option<T>
where
K: Into<RefKey<'a>>,
T: TryFromValue,
{
self.0.v.remove(key).and_then(|entry| {
let span = entry.value.span;
conv_diag(
T::try_from_value(entry.value),
&mut ctx.feedback.diags,
span,
)
})
}
/// This is the same as [`get`](Self::get), except that it generates an error about a
/// missing argument with the given `name` if the key does not exist.
pub fn need<'a, K, T>(
&mut self,
ctx: &mut EvalContext,
key: K,
name: &str,
) -> Option<T>
where
K: Into<RefKey<'a>>,
T: TryFromValue,
{
if let Some(entry) = self.0.v.remove(key) {
let span = entry.value.span;
conv_diag(
T::try_from_value(entry.value),
&mut ctx.feedback.diags,
span,
)
} else {
ctx.diag(error!(self.0.span, "missing argument: {}", name));
None
}
}
/// Retrieve and remove the first matching positional argument.
pub fn find<T>(&mut self) -> Option<T>
where
T: TryFromValue,
{
for (&key, entry) in self.0.v.nums_mut() {
let span = entry.value.span;
let slot = &mut entry.value;
let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
if let Some(t) = conv {
self.0.v.remove(key);
return Some(t);
}
}
None
}
/// Retrieve and remove all matching positional arguments.
pub fn find_all<T>(&mut self) -> impl Iterator<Item = T> + '_
where
T: TryFromValue,
{
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;
let slot = &mut entry.value;
let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
if let Some(t) = conv {
self.0.v.remove(key);
return Some(t);
}
skip += 1;
}
None
})
}
/// Retrieve and remove all matching named arguments.
pub fn find_all_str<T>(&mut self) -> impl Iterator<Item = (String, T)> + '_
where
T: TryFromValue,
{
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;
let slot = &mut entry.value;
let conv = conv_put_back(T::try_from_value(mem::take(slot)), slot, span);
if let Some(t) = conv {
let key = key.clone();
self.0.v.remove(&key);
return Some((key, t));
}
skip += 1;
}
None
})
}
/// Generated _unexpected argument_ errors for all remaining entries.
pub fn done(&self, ctx: &mut EvalContext) {
for entry in self.0.v.values() {
let span = entry.key_span.join(entry.value.span);
ctx.diag(error!(span, "unexpected argument"));
}
}
}
fn conv_diag<T>(conv: Conv<T>, diags: &mut SpanVec<Diag>, span: Span) -> Option<T> {
match conv {
Conv::Ok(t) => Some(t),
Conv::Warn(t, warn) => {
diags.push(warn.with_span(span));
Some(t)
}
Conv::Err(_, err) => {
diags.push(err.with_span(span));
None
}
}
}
fn conv_put_back<T>(conv: Conv<T>, slot: &mut Spanned<Value>, span: Span) -> Option<T> {
match conv {
Conv::Ok(t) => Some(t),
Conv::Warn(t, _) => Some(t),
Conv::Err(v, _) => {
*slot = v.with_span(span);
None
}
}
}
#[cfg(test)]
mod tests {
use super::super::{Dict, SpannedEntry, Value};
use super::*; use super::*;
fn entry(value: Value) -> SpannedEntry<Value> { /// Evaluated arguments to a function.
SpannedEntry::value(Spanned::zero(value)) #[derive(Debug)]
pub struct Args {
span: Span,
pos: SpanVec<Value>,
named: Vec<(Spanned<String>, Spanned<Value>)>,
} }
#[test] impl Eval for Spanned<&Arguments> {
fn test_args_find() { type Output = Args;
let mut args = Args(Spanned::zero(Dict::new()));
args.0.v.insert(1, entry(Value::Bool(false))); fn eval(self, ctx: &mut EvalContext) -> Self::Output {
args.0.v.insert(2, entry(Value::Str("hi".to_string()))); let mut pos = vec![];
assert_eq!(args.find::<String>(), Some("hi".to_string())); let mut named = vec![];
assert_eq!(args.0.v.len(), 1);
assert_eq!(args.find::<bool>(), Some(false)); for arg in self.v {
assert!(args.0.v.is_empty()); match arg {
Argument::Pos(expr) => {
pos.push(expr.as_ref().eval(ctx).with_span(expr.span));
}
Argument::Named(Named { name, expr }) => {
named.push((
name.as_ref().map(|id| id.0.clone()),
expr.as_ref().eval(ctx).with_span(expr.span),
));
}
}
} }
#[test] Args { span: self.span, pos, named }
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))); impl Args {
args.0.v.insert(7, entry(Value::Bool(true))); /// Find the first convertible positional argument.
assert_eq!(args.find_all::<bool>().collect::<Vec<_>>(), [false, true]); pub fn find<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
assert_eq!(args.0.v.len(), 1); where
assert_eq!(args.0.v[3].value.v, Value::Float(0.0)); T: Cast<Spanned<Value>>,
{
self.pos.iter_mut().find_map(move |slot| try_cast(ctx, slot))
}
/// Find the first convertible positional argument, producing an error if
/// no match was found.
pub fn require<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T>
where
T: Cast<Spanned<Value>>,
{
let found = self.find(ctx);
if found.is_none() {
ctx.diag(error!(self.span, "missing argument: {}", what));
}
found
}
/// Filter out all convertible positional arguments.
pub fn filter<'a, T>(
&'a mut self,
ctx: &'a mut EvalContext,
) -> impl Iterator<Item = T> + 'a
where
T: Cast<Spanned<Value>>,
{
self.pos.iter_mut().filter_map(move |slot| try_cast(ctx, slot))
}
/// Convert the value for the given named argument.
///
/// Generates an error if the conversion fails.
pub fn get<'a, T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
where
T: Cast<Spanned<Value>>,
{
let index = self.named.iter().position(|(k, _)| k.v.as_str() == name)?;
let value = self.named.remove(index).1;
cast(ctx, value)
}
/// Generate "unexpected argument" errors for all remaining arguments.
pub fn finish(self, ctx: &mut EvalContext) {
let a = self.pos.iter().map(|v| v.as_ref());
let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span)));
for value in a.chain(b) {
if value.v != &Value::Error {
ctx.diag(error!(value.span, "unexpected argument"));
}
}
}
}
/// Cast the value into `T`, generating an error if the conversion fails.
fn cast<T>(ctx: &mut EvalContext, value: Spanned<Value>) -> Option<T>
where
T: Cast<Spanned<Value>>,
{
let span = value.span;
match T::cast(value) {
CastResult::Ok(t) => Some(t),
CastResult::Warn(t, m) => {
ctx.diag(warning!(span, "{}", m));
Some(t)
}
CastResult::Err(value) => {
ctx.diag(error!(
span,
"expected {}, found {}",
T::TYPE_NAME,
value.v.type_name()
));
None
}
}
}
/// Try to cast the value in the slot into `T`, putting it back if the
/// conversion fails.
fn try_cast<T>(ctx: &mut EvalContext, slot: &mut Spanned<Value>) -> Option<T>
where
T: Cast<Spanned<Value>>,
{
// Replace with error placeholder when conversion works since error values
// are ignored when generating "unexpected argument" errors.
let value = std::mem::replace(slot, Spanned::zero(Value::Error));
let span = value.span;
match T::cast(value) {
CastResult::Ok(t) => Some(t),
CastResult::Warn(t, m) => {
ctx.diag(warning!(span, "{}", m));
Some(t)
}
CastResult::Err(value) => {
*slot = value;
None
}
} }
} }

View File

@ -1,522 +0,0 @@
//! A key-value map that can also model array-like structures.
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter};
use std::iter::{Extend, FromIterator};
use std::ops::Index;
use crate::syntax::{Span, Spanned};
/// A dictionary data structure, which maps from integers and strings to a
/// generic value type.
///
/// The dictionary can be used to model arrays by assigning values to successive
/// indices from `0..n`. The `push` method offers special support for this
/// pattern.
#[derive(Clone)]
pub struct Dict<V> {
nums: BTreeMap<u64, V>,
strs: BTreeMap<String, V>,
lowest_free: u64,
}
impl<V> Dict<V> {
/// Create a new empty dictionary.
pub fn new() -> Self {
Self {
nums: BTreeMap::new(),
strs: BTreeMap::new(),
lowest_free: 0,
}
}
/// The total number of entries in the dictionary.
pub fn len(&self) -> usize {
self.nums.len() + self.strs.len()
}
/// Whether the dictionary contains no entries.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// The first number key-value pair (with lowest number).
pub fn first(&self) -> Option<(u64, &V)> {
self.nums.iter().next().map(|(&k, v)| (k, v))
}
/// The last number key-value pair (with highest number).
pub fn last(&self) -> Option<(u64, &V)> {
self.nums.iter().next_back().map(|(&k, v)| (k, v))
}
/// Get a reference to the value with the given key.
pub fn get<'a, K>(&self, key: K) -> Option<&V>
where
K: Into<RefKey<'a>>,
{
match key.into() {
RefKey::Num(num) => self.nums.get(&num),
RefKey::Str(string) => self.strs.get(string),
}
}
/// Borrow the value with the given key mutably.
pub fn get_mut<'a, K>(&mut self, key: K) -> Option<&mut V>
where
K: Into<RefKey<'a>>,
{
match key.into() {
RefKey::Num(num) => self.nums.get_mut(&num),
RefKey::Str(string) => self.strs.get_mut(string),
}
}
/// Insert a value into the dictionary.
pub fn insert<K>(&mut self, key: K, value: V)
where
K: Into<DictKey>,
{
match key.into() {
DictKey::Num(num) => {
self.nums.insert(num, value);
if self.lowest_free == num {
self.lowest_free += 1;
}
}
DictKey::Str(string) => {
self.strs.insert(string, value);
}
}
}
/// Remove the value with the given key from the dictionary.
pub fn remove<'a, K>(&mut self, key: K) -> Option<V>
where
K: Into<RefKey<'a>>,
{
match key.into() {
RefKey::Num(num) => {
self.lowest_free = self.lowest_free.min(num);
self.nums.remove(&num)
}
RefKey::Str(string) => self.strs.remove(string),
}
}
/// Append a value to the dictionary.
///
/// This will associate the `value` with the lowest free number key (zero if
/// there is no number key so far).
pub fn push(&mut self, value: V) {
while self.nums.contains_key(&self.lowest_free) {
self.lowest_free += 1;
}
self.nums.insert(self.lowest_free, value);
self.lowest_free += 1;
}
}
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: Eq> Eq for Dict<V> {}
impl<V: PartialEq> PartialEq for Dict<V> {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
impl<V> Default for Dict<V> {
fn default() -> Self {
Self::new()
}
}
impl<V: Debug> Debug for Dict<V> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.is_empty() {
return f.write_str("()");
}
let mut builder = f.debug_tuple("");
struct Entry<'a>(bool, &'a dyn Display, &'a dyn Debug);
impl<'a> Debug for Entry<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.0 {
f.write_str("\"")?;
}
self.1.fmt(f)?;
if self.0 {
f.write_str("\"")?;
}
f.write_str(": ")?;
self.2.fmt(f)
}
}
for (key, value) in self.nums() {
builder.field(&Entry(false, &key, &value));
}
for (key, value) in self.strs() {
builder.field(&Entry(key.contains(' '), &key, &value));
}
builder.finish()
}
}
/// 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 {
Num(u64),
Str(String),
}
impl From<&Self> for DictKey {
fn from(key: &Self) -> Self {
key.clone()
}
}
impl From<RefKey<'_>> for DictKey {
fn from(key: RefKey<'_>) -> Self {
match key {
RefKey::Num(num) => Self::Num(num),
RefKey::Str(string) => Self::Str(string.to_string()),
}
}
}
impl From<u64> for DictKey {
fn from(num: u64) -> Self {
Self::Num(num)
}
}
impl From<String> for DictKey {
fn from(string: String) -> Self {
Self::Str(string)
}
}
impl From<&'static str> for DictKey {
fn from(string: &'static str) -> Self {
Self::Str(string.to_string())
}
}
/// The borrowed variant of a dictionary key.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum RefKey<'a> {
Num(u64),
Str(&'a str),
}
impl From<u64> for RefKey<'static> {
fn from(num: u64) -> Self {
Self::Num(num)
}
}
impl<'a> From<&'a String> for RefKey<'a> {
fn from(string: &'a String) -> Self {
Self::Str(&string)
}
}
impl<'a> From<&'a str> for RefKey<'a> {
fn from(string: &'a str) -> Self {
Self::Str(string)
}
}
/// A dictionary entry which combines key span and value.
///
/// This exists because a key in a directory can't track its span by itself.
#[derive(Clone, PartialEq)]
pub struct SpannedEntry<V> {
pub key_span: Span,
pub value: Spanned<V>,
}
impl<V> SpannedEntry<V> {
/// Create a new entry.
pub fn new(key: Span, val: Spanned<V>) -> Self {
Self { key_span: key, value: val }
}
/// Create an entry with the same span for key and value.
pub fn value(val: Spanned<V>) -> Self {
Self { key_span: val.span, value: val }
}
/// Convert from `&SpannedEntry<T>` to `SpannedEntry<&T>`
pub fn as_ref(&self) -> SpannedEntry<&V> {
SpannedEntry {
key_span: self.key_span,
value: self.value.as_ref(),
}
}
/// Map the entry to a different value type.
pub fn map<U>(self, f: impl FnOnce(V) -> U) -> SpannedEntry<U> {
SpannedEntry {
key_span: self.key_span,
value: self.value.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_span.fmt(f)?;
f.write_str(" ")?;
}
self.value.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::Dict;
#[test]
fn test_dict_different_key_types_dont_interfere() {
let mut dict = Dict::new();
dict.insert(10, "hello");
dict.insert("twenty", "there");
assert_eq!(dict.len(), 2);
assert_eq!(dict[10], "hello");
assert_eq!(dict["twenty"], "there");
}
#[test]
fn test_dict_push_skips_already_inserted_keys() {
let mut dict = Dict::new();
dict.insert(2, "2");
dict.push("0");
dict.insert(3, "3");
dict.push("1");
dict.push("4");
assert_eq!(dict.len(), 5);
assert_eq!(dict[0], "0");
assert_eq!(dict[1], "1");
assert_eq!(dict[2], "2");
assert_eq!(dict[3], "3");
assert_eq!(dict[4], "4");
}
#[test]
fn test_dict_push_remove_push_reuses_index() {
let mut dict = Dict::new();
dict.push("0");
dict.push("1");
dict.push("2");
dict.remove(1);
dict.push("a");
dict.push("3");
assert_eq!(dict.len(), 4);
assert_eq!(dict[0], "0");
assert_eq!(dict[1], "a");
assert_eq!(dict[2], "2");
assert_eq!(dict[3], "3");
}
#[test]
fn test_dict_first_and_last_are_correct() {
let mut dict = Dict::new();
assert_eq!(dict.first(), None);
assert_eq!(dict.last(), None);
dict.insert(4, "hi");
dict.insert("string", "hi");
assert_eq!(dict.first(), Some((4, &"hi")));
assert_eq!(dict.last(), Some((4, &"hi")));
dict.insert(2, "bye");
assert_eq!(dict.first(), Some((2, &"bye")));
assert_eq!(dict.last(), Some((4, &"hi")));
}
#[test]
fn test_dict_format_debug() {
let mut dict = Dict::new();
assert_eq!(format!("{:?}", dict), "()");
assert_eq!(format!("{:#?}", dict), "()");
dict.insert(10, "hello");
dict.insert("twenty", "there");
dict.insert("sp ace", "quotes");
assert_eq!(
format!("{:?}", dict),
r#"(10: "hello", "sp ace": "quotes", twenty: "there")"#,
);
assert_eq!(format!("{:#?}", dict).lines().collect::<Vec<_>>(), [
"(",
r#" 10: "hello","#,
r#" "sp ace": "quotes","#,
r#" twenty: "there","#,
")",
]);
}
}

View File

@ -3,12 +3,10 @@
#[macro_use] #[macro_use]
mod value; mod value;
mod args; mod args;
mod dict;
mod scope; mod scope;
mod state; mod state;
pub use args::*; pub use args::*;
pub use dict::*;
pub use scope::*; pub use scope::*;
pub use state::*; pub use state::*;
pub use value::*; pub use value::*;
@ -451,7 +449,13 @@ impl Eval for Spanned<&Lit> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
match *self.v { match *self.v {
Lit::Ident(ref v) => Value::Ident(v.clone()), Lit::Ident(ref v) => match ctx.state.scope.get(v.as_str()) {
Some(value) => value.clone(),
None => {
ctx.diag(error!(self.span, "unknown variable"));
Value::Error
}
},
Lit::Bool(v) => Value::Bool(v), Lit::Bool(v) => Value::Bool(v),
Lit::Int(v) => Value::Int(v), Lit::Int(v) => Value::Int(v),
Lit::Float(v) => Value::Float(v), Lit::Float(v) => Value::Float(v),
@ -459,28 +463,29 @@ impl Eval for Spanned<&Lit> {
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)), Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(Color::Rgba(v)), Lit::Color(v) => Value::Color(Color::Rgba(v)),
Lit::Str(ref v) => Value::Str(v.clone()), Lit::Str(ref v) => Value::Str(v.clone()),
Lit::Array(ref v) => Value::Array(v.with_span(self.span).eval(ctx)),
Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)), Lit::Dict(ref v) => Value::Dict(v.with_span(self.span).eval(ctx)),
Lit::Content(ref v) => Value::Content(v.clone()), Lit::Content(ref v) => Value::Content(v.clone()),
} }
} }
} }
impl Eval for Spanned<&LitDict> {
impl Eval for Spanned<&Array> {
type Output = ValueArray;
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
self.v.iter().map(|expr| expr.as_ref().eval(ctx)).collect()
}
}
impl Eval for Spanned<&Dict> {
type Output = ValueDict; type Output = ValueDict;
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let mut dict = ValueDict::new(); self.v
.iter()
for entry in &self.v.0 { .map(|Named { name, expr }| (name.v.0.clone(), expr.as_ref().eval(ctx)))
let val = entry.expr.as_ref().eval(ctx); .collect()
let spanned = val.with_span(entry.expr.span);
if let Some(key) = &entry.key {
dict.insert(&key.v, SpannedEntry::new(key.span, spanned));
} else {
dict.push(SpannedEntry::value(spanned));
}
}
dict
} }
} }
@ -490,19 +495,27 @@ impl Eval for Spanned<&ExprCall> {
fn eval(self, ctx: &mut EvalContext) -> Self::Output { fn eval(self, ctx: &mut EvalContext) -> Self::Output {
let name = &self.v.name.v; let name = &self.v.name.v;
let span = self.v.name.span; let span = self.v.name.span;
let dict = self.v.args.as_ref().eval(ctx);
if let Some(func) = ctx.state.scope.get(name) { if let Some(value) = ctx.state.scope.get(name) {
let args = Args(dict.with_span(self.v.args.span)); if let Value::Func(func) = value {
let func = func.clone();
ctx.feedback.decos.push(Deco::Resolved.with_span(span)); ctx.feedback.decos.push(Deco::Resolved.with_span(span));
(func.clone())(args, ctx)
let mut args = self.v.args.as_ref().eval(ctx);
let returned = func(ctx, &mut args);
args.finish(ctx);
return returned;
} else { } else {
if !name.is_empty() { let ty = value.type_name();
ctx.diag(error!(span, "a value of type {} is not callable", ty));
}
} else if !name.is_empty() {
ctx.diag(error!(span, "unknown function")); ctx.diag(error!(span, "unknown function"));
}
ctx.feedback.decos.push(Deco::Unresolved.with_span(span)); ctx.feedback.decos.push(Deco::Unresolved.with_span(span));
} Value::Error
Value::Dict(dict)
}
} }
} }
@ -554,7 +567,7 @@ fn neg(ctx: &mut EvalContext, span: Span, value: Value) -> Value {
Relative(v) => Relative(-v), Relative(v) => Relative(-v),
Linear(v) => Linear(-v), Linear(v) => Linear(-v),
v => { v => {
ctx.diag(error!(span, "cannot negate {}", v.ty())); ctx.diag(error!(span, "cannot negate {}", v.type_name()));
Value::Error Value::Error
} }
} }
@ -589,7 +602,12 @@ fn add(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Content(a), Content(b)) => Content(concat(a, b)), (Content(a), Content(b)) => Content(concat(a, b)),
(a, b) => { (a, b) => {
ctx.diag(error!(span, "cannot add {} and {}", a.ty(), b.ty())); ctx.diag(error!(
span,
"cannot add {} and {}",
a.type_name(),
b.type_name()
));
Value::Error Value::Error
} }
} }
@ -617,7 +635,12 @@ fn sub(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Linear(a), Linear(b)) => Linear(a - b), (Linear(a), Linear(b)) => Linear(a - b),
(a, b) => { (a, b) => {
ctx.diag(error!(span, "cannot subtract {1} from {0}", a.ty(), b.ty())); ctx.diag(error!(
span,
"cannot subtract {1} from {0}",
a.type_name(),
b.type_name()
));
Value::Error Value::Error
} }
} }
@ -652,7 +675,12 @@ fn mul(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)), (Str(a), Int(b)) => Str(a.repeat(0.max(b) as usize)),
(a, b) => { (a, b) => {
ctx.diag(error!(span, "cannot multiply {} with {}", a.ty(), b.ty())); ctx.diag(error!(
span,
"cannot multiply {} with {}",
a.type_name(),
b.type_name()
));
Value::Error Value::Error
} }
} }
@ -677,7 +705,12 @@ fn div(ctx: &mut EvalContext, span: Span, lhs: Value, rhs: Value) -> Value {
(Linear(a), Float(b)) => Linear(a / b), (Linear(a), Float(b)) => Linear(a / b),
(a, b) => { (a, b) => {
ctx.diag(error!(span, "cannot divide {} by {}", a.ty(), b.ty())); ctx.diag(error!(
span,
"cannot divide {} by {}",
a.type_name(),
b.type_name()
));
Value::Error Value::Error
} }
} }

View File

@ -3,12 +3,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use super::value::ValueFunc; use super::Value;
/// A map from identifiers to functions. /// A map from identifiers to functions.
#[derive(Default, Clone, PartialEq)] #[derive(Default, Clone, PartialEq)]
pub struct Scope { pub struct Scope {
functions: HashMap<String, ValueFunc>, values: HashMap<String, Value>,
} }
impl Scope { impl Scope {
@ -18,19 +18,19 @@ impl Scope {
Self::default() Self::default()
} }
/// Return the function with the given name if there is one. /// Return the value of the given variable.
pub fn get(&self, name: &str) -> Option<&ValueFunc> { pub fn get(&self, var: &str) -> Option<&Value> {
self.functions.get(name) self.values.get(var)
} }
/// Associate the given name with the function. /// Store the value for the given variable.
pub fn set(&mut self, name: impl Into<String>, function: ValueFunc) { pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) {
self.functions.insert(name.into(), function); self.values.insert(var.into(), value.into());
} }
} }
impl Debug for Scope { impl Debug for Scope {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_set().entries(self.functions.keys()).finish() self.values.fmt(f)
} }
} }

View File

@ -1,25 +1,21 @@
//! Computational values. //! Computational values.
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use fontdock::{FontStretch, FontStyle, FontWeight}; use super::{Args, Eval, EvalContext};
use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
use crate::color::Color; use crate::color::Color;
use crate::diag::Diag; use crate::geom::{Length, Linear, Relative};
use crate::geom::{Dir, Length, Linear, Relative}; use crate::syntax::{Spanned, SynTree, WithSpan};
use crate::paper::Paper;
use crate::syntax::{Ident, Spanned, SynTree, WithSpan};
/// A computational value. /// A computational value.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum Value { pub enum Value {
/// The value that indicates the absence of a meaningful value. /// The value that indicates the absence of a meaningful value.
None, None,
/// An identifier: `ident`.
Ident(Ident),
/// A boolean: `true, false`. /// A boolean: `true, false`.
Bool(bool), Bool(bool),
/// An integer: `120`. /// An integer: `120`.
@ -36,34 +32,46 @@ pub enum Value {
Color(Color), Color(Color),
/// A string: `"string"`. /// A string: `"string"`.
Str(String), Str(String),
/// A dictionary value: `(false, 12cm, greeting: "hi")`. /// An array value: `(1, "hi", 12cm)`.
Array(ValueArray),
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
Dict(ValueDict), Dict(ValueDict),
/// A content value: `{*Hi* there}`. /// A content value: `{*Hi* there}`.
Content(SynTree), Content(ValueContent),
/// An executable function. /// An executable function.
Func(ValueFunc), Func(ValueFunc),
/// Any object.
Any(ValueAny),
/// The result of invalid operations. /// The result of invalid operations.
Error, Error,
} }
impl Value { impl Value {
/// The natural-language name of this value's type for use in error /// Try to cast the value into a specific type.
/// messages. pub fn cast<T>(self) -> CastResult<T, Self>
pub fn ty(&self) -> &'static str { where
T: Cast<Value>,
{
T::cast(self)
}
/// The name of the stored value's type.
pub fn type_name(&self) -> &'static str {
match self { match self {
Self::None => "none", Self::None => "none",
Self::Ident(_) => "identifier", Self::Bool(_) => bool::TYPE_NAME,
Self::Bool(_) => "bool", Self::Int(_) => i64::TYPE_NAME,
Self::Int(_) => "integer", Self::Float(_) => f64::TYPE_NAME,
Self::Float(_) => "float", Self::Relative(_) => Relative::TYPE_NAME,
Self::Relative(_) => "relative", Self::Length(_) => Length::TYPE_NAME,
Self::Length(_) => "length", Self::Linear(_) => Linear::TYPE_NAME,
Self::Linear(_) => "linear", Self::Color(_) => Color::TYPE_NAME,
Self::Color(_) => "color", Self::Str(_) => String::TYPE_NAME,
Self::Str(_) => "string", Self::Array(_) => ValueArray::TYPE_NAME,
Self::Dict(_) => "dict", Self::Dict(_) => ValueDict::TYPE_NAME,
Self::Content(_) => "content", Self::Content(_) => ValueContent::TYPE_NAME,
Self::Func(_) => "function", Self::Func(_) => ValueFunc::TYPE_NAME,
Self::Any(v) => v.type_name(),
Self::Error => "error", Self::Error => "error",
} }
} }
@ -81,13 +89,6 @@ impl Eval for &Value {
// Pass through. // Pass through.
Value::Content(tree) => tree.eval(ctx), Value::Content(tree) => tree.eval(ctx),
// Forward to each dictionary entry.
Value::Dict(dict) => {
for entry in dict.values() {
entry.value.v.eval(ctx);
}
}
// Format with debug. // Format with debug.
val => ctx.push(ctx.make_text_node(format!("{:?}", val))), val => ctx.push(ctx.make_text_node(format!("{:?}", val))),
} }
@ -104,7 +105,6 @@ impl Debug for Value {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::None => f.pad("none"), Self::None => f.pad("none"),
Self::Ident(v) => v.fmt(f),
Self::Bool(v) => v.fmt(f), Self::Bool(v) => v.fmt(f),
Self::Int(v) => v.fmt(f), Self::Int(v) => v.fmt(f),
Self::Float(v) => v.fmt(f), Self::Float(v) => v.fmt(f),
@ -113,45 +113,36 @@ impl Debug for Value {
Self::Linear(v) => v.fmt(f), Self::Linear(v) => v.fmt(f),
Self::Color(v) => v.fmt(f), Self::Color(v) => v.fmt(f),
Self::Str(v) => v.fmt(f), Self::Str(v) => v.fmt(f),
Self::Array(v) => v.fmt(f),
Self::Dict(v) => v.fmt(f), Self::Dict(v) => v.fmt(f),
Self::Content(v) => v.fmt(f), Self::Content(v) => v.fmt(f),
Self::Func(v) => v.fmt(f), Self::Func(v) => v.fmt(f),
Self::Any(v) => v.fmt(f),
Self::Error => f.pad("<error>"), Self::Error => f.pad("<error>"),
} }
} }
} }
/// A dictionary of values. /// An array value: `(1, "hi", 12cm)`.
/// pub type ValueArray = Vec<Value>;
/// # Example
/// ```typst
/// (false, 12cm, greeting: "hi")
/// ```
pub type ValueDict = Dict<SpannedEntry<Value>>;
/// An wrapper around a reference-counted function trait object. /// A dictionary value: `(color: #f79143, pattern: dashed)`.
/// pub type ValueDict = HashMap<String, Value>;
/// The dynamic function object is wrapped in an `Rc` to keep [`Value`]
/// cloneable. /// A content value: `{*Hi* there}`.
/// pub type ValueContent = SynTree;
/// _Note_: This is needed because the compiler can't `derive(PartialEq)` for
/// [`Value`] when directly putting the `Rc` in there, see the [Rust /// A wrapper around a reference-counted executable function.
/// Issue].
///
/// [Rust Issue]: https://github.com/rust-lang/rust/issues/31740
#[derive(Clone)] #[derive(Clone)]
pub struct ValueFunc(pub Rc<Func>); pub struct ValueFunc(Rc<dyn Fn(&mut EvalContext, &mut Args) -> Value>);
/// The signature of executable functions.
type Func = dyn Fn(Args, &mut EvalContext) -> Value;
impl ValueFunc { impl ValueFunc {
/// Create a new function value from a rust function or closure. /// Create a new function value from a rust function or closure.
pub fn new<F>(f: F) -> Self pub fn new<F>(func: F) -> Self
where where
F: Fn(Args, &mut EvalContext) -> Value + 'static, F: Fn(&mut EvalContext, &mut Args) -> Value + 'static,
{ {
Self(Rc::new(f)) Self(Rc::new(func))
} }
} }
@ -162,7 +153,7 @@ impl PartialEq for ValueFunc {
} }
impl Deref for ValueFunc { impl Deref for ValueFunc {
type Target = Func; type Target = dyn Fn(&mut EvalContext, &mut Args) -> Value;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.0.as_ref() self.0.as_ref()
@ -175,160 +166,288 @@ impl Debug for ValueFunc {
} }
} }
/// Try to convert a value into a more specific type. /// A wrapper around a dynamic value.
pub trait TryFromValue: Sized { pub struct ValueAny(Box<dyn Bounds>);
/// Try to convert the value into yourself.
fn try_from_value(value: Spanned<Value>) -> Conv<Self>; impl ValueAny {
/// Create a new instance from any value that satisifies the required bounds.
pub fn new<T>(any: T) -> Self
where
T: Type + Debug + Clone + PartialEq + 'static,
{
Self(Box::new(any))
} }
/// The result of a conversion. /// Whether the wrapped type is `T`.
#[derive(Debug, Clone, PartialEq)] pub fn is<T: 'static>(&self) -> bool {
pub enum Conv<T> { self.0.as_any().is::<T>()
/// Success conversion. }
/// Try to downcast to a specific type.
pub fn downcast<T: 'static>(self) -> Result<T, Self> {
if self.is::<T>() {
Ok(*self.0.into_any().downcast().unwrap())
} else {
Err(self)
}
}
/// Try to downcast to a reference to a specific type.
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.0.as_any().downcast_ref()
}
/// The name of the stored object's type.
pub fn type_name(&self) -> &'static str {
self.0.dyn_type_name()
}
}
impl Clone for ValueAny {
fn clone(&self) -> Self {
Self(self.0.dyn_clone())
}
}
impl PartialEq for ValueAny {
fn eq(&self, other: &Self) -> bool {
self.0.dyn_eq(other)
}
}
impl Debug for ValueAny {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
trait Bounds: Debug + 'static {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
fn dyn_eq(&self, other: &ValueAny) -> bool;
fn dyn_clone(&self) -> Box<dyn Bounds>;
fn dyn_type_name(&self) -> &'static str;
}
impl<T> Bounds for T
where
T: Type + Debug + Clone + PartialEq + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn dyn_eq(&self, other: &ValueAny) -> bool {
if let Some(other) = other.downcast_ref::<Self>() {
self == other
} else {
false
}
}
fn dyn_clone(&self) -> Box<dyn Bounds> {
Box::new(self.clone())
}
fn dyn_type_name(&self) -> &'static str {
T::TYPE_NAME
}
}
/// Types that can be stored in values.
pub trait Type {
/// The name of the type.
const TYPE_NAME: &'static str;
}
impl<T> Type for Spanned<T>
where
T: Type,
{
const TYPE_NAME: &'static str = T::TYPE_NAME;
}
/// Cast from a value to a specific type.
pub trait Cast<V>: Type + Sized {
/// Try to cast the value into an instance of `Self`.
fn cast(value: V) -> CastResult<Self, V>;
}
/// The result of casting a value to a specific type.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum CastResult<T, V> {
/// The value was cast successfully.
Ok(T), Ok(T),
/// Sucessful conversion with a warning. /// The value was cast successfully, but with a warning message.
Warn(T, Diag), Warn(T, String),
/// Unsucessful conversion, gives back the value alongside the error. /// The value could not be cast into the specified type.
Err(Value, Diag), Err(V),
} }
impl<T> Conv<T> { impl<T, V> CastResult<T, V> {
/// Map the conversion result. /// Access the conversion resulting, discarding a possibly existing warning.
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Conv<U> { pub fn ok(self) -> Option<T> {
match self { match self {
Conv::Ok(t) => Conv::Ok(f(t)), CastResult::Ok(t) | CastResult::Warn(t, _) => Some(t),
Conv::Warn(t, warn) => Conv::Warn(f(t), warn), CastResult::Err(_) => None,
Conv::Err(v, err) => Conv::Err(v, err),
} }
} }
} }
impl<T: TryFromValue> TryFromValue for Spanned<T> { impl<T> Cast<Spanned<Value>> for T
fn try_from_value(value: Spanned<Value>) -> Conv<Self> { where
T: Cast<Value>,
{
fn cast(value: Spanned<Value>) -> CastResult<Self, Spanned<Value>> {
let span = value.span; let span = value.span;
T::try_from_value(value).map(|v| v.with_span(span)) match T::cast(value.v) {
CastResult::Ok(t) => CastResult::Ok(t),
CastResult::Warn(t, m) => CastResult::Warn(t, m),
CastResult::Err(v) => CastResult::Err(v.with_span(span)),
}
} }
} }
/// A value type that matches [identifier](Value::Ident) and [string](Value::Str) values. impl<T> Cast<Spanned<Value>> for Spanned<T>
pub struct StringLike(pub String); where
T: Cast<Value>,
impl From<StringLike> for String { {
fn from(like: StringLike) -> String { fn cast(value: Spanned<Value>) -> CastResult<Self, Spanned<Value>> {
like.0 let span = value.span;
match T::cast(value.v) {
CastResult::Ok(t) => CastResult::Ok(t.with_span(span)),
CastResult::Warn(t, m) => CastResult::Warn(t.with_span(span), m),
CastResult::Err(v) => CastResult::Err(v.with_span(span)),
}
} }
} }
impl Deref for StringLike { macro_rules! impl_primitive {
type Target = str; ($type:ty:
$type_name:literal,
$variant:path
$(, $pattern:pat => $out:expr)* $(,)?
) => {
impl Type for $type {
const TYPE_NAME: &'static str = $type_name;
}
fn deref(&self) -> &str { impl From<$type> for Value {
self.0.as_str() fn from(v: $type) -> Self {
$variant(v)
} }
} }
/// Implement [`TryFromValue`] through a match. impl Cast<Value> for $type {
macro_rules! try_from_match { fn cast(value: Value) -> CastResult<Self, Value> {
($type:ty[$name:literal] $(@ $span:ident)?: $($pattern:pat => $output:expr),* $(,)?) => { match value {
impl $crate::eval::TryFromValue for $type { $variant(v) => CastResult::Ok(v),
fn try_from_value(value: Spanned<Value>) -> $crate::eval::Conv<Self> { $($pattern => CastResult::Ok($out),)*
use $crate::eval::Conv; v => CastResult::Err(v),
#[allow(unused)]
$(let $span = value.span;)?
#[allow(unreachable_patterns)]
match value.v {
$($pattern => Conv::Ok($output)),*,
v => {
let e = error!("expected {}, found {}", $name, v.ty());
Conv::Err(v, e)
}
} }
} }
} }
}; };
} }
/// Implement [`TryFromValue`] through a function parsing an identifier. impl_primitive! { bool: "boolean", Value::Bool }
macro_rules! try_from_id { impl_primitive! { i64: "integer", Value::Int }
($type:ty[$name:literal]: $from_str:expr) => { impl_primitive! { Length: "length", Value::Length }
impl $crate::eval::TryFromValue for $type { impl_primitive! { Relative: "relative", Value::Relative }
fn try_from_value(value: Spanned<Value>) -> $crate::eval::Conv<Self> { impl_primitive! { Color: "color", Value::Color }
use $crate::eval::Conv; impl_primitive! { String: "string", Value::Str }
let v = value.v; impl_primitive! { ValueArray: "array", Value::Array }
if let Value::Ident(id) = v { impl_primitive! { ValueDict: "dictionary", Value::Dict }
if let Some(v) = $from_str(&id) { impl_primitive! { ValueContent: "content", Value::Content }
Conv::Ok(v) impl_primitive! { ValueFunc: "function", Value::Func }
} else {
Conv::Err(Value::Ident(id), error!("invalid {}", $name))
}
} else {
let e = error!("expected identifier, found {}", v.ty());
Conv::Err(v, e)
}
}
}
};
}
try_from_match!(Value["value"]: v => v); impl_primitive! {
try_from_match!(Ident["identifier"]: Value::Ident(v) => v); f64: "float",
try_from_match!(bool["bool"]: Value::Bool(v) => v); Value::Float,
try_from_match!(i64["integer"]: Value::Int(v) => v);
try_from_match!(f64["float"]:
Value::Int(v) => v as f64, Value::Int(v) => v as f64,
Value::Float(v) => v, }
);
try_from_match!(Length["length"]: Value::Length(v) => v); impl_primitive! {
try_from_match!(Relative["relative"]: Value::Relative(v) => v); Linear: "linear",
try_from_match!(Linear["linear"]: Value::Linear,
Value::Linear(v) => v,
Value::Length(v) => v.into(), Value::Length(v) => v.into(),
Value::Relative(v) => v.into(), Value::Relative(v) => v.into(),
); }
try_from_match!(Color["color"]: Value::Color(v) => v);
try_from_match!(String["string"]: Value::Str(v) => v);
try_from_match!(SynTree["tree"]: Value::Content(v) => v);
try_from_match!(ValueDict["dictionary"]: Value::Dict(v) => v);
try_from_match!(ValueFunc["function"]: Value::Func(v) => v);
try_from_match!(StringLike["identifier or string"]:
Value::Ident(Ident(v)) => Self(v),
Value::Str(v) => Self(v),
);
try_from_id!(Dir["direction"]: |v| match v {
"ltr" | "left-to-right" => Some(Self::LTR),
"rtl" | "right-to-left" => Some(Self::RTL),
"ttb" | "top-to-bottom" => Some(Self::TTB),
"btt" | "bottom-to-top" => Some(Self::BTT),
_ => None,
});
try_from_id!(FontStyle["font style"]: Self::from_str);
try_from_id!(FontStretch["font stretch"]: Self::from_str);
try_from_id!(Paper["paper"]: Self::from_name);
impl TryFromValue for FontWeight { impl From<&str> for Value {
fn try_from_value(value: Spanned<Value>) -> Conv<Self> { fn from(v: &str) -> Self {
match value.v { Self::Str(v.to_string())
Value::Int(number) => {
let [min, max] = [Self::THIN, Self::BLACK];
if number < i64::from(min.to_number()) {
Conv::Warn(min, warning!("the minimum font weight is {:#?}", min))
} else if number > i64::from(max.to_number()) {
Conv::Warn(max, warning!("the maximum font weight is {:#?}", max))
} else {
Conv::Ok(Self::from_number(number as u16))
} }
} }
Value::Ident(id) => {
if let Some(weight) = Self::from_str(&id) { impl<F> From<F> for Value
Conv::Ok(weight) where
} else { F: Fn(&mut EvalContext, &mut Args) -> Value + 'static,
Conv::Err(Value::Ident(id), error!("invalid font weight")) {
fn from(func: F) -> Self {
Self::Func(ValueFunc::new(func))
} }
} }
v => {
let e = error!("expected font weight, found {}", v.ty()); impl From<ValueAny> for Value {
Conv::Err(v, e) fn from(v: ValueAny) -> Self {
Self::Any(v)
}
}
/// Make a type usable with [`ValueAny`].
///
/// Given a type `T`, this implements the following traits:
/// - [`Type`] for `T`,
/// - [`From<T>`](From) for [`Value`],
/// - [`Cast<Value>`](Cast) for `T`.
#[macro_export]
macro_rules! impl_type {
($type:ty:
$type_name:literal
$(, $pattern:pat => $out:expr)*
$(, #($anyvar:ident: $anytype:ty) => $anyout:expr)*
$(,)?
) => {
impl $crate::eval::Type for $type {
const TYPE_NAME: &'static str = $type_name;
}
impl From<$type> for $crate::eval::Value {
fn from(any: $type) -> Self {
$crate::eval::Value::Any($crate::eval::ValueAny::new(any))
}
}
impl $crate::eval::Cast<$crate::eval::Value> for $type {
fn cast(
value: $crate::eval::Value,
) -> $crate::eval::CastResult<Self, $crate::eval::Value> {
use $crate::eval::*;
#[allow(unreachable_code)]
match value {
$($pattern => CastResult::Ok($out),)*
Value::Any(mut any) => {
any = match any.downcast::<Self>() {
Ok(t) => return CastResult::Ok(t),
Err(any) => any,
};
$(any = match any.downcast::<$anytype>() {
Ok($anyvar) => return CastResult::Ok($anyout),
Err(any) => any,
};)*
CastResult::Err(Value::Any(any))
},
v => CastResult::Err(v),
} }
} }
} }
};
} }

View File

@ -208,14 +208,14 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_length_formats_correctly() { fn test_length_unit_conversion() {
assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
}
#[test]
fn test_length_formatting() {
assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string()); assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string());
assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string()); assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string());
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string()); assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
} }
#[test]
fn test_length_unit_conversion() {
assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
}
} }

View File

@ -6,14 +6,14 @@ use crate::prelude::*;
/// `image`: Insert an image. /// `image`: Insert an image.
/// ///
/// # Positional arguments
/// - Path (`string`): The path to the image file.
///
/// Supports PNG and JPEG files. /// Supports PNG and JPEG files.
pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value { ///
let path = args.need::<_, Spanned<String>>(ctx, 0, "path"); /// # Positional arguments
let width = args.get::<_, Linear>(ctx, "width"); /// - Path to image file: of type `string`.
let height = args.get::<_, Linear>(ctx, "height"); pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
let path = args.require::<Spanned<String>>(ctx, "path to image file");
let width = args.get(ctx, "width");
let height = args.get(ctx, "height");
if let Some(path) = path { if let Some(path) = path {
let mut env = ctx.env.borrow_mut(); let mut env = ctx.env.borrow_mut();

View File

@ -1,51 +1,44 @@
use std::fmt::{self, Display, Formatter};
use crate::eval::Softness; use crate::eval::Softness;
use crate::geom::{Length, Linear};
use crate::layout::{Expansion, Fixed, Spacing, Stack}; use crate::layout::{Expansion, Fixed, Spacing, Stack};
use crate::paper::{Paper, PaperClass}; use crate::paper::{Paper, PaperClass};
use crate::prelude::*; use crate::prelude::*;
/// `align`: Align content along the layouting axes. /// `align`: Align content along the layouting axes.
/// ///
/// Which axis an alignment should apply to (main or cross) is inferred from
/// either the argument itself (for anything other than `center`) or from the
/// second argument if present, defaulting to the cross axis for a single
/// `center` alignment.
///
/// # Positional arguments /// # Positional arguments
/// - first (optional, `Alignment`): An alignment for any of the two axes. /// - Alignments: variadic, of type `alignment`.
/// - second (optional, `Alignment`): An alignment for the other axis.
/// ///
/// # Named arguments /// # Named arguments
/// - `horizontal` (`Alignment`): An alignment for the horizontal axis. /// - Horizontal alignment: `horizontal`, of type `alignment`.
/// - `vertical` (`Alignment`): An alignment for the vertical axis. /// - Vertical alignment: `vertical`, of type `alignment`.
/// ///
/// # Enumerations /// # Relevant types and constants
/// - `Alignment` /// - Type `alignment`
/// - `left` /// - `left`
/// - `right` /// - `right`
/// - `top` /// - `top`
/// - `bottom` /// - `bottom`
/// - `center` /// - `center`
/// pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value {
/// # Notes
/// Which axis an alignment should apply to (main or cross) is inferred from
/// either the argument itself (for anything other than `center`) or from the
/// second argument if present, defaulting to the cross axis for a single
/// `center` alignment.
pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
let first = args.get::<_, Spanned<SpecAlign>>(ctx, 0);
let second = args.get::<_, Spanned<SpecAlign>>(ctx, 1);
let hor = args.get::<_, Spanned<SpecAlign>>(ctx, "horizontal");
let ver = args.get::<_, Spanned<SpecAlign>>(ctx, "vertical");
args.done(ctx);
let prev_main = ctx.state.align.main; let first = args.find(ctx);
let second = args.find(ctx);
let hor = args.get(ctx, "horizontal");
let ver = args.get(ctx, "vertical");
let mut had = Gen::uniform(false); let mut had = Gen::uniform(false);
let mut had_center = false; let mut had_center = false;
for (axis, Spanned { v: arg, span }) in first for (axis, Spanned { v: arg, span }) in first
.into_iter() .into_iter()
.chain(second.into_iter()) .chain(second.into_iter())
.map(|arg| (arg.v.axis(), arg)) .map(|arg: Spanned<Alignment>| (arg.v.axis(), arg))
.chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg))) .chain(hor.into_iter().map(|arg| (Some(SpecAxis::Horizontal), arg)))
.chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg))) .chain(ver.into_iter().map(|arg| (Some(SpecAxis::Vertical), arg)))
{ {
@ -56,10 +49,7 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
let gen_align = arg.switch(ctx.state.flow); let gen_align = arg.switch(ctx.state.flow);
if arg.axis().map_or(false, |a| a != axis) { if arg.axis().map_or(false, |a| a != axis) {
ctx.diag(error!( ctx.diag(error!(span, "invalid alignment for {} axis", axis));
span,
"invalid alignment `{}` for {} axis", arg, axis,
));
} else if had.get(gen_axis) { } else if had.get(gen_axis) {
ctx.diag(error!(span, "duplicate alignment for {} axis", axis)); ctx.diag(error!(span, "duplicate alignment for {} axis", axis));
} else { } else {
@ -69,7 +59,7 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
} else { } else {
// We don't know the axis: This has to be a `center` alignment for a // We don't know the axis: This has to be a `center` alignment for a
// positional argument. // positional argument.
debug_assert_eq!(arg, SpecAlign::Center); debug_assert_eq!(arg, Alignment::Center);
if had.main && had.cross { if had.main && had.cross {
ctx.diag(error!(span, "duplicate alignment")); ctx.diag(error!(span, "duplicate alignment"));
@ -104,12 +94,12 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
ctx.state.align.cross = Align::Center; ctx.state.align.cross = Align::Center;
} }
if ctx.state.align.main != prev_main { if ctx.state.align.main != snapshot.align.main {
ctx.end_par_group(); ctx.end_par_group();
ctx.start_par_group(); ctx.start_par_group();
} }
if let Some(body) = body { if let Some(body) = args.find::<ValueContent>(ctx) {
body.eval(ctx); body.eval(ctx);
ctx.state = snapshot; ctx.state = snapshot;
} }
@ -117,30 +107,18 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
Value::None Value::None
} }
/// An argument to `[align]`.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
enum SpecAlign { pub(crate) enum Alignment {
Left, Left,
Center,
Right, Right,
Top, Top,
Bottom, Bottom,
Center,
} }
try_from_id!(SpecAlign["alignment"]: |v| match v { impl Alignment {
"left" => Some(Self::Left),
"right" => Some(Self::Right),
"top" => Some(Self::Top),
"bottom" => Some(Self::Bottom),
"center" => Some(Self::Center),
_ => None,
});
impl SpecAlign {
/// The specific axis this alignment refers to. /// The specific axis this alignment refers to.
/// fn axis(self) -> Option<SpecAxis> {
/// Returns `None` if this is `Center` since the axis is unknown.
pub fn axis(self) -> Option<SpecAxis> {
match self { match self {
Self::Left => Some(SpecAxis::Horizontal), Self::Left => Some(SpecAxis::Horizontal),
Self::Right => Some(SpecAxis::Horizontal), Self::Right => Some(SpecAxis::Horizontal),
@ -151,7 +129,7 @@ impl SpecAlign {
} }
} }
impl Switch for SpecAlign { impl Switch for Alignment {
type Other = Align; type Other = Align;
fn switch(self, flow: Flow) -> Self::Other { fn switch(self, flow: Flow) -> Self::Other {
@ -174,38 +152,34 @@ impl Switch for SpecAlign {
} }
} }
impl Display for SpecAlign { impl_type! {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { Alignment: "alignment"
f.pad(match self {
Self::Left => "left",
Self::Right => "right",
Self::Top => "top",
Self::Bottom => "bottom",
Self::Center => "center",
})
}
} }
/// `box`: Layout content into a box. /// `box`: Layout content into a box.
/// ///
/// # Named arguments /// # Named arguments
/// - `width` (`linear` relative to parent width): The width of the box. /// - Width of the box: `width`, of type `linear` relative to parent width.
/// - `height` (`linear` relative to parent height): The height of the box. /// - Height of the box: `height`, of type `linear` relative to parent height.
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value { pub fn boxed(ctx: &mut EvalContext, args: &mut Args) -> Value {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let body = args.find::<SynTree>().unwrap_or_default();
let width = args.get::<_, Linear>(ctx, "width"); let width = args.get(ctx, "width");
let height = args.get::<_, Linear>(ctx, "height"); let height = args.get(ctx, "height");
let main = args.get::<_, Spanned<Dir>>(ctx, "main-dir"); let main = args.get(ctx, "main-dir");
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross-dir"); let cross = args.get(ctx, "cross-dir");
ctx.set_flow(Gen::new(main, cross)); ctx.set_flow(Gen::new(main, cross));
args.done(ctx);
let flow = ctx.state.flow; let flow = ctx.state.flow;
let align = ctx.state.align; let align = ctx.state.align;
ctx.start_content_group(); ctx.start_content_group();
if let Some(body) = args.find::<ValueContent>(ctx) {
body.eval(ctx); body.eval(ctx);
}
let children = ctx.end_content_group(); let children = ctx.end_content_group();
ctx.push(Fixed { ctx.push(Fixed {
@ -227,31 +201,34 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
Value::None Value::None
} }
impl_type! {
Dir: "direction"
}
/// `h`: Add horizontal spacing. /// `h`: Add horizontal spacing.
/// ///
/// # Positional arguments /// # Positional arguments
/// - Spacing (`linear` relative to font size): The amount of spacing. /// - Amount of spacing: of type `linear` relative to current font size.
pub fn h(args: Args, ctx: &mut EvalContext) -> Value { pub fn h(ctx: &mut EvalContext, args: &mut Args) -> Value {
spacing(args, ctx, SpecAxis::Horizontal) spacing(ctx, args, SpecAxis::Horizontal)
} }
/// `v`: Add vertical spacing. /// `v`: Add vertical spacing.
/// ///
/// # Positional arguments /// # Positional arguments
/// - Spacing (`linear` relative to font size): The amount of spacing. /// - Amount of spacing: of type `linear` relative to current font size.
pub fn v(args: Args, ctx: &mut EvalContext) -> Value { pub fn v(ctx: &mut EvalContext, args: &mut Args) -> Value {
spacing(args, ctx, SpecAxis::Vertical) spacing(ctx, args, SpecAxis::Vertical)
} }
/// Apply spacing along a specific axis. /// Apply spacing along a specific axis.
fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value { fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value {
let spacing = args.need::<_, Linear>(ctx, 0, "spacing"); let spacing: Option<Linear> = args.require(ctx, "spacing");
args.done(ctx);
if let Some(linear) = spacing { if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.font_size()); let amount = linear.resolve(ctx.state.font.font_size());
let spacing = Spacing { amount, softness: Softness::Hard }; let spacing = Spacing { amount, softness: Softness::Hard };
if ctx.state.flow.main.axis() == axis { if axis == ctx.state.flow.main.axis() {
ctx.end_par_group(); ctx.end_par_group();
ctx.push(spacing); ctx.push(spacing);
ctx.start_par_group(); ctx.start_par_group();
@ -266,79 +243,78 @@ fn spacing(mut args: Args, ctx: &mut EvalContext, axis: SpecAxis) -> Value {
/// `page`: Configure pages. /// `page`: Configure pages.
/// ///
/// # Positional arguments /// # Positional arguments
/// - Paper name (optional, `Paper`). /// - Paper name: optional, of type `string`, see [here](crate::paper) for a
/// full list of all paper names.
/// ///
/// # Named arguments /// # Named arguments
/// - `width` (`length`): The width of pages. /// - Width of the page: `width`, of type `length`.
/// - `height` (`length`): The height of pages. /// - Height of the page: `height`, of type `length`.
/// - `margins` (`linear` relative to sides): The margins for all sides. /// - Margins for all sides: `margins`, of type `linear` relative to sides.
/// - `left` (`linear` relative to width): The left margin. /// - Left margin: `left`, of type `linear` relative to width.
/// - `right` (`linear` relative to width): The right margin. /// - Right margin: `right`, of type `linear` relative to width.
/// - `top` (`linear` relative to height): The top margin. /// - Top margin: `top`, of type `linear` relative to height.
/// - `bottom` (`linear` relative to height): The bottom margin. /// - Bottom margin: `bottom`, of type `linear` relative to height.
/// - `flip` (`bool`): Flips custom or paper-defined width and height. /// - Flip width and height: `flip`, of type `bool`.
/// pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
/// # Enumerations
/// - `Paper`: See [here](crate::paper) for a full list.
pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
if let Some(paper) = args.get::<_, Paper>(ctx, 0) { if let Some(name) = args.find::<Spanned<String>>(ctx) {
if let Some(paper) = Paper::from_name(&name.v) {
ctx.state.page.class = paper.class; ctx.state.page.class = paper.class;
ctx.state.page.size = paper.size(); ctx.state.page.size = paper.size();
} else {
ctx.diag(error!(name.span, "invalid paper name"));
}
} }
if let Some(width) = args.get::<_, Length>(ctx, "width") { if let Some(width) = args.get(ctx, "width") {
ctx.state.page.class = PaperClass::Custom; ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.width = width; ctx.state.page.size.width = width;
} }
if let Some(height) = args.get::<_, Length>(ctx, "height") { if let Some(height) = args.get(ctx, "height") {
ctx.state.page.class = PaperClass::Custom; ctx.state.page.class = PaperClass::Custom;
ctx.state.page.size.height = height; ctx.state.page.size.height = height;
} }
if let Some(margins) = args.get::<_, Linear>(ctx, "margins") { if let Some(margins) = args.get(ctx, "margins") {
ctx.state.page.margins = Sides::uniform(Some(margins)); ctx.state.page.margins = Sides::uniform(Some(margins));
} }
if let Some(left) = args.get::<_, Linear>(ctx, "left") { if let Some(left) = args.get(ctx, "left") {
ctx.state.page.margins.left = Some(left); ctx.state.page.margins.left = Some(left);
} }
if let Some(top) = args.get::<_, Linear>(ctx, "top") { if let Some(top) = args.get(ctx, "top") {
ctx.state.page.margins.top = Some(top); ctx.state.page.margins.top = Some(top);
} }
if let Some(right) = args.get::<_, Linear>(ctx, "right") { if let Some(right) = args.get(ctx, "right") {
ctx.state.page.margins.right = Some(right); ctx.state.page.margins.right = Some(right);
} }
if let Some(bottom) = args.get::<_, Linear>(ctx, "bottom") { if let Some(bottom) = args.get(ctx, "bottom") {
ctx.state.page.margins.bottom = Some(bottom); ctx.state.page.margins.bottom = Some(bottom);
} }
if args.get::<_, bool>(ctx, "flip").unwrap_or(false) { if args.get(ctx, "flip").unwrap_or(false) {
let size = &mut ctx.state.page.size; let size = &mut ctx.state.page.size;
std::mem::swap(&mut size.width, &mut size.height); std::mem::swap(&mut size.width, &mut size.height);
} }
let main = args.get::<_, Spanned<Dir>>(ctx, "main-dir"); let main = args.get(ctx, "main-dir");
let cross = args.get::<_, Spanned<Dir>>(ctx, "cross-dir"); let cross = args.get(ctx, "cross-dir");
ctx.set_flow(Gen::new(main, cross)); ctx.set_flow(Gen::new(main, cross));
args.done(ctx);
let mut softness = ctx.end_page_group(|_| false); let mut softness = ctx.end_page_group(|_| false);
if let Some(body) = args.find::<ValueContent>(ctx) {
if let Some(body) = body {
// TODO: Restrict body to a single page? // TODO: Restrict body to a single page?
ctx.start_page_group(Softness::Hard); ctx.start_page_group(Softness::Hard);
body.eval(ctx); body.eval(ctx);
ctx.end_page_group(|s| s == Softness::Hard); ctx.end_page_group(|s| s == Softness::Hard);
ctx.state = snapshot;
softness = Softness::Soft; softness = Softness::Soft;
ctx.state = snapshot;
} }
ctx.start_page_group(softness); ctx.start_page_group(softness);
@ -347,8 +323,7 @@ pub fn page(mut args: Args, ctx: &mut EvalContext) -> Value {
} }
/// `pagebreak`: Start a new page. /// `pagebreak`: Start a new page.
pub fn pagebreak(args: Args, ctx: &mut EvalContext) -> Value { pub fn pagebreak(ctx: &mut EvalContext, _: &mut Args) -> Value {
args.done(ctx);
ctx.end_page_group(|_| true); ctx.end_page_group(|_| true);
ctx.start_page_group(Softness::Hard); ctx.start_page_group(Softness::Hard);
Value::None Value::None

View File

@ -8,31 +8,60 @@ pub use insert::*;
pub use layout::*; pub use layout::*;
pub use style::*; pub use style::*;
use crate::eval::{Scope, ValueFunc}; use fontdock::{FontStretch, FontStyle, FontWeight};
macro_rules! std { use crate::eval::Scope;
($($func:expr $(=> $name:expr)?),* $(,)?) => { use crate::geom::Dir;
/// The scope containing all standard library functions.
/// The scope containing the standard library.
pub fn _std() -> Scope { pub fn _std() -> Scope {
let mut std = Scope::new(); let mut std = Scope::new();
$(
let _name = stringify!($func); // Functions.
$(let _name = $name;)? std.set("align", align);
std.set(_name, ValueFunc::new($func)); std.set("box", boxed);
)* std.set("font", font);
std.set("h", h);
std.set("image", image);
std.set("page", page);
std.set("pagebreak", pagebreak);
std.set("rgb", rgb);
std.set("v", v);
// Constants.
std.set("left", Alignment::Left);
std.set("center", Alignment::Center);
std.set("right", Alignment::Right);
std.set("top", Alignment::Top);
std.set("bottom", Alignment::Bottom);
std.set("ltr", Dir::LTR);
std.set("rtl", Dir::RTL);
std.set("ttb", Dir::TTB);
std.set("btt", Dir::BTT);
std.set("serif", FontFamily::Serif);
std.set("sans-serif", FontFamily::SansSerif);
std.set("monospace", FontFamily::Monospace);
std.set("normal", FontStyle::Normal);
std.set("italic", FontStyle::Italic);
std.set("oblique", FontStyle::Oblique);
std.set("thin", FontWeight::THIN);
std.set("extralight", FontWeight::EXTRALIGHT);
std.set("light", FontWeight::LIGHT);
std.set("regular", FontWeight::REGULAR);
std.set("medium", FontWeight::MEDIUM);
std.set("semibold", FontWeight::SEMIBOLD);
std.set("bold", FontWeight::BOLD);
std.set("extrabold", FontWeight::EXTRABOLD);
std.set("black", FontWeight::BLACK);
std.set("ultra-condensed", FontStretch::UltraCondensed);
std.set("extra-condensed", FontStretch::ExtraCondensed);
std.set("condensed", FontStretch::Condensed);
std.set("semi-condensed", FontStretch::SemiCondensed);
std.set("normal", FontStretch::Normal);
std.set("semi-expanded", FontStretch::SemiExpanded);
std.set("expanded", FontStretch::Expanded);
std.set("extra-expanded", FontStretch::ExtraExpanded);
std.set("ultra-expanded", FontStretch::UltraExpanded);
std std
} }
};
}
std! {
align,
boxed => "box",
font,
h,
image,
page,
pagebreak,
rgb,
v,
}

View File

@ -1,58 +1,41 @@
use std::fmt::{self, Display, Formatter};
use std::rc::Rc; use std::rc::Rc;
use fontdock::{FontStretch, FontStyle, FontWeight}; use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::color::{Color, RgbaColor}; use crate::color::{Color, RgbaColor};
use crate::eval::StringLike;
use crate::geom::Linear;
use crate::prelude::*; use crate::prelude::*;
/// `font`: Configure the font. /// `font`: Configure the font.
/// ///
/// # Positional arguments /// # Positional arguments
/// - Font size (optional, `linear` relative to current font size). /// - Font size: optional, of type `linear` relative to current font size.
/// - Font families ... (optional, variadic, `Family`) /// - Font families: variadic, of type `font-family`.
/// ///
/// # Named arguments /// # Named arguments
/// - `style` (`Style`): The font style. /// - Font Style: `style`, of type `font-style`.
/// - `weight` (`Weight`): The font weight. /// - Font Weight: `weight`, of type `font-weight`.
/// - `stretch` (`Stretch`): The font stretch. /// - Font Stretch: `stretch`, of type `font-stretch`.
/// - `serif` (`Family` or `dict` of type `Family`): The serif family. /// - Serif family definition: `serif`, of type `font-families`.
/// - `sans-serif` (`Family` or `dict` of type `Family`): The new sansserif family. /// - Sans-serif family definition: `sans-serif`, of type `font-families`.
/// - `monospace` (`Family` or `dict` of type `Family`): The monospace family. /// - Monospace family definition: `monospace`, of type `font-families`.
/// - `emoji` (`Family` or `dict` of type `Family`): The emoji family.
/// - `math` (`Family` or `dict` of type `Family`): The math family.
/// ///
/// # Examples /// # Relevant types and constants
/// Set font size and font families. /// - Type `font-families`
/// ```typst /// - coerces from `string`
/// [font 12pt, "Arial", "Noto Sans", sans-serif] /// - coerces from `array`
/// ``` /// - coerces from `font-family`
/// /// - Type `font-family`
/// Redefine the default sans-serif family to a single font family.
/// ```typst
/// [font sans-serif: "Source Sans Pro"]
/// ```
///
/// Redefine the default emoji family with a fallback.
/// ```typst
/// [font emoji: ("Segoe UI Emoji", "Noto Emoji")]
/// ```
///
/// # Enumerations
/// - `Family`
/// - `serif` /// - `serif`
/// - `sans-serif` /// - `sans-serif`
/// - `monospace` /// - `monospace`
/// - `emoji` /// - coerces from `string`
/// - `math` /// - Type `font-style`
/// - any string
/// - `Style`
/// - `normal` /// - `normal`
/// - `italic` /// - `italic`
/// - `oblique` /// - `oblique`
/// - `Weight` /// - Type `font-weight`
/// - `thin` or `hairline` (100) /// - `thin` (100)
/// - `extralight` (200) /// - `extralight` (200)
/// - `light` (300) /// - `light` (300)
/// - `regular` (400) /// - `regular` (400)
@ -61,8 +44,8 @@ use crate::prelude::*;
/// - `bold` (700) /// - `bold` (700)
/// - `extrabold` (800) /// - `extrabold` (800)
/// - `black` (900) /// - `black` (900)
/// - any integer between 100 and 900 /// - coerces from `integer`
/// - `Stretch` /// - Type `font-stretch`
/// - `ultra-condensed` /// - `ultra-condensed`
/// - `extra-condensed` /// - `extra-condensed`
/// - `condensed` /// - `condensed`
@ -72,11 +55,10 @@ use crate::prelude::*;
/// - `expanded` /// - `expanded`
/// - `extra-expanded` /// - `extra-expanded`
/// - `ultra-expanded` /// - `ultra-expanded`
pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let body = args.find::<SynTree>();
if let Some(linear) = args.find::<Linear>() { if let Some(linear) = args.find::<Linear>(ctx) {
if linear.is_absolute() { if linear.is_absolute() {
ctx.state.font.size = linear.abs; ctx.state.font.size = linear.abs;
ctx.state.font.scale = Relative::ONE.into(); ctx.state.font.scale = Relative::ONE.into();
@ -85,52 +67,35 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
} }
} }
let mut needs_flattening = false; let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect();
let list: Vec<_> = args.find_all::<StringLike>().map(|s| s.to_lowercase()).collect();
if !list.is_empty() { if !list.is_empty() {
Rc::make_mut(&mut ctx.state.font.families).list = list; let families = Rc::make_mut(&mut ctx.state.font.families);
needs_flattening = true; families.list = list;
families.flatten();
} }
if let Some(style) = args.get::<_, FontStyle>(ctx, "style") { if let Some(style) = args.get(ctx, "style") {
ctx.state.font.variant.style = style; ctx.state.font.variant.style = style;
} }
if let Some(weight) = args.get::<_, FontWeight>(ctx, "weight") { if let Some(weight) = args.get(ctx, "weight") {
ctx.state.font.variant.weight = weight; ctx.state.font.variant.weight = weight;
} }
if let Some(stretch) = args.get::<_, FontStretch>(ctx, "stretch") { if let Some(stretch) = args.get(ctx, "stretch") {
ctx.state.font.variant.stretch = stretch; ctx.state.font.variant.stretch = stretch;
} }
struct FamilyList(Vec<String>); for variant in FontFamily::VARIANTS {
if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) {
try_from_match!(FamilyList["family or list of families"] @ span: let strings = list.into_iter().map(|f| f.to_string()).collect();
Value::Str(v) => Self(vec![v.to_lowercase()]), let families = Rc::make_mut(&mut ctx.state.font.families);
Value::Dict(v) => Self(Args(v.with_span(span)) families.update_class_list(variant.to_string(), strings);
.find_all::<StringLike>() families.flatten();
.map(|s| s.to_lowercase())
.collect()
),
);
for &class in &["serif", "sans-serif", "monospace", "emoji", "math"] {
if let Some(list) = args.get::<_, FamilyList>(ctx, class) {
Rc::make_mut(&mut ctx.state.font.families)
.update_class_list(class.to_string(), list.0);
needs_flattening = true;
} }
} }
if needs_flattening { if let Some(body) = args.find::<ValueContent>(ctx) {
Rc::make_mut(&mut ctx.state.font.families).flatten();
}
args.done(ctx);
if let Some(body) = body {
body.eval(ctx); body.eval(ctx);
ctx.state = snapshot; ctx.state = snapshot;
} }
@ -138,24 +103,94 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value {
Value::None Value::None
} }
/// A list of font families.
#[derive(Debug, Clone, PartialEq)]
struct FontFamilies(Vec<FontFamily>);
impl_type! {
FontFamilies: "font family or array of font families",
Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]),
Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect()
),
#(family: FontFamily) => Self(vec![family]),
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum FontFamily {
Serif,
SansSerif,
Monospace,
Named(String),
}
impl FontFamily {
pub const VARIANTS: &'static [Self] =
&[Self::Serif, Self::SansSerif, Self::Monospace];
pub fn as_str(&self) -> &str {
match self {
Self::Serif => "serif",
Self::SansSerif => "sans-serif",
Self::Monospace => "monospace",
Self::Named(s) => s,
}
}
}
impl Display for FontFamily {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(self.as_str())
}
}
impl_type! {
FontFamily: "font family",
Value::Str(string) => Self::Named(string.to_lowercase())
}
impl_type! {
FontStyle: "font style"
}
impl_type! {
FontWeight: "font weight",
Value::Int(number) => {
let [min, max] = [Self::THIN, Self::BLACK];
let message = || format!("must be between {:#?} and {:#?}", min, max);
return if number < i64::from(min.to_number()) {
CastResult::Warn(min, message())
} else if number > i64::from(max.to_number()) {
CastResult::Warn(max, message())
} else {
CastResult::Ok(Self::from_number(number as u16))
};
},
}
impl_type! {
FontStretch: "font stretch"
}
/// `rgb`: Create an RGB(A) color. /// `rgb`: Create an RGB(A) color.
/// ///
/// # Positional arguments /// # Positional arguments
/// - Red component (`float` between 0.0 and 1.0). /// - Red component: of type `float`, between 0.0 and 1.0.
/// - Green component (`float` between 0.0 and 1.0). /// - Green component: of type `float`, between 0.0 and 1.0.
/// - Blue component (`float` between 0.0 and 1.0). /// - Blue component: of type `float`, between 0.0 and 1.0.
/// - Alpha component (optional, `float` between 0.0 and 1.0). /// - Alpha component: optional, of type `float`, between 0.0 and 1.0.
pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value { pub fn rgb(ctx: &mut EvalContext, args: &mut Args) -> Value {
let r = args.need::<_, Spanned<f64>>(ctx, 0, "red component"); let r = args.require(ctx, "red component");
let g = args.need::<_, Spanned<f64>>(ctx, 1, "green component"); let g = args.require(ctx, "green component");
let b = args.need::<_, Spanned<f64>>(ctx, 2, "blue component"); let b = args.require(ctx, "blue component");
let a = args.get::<_, Spanned<f64>>(ctx, 3); let a = args.find(ctx);
args.done(ctx);
let mut clamp = |component: Option<Spanned<f64>>, default| { let mut clamp = |component: Option<Spanned<f64>>, default| {
component.map_or(default, |c| { component.map_or(default, |c| {
if c.v < 0.0 || c.v > 1.0 { if c.v < 0.0 || c.v > 1.0 {
ctx.diag(error!(c.span, "should be between 0.0 and 1.0")); ctx.diag(warning!(c.span, "must be between 0.0 and 1.0"));
} }
(c.v.max(0.0).min(1.0) * 255.0).round() as u8 (c.v.max(0.0).min(1.0) * 255.0).round() as u8
}) })

142
src/parse/collection.rs Normal file
View File

@ -0,0 +1,142 @@
use super::*;
use crate::diag::Deco;
/// Parse the arguments to a function call.
pub fn arguments(p: &mut Parser) -> Arguments {
collection(p, vec![])
}
/// Parse a parenthesized group, which can be either of:
/// - Array literal
/// - Dictionary literal
/// - Parenthesized expression
pub fn parenthesized(p: &mut Parser) -> Expr {
p.start_group(Group::Paren);
let state = if p.eat_if(Token::Colon) {
collection(p, State::Dict(vec![]))
} else {
collection(p, State::Unknown)
};
p.end_group();
state.into_expr()
}
/// Parse a collection.
fn collection<T: Collection>(p: &mut Parser, mut collection: T) -> T {
let mut missing_coma = None;
while !p.eof() {
if let Some(arg) = p.span_if(argument) {
collection.push_arg(p, arg);
if let Some(pos) = missing_coma.take() {
p.diag_expected_at("comma", pos);
}
if p.eof() {
break;
}
let behind = p.last_end();
if p.eat_if(Token::Comma) {
collection.push_comma();
} else {
missing_coma = Some(behind);
}
}
}
collection
}
/// Parse an expression or a named pair.
fn argument(p: &mut Parser) -> Option<Argument> {
let first = p.span_if(expr)?;
if p.eat_if(Token::Colon) {
if let Expr::Lit(Lit::Ident(ident)) = first.v {
let expr = p.span_if(expr)?;
let name = ident.with_span(first.span);
p.deco(Deco::Name.with_span(name.span));
Some(Argument::Named(Named { name, expr }))
} else {
p.diag(error!(first.span, "name must be identifier"));
expr(p);
None
}
} else {
Some(Argument::Pos(first))
}
}
/// Abstraction for comma-separated list of expression / named pairs.
trait Collection {
fn push_arg(&mut self, p: &mut Parser, arg: Spanned<Argument>);
fn push_comma(&mut self) {}
}
impl Collection for Arguments {
fn push_arg(&mut self, _: &mut Parser, arg: Spanned<Argument>) {
self.push(arg.v);
}
}
/// State of collection parsing.
#[derive(Debug)]
enum State {
Unknown,
Expr(Spanned<Expr>),
Array(Array),
Dict(Dict),
}
impl State {
fn into_expr(self) -> Expr {
match self {
Self::Unknown => Expr::Lit(Lit::Array(vec![])),
Self::Expr(expr) => expr.v,
Self::Array(array) => Expr::Lit(Lit::Array(array)),
Self::Dict(dict) => Expr::Lit(Lit::Dict(dict)),
}
}
}
impl Collection for State {
fn push_arg(&mut self, p: &mut Parser, arg: Spanned<Argument>) {
match self {
Self::Unknown => match arg.v {
Argument::Pos(expr) => *self = Self::Expr(expr),
Argument::Named(named) => *self = Self::Dict(vec![named]),
},
Self::Expr(prev) => match arg.v {
Argument::Pos(expr) => *self = Self::Array(vec![take(prev), expr]),
Argument::Named(_) => diag(p, arg),
},
Self::Array(array) => match arg.v {
Argument::Pos(expr) => array.push(expr),
Argument::Named(_) => diag(p, arg),
},
Self::Dict(dict) => match arg.v {
Argument::Pos(_) => diag(p, arg),
Argument::Named(named) => dict.push(named),
},
}
}
fn push_comma(&mut self) {
if let Self::Expr(expr) = self {
*self = Self::Array(vec![take(expr)]);
}
}
}
fn take(expr: &mut Spanned<Expr>) -> Spanned<Expr> {
// Replace with anything, it's overwritten anyway.
std::mem::replace(expr, Spanned::zero(Expr::Lit(Lit::Bool(false))))
}
fn diag(p: &mut Parser, arg: Spanned<Argument>) {
p.diag(error!(arg.span, "{}", match arg.v {
Argument::Pos(_) => "expected named pair, found expression",
Argument::Named(_) => "expected expression, found named pair",
}));
}

View File

@ -1,5 +1,6 @@
//! Parsing and tokenization. //! Parsing and tokenization.
mod collection;
mod lines; mod lines;
mod parser; mod parser;
mod resolve; mod resolve;
@ -15,10 +16,11 @@ pub use tokens::*;
use std::str::FromStr; use std::str::FromStr;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::diag::{Deco, Pass}; use crate::diag::Pass;
use crate::eval::DictKey;
use crate::syntax::*; use crate::syntax::*;
use collection::{arguments, parenthesized};
/// Parse a string of source code. /// Parse a string of source code.
pub fn parse(src: &str) -> Pass<SynTree> { pub fn parse(src: &str) -> Pass<SynTree> {
let mut p = Parser::new(src); let mut p = Parser::new(src);
@ -153,6 +155,9 @@ fn block_expr(p: &mut Parser) -> Option<Expr> {
p.push_mode(TokenMode::Header); p.push_mode(TokenMode::Header);
p.start_group(Group::Brace); p.start_group(Group::Brace);
let expr = expr(p); let expr = expr(p);
while !p.eof() {
p.diag_unexpected();
}
p.pop_mode(); p.pop_mode();
p.end_group(); p.end_group();
expr expr
@ -161,7 +166,7 @@ fn block_expr(p: &mut Parser) -> Option<Expr> {
/// Parse a parenthesized function call. /// Parse a parenthesized function call.
fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall { fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall {
p.start_group(Group::Paren); p.start_group(Group::Paren);
let args = p.span(|p| dict_contents(p).0); let args = p.span(arguments);
p.end_group(); p.end_group();
ExprCall { name, args } ExprCall { name, args }
} }
@ -184,16 +189,16 @@ fn bracket_call(p: &mut Parser) -> ExprCall {
p.end_group(); p.end_group();
if p.peek() == Some(Token::LeftBracket) { if p.peek() == Some(Token::LeftBracket) {
let expr = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p)))); let body = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p))));
inner.span.expand(expr.span); inner.span.expand(body.span);
inner.v.args.v.0.push(LitDictEntry { key: None, expr }); inner.v.args.v.push(Argument::Pos(body));
} }
while let Some(mut top) = outer.pop() { while let Some(mut top) = outer.pop() {
let span = inner.span; let span = inner.span;
let node = inner.map(|c| SynNode::Expr(Expr::Call(c))); let node = inner.map(|c| SynNode::Expr(Expr::Call(c)));
let expr = Expr::Lit(Lit::Content(vec![node])).with_span(span); let expr = Expr::Lit(Lit::Content(vec![node])).with_span(span);
top.v.args.v.0.push(LitDictEntry { key: None, expr }); top.v.args.v.push(Argument::Pos(expr));
inner = top; inner = top;
} }
@ -215,9 +220,9 @@ fn bracket_subheader(p: &mut Parser) -> ExprCall {
Ident(String::new()).with_span(start) Ident(String::new()).with_span(start)
}); });
let args = p.span(|p| dict_contents(p).0); let args = p.span(arguments);
p.end_group(); p.end_group();
ExprCall { name, args } ExprCall { name, args }
} }
@ -231,75 +236,6 @@ fn bracket_body(p: &mut Parser) -> SynTree {
tree tree
} }
/// Parse the contents of a dictionary.
fn dict_contents(p: &mut Parser) -> (LitDict, bool) {
let mut dict = LitDict::new();
let mut missing_coma = None;
let mut comma_and_keyless = true;
while !p.eof() {
if let Some(entry) = dict_entry(p) {
let behind = entry.expr.span.end;
if let Some(pos) = missing_coma.take() {
p.diag_expected_at("comma", pos);
}
if let Some(key) = &entry.key {
comma_and_keyless = false;
p.deco(Deco::Name.with_span(key.span));
}
dict.0.push(entry);
if p.eof() {
break;
}
if p.eat_if(Token::Comma) {
comma_and_keyless = false;
} else {
missing_coma = Some(behind);
}
}
}
let coercible = comma_and_keyless && !dict.0.is_empty();
(dict, coercible)
}
/// Parse a single entry in a dictionary.
fn dict_entry(p: &mut Parser) -> Option<LitDictEntry> {
if let Some(ident) = p.span_if(ident) {
match p.peek() {
// Key-value pair.
Some(Token::Colon) => {
p.eat_assert(Token::Colon);
p.span_if(expr).map(|expr| LitDictEntry {
key: Some(ident.map(|id| DictKey::Str(id.0))),
expr,
})
}
// Function call.
Some(Token::LeftParen) => Some(LitDictEntry {
key: None,
expr: {
let start = ident.span.start;
let call = paren_call(p, ident);
Expr::Call(call).with_span(start .. p.last_end())
},
}),
// Just an identifier.
_ => Some(LitDictEntry {
key: None,
expr: ident.map(|id| Expr::Lit(Lit::Ident(id))),
}),
}
} else {
p.span_if(expr).map(|expr| LitDictEntry { key: None, expr })
}
}
/// Parse an expression: `term (+ term)*`. /// Parse an expression: `term (+ term)*`.
fn expr(p: &mut Parser) -> Option<Expr> { fn expr(p: &mut Parser) -> Option<Expr> {
binops(p, term, |token| match token { binops(p, term, |token| match token {
@ -418,19 +354,6 @@ fn content(p: &mut Parser) -> SynTree {
tree tree
} }
/// Parse a parenthesized expression: `(a + b)`, `(1, name: "value").
fn parenthesized(p: &mut Parser) -> Expr {
p.start_group(Group::Paren);
let (dict, coercible) = dict_contents(p);
let expr = if coercible {
dict.0.into_iter().next().expect("dict is coercible").expr.v
} else {
Expr::Lit(Lit::Dict(dict))
};
p.end_group();
expr
}
/// Parse an identifier. /// Parse an identifier.
fn ident(p: &mut Parser) -> Option<Ident> { fn ident(p: &mut Parser) -> Option<Ident> {
p.eat_map(|token| match token { p.eat_map(|token| match token {

View File

@ -5,7 +5,6 @@ use std::fmt::Debug;
use super::parse; use super::parse;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::diag::{Diag, Level, Pass}; use crate::diag::{Diag, Level, Pass};
use crate::eval::DictKey;
use crate::geom::Unit; use crate::geom::Unit;
use crate::syntax::*; use crate::syntax::*;
@ -154,21 +153,38 @@ fn Unary(op: impl Into<Spanned<UnOp>>, expr: impl Into<Spanned<Expr>>) -> Expr {
}) })
} }
macro_rules! Array {
(@$($expr:expr),* $(,)?) => {
vec![$(into!($expr)),*]
};
($($tts:tt)*) => (Expr::Lit(Lit::Array(Array![@$($tts)*])));
}
macro_rules! Dict { macro_rules! Dict {
(@$($a:expr $(=> $b:expr)?),* $(,)?) => { (@$($name:expr => $expr:expr),* $(,)?) => {
LitDict(vec![$(#[allow(unused)] { vec![$(Named {
let key: Option<Spanned<DictKey>> = None; name: into!($name).map(|s: &str| Ident(s.into())),
let expr = $a; expr: into!($expr)
$( }),*]
let key = Some(into!($a).map(|s: &str| s.into()));
let expr = $b;
)?
LitDictEntry { key, expr: into!(expr) }
}),*])
}; };
($($tts:tt)*) => (Expr::Lit(Lit::Dict(Dict![@$($tts)*]))); ($($tts:tt)*) => (Expr::Lit(Lit::Dict(Dict![@$($tts)*])));
} }
macro_rules! Args {
(@$a:expr) => {
Argument::Pos(into!($a))
};
(@$a:expr => $b:expr) => {
Argument::Named(Named {
name: into!($a).map(|s: &str| Ident(s.into())),
expr: into!($b)
})
};
($($a:expr $(=> $b:expr)?),* $(,)?) => {
vec![$(Args!(@$a $(=> $b)?)),*]
};
}
macro_rules! Content { macro_rules! Content {
(@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]); (@$($node:expr),* $(,)?) => (vec![$(into!($node)),*]);
($($tts:tt)*) => (Expr::Lit(Lit::Content(Content![@$($tts)*]))); ($($tts:tt)*) => (Expr::Lit(Lit::Content(Content![@$($tts)*])));
@ -188,10 +204,6 @@ macro_rules! Call {
($($tts:tt)*) => (SynNode::Expr(Call!(@$($tts)*))); ($($tts:tt)*) => (SynNode::Expr(Call!(@$($tts)*)));
} }
macro_rules! Args {
($($tts:tt)*) => (Dict![@$($tts)*]);
}
#[test] #[test]
fn test_parse_comments() { fn test_parse_comments() {
// In body. // In body.
@ -316,10 +328,9 @@ fn test_parse_groups() {
errors: [S(1..2, "expected function name, found closing paren"), errors: [S(1..2, "expected function name, found closing paren"),
S(2..2, "expected closing bracket")]); S(2..2, "expected closing bracket")]);
t!("[v {]}" t!("[v {*]_"
nodes: [Call!("v", Args![Content![]])], nodes: [Call!("v", Args![Content![Strong]]), Emph],
errors: [S(4..4, "expected closing brace"), errors: [S(5..5, "expected closing brace")]);
S(5..6, "unexpected closing brace")]);
// Test brace group. // Test brace group.
t!("{1 + [}" t!("{1 + [}"
@ -329,7 +340,7 @@ fn test_parse_groups() {
// Test subheader group. // Test subheader group.
t!("[v (|u )]" t!("[v (|u )]"
nodes: [Call!("v", Args![Dict![], Content![Call!("u")]])], nodes: [Call!("v", Args![Array![], Content![Call!("u")]])],
errors: [S(4..4, "expected closing paren"), errors: [S(4..4, "expected closing paren"),
S(7..8, "expected expression, found closing paren")]); S(7..8, "expected expression, found closing paren")]);
} }
@ -348,6 +359,12 @@ fn test_parse_blocks() {
nodes: [], nodes: [],
errors: [S(1..1, "expected expression"), errors: [S(1..1, "expected expression"),
S(3..5, "expected expression, found invalid token")]); S(3..5, "expected expression, found invalid token")]);
// Too much stuff.
t!("{1 #{} end"
nodes: [Block(Int(1)), Space, Text("end")],
errors: [S(3..4, "unexpected hex value"),
S(4..5, "unexpected opening brace")]);
} }
#[test] #[test]
@ -385,7 +402,7 @@ fn test_parse_bracket_funcs() {
nodes: [Call!("", Args![Int(1)])], nodes: [Call!("", Args![Int(1)])],
errors: [S(1..2, "expected function name, found hex value")]); errors: [S(1..2, "expected function name, found hex value")]);
// String header eats closing bracket. // String in header eats closing bracket.
t!(r#"[v "]"# t!(r#"[v "]"#
nodes: [Call!("v", Args![Str("]")])], nodes: [Call!("v", Args![Str("]")])],
errors: [S(5..5, "expected quote"), errors: [S(5..5, "expected quote"),
@ -428,16 +445,14 @@ fn test_parse_chaining() {
#[test] #[test]
fn test_parse_arguments() { fn test_parse_arguments() {
// Bracket functions. // Bracket functions.
t!("[v 1]" Call!("v", Args![Int(1)]));
t!("[v 1,]" Call!("v", Args![Int(1)]));
t!("[v a]" Call!("v", Args![Id("a")])); t!("[v a]" Call!("v", Args![Id("a")]));
t!("[v a,]" Call!("v", Args![Id("a")])); t!("[v 1,]" Call!("v", Args![Int(1)]));
t!("[v a:2]" Call!("v", Args!["a" => Int(2)])); t!("[v a:2]" Call!("v", Args!["a" => Int(2)]));
// Parenthesized function with nested dictionary literal. // Parenthesized function with nested array literal.
t!(r#"{f(1, a: (2, 3), #004, b: "five")}"# Block(Call!(@"f", Args![ t!(r#"{f(1, a: (2, 3), #004, b: "five")}"# Block(Call!(@"f", Args![
Int(1), Int(1),
"a" => Dict![Int(2), Int(3)], "a" => Array![Int(2), Int(3)],
Color(RgbaColor::new(0, 0, 0x44, 0xff)), Color(RgbaColor::new(0, 0, 0x44, 0xff)),
"b" => Str("five"), "b" => Str("five"),
]))); ])));
@ -447,56 +462,111 @@ fn test_parse_arguments() {
nodes: [Call!("v", Args![])], nodes: [Call!("v", Args![])],
errors: [S(3..5, "expected expression, found end of block comment")]); errors: [S(3..5, "expected expression, found end of block comment")]);
// Bad expression.
t!("[v a:1:]"
nodes: [Call!("v", Args!["a" => Int(1)])],
errors: [S(6..7, "expected expression, found colon")]);
// Missing comma between arguments. // Missing comma between arguments.
t!("[v 1 2]" t!("[v 1 2]"
nodes: [Call!("v", Args![Int(1), Int(2)])], nodes: [Call!("v", Args![Int(1), Int(2)])],
errors: [S(4..4, "expected comma")]); errors: [S(4..4, "expected comma")]);
// Missing expression after name. // Name has to be identifier.
t!("[v a:]"
nodes: [Call!("v", Args![])],
errors: [S(5..5, "expected expression")]);
// Bad expression after name.
t!("[v a:1:]"
nodes: [Call!("v", Args!["a" => Int(1)])],
errors: [S(6..7, "expected expression, found colon")]);
// Name has to be identifier. Number parsed as positional argument.
t!("[v 1:]" t!("[v 1:]"
nodes: [Call!("v", Args![Int(1)])], nodes: [Call!("v", Args![])],
errors: [S(4..5, "expected expression, found colon")]); errors: [S(3..4, "name must be identifier"),
S(5..5, "expected expression")]);
// Parsed as two positional arguments. // Name has to be identifier.
t!("[v 1:2]" t!("[v 1:2]"
nodes: [Call!("v", Args![Int(1), Int(2)])], nodes: [Call!("v", Args![])],
errors: [S(4..5, "expected expression, found colon"), errors: [S(3..4, "name must be identifier")]);
S(4..4, "expected comma")]);
} }
#[test] #[test]
fn test_parse_dict_literals() { fn test_parse_arrays() {
// Basic. // Empty array.
t!("{()}" Block(Dict![])); t!("{()}" Block(Array![]));
// With spans. // Array with one item and trailing comma + spans.
t!("{(1, two: 2)}" t!("{-(1,)}"
nodes: [S(0..13, Block(Dict![ nodes: [S(0..7, Block(Unary(
S(2..3, Int(1)), S(1..2, Neg),
S(5..8, "two") => S(10..11, Int(2)), S(2..6, Array![S(3..4, Int(1))])
]))], )))],
spans: true); spans: true);
// Array with three items and trailing comma.
t!(r#"{("one", 2, #003,)}"# Block(Array![
Str("one"),
Int(2),
Color(RgbaColor::new(0, 0, 0x33, 0xff))
]));
// Unclosed. // Unclosed.
t!("{(}" t!("{(}"
nodes: [Block(Dict![])], nodes: [Block(Array![])],
errors: [S(2..2, "expected closing paren")]); errors: [S(2..2, "expected closing paren")]);
// Missing comma + invalid token.
t!("{(1*/2)}"
nodes: [Block(Array![Int(1), Int(2)])],
errors: [S(3..5, "expected expression, found end of block comment"),
S(3..3, "expected comma")]);
// Invalid token.
t!("{(1, 1u 2)}"
nodes: [Block(Array![Int(1), Int(2)])],
errors: [S(5..7, "expected expression, found invalid token")]);
// Coerced to expression with leading comma.
t!("{(,1)}"
nodes: [Block(Int(1))],
errors: [S(2..3, "expected expression, found comma")]);
// Missing expression after name makes this an array.
t!("{(a:)}"
nodes: [Block(Array![])],
errors: [S(4..4, "expected expression")]);
// Expected expression, found named pair.
t!("{(1, b: 2)}"
nodes: [Block(Array![Int(1)])],
errors: [S(5..9, "expected expression, found named pair")]);
}
#[test]
fn test_parse_dictionaries() {
// Empty dictionary.
t!("{(:)}" Block(Dict![]));
// Dictionary with two pairs + spans.
t!("{(one: 1, two: 2)}"
nodes: [S(0..18, Block(Dict![
S(2..5, "one") => S(7..8, Int(1)),
S(10..13, "two") => S(15..16, Int(2)),
]))],
spans: true);
// Expected named pair, found expression.
t!("{(a: 1, b)}"
nodes: [Block(Dict!["a" => Int(1)])],
errors: [S(8..9, "expected named pair, found expression")]);
// Dictionary marker followed by more stuff.
t!("{(:1 b:2, true::)}"
nodes: [Block(Dict!["b" => Int(2)])],
errors: [S(3..4, "expected named pair, found expression"),
S(4..4, "expected comma"),
S(10..14, "name must be identifier"),
S(15..16, "expected expression, found colon")]);
} }
#[test] #[test]
fn test_parse_expressions() { fn test_parse_expressions() {
// Parenthesis. // Parentheses.
t!("{(x)}" Block(Id("x"))); t!("{(x)}{(1)}" Block(Id("x")), Block(Int(1)));
// Unary operations. // Unary operations.
t!("{-1}" Block(Unary(Neg, Int(1)))); t!("{-1}" Block(Unary(Neg, Int(1))));
@ -561,4 +631,12 @@ fn test_parse_values() {
t!("{#a5}" t!("{#a5}"
nodes: [Block(Color(RgbaColor::new(0, 0, 0, 0xff)))], nodes: [Block(Color(RgbaColor::new(0, 0, 0, 0xff)))],
errors: [S(1..4, "invalid color")]); errors: [S(1..4, "invalid color")]);
// Content.
t!("{{*Hi*}}" Block(Content![Strong, Text("Hi"), Strong]));
// Invalid tokens.
t!("{1u}"
nodes: [],
errors: [S(1..3, "expected expression, found invalid token")]);
} }

View File

@ -477,13 +477,9 @@ mod tests {
} }
#[test] #[test]
fn test_length_from_str_parses_correct_value_and_unit() { fn test_length_from_str() {
assert_eq!(parse_length("2.5cm"), Some((2.5, Cm))); assert_eq!(parse_length("2.5cm"), Some((2.5, Cm)));
assert_eq!(parse_length("1.e+2cm"), Some((100.0, Cm))); assert_eq!(parse_length("1.e+2cm"), Some((100.0, Cm)));
}
#[test]
fn test_length_from_str_works_with_non_ascii_chars() {
assert_eq!(parse_length("123🚚"), None); assert_eq!(parse_length("123🚚"), None);
} }

View File

@ -2,7 +2,10 @@
pub use crate::diag::{Feedback, Pass}; pub use crate::diag::{Feedback, Pass};
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::eval::{Args, Dict, Eval, EvalContext, Value, ValueDict}; pub use crate::eval::{
Args, CastResult, Eval, EvalContext, Value, ValueAny, ValueArray, ValueContent,
ValueDict,
};
pub use crate::geom::*; pub use crate::geom::*;
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::layout::LayoutNode; pub use crate::layout::LayoutNode;

View File

@ -2,7 +2,6 @@
use super::*; use super::*;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::eval::DictKey;
use crate::geom::Unit; use crate::geom::Unit;
/// An expression. /// An expression.
@ -24,10 +23,22 @@ pub struct ExprCall {
/// The name of the function. /// The name of the function.
pub name: Spanned<Ident>, pub name: Spanned<Ident>,
/// The arguments to the function. /// The arguments to the function.
pub args: Spanned<Arguments>,
}
/// The arguments to a function: `12, draw: false`.
/// ///
/// In case of a bracketed invocation with a body, the body is _not_ /// In case of a bracketed invocation with a body, the body is _not_
/// included in the span for the sake of clearer error messages. /// included in the span for the sake of clearer error messages.
pub args: Spanned<LitDict>, pub type Arguments = Vec<Argument>;
/// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
/// A positional arguments.
Pos(Spanned<Expr>),
/// A named argument.
Named(Named),
} }
/// A unary operation: `-x`. /// A unary operation: `-x`.
@ -92,28 +103,25 @@ pub enum Lit {
Color(RgbaColor), Color(RgbaColor),
/// A string literal: `"hello!"`. /// A string literal: `"hello!"`.
Str(String), Str(String),
/// A dictionary literal: `(false, 12cm, greeting: "hi")`. /// An array literal: `(1, "hi", 12cm)`.
Dict(LitDict), Array(Array),
/// A dictionary literal: `(color: #f79143, pattern: dashed)`.
Dict(Dict),
/// A content literal: `{*Hello* there!}`. /// A content literal: `{*Hello* there!}`.
Content(SynTree), Content(SynTree),
} }
/// A dictionary literal: `(false, 12cm, greeting: "hi")`. /// An array literal: `(1, "hi", 12cm)`.
#[derive(Debug, Default, Clone, PartialEq)] pub type Array = SpanVec<Expr>;
pub struct LitDict(pub Vec<LitDictEntry>);
/// An entry in a dictionary literal: `false` or `greeting: "hi"`. /// A dictionary literal: `(color: #f79143, pattern: dashed)`.
pub type Dict = Vec<Named>;
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LitDictEntry { pub struct Named {
/// The key of the entry if there was one: `greeting`. /// The name: `pattern`.
pub key: Option<Spanned<DictKey>>, pub name: Spanned<Ident>,
/// The value of the entry: `"hi"`. /// The right-hand side of the pair: `dashed`.
pub expr: Spanned<Expr>, pub expr: Spanned<Expr>,
} }
impl LitDict {
/// Create an empty dict literal.
pub fn new() -> Self {
Self::default()
}
}

View File

@ -30,7 +30,7 @@ pub enum SynNode {
Expr(Expr), Expr(Expr),
} }
/// A section heading: `# ...`. /// A section heading: `# Introduction`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading { pub struct NodeHeading {
/// The section depth (numer of hashtags minus 1). /// The section depth (numer of hashtags minus 1).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -6,12 +6,16 @@
// Wrong types. // Wrong types.
[font style: bold, weight: "thin", serif: 0] [font style: bold, weight: "thin", serif: 0]
// Weight out of range.
[font weight: 2700]
// Non-existing argument. // Non-existing argument.
[font something: "invalid"] [font something: "invalid"]
// compare-ref: false // compare-ref: false
// error: 4:7-4:12 unexpected argument // error: 4:7-4:12 unexpected argument
// error: 7:14-7:18 invalid font style // error: 7:14-7:18 expected font style, found font weight
// error: 7:28-7:34 expected font weight, found string // error: 7:28-7:34 expected font weight, found string
// error: 7:43-7:44 expected family or list of families, found integer // error: 7:43-7:44 expected font family or array of font families, found integer
// error: 10:7-10:27 unexpected argument // warning: 10:15-10:19 must be between 100 and 900
// error: 13:7-13:27 unexpected argument

View File

@ -4,15 +4,17 @@
Emoji: 🏀 Emoji: 🏀
// CMU Serif + Noto Emoji. // CMU Serif + Noto Emoji.
[font "CMU Serif", "Noto Emoji"][Emoji: 🏀] [font "CMU Serif", "Noto Emoji"][
Emoji: 🏀
]
// Class definitions. // Class definitions.
[font math: ("CMU Serif", "Latin Modern Math", "Noto Emoji")] [font serif: ("CMU Serif", "Latin Modern Math", "Noto Emoji")]
[font math][Math: α + β 3] [font serif][
Math: α + β 3
]
// Class redefinition. // Class definition reused.
[font sans-serif: "Noto Emoji"] [font sans-serif: "Noto Emoji"]
[font sans-serif: ("Archivo", sans-serif)] [font sans-serif: ("Archivo", sans-serif)]
New sans-serif. 🚀 New sans-serif. 🚀
// TODO: Add tests for other scripts.

View File

@ -7,5 +7,5 @@
[page main-dir: ltr] [page main-dir: ltr]
// compare-ref: false // compare-ref: false
// error: 4:7-4:18 invalid paper // error: 4:7-4:18 unknown variable
// error: 7:17-7:20 aligned axis // error: 7:17-7:20 aligned axis

View File

@ -22,4 +22,4 @@
[page margins: 0pt, left: 40pt][Overriden] [page margins: 0pt, left: 40pt][Overriden]
// Flip the page. // Flip the page.
[page a10, flip: true][Flipped] [page "a10", flip: true][Flipped]

View File

@ -1,7 +1,7 @@
// Test the `rgb` function. // Test the `rgb` function.
// Check the output. // Check the output.
[rgb 0.0, 0.3, 0.7] [val #004db3] [rgb 0.0, 0.3, 0.7]
// Alpha channel. // Alpha channel.
[rgb 1.0, 0.0, 0.0, 0.5] [rgb 1.0, 0.0, 0.0, 0.5]
@ -15,9 +15,8 @@
// Missing all components. // Missing all components.
[rgb] [rgb]
// error: 4:22-4:25 unknown function // warning: 10:6-10:9 must be between 0.0 and 1.0
// error: 10:6-10:9 should be between 0.0 and 1.0 // warning: 10:11-10:15 must be between 0.0 and 1.0
// error: 10:11-10:15 should be between 0.0 and 1.0
// error: 13:6-13:10 missing argument: blue component // error: 13:6-13:10 missing argument: blue component
// error: 16:5-16:5 missing argument: red component // error: 16:5-16:5 missing argument: red component
// error: 16:5-16:5 missing argument: green component // error: 16:5-16:5 missing argument: green component