1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
use typst::model::Regex;
use crate::prelude::*;
/// 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::Str(v) => v,
v => bail!(span, "cannot convert {} to string", v.type_name()),
}))
}
/// 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(Numbering::Letter, args)
}
/// Converts an integer into a roman numeral.
pub fn roman(_: &Vm, args: &mut Args) -> SourceResult<Value> {
numbered(Numbering::Roman, args)
}
/// Convert a number into a symbol.
pub fn symbol(_: &Vm, args: &mut Args) -> SourceResult<Value> {
numbered(Numbering::Symbol, args)
}
fn numbered(numbering: Numbering, args: &mut Args) -> SourceResult<Value> {
let n = args.expect::<usize>("non-negative integer")?;
Ok(Value::Str(numbering.apply(n).into()))
}
/// Allows to convert a number into letters, roman numerals and symbols.
#[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();
}
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 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()
}
}
}
}
const ROMANS: &[(&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),
];
const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶'];
|