1use crate::oo::ClassCode;
5use crate::unit::{LoadError, LoadedProgram, Loader, RunUnit};
6use crate::Compiled;
7use rt::unit::FoundClass;
8use std::path::{Path, PathBuf};
9use std::rc::Rc;
10use syntax::ast::Program;
11use syntax::copy;
12
13#[derive(Clone, Debug, Default)]
16pub struct Library {
17 pub programs: Vec<Program>,
20 pub dirs: Vec<PathBuf>,
21 pub copy: copy::Libraries,
22 pub flags: Vec<String>,
23 pub trace_statements: Option<rt::unit::StatementFilter>,
25 pub trace_input: bool,
27 pub statement_limit: Option<u64>,
29 pub screen: Option<std::rc::Rc<std::cell::RefCell<rt::crt::Crt>>>,
32 pub environment: std::collections::BTreeMap<String, String>,
34 pub time_limit: Option<u64>,
36 pub storage_limit: Option<u64>,
38 pub program_ids: Option<Vec<(String, PathBuf)>>,
41}
42
43const SOURCE_EXTENSIONS: &[&str] = &["cbl", "CBL", "cob", "COB"];
45
46impl Library {
47 fn search(&mut self, name: &str) -> Result<(Program, PathBuf), LoadError> {
50 let candidates = [name.to_owned(), name.to_ascii_lowercase()];
51 let member = self
52 .dirs
53 .iter()
54 .flat_map(|d| candidates.iter().flat_map(move |n| std::iter::once(String::new()).chain(SOURCE_EXTENSIONS.iter().map(|e| format!(".{e}"))).map(move |e| d.join(format!("{n}{e}")))))
55 .find(|p| p.is_file());
56 if let Some(path) = member {
57 let mut programs = self.read(name, &path)?;
58 let wanted = programs.iter().position(|p| loads_as(p, name)).or_else(|| programs.iter().position(|p| !p.is_prototype())).unwrap_or(0);
59 let found = programs.remove(wanted);
60 self.add_read(&path, programs);
61 return Ok((found, path));
62 }
63 for path in self.holding(name) {
64 let mut programs = self.read(name, &path)?;
65 if let Some(wanted) = programs.iter().position(|p| loads_as(p, name)) {
66 let found = programs.remove(wanted);
67 self.add_read(&path, programs);
68 return Ok((found, path));
69 }
70 }
71 Err(LoadError::NotFound)
72 }
73
74 fn read(&self, name: &str, path: &Path) -> Result<Vec<Program>, LoadError> {
75 let text = std::fs::read(path).map(|b| copy::decode(&b)).map_err(|e| LoadError::Compile(format!("{}: {e}", path.display())))?;
76 syntax::parse_all_with(&text, &self.copy.with_program(path)).map_err(|e| LoadError::Compile(format!("{name} does not compile: {}", e.place(&path.display().to_string()))))
77 }
78
79 fn holding(&mut self, name: &str) -> Vec<PathBuf> {
82 let (dirs, compliance) = (&self.dirs, self.copy.compliance());
83 let index = self.program_ids.get_or_insert_with(|| {
84 let mut index = Vec::new();
85 for dir in dirs {
86 let Ok(entries) = std::fs::read_dir(dir) else { continue };
87 let mut files: Vec<PathBuf> = entries.filter_map(|e| e.ok().map(|e| e.path())).filter(|p| p.is_file() && p.extension().is_some_and(|e| SOURCE_EXTENSIONS.iter().any(|x| e == *x))).collect();
88 files.sort();
89 for file in files {
90 let Ok(bytes) = std::fs::read(&file) else { continue };
91 index.extend(syntax::program_ids(©::decode(&bytes), compliance).into_iter().map(|id| (id, file.clone())));
92 }
93 }
94 index
95 });
96 let mut paths: Vec<PathBuf> = index.iter().filter(|(id, _)| id.eq_ignore_ascii_case(name)).map(|(_, path)| path.clone()).collect();
97 paths.dedup();
98 paths
99 }
100
101 pub fn find(&mut self, name: &str) -> Option<&Program> {
104 if !rt::module::member_name(name) {
105 return None;
106 }
107 if !self.programs.iter().any(|p| loads_as(p, name)) {
108 let (program, path) = self.search(name).ok()?;
109 self.add_read(&path, vec![program]);
110 }
111 self.programs.iter().find(|p| loads_as(p, name))
112 }
113
114 pub fn add_read(&mut self, path: &Path, programs: Vec<Program>) {
116 let shown = path.display().to_string();
117 self.programs.extend(programs.into_iter().map(|mut p| {
118 if let Some(own) = p.sources.first_mut() {
119 own.clone_from(&shown);
120 }
121 p
122 }));
123 }
124}
125
126pub(crate) fn loads_as(program: &Program, name: &str) -> bool {
129 !program.is_prototype() && program.load_name().eq_ignore_ascii_case(name)
130}
131
132impl Loader<Rc<Compiled>> for Library {
133 fn program(&mut self, name: &str) -> Result<LoadedProgram<Rc<Compiled>>, LoadError> {
134 if !rt::module::member_name(name) {
135 return Err(LoadError::NotFound);
136 }
137 let (program, source) = match self.programs.iter().position(|p| loads_as(p, name)) {
138 Some(i) => {
139 let program = self.programs.remove(i);
140 let source = program.sources.first().filter(|s| !s.is_empty()).map(PathBuf::from);
141 (program, source)
142 }
143 None => self.search(name).map(|(p, path)| (p, Some(path)))?,
144 };
145 let compiled = crate::compile(program, &self.flags).map_err(|errors| {
146 let first = syntax::most_severe(&errors).map(|e| e.place(name)).unwrap_or_default();
147 LoadError::Compile(format!("{name} does not compile: {first}"))
148 })?;
149 let compiled = Rc::new(compiled);
150 let (files, size) = Self::shape(&compiled);
151 Ok(LoadedProgram { name: compiled.program.load_name().to_ascii_uppercase(), files, size, source, compiled, recorded: Vec::new() })
152 }
153
154 fn holder(&self, entry: &str) -> Option<String> {
155 self.programs.iter().find(|p| crate::entry_points(p).iter().any(|e| e.name == entry)).map(|p| p.id.to_ascii_uppercase())
156 }
157
158 fn entry(program: &Rc<Compiled>, name: &str) -> Option<usize> {
159 program.entries.iter().position(|e| e.name == name)
160 }
161
162 fn shape(program: &Rc<Compiled>) -> (usize, usize) {
163 (program.program.files.len(), program.layout.size as usize)
164 }
165
166 fn nested(program: &Rc<Compiled>) -> &[String] {
167 &program.program.nested
168 }
169
170 fn facts(program: &Rc<Compiled>) -> numeric::governs::Facts {
171 compile::constructs::of(program)
172 }
173
174 fn source(program: &Rc<Compiled>, file: usize) -> Option<String> {
175 program.program.sources.get(file).cloned()
176 }
177
178 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode>>>, String> {
179 let Some((program, path)) = crate::oo::find_class(self, external)? else { return Ok(None) };
180 let at = crate::compile_time().map_err(|m| format!("class {external} does not compile: {m}"))?;
181 let (code, _) = crate::oo::class_code(&program, &self.flags, at).map_err(|errors| {
182 let first = syntax::most_severe(&errors).map(|e| e.place(external)).unwrap_or_default();
183 format!("class {external} does not compile: {first}")
184 })?;
185 let mut sources = program.sources;
186 if let (Some(own), Some(path)) = (sources.first_mut(), path) {
187 *own = path;
188 }
189 Ok(Some(FoundClass { code: Rc::new(code), sources }))
190 }
191
192 fn mapset(&mut self, name: &str) -> Option<Result<rt::bms::Mapset, String>> {
193 syntax::bms::find_mapset(&self.copy, name).map(|found| found.map_err(|e| e.message))
194 }
195}
196
197pub trait AddProgram {
199 fn add(&mut self, compiled: Option<Rc<Compiled>>, program: &Program, size: usize) -> usize;
200}
201
202impl AddProgram for RunUnit<'_> {
203 fn add(&mut self, compiled: Option<Rc<Compiled>>, program: &Program, size: usize) -> usize {
204 self.add_named(compiled, program.id.to_ascii_uppercase(), program.files.len(), size)
205 }
206}