summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-15 13:25:31 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-15 13:25:31 +0200
commiteb9c4b1a49c90e687d70e7bd712848d78ffbd909 (patch)
treeaf50502c3b5c1ddacfd3d2607ccf85f6bb1436ed /src
parent4b723add38f58c1c6fb1f13b5d2222b6def7f230 (diff)
Add table expressions with arg-parsing functions 🪔
Diffstat (limited to 'src')
-rw-r--r--src/syntax/expr.rs178
-rw-r--r--src/table.rs62
2 files changed, 221 insertions, 19 deletions
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index f5ca55ab..98c67392 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -10,8 +10,9 @@ use fontdock::{FontStyle, FontWeight, FontWidth};
use crate::layout::{Dir, SpecAlign};
use crate::length::{Length, ScaleLength};
use crate::paper::Paper;
+use crate::table::{BorrowedKey, Table};
use crate::Feedback;
-use super::span::{SpanVec, Spanned};
+use super::span::{Span, SpanVec, Spanned};
use super::tokens::is_identifier;
use super::tree::SyntaxTree;
@@ -398,6 +399,120 @@ impl Debug for Object {
}
}
+/// A table expression.
+///
+/// # Example
+/// ```typst
+/// (false, 12cm, greeting="hi")
+/// ```
+pub type TableExpr = Table<TableExprEntry>;
+
+/// An entry in a table expression.
+///
+/// Contains the key's span and the value.
+pub struct TableExprEntry {
+ pub key: Span,
+ pub val: Spanned<Expr>,
+}
+
+impl TableExpr {
+ /// Retrieve and remove the matching value with the lowest number key,
+ /// skipping and ignoring all non-matching entries with lower keys.
+ pub fn take<T: TryFromExpr>(&mut self) -> Option<T> {
+ for (&key, entry) in self.nums() {
+ let expr = entry.val.as_ref();
+ if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
+ self.remove(key);
+ return Some(val);
+ }
+ }
+ None
+ }
+
+ /// Retrieve and remove the matching value with the lowest number key,
+ /// removing and generating errors for all non-matching entries with lower
+ /// keys.
+ pub fn expect<T: TryFromExpr>(&mut self, f: &mut Feedback) -> Option<T> {
+ while let Some((num, _)) = self.first() {
+ let entry = self.remove(num).unwrap();
+ if let Some(val) = T::try_from_expr(entry.val.as_ref(), f) {
+ return Some(val);
+ }
+ }
+ None
+ }
+
+ /// Retrieve and remove a matching value associated with the given key if
+ /// there is any.
+ ///
+ /// Generates an error if the key exists but the value does not match.
+ pub fn take_with_key<'a, T, K>(&mut self, key: K, f: &mut Feedback) -> Option<T>
+ where
+ T: TryFromExpr,
+ K: Into<BorrowedKey<'a>>,
+ {
+ self.remove(key).and_then(|entry| {
+ let expr = entry.val.as_ref();
+ T::try_from_expr(expr, f)
+ })
+ }
+
+ /// Retrieve and remove all matching pairs with number keys, skipping and
+ /// ignoring non-matching entries.
+ ///
+ /// The pairs are returned in order of increasing keys.
+ pub fn take_all_num<'a, T>(&'a mut self) -> impl Iterator<Item = (u64, T)> + 'a
+ where
+ T: TryFromExpr,
+ {
+ let mut skip = 0;
+ std::iter::from_fn(move || {
+ for (&key, entry) in self.nums().skip(skip) {
+ let expr = entry.val.as_ref();
+ if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
+ self.remove(key);
+ return Some((key, val));
+ }
+ skip += 1;
+ }
+
+ None
+ })
+ }
+
+ /// Retrieve and remove all matching pairs with string keys, skipping and
+ /// ignoring non-matching entries.
+ ///
+ /// The pairs are returned in order of increasing keys.
+ pub fn take_all_str<'a, T>(&'a mut self) -> impl Iterator<Item = (String, T)> + 'a
+ where
+ T: TryFromExpr,
+ {
+ let mut skip = 0;
+ std::iter::from_fn(move || {
+ for (key, entry) in self.strs().skip(skip) {
+ let expr = entry.val.as_ref();
+ if let Some(val) = T::try_from_expr(expr, &mut Feedback::new()) {
+ let key = key.clone();
+ self.remove(&key);
+ return Some((key, val));
+ }
+ skip += 1;
+ }
+
+ None
+ })
+ }
+
+ /// Generated `"unexpected argument"` errors for all remaining entries.
+ pub fn unexpected(&self, f: &mut Feedback) {
+ for entry in self.values() {
+ let span = Span::merge(entry.key, entry.val.span);
+ error!(@f, span, "unexpected argument");
+ }
+ }
+}
+
/// A trait for converting expressions into specific types.
pub trait TryFromExpr: Sized {
// This trait takes references because we don't want to move the expression
@@ -581,3 +696,64 @@ impl TryFromExpr for FontWidth {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn entry(expr: Expr) -> TableExprEntry {
+ TableExprEntry {
+ key: Span::ZERO,
+ val: Spanned::zero(expr),
+ }
+ }
+
+ #[test]
+ fn test_table_take_removes_correct_entry() {
+ let mut table = TableExpr::new();
+ table.insert(1, entry(Expr::Bool(false)));
+ table.insert(2, entry(Expr::Str("hi".to_string())));
+ assert_eq!(table.take::<String>(), Some("hi".to_string()));
+ assert_eq!(table.len(), 1);
+ assert_eq!(table.take::<bool>(), Some(false));
+ assert!(table.is_empty());
+ }
+
+ #[test]
+ fn test_table_expect_errors_about_previous_entries() {
+ let mut f = Feedback::new();
+ let mut table = TableExpr::new();
+ table.insert(1, entry(Expr::Bool(false)));
+ table.insert(3, entry(Expr::Str("hi".to_string())));
+ table.insert(5, entry(Expr::Bool(true)));
+ assert_eq!(table.expect::<String>(&mut f), Some("hi".to_string()));
+ assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected string, found bool")]);
+ assert_eq!(table.len(), 1);
+ }
+
+ #[test]
+ fn test_table_take_with_key_removes_the_entry() {
+ let mut f = Feedback::new();
+ let mut table = TableExpr::new();
+ table.insert(1, entry(Expr::Bool(false)));
+ table.insert("hi", entry(Expr::Bool(true)));
+ assert_eq!(table.take_with_key::<bool, _>(1, &mut f), Some(false));
+ assert_eq!(table.take_with_key::<f64, _>("hi", &mut f), None);
+ assert_eq!(f.diagnostics, [error!(Span::ZERO, "expected number, found bool")]);
+ assert!(table.is_empty());
+ }
+
+ #[test]
+ fn test_table_take_all_removes_the_correct_entries() {
+ let mut table = TableExpr::new();
+ table.insert(1, entry(Expr::Bool(false)));
+ table.insert(3, entry(Expr::Number(0.0)));
+ table.insert(7, entry(Expr::Bool(true)));
+ assert_eq!(
+ table.take_all_num::<bool>().collect::<Vec<_>>(),
+ [(1, false), (7, true)],
+ );
+ assert_eq!(table.len(), 1);
+ assert_eq!(table[3].val.v, Expr::Number(0.0));
+ }
+}
diff --git a/src/table.rs b/src/table.rs
index f88a0ac7..2b49d451 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -15,7 +15,7 @@ use std::ops::Index;
#[derive(Default, Clone, PartialEq)]
pub struct Table<V> {
nums: BTreeMap<u64, V>,
- strings: BTreeMap<String, V>,
+ strs: BTreeMap<String, V>,
lowest_free: u64,
}
@@ -24,14 +24,19 @@ impl<V> Table<V> {
pub fn new() -> Self {
Self {
nums: BTreeMap::new(),
- strings: BTreeMap::new(),
+ strs: BTreeMap::new(),
lowest_free: 0,
}
}
/// The total number of entries in the table.
pub fn len(&self) -> usize {
- self.nums.len() + self.strings.len()
+ self.nums.len() + self.strs.len()
+ }
+
+ /// Whether the table contains no entries.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
}
/// The first number key-value pair (with lowest number).
@@ -50,8 +55,8 @@ impl<V> Table<V> {
K: Into<BorrowedKey<'a>>,
{
match key.into() {
- BorrowedKey::Number(num) => self.nums.get(&num),
- BorrowedKey::Str(string) => self.strings.get(string),
+ BorrowedKey::Num(num) => self.nums.get(&num),
+ BorrowedKey::Str(string) => self.strs.get(string),
}
}
@@ -61,8 +66,8 @@ impl<V> Table<V> {
K: Into<BorrowedKey<'a>>,
{
match key.into() {
- BorrowedKey::Number(num) => self.nums.get_mut(&num),
- BorrowedKey::Str(string) => self.strings.get_mut(string),
+ BorrowedKey::Num(num) => self.nums.get_mut(&num),
+ BorrowedKey::Str(string) => self.strs.get_mut(string),
}
}
@@ -72,14 +77,14 @@ impl<V> Table<V> {
K: Into<OwnedKey>,
{
match key.into() {
- OwnedKey::Number(num) => {
+ OwnedKey::Num(num) => {
self.nums.insert(num, value);
if self.lowest_free == num {
self.lowest_free += 1;
}
}
OwnedKey::Str(string) => {
- self.strings.insert(string, value);
+ self.strs.insert(string, value);
}
}
}
@@ -90,11 +95,11 @@ impl<V> Table<V> {
K: Into<BorrowedKey<'a>>,
{
match key.into() {
- BorrowedKey::Number(num) => {
+ BorrowedKey::Num(num) => {
self.lowest_free = self.lowest_free.min(num);
self.nums.remove(&num)
}
- BorrowedKey::Str(string) => self.strings.remove(string),
+ BorrowedKey::Str(string) => self.strs.remove(string),
}
}
@@ -109,6 +114,21 @@ impl<V> Table<V> {
self.nums.insert(self.lowest_free, value);
self.lowest_free += 1;
}
+
+ /// Iterate over the number key-value pairs.
+ pub fn nums(&self) -> std::collections::btree_map::Iter<u64, V> {
+ self.nums.iter()
+ }
+
+ /// Iterate over the string key-value pairs.
+ pub fn strs(&self) -> std::collections::btree_map::Iter<String, V> {
+ self.strs.iter()
+ }
+
+ /// Iterate over all values in the table.
+ pub fn values(&self) -> impl Iterator<Item = &V> {
+ self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v))
+ }
}
impl<'a, K, V> Index<K> for Table<V>
@@ -125,13 +145,13 @@ where
impl<V: Debug> Debug for Table<V> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("(")?;
- if f.alternate() && (!self.nums.is_empty() || !self.strings.is_empty()) {
+ if f.alternate() && (!self.nums.is_empty() || !self.strs.is_empty()) {
f.write_str("\n")?;
}
let len = self.len();
- let nums = self.nums.iter().map(|(k, v)| (k as &dyn Debug, v));
- let strings = self.strings.iter().map(|(k, v)| (k as &dyn Debug, v));
+ let nums = self.nums().map(|(k, v)| (k as &dyn Debug, v));
+ let strings = self.strs().map(|(k, v)| (k as &dyn Debug, v));
let pairs = nums.chain(strings);
for (i, (key, value)) in pairs.enumerate() {
@@ -159,13 +179,13 @@ impl<V: Debug> Debug for Table<V> {
/// The owned variant of a table key.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum OwnedKey {
- Number(u64),
+ Num(u64),
Str(String),
}
impl From<u64> for OwnedKey {
fn from(num: u64) -> Self {
- Self::Number(num)
+ Self::Num(num)
}
}
@@ -184,13 +204,19 @@ impl From<&str> for OwnedKey {
/// The borrowed variant of a table key.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum BorrowedKey<'a> {
- Number(u64),
+ Num(u64),
Str(&'a str),
}
impl From<u64> for BorrowedKey<'static> {
fn from(num: u64) -> Self {
- Self::Number(num)
+ Self::Num(num)
+ }
+}
+
+impl<'a> From<&'a String> for BorrowedKey<'a> {
+ fn from(string: &'a String) -> Self {
+ Self::Str(&string)
}
}