From 0579fd4409375aaa9fd8e87a06fd59097b5fcd97 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 26 Nov 2022 16:59:20 +0100 Subject: Reorganize library base --- library/src/base/calc.rs | 54 --------------- library/src/base/color.rs | 64 ----------------- library/src/base/create.rs | 149 ++++++++++++++++++++++++++++++++++++++++ library/src/base/mod.rs | 13 ++-- library/src/base/numbering.rs | 146 +++++++++++++++++++++++++++++++++++++++ library/src/base/string.rs | 58 ---------------- library/src/lib.rs | 22 +++--- library/src/shared/mod.rs | 2 - library/src/shared/numbering.rs | 139 ------------------------------------- library/src/structure/list.rs | 2 +- 10 files changed, 315 insertions(+), 334 deletions(-) delete mode 100644 library/src/base/color.rs create mode 100644 library/src/base/create.rs create mode 100644 library/src/base/numbering.rs delete mode 100644 library/src/base/string.rs delete mode 100644 library/src/shared/numbering.rs (limited to 'library') diff --git a/library/src/base/calc.rs b/library/src/base/calc.rs index db40df06..3541e08c 100644 --- a/library/src/base/calc.rs +++ b/library/src/base/calc.rs @@ -2,35 +2,6 @@ use std::cmp::Ordering; use crate::prelude::*; -/// Convert a value to an integer. -pub fn int(_: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Int(match v { - Value::Bool(v) => v as i64, - Value::Int(v) => v, - Value::Float(v) => v as i64, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid integer"), - }, - v => bail!(span, "cannot convert {} to integer", v.type_name()), - })) -} - -/// Convert a value to a float. -pub fn float(_: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Float(match v { - Value::Int(v) => v as f64, - Value::Float(v) => v, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid float"), - }, - v => bail!(span, "cannot convert {} to float", v.type_name()), - })) -} - /// The absolute value of a numeric value. pub fn abs(_: &Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("numeric value")?; @@ -115,28 +86,3 @@ pub fn mod_(_: &Vm, args: &mut Args) -> SourceResult { Ok(Value::Float(a % b)) } - -/// Create a sequence of numbers. -pub fn range(_: &Vm, args: &mut Args) -> SourceResult { - let first = args.expect::("end")?; - let (start, end) = match args.eat::()? { - Some(second) => (first, second), - None => (0, first), - }; - - let step: i64 = match args.named("step")? { - Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"), - Some(Spanned { v, .. }) => v, - None => 1, - }; - - let mut x = start; - let mut seq = vec![]; - - while x.cmp(&end) == 0.cmp(&step) { - seq.push(Value::Int(x)); - x += step; - } - - Ok(Value::Array(Array::from_vec(seq))) -} diff --git a/library/src/base/color.rs b/library/src/base/color.rs deleted file mode 100644 index 2db41ebf..00000000 --- a/library/src/base/color.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::str::FromStr; - -use crate::prelude::*; - -/// Create a grayscale color. -pub fn luma(_: &Vm, args: &mut Args) -> SourceResult { - let Component(luma) = args.expect("gray component")?; - Ok(Value::Color(LumaColor::new(luma).into())) -} - -/// Create an RGB(A) color. -pub fn rgb(_: &Vm, args: &mut Args) -> SourceResult { - Ok(Value::Color(if let Some(string) = args.find::>()? { - match RgbaColor::from_str(&string.v) { - Ok(color) => color.into(), - Err(msg) => bail!(string.span, msg), - } - } else { - let Component(r) = args.expect("red component")?; - let Component(g) = args.expect("green component")?; - let Component(b) = args.expect("blue component")?; - let Component(a) = args.eat()?.unwrap_or(Component(255)); - RgbaColor::new(r, g, b, a).into() - })) -} - -/// Create a CMYK color. -pub fn cmyk(_: &Vm, args: &mut Args) -> SourceResult { - let RatioComponent(c) = args.expect("cyan component")?; - let RatioComponent(m) = args.expect("magenta component")?; - let RatioComponent(y) = args.expect("yellow component")?; - let RatioComponent(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) -} - -/// An integer or ratio component. -struct Component(u8); - -castable! { - Component, - Expected: "integer or ratio", - Value::Int(v) => match v { - 0 ..= 255 => Self(v as u8), - _ => Err("must be between 0 and 255")?, - }, - Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, -} - -/// A component that must be a ratio. -struct RatioComponent(u8); - -castable! { - RatioComponent, - Expected: "ratio", - Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, -} diff --git a/library/src/base/create.rs b/library/src/base/create.rs new file mode 100644 index 00000000..be8e822f --- /dev/null +++ b/library/src/base/create.rs @@ -0,0 +1,149 @@ +use std::str::FromStr; + +use typst::model::Regex; + +use crate::prelude::*; + +/// Convert a value to an integer. +pub fn int(_: &Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Int(match v { + Value::Bool(v) => v as i64, + Value::Int(v) => v, + Value::Float(v) => v as i64, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid integer"), + }, + v => bail!(span, "cannot convert {} to integer", v.type_name()), + })) +} + +/// Convert a value to a float. +pub fn float(_: &Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Float(match v { + Value::Int(v) => v as f64, + Value::Float(v) => v, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid float"), + }, + v => bail!(span, "cannot convert {} to float", v.type_name()), + })) +} + +/// Create a grayscale color. +pub fn luma(_: &Vm, args: &mut Args) -> SourceResult { + let Component(luma) = args.expect("gray component")?; + Ok(Value::Color(LumaColor::new(luma).into())) +} + +/// Create an RGB(A) color. +pub fn rgb(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Value::Color(if let Some(string) = args.find::>()? { + match RgbaColor::from_str(&string.v) { + Ok(color) => color.into(), + Err(msg) => bail!(string.span, msg), + } + } else { + let Component(r) = args.expect("red component")?; + let Component(g) = args.expect("green component")?; + let Component(b) = args.expect("blue component")?; + let Component(a) = args.eat()?.unwrap_or(Component(255)); + RgbaColor::new(r, g, b, a).into() + })) +} + +/// Create a CMYK color. +pub fn cmyk(_: &Vm, args: &mut Args) -> SourceResult { + let RatioComponent(c) = args.expect("cyan component")?; + let RatioComponent(m) = args.expect("magenta component")?; + let RatioComponent(y) = args.expect("yellow component")?; + let RatioComponent(k) = args.expect("key component")?; + Ok(Value::Color(CmykColor::new(c, m, y, k).into())) +} + +/// An integer or ratio component. +struct Component(u8); + +castable! { + Component, + Expected: "integer or ratio", + Value::Int(v) => match v { + 0 ..= 255 => Self(v as u8), + _ => Err("must be between 0 and 255")?, + }, + Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, +} + +/// A component that must be a ratio. +struct RatioComponent(u8); + +castable! { + RatioComponent, + Expected: "ratio", + Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, +} + +/// Convert a value to a string. +pub fn str(_: &Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Str(match v { + Value::Int(v) => format_str!("{}", v), + Value::Float(v) => format_str!("{}", v), + Value::Label(label) => label.0.into(), + Value::Str(v) => v, + v => bail!(span, "cannot convert {} to string", v.type_name()), + })) +} + +/// Create a blind text string. +pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult { + let words: usize = args.expect("number of words")?; + Ok(Value::Str(lipsum::lipsum(words).into())) +} + +/// Create a label from a string. +pub fn label(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Value::Label(Label(args.expect("string")?))) +} + +/// Create a regular expression from a string. +pub fn regex(_: &Vm, args: &mut Args) -> SourceResult { + let Spanned { v, span } = args.expect::>("regular expression")?; + Ok(Regex::new(&v).at(span)?.into()) +} + +/// Create an array consisting of a sequence of numbers. +pub fn range(_: &Vm, args: &mut Args) -> SourceResult { + let first = args.expect::("end")?; + let (start, end) = match args.eat::()? { + Some(second) => (first, second), + None => (0, first), + }; + + let step: i64 = match args.named("step")? { + Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"), + Some(Spanned { v, .. }) => v, + None => 1, + }; + + let mut x = start; + let mut seq = vec![]; + + while x.cmp(&end) == 0.cmp(&step) { + seq.push(Value::Int(x)); + x += step; + } + + Ok(Value::Array(Array::from_vec(seq))) +} diff --git a/library/src/base/mod.rs b/library/src/base/mod.rs index 86ebd666..501edd71 100644 --- a/library/src/base/mod.rs +++ b/library/src/base/mod.rs @@ -1,14 +1,14 @@ //! Foundational functions. mod calc; -mod color; +mod create; mod data; -mod string; +mod numbering; pub use self::calc::*; -pub use self::color::*; +pub use self::create::*; pub use self::data::*; -pub use self::string::*; +pub use self::numbering::*; use comemo::Track; use typst::model::{self, Route, Vm}; @@ -21,6 +21,11 @@ pub fn type_(_: &Vm, args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.type_name().into()) } +/// The string representation of a value. +pub fn repr(_: &Vm, args: &mut Args) -> SourceResult { + Ok(args.expect::("value")?.repr().into()) +} + /// Ensure that a condition is fulfilled. pub fn assert(_: &Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("condition")?; diff --git a/library/src/base/numbering.rs b/library/src/base/numbering.rs new file mode 100644 index 00000000..ea45fbc6 --- /dev/null +++ b/library/src/base/numbering.rs @@ -0,0 +1,146 @@ +use std::str::FromStr; + +use unscanny::Scanner; + +use crate::prelude::*; + +/// Apply a numbering pattern to a number. +pub fn numbering(_: &Vm, args: &mut Args) -> SourceResult { + let number = args.expect::("number")?; + let pattern = args.expect::("pattern")?; + Ok(Value::Str(pattern.apply(number).into())) +} + +/// A numbering pattern for lists or headings. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct NumberingPattern { + prefix: EcoString, + numbering: NumberingKind, + upper: bool, + suffix: EcoString, +} + +impl NumberingPattern { + /// Apply the pattern to the given number. + pub fn apply(&self, n: usize) -> EcoString { + let fmt = self.numbering.apply(n); + let mid = if self.upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; + format_eco!("{}{}{}", self.prefix, mid, self.suffix) + } +} + +impl FromStr for NumberingPattern { + type Err = &'static str; + + fn from_str(pattern: &str) -> Result { + let mut s = Scanner::new(pattern); + let mut prefix; + let numbering = loop { + prefix = s.before(); + match s.eat().map(|c| c.to_ascii_lowercase()) { + Some('1') => break NumberingKind::Arabic, + Some('a') => break NumberingKind::Letter, + Some('i') => break NumberingKind::Roman, + Some('*') => break NumberingKind::Symbol, + Some(_) => {} + None => Err("invalid numbering pattern")?, + } + }; + let upper = s.scout(-1).map_or(false, char::is_uppercase); + let suffix = s.after().into(); + Ok(Self { prefix: prefix.into(), numbering, upper, suffix }) + } +} + +castable! { + NumberingPattern, + Expected: "numbering pattern", + Value::Str(s) => s.parse()?, +} + +/// Different kinds of numberings. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum NumberingKind { + Arabic, + Letter, + Roman, + Symbol, +} + +impl NumberingKind { + /// 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(); + } + + n -= 1; + + let mut letters = vec![]; + loop { + letters.push(b'a' + (n % 26) as u8); + n /= 26; + if n == 0 { + break; + } + } + + 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 &[ + ("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), + ] { + while n >= value { + n -= value; + fmt.push_str(name); + } + } + + fmt + } + Self::Symbol => { + if n == 0 { + return '-'.into(); + } + + const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; + let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; + let amount = ((n - 1) / SYMBOLS.len()) + 1; + std::iter::repeat(symbol).take(amount).collect() + } + } + } +} diff --git a/library/src/base/string.rs b/library/src/base/string.rs deleted file mode 100644 index 9c3b9562..00000000 --- a/library/src/base/string.rs +++ /dev/null @@ -1,58 +0,0 @@ -use typst::model::Regex; - -use crate::prelude::*; -use crate::shared::NumberingKind; - -/// The string representation of a value. -pub fn repr(_: &Vm, args: &mut Args) -> SourceResult { - Ok(args.expect::("value")?.repr().into()) -} - -/// Convert a value to a string. -pub fn str(_: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Str(match v { - Value::Int(v) => format_str!("{}", v), - Value::Float(v) => format_str!("{}", v), - Value::Label(label) => label.0.into(), - Value::Str(v) => v, - v => bail!(span, "cannot convert {} to string", v.type_name()), - })) -} - -/// Create a label from a string. -pub fn label(_: &Vm, args: &mut Args) -> SourceResult { - Ok(Value::Label(Label(args.expect("string")?))) -} - -/// Create blind text. -pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult { - let words: usize = args.expect("number of words")?; - Ok(Value::Str(lipsum::lipsum(words).into())) -} - -/// Create a regular expression. -pub fn regex(_: &Vm, args: &mut Args) -> SourceResult { - let Spanned { v, span } = args.expect::>("regular expression")?; - Ok(Regex::new(&v).at(span)?.into()) -} - -/// Converts an integer into one or multiple letters. -pub fn letter(_: &Vm, args: &mut Args) -> SourceResult { - numbered(NumberingKind::Letter, args) -} - -/// Converts an integer into a roman numeral. -pub fn roman(_: &Vm, args: &mut Args) -> SourceResult { - numbered(NumberingKind::Roman, args) -} - -/// Convert a number into a symbol. -pub fn symbol(_: &Vm, args: &mut Args) -> SourceResult { - numbered(NumberingKind::Symbol, args) -} - -fn numbered(numbering: NumberingKind, args: &mut Args) -> SourceResult { - let n = args.expect::("non-negative integer")?; - Ok(Value::Str(numbering.apply(n).into())) -} diff --git a/library/src/lib.rs b/library/src/lib.rs index 11baacc3..6107cf42 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -88,28 +88,26 @@ fn scope() -> Scope { // Base. std.def_fn("type", base::type_); + std.def_fn("repr", base::repr); std.def_fn("assert", base::assert); std.def_fn("eval", base::eval); std.def_fn("int", base::int); std.def_fn("float", base::float); - std.def_fn("abs", base::abs); - std.def_fn("min", base::min); - std.def_fn("max", base::max); - std.def_fn("even", base::even); - std.def_fn("odd", base::odd); - std.def_fn("mod", base::mod_); - std.def_fn("range", base::range); std.def_fn("luma", base::luma); std.def_fn("rgb", base::rgb); std.def_fn("cmyk", base::cmyk); - std.def_fn("repr", base::repr); std.def_fn("str", base::str); + std.def_fn("lorem", base::lorem); std.def_fn("label", base::label); std.def_fn("regex", base::regex); - std.def_fn("letter", base::letter); - std.def_fn("roman", base::roman); - std.def_fn("symbol", base::symbol); - std.def_fn("lorem", base::lorem); + std.def_fn("range", base::range); + std.def_fn("numbering", base::numbering); + std.def_fn("abs", base::abs); + std.def_fn("min", base::min); + std.def_fn("max", base::max); + std.def_fn("even", base::even); + std.def_fn("odd", base::odd); + std.def_fn("mod", base::mod_); std.def_fn("csv", base::csv); std.def_fn("json", base::json); std.def_fn("xml", base::xml); diff --git a/library/src/shared/mod.rs b/library/src/shared/mod.rs index 55522190..f54241cf 100644 --- a/library/src/shared/mod.rs +++ b/library/src/shared/mod.rs @@ -2,8 +2,6 @@ mod behave; mod ext; -mod numbering; pub use behave::*; pub use ext::*; -pub use numbering::*; diff --git a/library/src/shared/numbering.rs b/library/src/shared/numbering.rs deleted file mode 100644 index 739edafe..00000000 --- a/library/src/shared/numbering.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::str::FromStr; - -use typst::model::{castable, Value}; -use typst::util::{format_eco, EcoString}; -use unscanny::Scanner; - -/// A numbering pattern for lists or headings. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct NumberingPattern { - prefix: EcoString, - numbering: NumberingKind, - upper: bool, - suffix: EcoString, -} - -impl NumberingPattern { - /// Apply the pattern to the given number. - pub fn apply(&self, n: usize) -> EcoString { - let fmt = self.numbering.apply(n); - let mid = if self.upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; - format_eco!("{}{}{}", self.prefix, mid, self.suffix) - } -} - -impl FromStr for NumberingPattern { - type Err = &'static str; - - fn from_str(pattern: &str) -> Result { - let mut s = Scanner::new(pattern); - let mut prefix; - let numbering = loop { - prefix = s.before(); - match s.eat().map(|c| c.to_ascii_lowercase()) { - Some('1') => break NumberingKind::Arabic, - Some('a') => break NumberingKind::Letter, - Some('i') => break NumberingKind::Roman, - Some('*') => break NumberingKind::Symbol, - Some(_) => {} - None => Err("invalid numbering pattern")?, - } - }; - let upper = s.scout(-1).map_or(false, char::is_uppercase); - let suffix = s.after().into(); - Ok(Self { prefix: prefix.into(), numbering, upper, suffix }) - } -} - -castable! { - NumberingPattern, - Expected: "numbering pattern", - Value::Str(s) => s.parse()?, -} - -/// Different kinds of numberings. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum NumberingKind { - Arabic, - Letter, - Roman, - Symbol, -} - -impl NumberingKind { - /// 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(); - } - - n -= 1; - - let mut letters = vec![]; - loop { - letters.push(b'a' + (n % 26) as u8); - n /= 26; - if n == 0 { - break; - } - } - - 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 &[ - ("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), - ] { - while n >= value { - n -= value; - fmt.push_str(name); - } - } - - fmt - } - Self::Symbol => { - if n == 0 { - return '-'.into(); - } - - const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶']; - let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; - let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat(symbol).take(amount).collect() - } - } - } -} diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index d1727087..462e0c32 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -1,6 +1,6 @@ +use crate::base::NumberingPattern; use crate::layout::{BlockNode, GridNode, HNode, Spacing, TrackSizing}; use crate::prelude::*; -use crate::shared::NumberingPattern; use crate::text::{ParNode, SpaceNode, TextNode}; /// An unordered (bulleted) or ordered (numbered) list. -- cgit v1.2.3