1use super::{DirectoryEntry, LoadedModule, SourceFile, read};
6use crate::bms::Mapset;
7use crate::lir::{Class, ClassPart, Program, SymId};
8use crate::oo::{ClassCode, JAVA_LANG_OBJECT, MethodCode, Part};
9use crate::unit::{FoundClass, LoadError, LoadedProgram};
10use crate::vm::Code;
11use numeric::governs::Facts;
12use std::path::{Path, PathBuf};
13use std::rc::Rc;
14
15pub type Check = fn(&Program) -> Result<(), String>;
17
18pub fn member_name(name: &str) -> bool {
20 !name.is_empty() && name.len() <= 30 && name.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '@' | '#' | '$'))
21}
22
23pub struct Modules {
24 pub dirs: Vec<PathBuf>,
26 check: Check,
27 read: Vec<Read>,
28}
29
30struct Read {
33 path: PathBuf,
34 directory: Vec<DirectoryEntry>,
35 programs: Vec<Option<Program>>,
36 mapsets: Vec<Mapset>,
37 files: Vec<Vec<Option<SourceFile>>>,
38 facts: Vec<Option<Facts>>,
39 first: bool,
40}
41
42type Found = LoadedProgram<Rc<Code>>;
43type FoundCode = FoundClass<Rc<ClassCode<Rc<Code>>>>;
44
45impl Modules {
46 pub fn new(dirs: Vec<PathBuf>, check: Check) -> Self {
47 Self { dirs, check, read: Vec::new() }
48 }
49
50 pub fn add(&mut self, path: PathBuf, module: LoadedModule) -> usize {
53 self.register(path, module, false)
54 }
55
56 pub fn add_first(&mut self, path: PathBuf, module: LoadedModule) -> usize {
60 self.register(path, module, true)
61 }
62
63 fn register(&mut self, path: PathBuf, module: LoadedModule, first: bool) -> usize {
64 let LoadedModule { directory, programs, mapsets, files, facts } = module;
65 self.read.push(Read { path, directory, programs: programs.into_iter().map(Some).collect(), mapsets, files, facts, first });
66 self.read.len() - 1
67 }
68
69 pub fn take(&mut self, k: usize, ordinal: usize) -> Option<Result<Found, String>> {
71 let held = self.read.get_mut(k)?;
72 let program = held.programs.get_mut(ordinal)?.take()?;
73 let entry = &held.directory[ordinal];
74 let nested = held.directory.iter().filter(|e| e.parent == Some(ordinal as u32)).map(|e| e.id.clone()).collect();
75 let name = entry.load_name().to_ascii_uppercase();
76 let checked = (self.check)(&program).map_err(|e| format!("{}: program {}: {e}", held.path.display(), entry.id));
77 let own = held.files.get(ordinal).map(Vec::as_slice).unwrap_or_default();
78 let recorded = if held.first && own.first() == held.files.first().and_then(|f| f.first()) {
79 Vec::new()
80 } else {
81 program.debug.sources.iter().map(|&s| symbol(&program, s)).zip(own.iter().cloned()).collect()
82 };
83 let facts = held.facts(ordinal, &program);
84 Some(checked.map(|()| {
85 let code = code(program, nested, None, facts);
86 let (files, size) = code.shape();
87 LoadedProgram { compiled: Rc::new(code), name, files, size, source: None, recorded }
88 }))
89 }
90
91 pub fn loaded(&mut self, name: &str) -> Option<Result<Found, LoadError>> {
94 let (k, ordinal) = self.read.iter().enumerate().find_map(|(k, m)| m.untaken(|e| e.load_name().eq_ignore_ascii_case(name)).map(|o| (k, o)))?;
95 self.take(k, ordinal).map(|found| found.map_err(LoadError::Compile))
96 }
97
98 pub fn search(&mut self, name: &str) -> Result<Found, LoadError> {
101 if !member_name(name) {
102 return Err(LoadError::NotFound);
103 }
104 let Some(path) = self.file(&[name.to_owned(), name.to_ascii_lowercase()]) else { return Err(LoadError::NotFound) };
105 let module = open(&path).map_err(LoadError::Compile)?;
106 let Some(ordinal) = module.directory.iter().position(|e| e.load_name().eq_ignore_ascii_case(name)) else {
107 return Err(LoadError::Compile(format!("{}: the module holds no program {name}", path.display())));
108 };
109 let k = self.add(path, module);
110 match self.take(k, ordinal) {
111 Some(found) => found.map_err(LoadError::Compile),
112 None => Err(LoadError::NotFound),
113 }
114 }
115
116 pub fn holder(&self, entry: &str) -> Option<String> {
118 self.read.iter().find_map(|m| m.untaken(|e| e.entries.iter().any(|(n, _)| n == entry)).map(|o| m.directory[o].id.to_ascii_uppercase()))
119 }
120
121 pub fn loaded_class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
123 match self.read.iter().enumerate().find_map(|(k, m)| m.defining(external).map(|o| (k, o))) {
124 Some(found) => self.take_class(found).map(Some),
125 None => Ok(None),
126 }
127 }
128
129 pub fn search_class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
132 if external == JAVA_LANG_OBJECT {
133 return Ok(None);
134 }
135 let member = |n: &str| !n.is_empty() && n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
136 let simple = external.rsplit('.').next().unwrap_or(external).to_owned();
137 let mut names = vec![simple, external.replace('.', "_")];
138 names.dedup();
139 names.retain(|n| member(n));
140 let variants: Vec<String> = names.iter().flat_map(|n| [n.clone(), n.to_ascii_lowercase(), n.to_ascii_uppercase()]).collect();
141 for dir in self.dirs.clone() {
142 for variant in &variants {
143 let path = dir.join(format!("{variant}.iwm"));
144 if !path.is_file() {
145 continue;
146 }
147 let module = open(&path)?;
148 let Some(ordinal) = module.programs.iter().position(|p| defines(p, external)) else { continue };
149 let k = self.add(path, module);
150 return self.take_class((k, ordinal)).map(Some);
151 }
152 }
153 Ok(None)
154 }
155
156 pub fn mapset(&self, name: &str) -> Option<Mapset> {
158 self.read.iter().find_map(|m| m.mapsets.iter().find(|s| s.name.eq_ignore_ascii_case(name))).cloned()
159 }
160
161 fn file(&self, names: &[String]) -> Option<PathBuf> {
162 self.dirs.iter().flat_map(|d| names.iter().map(move |n| d.join(format!("{n}.iwm")))).find(|p| p.is_file())
163 }
164
165 fn take_class(&mut self, (k, ordinal): (usize, usize)) -> Result<FoundCode, String> {
168 let held = &mut self.read[k];
169 let Some(mut program) = held.programs[ordinal].take() else { return Err(format!("{}: program {ordinal} is taken", held.path.display())) };
170 (self.check)(&program).map_err(|e| format!("{}: class {}: {e}", held.path.display(), held.directory[ordinal].id))?;
171 let facts = held.facts(ordinal, &program);
172 let Some(class) = program.services.class.take() else { return Err(format!("{}: program {ordinal} is not a class", held.path.display())) };
173 let Class { parent, factory, object, methods, .. } = *class;
174 let sym = |id: SymId| symbol(&program, id);
175 let name = sym(program.id);
176 let part = |p: ClassPart| Part { data: Rc::new(code(p.data, Vec::new(), None, facts)), records: p.records };
177 let methods = methods
178 .into_iter()
179 .map(|m| {
180 let method = sym(m.name);
181 MethodCode {
182 factory: m.factory,
183 params: m.params.iter().map(|&p| sym(p)).collect(),
184 returns: m.returns.map(sym),
185 own_records: usize::from(m.own_records),
186 code: Rc::new(code(m.code, Vec::new(), Some(format!("{name}.{method}")), facts)),
187 name: method,
188 }
189 })
190 .collect();
191 let code = ClassCode { parent: sym(parent), factory: factory.map(part), object: object.map(part), methods };
192 let sources = program.debug.sources.iter().map(|&s| sym(s)).collect();
193 Ok(FoundClass { code: Rc::new(code), sources })
194 }
195}
196
197impl Read {
198 fn untaken(&self, wanted: impl Fn(&DirectoryEntry) -> bool) -> Option<usize> {
200 self.directory.iter().zip(&self.programs).position(|(e, p)| p.is_some() && wanted(e))
201 }
202
203 fn facts(&self, ordinal: usize, program: &Program) -> Facts {
204 super::held_facts(self.facts.get(ordinal).copied().flatten(), program)
205 }
206
207 fn defining(&self, external: &str) -> Option<usize> {
208 self.programs.iter().position(|p| p.as_ref().is_some_and(|p| defines(p, external)))
209 }
210}
211
212fn defines(program: &Program, external: &str) -> bool {
213 program.services.class.as_ref().is_some_and(|c| symbol(program, c.external) == external)
214}
215
216fn symbol(program: &Program, id: SymId) -> String {
217 program.symbols.get(id as usize).cloned().unwrap_or_default()
218}
219
220fn code(program: Program, nested: Vec<String>, method: Option<String>, facts: Facts) -> Code {
223 let entries = program.services.entries.iter().map(|e| symbol(&program, e.name)).collect();
224 let (files, size) = (program.services.files.len(), program.storage.size as usize);
225 Code::new(Ok(program), entries, files, size, nested, method, facts)
226}
227
228fn open(path: &Path) -> Result<LoadedModule, String> {
230 let bytes = std::fs::read(path).map_err(|e| format!("{}: {e}", path.display()))?;
231 read(&bytes).map_err(|e| format!("{}: {e}", path.display()))
232}