use alloc::vec::Vec;
use crate::allocation::{AllocationContext, RequestedCapacity, try_push, try_reserve_total_exact};
use crate::bytes::{CompactByte, NonAsciiCodeByte, NonPrintableCodeByte};
use crate::error::{ParseError, ParseErrorKind, ParseLimitError, ParseRepresentationError};
use crate::limits::{CodeLineByteCount, CodeLineByteLimit};
use crate::source::{SourceLineNumber, SourcePosition};
use super::location::{parse_allocation_error, source_column};
use super::rule_line::RuleSyntaxLine;
pub(super) struct RawSourceLine<'source> {
line_number: SourceLineNumber,
bytes: &'source [u8],
code_line_limit: CodeLineByteLimit,
}
impl<'source> RawSourceLine<'source> {
pub(super) fn new(
line_number: SourceLineNumber,
bytes: &'source [u8],
code_line_limit: CodeLineByteLimit,
) -> Self {
Self {
line_number,
bytes,
code_line_limit,
}
}
pub(super) fn into_code_line(self) -> Result<CodeLine<'source>, ParseError> {
let code_bytes = self
.bytes
.split(|&byte| byte == b'#')
.next()
.unwrap_or(self.bytes);
let attempted_len = CodeLineByteCount::new(code_bytes.len());
if !self.code_line_limit.accepts(attempted_len) {
return Err(ParseError::at_line(
self.line_number,
ParseErrorKind::Limit(ParseLimitError::code_line(
self.code_line_limit,
attempted_len,
)),
));
}
if let Some((zero_based_column, byte)) = code_bytes
.iter()
.copied()
.enumerate()
.find(|(_, byte)| !byte.is_ascii())
{
let rejected = NonAsciiCodeByte::parse(byte).ok_or_else(|| {
ParseError::at_line(
self.line_number,
ParseErrorKind::InternalInvariant(
crate::error::ParseInvariantError::RejectedNonAsciiCodeByteWithoutWitness,
),
)
})?;
return Err(ParseError::at_position(
SourcePosition::new(
self.line_number,
source_column(zero_based_column, self.line_number)?,
),
ParseErrorKind::NonAsciiInCode { byte: rejected },
));
}
Ok(CodeLine {
line_number: self.line_number,
bytes: code_bytes,
})
}
}
pub(super) struct CodeLine<'source> {
line_number: SourceLineNumber,
bytes: &'source [u8],
}
impl CodeLine<'_> {
pub(super) fn into_compact_line(self) -> Result<CompactCodeLine, ParseError> {
let compact_byte_count = self.compact_byte_count()?;
let bytes = self.compact_bytes(compact_byte_count)?;
Ok(CompactCodeLine {
line_number: self.line_number,
bytes,
})
}
fn compact_byte_count(&self) -> Result<CompactCodeByteCount, ParseError> {
let mut byte_count = CompactCodeByteCount::ZERO;
for (zero_based_column, byte) in self.bytes.iter().copied().enumerate() {
if byte.is_ascii_whitespace() {
continue;
}
if let Some(rejected) = NonPrintableCodeByte::parse(byte) {
return Err(ParseError::at_position(
SourcePosition::new(
self.line_number,
source_column(zero_based_column, self.line_number)?,
),
ParseErrorKind::NonPrintableAsciiInCode { byte: rejected },
));
}
byte_count = byte_count.checked_next(self.line_number)?;
}
Ok(byte_count)
}
fn compact_bytes(
&self,
compact_byte_count: CompactCodeByteCount,
) -> Result<Vec<CompactByte>, ParseError> {
let mut bytes = Vec::new();
try_reserve_total_exact(
&mut bytes,
RequestedCapacity::new(compact_byte_count.get()),
AllocationContext::ProgramCodeLine,
)
.map_err(|error| parse_allocation_error(self.line_number, error))?;
for (zero_based_column, byte) in self.bytes.iter().copied().enumerate() {
if byte.is_ascii_whitespace() {
continue;
}
try_push(
&mut bytes,
CompactByte::new(byte, source_column(zero_based_column, self.line_number)?),
AllocationContext::ProgramCodeLine,
)
.map_err(|error| parse_allocation_error(self.line_number, error))?;
}
Ok(bytes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct CompactCodeByteCount {
value: usize,
}
impl CompactCodeByteCount {
const ZERO: Self = Self { value: 0 };
fn checked_next(self, line_number: SourceLineNumber) -> Result<Self, ParseError> {
let value = self.value.checked_add(1).ok_or_else(|| {
ParseError::at_line(
line_number,
ParseErrorKind::Representation(ParseRepresentationError::CompactCodeByteCount),
)
})?;
Ok(Self { value })
}
const fn get(self) -> usize {
self.value
}
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct CompactCodeLine {
line_number: SourceLineNumber,
bytes: Vec<CompactByte>,
}
impl CompactCodeLine {
pub(super) fn into_non_empty(self) -> Option<NonEmptyCompactCodeLine> {
(!self.bytes.is_empty()).then_some(NonEmptyCompactCodeLine {
line_number: self.line_number,
bytes: self.bytes,
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct NonEmptyCompactCodeLine {
line_number: SourceLineNumber,
bytes: Vec<CompactByte>,
}
impl NonEmptyCompactCodeLine {
pub(super) fn into_rule_syntax(self) -> Result<RuleSyntaxLine, ParseError> {
RuleSyntaxLine::new(self.line_number, self.bytes)
}
}