mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Add table expressions with arg-parsing functions 🪔
This commit is contained in:
parent
4b723add38
commit
eb9c4b1a49
@ -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));
|
||||
}
|
||||
}
|
||||
|
62
src/table.rs
62
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user