1use crate::loader::{Library, loads_as};
6use crate::lower::{self, LowerError};
7use crate::unit::{Clock, Observer, Remains};
8use crate::{Compiled, Passed, cics, files, oo, sql};
9use rt::abend::{Abend, AbendCode, Ending};
10use rt::module::{LoadedModule, Modules};
11use rt::oo::{ClassCode, MethodCode, Part};
12use rt::unit::{FoundClass, LoadError, LoadedProgram, Loader, RunUnit};
13pub use rt::vm::{Code, Halt};
14use std::io::{BufRead, Write};
15use std::path::Path;
16use std::rc::Rc;
17use syntax::Pos;
18use zarch::ebcdic::CodePage;
19
20pub struct VmLibrary {
24 pub source: Library,
25 pub modules: Modules,
26 modules_first: bool,
28}
29
30type Found = LoadedProgram<Rc<Code>>;
31type FoundCode = FoundClass<Rc<ClassCode<Rc<Code>>>>;
32
33impl VmLibrary {
34 pub fn new(source: Library) -> Self {
36 let modules = Modules::new(source.dirs.clone(), lower::verify);
37 Self { source, modules, modules_first: false }
38 }
39
40 pub fn for_module(source: Library, path: &Path, module: LoadedModule) -> Self {
43 let mut library = Self::new(source);
44 library.modules_first = true;
45 library.modules.add_first(path.to_owned(), module);
46 library
47 }
48
49 fn first_program(&mut self, path: &Path, name: Option<&str>) -> Result<Found, String> {
52 let found = match name {
53 None => self.modules.take(0, 0).unwrap_or_else(|| Err(format!("{}: the module holds no program", path.display()))),
54 Some(name) => self.program(name).map_err(|e| match e {
55 LoadError::NotFound => format!("program {name} not found"),
56 LoadError::Compile(message) => message,
57 }),
58 }?;
59 match found.compiled.program() {
60 Some(p) if p.services.class.is_some() => Err(format!("{} is a class definition: run a program that uses it", p.symbols.get(p.id as usize).cloned().unwrap_or_default())),
61 _ => Ok(found),
62 }
63 }
64
65 fn read_source(&mut self, name: &str) -> Option<Result<Found, LoadError>> {
67 self.source.programs.iter().any(|p| loads_as(p, name)).then(|| self.lowered_source(name))
68 }
69
70 fn lowered_source(&mut self, name: &str) -> Result<Found, LoadError> {
71 let found = <Library as Loader<Rc<Compiled>>>::program(&mut self.source, name)?;
72 Ok(LoadedProgram { compiled: Rc::new(code(&found.compiled)), name: found.name, files: found.files, size: found.size, source: found.source, recorded: Vec::new() })
73 }
74
75 fn source_class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
78 let Some(found) = <Library as Loader<Rc<Compiled>>>::class(&mut self.source, external)? else { return Ok(None) };
79 let class = &found.code;
80 let lowered = |c: &Rc<Compiled>| Rc::new(code(c));
81 let part = |p: &oo::Part| Part { data: lowered(&p.data), records: p.records.clone() };
82 let methods = class
83 .methods
84 .iter()
85 .map(|m| MethodCode { name: m.name.clone(), factory: m.factory, params: m.params.clone(), returns: m.returns.clone(), code: lowered(&m.code), own_records: m.own_records })
86 .collect();
87 let code = ClassCode { parent: class.parent.clone(), factory: class.factory.as_ref().map(part), object: class.object.as_ref().map(part), methods };
88 Ok(Some(FoundClass { code: Rc::new(code), sources: found.sources }))
89 }
90}
91
92impl Loader<Rc<Code>> for VmLibrary {
93 fn program(&mut self, name: &str) -> Result<Found, LoadError> {
94 if !self.modules_first
95 && let Some(found) = self.read_source(name)
96 {
97 return found;
98 }
99 if let Some(found) = self.modules.loaded(name) {
100 return found;
101 }
102 if let Some(found) = self.read_source(name) {
103 return found;
104 }
105 match self.modules.search(name) {
106 Err(LoadError::NotFound) => self.lowered_source(name),
107 found => found,
108 }
109 }
110
111 fn holder(&self, entry: &str) -> Option<String> {
112 let source = || <Library as Loader<Rc<Compiled>>>::holder(&self.source, entry);
113 if self.modules_first { self.modules.holder(entry).or_else(source) } else { source().or_else(|| self.modules.holder(entry)) }
114 }
115
116 fn entry(program: &Rc<Code>, name: &str) -> Option<usize> {
117 program.entry(name)
118 }
119
120 fn shape(program: &Rc<Code>) -> (usize, usize) {
121 program.shape()
122 }
123
124 fn nested(program: &Rc<Code>) -> &[String] {
125 program.nested()
126 }
127
128 fn source(program: &Rc<Code>, file: usize) -> Option<String> {
129 let p = program.program()?;
130 p.debug.sources.get(file).map(|&s| p.symbols[s as usize].clone())
131 }
132
133 fn class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
134 let read_source = self.source.programs.iter().any(|p| oo::defined_class(p).as_deref() == Some(external));
135 if (!read_source || self.modules_first)
136 && let Some(found) = self.modules.loaded_class(external)?
137 {
138 return Ok(Some(found));
139 }
140 if !read_source && let Some(found) = self.modules.search_class(external)? {
141 return Ok(Some(found));
142 }
143 self.source_class(external)
144 }
145
146 fn mapset(&mut self, name: &str) -> Option<Result<rt::bms::Mapset, String>> {
148 self.modules.mapset(name).map(Ok).or_else(|| self.source.mapset(name))
149 }
150}
151
152pub fn code(compiled: &Compiled) -> Code {
154 let lowered = lower::lower(compiled).map_err(|e| match e {
155 LowerError::Unsupported(what, _) => what.to_owned(),
156 other => other.to_string(),
157 });
158 held(compiled, lowered)
159}
160
161pub fn lowered(compiled: &Compiled) -> Result<Code, LowerError> {
163 Ok(held(compiled, Ok(lower::lower(compiled)?)))
164}
165
166fn held(compiled: &Compiled, lowered: Result<rt::lir::Program, String>) -> Code {
167 let entries = compiled.entries.iter().map(|e| e.name.clone()).collect();
168 let method = compiled.program.oo.as_deref().and_then(|o| o.method()).map(|m| format!("{}.{}", m.class, m.name));
169 Code::new(lowered, entries, compiled.program.files.len(), compiled.layout.size as usize, compiled.program.nested.clone(), method)
170}
171
172#[allow(clippy::too_many_arguments)]
174fn run_unit<'w>(
175 library: VmLibrary,
176 dds: files::Dds,
177 sysin: Option<Box<dyn BufRead + 'w>>,
178 clock: Clock,
179 database: Option<&'w mut (dyn sql::Database + '_)>,
180 out: &'w mut dyn Write,
181 err: &'w mut dyn Write,
182 observer: Option<Observer<'w>>,
183) -> RunUnit<'w, Rc<Code>, VmLibrary> {
184 let (statements, taint) = (library.source.trace_statements.clone(), library.source.trace_input.then(rt::taint::Taint::default));
185 let limit = library.source.statement_limit;
186 let mut run_unit = RunUnit::new(library, dds, sysin, clock, out, err);
187 run_unit.observer = observer;
188 run_unit.statements = statements;
189 run_unit.taint = taint;
190 run_unit.statement_limit = limit;
191 run_unit.sql = database.map(sql::Session::new);
192 run_unit
193}
194
195fn run_main(code: &Code, id: &str, me: usize, run_unit: &mut RunUnit<'_, Rc<Code>, VmLibrary>, passed: Passed<'_>, page: &CodePage) -> Result<(Ending, i16), Halt> {
199 let trap_off = matches!(passed, Passed::Parm(p) if rt::le::parm::trap_off(p));
200 let addresses = passed.addresses(run_unit, page);
201 let ending = rt::vm::run(code, me, run_unit, &addresses, passed.main());
202 let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(id, ending.is_ok()).map(drop));
203 let closed = run_unit.close_all(trap_off && matches!(&ending, Err(Halt::Abend(a)) if a.code.bypasses_trap_off()));
204 let ending = ending?;
205 settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
206 closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
207 Ok((ending, run_unit.return_code()))
208}
209
210#[allow(clippy::too_many_arguments)]
214pub fn execute<'w>(
215 compiled: &Compiled,
216 code: &Code,
217 library: Library,
218 dds: files::Dds,
219 sysin: Option<Box<dyn BufRead + 'w>>,
220 clock: Clock,
221 database: Option<&'w mut (dyn sql::Database + '_)>,
222 out: &'w mut dyn Write,
223 err: &'w mut dyn Write,
224 observer: Option<Observer<'w>>,
225 passed: Passed<'_>,
226 kept: &mut Option<Remains>,
227) -> Result<(Ending, i16), Halt> {
228 oo::refuse_to_run(&compiled.program)?;
229 let mut run_unit = run_unit(VmLibrary::new(library), dds, sysin, clock, database, out, err, observer);
230 let me = run_unit.add_named(None, compiled.program.id.to_ascii_uppercase(), compiled.program.files.len(), compiled.layout.size as usize);
231 let ran = run_main(code, &compiled.program.id, me, &mut run_unit, passed, compiled.options.code_page());
232 *kept = Some(Remains::of(&run_unit));
233 ran
234}
235
236fn refused(message: String) -> Halt {
237 Halt::Abend(Abend::ironwork(message, Pos::default()))
238}
239
240fn sources(program: &rt::lir::Program) -> Vec<String> {
242 program.debug.sources.iter().map(|&s| program.symbols.get(s as usize).cloned().unwrap_or_default()).collect()
243}
244
245#[allow(clippy::too_many_arguments)]
249pub fn execute_module<'w>(
250 module: LoadedModule,
251 path: &Path,
252 library: Library,
253 dds: files::Dds,
254 sysin: Option<Box<dyn BufRead + 'w>>,
255 clock: Clock,
256 database: Option<&'w mut (dyn sql::Database + '_)>,
257 out: &'w mut dyn Write,
258 err: &'w mut dyn Write,
259 observer: Option<Observer<'w>>,
260 parm: Option<&str>,
261) -> Result<(Ending, i16), Halt> {
262 let mut library = VmLibrary::for_module(library, path, module);
263 let main = library.first_program(path, None).map_err(refused)?;
264 let code = main.compiled;
265 let Some(program) = code.program() else { return Err(refused(format!("{}: program 0 is not lowered", path.display()))) };
266 let id = program.symbols.get(program.id as usize).cloned().unwrap_or_default();
267 let page = program.options.options.code_page();
268 let mut run_unit = run_unit(library, dds, sysin, clock, database, out, err, observer);
269 let me = run_unit.add_named(None, main.name, main.files, main.size);
270 run_main(&code, &id, me, &mut run_unit, parm.map_or(Passed::Nothing, Passed::Parm), page)
271}
272
273pub fn module_program(module: &LoadedModule, path: &Path, library: Library, name: &str) -> Result<Vec<(String, Option<rt::module::SourceFile>)>, String> {
277 VmLibrary::for_module(library, path, module.clone()).first_program(path, Some(name)).map(|found| found.recorded)
278}
279
280#[allow(clippy::too_many_arguments)]
285pub fn execute_module_cics<'w>(
286 module: &LoadedModule,
287 path: &Path,
288 name: Option<&str>,
289 library: Library,
290 dds: files::Dds,
291 task: cics::Task,
292 clock: Clock,
293 database: Option<&'w mut (dyn sql::Database + '_)>,
294 out: &'w mut dyn Write,
295 err: &'w mut dyn Write,
296 observer: Option<Observer<'w>>,
297) -> (Result<Ending, Halt>, cics::Task, Vec<String>) {
298 let mut library = VmLibrary::for_module(library, path, module.clone());
299 let found = match library.first_program(path, name) {
300 Ok(found) => found,
301 Err(message) => return (Err(refused(message)), task, Vec::new()),
302 };
303 let code = found.compiled;
304 let program = code.program();
305 let mut names = program.map(sources).unwrap_or_default();
306 if let (Some(own), Some(path)) = (names.first_mut(), &found.source) {
307 *own = path.display().to_string();
308 }
309 let id = program.and_then(|p| p.symbols.get(p.id as usize).cloned()).unwrap_or_default();
310 let page = program.map_or_else(|| numeric::Options::default().code_page(), |p| p.options.options.code_page());
311 let first = crate::First { id: &id, name: found.name, files: found.files, size: found.size, page, source: found.source };
312 let run_unit = run_unit(library, dds, None, clock, database, out, err, observer);
313 let (ending, ended, task) = crate::run_task(first, run_unit, task, &mut None, |unit, me, commarea, length| rt::vm::run_task(&code, me, unit, commarea, length));
314 let ending = match ending {
315 Err(Halt::Abend(abend)) => Err(Halt::Abend(crate::asra(abend))),
316 other => other,
317 };
318 (ending.and_then(|e| ended.map(|()| e).map_err(Halt::Abend)), task, names)
319}
320
321#[allow(clippy::too_many_arguments)]
325pub fn execute_cics<'w>(
326 compiled: &Compiled,
327 code: &Code,
328 library: Library,
329 dds: files::Dds,
330 task: cics::Task,
331 clock: Clock,
332 database: Option<&'w mut (dyn sql::Database + '_)>,
333 out: &'w mut dyn Write,
334 err: &'w mut dyn Write,
335 observer: Option<Observer<'w>>,
336 kept: &mut Option<Remains>,
337) -> (Result<Ending, Halt>, cics::Task) {
338 if let Err(abend) = oo::refuse_to_run(&compiled.program) {
339 return (Err(abend.into()), task);
340 }
341 let run_unit = run_unit(VmLibrary::new(library), dds, None, clock, database, out, err, observer);
342 let (ending, ended, task) = crate::run_task(crate::First::of(compiled), run_unit, task, kept, |unit, me, commarea, length| rt::vm::run_task(code, me, unit, commarea, length));
343 let ending = match ending {
344 Err(Halt::Abend(abend)) => Err(Halt::Abend(crate::asra(abend))),
345 other => other,
346 };
347 (ending.and_then(|e| ended.map(|()| e).map_err(Halt::Abend)), task)
348}