mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Separate type for string values
This commit is contained in:
parent
fcb4e45118
commit
6ae6d86b9c
@ -1,5 +1,5 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@ -19,8 +19,8 @@ macro_rules! array {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A variably-typed array with clone-on-write value semantics.
|
/// An array of values with clone-on-write value semantics.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Array {
|
pub struct Array {
|
||||||
vec: Rc<Vec<Value>>,
|
vec: Rc<Vec<Value>>,
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ pub struct Array {
|
|||||||
impl Array {
|
impl Array {
|
||||||
/// Create a new, empty array.
|
/// Create a new, empty array.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { vec: Rc::new(vec![]) }
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new array from a vector of values.
|
/// Create a new array from a vector of values.
|
||||||
@ -36,16 +36,9 @@ impl Array {
|
|||||||
Self { vec: Rc::new(vec) }
|
Self { vec: Rc::new(vec) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new, empty array with the given `capacity`.
|
|
||||||
pub fn with_capacity(capacity: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
vec: Rc::new(Vec::with_capacity(capacity)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the array is empty.
|
/// Whether the array is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.vec.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The length of the array.
|
/// The length of the array.
|
||||||
@ -106,18 +99,28 @@ fn out_of_bounds(index: i64, len: i64) -> String {
|
|||||||
format!("array index out of bounds (index: {}, len: {})", index, len)
|
format!("array index out of bounds (index: {}, len: {})", index, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Array {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Array {
|
impl Debug for Array {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_list().entries(self.vec.iter()).finish()
|
f.debug_list().entries(self.vec.iter()).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Array {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_char('(')?;
|
||||||
|
for (i, value) in self.iter().enumerate() {
|
||||||
|
Display::fmt(value, f)?;
|
||||||
|
if i + 1 < self.vec.len() {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.len() == 1 {
|
||||||
|
f.write_char(',')?;
|
||||||
|
}
|
||||||
|
f.write_char(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Add for Array {
|
impl Add for Array {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::Value;
|
use super::{Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::pretty::pretty;
|
|
||||||
use crate::util::EcoString;
|
|
||||||
|
|
||||||
/// Create a new [`Dict`] from key-value pairs.
|
/// Create a new [`Dict`] from key-value pairs.
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
@ -15,15 +13,15 @@ macro_rules! dict {
|
|||||||
($($key:expr => $value:expr),* $(,)?) => {{
|
($($key:expr => $value:expr),* $(,)?) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut map = std::collections::BTreeMap::new();
|
let mut map = std::collections::BTreeMap::new();
|
||||||
$(map.insert($crate::util::EcoString::from($key), $crate::eval::Value::from($value));)*
|
$(map.insert($crate::eval::Str::from($key), $crate::eval::Value::from($value));)*
|
||||||
$crate::eval::Dict::from_map(map)
|
$crate::eval::Dict::from_map(map)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A variably-typed dictionary with clone-on-write value semantics.
|
/// A dictionary from strings to values with clone-on-write value semantics.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Dict {
|
pub struct Dict {
|
||||||
map: Rc<BTreeMap<EcoString, Value>>,
|
map: Rc<BTreeMap<Str, Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dict {
|
impl Dict {
|
||||||
@ -33,13 +31,13 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new dictionary from a mapping of strings to values.
|
/// Create a new dictionary from a mapping of strings to values.
|
||||||
pub fn from_map(map: BTreeMap<EcoString, Value>) -> Self {
|
pub fn from_map(map: BTreeMap<Str, Value>) -> Self {
|
||||||
Self { map: Rc::new(map) }
|
Self { map: Rc::new(map) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the dictionary is empty.
|
/// Whether the dictionary is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.map.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of pairs in the dictionary.
|
/// The number of pairs in the dictionary.
|
||||||
@ -48,21 +46,21 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the value the given `key` maps to.
|
/// Borrow the value the given `key` maps to.
|
||||||
pub fn get(&self, key: &str) -> StrResult<&Value> {
|
pub fn get(&self, key: Str) -> StrResult<&Value> {
|
||||||
self.map.get(key).ok_or_else(|| missing_key(key))
|
self.map.get(&key).ok_or_else(|| missing_key(&key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutably borrow the value the given `key` maps to.
|
/// Mutably borrow the value the given `key` maps to.
|
||||||
///
|
///
|
||||||
/// This inserts the key with [`None`](Value::None) as the value if not
|
/// This inserts the key with [`None`](Value::None) as the value if not
|
||||||
/// present so far.
|
/// present so far.
|
||||||
pub fn get_mut(&mut self, key: EcoString) -> &mut Value {
|
pub fn get_mut(&mut self, key: Str) -> &mut Value {
|
||||||
Rc::make_mut(&mut self.map).entry(key).or_default()
|
Rc::make_mut(&mut self.map).entry(key.into()).or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a mapping from the given `key` to the given `value`.
|
/// Insert a mapping from the given `key` to the given `value`.
|
||||||
pub fn insert(&mut self, key: EcoString, value: Value) {
|
pub fn insert(&mut self, key: Str, value: Value) {
|
||||||
Rc::make_mut(&mut self.map).insert(key, value);
|
Rc::make_mut(&mut self.map).insert(key.into(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the dictionary.
|
/// Clear the dictionary.
|
||||||
@ -75,20 +73,32 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over pairs of references to the contained keys and values.
|
/// Iterate over pairs of references to the contained keys and values.
|
||||||
pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> {
|
pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> {
|
||||||
self.map.iter()
|
self.map.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The missing key access error message.
|
/// The missing key access error message.
|
||||||
#[cold]
|
#[cold]
|
||||||
fn missing_key(key: &str) -> String {
|
fn missing_key(key: &Str) -> String {
|
||||||
format!("dictionary does not contain key: {}", pretty(key))
|
format!("dictionary does not contain key: {}", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Dict {
|
impl Display for Dict {
|
||||||
fn default() -> Self {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Self::new()
|
f.write_char('(')?;
|
||||||
|
if self.is_empty() {
|
||||||
|
f.write_char(':')?;
|
||||||
|
}
|
||||||
|
for (i, (key, value)) in self.iter().enumerate() {
|
||||||
|
f.write_str(key)?;
|
||||||
|
f.write_str(": ")?;
|
||||||
|
Display::fmt(value, f)?;
|
||||||
|
if i + 1 < self.map.len() {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char(')')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,21 +126,21 @@ impl AddAssign for Dict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<(EcoString, Value)> for Dict {
|
impl FromIterator<(Str, Value)> for Dict {
|
||||||
fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self {
|
||||||
Dict { map: Rc::new(iter.into_iter().collect()) }
|
Dict { map: Rc::new(iter.into_iter().collect()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extend<(EcoString, Value)> for Dict {
|
impl Extend<(Str, Value)> for Dict {
|
||||||
fn extend<T: IntoIterator<Item = (EcoString, Value)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
|
||||||
Rc::make_mut(&mut self.map).extend(iter);
|
Rc::make_mut(&mut self.map).extend(iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoIterator for Dict {
|
impl IntoIterator for Dict {
|
||||||
type Item = (EcoString, Value);
|
type Item = (Str, Value);
|
||||||
type IntoIter = std::collections::btree_map::IntoIter<EcoString, Value>;
|
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
match Rc::try_unwrap(self.map) {
|
match Rc::try_unwrap(self.map) {
|
||||||
@ -141,8 +151,8 @@ impl IntoIterator for Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Dict {
|
impl<'a> IntoIterator for &'a Dict {
|
||||||
type Item = (&'a EcoString, &'a Value);
|
type Item = (&'a Str, &'a Value);
|
||||||
type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>;
|
type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.iter()
|
self.iter()
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{Cast, EvalContext, Value};
|
use super::{Cast, EvalContext, Str, Value};
|
||||||
use crate::diag::{At, TypResult};
|
use crate::diag::{At, TypResult};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// An evaluatable function.
|
/// An evaluatable function.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Function(Rc<Repr<Func>>);
|
pub struct Function {
|
||||||
|
repr: Rc<Repr<Func>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// The unsized representation behind the [`Rc`].
|
/// The unsized representation behind the [`Rc`].
|
||||||
struct Repr<T: ?Sized> {
|
struct Repr<T: ?Sized> {
|
||||||
@ -17,20 +19,20 @@ struct Repr<T: ?Sized> {
|
|||||||
func: T,
|
func: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value>;
|
type Func = dyn Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value>;
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
/// Create a new function from a rust closure.
|
/// Create a new function from a rust closure.
|
||||||
pub fn new<F>(name: Option<EcoString>, func: F) -> Self
|
pub fn new<F>(name: Option<EcoString>, func: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + 'static,
|
F: Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value> + 'static,
|
||||||
{
|
{
|
||||||
Self(Rc::new(Repr { name, func }))
|
Self { repr: Rc::new(Repr { name, func }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of the function.
|
/// The name of the function.
|
||||||
pub fn name(&self) -> Option<&EcoString> {
|
pub fn name(&self) -> Option<&EcoString> {
|
||||||
self.0.name.as_ref()
|
self.repr.name.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,44 +40,55 @@ impl Deref for Function {
|
|||||||
type Target = Func;
|
type Target = Func;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0.func
|
&self.repr.func
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Function {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_str("<function")?;
|
||||||
|
if let Some(name) = self.name() {
|
||||||
|
f.write_char(' ')?;
|
||||||
|
f.write_str(name)?;
|
||||||
|
}
|
||||||
|
f.write_char('>')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Function {
|
impl Debug for Function {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_struct("ValueFunc").field("name", &self.0.name).finish()
|
f.debug_struct("Function").field("name", &self.repr.name).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Function {
|
impl PartialEq for Function {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
// We cast to thin pointers for comparison.
|
// We cast to thin pointers for comparison.
|
||||||
Rc::as_ptr(&self.0) as *const () == Rc::as_ptr(&other.0) as *const ()
|
Rc::as_ptr(&self.repr) as *const () == Rc::as_ptr(&other.repr) as *const ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluated arguments to a function.
|
/// Evaluated arguments to a function.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncArgs {
|
pub struct Arguments {
|
||||||
/// The span of the whole argument list.
|
/// The span of the whole argument list.
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
/// The positional and named arguments.
|
/// The positional and named arguments.
|
||||||
pub items: Vec<FuncArg>,
|
pub items: Vec<Argument>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An argument to a function call: `12` or `draw: false`.
|
/// An argument to a function call: `12` or `draw: false`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncArg {
|
pub struct Argument {
|
||||||
/// The span of the whole argument.
|
/// The span of the whole argument.
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
/// The name of the argument (`None` for positional arguments).
|
/// The name of the argument (`None` for positional arguments).
|
||||||
pub name: Option<EcoString>,
|
pub name: Option<Str>,
|
||||||
/// The value of the argument.
|
/// The value of the argument.
|
||||||
pub value: Spanned<Value>,
|
pub value: Spanned<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncArgs {
|
impl Arguments {
|
||||||
/// Find and consume the first castable positional argument.
|
/// Find and consume the first castable positional argument.
|
||||||
pub fn eat<T>(&mut self) -> Option<T>
|
pub fn eat<T>(&mut self) -> Option<T>
|
||||||
where
|
where
|
||||||
@ -150,16 +163,14 @@ impl FuncArgs {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl FuncArgs {
|
|
||||||
/// Reinterpret these arguments as actually being an array index.
|
/// Reinterpret these arguments as actually being an array index.
|
||||||
pub fn into_index(self) -> TypResult<i64> {
|
pub fn into_index(self) -> TypResult<i64> {
|
||||||
self.into_castable("index")
|
self.into_castable("index")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reinterpret these arguments as actually being a dictionary key.
|
/// Reinterpret these arguments as actually being a dictionary key.
|
||||||
pub fn into_key(self) -> TypResult<EcoString> {
|
pub fn into_key(self) -> TypResult<Str> {
|
||||||
self.into_castable("key")
|
self.into_castable("key")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,11 +181,11 @@ impl FuncArgs {
|
|||||||
{
|
{
|
||||||
let mut iter = self.items.into_iter();
|
let mut iter = self.items.into_iter();
|
||||||
let value = match iter.next() {
|
let value = match iter.next() {
|
||||||
Some(FuncArg { name: None, value, .. }) => value.v.cast().at(value.span)?,
|
Some(Argument { name: None, value, .. }) => value.v.cast().at(value.span)?,
|
||||||
None => {
|
None => {
|
||||||
bail!(self.span, "missing {}", what);
|
bail!(self.span, "missing {}", what);
|
||||||
}
|
}
|
||||||
Some(FuncArg { name: Some(_), span, .. }) => {
|
Some(Argument { name: Some(_), span, .. }) => {
|
||||||
bail!(span, "named pair is not allowed here");
|
bail!(span, "named pair is not allowed here");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -186,3 +197,24 @@ impl FuncArgs {
|
|||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Arguments {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_char('(')?;
|
||||||
|
for (i, arg) in self.items.iter().enumerate() {
|
||||||
|
if let Some(name) = &arg.name {
|
||||||
|
f.write_str(name)?;
|
||||||
|
f.write_str(": ")?;
|
||||||
|
}
|
||||||
|
Display::fmt(&arg.value.v, f)?;
|
||||||
|
if i + 1 < self.items.len() {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic! {
|
||||||
|
Arguments: "arguments",
|
||||||
|
}
|
||||||
|
@ -10,8 +10,10 @@ mod capture;
|
|||||||
mod function;
|
mod function;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod scope;
|
mod scope;
|
||||||
|
mod str;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
|
pub use self::str::*;
|
||||||
pub use array::*;
|
pub use array::*;
|
||||||
pub use capture::*;
|
pub use capture::*;
|
||||||
pub use dict::*;
|
pub use dict::*;
|
||||||
@ -35,7 +37,7 @@ use crate::parse::parse;
|
|||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
use crate::syntax::visit::Visit;
|
use crate::syntax::visit::Visit;
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
use crate::util::{EcoString, RefMutExt};
|
use crate::util::RefMutExt;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Evaluate a parsed source file into a module.
|
/// Evaluate a parsed source file into a module.
|
||||||
@ -214,7 +216,7 @@ impl Eval for Lit {
|
|||||||
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
||||||
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
|
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
|
||||||
Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)),
|
Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)),
|
||||||
Self::Str(_, ref v) => Value::Str(v.clone()),
|
Self::Str(_, ref v) => Value::Str(v.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,7 +246,7 @@ impl Eval for DictExpr {
|
|||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
self.items
|
self.items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|Named { name, expr }| Ok((name.string.clone(), expr.eval(ctx)?)))
|
.map(|Named { name, expr }| Ok(((&name.string).into(), expr.eval(ctx)?)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,7 +375,7 @@ impl Eval for CallExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Value::Dict(dict) => {
|
Value::Dict(dict) => {
|
||||||
dict.get(&args.into_key()?).map(Value::clone).at(self.span)
|
dict.get(args.into_key()?).map(Value::clone).at(self.span)
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::Func(func) => {
|
Value::Func(func) => {
|
||||||
@ -393,7 +395,7 @@ impl Eval for CallExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for CallArgs {
|
impl Eval for CallArgs {
|
||||||
type Output = FuncArgs;
|
type Output = Arguments;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let mut items = Vec::with_capacity(self.items.len());
|
let mut items = Vec::with_capacity(self.items.len());
|
||||||
@ -402,43 +404,49 @@ impl Eval for CallArgs {
|
|||||||
let span = arg.span();
|
let span = arg.span();
|
||||||
match arg {
|
match arg {
|
||||||
CallArg::Pos(expr) => {
|
CallArg::Pos(expr) => {
|
||||||
items.push(FuncArg {
|
items.push(Argument {
|
||||||
span,
|
span,
|
||||||
name: None,
|
name: None,
|
||||||
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CallArg::Named(Named { name, expr }) => {
|
CallArg::Named(Named { name, expr }) => {
|
||||||
items.push(FuncArg {
|
items.push(Argument {
|
||||||
span,
|
span,
|
||||||
name: Some(name.string.clone()),
|
name: Some((&name.string).into()),
|
||||||
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CallArg::Spread(expr) => match expr.eval(ctx)? {
|
CallArg::Spread(expr) => match expr.eval(ctx)? {
|
||||||
Value::Args(args) => {
|
|
||||||
items.extend(args.items.iter().cloned());
|
|
||||||
}
|
|
||||||
Value::Array(array) => {
|
Value::Array(array) => {
|
||||||
items.extend(array.into_iter().map(|value| FuncArg {
|
items.extend(array.into_iter().map(|value| Argument {
|
||||||
span,
|
span,
|
||||||
name: None,
|
name: None,
|
||||||
value: Spanned::new(value, span),
|
value: Spanned::new(value, span),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Value::Dict(dict) => {
|
Value::Dict(dict) => {
|
||||||
items.extend(dict.into_iter().map(|(key, value)| FuncArg {
|
items.extend(dict.into_iter().map(|(key, value)| Argument {
|
||||||
span,
|
span,
|
||||||
name: Some(key),
|
name: Some(key),
|
||||||
value: Spanned::new(value, span),
|
value: Spanned::new(value, span),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
v => bail!(expr.span(), "cannot spread {}", v.type_name()),
|
v => {
|
||||||
|
if let Value::Dyn(dynamic) = &v {
|
||||||
|
if let Some(args) = dynamic.downcast_ref::<Arguments>() {
|
||||||
|
items.extend(args.items.iter().cloned());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!(expr.span(), "cannot spread {}", v.type_name())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FuncArgs { span: self.span, items })
|
Ok(Arguments { span: self.span, items })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,7 +507,7 @@ impl Eval for ClosureExpr {
|
|||||||
|
|
||||||
// Put the remaining arguments into the sink.
|
// Put the remaining arguments into the sink.
|
||||||
if let Some(sink) = &sink {
|
if let Some(sink) = &sink {
|
||||||
ctx.scopes.def_mut(sink, Rc::new(args.take()));
|
ctx.scopes.def_mut(sink, args.take());
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = body.eval(ctx)?;
|
let value = body.eval(ctx)?;
|
||||||
@ -599,7 +607,7 @@ impl Eval for ForExpr {
|
|||||||
let iter = self.iter.eval(ctx)?;
|
let iter = self.iter.eval(ctx)?;
|
||||||
match (&self.pattern, iter) {
|
match (&self.pattern, iter) {
|
||||||
(ForPattern::Value(v), Value::Str(string)) => {
|
(ForPattern::Value(v), Value::Str(string)) => {
|
||||||
iter!(for (v => value) in string.chars().map(|c| Value::Str(c.into())))
|
iter!(for (v => value) in string.iter())
|
||||||
}
|
}
|
||||||
(ForPattern::Value(v), Value::Array(array)) => {
|
(ForPattern::Value(v), Value::Array(array)) => {
|
||||||
iter!(for (v => value) in array.into_iter())
|
iter!(for (v => value) in array.into_iter())
|
||||||
@ -627,7 +635,7 @@ impl Eval for ImportExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let path = self.path.eval(ctx)?.cast::<EcoString>().at(self.path.span())?;
|
let path = self.path.eval(ctx)?.cast::<Str>().at(self.path.span())?;
|
||||||
|
|
||||||
let file = ctx.import(&path, self.path.span())?;
|
let file = ctx.import(&path, self.path.span())?;
|
||||||
let module = &ctx.modules[&file];
|
let module = &ctx.modules[&file];
|
||||||
@ -657,7 +665,7 @@ impl Eval for IncludeExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let path = self.path.eval(ctx)?.cast::<EcoString>().at(self.path.span())?;
|
let path = self.path.eval(ctx)?.cast::<Str>().at(self.path.span())?;
|
||||||
|
|
||||||
let file = ctx.import(&path, self.path.span())?;
|
let file = ctx.import(&path, self.path.span())?;
|
||||||
let module = &ctx.modules[&file];
|
let module = &ctx.modules[&file];
|
||||||
|
@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{EvalContext, FuncArgs, Function, Value};
|
use super::{Arguments, EvalContext, Function, Value};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ impl Scope {
|
|||||||
/// Define a constant function.
|
/// Define a constant function.
|
||||||
pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
|
pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
|
||||||
where
|
where
|
||||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + 'static,
|
F: Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value> + 'static,
|
||||||
{
|
{
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
self.def_const(name.clone(), Function::new(Some(name), f));
|
self.def_const(name.clone(), Function::new(Some(name), f));
|
||||||
|
137
src/eval/str.rs
Normal file
137
src/eval/str.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
|
|
||||||
|
use crate::diag::StrResult;
|
||||||
|
use crate::util::EcoString;
|
||||||
|
|
||||||
|
/// A string value with inline storage and clone-on-write semantics.
|
||||||
|
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct Str {
|
||||||
|
string: EcoString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Str {
|
||||||
|
/// Create a new, empty string.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the string is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.string.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The length of the string in bytes.
|
||||||
|
pub fn len(&self) -> i64 {
|
||||||
|
self.string.len() as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow this as a string slice.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.string.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator over the chars as strings.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = Str> + '_ {
|
||||||
|
self.chars().map(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repeat this string `n` times.
|
||||||
|
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||||
|
let n = usize::try_from(n)
|
||||||
|
.ok()
|
||||||
|
.and_then(|n| self.string.len().checked_mul(n).map(|_| n))
|
||||||
|
.ok_or_else(|| format!("cannot repeat this string {} times", n))?;
|
||||||
|
|
||||||
|
Ok(self.string.repeat(n).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Str {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_char('"')?;
|
||||||
|
for c in self.chars() {
|
||||||
|
match c {
|
||||||
|
'\\' => f.write_str(r"\\")?,
|
||||||
|
'"' => f.write_str(r#"\""#)?,
|
||||||
|
'\n' => f.write_str(r"\n")?,
|
||||||
|
'\r' => f.write_str(r"\r")?,
|
||||||
|
'\t' => f.write_str(r"\t")?,
|
||||||
|
_ => f.write_char(c)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char('"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Str {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Debug::fmt(&self.string, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Str {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
self.string.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Str {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for Str {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.string.push_str(rhs.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for Str {
|
||||||
|
fn from(c: char) -> Self {
|
||||||
|
Self { string: c.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Str {
|
||||||
|
fn from(string: &str) -> Self {
|
||||||
|
Self { string: string.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Str {
|
||||||
|
fn from(string: String) -> Self {
|
||||||
|
Self { string: string.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EcoString> for Str {
|
||||||
|
fn from(string: EcoString) -> Self {
|
||||||
|
Self { string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&EcoString> for Str {
|
||||||
|
fn from(string: &EcoString) -> Self {
|
||||||
|
Self { string: string.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Str> for EcoString {
|
||||||
|
fn from(string: Str) -> Self {
|
||||||
|
string.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Str> for EcoString {
|
||||||
|
fn from(string: &Str) -> Self {
|
||||||
|
string.string.clone()
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::Value;
|
use super::{Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::exec::ExecContext;
|
use crate::exec::ExecContext;
|
||||||
use crate::syntax::{Expr, SyntaxTree};
|
use crate::syntax::{Expr, SyntaxTree};
|
||||||
@ -40,21 +40,9 @@ impl Template {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TemplateTree> for Template {
|
impl Display for Template {
|
||||||
fn from(tree: TemplateTree) -> Self {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Self::new(vec![TemplateNode::Tree(tree)])
|
f.pad("<template>")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TemplateFunc> for Template {
|
|
||||||
fn from(func: TemplateFunc) -> Self {
|
|
||||||
Self::new(vec![TemplateNode::Func(func)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcoString> for Template {
|
|
||||||
fn from(string: EcoString) -> Self {
|
|
||||||
Self::new(vec![TemplateNode::Str(string)])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,24 +71,42 @@ impl AddAssign for Template {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<EcoString> for Template {
|
impl Add<Str> for Template {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(mut self, rhs: EcoString) -> Self::Output {
|
fn add(mut self, rhs: Str) -> Self::Output {
|
||||||
Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs));
|
Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<Template> for EcoString {
|
impl Add<Template> for Str {
|
||||||
type Output = Template;
|
type Output = Template;
|
||||||
|
|
||||||
fn add(self, mut rhs: Template) -> Self::Output {
|
fn add(self, mut rhs: Template) -> Self::Output {
|
||||||
Rc::make_mut(&mut rhs.nodes).insert(0, TemplateNode::Str(self));
|
Rc::make_mut(&mut rhs.nodes).insert(0, TemplateNode::Str(self.into()));
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TemplateTree> for Template {
|
||||||
|
fn from(tree: TemplateTree) -> Self {
|
||||||
|
Self::new(vec![TemplateNode::Tree(tree)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TemplateFunc> for Template {
|
||||||
|
fn from(func: TemplateFunc) -> Self {
|
||||||
|
Self::new(vec![TemplateNode::Func(func)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Str> for Template {
|
||||||
|
fn from(string: Str) -> Self {
|
||||||
|
Self::new(vec![TemplateNode::Str(string.into())])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// One node of a template.
|
/// One node of a template.
|
||||||
///
|
///
|
||||||
/// Evaluating a template expression creates only a single node. Adding multiple
|
/// Evaluating a template expression creates only a single node. Adding multiple
|
||||||
|
@ -3,7 +3,7 @@ use std::cmp::Ordering;
|
|||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{ops, Array, Dict, FuncArgs, Function, Template, TemplateFunc};
|
use super::{ops, Array, Dict, Function, Str, Template, TemplateFunc};
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::exec::ExecContext;
|
use crate::exec::ExecContext;
|
||||||
@ -37,7 +37,7 @@ pub enum Value {
|
|||||||
/// A color value: `#f79143ff`.
|
/// A color value: `#f79143ff`.
|
||||||
Color(Color),
|
Color(Color),
|
||||||
/// A string: `"string"`.
|
/// A string: `"string"`.
|
||||||
Str(EcoString),
|
Str(Str),
|
||||||
/// An array of values: `(1, "hi", 12cm)`.
|
/// An array of values: `(1, "hi", 12cm)`.
|
||||||
Array(Array),
|
Array(Array),
|
||||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||||
@ -48,8 +48,6 @@ pub enum Value {
|
|||||||
Func(Function),
|
Func(Function),
|
||||||
/// A dynamic value.
|
/// A dynamic value.
|
||||||
Dyn(Dynamic),
|
Dyn(Dynamic),
|
||||||
/// Captured arguments to a function.
|
|
||||||
Args(Rc<FuncArgs>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
@ -75,12 +73,11 @@ impl Value {
|
|||||||
Self::Linear(_) => Linear::TYPE_NAME,
|
Self::Linear(_) => Linear::TYPE_NAME,
|
||||||
Self::Fractional(_) => Fractional::TYPE_NAME,
|
Self::Fractional(_) => Fractional::TYPE_NAME,
|
||||||
Self::Color(_) => Color::TYPE_NAME,
|
Self::Color(_) => Color::TYPE_NAME,
|
||||||
Self::Str(_) => EcoString::TYPE_NAME,
|
Self::Str(_) => Str::TYPE_NAME,
|
||||||
Self::Array(_) => Array::TYPE_NAME,
|
Self::Array(_) => Array::TYPE_NAME,
|
||||||
Self::Dict(_) => Dict::TYPE_NAME,
|
Self::Dict(_) => Dict::TYPE_NAME,
|
||||||
Self::Template(_) => Template::TYPE_NAME,
|
Self::Template(_) => Template::TYPE_NAME,
|
||||||
Self::Func(_) => Function::TYPE_NAME,
|
Self::Func(_) => Function::TYPE_NAME,
|
||||||
Self::Args(_) => Rc::<FuncArgs>::TYPE_NAME,
|
|
||||||
Self::Dyn(v) => v.type_name(),
|
Self::Dyn(v) => v.type_name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,6 +99,48 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Value {
|
||||||
|
fn default() -> Self {
|
||||||
|
Value::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::None => f.pad("none"),
|
||||||
|
Self::Auto => f.pad("auto"),
|
||||||
|
Self::Bool(v) => Display::fmt(v, f),
|
||||||
|
Self::Int(v) => Display::fmt(v, f),
|
||||||
|
Self::Float(v) => Display::fmt(v, f),
|
||||||
|
Self::Length(v) => Display::fmt(v, f),
|
||||||
|
Self::Angle(v) => Display::fmt(v, f),
|
||||||
|
Self::Relative(v) => Display::fmt(v, f),
|
||||||
|
Self::Linear(v) => Display::fmt(v, f),
|
||||||
|
Self::Fractional(v) => Display::fmt(v, f),
|
||||||
|
Self::Color(v) => Display::fmt(v, f),
|
||||||
|
Self::Str(v) => Display::fmt(v, f),
|
||||||
|
Self::Array(v) => Display::fmt(v, f),
|
||||||
|
Self::Dict(v) => Display::fmt(v, f),
|
||||||
|
Self::Template(v) => Display::fmt(v, f),
|
||||||
|
Self::Func(v) => Display::fmt(v, f),
|
||||||
|
Self::Dyn(v) => Display::fmt(v, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Value {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
ops::equal(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Value {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
ops::compare(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<i32> for Value {
|
impl From<i32> for Value {
|
||||||
fn from(v: i32) -> Self {
|
fn from(v: i32) -> Self {
|
||||||
Self::Int(v as i64)
|
Self::Int(v as i64)
|
||||||
@ -126,6 +165,12 @@ impl From<String> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<EcoString> for Value {
|
||||||
|
fn from(v: EcoString) -> Self {
|
||||||
|
Self::Str(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<RgbaColor> for Value {
|
impl From<RgbaColor> for Value {
|
||||||
fn from(v: RgbaColor) -> Self {
|
fn from(v: RgbaColor) -> Self {
|
||||||
Self::Color(Color::Rgba(v))
|
Self::Color(Color::Rgba(v))
|
||||||
@ -137,25 +182,6 @@ impl From<Dynamic> for Value {
|
|||||||
Self::Dyn(v)
|
Self::Dyn(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Value {
|
|
||||||
fn default() -> Self {
|
|
||||||
Value::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Value {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
ops::equal(self, other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Value {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
ops::compare(self, other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dynamic value.
|
/// A dynamic value.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Dynamic(Rc<dyn Bounds>);
|
pub struct Dynamic(Rc<dyn Bounds>);
|
||||||
@ -193,7 +219,7 @@ impl Display for Dynamic {
|
|||||||
|
|
||||||
impl Debug for Dynamic {
|
impl Debug for Dynamic {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.debug_tuple("ValueAny").field(&self.0).finish()
|
Debug::fmt(&self.0, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +358,7 @@ macro_rules! dynamic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
$type: Self::TYPE_NAME,
|
$type: <Self as $crate::eval::Type>::TYPE_NAME,
|
||||||
$($tts)*
|
$($tts)*
|
||||||
@this: Self => this.clone(),
|
@this: Self => this.clone(),
|
||||||
}
|
}
|
||||||
@ -379,16 +405,62 @@ macro_rules! castable {
|
|||||||
|
|
||||||
primitive! { bool: "boolean", Bool }
|
primitive! { bool: "boolean", Bool }
|
||||||
primitive! { i64: "integer", Int }
|
primitive! { i64: "integer", Int }
|
||||||
|
primitive! { f64: "float", Float, Int(v) => v as f64 }
|
||||||
primitive! { Length: "length", Length }
|
primitive! { Length: "length", Length }
|
||||||
primitive! { Angle: "angle", Angle }
|
primitive! { Angle: "angle", Angle }
|
||||||
primitive! { Relative: "relative", Relative }
|
primitive! { Relative: "relative", Relative }
|
||||||
primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
|
primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
|
||||||
primitive! { Fractional: "fractional", Fractional }
|
primitive! { Fractional: "fractional", Fractional }
|
||||||
primitive! { Color: "color", Color }
|
primitive! { Color: "color", Color }
|
||||||
primitive! { EcoString: "string", Str }
|
primitive! { Str: "string", Str }
|
||||||
primitive! { Array: "array", Array }
|
primitive! { Array: "array", Array }
|
||||||
primitive! { Dict: "dictionary", Dict }
|
primitive! { Dict: "dictionary", Dict }
|
||||||
primitive! { Template: "template", Template }
|
primitive! { Template: "template", Template }
|
||||||
primitive! { Function: "function", Func }
|
primitive! { Function: "function", Func }
|
||||||
primitive! { Rc<FuncArgs>: "arguments", Args }
|
|
||||||
primitive! { f64: "float", Float, Int(v) => v as f64 }
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn test(value: impl Into<Value>, exp: &str) {
|
||||||
|
assert_eq!(value.into().to_string(), exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value_to_string() {
|
||||||
|
// Primitives.
|
||||||
|
test(Value::None, "none");
|
||||||
|
test(false, "false");
|
||||||
|
test(12i64, "12");
|
||||||
|
test(3.14, "3.14");
|
||||||
|
test(Length::pt(5.5), "5.5pt");
|
||||||
|
test(Angle::deg(90.0), "90deg");
|
||||||
|
test(Relative::one() / 2.0, "50%");
|
||||||
|
test(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
|
||||||
|
test(Fractional::one() * 7.55, "7.55fr");
|
||||||
|
test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
||||||
|
|
||||||
|
// Collections.
|
||||||
|
test("hello", r#""hello""#);
|
||||||
|
test("\n", r#""\n""#);
|
||||||
|
test("\\", r#""\\""#);
|
||||||
|
test("\"", r#""\"""#);
|
||||||
|
test(array![], "()");
|
||||||
|
test(array![Value::None], "(none,)");
|
||||||
|
test(array![1, 2], "(1, 2)");
|
||||||
|
test(dict![], "(:)");
|
||||||
|
test(dict!["one" => 1], "(one: 1)");
|
||||||
|
test(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
|
||||||
|
|
||||||
|
// Functions.
|
||||||
|
test(Function::new(None, |_, _| Ok(Value::None)), "<function>");
|
||||||
|
test(
|
||||||
|
Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
|
||||||
|
"<function nil>",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dynamics.
|
||||||
|
test(Dynamic::new(1), "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,7 +11,6 @@ use std::fmt::Write;
|
|||||||
use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value};
|
use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value};
|
||||||
use crate::geom::Gen;
|
use crate::geom::Gen;
|
||||||
use crate::layout::{LayoutTree, StackChild, StackNode};
|
use crate::layout::{LayoutTree, StackChild, StackNode};
|
||||||
use crate::pretty::pretty;
|
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
@ -133,13 +132,13 @@ impl Exec for Value {
|
|||||||
fn exec(&self, ctx: &mut ExecContext) {
|
fn exec(&self, ctx: &mut ExecContext) {
|
||||||
match self {
|
match self {
|
||||||
Value::None => {}
|
Value::None => {}
|
||||||
Value::Int(v) => ctx.push_text(pretty(v)),
|
Value::Int(v) => ctx.push_text(v.to_string()),
|
||||||
Value::Float(v) => ctx.push_text(pretty(v)),
|
Value::Float(v) => ctx.push_text(v.to_string()),
|
||||||
Value::Str(v) => ctx.push_text(v),
|
Value::Str(v) => ctx.push_text(v),
|
||||||
Value::Template(v) => v.exec(ctx),
|
Value::Template(v) => v.exec(ctx),
|
||||||
// For values which can't be shown "naturally", we print the
|
// For values which can't be shown "naturally", we print the
|
||||||
// representation in monospace.
|
// representation in monospace.
|
||||||
other => ctx.push_monospace_text(pretty(other)),
|
other => ctx.push_monospace_text(other.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,7 +383,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let frames = self.layout_multi_row(ctx, first, &rest, y);
|
let frames = self.layout_multi_row(ctx, first, &rest, y);
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
if i + 1 != len {
|
if i + 1 < len {
|
||||||
self.constraints.exact.set(self.main, Some(self.full));
|
self.constraints.exact.set(self.main, Some(self.full));
|
||||||
}
|
}
|
||||||
self.push_row(ctx, frame);
|
self.push_row(ctx, frame);
|
||||||
|
@ -111,7 +111,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
let nodes = node.layout(ctx, &self.regions);
|
let nodes = node.layout(ctx, &self.regions);
|
||||||
let len = nodes.len();
|
let len = nodes.len();
|
||||||
for (i, frame) in nodes.into_iter().enumerate() {
|
for (i, frame) in nodes.into_iter().enumerate() {
|
||||||
if i + 1 != len {
|
if i + 1 < len {
|
||||||
self.constraints.exact = self.full.to_spec().map(Some);
|
self.constraints.exact = self.full.to_spec().map(Some);
|
||||||
}
|
}
|
||||||
self.push_frame(frame.item, aligns);
|
self.push_frame(frame.item, aligns);
|
||||||
|
@ -43,7 +43,6 @@ pub mod library;
|
|||||||
pub mod loading;
|
pub mod loading;
|
||||||
pub mod paper;
|
pub mod paper;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub mod pretty;
|
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -10,8 +10,8 @@ use crate::layout::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// `image`: An image.
|
/// `image`: An image.
|
||||||
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn image(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
let path = args.expect::<Spanned<Str>>("path to image file")?;
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `rect`: A rectangle with optional content.
|
/// `rect`: A rectangle with optional content.
|
||||||
pub fn rect(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn rect(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let fill = args.named("fill")?;
|
let fill = args.named("fill")?;
|
||||||
@ -38,7 +38,7 @@ pub fn rect(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `square`: A square with optional content.
|
/// `square`: A square with optional content.
|
||||||
pub fn square(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn square(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let length = args.named::<Length>("length")?.map(Linear::from);
|
let length = args.named::<Length>("length")?.map(Linear::from);
|
||||||
let width = match length {
|
let width = match length {
|
||||||
Some(length) => Some(length),
|
Some(length) => Some(length),
|
||||||
@ -80,7 +80,7 @@ fn rect_impl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `ellipse`: An ellipse with optional content.
|
/// `ellipse`: An ellipse with optional content.
|
||||||
pub fn ellipse(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn ellipse(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let fill = args.named("fill")?;
|
let fill = args.named("fill")?;
|
||||||
@ -89,7 +89,7 @@ pub fn ellipse(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `circle`: A circle with optional content.
|
/// `circle`: A circle with optional content.
|
||||||
pub fn circle(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn circle(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let diameter = args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r));
|
let diameter = args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r));
|
||||||
let width = match diameter {
|
let width = match diameter {
|
||||||
None => args.named("width")?,
|
None => args.named("width")?,
|
||||||
|
@ -3,8 +3,8 @@ use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSi
|
|||||||
use crate::paper::{Paper, PaperClass};
|
use crate::paper::{Paper, PaperClass};
|
||||||
|
|
||||||
/// `page`: Configure pages.
|
/// `page`: Configure pages.
|
||||||
pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let paper = match args.eat::<Spanned<EcoString>>() {
|
let paper = match args.eat::<Spanned<Str>>() {
|
||||||
Some(name) => match Paper::from_name(&name.v) {
|
Some(name) => match Paper::from_name(&name.v) {
|
||||||
None => bail!(name.span, "invalid paper name"),
|
None => bail!(name.span, "invalid paper name"),
|
||||||
paper => paper,
|
paper => paper,
|
||||||
@ -74,21 +74,21 @@ pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `pagebreak`: Start a new page.
|
/// `pagebreak`: Start a new page.
|
||||||
pub fn pagebreak(_: &mut EvalContext, _: &mut FuncArgs) -> TypResult<Value> {
|
pub fn pagebreak(_: &mut EvalContext, _: &mut Arguments) -> TypResult<Value> {
|
||||||
Ok(Value::template(move |ctx| ctx.pagebreak(true, true)))
|
Ok(Value::template(move |ctx| ctx.pagebreak(true, true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `h`: Horizontal spacing.
|
/// `h`: Horizontal spacing.
|
||||||
pub fn h(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn h(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
spacing_impl(args, GenAxis::Cross)
|
spacing_impl(args, GenAxis::Cross)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `v`: Vertical spacing.
|
/// `v`: Vertical spacing.
|
||||||
pub fn v(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn v(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
spacing_impl(args, GenAxis::Main)
|
spacing_impl(args, GenAxis::Main)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spacing_impl(args: &mut FuncArgs, axis: GenAxis) -> TypResult<Value> {
|
fn spacing_impl(args: &mut Arguments, axis: GenAxis) -> TypResult<Value> {
|
||||||
let spacing = args.expect::<Linear>("spacing")?;
|
let spacing = args.expect::<Linear>("spacing")?;
|
||||||
Ok(Value::template(move |ctx| {
|
Ok(Value::template(move |ctx| {
|
||||||
// TODO: Should this really always be font-size relative?
|
// TODO: Should this really always be font-size relative?
|
||||||
@ -98,7 +98,7 @@ fn spacing_impl(args: &mut FuncArgs, axis: GenAxis) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `align`: Configure the alignment along the layouting axes.
|
/// `align`: Configure the alignment along the layouting axes.
|
||||||
pub fn align(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn align(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let mut horizontal = args.named("horizontal")?;
|
let mut horizontal = args.named("horizontal")?;
|
||||||
let mut vertical = args.named("vertical")?;
|
let mut vertical = args.named("vertical")?;
|
||||||
let first = args.eat::<Align>();
|
let first = args.eat::<Align>();
|
||||||
@ -132,7 +132,7 @@ pub fn align(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `box`: Place content in a rectangular box.
|
/// `box`: Place content in a rectangular box.
|
||||||
pub fn boxed(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn boxed(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let body = args.eat().unwrap_or_default();
|
let body = args.eat().unwrap_or_default();
|
||||||
@ -143,7 +143,7 @@ pub fn boxed(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `block`: Place content in a block.
|
/// `block`: Place content in a block.
|
||||||
pub fn block(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn block(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let body = args.expect("body")?;
|
let body = args.expect("body")?;
|
||||||
Ok(Value::template(move |ctx| {
|
Ok(Value::template(move |ctx| {
|
||||||
let block = ctx.exec_template_stack(&body);
|
let block = ctx.exec_template_stack(&body);
|
||||||
@ -152,7 +152,7 @@ pub fn block(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `pad`: Pad content at the sides.
|
/// `pad`: Pad content at the sides.
|
||||||
pub fn pad(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn pad(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let all = args.eat();
|
let all = args.eat();
|
||||||
let left = args.named("left")?;
|
let left = args.named("left")?;
|
||||||
let top = args.named("top")?;
|
let top = args.named("top")?;
|
||||||
@ -174,7 +174,7 @@ pub fn pad(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `stack`: Stack children along an axis.
|
/// `stack`: Stack children along an axis.
|
||||||
pub fn stack(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn stack(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let dir = args.named("dir")?;
|
let dir = args.named("dir")?;
|
||||||
let children: Vec<_> = args.all().collect();
|
let children: Vec<_> = args.all().collect();
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ pub fn stack(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `grid`: Arrange children into a grid.
|
/// `grid`: Arrange children into a grid.
|
||||||
pub fn grid(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn grid(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let columns = args.named("columns")?.unwrap_or_default();
|
let columns = args.named("columns")?.unwrap_or_default();
|
||||||
let rows = args.named("rows")?.unwrap_or_default();
|
let rows = args.named("rows")?.unwrap_or_default();
|
||||||
|
|
||||||
|
@ -18,12 +18,11 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{EvalContext, FuncArgs, Scope, Template, Type, Value};
|
use crate::eval::{Arguments, EvalContext, Scope, Str, Template, Value};
|
||||||
use crate::exec::Exec;
|
use crate::exec::Exec;
|
||||||
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::EcoString;
|
|
||||||
|
|
||||||
/// Construct a scope containing all standard library definitions.
|
/// Construct a scope containing all standard library definitions.
|
||||||
pub fn new() -> Scope {
|
pub fn new() -> Scope {
|
||||||
|
@ -4,7 +4,7 @@ use crate::layout::Paint;
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// `font`: Configure the font.
|
/// `font`: Configure the font.
|
||||||
pub fn font(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn font(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let size = args.named::<Linear>("size")?.or_else(|| args.eat());
|
let size = args.named::<Linear>("size")?.or_else(|| args.eat());
|
||||||
let style = args.named("style")?;
|
let style = args.named("style")?;
|
||||||
let weight = args.named("weight")?;
|
let weight = args.named("weight")?;
|
||||||
@ -98,13 +98,13 @@ castable! {
|
|||||||
values
|
values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|v| v.cast().ok())
|
.filter_map(|v| v.cast().ok())
|
||||||
.map(|string: EcoString| string.to_lowercase())
|
.map(|string: Str| string.to_lowercase())
|
||||||
.collect()
|
.collect()
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `par`: Configure paragraphs.
|
/// `par`: Configure paragraphs.
|
||||||
pub fn par(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn par(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let par_spacing = args.named("spacing")?;
|
let par_spacing = args.named("spacing")?;
|
||||||
let line_spacing = args.named("leading")?;
|
let line_spacing = args.named("leading")?;
|
||||||
let body = args.expect::<Template>("body")?;
|
let body = args.expect::<Template>("body")?;
|
||||||
@ -126,8 +126,8 @@ pub fn par(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `lang`: Configure the language.
|
/// `lang`: Configure the language.
|
||||||
pub fn lang(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let iso = args.eat::<EcoString>();
|
let iso = args.eat::<Str>();
|
||||||
let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? {
|
let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? {
|
||||||
if dir.v.axis() == SpecAxis::Horizontal {
|
if dir.v.axis() == SpecAxis::Horizontal {
|
||||||
Some(dir.v)
|
Some(dir.v)
|
||||||
@ -160,22 +160,22 @@ fn lang_dir(iso: &str) -> Dir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `strike`: Enable striken-through text.
|
/// `strike`: Enable striken-through text.
|
||||||
pub fn strike(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn strike(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
line_impl(args, |font| &mut font.strikethrough)
|
line_impl(args, |font| &mut font.strikethrough)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `underline`: Enable underlined text.
|
/// `underline`: Enable underlined text.
|
||||||
pub fn underline(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn underline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
line_impl(args, |font| &mut font.underline)
|
line_impl(args, |font| &mut font.underline)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `overline`: Add an overline above text.
|
/// `overline`: Add an overline above text.
|
||||||
pub fn overline(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn overline(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
line_impl(args, |font| &mut font.overline)
|
line_impl(args, |font| &mut font.overline)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_impl(
|
fn line_impl(
|
||||||
args: &mut FuncArgs,
|
args: &mut Arguments,
|
||||||
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
|
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
|
||||||
) -> TypResult<Value> {
|
) -> TypResult<Value> {
|
||||||
let stroke = args.named("stroke")?.or_else(|| args.eat());
|
let stroke = args.named("stroke")?.or_else(|| args.eat());
|
||||||
|
@ -2,37 +2,34 @@ use std::cmp::Ordering;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
use crate::pretty::pretty;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// `type`: The name of a value's type.
|
/// `type`: The name of a value's type.
|
||||||
pub fn type_(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn type_(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let value = args.expect::<Value>("value")?;
|
Ok(args.expect::<Value>("value")?.type_name().into())
|
||||||
Ok(value.type_name().into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `repr`: The string representation of a value.
|
/// `repr`: The string representation of a value.
|
||||||
pub fn repr(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn repr(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let value = args.expect::<Value>("value")?;
|
Ok(args.expect::<Value>("value")?.to_string().into())
|
||||||
Ok(pretty(&value).into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `len`: The length of a string, an array or a dictionary.
|
/// `len`: The length of a string, an array or a dictionary.
|
||||||
pub fn len(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn len(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
let Spanned { v, span } = args.expect("collection")?;
|
let Spanned { v, span } = args.expect("collection")?;
|
||||||
Ok(match v {
|
Ok(Value::Int(match v {
|
||||||
Value::Str(v) => Value::Int(v.len() as i64),
|
Value::Str(v) => v.len(),
|
||||||
Value::Array(v) => Value::Int(v.len()),
|
Value::Array(v) => v.len(),
|
||||||
Value::Dict(v) => Value::Int(v.len()),
|
Value::Dict(v) => v.len(),
|
||||||
_ => bail!(span, "expected string, array or dictionary"),
|
_ => bail!(span, "expected string, array or dictionary"),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `rgb`: Create an RGB(A) color.
|
/// `rgb`: Create an RGB(A) color.
|
||||||
pub fn rgb(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn rgb(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
Ok(Value::Color(Color::Rgba(
|
Ok(Value::Color(Color::Rgba(
|
||||||
if let Some(string) = args.eat::<Spanned<EcoString>>() {
|
if let Some(string) = args.eat::<Spanned<Str>>() {
|
||||||
match RgbaColor::from_str(&string.v) {
|
match RgbaColor::from_str(&string.v) {
|
||||||
Ok(color) => color,
|
Ok(color) => color,
|
||||||
Err(_) => bail!(string.span, "invalid color"),
|
Err(_) => bail!(string.span, "invalid color"),
|
||||||
@ -49,35 +46,32 @@ pub fn rgb(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `min`: The minimum of a sequence of values.
|
/// `min`: The minimum of a sequence of values.
|
||||||
pub fn min(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn min(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
minmax(args, Ordering::Less)
|
minmax(args, Ordering::Less)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `max`: The maximum of a sequence of values.
|
/// `max`: The maximum of a sequence of values.
|
||||||
pub fn max(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
pub fn max(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
minmax(args, Ordering::Greater)
|
minmax(args, Ordering::Greater)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the minimum or maximum of a sequence of values.
|
/// Find the minimum or maximum of a sequence of values.
|
||||||
fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult<Value> {
|
fn minmax(args: &mut Arguments, goal: Ordering) -> TypResult<Value> {
|
||||||
let span = args.span;
|
|
||||||
|
|
||||||
let mut extremum = args.expect::<Value>("value")?;
|
let mut extremum = args.expect::<Value>("value")?;
|
||||||
for value in args.all::<Value>() {
|
for Spanned { v, span } in args.all::<Spanned<Value>>() {
|
||||||
match value.partial_cmp(&extremum) {
|
match v.partial_cmp(&extremum) {
|
||||||
Some(ordering) => {
|
Some(ordering) => {
|
||||||
if ordering == goal {
|
if ordering == goal {
|
||||||
extremum = value;
|
extremum = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => bail!(
|
None => bail!(
|
||||||
span,
|
span,
|
||||||
"cannot compare {} with {}",
|
"cannot compare {} with {}",
|
||||||
extremum.type_name(),
|
extremum.type_name(),
|
||||||
value.type_name(),
|
v.type_name(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(extremum)
|
Ok(extremum)
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@ pub fn resolve_string(string: &str) -> EcoString {
|
|||||||
out.push(c);
|
out.push(c);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Feedback that unicode escape sequence is wrong.
|
// TODO: Feedback that unicode escape sequence is wrong.
|
||||||
out += s.eaten_from(start);
|
out.push_str(s.eaten_from(start));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Feedback about invalid escape sequence.
|
// TODO: Feedback about invalid escape sequence.
|
||||||
_ => out += s.eaten_from(start),
|
_ => out.push_str(s.eaten_from(start)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ mod tests {
|
|||||||
fn test_resolve_strings() {
|
fn test_resolve_strings() {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test(string: &str, expected: &str) {
|
fn test(string: &str, expected: &str) {
|
||||||
assert_eq!(resolve_string(string), expected.to_string());
|
assert_eq!(resolve_string(string), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
test(r#"hello world"#, "hello world");
|
test(r#"hello world"#, "hello world");
|
||||||
|
@ -606,20 +606,20 @@ mod tests {
|
|||||||
}};
|
}};
|
||||||
(@$mode:ident: $src:expr => $($token:expr),*) => {{
|
(@$mode:ident: $src:expr => $($token:expr),*) => {{
|
||||||
let src = $src;
|
let src = $src;
|
||||||
let exp = vec![$($token),*];
|
|
||||||
let found = Tokens::new(&src, $mode).collect::<Vec<_>>();
|
let found = Tokens::new(&src, $mode).collect::<Vec<_>>();
|
||||||
check(&src, exp, found);
|
let expected = vec![$($token),*];
|
||||||
|
check(&src, found, expected);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check<T>(src: &str, exp: T, found: T)
|
fn check<T>(src: &str, found: T, expected: T)
|
||||||
where
|
where
|
||||||
T: Debug + PartialEq,
|
T: Debug + PartialEq,
|
||||||
{
|
{
|
||||||
if exp != found {
|
if found != expected {
|
||||||
println!("source: {:?}", src);
|
println!("source: {:?}", src);
|
||||||
println!("expected: {:#?}", exp);
|
println!("expected: {:#?}", expected);
|
||||||
println!("found: {:#?}", found);
|
println!("found: {:#?}", found);
|
||||||
panic!("test failed");
|
panic!("test failed");
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,11 @@ pub struct Ident {
|
|||||||
impl Ident {
|
impl Ident {
|
||||||
/// Create a new identifier from a string checking that it is a valid.
|
/// Create a new identifier from a string checking that it is a valid.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
string: impl AsRef<str> + Into<String>,
|
string: impl AsRef<str> + Into<EcoString>,
|
||||||
span: impl Into<Span>,
|
span: impl Into<Span>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
if is_ident(string.as_ref()) {
|
if is_ident(string.as_ref()) {
|
||||||
Some(Self {
|
Some(Self { span: span.into(), string: string.into() })
|
||||||
span: span.into(),
|
|
||||||
string: EcoString::from_str(string),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
mod expr;
|
mod expr;
|
||||||
mod ident;
|
mod ident;
|
||||||
mod node;
|
mod node;
|
||||||
|
mod pretty;
|
||||||
mod span;
|
mod span;
|
||||||
mod token;
|
mod token;
|
||||||
pub mod visit;
|
pub mod visit;
|
||||||
@ -10,6 +11,7 @@ pub mod visit;
|
|||||||
pub use expr::*;
|
pub use expr::*;
|
||||||
pub use ident::*;
|
pub use ident::*;
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
|
pub use pretty::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use token::*;
|
pub use token::*;
|
||||||
|
|
||||||
|
@ -2,10 +2,7 @@
|
|||||||
|
|
||||||
use std::fmt::{self, Arguments, Write};
|
use std::fmt::{self, Arguments, Write};
|
||||||
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use super::*;
|
||||||
use crate::eval::*;
|
|
||||||
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
|
|
||||||
use crate::syntax::*;
|
|
||||||
|
|
||||||
/// Pretty print an item and return the resulting string.
|
/// Pretty print an item and return the resulting string.
|
||||||
pub fn pretty<T>(item: &T) -> String
|
pub fn pretty<T>(item: &T) -> String
|
||||||
@ -23,7 +20,7 @@ pub trait Pretty {
|
|||||||
fn pretty(&self, p: &mut Printer);
|
fn pretty(&self, p: &mut Printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A buffer into which items are printed.
|
/// A buffer into which items can be pretty printed.
|
||||||
pub struct Printer {
|
pub struct Printer {
|
||||||
buf: String,
|
buf: String,
|
||||||
}
|
}
|
||||||
@ -223,14 +220,14 @@ impl Pretty for Lit {
|
|||||||
match self {
|
match self {
|
||||||
Self::None(_) => p.push_str("none"),
|
Self::None(_) => p.push_str("none"),
|
||||||
Self::Auto(_) => p.push_str("auto"),
|
Self::Auto(_) => p.push_str("auto"),
|
||||||
Self::Bool(_, v) => v.pretty(p),
|
Self::Bool(_, v) => write!(p, "{}", v).unwrap(),
|
||||||
Self::Int(_, v) => v.pretty(p),
|
Self::Int(_, v) => write!(p, "{}", v).unwrap(),
|
||||||
Self::Float(_, v) => v.pretty(p),
|
Self::Float(_, v) => write!(p, "{}", v).unwrap(),
|
||||||
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||||
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||||
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
|
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
|
||||||
Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(),
|
Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(),
|
||||||
Self::Str(_, v) => v.pretty(p),
|
Self::Str(_, v) => write!(p, "{:?}", v).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,134 +490,6 @@ impl Pretty for Ident {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for Value {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
match self {
|
|
||||||
Self::None => p.push_str("none"),
|
|
||||||
Self::Auto => p.push_str("auto"),
|
|
||||||
Self::Bool(v) => v.pretty(p),
|
|
||||||
Self::Int(v) => v.pretty(p),
|
|
||||||
Self::Float(v) => v.pretty(p),
|
|
||||||
Self::Length(v) => v.pretty(p),
|
|
||||||
Self::Angle(v) => v.pretty(p),
|
|
||||||
Self::Relative(v) => v.pretty(p),
|
|
||||||
Self::Linear(v) => v.pretty(p),
|
|
||||||
Self::Fractional(v) => v.pretty(p),
|
|
||||||
Self::Color(v) => v.pretty(p),
|
|
||||||
Self::Str(v) => v.pretty(p),
|
|
||||||
Self::Array(v) => v.pretty(p),
|
|
||||||
Self::Dict(v) => v.pretty(p),
|
|
||||||
Self::Template(v) => v.pretty(p),
|
|
||||||
Self::Func(v) => v.pretty(p),
|
|
||||||
Self::Args(v) => v.pretty(p),
|
|
||||||
Self::Dyn(v) => v.pretty(p),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for Array {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
p.push('(');
|
|
||||||
p.join(self, ", ", |item, p| item.pretty(p));
|
|
||||||
if self.len() == 1 {
|
|
||||||
p.push(',');
|
|
||||||
}
|
|
||||||
p.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for Dict {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
p.push('(');
|
|
||||||
if self.is_empty() {
|
|
||||||
p.push(':');
|
|
||||||
} else {
|
|
||||||
p.join(self, ", ", |(key, value), p| {
|
|
||||||
p.push_str(key);
|
|
||||||
p.push_str(": ");
|
|
||||||
value.pretty(p);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
p.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for Template {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
p.push_str("<template>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for Function {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
p.push_str("<function");
|
|
||||||
if let Some(name) = self.name() {
|
|
||||||
p.push(' ');
|
|
||||||
p.push_str(name);
|
|
||||||
}
|
|
||||||
p.push('>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for FuncArgs {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
p.push('(');
|
|
||||||
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
||||||
p.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for FuncArg {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
if let Some(name) = &self.name {
|
|
||||||
p.push_str(&name);
|
|
||||||
p.push_str(": ");
|
|
||||||
}
|
|
||||||
self.value.v.pretty(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for str {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
p.push('"');
|
|
||||||
for c in self.chars() {
|
|
||||||
match c {
|
|
||||||
'\\' => p.push_str(r"\\"),
|
|
||||||
'"' => p.push_str(r#"\""#),
|
|
||||||
'\n' => p.push_str(r"\n"),
|
|
||||||
'\r' => p.push_str(r"\r"),
|
|
||||||
'\t' => p.push_str(r"\t"),
|
|
||||||
_ => p.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.push('"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! pretty_display {
|
|
||||||
($($type:ty),* $(,)?) => {
|
|
||||||
$(impl Pretty for $type {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
write!(p, "{}", self).unwrap();
|
|
||||||
}
|
|
||||||
})*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pretty_display! {
|
|
||||||
i64,
|
|
||||||
f64,
|
|
||||||
bool,
|
|
||||||
Length,
|
|
||||||
Angle,
|
|
||||||
Relative,
|
|
||||||
Linear,
|
|
||||||
Fractional,
|
|
||||||
RgbaColor,
|
|
||||||
Color,
|
|
||||||
Dynamic,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -633,23 +502,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn test_parse(src: &str, exp: &str) {
|
fn test_parse(src: &str, expected: &str) {
|
||||||
let source = SourceFile::detached(src);
|
let source = SourceFile::detached(src);
|
||||||
let ast = parse(&source).unwrap();
|
let ast = parse(&source).unwrap();
|
||||||
let found = pretty(&ast);
|
let found = pretty(&ast);
|
||||||
if exp != found {
|
if found != expected {
|
||||||
println!("tree: {:#?}", ast);
|
println!("tree: {:#?}", ast);
|
||||||
println!("expected: {}", exp);
|
println!("expected: {}", expected);
|
||||||
println!("found: {}", found);
|
println!("found: {}", found);
|
||||||
panic!("test failed");
|
panic!("test failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn test_value(value: impl Into<Value>, exp: &str) {
|
|
||||||
assert_eq!(pretty(&value.into()), exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pretty_print_node() {
|
fn test_pretty_print_node() {
|
||||||
// Basic text and markup.
|
// Basic text and markup.
|
||||||
@ -746,41 +610,4 @@ mod tests {
|
|||||||
roundtrip("#import * from \"file.typ\"");
|
roundtrip("#import * from \"file.typ\"");
|
||||||
roundtrip("#include \"chapter1.typ\"");
|
roundtrip("#include \"chapter1.typ\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_pretty_print_value() {
|
|
||||||
// Primitives.
|
|
||||||
test_value(Value::None, "none");
|
|
||||||
test_value(false, "false");
|
|
||||||
test_value(12i64, "12");
|
|
||||||
test_value(3.14, "3.14");
|
|
||||||
test_value(Length::pt(5.5), "5.5pt");
|
|
||||||
test_value(Angle::deg(90.0), "90deg");
|
|
||||||
test_value(Relative::one() / 2.0, "50%");
|
|
||||||
test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
|
|
||||||
test_value(Fractional::one() * 7.55, "7.55fr");
|
|
||||||
test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
|
||||||
|
|
||||||
// Collections.
|
|
||||||
test_value("hello", r#""hello""#);
|
|
||||||
test_value("\n", r#""\n""#);
|
|
||||||
test_value("\\", r#""\\""#);
|
|
||||||
test_value("\"", r#""\"""#);
|
|
||||||
test_value(array![], "()");
|
|
||||||
test_value(array![Value::None], "(none,)");
|
|
||||||
test_value(array![1, 2], "(1, 2)");
|
|
||||||
test_value(dict![], "(:)");
|
|
||||||
test_value(dict!["one" => 1], "(one: 1)");
|
|
||||||
test_value(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
|
|
||||||
|
|
||||||
// Functions.
|
|
||||||
test_value(Function::new(None, |_, _| Ok(Value::None)), "<function>");
|
|
||||||
test_value(
|
|
||||||
Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
|
|
||||||
"<function nil>",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Dynamics.
|
|
||||||
test_value(Dynamic::new(1), "1");
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -142,18 +142,6 @@ impl Pos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for Pos {
|
|
||||||
fn from(index: u32) -> Self {
|
|
||||||
Self(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<usize> for Pos {
|
|
||||||
fn from(index: usize) -> Self {
|
|
||||||
Self(index as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Pos {
|
impl Debug for Pos {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Debug::fmt(&self.0, f)
|
Debug::fmt(&self.0, f)
|
||||||
@ -171,6 +159,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Pos {
|
||||||
|
fn from(index: u32) -> Self {
|
||||||
|
Self(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for Pos {
|
||||||
|
fn from(index: usize) -> Self {
|
||||||
|
Self(index as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a position or range into a span.
|
/// Convert a position or range into a span.
|
||||||
pub trait IntoSpan {
|
pub trait IntoSpan {
|
||||||
/// Convert into a span by providing the source id.
|
/// Convert into a span by providing the source id.
|
||||||
|
188
src/util/eco.rs
188
src/util/eco.rs
@ -1,14 +1,11 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::diag::StrResult;
|
/// An economical string with inline storage and clone-on-write semantics.
|
||||||
|
|
||||||
/// An economical string with inline storage and clone-on-write value semantics.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EcoString(Repr);
|
pub struct EcoString(Repr);
|
||||||
|
|
||||||
@ -144,75 +141,25 @@ impl EcoString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Repeat this string `n` times.
|
/// Repeat this string `n` times.
|
||||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
pub fn repeat(&self, n: usize) -> Self {
|
||||||
let (n, new) = usize::try_from(n)
|
if n == 0 {
|
||||||
.ok()
|
return Self::new();
|
||||||
.and_then(|n| self.len().checked_mul(n).map(|new| (n, new)))
|
}
|
||||||
.ok_or_else(|| format!("cannot repeat this string {} times", n))?;
|
|
||||||
|
|
||||||
if let Repr::Small { buf, len } = &self.0 {
|
if let Repr::Small { buf, len } = &self.0 {
|
||||||
let prev = usize::from(*len);
|
let prev = usize::from(*len);
|
||||||
|
let new = prev.saturating_mul(n);
|
||||||
if new <= LIMIT {
|
if new <= LIMIT {
|
||||||
let src = &buf[.. prev];
|
let src = &buf[.. prev];
|
||||||
let mut buf = [0; LIMIT];
|
let mut buf = [0; LIMIT];
|
||||||
for i in 0 .. n {
|
for i in 0 .. n {
|
||||||
buf[prev * i .. prev * (i + 1)].copy_from_slice(src);
|
buf[prev * i .. prev * (i + 1)].copy_from_slice(src);
|
||||||
}
|
}
|
||||||
return Ok(Self(Repr::Small { buf, len: new as u8 }));
|
return Self(Repr::Small { buf, len: new as u8 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self.as_str().repeat(n).into())
|
self.as_str().repeat(n).into()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Self> for EcoString {
|
|
||||||
fn from(s: &Self) -> Self {
|
|
||||||
s.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<char> for EcoString {
|
|
||||||
fn from(c: char) -> Self {
|
|
||||||
let mut buf = [0; LIMIT];
|
|
||||||
let len = c.encode_utf8(&mut buf).len();
|
|
||||||
Self(Repr::Small { buf, len: len as u8 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for EcoString {
|
|
||||||
fn from(s: &str) -> Self {
|
|
||||||
Self::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for EcoString {
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
Self::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&String> for EcoString {
|
|
||||||
fn from(s: &String) -> Self {
|
|
||||||
Self::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EcoString> for String {
|
|
||||||
fn from(s: EcoString) -> Self {
|
|
||||||
match s.0 {
|
|
||||||
Repr::Small { .. } => s.as_str().to_owned(),
|
|
||||||
Repr::Large(rc) => match Rc::try_unwrap(rc) {
|
|
||||||
Ok(string) => string,
|
|
||||||
Err(rc) => (*rc).clone(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&EcoString> for String {
|
|
||||||
fn from(s: &EcoString) -> Self {
|
|
||||||
s.as_str().to_owned()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,18 +183,6 @@ impl Deref for EcoString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<str> for EcoString {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Borrow<str> for EcoString {
|
|
||||||
fn borrow(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EcoString {
|
impl Default for EcoString {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
@ -286,12 +221,6 @@ impl PartialEq<&str> for EcoString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<String> for EcoString {
|
|
||||||
fn eq(&self, other: &String) -> bool {
|
|
||||||
self.as_str().eq(other.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for EcoString {
|
impl Ord for EcoString {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
self.as_str().cmp(other.as_str())
|
self.as_str().cmp(other.as_str())
|
||||||
@ -304,35 +233,6 @@ impl PartialOrd for EcoString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for EcoString {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
|
||||||
self + rhs.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddAssign for EcoString {
|
|
||||||
fn add_assign(&mut self, rhs: EcoString) {
|
|
||||||
self.push_str(&rhs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<&str> for EcoString {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(mut self, rhs: &str) -> Self::Output {
|
|
||||||
self.push_str(rhs);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddAssign<&str> for EcoString {
|
|
||||||
fn add_assign(&mut self, rhs: &str) {
|
|
||||||
self.push_str(rhs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for EcoString {
|
impl Hash for EcoString {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.as_str().hash(state);
|
self.as_str().hash(state);
|
||||||
@ -351,6 +251,62 @@ impl Write for EcoString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for EcoString {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for EcoString {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Self> for EcoString {
|
||||||
|
fn from(s: &Self) -> Self {
|
||||||
|
s.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<char> for EcoString {
|
||||||
|
fn from(c: char) -> Self {
|
||||||
|
let mut buf = [0; LIMIT];
|
||||||
|
let len = c.encode_utf8(&mut buf).len();
|
||||||
|
Self(Repr::Small { buf, len: len as u8 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for EcoString {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Self::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for EcoString {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EcoString> for String {
|
||||||
|
fn from(s: EcoString) -> Self {
|
||||||
|
match s.0 {
|
||||||
|
Repr::Small { .. } => s.as_str().to_owned(),
|
||||||
|
Repr::Large(rc) => match Rc::try_unwrap(rc) {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(rc) => (*rc).clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&EcoString> for String {
|
||||||
|
fn from(s: &EcoString) -> Self {
|
||||||
|
s.as_str().to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -436,17 +392,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_str_repeat() {
|
fn test_str_repeat() {
|
||||||
// Test with empty string.
|
// Test with empty string.
|
||||||
assert_eq!(EcoString::new().repeat(0).unwrap(), "");
|
assert_eq!(EcoString::new().repeat(0), "");
|
||||||
assert_eq!(EcoString::new().repeat(100).unwrap(), "");
|
assert_eq!(EcoString::new().repeat(100), "");
|
||||||
|
|
||||||
// Test non-spilling and spilling case.
|
// Test non-spilling and spilling case.
|
||||||
let v = EcoString::from("abc");
|
let v = EcoString::from("abc");
|
||||||
assert_eq!(v.repeat(0).unwrap(), "");
|
assert_eq!(v.repeat(0), "");
|
||||||
assert_eq!(v.repeat(3).unwrap(), "abcabcabc");
|
assert_eq!(v.repeat(3), "abcabcabc");
|
||||||
assert_eq!(v.repeat(5).unwrap(), "abcabcabcabcabc");
|
assert_eq!(v.repeat(5), "abcabcabcabcabc");
|
||||||
assert_eq!(
|
|
||||||
v.repeat(-1).unwrap_err(),
|
|
||||||
"cannot repeat this string -1 times",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,5 @@
|
|||||||
#min()
|
#min()
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 10-19 cannot compare integer with string
|
// Error: 14-18 cannot compare integer with string
|
||||||
#test(min(1, "hi"), error)
|
#test(min(1, "hi"), error)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user