runestick/modules/
string.rs

1//! The `std::string` module.
2
3use crate::{Any, Bytes, ContextError, Iterator, Module, Protocol, Value, VmError, VmErrorKind};
4
5/// Construct the `std::string` module.
6pub fn module() -> Result<Module, ContextError> {
7    let mut module = Module::with_crate_item("std", &["string"]);
8
9    module.ty::<String>()?;
10
11    module.function(&["String", "from_str"], <String as From<&str>>::from)?;
12    module.function(&["String", "new"], String::new)?;
13    module.function(&["String", "with_capacity"], String::with_capacity)?;
14
15    module.inst_fn("cmp", str::cmp)?;
16    module.inst_fn("len", String::len)?;
17    module.inst_fn("starts_with", str::starts_with::<&str>)?;
18    module.inst_fn("ends_with", str::ends_with::<&str>)?;
19    module.inst_fn("capacity", String::capacity)?;
20    module.inst_fn("clear", String::clear)?;
21    module.inst_fn("push", String::push)?;
22    module.inst_fn("push_str", String::push_str)?;
23    module.inst_fn("reserve", String::reserve)?;
24    module.inst_fn("reserve_exact", String::reserve_exact)?;
25    module.inst_fn("into_bytes", into_bytes)?;
26    module.inst_fn("clone", String::clone)?;
27    module.inst_fn("shrink_to_fit", String::shrink_to_fit)?;
28    module.inst_fn("char_at", char_at)?;
29    module.inst_fn("split", string_split)?;
30    module.inst_fn("trim", string_trim)?;
31    module.inst_fn("trim_end", string_trim_end)?;
32    module.inst_fn("replace", str::replace::<&str>)?;
33    // TODO: deprecate this variant.
34    module.inst_fn("split_str", string_split)?;
35    module.inst_fn("is_empty", str::is_empty)?;
36    module.inst_fn("chars", string_chars)?;
37    module.inst_fn(Protocol::ADD, add)?;
38    module.inst_fn(Protocol::ADD_ASSIGN, String::push_str)?;
39    module.inst_fn(Protocol::INDEX_GET, string_index_get)?;
40    module.inst_fn("get", string_get)?;
41
42    // TODO: parameterize once generics are available.
43    module.function(&["parse_int"], parse_int)?;
44    module.function(&["parse_char"], parse_char)?;
45
46    Ok(module)
47}
48
49#[derive(Any, Debug, Clone, Copy)]
50#[rune(module = "crate", install_with = "NotCharBoundary::install")]
51struct NotCharBoundary(());
52
53impl NotCharBoundary {
54    fn string_display(&self, s: &mut String) -> std::fmt::Result {
55        use std::fmt::Write as _;
56        write!(s, "index outside of character boundary")
57    }
58
59    fn install(m: &mut Module) -> Result<(), ContextError> {
60        m.inst_fn(crate::Protocol::STRING_DISPLAY, Self::string_display)?;
61        Ok(())
62    }
63}
64
65/// into_bytes shim for strings.
66fn into_bytes(s: String) -> Bytes {
67    Bytes::from_vec(s.into_bytes())
68}
69
70fn char_at(s: &str, index: usize) -> Option<char> {
71    if !s.is_char_boundary(index) {
72        return None;
73    }
74
75    s[index..].chars().next()
76}
77
78fn string_split(this: &str, value: Value) -> Result<Iterator, VmError> {
79    let lines = match value {
80        Value::String(s) => this
81            .split(s.borrow_ref()?.as_str())
82            .map(String::from)
83            .collect::<Vec<String>>(),
84        Value::StaticString(s) => this
85            .split(s.as_str())
86            .map(String::from)
87            .collect::<Vec<String>>(),
88        Value::Char(pat) => this.split(pat).map(String::from).collect::<Vec<String>>(),
89        value => return Err(VmError::bad_argument::<String>(0, &value)?),
90    };
91
92    Ok(Iterator::from_double_ended(
93        "std::str::Split",
94        lines.into_iter(),
95    ))
96}
97
98fn string_trim(this: &str) -> String {
99    this.trim().to_owned()
100}
101
102fn string_trim_end(this: &str) -> String {
103    this.trim_end().to_owned()
104}
105
106fn parse_int(s: &str) -> Result<i64, std::num::ParseIntError> {
107    str::parse::<i64>(s)
108}
109
110fn parse_char(s: &str) -> Result<char, std::char::ParseCharError> {
111    str::parse::<char>(s)
112}
113
114/// The add operation for strings.
115fn add(a: &str, b: &str) -> String {
116    let mut string = String::with_capacity(a.len() + b.len());
117    string.push_str(a);
118    string.push_str(b);
119    string
120}
121
122fn string_chars(s: &str) -> Iterator {
123    let iter = s.chars().collect::<Vec<_>>().into_iter();
124    Iterator::from_double_ended("std::str::Chars", iter)
125}
126
127/// Get a specific string index.
128fn string_get(s: &str, key: Value) -> Result<Option<String>, VmError> {
129    use crate::{FromValue as _, RangeLimits, TypeOf as _};
130
131    match key {
132        Value::Range(range) => {
133            let range = range.borrow_ref()?;
134
135            let start = match range.start.clone() {
136                Some(value) => Some(<usize>::from_value(value)?),
137                None => None,
138            };
139
140            let end = match range.end.clone() {
141                Some(value) => Some(<usize>::from_value(value)?),
142                None => None,
143            };
144
145            let out = match range.limits {
146                RangeLimits::HalfOpen => match (start, end) {
147                    (Some(start), Some(end)) => s.get(start..end),
148                    (Some(start), None) => s.get(start..),
149                    (None, Some(end)) => s.get(..end),
150                    (None, None) => s.get(..),
151                },
152                RangeLimits::Closed => match (start, end) {
153                    (Some(start), Some(end)) => s.get(start..=end),
154                    (None, Some(end)) => s.get(..=end),
155                    _ => return Err(VmError::from(VmErrorKind::UnsupportedRange)),
156                },
157            };
158
159            return Ok(match out {
160                Some(out) => Some(out.to_owned()),
161                None => None,
162            });
163        }
164        index => Err(VmError::from(VmErrorKind::UnsupportedIndexGet {
165            target: String::type_info(),
166            index: index.type_info()?,
167        })),
168    }
169}
170
171/// Get a specific string index.
172fn string_index_get(s: &str, key: Value) -> Result<String, VmError> {
173    string_get(s, key)?.ok_or_else(|| VmError::panic("missing string slice"))
174}