diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-08-02 16:31:34 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-08-02 16:31:34 +0200 |
| commit | 533374db14087ac54fdc86afa5f009487ac1b850 (patch) | |
| tree | 0970eb1ca893fe45369d622b5bc1f226f0f66004 /src/syntax/value.rs | |
| parent | 2188ef6b899cc10c84ed985e9ad9049fcc3eb662 (diff) | |
Refactor argument parsing 🔬
Diffstat (limited to 'src/syntax/value.rs')
| -rw-r--r-- | src/syntax/value.rs | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/src/syntax/value.rs b/src/syntax/value.rs new file mode 100644 index 00000000..5c264ca0 --- /dev/null +++ b/src/syntax/value.rs @@ -0,0 +1,193 @@ +//! Value types for extracting function arguments. + +use fontdock::{FontStyle, FontWeight, FontWidth}; + +use crate::Feedback; +use crate::layout::prelude::*; +use crate::length::{Length, ScaleLength}; +use crate::paper::Paper; +use super::span::Spanned; +use super::expr::*; + +/// Value types are used to extract values from functions, tuples and +/// objects. They represent the value part of an argument. +/// ```typst +/// [func: value, key=value] +/// ^^^^^ ^^^^^ +/// ``` +pub trait Value: Sized { + /// Try to parse this value from an expression. + /// + /// Returns `None` and generates an appropriate error if the expression is + /// not valid for this value type + fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self>; +} + +impl<V: Value> Value for Spanned<V> { + fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> { + let span = expr.span; + V::parse(expr, f).map(|v| Spanned { v, span }) + } +} + +macro_rules! match_value { + ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { + impl Value for $type { + fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> { + #[allow(unreachable_patterns)] + match expr.v { + $($p => Some($r)),*, + other => { + error!( + @f, expr.span, + "expected {}, found {}", $name, other.name() + ); + None + } + } + } + } + }; +} + +match_value!(Expr, "expression", e => e); +match_value!(Ident, "identifier", Expr::Ident(i) => i); +match_value!(String, "string", Expr::Str(s) => s); +match_value!(f64, "number", Expr::Number(n) => n); +match_value!(bool, "bool", Expr::Bool(b) => b); +match_value!(Length, "length", Expr::Length(l) => l); +match_value!(Tuple, "tuple", Expr::Tuple(t) => t); +match_value!(Object, "object", Expr::Object(o) => o); +match_value!(ScaleLength, "number or length", + Expr::Length(length) => ScaleLength::Absolute(length), + Expr::Number(scale) => ScaleLength::Scaled(scale), +); + +/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements +/// `Into<String>`. +pub struct StringLike(pub String); + +impl From<StringLike> for String { + fn from(like: StringLike) -> String { + like.0 + } +} + +match_value!(StringLike, "identifier or string", + Expr::Ident(Ident(s)) => StringLike(s), + Expr::Str(s) => StringLike(s), +); + +macro_rules! ident_value { + ($type:ty, $name:expr, $parse:expr) => { + impl Value for $type { + fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> { + if let Expr::Ident(ident) = expr.v { + let val = $parse(ident.as_str()); + if val.is_none() { + error!(@f, expr.span, "invalid {}", $name); + } + val + } else { + error!( + @f, expr.span, + "expected {}, found {}", $name, expr.v.name() + ); + None + } + } + } + }; +} + +ident_value!(Dir, "direction", |s| match s { + "ltr" => Some(LTT), + "rtl" => Some(RTL), + "ttb" => Some(TTB), + "btt" => Some(BTT), + _ => None, +}); + +ident_value!(SpecAlign, "alignment", |s| match s { + "left" => Some(SpecAlign::Left), + "right" => Some(SpecAlign::Right), + "top" => Some(SpecAlign::Top), + "bottom" => Some(SpecAlign::Bottom), + "center" => Some(SpecAlign::Center), + _ => None, +}); + +ident_value!(FontStyle, "font style", FontStyle::from_name); +ident_value!(Paper, "paper", Paper::from_name); + +impl Value for FontWeight { + fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> { + match expr.v { + Expr::Number(weight) => { + const MIN: u16 = 100; + const MAX: u16 = 900; + + Some(FontWeight(if weight < MIN as f64 { + error!(@f, expr.span, "the minimum font weight is {}", MIN); + MIN + } else if weight > MAX as f64 { + error!(@f, expr.span, "the maximum font weight is {}", MAX); + MAX + } else { + weight.round() as u16 + })) + } + Expr::Ident(ident) => { + let weight = Self::from_name(ident.as_str()); + if weight.is_none() { + error!(@f, expr.span, "invalid font weight"); + } + weight + } + other => { + error!( + @f, expr.span, + "expected font weight (name or number), found {}", + other.name(), + ); + None + } + } + } +} + +impl Value for FontWidth { + fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> { + match expr.v { + Expr::Number(width) => { + const MIN: u16 = 1; + const MAX: u16 = 9; + + FontWidth::new(if width < MIN as f64 { + error!(@f, expr.span, "the minimum font width is {}", MIN); + MIN + } else if width > MAX as f64 { + error!(@f, expr.span, "the maximum font width is {}", MAX); + MAX + } else { + width.round() as u16 + }) + } + Expr::Ident(ident) => { + let width = Self::from_name(ident.as_str()); + if width.is_none() { + error!(@f, expr.span, "invalid font width"); + } + width + } + other => { + error!( + @f, expr.span, + "expected font width (name or number), found {}", + other.name(), + ); + None + } + } + } +} |
