summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-08-14 15:24:59 +0200
committerLaurenz <laurmaedje@gmail.com>2021-08-14 15:55:39 +0200
commit6ae6d86b9c6fefe6c5379ac1b20ea90634c09c81 (patch)
tree2504c3b46807be148b9efbadf9b23e57bb77b8f3
parentfcb4e451186067cdf6efe3c14cbfa7561b366a6c (diff)
Separate type for string values
-rw-r--r--src/eval/array.rs37
-rw-r--r--src/eval/dict.rs70
-rw-r--r--src/eval/function.rs72
-rw-r--r--src/eval/mod.rs46
-rw-r--r--src/eval/scope.rs4
-rw-r--r--src/eval/str.rs137
-rw-r--r--src/eval/template.rs50
-rw-r--r--src/eval/value.rs132
-rw-r--r--src/exec/mod.rs7
-rw-r--r--src/layout/grid.rs2
-rw-r--r--src/layout/stack.rs2
-rw-r--r--src/lib.rs1
-rw-r--r--src/library/elements.rs12
-rw-r--r--src/library/layout.rs24
-rw-r--r--src/library/mod.rs3
-rw-r--r--src/library/text.rs18
-rw-r--r--src/library/utility.rs44
-rw-r--r--src/parse/resolve.rs6
-rw-r--r--src/parse/tokens.rs10
-rw-r--r--src/syntax/ident.rs7
-rw-r--r--src/syntax/mod.rs2
-rw-r--r--src/syntax/pretty.rs (renamed from src/pretty.rs)191
-rw-r--r--src/syntax/span.rs24
-rw-r--r--src/util/eco.rs180
-rw-r--r--tests/typ/utility/math.typ2
25 files changed, 560 insertions, 523 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs
index 356aa0ca..ec8f46d3 100644
--- a/src/eval/array.rs
+++ b/src/eval/array.rs
@@ -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,15 +99,25 @@ 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 Debug for Array {
+impl Display for Array {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.debug_list().entries(self.vec.iter()).finish()
+ 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(')')
}
}
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index 730392dc..e71d0c6a 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -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()
diff --git a/src/eval/function.rs b/src/eval/function.rs
index 550db59d..f18af05e 100644
--- a/src/eval/function.rs
+++ b/src/eval/function.rs
@@ -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",
+}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 9e6fad8f..30b34798 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -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];
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index 2eb048fa..2968ca20 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -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));
diff --git a/src/eval/str.rs b/src/eval/str.rs
new file mode 100644
index 00000000..7f84f80f
--- /dev/null
+++ b/src/eval/str.rs
@@ -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()
+ }
+}
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 4e20b8f8..594036af 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -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
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 958077da..62899cc1 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -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");
+ }
+}
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 373a3263..ac4c1fca 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -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()),
}
}
}
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
index bccdf381..0a189513 100644
--- a/src/layout/grid.rs
+++ b/src/layout/grid.rs
@@ -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);
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index ed053dd7..7f0f2b9d 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -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);
diff --git a/src/lib.rs b/src/lib.rs
index 7447dad7..ce753f86 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
diff --git a/src/library/elements.rs b/src/library/elements.rs
index f0e36994..1ad56a81 100644
--- a/src/library/elements.rs
+++ b/src/library/elements.rs
@@ -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")?,
diff --git a/src/library/layout.rs b/src/library/layout.rs
index 53e3a450..20673c4a 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -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();
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 3591e742..f1134282 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -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 {
diff --git a/src/library/text.rs b/src/library/text.rs
index 55cabd15..9ffb182a 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -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());
diff --git a/src/library/utility.rs b/src/library/utility.rs
index 20d10830..84f1d7ab 100644
--- a/src/library/utility.rs
+++ b/src/library/utility.rs
@@ -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)
}
diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs
index 7ceac128..80e3bbc0 100644
--- a/src/parse/resolve.rs
+++ b/src/parse/resolve.rs
@@ -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");
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index acd939e8..684e340e 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -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");
}
diff --git a/src/syntax/ident.rs b/src/syntax/ident.rs
index 462361a5..2a7df8e1 100644
--- a/src/syntax/ident.rs
+++ b/src/syntax/ident.rs
@@ -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
}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index 9a4ca708..f07e3554 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -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::*;
diff --git a/src/pretty.rs b/src/syntax/pretty.rs
index 573dc6e9..40ebf758 100644
--- a/src/pretty.rs
+++ b/src/syntax/pretty.rs
@@ -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");
- }
}
diff --git a/src/syntax/span.rs b/src/syntax/span.rs
index cad9c5d5..e4a4fd32 100644
--- a/src/syntax/span.rs
+++ b/src/syntax/span.rs
@@ -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.
diff --git a/src/util/eco.rs b/src/util/eco.rs
index 08c2117d..207f1753 100644
--- a/src/util/eco.rs
+++ b/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,50 +233,77 @@ impl PartialOrd for EcoString {
}
}
-impl Add for EcoString {
- type Output = Self;
-
- fn add(self, rhs: Self) -> Self::Output {
- self + rhs.as_str()
+impl Hash for EcoString {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.as_str().hash(state);
}
}
-impl AddAssign for EcoString {
- fn add_assign(&mut self, rhs: EcoString) {
- self.push_str(&rhs);
+impl Write for EcoString {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ self.push_str(s);
+ Ok(())
+ }
+
+ fn write_char(&mut self, c: char) -> fmt::Result {
+ self.push(c);
+ Ok(())
}
}
-impl Add<&str> for EcoString {
- type Output = Self;
+impl AsRef<str> for EcoString {
+ fn as_ref(&self) -> &str {
+ self
+ }
+}
- fn add(mut self, rhs: &str) -> Self::Output {
- self.push_str(rhs);
+impl Borrow<str> for EcoString {
+ fn borrow(&self) -> &str {
self
}
}
-impl AddAssign<&str> for EcoString {
- fn add_assign(&mut self, rhs: &str) {
- self.push_str(rhs);
+impl From<&Self> for EcoString {
+ fn from(s: &Self) -> Self {
+ s.clone()
}
}
-impl Hash for EcoString {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.as_str().hash(state);
+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 Write for EcoString {
- fn write_str(&mut self, s: &str) -> fmt::Result {
- self.push_str(s);
- Ok(())
+impl From<&str> for EcoString {
+ fn from(s: &str) -> Self {
+ Self::from_str(s)
}
+}
- fn write_char(&mut self, c: char) -> fmt::Result {
- self.push(c);
- Ok(())
+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()
}
}
@@ -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");
}
}
diff --git a/tests/typ/utility/math.typ b/tests/typ/utility/math.typ
index 3718866b..05c3639f 100644
--- a/tests/typ/utility/math.typ
+++ b/tests/typ/utility/math.typ
@@ -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)