diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/template.rs | 23 | ||||
| -rw-r--r-- | src/library/heading.rs | 12 | ||||
| -rw-r--r-- | src/library/list.rs | 126 | ||||
| -rw-r--r-- | src/library/mod.rs | 3 | ||||
| -rw-r--r-- | src/library/numbering.rs | 108 | ||||
| -rw-r--r-- | src/library/utility.rs | 65 | ||||
| -rw-r--r-- | src/parse/scanner.rs | 6 | ||||
| -rw-r--r-- | src/parse/tokens.rs | 2 | ||||
| -rw-r--r-- | src/util/eco_string.rs | 10 |
9 files changed, 251 insertions, 104 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs index 0db63535..68974452 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -13,7 +13,7 @@ use crate::diag::StrResult; use crate::layout::{Layout, LayoutNode}; use crate::library::prelude::*; use crate::library::{ - DecoNode, FlowChild, FlowNode, Labelling, ListItem, ListNode, PageNode, ParChild, + DecoNode, FlowChild, FlowNode, ListItem, ListKind, ListNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, TextNode, ORDERED, UNDERLINE, UNORDERED, }; use crate::util::EcoString; @@ -307,14 +307,14 @@ impl<'a> Builder<'a> { builder.staged.push((template, styles)); return Ok(()); } - Template::List(item) if builder.labelling == UNORDERED => { + Template::List(item) if builder.kind == UNORDERED => { builder.wide |= builder.staged.iter().any(|&(t, _)| *t == Template::Parbreak); builder.staged.clear(); builder.items.push(item.clone()); return Ok(()); } - Template::Enum(item) if builder.labelling == ORDERED => { + Template::Enum(item) if builder.kind == ORDERED => { builder.wide |= builder.staged.iter().any(|&(t, _)| *t == Template::Parbreak); builder.staged.clear(); @@ -376,7 +376,7 @@ impl<'a> Builder<'a> { Template::List(item) => { self.list = Some(ListBuilder { styles, - labelling: UNORDERED, + kind: UNORDERED, items: vec![item.clone()], wide: false, staged: vec![], @@ -385,7 +385,7 @@ impl<'a> Builder<'a> { Template::Enum(item) => { self.list = Some(ListBuilder { styles, - labelling: ORDERED, + kind: ORDERED, items: vec![item.clone()], wide: false, staged: vec![], @@ -447,13 +447,12 @@ impl<'a> Builder<'a> { /// Finish the currently built list. fn finish_list(&mut self, vm: &mut Vm) -> TypResult<()> { - let ListBuilder { styles, labelling, items, wide, staged } = - match self.list.take() { - Some(list) => list, - None => return Ok(()), - }; + let ListBuilder { styles, kind, items, wide, staged } = match self.list.take() { + Some(list) => list, + None => return Ok(()), + }; - let template = match labelling { + let template = match kind { UNORDERED => Template::show(ListNode::<UNORDERED> { items, wide, start: 1 }), ORDERED | _ => Template::show(ListNode::<ORDERED> { items, wide, start: 1 }), }; @@ -492,7 +491,7 @@ impl<'a> Builder<'a> { /// Builds an unordered or ordered list from items. struct ListBuilder<'a> { styles: StyleChain<'a>, - labelling: Labelling, + kind: ListKind, items: Vec<ListItem>, wide: bool, staged: Vec<(&'a Template, StyleChain<'a>)>, diff --git a/src/library/heading.rs b/src/library/heading.rs index dd78b147..3438c7b7 100644 --- a/src/library/heading.rs +++ b/src/library/heading.rs @@ -113,9 +113,9 @@ pub enum Leveled<T> { impl<T: Cast> Leveled<T> { /// Resolve the value based on the level. pub fn resolve(self, vm: &mut Vm, level: usize) -> TypResult<T> { - match self { - Self::Value(value) => Ok(value), - Self::Mapping(mapping) => Ok(mapping(level)), + Ok(match self { + Self::Value(value) => value, + Self::Mapping(mapping) => mapping(level), Self::Func(func, span) => { let args = Args { span, @@ -125,9 +125,9 @@ impl<T: Cast> Leveled<T> { value: Spanned::new(Value::Int(level as i64), span), }], }; - func.call(vm, args)?.cast().at(span) + func.call(vm, args)?.cast().at(span)? } - } + }) } } @@ -138,7 +138,7 @@ impl<T: Cast> Cast<Spanned<Value>> for Leveled<T> { fn cast(value: Spanned<Value>) -> StrResult<Self> { match value.v { - Value::Func(f) => Ok(Self::Func(f, value.span)), + Value::Func(v) => Ok(Self::Func(v, value.span)), v => T::cast(v) .map(Self::Value) .map_err(|msg| with_alternative(msg, "function")), diff --git a/src/library/list.rs b/src/library/list.rs index 1ec89da2..fe499cb1 100644 --- a/src/library/list.rs +++ b/src/library/list.rs @@ -1,11 +1,13 @@ //! Unordered (bulleted) and ordered (numbered) lists. use super::prelude::*; -use super::{GridNode, ParNode, TextNode, TrackSizing}; +use super::{GridNode, Numbering, ParNode, TextNode, TrackSizing}; + +use crate::parse::Scanner; /// An unordered or ordered list. #[derive(Debug, Hash)] -pub struct ListNode<const L: Labelling> { +pub struct ListNode<const L: ListKind> { /// The individual bulleted or numbered items. pub items: Vec<ListItem>, /// If true, there is paragraph spacing between the items, if false @@ -25,13 +27,15 @@ pub struct ListItem { } #[class] -impl<const L: Labelling> ListNode<L> { +impl<const L: ListKind> ListNode<L> { + /// How the list is labelled. + pub const LABEL: Label = Label::Default; + /// The spacing between the list items of a non-wide list. + pub const SPACING: Linear = Linear::zero(); /// The indentation of each item's label. pub const LABEL_INDENT: Linear = Relative::new(0.0).into(); /// The space between the label and the body of each item. pub const BODY_INDENT: Linear = Relative::new(0.5).into(); - /// The spacing between the list items of a non-wide list. - pub const SPACING: Linear = Linear::zero(); fn construct(_: &mut Vm, args: &mut Args) -> TypResult<Template> { Ok(Template::show(Self { @@ -46,30 +50,27 @@ impl<const L: Labelling> ListNode<L> { } } -impl<const L: Labelling> Show for ListNode<L> { - fn show(&self, _: &mut Vm, styles: StyleChain) -> TypResult<Template> { +impl<const L: ListKind> Show for ListNode<L> { + fn show(&self, vm: &mut Vm, styles: StyleChain) -> TypResult<Template> { let mut children = vec![]; let mut number = self.start; + let label = styles.get_ref(Self::LABEL); + for item in &self.items { number = item.number.unwrap_or(number); - - let label = match L { - UNORDERED => '•'.into(), - ORDERED | _ => format_eco!("{}.", number), - }; + if L == UNORDERED { + number = 1; + } children.push(LayoutNode::default()); - children.push(Template::Text(label).pack()); + children.push(label.resolve(vm, L, number)?.pack()); children.push(LayoutNode::default()); children.push(item.body.clone()); - number += 1; } let em = styles.get(TextNode::SIZE).abs; - let label_indent = styles.get(Self::LABEL_INDENT).resolve(em); - let body_indent = styles.get(Self::BODY_INDENT).resolve(em); let leading = styles.get(ParNode::LEADING); let spacing = if self.wide { styles.get(ParNode::SPACING) @@ -78,6 +79,9 @@ impl<const L: Labelling> Show for ListNode<L> { }; let gutter = (leading + spacing).resolve(em); + let label_indent = styles.get(Self::LABEL_INDENT).resolve(em); + let body_indent = styles.get(Self::BODY_INDENT).resolve(em); + Ok(Template::block(GridNode { tracks: Spec::with_x(vec![ TrackSizing::Linear(label_indent.into()), @@ -91,17 +95,99 @@ impl<const L: Labelling> Show for ListNode<L> { } } -impl<const L: Labelling> From<ListItem> for ListNode<L> { +impl<const L: ListKind> From<ListItem> for ListNode<L> { fn from(item: ListItem) -> Self { Self { items: vec![item], wide: false, start: 1 } } } /// How to label a list. -pub type Labelling = usize; +pub type ListKind = usize; /// Unordered list labelling style. -pub const UNORDERED: Labelling = 0; +pub const UNORDERED: ListKind = 0; /// Ordered list labelling style. -pub const ORDERED: Labelling = 1; +pub const ORDERED: ListKind = 1; + +/// Either a template or a closure mapping to a template. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum Label { + /// The default labelling. + Default, + /// A pattern with prefix, numbering, lower / upper case and suffix. + Pattern(EcoString, Numbering, bool, EcoString), + /// A bare template. + Template(Template), + /// A simple mapping from an item number to a template. + Mapping(fn(usize) -> Template), + /// A closure mapping from an item number to a value. + Func(Func, Span), +} + +impl Label { + /// Resolve the value based on the level. + pub fn resolve( + &self, + vm: &mut Vm, + kind: ListKind, + number: usize, + ) -> TypResult<Template> { + Ok(match self { + Self::Default => match kind { + UNORDERED => Template::Text('•'.into()), + ORDERED | _ => Template::Text(format_eco!("{}.", number)), + }, + Self::Pattern(prefix, numbering, upper, suffix) => { + let fmt = numbering.apply(number); + let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; + Template::Text(format_eco!("{}{}{}", prefix, mid, suffix)) + } + Self::Template(template) => template.clone(), + Self::Mapping(mapping) => mapping(number), + &Self::Func(ref func, span) => { + let args = Args { + span, + items: vec![Arg { + span, + name: None, + value: Spanned::new(Value::Int(number as i64), span), + }], + }; + func.call(vm, args)?.cast().at(span)? + } + }) + } +} + +impl Cast<Spanned<Value>> for Label { + fn is(value: &Spanned<Value>) -> bool { + matches!(&value.v, Value::Template(_) | Value::Func(_)) + } + + fn cast(value: Spanned<Value>) -> StrResult<Self> { + match value.v { + Value::Str(pattern) => { + let mut s = Scanner::new(&pattern); + let mut prefix; + let numbering = loop { + prefix = s.eaten(); + match s.eat().map(|c| c.to_ascii_lowercase()) { + Some('1') => break Numbering::Arabic, + Some('a') => break Numbering::Letter, + Some('i') => break Numbering::Roman, + Some('*') => break Numbering::Symbol, + Some(_) => {} + None => Err("invalid pattern")?, + } + }; + let upper = s.prev(0).map_or(false, char::is_uppercase); + let suffix = s.rest().into(); + Ok(Self::Pattern(prefix.into(), numbering, upper, suffix)) + } + Value::Template(v) => Ok(Self::Template(v)), + Value::Func(v) => Ok(Self::Func(v, value.span)), + _ => Err("expected pattern, template or function")?, + } + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index 707264d9..ad66f2c3 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -15,6 +15,7 @@ pub mod image; pub mod link; pub mod list; pub mod math; +pub mod numbering; pub mod pad; pub mod page; pub mod par; @@ -40,6 +41,7 @@ pub use hide::*; pub use link::*; pub use list::*; pub use math::*; +pub use numbering::*; pub use pad::*; pub use page::*; pub use par::*; @@ -144,6 +146,7 @@ pub fn new() -> Scope { std.def_func("cmyk", cmyk); std.def_func("lower", lower); std.def_func("upper", upper); + std.def_func("letter", letter); std.def_func("roman", roman); std.def_func("symbol", symbol); std.def_func("len", len); diff --git a/src/library/numbering.rs b/src/library/numbering.rs new file mode 100644 index 00000000..4a2fdbb5 --- /dev/null +++ b/src/library/numbering.rs @@ -0,0 +1,108 @@ +//! Conversion of numbers into letters, roman numerals and symbols. + +use super::prelude::*; + +static ROMANS: &'static [(&'static str, usize)] = &[ + ("M̅", 1000000), + ("D̅", 500000), + ("C̅", 100000), + ("L̅", 50000), + ("X̅", 10000), + ("V̅", 5000), + ("I̅V̅", 4000), + ("M", 1000), + ("CM", 900), + ("D", 500), + ("CD", 400), + ("C", 100), + ("XC", 90), + ("L", 50), + ("XL", 40), + ("X", 10), + ("IX", 9), + ("V", 5), + ("IV", 4), + ("I", 1), +]; + +static SYMBOLS: &'static [char] = &['*', '†', '‡', '§', '‖', '¶']; + +/// The different kinds of numberings. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Numbering { + Arabic, + Letter, + Roman, + Symbol, +} + +impl Numbering { + /// Apply the numbering to the given number. + pub fn apply(self, mut n: usize) -> EcoString { + match self { + Self::Arabic => { + format_eco!("{}", n) + } + Self::Letter => { + if n == 0 { + return '-'.into(); + } + + let mut letters = vec![]; + while n > 0 { + letters.push(b'a' - 1 + (n % 26) as u8); + n /= 26; + } + + letters.reverse(); + String::from_utf8(letters).unwrap().into() + } + Self::Roman => { + if n == 0 { + return 'N'.into(); + } + + // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at + // https://github.com/linfir/roman.rs/ + let mut fmt = EcoString::new(); + for &(name, value) in ROMANS { + while n >= value { + n -= value; + fmt.push_str(name); + } + } + + fmt + } + Self::Symbol => { + if n == 0 { + return '-'.into(); + } + + let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; + let amount = ((n - 1) / SYMBOLS.len()) + 1; + std::iter::repeat(symbol).take(amount).collect() + } + } + } +} + +/// Converts an integer into one or multiple letters. +pub fn letter(_: &mut Vm, args: &mut Args) -> TypResult<Value> { + convert(Numbering::Letter, args) +} + +/// Converts an integer into a roman numeral. +pub fn roman(_: &mut Vm, args: &mut Args) -> TypResult<Value> { + convert(Numbering::Roman, args) +} + +/// Convert a number into a symbol. +pub fn symbol(_: &mut Vm, args: &mut Args) -> TypResult<Value> { + convert(Numbering::Symbol, args) +} + +fn convert(numbering: Numbering, args: &mut Args) -> TypResult<Value> { + let n = args.expect::<usize>("non-negative integer")?; + Ok(Value::Str(numbering.apply(n))) +} diff --git a/src/library/utility.rs b/src/library/utility.rs index 91f0e3ad..f7f46a8f 100644 --- a/src/library/utility.rs +++ b/src/library/utility.rs @@ -261,71 +261,6 @@ pub fn upper(_: &mut Vm, args: &mut Args) -> TypResult<Value> { Ok(args.expect::<EcoString>("string")?.to_uppercase().into()) } -/// Converts an integer into a roman numeral. -/// -/// Works for integer between 0 and 3,999,999. -pub fn roman(_: &mut Vm, args: &mut Args) -> TypResult<Value> { - // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at - // https://github.com/linfir/roman.rs/ - static PAIRS: &'static [(&'static str, usize)] = &[ - ("M̅", 1000000), - ("D̅", 500000), - ("C̅", 100000), - ("L̅", 50000), - ("X̅", 10000), - ("V̅", 5000), - ("I̅V̅", 4000), - ("M", 1000), - ("CM", 900), - ("D", 500), - ("CD", 400), - ("C", 100), - ("XC", 90), - ("L", 50), - ("XL", 40), - ("X", 10), - ("IX", 9), - ("V", 5), - ("IV", 4), - ("I", 1), - ]; - - let Spanned { mut v, span } = args.expect("non-negative integer")?; - match v { - 0_usize => return Ok("N".into()), - 4_000_000 .. => { - bail!( - span, - "cannot convert integers greater than 3,999,999 to roman numerals" - ) - } - _ => {} - } - - let mut roman = String::new(); - for &(name, value) in PAIRS { - while v >= value { - v -= value; - roman.push_str(name); - } - } - - Ok(roman.into()) -} - -/// Convert a number into a roman numeral. -pub fn symbol(_: &mut Vm, args: &mut Args) -> TypResult<Value> { - static SYMBOLS: &'static [char] = &['*', '†', '‡', '§', '‖', '¶']; - - let n = args.expect::<usize>("non-negative integer")?; - - let symbol = SYMBOLS[n % SYMBOLS.len()]; - let amount = (n / SYMBOLS.len()) + 1; - - let symbols: String = std::iter::repeat(symbol).take(amount).collect(); - Ok(symbols.into()) -} - /// The length of a string, an array or a dictionary. pub fn len(_: &mut Vm, args: &mut Args) -> TypResult<Value> { let Spanned { v, span } = args.expect("collection")?; diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index 81fa4fba..685503c3 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -90,6 +90,12 @@ impl<'s> Scanner<'s> { self.rest().chars().next() } + /// Get the nth-previous eaten char. + #[inline] + pub fn prev(&self, n: usize) -> Option<char> { + self.eaten().chars().nth_back(n) + } + /// Checks whether the next char fulfills a condition. /// /// Returns `default` if there is no next char. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index ca2cb74a..e88b49f9 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -538,7 +538,7 @@ impl<'s> Tokens<'s> { fn in_word(&self) -> bool { let alphanumeric = |c: Option<char>| c.map_or(false, |c| c.is_alphanumeric()); - let prev = self.s.get(.. self.s.last_index()).chars().next_back(); + let prev = self.s.prev(1); let next = self.s.peek(); alphanumeric(prev) && alphanumeric(next) } diff --git a/src/util/eco_string.rs b/src/util/eco_string.rs index 953508ce..e3c8fc8a 100644 --- a/src/util/eco_string.rs +++ b/src/util/eco_string.rs @@ -366,6 +366,16 @@ impl From<EcoString> for String { } } +impl FromIterator<char> for EcoString { + fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self { + let mut s = Self::new(); + for c in iter { + s.push(c); + } + s + } +} + #[cfg(test)] mod tests { use super::*; |
