From eb9c4b1a49c90e687d70e7bd712848d78ffbd909 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 15 Aug 2020 13:25:31 +0200 Subject: [PATCH] =?UTF-8?q?Add=20table=20expressions=20with=20arg-parsing?= =?UTF-8?q?=20functions=20=F0=9F=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/syntax/expr.rs | 178 ++++++++++++++++++++++++++++++++++++++++++++- src/table.rs | 62 +++++++++++----- 2 files changed, 221 insertions(+), 19 deletions(-) diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index f5ca55ab9..98c673924 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; + +/// An entry in a table expression. +/// +/// Contains the key's span and the value. +pub struct TableExprEntry { + pub key: Span, + pub val: Spanned, +} + +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(&mut self) -> Option { + 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(&mut self, f: &mut Feedback) -> Option { + 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 + where + T: TryFromExpr, + K: Into>, + { + 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 + '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 + '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::(), Some("hi".to_string())); + assert_eq!(table.len(), 1); + assert_eq!(table.take::(), 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::(&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::(1, &mut f), Some(false)); + assert_eq!(table.take_with_key::("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::().collect::>(), + [(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 f88a0ac76..2b49d4517 100644 --- a/src/table.rs +++ b/src/table.rs @@ -15,7 +15,7 @@ use std::ops::Index; #[derive(Default, Clone, PartialEq)] pub struct Table { nums: BTreeMap, - strings: BTreeMap, + strs: BTreeMap, lowest_free: u64, } @@ -24,14 +24,19 @@ impl Table { 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 Table { K: Into>, { 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 Table { K: Into>, { 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 Table { K: Into, { 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 Table { K: Into>, { 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 Table { 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 { + self.nums.iter() + } + + /// Iterate over the string key-value pairs. + pub fn strs(&self) -> std::collections::btree_map::Iter { + self.strs.iter() + } + + /// Iterate over all values in the table. + pub fn values(&self) -> impl Iterator { + self.nums().map(|(_, v)| v).chain(self.strs().map(|(_, v)| v)) + } } impl<'a, K, V> Index for Table @@ -125,13 +145,13 @@ where impl Debug for Table { 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 Debug for Table { /// 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 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 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) } }