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 | |
| parent | 2188ef6b899cc10c84ed985e9ad9049fcc3eb662 (diff) | |
Refactor argument parsing 🔬
Diffstat (limited to 'src/syntax')
| -rw-r--r-- | src/syntax/decoration.rs | 41 | ||||
| -rw-r--r-- | src/syntax/expr.rs | 242 | ||||
| -rw-r--r-- | src/syntax/func/keys.rs | 169 | ||||
| -rw-r--r-- | src/syntax/func/maps.rs | 233 | ||||
| -rw-r--r-- | src/syntax/func/mod.rs | 107 | ||||
| -rw-r--r-- | src/syntax/func/values.rs | 301 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 181 | ||||
| -rw-r--r-- | src/syntax/model.rs | 134 | ||||
| -rw-r--r-- | src/syntax/parsing.rs | 100 | ||||
| -rw-r--r-- | src/syntax/scope.rs | 2 | ||||
| -rw-r--r-- | src/syntax/span.rs | 2 | ||||
| -rw-r--r-- | src/syntax/test.rs | 65 | ||||
| -rw-r--r-- | src/syntax/tokens.rs | 3 | ||||
| -rw-r--r-- | src/syntax/value.rs | 193 |
14 files changed, 571 insertions, 1202 deletions
diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs new file mode 100644 index 00000000..ab327237 --- /dev/null +++ b/src/syntax/decoration.rs @@ -0,0 +1,41 @@ +//! Decorations for semantic syntax highlighting. + +use serde::Serialize; +use super::span::SpanVec; + +/// A list of spanned decorations. +pub type Decorations = SpanVec<Decoration>; + +/// Decorations for semantic syntax highlighting. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum Decoration { + /// A valid function name. + /// ```typst + /// [box] + /// ^^^ + /// ``` + ValidFuncName, + /// An invalid function name. + /// ```typst + /// [blabla] + /// ^^^^^^ + /// ``` + InvalidFuncName, + /// A key of a keyword argument. + /// ```typst + /// [box: width=5cm] + /// ^^^^^ + /// ``` + ArgumentKey, + /// A key in an object. + /// ```typst + /// [box: padding={ left: 1cm, right: 2cm}] + /// ^^^^ ^^^^^ + /// ``` + ObjectKey, + /// An italic word. + Italic, + /// A bold word. + Bold, +} diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 51daf304..a551c2b6 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,16 +1,15 @@ //! Expressions in function headers. use std::fmt::{self, Debug, Formatter}; -use std::iter::FromIterator; use std::ops::Deref; use std::str::FromStr; use std::u8; -use crate::diagnostic::Diagnostics; +use crate::Feedback; use crate::length::Length; -use super::func::{Key, Value}; -use super::span::{Span, Spanned}; +use super::span::Spanned; use super::tokens::is_identifier; +use super::value::Value; /// An argument or return value. #[derive(Clone, PartialEq)] @@ -238,103 +237,63 @@ impl fmt::Display for ParseColorError { /// (false, 12cm, "hi") /// ``` #[derive(Default, Clone, PartialEq)] -pub struct Tuple { - /// The elements of the tuple. - pub items: Vec<Spanned<Expr>>, -} +pub struct Tuple(pub Vec<Spanned<Expr>>); impl Tuple { /// Create an empty tuple. pub fn new() -> Tuple { - Tuple { items: vec![] } + Tuple(vec![]) } /// Add an element. - pub fn add(&mut self, item: Spanned<Expr>) { - self.items.push(item); + pub fn push(&mut self, item: Spanned<Expr>) { + self.0.push(item); } - /// Extract (and remove) the first matching value and remove and generate - /// diagnostics for all previous items that did not match. - pub fn get<V: Value>(&mut self, diagnostics: &mut Diagnostics) -> Option<V> { - while !self.items.is_empty() { - let expr = self.items.remove(0); - let span = expr.span; - match V::parse(expr) { - Ok(output) => return Some(output), - Err(v) => diagnostics.push(Spanned { v, span }), + /// Expect a specific value type and generate errors for every argument + /// until an argument of the value type is found. + pub fn expect<V: Value>(&mut self, f: &mut Feedback) -> Option<V> { + while !self.0.is_empty() { + let item = self.0.remove(0); + if let Some(val) = V::parse(item, f) { + return Some(val); } } None } - /// Extract (and remove) the first matching value without removing and - /// generating diagnostics for all previous items that did not match. - pub fn get_first<V: Value>(&mut self, _: &mut Diagnostics) -> Option<V> { - let mut i = 0; - while i < self.items.len() { - let expr = self.items[i].clone(); - match V::parse(expr) { - Ok(output) => { - self.items.remove(i); - return Some(output) - } - Err(_) => {}, + /// Extract the first argument of the value type if there is any. + pub fn get<V: Value>(&mut self) -> Option<V> { + for (i, item) in self.0.iter().enumerate() { + if let Some(val) = V::parse(item.clone(), &mut Feedback::new()) { + self.0.remove(i); + return Some(val); } - i += 1; } - None } - /// Extract and return an iterator over all values that match and generate - /// diagnostics for all items that do not match. - pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics) - -> impl Iterator<Item=V> + 'a { - self.items.drain(..).filter_map(move |expr| { - let span = expr.span; - match V::parse(expr) { - Ok(output) => Some(output), - Err(v) => { diagnostics.push(Spanned { v, span }); None } + /// Extract all arguments of the value type. + pub fn all<'a, V: Value>(&'a mut self) -> impl Iterator<Item = V> + 'a { + let mut i = 0; + std::iter::from_fn(move || { + while i < self.0.len() { + let val = V::parse(self.0[i].clone(), &mut Feedback::new()); + if val.is_some() { + self.0.remove(i); + return val; + } else { + i += 1; + } } + None }) } - - /// Iterate over the items of this tuple. - pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Expr>> { - self.items.iter() - } -} - -impl IntoIterator for Tuple { - type Item = Spanned<Expr>; - type IntoIter = std::vec::IntoIter<Spanned<Expr>>; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -impl<'a> IntoIterator for &'a Tuple { - type Item = &'a Spanned<Expr>; - type IntoIter = std::slice::Iter<'a, Spanned<Expr>>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl FromIterator<Spanned<Expr>> for Tuple { - fn from_iter<I: IntoIterator<Item=Spanned<Expr>>>(iter: I) -> Self { - Tuple { items: iter.into_iter().collect() } - } } impl Debug for Tuple { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list() - .entries(&self.items) - .finish() + f.debug_list().entries(&self.0).finish() } } @@ -374,10 +333,7 @@ impl Deref for NamedTuple { /// { fit: false, width: 12cm, items: (1, 2, 3) } /// ``` #[derive(Default, Clone, PartialEq)] -pub struct Object { - /// The key-value pairs of the object. - pub pairs: Vec<Spanned<Pair>>, -} +pub struct Object(pub Vec<Spanned<Pair>>); /// A key-value pair in an object. #[derive(Debug, Clone, PartialEq)] @@ -399,126 +355,52 @@ pub struct Pair { impl Object { /// Create an empty object. pub fn new() -> Object { - Object { pairs: vec![] } + Object(vec![]) } /// Add a pair to object. - pub fn add(&mut self, pair: Spanned<Pair>) { - self.pairs.push(pair); - } - - /// Extract (and remove) a pair with the given key string and matching - /// value. - /// - /// Inserts an error if the value does not match. If the key is not - /// contained, no error is inserted. - pub fn get<V: Value>(&mut self, diagnostics: &mut Diagnostics, key: &str) -> Option<V> { - let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?; - self.get_index::<V>(diagnostics, index) + pub fn push(&mut self, pair: Spanned<Pair>) { + self.0.push(pair); } - /// Extract (and remove) a pair with a matching key and value. + /// Extract an argument with the given key if there is any. /// - /// Inserts an error if the value does not match. If no matching key is - /// found, no error is inserted. - pub fn get_with_key<K: Key, V: Value>( - &mut self, - diagnostics: &mut Diagnostics, - ) -> Option<(K, V)> { - for (index, pair) in self.pairs.iter().enumerate() { - let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span }; - if let Some(key) = K::parse(key) { - return self.get_index::<V>(diagnostics, index).map(|value| (key, value)); + /// Generates an error if there is a matching key, but the value is of the + /// wrong type. + pub fn get<V: Value>(&mut self, key: &str, f: &mut Feedback) -> Option<V> { + for (i, pair) in self.0.iter().enumerate() { + if pair.v.key.v.as_str() == key { + let pair = self.0.remove(i); + return V::parse(pair.v.value, f); } } None } - /// Extract (and remove) all pairs with matching keys and values. - /// - /// Inserts errors for values that do not match. - pub fn get_all<'a, K: Key, V: Value>( - &'a mut self, - diagnostics: &'a mut Diagnostics, - ) -> impl Iterator<Item=(K, V)> + 'a { - let mut index = 0; + /// Extract all key-value pairs where the value is of the given type. + pub fn all<'a, V: Value>(&'a mut self) + -> impl Iterator<Item = (Spanned<Ident>, V)> + 'a + { + let mut i = 0; std::iter::from_fn(move || { - if index < self.pairs.len() { - let key = &self.pairs[index].v.key; - let key = Spanned { v: key.v.as_str(), span: key.span }; - - Some(if let Some(key) = K::parse(key) { - self.get_index::<V>(diagnostics, index).map(|v| (key, v)) - } else { - index += 1; - None - }) - } else { - None + while i < self.0.len() { + let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new()); + if let Some(val) = val { + let pair = self.0.remove(i); + return Some((pair.v.key, val)); + } else { + i += 1; + } } - }).filter_map(|x| x) - } - - /// Extract all key value pairs with span information. - /// - /// The spans are over both key and value, like so: - /// ```typst - /// { key: value } - /// ^^^^^^^^^^ - /// ``` - pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>( - &'a mut self, - diagnostics: &'a mut Diagnostics, - ) -> impl Iterator<Item=Spanned<(K, V)>> + 'a { - self.get_all::<Spanned<K>, Spanned<V>>(diagnostics) - .map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span))) - } - - /// Extract the argument at the given index and insert an error if the value - /// does not match. - fn get_index<V: Value>(&mut self, diagnostics: &mut Diagnostics, index: usize) -> Option<V> { - let expr = self.pairs.remove(index).v.value; - let span = expr.span; - match V::parse(expr) { - Ok(output) => Some(output), - Err(v) => { diagnostics.push(Spanned { v, span }); None } - } - } - - /// Iterate over the pairs of this object. - pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Pair>> { - self.pairs.iter() - } -} - -impl IntoIterator for Object { - type Item = Spanned<Pair>; - type IntoIter = std::vec::IntoIter<Spanned<Pair>>; - - fn into_iter(self) -> Self::IntoIter { - self.pairs.into_iter() - } -} - -impl<'a> IntoIterator for &'a Object { - type Item = &'a Spanned<Pair>; - type IntoIter = std::slice::Iter<'a, Spanned<Pair>>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl FromIterator<Spanned<Pair>> for Object { - fn from_iter<I: IntoIterator<Item=Spanned<Pair>>>(iter: I) -> Self { - Object { pairs: iter.into_iter().collect() } + None + }) } } impl Debug for Object { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_map() - .entries(self.pairs.iter().map(|p| (&p.v.key.v, &p.v.value.v))) + .entries(self.0.iter().map(|p| (&p.v.key.v, &p.v.value.v))) .finish() } } diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs deleted file mode 100644 index 558667dd..00000000 --- a/src/syntax/func/keys.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Key types for identifying keyword arguments. - -use crate::layout::prelude::*; -use super::values::AlignmentValue::{self, *}; -use super::*; - -use self::AxisKey::*; -use self::PaddingKey::*; - -/// Key types are used to extract keyword arguments from -/// [`Objects`](crate::syntax::expr::Object). They represent the key part of a -/// keyword argument. -/// ```typst -/// [func: key=value] -/// ^^^ -/// ``` -/// -/// # Example implementation -/// An implementation for the `AxisKey` that identifies layouting axes might -/// look as follows: -/// ``` -/// # use typstc::syntax::func::Key; -/// # use typstc::syntax::span::Spanned; -/// # #[derive(Eq, PartialEq)] enum Axis { Horizontal, Vertical, Primary, Secondary } -/// # #[derive(Eq, PartialEq)] enum AxisKey { Specific(Axis), Generic(Axis) } -/// # use Axis::*; -/// # use AxisKey::*; -/// impl Key for AxisKey { -/// fn parse(key: Spanned<&str>) -> Option<Self> { -/// match key.v { -/// "horizontal" | "h" => Some(Specific(Horizontal)), -/// "vertical" | "v" => Some(Specific(Vertical)), -/// "primary" | "p" => Some(Generic(Primary)), -/// "secondary" | "s" => Some(Generic(Secondary)), -/// _ => None, -/// } -/// } -/// } -/// ``` -pub trait Key: Sized + Eq { - /// Parse a key string into this type if it is valid for it. - fn parse(key: Spanned<&str>) -> Option<Self>; -} - -impl Key for String { - fn parse(key: Spanned<&str>) -> Option<Self> { - Some(key.v.to_string()) - } -} - -impl<K: Key> Key for Spanned<K> { - fn parse(key: Spanned<&str>) -> Option<Self> { - K::parse(key).map(|v| Spanned { v, span: key.span }) - } -} - -/// Implements [`Key`] for types that just need to match on strings. -macro_rules! key { - ($type:ty, $($($p:pat)|* => $r:expr),* $(,)?) => { - impl Key for $type { - fn parse(key: Spanned<&str>) -> Option<Self> { - match key.v { - $($($p)|* => Some($r)),*, - _ => None, - } - } - } - }; -} - -/// A key which identifies a layouting axis. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[allow(missing_docs)] -pub enum AxisKey { - Generic(GenericAxis), - Specific(SpecificAxis), -} - -impl AxisKey { - /// The generic version of this axis key in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { - match self { - Generic(axis) => axis, - Specific(axis) => axis.to_generic(axes), - } - } - - /// The specific version of this axis key in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - match self { - Generic(axis) => axis.to_specific(axes), - Specific(axis) => axis, - } - } -} - -key!(AxisKey, - "horizontal" | "h" => Specific(Horizontal), - "vertical" | "v" => Specific(Vertical), - "primary" | "p" => Generic(Primary), - "secondary" | "s" => Generic(Secondary), -); - -/// A key which is equivalent to a [`AxisKey`] but uses typical extent keywords -/// instead of axis keywords, e.g. `width` instead of `horizontal`. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ExtentKey(pub AxisKey); - -key!(ExtentKey, - "width" | "w" => ExtentKey(Specific(Horizontal)), - "height" | "h" => ExtentKey(Specific(Vertical)), - "primary-size" | "ps" => ExtentKey(Generic(Primary)), - "secondary-size" | "ss" => ExtentKey(Generic(Secondary)), -); - -impl From<ExtentKey> for AxisKey { - fn from(key: ExtentKey) -> AxisKey { - key.0 - } -} - -/// A key which identifies an axis, but alternatively allows for two positional -/// arguments with unspecified axes. -/// -/// This type does not implement `Key` in itself since it cannot be parsed from -/// a string. Rather, [`AxisKeys`](AxisKey) and positional arguments should be -/// parsed separately and mapped onto this key, as happening in the -/// [`PosAxisMap`](super::maps::PosAxisMap). -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PosAxisKey { - /// The first positional argument. - First, - /// The second positional argument. - Second, - /// An axis keyword argument. - Keyword(AxisKey), -} - -/// An argument key which identifies a margin or padding target. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PaddingKey<Axis> { - /// All four sides should have the specified padding. - All, - /// Both sides of the given axis should have the specified padding. - Both(Axis), - /// Only the given side of the given axis should have the specified padding. - Side(Axis, AlignmentValue), -} - -key!(PaddingKey<AxisKey>, - "horizontal" | "h" => Both(Specific(Horizontal)), - "vertical" | "v" => Both(Specific(Vertical)), - "primary" | "p" => Both(Generic(Primary)), - "secondary" | "s" => Both(Generic(Secondary)), - - "left" => Side(Specific(Horizontal), Left), - "right" => Side(Specific(Horizontal), Right), - "top" => Side(Specific(Vertical), Top), - "bottom" => Side(Specific(Vertical), Bottom), - - "primary-origin" => Side(Generic(Primary), Align(Origin)), - "primary-end" => Side(Generic(Primary), Align(End)), - "secondary-origin" => Side(Generic(Secondary), Align(Origin)), - "secondary-end" => Side(Generic(Secondary), Align(End)), - "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)), - "horizontal-end" => Side(Specific(Horizontal), Align(End)), - "vertical-origin" => Side(Specific(Vertical), Align(Origin)), - "vertical-end" => Side(Specific(Vertical), Align(End)), -); diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs deleted file mode 100644 index bc290a9e..00000000 --- a/src/syntax/func/maps.rs +++ /dev/null @@ -1,233 +0,0 @@ -//! Deduplicating maps and keys for argument parsing. - -use crate::diagnostic::Diagnostics; -use crate::geom::Value4; -use crate::layout::prelude::*; -use crate::length::ScaleLength; -use crate::syntax::span::Spanned; -use super::keys::*; -use super::values::*; -use super::*; - -/// A map which deduplicates redundant arguments. -/// -/// Whenever a duplicate argument is inserted into the map, through the -/// functions `from_iter`, `insert` or `extend` an diagnostics is added to the error -/// list that needs to be passed to those functions. -/// -/// All entries need to have span information to enable the error reporting. -#[derive(Debug, Default, Clone, Eq, PartialEq)] -pub struct DedupMap<K, V> where K: Eq { - map: Vec<Spanned<(K, V)>>, -} - -impl<K, V> DedupMap<K, V> where K: Eq { - /// Create a new deduplicating map. - pub fn new() -> DedupMap<K, V> { - DedupMap { map: vec![] } - } - - /// Create a new map from an iterator of spanned keys and values. - pub fn from_iter<I>(diagnostics: &mut Diagnostics, iter: I) -> DedupMap<K, V> - where I: IntoIterator<Item=Spanned<(K, V)>> { - let mut map = DedupMap::new(); - map.extend(diagnostics, iter); - map - } - - /// Add a spanned key-value pair. - pub fn insert(&mut self, diagnostics: &mut Diagnostics, entry: Spanned<(K, V)>) { - if self.map.iter().any(|e| e.v.0 == entry.v.0) { - diagnostics.push(error!(entry.span, "duplicate argument")); - } else { - self.map.push(entry); - } - } - - /// Add multiple spanned key-value pairs. - pub fn extend<I>(&mut self, diagnostics: &mut Diagnostics, items: I) - where I: IntoIterator<Item=Spanned<(K, V)>> { - for item in items.into_iter() { - self.insert(diagnostics, item); - } - } - - /// Get the value corresponding to a key if it is present. - pub fn get(&self, key: K) -> Option<&V> { - self.map.iter().find(|e| e.v.0 == key).map(|e| &e.v.1) - } - - /// Get the value and its span corresponding to a key if it is present. - pub fn get_spanned(&self, key: K) -> Option<Spanned<&V>> { - self.map.iter().find(|e| e.v.0 == key) - .map(|e| Spanned { v: &e.v.1, span: e.span }) - } - - /// Call a function with the value if the key is present. - pub fn with<F>(&self, key: K, callback: F) where F: FnOnce(&V) { - if let Some(value) = self.get(key) { - callback(value); - } - } - - /// Create a new map where keys and values are mapped to new keys and - /// values. When the mapping introduces new duplicates, diagnostics are - /// generated. - pub fn dedup<F, K2, V2>(&self, diagnostics: &mut Diagnostics, mut f: F) -> DedupMap<K2, V2> - where F: FnMut(&K, &V) -> (K2, V2), K2: Eq { - let mut map = DedupMap::new(); - - for Spanned { v: (key, value), span } in self.map.iter() { - let (key, value) = f(key, value); - map.insert(diagnostics, Spanned { v: (key, value), span: *span }); - } - - map - } - - /// Iterate over the (key, value) pairs. - pub fn iter(&self) -> impl Iterator<Item=&(K, V)> { - self.map.iter().map(|e| &e.v) - } -} - -/// A map for storing a value for axes given by keyword arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct AxisMap<V>(DedupMap<AxisKey, V>); - -impl<V: Value> AxisMap<V> { - /// Parse an axis map from the object. - pub fn parse<K>( - diagnostics: &mut Diagnostics, - object: &mut Object, - ) -> AxisMap<V> where K: Key + Into<AxisKey> { - let values: Vec<_> = object - .get_all_spanned::<K, V>(diagnostics) - .map(|s| s.map(|(k, v)| (k.into(), v))) - .collect(); - - AxisMap(DedupMap::from_iter(diagnostics, values)) - } - - /// Deduplicate from specific or generic to just specific axes. - pub fn dedup(&self, diagnostics: &mut Diagnostics, axes: LayoutAxes) -> DedupMap<SpecificAxis, V> - where V: Clone { - self.0.dedup(diagnostics, |key, val| (key.to_specific(axes), val.clone())) - } -} - -/// A map for storing values for axes that are given through a combination of -/// (two) positional and keyword arguments. -#[derive(Debug, Clone, PartialEq)] -pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>); - -impl<V: Value> PosAxisMap<V> { - /// Parse a positional/axis map from the function arguments. - pub fn parse<K>( - diagnostics: &mut Diagnostics, - args: &mut FuncArgs, - ) -> PosAxisMap<V> where K: Key + Into<AxisKey> { - let mut map = DedupMap::new(); - - for &key in &[PosAxisKey::First, PosAxisKey::Second] { - if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(diagnostics) { - map.insert(diagnostics, Spanned { v: (key, v), span }) - } - } - - let keywords: Vec<_> = args.key - .get_all_spanned::<K, V>(diagnostics) - .map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v))) - .collect(); - - map.extend(diagnostics, keywords); - - PosAxisMap(map) - } - - /// Deduplicate from positional arguments and keyword arguments for generic - /// or specific axes to just generic axes. - pub fn dedup<F>( - &self, - diagnostics: &mut Diagnostics, - axes: LayoutAxes, - mut f: F, - ) -> DedupMap<GenericAxis, V> - where - F: FnMut(&V) -> Option<GenericAxis>, - V: Clone, - { - self.0.dedup(diagnostics, |key, val| { - (match key { - PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary), - PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary), - PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), - PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, - }, val.clone()) - }) - } -} - -/// A map for storing padding given for a combination of all sides, opposing -/// sides or single sides. -#[derive(Debug, Clone, PartialEq)] -pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<ScaleLength>>); - -impl PaddingMap { - /// Parse a padding map from the function arguments. - pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap { - let mut map = DedupMap::new(); - - let all = args.key.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics, "margins"); - if let Some(Spanned { v, span }) = all { - map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span }); - } - - let paddings: Vec<_> = args.key - .get_all_spanned::<PaddingKey<AxisKey>, Defaultable<ScaleLength>>(diagnostics) - .map(|s| s.map(|(k, v)| (k, v.into()))) - .collect(); - - map.extend(diagnostics, paddings); - - PaddingMap(map) - } - - /// Apply the specified padding on a value box of optional, scalable sizes. - pub fn apply( - &self, - diagnostics: &mut Diagnostics, - axes: LayoutAxes, - padding: &mut Value4<Option<ScaleLength>> - ) { - use PaddingKey::*; - - let map = self.0.dedup(diagnostics, |key, &val| { - (match key { - All => All, - Both(axis) => Both(axis.to_specific(axes)), - Side(axis, alignment) => { - let generic = axis.to_generic(axes); - let axis = axis.to_specific(axes); - Side(axis, alignment.to_specific(axes, generic)) - } - }, val) - }); - - map.with(All, |&val| padding.set_all(val)); - map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); - map.with(Both(Vertical), |&val| padding.set_vertical(val)); - - for &(key, val) in map.iter() { - if let Side(_, alignment) = key { - match alignment { - AlignmentValue::Left => padding.left = val, - AlignmentValue::Right => padding.right = val, - AlignmentValue::Top => padding.top = val, - AlignmentValue::Bottom => padding.bottom = val, - _ => {}, - } - } - } - } -} diff --git a/src/syntax/func/mod.rs b/src/syntax/func/mod.rs deleted file mode 100644 index 37dccc3d..00000000 --- a/src/syntax/func/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Primitives for argument parsing in library functions. - -use std::iter::FromIterator; -use crate::diagnostic::{Diagnostic, Diagnostics}; -use super::expr::{Expr, Ident, Tuple, Object, Pair}; -use super::span::{Span, Spanned}; - -pub_use_mod!(maps); -pub_use_mod!(keys); -pub_use_mod!(values); - -/// An invocation of a function. -#[derive(Debug, Clone, PartialEq)] -pub struct FuncCall<'s> { - pub header: FuncHeader, - /// The body as a raw string containing what's inside of the brackets. - pub body: Option<Spanned<&'s str>>, -} - -/// The parsed header of a function (everything in the first set of brackets). -#[derive(Debug, Clone, PartialEq)] -pub struct FuncHeader { - pub name: Spanned<Ident>, - pub args: FuncArgs, -} - -/// The positional and keyword arguments passed to a function. -#[derive(Debug, Default, Clone, PartialEq)] -pub struct FuncArgs { - /// The positional arguments. - pub pos: Tuple, - /// They keyword arguments. - pub key: Object, -} - -impl FuncArgs { - /// Create new empty function arguments. - pub fn new() -> FuncArgs { - FuncArgs { - pos: Tuple::new(), - key: Object::new(), - } - } - - /// Add an argument. - pub fn add(&mut self, arg: Spanned<FuncArg>) { - match arg.v { - FuncArg::Pos(item) => self.pos.add(Spanned::new(item, arg.span)), - FuncArg::Key(pair) => self.key.add(Spanned::new(pair, arg.span)), - } - } - - /// Iterate over all arguments. - pub fn into_iter(self) -> impl Iterator<Item=Spanned<FuncArg>> { - let pos = self.pos.items.into_iter() - .map(|spanned| spanned.map(|item| FuncArg::Pos(item))); - - let key = self.key.pairs.into_iter() - .map(|spanned| spanned.map(|pair| FuncArg::Key(pair))); - - pos.chain(key) - } -} - -impl FromIterator<Spanned<FuncArg>> for FuncArgs { - fn from_iter<I: IntoIterator<Item=Spanned<FuncArg>>>(iter: I) -> Self { - let mut args = FuncArgs::new(); - for item in iter.into_iter() { - args.add(item); - } - args - } -} - -/// Either a positional or keyword argument. -#[derive(Debug, Clone, PartialEq)] -pub enum FuncArg { - /// A positional argument. - Pos(Expr), - /// A keyword argument. - Key(Pair), -} - -/// Extra methods on [`Options`](Option) used for argument parsing. -pub trait OptionExt<T>: Sized { - /// Calls `f` with `val` if this is `Some(val)`. - fn with(self, f: impl FnOnce(T)); - - /// Add an error about a missing argument `arg` with the given span if the - /// option is `None`. - fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self; -} - -impl<T> OptionExt<T> for Option<T> { - fn with(self, f: impl FnOnce(T)) { - if let Some(val) = self { - f(val); - } - } - - fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self { - if self.is_none() { - diagnostics.push(error!(span, "missing argument: {}", arg)); - } - self - } -} diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs deleted file mode 100644 index d5e9c6e8..00000000 --- a/src/syntax/func/values.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Value types for extracting function arguments. - -use std::fmt::{self, Display, Formatter}; -use fontdock::{FontStyle, FontWeight, FontWidth}; - -use crate::layout::prelude::*; -use crate::length::{Length, ScaleLength}; -use crate::paper::Paper; -use super::*; - -use self::AlignmentValue::*; - -/// Value types are used to extract the values of positional and keyword -/// arguments from [`Tuples`](crate::syntax::expr::Tuple) and -/// [`Objects`](crate::syntax::expr::Object). They represent the value part of -/// an argument. -/// ```typst -/// [func: value, key=value] -/// ^^^^^ ^^^^^ -/// ``` -/// -/// # Example implementation -/// An implementation for `bool` might look as follows: -/// ``` -/// # use typstc::error; -/// # use typstc::diagnostic::Diagnostic; -/// # use typstc::syntax::expr::Expr; -/// # use typstc::syntax::func::Value; -/// # use typstc::syntax::span::Spanned; -/// # struct Bool; /* -/// impl Value for bool { -/// # */ impl Value for Bool { -/// fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { -/// match expr.v { -/// # /* -/// Expr::Bool(b) => Ok(b), -/// # */ Expr::Bool(_) => Ok(Bool), -/// other => Err(error!("expected bool, found {}", other.name())), -/// } -/// } -/// } -/// ``` -pub trait Value: Sized { - /// Parse an expression into this value or return an error if the expression - /// is valid for this value type. - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic>; -} - -impl<V: Value> Value for Spanned<V> { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - let span = expr.span; - V::parse(expr).map(|v| Spanned { v, span }) - } -} - -/// Implements [`Value`] for types that just need to match on expressions. -macro_rules! value { - ($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { - impl Value for $type { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - #[allow(unreachable_patterns)] - match expr.v { - $($p => Ok($r)),*, - other => Err( - error!("expected {}, found {}", $name, other.name()) - ), - } - } - } - }; -} - -value!(Expr, "expression", e => e); - -value!(Ident, "identifier", Expr::Ident(i) => i); -value!(String, "string", Expr::Str(s) => s); -value!(f64, "number", Expr::Number(n) => n); -value!(bool, "bool", Expr::Bool(b) => b); -value!(Length, "length", Expr::Length(l) => l); -value!(Tuple, "tuple", Expr::Tuple(t) => t); -value!(Object, "object", Expr::Object(o) => o); - -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); - -value!(StringLike, "identifier or string", - Expr::Ident(Ident(s)) => StringLike(s), - Expr::Str(s) => StringLike(s), -); - -impl From<StringLike> for String { - fn from(like: StringLike) -> String { - like.0 - } -} - -/// A value type that matches the identifier `default` or a value type `V` and -/// implements `Into<Option>` yielding `Option::Some(V)` for a value and -/// `Option::None` for `default`. -/// -/// # Example -/// ``` -/// # use typstc::syntax::func::{FuncArgs, Defaultable}; -/// # use typstc::length::Length; -/// # let mut args = FuncArgs::new(); -/// # let mut errors = vec![]; -/// args.key.get::<Defaultable<Length>>(&mut errors, "length"); -/// ``` -/// This will yield. -/// ```typst -/// [func: length=default] => None -/// [func: length=2cm] => Some(Length::cm(2.0)) -/// ``` -pub struct Defaultable<V>(pub Option<V>); - -impl<V: Value> Value for Defaultable<V> { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - Ok(Defaultable(match expr.v { - Expr::Ident(ident) if ident.as_str() == "default" => None, - _ => Some(V::parse(expr)?) - })) - } -} - -impl<V> From<Defaultable<V>> for Option<V> { - fn from(defaultable: Defaultable<V>) -> Option<V> { - defaultable.0 - } -} - -impl Value for FontStyle { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - FontStyle::from_name(Ident::parse(expr)?.as_str()) - .ok_or_else(|| error!("invalid font style")) - } -} - -/// The additional boolean specifies whether a number was clamped into the range -/// 100 - 900 to make it a valid font weight. -impl Value for FontWeight { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - match expr.v { - Expr::Number(weight) => { - let weight = weight.round(); - if weight >= 100.0 && weight <= 900.0 { - Ok(FontWeight(weight as u16)) - } else { - let clamped = weight.min(900.0).max(100.0); - Ok(FontWeight(clamped as u16)) - } - } - Expr::Ident(id) => { - FontWeight::from_name(id.as_str()) - .ok_or_else(|| error!("invalid font weight")) - } - other => Err( - error!("expected identifier or number, found {}", other.name()) - ), - } - } -} - -/// The additional boolean specifies whether a number was clamped into the range -/// 1 - 9 to make it a valid font width. -impl Value for FontWidth { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - match expr.v { - Expr::Number(width) => { - let width = width.round(); - if width >= 1.0 && width <= 9.0 { - Ok(FontWidth::new(width as u16).unwrap()) - } else { - let clamped = width.min(9.0).max(1.0); - Ok(FontWidth::new(clamped as u16).unwrap()) - } - } - Expr::Ident(id) => { - FontWidth::from_name(id.as_str()) - .ok_or_else(|| error!("invalid font width")) - } - other => Err( - error!("expected identifier or number, found {}", other.name()) - ), - } - } -} - -impl Value for Paper { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - Paper::from_name(Ident::parse(expr)?.as_str()) - .ok_or_else(|| error!("invalid paper type")) - } -} - -impl Value for Direction { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - Ok(match Ident::parse(expr)?.as_str() { - "left-to-right" | "ltr" | "LTR" => LeftToRight, - "right-to-left" | "rtl" | "RTL" => RightToLeft, - "top-to-bottom" | "ttb" | "TTB" => TopToBottom, - "bottom-to-top" | "btt" | "BTT" => BottomToTop, - _ => return Err(error!("invalid direction")) - }) - } -} - -/// A value type that matches identifiers that are valid alignments like -/// `origin` or `right`. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[allow(missing_docs)] -pub enum AlignmentValue { - /// A generic alignment. - Align(Alignment), - Left, - Top, - Right, - Bottom, -} - -impl AlignmentValue { - /// The specific axis this alignment corresponds to. `None` if the alignment - /// is generic. - pub fn axis(self) -> Option<SpecificAxis> { - match self { - Left | Right => Some(Horizontal), - Top | Bottom => Some(Vertical), - Align(_) => None, - } - } - - /// The generic version of this alignment on the given axis in the given - /// system of layouting axes. - /// - /// Returns `None` if the alignment is invalid for the given axis. - pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option<Alignment> { - let specific = axis.to_specific(axes); - let positive = axes.get(axis).is_positive(); - - // The alignment matching the origin of the positive coordinate direction. - let start = if positive { Origin } else { End }; - - match (self, specific) { - (Align(alignment), _) => Some(alignment), - (Left, Horizontal) | (Top, Vertical) => Some(start), - (Right, Horizontal) | (Bottom, Vertical) => Some(start.inv()), - _ => None - } - } - - /// The specific version of this alignment on the given axis in the given - /// system of layouting axes. - pub fn to_specific(self, axes: LayoutAxes, axis: GenericAxis) -> AlignmentValue { - let direction = axes.get(axis); - if let Align(alignment) = self { - match (direction, alignment) { - (LeftToRight, Origin) | (RightToLeft, End) => Left, - (LeftToRight, End) | (RightToLeft, Origin) => Right, - (TopToBottom, Origin) | (BottomToTop, End) => Top, - (TopToBottom, End) | (BottomToTop, Origin) => Bottom, - (_, Center) => self, - } - } else { - self - } - } -} - -impl Value for AlignmentValue { - fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> { - Ok(match Ident::parse(expr)?.as_str() { - "origin" => Align(Origin), - "center" => Align(Center), - "end" => Align(End), - "left" => Left, - "top" => Top, - "right" => Right, - "bottom" => Bottom, - _ => return Err(error!("invalid alignment")) - }) - } -} - -impl Display for AlignmentValue { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Align(Origin) => write!(f, "origin"), - Align(Center) => write!(f, "center"), - Align(End) => write!(f, "end"), - Left => write!(f, "left"), - Top => write!(f, "top"), - Right => write!(f, "right"), - Bottom => write!(f, "bottom"), - } - } -} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index b67d8cd7..e844fdf1 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,183 +1,14 @@ //! Syntax models, parsing and tokenization. -use std::any::Any; -use std::fmt::Debug; -use async_trait::async_trait; -use serde::Serialize; - -use crate::{Pass, Feedback}; -use crate::layout::{LayoutContext, Commands, Command}; -use self::span::{Spanned, SpanVec}; - #[cfg(test)] #[macro_use] mod test; +pub mod decoration; pub mod expr; -pub mod func; +pub mod model; +pub mod parsing; pub mod span; -pub_use_mod!(scope); -pub_use_mod!(parsing); -pub_use_mod!(tokens); - -/// Represents a parsed piece of source that can be layouted and in the future -/// also be queried for information used for refactorings, autocomplete, etc. -#[async_trait(?Send)] -pub trait Model: Debug + ModelBounds { - /// Layout the model into a sequence of commands processed by a - /// [`ModelLayouter`](crate::layout::ModelLayouter). - async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>; -} - -/// A tree representation of source code. -#[derive(Debug, Default, Clone, PartialEq)] -pub struct SyntaxModel { - /// The syntactical elements making up this model. - pub nodes: SpanVec<Node>, -} - -impl SyntaxModel { - /// Create an empty syntax model. - pub fn new() -> SyntaxModel { - SyntaxModel { nodes: vec![] } - } - - /// Add a node to the model. - pub fn add(&mut self, node: Spanned<Node>) { - self.nodes.push(node); - } -} - -#[async_trait(?Send)] -impl Model for SyntaxModel { - async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> { - Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new()) - } -} - -/// A node in the [syntax model](SyntaxModel). -#[derive(Debug, Clone)] -pub enum Node { - /// Whitespace containing less than two newlines. - Space, - /// Whitespace with more than two newlines. - Parbreak, - /// A forced line break. - Linebreak, - /// Plain text. - Text(String), - /// Lines of raw text. - Raw(Vec<String>), - /// Italics were enabled / disabled. - ToggleItalic, - /// Bolder was enabled / disabled. - ToggleBolder, - /// A submodel, typically a function invocation. - Model(Box<dyn Model>), -} - -impl PartialEq for Node { - fn eq(&self, other: &Node) -> bool { - use Node::*; - match (self, other) { - (Space, Space) => true, - (Parbreak, Parbreak) => true, - (Linebreak, Linebreak) => true, - (Text(a), Text(b)) => a == b, - (Raw(a), Raw(b)) => a == b, - (ToggleItalic, ToggleItalic) => true, - (ToggleBolder, ToggleBolder) => true, - (Model(a), Model(b)) => a == b, - _ => false, - } - } -} - -/// A list of spanned decorations. -pub type Decorations = SpanVec<Decoration>; - -/// Decorations for semantic syntax highlighting. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum Decoration { - /// A valid function name: - /// ```typst - /// [box] - /// ^^^ - /// ``` - ValidFuncName, - /// An invalid function name: - /// ```typst - /// [blabla] - /// ^^^^^^ - /// ``` - InvalidFuncName, - /// A key of a keyword argument: - /// ```typst - /// [box: width=5cm] - /// ^^^^^ - /// ``` - ArgumentKey, - /// A key in an object. - /// ```typst - /// [box: padding={ left: 1cm, right: 2cm}] - /// ^^^^ ^^^^^ - /// ``` - ObjectKey, - /// An italic word. - Italic, - /// A bold word. - Bold, -} - -impl dyn Model { - /// Downcast this model to a concrete type implementing [`Model`]. - pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static { - self.as_any().downcast_ref::<T>() - } -} - -impl PartialEq for dyn Model { - fn eq(&self, other: &dyn Model) -> bool { - self.bound_eq(other) - } -} - -impl Clone for Box<dyn Model> { - fn clone(&self) -> Self { - self.bound_clone() - } -} - -/// This trait describes bounds necessary for types implementing [`Model`]. It is -/// automatically implemented for all types that are [`Model`], [`PartialEq`], -/// [`Clone`] and `'static`. -/// -/// It is necessary to make models comparable and clonable. -pub trait ModelBounds { - /// Convert into a `dyn Any`. - fn as_any(&self) -> &dyn Any; - - /// Check for equality with another model. - fn bound_eq(&self, other: &dyn Model) -> bool; - - /// Clone into a boxed model trait object. - fn bound_clone(&self) -> Box<dyn Model>; -} - -impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static { - fn as_any(&self) -> &dyn Any { - self - } - - fn bound_eq(&self, other: &dyn Model) -> bool { - match other.as_any().downcast_ref::<Self>() { - Some(other) => self == other, - None => false, - } - } - - fn bound_clone(&self) -> Box<dyn Model> { - Box::new(self.clone()) - } -} +pub mod scope; +pub mod tokens; +pub mod value; diff --git a/src/syntax/model.rs b/src/syntax/model.rs new file mode 100644 index 00000000..4eb2abe0 --- /dev/null +++ b/src/syntax/model.rs @@ -0,0 +1,134 @@ +//! The syntax model. + +use std::any::Any; +use std::fmt::Debug; +use async_trait::async_trait; + +use crate::{Pass, Feedback}; +use crate::layout::{LayoutContext, Commands, Command}; +use super::span::{Spanned, SpanVec}; + +/// Represents a parsed piece of source that can be layouted and in the future +/// also be queried for information used for refactorings, autocomplete, etc. +#[async_trait(?Send)] +pub trait Model: Debug + ModelBounds { + /// Layout the model into a sequence of commands processed by a + /// [`ModelLayouter`](crate::layout::ModelLayouter). + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>; +} + +/// A tree representation of source code. +#[derive(Debug, Default, Clone, PartialEq)] +pub struct SyntaxModel { + /// The syntactical elements making up this model. + pub nodes: SpanVec<Node>, +} + +impl SyntaxModel { + /// Create an empty syntax model. + pub fn new() -> SyntaxModel { + SyntaxModel { nodes: vec![] } + } + + /// Add a node to the model. + pub fn add(&mut self, node: Spanned<Node>) { + self.nodes.push(node); + } +} + +#[async_trait(?Send)] +impl Model for SyntaxModel { + async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> { + Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new()) + } +} + +/// A node in the [syntax model](SyntaxModel). +#[derive(Debug, Clone)] +pub enum Node { + /// Whitespace containing less than two newlines. + Space, + /// Whitespace with more than two newlines. + Parbreak, + /// A forced line break. + Linebreak, + /// Plain text. + Text(String), + /// Lines of raw text. + Raw(Vec<String>), + /// Italics were enabled / disabled. + ToggleItalic, + /// Bolder was enabled / disabled. + ToggleBolder, + /// A submodel, typically a function invocation. + Model(Box<dyn Model>), +} + +impl PartialEq for Node { + fn eq(&self, other: &Node) -> bool { + use Node::*; + match (self, other) { + (Space, Space) => true, + (Parbreak, Parbreak) => true, + (Linebreak, Linebreak) => true, + (Text(a), Text(b)) => a == b, + (Raw(a), Raw(b)) => a == b, + (ToggleItalic, ToggleItalic) => true, + (ToggleBolder, ToggleBolder) => true, + (Model(a), Model(b)) => a == b, + _ => false, + } + } +} + +impl dyn Model { + /// Downcast this model to a concrete type implementing [`Model`]. + pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static { + self.as_any().downcast_ref::<T>() + } +} + +impl PartialEq for dyn Model { + fn eq(&self, other: &dyn Model) -> bool { + self.bound_eq(other) + } +} + +impl Clone for Box<dyn Model> { + fn clone(&self) -> Self { + self.bound_clone() + } +} + +/// This trait describes bounds necessary for types implementing [`Model`]. It is +/// automatically implemented for all types that are [`Model`], [`PartialEq`], +/// [`Clone`] and `'static`. +/// +/// It is necessary to make models comparable and clonable. +pub trait ModelBounds { + /// Convert into a `dyn Any`. + fn as_any(&self) -> &dyn Any; + + /// Check for equality with another model. + fn bound_eq(&self, other: &dyn Model) -> bool; + + /// Clone into a boxed model trait object. + fn bound_clone(&self) -> Box<dyn Model>; +} + +impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static { + fn as_any(&self) -> &dyn Any { + self + } + + fn bound_eq(&self, other: &dyn Model) -> bool { + match other.as_any().downcast_ref::<Self>() { + Some(other) => self == other, + None => false, + } + } + + fn bound_clone(&self) -> Box<dyn Model> { + Box::new(self.clone()) + } +} diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index e63d6b35..75e30177 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -2,14 +2,66 @@ use std::str::FromStr; +use crate::{Pass, Feedback}; +use super::decoration::Decoration; use super::expr::*; -use super::func::{FuncCall, FuncHeader, FuncArgs, FuncArg}; +use super::scope::Scope; use super::span::{Pos, Span, Spanned}; -use super::*; +use super::tokens::{is_newline_char, Token, Tokens, TokenMode}; +use super::model::{SyntaxModel, Node, Model}; /// A function which parses a function call into a model. pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>; +/// An invocation of a function. +#[derive(Debug, Clone, PartialEq)] +pub struct FuncCall<'s> { + pub header: FuncHeader, + /// The body as a raw string containing what's inside of the brackets. + pub body: Option<Spanned<&'s str>>, +} + +/// The parsed header of a function (everything in the first set of brackets). +#[derive(Debug, Clone, PartialEq)] +pub struct FuncHeader { + pub name: Spanned<Ident>, + pub args: FuncArgs, +} + +/// The positional and keyword arguments passed to a function. +#[derive(Debug, Default, Clone, PartialEq)] +pub struct FuncArgs { + pub pos: Tuple, + pub key: Object, +} + +impl FuncArgs { + /// Create new empty function arguments. + pub fn new() -> FuncArgs { + FuncArgs { + pos: Tuple::new(), + key: Object::new(), + } + } + + /// Add an argument. + pub fn push(&mut self, arg: Spanned<FuncArg>) { + match arg.v { + FuncArg::Pos(item) => self.pos.push(Spanned::new(item, arg.span)), + FuncArg::Key(pair) => self.key.push(Spanned::new(pair, arg.span)), + } + } +} + +/// Either a positional or keyword argument. +#[derive(Debug, Clone, PartialEq)] +pub enum FuncArg { + /// A positional argument. + Pos(Expr), + /// A keyword argument. + Key(Pair), +} + /// The state which can influence how a string of source code is parsed. /// /// Parsing is pure - when passed in the same state and source code, the output @@ -216,7 +268,7 @@ impl<'s> FuncParser<'s> { }; let behind_arg = arg.span.end; - args.add(arg); + args.push(arg); self.skip_white(); if self.eof() { @@ -348,7 +400,7 @@ impl FuncParser<'_> { let (tuple, coercable) = self.parse_tuple(); Some(if coercable { tuple.map(|v| { - v.into_iter().next().expect("tuple is coercable").v + v.0.into_iter().next().expect("tuple is coercable").v }) } else { tuple.map(|tup| Expr::Tuple(tup)) @@ -388,7 +440,7 @@ impl FuncParser<'_> { }); let behind_expr = expr.span.end; - tuple.add(expr); + tuple.push(expr); self.skip_white(); if self.eof() || self.check(Token::RightParen) { @@ -401,7 +453,7 @@ impl FuncParser<'_> { self.expect(Token::RightParen); let end = self.pos(); - let coercable = commaless && !tuple.items.is_empty(); + let coercable = commaless && !tuple.0.is_empty(); (Spanned::new(tuple, Span::new(start, end)), coercable) } @@ -440,7 +492,7 @@ impl FuncParser<'_> { let behind_value = value.span.end; let span = Span::merge(key.span, value.span); - object.add(Spanned::new(Pair { key, value }, span)); + object.push(Spanned::new(Pair { key, value }, span)); self.skip_white(); if self.eof() || self.check(Token::RightBrace) { @@ -611,7 +663,6 @@ fn unescape_raw(raw: &str) -> Vec<String> { mod tests { use crate::length::Length; use super::super::test::{check, DebugFn}; - use super::super::func::Value; use super::*; use Decoration::*; @@ -682,7 +733,7 @@ mod tests { macro_rules! tuple { ($($tts:tt)*) => { - Expr::Tuple(Tuple { items: span_vec![$($tts)*].0 }) + Expr::Tuple(Tuple(span_vec![$($tts)*].0)) }; } @@ -690,19 +741,17 @@ mod tests { ($name:tt $(, $($tts:tt)*)?) => { Expr::NamedTuple(NamedTuple::new( span_item!($name).map(|n| Ident(n.to_string())), - Z(Tuple { items: span_vec![$($($tts)*)?].0 }) + Z(Tuple(span_vec![$($($tts)*)?].0)) )) }; } macro_rules! object { ($($key:tt => $value:expr),* $(,)?) => { - Expr::Object(Object { - pairs: vec![$(Z(Pair { - key: span_item!($key).map(|k| Ident(k.to_string())), - value: Z($value), - })),*] - }) + Expr::Object(Object(vec![$(Z(Pair { + key: span_item!($key).map(|k| Ident(k.to_string())), + value: Z($value), + })),*])) }; } @@ -713,11 +762,22 @@ mod tests { } macro_rules! func { - ($name:tt $(: ($($pos:tt)*) $(, { $($key:tt)* })? )? $(; $($body:tt)*)?) => {{ + ($name:tt + $(: ($($pos:tt)*) $(, { $($key:tt => $value:expr),* })? )? + $(; $($body:tt)*)? + ) => {{ #[allow(unused_mut)] let mut args = FuncArgs::new(); - $(args.pos = Tuple::parse(Z(tuple!($($pos)*))).unwrap(); - $(args.key = Object::parse(Z(object! { $($key)* })).unwrap();)?)? + $( + let items: Vec<Spanned<Expr>> = span_vec![$($pos)*].0; + for item in items { + args.push(item.map(|v| FuncArg::Pos(v))); + } + $($(args.push(Z(FuncArg::Key(Pair { + key: span_item!($key).map(|k| Ident(k.to_string())), + value: Z($value), + })));)*)? + )? Node::Model(Box::new(DebugFn { header: FuncHeader { name: span_item!($name).map(|s| Ident(s.to_string())), @@ -1101,7 +1161,7 @@ mod tests { [func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })], [], [(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)], ); - pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b"), }); + pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") }); } #[test] diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index c6350836..83b9fdee 100644 --- a/src/syntax/scope.rs +++ b/src/syntax/scope.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter}; use crate::func::ParseFunc; use super::parsing::CallParser; -use super::Model; +use super::model::Model; /// A map from identifiers to function parsers. pub struct Scope { diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 19562fb1..52b90cee 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -1,4 +1,4 @@ -//! Spans map elements to the part of source code they originate from. +//! Mapping of values to the locations they originate from in source code. use std::fmt::{self, Debug, Formatter}; use std::ops::{Add, Sub}; diff --git a/src/syntax/test.rs b/src/syntax/test.rs index 639fbb61..38a124aa 100644 --- a/src/syntax/test.rs +++ b/src/syntax/test.rs @@ -1,9 +1,11 @@ use std::fmt::Debug; -use super::func::FuncHeader; +use super::decoration::Decoration; +use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair}; +use super::parsing::{FuncHeader, FuncArgs, FuncArg}; use super::span::Spanned; -use super::expr::{Expr, Tuple, NamedTuple, Object}; -use super::*; +use super::tokens::Token; +use super::model::{SyntaxModel, Model, Node}; /// Check whether the expected and found results are the same. pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool) @@ -65,8 +67,8 @@ function! { parse(header, body, state, f) { let cloned = header.clone(); - header.args.pos.items.clear(); - header.args.key.pairs.clear(); + header.args.pos.0.clear(); + header.args.key.0.clear(); DebugFn { header: cloned, body: body!(opt: body, state, f), @@ -104,9 +106,31 @@ impl SpanlessEq for Node { impl SpanlessEq for DebugFn { fn spanless_eq(&self, other: &DebugFn) -> bool { - self.header.name.v == other.header.name.v - && self.header.args.pos.spanless_eq(&other.header.args.pos) - && self.header.args.key.spanless_eq(&other.header.args.key) + self.header.spanless_eq(&other.header) + && self.body.spanless_eq(&other.body) + } +} + +impl SpanlessEq for FuncHeader { + fn spanless_eq(&self, other: &Self) -> bool { + self.name.spanless_eq(&other.name) && self.args.spanless_eq(&other.args) + } +} + +impl SpanlessEq for FuncArgs { + fn spanless_eq(&self, other: &Self) -> bool { + self.key.spanless_eq(&other.key) + && self.pos.spanless_eq(&other.pos) + } +} + +impl SpanlessEq for FuncArg { + fn spanless_eq(&self, other: &Self) -> bool { + match (self, other) { + (FuncArg::Pos(a), FuncArg::Pos(b)) => a.spanless_eq(b), + (FuncArg::Key(a), FuncArg::Key(b)) => a.spanless_eq(b), + _ => false, + } } } @@ -128,9 +152,7 @@ impl SpanlessEq for Expr { impl SpanlessEq for Tuple { fn spanless_eq(&self, other: &Tuple) -> bool { - self.items.len() == other.items.len() - && self.items.iter().zip(&other.items) - .all(|(x, y)| x.v.spanless_eq(&y.v)) + self.0.spanless_eq(&other.0) } } @@ -143,9 +165,13 @@ impl SpanlessEq for NamedTuple { impl SpanlessEq for Object { fn spanless_eq(&self, other: &Object) -> bool { - self.pairs.len() == other.pairs.len() - && self.pairs.iter().zip(&other.pairs) - .all(|(x, y)| x.v.key.v == y.v.key.v && x.v.value.v.spanless_eq(&y.v.value.v)) + self.0.spanless_eq(&other.0) + } +} + +impl SpanlessEq for Pair { + fn spanless_eq(&self, other: &Self) -> bool { + self.key.spanless_eq(&other.key) && self.value.spanless_eq(&other.value) } } @@ -168,6 +194,16 @@ impl<T: SpanlessEq> SpanlessEq for Box<T> { } } +impl<T: SpanlessEq> SpanlessEq for Option<T> { + fn spanless_eq(&self, other: &Self) -> bool { + match (self, other) { + (Some(a), Some(b)) => a.spanless_eq(b), + (None, None) => true, + _ => false, + } + } +} + macro_rules! impl_through_partial_eq { ($type:ty) => { impl SpanlessEq for $type { @@ -179,6 +215,7 @@ macro_rules! impl_through_partial_eq { } impl_through_partial_eq!(Token<'_>); +impl_through_partial_eq!(Ident); // Implement for string and decoration to be able to compare feedback. impl_through_partial_eq!(String); diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 4998ea19..a0af5a02 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -1,3 +1,5 @@ +//! Tokenization. + use std::iter::Peekable; use std::str::Chars; use unicode_xid::UnicodeXID; @@ -161,7 +163,6 @@ pub struct Tokens<'s> { /// similar tokens or in body mode which yields text and star, underscore, /// backtick tokens. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -#[allow(missing_docs)] pub enum TokenMode { Header, Body, 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 + } + } + } +} |
