summaryrefslogtreecommitdiff
path: root/library/src/base
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/base')
-rw-r--r--library/src/base/calc.rs54
-rw-r--r--library/src/base/color.rs64
-rw-r--r--library/src/base/create.rs149
-rw-r--r--library/src/base/mod.rs13
-rw-r--r--library/src/base/numbering.rs146
-rw-r--r--library/src/base/string.rs58
6 files changed, 304 insertions, 180 deletions
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<Value> {
- 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<Value> {
- 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<Value> {
let Spanned { v, span } = args.expect("numeric value")?;
@@ -115,28 +86,3 @@ pub fn mod_(_: &Vm, args: &mut Args) -> SourceResult<Value> {
Ok(Value::Float(a % b))
}
-
-/// Create a sequence of numbers.
-pub fn range(_: &Vm, args: &mut Args) -> SourceResult<Value> {
- let first = args.expect::<i64>("end")?;
- let (start, end) = match args.eat::<i64>()? {
- 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<Value> {
- 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<Value> {
- Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
- 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<Value> {
- 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ Ok(Value::Label(Label(args.expect("string")?)))
+}
+
+/// Create a regular expression from a string.
+pub fn regex(_: &Vm, args: &mut Args) -> SourceResult<Value> {
+ let Spanned { v, span } = args.expect::<Spanned<EcoString>>("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<Value> {
+ let first = args.expect::<i64>("end")?;
+ let (start, end) = match args.eat::<i64>()? {
+ 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<Value> {
Ok(args.expect::<Value>("value")?.type_name().into())
}
+/// The string representation of a value.
+pub fn repr(_: &Vm, args: &mut Args) -> SourceResult<Value> {
+ Ok(args.expect::<Value>("value")?.repr().into())
+}
+
/// Ensure that a condition is fulfilled.
pub fn assert(_: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<bool>>("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<Value> {
+ let number = args.expect::<usize>("number")?;
+ let pattern = args.expect::<NumberingPattern>("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<Self, Self::Err> {
+ 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<Value> {
- Ok(args.expect::<Value>("value")?.repr().into())
-}
-
-/// Convert a value to a string.
-pub fn str(_: &Vm, args: &mut Args) -> SourceResult<Value> {
- 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<Value> {
- Ok(Value::Label(Label(args.expect("string")?)))
-}
-
-/// Create blind text.
-pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult<Value> {
- 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<Value> {
- let Spanned { v, span } = args.expect::<Spanned<EcoString>>("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<Value> {
- numbered(NumberingKind::Letter, args)
-}
-
-/// Converts an integer into a roman numeral.
-pub fn roman(_: &Vm, args: &mut Args) -> SourceResult<Value> {
- numbered(NumberingKind::Roman, args)
-}
-
-/// Convert a number into a symbol.
-pub fn symbol(_: &Vm, args: &mut Args) -> SourceResult<Value> {
- numbered(NumberingKind::Symbol, args)
-}
-
-fn numbered(numbering: NumberingKind, args: &mut Args) -> SourceResult<Value> {
- let n = args.expect::<usize>("non-negative integer")?;
- Ok(Value::Str(numbering.apply(n).into()))
-}