mirror of
https://github.com/typst/typst
synced 2025-08-19 09:28:33 +08:00
New #[elem]
implementation
This commit is contained in:
parent
2f401f26e1
commit
533f17c28b
@ -15,9 +15,9 @@ use typst_utils::{fat, singleton, LazyHash, SmallBitSet};
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
elem, func, scope, ty, Context, Dict, Element, Fields, IntoValue, Label,
|
||||
NativeElement, Recipe, RecipeIndex, Repr, Selector, Str, Style, StyleChain, Styles,
|
||||
Value,
|
||||
elem, func, repr, scope, ty, Context, Dict, Element, Field, IntoValue, Label,
|
||||
NativeElement, Property, Recipe, RecipeIndex, Repr, Selector, SettableProperty, Str,
|
||||
Style, StyleChain, Styles, Value,
|
||||
};
|
||||
use crate::introspection::Location;
|
||||
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
|
||||
@ -372,6 +372,15 @@ impl Content {
|
||||
Self::sequence(std::iter::repeat_with(|| self.clone()).take(count))
|
||||
}
|
||||
|
||||
/// Sets a style property on the content.
|
||||
pub fn set<E, const I: u8>(self, field: Field<E, I>, value: E::Type) -> Self
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Debug + Clone + Hash + Send + Sync + 'static,
|
||||
{
|
||||
self.styled(Property::new(field, value))
|
||||
}
|
||||
|
||||
/// Style this content with a style entry.
|
||||
pub fn styled(mut self, style: impl Into<Style>) -> Self {
|
||||
if let Some(style_elem) = self.to_packed_mut::<StyledElem>() {
|
||||
@ -636,7 +645,18 @@ impl PartialEq for Content {
|
||||
|
||||
impl Repr for Content {
|
||||
fn repr(&self) -> EcoString {
|
||||
self.inner.elem.repr()
|
||||
self.inner.elem.repr().unwrap_or_else(|| {
|
||||
let fields = self
|
||||
.fields()
|
||||
.into_iter()
|
||||
.map(|(name, value)| eco_format!("{}: {}", name, value.repr()))
|
||||
.collect::<Vec<_>>();
|
||||
eco_format!(
|
||||
"{}{}",
|
||||
self.elem().name(),
|
||||
repr::pretty_array_like(&fields, false),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -718,15 +738,29 @@ impl Serialize for Content {
|
||||
}
|
||||
|
||||
/// The trait that combines all the other traits into a trait object.
|
||||
trait Bounds: Debug + Repr + Fields + Send + Sync + 'static {
|
||||
trait Bounds: Debug + Send + Sync + 'static {
|
||||
fn dyn_type_id(&self) -> TypeId;
|
||||
fn dyn_elem(&self) -> Element;
|
||||
fn dyn_clone(&self, inner: &Inner<dyn Bounds>, span: Span) -> Content;
|
||||
fn dyn_hash(&self, hasher: &mut dyn Hasher);
|
||||
fn dyn_eq(&self, other: &Content) -> bool;
|
||||
|
||||
fn has(&self, id: u8) -> bool;
|
||||
fn field(&self, id: u8) -> Result<Value, FieldAccessError>;
|
||||
fn field_with_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError>;
|
||||
fn materialize(&mut self, styles: StyleChain);
|
||||
fn fields(&self) -> Dict;
|
||||
fn repr(&self) -> Option<EcoString>;
|
||||
}
|
||||
|
||||
impl<T: NativeElement> Bounds for T {
|
||||
impl<T> Bounds for T
|
||||
where
|
||||
T: NativeElement,
|
||||
{
|
||||
fn dyn_type_id(&self) -> TypeId {
|
||||
TypeId::of::<Self>()
|
||||
}
|
||||
@ -756,7 +790,60 @@ impl<T: NativeElement> Bounds for T {
|
||||
let Some(other) = other.to_packed::<Self>() else {
|
||||
return false;
|
||||
};
|
||||
*self == **other
|
||||
if let Some(f) = T::ELEMENT.eq {
|
||||
f(self, other)
|
||||
} else {
|
||||
T::ELEMENT.fields.iter().all(|field| (field.eq)(self, other))
|
||||
}
|
||||
}
|
||||
|
||||
fn has(&self, id: u8) -> bool {
|
||||
match T::ELEMENT.get(id) {
|
||||
Some(data) => (data.has)(self),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn field(&self, id: u8) -> Result<Value, FieldAccessError> {
|
||||
match T::ELEMENT.get(id) {
|
||||
Some(data) => (data.get)(self).ok_or(FieldAccessError::Unset),
|
||||
None => Err(FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_with_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError> {
|
||||
match T::ELEMENT.get(id) {
|
||||
Some(data) => {
|
||||
(data.get_with_styles)(self, styles).ok_or(FieldAccessError::Unset)
|
||||
}
|
||||
None => Err(FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize(&mut self, styles: StyleChain) {
|
||||
for field in T::ELEMENT.fields {
|
||||
(field.materialize)(self, styles);
|
||||
}
|
||||
}
|
||||
|
||||
fn fields(&self) -> Dict {
|
||||
let mut dict = Dict::new();
|
||||
for field in T::ELEMENT.fields {
|
||||
if let Some(value) = (field.get)(self) {
|
||||
dict.insert(field.name.into(), value);
|
||||
}
|
||||
}
|
||||
dict
|
||||
}
|
||||
|
||||
fn repr(&self) -> Option<EcoString> {
|
||||
// Returns None if the element doesn't have a special `Repr` impl.
|
||||
// Then, we use a generic one based on `Self::fields`.
|
||||
T::ELEMENT.repr.map(|f| f(self))
|
||||
}
|
||||
}
|
||||
|
||||
@ -767,7 +854,7 @@ impl Hash for dyn Bounds {
|
||||
}
|
||||
|
||||
/// A packed element of a static type.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct Packed<T: NativeElement>(
|
||||
/// Invariant: Must be of type `T`.
|
||||
@ -899,8 +986,20 @@ impl<T: NativeElement + Debug> Debug for Packed<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NativeElement> PartialEq for Packed<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NativeElement> Hash for Packed<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of content.
|
||||
#[elem(Debug, Repr, PartialEq)]
|
||||
#[elem(Debug, Repr)]
|
||||
pub struct SequenceElem {
|
||||
/// The elements.
|
||||
#[required]
|
||||
@ -922,19 +1021,13 @@ impl Default for SequenceElem {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for SequenceElem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.children.iter().eq(other.children.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for SequenceElem {
|
||||
fn repr(&self) -> EcoString {
|
||||
if self.children.is_empty() {
|
||||
"[]".into()
|
||||
} else {
|
||||
let elements = crate::foundations::repr::pretty_array_like(
|
||||
&self.children.iter().map(|c| c.inner.elem.repr()).collect::<Vec<_>>(),
|
||||
&self.children.iter().map(|c| c.repr()).collect::<Vec<_>>(),
|
||||
false,
|
||||
);
|
||||
eco_format!("sequence{}", elements)
|
||||
@ -985,7 +1078,6 @@ pub trait PlainText {
|
||||
pub enum FieldAccessError {
|
||||
Unknown,
|
||||
Unset,
|
||||
Internal,
|
||||
}
|
||||
|
||||
impl FieldAccessError {
|
||||
@ -1003,12 +1095,6 @@ impl FieldAccessError {
|
||||
field.repr()
|
||||
)
|
||||
}
|
||||
FieldAccessError::Internal => {
|
||||
eco_format!(
|
||||
"internal error when accessing field {} in {elem_name} – this is a bug",
|
||||
field.repr()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,9 @@ use std::any::TypeId;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
|
||||
use ecow::EcoString;
|
||||
use smallvec::SmallVec;
|
||||
@ -14,10 +15,11 @@ use typst_utils::Static;
|
||||
use crate::diag::SourceResult;
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
cast, Args, Content, Dict, FieldAccessError, Func, ParamInfo, Repr, Scope, Selector,
|
||||
cast, Args, CastInfo, Container, Content, FieldAccessError, Fold, Func, IntoValue,
|
||||
NativeScope, Packed, ParamInfo, Property, Reflect, Repr, Resolve, Scope, Selector,
|
||||
StyleChain, Styles, Value,
|
||||
};
|
||||
use crate::text::{Lang, Region};
|
||||
use crate::text::{Lang, LocalName, Region};
|
||||
|
||||
/// A document element.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
@ -169,19 +171,15 @@ cast! {
|
||||
|
||||
/// A Typst element that is defined by a native Rust type.
|
||||
pub trait NativeElement:
|
||||
Debug
|
||||
+ Clone
|
||||
+ PartialEq
|
||||
+ Hash
|
||||
+ Construct
|
||||
+ Set
|
||||
+ Capable
|
||||
+ Fields
|
||||
+ Repr
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
Debug + Clone + Hash + Construct + Set + Capable + Send + Sync + 'static
|
||||
{
|
||||
const ELEMENT: TypedElementData<Self>;
|
||||
|
||||
/// Get the element data for the native Rust element.
|
||||
fn data() -> &'static NativeElementData
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Get the element for the native Rust element.
|
||||
fn elem() -> Element
|
||||
where
|
||||
@ -197,11 +195,12 @@ pub trait NativeElement:
|
||||
{
|
||||
Content::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the element data for the native Rust element.
|
||||
fn data() -> &'static NativeElementData
|
||||
where
|
||||
Self: Sized;
|
||||
impl<T: NativeElement> IntoValue for T {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Content(self.pack())
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to cast an element to a trait object for a trait it implements.
|
||||
@ -215,38 +214,6 @@ pub unsafe trait Capable {
|
||||
fn vtable(capability: TypeId) -> Option<NonNull<()>>;
|
||||
}
|
||||
|
||||
/// Defines how fields of an element are accessed.
|
||||
pub trait Fields {
|
||||
/// An enum with the fields of the element.
|
||||
type Enum
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Whether the element has the given field set.
|
||||
fn has(&self, id: u8) -> bool;
|
||||
|
||||
/// Get the field with the given field ID.
|
||||
fn field(&self, id: u8) -> Result<Value, FieldAccessError>;
|
||||
|
||||
/// Get the field with the given ID in the presence of styles.
|
||||
fn field_with_styles(
|
||||
&self,
|
||||
id: u8,
|
||||
styles: StyleChain,
|
||||
) -> Result<Value, FieldAccessError>;
|
||||
|
||||
/// Get the field with the given ID from the styles.
|
||||
fn field_from_styles(id: u8, styles: StyleChain) -> Result<Value, FieldAccessError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Resolve all fields with the styles and save them in-place.
|
||||
fn materialize(&mut self, styles: StyleChain);
|
||||
|
||||
/// Get the fields of the element.
|
||||
fn fields(&self) -> Dict;
|
||||
}
|
||||
|
||||
/// An element's constructor function.
|
||||
pub trait Construct {
|
||||
/// Construct an element from the arguments.
|
||||
@ -266,48 +233,6 @@ pub trait Set {
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Defines a native element.
|
||||
#[derive(Debug)]
|
||||
pub struct NativeElementData {
|
||||
/// The element's normal name (e.g. `align`), as exposed to Typst.
|
||||
pub name: &'static str,
|
||||
/// The element's title case name (e.g. `Align`).
|
||||
pub title: &'static str,
|
||||
/// The documentation for this element as a string.
|
||||
pub docs: &'static str,
|
||||
/// A list of alternate search terms for this element.
|
||||
pub keywords: &'static [&'static str],
|
||||
/// The constructor for this element (see [`Construct`]).
|
||||
pub construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>,
|
||||
/// Executes this element's set rule (see [`Set`]).
|
||||
pub set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>,
|
||||
/// Gets the vtable for one of this element's capabilities
|
||||
/// (see [`Capable`]).
|
||||
pub vtable: fn(capability: TypeId) -> Option<NonNull<()>>,
|
||||
/// Gets the numeric index of this field by its name.
|
||||
pub field_id: fn(name: &str) -> Option<u8>,
|
||||
/// Gets the name of a field by its numeric index.
|
||||
pub field_name: fn(u8) -> Option<&'static str>,
|
||||
/// Get the field with the given ID in the presence of styles (see [`Fields`]).
|
||||
pub field_from_styles: fn(u8, StyleChain) -> Result<Value, FieldAccessError>,
|
||||
/// Gets the localized name for this element (see [`LocalName`][crate::text::LocalName]).
|
||||
pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
|
||||
pub scope: LazyLock<Scope>,
|
||||
/// A list of parameter information for each field.
|
||||
pub params: LazyLock<Vec<ParamInfo>>,
|
||||
}
|
||||
|
||||
impl From<&'static NativeElementData> for Element {
|
||||
fn from(data: &'static NativeElementData) -> Self {
|
||||
Self(Static(data))
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
&'static NativeElementData,
|
||||
self => Element::from(self).into_value(),
|
||||
}
|
||||
|
||||
/// Synthesize fields on an element. This happens before execution of any show
|
||||
/// rule.
|
||||
pub trait Synthesize {
|
||||
@ -331,3 +256,697 @@ pub trait ShowSet {
|
||||
/// that should work even in the face of a user-defined show rule.
|
||||
fn show_set(&self, styles: StyleChain) -> Styles;
|
||||
}
|
||||
|
||||
/// Type-erased metadata and routines for a native element.
|
||||
#[derive(Debug)]
|
||||
pub struct NativeElementData {
|
||||
/// The element's normal name (e.g. `align`), as exposed to Typst.
|
||||
pub name: &'static str,
|
||||
/// The element's title case name (e.g. `Align`).
|
||||
pub title: &'static str,
|
||||
/// The documentation for this element as a string.
|
||||
pub docs: &'static str,
|
||||
/// A list of alternate search terms for this element.
|
||||
pub keywords: &'static [&'static str],
|
||||
/// The constructor for this element (see [`Construct`]).
|
||||
pub construct: fn(&mut Engine, &mut Args) -> SourceResult<Content>,
|
||||
/// Executes this element's set rule (see [`Set`]).
|
||||
pub set: fn(&mut Engine, &mut Args) -> SourceResult<Styles>,
|
||||
/// Gets the vtable for one of this element's capabilities
|
||||
/// (see [`Capable`]).
|
||||
pub vtable: fn(capability: TypeId) -> Option<NonNull<()>>,
|
||||
/// Gets the numeric index of this field by its name.
|
||||
pub field_id: fn(name: &str) -> Option<u8>,
|
||||
/// Gets the name of a field by its numeric index.
|
||||
pub field_name: fn(u8) -> Option<&'static str>,
|
||||
/// Get the field with the given ID in the presence of styles.
|
||||
pub field_from_styles: fn(u8, StyleChain) -> Result<Value, FieldAccessError>,
|
||||
/// Gets the localized name for this element (see [`LocalName`][crate::text::LocalName]).
|
||||
pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
|
||||
/// Associated definitions of the element.
|
||||
pub scope: LazyLock<Scope>,
|
||||
/// A list of parameter information for each field.
|
||||
pub params: LazyLock<Vec<ParamInfo>>,
|
||||
}
|
||||
|
||||
impl NativeElementData {
|
||||
/// Creates type-erased element data for the given element.
|
||||
pub const fn new<E>() -> Self
|
||||
where
|
||||
E: NativeElement,
|
||||
{
|
||||
Self {
|
||||
name: E::ELEMENT.name,
|
||||
title: E::ELEMENT.title,
|
||||
docs: E::ELEMENT.docs,
|
||||
keywords: E::ELEMENT.keywords,
|
||||
local_name: E::ELEMENT.local_name,
|
||||
field_from_styles: |i, styles| match E::ELEMENT.get(i) {
|
||||
Some(field) => {
|
||||
(field.get_from_styles)(styles).ok_or(FieldAccessError::Unknown)
|
||||
}
|
||||
None => Err(FieldAccessError::Unknown),
|
||||
},
|
||||
field_id: E::ELEMENT.field_id,
|
||||
field_name: |i| E::ELEMENT.get(i).map(|data| data.name),
|
||||
construct: <E as Construct>::construct,
|
||||
set: <E as Set>::set,
|
||||
vtable: <E as Capable>::vtable,
|
||||
scope: LazyLock::new(E::ELEMENT.scope),
|
||||
params: LazyLock::new(|| {
|
||||
E::ELEMENT
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| !field.synthesized)
|
||||
.map(|field| ParamInfo {
|
||||
name: field.name,
|
||||
docs: field.docs,
|
||||
input: (field.input)(),
|
||||
default: field.default,
|
||||
positional: field.positional,
|
||||
named: !field.positional,
|
||||
variadic: field.variadic,
|
||||
required: field.required,
|
||||
settable: field.settable,
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static NativeElementData> for Element {
|
||||
fn from(data: &'static NativeElementData) -> Self {
|
||||
Self(Static(data))
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
&'static NativeElementData,
|
||||
self => Element::from(self).into_value(),
|
||||
}
|
||||
|
||||
/// Type-aware metadata and routines for a native element.
|
||||
pub struct TypedElementData<E: NativeElement> {
|
||||
pub name: &'static str,
|
||||
pub title: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub keywords: &'static [&'static str],
|
||||
pub fields: &'static [TypedFieldData<E>],
|
||||
pub field_id: fn(name: &str) -> Option<u8>,
|
||||
pub repr: Option<fn(&E) -> EcoString>,
|
||||
pub eq: Option<fn(&E, &E) -> bool>,
|
||||
pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
|
||||
pub scope: fn() -> Scope,
|
||||
}
|
||||
|
||||
impl<E: NativeElement> TypedElementData<E> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
title: &'static str,
|
||||
docs: &'static str,
|
||||
fields: &'static [TypedFieldData<E>],
|
||||
field_id: fn(name: &str) -> Option<u8>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
title,
|
||||
docs,
|
||||
keywords: &[],
|
||||
fields,
|
||||
field_id,
|
||||
repr: None,
|
||||
eq: None,
|
||||
local_name: None,
|
||||
scope: || Scope::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches search keywords for the documentation.
|
||||
pub const fn with_keywords(self, keywords: &'static [&'static str]) -> Self {
|
||||
Self { keywords, ..self }
|
||||
}
|
||||
|
||||
/// Takes a `Repr` impl into account.
|
||||
pub const fn with_repr(self) -> Self
|
||||
where
|
||||
E: Repr,
|
||||
{
|
||||
Self { repr: Some(E::repr), ..self }
|
||||
}
|
||||
|
||||
/// Takes a `PartialEq` impl into account.
|
||||
pub const fn with_partial_eq(self) -> Self
|
||||
where
|
||||
E: PartialEq,
|
||||
{
|
||||
Self { eq: Some(E::eq), ..self }
|
||||
}
|
||||
|
||||
/// Takes a `LocalName` impl into account.
|
||||
pub const fn with_local_name(self) -> Self
|
||||
where
|
||||
Packed<E>: LocalName,
|
||||
{
|
||||
Self {
|
||||
local_name: Some(<Packed<E> as LocalName>::local_name),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a `NativeScope` impl into account.
|
||||
pub const fn with_scope(self) -> Self
|
||||
where
|
||||
E: NativeScope,
|
||||
{
|
||||
Self { scope: || E::scope(), ..self }
|
||||
}
|
||||
|
||||
/// Retrieves the field with the given index.
|
||||
pub fn get(&self, i: u8) -> Option<&'static TypedFieldData<E>> {
|
||||
self.fields.get(usize::from(i))
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a field and routines that is aware of concrete element, but
|
||||
/// abstracts over what kind of field it is (required / variadic / synthesized /
|
||||
/// settable / ghost).
|
||||
pub struct TypedFieldData<E: NativeElement> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub positional: bool,
|
||||
pub variadic: bool,
|
||||
pub required: bool,
|
||||
pub settable: bool,
|
||||
pub synthesized: bool,
|
||||
pub input: fn() -> CastInfo,
|
||||
pub default: Option<fn() -> Value>,
|
||||
pub has: fn(content: &E) -> bool,
|
||||
pub get: fn(content: &E) -> Option<Value>,
|
||||
pub get_with_styles: fn(content: &E, StyleChain) -> Option<Value>,
|
||||
pub get_from_styles: fn(StyleChain) -> Option<Value>,
|
||||
pub materialize: fn(content: &mut E, styles: StyleChain),
|
||||
pub eq: fn(a: &E, b: &E) -> bool,
|
||||
}
|
||||
|
||||
impl<E: NativeElement> TypedFieldData<E> {
|
||||
/// Creates type-erased metadata and routines for a `#[required]` field.
|
||||
pub const fn required<const I: u8>() -> Self
|
||||
where
|
||||
E: RequiredField<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
name: E::FIELD.name,
|
||||
docs: E::FIELD.docs,
|
||||
positional: true,
|
||||
required: true,
|
||||
variadic: false,
|
||||
settable: false,
|
||||
synthesized: false,
|
||||
input: || <E::Type as Reflect>::input(),
|
||||
default: None,
|
||||
has: |_| true,
|
||||
get: |elem| Some((E::FIELD.get)(elem).clone().into_value()),
|
||||
get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()),
|
||||
get_from_styles: |_| None,
|
||||
materialize: |_, _| {},
|
||||
eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a `#[variadic]` field.
|
||||
pub const fn variadic<const I: u8>() -> Self
|
||||
where
|
||||
E: RequiredField<I>,
|
||||
E::Type: Container + IntoValue + PartialEq,
|
||||
<E::Type as Container>::Inner: Reflect,
|
||||
{
|
||||
Self {
|
||||
name: E::FIELD.name,
|
||||
docs: E::FIELD.docs,
|
||||
positional: true,
|
||||
required: true,
|
||||
variadic: true,
|
||||
settable: false,
|
||||
synthesized: false,
|
||||
input: || <<E::Type as Container>::Inner as Reflect>::input(),
|
||||
default: None,
|
||||
has: |_| true,
|
||||
get: |elem| Some((E::FIELD.get)(elem).clone().into_value()),
|
||||
get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()),
|
||||
get_from_styles: |_| None,
|
||||
materialize: |_, _| {},
|
||||
eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a `#[synthesized]` field.
|
||||
pub const fn synthesized<const I: u8>() -> Self
|
||||
where
|
||||
E: SynthesizedField<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
name: E::FIELD.name,
|
||||
docs: E::FIELD.docs,
|
||||
positional: false,
|
||||
required: false,
|
||||
variadic: false,
|
||||
settable: false,
|
||||
synthesized: true,
|
||||
input: || <E::Type as Reflect>::input(),
|
||||
default: None,
|
||||
has: |elem| (E::FIELD.get)(elem).is_some(),
|
||||
get: |elem| (E::FIELD.get)(elem).clone().map(|v| v.into_value()),
|
||||
get_with_styles: |elem, _| {
|
||||
(E::FIELD.get)(elem).clone().map(|v| v.into_value())
|
||||
},
|
||||
get_from_styles: |_| None,
|
||||
materialize: |_, _| {},
|
||||
// Synthesized fields don't affect equality.
|
||||
eq: |_, _| true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a normal settable field.
|
||||
pub const fn settable<const I: u8>() -> Self
|
||||
where
|
||||
E: SettableField<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
name: E::FIELD.property.name,
|
||||
docs: E::FIELD.property.docs,
|
||||
positional: E::FIELD.property.positional,
|
||||
required: false,
|
||||
variadic: false,
|
||||
settable: true,
|
||||
synthesized: false,
|
||||
input: || <E::Type as Reflect>::input(),
|
||||
default: Some(|| E::PROPERTY.default().into_value()),
|
||||
has: |elem| (E::FIELD.get)(elem).is_set(),
|
||||
get: |elem| (E::FIELD.get)(elem).as_option().clone().map(|v| v.into_value()),
|
||||
get_with_styles: |elem, styles| {
|
||||
Some((E::FIELD.get)(elem).get(styles).into_value())
|
||||
},
|
||||
get_from_styles: |styles| Some(styles.get::<E, I>(Field::new()).into_value()),
|
||||
materialize: |elem, styles| {
|
||||
if !(E::FIELD.get)(elem).is_set() {
|
||||
(E::FIELD.get_mut)(elem).set(styles.get::<E, I>(Field::new()));
|
||||
}
|
||||
},
|
||||
eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for a `#[ghost]` field.
|
||||
pub const fn ghost<const I: u8>() -> Self
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Reflect + IntoValue + PartialEq,
|
||||
{
|
||||
Self {
|
||||
name: E::PROPERTY.name,
|
||||
docs: E::PROPERTY.docs,
|
||||
positional: E::PROPERTY.positional,
|
||||
required: false,
|
||||
variadic: false,
|
||||
settable: true,
|
||||
synthesized: false,
|
||||
input: || <E::Type as Reflect>::input(),
|
||||
default: Some(|| E::PROPERTY.default().into_value()),
|
||||
has: |_| false,
|
||||
get: |_| None,
|
||||
get_with_styles: |_, styles| {
|
||||
Some(styles.get::<E, I>(Field::new()).into_value())
|
||||
},
|
||||
get_from_styles: |styles| Some(styles.get::<E, I>(Field::new()).into_value()),
|
||||
materialize: |_, _| {},
|
||||
eq: |_, _| true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates type-erased metadata and routines for an `#[external]` field.
|
||||
pub const fn external<const I: u8>() -> Self
|
||||
where
|
||||
E: ExternalField<I>,
|
||||
E::Type: Reflect + IntoValue,
|
||||
{
|
||||
Self {
|
||||
name: E::FIELD.name,
|
||||
docs: E::FIELD.docs,
|
||||
positional: false,
|
||||
required: false,
|
||||
variadic: false,
|
||||
settable: false,
|
||||
synthesized: false,
|
||||
input: || <E::Type as Reflect>::input(),
|
||||
default: Some(|| (E::FIELD.default)().into_value()),
|
||||
has: |_| false,
|
||||
get: |_| None,
|
||||
get_with_styles: |_, _| None,
|
||||
get_from_styles: |_| None,
|
||||
materialize: |_, _| {},
|
||||
eq: |_, _| true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A field that is present on every instance of the element.
|
||||
pub trait RequiredField<const I: u8>: NativeElement {
|
||||
type Type: Clone;
|
||||
|
||||
const FIELD: RequiredFieldData<Self, I>;
|
||||
}
|
||||
|
||||
/// Metadata and routines for a [`RequiredField`].
|
||||
pub struct RequiredFieldData<E: RequiredField<I>, const I: u8> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub get: fn(&E) -> &E::Type,
|
||||
pub get_mut: fn(&mut E) -> &mut E::Type,
|
||||
}
|
||||
|
||||
impl<E: RequiredField<I>, const I: u8> RequiredFieldData<E, I> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
docs: &'static str,
|
||||
get: fn(&E) -> &E::Type,
|
||||
get_mut: fn(&mut E) -> &mut E::Type,
|
||||
) -> Self {
|
||||
Self { name, docs, get, get_mut }
|
||||
}
|
||||
}
|
||||
|
||||
/// A field that is initially unset, but may be set through a [`Synthesize`]
|
||||
/// implementation.
|
||||
pub trait SynthesizedField<const I: u8>: NativeElement {
|
||||
type Type: Clone;
|
||||
|
||||
const FIELD: SynthesizedFieldData<Self, I>;
|
||||
}
|
||||
|
||||
/// Metadata and routines for a [`SynthesizedField`].
|
||||
pub struct SynthesizedFieldData<E: SynthesizedField<I>, const I: u8> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub get: fn(&E) -> &Option<E::Type>,
|
||||
pub get_mut: fn(&mut E) -> &mut Option<E::Type>,
|
||||
}
|
||||
|
||||
impl<E: SynthesizedField<I>, const I: u8> SynthesizedFieldData<E, I> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
docs: &'static str,
|
||||
get: fn(&E) -> &Option<E::Type>,
|
||||
get_mut: fn(&mut E) -> &mut Option<E::Type>,
|
||||
) -> Self {
|
||||
Self { name, docs, get, get_mut }
|
||||
}
|
||||
}
|
||||
|
||||
/// A field that has a default value and can be configured via a set rule, but
|
||||
/// can also present on elements and be present in the constructor.
|
||||
pub trait SettableField<const I: u8>: NativeElement {
|
||||
type Type: Clone;
|
||||
|
||||
const FIELD: SettableFieldData<Self, I>;
|
||||
}
|
||||
|
||||
/// Metadata and routines for a [`SettableField`].
|
||||
pub struct SettableFieldData<E: SettableField<I>, const I: u8> {
|
||||
pub get: fn(&E) -> &Settable<E, I>,
|
||||
pub get_mut: fn(&mut E) -> &mut Settable<E, I>,
|
||||
pub property: SettablePropertyData<E, I>,
|
||||
}
|
||||
|
||||
impl<E: SettableField<I>, const I: u8> SettableFieldData<E, I> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
docs: &'static str,
|
||||
positional: bool,
|
||||
get: fn(&E) -> &Settable<E, I>,
|
||||
get_mut: fn(&mut E) -> &mut Settable<E, I>,
|
||||
default: fn() -> E::Type,
|
||||
slot: fn() -> &'static OnceLock<E::Type>,
|
||||
) -> Self {
|
||||
Self {
|
||||
get,
|
||||
get_mut,
|
||||
property: SettablePropertyData::new(name, docs, positional, default, slot),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that the property is folded on every access. See the
|
||||
/// documentation of the [`Fold`] trait for more details.
|
||||
pub const fn with_fold(mut self) -> Self
|
||||
where
|
||||
E::Type: Fold,
|
||||
{
|
||||
self.property.fold = Some(E::Type::fold);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A field that has a default value and can be configured via a set rule, but
|
||||
/// is never present on elements.
|
||||
///
|
||||
/// This is provided for all `SettableField` impls through a blanket impl. In
|
||||
/// the case of `#[ghost]` fields, which only live in the style chain and not in
|
||||
/// elements, it is also implemented manually.
|
||||
pub trait SettableProperty<const I: u8>: NativeElement {
|
||||
type Type: Clone;
|
||||
|
||||
const PROPERTY: SettablePropertyData<Self, I>;
|
||||
}
|
||||
|
||||
impl<T, const I: u8> SettableProperty<I> for T
|
||||
where
|
||||
T: SettableField<I>,
|
||||
{
|
||||
type Type = <Self as SettableField<I>>::Type;
|
||||
|
||||
const PROPERTY: SettablePropertyData<Self, I> =
|
||||
<Self as SettableField<I>>::FIELD.property;
|
||||
}
|
||||
|
||||
/// Metadata and routines for a [`SettableProperty`].
|
||||
pub struct SettablePropertyData<E: SettableProperty<I>, const I: u8> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub positional: bool,
|
||||
pub default: fn() -> E::Type,
|
||||
pub slot: fn() -> &'static OnceLock<E::Type>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fold: Option<fn(E::Type, E::Type) -> E::Type>,
|
||||
}
|
||||
|
||||
impl<E: SettableProperty<I>, const I: u8> SettablePropertyData<E, I> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
docs: &'static str,
|
||||
positional: bool,
|
||||
default: fn() -> E::Type,
|
||||
slot: fn() -> &'static OnceLock<E::Type>,
|
||||
) -> Self {
|
||||
Self { name, docs, positional, default, slot, fold: None }
|
||||
}
|
||||
|
||||
/// Ensures that the property is folded on every access. See the
|
||||
/// documentation of the [`Fold`] trait for more details.
|
||||
pub const fn with_fold(self) -> Self
|
||||
where
|
||||
E::Type: Fold,
|
||||
{
|
||||
Self { fold: Some(E::Type::fold), ..self }
|
||||
}
|
||||
|
||||
/// Produces an instance of the property's default value.
|
||||
pub fn default(&self) -> E::Type
|
||||
where
|
||||
E::Type: Clone,
|
||||
{
|
||||
// Avoid recreating an expensive instance over and over, but also
|
||||
// avoid unnecessary lazy initialization for cheap types.
|
||||
if std::mem::needs_drop::<E::Type>() {
|
||||
self.default_ref().clone()
|
||||
} else {
|
||||
(self.default)()
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a static reference to this property's default value.
|
||||
pub fn default_ref(&self) -> &'static E::Type {
|
||||
(self.slot)().get_or_init(self.default)
|
||||
}
|
||||
}
|
||||
|
||||
/// A settable property that can be accessed by reference (because it is not
|
||||
/// folded).
|
||||
pub trait RefableProperty<const I: u8>: SettableProperty<I> {}
|
||||
|
||||
/// A settable field of an element.
|
||||
///
|
||||
/// The field can be in two states: Unset or present.
|
||||
#[derive(Copy, Clone, Hash)]
|
||||
pub struct Settable<E: NativeElement, const I: u8>(Option<E::Type>)
|
||||
where
|
||||
E: SettableProperty<I>;
|
||||
|
||||
impl<E: NativeElement, const I: u8> Settable<E, I>
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
{
|
||||
/// Creates a new unset instance.
|
||||
pub fn new() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
|
||||
/// Sets the instance to a value.
|
||||
pub fn set(&mut self, value: E::Type) {
|
||||
self.0 = Some(value);
|
||||
}
|
||||
|
||||
/// Clears the value from the instance.
|
||||
pub fn unset(&mut self) {
|
||||
self.0 = None;
|
||||
}
|
||||
|
||||
/// Views the type as an [`Option`] which is `Some` if the type is set
|
||||
/// and `None` if it is unset.
|
||||
pub fn as_option(&self) -> &Option<E::Type> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Views the type as a mutable [`Option`].
|
||||
pub fn as_option_mut(&mut self) -> &mut Option<E::Type> {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
/// Whether the field is set.
|
||||
pub fn is_set(&self) -> bool {
|
||||
self.0.is_some()
|
||||
}
|
||||
|
||||
/// Retrieves the value given styles. The styles are used if the value is
|
||||
/// unset or if it needs folding.
|
||||
pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type
|
||||
where
|
||||
E::Type: Clone,
|
||||
{
|
||||
styles.get_with::<E, I>(Field::new(), self.0.as_ref())
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the value given styles. Not possible if the
|
||||
/// value needs folding.
|
||||
pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type
|
||||
where
|
||||
E: RefableProperty<I>,
|
||||
{
|
||||
styles.get_ref_with::<E, I>(Field::new(), self.0.as_ref())
|
||||
}
|
||||
|
||||
/// Retrieves the value and then immediately [resolves](Resolve) it.
|
||||
pub fn resolve<'a>(&'a self, styles: StyleChain<'a>) -> <E::Type as Resolve>::Output
|
||||
where
|
||||
E::Type: Resolve + Clone,
|
||||
{
|
||||
self.get(styles).resolve(styles)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: NativeElement, const I: u8> Debug for Settable<E, I>
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: NativeElement, const I: u8> Default for Settable<E, I>
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: NativeElement, const I: u8> From<Option<E::Type>> for Settable<E, I>
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
{
|
||||
fn from(value: Option<E::Type>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// An accessor for the `I`-th field of the element `E`. Values of this type are
|
||||
/// generated for each field of an element can be used to interact with this
|
||||
/// field programmatically, for example to access the style chain, as in
|
||||
/// `styles.get(TextElem::size)`.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Field<E: NativeElement, const I: u8>(pub PhantomData<E>);
|
||||
|
||||
impl<E: NativeElement, const I: u8> Field<E, I> {
|
||||
/// Creates a new zero-sized accessor.
|
||||
pub const fn new() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
|
||||
/// The index of the projected field.
|
||||
pub const fn index(self) -> u8 {
|
||||
I
|
||||
}
|
||||
|
||||
/// Creates a dynamic property instance for this field.
|
||||
///
|
||||
/// Prefer [`Content::set`] or [`Styles::set`] when working with existing
|
||||
/// content or style value.
|
||||
pub fn set(self, value: E::Type) -> Property
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Debug + Clone + Hash + Send + Sync + 'static,
|
||||
{
|
||||
Property::new(self, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: NativeElement, const I: u8> Default for Field<E, I> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A field that is not actually there. It's only visible in the docs.
|
||||
pub trait ExternalField<const I: u8>: NativeElement {
|
||||
type Type;
|
||||
|
||||
const FIELD: ExternalFieldData<Self, I>;
|
||||
}
|
||||
|
||||
/// Metadata for an [`ExternalField`].
|
||||
pub struct ExternalFieldData<E: ExternalField<I>, const I: u8> {
|
||||
pub name: &'static str,
|
||||
pub docs: &'static str,
|
||||
pub default: fn() -> E::Type,
|
||||
}
|
||||
|
||||
impl<E: ExternalField<I>, const I: u8> ExternalFieldData<E, I> {
|
||||
/// Creates the data from its parts. This is called in the `#[elem]` macro.
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
docs: &'static str,
|
||||
default: fn() -> E::Type,
|
||||
) -> Self {
|
||||
Self { name, docs, default }
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ macro_rules! __select_where {
|
||||
let mut fields = ::smallvec::SmallVec::new();
|
||||
$(
|
||||
fields.push((
|
||||
<$ty as $crate::foundations::Fields>::Enum::$field as u8,
|
||||
<$ty>::$field.index(),
|
||||
$crate::foundations::IntoValue::into_value($value),
|
||||
));
|
||||
)*
|
||||
|
@ -12,8 +12,8 @@ use typst_utils::LazyHash;
|
||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
cast, ty, Content, Context, Element, Func, NativeElement, OneOrMultiple, Repr,
|
||||
Selector,
|
||||
cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple,
|
||||
RefableProperty, Repr, Selector, SettableProperty,
|
||||
};
|
||||
use crate::text::{FontFamily, FontList, TextElem};
|
||||
|
||||
@ -48,7 +48,16 @@ impl Styles {
|
||||
/// If the property needs folding and the value is already contained in the
|
||||
/// style map, `self` contributes the outer values and `value` is the inner
|
||||
/// one.
|
||||
pub fn set(&mut self, style: impl Into<Style>) {
|
||||
pub fn set<E, const I: u8>(&mut self, field: Field<E, I>, value: E::Type)
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Debug + Clone + Hash + Send + Sync + 'static,
|
||||
{
|
||||
self.push(Property::new(field, value));
|
||||
}
|
||||
|
||||
/// Add a new style to the list.
|
||||
pub fn push(&mut self, style: impl Into<Style>) {
|
||||
self.0.push(LazyHash::new(style.into()));
|
||||
}
|
||||
|
||||
@ -101,12 +110,12 @@ impl Styles {
|
||||
}
|
||||
|
||||
/// Whether there is a style for the given field of the given element.
|
||||
pub fn has<T: NativeElement>(&self, field: u8) -> bool {
|
||||
pub fn has<E: NativeElement, const I: u8>(&self, _: Field<E, I>) -> bool {
|
||||
let elem = E::ELEM;
|
||||
self.0
|
||||
.iter()
|
||||
.filter_map(|style| style.property())
|
||||
.any(|property| property.is_of(elem) && property.id == field)
|
||||
.any(|property| property.is_of(elem) && property.id == I)
|
||||
}
|
||||
|
||||
/// Set a font family composed of a preferred family and existing families
|
||||
@ -284,14 +293,14 @@ pub struct Property {
|
||||
|
||||
impl Property {
|
||||
/// Create a new property from a key-value pair.
|
||||
pub fn new<E, T>(id: u8, value: T) -> Self
|
||||
pub fn new<E, const I: u8>(_: Field<E, I>, value: E::Type) -> Self
|
||||
where
|
||||
E: NativeElement,
|
||||
T: Debug + Clone + Hash + Send + Sync + 'static,
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Debug + Clone + Hash + Send + Sync + 'static,
|
||||
{
|
||||
Self {
|
||||
elem: E::ELEM,
|
||||
id,
|
||||
id: I,
|
||||
value: Block::new(value),
|
||||
span: Span::detached(),
|
||||
liftable: false,
|
||||
@ -543,53 +552,79 @@ impl<'a> StyleChain<'a> {
|
||||
Chainable::chain(local, self)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get<T: Clone + 'static>(
|
||||
self,
|
||||
func: Element,
|
||||
id: u8,
|
||||
inherent: Option<&T>,
|
||||
default: impl Fn() -> T,
|
||||
) -> T {
|
||||
self.properties::<T>(func, id, inherent)
|
||||
.next()
|
||||
.cloned()
|
||||
.unwrap_or_else(default)
|
||||
/// Retrieves the value of the given field from the style chain.
|
||||
///
|
||||
/// A `Field` value is a zero-sized value that specifies which field of an
|
||||
/// element you want to retrieve on the type-system level. It also ensures
|
||||
/// that Rust can infer the correct return type.
|
||||
pub fn get<E, const I: u8>(self, field: Field<E, I>) -> E::Type
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
{
|
||||
self.get_with(field, None)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain,
|
||||
/// returning a borrowed value.
|
||||
pub fn get_ref<T: 'static>(
|
||||
/// Retrieves the value and then immediately [resolves](Resolve) it.
|
||||
pub fn resolve<E, const I: u8>(
|
||||
self,
|
||||
func: Element,
|
||||
id: u8,
|
||||
inherent: Option<&'a T>,
|
||||
default: impl Fn() -> &'a T,
|
||||
) -> &'a T {
|
||||
self.properties::<T>(func, id, inherent)
|
||||
.next()
|
||||
.unwrap_or_else(default)
|
||||
field: Field<E, I>,
|
||||
) -> <E::Type as Resolve>::Output
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
E::Type: Resolve,
|
||||
{
|
||||
self.get(field).resolve(self)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain, taking
|
||||
/// `Fold` implementations into account.
|
||||
pub fn get_folded<T: Fold + Clone + 'static>(
|
||||
/// Retrieves a reference to the value of the given field from the style
|
||||
/// chain.
|
||||
pub fn get_ref<E, const I: u8>(self, field: Field<E, I>) -> &'a E::Type
|
||||
where
|
||||
E: RefableProperty<I>,
|
||||
{
|
||||
self.get_ref_with(field, None)
|
||||
}
|
||||
|
||||
/// Retrieves a field, also taking into account the instance's value if any.
|
||||
pub(crate) fn get_with<E, const I: u8>(
|
||||
self,
|
||||
func: Element,
|
||||
id: u8,
|
||||
inherent: Option<&T>,
|
||||
default: impl Fn() -> T,
|
||||
) -> T {
|
||||
fn next<T: Fold>(
|
||||
_: Field<E, I>,
|
||||
inherent: Option<&'a E::Type>,
|
||||
) -> E::Type
|
||||
where
|
||||
E: SettableProperty<I>,
|
||||
{
|
||||
let mut properties = self.properties::<E::Type>(E::elem(), I, inherent);
|
||||
if let Some(fold) = E::PROPERTY.fold {
|
||||
fn next<T>(
|
||||
mut values: impl Iterator<Item = T>,
|
||||
default: &impl Fn() -> T,
|
||||
default: fn() -> T,
|
||||
fold: fn(T, T) -> T,
|
||||
) -> T {
|
||||
values
|
||||
.next()
|
||||
.map(|value| value.fold(next(values, default)))
|
||||
.map(|value| fold(value, next(values, default, fold)))
|
||||
.unwrap_or_else(default)
|
||||
}
|
||||
next(self.properties::<T>(func, id, inherent).cloned(), &default)
|
||||
next(properties.cloned(), || E::PROPERTY.default(), fold)
|
||||
} else {
|
||||
properties.next().cloned().unwrap_or_else(|| E::PROPERTY.default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves a reference to a field, also taking into account the
|
||||
/// instance's value if any.
|
||||
pub(crate) fn get_ref_with<E, const I: u8>(
|
||||
self,
|
||||
_: Field<E, I>,
|
||||
inherent: Option<&'a E::Type>,
|
||||
) -> &'a E::Type
|
||||
where
|
||||
E: RefableProperty<I>,
|
||||
{
|
||||
self.properties::<E::Type>(E::elem(), I, inherent)
|
||||
.next()
|
||||
.unwrap_or_else(|| E::PROPERTY.default_ref())
|
||||
}
|
||||
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
|
@ -1,9 +1,9 @@
|
||||
use heck::{ToKebabCase, ToShoutySnakeCase, ToUpperCamelCase};
|
||||
use heck::ToKebabCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{parse_quote, Ident, Result, Token};
|
||||
use syn::{Ident, Result, Token};
|
||||
|
||||
use crate::util::{
|
||||
determine_name_and_title, documentation, foundations, has_attr, kw, parse_attr,
|
||||
@ -39,14 +39,13 @@ struct Elem {
|
||||
}
|
||||
|
||||
impl Elem {
|
||||
/// Calls the closure to produce a token stream if the
|
||||
/// element has the given capability.
|
||||
/// Whether the element has the given trait listed as a capability.
|
||||
fn can(&self, name: &str) -> bool {
|
||||
self.capabilities.iter().any(|capability| capability == name)
|
||||
}
|
||||
|
||||
/// Calls the closure to produce a token stream if the
|
||||
/// element does not have the given capability.
|
||||
/// Whether the element does not have the given trait listed as a
|
||||
/// capability.
|
||||
fn cannot(&self, name: &str) -> bool {
|
||||
!self.can(name)
|
||||
}
|
||||
@ -68,21 +67,6 @@ impl Elem {
|
||||
self.struct_fields().filter(|field| !field.required)
|
||||
}
|
||||
|
||||
/// Fields that are relevant for equality.
|
||||
///
|
||||
/// Synthesized fields are excluded to ensure equality before and after
|
||||
/// synthesis.
|
||||
fn eq_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||
self.struct_fields().filter(|field| !field.synthesized)
|
||||
}
|
||||
|
||||
/// Fields that show up in the documentation.
|
||||
fn doc_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|field| !field.internal && !field.synthesized)
|
||||
}
|
||||
|
||||
/// Fields that are relevant for `Construct` impl.
|
||||
///
|
||||
/// The reason why fields that are `parse` and internal are allowed is
|
||||
@ -98,44 +82,20 @@ impl Elem {
|
||||
fn set_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||
self.construct_fields().filter(|field| !field.required)
|
||||
}
|
||||
|
||||
/// Fields that can be accessed from the style chain.
|
||||
fn style_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||
self.real_fields()
|
||||
.filter(|field| !field.required && !field.synthesized)
|
||||
}
|
||||
|
||||
/// Fields that are visible to the user.
|
||||
fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||
self.real_fields().filter(|field| !field.internal)
|
||||
}
|
||||
}
|
||||
|
||||
/// A field of an [element definition][`Elem`].
|
||||
struct Field {
|
||||
/// The index of the field among all.
|
||||
i: u8,
|
||||
/// The name of this field.
|
||||
ident: Ident,
|
||||
/// The identifier `{ident}_in`.
|
||||
ident_in: Ident,
|
||||
/// The identifier `with_{ident}`.
|
||||
with_ident: Ident,
|
||||
/// The identifier `push_{ident}`.
|
||||
push_ident: Ident,
|
||||
/// The identifier `set_{ident}`.
|
||||
set_ident: Ident,
|
||||
/// The upper camel-case version of `ident`, used for the enum variant name.
|
||||
enum_ident: Ident,
|
||||
/// The all-caps snake-case version of `ident`, used for the constant name.
|
||||
const_ident: Ident,
|
||||
/// The visibility of this field.
|
||||
vis: syn::Visibility,
|
||||
/// The type of this field.
|
||||
ty: syn::Type,
|
||||
/// The type returned by accessor methods for this field.
|
||||
///
|
||||
/// Usually, this is the same as `ty`, but this might be different
|
||||
/// if this field has a `#[resolve]` attribute.
|
||||
output: syn::Type,
|
||||
/// The field's identifier as exposed to Typst.
|
||||
name: String,
|
||||
/// The documentation for this field as a string.
|
||||
@ -147,16 +107,12 @@ struct Field {
|
||||
/// Whether this field is variadic; that is, has its values
|
||||
/// taken from a variable number of arguments.
|
||||
variadic: bool,
|
||||
/// Whether this field has a `#[resolve]` attribute.
|
||||
resolve: bool,
|
||||
/// Whether this field has a `#[fold]` attribute.
|
||||
fold: bool,
|
||||
/// Whether this field is excluded from documentation.
|
||||
internal: bool,
|
||||
/// Whether this field exists only in documentation.
|
||||
external: bool,
|
||||
/// Whether this field has a `#[borrowed]` attribute.
|
||||
borrowed: bool,
|
||||
/// Whether this field has a `#[ghost]` attribute.
|
||||
ghost: bool,
|
||||
/// Whether this field has a `#[synthesized]` attribute.
|
||||
@ -206,7 +162,12 @@ fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
|
||||
bail!(body, "expected named fields");
|
||||
};
|
||||
|
||||
let fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?;
|
||||
let mut fields = named.named.iter().map(parse_field).collect::<Result<Vec<_>>>()?;
|
||||
fields.sort_by_key(|field| field.internal);
|
||||
for (i, field) in fields.iter_mut().enumerate() {
|
||||
field.i = i as u8;
|
||||
}
|
||||
|
||||
if fields.iter().any(|field| field.ghost && !field.internal)
|
||||
&& meta.capabilities.iter().all(|capability| capability != "Construct")
|
||||
{
|
||||
@ -243,27 +204,20 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
||||
let required = has_attr(&mut attrs, "required") || variadic;
|
||||
let positional = has_attr(&mut attrs, "positional") || required;
|
||||
|
||||
let mut field = Field {
|
||||
let field = Field {
|
||||
i: 0,
|
||||
ident: ident.clone(),
|
||||
ident_in: format_ident!("{ident}_in"),
|
||||
with_ident: format_ident!("with_{ident}"),
|
||||
push_ident: format_ident!("push_{ident}"),
|
||||
set_ident: format_ident!("set_{ident}"),
|
||||
enum_ident: Ident::new(&ident.to_string().to_upper_camel_case(), ident.span()),
|
||||
const_ident: Ident::new(&ident.to_string().to_shouty_snake_case(), ident.span()),
|
||||
vis: field.vis.clone(),
|
||||
ty: field.ty.clone(),
|
||||
output: field.ty.clone(),
|
||||
name: ident.to_string().to_kebab_case(),
|
||||
docs: documentation(&attrs),
|
||||
positional,
|
||||
required,
|
||||
variadic,
|
||||
resolve: has_attr(&mut attrs, "resolve"),
|
||||
fold: has_attr(&mut attrs, "fold"),
|
||||
internal: has_attr(&mut attrs, "internal"),
|
||||
external: has_attr(&mut attrs, "external"),
|
||||
borrowed: has_attr(&mut attrs, "borrowed"),
|
||||
ghost: has_attr(&mut attrs, "ghost"),
|
||||
synthesized: has_attr(&mut attrs, "synthesized"),
|
||||
parse: parse_attr(&mut attrs, "parse")?.flatten(),
|
||||
@ -275,17 +229,9 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
||||
}
|
||||
|
||||
if (field.required || field.synthesized)
|
||||
&& (field.default.is_some() || field.fold || field.resolve || field.ghost)
|
||||
&& (field.default.is_some() || field.fold || field.ghost)
|
||||
{
|
||||
bail!(
|
||||
ident,
|
||||
"required and synthesized fields cannot be default, fold, resolve, or ghost"
|
||||
);
|
||||
}
|
||||
|
||||
if field.resolve {
|
||||
let ty = &field.ty;
|
||||
field.output = parse_quote! { <#ty as #foundations::Resolve>::Output };
|
||||
bail!(ident, "required and synthesized fields cannot be default, fold, or ghost");
|
||||
}
|
||||
|
||||
validate_attrs(&attrs)?;
|
||||
@ -297,30 +243,18 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
|
||||
fn create(element: &Elem) -> Result<TokenStream> {
|
||||
// The struct itself.
|
||||
let struct_ = create_struct(element);
|
||||
|
||||
// Implementations.
|
||||
let inherent_impl = create_inherent_impl(element);
|
||||
|
||||
// The enum with the struct's fields.
|
||||
let fields_enum = create_fields_enum(element);
|
||||
|
||||
// The statics with borrowed fields' default values.
|
||||
let default_statics = element
|
||||
.style_fields()
|
||||
.filter(|field| field.borrowed)
|
||||
.map(create_default_static);
|
||||
|
||||
// Trait implementations.
|
||||
let native_element_impl = create_native_elem_impl(element);
|
||||
let partial_eq_impl =
|
||||
element.cannot("PartialEq").then(|| create_partial_eq_impl(element));
|
||||
let field_impls =
|
||||
element.fields.iter().map(|field| create_field_impl(element, field));
|
||||
let capable_impl = create_capable_impl(element);
|
||||
let construct_impl =
|
||||
element.cannot("Construct").then(|| create_construct_impl(element));
|
||||
let set_impl = element.cannot("Set").then(|| create_set_impl(element));
|
||||
let capable_impl = create_capable_impl(element);
|
||||
let fields_impl = create_fields_impl(element);
|
||||
let repr_impl = element.cannot("Repr").then(|| create_repr_impl(element));
|
||||
let locatable_impl = element.can("Locatable").then(|| create_locatable_impl(element));
|
||||
let mathy_impl = element.can("Mathy").then(|| create_mathy_impl(element));
|
||||
let into_value_impl = create_into_value_impl(element);
|
||||
|
||||
// We use a const block to create an anonymous scope, as to not leak any
|
||||
// local definitions.
|
||||
@ -328,19 +262,14 @@ fn create(element: &Elem) -> Result<TokenStream> {
|
||||
#struct_
|
||||
|
||||
const _: () = {
|
||||
#fields_enum
|
||||
#(#default_statics)*
|
||||
#inherent_impl
|
||||
#native_element_impl
|
||||
#fields_impl
|
||||
#(#field_impls)*
|
||||
#capable_impl
|
||||
#construct_impl
|
||||
#set_impl
|
||||
#partial_eq_impl
|
||||
#repr_impl
|
||||
#locatable_impl
|
||||
#mathy_impl
|
||||
#into_value_impl
|
||||
};
|
||||
})
|
||||
}
|
||||
@ -365,80 +294,13 @@ fn create_struct(element: &Elem) -> TokenStream {
|
||||
|
||||
/// Create a field declaration for the struct.
|
||||
fn create_field(field: &Field) -> TokenStream {
|
||||
let Field { vis, ident, ty, .. } = field;
|
||||
let Field { i, vis, ident, ty, .. } = field;
|
||||
if field.required {
|
||||
quote! { #vis #ident: #ty }
|
||||
} else if field.synthesized {
|
||||
quote! { #vis #ident: ::std::option::Option<#ty> }
|
||||
} else {
|
||||
quote! { #ident: ::std::option::Option<#ty> }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the element's enum for field identifiers.
|
||||
fn create_fields_enum(element: &Elem) -> TokenStream {
|
||||
let variants: Vec<_> = element.real_fields().map(|field| &field.enum_ident).collect();
|
||||
let names: Vec<_> = element.real_fields().map(|field| &field.name).collect();
|
||||
let consts: Vec<_> = element.real_fields().map(|field| &field.const_ident).collect();
|
||||
let repr = (!variants.is_empty()).then(|| quote! { #[repr(u8)] });
|
||||
|
||||
quote! {
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#repr
|
||||
pub enum Fields {
|
||||
#(#variants,)*
|
||||
}
|
||||
|
||||
impl Fields {
|
||||
/// Converts the field identifier to the field name.
|
||||
pub fn to_str(self) -> &'static str {
|
||||
match self {
|
||||
#(Self::#variants => #names,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<u8> for Fields {
|
||||
type Error = #foundations::FieldAccessError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
#(const #consts: u8 = Fields::#variants as u8;)*
|
||||
match value {
|
||||
#(#consts => Ok(Self::#variants),)*
|
||||
_ => Err(#foundations::FieldAccessError::Internal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::str::FromStr for Fields {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
#(#names => Ok(Self::#variants),)*
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for Fields {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
f.pad(self.to_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a static with a borrowed field's default value.
|
||||
fn create_default_static(field: &Field) -> TokenStream {
|
||||
let Field { const_ident, default, ty, .. } = field;
|
||||
|
||||
let init = match default {
|
||||
Some(default) => quote! { || #default },
|
||||
None => quote! { ::std::default::Default::default },
|
||||
};
|
||||
|
||||
quote! {
|
||||
static #const_ident: ::std::sync::LazyLock<#ty> =
|
||||
::std::sync::LazyLock::new(#init);
|
||||
quote! { #vis #ident: #foundations::Settable<Self, #i> }
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,19 +310,23 @@ fn create_inherent_impl(element: &Elem) -> TokenStream {
|
||||
|
||||
let new_func = create_new_func(element);
|
||||
let with_field_methods = element.accessor_fields().map(create_with_field_method);
|
||||
let push_field_methods = element.accessor_fields().map(create_push_field_method);
|
||||
let field_methods = element.accessor_fields().map(create_field_method);
|
||||
let field_in_methods = element.style_fields().map(create_field_in_method);
|
||||
let set_field_methods = element.style_fields().map(create_set_field_method);
|
||||
|
||||
let style_consts = element.real_fields().map(|field| {
|
||||
let Field { i, vis, ident, .. } = field;
|
||||
quote! {
|
||||
#vis const #ident: #foundations::Field<Self, #i>
|
||||
= #foundations::Field::new();
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl #ident {
|
||||
#new_func
|
||||
#(#with_field_methods)*
|
||||
#(#push_field_methods)*
|
||||
#(#field_methods)*
|
||||
#(#field_in_methods)*
|
||||
#(#set_field_methods)*
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
impl #ident {
|
||||
#(#style_consts)*
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -476,8 +342,10 @@ fn create_new_func(element: &Elem) -> TokenStream {
|
||||
let ident = &field.ident;
|
||||
if field.required {
|
||||
quote! { #ident }
|
||||
} else {
|
||||
} else if field.synthesized {
|
||||
quote! { #ident: None }
|
||||
} else {
|
||||
quote! { #ident: #foundations::Settable::new() }
|
||||
}
|
||||
});
|
||||
|
||||
@ -491,254 +359,190 @@ fn create_new_func(element: &Elem) -> TokenStream {
|
||||
|
||||
/// Create a builder-style setter method for a field.
|
||||
fn create_with_field_method(field: &Field) -> TokenStream {
|
||||
let Field { vis, ident, with_ident, push_ident, name, ty, .. } = field;
|
||||
let Field { vis, ident, with_ident, name, ty, .. } = field;
|
||||
let doc = format!("Builder-style setter for the [`{name}`](Self::{ident}) field.");
|
||||
|
||||
let expr = if field.required {
|
||||
quote! { self.#ident = #ident }
|
||||
} else if field.synthesized {
|
||||
quote! { self.#ident = Some(#ident); }
|
||||
} else {
|
||||
quote! { self.#ident.set(#ident); }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#vis fn #with_ident(mut self, #ident: #ty) -> Self {
|
||||
self.#push_ident(#ident);
|
||||
#expr;
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a setter method for a field.
|
||||
fn create_push_field_method(field: &Field) -> TokenStream {
|
||||
let Field { vis, ident, push_ident, name, ty, .. } = field;
|
||||
let doc = format!("Setter for the [`{name}`](Self::{ident}) field.");
|
||||
|
||||
let expr = if field.required {
|
||||
quote! { #ident }
|
||||
} else {
|
||||
quote! { Some(#ident) }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#vis fn #push_ident(&mut self, #ident: #ty) {
|
||||
self.#ident = #expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an accessor method for a field.
|
||||
fn create_field_method(field: &Field) -> TokenStream {
|
||||
let Field { vis, docs, ident, output, .. } = field;
|
||||
|
||||
if field.required {
|
||||
quote! {
|
||||
#[doc = #docs]
|
||||
#vis fn #ident(&self) -> &#output {
|
||||
&self.#ident
|
||||
}
|
||||
}
|
||||
} else if field.synthesized {
|
||||
quote! {
|
||||
#[doc = #docs]
|
||||
#[track_caller]
|
||||
#vis fn #ident(&self) -> ::std::option::Option<&#output> {
|
||||
self.#ident.as_ref()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let sig = if field.borrowed {
|
||||
quote! { <'a>(&'a self, styles: #foundations::StyleChain<'a>) -> &'a #output }
|
||||
} else {
|
||||
quote! { (&self, styles: #foundations::StyleChain) -> #output }
|
||||
};
|
||||
|
||||
let mut value = create_style_chain_access(
|
||||
field,
|
||||
field.borrowed,
|
||||
quote! { self.#ident.as_ref() },
|
||||
);
|
||||
if field.resolve {
|
||||
value = quote! { #foundations::Resolve::resolve(#value, styles) };
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[doc = #docs]
|
||||
#vis fn #ident #sig {
|
||||
#value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a style accessor method for a field.
|
||||
fn create_field_in_method(field: &Field) -> TokenStream {
|
||||
let Field { vis, ident_in, name, output, .. } = field;
|
||||
let doc = format!("Access the `{name}` field in the given style chain.");
|
||||
|
||||
let ref_ = field.borrowed.then(|| quote! { & });
|
||||
|
||||
let mut value = create_style_chain_access(field, field.borrowed, quote! { None });
|
||||
if field.resolve {
|
||||
value = quote! { #foundations::Resolve::resolve(#value, styles) };
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#vis fn #ident_in(styles: #foundations::StyleChain) -> #ref_ #output {
|
||||
#value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a style setter method for a field.
|
||||
fn create_set_field_method(field: &Field) -> TokenStream {
|
||||
let Field { vis, ident, set_ident, enum_ident, ty, name, .. } = field;
|
||||
let doc = format!("Create a style property for the `{name}` field.");
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
#vis fn #set_ident(#ident: #ty) -> #foundations::Property {
|
||||
#foundations::Property::new::<Self, _>(
|
||||
Fields::#enum_ident as u8,
|
||||
#ident,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a style chain access method for a field.
|
||||
fn create_style_chain_access(
|
||||
field: &Field,
|
||||
borrowed: bool,
|
||||
inherent: TokenStream,
|
||||
) -> TokenStream {
|
||||
let Field { ty, default, enum_ident, const_ident, .. } = field;
|
||||
|
||||
let getter = match (field.fold, borrowed) {
|
||||
(false, false) => quote! { get },
|
||||
(false, true) => quote! { get_ref },
|
||||
(true, _) => quote! { get_folded },
|
||||
};
|
||||
|
||||
let default = if borrowed {
|
||||
quote! { || &#const_ident }
|
||||
} else {
|
||||
match default {
|
||||
Some(default) => quote! { || #default },
|
||||
None => quote! { ::std::default::Default::default },
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
styles.#getter::<#ty>(
|
||||
<Self as #foundations::NativeElement>::elem(),
|
||||
Fields::#enum_ident as u8,
|
||||
#inherent,
|
||||
#default,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the element's `NativeElement` implementation.
|
||||
fn create_native_elem_impl(element: &Elem) -> TokenStream {
|
||||
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
|
||||
|
||||
let local_name = if element.can("LocalName") {
|
||||
quote! { Some(<#foundations::Packed<#ident> as ::typst_library::text::LocalName>::local_name) }
|
||||
let fields = element.fields.iter().filter(|field| !field.internal).map(|field| {
|
||||
let method = if field.external {
|
||||
quote! { external }
|
||||
} else if field.variadic {
|
||||
quote! { variadic }
|
||||
} else if field.required {
|
||||
quote! { required }
|
||||
} else if field.synthesized {
|
||||
quote! { synthesized }
|
||||
} else if field.ghost {
|
||||
quote! { ghost }
|
||||
} else {
|
||||
quote! { None }
|
||||
quote! { settable }
|
||||
};
|
||||
let i = field.i;
|
||||
quote! { #foundations::TypedFieldData::<Self>::#method::<#i>() }
|
||||
});
|
||||
|
||||
let scope = if *scope {
|
||||
quote! { <#ident as #foundations::NativeScope>::scope() }
|
||||
} else {
|
||||
quote! { #foundations::Scope::new() }
|
||||
};
|
||||
|
||||
let params = element.doc_fields().map(create_param_info);
|
||||
|
||||
let data = quote! {
|
||||
#foundations::NativeElementData {
|
||||
name: #name,
|
||||
title: #title,
|
||||
docs: #docs,
|
||||
keywords: &[#(#keywords),*],
|
||||
construct: <#ident as #foundations::Construct>::construct,
|
||||
set: <#ident as #foundations::Set>::set,
|
||||
vtable: <#ident as #foundations::Capable>::vtable,
|
||||
field_id: |name| name.parse().ok().map(|id: Fields| id as u8),
|
||||
field_name: |id| id.try_into().ok().map(Fields::to_str),
|
||||
field_from_styles: <#ident as #foundations::Fields>::field_from_styles,
|
||||
local_name: #local_name,
|
||||
scope: ::std::sync::LazyLock::new(|| #scope),
|
||||
params: ::std::sync::LazyLock::new(|| ::std::vec![#(#params),*])
|
||||
let field_arms = element
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| !field.internal && !field.external)
|
||||
.map(|field| {
|
||||
let Field { name, i, .. } = field;
|
||||
quote! { #name => Some(#i) }
|
||||
});
|
||||
let field_id = quote! {
|
||||
|name| match name {
|
||||
#(#field_arms,)*
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let with_keywords =
|
||||
(!keywords.is_empty()).then(|| quote! { .with_keywords(&[#(#keywords),*]) });
|
||||
let with_repr = element.can("Repr").then(|| quote! { .with_repr() });
|
||||
let with_partial_eq = element.can("PartialEq").then(|| quote! { .with_partial_eq() });
|
||||
let with_local_name = element.can("LocalName").then(|| quote! { .with_local_name() });
|
||||
let with_scope = scope.then(|| quote! { .with_scope() });
|
||||
|
||||
quote! {
|
||||
impl #foundations::NativeElement for #ident {
|
||||
const ELEMENT: #foundations::TypedElementData<Self> =
|
||||
#foundations::TypedElementData::new(
|
||||
#name,
|
||||
#title,
|
||||
#docs,
|
||||
&[#(#fields),*],
|
||||
#field_id,
|
||||
) #with_keywords
|
||||
#with_repr
|
||||
#with_partial_eq
|
||||
#with_local_name
|
||||
#with_scope;
|
||||
|
||||
fn data() -> &'static #foundations::NativeElementData {
|
||||
static DATA: #foundations::NativeElementData = #data;
|
||||
static DATA: #foundations::NativeElementData =
|
||||
#foundations::NativeElementData::new::<#ident>();
|
||||
&DATA
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a parameter info for a field.
|
||||
fn create_param_info(field: &Field) -> TokenStream {
|
||||
let Field {
|
||||
name,
|
||||
docs,
|
||||
positional,
|
||||
variadic,
|
||||
required,
|
||||
default,
|
||||
ty,
|
||||
..
|
||||
} = field;
|
||||
/// Creates the appropriate trait implementation for a field.
|
||||
fn create_field_impl(element: &Elem, field: &Field) -> TokenStream {
|
||||
let elem_ident = &element.ident;
|
||||
let Field { i, ty, ident, default, positional, name, docs, .. } = field;
|
||||
|
||||
let named = !positional;
|
||||
let settable = !field.required;
|
||||
|
||||
let default = if settable {
|
||||
let default = default
|
||||
.clone()
|
||||
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() });
|
||||
quote! {
|
||||
Some(|| <#ty as #foundations::IntoValue>::into_value(#default))
|
||||
}
|
||||
} else {
|
||||
quote! { None }
|
||||
let default = match default {
|
||||
Some(default) => quote! { || #default },
|
||||
None => quote! { std::default::Default::default },
|
||||
};
|
||||
|
||||
let ty = if *variadic {
|
||||
quote! { <#ty as #foundations::Container>::Inner }
|
||||
} else {
|
||||
quote! { #ty }
|
||||
let getters = quote! {
|
||||
|elem| &elem.#ident,
|
||||
|elem| &mut elem.#ident
|
||||
};
|
||||
|
||||
if field.external {
|
||||
quote! {
|
||||
#foundations::ParamInfo {
|
||||
name: #name,
|
||||
docs: #docs,
|
||||
input: <#ty as #foundations::Reflect>::input(),
|
||||
default: #default,
|
||||
positional: #positional,
|
||||
named: #named,
|
||||
variadic: #variadic,
|
||||
required: #required,
|
||||
settable: #settable,
|
||||
impl #foundations::ExternalField<#i> for #elem_ident {
|
||||
type Type = #ty;
|
||||
const FIELD: #foundations::ExternalFieldData<Self, #i> =
|
||||
#foundations::ExternalFieldData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#default,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the element's `PartialEq` implementation.
|
||||
fn create_partial_eq_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
let empty = element.eq_fields().next().is_none().then(|| quote! { true });
|
||||
let fields = element.eq_fields().map(|field| &field.ident);
|
||||
|
||||
} else if field.required {
|
||||
quote! {
|
||||
impl PartialEq for #ident {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
#empty
|
||||
#(self.#fields == other.#fields)&&*
|
||||
impl #foundations::RequiredField<#i> for #elem_ident {
|
||||
type Type = #ty;
|
||||
const FIELD: #foundations::RequiredFieldData<Self, #i> =
|
||||
#foundations::RequiredFieldData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#getters,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if field.synthesized {
|
||||
quote! {
|
||||
impl #foundations::SynthesizedField<#i> for #elem_ident {
|
||||
type Type = #ty;
|
||||
const FIELD: #foundations::SynthesizedFieldData<Self, #i> =
|
||||
#foundations::SynthesizedFieldData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#getters,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let slot = quote! {
|
||||
|| {
|
||||
static LOCK: ::std::sync::OnceLock<#ty> = ::std::sync::OnceLock::new();
|
||||
&LOCK
|
||||
}
|
||||
};
|
||||
|
||||
let with_fold = field.fold.then(|| quote! { .with_fold() });
|
||||
let refable = (!field.fold).then(|| {
|
||||
quote! {
|
||||
impl #foundations::RefableProperty<#i> for #elem_ident {}
|
||||
}
|
||||
});
|
||||
|
||||
if field.ghost {
|
||||
quote! {
|
||||
impl #foundations::SettableProperty<#i> for #elem_ident {
|
||||
type Type = #ty;
|
||||
const PROPERTY: #foundations::SettablePropertyData<Self, #i> =
|
||||
#foundations::SettablePropertyData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#positional,
|
||||
#default,
|
||||
#slot,
|
||||
) #with_fold;
|
||||
}
|
||||
#refable
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
impl #foundations::SettableField<#i> for #elem_ident {
|
||||
type Type = #ty;
|
||||
const FIELD: #foundations::SettableFieldData<Self, #i> =
|
||||
#foundations::SettableFieldData::<Self, #i>::new(
|
||||
#name,
|
||||
#docs,
|
||||
#positional,
|
||||
#getters,
|
||||
#default,
|
||||
#slot,
|
||||
) #with_fold;
|
||||
}
|
||||
#refable
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -758,10 +562,12 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
|
||||
|
||||
let fields = element.struct_fields().map(|field| {
|
||||
let ident = &field.ident;
|
||||
if field.synthesized {
|
||||
if field.required {
|
||||
quote! { #ident }
|
||||
} else if field.synthesized {
|
||||
quote! { #ident: None }
|
||||
} else {
|
||||
quote! { #ident }
|
||||
quote! { #ident: #foundations::Settable::from(#ident) }
|
||||
}
|
||||
});
|
||||
|
||||
@ -782,12 +588,12 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
|
||||
fn create_set_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
let handlers = element.set_fields().map(|field| {
|
||||
let set_ident = &field.set_ident;
|
||||
let field_ident = &field.ident;
|
||||
let (prefix, value) = create_field_parser(field);
|
||||
quote! {
|
||||
#prefix
|
||||
if let Some(value) = #value {
|
||||
styles.set(Self::#set_ident(value));
|
||||
styles.set(Self::#field_ident, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -861,196 +667,6 @@ fn create_capable_impl(element: &Elem) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the element's `Fields` implementation.
|
||||
fn create_fields_impl(element: &Elem) -> TokenStream {
|
||||
let into_value = quote! { #foundations::IntoValue::into_value };
|
||||
let visible_non_ghost = || element.visible_fields().filter(|field| !field.ghost);
|
||||
|
||||
// Fields that can be checked using the `has` method.
|
||||
let has_arms = visible_non_ghost().map(|field| {
|
||||
let Field { enum_ident, ident, .. } = field;
|
||||
|
||||
let expr = if field.required {
|
||||
quote! { true }
|
||||
} else {
|
||||
quote! { self.#ident.is_some() }
|
||||
};
|
||||
|
||||
quote! { Fields::#enum_ident => #expr }
|
||||
});
|
||||
|
||||
// Fields that can be accessed using the `field` method.
|
||||
let field_arms = visible_non_ghost().map(|field| {
|
||||
let Field { enum_ident, ident, .. } = field;
|
||||
|
||||
let expr = if field.required {
|
||||
quote! { Ok(#into_value(self.#ident.clone())) }
|
||||
} else {
|
||||
quote! { self.#ident.clone().map(#into_value).ok_or(#foundations::FieldAccessError::Unset) }
|
||||
};
|
||||
|
||||
quote! { Fields::#enum_ident => #expr }
|
||||
});
|
||||
|
||||
// Fields that can be accessed using the `field_with_styles` method.
|
||||
let field_with_styles_arms = element.visible_fields().map(|field| {
|
||||
let Field { enum_ident, ident, .. } = field;
|
||||
|
||||
let expr = if field.required {
|
||||
quote! { Ok(#into_value(self.#ident.clone())) }
|
||||
} else if field.synthesized {
|
||||
quote! { self.#ident.clone().map(#into_value).ok_or(#foundations::FieldAccessError::Unset) }
|
||||
} else {
|
||||
let value = create_style_chain_access(
|
||||
field,
|
||||
false,
|
||||
if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) },
|
||||
);
|
||||
|
||||
quote! { Ok(#into_value(#value)) }
|
||||
};
|
||||
|
||||
quote! { Fields::#enum_ident => #expr }
|
||||
});
|
||||
|
||||
// Fields that can be accessed using the `field_from_styles` method.
|
||||
let field_from_styles_arms = element.visible_fields().map(|field| {
|
||||
let Field { enum_ident, .. } = field;
|
||||
|
||||
let expr = if field.required || field.synthesized {
|
||||
quote! { Err(#foundations::FieldAccessError::Unknown) }
|
||||
} else {
|
||||
let value = create_style_chain_access(field, false, quote!(None));
|
||||
quote! { Ok(#into_value(#value)) }
|
||||
};
|
||||
|
||||
quote! { Fields::#enum_ident => #expr }
|
||||
});
|
||||
|
||||
// Sets fields from the style chain.
|
||||
let materializes = visible_non_ghost()
|
||||
.filter(|field| !field.required && !field.synthesized)
|
||||
.map(|field| {
|
||||
let Field { ident, .. } = field;
|
||||
let value = create_style_chain_access(
|
||||
field,
|
||||
false,
|
||||
if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) },
|
||||
);
|
||||
|
||||
if field.fold {
|
||||
quote! { self.#ident = Some(#value); }
|
||||
} else {
|
||||
quote! {
|
||||
if self.#ident.is_none() {
|
||||
self.#ident = Some(#value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Creation of the `fields` dictionary for inherent fields.
|
||||
let field_inserts = visible_non_ghost().map(|field| {
|
||||
let Field { ident, name, .. } = field;
|
||||
let string = quote! { #name.into() };
|
||||
|
||||
if field.required {
|
||||
quote! {
|
||||
fields.insert(#string, #into_value(self.#ident.clone()));
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if let Some(value) = &self.#ident {
|
||||
fields.insert(#string, #into_value(value.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let Elem { ident, .. } = element;
|
||||
|
||||
let result = quote! {
|
||||
Result<#foundations::Value, #foundations::FieldAccessError>
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #foundations::Fields for #ident {
|
||||
type Enum = Fields;
|
||||
|
||||
fn has(&self, id: u8) -> bool {
|
||||
let Ok(id) = Fields::try_from(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match id {
|
||||
#(#has_arms,)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn field(&self, id: u8) -> #result {
|
||||
let id = Fields::try_from(id)?;
|
||||
match id {
|
||||
#(#field_arms,)*
|
||||
// This arm might be reached if someone tries to access an
|
||||
// internal field.
|
||||
_ => Err(#foundations::FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_with_styles(&self, id: u8, styles: #foundations::StyleChain) -> #result {
|
||||
let id = Fields::try_from(id)?;
|
||||
match id {
|
||||
#(#field_with_styles_arms,)*
|
||||
// This arm might be reached if someone tries to access an
|
||||
// internal field.
|
||||
_ => Err(#foundations::FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_from_styles(id: u8, styles: #foundations::StyleChain) -> #result {
|
||||
let id = Fields::try_from(id)?;
|
||||
match id {
|
||||
#(#field_from_styles_arms,)*
|
||||
// This arm might be reached if someone tries to access an
|
||||
// internal field.
|
||||
_ => Err(#foundations::FieldAccessError::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize(&mut self, styles: #foundations::StyleChain) {
|
||||
#(#materializes)*
|
||||
}
|
||||
|
||||
fn fields(&self) -> #foundations::Dict {
|
||||
let mut fields = #foundations::Dict::new();
|
||||
#(#field_inserts)*
|
||||
fields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the element's `Repr` implementation.
|
||||
fn create_repr_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
let repr_format = format!("{}{{}}", element.name);
|
||||
quote! {
|
||||
impl #foundations::Repr for #ident {
|
||||
fn repr(&self) -> ::ecow::EcoString {
|
||||
let fields = #foundations::Fields::fields(self)
|
||||
.into_iter()
|
||||
.map(|(name, value)| ::ecow::eco_format!("{}: {}", name, value.repr()))
|
||||
.collect::<Vec<_>>();
|
||||
::ecow::eco_format!(
|
||||
#repr_format,
|
||||
#foundations::repr::pretty_array_like(&fields, false),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the element's `Locatable` implementation.
|
||||
fn create_locatable_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
@ -1062,15 +678,3 @@ fn create_mathy_impl(element: &Elem) -> TokenStream {
|
||||
let ident = &element.ident;
|
||||
quote! { impl ::typst_library::math::Mathy for #foundations::Packed<#ident> {} }
|
||||
}
|
||||
|
||||
/// Creates the element's `IntoValue` implementation.
|
||||
fn create_into_value_impl(element: &Elem) -> TokenStream {
|
||||
let Elem { ident, .. } = element;
|
||||
quote! {
|
||||
impl #foundations::IntoValue for #ident {
|
||||
fn into_value(self) -> #foundations::Value {
|
||||
#foundations::Value::Content(#foundations::Content::new(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user