use alloc::vec::Vec;
use crate::allocation::{AllocationContext, try_push};
use crate::bytes::{CompactByte, Payload, PayloadByteCount, PayloadSyntax};
use crate::error::{
LeftModifierKind, ParseError, ParseErrorKind, ParseLimitError, PayloadKind, RightActionKind,
};
use crate::limits::PayloadByteLimit;
use crate::rule::{
ParsedRule, RewriteAction, RuleAction, RuleAnchorSyntax, RuleBody, RuleHead, RuleRepeatSyntax,
};
use crate::source::{SourceColumn, SourceLineNumber, SourcePosition};
use crate::syntax::SyntaxToken;
#[derive(Debug, PartialEq, Eq)]
pub(super) struct RuleSyntaxLine {
line_number: SourceLineNumber,
left: OwnedCompactSyntax,
right: OwnedCompactSyntax,
}
impl RuleSyntaxLine {
pub(super) fn new(
line_number: SourceLineNumber,
bytes: Vec<CompactByte>,
) -> Result<Self, ParseError> {
let parts = RuleSyntaxParts::split(line_number, bytes)?;
Ok(Self {
line_number,
left: parts.left,
right: parts.right,
})
}
pub(super) fn parse(&self, payload_limit: PayloadByteLimit) -> Result<ParsedRule, ParseError> {
let (left, right) = self.syntax_parts();
let head = left.parse(payload_limit)?;
let body = right.parse(payload_limit)?;
Ok(ParsedRule::from_parts(self.line_number, head, body))
}
fn syntax_parts(&self) -> (LeftSyntax<'_>, RightSyntax<'_>) {
(
LeftSyntax {
line_number: self.line_number,
bytes: self.left.as_syntax(),
},
RightSyntax {
line_number: self.line_number,
bytes: self.right.as_syntax(),
},
)
}
}
#[derive(Debug, PartialEq, Eq)]
struct RuleSyntaxParts {
left: OwnedCompactSyntax,
right: OwnedCompactSyntax,
}
#[derive(Debug, PartialEq, Eq)]
struct OwnedCompactSyntax {
bytes: Vec<CompactByte>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct CompactSyntax<'code> {
bytes: &'code [CompactByte],
}
#[derive(Clone, Copy)]
struct MatchedSyntaxPrefix<'code> {
column: SourceColumn,
rest: CompactSyntax<'code>,
}
#[derive(Clone, Copy)]
struct MatchedToken<T> {
kind: T,
column: SourceColumn,
}
impl RuleSyntaxParts {
fn split(line_number: SourceLineNumber, bytes: Vec<CompactByte>) -> Result<Self, ParseError> {
let mut left = OwnedCompactSyntax::new();
let mut right = OwnedCompactSyntax::new();
let mut seen_separator = false;
for byte in bytes {
if byte.as_u8() == b'=' {
if seen_separator {
return Err(ParseError::at_position(
SourcePosition::new(line_number, byte.source_column()),
ParseErrorKind::MultipleEquals,
));
}
seen_separator = true;
continue;
}
if seen_separator {
right.push(byte, line_number)?;
} else {
left.push(byte, line_number)?;
}
}
if seen_separator {
Ok(Self { left, right })
} else {
Err(ParseError::at_line(
line_number,
ParseErrorKind::MissingEquals,
))
}
}
}
impl OwnedCompactSyntax {
const fn new() -> Self {
Self { bytes: Vec::new() }
}
fn push(&mut self, byte: CompactByte, line_number: SourceLineNumber) -> Result<(), ParseError> {
try_push(&mut self.bytes, byte, AllocationContext::ProgramCodeLine)
.map_err(|error| ParseError::at_line(line_number, ParseErrorKind::Allocation(error)))
}
fn as_syntax(&self) -> CompactSyntax<'_> {
CompactSyntax { bytes: &self.bytes }
}
}
impl<'code> CompactSyntax<'code> {
const fn as_slice(self) -> &'code [CompactByte] {
self.bytes
}
fn strip_token(self, token: SyntaxToken) -> Option<MatchedSyntaxPrefix<'code>> {
let token_bytes = token.bytes();
if self.bytes.len() < token_bytes.len() {
return None;
}
let starts_with_token = self
.bytes
.iter()
.take(token_bytes.len())
.copied()
.map(CompactByte::as_u8)
.eq(token_bytes.iter().copied());
if starts_with_token {
let column = self.bytes.first().copied()?.source_column();
self.bytes
.get(token_bytes.len()..)
.map(|bytes| MatchedSyntaxPrefix {
column,
rest: Self { bytes },
})
} else {
None
}
}
}
#[derive(Clone, Copy)]
struct LeftSyntax<'code> {
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
}
impl<'code> LeftSyntax<'code> {
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleHead, ParseError> {
self.into_after_repeat().parse(payload_limit)
}
fn into_after_repeat(self) -> LeftAfterRepeat<'code> {
if let Some(matched) = self.bytes.strip_token(SyntaxToken::Once) {
LeftAfterRepeat {
line_number: self.line_number,
bytes: matched.rest,
repeat: RuleRepeatSyntax::Once,
}
} else {
LeftAfterRepeat {
line_number: self.line_number,
bytes: self.bytes,
repeat: RuleRepeatSyntax::Always,
}
}
}
}
#[derive(Clone, Copy)]
struct LeftAfterRepeat<'code> {
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
repeat: RuleRepeatSyntax,
}
impl<'code> LeftAfterRepeat<'code> {
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleHead, ParseError> {
self.into_payload_syntax()?.parse(payload_limit)
}
fn into_payload_syntax(self) -> Result<LeftPayloadSyntax<'code>, ParseError> {
let (anchor, bytes) = if let Some(matched) = self.bytes.strip_token(SyntaxToken::Start) {
(RuleAnchorSyntax::Start, matched.rest)
} else if let Some(matched) = self.bytes.strip_token(SyntaxToken::End) {
(RuleAnchorSyntax::End, matched.rest)
} else {
(RuleAnchorSyntax::Anywhere, self.bytes)
};
if let Some(modifier) = left_modifier_kind(bytes) {
return Err(ParseError::at_position(
SourcePosition::new(self.line_number, modifier.column),
ParseErrorKind::UnsupportedLeftModifierOrder {
modifier: modifier.kind,
},
));
}
Ok(LeftPayloadSyntax {
line_number: self.line_number,
bytes,
repeat: self.repeat,
anchor,
})
}
}
#[derive(Clone, Copy)]
struct LeftPayloadSyntax<'code> {
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
repeat: RuleRepeatSyntax,
anchor: RuleAnchorSyntax,
}
impl LeftPayloadSyntax<'_> {
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleHead, ParseError> {
let payload = parse_payload(
self.bytes.as_slice(),
self.line_number,
PayloadKind::LeftSideData,
payload_limit,
)?;
Ok(RuleHead::new(self.repeat, self.anchor, payload))
}
}
#[derive(Clone, Copy)]
struct RightSyntax<'code> {
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
}
impl<'code> RightSyntax<'code> {
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleBody, ParseError> {
self.into_payload_syntax()?.parse(payload_limit)
}
fn into_payload_syntax(self) -> Result<RightPayloadSyntax<'code>, ParseError> {
if let Some(matched) = self.bytes.strip_token(SyntaxToken::Start) {
return Ok(RightPayloadSyntax::Action(RightActionPayloadSyntax::new(
self.line_number,
matched.rest,
RightActionSyntax::MoveStart,
)?));
} else if let Some(matched) = self.bytes.strip_token(SyntaxToken::End) {
return Ok(RightPayloadSyntax::Action(RightActionPayloadSyntax::new(
self.line_number,
matched.rest,
RightActionSyntax::MoveEnd,
)?));
} else if let Some(matched) = self.bytes.strip_token(SyntaxToken::Return) {
return Ok(RightPayloadSyntax::Action(RightActionPayloadSyntax::new(
self.line_number,
matched.rest,
RightActionSyntax::Return,
)?));
}
Ok(RightPayloadSyntax::Replace(RightReplacePayloadSyntax {
line_number: self.line_number,
bytes: self.bytes,
}))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RightActionSyntax {
MoveStart,
MoveEnd,
Return,
}
impl RightActionSyntax {
const fn payload_kind(self) -> PayloadKind {
match self {
Self::MoveStart => PayloadKind::RightSideMoveStartPayload,
Self::MoveEnd => PayloadKind::RightSideMoveEndPayload,
Self::Return => PayloadKind::RightSideReturnPayload,
}
}
fn into_body(self, payload: Payload) -> RuleBody {
let action = match self {
Self::MoveStart => RuleAction::Rewrite(RewriteAction::MoveStart(payload)),
Self::MoveEnd => RuleAction::Rewrite(RewriteAction::MoveEnd(payload)),
Self::Return => RuleAction::Return(payload),
};
RuleBody::new(action)
}
}
#[derive(Clone, Copy)]
enum RightPayloadSyntax<'code> {
Replace(RightReplacePayloadSyntax<'code>),
Action(RightActionPayloadSyntax<'code>),
}
impl RightPayloadSyntax<'_> {
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleBody, ParseError> {
match self {
Self::Replace(payload) => payload.parse(payload_limit),
Self::Action(payload) => payload.parse(payload_limit),
}
}
}
#[derive(Clone, Copy)]
struct RightReplacePayloadSyntax<'code> {
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
}
impl RightReplacePayloadSyntax<'_> {
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleBody, ParseError> {
let payload = parse_payload(
self.bytes.as_slice(),
self.line_number,
PayloadKind::RightSideData,
payload_limit,
)?;
Ok(RuleBody::new(RuleAction::Rewrite(RewriteAction::Replace(
payload,
))))
}
}
#[derive(Clone, Copy)]
struct RightActionPayloadSyntax<'code> {
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
action: RightActionSyntax,
}
impl<'code> RightActionPayloadSyntax<'code> {
fn new(
line_number: SourceLineNumber,
bytes: CompactSyntax<'code>,
action: RightActionSyntax,
) -> Result<Self, ParseError> {
reject_nested_rhs_action(bytes, line_number)?;
Ok(Self {
line_number,
bytes,
action,
})
}
fn parse(self, payload_limit: PayloadByteLimit) -> Result<RuleBody, ParseError> {
let payload = parse_payload(
self.bytes.as_slice(),
self.line_number,
self.action.payload_kind(),
payload_limit,
)?;
Ok(self.action.into_body(payload))
}
}
fn parse_payload(
bytes: &[CompactByte],
line_number: SourceLineNumber,
payload_kind: PayloadKind,
limit: PayloadByteLimit,
) -> Result<Payload, ParseError> {
let syntax = PayloadSyntax::new(bytes, line_number, payload_kind);
ensure_payload_within_limit(line_number, syntax.byte_count(), limit)?;
Ok(syntax.validate()?.into_payload())
}
fn ensure_payload_within_limit(
line_number: SourceLineNumber,
attempted_len: PayloadByteCount,
limit: PayloadByteLimit,
) -> Result<(), ParseError> {
if limit.accepts(attempted_len) {
return Ok(());
}
Err(ParseError::at_line(
line_number,
ParseErrorKind::Limit(ParseLimitError::payload(limit, attempted_len)),
))
}
fn first_matching_token_kind<T: Copy>(
input: CompactSyntax<'_>,
mappings: &[(SyntaxToken, T)],
) -> Option<MatchedToken<T>> {
mappings.iter().find_map(|&(token, kind)| {
input.strip_token(token).map(|matched| MatchedToken {
kind,
column: matched.column,
})
})
}
fn left_modifier_kind(input: CompactSyntax<'_>) -> Option<MatchedToken<LeftModifierKind>> {
first_matching_token_kind(
input,
&[
(SyntaxToken::Once, LeftModifierKind::Once),
(SyntaxToken::Start, LeftModifierKind::Start),
(SyntaxToken::End, LeftModifierKind::End),
],
)
}
fn right_action_kind(input: CompactSyntax<'_>) -> Option<MatchedToken<RightActionKind>> {
first_matching_token_kind(
input,
&[
(SyntaxToken::Start, RightActionKind::Start),
(SyntaxToken::End, RightActionKind::End),
(SyntaxToken::Return, RightActionKind::Return),
],
)
}
fn reject_nested_rhs_action(
input: CompactSyntax<'_>,
line_number: SourceLineNumber,
) -> Result<(), ParseError> {
if let Some(action) = right_action_kind(input) {
return Err(ParseError::at_position(
SourcePosition::new(line_number, action.column),
ParseErrorKind::UnsupportedRightActionSyntax {
action: action.kind,
},
));
}
Ok(())
}