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