Skip to main content

ironwork_exec/
loader.rs

1//! The interpreter's program loader: the programs CALL can reach, read from source and compiled
2//! the first time one is called.
3
4use crate::oo::ClassCode;
5use crate::unit::{LoadError, LoadedProgram, Loader, RunUnit};
6use crate::Compiled;
7use rt::unit::FoundClass;
8use std::path::PathBuf;
9use std::rc::Rc;
10use syntax::ast::Program;
11use syntax::copy;
12
13/// Where CALL finds programs: the other programs of the first program's source, then program
14/// libraries searched by member name.
15#[derive(Clone, Debug, Default)]
16pub struct Library {
17    pub programs: Vec<Program>,
18    pub dirs: Vec<PathBuf>,
19    pub copy: copy::Libraries,
20    pub flags: Vec<String>,
21    /// The statements whose start the run unit tells its observer of (`RunUnit::statements`).
22    pub trace_statements: Option<rt::unit::StatementFilter>,
23}
24
25fn member_name(name: &str) -> bool {
26    !name.is_empty() && name.len() <= 30 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '@' || c == '#' || c == '$')
27}
28
29impl Library {
30    fn search(&mut self, name: &str) -> Result<(Program, PathBuf), LoadError> {
31        let candidates = [name.to_owned(), name.to_ascii_lowercase()];
32        let path = self
33            .dirs
34            .iter()
35            .flat_map(|d| candidates.iter().flat_map(move |n| ["", ".cbl", ".CBL", ".cob", ".COB"].iter().map(move |e| d.join(format!("{n}{e}")))))
36            .find(|p| p.is_file())
37            .ok_or(LoadError::NotFound)?;
38        let text = std::fs::read(&path).map(|b| copy::decode(&b)).map_err(|e| LoadError::Compile(format!("{}: {e}", path.display())))?;
39        let mut programs = syntax::parse_all_with(&text, &self.copy.with_program(&path))
40            .map_err(|e| LoadError::Compile(format!("{name} does not compile: {}", e.place(&path.display().to_string()))))?;
41        let first = programs.remove(0);
42        self.programs.extend(programs);
43        Ok((first, path))
44    }
45}
46
47impl Loader<Rc<Compiled>> for Library {
48    type Class = Rc<ClassCode>;
49
50    fn program(&mut self, name: &str) -> Result<LoadedProgram<Rc<Compiled>>, LoadError> {
51        if !member_name(name) {
52            return Err(LoadError::NotFound);
53        }
54        let (program, source) = match self.programs.iter().position(|p| p.id.eq_ignore_ascii_case(name)) {
55            Some(i) => (self.programs.remove(i), None),
56            None => self.search(name).map(|(p, path)| (p, Some(path)))?,
57        };
58        let compiled = crate::compile(program, &self.flags).map_err(|errors| {
59            let first = syntax::most_severe(&errors).map(|e| e.place(name)).unwrap_or_default();
60            LoadError::Compile(format!("{name} does not compile: {first}"))
61        })?;
62        let compiled = Rc::new(compiled);
63        let (files, size) = Self::shape(&compiled);
64        Ok(LoadedProgram { name: compiled.program.id.to_ascii_uppercase(), files, size, source, compiled })
65    }
66
67    fn holder(&self, entry: &str) -> Option<String> {
68        self.programs.iter().find(|p| crate::entry_points(p).iter().any(|e| e.name == entry)).map(|p| p.id.to_ascii_uppercase())
69    }
70
71    fn entry(program: &Rc<Compiled>, name: &str) -> Option<usize> {
72        program.entries.iter().position(|e| e.name == name)
73    }
74
75    fn shape(program: &Rc<Compiled>) -> (usize, usize) {
76        (program.program.files.len(), program.layout.size as usize)
77    }
78
79    fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode>>>, String> {
80        let Some((program, path)) = crate::oo::find_class(self, external)? else { return Ok(None) };
81        let at = crate::compile_time().map_err(|m| format!("class {external} does not compile: {m}"))?;
82        let (code, _) = crate::oo::class_code(&program, &self.flags, at).map_err(|errors| {
83            let first = syntax::most_severe(&errors).map(|e| e.place(external)).unwrap_or_default();
84            format!("class {external} does not compile: {first}")
85        })?;
86        let mut sources = program.sources;
87        if let (Some(own), Some(path)) = (sources.first_mut(), path) {
88            *own = path;
89        }
90        Ok(Some(FoundClass { code: Rc::new(code), sources }))
91    }
92}
93
94/// Adding a program to the interpreter's run unit by its source's PROGRAM-ID and FILE-CONTROL.
95pub trait AddProgram {
96    fn add(&mut self, compiled: Option<Rc<Compiled>>, program: &Program, size: usize) -> usize;
97}
98
99impl AddProgram for RunUnit<'_> {
100    fn add(&mut self, compiled: Option<Rc<Compiled>>, program: &Program, size: usize) -> usize {
101        self.add_named(compiled, program.id.to_ascii_uppercase(), program.files.len(), size)
102    }
103}