lang_interpreter/interpreter/platform/
wasm.rs

1use std::collections::HashMap;
2use std::ffi::OsString;
3use std::io::{Error, ErrorKind};
4use std::mem;
5use std::path::{Path, PathBuf};
6use web_sys::{js_sys, Url, WorkerLocation, XmlHttpRequest, XmlHttpRequestResponseType};
7use web_sys::js_sys::{ArrayBuffer, Uint8Array};
8use web_sys::wasm_bindgen::JsValue;
9use crate::interpreter::data::function::native::NativeError;
10use crate::interpreter::platform::PlatformAPI;
11use crate::regex_patterns;
12
13/// This uses fetch for io operations.
14///
15/// <div class="warning">
16///
17/// [WASMPlatformAPI] can only be used if the [Interpreter](crate::interpreter::Interpreter) is used within a [Web Worker].
18///
19/// </div>
20///
21/// The following functions are not implemented and will always return an [Err]:
22///
23/// * [get_lang_files](PlatformAPI::get_lang_files)
24/// * [write_lang_file](PlatformAPI::write_lang_file)
25/// * [show_input_dialog](PlatformAPI::show_input_dialog)
26///
27/// [Web Worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
28#[derive(Debug)]
29pub struct WASMPlatformAPI {
30    print_buffer: String,
31    print_error_buffer: String,
32}
33
34impl WASMPlatformAPI {
35    pub fn new() -> Self {
36        Self {
37            print_buffer: String::new(),
38            print_error_buffer: String::new(),
39        }
40    }
41}
42
43impl Default for WASMPlatformAPI {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl PlatformAPI for WASMPlatformAPI {
50    /// This method is not implemented
51    ///
52    /// Trait doc: [get_lang_files](PlatformAPI::get_lang_files)
53    fn get_lang_files(&self, _: &Path) -> Result<Vec<PathBuf>, Error> {
54        Err(Error::new(
55            ErrorKind::Unsupported,
56            "Not Implemented",
57        ))
58    }
59
60    fn get_lang_path(&self, lang_file: &Path) -> Result<PathBuf, Error> {
61        let path = lang_file;
62        let mut path = path.parent().unwrap_or(Path::new(""));
63        if path == Path::new("") {
64            path = Path::new("./");
65        }
66
67        let path = &*regex_patterns::WASM_STARTS_WITH_MULTIPLE_DOUBLE_SLASH.replace_all(&path.to_string_lossy(), "/").to_string();
68
69        let location = js_sys::Reflect::get(js_sys::global().as_ref(), &"location".into()).map_err(|err| Error::new(
70            ErrorKind::Other,
71            err.as_string().as_deref().unwrap_or("Error"),
72        ))?;
73
74        let location = WorkerLocation::from(location);
75
76        let location = Url::new_with_base(path, &location.href()).map_err(|err| Error::new(
77            ErrorKind::Other,
78            err.as_string().as_deref().unwrap_or("Error"),
79        ))?;
80
81        Ok(PathBuf::from(location.pathname()))
82    }
83
84    fn get_lang_file_name(&self, lang_file: &Path) -> Option<OsString> {
85        let path = lang_file;
86
87        path.file_name().map(|str| str.to_os_string())
88    }
89
90    fn get_lang_reader(&self, lang_file: &Path) -> Result<Box<[u8]>, Error> {
91        #[inline(always)]
92        fn internal(lang_file: &Path) -> Result<Box<[u8]>, JsValue> {
93            let lang_file = &*regex_patterns::WASM_STARTS_WITH_MULTIPLE_DOUBLE_SLASH.replace_all(&lang_file.to_string_lossy(), "/").to_string();
94
95            let request = XmlHttpRequest::new()?;
96            request.set_response_type(XmlHttpRequestResponseType::Arraybuffer);
97            request.open_with_async("GET", lang_file, false)?;
98            request.send()?;
99
100            let status_code = request.status().unwrap_or(500);
101            if status_code != 200 {
102                return Err(format!("Invalid response code: {status_code}").into());
103            }
104
105            let ret = request.response()?;
106            let ret = ArrayBuffer::from(ret);
107            let ret = Uint8Array::new(&ret);
108
109            let mut bytes = vec![0; ret.length() as usize];
110            ret.copy_to(&mut bytes);
111
112            Ok(bytes.into_boxed_slice())
113        }
114
115        internal(lang_file).map_err(|err| Error::new(
116            ErrorKind::Other,
117            err.as_string().as_deref().unwrap_or("Error"),
118        ))
119    }
120
121    /// This method is not implemented
122    ///
123    /// Trait doc: [write_lang_file](PlatformAPI::write_lang_file)
124    fn write_lang_file(&self, _: &Path, _: HashMap<String, String>) -> Result<(), Error> {
125        Err(Error::new(
126            ErrorKind::Unsupported,
127            "Not Implemented",
128        ))
129    }
130
131    /// This method is not implemented
132    ///
133    /// Trait doc: [show_input_dialog](PlatformAPI::show_input_dialog)
134    fn show_input_dialog(&self, _text: &str) -> Result<String, NativeError> {
135        Err(NativeError::new(
136            "Not Implemented",
137            None,
138        ))
139    }
140
141    fn print(&mut self, text: &str) {
142        self.print_buffer += text;
143    }
144
145    fn println(&mut self, text: &str) {
146        let text = if self.print_buffer.is_empty() {
147            text.to_string()
148        }else {
149            mem::take(&mut self.print_buffer) + text
150        };
151
152        if text.is_empty() {
153            web_sys::console::log_0();
154        }else {
155            web_sys::console::log_1(&text.into());
156        }
157    }
158
159    fn print_error(&mut self, text: &str) {
160        self.print_error_buffer += text;
161    }
162
163    fn println_error(&mut self, text: &str) {
164        let text = if self.print_error_buffer.is_empty() {
165            text.to_string()
166        }else {
167            mem::take(&mut self.print_error_buffer) + text
168        };
169
170        if text.is_empty() {
171            web_sys::console::error_0();
172        }else {
173            web_sys::console::error_1(&text.into());
174        }
175    }
176}