summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-03 11:44:53 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-03 13:35:39 +0100
commit37a7afddfaffd44cb9bc013c9506599267e08983 (patch)
tree20e7d62d3c5418baff01a21d0406b91bf3096214 /src/model
parent56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff)
Split crates
Diffstat (limited to 'src/model')
-rw-r--r--src/model/args.rs2
-rw-r--r--src/model/array.rs10
-rw-r--r--src/model/cast.rs142
-rw-r--r--src/model/content.rs10
-rw-r--r--src/model/dict.rs8
-rw-r--r--src/model/eval.rs183
-rw-r--r--src/model/func.rs4
-rw-r--r--src/model/items.rs123
-rw-r--r--src/model/methods.rs2
-rw-r--r--src/model/mod.rs8
-rw-r--r--src/model/ops.rs21
-rw-r--r--src/model/str.rs18
-rw-r--r--src/model/styles.rs118
-rw-r--r--src/model/value.rs8
-rw-r--r--src/model/vm.rs23
15 files changed, 452 insertions, 228 deletions
diff --git a/src/model/args.rs b/src/model/args.rs
index f95fbf08..9fb30b9c 100644
--- a/src/model/args.rs
+++ b/src/model/args.rs
@@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter, Write};
use super::{Array, Cast, Dict, Str, Value};
-use crate::diag::{At, SourceResult};
+use crate::diag::{bail, At, SourceResult};
use crate::syntax::{Span, Spanned};
/// Evaluated arguments to a function.
diff --git a/src/model/array.rs b/src/model/array.rs
index 196f02ec..053248ec 100644
--- a/src/model/array.rs
+++ b/src/model/array.rs
@@ -9,8 +9,9 @@ use crate::syntax::Spanned;
use crate::util::ArcExt;
/// Create a new [`Array`] from values.
-#[allow(unused_macros)]
-macro_rules! array {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __array {
($value:expr; $count:expr) => {
$crate::model::Array::from_vec(vec![$value.into(); $count])
};
@@ -20,6 +21,9 @@ macro_rules! array {
};
}
+#[doc(inline)]
+pub use crate::__array as array;
+
/// A reference counted array with value semantics.
#[derive(Default, Clone, PartialEq, Hash)]
pub struct Array(Arc<Vec<Value>>);
@@ -97,7 +101,7 @@ impl Array {
.ok_or_else(|| out_of_bounds(index, len))?;
Arc::make_mut(&mut self.0).remove(i);
- return Ok(());
+ Ok(())
}
/// Extract a contigous subregion of the array.
diff --git a/src/model/cast.rs b/src/model/cast.rs
index 7356ef70..cbb2952d 100644
--- a/src/model/cast.rs
+++ b/src/model/cast.rs
@@ -1,8 +1,13 @@
use std::num::NonZeroUsize;
+use std::str::FromStr;
use super::{Pattern, Regex, Value};
use crate::diag::{with_alternative, StrResult};
-use crate::geom::{Corners, Dir, Paint, Sides};
+use crate::font::{FontStretch, FontStyle, FontWeight};
+use crate::frame::{Destination, Lang, Location, Region};
+use crate::geom::{
+ Axes, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Rel, Sides,
+};
use crate::syntax::Spanned;
use crate::util::EcoString;
@@ -16,7 +21,9 @@ pub trait Cast<V = Value>: Sized {
}
/// Implement traits for dynamic types.
-macro_rules! dynamic {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __dynamic {
($type:ty: $name:literal, $($tts:tt)*) => {
impl $crate::model::Type for $type {
const TYPE_NAME: &'static str = $name;
@@ -37,8 +44,13 @@ macro_rules! dynamic {
};
}
+#[doc(inline)]
+pub use crate::__dynamic as dynamic;
+
/// Make a type castable from a value.
-macro_rules! castable {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __castable {
($type:ty: $inner:ty) => {
impl $crate::model::Cast<$crate::model::Value> for $type {
fn is(value: &$crate::model::Value) -> bool {
@@ -88,6 +100,9 @@ macro_rules! castable {
};
}
+#[doc(inline)]
+pub use crate::__castable as castable;
+
impl Cast for Value {
fn is(_: &Value) -> bool {
true
@@ -119,14 +134,6 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
}
}
-dynamic! {
- Dir: "direction",
-}
-
-dynamic! {
- Regex: "regular expression",
-}
-
castable! {
usize,
Expected: "non-negative integer",
@@ -170,6 +177,10 @@ castable! {
Value::Str(string) => string.into(),
}
+dynamic! {
+ Regex: "regular expression",
+}
+
castable! {
Pattern,
Expected: "function, string or regular expression",
@@ -178,6 +189,115 @@ castable! {
@regex: Regex => Self::Regex(regex.clone()),
}
+dynamic! {
+ Dir: "direction",
+}
+
+dynamic! {
+ GenAlign: "alignment",
+}
+
+dynamic! {
+ Axes<GenAlign>: "2d alignment",
+}
+
+castable! {
+ Axes<Option<GenAlign>>,
+ Expected: "1d or 2d alignment",
+ @align: GenAlign => {
+ let mut aligns = Axes::default();
+ aligns.set(align.axis(), Some(*align));
+ aligns
+ },
+ @aligns: Axes<GenAlign> => aligns.map(Some),
+}
+
+dynamic! {
+ PartialStroke: "stroke",
+ Value::Length(thickness) => Self {
+ paint: Smart::Auto,
+ thickness: Smart::Custom(thickness),
+ },
+ Value::Color(color) => Self {
+ paint: Smart::Custom(color.into()),
+ thickness: Smart::Auto,
+ },
+}
+
+castable! {
+ Axes<Rel<Length>>,
+ Expected: "array of two relative lengths",
+ Value::Array(array) => {
+ let mut iter = array.into_iter();
+ match (iter.next(), iter.next(), iter.next()) {
+ (Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
+ _ => Err("point array must contain exactly two entries")?,
+ }
+ },
+}
+
+castable! {
+ Destination,
+ Expected: "string or dictionary with `page`, `x`, and `y` keys",
+ Value::Str(string) => Self::Url(string.into()),
+ Value::Dict(dict) => {
+ let page = dict.get("page")?.clone().cast()?;
+ let x: Length = dict.get("x")?.clone().cast()?;
+ let y: Length = dict.get("y")?.clone().cast()?;
+ Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
+ },
+}
+
+castable! {
+ FontStyle,
+ Expected: "string",
+ Value::Str(string) => match string.as_str() {
+ "normal" => Self::Normal,
+ "italic" => Self::Italic,
+ "oblique" => Self::Oblique,
+ _ => Err(r#"expected "normal", "italic" or "oblique""#)?,
+ },
+}
+
+castable! {
+ FontWeight,
+ Expected: "integer or string",
+ Value::Int(v) => Value::Int(v)
+ .cast::<usize>()?
+ .try_into()
+ .map_or(Self::BLACK, Self::from_number),
+ Value::Str(string) => match string.as_str() {
+ "thin" => Self::THIN,
+ "extralight" => Self::EXTRALIGHT,
+ "light" => Self::LIGHT,
+ "regular" => Self::REGULAR,
+ "medium" => Self::MEDIUM,
+ "semibold" => Self::SEMIBOLD,
+ "bold" => Self::BOLD,
+ "extrabold" => Self::EXTRABOLD,
+ "black" => Self::BLACK,
+ _ => Err("unknown font weight")?,
+ },
+}
+
+castable! {
+ FontStretch,
+ Expected: "ratio",
+ Value::Ratio(v) => Self::from_ratio(v.get() as f32),
+}
+
+castable! {
+ Lang,
+ Expected: "string",
+ Value::Str(string) => Self::from_str(&string)?,
+}
+
+castable! {
+ Region,
+ Expected: "string",
+ Value::Str(string) => Self::from_str(&string)?,
+}
+
impl<T: Cast> Cast for Option<T> {
fn is(value: &Value) -> bool {
matches!(value, Value::None) || T::is(value)
diff --git a/src/model/content.rs b/src/model/content.rs
index 1cffa773..372f6ff6 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -9,8 +9,9 @@ use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node;
use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm};
+use crate as typst;
use crate::diag::{SourceResult, StrResult};
-use crate::util::ReadableTypeId;
+use crate::util::{EcoString, ReadableTypeId};
/// Composable representation of styled content.
///
@@ -26,6 +27,11 @@ impl Content {
SequenceNode(vec![]).pack()
}
+ /// Create content from a string of text.
+ pub fn text(text: impl Into<EcoString>) -> Self {
+ item!(text)(text.into())
+ }
+
/// Create a new sequence node from multiples nodes.
pub fn sequence(seq: Vec<Self>) -> Self {
match seq.as_slice() {
@@ -146,7 +152,7 @@ impl Add for Content {
let mut lhs = self;
if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() {
if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() {
- lhs_mut.0.extend(rhs_mut.0.drain(..));
+ lhs_mut.0.append(&mut rhs_mut.0);
} else if let Some(rhs) = rhs.downcast::<SequenceNode>() {
lhs_mut.0.extend(rhs.0.iter().cloned());
} else {
diff --git a/src/model/dict.rs b/src/model/dict.rs
index 3e4fd956..49e50aa0 100644
--- a/src/model/dict.rs
+++ b/src/model/dict.rs
@@ -10,8 +10,9 @@ use crate::syntax::Spanned;
use crate::util::ArcExt;
/// Create a new [`Dict`] from key-value pairs.
-#[allow(unused_macros)]
-macro_rules! dict {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __dict {
($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut map = std::collections::BTreeMap::new();
@@ -20,6 +21,9 @@ macro_rules! dict {
}};
}
+#[doc(inline)]
+pub use crate::__dict as dict;
+
/// A reference-counted dictionary with value semantics.
#[derive(Default, Clone, PartialEq, Hash)]
pub struct Dict(Arc<BTreeMap<Str, Value>>);
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 02617ed6..8e287f14 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -1,22 +1,19 @@
//! Evaluation of markup into modules.
use std::collections::BTreeMap;
-use std::sync::Arc;
use comemo::{Track, Tracked};
use unicode_segmentation::UnicodeSegmentation;
use super::{
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
- Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
+ Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
};
-use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
+use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
-use crate::library::math;
-use crate::library::text::TextNode;
use crate::syntax::ast::TypedNode;
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
use crate::World;
/// Evaluate a source file and return the resulting module.
@@ -39,7 +36,7 @@ pub fn eval(
// Evaluate the module.
let route = unsafe { Route::insert(route, id) };
let ast = world.source(id).ast()?;
- let std = &world.config().std;
+ let std = &world.config().scope;
let scopes = Scopes::new(Some(std));
let mut vm = Vm::new(world, route.track(), Some(id), scopes);
let result = ast.eval(&mut vm);
@@ -136,8 +133,7 @@ fn eval_markup(
break;
}
- eval_markup(vm, nodes)?
- .styled_with_entry(StyleEntry::Recipe(recipe).into())
+ eval_markup(vm, nodes)?.styled_with_entry(StyleEntry::Recipe(recipe))
}
ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => {
let tail = eval_markup(vm, nodes)?;
@@ -165,10 +161,13 @@ impl Eval for ast::MarkupNode {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
match self {
- Self::Space(v) => v.eval(vm),
+ Self::Space(v) => Ok(match v.newlines() {
+ 0 ..= 1 => (vm.items.space)(),
+ _ => (vm.items.parbreak)(),
+ }),
Self::Linebreak(v) => v.eval(vm),
Self::Text(v) => v.eval(vm),
- Self::Escape(v) => v.eval(vm),
+ Self::Escape(v) => Ok((vm.items.text)(v.get().into())),
Self::Shorthand(v) => v.eval(vm),
Self::SmartQuote(v) => v.eval(vm),
Self::Strong(v) => v.eval(vm),
@@ -187,23 +186,11 @@ impl Eval for ast::MarkupNode {
}
}
-impl Eval for ast::Space {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(if self.newlines() < 2 {
- (vm.items().space)()
- } else {
- (vm.items().parbreak)()
- })
- }
-}
-
impl Eval for ast::Linebreak {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().linebreak)(false))
+ Ok((vm.items.linebreak)(false))
}
}
@@ -211,15 +198,7 @@ impl Eval for ast::Text {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(vm.text(self.get().clone()))
- }
-}
-
-impl Eval for ast::Escape {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(vm.text(self.get()))
+ Ok((vm.items.text)(self.get().clone()))
}
}
@@ -227,7 +206,7 @@ impl Eval for ast::Shorthand {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(vm.text(self.get()))
+ Ok((vm.items.text)(self.get().into()))
}
}
@@ -235,7 +214,7 @@ impl Eval for ast::SmartQuote {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().smart_quote)(self.double()))
+ Ok((vm.items.smart_quote)(self.double()))
}
}
@@ -243,7 +222,7 @@ impl Eval for ast::Strong {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().strong)(self.body().eval(vm)?))
+ Ok((vm.items.strong)(self.body().eval(vm)?))
}
}
@@ -251,7 +230,7 @@ impl Eval for ast::Emph {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().emph)(self.body().eval(vm)?))
+ Ok((vm.items.emph)(self.body().eval(vm)?))
}
}
@@ -262,7 +241,7 @@ impl Eval for ast::Raw {
let text = self.text().clone();
let lang = self.lang().cloned();
let block = self.block();
- Ok((vm.items().raw)(text, lang, block))
+ Ok((vm.items.raw)(text, lang, block))
}
}
@@ -270,7 +249,7 @@ impl Eval for ast::Link {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().link)(self.url().clone()))
+ Ok((vm.items.link)(self.url().clone()))
}
}
@@ -286,7 +265,7 @@ impl Eval for ast::Ref {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().ref_)(self.get().clone()))
+ Ok((vm.items.ref_)(self.get().clone()))
}
}
@@ -296,7 +275,7 @@ impl Eval for ast::Heading {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let level = self.level();
let body = self.body().eval(vm)?;
- Ok((vm.items().heading)(level, body))
+ Ok((vm.items.heading)(level, body))
}
}
@@ -304,7 +283,7 @@ impl Eval for ast::ListItem {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items().list_item)(self.body().eval(vm)?))
+ Ok((vm.items.list_item)(self.body().eval(vm)?))
}
}
@@ -314,7 +293,7 @@ impl Eval for ast::EnumItem {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let number = self.number();
let body = self.body().eval(vm)?;
- Ok((vm.items().enum_item)(number, body))
+ Ok((vm.items.enum_item)(number, body))
}
}
@@ -324,7 +303,7 @@ impl Eval for ast::DescItem {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let term = self.term().eval(vm)?;
let body = self.body().eval(vm)?;
- Ok((vm.items().desc_item)(term, body))
+ Ok((vm.items.desc_item)(term, body))
}
}
@@ -332,82 +311,76 @@ impl Eval for ast::Math {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let nodes = self
- .children()
- .map(|node| node.eval(vm))
- .collect::<SourceResult<_>>()?;
- Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack())
+ Ok((vm.items.math)(
+ self.children()
+ .map(|node| node.eval(vm))
+ .collect::<SourceResult<_>>()?,
+ self.display(),
+ ))
}
}
impl Eval for ast::MathNode {
- type Output = math::MathNode;
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok(match self {
- Self::Space(_) => math::MathNode::Space,
- Self::Linebreak(_) => math::MathNode::Linebreak,
- Self::Escape(c) => math::MathNode::Atom(c.get().into()),
- Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()),
- Self::Script(node) => node.eval(vm)?,
- Self::Frac(node) => node.eval(vm)?,
- Self::Align(node) => node.eval(vm)?,
- Self::Group(node) => math::MathNode::Row(
- Arc::new(
- node.children()
- .map(|node| node.eval(vm))
- .collect::<SourceResult<_>>()?,
- ),
- node.span(),
- ),
- Self::Expr(expr) => {
- let content = expr.eval(vm)?.display(vm.world);
- if let Some(node) = content.downcast::<TextNode>() {
- math::MathNode::Atom(node.0.clone())
- } else {
- bail!(expr.span(), "expected text")
- }
- }
+ Self::Space(_) => (vm.items.space)(),
+ Self::Linebreak(v) => v.eval(vm)?,
+ Self::Escape(v) => (vm.items.math_atom)(v.get().into()),
+ Self::Atom(v) => v.eval(vm)?,
+ Self::Script(v) => v.eval(vm)?,
+ Self::Frac(v) => v.eval(vm)?,
+ Self::Align(v) => v.eval(vm)?,
+ Self::Group(v) => v.eval(vm)?,
+ Self::Expr(v) => match v.eval(vm)? {
+ Value::None => Content::empty(),
+ Value::Int(v) => (vm.items.math_atom)(format_eco!("{}", v)),
+ Value::Float(v) => (vm.items.math_atom)(format_eco!("{}", v)),
+ Value::Str(v) => (vm.items.math_atom)(v.into()),
+ Value::Content(v) => v,
+ _ => bail!(v.span(), "unexpected garbage"),
+ },
})
}
}
+impl Eval for ast::Atom {
+ type Output = Content;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok((vm.items.math_atom)(self.get().clone()))
+ }
+}
+
impl Eval for ast::Script {
- type Output = math::MathNode;
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(math::MathNode::Script(Arc::new(math::ScriptNode {
- base: self.base().eval(vm)?,
- sub: self
- .sub()
- .map(|node| node.eval(vm))
- .transpose()?
- .map(|node| node.unparen()),
- sup: self
- .sup()
- .map(|node| node.eval(vm))
- .transpose()?
- .map(|node| node.unparen()),
- })))
+ Ok((vm.items.math_script)(
+ self.base().eval(vm)?,
+ self.sub().map(|node| node.eval(vm)).transpose()?,
+ self.sup().map(|node| node.eval(vm)).transpose()?,
+ ))
}
}
impl Eval for ast::Frac {
- type Output = math::MathNode;
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok(math::MathNode::Frac(Arc::new(math::FracNode {
- num: self.num().eval(vm)?.unparen(),
- denom: self.denom().eval(vm)?.unparen(),
- })))
+ Ok((vm.items.math_frac)(
+ self.num().eval(vm)?,
+ self.denom().eval(vm)?,
+ ))
}
}
impl Eval for ast::Align {
- type Output = math::MathNode;
+ type Output = Content;
- fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
- Ok(math::MathNode::Align(self.count()))
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok((vm.items.math_align)(self.count()))
}
}
@@ -515,7 +488,7 @@ fn eval_code(
}
ast::Expr::Show(show) => {
let recipe = show.eval(vm)?;
- let entry = StyleEntry::Recipe(recipe).into();
+ let entry = StyleEntry::Recipe(recipe);
if vm.flow.is_some() {
break;
}
@@ -627,7 +600,7 @@ impl Eval for ast::Unary {
ast::UnOp::Neg => ops::neg(value),
ast::UnOp::Not => ops::not(value),
};
- Ok(result.at(self.span())?)
+ result.at(self.span())
}
}
@@ -676,7 +649,7 @@ impl ast::Binary {
}
let rhs = self.rhs().eval(vm)?;
- Ok(op(lhs, rhs).at(self.span())?)
+ op(lhs, rhs).at(self.span())
}
/// Apply an assignment operation.
@@ -708,8 +681,7 @@ impl Eval for ast::FieldAccess {
.to::<dyn Show>()
.and_then(|node| node.field(&field))
.ok_or_else(|| format!("unknown field {field:?}"))
- .at(span)?
- .clone(),
+ .at(span)?,
v => bail!(
self.target().span(),
@@ -754,9 +726,8 @@ impl Eval for ast::MethodCall {
Ok(if methods::is_mutating(&method) {
let args = self.args().eval(vm)?;
- let mut value = self.target().access(vm)?;
- methods::call_mut(&mut value, &method, args, span)
- .trace(vm.world, point, span)?;
+ let value = self.target().access(vm)?;
+ methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?;
Value::None
} else {
let value = self.target().eval(vm)?;
@@ -882,7 +853,7 @@ impl Eval for ast::SetRule {
let target = self.target();
let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
let args = self.args().eval(vm)?;
- Ok(target.set(args)?)
+ target.set(args)
}
}
@@ -1085,14 +1056,14 @@ impl Eval for ast::ModuleInclude {
let span = self.path().span();
let path = self.path().eval(vm)?.cast::<EcoString>().at(span)?;
let module = import(vm, &path, span)?;
- Ok(module.content.clone())
+ Ok(module.content)
}
}
/// Process an import of a module relative to the current location.
fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
// Load the source file.
- let full = vm.locate(&path).at(span)?;
+ let full = vm.locate(path).at(span)?;
let id = vm.world.resolve(&full).at(span)?;
// Prevent cyclic importing.
diff --git a/src/model/func.rs b/src/model/func.rs
index dff58233..5be1aae3 100644
--- a/src/model/func.rs
+++ b/src/model/func.rs
@@ -5,7 +5,7 @@ use std::sync::Arc;
use comemo::{Track, Tracked};
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
-use crate::diag::{SourceResult, StrResult};
+use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::ast::{self, Expr, TypedNode};
use crate::syntax::{SourceId, SyntaxNode};
use crate::util::EcoString;
@@ -229,7 +229,7 @@ impl Closure {
}
/// A visitor that determines which variables to capture for a closure.
-pub struct CapturesVisitor<'a> {
+pub(super) struct CapturesVisitor<'a> {
external: &'a Scopes<'a>,
internal: Scopes<'a>,
captures: Scope,
diff --git a/src/model/items.rs b/src/model/items.rs
new file mode 100644
index 00000000..164d9602
--- /dev/null
+++ b/src/model/items.rs
@@ -0,0 +1,123 @@
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
+use std::num::NonZeroUsize;
+
+use comemo::Tracked;
+use once_cell::sync::OnceCell;
+
+use super::{Content, StyleChain};
+use crate::diag::SourceResult;
+use crate::frame::Frame;
+use crate::geom::{Abs, Dir};
+use crate::util::{hash128, EcoString};
+use crate::World;
+
+/// Global storage for lang items.
+#[doc(hidden)]
+pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new();
+
+/// Set the lang items. This is a hack :(
+///
+/// Passing the lang items everywhere they are needed (especially the text node
+/// related things) is very painful. By storing them globally, in theory, we
+/// break incremental, but only when different sets of lang items are used in
+/// the same program. For this reason, if this function is called multiple
+/// times, the items must be the same.
+pub fn set_lang_items(items: LangItems) {
+ if LANG_ITEMS.set(items).is_err() {
+ let first = hash128(LANG_ITEMS.get().unwrap());
+ let second = hash128(&items);
+ assert_eq!(first, second, "set differing lang items");
+ }
+}
+
+/// Access a lang item.
+macro_rules! item {
+ ($name:ident) => {
+ $crate::model::LANG_ITEMS.get().unwrap().$name
+ };
+}
+
+/// Definition of certain standard library items the language is aware of.
+#[derive(Copy, Clone)]
+pub struct LangItems {
+ /// The root layout function.
+ pub root:
+ fn(world: Tracked<dyn World>, document: &Content) -> SourceResult<Vec<Frame>>,
+ /// Access the em size.
+ pub em: fn(StyleChain) -> Abs,
+ /// Access the text direction.
+ pub dir: fn(StyleChain) -> Dir,
+ /// A space.
+ pub space: fn() -> Content,
+ /// A forced line break.
+ pub linebreak: fn(justify: bool) -> Content,
+ /// Plain text.
+ pub text: fn(text: EcoString) -> Content,
+ /// A smart quote: `'` or `"`.
+ pub smart_quote: fn(double: bool) -> Content,
+ /// A paragraph break.
+ pub parbreak: fn() -> Content,
+ /// Strong content: `*Strong*`.
+ pub strong: fn(body: Content) -> Content,
+ /// Emphasized content: `_Emphasized_`.
+ pub emph: fn(body: Content) -> Content,
+ /// A raw block with optional syntax highlighting: `` `...` ``.
+ pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
+ /// A hyperlink: `https://typst.org`.
+ pub link: fn(url: EcoString) -> Content,
+ /// A reference: `@target`.
+ pub ref_: fn(target: EcoString) -> Content,
+ /// A section heading: `= Introduction`.
+ pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
+ /// An item in an unordered list: `- ...`.
+ pub list_item: fn(body: Content) -> Content,
+ /// An item in an enumeration (ordered list): `1. ...`.
+ pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
+ /// An item in a description list: `/ Term: Details`.
+ pub desc_item: fn(term: Content, body: Content) -> Content,
+ /// A math formula: `$x$`, `$ x^2 $`.
+ pub math: fn(children: Vec<Content>, display: bool) -> Content,
+ /// A atom in a formula: `x`, `+`, `12`.
+ pub math_atom: fn(atom: EcoString) -> Content,
+ /// A base with an optional sub- and superscript in a formula: `a_1^2`.
+ pub math_script:
+ fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
+ /// A fraction in a formula: `x/2`
+ pub math_frac: fn(num: Content, denom: Content) -> Content,
+ /// An alignment indicator in a formula: `&`, `&&`.
+ pub math_align: fn(count: usize) -> Content,
+}
+
+impl Debug for LangItems {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad("LangItems { .. }")
+ }
+}
+
+impl Hash for LangItems {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ (self.root as usize).hash(state);
+ (self.em as usize).hash(state);
+ (self.dir as usize).hash(state);
+ self.space.hash(state);
+ self.linebreak.hash(state);
+ self.text.hash(state);
+ self.smart_quote.hash(state);
+ self.parbreak.hash(state);
+ self.strong.hash(state);
+ self.emph.hash(state);
+ self.raw.hash(state);
+ self.link.hash(state);
+ self.ref_.hash(state);
+ self.heading.hash(state);
+ self.list_item.hash(state);
+ self.enum_item.hash(state);
+ self.desc_item.hash(state);
+ self.math.hash(state);
+ self.math_atom.hash(state);
+ self.math_script.hash(state);
+ self.math_frac.hash(state);
+ self.math_align.hash(state);
+ }
+}
diff --git a/src/model/methods.rs b/src/model/methods.rs
index 07cbb822..26d27dfa 100644
--- a/src/model/methods.rs
+++ b/src/model/methods.rs
@@ -97,7 +97,7 @@ pub fn call(
},
Value::Func(func) => match method {
- "with" => Value::Func(func.clone().with(args.take())),
+ "with" => Value::Func(func.with(args.take())),
_ => return missing(),
},
diff --git a/src/model/mod.rs b/src/model/mod.rs
index b4f8f653..fdebce0a 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -1,6 +1,8 @@
//! Layout and computation model.
#[macro_use]
+mod items;
+#[macro_use]
mod cast;
#[macro_use]
mod array;
@@ -16,12 +18,11 @@ mod args;
mod content;
mod eval;
mod func;
+mod methods;
+mod ops;
mod scope;
mod vm;
-pub mod methods;
-pub mod ops;
-
pub use self::str::*;
pub use args::*;
pub use array::*;
@@ -30,6 +31,7 @@ pub use content::*;
pub use dict::*;
pub use eval::*;
pub use func::*;
+pub use items::*;
pub use scope::*;
pub use styles::*;
pub use value::*;
diff --git a/src/model/ops.rs b/src/model/ops.rs
index 7eb814c1..ee126b03 100644
--- a/src/model/ops.rs
+++ b/src/model/ops.rs
@@ -1,12 +1,9 @@
//! Operations on values.
-use std::cmp::Ordering;
-
-use super::{Node, Regex, Smart, Value};
+use super::{Regex, Smart, Value};
use crate::diag::StrResult;
-use crate::geom::{Axes, Axis, Length, Numeric, Rel};
-use crate::library::text::TextNode;
-use crate::library::{RawAlign, RawStroke};
+use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel};
+use std::cmp::Ordering;
use Value::*;
/// Bail with a type mismatch error.
@@ -22,8 +19,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(a, None) => a,
(None, b) => b,
(Str(a), Str(b)) => Str(a + b),
- (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
- (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
+ (Str(a), Content(b)) => Content(super::Content::text(a) + b),
+ (Content(a), Str(b)) => Content(a + super::Content::text(b)),
(Content(a), Content(b)) => Content(a + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
@@ -88,14 +85,14 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Str(a), Str(b)) => Str(a + b),
(Content(a), Content(b)) => Content(a + b),
- (Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
- (Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
+ (Content(a), Str(b)) => Content(a + super::Content::text(b)),
+ (Str(a), Content(b)) => Content(super::Content::text(a) + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
(Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
- Value::dynamic(RawStroke {
+ Value::dynamic(PartialStroke {
paint: Smart::Custom(color.into()),
thickness: Smart::Custom(thickness),
})
@@ -104,7 +101,7 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Dyn(a), Dyn(b)) => {
// 1D alignments can be summed into 2D alignments.
if let (Some(&a), Some(&b)) =
- (a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
+ (a.downcast::<GenAlign>(), b.downcast::<GenAlign>())
{
if a.axis() != b.axis() {
Value::dynamic(match a.axis() {
diff --git a/src/model/str.rs b/src/model/str.rs
index 843da9a8..4aa40c54 100644
--- a/src/model/str.rs
+++ b/src/model/str.rs
@@ -5,19 +5,23 @@ use std::ops::{Add, AddAssign, Deref};
use unicode_segmentation::UnicodeSegmentation;
-use super::{Array, Dict, Value};
+use super::{castable, dict, Array, Dict, Value};
use crate::diag::StrResult;
-use crate::library::RawAlign;
+use crate::geom::GenAlign;
use crate::util::EcoString;
/// Create a new [`Str`] from a format string.
-#[allow(unused_macros)]
-macro_rules! format_str {
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __format_str {
($($tts:tt)*) => {{
$crate::model::Str::from(format_eco!($($tts)*))
}};
}
+#[doc(inline)]
+pub use crate::__format_str as format_str;
+
/// An immutable reference counted string.
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Str(EcoString);
@@ -463,9 +467,9 @@ pub enum StrSide {
castable! {
StrSide,
Expected: "start or end",
- @align: RawAlign => match align {
- RawAlign::Start => Self::Start,
- RawAlign::End => Self::End,
+ @align: GenAlign => match align {
+ GenAlign::Start => Self::Start,
+ GenAlign::End => Self::End,
_ => Err("expected either `start` or `end`")?,
},
}
diff --git a/src/model/styles.rs b/src/model/styles.rs
index c58a1beb..24566b09 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -7,12 +7,11 @@ use std::sync::Arc;
use comemo::{Prehashed, Tracked};
-use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value};
+use super::{capability, Args, Content, Func, NodeId, Regex, Smart, Value};
use crate::diag::SourceResult;
-use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
-use crate::library::layout::PageNode;
-use crate::library::structure::{DescNode, EnumNode, ListNode};
-use crate::library::text::{ParNode, TextNode};
+use crate::geom::{
+ Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
+};
use crate::syntax::Spanned;
use crate::util::ReadableTypeId;
use crate::World;
@@ -111,9 +110,9 @@ impl StyleMap {
self
}
- /// The highest-level kind of of structure the map interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- self.0.iter().filter_map(|entry| entry.interruption()).max()
+ /// Whether this map contains styles for the given `node.`
+ pub fn interrupts<T: 'static>(&self) -> bool {
+ self.0.iter().any(|entry| entry.is_of(NodeId::of::<T>()))
}
}
@@ -132,17 +131,6 @@ impl Debug for StyleMap {
}
}
-/// Determines whether a style could interrupt some composable structure.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub enum Interruption {
- /// The style forces a list break.
- List,
- /// The style forces a paragraph break.
- Par,
- /// The style forces a page break.
- Page,
-}
-
/// An entry for a single style property, recipe or barrier.
#[derive(Clone, PartialEq, Hash)]
pub enum StyleEntry {
@@ -193,12 +181,12 @@ impl StyleEntry {
}
}
- /// The highest-level kind of structure the entry interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
+ /// Whether this entry contains styles for the given `node.`
+ pub fn is_of(&self, node: NodeId) -> bool {
match self {
- Self::Property(property) => property.interruption(),
- Self::Recipe(recipe) => recipe.interruption(),
- _ => None,
+ Self::Property(property) => property.is_of(node),
+ Self::Recipe(recipe) => recipe.is_of(node),
+ _ => false,
}
}
}
@@ -397,7 +385,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
type Item = &'a K::Value;
fn next(&mut self) -> Option<Self::Item> {
- while let Some(entry) = self.entries.next() {
+ for entry in &mut self.entries {
match entry {
StyleEntry::Property(property) => {
if let Some(value) = property.downcast::<K>() {
@@ -662,9 +650,9 @@ impl Property {
self.key == KeyId::of::<K>()
}
- /// Whether this property belongs to the node `T`.
- pub fn is_of<T: 'static>(&self) -> bool {
- self.node == NodeId::of::<T>()
+ /// Whether this property belongs to the node with the given id.
+ pub fn is_of(&self, node: NodeId) -> bool {
+ self.node == node
}
/// Access the property's value if it is of the given key.
@@ -690,22 +678,6 @@ impl Property {
pub fn make_scoped(&mut self) {
self.scoped = true;
}
-
- /// What kind of structure the property interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- if self.is_of::<PageNode>() {
- Some(Interruption::Page)
- } else if self.is_of::<ParNode>() {
- Some(Interruption::Par)
- } else if self.is_of::<ListNode>()
- || self.is_of::<EnumNode>()
- || self.is_of::<DescNode>()
- {
- Some(Interruption::List)
- } else {
- None
- }
- }
}
impl Debug for Property {
@@ -826,7 +798,7 @@ impl Resolve for Em {
if self.is_zero() {
Abs::zero()
} else {
- self.at(styles.get(TextNode::SIZE))
+ self.at(item!(em)(styles))
}
}
}
@@ -891,6 +863,30 @@ where
}
}
+impl Resolve for GenAlign {
+ type Output = Align;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ let dir = item!(dir)(styles);
+ match self {
+ Self::Start => dir.start().into(),
+ Self::End => dir.end().into(),
+ Self::Specific(align) => align,
+ }
+ }
+}
+
+impl Resolve for PartialStroke {
+ type Output = PartialStroke<Abs>;
+
+ fn resolve(self, styles: StyleChain) -> Self::Output {
+ PartialStroke {
+ paint: self.paint,
+ thickness: self.thickness.resolve(styles),
+ }
+ }
+}
+
/// A property that is folded to determine its final value.
pub trait Fold {
/// The type of the folded output.
@@ -970,6 +966,17 @@ impl Fold for Corners<Option<Rel<Abs>>> {
}
}
+impl Fold for PartialStroke<Abs> {
+ type Output = Self;
+
+ fn fold(self, outer: Self::Output) -> Self::Output {
+ Self {
+ paint: self.paint.or(outer.paint),
+ thickness: self.thickness.or(outer.thickness),
+ }
+ }
+}
+
/// A show rule recipe.
#[derive(Clone, PartialEq, Hash)]
pub struct Recipe {
@@ -1003,13 +1010,14 @@ impl Recipe {
}
(Target::Text(text), Pattern::Regex(regex)) => {
+ let make = world.config().items.text;
let mut result = vec![];
let mut cursor = 0;
for mat in regex.find_iter(text) {
let start = mat.start();
if cursor < start {
- result.push(TextNode(text[cursor .. start].into()).pack());
+ result.push(make(text[cursor .. start].into()));
}
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
@@ -1021,7 +1029,7 @@ impl Recipe {
}
if cursor < text.len() {
- result.push(TextNode(text[cursor ..].into()).pack());
+ result.push(make(text[cursor ..].into()));
}
Content::sequence(result)
@@ -1047,18 +1055,12 @@ impl Recipe {
Ok(self.func.v.call_detached(world, args)?.display(world))
}
- /// What kind of structure the property interrupts.
- pub fn interruption(&self) -> Option<Interruption> {
- if let Pattern::Node(id) = self.pattern {
- if id == NodeId::of::<ListNode>()
- || id == NodeId::of::<EnumNode>()
- || id == NodeId::of::<DescNode>()
- {
- return Some(Interruption::List);
- }
+ /// Whether this recipe is for the given node.
+ pub fn is_of(&self, node: NodeId) -> bool {
+ match self.pattern {
+ Pattern::Node(id) => id == node,
+ _ => false,
}
-
- None
}
}
diff --git a/src/model/value.rs b/src/model/value.rs
index d68f42a0..07719883 100644
--- a/src/model/value.rs
+++ b/src/model/value.rs
@@ -7,11 +7,10 @@ use std::sync::Arc;
use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher};
-use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str};
+use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Str};
use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
-use crate::library::text::TextNode;
-use crate::util::EcoString;
+use crate::util::{format_eco, EcoString};
use crate::World;
/// A computational value.
@@ -385,7 +384,7 @@ primitive! { Str: "string", Str }
primitive! { Content: "content",
Content,
None => Content::empty(),
- Str(text) => TextNode(text.into()).pack()
+ Str(text) => Content::text(text)
}
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
@@ -395,6 +394,7 @@ primitive! { Args: "arguments", Args }
#[cfg(test)]
mod tests {
use super::*;
+ use crate::model::{array, dict};
#[track_caller]
fn test(value: impl Into<Value>, exp: &str) {
diff --git a/src/model/vm.rs b/src/model/vm.rs
index 4de57d1c..db0bf77c 100644
--- a/src/model/vm.rs
+++ b/src/model/vm.rs
@@ -2,11 +2,11 @@ use std::path::PathBuf;
use comemo::Tracked;
-use super::{Content, Route, Scopes, Value};
-use crate::diag::{SourceError, StrResult};
+use super::{LangItems, Route, Scopes, Value};
+use crate::diag::{error, SourceError, StrResult};
use crate::syntax::{SourceId, Span};
-use crate::util::{EcoString, PathExt};
-use crate::{LangItems, World};
+use crate::util::PathExt;
+use crate::World;
/// A virtual machine.
pub struct Vm<'a> {
@@ -20,6 +20,8 @@ pub struct Vm<'a> {
pub scopes: Scopes<'a>,
/// A control flow event that is currently happening.
pub flow: Option<Flow>,
+ /// The language items.
+ pub items: LangItems,
}
impl<'a> Vm<'a> {
@@ -36,6 +38,7 @@ impl<'a> Vm<'a> {
location,
scopes,
flow: None,
+ items: world.config().items,
}
}
@@ -54,18 +57,6 @@ impl<'a> Vm<'a> {
Err("cannot access file system from here".into())
}
-
- /// The language items.
- pub fn items(&self) -> &LangItems {
- &self.world.config().items
- }
-
- /// Create text content.
- ///
- /// This is a shorthand for `(vm.items().text)(..)`.
- pub fn text(&self, text: impl Into<EcoString>) -> Content {
- (self.items().text)(text.into())
- }
}
/// A control flow event that occurred during evaluation.