mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Array and dictionary indexing
This commit is contained in:
parent
8207c31aec
commit
eaa3cbaa9c
11
src/diag.rs
11
src/diag.rs
@ -58,21 +58,20 @@ impl Error {
|
|||||||
Box::new(vec![Self::new(source, span, message)])
|
Box::new(vec![Self::new(source, span, message)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Partially build a vec-boxed error, returning a function that just needs
|
/// Create a closure that contains the positional information for an error
|
||||||
/// the message.
|
/// and just needs the message to yield a vec-boxed error.
|
||||||
///
|
///
|
||||||
/// This is useful in to convert from [`StrResult`] to a [`TypResult`] using
|
/// This is useful in to convert from [`StrResult`] to a [`TypResult`] using
|
||||||
/// [`map_err`](Result::map_err).
|
/// [`map_err`](Result::map_err).
|
||||||
pub fn partial(
|
pub fn at<S: Into<String>>(
|
||||||
source: SourceId,
|
source: SourceId,
|
||||||
span: impl Into<Span>,
|
span: impl Into<Span>,
|
||||||
) -> impl FnOnce(String) -> Box<Vec<Self>> {
|
) -> impl FnOnce(S) -> Box<Vec<Self>> {
|
||||||
move |message| Self::boxed(source, span, message)
|
move |message| Self::boxed(source, span, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Early-return with a vec-boxed [`Error`].
|
/// Early-return with a vec-boxed [`Error`].
|
||||||
#[macro_export]
|
|
||||||
macro_rules! bail {
|
macro_rules! bail {
|
||||||
($source:expr, $span:expr, $message:expr $(,)?) => {
|
($source:expr, $span:expr, $message:expr $(,)?) => {
|
||||||
return Err(Box::new(vec![$crate::diag::Error::new(
|
return Err(Box::new(vec![$crate::diag::Error::new(
|
||||||
@ -81,6 +80,6 @@ macro_rules! bail {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($source:expr, $span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {
|
($source:expr, $span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {
|
||||||
$crate::bail!($source, $span, format!($fmt, $($arg),+));
|
bail!($source, $span, format!($fmt, $($arg),+));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
use crate::diag::StrResult;
|
||||||
|
|
||||||
/// Create a new [`Array`] from values.
|
/// Create a new [`Array`] from values.
|
||||||
#[macro_export]
|
#[allow(unused_macros)]
|
||||||
macro_rules! array {
|
macro_rules! array {
|
||||||
($value:expr; $count:expr) => {
|
($value:expr; $count:expr) => {
|
||||||
$crate::eval::Array::from_vec(vec![$crate::eval::Value::from($value); $count])
|
$crate::eval::Array::from_vec(vec![$crate::eval::Value::from($value); $count])
|
||||||
@ -47,25 +49,25 @@ impl Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The length of the array.
|
/// The length of the array.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> i64 {
|
||||||
self.vec.len()
|
self.vec.len() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the value at the given index.
|
/// Borrow the value at the given index.
|
||||||
pub fn get(&self, index: usize) -> Option<&Value> {
|
pub fn get(&self, index: i64) -> StrResult<&Value> {
|
||||||
self.vec.get(index)
|
usize::try_from(index)
|
||||||
|
.ok()
|
||||||
|
.and_then(|i| self.vec.get(i))
|
||||||
|
.ok_or_else(|| out_of_bounds(index, self.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutably borrow the value at the given index.
|
/// Mutably borrow the value at the given index.
|
||||||
pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
|
pub fn get_mut(&mut self, index: i64) -> StrResult<&mut Value> {
|
||||||
Rc::make_mut(&mut self.vec).get_mut(index)
|
let len = self.len();
|
||||||
}
|
usize::try_from(index)
|
||||||
|
.ok()
|
||||||
/// Set the value at the given index.
|
.and_then(move |i| Rc::make_mut(&mut self.vec).get_mut(i))
|
||||||
///
|
.ok_or_else(|| out_of_bounds(index, len))
|
||||||
/// This panics the `index` is out of range.
|
|
||||||
pub fn set(&mut self, index: usize, value: Value) {
|
|
||||||
Rc::make_mut(&mut self.vec)[index] = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a value to the end of the array.
|
/// Push a value to the end of the array.
|
||||||
@ -82,16 +84,26 @@ impl Array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Repeat this array `n` times.
|
|
||||||
pub fn repeat(&self, n: usize) -> Self {
|
|
||||||
let len = self.len().checked_mul(n).expect("capacity overflow");
|
|
||||||
self.iter().cloned().cycle().take(len).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over references to the contained values.
|
/// Iterate over references to the contained values.
|
||||||
pub fn iter(&self) -> std::slice::Iter<Value> {
|
pub fn iter(&self) -> std::slice::Iter<Value> {
|
||||||
self.vec.iter()
|
self.vec.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Repeat this array `n` times.
|
||||||
|
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||||
|
let count = usize::try_from(n)
|
||||||
|
.ok()
|
||||||
|
.and_then(|n| self.vec.len().checked_mul(n))
|
||||||
|
.ok_or_else(|| format!("cannot repeat this array {} times", n))?;
|
||||||
|
|
||||||
|
Ok(self.iter().cloned().cycle().take(count).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The out of bounds access error message.
|
||||||
|
#[cold]
|
||||||
|
fn out_of_bounds(index: i64, len: i64) -> String {
|
||||||
|
format!("array index out of bounds (index: {}, len: {})", index, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Array {
|
impl Default for Array {
|
||||||
|
@ -5,10 +5,12 @@ use std::ops::{Add, AddAssign};
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
use crate::diag::StrResult;
|
||||||
|
use crate::pretty::pretty;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Create a new [`Dict`] from key-value pairs.
|
/// Create a new [`Dict`] from key-value pairs.
|
||||||
#[macro_export]
|
#[allow(unused_macros)]
|
||||||
macro_rules! dict {
|
macro_rules! dict {
|
||||||
($($key:expr => $value:expr),* $(,)?) => {{
|
($($key:expr => $value:expr),* $(,)?) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
@ -41,18 +43,21 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The number of pairs in the dictionary.
|
/// The number of pairs in the dictionary.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> i64 {
|
||||||
self.map.len()
|
self.map.len() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the value the given `key` maps to.
|
/// Borrow the value the given `key` maps to.
|
||||||
pub fn get(&self, key: &str) -> Option<&Value> {
|
pub fn get(&self, key: &str) -> StrResult<&Value> {
|
||||||
self.map.get(key)
|
self.map.get(key).ok_or_else(|| missing_key(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutably borrow the value the given `key` maps to.
|
/// Mutably borrow the value the given `key` maps to.
|
||||||
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
|
///
|
||||||
Rc::make_mut(&mut self.map).get_mut(key)
|
/// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a mapping from the given `key` to the given `value`.
|
/// Insert a mapping from the given `key` to the given `value`.
|
||||||
@ -75,6 +80,12 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The missing key access error message.
|
||||||
|
#[cold]
|
||||||
|
fn missing_key(key: &str) -> String {
|
||||||
|
format!("dictionary does not contain key: {}", pretty(key))
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Dict {
|
impl Default for Dict {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
@ -134,7 +134,7 @@ impl FuncArgs {
|
|||||||
let value = self.items.remove(index).value;
|
let value = self.items.remove(index).value;
|
||||||
let span = value.span;
|
let span = value.span;
|
||||||
|
|
||||||
T::cast(value).map(Some).map_err(Error::partial(self.source, span))
|
T::cast(value).map(Some).map_err(Error::at(self.source, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an "unexpected argument" error if there is any remaining
|
/// Return an "unexpected argument" error if there is any remaining
|
||||||
@ -146,3 +146,40 @@ impl FuncArgs {
|
|||||||
Ok(())
|
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> {
|
||||||
|
self.into_castable("key")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reinterpret these arguments as actually being a single castable thing.
|
||||||
|
fn into_castable<T>(self, what: &str) -> TypResult<T>
|
||||||
|
where
|
||||||
|
T: Cast<Value>,
|
||||||
|
{
|
||||||
|
let mut iter = self.items.into_iter();
|
||||||
|
let value = match iter.next() {
|
||||||
|
None => {
|
||||||
|
bail!(self.source, self.span, "missing {}", what);
|
||||||
|
}
|
||||||
|
Some(FuncArg { name: Some(_), span, .. }) => {
|
||||||
|
bail!(self.source, span, "named pair is not allowed here");
|
||||||
|
}
|
||||||
|
Some(FuncArg { name: None, value, .. }) => {
|
||||||
|
value.v.cast().map_err(Error::at(self.source, value.span))?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(arg) = iter.next() {
|
||||||
|
bail!(self.source, arg.span, "only one {} is allowed", what);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
253
src/eval/mod.rs
253
src/eval/mod.rs
@ -20,6 +20,7 @@ pub use scope::*;
|
|||||||
pub use template::*;
|
pub use template::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|
||||||
|
use std::cell::RefMut;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@ -34,7 +35,7 @@ use crate::parse::parse;
|
|||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
use crate::syntax::visit::Visit;
|
use crate::syntax::visit::Visit;
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
use crate::util::EcoString;
|
use crate::util::{EcoString, RefMutExt};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Evaluate a parsed source file into a module.
|
/// Evaluate a parsed source file into a module.
|
||||||
@ -60,15 +61,6 @@ pub struct Module {
|
|||||||
pub template: Template,
|
pub template: Template,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an expression.
|
|
||||||
pub trait Eval {
|
|
||||||
/// The output of evaluating the expression.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// Evaluate the expression to the output value.
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The context for evaluation.
|
/// The context for evaluation.
|
||||||
pub struct EvalContext<'a> {
|
pub struct EvalContext<'a> {
|
||||||
/// The loader from which resources (files and images) are loaded.
|
/// The loader from which resources (files and images) are loaded.
|
||||||
@ -168,45 +160,19 @@ impl<'a> EvalContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate an expression.
|
||||||
|
pub trait Eval {
|
||||||
|
/// The output of evaluating the expression.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// Evaluate the expression to the output value.
|
||||||
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output>;
|
||||||
|
}
|
||||||
|
|
||||||
impl Eval for Rc<SyntaxTree> {
|
impl Eval for Rc<SyntaxTree> {
|
||||||
type Output = Template;
|
type Output = Template;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
trait Walk {
|
|
||||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Walk for SyntaxTree {
|
|
||||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
|
||||||
for node in self.iter() {
|
|
||||||
node.walk(ctx)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Walk for SyntaxNode {
|
|
||||||
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
|
||||||
match self {
|
|
||||||
Self::Text(_) => {}
|
|
||||||
Self::Space => {}
|
|
||||||
Self::Linebreak(_) => {}
|
|
||||||
Self::Parbreak(_) => {}
|
|
||||||
Self::Strong(_) => {}
|
|
||||||
Self::Emph(_) => {}
|
|
||||||
Self::Raw(_) => {}
|
|
||||||
Self::Heading(n) => n.body.walk(ctx)?,
|
|
||||||
Self::List(n) => n.body.walk(ctx)?,
|
|
||||||
Self::Enum(n) => n.body.walk(ctx)?,
|
|
||||||
Self::Expr(n) => {
|
|
||||||
let value = n.eval(ctx)?;
|
|
||||||
ctx.map.insert(n as *const _, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let map = {
|
let map = {
|
||||||
let prev = mem::take(&mut ctx.map);
|
let prev = mem::take(&mut ctx.map);
|
||||||
self.walk(ctx)?;
|
self.walk(ctx)?;
|
||||||
@ -302,8 +268,8 @@ impl Eval for BlockExpr {
|
|||||||
let mut output = Value::None;
|
let mut output = Value::None;
|
||||||
for expr in &self.exprs {
|
for expr in &self.exprs {
|
||||||
let value = expr.eval(ctx)?;
|
let value = expr.eval(ctx)?;
|
||||||
output = ops::join(output, value)
|
output =
|
||||||
.map_err(Error::partial(ctx.source, expr.span()))?;
|
ops::join(output, value).map_err(Error::at(ctx.source, expr.span()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.scoping {
|
if self.scoping {
|
||||||
@ -324,7 +290,7 @@ impl Eval for UnaryExpr {
|
|||||||
UnOp::Neg => ops::neg(value),
|
UnOp::Neg => ops::neg(value),
|
||||||
UnOp::Not => ops::not(value),
|
UnOp::Not => ops::not(value),
|
||||||
};
|
};
|
||||||
result.map_err(Error::partial(ctx.source, self.span))
|
result.map_err(Error::at(ctx.source, self.span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +337,7 @@ impl BinaryExpr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rhs = self.rhs.eval(ctx)?;
|
let rhs = self.rhs.eval(ctx)?;
|
||||||
op(lhs, rhs).map_err(Error::partial(ctx.source, self.span))
|
op(lhs, rhs).map_err(Error::at(ctx.source, self.span))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply an assignment operation.
|
/// Apply an assignment operation.
|
||||||
@ -379,27 +345,11 @@ impl BinaryExpr {
|
|||||||
where
|
where
|
||||||
F: FnOnce(Value, Value) -> StrResult<Value>,
|
F: FnOnce(Value, Value) -> StrResult<Value>,
|
||||||
{
|
{
|
||||||
let lspan = self.lhs.span();
|
let source = ctx.source;
|
||||||
let slot = if let Expr::Ident(id) = self.lhs.as_ref() {
|
|
||||||
match ctx.scopes.get(id) {
|
|
||||||
Some(slot) => Rc::clone(slot),
|
|
||||||
None => bail!(ctx.source, lspan, "unknown variable"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!(ctx.source, lspan, "cannot assign to this expression",);
|
|
||||||
};
|
|
||||||
|
|
||||||
let rhs = self.rhs.eval(ctx)?;
|
let rhs = self.rhs.eval(ctx)?;
|
||||||
let mut mutable = match slot.try_borrow_mut() {
|
let mut target = self.lhs.access(ctx)?;
|
||||||
Ok(mutable) => mutable,
|
let lhs = mem::take(&mut *target);
|
||||||
Err(_) => {
|
*target = op(lhs, rhs).map_err(Error::at(source, self.span))?;
|
||||||
bail!(ctx.source, lspan, "cannot assign to a constant",);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let lhs = mem::take(&mut *mutable);
|
|
||||||
*mutable = op(lhs, rhs).map_err(Error::partial(ctx.source, self.span))?;
|
|
||||||
|
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,32 +358,45 @@ impl Eval for CallExpr {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
let callee = self
|
let callee = self.callee.eval(ctx)?;
|
||||||
.callee
|
|
||||||
.eval(ctx)?
|
|
||||||
.cast::<Function>()
|
|
||||||
.map_err(Error::partial(ctx.source, self.callee.span()))?;
|
|
||||||
|
|
||||||
let mut args = self.args.eval(ctx)?;
|
let mut args = self.args.eval(ctx)?;
|
||||||
let returned = callee(ctx, &mut args).map_err(|mut errors| {
|
|
||||||
for error in errors.iter_mut() {
|
|
||||||
// Skip errors directly related to arguments.
|
|
||||||
if error.source == ctx.source && self.span.contains(error.span) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
error.trace.push((
|
match callee {
|
||||||
ctx.source,
|
Value::Array(array) => array
|
||||||
self.span,
|
.get(args.into_index()?)
|
||||||
Tracepoint::Call(callee.name().map(Into::into)),
|
.map(Value::clone)
|
||||||
));
|
.map_err(Error::at(ctx.source, self.span)),
|
||||||
|
|
||||||
|
Value::Dict(dict) => dict
|
||||||
|
.get(&args.into_key()?)
|
||||||
|
.map(Value::clone)
|
||||||
|
.map_err(Error::at(ctx.source, self.span)),
|
||||||
|
|
||||||
|
Value::Func(func) => {
|
||||||
|
let returned = func(ctx, &mut args).map_err(|mut errors| {
|
||||||
|
for error in errors.iter_mut() {
|
||||||
|
// Skip errors directly related to arguments.
|
||||||
|
if error.source != ctx.source || !self.span.contains(error.span) {
|
||||||
|
error.trace.push((
|
||||||
|
ctx.source,
|
||||||
|
self.span,
|
||||||
|
Tracepoint::Call(func.name().map(Into::into)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors
|
||||||
|
})?;
|
||||||
|
args.finish()?;
|
||||||
|
Ok(returned)
|
||||||
}
|
}
|
||||||
errors
|
|
||||||
})?;
|
|
||||||
|
|
||||||
args.finish()?;
|
v => bail!(
|
||||||
|
ctx.source,
|
||||||
Ok(returned)
|
self.callee.span(),
|
||||||
|
"expected function or collection, found {}",
|
||||||
|
v.type_name(),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,7 +481,7 @@ impl Eval for WithExpr {
|
|||||||
.callee
|
.callee
|
||||||
.eval(ctx)?
|
.eval(ctx)?
|
||||||
.cast::<Function>()
|
.cast::<Function>()
|
||||||
.map_err(Error::partial(ctx.source, self.callee.span()))?;
|
.map_err(Error::at(ctx.source, self.callee.span()))?;
|
||||||
|
|
||||||
let applied = self.args.eval(ctx)?;
|
let applied = self.args.eval(ctx)?;
|
||||||
|
|
||||||
@ -568,7 +531,7 @@ impl Eval for IfExpr {
|
|||||||
.condition
|
.condition
|
||||||
.eval(ctx)?
|
.eval(ctx)?
|
||||||
.cast::<bool>()
|
.cast::<bool>()
|
||||||
.map_err(Error::partial(ctx.source, self.condition.span()))?;
|
.map_err(Error::at(ctx.source, self.condition.span()))?;
|
||||||
|
|
||||||
if condition {
|
if condition {
|
||||||
self.if_body.eval(ctx)
|
self.if_body.eval(ctx)
|
||||||
@ -590,11 +553,11 @@ impl Eval for WhileExpr {
|
|||||||
.condition
|
.condition
|
||||||
.eval(ctx)?
|
.eval(ctx)?
|
||||||
.cast::<bool>()
|
.cast::<bool>()
|
||||||
.map_err(Error::partial(ctx.source, self.condition.span()))?
|
.map_err(Error::at(ctx.source, self.condition.span()))?
|
||||||
{
|
{
|
||||||
let value = self.body.eval(ctx)?;
|
let value = self.body.eval(ctx)?;
|
||||||
output = ops::join(output, value)
|
output = ops::join(output, value)
|
||||||
.map_err(Error::partial(ctx.source, self.body.span()))?;
|
.map_err(Error::at(ctx.source, self.body.span()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
@ -616,7 +579,7 @@ impl Eval for ForExpr {
|
|||||||
|
|
||||||
let value = self.body.eval(ctx)?;
|
let value = self.body.eval(ctx)?;
|
||||||
output = ops::join(output, value)
|
output = ops::join(output, value)
|
||||||
.map_err(Error::partial(ctx.source, self.body.span()))?;
|
.map_err(Error::at(ctx.source, self.body.span()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.scopes.exit();
|
ctx.scopes.exit();
|
||||||
@ -662,7 +625,7 @@ impl Eval for ImportExpr {
|
|||||||
.path
|
.path
|
||||||
.eval(ctx)?
|
.eval(ctx)?
|
||||||
.cast::<EcoString>()
|
.cast::<EcoString>()
|
||||||
.map_err(Error::partial(ctx.source, self.path.span()))?;
|
.map_err(Error::at(ctx.source, self.path.span()))?;
|
||||||
|
|
||||||
let file = ctx.import(&path, self.path.span())?;
|
let file = ctx.import(&path, self.path.span())?;
|
||||||
let module = &ctx.modules[&file];
|
let module = &ctx.modules[&file];
|
||||||
@ -696,7 +659,7 @@ impl Eval for IncludeExpr {
|
|||||||
.path
|
.path
|
||||||
.eval(ctx)?
|
.eval(ctx)?
|
||||||
.cast::<EcoString>()
|
.cast::<EcoString>()
|
||||||
.map_err(Error::partial(ctx.source, self.path.span()))?;
|
.map_err(Error::at(ctx.source, self.path.span()))?;
|
||||||
|
|
||||||
let file = ctx.import(&path, self.path.span())?;
|
let file = ctx.import(&path, self.path.span())?;
|
||||||
let module = &ctx.modules[&file];
|
let module = &ctx.modules[&file];
|
||||||
@ -704,3 +667,97 @@ impl Eval for IncludeExpr {
|
|||||||
Ok(Value::Template(module.template.clone()))
|
Ok(Value::Template(module.template.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Walk a node in a template, filling the context's expression map.
|
||||||
|
pub trait Walk {
|
||||||
|
/// Walk the node.
|
||||||
|
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Walk for SyntaxTree {
|
||||||
|
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||||
|
for node in self.iter() {
|
||||||
|
node.walk(ctx)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Walk for SyntaxNode {
|
||||||
|
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
|
||||||
|
match self {
|
||||||
|
Self::Text(_) => {}
|
||||||
|
Self::Space => {}
|
||||||
|
Self::Linebreak(_) => {}
|
||||||
|
Self::Parbreak(_) => {}
|
||||||
|
Self::Strong(_) => {}
|
||||||
|
Self::Emph(_) => {}
|
||||||
|
Self::Raw(_) => {}
|
||||||
|
Self::Heading(n) => n.body.walk(ctx)?,
|
||||||
|
Self::List(n) => n.body.walk(ctx)?,
|
||||||
|
Self::Enum(n) => n.body.walk(ctx)?,
|
||||||
|
Self::Expr(n) => {
|
||||||
|
let value = n.eval(ctx)?;
|
||||||
|
ctx.map.insert(n as *const _, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to mutably access the value an expression points to.
|
||||||
|
///
|
||||||
|
/// This only works if the expression is a valid lvalue.
|
||||||
|
pub trait Access {
|
||||||
|
/// Try to access the value.
|
||||||
|
fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult<RefMut<'a, Value>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Access for Expr {
|
||||||
|
fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult<RefMut<'a, Value>> {
|
||||||
|
match self {
|
||||||
|
Expr::Ident(ident) => ident.access(ctx),
|
||||||
|
Expr::Call(call) => call.access(ctx),
|
||||||
|
_ => bail!(
|
||||||
|
ctx.source,
|
||||||
|
self.span(),
|
||||||
|
"cannot access this expression mutably",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Access for Ident {
|
||||||
|
fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult<RefMut<'a, Value>> {
|
||||||
|
ctx.scopes
|
||||||
|
.get(self)
|
||||||
|
.ok_or("unknown variable")
|
||||||
|
.and_then(|slot| {
|
||||||
|
slot.try_borrow_mut().map_err(|_| "cannot mutate a constant")
|
||||||
|
})
|
||||||
|
.map_err(Error::at(ctx.source, self.span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Access for CallExpr {
|
||||||
|
fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult<RefMut<'a, Value>> {
|
||||||
|
let source = ctx.source;
|
||||||
|
let args = self.args.eval(ctx)?;
|
||||||
|
let guard = self.callee.access(ctx)?;
|
||||||
|
|
||||||
|
RefMut::try_map(guard, |value| match value {
|
||||||
|
Value::Array(array) => array
|
||||||
|
.get_mut(args.into_index()?)
|
||||||
|
.map_err(Error::at(source, self.span)),
|
||||||
|
|
||||||
|
Value::Dict(dict) => Ok(dict.get_mut(args.into_key()?)),
|
||||||
|
|
||||||
|
v => bail!(
|
||||||
|
source,
|
||||||
|
self.callee.span(),
|
||||||
|
"expected collection, found {}",
|
||||||
|
v.type_name(),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -150,12 +150,12 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
(Fractional(a), Float(b)) => Fractional(a * b),
|
(Fractional(a), Float(b)) => Fractional(a * b),
|
||||||
(Int(a), Fractional(b)) => Fractional(a as f64 * b),
|
(Int(a), Fractional(b)) => Fractional(a as f64 * b),
|
||||||
|
|
||||||
(Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)),
|
(Str(a), Int(b)) => Str(a.repeat(b)?),
|
||||||
(Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)),
|
(Int(a), Str(b)) => Str(b.repeat(a)?),
|
||||||
(Array(a), Int(b)) => Array(a.repeat(b.max(0) as usize)),
|
(Array(a), Int(b)) => Array(a.repeat(b)?),
|
||||||
(Int(a), Array(b)) => Array(b.repeat(a.max(0) as usize)),
|
(Int(a), Array(b)) => Array(b.repeat(a)?),
|
||||||
(Template(a), Int(b)) => Template(a.repeat(b.max(0) as usize)),
|
(Template(a), Int(b)) => Template(a.repeat(b)?),
|
||||||
(Int(a), Template(b)) => Template(b.repeat(a.max(0) as usize)),
|
(Int(a), Template(b)) => Template(b.repeat(a)?),
|
||||||
|
|
||||||
(a, b) => mismatch!("cannot multiply {} with {}", a, b),
|
(a, b) => mismatch!("cannot multiply {} with {}", a, b),
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
use crate::diag::StrResult;
|
||||||
use crate::exec::ExecContext;
|
use crate::exec::ExecContext;
|
||||||
use crate::syntax::{Expr, SyntaxTree};
|
use crate::syntax::{Expr, SyntaxTree};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -26,10 +28,15 @@ impl Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Repeat this template `n` times.
|
/// Repeat this template `n` times.
|
||||||
pub fn repeat(&self, n: usize) -> Self {
|
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||||
let len = self.nodes.len().checked_mul(n).expect("capacity overflow");
|
let count = usize::try_from(n)
|
||||||
let nodes = self.iter().cloned().cycle().take(len).collect();
|
.ok()
|
||||||
Self { nodes: Rc::new(nodes) }
|
.and_then(|n| self.nodes.len().checked_mul(n))
|
||||||
|
.ok_or_else(|| format!("cannot repeat this template {} times", n))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
nodes: Rc::new(self.iter().cloned().cycle().take(count).collect()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +130,7 @@ pub struct TemplateTree {
|
|||||||
/// The raw pointers point into the expressions contained in some
|
/// The raw pointers point into the expressions contained in some
|
||||||
/// [`SyntaxTree`]. Since the lifetime is erased, the tree could go out of scope
|
/// [`SyntaxTree`]. Since the lifetime is erased, the tree could go out of scope
|
||||||
/// while the hash map still lives. Although this could lead to lookup panics,
|
/// while the hash map still lives. Although this could lead to lookup panics,
|
||||||
/// it is not unsafe since the pointers are never dereferenced.
|
/// it is safe since the pointers are never dereferenced.
|
||||||
pub type ExprMap = HashMap<*const Expr, Value>;
|
pub type ExprMap = HashMap<*const Expr, Value>;
|
||||||
|
|
||||||
/// A reference-counted dynamic template node that can implement custom
|
/// A reference-counted dynamic template node that can implement custom
|
||||||
|
@ -173,7 +173,7 @@ pub struct LineMetrics {
|
|||||||
impl Face {
|
impl Face {
|
||||||
/// Parse a font face from a buffer and collection index.
|
/// Parse a font face from a buffer and collection index.
|
||||||
pub fn new(buffer: Rc<Vec<u8>>, index: u32) -> Option<Self> {
|
pub fn new(buffer: Rc<Vec<u8>>, index: u32) -> Option<Self> {
|
||||||
// SAFETY:
|
// Safety:
|
||||||
// - The slices's location is stable in memory:
|
// - The slices's location is stable in memory:
|
||||||
// - We don't move the underlying vector
|
// - We don't move the underlying vector
|
||||||
// - Nobody else can move it since we have a strong ref to the `Rc`.
|
// - Nobody else can move it since we have a strong ref to the `Rc`.
|
||||||
|
@ -23,8 +23,8 @@ pub fn len(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
|
|||||||
let Spanned { v, span } = args.expect("collection")?;
|
let Spanned { v, span } = args.expect("collection")?;
|
||||||
Ok(match v {
|
Ok(match v {
|
||||||
Value::Str(v) => Value::Int(v.len() as i64),
|
Value::Str(v) => Value::Int(v.len() as i64),
|
||||||
Value::Array(v) => Value::Int(v.len() as i64),
|
Value::Array(v) => Value::Int(v.len()),
|
||||||
Value::Dict(v) => Value::Int(v.len() as i64),
|
Value::Dict(v) => Value::Int(v.len()),
|
||||||
_ => bail!(args.source, span, "expected string, array or dictionary"),
|
_ => bail!(args.source, span, "expected string, array or dictionary"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -128,9 +128,10 @@ impl<'s> Scanner<'s> {
|
|||||||
/// The remaining source string after the current index.
|
/// The remaining source string after the current index.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rest(&self) -> &'s str {
|
pub fn rest(&self) -> &'s str {
|
||||||
// SAFETY: The index is always in bounds and on a codepoint boundary
|
// Safety: The index is always in bounds and on a codepoint boundary
|
||||||
// since it is:
|
// since it starts at zero and is is:
|
||||||
// - either increased by the length of a scanned character,
|
// - either increased by the length of a scanned character, advacing
|
||||||
|
// from one codepoint boundary to the next,
|
||||||
// - or checked upon jumping.
|
// - or checked upon jumping.
|
||||||
unsafe { self.src.get_unchecked(self.index ..) }
|
unsafe { self.src.get_unchecked(self.index ..) }
|
||||||
}
|
}
|
||||||
@ -138,7 +139,7 @@ impl<'s> Scanner<'s> {
|
|||||||
/// The full source string up to the current index.
|
/// The full source string up to the current index.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn eaten(&self) -> &'s str {
|
pub fn eaten(&self) -> &'s str {
|
||||||
// SAFETY: The index is always okay, for details see `rest()`.
|
// Safety: The index is always okay, for details see `rest()`.
|
||||||
unsafe { self.src.get_unchecked(.. self.index) }
|
unsafe { self.src.get_unchecked(.. self.index) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,9 +595,9 @@ mod tests {
|
|||||||
// Test with each applicable suffix.
|
// Test with each applicable suffix.
|
||||||
for &(block, mode, suffix, token) in SUFFIXES {
|
for &(block, mode, suffix, token) in SUFFIXES {
|
||||||
let src = $src;
|
let src = $src;
|
||||||
#[allow(unused)]
|
#[allow(unused_variables)]
|
||||||
let mut blocks = BLOCKS;
|
let blocks = BLOCKS;
|
||||||
$(blocks = $blocks;)?
|
$(let blocks = $blocks;)?
|
||||||
assert!(!blocks.contains(|c| !BLOCKS.contains(c)));
|
assert!(!blocks.contains(|c| !BLOCKS.contains(c)));
|
||||||
if (mode.is_none() || mode == Some($mode)) && blocks.contains(block) {
|
if (mode.is_none() || mode == Some($mode)) && blocks.contains(block) {
|
||||||
t!(@$mode: format!("{}{}", src, suffix) => $($token,)* token);
|
t!(@$mode: format!("{}{}", src, suffix) => $($token,)* token);
|
||||||
|
@ -554,18 +554,6 @@ impl Pretty for FuncArg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pretty for i64 {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
write!(p, "{}", self).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for f64 {
|
|
||||||
fn pretty(&self, p: &mut Printer) {
|
|
||||||
write!(p, "{}", self).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pretty for str {
|
impl Pretty for str {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push('"');
|
p.push('"');
|
||||||
@ -594,6 +582,8 @@ macro_rules! pretty_display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pretty_display! {
|
pretty_display! {
|
||||||
|
i64,
|
||||||
|
f64,
|
||||||
bool,
|
bool,
|
||||||
Length,
|
Length,
|
||||||
Angle,
|
Angle,
|
||||||
|
@ -68,7 +68,6 @@ macro_rules! impl_visitor {
|
|||||||
pub mod $module {
|
pub mod $module {
|
||||||
use super::*;
|
use super::*;
|
||||||
$(
|
$(
|
||||||
#[allow(unused_variables)]
|
|
||||||
pub fn $name<'ast, V>($v: &mut V, $node: &'ast $($fmut)? $ty)
|
pub fn $name<'ast, V>($v: &mut V, $node: &'ast $($fmut)? $ty)
|
||||||
where
|
where
|
||||||
V: $visit<'ast> + ?Sized
|
V: $visit<'ast> + ?Sized
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
use std::rc::Rc;
|
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 value semantics.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EcoString(Repr);
|
pub struct EcoString(Repr);
|
||||||
@ -143,21 +146,25 @@ impl EcoString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Repeat this string `n` times.
|
/// Repeat this string `n` times.
|
||||||
pub fn repeat(&self, n: usize) -> Self {
|
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))?;
|
||||||
|
|
||||||
if let Repr::Small { buf, len } = &self.0 {
|
if let Repr::Small { buf, len } = &self.0 {
|
||||||
let prev = usize::from(*len);
|
let prev = usize::from(*len);
|
||||||
let new = prev.saturating_mul(n);
|
|
||||||
if new <= LIMIT {
|
if new <= LIMIT {
|
||||||
let src = &buf[.. prev];
|
let src = &buf[.. prev];
|
||||||
let mut buf = [0; LIMIT];
|
let mut buf = [0; LIMIT];
|
||||||
for i in 0 .. n {
|
for i in 0 .. n {
|
||||||
buf[prev * i .. prev * (i + 1)].copy_from_slice(src);
|
buf[prev * i .. prev * (i + 1)].copy_from_slice(src);
|
||||||
}
|
}
|
||||||
return Self(Repr::Small { buf, len: new as u8 });
|
return Ok(Self(Repr::Small { buf, len: new as u8 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.as_str().repeat(n).into()
|
Ok(self.as_str().repeat(n).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,6 +223,13 @@ impl Deref for EcoString {
|
|||||||
|
|
||||||
fn deref(&self) -> &str {
|
fn deref(&self) -> &str {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
|
// Safety:
|
||||||
|
// The buffer contents stem from correct UTF-8 sources:
|
||||||
|
// - Valid ASCII characters
|
||||||
|
// - Other string slices
|
||||||
|
// - Chars that were encoded with char::encode_utf8
|
||||||
|
// Furthermore, we still do the bounds-check on the len in case
|
||||||
|
// it gets corrupted somehow.
|
||||||
Repr::Small { buf, len } => unsafe {
|
Repr::Small { buf, len } => unsafe {
|
||||||
std::str::from_utf8_unchecked(&buf[.. usize::from(*len)])
|
std::str::from_utf8_unchecked(&buf[.. usize::from(*len)])
|
||||||
},
|
},
|
||||||
@ -424,13 +438,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_str_repeat() {
|
fn test_str_repeat() {
|
||||||
// Test with empty string.
|
// Test with empty string.
|
||||||
assert_eq!(EcoString::new().repeat(0), "");
|
assert_eq!(EcoString::new().repeat(0).unwrap(), "");
|
||||||
assert_eq!(EcoString::new().repeat(100), "");
|
assert_eq!(EcoString::new().repeat(100).unwrap(), "");
|
||||||
|
|
||||||
// Test non-spilling and spilling case.
|
// Test non-spilling and spilling case.
|
||||||
let v = EcoString::from("abc");
|
let v = EcoString::from("abc");
|
||||||
assert_eq!(v.repeat(0), "");
|
assert_eq!(v.repeat(0).unwrap(), "");
|
||||||
assert_eq!(v.repeat(3), "abcabcabc");
|
assert_eq!(v.repeat(3).unwrap(), "abcabcabc");
|
||||||
assert_eq!(v.repeat(5), "abcabcabcabcabc");
|
assert_eq!(v.repeat(5).unwrap(), "abcabcabcabcabc");
|
||||||
|
assert_eq!(
|
||||||
|
v.repeat(-1).unwrap_err(),
|
||||||
|
"cannot repeat this string -1 times",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ mod eco;
|
|||||||
|
|
||||||
pub use eco::EcoString;
|
pub use eco::EcoString;
|
||||||
|
|
||||||
|
use std::cell::RefMut;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
@ -153,3 +154,23 @@ impl PathExt for Path {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional methods for [`RefMut`].
|
||||||
|
pub trait RefMutExt<'a, T> {
|
||||||
|
fn try_map<U, F, E>(orig: Self, f: F) -> Result<RefMut<'a, U>, E>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> Result<&mut U, E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> RefMutExt<'a, T> for RefMut<'a, T> {
|
||||||
|
fn try_map<U, F, E>(mut orig: Self, f: F) -> Result<RefMut<'a, U>, E>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> Result<&mut U, E>,
|
||||||
|
{
|
||||||
|
// Taken from here:
|
||||||
|
// https://github.com/rust-lang/rust/issues/27746#issuecomment-172899746
|
||||||
|
f(&mut orig)
|
||||||
|
.map(|new| new as *mut U)
|
||||||
|
.map(|raw| RefMut::map(orig, |_| unsafe { &mut *raw }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
// Test arrays.
|
// Test arrays.
|
||||||
|
// Ref: false
|
||||||
|
|
||||||
---
|
---
|
||||||
|
// Ref: true
|
||||||
|
|
||||||
// Empty.
|
// Empty.
|
||||||
{()}
|
{()}
|
||||||
|
|
||||||
@ -18,6 +21,39 @@
|
|||||||
, rgb("002")
|
, rgb("002")
|
||||||
,)}
|
,)}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test lvalue and rvalue access.
|
||||||
|
{
|
||||||
|
let array = (1, 2)
|
||||||
|
array(1) += 5 + array(0)
|
||||||
|
test(array, (1, 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test rvalue out of bounds.
|
||||||
|
{
|
||||||
|
let array = (1, 2, 3)
|
||||||
|
// Error: 3-11 array index out of bounds (index: 5, len: 3)
|
||||||
|
array(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test lvalue out of bounds.
|
||||||
|
{
|
||||||
|
let array = (1, 2, 3)
|
||||||
|
// Error: 3-12 array index out of bounds (index: -1, len: 3)
|
||||||
|
array(-1) = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test non-collection indexing.
|
||||||
|
|
||||||
|
{
|
||||||
|
let x = 10pt
|
||||||
|
// Error: 3-4 expected collection, found length
|
||||||
|
x() = 1
|
||||||
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3 expected closing paren
|
// Error: 3 expected closing paren
|
||||||
{(}
|
{(}
|
||||||
|
@ -68,25 +68,25 @@ C
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-6 expected function, found boolean
|
// Error: 2-6 expected function or collection, found boolean
|
||||||
{true()}
|
{true()}
|
||||||
|
|
||||||
---
|
---
|
||||||
#let x = "x"
|
#let x = "x"
|
||||||
|
|
||||||
// Error: 1-3 expected function, found string
|
// Error: 1-3 expected function or collection, found string
|
||||||
#x()
|
#x()
|
||||||
|
|
||||||
---
|
---
|
||||||
#let f(x) = x
|
#let f(x) = x
|
||||||
|
|
||||||
// Error: 1-6 expected function, found integer
|
// Error: 1-6 expected function or collection, found integer
|
||||||
#f(1)(2)
|
#f(1)(2)
|
||||||
|
|
||||||
---
|
---
|
||||||
#let f(x) = x
|
#let f(x) = x
|
||||||
|
|
||||||
// Error: 1-6 expected function, found template
|
// Error: 1-6 expected function or collection, found template
|
||||||
#f[1](2)
|
#f[1](2)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -1,12 +1,40 @@
|
|||||||
// Test dictionaries.
|
// Test dictionaries.
|
||||||
|
// Ref: false
|
||||||
|
|
||||||
---
|
---
|
||||||
|
// Ref: true
|
||||||
|
|
||||||
// Empty
|
// Empty
|
||||||
{(:)}
|
{(:)}
|
||||||
|
|
||||||
// Two pairs.
|
// Two pairs.
|
||||||
{(a1: 1, a2: 2)}
|
{(a1: 1, a2: 2)}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test lvalue and rvalue access.
|
||||||
|
{
|
||||||
|
let dict = (a: 1, b: 1)
|
||||||
|
dict("b") += 1
|
||||||
|
dict("c") = 3
|
||||||
|
test(dict, (a: 1, b: 2, c: 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test rvalue missing key.
|
||||||
|
{
|
||||||
|
let dict = (a: 1, b: 2)
|
||||||
|
// Error: 11-20 dictionary does not contain key: "c"
|
||||||
|
let x = dict("c")
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Missing lvalue is automatically none-initialized.
|
||||||
|
{
|
||||||
|
let dict = (:)
|
||||||
|
// Error: 3-17 cannot add none and integer
|
||||||
|
dict("b") += 1
|
||||||
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Simple expression after already being identified as a dictionary.
|
// Simple expression after already being identified as a dictionary.
|
||||||
// Error: 9-10 expected named pair, found expression
|
// Error: 9-10 expected named pair, found expression
|
||||||
|
@ -46,6 +46,20 @@
|
|||||||
// Error: 2-10 cannot divide integer by length
|
// Error: 2-10 cannot divide integer by length
|
||||||
{3 / 12pt}
|
{3 / 12pt}
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 2-9 cannot repeat this string -1 times
|
||||||
|
{-1 * ""}
|
||||||
|
|
||||||
|
---
|
||||||
|
{
|
||||||
|
let x = 2
|
||||||
|
for _ in 0..60 {
|
||||||
|
x *= 2
|
||||||
|
}
|
||||||
|
// Error: 4-18 cannot repeat this string 4611686018427387904 times
|
||||||
|
{x * "abcdefgh"}
|
||||||
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 14-22 cannot add integer and string
|
// Error: 14-22 cannot add integer and string
|
||||||
{ let x = 1; x += "2" }
|
{ let x = 1; x += "2" }
|
||||||
@ -63,11 +77,11 @@
|
|||||||
{ 1 .. "" }
|
{ 1 .. "" }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-6 cannot assign to this expression
|
// Error: 3-6 cannot access this expression mutably
|
||||||
{ (x) = "" }
|
{ (x) = "" }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-8 cannot assign to this expression
|
// Error: 3-8 cannot access this expression mutably
|
||||||
{ 1 + 2 += 3 }
|
{ 1 + 2 += 3 }
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -75,7 +89,7 @@
|
|||||||
{ z = 1 }
|
{ z = 1 }
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-7 cannot assign to a constant
|
// Error: 3-7 cannot mutate a constant
|
||||||
{ rect = "hi" }
|
{ rect = "hi" }
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#test(not "b" == "b", false)
|
#test(not "b" == "b", false)
|
||||||
|
|
||||||
// Assignment binds stronger than boolean operations.
|
// Assignment binds stronger than boolean operations.
|
||||||
// Error: 2-7 cannot assign to this expression
|
// Error: 2-7 cannot access this expression mutably
|
||||||
{not x = "a"}
|
{not x = "a"}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -70,13 +70,11 @@ fn main() {
|
|||||||
let lhs = args.expect::<Value>("left-hand side")?;
|
let lhs = args.expect::<Value>("left-hand side")?;
|
||||||
let rhs = args.expect::<Value>("right-hand side")?;
|
let rhs = args.expect::<Value>("right-hand side")?;
|
||||||
if lhs != rhs {
|
if lhs != rhs {
|
||||||
typst::bail!(
|
return Err(Error::boxed(
|
||||||
args.source,
|
args.source,
|
||||||
args.span,
|
args.span,
|
||||||
"Assertion failed: {:?} != {:?}",
|
format!("Assertion failed: {:?} != {:?}", lhs, rhs),
|
||||||
lhs,
|
));
|
||||||
rhs
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user