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
142
143
144
145
146
147
148
|
use crate::library::prelude::*;
use crate::library::text::{Case, TextNode};
/// The string representation of a value.
pub fn repr(_: &mut Context, args: &mut Args) -> TypResult<Value> {
Ok(args.expect::<Value>("value")?.repr().into())
}
/// Cconvert a value to a string.
pub fn str(_: &mut Context, args: &mut Args) -> TypResult<Value> {
let Spanned { v, span } = args.expect("value")?;
Ok(Value::Str(match v {
Value::Int(v) => format_eco!("{}", v),
Value::Float(v) => format_eco!("{}", v),
Value::Str(v) => v,
v => bail!(span, "cannot convert {} to string", v.type_name()),
}))
}
/// Convert a string to lowercase.
pub fn lower(_: &mut Context, args: &mut Args) -> TypResult<Value> {
case(Case::Lower, args)
}
/// Convert a string to uppercase.
pub fn upper(_: &mut Context, args: &mut Args) -> TypResult<Value> {
case(Case::Upper, args)
}
/// Change the case of a string or content.
fn case(case: Case, args: &mut Args) -> TypResult<Value> {
let Spanned { v, span } = args.expect("string or content")?;
Ok(match v {
Value::Str(v) => Value::Str(case.apply(&v).into()),
Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))),
v => bail!(span, "expected string or content, found {}", v.type_name()),
})
}
/// Converts an integer into one or multiple letters.
pub fn letter(_: &mut Context, args: &mut Args) -> TypResult<Value> {
convert(Numbering::Letter, args)
}
/// Converts an integer into a roman numeral.
pub fn roman(_: &mut Context, args: &mut Args) -> TypResult<Value> {
convert(Numbering::Roman, args)
}
/// Convert a number into a symbol.
pub fn symbol(_: &mut Context, 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)))
}
/// 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()
}
}
}
}
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] = &['*', '†', '‡', '§', '‖', '¶'];
|