mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +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::fmt::{self, Debug, Formatter};
|
||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::rc::Rc;
|
||||
@ -19,8 +19,8 @@ macro_rules! array {
|
||||
};
|
||||
}
|
||||
|
||||
/// A variably-typed array with clone-on-write value semantics.
|
||||
#[derive(Clone, PartialEq)]
|
||||
/// An array of values with clone-on-write value semantics.
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct Array {
|
||||
vec: Rc<Vec<Value>>,
|
||||
}
|
||||
@ -28,7 +28,7 @@ pub struct Array {
|
||||
impl Array {
|
||||
/// Create a new, empty array.
|
||||
pub fn new() -> Self {
|
||||
Self { vec: Rc::new(vec![]) }
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create a new array from a vector of values.
|
||||
@ -36,16 +36,9 @@ impl Array {
|
||||
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.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
self.vec.is_empty()
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
impl Default for Array {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Array {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
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 {
|
||||
type Output = Self;
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Value;
|
||||
use super::{Str, Value};
|
||||
use crate::diag::StrResult;
|
||||
use crate::pretty::pretty;
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// Create a new [`Dict`] from key-value pairs.
|
||||
#[allow(unused_macros)]
|
||||
@ -15,15 +13,15 @@ macro_rules! dict {
|
||||
($($key:expr => $value:expr),* $(,)?) => {{
|
||||
#[allow(unused_mut)]
|
||||
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)
|
||||
}};
|
||||
}
|
||||
|
||||
/// A variably-typed dictionary with clone-on-write value semantics.
|
||||
#[derive(Clone, PartialEq)]
|
||||
/// A dictionary from strings to values with clone-on-write value semantics.
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct Dict {
|
||||
map: Rc<BTreeMap<EcoString, Value>>,
|
||||
map: Rc<BTreeMap<Str, Value>>,
|
||||
}
|
||||
|
||||
impl Dict {
|
||||
@ -33,13 +31,13 @@ impl Dict {
|
||||
}
|
||||
|
||||
/// 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) }
|
||||
}
|
||||
|
||||
/// Whether the dictionary is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
/// The number of pairs in the dictionary.
|
||||
@ -48,21 +46,21 @@ impl Dict {
|
||||
}
|
||||
|
||||
/// Borrow the value the given `key` maps to.
|
||||
pub fn get(&self, key: &str) -> StrResult<&Value> {
|
||||
self.map.get(key).ok_or_else(|| missing_key(key))
|
||||
pub fn get(&self, key: Str) -> StrResult<&Value> {
|
||||
self.map.get(&key).ok_or_else(|| missing_key(&key))
|
||||
}
|
||||
|
||||
/// Mutably borrow the value the given `key` maps to.
|
||||
///
|
||||
/// This inserts the key with [`None`](Value::None) as the value if not
|
||||
/// present so far.
|
||||
pub fn get_mut(&mut self, key: EcoString) -> &mut Value {
|
||||
Rc::make_mut(&mut self.map).entry(key).or_default()
|
||||
pub fn get_mut(&mut self, key: Str) -> &mut Value {
|
||||
Rc::make_mut(&mut self.map).entry(key.into()).or_default()
|
||||
}
|
||||
|
||||
/// Insert a mapping from the given `key` to the given `value`.
|
||||
pub fn insert(&mut self, key: EcoString, value: Value) {
|
||||
Rc::make_mut(&mut self.map).insert(key, value);
|
||||
pub fn insert(&mut self, key: Str, value: Value) {
|
||||
Rc::make_mut(&mut self.map).insert(key.into(), value);
|
||||
}
|
||||
|
||||
/// Clear the dictionary.
|
||||
@ -75,20 +73,32 @@ impl Dict {
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
||||
/// The missing key access error message.
|
||||
#[cold]
|
||||
fn missing_key(key: &str) -> String {
|
||||
format!("dictionary does not contain key: {}", pretty(key))
|
||||
fn missing_key(key: &Str) -> String {
|
||||
format!("dictionary does not contain key: {}", key)
|
||||
}
|
||||
|
||||
impl Default for Dict {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
impl Display for Dict {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
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 {
|
||||
fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self {
|
||||
impl FromIterator<(Str, Value)> for Dict {
|
||||
fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self {
|
||||
Dict { map: Rc::new(iter.into_iter().collect()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(EcoString, Value)> for Dict {
|
||||
fn extend<T: IntoIterator<Item = (EcoString, Value)>>(&mut self, iter: T) {
|
||||
impl Extend<(Str, Value)> for Dict {
|
||||
fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
|
||||
Rc::make_mut(&mut self.map).extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Dict {
|
||||
type Item = (EcoString, Value);
|
||||
type IntoIter = std::collections::btree_map::IntoIter<EcoString, Value>;
|
||||
type Item = (Str, Value);
|
||||
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match Rc::try_unwrap(self.map) {
|
||||
@ -141,8 +151,8 @@ impl IntoIterator for Dict {
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Dict {
|
||||
type Item = (&'a EcoString, &'a Value);
|
||||
type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>;
|
||||
type Item = (&'a Str, &'a Value);
|
||||
type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
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::rc::Rc;
|
||||
|
||||
use super::{Cast, EvalContext, Value};
|
||||
use super::{Cast, EvalContext, Str, Value};
|
||||
use crate::diag::{At, TypResult};
|
||||
use crate::syntax::{Span, Spanned};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// An evaluatable function.
|
||||
#[derive(Clone)]
|
||||
pub struct Function(Rc<Repr<Func>>);
|
||||
pub struct Function {
|
||||
repr: Rc<Repr<Func>>,
|
||||
}
|
||||
|
||||
/// The unsized representation behind the [`Rc`].
|
||||
struct Repr<T: ?Sized> {
|
||||
@ -17,20 +19,20 @@ struct Repr<T: ?Sized> {
|
||||
func: T,
|
||||
}
|
||||
|
||||
type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value>;
|
||||
type Func = dyn Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value>;
|
||||
|
||||
impl Function {
|
||||
/// Create a new function from a rust closure.
|
||||
pub fn new<F>(name: Option<EcoString>, func: F) -> Self
|
||||
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.
|
||||
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;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// 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.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
pub struct Arguments {
|
||||
/// The span of the whole argument list.
|
||||
pub span: Span,
|
||||
/// The positional and named arguments.
|
||||
pub items: Vec<FuncArg>,
|
||||
pub items: Vec<Argument>,
|
||||
}
|
||||
|
||||
/// An argument to a function call: `12` or `draw: false`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArg {
|
||||
pub struct Argument {
|
||||
/// The span of the whole argument.
|
||||
pub span: Span,
|
||||
/// The name of the argument (`None` for positional arguments).
|
||||
pub name: Option<EcoString>,
|
||||
pub name: Option<Str>,
|
||||
/// The value of the argument.
|
||||
pub value: Spanned<Value>,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
impl Arguments {
|
||||
/// Find and consume the first castable positional argument.
|
||||
pub fn eat<T>(&mut self) -> Option<T>
|
||||
where
|
||||
@ -150,16 +163,14 @@ impl FuncArgs {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Reinterpret these arguments as actually being an array index.
|
||||
pub fn into_index(self) -> TypResult<i64> {
|
||||
self.into_castable("index")
|
||||
}
|
||||
|
||||
/// 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")
|
||||
}
|
||||
|
||||
@ -170,11 +181,11 @@ impl FuncArgs {
|
||||
{
|
||||
let mut iter = self.items.into_iter();
|
||||
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 => {
|
||||
bail!(self.span, "missing {}", what);
|
||||
}
|
||||
Some(FuncArg { name: Some(_), span, .. }) => {
|
||||
Some(Argument { name: Some(_), span, .. }) => {
|
||||
bail!(span, "named pair is not allowed here");
|
||||
}
|
||||
};
|
||||
@ -186,3 +197,24 @@ impl FuncArgs {
|
||||
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 ops;
|
||||
mod scope;
|
||||
mod str;
|
||||
mod template;
|
||||
|
||||
pub use self::str::*;
|
||||
pub use array::*;
|
||||
pub use capture::*;
|
||||
pub use dict::*;
|
||||
@ -35,7 +37,7 @@ use crate::parse::parse;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::syntax::visit::Visit;
|
||||
use crate::syntax::*;
|
||||
use crate::util::{EcoString, RefMutExt};
|
||||
use crate::util::RefMutExt;
|
||||
use crate::Context;
|
||||
|
||||
/// 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::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
|
||||
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> {
|
||||
self.items
|
||||
.iter()
|
||||
.map(|Named { name, expr }| Ok((name.string.clone(), expr.eval(ctx)?)))
|
||||
.map(|Named { name, expr }| Ok(((&name.string).into(), expr.eval(ctx)?)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@ -373,7 +375,7 @@ impl Eval for CallExpr {
|
||||
}
|
||||
|
||||
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) => {
|
||||
@ -393,7 +395,7 @@ impl Eval for CallExpr {
|
||||
}
|
||||
|
||||
impl Eval for CallArgs {
|
||||
type Output = FuncArgs;
|
||||
type Output = Arguments;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||
let mut items = Vec::with_capacity(self.items.len());
|
||||
@ -402,43 +404,49 @@ impl Eval for CallArgs {
|
||||
let span = arg.span();
|
||||
match arg {
|
||||
CallArg::Pos(expr) => {
|
||||
items.push(FuncArg {
|
||||
items.push(Argument {
|
||||
span,
|
||||
name: None,
|
||||
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
||||
});
|
||||
}
|
||||
CallArg::Named(Named { name, expr }) => {
|
||||
items.push(FuncArg {
|
||||
items.push(Argument {
|
||||
span,
|
||||
name: Some(name.string.clone()),
|
||||
name: Some((&name.string).into()),
|
||||
value: Spanned::new(expr.eval(ctx)?, expr.span()),
|
||||
});
|
||||
}
|
||||
CallArg::Spread(expr) => match expr.eval(ctx)? {
|
||||
Value::Args(args) => {
|
||||
items.extend(args.items.iter().cloned());
|
||||
}
|
||||
Value::Array(array) => {
|
||||
items.extend(array.into_iter().map(|value| FuncArg {
|
||||
items.extend(array.into_iter().map(|value| Argument {
|
||||
span,
|
||||
name: None,
|
||||
value: Spanned::new(value, span),
|
||||
}));
|
||||
}
|
||||
Value::Dict(dict) => {
|
||||
items.extend(dict.into_iter().map(|(key, value)| FuncArg {
|
||||
items.extend(dict.into_iter().map(|(key, value)| Argument {
|
||||
span,
|
||||
name: Some(key),
|
||||
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.
|
||||
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)?;
|
||||
@ -599,7 +607,7 @@ impl Eval for ForExpr {
|
||||
let iter = self.iter.eval(ctx)?;
|
||||
match (&self.pattern, iter) {
|
||||
(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)) => {
|
||||
iter!(for (v => value) in array.into_iter())
|
||||
@ -627,7 +635,7 @@ impl Eval for ImportExpr {
|
||||
type Output = Value;
|
||||
|
||||
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 module = &ctx.modules[&file];
|
||||
@ -657,7 +665,7 @@ impl Eval for IncludeExpr {
|
||||
type Output = Value;
|
||||
|
||||
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 module = &ctx.modules[&file];
|
||||
|
@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{EvalContext, FuncArgs, Function, Value};
|
||||
use super::{Arguments, EvalContext, Function, Value};
|
||||
use crate::diag::TypResult;
|
||||
use crate::util::EcoString;
|
||||
|
||||
@ -91,7 +91,7 @@ impl Scope {
|
||||
/// Define a constant function.
|
||||
pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
|
||||
where
|
||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + 'static,
|
||||
F: Fn(&mut EvalContext, &mut Arguments) -> TypResult<Value> + 'static,
|
||||
{
|
||||
let name = name.into();
|
||||
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::convert::TryFrom;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::{Add, AddAssign, Deref};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Value;
|
||||
use super::{Str, Value};
|
||||
use crate::diag::StrResult;
|
||||
use crate::exec::ExecContext;
|
||||
use crate::syntax::{Expr, SyntaxTree};
|
||||
@ -40,21 +40,9 @@ impl Template {
|
||||
}
|
||||
}
|
||||
|
||||
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<EcoString> for Template {
|
||||
fn from(string: EcoString) -> Self {
|
||||
Self::new(vec![TemplateNode::Str(string)])
|
||||
impl Display for Template {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("<template>")
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,24 +71,42 @@ impl AddAssign for Template {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<EcoString> for Template {
|
||||
impl Add<Str> for Template {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: EcoString) -> Self::Output {
|
||||
Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs));
|
||||
fn add(mut self, rhs: Str) -> Self::Output {
|
||||
Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs.into()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Template> for EcoString {
|
||||
impl Add<Template> for Str {
|
||||
type Output = Template;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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::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::diag::StrResult;
|
||||
use crate::exec::ExecContext;
|
||||
@ -37,7 +37,7 @@ pub enum Value {
|
||||
/// A color value: `#f79143ff`.
|
||||
Color(Color),
|
||||
/// A string: `"string"`.
|
||||
Str(EcoString),
|
||||
Str(Str),
|
||||
/// An array of values: `(1, "hi", 12cm)`.
|
||||
Array(Array),
|
||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||
@ -48,8 +48,6 @@ pub enum Value {
|
||||
Func(Function),
|
||||
/// A dynamic value.
|
||||
Dyn(Dynamic),
|
||||
/// Captured arguments to a function.
|
||||
Args(Rc<FuncArgs>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
@ -75,12 +73,11 @@ impl Value {
|
||||
Self::Linear(_) => Linear::TYPE_NAME,
|
||||
Self::Fractional(_) => Fractional::TYPE_NAME,
|
||||
Self::Color(_) => Color::TYPE_NAME,
|
||||
Self::Str(_) => EcoString::TYPE_NAME,
|
||||
Self::Str(_) => Str::TYPE_NAME,
|
||||
Self::Array(_) => Array::TYPE_NAME,
|
||||
Self::Dict(_) => Dict::TYPE_NAME,
|
||||
Self::Template(_) => Template::TYPE_NAME,
|
||||
Self::Func(_) => Function::TYPE_NAME,
|
||||
Self::Args(_) => Rc::<FuncArgs>::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 {
|
||||
fn from(v: i32) -> Self {
|
||||
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 {
|
||||
fn from(v: RgbaColor) -> Self {
|
||||
Self::Color(Color::Rgba(v))
|
||||
@ -137,25 +182,6 @@ impl From<Dynamic> for Value {
|
||||
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.
|
||||
#[derive(Clone)]
|
||||
pub struct Dynamic(Rc<dyn Bounds>);
|
||||
@ -193,7 +219,7 @@ impl Display for Dynamic {
|
||||
|
||||
impl Debug for Dynamic {
|
||||
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! {
|
||||
$type: Self::TYPE_NAME,
|
||||
$type: <Self as $crate::eval::Type>::TYPE_NAME,
|
||||
$($tts)*
|
||||
@this: Self => this.clone(),
|
||||
}
|
||||
@ -379,16 +405,62 @@ macro_rules! castable {
|
||||
|
||||
primitive! { bool: "boolean", Bool }
|
||||
primitive! { i64: "integer", Int }
|
||||
primitive! { f64: "float", Float, Int(v) => v as f64 }
|
||||
primitive! { Length: "length", Length }
|
||||
primitive! { Angle: "angle", Angle }
|
||||
primitive! { Relative: "relative", Relative }
|
||||
primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
|
||||
primitive! { Fractional: "fractional", Fractional }
|
||||
primitive! { Color: "color", Color }
|
||||
primitive! { EcoString: "string", Str }
|
||||
primitive! { Str: "string", Str }
|
||||
primitive! { Array: "array", Array }
|
||||
primitive! { Dict: "dictionary", Dict }
|
||||
primitive! { Template: "template", Template }
|
||||
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::geom::Gen;
|
||||
use crate::layout::{LayoutTree, StackChild, StackNode};
|
||||
use crate::pretty::pretty;
|
||||
use crate::syntax::*;
|
||||
use crate::util::EcoString;
|
||||
use crate::Context;
|
||||
@ -133,13 +132,13 @@ impl Exec for Value {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
match self {
|
||||
Value::None => {}
|
||||
Value::Int(v) => ctx.push_text(pretty(v)),
|
||||
Value::Float(v) => ctx.push_text(pretty(v)),
|
||||
Value::Int(v) => ctx.push_text(v.to_string()),
|
||||
Value::Float(v) => ctx.push_text(v.to_string()),
|
||||
Value::Str(v) => ctx.push_text(v),
|
||||
Value::Template(v) => v.exec(ctx),
|
||||
// For values which can't be shown "naturally", we print the
|
||||
// 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 len = frames.len();
|
||||
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.push_row(ctx, frame);
|
||||
|
@ -111,7 +111,7 @@ impl<'a> StackLayouter<'a> {
|
||||
let nodes = node.layout(ctx, &self.regions);
|
||||
let len = nodes.len();
|
||||
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.push_frame(frame.item, aligns);
|
||||
|
@ -43,7 +43,6 @@ pub mod library;
|
||||
pub mod loading;
|
||||
pub mod paper;
|
||||
pub mod parse;
|
||||
pub mod pretty;
|
||||
pub mod source;
|
||||
pub mod syntax;
|
||||
pub mod util;
|
||||
|
@ -10,8 +10,8 @@ use crate::layout::{
|
||||
};
|
||||
|
||||
/// `image`: An image.
|
||||
pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||
pub fn image(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||
let path = args.expect::<Spanned<Str>>("path to image file")?;
|
||||
let width = args.named("width")?;
|
||||
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.
|
||||
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 height = args.named("height")?;
|
||||
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.
|
||||
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 width = match length {
|
||||
Some(length) => Some(length),
|
||||
@ -80,7 +80,7 @@ fn rect_impl(
|
||||
}
|
||||
|
||||
/// `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 height = args.named("height")?;
|
||||
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.
|
||||
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 width = match diameter {
|
||||
None => args.named("width")?,
|
||||
|
@ -3,8 +3,8 @@ use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSi
|
||||
use crate::paper::{Paper, PaperClass};
|
||||
|
||||
/// `page`: Configure pages.
|
||||
pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
let paper = match args.eat::<Spanned<EcoString>>() {
|
||||
pub fn page(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||
let paper = match args.eat::<Spanned<Str>>() {
|
||||
Some(name) => match Paper::from_name(&name.v) {
|
||||
None => bail!(name.span, "invalid paper name"),
|
||||
paper => paper,
|
||||
@ -74,21 +74,21 @@ pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
}
|
||||
|
||||
/// `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)))
|
||||
}
|
||||
|
||||
/// `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)
|
||||
}
|
||||
|
||||
/// `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)
|
||||
}
|
||||
|
||||
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")?;
|
||||
Ok(Value::template(move |ctx| {
|
||||
// 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.
|
||||
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 vertical = args.named("vertical")?;
|
||||
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.
|
||||
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 height = args.named("height")?;
|
||||
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.
|
||||
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")?;
|
||||
Ok(Value::template(move |ctx| {
|
||||
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.
|
||||
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 left = args.named("left")?;
|
||||
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.
|
||||
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 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.
|
||||
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 rows = args.named("rows")?.unwrap_or_default();
|
||||
|
||||
|
@ -18,12 +18,11 @@ use std::rc::Rc;
|
||||
|
||||
use crate::color::{Color, RgbaColor};
|
||||
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::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
||||
use crate::geom::*;
|
||||
use crate::syntax::Spanned;
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// Construct a scope containing all standard library definitions.
|
||||
pub fn new() -> Scope {
|
||||
|
@ -4,7 +4,7 @@ use crate::layout::Paint;
|
||||
use super::*;
|
||||
|
||||
/// `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 style = args.named("style")?;
|
||||
let weight = args.named("weight")?;
|
||||
@ -98,13 +98,13 @@ castable! {
|
||||
values
|
||||
.into_iter()
|
||||
.filter_map(|v| v.cast().ok())
|
||||
.map(|string: EcoString| string.to_lowercase())
|
||||
.map(|string: Str| string.to_lowercase())
|
||||
.collect()
|
||||
)),
|
||||
}
|
||||
|
||||
/// `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 line_spacing = args.named("leading")?;
|
||||
let body = args.expect::<Template>("body")?;
|
||||
@ -126,8 +126,8 @@ pub fn par(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
}
|
||||
|
||||
/// `lang`: Configure the language.
|
||||
pub fn lang(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
let iso = args.eat::<EcoString>();
|
||||
pub fn lang(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||
let iso = args.eat::<Str>();
|
||||
let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? {
|
||||
if dir.v.axis() == SpecAxis::Horizontal {
|
||||
Some(dir.v)
|
||||
@ -160,22 +160,22 @@ fn lang_dir(iso: &str) -> Dir {
|
||||
}
|
||||
|
||||
/// `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)
|
||||
}
|
||||
|
||||
/// `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)
|
||||
}
|
||||
|
||||
/// `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)
|
||||
}
|
||||
|
||||
fn line_impl(
|
||||
args: &mut FuncArgs,
|
||||
args: &mut Arguments,
|
||||
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
|
||||
) -> TypResult<Value> {
|
||||
let stroke = args.named("stroke")?.or_else(|| args.eat());
|
||||
|
@ -2,37 +2,34 @@ use std::cmp::Ordering;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::pretty::pretty;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// `type`: The name of a value's type.
|
||||
pub fn type_(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
let value = args.expect::<Value>("value")?;
|
||||
Ok(value.type_name().into())
|
||||
pub fn type_(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||
Ok(args.expect::<Value>("value")?.type_name().into())
|
||||
}
|
||||
|
||||
/// `repr`: The string representation of a value.
|
||||
pub fn repr(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
let value = args.expect::<Value>("value")?;
|
||||
Ok(pretty(&value).into())
|
||||
pub fn repr(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||
Ok(args.expect::<Value>("value")?.to_string().into())
|
||||
}
|
||||
|
||||
/// `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")?;
|
||||
Ok(match v {
|
||||
Value::Str(v) => Value::Int(v.len() as i64),
|
||||
Value::Array(v) => Value::Int(v.len()),
|
||||
Value::Dict(v) => Value::Int(v.len()),
|
||||
Ok(Value::Int(match v {
|
||||
Value::Str(v) => v.len(),
|
||||
Value::Array(v) => v.len(),
|
||||
Value::Dict(v) => v.len(),
|
||||
_ => bail!(span, "expected string, array or dictionary"),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// `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(
|
||||
if let Some(string) = args.eat::<Spanned<EcoString>>() {
|
||||
if let Some(string) = args.eat::<Spanned<Str>>() {
|
||||
match RgbaColor::from_str(&string.v) {
|
||||
Ok(color) => 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.
|
||||
pub fn min(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
||||
pub fn min(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||
minmax(args, Ordering::Less)
|
||||
}
|
||||
|
||||
/// `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)
|
||||
}
|
||||
|
||||
/// Find the minimum or maximum of a sequence of values.
|
||||
fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult<Value> {
|
||||
let span = args.span;
|
||||
|
||||
fn minmax(args: &mut Arguments, goal: Ordering) -> TypResult<Value> {
|
||||
let mut extremum = args.expect::<Value>("value")?;
|
||||
for value in args.all::<Value>() {
|
||||
match value.partial_cmp(&extremum) {
|
||||
for Spanned { v, span } in args.all::<Spanned<Value>>() {
|
||||
match v.partial_cmp(&extremum) {
|
||||
Some(ordering) => {
|
||||
if ordering == goal {
|
||||
extremum = value;
|
||||
extremum = v;
|
||||
}
|
||||
}
|
||||
None => bail!(
|
||||
span,
|
||||
"cannot compare {} with {}",
|
||||
extremum.type_name(),
|
||||
value.type_name(),
|
||||
v.type_name(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(extremum)
|
||||
}
|
||||
|
@ -29,12 +29,12 @@ pub fn resolve_string(string: &str) -> EcoString {
|
||||
out.push(c);
|
||||
} else {
|
||||
// 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.
|
||||
_ => out += s.eaten_from(start),
|
||||
_ => out.push_str(s.eaten_from(start)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ mod tests {
|
||||
fn test_resolve_strings() {
|
||||
#[track_caller]
|
||||
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");
|
||||
|
@ -606,20 +606,20 @@ mod tests {
|
||||
}};
|
||||
(@$mode:ident: $src:expr => $($token:expr),*) => {{
|
||||
let src = $src;
|
||||
let exp = vec![$($token),*];
|
||||
let found = Tokens::new(&src, $mode).collect::<Vec<_>>();
|
||||
check(&src, exp, found);
|
||||
let expected = vec![$($token),*];
|
||||
check(&src, found, expected);
|
||||
}};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check<T>(src: &str, exp: T, found: T)
|
||||
fn check<T>(src: &str, found: T, expected: T)
|
||||
where
|
||||
T: Debug + PartialEq,
|
||||
{
|
||||
if exp != found {
|
||||
if found != expected {
|
||||
println!("source: {:?}", src);
|
||||
println!("expected: {:#?}", exp);
|
||||
println!("expected: {:#?}", expected);
|
||||
println!("found: {:#?}", found);
|
||||
panic!("test failed");
|
||||
}
|
||||
|
@ -23,14 +23,11 @@ pub struct Ident {
|
||||
impl Ident {
|
||||
/// Create a new identifier from a string checking that it is a valid.
|
||||
pub fn new(
|
||||
string: impl AsRef<str> + Into<String>,
|
||||
string: impl AsRef<str> + Into<EcoString>,
|
||||
span: impl Into<Span>,
|
||||
) -> Option<Self> {
|
||||
if is_ident(string.as_ref()) {
|
||||
Some(Self {
|
||||
span: span.into(),
|
||||
string: EcoString::from_str(string),
|
||||
})
|
||||
Some(Self { span: span.into(), string: string.into() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
mod expr;
|
||||
mod ident;
|
||||
mod node;
|
||||
mod pretty;
|
||||
mod span;
|
||||
mod token;
|
||||
pub mod visit;
|
||||
@ -10,6 +11,7 @@ pub mod visit;
|
||||
pub use expr::*;
|
||||
pub use ident::*;
|
||||
pub use node::*;
|
||||
pub use pretty::*;
|
||||
pub use span::*;
|
||||
pub use token::*;
|
||||
|
||||
|
@ -2,10 +2,7 @@
|
||||
|
||||
use std::fmt::{self, Arguments, Write};
|
||||
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::eval::*;
|
||||
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
|
||||
use crate::syntax::*;
|
||||
use super::*;
|
||||
|
||||
/// Pretty print an item and return the resulting string.
|
||||
pub fn pretty<T>(item: &T) -> String
|
||||
@ -23,7 +20,7 @@ pub trait Pretty {
|
||||
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 {
|
||||
buf: String,
|
||||
}
|
||||
@ -223,14 +220,14 @@ impl Pretty for Lit {
|
||||
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::Bool(_, v) => write!(p, "{}", v).unwrap(),
|
||||
Self::Int(_, v) => write!(p, "{}", v).unwrap(),
|
||||
Self::Float(_, v) => write!(p, "{}", v).unwrap(),
|
||||
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
||||
Self::Percent(_, v) => write!(p, "{}%", 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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -633,23 +502,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_parse(src: &str, exp: &str) {
|
||||
fn test_parse(src: &str, expected: &str) {
|
||||
let source = SourceFile::detached(src);
|
||||
let ast = parse(&source).unwrap();
|
||||
let found = pretty(&ast);
|
||||
if exp != found {
|
||||
if found != expected {
|
||||
println!("tree: {:#?}", ast);
|
||||
println!("expected: {}", exp);
|
||||
println!("expected: {}", expected);
|
||||
println!("found: {}", found);
|
||||
panic!("test failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_value(value: impl Into<Value>, exp: &str) {
|
||||
assert_eq!(pretty(&value.into()), exp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pretty_print_node() {
|
||||
// Basic text and markup.
|
||||
@ -746,41 +610,4 @@ mod tests {
|
||||
roundtrip("#import * from \"file.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 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
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.
|
||||
pub trait IntoSpan {
|
||||
/// 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::cmp::Ordering;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, AddAssign, Deref};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::diag::StrResult;
|
||||
|
||||
/// An economical string with inline storage and clone-on-write value semantics.
|
||||
/// An economical string with inline storage and clone-on-write semantics.
|
||||
#[derive(Clone)]
|
||||
pub struct EcoString(Repr);
|
||||
|
||||
@ -144,75 +141,25 @@ impl EcoString {
|
||||
}
|
||||
|
||||
/// Repeat this string `n` times.
|
||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||
let (n, new) = usize::try_from(n)
|
||||
.ok()
|
||||
.and_then(|n| self.len().checked_mul(n).map(|new| (n, new)))
|
||||
.ok_or_else(|| format!("cannot repeat this string {} times", n))?;
|
||||
pub fn repeat(&self, n: usize) -> Self {
|
||||
if n == 0 {
|
||||
return Self::new();
|
||||
}
|
||||
|
||||
if let Repr::Small { buf, len } = &self.0 {
|
||||
let prev = usize::from(*len);
|
||||
let new = prev.saturating_mul(n);
|
||||
if new <= LIMIT {
|
||||
let src = &buf[.. prev];
|
||||
let mut buf = [0; LIMIT];
|
||||
for i in 0 .. n {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
self.as_str().repeat(n).into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
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 {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -436,17 +392,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_str_repeat() {
|
||||
// Test with empty string.
|
||||
assert_eq!(EcoString::new().repeat(0).unwrap(), "");
|
||||
assert_eq!(EcoString::new().repeat(100).unwrap(), "");
|
||||
assert_eq!(EcoString::new().repeat(0), "");
|
||||
assert_eq!(EcoString::new().repeat(100), "");
|
||||
|
||||
// Test non-spilling and spilling case.
|
||||
let v = EcoString::from("abc");
|
||||
assert_eq!(v.repeat(0).unwrap(), "");
|
||||
assert_eq!(v.repeat(3).unwrap(), "abcabcabc");
|
||||
assert_eq!(v.repeat(5).unwrap(), "abcabcabcabcabc");
|
||||
assert_eq!(
|
||||
v.repeat(-1).unwrap_err(),
|
||||
"cannot repeat this string -1 times",
|
||||
);
|
||||
assert_eq!(v.repeat(0), "");
|
||||
assert_eq!(v.repeat(3), "abcabcabc");
|
||||
assert_eq!(v.repeat(5), "abcabcabcabcabc");
|
||||
}
|
||||
}
|
||||
|
@ -13,5 +13,5 @@
|
||||
#min()
|
||||
|
||||
---
|
||||
// Error: 10-19 cannot compare integer with string
|
||||
// Error: 14-18 cannot compare integer with string
|
||||
#test(min(1, "hi"), error)
|
||||
|
Loading…
x
Reference in New Issue
Block a user