mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Reference-count complex values
Rename some nodes types
This commit is contained in:
parent
36b3067c19
commit
6a4823461f
49
src/eco.rs
49
src/eco.rs
@ -7,7 +7,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, AddAssign, Deref};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A 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)]
|
||||
pub struct EcoString(Repr);
|
||||
|
||||
@ -22,8 +22,9 @@ enum Repr {
|
||||
|
||||
/// The maximum number of bytes that can be stored inline.
|
||||
///
|
||||
/// The value is chosen such that `Repr` fits exactly into 16 bytes
|
||||
/// (which are needed anyway due to `Rc`s alignment).
|
||||
/// The value is chosen such that an `EcoString` fits exactly into 16 bytes
|
||||
/// (which are needed anyway due to the `Rc`s alignment, at least on 64-bit
|
||||
/// platforms).
|
||||
///
|
||||
/// Must be at least 4 to hold any char.
|
||||
const LIMIT: usize = 14;
|
||||
@ -77,7 +78,7 @@ impl EcoString {
|
||||
self
|
||||
}
|
||||
|
||||
/// Appends the given character at the end.
|
||||
/// Append the given character at the end.
|
||||
pub fn push(&mut self, c: char) {
|
||||
match &mut self.0 {
|
||||
Repr::Small { buf, len } => {
|
||||
@ -93,7 +94,7 @@ impl EcoString {
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends the given string slice at the end.
|
||||
/// Append the given string slice at the end.
|
||||
pub fn push_str(&mut self, string: &str) {
|
||||
match &mut self.0 {
|
||||
Repr::Small { buf, len } => {
|
||||
@ -113,7 +114,7 @@ impl EcoString {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the last character from the string.
|
||||
/// Remove the last character from the string.
|
||||
pub fn pop(&mut self) -> Option<char> {
|
||||
let c = self.as_str().chars().rev().next()?;
|
||||
match &mut self.0 {
|
||||
@ -127,7 +128,21 @@ impl EcoString {
|
||||
Some(c)
|
||||
}
|
||||
|
||||
/// Repeats this string `n` times.
|
||||
/// Clear the string.
|
||||
pub fn clear(&mut self) {
|
||||
match &mut self.0 {
|
||||
Repr::Small { len, .. } => *len = 0,
|
||||
Repr::Large(rc) => {
|
||||
if Rc::strong_count(rc) == 1 {
|
||||
Rc::make_mut(rc).clear();
|
||||
} else {
|
||||
*self = Self::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeat this string `n` times.
|
||||
pub fn repeat(&self, n: usize) -> Self {
|
||||
if let Repr::Small { buf, len } = &self.0 {
|
||||
let prev = usize::from(*len);
|
||||
@ -209,18 +224,18 @@ impl Default for EcoString {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EcoString {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Debug::fmt(self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EcoString {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EcoString {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Debug::fmt(self.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for EcoString {}
|
||||
|
||||
impl PartialEq for EcoString {
|
||||
@ -259,6 +274,14 @@ impl PartialOrd for EcoString {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Self> for EcoString {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: &Self) -> Self::Output {
|
||||
self + rhs.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&str> for EcoString {
|
||||
type Output = Self;
|
||||
|
||||
|
148
src/eval/array.rs
Normal file
148
src/eval/array.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Value;
|
||||
|
||||
/// Create a new [`Array`] from values.
|
||||
#[macro_export]
|
||||
macro_rules! array {
|
||||
($value:expr; $count:expr) => {
|
||||
$crate::eval::Array::from_vec(vec![$crate::eval::Value::from($value); $count])
|
||||
};
|
||||
|
||||
($($value:expr),* $(,)?) => {
|
||||
$crate::eval::Array::from_vec(vec![$($crate::eval::Value::from($value)),*])
|
||||
};
|
||||
}
|
||||
|
||||
/// A variably-typed array with clone-on-write value semantics.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Array {
|
||||
vec: Rc<Vec<Value>>,
|
||||
}
|
||||
|
||||
impl Array {
|
||||
/// Create a new, empty array.
|
||||
pub fn new() -> Self {
|
||||
Self { vec: Rc::new(vec![]) }
|
||||
}
|
||||
|
||||
/// Create a new array from a vector of values.
|
||||
pub fn from_vec(vec: Vec<Value>) -> Self {
|
||||
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
|
||||
}
|
||||
|
||||
/// The length of the array.
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
|
||||
/// Borrow the value at the given index.
|
||||
pub fn get(&self, index: usize) -> Option<&Value> {
|
||||
self.vec.get(index)
|
||||
}
|
||||
|
||||
/// Mutably borrow the value at the given index.
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<&mut Value> {
|
||||
Rc::make_mut(&mut self.vec).get_mut(index)
|
||||
}
|
||||
|
||||
/// Set the value at the given index.
|
||||
///
|
||||
/// 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.
|
||||
pub fn push(&mut self, value: Value) {
|
||||
Rc::make_mut(&mut self.vec).push(value);
|
||||
}
|
||||
|
||||
/// Extend the array with the values from another array.
|
||||
pub fn extend(&mut self, other: &Array) {
|
||||
Rc::make_mut(&mut self.vec).extend(other.into_iter())
|
||||
}
|
||||
|
||||
/// Clear the array.
|
||||
pub fn clear(&mut self) {
|
||||
if Rc::strong_count(&mut self.vec) == 1 {
|
||||
Rc::make_mut(&mut self.vec).clear();
|
||||
} else {
|
||||
*self = Self::new();
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeat this array `n` times.
|
||||
pub fn repeat(&self, n: usize) -> Self {
|
||||
let len = self.len().checked_mul(n).expect("capacity overflow");
|
||||
self.into_iter().cycle().take(len).collect()
|
||||
}
|
||||
|
||||
/// Iterate over references to the contained values.
|
||||
pub fn iter(&self) -> std::slice::Iter<Value> {
|
||||
self.vec.iter()
|
||||
}
|
||||
|
||||
/// Iterate over the contained values.
|
||||
pub fn into_iter(&self) -> impl Iterator<Item = Value> + Clone + '_ {
|
||||
// TODO: Actually consume the vector if the ref-count is 1?
|
||||
self.iter().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
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 FromIterator<Value> for Array {
|
||||
fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
|
||||
Array { vec: Rc::new(iter.into_iter().collect()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Array {
|
||||
type Item = &'a Value;
|
||||
type IntoIter = std::slice::Iter<'a, Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Array> for Array {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: &Array) -> Self::Output {
|
||||
self.extend(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&Array> for Array {
|
||||
fn add_assign(&mut self, rhs: &Array) {
|
||||
self.extend(rhs);
|
||||
}
|
||||
}
|
129
src/eval/dict.rs
Normal file
129
src/eval/dict.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Value;
|
||||
use crate::eco::EcoString;
|
||||
|
||||
/// Create a new [`Dict`] from key-value pairs.
|
||||
#[macro_export]
|
||||
macro_rules! dict {
|
||||
($($key:expr => $value:expr),* $(,)?) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut map = std::collections::BTreeMap::new();
|
||||
$(map.insert($crate::eco::EcoString::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)]
|
||||
pub struct Dict {
|
||||
map: Rc<BTreeMap<EcoString, Value>>,
|
||||
}
|
||||
|
||||
impl Dict {
|
||||
/// Create a new, empty dictionary.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create a new dictionary from a mapping of strings to values.
|
||||
pub fn from_map(map: BTreeMap<EcoString, Value>) -> Self {
|
||||
Self { map: Rc::new(map) }
|
||||
}
|
||||
|
||||
/// Whether the dictionary is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// The number of pairs in the dictionary.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Borrow the value the given `key` maps to.
|
||||
pub fn get(&self, key: &str) -> Option<&Value> {
|
||||
self.map.get(key)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Extend the dictionary with the values from another dictionary.
|
||||
pub fn extend(&mut self, other: &Dict) {
|
||||
Rc::make_mut(&mut self.map).extend(other.into_iter())
|
||||
}
|
||||
|
||||
/// Clear the dictionary.
|
||||
pub fn clear(&mut self) {
|
||||
if Rc::strong_count(&mut self.map) == 1 {
|
||||
Rc::make_mut(&mut self.map).clear();
|
||||
} else {
|
||||
*self = Self::new();
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over pairs of the contained keys and values.
|
||||
pub fn into_iter(&self) -> impl Iterator<Item = (EcoString, Value)> + Clone + '_ {
|
||||
// TODO: Actually consume the map if the ref-count is 1?
|
||||
self.iter().map(|(k, v)| (k.clone(), v.clone()))
|
||||
}
|
||||
|
||||
/// Iterate over pairs of references to the contained keys and values.
|
||||
pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> {
|
||||
self.map.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Dict {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Dict {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_map().entries(self.map.iter()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(EcoString, Value)> for Dict {
|
||||
fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self {
|
||||
Dict { map: Rc::new(iter.into_iter().collect()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Dict {
|
||||
type Item = (&'a EcoString, &'a Value);
|
||||
type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Dict> for Dict {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: &Dict) -> Self::Output {
|
||||
self.extend(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&Dict> for Dict {
|
||||
fn add_assign(&mut self, rhs: &Dict) {
|
||||
self.extend(rhs);
|
||||
}
|
||||
}
|
176
src/eval/function.rs
Normal file
176
src/eval/function.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{Cast, CastResult, EvalContext, Value};
|
||||
use crate::eco::EcoString;
|
||||
use crate::syntax::{Span, Spanned};
|
||||
|
||||
/// An evaluatable function.
|
||||
#[derive(Clone)]
|
||||
pub struct Function {
|
||||
/// The name of the function.
|
||||
///
|
||||
/// The string is boxed to make the whole struct fit into 24 bytes, so that
|
||||
/// a value fits into 32 bytes.
|
||||
name: Option<Box<EcoString>>,
|
||||
/// The closure that defines the function.
|
||||
f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/// Create a new function from a rust closure.
|
||||
pub fn new<F>(name: Option<EcoString>, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
||||
{
|
||||
Self { name: name.map(Box::new), f: Rc::new(f) }
|
||||
}
|
||||
|
||||
/// The name of the function.
|
||||
pub fn name(&self) -> Option<&EcoString> {
|
||||
self.name.as_ref().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Function {
|
||||
type Target = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.f.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Function {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("ValueFunc").field("name", &self.name).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Function {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We cast to thin pointers because we don't want to compare vtables.
|
||||
Rc::as_ptr(&self.f) as *const () == Rc::as_ptr(&other.f) as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluated arguments to a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
/// The span of the whole argument list.
|
||||
pub span: Span,
|
||||
/// The positional arguments.
|
||||
pub items: Vec<FuncArg>,
|
||||
}
|
||||
|
||||
/// An argument to a function call: `12` or `draw: false`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArg {
|
||||
/// The span of the whole argument.
|
||||
pub span: Span,
|
||||
/// The name of the argument (`None` for positional arguments).
|
||||
pub name: Option<EcoString>,
|
||||
/// The value of the argument.
|
||||
pub value: Spanned<Value>,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Find and consume the first castable positional argument.
|
||||
pub fn eat<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
(0 .. self.items.len()).find_map(|index| {
|
||||
let slot = &mut self.items[index];
|
||||
if slot.name.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None));
|
||||
let span = value.span;
|
||||
|
||||
match T::cast(value) {
|
||||
CastResult::Ok(t) => {
|
||||
self.items.remove(index);
|
||||
Some(t)
|
||||
}
|
||||
CastResult::Warn(t, m) => {
|
||||
self.items.remove(index);
|
||||
ctx.diag(warning!(span, "{}", m));
|
||||
Some(t)
|
||||
}
|
||||
CastResult::Err(value) => {
|
||||
slot.value = value;
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Find and consume the first castable positional argument, producing a
|
||||
/// `missing argument: {what}` error if no match was found.
|
||||
pub fn expect<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
let found = self.eat(ctx);
|
||||
if found.is_none() {
|
||||
ctx.diag(error!(self.span, "missing argument: {}", what));
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// Find, consume and collect all castable positional arguments.
|
||||
///
|
||||
/// This function returns a vector instead of an iterator because the
|
||||
/// iterator would require unique access to the context, rendering it rather
|
||||
/// unusable. If you need to process arguments one-by-one, you probably want
|
||||
/// to use a while-let loop together with [`eat()`](Self::eat).
|
||||
pub fn all<T>(&mut self, ctx: &mut EvalContext) -> Vec<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
std::iter::from_fn(|| self.eat(ctx)).collect()
|
||||
}
|
||||
|
||||
/// Cast and remove the value for the given named argument, producing an
|
||||
/// error if the conversion fails.
|
||||
pub fn named<T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
let index = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?;
|
||||
|
||||
let value = self.items.remove(index).value;
|
||||
let span = value.span;
|
||||
|
||||
match T::cast(value) {
|
||||
CastResult::Ok(t) => Some(t),
|
||||
CastResult::Warn(t, m) => {
|
||||
ctx.diag(warning!(span, "{}", m));
|
||||
Some(t)
|
||||
}
|
||||
CastResult::Err(value) => {
|
||||
ctx.diag(error!(
|
||||
span,
|
||||
"expected {}, found {}",
|
||||
T::TYPE_NAME,
|
||||
value.v.type_name(),
|
||||
));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce "unexpected argument" errors for all remaining arguments.
|
||||
pub fn finish(self, ctx: &mut EvalContext) {
|
||||
for arg in &self.items {
|
||||
if arg.value.v != Value::Error {
|
||||
ctx.diag(error!(arg.span, "unexpected argument"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,23 @@
|
||||
//! Evaluation of syntax trees.
|
||||
|
||||
#[macro_use]
|
||||
mod array;
|
||||
#[macro_use]
|
||||
mod dict;
|
||||
#[macro_use]
|
||||
mod value;
|
||||
mod capture;
|
||||
mod function;
|
||||
mod ops;
|
||||
mod scope;
|
||||
mod template;
|
||||
|
||||
pub use array::*;
|
||||
pub use capture::*;
|
||||
pub use dict::*;
|
||||
pub use function::*;
|
||||
pub use scope::*;
|
||||
pub use template::*;
|
||||
pub use value::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
@ -45,7 +55,7 @@ pub struct Module {
|
||||
/// The top-level definitions that were bound in this module.
|
||||
pub scope: Scope,
|
||||
/// The template defined by this module.
|
||||
pub template: TemplateValue,
|
||||
pub template: Template,
|
||||
}
|
||||
|
||||
/// The context for evaluation.
|
||||
@ -213,7 +223,7 @@ pub trait Eval {
|
||||
}
|
||||
|
||||
impl Eval for Rc<SyntaxTree> {
|
||||
type Output = TemplateValue;
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||
struct ExprVisitor<'a, 'b> {
|
||||
@ -230,10 +240,7 @@ impl Eval for Rc<SyntaxTree> {
|
||||
let mut visitor = ExprVisitor { ctx, map: ExprMap::new() };
|
||||
visitor.visit_tree(self);
|
||||
|
||||
Rc::new(vec![TemplateNode::Tree {
|
||||
tree: Rc::clone(self),
|
||||
map: visitor.map,
|
||||
}])
|
||||
TemplateTree { tree: Rc::clone(self), map: visitor.map }.into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +287,7 @@ impl Eval for Expr {
|
||||
}
|
||||
|
||||
impl Eval for ArrayExpr {
|
||||
type Output = ArrayValue;
|
||||
type Output = Array;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||
self.items.iter().map(|expr| expr.eval(ctx)).collect()
|
||||
@ -288,7 +295,7 @@ impl Eval for ArrayExpr {
|
||||
}
|
||||
|
||||
impl Eval for DictExpr {
|
||||
type Output = DictValue;
|
||||
type Output = Dict;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||
self.items
|
||||
@ -299,7 +306,7 @@ impl Eval for DictExpr {
|
||||
}
|
||||
|
||||
impl Eval for TemplateExpr {
|
||||
type Output = TemplateValue;
|
||||
type Output = Template;
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||
self.tree.eval(ctx)
|
||||
@ -476,7 +483,7 @@ impl Eval for CallExpr {
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let callee = self.callee.eval(ctx);
|
||||
if let Some(func) = ctx.cast::<FuncValue>(callee, self.callee.span()) {
|
||||
if let Some(func) = ctx.cast::<Function>(callee, self.callee.span()) {
|
||||
let mut args = self.args.eval(ctx);
|
||||
let returned = func(ctx, &mut args);
|
||||
args.finish(ctx);
|
||||
@ -530,7 +537,7 @@ impl Eval for ClosureExpr {
|
||||
};
|
||||
|
||||
let name = self.name.as_ref().map(|name| name.string.clone());
|
||||
Value::Func(FuncValue::new(name, move |ctx, args| {
|
||||
Value::Func(Function::new(name, move |ctx, args| {
|
||||
// Don't leak the scopes from the call site. Instead, we use the
|
||||
// scope of captured variables we collected earlier.
|
||||
let prev = mem::take(&mut ctx.scopes);
|
||||
@ -554,10 +561,10 @@ impl Eval for WithExpr {
|
||||
|
||||
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
|
||||
let callee = self.callee.eval(ctx);
|
||||
if let Some(func) = ctx.cast::<FuncValue>(callee, self.callee.span()) {
|
||||
if let Some(func) = ctx.cast::<Function>(callee, self.callee.span()) {
|
||||
let applied = self.args.eval(ctx);
|
||||
let name = func.name().cloned();
|
||||
Value::Func(FuncValue::new(name, move |ctx, args| {
|
||||
Value::Func(Function::new(name, move |ctx, args| {
|
||||
// Remove named arguments that were overridden.
|
||||
let kept: Vec<_> = applied
|
||||
.items
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::cmp::Ordering::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{TemplateNode, Value};
|
||||
use super::Value;
|
||||
use Value::*;
|
||||
|
||||
/// Apply the plus operator to a value.
|
||||
@ -90,11 +89,6 @@ pub fn sub(lhs: Value, rhs: Value) -> Value {
|
||||
|
||||
/// Compute the product of two values.
|
||||
pub fn mul(lhs: Value, rhs: Value) -> Value {
|
||||
fn repeat<T: Clone>(vec: Vec<T>, n: usize) -> Vec<T> {
|
||||
let len = n * vec.len();
|
||||
vec.into_iter().cycle().take(len).collect()
|
||||
}
|
||||
|
||||
match (lhs, rhs) {
|
||||
(Int(a), Int(b)) => Int(a * b),
|
||||
(Int(a), Float(b)) => Float(a as f64 * b),
|
||||
@ -128,8 +122,8 @@ pub fn mul(lhs: Value, rhs: Value) -> Value {
|
||||
|
||||
(Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)),
|
||||
(Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)),
|
||||
(Array(a), Int(b)) => Array(repeat(a, b.max(0) as usize)),
|
||||
(Int(a), Array(b)) => Array(repeat(b, a.max(0) as usize)),
|
||||
(Array(a), Int(b)) => Array(a.repeat(b.max(0) as usize)),
|
||||
(Int(a), Array(b)) => Array(b.repeat(a.max(0) as usize)),
|
||||
|
||||
_ => Error,
|
||||
}
|
||||
@ -240,26 +234,11 @@ pub fn join(lhs: Value, rhs: Value) -> Result<Value, Value> {
|
||||
fn concat(lhs: Value, rhs: Value) -> Result<Value, Value> {
|
||||
Ok(match (lhs, rhs) {
|
||||
(Str(a), Str(b)) => Str(a + &b),
|
||||
(Array(mut a), Array(b)) => Array({
|
||||
a.extend(b);
|
||||
a
|
||||
}),
|
||||
(Dict(mut a), Dict(b)) => Dict({
|
||||
a.extend(b);
|
||||
a
|
||||
}),
|
||||
(Template(mut a), Template(b)) => Template({
|
||||
Rc::make_mut(&mut a).extend(b.iter().cloned());
|
||||
a
|
||||
}),
|
||||
(Template(mut a), Str(b)) => Template({
|
||||
Rc::make_mut(&mut a).push(TemplateNode::Str(b));
|
||||
a
|
||||
}),
|
||||
(Str(a), Template(mut b)) => Template({
|
||||
Rc::make_mut(&mut b).insert(0, TemplateNode::Str(a));
|
||||
b
|
||||
}),
|
||||
(Array(a), Array(b)) => Array(a + &b),
|
||||
(Dict(a), Dict(b)) => Dict(a + &b),
|
||||
(Template(a), Template(b)) => Template(a + &b),
|
||||
(Template(a), Str(b)) => Template(a + b),
|
||||
(Str(a), Template(b)) => Template(a + b),
|
||||
(a, _) => return Err(a),
|
||||
})
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{AnyValue, EcoString, EvalContext, FuncArgs, FuncValue, Type, Value};
|
||||
use super::{AnyValue, EcoString, EvalContext, FuncArgs, Function, Type, Value};
|
||||
|
||||
/// A slot where a variable is stored.
|
||||
pub type Slot = Rc<RefCell<Value>>;
|
||||
@ -92,7 +92,7 @@ impl Scope {
|
||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
||||
{
|
||||
let name = name.into();
|
||||
self.def_const(name.clone(), FuncValue::new(Some(name), f));
|
||||
self.def_const(name.clone(), Function::new(Some(name), f));
|
||||
}
|
||||
|
||||
/// Define a constant variable with a value of variant `Value::Any`.
|
||||
|
139
src/eval/template.rs
Normal file
139
src/eval/template.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::{Add, Deref};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::Value;
|
||||
use crate::eco::EcoString;
|
||||
use crate::exec::ExecContext;
|
||||
use crate::syntax::{Expr, SyntaxTree};
|
||||
|
||||
/// A template value: `[*Hi* there]`.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Template {
|
||||
nodes: Rc<Vec<TemplateNode>>,
|
||||
}
|
||||
|
||||
impl Template {
|
||||
/// Create a new template from a vector of nodes.
|
||||
pub fn new(nodes: Vec<TemplateNode>) -> Self {
|
||||
Self { nodes: Rc::new(nodes) }
|
||||
}
|
||||
|
||||
/// Iterate over the contained template nodes.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &TemplateNode> + '_ {
|
||||
self.nodes.iter()
|
||||
}
|
||||
}
|
||||
|
||||
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 PartialEq for Template {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.nodes, &other.nodes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Template> for Template {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: &Self) -> Self::Output {
|
||||
Rc::make_mut(&mut self.nodes).extend(rhs.nodes.iter().cloned());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<EcoString> for Template {
|
||||
type Output = Self;
|
||||
|
||||
fn add(mut self, rhs: EcoString) -> Self::Output {
|
||||
Rc::make_mut(&mut self.nodes).push(TemplateNode::Str(rhs));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Template> for EcoString {
|
||||
type Output = Template;
|
||||
|
||||
fn add(self, mut rhs: Template) -> Self::Output {
|
||||
Rc::make_mut(&mut rhs.nodes).insert(0, TemplateNode::Str(self));
|
||||
rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// One node of a template.
|
||||
///
|
||||
/// Evaluating a template expression creates only a single node. Adding multiple
|
||||
/// templates can yield multi-node templates.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TemplateNode {
|
||||
/// A template that was evaluated from a template expression.
|
||||
Tree(TemplateTree),
|
||||
/// A function template that can implement custom behaviour.
|
||||
Func(TemplateFunc),
|
||||
/// A template that was converted from a string.
|
||||
Str(EcoString),
|
||||
}
|
||||
|
||||
/// A template that consists of a syntax tree plus already evaluated
|
||||
/// expressions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TemplateTree {
|
||||
/// The syntax tree of the corresponding template expression.
|
||||
pub tree: Rc<SyntaxTree>,
|
||||
/// The evaluated expressions in the syntax tree.
|
||||
pub map: ExprMap,
|
||||
}
|
||||
|
||||
/// A map from expressions to the values they evaluated to.
|
||||
///
|
||||
/// The raw pointers point into the expressions contained in some
|
||||
/// [`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,
|
||||
/// it is not unsafe since the pointers are never dereferenced.
|
||||
pub type ExprMap = HashMap<*const Expr, Value>;
|
||||
|
||||
/// A reference-counted dynamic template node that can implement custom
|
||||
/// behaviour.
|
||||
#[derive(Clone)]
|
||||
pub struct TemplateFunc(Rc<dyn Fn(&mut ExecContext)>);
|
||||
|
||||
impl TemplateFunc {
|
||||
/// Create a new function template from a rust function or closure.
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ExecContext) + 'static,
|
||||
{
|
||||
Self(Rc::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TemplateFunc {
|
||||
type Target = dyn Fn(&mut ExecContext);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for TemplateFunc {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("TemplateFunc").finish()
|
||||
}
|
||||
}
|
@ -1,15 +1,13 @@
|
||||
use std::any::Any;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
use super::{ops, Array, Dict, EvalContext, Function, Template, TemplateFunc};
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::eco::EcoString;
|
||||
use crate::exec::ExecContext;
|
||||
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
|
||||
use crate::syntax::{Expr, Span, Spanned, SyntaxTree};
|
||||
use crate::syntax::{Span, Spanned};
|
||||
|
||||
/// A computational value.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -38,14 +36,14 @@ pub enum Value {
|
||||
Color(Color),
|
||||
/// A string: `"string"`.
|
||||
Str(EcoString),
|
||||
/// An array value: `(1, "hi", 12cm)`.
|
||||
Array(ArrayValue),
|
||||
/// An array of values: `(1, "hi", 12cm)`.
|
||||
Array(Array),
|
||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||
Dict(DictValue),
|
||||
Dict(Dict),
|
||||
/// A template value: `[*Hi* there]`.
|
||||
Template(TemplateValue),
|
||||
Template(Template),
|
||||
/// An executable function.
|
||||
Func(FuncValue),
|
||||
Func(Function),
|
||||
/// Any object.
|
||||
Any(AnyValue),
|
||||
/// The result of invalid operations.
|
||||
@ -53,12 +51,12 @@ pub enum Value {
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Create a new template value consisting of a single dynamic node.
|
||||
/// Create a new template consisting of a single function node.
|
||||
pub fn template<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ExecContext) + 'static,
|
||||
{
|
||||
Self::Template(Rc::new(vec![TemplateNode::Func(TemplateFunc::new(f))]))
|
||||
Self::Template(TemplateFunc::new(f).into())
|
||||
}
|
||||
|
||||
/// The name of the stored value's type.
|
||||
@ -76,10 +74,10 @@ impl Value {
|
||||
Self::Fractional(_) => Fractional::TYPE_NAME,
|
||||
Self::Color(_) => Color::TYPE_NAME,
|
||||
Self::Str(_) => EcoString::TYPE_NAME,
|
||||
Self::Array(_) => ArrayValue::TYPE_NAME,
|
||||
Self::Dict(_) => DictValue::TYPE_NAME,
|
||||
Self::Template(_) => TemplateValue::TYPE_NAME,
|
||||
Self::Func(_) => FuncValue::TYPE_NAME,
|
||||
Self::Array(_) => Array::TYPE_NAME,
|
||||
Self::Dict(_) => Dict::TYPE_NAME,
|
||||
Self::Template(_) => Template::TYPE_NAME,
|
||||
Self::Func(_) => Function::TYPE_NAME,
|
||||
Self::Any(v) => v.type_name(),
|
||||
Self::Error => "error",
|
||||
}
|
||||
@ -101,7 +99,6 @@ impl Value {
|
||||
a.len() == b.len()
|
||||
&& a.iter().all(|(k, x)| b.get(k).map_or(false, |y| x.eq(y)))
|
||||
}
|
||||
(Self::Template(a), Self::Template(b)) => Rc::ptr_eq(a, b),
|
||||
(a, b) => a == b,
|
||||
}
|
||||
}
|
||||
@ -146,245 +143,6 @@ impl Default for Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// An array value: `(1, "hi", 12cm)`.
|
||||
pub type ArrayValue = Vec<Value>;
|
||||
|
||||
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
|
||||
pub type DictValue = BTreeMap<EcoString, Value>;
|
||||
|
||||
/// A template value: `[*Hi* there]`.
|
||||
pub type TemplateValue = Rc<Vec<TemplateNode>>;
|
||||
|
||||
/// One chunk of a template.
|
||||
///
|
||||
/// Evaluating a template expression creates only a single node. Adding multiple
|
||||
/// templates can yield multi-node templates.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TemplateNode {
|
||||
/// A template that consists of a syntax tree plus already evaluated
|
||||
/// expression.
|
||||
Tree {
|
||||
/// The syntax tree of the corresponding template expression.
|
||||
tree: Rc<SyntaxTree>,
|
||||
/// The evaluated expressions in the syntax tree.
|
||||
map: ExprMap,
|
||||
},
|
||||
/// A template that was converted from a string.
|
||||
Str(EcoString),
|
||||
/// A function template that can implement custom behaviour.
|
||||
Func(TemplateFunc),
|
||||
}
|
||||
|
||||
impl PartialEq for TemplateNode {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A map from expressions to the values they evaluated to.
|
||||
///
|
||||
/// The raw pointers point into the expressions contained in some
|
||||
/// [`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,
|
||||
/// it is not unsafe since the pointers are never dereferenced.
|
||||
pub type ExprMap = HashMap<*const Expr, Value>;
|
||||
|
||||
/// A reference-counted dynamic template node that can implement custom
|
||||
/// behaviour.
|
||||
#[derive(Clone)]
|
||||
pub struct TemplateFunc(Rc<dyn Fn(&mut ExecContext)>);
|
||||
|
||||
impl TemplateFunc {
|
||||
/// Create a new function template from a rust function or closure.
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ExecContext) + 'static,
|
||||
{
|
||||
Self(Rc::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TemplateFunc {
|
||||
type Target = dyn Fn(&mut ExecContext);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for TemplateFunc {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("TemplateAny").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a reference-counted executable function.
|
||||
#[derive(Clone)]
|
||||
pub struct FuncValue {
|
||||
/// The string is boxed to make the whole struct fit into 24 bytes, so that
|
||||
/// a [`Value`] fits into 32 bytes.
|
||||
name: Option<Box<EcoString>>,
|
||||
/// The closure that defines the function.
|
||||
f: Rc<dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value>,
|
||||
}
|
||||
|
||||
impl FuncValue {
|
||||
/// Create a new function value from a rust function or closure.
|
||||
pub fn new<F>(name: Option<EcoString>, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
|
||||
{
|
||||
Self { name: name.map(Box::new), f: Rc::new(f) }
|
||||
}
|
||||
|
||||
/// The name of the function.
|
||||
pub fn name(&self) -> Option<&EcoString> {
|
||||
self.name.as_ref().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FuncValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We cast to thin pointers because we don't want to compare vtables.
|
||||
Rc::as_ptr(&self.f) as *const () == Rc::as_ptr(&other.f) as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for FuncValue {
|
||||
type Target = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.f.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FuncValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_struct("ValueFunc").field("name", &self.name).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluated arguments to a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
/// The span of the whole argument list.
|
||||
pub span: Span,
|
||||
/// The positional arguments.
|
||||
pub items: Vec<FuncArg>,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Find and consume the first castable positional argument.
|
||||
pub fn eat<T>(&mut self, ctx: &mut EvalContext) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
(0 .. self.items.len()).find_map(|index| {
|
||||
let slot = &mut self.items[index];
|
||||
if slot.name.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None));
|
||||
let span = value.span;
|
||||
|
||||
match T::cast(value) {
|
||||
CastResult::Ok(t) => {
|
||||
self.items.remove(index);
|
||||
Some(t)
|
||||
}
|
||||
CastResult::Warn(t, m) => {
|
||||
self.items.remove(index);
|
||||
ctx.diag(warning!(span, "{}", m));
|
||||
Some(t)
|
||||
}
|
||||
CastResult::Err(value) => {
|
||||
slot.value = value;
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Find and consume the first castable positional argument, producing a
|
||||
/// `missing argument: {what}` error if no match was found.
|
||||
pub fn expect<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
let found = self.eat(ctx);
|
||||
if found.is_none() {
|
||||
ctx.diag(error!(self.span, "missing argument: {}", what));
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
/// Find, consume and collect all castable positional arguments.
|
||||
///
|
||||
/// This function returns a vector instead of an iterator because the
|
||||
/// iterator would require unique access to the context, rendering it rather
|
||||
/// unusable. If you need to process arguments one-by-one, you probably want
|
||||
/// to use a while-let loop together with [`eat()`](Self::eat).
|
||||
pub fn all<T>(&mut self, ctx: &mut EvalContext) -> Vec<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
std::iter::from_fn(|| self.eat(ctx)).collect()
|
||||
}
|
||||
|
||||
/// Cast and remove the value for the given named argument, producing an
|
||||
/// error if the conversion fails.
|
||||
pub fn named<T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
|
||||
where
|
||||
T: Cast<Spanned<Value>>,
|
||||
{
|
||||
let index = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?;
|
||||
|
||||
let value = self.items.remove(index).value;
|
||||
let span = value.span;
|
||||
|
||||
match T::cast(value) {
|
||||
CastResult::Ok(t) => Some(t),
|
||||
CastResult::Warn(t, m) => {
|
||||
ctx.diag(warning!(span, "{}", m));
|
||||
Some(t)
|
||||
}
|
||||
CastResult::Err(value) => {
|
||||
ctx.diag(error!(
|
||||
span,
|
||||
"expected {}, found {}",
|
||||
T::TYPE_NAME,
|
||||
value.v.type_name(),
|
||||
));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce "unexpected argument" errors for all remaining arguments.
|
||||
pub fn finish(self, ctx: &mut EvalContext) {
|
||||
for arg in &self.items {
|
||||
if arg.value.v != Value::Error {
|
||||
ctx.diag(error!(arg.span, "unexpected argument"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An argument to a function call: `12` or `draw: false`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncArg {
|
||||
/// The span of the whole argument.
|
||||
pub span: Span,
|
||||
/// The name of the argument (`None` for positional arguments).
|
||||
pub name: Option<EcoString>,
|
||||
/// The value of the argument.
|
||||
pub value: Spanned<Value>,
|
||||
}
|
||||
|
||||
/// A wrapper around a dynamic value.
|
||||
pub struct AnyValue(Box<dyn Bounds>);
|
||||
|
||||
@ -422,15 +180,9 @@ impl AnyValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AnyValue {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.dyn_clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.dyn_eq(other)
|
||||
impl Display for AnyValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,9 +192,15 @@ impl Debug for AnyValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AnyValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
impl Clone for AnyValue {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.dyn_clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AnyValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.dyn_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
@ -608,14 +366,20 @@ primitive! {
|
||||
primitive! { Fractional: "fractional", Value::Fractional }
|
||||
primitive! { Color: "color", Value::Color }
|
||||
primitive! { EcoString: "string", Value::Str }
|
||||
primitive! { ArrayValue: "array", Value::Array }
|
||||
primitive! { DictValue: "dictionary", Value::Dict }
|
||||
primitive! { Array: "array", Value::Array }
|
||||
primitive! { Dict: "dictionary", Value::Dict }
|
||||
primitive! {
|
||||
TemplateValue: "template",
|
||||
Template: "template",
|
||||
Value::Template,
|
||||
Value::Str(v) => Rc::new(vec![TemplateNode::Str(v)]),
|
||||
Value::Str(v) => v.into(),
|
||||
}
|
||||
primitive! { Function: "function", Value::Func }
|
||||
|
||||
impl From<i32> for Value {
|
||||
fn from(v: i32) -> Self {
|
||||
Self::Int(v as i64)
|
||||
}
|
||||
}
|
||||
primitive! { FuncValue: "function", Value::Func }
|
||||
|
||||
impl From<usize> for Value {
|
||||
fn from(v: usize) -> Self {
|
||||
|
@ -4,10 +4,10 @@ use std::rc::Rc;
|
||||
use super::{Exec, ExecWithMap, FontFamily, State};
|
||||
use crate::diag::{Diag, DiagSet, Pass};
|
||||
use crate::eco::EcoString;
|
||||
use crate::eval::{ExprMap, TemplateValue};
|
||||
use crate::eval::{ExprMap, Template};
|
||||
use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size};
|
||||
use crate::layout::{
|
||||
AnyNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
|
||||
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
|
||||
};
|
||||
use crate::syntax::{Span, SyntaxTree};
|
||||
|
||||
@ -53,7 +53,7 @@ impl ExecContext {
|
||||
}
|
||||
|
||||
/// Execute a template and return the result as a stack node.
|
||||
pub fn exec_template_stack(&mut self, template: &TemplateValue) -> StackNode {
|
||||
pub fn exec_template_stack(&mut self, template: &Template) -> StackNode {
|
||||
self.exec_stack(|ctx| template.exec(ctx))
|
||||
}
|
||||
|
||||
@ -88,13 +88,13 @@ impl ExecContext {
|
||||
}
|
||||
|
||||
/// Push any node into the active paragraph.
|
||||
pub fn push_into_par(&mut self, node: impl Into<AnyNode>) {
|
||||
pub fn push_into_par(&mut self, node: impl Into<LayoutNode>) {
|
||||
let align = self.state.aligns.cross;
|
||||
self.stack.par.push(ParChild::Any(node.into(), align));
|
||||
}
|
||||
|
||||
/// Push any node into the active stack.
|
||||
pub fn push_into_stack(&mut self, node: impl Into<AnyNode>) {
|
||||
pub fn push_into_stack(&mut self, node: impl Into<LayoutNode>) {
|
||||
self.parbreak();
|
||||
let aligns = self.state.aligns;
|
||||
self.stack.push(StackChild::Any(node.into(), aligns));
|
||||
|
@ -10,15 +10,15 @@ use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::diag::Pass;
|
||||
use crate::eval::{ExprMap, TemplateFunc, TemplateNode, TemplateValue, Value};
|
||||
use crate::eco::EcoString;
|
||||
use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value};
|
||||
use crate::geom::{Dir, Gen};
|
||||
use crate::layout::{LayoutTree, StackChild, StackNode};
|
||||
use crate::pretty::pretty;
|
||||
use crate::eco::EcoString;
|
||||
use crate::syntax::*;
|
||||
|
||||
/// Execute a template to produce a layout tree.
|
||||
pub fn exec(template: &TemplateValue, state: State) -> Pass<LayoutTree> {
|
||||
pub fn exec(template: &Template, state: State) -> Pass<LayoutTree> {
|
||||
let mut ctx = ExecContext::new(state);
|
||||
template.exec(&mut ctx);
|
||||
ctx.finish()
|
||||
@ -50,7 +50,7 @@ impl ExecWithMap for SyntaxTree {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecWithMap for Node {
|
||||
impl ExecWithMap for SyntaxNode {
|
||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
||||
match self {
|
||||
Self::Text(text) => ctx.push_text(text),
|
||||
@ -117,12 +117,7 @@ impl ExecWithMap for EnumItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_item(
|
||||
ctx: &mut ExecContext,
|
||||
label: EcoString,
|
||||
body: &SyntaxTree,
|
||||
map: &ExprMap,
|
||||
) {
|
||||
fn exec_item(ctx: &mut ExecContext, label: EcoString, body: &SyntaxTree, map: &ExprMap) {
|
||||
let label = ctx.exec_stack(|ctx| ctx.push_text(label));
|
||||
let body = ctx.exec_tree_stack(body, map);
|
||||
let stack = StackNode {
|
||||
@ -159,7 +154,7 @@ impl Exec for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Exec for TemplateValue {
|
||||
impl Exec for Template {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
for node in self.iter() {
|
||||
node.exec(ctx);
|
||||
@ -170,13 +165,19 @@ impl Exec for TemplateValue {
|
||||
impl Exec for TemplateNode {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
match self {
|
||||
Self::Tree { tree, map } => tree.exec_with_map(ctx, &map),
|
||||
Self::Str(v) => ctx.push_text(v),
|
||||
Self::Tree(v) => v.exec(ctx),
|
||||
Self::Func(v) => v.exec(ctx),
|
||||
Self::Str(v) => ctx.push_text(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Exec for TemplateTree {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
self.tree.exec_with_map(ctx, &self.map)
|
||||
}
|
||||
}
|
||||
|
||||
impl Exec for TemplateFunc {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
let snapshot = ctx.state.clone();
|
||||
|
@ -9,7 +9,7 @@ pub struct BackgroundNode {
|
||||
/// Background color / texture.
|
||||
pub fill: Paint,
|
||||
/// The child node to be filled.
|
||||
pub child: AnyNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
/// The kind of shape to use as a background.
|
||||
@ -45,7 +45,7 @@ impl Layout for BackgroundNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BackgroundNode> for AnyNode {
|
||||
impl From<BackgroundNode> for LayoutNode {
|
||||
fn from(background: BackgroundNode) -> Self {
|
||||
Self::new(background)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ pub struct FixedNode {
|
||||
/// The fixed height, if any.
|
||||
pub height: Option<Linear>,
|
||||
/// The child node whose size to fix.
|
||||
pub child: AnyNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Layout for FixedNode {
|
||||
@ -47,7 +47,7 @@ impl Layout for FixedNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FixedNode> for AnyNode {
|
||||
impl From<FixedNode> for LayoutNode {
|
||||
fn from(fixed: FixedNode) -> Self {
|
||||
Self::new(fixed)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pub struct GridNode {
|
||||
/// Defines sizing of gutter rows and columns between content.
|
||||
pub gutter: Gen<Vec<TrackSizing>>,
|
||||
/// The nodes to be arranged in a grid.
|
||||
pub children: Vec<AnyNode>,
|
||||
pub children: Vec<LayoutNode>,
|
||||
}
|
||||
|
||||
/// Defines how to size a grid cell along an axis.
|
||||
@ -45,7 +45,7 @@ impl Layout for GridNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GridNode> for AnyNode {
|
||||
impl From<GridNode> for LayoutNode {
|
||||
fn from(grid: GridNode) -> Self {
|
||||
Self::new(grid)
|
||||
}
|
||||
@ -64,7 +64,7 @@ struct GridLayouter<'a> {
|
||||
/// The row tracks including gutter tracks.
|
||||
rows: Vec<TrackSizing>,
|
||||
/// The children of the grid.
|
||||
children: &'a [AnyNode],
|
||||
children: &'a [LayoutNode],
|
||||
/// The region to layout into.
|
||||
regions: Regions,
|
||||
/// Resolved column sizes.
|
||||
@ -517,7 +517,7 @@ impl<'a> GridLayouter<'a> {
|
||||
/// Get the node in the cell in column `x` and row `y`.
|
||||
///
|
||||
/// Returns `None` if it's a gutter cell.
|
||||
fn cell(&self, x: usize, y: usize) -> Option<&'a AnyNode> {
|
||||
fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> {
|
||||
assert!(x < self.cols.len());
|
||||
assert!(y < self.rows.len());
|
||||
|
||||
|
@ -59,7 +59,7 @@ impl Layout for ImageNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageNode> for AnyNode {
|
||||
impl From<ImageNode> for LayoutNode {
|
||||
fn from(image: ImageNode) -> Self {
|
||||
Self::new(image)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ pub struct PageRun {
|
||||
pub size: Size,
|
||||
/// The layout node that produces the actual pages (typically a
|
||||
/// [`StackNode`]).
|
||||
pub child: AnyNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
impl PageRun {
|
||||
@ -86,14 +86,14 @@ impl PageRun {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a dynamic layouting node.
|
||||
pub struct AnyNode {
|
||||
/// A dynamic layouting node.
|
||||
pub struct LayoutNode {
|
||||
node: Box<dyn Bounds>,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
hash: u64,
|
||||
}
|
||||
|
||||
impl AnyNode {
|
||||
impl LayoutNode {
|
||||
/// Create a new instance from any node that satisifies the required bounds.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub fn new<T>(node: T) -> Self
|
||||
@ -120,7 +120,7 @@ impl AnyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for AnyNode {
|
||||
impl Layout for LayoutNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -143,7 +143,13 @@ impl Layout for AnyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AnyNode {
|
||||
impl Debug for LayoutNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.node.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LayoutNode {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
node: self.node.dyn_clone(),
|
||||
@ -153,27 +159,21 @@ impl Clone for AnyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AnyNode {}
|
||||
impl Eq for LayoutNode {}
|
||||
|
||||
impl PartialEq for AnyNode {
|
||||
impl PartialEq for LayoutNode {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node.dyn_eq(other.node.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
impl Hash for AnyNode {
|
||||
impl Hash for LayoutNode {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.hash);
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AnyNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.node.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
trait Bounds: Layout + Debug + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn dyn_eq(&self, other: &dyn Bounds) -> bool;
|
||||
|
@ -7,7 +7,7 @@ pub struct PadNode {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Linear>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: AnyNode,
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
|
||||
impl Layout for PadNode {
|
||||
@ -61,7 +61,7 @@ fn solve(padding: Sides<Linear>, size: Size) -> Size {
|
||||
)
|
||||
}
|
||||
|
||||
impl From<PadNode> for AnyNode {
|
||||
impl From<PadNode> for LayoutNode {
|
||||
fn from(pad: PadNode) -> Self {
|
||||
Self::new(pad)
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ use unicode_bidi::{BidiInfo, Level};
|
||||
use xi_unicode::LineBreakIterator;
|
||||
|
||||
use super::*;
|
||||
use crate::exec::FontState;
|
||||
use crate::eco::EcoString;
|
||||
use crate::exec::FontState;
|
||||
use crate::util::{RangeExt, SliceExt};
|
||||
|
||||
type Range = std::ops::Range<usize>;
|
||||
@ -32,7 +32,7 @@ pub enum ParChild {
|
||||
/// A run of text and how to align it in its line.
|
||||
Text(EcoString, Align, Rc<FontState>),
|
||||
/// Any child node and how to align it in its line.
|
||||
Any(AnyNode, Align),
|
||||
Any(LayoutNode, Align),
|
||||
}
|
||||
|
||||
impl Layout for ParNode {
|
||||
@ -89,7 +89,7 @@ impl ParNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParNode> for AnyNode {
|
||||
impl From<ParNode> for LayoutNode {
|
||||
fn from(par: ParNode) -> Self {
|
||||
Self::new(par)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ pub enum StackChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(Length),
|
||||
/// Any child node and how to align it in the stack.
|
||||
Any(AnyNode, Gen<Align>),
|
||||
Any(LayoutNode, Gen<Align>),
|
||||
}
|
||||
|
||||
impl Layout for StackNode {
|
||||
@ -39,7 +39,7 @@ impl Layout for StackNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StackNode> for AnyNode {
|
||||
impl From<StackNode> for LayoutNode {
|
||||
fn from(stack: StackNode) -> Self {
|
||||
Self::new(stack)
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ fn rect_impl(
|
||||
height: Option<Linear>,
|
||||
aspect: Option<N64>,
|
||||
fill: Option<Color>,
|
||||
body: TemplateValue,
|
||||
body: Template,
|
||||
) -> Value {
|
||||
Value::template(move |ctx| {
|
||||
let mut stack = ctx.exec_template_stack(&body);
|
||||
@ -99,7 +99,7 @@ fn ellipse_impl(
|
||||
height: Option<Linear>,
|
||||
aspect: Option<N64>,
|
||||
fill: Option<Color>,
|
||||
body: TemplateValue,
|
||||
body: Template,
|
||||
) -> Value {
|
||||
Value::template(move |ctx| {
|
||||
// This padding ratio ensures that the rectangular padded region fits
|
||||
|
@ -20,7 +20,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
let right = args.named(ctx, "right");
|
||||
let bottom = args.named(ctx, "bottom");
|
||||
let flip = args.named(ctx, "flip");
|
||||
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
|
||||
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
|
||||
|
||||
Value::template(move |ctx| {
|
||||
let snapshot = ctx.state.clone();
|
||||
@ -108,7 +108,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
let second = args.eat::<AlignValue>(ctx);
|
||||
let mut horizontal = args.named::<AlignValue>(ctx, "horizontal");
|
||||
let mut vertical = args.named::<AlignValue>(ctx, "vertical");
|
||||
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
|
||||
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
|
||||
|
||||
for value in first.into_iter().chain(second) {
|
||||
match value.axis() {
|
||||
|
@ -18,7 +18,7 @@ use std::rc::Rc;
|
||||
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::eco::EcoString;
|
||||
use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value};
|
||||
use crate::eval::{EvalContext, FuncArgs, Scope, Template, Value};
|
||||
use crate::exec::{Exec, FontFamily};
|
||||
use crate::font::{FontStyle, FontWeight, VerticalFontMetric};
|
||||
use crate::geom::*;
|
||||
|
@ -23,7 +23,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
let serif = args.named(ctx, "serif");
|
||||
let sans_serif = args.named(ctx, "sans-serif");
|
||||
let monospace = args.named(ctx, "monospace");
|
||||
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
|
||||
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
|
||||
|
||||
Value::template(move |ctx| {
|
||||
let font = ctx.state.font_mut();
|
||||
@ -163,7 +163,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
let spacing = args.named(ctx, "spacing");
|
||||
let leading = args.named(ctx, "leading");
|
||||
let word_spacing = args.named(ctx, "word-spacing");
|
||||
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
|
||||
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
|
||||
|
||||
Value::template(move |ctx| {
|
||||
if let Some(spacing) = spacing {
|
||||
@ -194,7 +194,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
|
||||
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
|
||||
|
||||
Value::template(move |ctx| {
|
||||
if let Some(dir) = dir.or(iso) {
|
||||
@ -239,7 +239,7 @@ fn line_impl(
|
||||
let thickness = args.eat(ctx).or_else(|| args.named::<Linear>(ctx, "thickness"));
|
||||
let offset = args.named(ctx, "offset");
|
||||
let extent = args.named(ctx, "extent").unwrap_or_default();
|
||||
let body = args.expect::<TemplateValue>(ctx, "body").unwrap_or_default();
|
||||
let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
|
||||
|
||||
// Suppress any existing strikethrough if strength is explicitly zero.
|
||||
let state = thickness.map_or(true, |s| !s.is_zero()).then(|| {
|
||||
|
@ -55,10 +55,10 @@ where
|
||||
let mut tree = vec![];
|
||||
while !p.eof() && f(p) {
|
||||
if let Some(mut node) = node(p, &mut at_start) {
|
||||
at_start &= matches!(node, Node::Space | Node::Parbreak(_));
|
||||
at_start &= matches!(node, SyntaxNode::Space | SyntaxNode::Parbreak(_));
|
||||
|
||||
// Look for wide call.
|
||||
if let Node::Expr(Expr::Call(call)) = &mut node {
|
||||
if let SyntaxNode::Expr(Expr::Call(call)) = &mut node {
|
||||
if call.wide {
|
||||
let start = p.next_start();
|
||||
let tree = tree_while(p, true, f);
|
||||
@ -77,7 +77,7 @@ where
|
||||
}
|
||||
|
||||
/// Parse a syntax node.
|
||||
fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
fn node(p: &mut Parser, at_start: &mut bool) -> Option<SyntaxNode> {
|
||||
let token = p.peek()?;
|
||||
let span = p.peek_span();
|
||||
let node = match token {
|
||||
@ -85,30 +85,32 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
Token::Space(newlines) => {
|
||||
*at_start |= newlines > 0;
|
||||
if newlines < 2 {
|
||||
Node::Space
|
||||
SyntaxNode::Space
|
||||
} else {
|
||||
Node::Parbreak(span)
|
||||
SyntaxNode::Parbreak(span)
|
||||
}
|
||||
}
|
||||
|
||||
// Text.
|
||||
Token::Text(text) => Node::Text(text.into()),
|
||||
Token::Tilde => Node::Text("\u{00A0}".into()),
|
||||
Token::HyphHyph => Node::Text("\u{2013}".into()),
|
||||
Token::HyphHyphHyph => Node::Text("\u{2014}".into()),
|
||||
Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)),
|
||||
Token::Text(text) => SyntaxNode::Text(text.into()),
|
||||
Token::Tilde => SyntaxNode::Text("\u{00A0}".into()),
|
||||
Token::HyphHyph => SyntaxNode::Text("\u{2013}".into()),
|
||||
Token::HyphHyphHyph => SyntaxNode::Text("\u{2014}".into()),
|
||||
Token::UnicodeEscape(t) => SyntaxNode::Text(unicode_escape(p, t)),
|
||||
|
||||
// Markup.
|
||||
Token::Backslash => Node::Linebreak(span),
|
||||
Token::Star => Node::Strong(span),
|
||||
Token::Underscore => Node::Emph(span),
|
||||
Token::Backslash => SyntaxNode::Linebreak(span),
|
||||
Token::Star => SyntaxNode::Strong(span),
|
||||
Token::Underscore => SyntaxNode::Emph(span),
|
||||
Token::Raw(t) => raw(p, t),
|
||||
Token::Eq if *at_start => return Some(heading(p)),
|
||||
Token::Hyph if *at_start => return Some(list_item(p)),
|
||||
Token::Numbering(number) if *at_start => return Some(enum_item(p, number)),
|
||||
|
||||
// Line-based markup that is not currently at the start of the line.
|
||||
Token::Eq | Token::Hyph | Token::Numbering(_) => Node::Text(p.peek_src().into()),
|
||||
Token::Eq | Token::Hyph | Token::Numbering(_) => {
|
||||
SyntaxNode::Text(p.peek_src().into())
|
||||
}
|
||||
|
||||
// Hashtag + keyword / identifier.
|
||||
Token::Ident(_)
|
||||
@ -128,12 +130,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
}
|
||||
p.end_group();
|
||||
|
||||
return expr.map(Node::Expr);
|
||||
return expr.map(SyntaxNode::Expr);
|
||||
}
|
||||
|
||||
// Block and template.
|
||||
Token::LeftBrace => return Some(Node::Expr(block(p, false))),
|
||||
Token::LeftBracket => return Some(Node::Expr(template(p))),
|
||||
Token::LeftBrace => return Some(SyntaxNode::Expr(block(p, false))),
|
||||
Token::LeftBracket => return Some(SyntaxNode::Expr(template(p))),
|
||||
|
||||
// Comments.
|
||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
||||
@ -170,17 +172,17 @@ fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> EcoString {
|
||||
}
|
||||
|
||||
/// Handle a raw block.
|
||||
fn raw(p: &mut Parser, token: RawToken) -> Node {
|
||||
fn raw(p: &mut Parser, token: RawToken) -> SyntaxNode {
|
||||
let span = p.peek_span();
|
||||
let raw = resolve::resolve_raw(span, token.text, token.backticks);
|
||||
if !token.terminated {
|
||||
p.diag(error!(p.peek_span().end, "expected backtick(s)"));
|
||||
}
|
||||
Node::Raw(raw)
|
||||
SyntaxNode::Raw(raw)
|
||||
}
|
||||
|
||||
/// Parse a heading.
|
||||
fn heading(p: &mut Parser) -> Node {
|
||||
fn heading(p: &mut Parser) -> SyntaxNode {
|
||||
let start = p.next_start();
|
||||
p.assert(Token::Eq);
|
||||
|
||||
@ -197,7 +199,7 @@ fn heading(p: &mut Parser) -> Node {
|
||||
|
||||
let body = tree_indented(p);
|
||||
|
||||
Node::Heading(HeadingNode {
|
||||
SyntaxNode::Heading(HeadingNode {
|
||||
span: p.span(start),
|
||||
level,
|
||||
body: Rc::new(body),
|
||||
@ -205,19 +207,19 @@ fn heading(p: &mut Parser) -> Node {
|
||||
}
|
||||
|
||||
/// Parse a single list item.
|
||||
fn list_item(p: &mut Parser) -> Node {
|
||||
fn list_item(p: &mut Parser) -> SyntaxNode {
|
||||
let start = p.next_start();
|
||||
p.assert(Token::Hyph);
|
||||
let body = tree_indented(p);
|
||||
Node::List(ListItem { span: p.span(start), body })
|
||||
SyntaxNode::List(ListItem { span: p.span(start), body })
|
||||
}
|
||||
|
||||
/// Parse a single enum item.
|
||||
fn enum_item(p: &mut Parser, number: Option<usize>) -> Node {
|
||||
fn enum_item(p: &mut Parser, number: Option<usize>) -> SyntaxNode {
|
||||
let start = p.next_start();
|
||||
p.assert(Token::Numbering(number));
|
||||
let body = tree_indented(p);
|
||||
Node::Enum(EnumItem { span: p.span(start), number, body })
|
||||
SyntaxNode::Enum(EnumItem { span: p.span(start), number, body })
|
||||
}
|
||||
|
||||
/// Parse an expression.
|
||||
|
@ -86,7 +86,7 @@ impl Pretty for SyntaxTree {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for Node {
|
||||
impl Pretty for SyntaxNode {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
// TODO: Handle escaping.
|
||||
@ -493,7 +493,7 @@ impl Pretty for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for ArrayValue {
|
||||
impl Pretty for Array {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push('(');
|
||||
p.join(self, ", ", |item, p| item.pretty(p));
|
||||
@ -504,7 +504,7 @@ impl Pretty for ArrayValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for DictValue {
|
||||
impl Pretty for Dict {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push('(');
|
||||
if self.is_empty() {
|
||||
@ -520,13 +520,13 @@ impl Pretty for DictValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for TemplateValue {
|
||||
impl Pretty for Template {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("<template>");
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for FuncValue {
|
||||
impl Pretty for Function {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("<function");
|
||||
if let Some(name) = self.name() {
|
||||
@ -608,20 +608,9 @@ pretty_display! {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
use crate::parse::parse;
|
||||
|
||||
macro_rules! map {
|
||||
($($k:ident: $v:expr),* $(,)?) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut m = BTreeMap::new();
|
||||
$(m.insert(stringify!($k).into(), $v);)*
|
||||
m
|
||||
}};
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn roundtrip(src: &str) {
|
||||
test_parse(src, src);
|
||||
@ -757,18 +746,15 @@ mod tests {
|
||||
test_value("\n", r#""\n""#);
|
||||
test_value("\\", r#""\\""#);
|
||||
test_value("\"", r#""\"""#);
|
||||
test_value(Value::Array(vec![]), "()");
|
||||
test_value(vec![Value::None], "(none,)");
|
||||
test_value(vec![Value::Int(1), Value::Int(2)], "(1, 2)");
|
||||
test_value(map![], "(:)");
|
||||
test_value(map![one: Value::Int(1)], "(one: 1)");
|
||||
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)");
|
||||
test_value(Function::new(None, |_, _| Value::None), "<function>");
|
||||
test_value(
|
||||
map![two: Value::Bool(false), one: Value::Int(1)],
|
||||
"(one: 1, two: false)",
|
||||
);
|
||||
test_value(FuncValue::new(None, |_, _| Value::None), "<function>");
|
||||
test_value(
|
||||
FuncValue::new(Some("nil".into()), |_, _| Value::None),
|
||||
Function::new(Some("nil".into()), |_, _| Value::None),
|
||||
"<function nil>",
|
||||
);
|
||||
test_value(AnyValue::new(1), "1");
|
||||
|
@ -18,4 +18,4 @@ use crate::eco::EcoString;
|
||||
/// The abstract syntax tree.
|
||||
///
|
||||
/// This type can represent a full parsed document.
|
||||
pub type SyntaxTree = Vec<Node>;
|
||||
pub type SyntaxTree = Vec<SyntaxNode>;
|
||||
|
@ -4,7 +4,7 @@ use super::*;
|
||||
|
||||
/// A syntax node, encompassing a single logical entity of parsed source code.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Node {
|
||||
pub enum SyntaxNode {
|
||||
/// Plain text.
|
||||
Text(EcoString),
|
||||
/// Whitespace containing less than two newlines.
|
||||
|
@ -120,20 +120,6 @@ impl Span {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Span {}
|
||||
|
||||
impl PartialEq for Span {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
!Self::cmp() || (self.start == other.start && self.end == other.end)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Span {
|
||||
fn default() -> Self {
|
||||
Span::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Span
|
||||
where
|
||||
T: Into<Pos> + Copy,
|
||||
@ -152,12 +138,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Span {
|
||||
fn default() -> Self {
|
||||
Span::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Span {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "<{:?}-{:?}>", self.start, self.end)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Span {}
|
||||
|
||||
impl PartialEq for Span {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
!Self::cmp() || (self.start == other.start && self.end == other.end)
|
||||
}
|
||||
}
|
||||
|
||||
/// A byte position in source code.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct Pos(pub u32);
|
||||
@ -172,17 +172,6 @@ impl Pos {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<T> for Pos
|
||||
where
|
||||
T: Into<Pos>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: T) -> Self {
|
||||
Pos(self.0 + rhs.into().0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Pos {
|
||||
fn from(index: u32) -> Self {
|
||||
Self(index)
|
||||
@ -207,6 +196,17 @@ impl Debug for Pos {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Add<T> for Pos
|
||||
where
|
||||
T: Into<Pos>,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: T) -> Self {
|
||||
Pos(self.0 + rhs.into().0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A one-indexed line-column position in source code.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct Location {
|
||||
|
@ -85,19 +85,19 @@ impl_visitors! {
|
||||
}
|
||||
}
|
||||
|
||||
visit_node(v, node: Node) {
|
||||
visit_node(v, node: SyntaxNode) {
|
||||
match node {
|
||||
Node::Text(_) => {}
|
||||
Node::Space => {}
|
||||
Node::Linebreak(_) => {}
|
||||
Node::Parbreak(_) => {}
|
||||
Node::Strong(_) => {}
|
||||
Node::Emph(_) => {}
|
||||
Node::Raw(_) => {}
|
||||
Node::Heading(n) => v.visit_heading(n),
|
||||
Node::List(n) => v.visit_list(n),
|
||||
Node::Enum(n) => v.visit_enum(n),
|
||||
Node::Expr(n) => v.visit_expr(n),
|
||||
SyntaxNode::Text(_) => {}
|
||||
SyntaxNode::Space => {}
|
||||
SyntaxNode::Linebreak(_) => {}
|
||||
SyntaxNode::Parbreak(_) => {}
|
||||
SyntaxNode::Strong(_) => {}
|
||||
SyntaxNode::Emph(_) => {}
|
||||
SyntaxNode::Raw(_) => {}
|
||||
SyntaxNode::Heading(n) => v.visit_heading(n),
|
||||
SyntaxNode::List(n) => v.visit_list(n),
|
||||
SyntaxNode::Enum(n) => v.visit_enum(n),
|
||||
SyntaxNode::Expr(n) => v.visit_expr(n),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user