1use crate::loader::{Library, loads_as};
6use crate::lower::{self, LowerError};
7use crate::unit::{Clock, Observer, Remains};
8use crate::{Compiled, 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;
18
19pub struct VmLibrary {
23 pub source: Library,
24 pub modules: Modules,
25 modules_first: bool,
27}
28
29type Found = LoadedProgram<Rc<Code>>;
30type FoundCode = FoundClass<Rc<ClassCode<Rc<Code>>>>;
31
32impl VmLibrary {
33 pub fn new(source: Library) -> Self {
35 let modules = Modules::new(source.dirs.clone(), lower::verify);
36 Self { source, modules, modules_first: false }
37 }
38
39 fn read_source(&mut self, name: &str) -> Option<Result<Found, LoadError>> {
41 self.source.programs.iter().any(|p| loads_as(p, name)).then(|| self.lowered_source(name))
42 }
43
44 fn lowered_source(&mut self, name: &str) -> Result<Found, LoadError> {
45 let found = <Library as Loader<Rc<Compiled>>>::program(&mut self.source, name)?;
46 Ok(LoadedProgram { compiled: Rc::new(code(&found.compiled)), name: found.name, files: found.files, size: found.size, source: found.source })
47 }
48
49 fn source_class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
52 let Some(found) = <Library as Loader<Rc<Compiled>>>::class(&mut self.source, external)? else { return Ok(None) };
53 let class = &found.code;
54 let lowered = |c: &Rc<Compiled>| Rc::new(code(c));
55 let part = |p: &oo::Part| Part { data: lowered(&p.data), records: p.records.clone() };
56 let methods = class
57 .methods
58 .iter()
59 .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 })
60 .collect();
61 let code = ClassCode { parent: class.parent.clone(), factory: class.factory.as_ref().map(part), object: class.object.as_ref().map(part), methods };
62 Ok(Some(FoundClass { code: Rc::new(code), sources: found.sources }))
63 }
64}
65
66impl Loader<Rc<Code>> for VmLibrary {
67 fn program(&mut self, name: &str) -> Result<Found, LoadError> {
68 if !self.modules_first
69 && let Some(found) = self.read_source(name)
70 {
71 return found;
72 }
73 if let Some(found) = self.modules.loaded(name) {
74 return found;
75 }
76 if let Some(found) = self.read_source(name) {
77 return found;
78 }
79 match self.modules.search(name) {
80 Err(LoadError::NotFound) => self.lowered_source(name),
81 found => found,
82 }
83 }
84
85 fn holder(&self, entry: &str) -> Option<String> {
86 let source = || <Library as Loader<Rc<Compiled>>>::holder(&self.source, entry);
87 if self.modules_first { self.modules.holder(entry).or_else(source) } else { source().or_else(|| self.modules.holder(entry)) }
88 }
89
90 fn entry(program: &Rc<Code>, name: &str) -> Option<usize> {
91 program.entry(name)
92 }
93
94 fn shape(program: &Rc<Code>) -> (usize, usize) {
95 program.shape()
96 }
97
98 fn nested(program: &Rc<Code>) -> &[String] {
99 program.nested()
100 }
101
102 fn source(program: &Rc<Code>, file: usize) -> Option<String> {
103 let p = program.program()?;
104 p.debug.sources.get(file).map(|&s| p.symbols[s as usize].clone())
105 }
106
107 fn class(&mut self, external: &str) -> Result<Option<FoundCode>, String> {
108 let read_source = self.source.programs.iter().any(|p| oo::defined_class(p).as_deref() == Some(external));
109 if (!read_source || self.modules_first)
110 && let Some(found) = self.modules.loaded_class(external)?
111 {
112 return Ok(Some(found));
113 }
114 if !read_source && let Some(found) = self.modules.search_class(external)? {
115 return Ok(Some(found));
116 }
117 self.source_class(external)
118 }
119
120 fn mapset(&mut self, name: &str) -> Option<Result<rt::bms::Mapset, String>> {
122 self.modules.mapset(name).map(Ok).or_else(|| self.source.mapset(name))
123 }
124}
125
126pub fn code(compiled: &Compiled) -> Code {
128 let lowered = lower::lower(compiled).map_err(|e| match e {
129 LowerError::Unsupported(what, _) => what.to_owned(),
130 other => other.to_string(),
131 });
132 held(compiled, lowered)
133}
134
135pub fn lowered(compiled: &Compiled) -> Result<Code, LowerError> {
137 Ok(held(compiled, Ok(lower::lower(compiled)?)))
138}
139
140fn held(compiled: &Compiled, lowered: Result<rt::lir::Program, String>) -> Code {
141 let entries = compiled.entries.iter().map(|e| e.name.clone()).collect();
142 let method = compiled.program.oo.as_deref().and_then(|o| o.method()).map(|m| format!("{}.{}", m.class, m.name));
143 Code::new(lowered, entries, compiled.program.files.len(), compiled.layout.size as usize, compiled.program.nested.clone(), method)
144}
145
146#[allow(clippy::too_many_arguments)]
148fn run_unit<'w>(
149 library: VmLibrary,
150 dds: files::Dds,
151 sysin: Option<Box<dyn BufRead + 'w>>,
152 clock: Clock,
153 database: Option<&'w mut (dyn sql::Database + '_)>,
154 out: &'w mut dyn Write,
155 err: &'w mut dyn Write,
156 observer: Option<Observer<'w>>,
157) -> RunUnit<'w, Rc<Code>, VmLibrary> {
158 let (statements, taint) = (library.source.trace_statements.clone(), library.source.trace_input.then(rt::taint::Taint::default));
159 let limit = library.source.statement_limit;
160 let mut run_unit = RunUnit::new(library, dds, sysin, clock, out, err);
161 run_unit.observer = observer;
162 run_unit.statements = statements;
163 run_unit.taint = taint;
164 run_unit.statement_limit = limit;
165 run_unit.sql = database.map(sql::Session::new);
166 run_unit
167}
168
169fn run_main(code: &Code, id: &str, me: usize, run_unit: &mut RunUnit<'_, Rc<Code>, VmLibrary>, parm: Option<(usize, bool)>) -> Result<(Ending, i16), Halt> {
173 let (parm, trap_off) = parm.unzip();
174 let ending = rt::vm::run(code, me, run_unit, &parm.map_or_else(Vec::new, |p| vec![Some(p)]));
175 let settled = run_unit.sql.as_mut().map_or(Ok(()), |s| s.settle(id, ending.is_ok()).map(drop));
176 let closed = run_unit.close_all(trap_off == Some(true) && matches!(&ending, Err(Halt::Abend(a)) if a.code.bypasses_trap_off()));
177 let ending = ending?;
178 settled.map_err(|a| Abend { code: a.code.into(), message: a.message, pos: Pos::default(), file: None })?;
179 closed.map_err(|m| Abend { code: AbendCode::Ironwork, message: m, pos: Pos::default(), file: None })?;
180 Ok((ending, run_unit.return_code()))
181}
182
183#[allow(clippy::too_many_arguments)]
187pub fn execute<'w>(
188 compiled: &Compiled,
189 code: &Code,
190 library: Library,
191 dds: files::Dds,
192 sysin: Option<Box<dyn BufRead + 'w>>,
193 clock: Clock,
194 database: Option<&'w mut (dyn sql::Database + '_)>,
195 out: &'w mut dyn Write,
196 err: &'w mut dyn Write,
197 observer: Option<Observer<'w>>,
198 parm: Option<&str>,
199 kept: &mut Option<Remains>,
200) -> Result<(Ending, i16), Halt> {
201 oo::refuse_to_run(&compiled.program)?;
202 let mut run_unit = run_unit(VmLibrary::new(library), dds, sysin, clock, database, out, err, observer);
203 let me = run_unit.add_named(None, compiled.program.id.to_ascii_uppercase(), compiled.program.files.len(), compiled.layout.size as usize);
204 let parm = parm.map(|p| (crate::push_parm(&mut run_unit, compiled.options.code_page(), p), rt::le::parm::trap_off(p)));
205 let ran = run_main(code, &compiled.program.id, me, &mut run_unit, parm);
206 *kept = Some(Remains::of(&run_unit));
207 ran
208}
209
210#[allow(clippy::too_many_arguments)]
214pub fn execute_module<'w>(
215 module: LoadedModule,
216 path: &Path,
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 parm: Option<&str>,
225) -> Result<(Ending, i16), Halt> {
226 let refused = |message: String| Halt::Abend(Abend::ironwork(message, Pos::default()));
227 let mut library = VmLibrary::new(library);
228 library.modules_first = true;
229 let first = library.modules.add(path.to_owned(), module);
230 let main = match library.modules.take(first, 0) {
231 Some(found) => found.map_err(refused)?,
232 None => return Err(refused(format!("{}: the module holds no program", path.display()))),
233 };
234 let code = main.compiled;
235 let Some(program) = code.program() else { return Err(refused(format!("{}: program 0 is not lowered", path.display()))) };
236 let id = program.symbols.get(program.id as usize).cloned().unwrap_or_default();
237 if program.services.class.is_some() {
238 return Err(refused(format!("{id} is a class definition: run a program that uses it")));
239 }
240 let page = program.options.options.code_page();
241 let mut run_unit = run_unit(library, dds, sysin, clock, database, out, err, None);
242 let me = run_unit.add_named(None, main.name, main.files, main.size);
243 let parm = parm.map(|p| (crate::push_parm(&mut run_unit, page, p), rt::le::parm::trap_off(p)));
244 run_main(&code, &id, me, &mut run_unit, parm)
245}
246
247#[allow(clippy::too_many_arguments)]
251pub fn execute_cics<'w>(
252 compiled: &Compiled,
253 code: &Code,
254 library: Library,
255 dds: files::Dds,
256 task: cics::Task,
257 clock: Clock,
258 database: Option<&'w mut (dyn sql::Database + '_)>,
259 out: &'w mut dyn Write,
260 err: &'w mut dyn Write,
261 observer: Option<Observer<'w>>,
262 kept: &mut Option<Remains>,
263) -> (Result<Ending, Halt>, cics::Task) {
264 if let Err(abend) = oo::refuse_to_run(&compiled.program) {
265 return (Err(abend.into()), task);
266 }
267 let run_unit = run_unit(VmLibrary::new(library), dds, None, clock, database, out, err, observer);
268 let (ending, ended, task) = crate::run_task(compiled, run_unit, task, kept, |unit, me, commarea, length| rt::vm::run_task(code, me, unit, commarea, length));
269 let ending = match ending {
270 Err(Halt::Abend(abend)) => Err(Halt::Abend(crate::asra(abend))),
271 other => other,
272 };
273 (ending.and_then(|e| ended.map(|()| e).map_err(Halt::Abend)), task)
274}