1use crate::abend::Abend;
9use crate::files::{Dds, Open};
10use crate::vocab::{OpenMode, Pos};
11use std::collections::HashMap;
12use std::io::{BufRead, Write};
13use std::path::{Path, PathBuf};
14
15pub const ADDRESS_BASE: u32 = 0x0001_0000;
18pub const RETURN_CODE: usize = 0;
20const RESERVED: usize = 8;
21const ALIGNMENT: usize = 8;
22
23pub struct Loaded<H> {
24 pub compiled: Option<H>,
26 pub name: String,
27 pub base: usize,
28 pub files: Vec<Option<Open>>,
29 pub locked: Vec<bool>,
31 pub initialized: bool,
32 pub active: bool,
33 pub entry: Option<usize>,
36 pub altered: Vec<Option<usize>>,
38 pub source: Option<PathBuf>,
40}
41
42pub enum LoadError {
43 NotFound,
44 Compile(String),
45}
46
47pub struct LoadedProgram<H> {
49 pub compiled: H,
50 pub name: String,
52 pub files: usize,
53 pub size: usize,
54 pub source: Option<PathBuf>,
56}
57
58pub struct FoundClass<C> {
61 pub code: C,
62 pub sources: Vec<String>,
63}
64
65pub trait Loader<H> {
67 type Class;
69
70 fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
72
73 fn holder(&self, entry: &str) -> Option<String>;
75
76 fn entry(program: &H, name: &str) -> Option<usize>;
78
79 fn shape(program: &H) -> (usize, usize);
81
82 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Self::Class>>, String>;
84}
85
86#[derive(Clone, Copy, Debug)]
87pub enum Clock {
88 System,
89 Fixed(i64, u32),
91}
92
93pub enum Event<'a> {
97 Open { dd: &'a str, mode: OpenMode, path: &'a Path },
98 Close { dd: &'a str, path: &'a Path },
99 Load { program: &'a str, source: Option<&'a Path> },
100 Paragraph { program: &'a str, name: &'a str, index: usize },
102 Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str },
105}
106
107pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
108
109pub const MAX_DEPTH: usize = 100;
111
112pub struct RunUnit<'w, H, L: Loader<H>> {
113 pub mem: Vec<u8>,
114 pub depth: usize,
116 pub programs: Vec<Loaded<H>>,
117 names: HashMap<String, usize>,
118 pub library: L,
119 pub dds: Dds,
120 pub sysin: Option<Box<dyn BufRead + 'w>>,
121 pub clock: Clock,
122 pub out: &'w mut dyn Write,
123 pub err: &'w mut dyn Write,
124 pub cics: Option<crate::cics::Task>,
126 pub eib: usize,
127 pub cics_files: HashMap<String, Open>,
128 pub sql: Option<crate::sql::Session<'w>>,
130 pub le: crate::le::State,
132 pub oo: crate::oo::Objects<L::Class>,
134 pub observer: Option<Observer<'w>>,
136 pub random: Option<u32>,
138}
139
140impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
141 pub fn new(library: L, dds: Dds, sysin: Option<Box<dyn BufRead + 'w>>, clock: Clock, out: &'w mut dyn Write, err: &'w mut dyn Write) -> Self {
142 Self {
143 mem: vec![0; RESERVED],
144 depth: 0,
145 programs: Vec::new(),
146 names: HashMap::new(),
147 library,
148 dds,
149 sysin,
150 clock,
151 out,
152 err,
153 cics: None,
154 eib: 0,
155 cics_files: HashMap::new(),
156 sql: None,
157 le: crate::le::State::default(),
158 oo: Default::default(),
159 observer: None,
160 random: None,
161 }
162 }
163
164 fn allocate(&mut self, size: usize) -> usize {
165 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
166 self.mem.resize(base + size, 0);
167 base
168 }
169
170 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
172 let base = self.allocate(size);
173 let index = self.programs.len();
174 self.names.insert(name.clone(), index);
175 self.programs.push(Loaded { compiled, name, base, files: (0..files).map(|_| None).collect(), locked: vec![false; files], initialized: false, active: false, entry: None, altered: Vec::new(), source: None });
176 index
177 }
178
179 pub fn load_entry(&mut self, name: &str, dynamic: bool) -> Result<(usize, Option<usize>), LoadError> {
183 let name = name.to_ascii_uppercase();
184 if let Some(i) = self.find(&name) {
185 return Ok((i, self.programs[i].entry));
186 }
187 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
188 Some(i) => i,
189 None => {
190 let holder = self.library.holder(&name);
191 self.load(holder.as_deref().unwrap_or(&name))?
192 }
193 };
194 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
195 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
196 if !dynamic {
197 return Ok((index, Some(entry)));
198 }
199 let (files, size) = L::shape(&compiled);
200 let copy = self.add_named(Some(compiled), name, files, size);
201 self.programs[copy].entry = Some(entry);
202 self.programs[copy].source = self.programs[index].source.clone();
203 Ok((copy, Some(entry)))
204 }
205
206 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
208 let at = self.allocate(bytes.len());
209 self.mem[at..at + bytes.len()].copy_from_slice(bytes);
210 at
211 }
212
213 pub fn release_temporaries(&mut self, mark: usize) {
215 if self.programs.iter().all(|p| p.base < mark) && self.le.heap_end() <= mark {
216 self.mem.truncate(mark.max(RESERVED));
217 }
218 }
219
220 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
222 if self.depth >= MAX_DEPTH {
223 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
224 }
225 self.depth += 1;
226 Ok(())
227 }
228
229 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
233 let program = &mut self.programs[me];
234 program.active = true;
235 (program.base, !program.initialized || initial)
236 }
237
238 pub fn initialized(&mut self, me: usize) {
240 self.programs[me].initialized = true;
241 self.programs[me].altered.clear();
242 }
243
244 pub fn find(&self, name: &str) -> Option<usize> {
245 self.names.get(&name.to_ascii_uppercase()).copied()
246 }
247
248 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
250 let name = name.to_ascii_uppercase();
251 if let Some(i) = self.find(&name) {
252 return Ok(i);
253 }
254 let loaded = self.library.program(&name)?;
255 self.notify(Event::Load { program: &name, source: loaded.source.as_deref() });
256 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
257 self.programs[index].source = loaded.source;
258 Ok(index)
259 }
260
261 pub const fn observed(&self) -> bool {
262 self.observer.is_some()
263 }
264
265 pub fn notify(&mut self, event: Event<'_>) {
266 if let Some(observer) = self.observer.as_mut() {
267 observer(event);
268 }
269 }
270
271 pub fn close_all(&mut self) -> Result<(), String> {
273 for program in &mut self.programs {
274 for f in program.files.iter_mut().filter_map(Option::take) {
275 f.close().map_err(|e| format!("closing a file of {}: {e}", program.name))?;
276 }
277 }
278 Ok(())
279 }
280
281 pub fn return_code(&self) -> i16 {
282 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
283 }
284
285 pub fn now(&self) -> (i64, u32) {
287 match self.clock {
288 Clock::Fixed(s, h) => (s, h),
289 Clock::System => {
290 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
291 (d.as_secs() as i64, d.subsec_millis() / 10)
292 }
293 }
294 }
295}