Skip to main content

ad_astra/exports/
string.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming       //
3// language platform.                                                         //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md               //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{cell::RefCell, mem::take};
36
37use crate::{
38    export,
39    exports::utils::Stringifier,
40    runtime::{
41        ops::{ScriptConcat, ScriptDisplay, ScriptPartialEq},
42        Arg,
43        Cell,
44        Downcast,
45        Origin,
46        Provider,
47        RuntimeError,
48        RuntimeResult,
49        ScriptType,
50        TypeHint,
51        Upcast,
52    },
53};
54
55/// An immutable string type: `"Quick brown fox."`
56#[export(include)]
57pub(crate) type StringType = str;
58
59impl<'a> Downcast<'a> for &'a str {
60    fn downcast(origin: Origin, provider: Provider<'a>) -> RuntimeResult<Self> {
61        let mut type_match = provider.type_match();
62
63        if type_match.is::<str>() {
64            return provider.to_borrowed(&origin)?.borrow_str(origin);
65        }
66
67        return Err(type_match.mismatch(origin));
68    }
69
70    #[inline(always)]
71    fn hint() -> TypeHint {
72        TypeHint::Type(StringType::type_meta())
73    }
74}
75
76impl<'a> Downcast<'a> for Box<str> {
77    fn downcast(origin: Origin, provider: Provider<'a>) -> RuntimeResult<Self> {
78        let mut type_match = provider.type_match();
79
80        if type_match.is::<str>() {
81            return Ok(provider.to_owned().take_string(origin)?.into_boxed_str());
82        }
83
84        return Err(type_match.mismatch(origin));
85    }
86
87    #[inline(always)]
88    fn hint() -> TypeHint {
89        TypeHint::Type(StringType::type_meta())
90    }
91}
92
93impl<'a> Upcast<'a> for &'a str {
94    type Output = &'a str;
95
96    #[inline(always)]
97    fn upcast(_origin: Origin, this: Self) -> RuntimeResult<Self::Output> {
98        Ok(this)
99    }
100
101    #[inline(always)]
102    fn hint() -> TypeHint {
103        TypeHint::Type(StringType::type_meta())
104    }
105}
106
107impl<'a> Upcast<'a> for Box<str> {
108    type Output = String;
109
110    #[inline(always)]
111    fn upcast(_origin: Origin, this: Self) -> RuntimeResult<Self::Output> {
112        Ok(this.into_string())
113    }
114
115    #[inline(always)]
116    fn hint() -> TypeHint {
117        TypeHint::Type(StringType::type_meta())
118    }
119}
120
121#[export(include)]
122impl ScriptDisplay for str {}
123
124#[export(include)]
125impl ScriptConcat for str {
126    type Result = str;
127
128    fn script_concat(origin: Origin, items: &mut [Arg]) -> RuntimeResult<Cell> {
129        let mut result = String::new();
130
131        for item in items {
132            if item.data.is_nil() {
133                continue;
134            }
135
136            let cell = take(&mut item.data);
137
138            let stringifier = Stringifier {
139                origin: item.origin,
140                cell: &cell,
141                error: RefCell::new(None),
142                fallback_to_type: false,
143            };
144
145            let string = stringifier.to_string();
146
147            if let Some(error) = stringifier.error.take() {
148                return Err(error);
149            }
150
151            result.push_str(&string);
152        }
153
154        Cell::give(origin, result)
155    }
156}
157
158#[export(include)]
159impl ScriptPartialEq for str {
160    type RHS = str;
161
162    fn script_eq(_origin: Origin, mut lhs: Arg, mut rhs: Arg) -> RuntimeResult<bool> {
163        let lhs = lhs.data.borrow_str(lhs.origin)?;
164        let rhs = rhs.data.borrow_str(rhs.origin)?;
165
166        Ok(lhs == rhs)
167    }
168}
169
170impl<'a> Downcast<'a> for char {
171    fn downcast(origin: Origin, provider: Provider<'a>) -> RuntimeResult<Self> {
172        let mut type_match = provider.type_match();
173
174        if type_match.is::<str>() {
175            let string = provider.to_owned().take_string(origin)?;
176            let mut chars = string.chars();
177
178            let first = match chars.next() {
179                Some(ch) => ch,
180                None => {
181                    return Err(RuntimeError::OutOfBounds {
182                        access_origin: origin,
183                        index: 0,
184                        length: 0,
185                    })
186                }
187            };
188
189            if chars.next().is_some() {
190                return Err(RuntimeError::NonSingleton {
191                    access_origin: origin,
192                    actual: string.len(),
193                });
194            }
195
196            return Ok(first);
197        }
198
199        return Err(type_match.mismatch(origin));
200    }
201
202    #[inline(always)]
203    fn hint() -> TypeHint {
204        TypeHint::Type(StringType::type_meta())
205    }
206}
207
208impl<'a> Upcast<'a> for char {
209    type Output = String;
210
211    #[inline]
212    fn upcast(_origin: Origin, this: Self) -> RuntimeResult<Self::Output> {
213        Ok(String::from(this))
214    }
215
216    #[inline(always)]
217    fn hint() -> TypeHint {
218        TypeHint::Type(StringType::type_meta())
219    }
220}
221
222impl<'a> Downcast<'a> for String {
223    fn downcast(origin: Origin, provider: Provider<'a>) -> RuntimeResult<Self> {
224        let mut type_match = provider.type_match();
225
226        if type_match.is::<str>() {
227            return provider.to_owned().take_string(origin);
228        }
229
230        if type_match.cell().ty().prototype().implements_display() {
231            let stringifier = Stringifier {
232                origin,
233                cell: &provider.to_owned(),
234                error: RefCell::new(None),
235                fallback_to_type: false,
236            };
237
238            let string = stringifier.to_string();
239
240            if let Some(error) = stringifier.error.take() {
241                return Err(error);
242            }
243
244            return Ok(string);
245        }
246
247        return Err(type_match.mismatch(origin));
248    }
249
250    #[inline(always)]
251    fn hint() -> TypeHint {
252        TypeHint::Type(StringType::type_meta())
253    }
254}
255
256impl<'a> Upcast<'a> for String {
257    type Output = Self;
258
259    #[inline(always)]
260    fn upcast(_origin: Origin, this: Self) -> RuntimeResult<Self::Output> {
261        Ok(this)
262    }
263
264    #[inline(always)]
265    fn hint() -> TypeHint {
266        TypeHint::Family(StringType::type_meta().family())
267    }
268}
269
270impl<'a> Upcast<'a> for &'a String {
271    type Output = &'a str;
272
273    #[inline(always)]
274    fn upcast(_origin: Origin, this: Self) -> RuntimeResult<Self::Output> {
275        Ok(this.as_str())
276    }
277
278    #[inline(always)]
279    fn hint() -> TypeHint {
280        TypeHint::Type(StringType::type_meta())
281    }
282}
283
284impl<'a> Upcast<'a> for &'a mut String {
285    type Output = &'a str;
286
287    #[inline(always)]
288    fn upcast(_origin: Origin, this: Self) -> RuntimeResult<Self::Output> {
289        Ok(this.as_str())
290    }
291
292    #[inline(always)]
293    fn hint() -> TypeHint {
294        TypeHint::Type(StringType::type_meta())
295    }
296}