use crate::files::{Dds, Open};
use crate::Compiled;
use std::collections::HashMap;
use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use syntax::ast::{OpenMode, Program};
use syntax::copy;
pub const ADDRESS_BASE: u32 = 0x0001_0000;
pub const RETURN_CODE: usize = 0;
const RESERVED: usize = 8;
const ALIGNMENT: usize = 8;
pub struct Loaded {
pub compiled: Option<Rc<Compiled>>,
pub name: String,
pub base: usize,
pub files: Vec<Option<Open>>,
pub initialized: bool,
pub active: bool,
pub entry: Option<usize>,
pub altered: Vec<Option<usize>>,
}
#[derive(Clone, Debug, Default)]
pub struct Library {
pub programs: Vec<Program>,
pub dirs: Vec<PathBuf>,
pub copy: copy::Libraries,
pub flags: Vec<String>,
}
pub enum LoadError {
NotFound,
Compile(String),
}
#[derive(Clone, Copy, Debug)]
pub enum Clock {
System,
Fixed(i64, u32),
}
pub enum Event<'a> {
Open { dd: &'a str, mode: OpenMode, path: &'a Path },
Close { dd: &'a str, path: &'a Path },
Load { program: &'a str, source: Option<&'a Path> },
}
pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
pub const MAX_DEPTH: usize = 100;
pub struct RunUnit<'w> {
pub mem: Vec<u8>,
pub depth: usize,
pub programs: Vec<Loaded>,
names: HashMap<String, usize>,
pub(crate) library: Library,
pub dds: Dds,
pub sysin: Option<Box<dyn BufRead + 'w>>,
pub clock: Clock,
pub out: &'w mut dyn Write,
pub err: &'w mut dyn Write,
pub cics: Option<crate::cics::Task>,
pub eib: usize,
pub cics_files: HashMap<String, Open>,
pub sql: Option<crate::sql::Session<'w>>,
pub le: crate::le::State,
pub oo: crate::oo::Objects,
pub observer: Option<Observer<'w>>,
pub random: Option<u32>,
}
fn member_name(name: &str) -> bool {
!name.is_empty() && name.len() <= 30 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '#' || c == '$')
}
impl<'w> RunUnit<'w> {
pub fn new(library: Library, dds: Dds, sysin: Option<Box<dyn BufRead + 'w>>, clock: Clock, out: &'w mut dyn Write, err: &'w mut dyn Write) -> Self {
Self {
mem: vec![0; RESERVED],
depth: 0,
programs: Vec::new(),
names: HashMap::new(),
library,
dds,
sysin,
clock,
out,
err,
cics: None,
eib: 0,
cics_files: HashMap::new(),
sql: None,
le: crate::le::State::default(),
oo: Default::default(),
observer: None,
random: None,
}
}
fn allocate(&mut self, size: usize) -> usize {
let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
self.mem.resize(base + size, 0);
base
}
pub fn add(&mut self, compiled: Option<Rc<Compiled>>, program: &Program, size: usize) -> usize {
self.add_named(compiled, program.id.to_ascii_uppercase(), program.files.len(), size)
}
fn add_named(&mut self, compiled: Option<Rc<Compiled>>, name: String, files: usize, size: usize) -> usize {
let base = self.allocate(size);
let index = self.programs.len();
self.names.insert(name.clone(), index);
self.programs.push(Loaded { compiled, name, base, files: (0..files).map(|_| None).collect(), initialized: false, active: false, entry: None, altered: Vec::new() });
index
}
pub fn load_entry(&mut self, name: &str, dynamic: bool) -> Result<(usize, Option<usize>), LoadError> {
let name = name.to_ascii_uppercase();
if let Some(i) = self.find(&name) {
return Ok((i, self.programs[i].entry));
}
let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| c.entries.iter().any(|e| e.name == name))) {
Some(i) => i,
None => {
let holder = self.library.programs.iter().find(|p| crate::entry_points(p).iter().any(|e| e.name == name)).map(|p| p.id.to_ascii_uppercase());
self.load(holder.as_deref().unwrap_or(&name))?
}
};
let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
let Some(entry) = compiled.entries.iter().position(|e| e.name == name) else { return Ok((index, None)) };
if !dynamic {
return Ok((index, Some(entry)));
}
let (files, size) = (compiled.program.files.len(), compiled.layout.size as usize);
let copy = self.add_named(Some(compiled), name, files, size);
self.programs[copy].entry = Some(entry);
Ok((copy, Some(entry)))
}
pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
let at = self.allocate(bytes.len());
self.mem[at..at + bytes.len()].copy_from_slice(bytes);
at
}
pub fn release_temporaries(&mut self, mark: usize) {
if self.programs.iter().all(|p| p.base < mark) && self.le.heap_end() <= mark {
self.mem.truncate(mark.max(RESERVED));
}
}
pub fn find(&self, name: &str) -> Option<usize> {
self.names.get(&name.to_ascii_uppercase()).copied()
}
pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
let name = name.to_ascii_uppercase();
if let Some(i) = self.find(&name) {
return Ok(i);
}
if !member_name(&name) {
return Err(LoadError::NotFound);
}
let (program, source) = match self.library.programs.iter().position(|p| p.id.eq_ignore_ascii_case(&name)) {
Some(i) => (self.library.programs.remove(i), None),
None => self.search_libraries(&name).map(|(p, path)| (p, Some(path)))?,
};
let compiled = crate::compile(program, &self.library.flags).map_err(|errors| {
let first = syntax::most_severe(&errors).map(|e| e.place(&name)).unwrap_or_default();
LoadError::Compile(format!("{name} does not compile: {first}"))
})?;
self.notify(Event::Load { program: &name, source: source.as_deref() });
let compiled = Rc::new(compiled);
let size = compiled.layout.size as usize;
let program = compiled.program.clone();
Ok(self.add(Some(compiled), &program, size))
}
pub(crate) fn notify(&mut self, event: Event<'_>) {
if let Some(observer) = self.observer.as_mut() {
observer(event);
}
}
fn search_libraries(&mut self, name: &str) -> Result<(Program, PathBuf), LoadError> {
let candidates = [name.to_owned(), name.to_ascii_lowercase()];
let path = self
.library
.dirs
.iter()
.flat_map(|d| candidates.iter().flat_map(move |n| ["", ".cbl", ".CBL", ".cob", ".COB"].iter().map(move |e| d.join(format!("{n}{e}")))))
.find(|p| p.is_file())
.ok_or(LoadError::NotFound)?;
let text = std::fs::read(&path).map(|b| copy::decode(&b)).map_err(|e| LoadError::Compile(format!("{}: {e}", path.display())))?;
let mut programs = syntax::parse_all_with(&text, &self.library.copy.with_program(&path))
.map_err(|e| LoadError::Compile(format!("{name} does not compile: {}", e.place(&path.display().to_string()))))?;
let first = programs.remove(0);
self.library.programs.extend(programs);
Ok((first, path))
}
pub fn close_all(&mut self) -> Result<(), String> {
for program in &mut self.programs {
for f in program.files.iter_mut().filter_map(Option::take) {
f.close().map_err(|e| format!("closing a file of {}: {e}", program.name))?;
}
}
Ok(())
}
pub fn copy_libraries(&self) -> ©::Libraries {
&self.library.copy
}
pub fn return_code(&self) -> i16 {
i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
}
pub fn now(&self) -> (i64, u32) {
match self.clock {
Clock::Fixed(s, h) => (s, h),
Clock::System => {
let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
(d.as_secs() as i64, d.subsec_millis() / 10)
}
}
}
}