Derive Cast for enums

This commit is contained in:
Laurenz 2023-03-13 21:39:38 +01:00
parent cb3c263c4a
commit 880b1847bd
13 changed files with 109 additions and 172 deletions

1
Cargo.lock generated
View File

@ -1269,6 +1269,7 @@ dependencies = [
name = "typst-macros" name = "typst-macros"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",

View File

@ -193,30 +193,15 @@ impl Resolve for HorizontalAlign {
} }
/// How to determine line breaks in a paragraph. /// 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 { pub enum Linebreaks {
/// Determine the line breaks in a simple first-fit style. /// Determine the line breaks in a simple first-fit style.
Simple, Simple,
/// Optimize the line breaks for the whole paragraph. /// 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 /// Typst will try to produce more evenly filled lines of text by
/// considering the whole paragraph when calculating line breaks. /// considering the whole paragraph when calculating line breaks.
"optimized" => Self::Optimized, Optimized,
}
cast_to_value! {
v: Linebreaks => Value::from(match v {
Linebreaks::Simple => "simple",
Linebreaks::Optimized => "optimized",
})
} }
/// A paragraph break. /// A paragraph break.

View File

@ -169,12 +169,22 @@ impl LayoutMath for CasesNode {
} }
/// A vector / matrix delimiter. /// A vector / matrix delimiter.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Delimiter { pub enum Delimiter {
/// Delimit with parentheses.
#[string("(")]
Paren, Paren,
/// Delimit with brackets.
#[string("[")]
Bracket, Bracket,
/// Delimit with curly braces.
#[string("{")]
Brace, Brace,
/// Delimit with vertical bars.
#[string("|")]
Bar, Bar,
/// Delimit with double vertical bars.
#[string("||")]
DoubleBar, 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. /// Layout the inner contents of a vector.
fn layout_vec_body( fn layout_vec_body(
ctx: &mut MathContext, ctx: &mut MathContext,

View File

@ -246,7 +246,7 @@ cast_from_value! {
} }
/// A case transformation on text. /// A case transformation on text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Case { pub enum Case {
/// Everything is lowercased. /// Everything is lowercased.
Lower, 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. /// Display text in small capitals.
/// ///
/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts /// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts

View File

@ -686,53 +686,23 @@ cast_to_value! {
} }
/// Which kind of numbers / figures to select. /// 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 { pub enum NumberType {
/// Numbers that fit well with capital text. ("lnum") /// Numbers that fit well with capital text (the OpenType `lnum`
/// font feature).
Lining, 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, 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. /// The width of numbers / figures.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum NumberWidth { 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). /// Numbers with glyph-specific widths (the OpenType `pnum` font feature).
"proportional" => Self::Proportional, Proportional,
/// Numbers of equal width (the OpenType `tnum` font feature). /// Numbers of equal width (the OpenType `tnum` font feature).
"tabular" => Self::Tabular, Tabular,
}
cast_to_value! {
v: NumberWidth => Value::from(match v {
NumberWidth::Proportional => "proportional",
NumberWidth::Tabular => "tabular",
})
} }
/// OpenType font features settings. /// OpenType font features settings.

View File

@ -113,33 +113,15 @@ impl Layout for ImageNode {
} }
/// How an image should adjust itself to a given area. /// 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 { pub enum ImageFit {
/// The image should completely cover the area. /// The image should completely cover the area. This is the default.
Cover, Cover,
/// The image should be fully contained in the area. /// The image should be fully contained in the area.
Contain, 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 /// The image should be stretched so that it exactly fills the area, even if
/// this means that the image will be distorted. /// this means that the image will be distorted.
"stretch" => Self::Stretch, Stretch,
}
cast_to_value! {
fit: ImageFit => Value::from(match fit {
ImageFit::Cover => "cover",
ImageFit::Contain => "contain",
ImageFit::Stretch => "stretch",
})
} }
/// Load an image from a path. /// Load an image from a path.

View File

@ -15,3 +15,4 @@ proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] } syn = { version = "1", features = ["full", "extra-traits"] }
unscanny = "0.1" unscanny = "0.1"
heck = "0.4"

View File

@ -1,5 +1,67 @@
use super::*; use super::*;
/// Expand the `#[derive(Cast)]` macro.
pub fn cast(item: DeriveInput) -> Result<TokenStream> {
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::<syn::LitStr>()?.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. /// Expand the `cast_from_value!` macro.
pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> { pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> {
let castable: Castable = syn::parse2(stream)?; let castable: Castable = syn::parse2(stream)?;

View File

@ -15,7 +15,7 @@ use quote::quote;
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Parser}; use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::{parse_quote, Ident, Result, Token}; use syn::{parse_quote, DeriveInput, Ident, Result, Token};
use self::util::*; use self::util::*;
@ -35,6 +35,15 @@ pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
.into() .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. /// Implement `Cast` and optionally `Type` for a type.
#[proc_macro] #[proc_macro]
pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream { pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream {

View File

@ -1,3 +1,5 @@
use heck::ToKebabCase;
use super::*; use super::*;
/// Return an error at the given item. /// 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. /// Convert an identifier to a kebab-case string.
pub fn kebab_case(name: &Ident) -> 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. /// Extract documentation comments from an attribute list.

View File

@ -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::num::{NonZeroI64, NonZeroUsize};
use std::ops::Add; use std::ops::Add;

View File

@ -12,7 +12,7 @@ use std::sync::Arc;
use ttf_parser::GlyphId; use ttf_parser::GlyphId;
use crate::eval::{cast_from_value, cast_to_value, Value}; use crate::eval::Cast;
use crate::geom::Em; use crate::geom::Em;
use crate::util::Buffer; use crate::util::Buffer;
@ -231,12 +231,9 @@ pub struct LineMetrics {
} }
/// Identifies a vertical metric of a font. /// 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 { pub enum VerticalFontMetric {
/// The typographic ascender. /// The font's ascender, which typically exceeds the height of all glyphs.
///
/// Corresponds to the typographic ascender from the `OS/2` table if present
/// and falls back to the ascender from the `hhea` table otherwise.
Ascender, Ascender,
/// The approximate height of uppercase letters. /// The approximate height of uppercase letters.
CapHeight, CapHeight,
@ -244,33 +241,6 @@ pub enum VerticalFontMetric {
XHeight, XHeight,
/// The baseline on which the letters rest. /// The baseline on which the letters rest.
Baseline, Baseline,
/// The typographic descender. /// The font's ascender, which typically exceeds the depth of all glyphs.
///
/// Corresponds to the typographic descender from the `OS/2` table if
/// present and falls back to the descender from the `hhea` table otherwise.
Descender, 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",
})
}

View File

@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
use serde::{Deserialize, Serialize}; 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; use crate::geom::Ratio;
/// Properties that distinguish a font from other fonts in the same family. /// 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. /// The style of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Cast)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum FontStyle { pub enum FontStyle {
/// The default, typically upright style. /// 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. /// The weight of a font.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]