use super::keywords::{self, Follow};
use super::line::Named;
use super::{Record, Symbol};
use crate::unit::Mark;
const PAIR_FIRST: &[u8] = b" teisaprnl(of)=c";
const PAIR_SECOND: &[u8] = b" tnerpla";
#[derive(Clone, Copy, PartialEq, Eq)]
enum Unit {
Space,
Pair(u8),
Byte(u8),
}
fn unit_of(byte: u8) -> Unit {
if matches!(byte, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r') {
Unit::Space
} else {
Unit::Byte(byte)
}
}
pub(super) fn render(line: u32, text: &[u8], codes: &[(usize, u8)], names: &[Named]) -> Record {
let mut code_of = vec![None; text.len() + 1];
for &(at, code) in codes {
if let Some(slot) = code_of.get_mut(at) {
*slot = Some(code);
}
}
let code_at = |at: usize| code_of.get(at).copied().flatten();
let mut out = Vec::with_capacity(text.len());
let mut units = Vec::with_capacity(text.len());
let mut symbols = Vec::with_capacity(names.len());
let mut after_name = Vec::with_capacity(names.len());
let mut blank = false;
let mut next = 0;
let mut at = 0;
while at < text.len() {
let code = code_at(at);
let byte = text[at];
if code.is_none() && (byte == b' ' || byte == b'\t') {
blank = true;
at += 1;
continue;
}
if let Some(named) = names
.get(next)
.filter(|named| named.start == at && named.end <= text.len())
{
if blank {
out.push(b' ');
units.push(Unit::Space);
blank = false;
}
symbols.push(Symbol {
mark: named.mark,
at: out.len(),
len: named.end - named.start,
assigned: named.source_assigned,
});
out.extend((named.start..named.end).map(|at| code_at(at).unwrap_or(text[at])));
units.push(Unit::Space);
after_name.push(units.len());
if let Some(keyword) = (named.mark == Mark::Define)
.then(|| keywords::keyword(&text[named.start..named.end]))
.flatten()
.filter(|keyword| keyword.code.is_some())
{
keyword_suffix(&mut out, keyword.follow);
at = skip_after_keyword(keyword.follow, text, named.start, |at| {
code_at(at).is_some()
});
} else {
at = named.end;
}
next += 1;
continue;
}
if blank {
out.push(b' ');
blank = false;
if code.is_none() && PAIR_SECOND.contains(&byte) {
out.push(byte);
units.push(Unit::Pair(b' '));
at += 1;
continue;
}
units.push(Unit::Space);
} else if code.is_none()
&& next < names.len()
&& names[next].start != at + 1
&& PAIR_FIRST.contains(&byte)
&& text
.get(at + 1)
.is_some_and(|second| PAIR_SECOND.contains(second))
&& code_at(at + 1).is_none()
{
out.push(byte);
out.push(text[at + 1]);
units.push(Unit::Pair(byte));
at += 2;
continue;
}
if let Some(keyword) = code.and_then(keywords::by_code) {
out.extend_from_slice(keyword.word);
keyword_suffix(&mut out, keyword.follow);
units.push(unit_of(keyword.code.unwrap_or(0)));
at = skip_after_keyword(keyword.follow, text, at, |at| code_at(at).is_some());
} else if byte < 0x20 && byte != b'\t' {
if byte != b'\n' {
out.push(byte);
}
units.push(unit_of(byte));
let follow = keywords::by_code(byte).map_or(Follow::Nothing, |keyword| keyword.follow);
at = skip_after_keyword(follow, text, at, |at| code_at(at).is_some());
} else {
out.push(byte);
units.push(Unit::Byte(byte));
at += 1;
}
}
for (index, symbol) in symbols.iter_mut().enumerate() {
let from = after_name[index];
let to = after_name
.get(index + 1)
.map_or(units.len(), |next_from| next_from - 1);
symbol.assigned |= assignment_follows(&units[from..to.max(from)]);
}
let define_end_after = names
.get(next)
.is_some_and(|named| named.mark == Mark::DefineEnd);
Record {
line,
text: out,
symbols,
define_end_after,
}
}
fn keyword_suffix(out: &mut Vec<u8>, follow: Follow) {
match follow {
Follow::Nothing => {}
Follow::Blank => out.push(b' '),
Follow::Parenthesis => out.extend_from_slice(b" ("),
}
}
fn assignment_follows(units: &[Unit]) -> bool {
let start = units.iter().position(|unit| *unit != Unit::Space);
let Some(start) = start else { return false };
let unit = |offset: usize| units.get(start + offset).copied();
let equals_pair = |unit: Option<Unit>| unit == Some(Unit::Pair(b'='));
match unit(0) {
Some(Unit::Pair(b'=')) => true,
Some(Unit::Byte(b'=')) => match unit(1) {
Some(Unit::Byte(b'=')) => false,
Some(Unit::Pair(first)) => first != b'=',
_ => true,
},
Some(Unit::Byte(operator)) if b"+-*/%&|^".contains(&operator) => {
unit(1) == Some(Unit::Byte(b'=')) || equals_pair(unit(1))
}
Some(Unit::Byte(shift)) if shift == b'<' || shift == b'>' => {
unit(1) == Some(Unit::Byte(shift))
&& (unit(2) == Some(Unit::Byte(b'=')) || equals_pair(unit(2)))
}
_ => false,
}
}
fn skip_after_keyword(
follow: Follow,
text: &[u8],
at: usize,
coded: impl Fn(usize) -> bool,
) -> usize {
let is_blank = |byte: &u8| *byte == b' ' || *byte == b'\t';
let mut next = at + 1;
while text.get(next).is_some_and(is_blank) {
next += 1;
}
while text
.get(next)
.is_some_and(|byte| byte.is_ascii_alphabetic())
&& !coded(next)
{
next += 1;
}
if follow != Follow::Nothing {
while text.get(next).is_some_and(is_blank) {
next += 1;
}
}
if follow == Follow::Parenthesis && text.get(next) == Some(&b'(') {
next += 1;
}
next
}
#[cfg(test)]
pub(crate) fn reference_view(bytes: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(bytes.len());
for &byte in bytes {
if byte > 0x7f {
let code = usize::from(byte & 0x7f);
out.push(PAIR_FIRST[code / 8]);
out.push(PAIR_SECOND[code & 7]);
} else if byte < 0x20 && byte != b'\n' {
match keywords::by_code(byte) {
Some(keyword) => {
out.extend_from_slice(keyword.word);
match keyword.follow {
Follow::Nothing => {}
Follow::Blank => out.push(b' '),
Follow::Parenthesis => out.extend_from_slice(b" ("),
}
}
None => out.push(byte),
}
} else {
out.push(byte);
}
}
out
}