1use crate::abend::Abend;
7use crate::files::{self, Dd, FileStatus, Format, KeySpan, Keyed, Keying, Move, Open, Record};
8use crate::host::{self, Host};
9use crate::linage::{Geometry, Motion, Page};
10use crate::lir::{Access, Carriage, Organization, Spacing, StartRel};
11use crate::printer::{self, Controls};
12use crate::storage::{Loc, Val};
13use crate::store::{self, ProgramFacts};
14use crate::unit::Event;
15use crate::vocab::{Closing, OpenMode, Pos};
16use numeric::VsamOpenFs;
17use numeric::precision::{Fixed, Places};
18use std::cmp::Ordering;
19use zarch::ebcdic;
20
21type R<T> = Result<T, Abend>;
22
23#[derive(Clone, Copy)]
27pub struct File<'a, P, X> {
28 pub index: usize,
29 pub name: &'a str,
30 pub assign: &'a str,
32 pub organization: Organization,
33 pub access: Access,
34 pub optional: bool,
35 pub format: Format,
37 pub status: Option<P>,
38 pub relative: Option<P>,
39 pub linage: Option<Linage<X>>,
40 pub carriage: Option<Carriage>,
41 pub area: (usize, usize),
43 pub read_lengths: (usize, usize),
46 pub depending: Option<Depending<P>>,
47}
48
49#[derive(Clone, Copy)]
52pub struct Depending<P> {
53 pub item: P,
54 pub lengths: (usize, usize),
55}
56
57#[derive(Clone, Copy)]
60pub struct Linage<X> {
61 pub lines: X,
62 pub footing: Option<X>,
63 pub top: Option<X>,
64 pub bottom: Option<X>,
65 pub counter: Option<Loc>,
66}
67
68#[derive(Clone, Copy)]
70pub struct Read<P> {
71 pub sequential: bool,
72 pub previous: bool,
73 pub into: Option<P>,
74 pub key: Option<P>,
75}
76
77#[derive(Clone, Copy)]
80pub enum Advance<'a, X> {
81 Lines { before: bool, count: X },
82 Page { before: bool },
83 Mnemonic { before: bool, space: Option<Spacing>, name: &'a str, environment: &'a str },
84}
85
86pub enum Outcome {
88 Done,
90 Status { status: FileStatus, at_end: bool },
93 Page { end_of_page: bool },
95 Failed(Failure),
97}
98
99pub struct Failure {
101 pub status: FileStatus,
102 pub mode: Option<OpenMode>,
103 pub message: String,
104}
105
106pub trait Files<P: Copy, X: Copy>: Host<P> {
108 fn slot(&mut self, k: usize) -> &mut Option<Open>;
110 fn locked(&mut self, k: usize) -> &mut bool;
112 fn dd(&self, assign: &str) -> Option<Dd>;
113 fn notify(&mut self, event: Event<'_>);
114 fn int(&mut self, value: X, pos: Pos) -> R<i64>;
115 fn keying(&mut self, k: usize, pos: Pos) -> R<Keying>;
117 fn key_value(&mut self, k: usize, keying: &Keying, key: P, partial: bool, pos: Pos) -> R<(usize, Vec<u8>)>;
120}
121
122pub fn set_status<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, status: FileStatus, pos: Pos) -> R<()> {
123 if let Some(p) = file.status {
124 let loc = receiver(x, p)?;
125 let bytes = x.facts().page().encode(status.as_str()).map_err(|e| Abend::ironwork(e.to_string(), pos))?;
126 x.assign(loc, Val::Bytes(bytes), None, pos)?;
127 }
128 Ok(())
129}
130
131fn receiver<P: Copy>(x: &mut impl Host<P>, p: P) -> R<Loc> {
134 let was = x.taint().is_some_and(|t| t.writing(true));
135 let loc = x.locate(p, false);
136 if let Some(t) = x.taint() {
137 t.writing(was);
138 }
139 loc
140}
141
142fn failed<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, status: FileStatus, message: String) -> Outcome {
144 let mode = x.slot(file.index).as_ref().map(|f| f.mode);
145 Outcome::Failed(Failure { status, mode, message })
146}
147
148pub fn sequential<P, X>(file: &File<'_, P, X>) -> bool {
149 file.access == Access::Sequential || file.organization == Organization::Sequential
150}
151
152pub fn adds_control_byte<P, X>(file: &File<'_, P, X>, format: Format) -> bool {
155 file.carriage.is_some_and(|c| !c.reserved) && format != Format::Text
156}
157
158pub fn dd_format<P: Copy, X: Copy>(x: &impl Files<P, X>, file: &File<'_, P, X>) -> Format {
160 x.dd(file.assign).and_then(|d| d.format).unwrap_or(file.format)
161}
162
163fn record_area<P: Copy>(x: &mut impl Host<P>, (offset, size): (usize, usize)) -> &[u8] {
164 &x.mem()[offset..offset + size]
165}
166
167fn read_prime_key<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, prime: &KeySpan) -> Vec<u8> {
169 if let Some(taint) = x.taint() {
170 taint.read(file.area.0 + prime.offset, prime.len);
171 }
172 prime.of(record_area(x, file.area))
173}
174
175fn relative_value<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<i64> {
176 let r = file.relative.ok_or_else(|| Abend::ironwork(format!("{} has no RELATIVE KEY", file.name), pos))?;
177 x.integer(r, pos)
178}
179
180fn relative_number<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<Option<Vec<u8>>> {
182 let n = relative_value(x, file, pos)?;
183 Ok((n >= 1).then(|| files::record_number(n as u64)))
184}
185
186fn relative_fits<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, n: u64) -> R<bool> {
189 let Some(r) = file.relative else { return Ok(true) };
190 let loc = x.locate(r, false)?;
191 Ok(match loc.kind.digits_scale() {
192 Some((digits, scale)) => digits.saturating_sub(scale) >= 19 || n < 10u64.pow(digits.saturating_sub(scale)),
193 None => true,
194 })
195}
196
197fn held<P: Copy, X: Copy, F: Files<P, X>, T>(x: &mut F, k: usize, op: impl FnOnce(&mut F, OpenMode, Format, &mut Keyed) -> R<T>) -> R<Option<T>> {
199 let Some(mut f) = x.slot(k).take() else { return Ok(None) };
200 let (mode, format) = (f.mode, f.format);
201 let result = match f.keyed() {
202 Some(keyed) => op(x, mode, format, keyed).map(Some),
203 None => Ok(None),
204 };
205 *x.slot(k) = Some(f);
206 result
207}
208
209fn is_held<P: Copy, X: Copy>(x: &mut impl Files<P, X>, k: usize) -> bool {
210 x.slot(k).as_ref().is_some_and(|f| f.is_keyed())
211}
212
213fn held_control_byte<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>) -> bool {
216 x.slot(file.index).as_ref().is_some_and(|f| f.is_keyed() && adds_control_byte(file, f.format))
217}
218
219fn record_bytes<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, format: Format) -> Vec<u8> {
221 let mut bytes = store::bytes(x.mem(), loc).to_vec();
222 if format != Format::Variable {
223 bytes.resize(file.area.1.max(bytes.len()), ebcdic::SPACE);
224 }
225 bytes
226}
227
228pub fn record_length<P: Copy, X: Copy>(x: &mut impl Host<P>, file: &File<'_, P, X>, loc: Loc, pos: Pos) -> R<Result<Loc, FileStatus>> {
232 let Some(d) = file.depending else { return Ok(Ok(loc)) };
233 let n = x.integer(d.item, pos)?;
234 let (shortest, longest) = d.lengths;
235 if n < shortest as i64 || n > longest.min(file.area.1) as i64 {
236 return Ok(Err(FileStatus::RecordLengthChanged));
237 }
238 Ok(Ok(Loc { offset: file.area.0, len: n as usize, ..loc }))
239}
240
241fn deliver<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, record: &[u8], variable: bool, into: Option<P>, pos: Pos) -> R<bool> {
245 let (offset, size) = file.area;
246 let n = record.len().min(size);
247 let mem = x.mem();
248 mem[offset..offset + n].copy_from_slice(&record[..n]);
249 if !variable {
250 mem[offset + n..offset + size].fill(ebcdic::SPACE);
251 }
252 if let Some(t) = x.taint() {
253 t.set(offset, n, true);
254 t.set(offset + n, if variable { 0 } else { size - n }, false);
255 }
256 if let Some(d) = file.depending {
257 host::set_integer(x, d.item, n as i64, pos)?;
258 }
259 if let Some(r) = into {
260 let dest = x.locate(r, true)?;
261 let moved = if variable { n } else { size };
262 if let Some(t) = x.taint() {
263 t.read(offset, moved);
264 }
265 let bytes = x.mem()[offset..offset + moved].to_vec();
266 x.assign(dest, Val::Bytes(bytes), None, pos)?;
267 }
268 Ok(record.len() > size)
269}
270
271fn length_conflict<P, X>(file: &File<'_, P, X>, len: usize, variable: bool, long: bool) -> bool {
275 let (shortest, longest) = file.read_lengths;
276 long || variable && !(shortest..=longest).contains(&len)
277}
278
279pub fn open<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, mode: OpenMode, pos: Pos) -> R<Outcome> {
284 let (k, name) = (file.index, file.name);
285 let failure = |status, message| Ok(Outcome::Failed(Failure { status, mode: Some(mode), message }));
286 if *x.locked(k) {
287 return failure(FileStatus::ClosedWithLock, format!("{name} was closed WITH LOCK"));
288 }
289 if x.slot(k).is_some() {
290 return failure(FileStatus::AlreadyOpen, format!("{name} is already open"));
291 }
292 let page = match (&file.linage, mode) {
293 (Some(_), OpenMode::Output | OpenMode::Extend) => Some(Page::opened(geometry(x, file, pos)?)),
294 _ => None,
295 };
296 let default = file.format;
297 let dd = x.dd(file.assign);
298 if let Some(d) = &dd {
299 x.notify(Event::Open { dd: file.assign, mode, path: &d.path });
300 }
301 let no_dd = format!("{name}: no DD {} was given (--dd {}=path)", file.assign, file.assign);
302 let held = match file.organization {
303 Organization::Indexed | Organization::Relative => true,
304 Organization::Sequential => mode == OpenMode::InputOutput,
305 Organization::LineSequential => false,
306 };
307 if held {
308 let status = match &dd {
309 Some(d) if mode == OpenMode::Output || d.path.exists() => FileStatus::Success,
310 _ if file.optional && mode != OpenMode::Output => FileStatus::SuccessOptional,
311 None => return failure(FileStatus::FileNotFound, no_dd),
312 Some(d) => return failure(FileStatus::FileNotFound, format!("{name}: {}: no such file", d.path.display())),
313 };
314 let keying = x.keying(k, pos)?;
315 let format = dd.as_ref().and_then(|d| d.format).unwrap_or(default);
316 let record_len = file.area.1 + usize::from(adds_control_byte(file, format));
317 let vsam = dd.as_ref().filter(|_| matches!(file.organization, Organization::Indexed | Organization::Relative));
318 let verified = vsam.is_some_and(|d| files::left_open(&d.path));
319 let mut f = match files::open_keyed(dd.as_ref(), mode, format, keying, record_len, x.facts().page()) {
320 Ok(f) => f,
321 Err(e) => return failure(FileStatus::PermanentError, format!("{name}: {e}")),
322 };
323 if let Some(d) = vsam.filter(|_| mode != OpenMode::Input)
324 && let Err(e) = f.mark_open(&d.path)
325 {
326 return failure(FileStatus::PermanentError, format!("{name}: {}: {e}", files::open_mark(&d.path).display()));
327 }
328 let status = match x.facts().options().vsamopenfs {
329 _ if !verified => status,
330 VsamOpenFs::Compat => FileStatus::SuccessVerified,
331 VsamOpenFs::Succ => FileStatus::Success,
332 };
333 opened(x, file, f, status, pos)?;
334 return Ok(match (status, vsam) {
335 (FileStatus::SuccessVerified, Some(d)) => failed(x, file, status, format!("{name}: {} was left open for output, and OPEN verified it", d.path.display())),
336 _ => Outcome::Done,
337 });
338 }
339 match dd {
340 _ if file.optional && mode == OpenMode::Input && dd.as_ref().is_none_or(|d| !d.path.exists()) => opened(x, file, files::absent(), FileStatus::SuccessOptional, pos),
341 None => failure(FileStatus::FileNotFound, no_dd),
342 Some(dd) if mode == OpenMode::Extend && !file.optional && !dd.path.exists() => failure(FileStatus::FileNotFound, format!("{name}: {}: no such file", dd.path.display())),
343 Some(dd) => {
344 let created = mode == OpenMode::Extend && !dd.path.exists();
345 match files::open(&dd, mode, dd.format.unwrap_or(default)) {
346 Ok(mut f) => {
347 f.page = page;
348 opened(x, file, f, if created { FileStatus::SuccessOptional } else { FileStatus::Success }, pos)
349 }
350 Err(e) => {
351 let status = match e.kind() {
352 std::io::ErrorKind::NotFound => FileStatus::FileNotFound,
353 std::io::ErrorKind::Unsupported => FileStatus::OpenModeUnsupported,
354 _ => FileStatus::PermanentError,
355 };
356 failure(status, format!("{name}: {}: {e}", dd.path.display()))
357 }
358 }
359 }
360 }
361}
362
363fn opened<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, f: Open, status: FileStatus, pos: Pos) -> R<Outcome> {
366 *x.slot(file.index) = Some(f);
367 set_linage_counter(x, file, 1, pos)?;
368 set_status(x, file, status, pos)?;
369 Ok(Outcome::Done)
370}
371
372fn geometry<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<Geometry> {
374 let Some(linage) = file.linage else { return Err(Abend::ironwork(format!("{} has no LINAGE clause", file.name), pos)) };
375 let body = x.int(linage.lines, pos)?;
376 let footing = match linage.footing {
377 Some(v) => Some(x.int(v, pos)?),
378 None => None,
379 };
380 let top = match linage.top {
381 Some(v) => x.int(v, pos)?,
382 None => 0,
383 };
384 let bottom = match linage.bottom {
385 Some(v) => x.int(v, pos)?,
386 None => 0,
387 };
388 Geometry::new(body, footing, top, bottom).map_err(|why| Abend::ironwork(format!("{}: {why} ({})", file.name, numeric::assumptions::LINAGE_VALUES), pos))
389}
390
391fn set_linage_counter<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, value: u64, pos: Pos) -> R<()> {
392 let Some(loc) = file.linage.and_then(|l| l.counter) else { return Ok(()) };
393 x.store_fixed(loc, &Fixed::new(value as i128, Places::new(19, 0)), pos)
394}
395
396pub fn close<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, closing: Option<Closing>, pos: Pos) -> R<Outcome> {
401 let name = file.name;
402 if closing == Some(Closing::Volume) && x.slot(file.index).is_some() {
403 set_status(x, file, FileStatus::SuccessNonReel, pos)?;
404 return Ok(Outcome::Done);
405 }
406 match x.slot(file.index).take() {
407 None => Ok(failed(x, file, FileStatus::NotOpen, format!("{name} is not open"))),
408 Some(f) => {
409 let mode = Some(f.mode);
410 match f.close() {
411 Ok(()) => {
412 if let Some(d) = x.dd(file.assign) {
413 x.notify(Event::Close { dd: file.assign, path: &d.path });
414 }
415 *x.locked(file.index) |= closing == Some(Closing::Lock);
416 let status = if closing == Some(Closing::NoRewind) { FileStatus::SuccessNonReel } else { FileStatus::Success };
417 set_status(x, file, status, pos)?;
418 Ok(Outcome::Done)
419 }
420 Err(e) => Ok(Outcome::Failed(Failure { status: FileStatus::PermanentError, mode, message: format!("{name}: {e}") })),
421 }
422 }
423 }
424}
425
426pub fn read<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, r: Read<P>, pos: Pos) -> R<Outcome> {
428 let k = file.index;
429 if !is_held(x, k) {
430 return read_stream(x, file, r.into, pos);
431 }
432 let sequential = r.sequential;
433 let added = held_control_byte(x, file);
434 let (status, found, variable) = held(x, k, |x, mode, format, keyed| {
435 let variable = format == Format::Variable;
436 if !matches!(mode, OpenMode::Input | OpenMode::InputOutput) {
437 return Ok((FileStatus::NotOpenInput, None, variable));
438 }
439 if sequential {
440 return Ok(match keyed.read_next(r.previous) {
441 Err(status) => (status, None, variable),
442 Ok(None) => (FileStatus::AtEnd, None, variable),
443 Ok(Some(found)) if keyed.keying == Keying::Relative && !relative_fits(x, file, files::number_of(&found.key))? => (FileStatus::RelativeKeyOverflow, None, variable),
444 Ok(Some(found)) => (if found.duplicate { FileStatus::SuccessDuplicate } else { FileStatus::Success }, Some(found), variable),
445 });
446 }
447 let keying = keyed.keying.clone();
448 let (which, value) = match (&keying, r.key) {
449 (Keying::Indexed { .. }, Some(key)) => x.key_value(k, &keying, key, false, pos)?,
450 (Keying::Indexed { prime, .. }, None) => (0, read_prime_key(x, file, prime)),
451 _ => match relative_number(x, file, pos)? {
452 Some(key) => (0, key),
453 None => {
454 keyed.lose_position();
455 return Ok((FileStatus::NotFound, None, variable));
456 }
457 },
458 };
459 Ok(match keyed.get(which, &value) {
460 None => {
461 keyed.lose_position();
462 (FileStatus::NotFound, None, variable)
463 }
464 Some(found) => {
465 keyed.read_at(which, &found.key);
466 (if found.duplicate { FileStatus::SuccessDuplicate } else { FileStatus::Success }, Some(found), variable)
467 }
468 })
469 })?
470 .unwrap_or((FileStatus::NotOpenInput, None, false));
471 let mut status = status;
472 if let Some(found) = found {
473 if sequential && file.organization == Organization::Relative
474 && let Some(rk) = file.relative
475 {
476 host::set_integer(x, rk, files::number_of(&found.key) as i64, pos)?;
477 }
478 let record = found.record.get(usize::from(added)..).unwrap_or_default();
479 let long = deliver(x, file, record, variable, r.into, pos)?;
480 if length_conflict(file, record.len(), variable, long) && status == FileStatus::Success {
481 status = FileStatus::SuccessWrongLength;
482 }
483 }
484 Ok(Outcome::Status { status, at_end: sequential })
485}
486
487fn read_stream<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, into: Option<P>, pos: Pos) -> R<Outcome> {
488 let k = file.index;
489 let at_end = |status| Ok(Outcome::Status { status, at_end: true });
490 let size = file.area.1;
491 let Some(mut f) = x.slot(k).take() else {
492 return at_end(FileStatus::NotOpenInput);
493 };
494 let added = adds_control_byte(file, f.format);
495 let read = f.read(size + usize::from(added));
496 let format = f.format;
497 let input = f.mode == OpenMode::Input;
498 *x.slot(k) = Some(f);
499 if !input {
500 return at_end(FileStatus::NotOpenInput);
501 }
502 let (record, wrong_length) = match read {
503 Err(e) => return Ok(failed(x, file, FileStatus::PermanentError, format!("READ {}: {e}", file.name))),
504 Ok(Record::End) => return at_end(FileStatus::AtEnd),
505 Ok(Record::Data(bytes)) => (bytes, false),
506 Ok(Record::WrongLength(bytes)) => (bytes, true),
507 };
508 let record = if added { record.get(1..).unwrap_or_default().to_vec() } else { record };
509 let record = if format == Format::Text {
510 let page = x.facts().page();
511 let unknown = page.encode_char('?').unwrap_or(0x6F);
512 String::from_utf8_lossy(&record).chars().map(|c| page.encode_char(c).unwrap_or(unknown)).collect()
513 } else {
514 record
515 };
516 let variable = format == Format::Variable;
517 let long = deliver(x, file, &record, variable, into, pos)?;
518 at_end(if wrong_length || length_conflict(file, record.len(), variable, long) { FileStatus::SuccessWrongLength } else { FileStatus::Success })
519}
520
521pub fn write<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, advancing: Option<Advance<'_, X>>, pos: Pos) -> R<Outcome> {
523 let k = file.index;
524 let loc = match record_length(x, file, loc, pos)? {
525 Ok(loc) => loc,
526 Err(status) => return Ok(Outcome::Status { status, at_end: false }),
527 };
528 if paged(x, k) {
529 return write_page(x, file, loc, advancing, pos);
530 }
531 if !is_held(x, k) {
532 let (before, space) = match advancing {
533 Some(a) => advance(x, a, pos)?,
534 None => (false, Spacing::Lines(1)),
535 };
536 return write_stream(x, file, loc, before, space, pos);
537 }
538 let sequential = sequential(file);
539 let status = held(x, k, |x, mode, format, keyed| {
540 let allowed = match mode {
541 OpenMode::Output => true,
542 OpenMode::Extend => sequential,
543 OpenMode::InputOutput => !sequential,
544 OpenMode::Input => false,
545 };
546 if !allowed || keyed.keying == Keying::Position {
547 return Ok(FileStatus::NotOpenOutput);
548 }
549 keyed.last_read = None;
550 let bytes = record_bytes(x, file, loc, format);
551 let key = match &keyed.keying {
552 Keying::Indexed { prime, .. } => {
553 let key = prime.of(&bytes);
554 if sequential && keyed.highest_key().is_some_and(|h| key <= *h) {
555 return Ok(FileStatus::SequenceError);
556 }
557 key
558 }
559 _ if sequential => {
560 let n = keyed.highest_key().map_or(0, |h| files::number_of(h)) + 1;
561 if n > files::MAX_RELATIVE || !relative_fits(x, file, n)? {
562 return Ok(FileStatus::BoundaryViolation);
563 }
564 if let Some(rk) = file.relative {
565 host::set_integer(x, rk, n as i64, pos)?;
566 }
567 files::record_number(n)
568 }
569 _ => match relative_number(x, file, pos)? {
570 Some(key) if files::number_of(&key) <= files::MAX_RELATIVE => key,
571 _ => return Ok(FileStatus::BoundaryViolation),
572 },
573 };
574 Ok(match keyed.insert(key, bytes) {
575 Err(status) => status,
576 Ok(true) => FileStatus::SuccessDuplicate,
577 Ok(false) => FileStatus::Success,
578 })
579 })?
580 .unwrap_or(FileStatus::NotOpenOutput);
581 Ok(Outcome::Status { status, at_end: false })
582}
583
584fn paged<P: Copy, X: Copy>(x: &mut impl Files<P, X>, k: usize) -> bool {
585 x.slot(k).as_ref().is_some_and(|f| f.page.is_some())
586}
587
588fn write_page<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, advancing: Option<Advance<'_, X>>, pos: Pos) -> R<Outcome> {
592 let k = file.index;
593 let (before, motion) = match advancing {
594 None => (false, Motion::Lines(1)),
595 Some(Advance::Lines { before, count }) => (before, Motion::Lines(x.int(count, pos)?.max(0) as u64)),
596 Some(Advance::Page { before }) => (before, Motion::Page),
597 Some(Advance::Mnemonic { name, .. }) => {
598 return Err(Abend::ironwork(format!("ADVANCING {name} on {}, whose FD has LINAGE, is not supported yet", file.name), pos));
599 }
600 };
601 let Some(mut page) = x.slot(k).as_ref().and_then(|f| f.page) else { return Ok(Outcome::Done) };
602 let step = page.write(before, motion, || geometry(x, file, pos))?;
603 let controls = file.carriage.map(|c| printer::moving(c.machine, step.ahead, step.behind));
604 let text = (Some(Move::Lines(step.ahead)), before.then_some(Move::Lines(step.behind)));
605 if let failure @ Outcome::Failed(_) = put_line(x, file, loc, controls, text, pos)? {
606 return Ok(failure);
607 }
608 if let Some(f) = x.slot(k).as_mut() {
609 f.page = Some(page);
610 }
611 set_linage_counter(x, file, page.counter, pos)?;
612 Ok(Outcome::Page { end_of_page: step.end_of_page })
613}
614
615fn advance<P: Copy, X: Copy>(x: &mut impl Files<P, X>, a: Advance<'_, X>, pos: Pos) -> R<(bool, Spacing)> {
618 Ok(match a {
619 Advance::Lines { before, count } => (before, Spacing::Lines(x.int(count, pos)?.max(0) as u64)),
620 Advance::Page { before } => (before, Spacing::Channel(1)),
621 Advance::Mnemonic { before, space, name, environment } => {
622 let space = space.ok_or_else(|| Abend::ironwork(format!("ADVANCING {name}: {environment} is not a printer channel"), pos))?;
623 (before, space)
624 }
625 })
626}
627
628pub fn write_stream<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, before: bool, space: Spacing, pos: Pos) -> R<Outcome> {
631 let controls = file.carriage.map(|c| printer::controls(c.machine, before, space));
632 put_line(x, file, loc, controls, printer::text_motion(before, space), pos)
633}
634
635fn put_line<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, controls: Option<Controls>, text: (Option<Move>, Option<Move>), pos: Pos) -> R<Outcome> {
638 let (k, name) = (file.index, file.name);
639 let Some(mut f) = x.slot(k).take() else {
640 return Ok(failed(x, file, FileStatus::NotOpenOutput, format!("WRITE {name}: {}", FileStatus::NotOpenOutput.meaning())));
641 };
642 if f.mode == OpenMode::Input {
643 *x.slot(k) = Some(f);
644 return Ok(failed(x, file, FileStatus::NotOpenOutput, format!("WRITE {name}: {}", FileStatus::NotOpenOutput.meaning())));
645 }
646 let reserved = usize::from(file.carriage.is_some_and(|c| c.reserved));
647 if let Some(c) = controls.filter(|_| reserved == 1 && loc.len > 0) {
648 x.mem()[loc.offset] = c.data;
649 host::mark(x, loc.offset, 1);
650 }
651 let bytes = record_bytes(x, file, loc, f.format);
652 let written = match (f.format, controls) {
653 (Format::Text, _) => {
654 let line = x.facts().page().decode(bytes.get(reserved..).unwrap_or_default()).trim_end().to_owned();
655 f.print(text.0, &line, text.1)
656 }
657 (_, None) => f.write(&bytes),
658 (_, Some(c)) => c.records().try_for_each(|(control, line)| {
659 let mut record = Vec::with_capacity(bytes.len() + 1);
660 record.push(control);
661 if line {
662 record.extend_from_slice(&bytes[reserved.min(bytes.len())..]);
663 } else {
664 record.resize(bytes.len() + 1 - reserved.min(bytes.len()), ebcdic::SPACE);
665 }
666 f.write(&record)
667 }),
668 };
669 *x.slot(k) = Some(f);
670 match written {
671 Ok(()) => set_status(x, file, FileStatus::Success, pos).map(|()| Outcome::Done),
672 Err(e) => Ok(failed(x, file, FileStatus::PermanentError, format!("WRITE {name}: {e}"))),
673 }
674}
675
676pub fn rewrite<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, loc: Loc, pos: Pos) -> R<FileStatus> {
678 let loc = match record_length(x, file, loc, pos)? {
679 Ok(loc) => loc,
680 Err(status) => return Ok(status),
681 };
682 let sequential = sequential(file);
683 let added = held_control_byte(x, file);
684 Ok(held(x, file.index, |x, mode, format, keyed| {
685 if mode != OpenMode::InputOutput {
686 return Ok(FileStatus::NotOpenInputOutput);
687 }
688 let mut bytes = record_bytes(x, file, loc, format);
689 if added {
690 let control = keyed.last_read.as_ref().and_then(|key| keyed.record(key)).and_then(|old| old.first().copied()).unwrap_or(ebcdic::SPACE);
691 bytes.insert(0, control);
692 }
693 let prior = keyed.last_read.take();
694 let key = if sequential {
695 let Some(prior) = prior else { return Ok(FileStatus::NoPriorRead) };
696 if keyed.prime_key(&bytes).is_some_and(|key| key != prior) {
697 return Ok(FileStatus::SequenceError);
698 }
699 prior
700 } else {
701 match keyed.prime_key(&bytes) {
702 Some(key) => key,
703 None => match relative_number(x, file, pos)? {
704 Some(key) => key,
705 None => return Ok(FileStatus::NotFound),
706 },
707 }
708 };
709 if keyed.keying == Keying::Position && keyed.record(&key).is_some_and(|old| old.len() != bytes.len()) {
710 return Ok(FileStatus::RecordLengthChanged);
711 }
712 Ok(match keyed.replace(key, bytes) {
713 Err(status) => status,
714 Ok(true) => FileStatus::SuccessDuplicate,
715 Ok(false) => FileStatus::Success,
716 })
717 })?
718 .unwrap_or(FileStatus::NotOpenInputOutput))
719}
720
721pub fn delete<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, pos: Pos) -> R<FileStatus> {
723 let sequential = sequential(file);
724 Ok(held(x, file.index, |x, mode, _, keyed| {
725 if mode != OpenMode::InputOutput || keyed.keying == Keying::Position {
726 return Ok(FileStatus::NotOpenInputOutput);
727 }
728 let prior = keyed.last_read.take();
729 let key = match (&keyed.keying, sequential) {
730 (_, true) => match prior {
731 Some(key) => key,
732 None => return Ok(FileStatus::NoPriorRead),
733 },
734 (Keying::Indexed { prime, .. }, false) => read_prime_key(x, file, prime),
735 _ => match relative_number(x, file, pos)? {
736 Some(key) => key,
737 None => return Ok(FileStatus::NotFound),
738 },
739 };
740 Ok(if keyed.remove(&key).is_some() { FileStatus::Success } else { FileStatus::NotFound })
741 })?
742 .unwrap_or(FileStatus::NotOpenInputOutput))
743}
744
745pub fn start<P: Copy, X: Copy>(x: &mut impl Files<P, X>, file: &File<'_, P, X>, rel: StartRel, key: Option<P>, pos: Pos) -> R<FileStatus> {
747 let (wanted, or_equal) = match rel {
748 StartRel::Equal => (Ordering::Equal, false),
749 StartRel::Greater => (Ordering::Greater, false),
750 StartRel::NotLess => (Ordering::Greater, true),
751 };
752 let k = file.index;
753 Ok(held(x, k, |x, mode, _, keyed| {
754 if !matches!(mode, OpenMode::Input | OpenMode::InputOutput) || keyed.keying == Keying::Position {
755 return Ok(FileStatus::NotOpenInput);
756 }
757 let keying = keyed.keying.clone();
758 let (which, value) = match (&keying, key) {
759 (Keying::Indexed { .. }, Some(r)) => x.key_value(k, &keying, r, true, pos)?,
760 (Keying::Indexed { prime, .. }, None) => (0, read_prime_key(x, file, prime)),
761 (_, Some(r)) => (0, files::record_number(x.integer(r, pos)?.max(0) as u64)),
762 (_, None) => (0, files::record_number(relative_value(x, file, pos)?.max(0) as u64)),
763 };
764 Ok(if keyed.start(which, wanted, or_equal, &value) { FileStatus::Success } else { FileStatus::NotFound })
765 })?
766 .unwrap_or(FileStatus::NotOpenInput))
767}