use super::flow::Ctx;
use super::{Lower, R, push, unsupported};
use crate::layout::Resolved;
use crate::sort::{collates, table_key};
use rt::lir::{FileSort, FromMove, Op, PlaceId, RangeId, RangeKind, ReleasePlan, ReturnPlan, SortIo, SortKey, SortKeys, SortPlan, TableSort, Terminator};
use syntax::Pos;
use syntax::ast::{self, Expr, Handlers, Literal, Operand, ProcName, Ref, SortStmt, Sorting};
fn register(name: &str, pos: Pos) -> Ref {
Ref { name: name.into(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos }
}
impl Lower<'_> {
pub(super) fn sort_file_of(&self, st: &SortStmt) -> Option<usize> {
let s = &st.subject;
self.program.files.iter().position(|f| f.name == s.name).filter(|_| s.qualifiers.is_empty() && s.subscripts.is_empty())
}
pub(super) fn collect_sort(&mut self, st: &SortStmt) -> R<()> {
if self.sort_file_of(st).is_none() {
return Ok(());
}
for io in [&st.input, &st.output].into_iter().flatten() {
if let ast::SortIo::Procedure { from, thru } = io {
self.sort_procedure(from, thru.as_ref(), st.pos)?;
}
}
Ok(())
}
fn sort_procedure(&mut self, from: &ProcName, thru: Option<&ProcName>, pos: Pos) -> R<RangeId> {
let program = self.program;
let Ok((first, first_end)) = crate::procedure(program, from) else { return unsupported("a SORT or MERGE procedure the walker cannot find", pos) };
let last = match thru {
None => first_end,
Some(t) => match crate::procedure(program, t) {
Ok((_, last)) => last,
Err(_) => return unsupported("a SORT or MERGE procedure the walker cannot find", pos),
},
};
self.span_range((first, last), RangeKind::SortProcedure)
}
pub(super) fn sorting(&mut self, so: &Sorting, pos: Pos, ctx: &Ctx) -> R<()> {
match so {
Sorting::Sort(st) => {
let SortStmt { merge: _, subject: _, keys: _, duplicates: _, collating: _, input: _, output: _, pos: _ } = st;
let plan = match self.sort_file_of(st) {
Some(sd) => SortPlan::File(self.file_sort(st, sd)?),
None => SortPlan::Table(self.table_sort(st)?),
};
let id = push(&mut self.services.sorts, plan, "SORT and MERGE statements")?;
self.op(Op::Sort(id), pos)
}
Sorting::Release { record, from, pos } => {
let plan = self.release_plan(record, from.as_ref(), *pos)?;
let id = push(&mut self.services.releases, plan, "RELEASE statements")?;
self.op(Op::Release(id), *pos)
}
Sorting::Return { file, into, at_end, pos } => {
let plan = self.return_plan(file, into.as_ref(), *pos)?;
let id = push(&mut self.services.returns, plan, "RETURN statements")?;
self.op(Op::Return(id), *pos)?;
self.at_end(at_end, *pos, ctx)
}
}
}
fn at_end(&mut self, at_end: &Handlers, pos: Pos, ctx: &Ctx) -> R<()> {
let Handlers { on, not_on } = at_end;
let (end, record, join) = (self.new_block()?, self.new_block()?, self.new_block()?);
self.end(Terminator::Select(vec![end, record]), pos)?;
self.switch(end)?;
self.statements(on.as_deref().unwrap_or_default(), ctx)?;
self.jump(join, pos)?;
self.switch(record)?;
self.statements(not_on.as_deref().unwrap_or_default(), ctx)?;
self.jump(join, pos)?;
self.switch(join)
}
fn sort_register(&mut self, name: &str, pos: Pos) -> R<PlaceId> {
if !matches!(self.layout.resolve(name, &[], pos), Ok(Resolved::Item(_))) {
return unsupported("a sort statement in a program without the sort special registers", pos);
}
self.place(®ister(name, pos), false)
}
fn file_sort(&mut self, st: &SortStmt, sd: usize) -> R<FileSort> {
let pos = st.pos;
let sort_control = self.sort_register("SORT-CONTROL", pos)?;
let sort_return = self.sort_register("SORT-RETURN", pos)?;
let keys = self.file_keys(st, sd)?;
let input = st.input.as_ref().map(|io| self.sort_io(io, pos)).transpose()?;
let output = st.output.as_ref().map(|io| self.sort_io(io, pos)).transpose()?;
let sd = u16::try_from(sd).map_err(|_| super::LowerError::Exceeds("files", pos))?;
Ok(FileSort { sd, merge: st.merge, keys, input, output, sort_return, sort_control })
}
fn sort_io(&mut self, io: &ast::SortIo, pos: Pos) -> R<SortIo> {
Ok(match io {
ast::SortIo::Procedure { from, thru } => SortIo::Procedure(self.sort_procedure(from, thru.as_ref(), pos)?),
ast::SortIo::Files(names) => {
let mut files = Vec::with_capacity(names.len());
for name in names {
match self.program.files.iter().position(|f| f.name == *name).map(u16::try_from) {
Some(Ok(k)) => files.push(k),
Some(Err(_)) => return Err(super::LowerError::Exceeds("files", pos)),
None => return unsupported("a USING or GIVING file the walker cannot find", pos),
}
}
SortIo::Files(files)
}
})
}
fn key_collating(&self, st: &SortStmt, file: bool) -> R<Option<Box<[u8; 256]>>> {
let named;
let sequence = match &st.collating {
Some(name) => match crate::collating::Sequence::named(&self.program.environment, name, self.page, self.c.options.quote) {
Ok(s) => {
named = s;
&named
}
Err(_) => return unsupported("a COLLATING SEQUENCE the walker refuses when the SORT runs", st.pos),
},
None if file => &self.c.collating,
None => return Ok(None),
};
Ok((!sequence.is_native()).then(|| Box::new(sequence.positions())))
}
fn file_keys(&mut self, st: &SortStmt, sd: usize) -> R<SortKeys> {
let layout = self.layout;
let area = layout.file_areas[sd].0;
let collating = self.key_collating(st, true)?;
let mut keys = Vec::with_capacity(st.keys.len());
for (ascending, r) in &st.keys {
let Ok(Resolved::Item(i)) = layout.resolve(&r.name, &r.qualifiers, r.pos) else { return unsupported("a SORT key that is not a data item", r.pos) };
let item = &layout.items[i];
let Some(offset) = item.offset.checked_sub(area) else { return unsupported("a SORT key outside the sort file's records", r.pos) };
keys.push(SortKey { ascending: *ascending, offset, len: item.size, kind: item.kind, item: i as u32, collated: collates(item.kind) });
}
Ok(SortKeys { keys, collating })
}
fn table_sort(&mut self, st: &SortStmt) -> R<TableSort> {
let pos = st.pos;
let layout = self.layout;
let Ok(Resolved::Item(t)) = layout.resolve(&st.subject.name, &st.subject.qualifiers, st.subject.pos) else {
return unsupported("a table SORT of a name that is not a data item", pos);
};
let table = &layout.items[t];
let count = self.occurs(t, pos)?;
let mut first = Ref { refmod: None, ..st.subject.clone() };
first.subscripts.push(Expr::Operand(Operand::Literal(Literal::Number("1".into()))));
let first = self.place(&first, false)?;
let named = if st.keys.is_empty() { &table.keys } else { &st.keys };
let collating = self.key_collating(st, false)?;
let mut keys = Vec::with_capacity(named.len());
for (ascending, r) in named {
let Some(k) = table_key(layout, t, &r.name) else { return unsupported("a table SORT key that is not within its element", r.pos) };
let item = &layout.items[k];
let offset = item.offset.saturating_sub(table.offset);
keys.push(SortKey { ascending: *ascending, offset, len: item.size, kind: item.kind, item: k as u32, collated: collates(item.kind) });
}
let name = self.sym(&st.subject.name);
Ok(TableSort { first, count, stride: table.size, keys: SortKeys { keys, collating }, name })
}
fn release_plan(&mut self, record: &Ref, from: Option<&Operand>, pos: Pos) -> R<ReleasePlan> {
let place = self.place(record, false)?;
let file = self.place_items[place as usize].and_then(|i| self.layout.items[i].file);
let sort_return = self.sort_register("SORT-RETURN", pos)?;
let from = match from {
None => None,
Some(op) => {
let to = self.place(record, true)?;
let sender = self.operand(op, pos)?;
let plan = self.move_plan(&sender.side, self.kind_of(to), self.place_items[to as usize])?;
Some(FromMove { from: sender.operand, to, plan, check: self.move_check(sender.operand, to) })
}
};
Ok(ReleasePlan { record: place, file, from, sort_return, name: self.sym(&record.name) })
}
fn return_plan(&mut self, file: &str, into: Option<&Ref>, pos: Pos) -> R<ReturnPlan> {
let k = match self.program.files.iter().position(|f| f.name == file).map(u16::try_from) {
Some(Ok(k)) => Some(k),
Some(Err(_)) => return Err(super::LowerError::Exceeds("files", pos)),
None => None,
};
let sort_return = self.sort_register("SORT-RETURN", pos)?;
let into = match into {
None => None,
Some(r) => {
let place = self.place(r, true)?;
Some((place, self.bytes_into(place)?))
}
};
Ok(ReturnPlan { file: k, into, sort_return, name: self.sym(file) })
}
}