summaryrefslogtreecommitdiff
path: root/src/syntax
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-02 16:31:34 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-02 16:31:34 +0200
commit533374db14087ac54fdc86afa5f009487ac1b850 (patch)
tree0970eb1ca893fe45369d622b5bc1f226f0f66004 /src/syntax
parent2188ef6b899cc10c84ed985e9ad9049fcc3eb662 (diff)
Refactor argument parsing 🔬
Diffstat (limited to 'src/syntax')
-rw-r--r--src/syntax/decoration.rs41
-rw-r--r--src/syntax/expr.rs242
-rw-r--r--src/syntax/func/keys.rs169
-rw-r--r--src/syntax/func/maps.rs233
-rw-r--r--src/syntax/func/mod.rs107
-rw-r--r--src/syntax/func/values.rs301
-rw-r--r--src/syntax/mod.rs181
-rw-r--r--src/syntax/model.rs134
-rw-r--r--src/syntax/parsing.rs100
-rw-r--r--src/syntax/scope.rs2
-rw-r--r--src/syntax/span.rs2
-rw-r--r--src/syntax/test.rs65
-rw-r--r--src/syntax/tokens.rs3
-rw-r--r--src/syntax/value.rs193
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
+ }
+ }
+ }
+}