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"
version = "0.0.0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",

View File

@ -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.

View File

@ -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,

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

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

View File

@ -1,5 +1,67 @@
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.
pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> {
let castable: Castable = syn::parse2(stream)?;

View File

@ -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 {

View File

@ -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.

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::ops::Add;

View File

@ -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",
})
}

View File

@ -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)]