1use crate::abend::Abend;
9use crate::files::{Dds, Open};
10use crate::vocab::{OpenMode, Pos};
11use std::collections::{HashMap, HashSet};
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 dynamic: bool,
35 pub entry: Option<usize>,
38 pub altered: Vec<Option<usize>>,
40 pub source: Option<PathBuf>,
42}
43
44pub enum LoadError {
45 NotFound,
46 Compile(String),
47}
48
49pub struct LoadedProgram<H> {
51 pub compiled: H,
52 pub name: String,
54 pub files: usize,
55 pub size: usize,
56 pub source: Option<PathBuf>,
58}
59
60pub struct FoundClass<C> {
63 pub code: C,
64 pub sources: Vec<String>,
65}
66
67pub trait Loader<H> {
69 type Class;
71
72 fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
74
75 fn holder(&self, entry: &str) -> Option<String>;
77
78 fn entry(program: &H, name: &str) -> Option<usize>;
80
81 fn shape(program: &H) -> (usize, usize);
83
84 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Self::Class>>, String>;
86}
87
88#[derive(Clone, Copy, Debug)]
89pub enum Clock {
90 System,
91 Fixed(i64, u32),
93}
94
95pub enum Event<'a> {
99 Open { dd: &'a str, mode: OpenMode, path: &'a Path },
100 Close { dd: &'a str, path: &'a Path },
101 Load { program: &'a str, source: Option<&'a Path> },
102 Paragraph { program: &'a str, name: &'a str, index: usize },
104 Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str },
107 Statement { file: &'a str, line: u32 },
109}
110
111#[derive(Clone, Debug, PartialEq, Eq)]
114pub enum StatementFilter {
115 All,
116 Lines(HashSet<u32>),
117}
118
119pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
120
121pub const MAX_DEPTH: usize = 100;
123
124#[derive(Default)]
128pub struct Externals {
129 storage: HashMap<(bool, String), (usize, usize)>,
132 files: Vec<Option<Open>>,
133 locked: Vec<bool>,
135 file_names: HashMap<String, usize>,
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub enum Connector {
141 External(usize),
143 Program(usize, usize),
145}
146
147pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
150
151pub struct RunUnit<'w, H, L: Loader<H>> {
152 pub mem: Vec<u8>,
153 pub depth: usize,
155 pub programs: Vec<Loaded<H>>,
156 names: HashMap<String, usize>,
157 pub library: L,
158 pub dds: Dds,
159 pub sysin: Option<Box<dyn BufRead + 'w>>,
160 pub clock: Clock,
161 pub out: &'w mut dyn Write,
162 pub err: &'w mut dyn Write,
163 pub cics: Option<crate::cics::Task>,
165 pub eib: usize,
166 pub cics_files: HashMap<String, Open>,
167 pub sql: Option<crate::sql::Session<'w>>,
169 pub le: crate::le::State,
171 pub oo: crate::oo::Objects<L::Class>,
173 pub observer: Option<Observer<'w>>,
175 pub random: Option<u32>,
177 externals: Externals,
178 connectors: HashMap<(usize, usize), Connector>,
180 pub entries: Vec<crate::set::Entry>,
182 pub statements: Option<StatementFilter>,
184}
185
186impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
187 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 {
188 Self {
189 mem: vec![0; RESERVED],
190 depth: 0,
191 programs: Vec::new(),
192 names: HashMap::new(),
193 library,
194 dds,
195 sysin,
196 clock,
197 out,
198 err,
199 cics: None,
200 eib: 0,
201 cics_files: HashMap::new(),
202 sql: None,
203 le: crate::le::State::default(),
204 oo: Default::default(),
205 observer: None,
206 random: None,
207 externals: Externals::default(),
208 connectors: HashMap::new(),
209 entries: Vec::new(),
210 statements: None,
211 }
212 }
213
214 fn allocate(&mut self, size: usize) -> usize {
215 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
216 self.mem.resize(base + size, 0);
217 base
218 }
219
220 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
222 let base = self.allocate(size);
223 let index = self.programs.len();
224 self.names.insert(name.clone(), index);
225 self.programs.push(Loaded { compiled, name, base, files: (0..files).map(|_| None).collect(), locked: vec![false; files], initialized: false, active: false, dynamic: false, entry: None, altered: Vec::new(), source: None });
226 index
227 }
228
229 pub fn load_entry(&mut self, name: &str, dynamic: bool) -> Result<(usize, Option<usize>), LoadError> {
233 let name = name.to_ascii_uppercase();
234 if let Some(i) = self.find(&name) {
235 return Ok((i, self.programs[i].entry));
236 }
237 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
238 Some(i) => i,
239 None => {
240 let holder = self.library.holder(&name);
241 self.load(holder.as_deref().unwrap_or(&name))?
242 }
243 };
244 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
245 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
246 if !dynamic {
247 return Ok((index, Some(entry)));
248 }
249 let (files, size) = L::shape(&compiled);
250 let copy = self.add_named(Some(compiled), name, files, size);
251 self.programs[copy].entry = Some(entry);
252 self.programs[copy].source = self.programs[index].source.clone();
253 Ok((copy, Some(entry)))
254 }
255
256 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
258 let at = self.allocate(bytes.len());
259 self.mem[at..at + bytes.len()].copy_from_slice(bytes);
260 at
261 }
262
263 pub fn release_temporaries(&mut self, mark: usize) {
266 let external = self.externals.storage.values().all(|&(at, _)| at < mark);
267 if self.programs.iter().all(|p| p.base < mark) && self.le.heap_end() <= mark && external {
268 self.mem.truncate(mark.max(RESERVED));
269 }
270 }
271
272 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
274 if self.depth >= MAX_DEPTH {
275 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
276 }
277 self.depth += 1;
278 Ok(())
279 }
280
281 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
285 let program = &mut self.programs[me];
286 program.active = true;
287 (program.base, !program.initialized || initial)
288 }
289
290 pub fn initialized(&mut self, me: usize) {
292 self.programs[me].initialized = true;
293 self.programs[me].altered.clear();
294 }
295
296 pub fn find(&self, name: &str) -> Option<usize> {
297 self.names.get(&name.to_ascii_uppercase()).copied()
298 }
299
300 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
302 let name = name.to_ascii_uppercase();
303 if let Some(i) = self.find(&name) {
304 return Ok(i);
305 }
306 let loaded = self.library.program(&name)?;
307 self.notify(Event::Load { program: &name, source: loaded.source.as_deref() });
308 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
309 self.programs[index].source = loaded.source;
310 Ok(index)
311 }
312
313 pub const fn observed(&self) -> bool {
314 self.observer.is_some()
315 }
316
317 pub fn traces(&self, line: u32) -> bool {
319 match &self.statements {
320 None => false,
321 Some(StatementFilter::All) => self.observer.is_some(),
322 Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
323 }
324 }
325
326 pub fn notify(&mut self, event: Event<'_>) {
327 if let Some(observer) = self.observer.as_mut() {
328 observer(event);
329 }
330 }
331
332 pub fn close_all(&mut self) -> Result<(), String> {
334 for program in &mut self.programs {
335 for f in program.files.iter_mut().filter_map(Option::take) {
336 f.close().map_err(|e| format!("closing a file of {}: {e}", program.name))?;
337 }
338 }
339 for (name, &k) in &self.externals.file_names {
340 if let Some(f) = self.externals.files[k].take() {
341 f.close().map_err(|e| format!("closing EXTERNAL file {name}: {e}"))?;
342 }
343 }
344 Ok(())
345 }
346
347 pub fn external(&mut self, name: &str, file: bool, size: usize) -> Result<usize, String> {
351 let key = (file, name.to_owned());
352 if let Some(&(at, had)) = self.externals.storage.get(&key) {
353 return if had == size {
354 Ok(at)
355 } else {
356 let what = if file { "the record area of EXTERNAL file" } else { "EXTERNAL record" };
357 Err(format!("{what} {name} has {had} bytes in the run unit, and this program describes {size}"))
358 };
359 }
360 let at = self.allocate(size);
361 self.externals.storage.insert(key, (at, size));
362 Ok(at)
363 }
364
365 pub fn external_file(&mut self, name: &str) -> Connector {
367 let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
368 let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
369 files.push(None);
370 locked.push(false);
371 files.len() - 1
372 });
373 Connector::External(k)
374 }
375
376 pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
378 self.connectors.insert((me, k), to);
379 }
380
381 fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
382 let mut to = None;
383 while let Some(&c) = self.connectors.get(&(me, k)) {
384 to = Some(c);
385 match c {
386 Connector::External(_) => break,
387 Connector::Program(p, j) => (me, k) = (p, j),
388 }
389 }
390 to
391 }
392
393 pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
395 match self.connector(me, k) {
396 None => &mut self.programs[me].files[k],
397 Some(Connector::External(e)) => &mut self.externals.files[e],
398 Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
399 }
400 }
401
402 pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
404 match self.connector(me, k) {
405 None => &mut self.programs[me].locked[k],
406 Some(Connector::External(e)) => &mut self.externals.locked[e],
407 Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
408 }
409 }
410
411 pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
412 match self.connector(me, k) {
413 None => &self.programs[me].files[k],
414 Some(Connector::External(e)) => &self.externals.files[e],
415 Some(Connector::Program(p, j)) => &self.programs[p].files[j],
416 }
417 }
418
419 pub fn return_code(&self) -> i16 {
420 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
421 }
422
423 pub fn now(&self) -> (i64, u32) {
425 match self.clock {
426 Clock::Fixed(s, h) => (s, h),
427 Clock::System => {
428 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
429 (d.as_secs() as i64, d.subsec_millis() / 10)
430 }
431 }
432 }
433}