use crate::picture::{self, Notation};
use syntax::ast::*;
use syntax::{Error, Pos};
struct Laid {
line: u32,
column: u32,
item: Option<String>,
}
pub(crate) fn expand(program: &mut Program, extended: bool, errors: &mut Vec<Error>) {
let Some(first) = program.screens.first() else { return };
if !extended {
errors.push(syntax::messages::IWC0298.at(first.pos, "the SCREEN SECTION: Micro Focus's and GnuCOBOL's, not Enterprise COBOL's; --compliance extended reads it"));
return;
}
let notation = Notation::of(&program.environment);
let laid = layout(&program.screens, notation, &mut program.working_storage, errors);
let screens = std::mem::take(&mut program.screens);
let context = Context { screens: &screens, laid: &laid };
for p in &mut program.paragraphs {
context.rewrite(&mut p.statements, errors);
}
program.screens = screens;
}
fn layout(entries: &[ScreenEntry], notation: Notation, storage: &mut Vec<DataEntry>, errors: &mut Vec<Error>) -> Vec<Laid> {
let (mut line, mut end) = (1u32, 0u32);
let mut laid = Vec::with_capacity(entries.len());
for (k, e) in entries.iter().enumerate() {
let at = |place: Option<ScreenPlace>, base: u32| match place {
Some(ScreenPlace::At(n)) => n,
Some(ScreenPlace::Plus(n)) => base + n,
Some(ScreenPlace::Minus(n)) => base.saturating_sub(n).max(1),
None => base,
};
let new_line = at(e.line, line);
let column = match (e.line, e.column) {
(_, Some(c)) => at(Some(c), end),
(Some(_), None) => 1,
(None, None) => end + 1,
}
.max(1);
let len = match (&e.value, &e.picture) {
(Some(v), _) => literal_text(v).chars().count() as u32,
(None, Some(p)) => match picture::analyse_with(p, notation) {
Ok(pic) => pic.size,
Err((message, m)) => {
errors.push(message.at(e.pos, m));
0
}
},
(None, None) => 0,
};
let item = e.picture.as_ref().filter(|_| e.value.is_none()).map(|p| {
let name = format!("SCREEN%{k}");
let mut entry = crate::report::entry(1, Some(name.clone()), Some(p.clone()), None, e.pos);
let numeric = picture::analyse_with(p, notation).is_ok_and(|pic| matches!(pic.category, picture::Category::Numeric));
entry.value = Some(Literal::Figurative(if numeric { Figurative::Zero } else { Figurative::Space }));
storage.push(entry);
name
});
line = new_line;
if e.value.is_some() || e.picture.is_some() {
end = column + len - len.min(1);
} else {
end = column.saturating_sub(1);
}
laid.push(Laid { line: new_line, column, item });
}
laid
}
fn literal_text(l: &Literal) -> String {
match l {
Literal::Alnum(s) | Literal::National(s) | Literal::Dbcs(s) | Literal::Number(s) => s.clone(),
Literal::Hex(b) => " ".repeat(b.len()),
Literal::All(inner) => literal_text(inner),
Literal::Figurative(_) => " ".into(),
}
}
struct Context<'a> {
screens: &'a [ScreenEntry],
laid: &'a [Laid],
}
impl Context<'_> {
fn screen(&self, name: &str) -> Option<std::ops::Range<usize>> {
let k = self.screens.iter().position(|e| e.name.as_deref() == Some(name))?;
let level = self.screens[k].level;
let end = self.screens[k + 1..].iter().position(|e| e.level <= level).map_or(self.screens.len(), |n| k + 1 + n);
Some(k..end)
}
fn rewrite(&self, statements: &mut Vec<Stmt>, errors: &mut Vec<Error>) {
let mut k = 0;
while k < statements.len() {
for body in crate::oo::bodies_mut(&mut statements[k]) {
self.rewrite(body, errors);
}
if let Some(written) = self.written_out(&statements[k], errors) {
let n = written.len();
statements.splice(k..=k, written);
k += n;
} else {
k += 1;
}
}
}
fn written_out(&self, s: &Stmt, errors: &mut Vec<Error>) -> Option<Vec<Stmt>> {
let (name, phrases, accept, pos) = match s {
Stmt::Display { items, screen, pos, .. } => match items.as_slice() {
[Operand::Ref(r)] if r.qualifiers.is_empty() && r.subscripts.is_empty() => (r, screen.as_deref(), None, *pos),
_ => return None,
},
Stmt::Accept { target, screen, exception, pos, .. } if target.qualifiers.is_empty() => (target, screen.as_deref(), Some(exception), *pos),
_ => return None,
};
let range = self.screen(&name.name)?;
let (line_offset, column_offset) = match phrases.and_then(|p| p.at.as_ref()) {
None => (0, 0),
Some(ScreenAt::Combined(Operand::Literal(Literal::Number(n)))) => {
let n: u64 = n.parse().unwrap_or(0);
let (l, c) = if n > 9999 { (n / 1000, n % 1000) } else { (n / 100, n % 100) };
(l.saturating_sub(1) as u32, c.saturating_sub(1) as u32)
}
Some(ScreenAt::LineColumn { line, column }) => {
let number = |o: &Option<Operand>| match o {
Some(Operand::Literal(Literal::Number(n))) => n.parse::<u32>().ok().map(|n| n.saturating_sub(1)),
None => Some(0),
_ => None,
};
match (number(line), number(column)) {
(Some(l), Some(c)) => (l, c),
_ => return Some(vec![refused(name, pos, errors)]),
}
}
Some(_) => return Some(vec![refused(name, pos, errors)]),
};
let verb = if accept.is_some() { "ACCEPT" } else { "DISPLAY" };
errors.push(syntax::messages::IWX0020.at(pos, format!("{verb} {} on the screen (Micro Focus and GnuCOBOL; Enterprise COBOL has none): at the lines and columns its SCREEN SECTION entries give", name.name)));
let position = |k: usize| (self.laid[k].line + line_offset, self.laid[k].column + column_offset);
let reference = |name: &str| Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos };
let mut out = Vec::new();
let mut inputs = Vec::new();
for k in range {
let e = &self.screens[k];
if e.value.is_none() && e.picture.is_none() && !(e.blank_screen || e.blank_line || e.erase_eol || e.erase_eos) {
continue;
}
let (line, column) = position(k);
let at = Some(ScreenAt::LineColumn { line: Some(Operand::Literal(Literal::Number(line.to_string()))), column: Some(Operand::Literal(Literal::Number(column.to_string()))) });
let items = match (&e.value, &self.laid[k].item) {
(Some(v), _) => vec![Operand::Literal(v.clone())],
(None, Some(item)) => {
if let Some(source) = e.from.clone().or_else(|| e.using.clone().map(Operand::Ref)) {
out.push(Stmt::Move { from: source, to: vec![reference(item)], pos });
}
if let (Some(target), true) = (e.to.as_ref().or(e.using.as_ref()), accept.is_some()) {
inputs.push(ScreenInput { field: reference(item), target: target.clone(), line, column, update: e.using.is_some() || e.from.is_some(), secure: e.secure });
}
vec![Operand::Ref(reference(item))]
}
(None, None) => Vec::new(),
};
let phrases = ScreenPhrases {
at,
blank_screen: e.blank_screen,
blank_line: e.blank_line,
erase_eol: e.erase_eol,
erase_eos: e.erase_eos,
secure: e.secure,
attributes: e.attributes.clone(),
screen: Some(name.name.clone()),
pos: e.pos,
..ScreenPhrases::default()
};
out.push(Stmt::Display { items, upon: None, no_advancing: false, screen: Some(Box::new(phrases)), pos });
}
if let Some(exception) = accept {
let phrases = ScreenPhrases { screen: Some(name.name.clone()), inputs, pos, ..ScreenPhrases::default() };
out.push(Stmt::Accept { target: name.clone(), from: AcceptFrom::Sysin, exception: exception.clone(), screen: Some(Box::new(phrases)), pos });
}
Some(out)
}
}
fn refused(name: &Ref, pos: Pos, errors: &mut Vec<Error>) -> Stmt {
errors.push(syntax::messages::IWR0057.at(pos, format!("DISPLAY or ACCEPT of screen {} at a place an item gives is not supported yet", name.name)));
Stmt::Display { items: Vec::new(), upon: None, no_advancing: false, screen: None, pos }
}