lang_interpreter/interpreter/
module.rs1use 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 fn load(&self, module: Rc<Module>, interpreter: &mut Interpreter, args: &[DataObjectRef]) -> OptionDataObjectRef;
282
283 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 {}