From 880b1847bd4170ce80be5781c2163ba085cdcaff Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 13 Mar 2023 21:39:38 +0100 Subject: [PATCH] Derive `Cast` for enums --- Cargo.lock | 1 + library/src/layout/par.rs | 19 ++--------- library/src/math/matrix.rs | 36 ++++++-------------- library/src/text/misc.rs | 15 +------- library/src/text/mod.rs | 46 +++++-------------------- library/src/visualize/image.rs | 24 ++----------- macros/Cargo.toml | 1 + macros/src/castable.rs | 62 ++++++++++++++++++++++++++++++++++ macros/src/lib.rs | 11 +++++- macros/src/util.rs | 4 ++- src/eval/cast.rs | 2 +- src/font/mod.rs | 38 +++------------------ src/font/variant.rs | 22 ++---------- 13 files changed, 109 insertions(+), 172 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a0784eee..ac4c3a1d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1269,6 +1269,7 @@ dependencies = [ name = "typst-macros" version = "0.0.0" dependencies = [ + "heck", "proc-macro2", "quote", "syn", diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index 6178c0599..2d7c5d625 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -193,30 +193,15 @@ impl Resolve for HorizontalAlign { } /// How to determine line breaks in a paragraph. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum Linebreaks { /// Determine the line breaks in a simple first-fit style. Simple, /// Optimize the line breaks for the whole paragraph. - Optimized, -} - -cast_from_value! { - Linebreaks, - /// Determine the line breaks in a simple first-fit style. - "simple" => Self::Simple, - /// Optimize the line breaks for the whole paragraph. /// /// Typst will try to produce more evenly filled lines of text by /// considering the whole paragraph when calculating line breaks. - "optimized" => Self::Optimized, -} - -cast_to_value! { - v: Linebreaks => Value::from(match v { - Linebreaks::Simple => "simple", - Linebreaks::Optimized => "optimized", - }) + Optimized, } /// A paragraph break. diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index 148d79e8c..d79c7ca57 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -169,12 +169,22 @@ impl LayoutMath for CasesNode { } /// A vector / matrix delimiter. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum Delimiter { + /// Delimit with parentheses. + #[string("(")] Paren, + /// Delimit with brackets. + #[string("[")] Bracket, + /// Delimit with curly braces. + #[string("{")] Brace, + /// Delimit with vertical bars. + #[string("|")] Bar, + /// Delimit with double vertical bars. + #[string("||")] DoubleBar, } @@ -202,30 +212,6 @@ impl Delimiter { } } -cast_from_value! { - Delimiter, - /// Delimit with parentheses. - "(" => Self::Paren, - /// Delimit with brackets. - "[" => Self::Bracket, - /// Delimit with curly braces. - "{" => Self::Brace, - /// Delimit with vertical bars. - "|" => Self::Bar, - /// Delimit with double vertical bars. - "||" => Self::DoubleBar, -} - -cast_to_value! { - v: Delimiter => Value::from(match v { - Delimiter::Paren => "(", - Delimiter::Bracket => "[", - Delimiter::Brace => "{", - Delimiter::Bar => "|", - Delimiter::DoubleBar => "||", - }) -} - /// Layout the inner contents of a vector. fn layout_vec_body( ctx: &mut MathContext, diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 029e15f4c..5a5c8514d 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -246,7 +246,7 @@ cast_from_value! { } /// A case transformation on text. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum Case { /// Everything is lowercased. Lower, @@ -264,19 +264,6 @@ impl Case { } } -cast_from_value! { - Case, - "lower" => Self::Lower, - "upper" => Self::Upper, -} - -cast_to_value! { - v: Case => Value::from(match v { - Case::Lower => "lower", - Case::Upper => "upper", - }) -} - /// Display text in small capitals. /// /// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index a81ef3d71..845ffe294 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -686,53 +686,23 @@ cast_to_value! { } /// Which kind of numbers / figures to select. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum NumberType { - /// Numbers that fit well with capital text. ("lnum") + /// Numbers that fit well with capital text (the OpenType `lnum` + /// font feature). Lining, - /// Numbers that fit well into a flow of upper- and lowercase text. ("onum") + /// Numbers that fit well into a flow of upper- and lowercase text (the + /// OpenType `onum` font feature). OldStyle, } -cast_from_value! { - NumberType, - /// Numbers that fit well with capital text (the OpenType `lnum` - /// font feature). - "lining" => Self::Lining, - // Numbers that fit well into a flow of upper- and lowercase text (the - /// OpenType `onum` font feature). - "old-style" => Self::OldStyle, -} - -cast_to_value! { - v: NumberType => Value::from(match v { - NumberType::Lining => "lining", - NumberType::OldStyle => "old-style", - }) -} - /// The width of numbers / figures. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum NumberWidth { - /// Number widths are glyph specific. ("pnum") - Proportional, - /// All numbers are of equal width / monospaced. ("tnum") - Tabular, -} - -cast_from_value! { - NumberWidth, /// Numbers with glyph-specific widths (the OpenType `pnum` font feature). - "proportional" => Self::Proportional, + Proportional, /// Numbers of equal width (the OpenType `tnum` font feature). - "tabular" => Self::Tabular, -} - -cast_to_value! { - v: NumberWidth => Value::from(match v { - NumberWidth::Proportional => "proportional", - NumberWidth::Tabular => "tabular", - }) + Tabular, } /// OpenType font features settings. diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 29a04c96b..4509cf5a4 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -113,33 +113,15 @@ impl Layout for ImageNode { } /// How an image should adjust itself to a given area. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum ImageFit { - /// The image should completely cover the area. + /// The image should completely cover the area. This is the default. Cover, /// The image should be fully contained in the area. Contain, - /// The image should be stretched so that it exactly fills the area. - Stretch, -} - -cast_from_value! { - ImageFit, - /// The image should completely cover the area. This is the default. - "cover" => Self::Cover, - /// The image should be fully contained in the area. - "contain" => Self::Contain, /// The image should be stretched so that it exactly fills the area, even if /// this means that the image will be distorted. - "stretch" => Self::Stretch, -} - -cast_to_value! { - fit: ImageFit => Value::from(match fit { - ImageFit::Cover => "cover", - ImageFit::Contain => "contain", - ImageFit::Stretch => "stretch", - }) + Stretch, } /// Load an image from a path. diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 8b4280fee..24d537be9 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -15,3 +15,4 @@ proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "extra-traits"] } unscanny = "0.1" +heck = "0.4" diff --git a/macros/src/castable.rs b/macros/src/castable.rs index c0d0c1ad6..cd05ed2da 100644 --- a/macros/src/castable.rs +++ b/macros/src/castable.rs @@ -1,5 +1,67 @@ use super::*; +/// Expand the `#[derive(Cast)]` macro. +pub fn cast(item: DeriveInput) -> Result { + let ty = &item.ident; + + let syn::Data::Enum(data) = &item.data else { + bail!(item, "only enums are supported"); + }; + + let mut variants = vec![]; + for variant in &data.variants { + if let Some((_, expr)) = &variant.discriminant { + bail!(expr, "explicit discriminant is not allowed"); + } + + let string = if let Some(attr) = + variant.attrs.iter().find(|attr| attr.path.is_ident("string")) + { + attr.parse_args::()?.value() + } else { + kebab_case(&variant.ident) + }; + + variants.push(Variant { + ident: variant.ident.clone(), + string, + docs: documentation(&variant.attrs), + }); + } + + let strs_to_variants = variants.iter().map(|Variant { ident, string, docs }| { + quote! { + #[doc = #docs] + #string => Self::#ident + } + }); + + let variants_to_strs = variants.iter().map(|Variant { ident, string, .. }| { + quote! { + #ty::#ident => #string + } + }); + + Ok(quote! { + ::typst::eval::cast_from_value! { + #ty, + #(#strs_to_variants),* + } + + ::typst::eval::cast_to_value! { + v: #ty => ::typst::eval::Value::from(match v { + #(#variants_to_strs),* + }) + } + }) +} + +struct Variant { + ident: Ident, + string: String, + docs: String, +} + /// Expand the `cast_from_value!` macro. pub fn cast_from_value(stream: TokenStream) -> Result { let castable: Castable = syn::parse2(stream)?; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 889eaa7bf..fafe8eea1 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,7 +15,7 @@ use quote::quote; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; -use syn::{parse_quote, Ident, Result, Token}; +use syn::{parse_quote, DeriveInput, Ident, Result, Token}; use self::util::*; @@ -35,6 +35,15 @@ pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { .into() } +/// Implement `Cast` for an enum. +#[proc_macro_derive(Cast, attributes(string))] +pub fn cast(item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as DeriveInput); + castable::cast(item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + /// Implement `Cast` and optionally `Type` for a type. #[proc_macro] pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream { diff --git a/macros/src/util.rs b/macros/src/util.rs index d94ba932e..53a8354e8 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -1,3 +1,5 @@ +use heck::ToKebabCase; + use super::*; /// Return an error at the given item. @@ -55,7 +57,7 @@ pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> { /// Convert an identifier to a kebab-case string. pub fn kebab_case(name: &Ident) -> String { - name.to_string().to_lowercase().replace('_', "-") + name.to_string().to_kebab_case() } /// Extract documentation comments from an attribute list. diff --git a/src/eval/cast.rs b/src/eval/cast.rs index ac23bd3ad..7b507dbb0 100644 --- a/src/eval/cast.rs +++ b/src/eval/cast.rs @@ -1,4 +1,4 @@ -pub use typst_macros::{cast_from_value, cast_to_value}; +pub use typst_macros::{cast_from_value, cast_to_value, Cast}; use std::num::{NonZeroI64, NonZeroUsize}; use std::ops::Add; diff --git a/src/font/mod.rs b/src/font/mod.rs index 94ec170e9..bfb790bbf 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use ttf_parser::GlyphId; -use crate::eval::{cast_from_value, cast_to_value, Value}; +use crate::eval::Cast; use crate::geom::Em; use crate::util::Buffer; @@ -231,12 +231,9 @@ pub struct LineMetrics { } /// Identifies a vertical metric of a font. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum VerticalFontMetric { - /// The typographic ascender. - /// - /// Corresponds to the typographic ascender from the `OS/2` table if present - /// and falls back to the ascender from the `hhea` table otherwise. + /// The font's ascender, which typically exceeds the height of all glyphs. Ascender, /// The approximate height of uppercase letters. CapHeight, @@ -244,33 +241,6 @@ pub enum VerticalFontMetric { XHeight, /// The baseline on which the letters rest. Baseline, - /// The typographic descender. - /// - /// Corresponds to the typographic descender from the `OS/2` table if - /// present and falls back to the descender from the `hhea` table otherwise. + /// The font's ascender, which typically exceeds the depth of all glyphs. Descender, } - -cast_from_value! { - VerticalFontMetric, - /// The font's ascender, which typically exceeds the height of all glyphs. - "ascender" => Self::Ascender, - /// The approximate height of uppercase letters. - "cap-height" => Self::CapHeight, - /// The approximate height of non-ascending lowercase letters. - "x-height" => Self::XHeight, - /// The baseline on which the letters rest. - "baseline" => Self::Baseline, - /// The font's ascender, which typically exceeds the depth of all glyphs. - "descender" => Self::Descender, -} - -cast_to_value! { - v: VerticalFontMetric => Value::from(match v { - VerticalFontMetric::Ascender => "ascender", - VerticalFontMetric::CapHeight => "cap-height", - VerticalFontMetric::XHeight => "x-height", - VerticalFontMetric::Baseline => "baseline" , - VerticalFontMetric::Descender => "descender", - }) -} diff --git a/src/font/variant.rs b/src/font/variant.rs index 4eda80ad7..9391aae1e 100644 --- a/src/font/variant.rs +++ b/src/font/variant.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter}; use serde::{Deserialize, Serialize}; -use crate::eval::{cast_from_value, cast_to_value, Value}; +use crate::eval::{cast_from_value, cast_to_value, Cast, Value}; use crate::geom::Ratio; /// Properties that distinguish a font from other fonts in the same family. @@ -32,7 +32,7 @@ impl Debug for FontVariant { /// The style of a font. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Cast)] #[serde(rename_all = "kebab-case")] pub enum FontStyle { /// The default, typically upright style. @@ -62,24 +62,6 @@ impl Default for FontStyle { } } -cast_from_value! { - FontStyle, - /// The default, typically upright style. - "normal" => Self::Normal, - /// A cursive style with custom letterform. - "italic" => Self::Italic, - /// Just a slanted version of the normal style. - "oblique" => Self::Oblique, -} - -cast_to_value! { - v: FontStyle => Value::from(match v { - FontStyle::Normal => "normal", - FontStyle::Italic => "italic", - FontStyle::Oblique => "oblique", - }) -} - /// The weight of a font. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Serialize, Deserialize)]