lang_interpreter/interpreter/
module.rs

1use std::cell::RefCell;
2use std::error::Error;
3use std::fmt::{Debug, Display, Formatter};
4use std::rc::Rc;
5use ahash::AHashMap;
6use crate::interpreter::data::{DataObjectRef, OptionDataObjectRef};
7use crate::interpreter::Interpreter;
8
9#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
10pub enum ModuleType {
11    Lang,
12    Native,
13}
14
15#[derive(Debug, Clone)]
16pub struct LangModuleConfiguration {
17    name: Box<str>,
18    description: Option<Box<str>>,
19    version: Option<Box<str>>,
20
21    min_supported_version: Option<Box<str>>,
22    max_supported_version: Option<Box<str>>,
23
24    supported_implementations: Option<Box<[Box<str>]>>,
25
26    module_type: ModuleType,
27
28    native_entry_point: Option<Box<str>>,
29}
30
31impl LangModuleConfiguration {
32    pub fn parse_lmc(lmc: &str) -> Result<Self, InvalidModuleConfigurationException> {
33        let mut data = AHashMap::new();
34
35        for mut line in lmc.split("\n") {
36            if let Some(index) = line.find("#") {
37                line = &line[..index];
38            }
39
40            line = line.trim();
41
42            if line.is_empty() {
43                continue;
44            }
45
46            let tokens = line.splitn(2, " = ").
47                    collect::<Vec<_>>();
48            if tokens.len() != 2 {
49                return Err(InvalidModuleConfigurationException::new(format!(
50                    "Invalid configuration line (\" = \" is missing): {line}",
51                )));
52            }
53
54            data.insert(tokens[0], tokens[1]);
55        }
56
57        let name = data.get("name");
58        let Some(name) = name else {
59            return Err(InvalidModuleConfigurationException::new(
60                "Mandatory configuration of \"name\" is missing",
61            ));
62        };
63        for c in name.bytes() {
64            if !c.is_ascii_alphanumeric() && c != b'_' {
65                return Err(InvalidModuleConfigurationException::new(
66                    "The module name may only contain alphanumeric characters and underscores (_)",
67                ));
68            }
69        }
70
71        let supported_implementations = data.get("supportedImplementations");
72        let supported_implementations = supported_implementations.map(|supported_implementations| {
73            supported_implementations.split(",").
74                    map(|ele| Box::from(ele.trim())).
75                    collect::<Vec<Box<str>>>().
76                    into_boxed_slice()
77        });
78
79        let module_type = data.get("moduleType");
80        let Some(module_type) = module_type else {
81            return Err(InvalidModuleConfigurationException::new(
82                "Mandatory configuration of \"moduleType\" is missing",
83            ));
84        };
85
86        let module_type = match *module_type {
87            "lang" => ModuleType::Lang,
88            "native" => ModuleType::Native,
89            module_type => {
90                return Err(InvalidModuleConfigurationException::new(format!(
91                    "Invalid configuration for \"moduleType\" (Must be one of \"lang\", \"native\"): {module_type}",
92                )));
93            },
94        };
95
96        Ok(Self::new(
97            Box::from(*name),
98            data.get("description").map(|ele| Box::from(*ele)),
99            data.get("version").map(|ele| Box::from(*ele)),
100
101            data.get("minSupportedVersion").map(|ele| Box::from(*ele)),
102            data.get("maxSupportedVersion").map(|ele| Box::from(*ele)),
103
104            supported_implementations,
105
106            module_type,
107
108            data.get("nativeEntryPoint").map(|ele| Box::from(*ele)),
109        ))
110    }
111
112    #[expect(clippy::too_many_arguments)]
113    pub fn new(
114        name: Box<str>,
115        description: Option<Box<str>>,
116        version: Option<Box<str>>,
117
118        min_supported_version: Option<Box<str>>,
119        max_supported_version: Option<Box<str>>,
120
121        supported_implementations: Option<Box<[Box<str>]>>,
122
123        module_type: ModuleType,
124
125        native_entry_point: Option<Box<str>>,
126    ) -> Self {
127        Self {
128            name,
129            description,
130            version,
131
132            min_supported_version,
133            max_supported_version,
134
135            supported_implementations,
136
137            module_type,
138
139            native_entry_point,
140        }
141    }
142
143    pub fn name(&self) -> &str {
144        &self.name
145    }
146
147    pub fn description(&self) -> Option<&str> {
148        self.description.as_deref()
149    }
150
151    pub fn version(&self) -> Option<&str> {
152        self.version.as_deref()
153    }
154
155    pub fn min_supported_version(&self) -> Option<&str> {
156        self.min_supported_version.as_deref()
157    }
158
159    pub fn max_supported_version(&self) -> Option<&str> {
160        self.max_supported_version.as_deref()
161    }
162
163    pub fn supported_implementations(&self) -> Option<&[Box<str>]> {
164        self.supported_implementations.as_deref()
165    }
166
167    pub fn module_type(&self) -> ModuleType {
168        self.module_type
169    }
170
171    pub fn native_entry_point(&self) -> Option<&str> {
172        self.native_entry_point.as_deref()
173    }
174}
175
176#[derive(Debug)]
177pub struct ZipEntry {
178    is_directory: bool,
179}
180
181impl ZipEntry {
182    pub(crate) fn new(is_directory: bool) -> Self {
183        Self { is_directory }
184    }
185
186    pub fn is_directory(&self) -> bool {
187        self.is_directory
188    }
189}
190
191#[derive(Debug)]
192pub struct Module {
193    file: Box<str>,
194
195    load: RefCell<bool>,
196
197    zip_entries: AHashMap<Box<str>, ZipEntry>,
198    zip_data: AHashMap<Box<str>, Box<[u8]>>,
199
200    lmc: LangModuleConfiguration,
201
202    exported_functions: RefCell<Vec<Box<str>>>,
203    exported_variables: RefCell<AHashMap<Rc<str>, DataObjectRef>>,
204
205    loaded_native_modules: RefCell<AHashMap<Box<str>, Box<dyn NativeModule>>>,
206}
207
208impl Module {
209    pub fn new(
210        file: Box<str>,
211
212        load: bool,
213
214        zip_entries: AHashMap<Box<str>, ZipEntry>,
215        zip_data: AHashMap<Box<str>, Box<[u8]>>,
216
217        lmc: LangModuleConfiguration,
218    ) -> Self {
219        Self {
220            file,
221
222            load: RefCell::new(load),
223
224            zip_entries,
225            zip_data,
226
227            lmc,
228
229            exported_functions: Default::default(),
230            exported_variables: Default::default(),
231
232            loaded_native_modules: Default::default(),
233        }
234    }
235
236    pub(crate) fn exported_functions(&self) -> &RefCell<Vec<Box<str>>> {
237        &self.exported_functions
238    }
239
240    pub(crate) fn exported_variables(&self) -> &RefCell<AHashMap<Rc<str>, DataObjectRef>> {
241        &self.exported_variables
242    }
243
244    #[expect(dead_code)]
245    pub(crate) fn loaded_native_modules(&self) -> &RefCell<AHashMap<Box<str>, Box<dyn NativeModule>>> {
246        &self.loaded_native_modules
247    }
248
249    pub fn file(&self) -> &str {
250        &self.file
251    }
252
253    pub fn is_load(&self) -> bool {
254        *self.load.borrow()
255    }
256
257    pub fn zip_entries(&self) -> &AHashMap<Box<str>, ZipEntry> {
258        &self.zip_entries
259    }
260
261    pub fn zip_data(&self) -> &AHashMap<Box<str>, Box<[u8]>> {
262        &self.zip_data
263    }
264
265    pub fn lang_module_configuration(&self) -> &LangModuleConfiguration {
266        &self.lmc
267    }
268
269    pub(crate) fn set_load(&self, load: bool) {
270        *self.load.borrow_mut() = load;
271    }
272}
273
274pub trait NativeModule {
275    /**
276     * Will be called if the module is loaded
277     *
278     * @param args provided to "func.loadModule()" [modulePath included] separated with ARGUMENT_SEPARATORs
279     * @return Will be returned for the "linker.loadModule()" call
280     */
281    fn load(&self, module: Rc<Module>, interpreter: &mut Interpreter, args: &[DataObjectRef]) -> OptionDataObjectRef;
282
283    /**
284     * Will be called if the module is unloaded
285     *
286     * @param args provided to "func.unloadModule()" [modulePath included] separated with ARGUMENT_SEPARATORs
287     * @return Will be returned for the "linker.unloadModule()" call
288     */
289    fn unload(&self, module: Rc<Module>, interpreter: &mut Interpreter, args: &[DataObjectRef]) -> OptionDataObjectRef;
290}
291
292impl Debug for dyn NativeModule {
293    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
294        write!(f, "Native module at {:p}", self)
295    }
296}
297
298#[derive(Debug)]
299pub struct InvalidModuleConfigurationException {
300    message: String
301}
302
303impl InvalidModuleConfigurationException {
304    pub fn new(message: impl Into<String>) -> Self {
305        Self { message: message.into() }
306    }
307
308    pub fn message(&self) -> &str {
309        &self.message
310    }
311}
312
313impl Display for InvalidModuleConfigurationException {
314    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
315        f.write_str(&self.message)
316    }
317}
318
319impl Error for InvalidModuleConfigurationException {}