1use super::keys::{Collating, Format, KeyValue, decimal, order};
7use crate::abend::{Abend, AbendCode, Ending, Signal};
8use crate::fileio::{self, File, Files};
9use crate::files::{FileStatus, Format as Records, Move, Record};
10use crate::host::{self, Host};
11use crate::lir::{FileSort, Organization, SortIo, SortKeys};
12use crate::storage::{Kind, Loc, Val};
13use crate::store::{self, ProgramFacts};
14use crate::vocab::{OpenMode, Pos};
15use numeric::precision::{Fixed, Places};
16use numeric::{FastsrtAdvPrint, SortKeys as KeyReading, TruncCheck, assumptions};
17use std::io::Write;
18use std::rc::Rc;
19use zarch::ebcdic;
20
21type R<T> = Result<T, Abend>;
22
23#[derive(Clone, Debug)]
25pub struct ItemKey {
26 pub ascending: bool,
27 pub offset: usize,
28 pub len: usize,
29 pub kind: Kind,
30 pub item: usize,
31 pub collating: Collating,
33}
34
35pub fn item_keys(keys: &SortKeys) -> Vec<ItemKey> {
37 let collating = keys.collating.as_ref().map_or(Collating::Ebcdic, |p| Collating::Positions(Rc::new(**p)));
38 keys.keys
39 .iter()
40 .map(|k| ItemKey {
41 ascending: k.ascending,
42 offset: k.offset as usize,
43 len: k.len as usize,
44 kind: k.kind,
45 item: k.item as usize,
46 collating: if k.collated { collating.clone() } else { Collating::Ebcdic },
47 })
48 .collect()
49}
50
51#[derive(Clone, Copy)]
53pub struct SortFile<'a, P, X> {
54 pub file: File<'a, P, X>,
55 pub fixed: bool,
57 pub status_name: Option<&'a str>,
59 pub relative_name: Option<&'a str>,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum Procedure {
65 SortInput,
66 SortOutput,
67 MergeOutput,
68}
69
70impl Procedure {
71 pub fn name(self) -> &'static str {
72 match self {
73 Procedure::SortInput => "SORT INPUT",
74 Procedure::SortOutput => "SORT OUTPUT",
75 Procedure::MergeOutput => "MERGE OUTPUT",
76 }
77 }
78}
79
80pub trait SortHost<'a, P: Copy, X: Copy>: Files<P, X> {
84 type Register: Copy;
85 type Procedure: Copy;
86 type File;
87 type Keys;
88 fn locate_register(&mut self, register: Self::Register) -> R<Loc>;
89 fn register_value(&mut self, register: Self::Register, pos: Pos) -> R<i64>;
90 fn file_index(&self, file: &Self::File, pos: Pos) -> R<usize>;
91 fn keys(&mut self, keys: &Self::Keys, pos: Pos) -> R<Vec<ItemKey>>;
92 fn sort_file(&self, k: usize) -> SortFile<'a, P, X>;
93 fn active(&mut self) -> &mut Option<Active>;
94 fn open(&mut self, k: usize, mode: OpenMode, pos: Pos) -> R<()>;
96 fn close(&mut self, k: usize, pos: Pos) -> R<bool>;
98 fn put(&mut self, k: usize, loc: Loc, pos: Pos) -> R<bool>;
100 fn fail(&mut self, k: usize, status: FileStatus, mode: Option<OpenMode>, open_or_close: bool, message: String, pos: Pos) -> R<()>;
103 fn has_error_procedure(&self, k: usize, mode: OpenMode) -> bool;
105 fn run_procedure(&mut self, procedure: Self::Procedure, kind: Procedure, pos: Pos) -> R<Option<Ending>>;
107 fn err(&mut self) -> &mut dyn Write;
108}
109
110pub struct Active {
112 sd: usize,
113 keys: Vec<ItemKey>,
114 phase: Phase,
115}
116
117enum Phase {
118 Input(Vec<Entry>),
119 Output { records: Vec<Vec<u8>>, next: usize },
120}
121
122struct Entry {
123 record: Vec<u8>,
124 keys: Vec<KeyValue>,
125}
126
127enum Input {
128 Record(Vec<u8>),
129 End,
130 Failed(String),
131}
132
133struct Fastsrt {
135 input: bool,
136 file: usize,
137 refusal: Option<String>,
138}
139
140impl Fastsrt {
141 fn dfsort(&self, fastsrt: bool) -> bool {
142 fastsrt && self.refusal.is_none()
143 }
144}
145
146fn sortin(plan: &[Fastsrt]) -> Option<&Fastsrt> {
148 plan.iter().find(|f| f.input && f.refusal.is_none())
149}
150
151type Outcome = Result<(), String>;
153
154type Ended = Result<Option<Ending>, String>;
157
158fn stop(why: String, pos: Pos) -> Abend {
159 Abend { code: AbendCode::Signal(Signal::SortStopped), message: why, pos, file: None }
160}
161
162fn stopped_by_program() -> String {
163 format!("SORT-RETURN was set to 16 (see {})", assumptions::SORT_RETURN_STOPS)
164}
165
166fn ascending(keys: &[ItemKey]) -> Vec<bool> {
167 keys.iter().map(|k| k.ascending).collect()
168}
169
170fn set_register<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, register: H::Register, value: i64, pos: Pos) -> R<()> {
171 let dest = x.locate_register(register)?;
172 x.store_fixed(dest, &Fixed::new(value as i128, Places::new(19, 0)), pos)
173}
174
175pub fn key_values<P: Copy>(x: &mut impl Host<P>, base: usize, keys: &[ItemKey], dfsort: bool, pos: Pos) -> R<Vec<KeyValue>> {
179 let mut out = Vec::with_capacity(keys.len());
180 for k in keys {
181 let loc = Loc { offset: base + k.offset, len: k.len, kind: k.kind, item: k.item };
182 let bytes = store::bytes(x.mem(), loc);
183 let value = match (dfsort.then(|| Format::of_decimal(k.kind).and_then(|f| decimal(bytes, f))).flatten(), &k.collating) {
184 (Some((negative, digits)), _) => KeyValue::Decimal { negative, digits },
185 (None, Collating::Positions(_)) => KeyValue::Collated(k.collating.collate(bytes)),
186 (None, Collating::Ebcdic) => KeyValue::Read(host::read(x, loc, pos)?),
187 };
188 out.push(value);
189 }
190 Ok(out)
191}
192
193fn refuse_control_statements<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, sort_control: H::Register, pos: Pos) -> R<()> {
195 let loc = x.locate_register(sort_control)?;
196 let page = x.facts().page();
197 let dd = page.decode(store::bytes(x.mem(), loc)).trim().to_ascii_uppercase();
198 if !dd.is_empty() && x.dd(&dd).is_some() {
199 return Err(Abend::ironwork(format!("DD {dd} holds sort control statements, which ironwork for COBOL does not read"), pos));
200 }
201 Ok(())
202}
203
204fn entry<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, sd: usize, keys: &[ItemKey], mut record: Vec<u8>, extent: usize, pos: Pos) -> R<Result<Entry, String>> {
208 let f = x.sort_file(sd);
209 let (area, size) = f.file.area;
210 if f.fixed {
211 record.resize(extent, ebcdic::SPACE);
212 } else {
213 record.truncate(extent);
214 }
215 if keys.iter().any(|k| k.offset + k.len > record.len()) {
216 return Ok(Err(format!("a record of {} bytes ends inside a key", record.len())));
217 }
218 let held = record.len().min(size);
219 x.mem()[area..area + held].copy_from_slice(&record[..held]);
220 let dfsort = x.facts().options().sort_keys == KeyReading::Dfsort;
221 let keys = key_values(x, area, keys, dfsort, pos)?;
222 Ok(Ok(Entry { record, keys }))
223}
224
225fn after_failure<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, k: usize, mode: OpenMode, why: String, sort_return: H::Register, pos: Pos) -> R<Outcome> {
229 if !x.has_error_procedure(k, mode) {
230 return Ok(Err(why));
231 }
232 if x.register_value(sort_return, pos)? == 16 {
233 return Ok(Err(stopped_by_program()));
234 }
235 Ok(Ok(()))
236}
237
238fn is_open<P: Copy, X: Copy>(x: &mut impl Files<P, X>, k: usize) -> bool {
239 x.slot(k).is_some()
240}
241
242fn ended_inside(verb: &str, name: &str, ending: Ending, pos: Pos) -> Abend {
245 let how = match ending {
246 Ending::StopRun => "STOP RUN",
247 _ => "GOBACK or EXIT PROGRAM",
248 };
249 Abend { code: AbendCode::user(4038), message: format!("IGZ0012S There was an invalid attempt to end a sort or merge. ({how} in a procedure of {verb} {name})"), pos, file: None }
250}
251
252fn open_for_sort<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, k: usize, mode: OpenMode, pos: Pos) -> R<Outcome> {
254 let name = x.sort_file(k).file.name;
255 if is_open(x, k) {
256 x.fail(k, FileStatus::AlreadyOpen, Some(mode), true, format!("{name} is open, and a SORT or MERGE opens it itself"), pos)?;
257 return Ok(Err(format!("{name} is already open (file status {})", FileStatus::AlreadyOpen.as_str())));
258 }
259 x.open(k, mode, pos)?;
260 if is_open(x, k) { Ok(Ok(())) } else { Ok(Err(format!("OPEN {name} failed"))) }
261}
262
263fn close_for_sort<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, k: usize, pos: Pos) -> R<Outcome> {
264 let name = x.sort_file(k).file.name;
265 if x.close(k, pos)? { Ok(Err(format!("CLOSE {name} failed"))) } else { Ok(Ok(())) }
266}
267
268fn io_status<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, k: usize, status: FileStatus, message: String, pos: Pos) -> R<()> {
270 let mode = x.slot(k).as_ref().map(|f| f.mode);
271 x.fail(k, status, mode, false, message, pos)
272}
273
274fn next_input<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, k: usize, dfsort: bool, pos: Pos) -> R<Input> {
278 let file = x.sort_file(k).file;
279 let size = file.area.1;
280 let Some(mut f) = x.slot(k).take() else { return Ok(Input::Failed("the file is not open".into())) };
281 let text = f.format == Records::Text && !f.is_keyed();
282 let added = fileio::adds_control_byte(&file, f.format);
283 let read = match f.keyed() {
284 Some(keyed) => match keyed.step(false) {
285 Ok(Some(found)) => Ok(Record::Data(found.record)),
286 Ok(None) => Ok(Record::End),
287 Err(code) => Err((FileStatus::from(code), "there is no next record".to_owned())),
288 },
289 None => f.read(size + usize::from(added)).map_err(|e| (FileStatus::PermanentError, e.to_string())),
290 };
291 *x.slot(k) = Some(f);
292 let (record, code) = match read {
293 Err((code, message)) => {
294 io_status(x, k, code, format!("{}: {message}", file.name), pos)?;
295 return Ok(Input::Failed(format!("reading {} failed (file status {})", file.name, code.as_str())));
296 }
297 Ok(Record::End) => {
298 fileio::set_status(x, &file, FileStatus::AtEnd, pos)?;
299 return Ok(Input::End);
300 }
301 Ok(Record::Data(r)) => (r, FileStatus::Success),
302 Ok(Record::WrongLength(r)) => (r, FileStatus::SuccessWrongLength),
303 };
304 fileio::set_status(x, &file, code, pos)?;
305 if added && !dfsort {
306 return Ok(Input::Record(record.get(1..).unwrap_or_default().to_vec()));
307 }
308 if !text {
309 return Ok(Input::Record(record));
310 }
311 let page = x.facts().page();
312 let unknown = page.encode_char('?').unwrap_or(0x6F);
313 Ok(Input::Record(String::from_utf8_lossy(&record).chars().map(|c| page.encode_char(c).unwrap_or(unknown)).collect()))
314}
315
316struct Run<'k, G> {
318 sd: usize,
319 keys: &'k [ItemKey],
320 sort_return: G,
321 pos: Pos,
322}
323
324fn gather<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, run: &Run<'_, H::Register>, plan: &[Fastsrt]) -> R<Result<Vec<Vec<Entry>>, String>> {
326 let mut files: Vec<Vec<Entry>> = Vec::new();
327 for f in plan.iter().filter(|f| f.input) {
328 let read = if f.dfsort(x.facts().options().fastsrt) {
329 by_dfsort(x, f.file, |x| read_using(x, run, f.file, true))?
330 } else {
331 read_using(x, run, f.file, false)?
332 };
333 match read {
334 Ok(records) => files.push(records),
335 Err(why) => return Ok(Err(why)),
336 }
337 }
338 Ok(Ok(files))
339}
340
341fn sorted(mut entries: Vec<Entry>, ascending: &[bool]) -> Vec<Entry> {
344 entries.sort_by(|a, b| order(&a.keys, &b.keys, ascending));
345 entries
346}
347
348fn merged(files: Vec<Vec<Entry>>, ascending: &[bool]) -> Vec<Entry> {
353 let mut out = Vec::with_capacity(files.iter().map(Vec::len).sum());
354 let mut heads: Vec<std::collections::VecDeque<Entry>> = files.into_iter().map(Into::into).collect();
355 while let Some(k) = (0..heads.len()).filter_map(|k| heads[k].front().map(|e| (k, e))).min_by(|(_, a), (_, b)| order(&a.keys, &b.keys, ascending)).map(|(k, _)| k) {
356 out.extend(heads[k].pop_front());
357 }
358 out
359}
360
361fn read_using<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, run: &Run<'_, H::Register>, k: usize, dfsort: bool) -> R<Result<Vec<Entry>, String>> {
363 let (pos, sd, keys) = (run.pos, run.sd, run.keys);
364 let file = x.sort_file(k).file;
365 let name = file.name;
366 if let Err(why) = open_for_sort(x, k, OpenMode::Input, pos)? {
367 return Ok(after_failure(x, k, OpenMode::Input, why, run.sort_return, pos)?.map(|()| Vec::new()));
368 }
369 let extent = x.sort_file(sd).file.area.1 + if dfsort { control_byte(x, &file) } else { 0 };
370 let mut entries = Vec::new();
371 let mut closed = false;
372 loop {
373 let failure = match next_input(x, k, dfsort, pos)? {
374 Input::End => break,
375 Input::Failed(why) => {
376 x.close(k, pos)?;
377 if let Err(why) = after_failure(x, k, OpenMode::Input, why, run.sort_return, pos)? {
378 return Ok(Err(why));
379 }
380 closed = true;
381 break;
382 }
383 Input::Record(r) => match entry(x, sd, keys, r, extent, pos)? {
384 Ok(e) => {
385 entries.push(e);
386 continue;
387 }
388 Err(why) => format!("{name}: {why}"),
389 },
390 };
391 x.close(k, pos)?;
392 return Ok(Err(failure));
393 }
394 if !closed
395 && let Err(why) = close_for_sort(x, k, pos)?
396 && let Err(why) = after_failure(x, k, OpenMode::Input, why, run.sort_return, pos)?
397 {
398 return Ok(Err(why));
399 }
400 if dfsort && entries.is_empty() && matches!(file.organization, Organization::Indexed | Organization::Relative) {
401 return Ok(Err(format!("{name} is an empty VSAM file, which FASTSRT cannot take as input (see {})", assumptions::FASTSRT_FAILURE)));
402 }
403 Ok(Ok(entries))
404}
405
406fn scatter<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, run: &Run<'_, H::Register>, records: &[Vec<u8>], plan: &[Fastsrt]) -> R<Outcome> {
409 for f in plan.iter().filter(|f| !f.input) {
410 let written = if f.dfsort(x.facts().options().fastsrt) {
411 by_dfsort(x, f.file, |x| write_giving(x, run, records, f.file, true))?
412 } else {
413 write_giving(x, run, records, f.file, false)?
414 };
415 if written.is_err() {
416 return Ok(written);
417 }
418 }
419 Ok(Ok(()))
420}
421
422fn write_giving<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, run: &Run<'_, H::Register>, records: &[Vec<u8>], k: usize, dfsort: bool) -> R<Outcome> {
425 let pos = run.pos;
426 let file = x.sort_file(k).file;
427 let name = file.name;
428 if let Err(why) = open_for_sort(x, k, OpenMode::Output, pos)? {
429 return after_failure(x, k, OpenMode::Output, why, run.sort_return, pos);
430 }
431 let (area, size) = file.area;
432 let limit = size.min(x.sort_file(run.sd).file.area.1);
433 for record in records {
434 if dfsort && file.organization == Organization::Sequential {
435 if let Err(why) = dfsort_put(x, k, record)? {
436 x.close(k, pos)?;
437 return Ok(Err(why));
438 }
439 continue;
440 }
441 let len = record.len().min(limit);
442 x.mem()[area..area + len].copy_from_slice(&record[..len]);
443 let loc = Loc { offset: area, len, kind: Kind::Alnum { justified: false }, item: usize::MAX };
444 if x.put(k, loc, pos)? {
445 x.close(k, pos)?;
446 return after_failure(x, k, OpenMode::Output, format!("WRITE {name} failed"), run.sort_return, pos);
447 }
448 }
449 match close_for_sort(x, k, pos)? {
450 Err(why) => after_failure(x, k, OpenMode::Output, why, run.sort_return, pos),
451 closed => Ok(closed),
452 }
453}
454
455fn control_byte<P: Copy, X: Copy>(x: &impl Files<P, X>, file: &File<'_, P, X>) -> usize {
458 usize::from(fileio::adds_control_byte(file, fileio::dd_format(x, file)))
459}
460
461fn dfsort_put<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, k: usize, record: &[u8]) -> R<Outcome> {
466 let file = x.sort_file(k).file;
467 let name = file.name;
468 let Some(mut f) = x.slot(k).take() else { return Ok(Err(format!("{name} is not open"))) };
469 let lrecl = file.area.1 + control_byte(x, &file);
470 let written = match f.format {
471 Records::Text => {
472 let line = x.facts().page().decode(record).trim_end().to_owned();
473 f.print(Some(Move::Lines(1)), &line, None)
474 }
475 Records::Fixed => {
476 let mut bytes = record.to_vec();
477 bytes.resize(lrecl, 0x00);
478 f.write(&bytes)
479 }
480 Records::Variable if record.len() > lrecl => {
481 *x.slot(k) = Some(f);
482 let why = format!("a {}-byte record is longer than the largest of GIVING {name}'s data set, {lrecl} bytes: ICE217A (see {})", record.len(), assumptions::FASTSRT_RECORD_LENGTHS);
483 return Ok(Err(why));
484 }
485 Records::Variable => f.write(record),
486 };
487 *x.slot(k) = Some(f);
488 Ok(written.map_err(|e| format!("writing {name} failed: {e}")))
489}
490
491fn dfsort_refuses<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &H, sd: usize, plan: &[Fastsrt]) -> Option<String> {
495 if !x.facts().options().fastsrt || sortin(plan).is_some() {
496 return None;
497 }
498 let length = x.sort_file(sd).file.area.1;
499 plan.iter().filter(|f| !f.input && f.refusal.is_none() && fileio::dd_format(x, &x.sort_file(f.file).file) == Records::Fixed).find_map(|f| {
500 let file = x.sort_file(f.file).file;
501 let lrecl = file.area.1 + control_byte(x, &file);
502 (lrecl > length).then(|| {
503 format!("GIVING {}'s data set has {lrecl}-byte records, and with no USING file of its own DFSORT does not pad the SD's {length}-byte records: ICE043A (see {})", file.name, assumptions::FASTSRT_ADV_PRINT)
504 })
505 })
506}
507
508fn by_dfsort<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>, T>(x: &mut H, k: usize, op: impl FnOnce(&mut H) -> R<Result<T, String>>) -> R<Result<T, String>> {
512 let file = x.sort_file(k).file;
513 let mut kept = Vec::new();
514 for p in file.status.into_iter().chain(file.relative) {
515 let loc = x.locate(p, false)?;
516 kept.push((loc, store::bytes(x.mem(), loc).to_vec()));
517 }
518 let was_open = is_open(x, k);
519 let result = match op(x) {
520 Err(a) if fileio::is_unhandled_io(&a) => Ok(Err(a.message)),
521 other => other,
522 };
523 if !was_open && let Some(f) = x.slot(k).take() {
524 let _ = f.close();
525 }
526 for (loc, bytes) in kept {
527 store::write(x.mem(), loc, &bytes);
528 }
529 result
530}
531
532fn fastsrt_plan<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &H, sort: &FileSort<H::Register, H::Procedure, H::Keys, H::File>, sd: usize, pos: Pos) -> R<Vec<Fastsrt>> {
535 let mut plan = Vec::new();
536 for (input, io) in [(true, &sort.input), (false, &sort.output)] {
537 let Some(SortIo::Files(names)) = io else { continue };
538 for name in names {
539 let k = x.file_index(name, pos)?;
540 let refusal = fastsrt_refusal(x, sort.merge, sd, k, input, names.len(), &plan);
541 plan.push(Fastsrt { input, file: k, refusal });
542 }
543 }
544 Ok(plan)
545}
546
547fn fastsrt_refusal<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &H, merge: bool, sd: usize, k: usize, input: bool, count: usize, earlier: &[Fastsrt]) -> Option<String> {
548 let (this, the_sd) = (x.sort_file(k), x.sort_file(sd));
549 let decl = this.file;
550 let format = |f: &SortFile<'_, P, X>| if f.fixed { "fixed" } else { "variable" };
551 let dfsort_reads = |f: &Fastsrt| f.input && f.refusal.is_none() && (f.file == k || x.sort_file(f.file).file.assign == decl.assign);
552 let options = x.facts().options();
553 Some(if merge {
554 "it applies only to SORT".into()
555 } else if count > 1 {
556 format!("{} names more than one file", if input { "USING" } else { "GIVING" })
557 } else if decl.organization == Organization::LineSequential {
558 "it is a line-sequential file".into()
559 } else if decl.organization == Organization::Relative && !this.fixed {
560 "it is a variable-length relative file".into()
561 } else if !input && decl.linage.is_some() {
562 "its FD has LINAGE".into()
563 } else if x.has_error_procedure(k, if input { OpenMode::Input } else { OpenMode::Output }) {
564 "an EXCEPTION/ERROR procedure applies to it".into()
565 } else if decl.carriage.is_some_and(|c| !c.reserved) && options.fastsrt_adv_print == FastsrtAdvPrint::Exclude {
566 format!("it is a print file, whose records ADV makes a byte longer than its FD's {} ({})", decl.area.1, FastsrtAdvPrint::Exclude.flag())
567 } else if this.fixed != the_sd.fixed {
568 format!("its records are {}-length and the SD's {}-length", format(&this), format(&the_sd))
569 } else if decl.area.1 != the_sd.file.area.1 {
570 format!("its largest record is {} bytes and the SD's {}", decl.area.1, the_sd.file.area.1)
571 } else if !input && earlier.iter().any(dfsort_reads) {
572 "it is also the USING file, whose I/O DFSORT does".into()
573 } else {
574 return None;
575 })
576}
577
578fn report_fastsrt<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, merge: bool, sd: usize, plan: &[Fastsrt], pos: Pos) {
581 let options = x.facts().options();
582 if options.trunc_check == TruncCheck::Silent {
583 return;
584 }
585 let verb = if merge { "MERGE" } else { "SORT" };
586 let fastsrt = options.fastsrt;
587 let mut reports = Vec::new();
588 for f in plan {
589 let this = x.sort_file(f.file);
590 let name = this.file.name;
591 let phrase = if f.input { "USING" } else { "GIVING" };
592 match &f.refusal {
593 Some(why) if fastsrt => reports.push(format!("FASTSRT does not apply to {phrase} {name}: {why}; COBOL does its I/O")),
594 Some(_) => {}
595 None => {
596 if let Some(records) = dfsort_records(x, f, sd, plan) {
597 let does = if fastsrt { "FASTSRT: DFSORT does" } else { "under FASTSRT, DFSORT would do" };
598 reports.push(format!("{does} the I/O of {phrase} {name}{records}"));
599 }
600 let mut kept: Vec<String> = this.status_name.iter().map(|r| format!("FILE STATUS {r}")).collect();
601 if !f.input && this.file.organization == Organization::Relative {
602 kept.extend(this.relative_name.iter().map(|r| format!("RELATIVE KEY {r}")));
603 }
604 if kept.is_empty() {
605 continue;
606 }
607 let kept = kept.join(" and ");
608 reports.push(if fastsrt {
609 format!("FASTSRT: DFSORT does the I/O of {phrase} {name}, so its {kept} is not updated by the {verb}")
610 } else {
611 format!("under FASTSRT, DFSORT would do the I/O of {phrase} {name} and its {kept} would not be updated by the {verb}")
612 });
613 }
614 }
615 }
616 let sd_name = x.sort_file(sd).file.name;
617 for report in reports {
618 let _ = writeln!(x.err(), "ironwork: {pos}: {verb} {sd_name}: {report} (-silent stops these reports)");
619 }
620}
621
622fn dfsort_records<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &H, f: &Fastsrt, sd: usize, plan: &[Fastsrt]) -> Option<String> {
625 let length = x.sort_file(sd).file.area.1;
626 let file = x.sort_file(f.file).file;
627 let byte = control_byte(x, &file);
628 let choice = x.facts().options().fastsrt_adv_print.flag();
629 let mut notes = Vec::new();
630 match file.carriage {
631 Some(c) if c.reserved && !f.input => notes.push(", a print file under NOADV, whose records DFSORT writes as the SD holds them, with no printer control character".to_owned()),
632 Some(c) if !c.reserved && byte == 1 && f.input => notes.push(format!(
633 ", a print file under ADV taken by {choice}, whose records DFSORT reads as its data set holds them, {} bytes with the printer control character first, so each key is read a byte before where the FD has it",
634 length + 1
635 )),
636 Some(c) if !c.reserved && byte == 1 => notes.push(format!(", a print file under ADV taken by {choice}, whose records DFSORT writes with no printer control character")),
637 _ => {}
638 }
639 if !f.input && fileio::dd_format(x, &file) == Records::Fixed && file.organization == Organization::Sequential {
640 let lrecl = file.area.1 + byte;
641 let sorted = sortin(plan).map(|u| length + control_byte(x, &x.sort_file(u.file).file));
642 match sorted {
643 Some(r) if lrecl > r => notes.push(format!("; DFSORT pads each record with X'00' to the data set's {lrecl} bytes (ICE171I)")),
644 Some(r) if lrecl < r => notes.push(format!("; DFSORT cuts each {r}-byte record to the data set's {lrecl} bytes (ICE171I)")),
645 _ => {}
646 }
647 }
648 (!notes.is_empty()).then(|| notes.concat())
649}
650
651fn run_sort_procedure<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, procedure: H::Procedure, active: Active, kind: Procedure, pos: Pos) -> R<(Ended, Option<Active>)> {
654 *x.active() = Some(active);
655 let ended = x.run_procedure(procedure, kind, pos);
656 let active = x.active().take();
657 match ended {
658 Err(a) if a.code == AbendCode::Signal(Signal::SortStopped) => Ok((Err(a.message), active)),
659 Err(a) => Err(a),
660 Ok(ended) => Ok((Ok(ended), active)),
661 }
662}
663
664pub fn sort<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, plan: &FileSort<H::Register, H::Procedure, H::Keys, H::File>, pos: Pos) -> R<Option<Ending>> {
668 crate::host::unfollowed(x, "SORT and MERGE");
669 let sd = usize::from(plan.sd);
670 let name = x.sort_file(sd).file.name;
671 let verb = if plan.merge { "MERGE" } else { "SORT" };
672 if x.active().is_some() {
673 return Err(Abend { code: AbendCode::user(4038), message: format!("IGZ0173S There was an invalid attempt to start a sort or merge. ({verb} {name} while another is in progress)"), pos, file: None });
674 }
675 refuse_control_statements(x, plan.sort_control, pos)?;
676 set_register(x, plan.sort_return, 0, pos)?;
677 let keys = x.keys(&plan.keys, pos)?;
678 let fastsrt = fastsrt_plan(x, plan, sd, pos)?;
679 report_fastsrt(x, plan.merge, sd, &fastsrt, pos);
680 let end = |x: &mut H, outcome| sort_end(x, plan.merge, name, plan.sort_return, outcome, pos);
681 if let Some(why) = dfsort_refuses(x, sd, &fastsrt) {
682 return end(x, Err(why));
683 }
684 let run = Run { sd, keys: &keys, sort_return: plan.sort_return, pos };
685 let up = ascending(&keys);
686 let entries = match &plan.input {
687 Some(SortIo::Files(_)) => match gather(x, &run, &fastsrt)? {
688 Ok(files) if plan.merge => merged(files, &up),
689 Ok(files) => sorted(files.into_iter().flatten().collect(), &up),
690 Err(why) => return end(x, Err(why)),
691 },
692 Some(SortIo::Procedure(procedure)) => {
693 let active = Active { sd, keys: keys.clone(), phase: Phase::Input(Vec::new()) };
694 let (ended, active) = run_sort_procedure(x, *procedure, active, Procedure::SortInput, pos)?;
695 match ended {
696 Err(why) => return end(x, Err(why)),
697 Ok(Some(e)) => return Err(ended_inside(verb, name, e, pos)),
698 Ok(None) => {}
699 }
700 if x.register_value(plan.sort_return, pos)? == 16 {
701 return end(x, Err(stopped_by_program()));
702 }
703 match active.map(|a| a.phase) {
704 Some(Phase::Input(entries)) => sorted(entries, &up),
705 _ => Vec::new(),
706 }
707 }
708 None => return Err(Abend::ironwork(format!("{verb} {name}: no input"), pos)),
709 };
710 let records: Vec<Vec<u8>> = entries.into_iter().map(|e| e.record).collect();
711 match &plan.output {
712 Some(SortIo::Files(_)) => {
713 let outcome = scatter(x, &run, &records, &fastsrt)?;
714 end(x, outcome)
715 }
716 Some(SortIo::Procedure(procedure)) => {
717 let active = Active { sd, keys: keys.clone(), phase: Phase::Output { records, next: 0 } };
718 let kind = if plan.merge { Procedure::MergeOutput } else { Procedure::SortOutput };
719 match run_sort_procedure(x, *procedure, active, kind, pos)?.0 {
720 Err(why) => end(x, Err(why)),
721 Ok(Some(e)) => Err(ended_inside(verb, name, e, pos)),
722 Ok(None) => end(x, Ok(())),
723 }
724 }
725 None => Err(Abend::ironwork(format!("{verb} {name}: no output"), pos)),
726 }
727}
728
729fn sort_end<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, merge: bool, name: &str, sort_return: H::Register, outcome: Outcome, pos: Pos) -> R<Option<Ending>> {
731 let code = if outcome.is_ok() { 0 } else { 16 };
732 set_register(x, sort_return, code, pos)?;
733 if let Err(why) = outcome {
734 let verb = if merge { "MERGE" } else { "SORT" };
735 let _ = writeln!(x.err(), "ironwork: {pos}: {verb} {name} failed: {why}; SORT-RETURN is 16");
736 }
737 Ok(None)
738}
739
740pub fn release_ready<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, file: Option<usize>, sort_return: H::Register, name: &str, pos: Pos) -> R<()> {
744 crate::host::unfollowed(x, "SORT and MERGE");
745 let Some(Active { sd, phase: Phase::Input(_), .. }) = x.active() else {
746 return Err(Abend::ironwork(format!("RELEASE {name}: no SORT input procedure is running"), pos));
747 };
748 if file != Some(*sd) {
749 return Err(Abend::ironwork(format!("RELEASE {name}: not a record of the file being sorted"), pos));
750 }
751 if x.register_value(sort_return, pos)? == 16 {
752 return Err(stop(stopped_by_program(), pos));
753 }
754 Ok(())
755}
756
757pub fn release<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, loc: Loc, name: &str, pos: Pos) -> R<()> {
760 crate::host::unfollowed(x, "SORT and MERGE");
761 let Some(Active { sd, keys, phase: Phase::Input(_) }) = x.active() else {
762 return Err(Abend::ironwork(format!("RELEASE {name}: no SORT input procedure is running"), pos));
763 };
764 let (sd, keys) = (*sd, keys.clone());
765 let f = x.sort_file(sd);
766 let (area, size) = f.file.area;
767 let Ok(loc) = fileio::record_length(x, &f.file, loc, pos)? else {
768 return Err(stop(format!("RELEASE {name}: the record's length is outside its RECORD clause"), pos));
769 };
770 let bytes = if f.fixed { x.mem()[area..area + size].to_vec() } else { store::bytes(x.mem(), loc).to_vec() };
771 match entry(x, sd, &keys, bytes, size, pos)? {
772 Ok(e) => {
773 if let Some(Active { phase: Phase::Input(entries), .. }) = x.active() {
774 entries.push(e);
775 }
776 Ok(())
777 }
778 Err(why) => Err(stop(format!("RELEASE {name}: {why}"), pos)),
779 }
780}
781
782pub fn return_record<'a, P: Copy, X: Copy, H: SortHost<'a, P, X>>(x: &mut H, file: Option<usize>, into: Option<P>, sort_return: H::Register, name: &str, pos: Pos) -> R<bool> {
785 crate::host::unfollowed(x, "SORT and MERGE");
786 let Some(Active { sd, phase: Phase::Output { .. }, .. }) = x.active() else {
787 return Err(Abend::ironwork(format!("RETURN {name}: no SORT or MERGE output procedure is running"), pos));
788 };
789 let sd = *sd;
790 if file != Some(sd) {
791 return Err(Abend::ironwork(format!("RETURN {name}: not the file being sorted or merged"), pos));
792 }
793 if x.register_value(sort_return, pos)? == 16 {
794 return Err(stop(stopped_by_program(), pos));
795 }
796 let record = match x.active() {
797 Some(Active { phase: Phase::Output { records, next }, .. }) if *next < records.len() => {
798 *next += 1;
799 Some(std::mem::take(&mut records[*next - 1]))
800 }
801 _ => None,
802 };
803 let Some(record) = record else { return Ok(false) };
804 let f = x.sort_file(sd);
805 let (area, size) = f.file.area;
806 let len = record.len().min(size);
807 x.mem()[area..area + len].copy_from_slice(&record[..len]);
808 if f.fixed {
809 x.mem()[area + len..area + size].fill(ebcdic::SPACE);
810 }
811 if let Some(d) = f.file.depending {
812 host::set_integer(x, d.item, len as i64, pos)?;
813 }
814 if let Some(p) = into {
815 let dest = x.locate(p, true)?;
816 let bytes = x.mem()[area..area + if f.fixed { size } else { len }].to_vec();
817 x.assign(dest, Val::Bytes(bytes), None, pos)?;
818 }
819 Ok(true)
820}
821
822pub fn sort_table<P: Copy, H: Host<P>>(x: &mut H, base: usize, count: usize, stride: usize, keys: impl FnOnce(&mut H) -> R<Vec<ItemKey>>, name: &str, pos: Pos) -> R<()> {
826 crate::host::unfollowed(x, "SORT and MERGE");
827 if base + count * stride > x.mem().len() {
828 return Err(Abend { code: AbendCode::Protection, message: format!("SORT {name} reaches outside the run unit's storage"), pos, file: None });
829 }
830 let keys = keys(x)?;
831 let mut entries = Vec::with_capacity(count);
832 for i in 0..count {
833 let at = base + i * stride;
834 let values = key_values(x, at, &keys, false, pos)?;
835 entries.push(Entry { record: x.mem()[at..at + stride].to_vec(), keys: values });
836 }
837 let up = ascending(&keys);
838 entries.sort_by(|a, b| order(&a.keys, &b.keys, &up));
839 for (i, e) in entries.iter().enumerate() {
840 let at = base + i * stride;
841 x.mem()[at..at + stride].copy_from_slice(&e.record);
842 }
843 Ok(())
844}