use alloc::vec::Vec;
use crate::allocation::{
AllocationContext, AllocationError, RequestedCapacity, try_push, try_reserve_total_exact,
};
use crate::bytes::Payload;
use crate::syntax::SyntaxToken;
use super::model::{CanonicalRightSide, Rule, RuleAnchorSyntax, RuleAvailability};
pub(crate) fn canonical_source(rule: &Rule) -> Result<Vec<u8>, AllocationError> {
let mut output = Vec::new();
try_reserve_total_exact(
&mut output,
RequestedCapacity::new(canonical_source_len(rule)?),
AllocationContext::CanonicalSource,
)?;
if matches!(rule.availability(), RuleAvailability::Once(_)) {
push_token(&mut output, SyntaxToken::Once)?;
}
match rule.anchor() {
RuleAnchorSyntax::Anywhere => {}
RuleAnchorSyntax::Start => push_token(&mut output, SyntaxToken::Start)?,
RuleAnchorSyntax::End => push_token(&mut output, SyntaxToken::End)?,
}
push_payload(&mut output, rule.lhs())?;
try_push(&mut output, b'=', AllocationContext::CanonicalSource)?;
match rule.action().canonical_right_side() {
CanonicalRightSide::Replace(payload) => {
push_payload(&mut output, payload)?;
}
CanonicalRightSide::MoveStart(payload) => {
push_token(&mut output, SyntaxToken::Start)?;
push_payload(&mut output, payload)?;
}
CanonicalRightSide::MoveEnd(payload) => {
push_token(&mut output, SyntaxToken::End)?;
push_payload(&mut output, payload)?;
}
CanonicalRightSide::Return(payload) => {
push_token(&mut output, SyntaxToken::Return)?;
push_payload(&mut output, payload)?;
}
}
Ok(output)
}
fn canonical_source_len(rule: &Rule) -> Result<usize, AllocationError> {
let mut len = rule.lhs().byte_count().get();
len = checked_source_len_add(len, availability_token_len(rule.availability()))?;
len = checked_source_len_add(len, anchor_token_len(rule.anchor()))?;
len = checked_source_len_add(len, 1)?;
len = checked_source_len_add(len, right_side_len(rule.action().canonical_right_side())?)?;
Ok(len)
}
fn availability_token_len(availability: RuleAvailability) -> usize {
match availability {
RuleAvailability::Always => 0,
RuleAvailability::Once(_) => SyntaxToken::Once.len(),
}
}
fn anchor_token_len(anchor: RuleAnchorSyntax) -> usize {
match anchor {
RuleAnchorSyntax::Anywhere => 0,
RuleAnchorSyntax::Start => SyntaxToken::Start.len(),
RuleAnchorSyntax::End => SyntaxToken::End.len(),
}
}
fn right_side_len(right_side: CanonicalRightSide<'_>) -> Result<usize, AllocationError> {
let payload_len = right_side_payload(right_side).byte_count().get();
match right_side_token(right_side) {
Some(token) => checked_source_len_add(token.len(), payload_len),
None => Ok(payload_len),
}
}
fn right_side_token(right_side: CanonicalRightSide<'_>) -> Option<SyntaxToken> {
match right_side {
CanonicalRightSide::Replace(_) => None,
CanonicalRightSide::MoveStart(_) => Some(SyntaxToken::Start),
CanonicalRightSide::MoveEnd(_) => Some(SyntaxToken::End),
CanonicalRightSide::Return(_) => Some(SyntaxToken::Return),
}
}
fn right_side_payload(right_side: CanonicalRightSide<'_>) -> &Payload {
match right_side {
CanonicalRightSide::Replace(payload)
| CanonicalRightSide::MoveStart(payload)
| CanonicalRightSide::MoveEnd(payload)
| CanonicalRightSide::Return(payload) => payload,
}
}
fn checked_source_len_add(len: usize, segment_len: usize) -> Result<usize, AllocationError> {
len.checked_add(segment_len)
.ok_or_else(|| AllocationError::capacity_overflow(AllocationContext::CanonicalSource))
}
fn push_token(output: &mut Vec<u8>, token: SyntaxToken) -> Result<(), AllocationError> {
for byte in token.bytes().iter().copied() {
try_push(output, byte, AllocationContext::CanonicalSource)?;
}
Ok(())
}
fn push_payload(output: &mut Vec<u8>, payload: &Payload) -> Result<(), AllocationError> {
payload.push_bytes_to(output, AllocationContext::CanonicalSource)
}