use alloc::vec::Vec;
use core::fmt;
use vize_l0::String;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipelineSpec<'a> {
pub stage: &'a str,
pub passes: Vec<&'a str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PipelineSyntaxError {
Empty,
ExpectedStage { offset: usize },
ExpectedOpenParen { offset: usize },
ExpectedPass { offset: usize },
UnterminatedPassList { offset: usize },
ExpectedCommaOrEnd { offset: usize },
UnexpectedCharacter { offset: usize, character: char },
TrailingHyphen { offset: usize },
}
impl PipelineSyntaxError {
#[must_use]
pub const fn offset(self) -> usize {
match self {
PipelineSyntaxError::Empty => 0,
PipelineSyntaxError::ExpectedStage { offset }
| PipelineSyntaxError::ExpectedOpenParen { offset }
| PipelineSyntaxError::ExpectedPass { offset }
| PipelineSyntaxError::UnterminatedPassList { offset }
| PipelineSyntaxError::ExpectedCommaOrEnd { offset }
| PipelineSyntaxError::UnexpectedCharacter { offset, .. }
| PipelineSyntaxError::TrailingHyphen { offset } => offset,
}
}
}
impl fmt::Display for PipelineSyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PipelineSyntaxError::Empty => write!(f, "empty pipeline string at offset 0"),
PipelineSyntaxError::ExpectedStage { offset } => {
write!(f, "expected a stage name at offset {offset}")
}
PipelineSyntaxError::ExpectedOpenParen { offset } => {
write!(f, "expected `(` after stage name at offset {offset}")
}
PipelineSyntaxError::ExpectedPass { offset } => {
write!(f, "expected a pass name at offset {offset}")
}
PipelineSyntaxError::UnterminatedPassList { offset } => {
write!(f, "unterminated pass list, expected `)` at offset {offset}")
}
PipelineSyntaxError::ExpectedCommaOrEnd { offset } => {
write!(
f,
"expected `,` or end of input after `)` at offset {offset}"
)
}
PipelineSyntaxError::UnexpectedCharacter { offset, character } => {
write!(f, "unexpected character `{character}` at offset {offset}")
}
PipelineSyntaxError::TrailingHyphen { offset } => {
write!(f, "identifier must not end with `-` at offset {offset}")
}
}
}
}
impl core::error::Error for PipelineSyntaxError {}
const fn is_ident_byte(byte: u8) -> bool {
byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-'
}
fn read_ident(input: &str, start: usize) -> Result<(&str, usize), PipelineSyntaxError> {
let rest = input.get(start..).unwrap_or_default();
let len = rest.bytes().take_while(|byte| is_ident_byte(*byte)).count();
let (ident, _) = rest.split_at_checked(len).unwrap_or((rest, ""));
if ident.is_empty() {
return match rest.chars().next() {
Some(character) => Err(PipelineSyntaxError::UnexpectedCharacter {
offset: start,
character,
}),
None => Err(PipelineSyntaxError::ExpectedStage { offset: start }),
};
}
let end = start + ident.len();
if ident.ends_with('-') {
return Err(PipelineSyntaxError::TrailingHyphen { offset: end - 1 });
}
Ok((ident, end))
}
pub fn parse_pipelines(input: &str) -> Result<Vec<PipelineSpec<'_>>, PipelineSyntaxError> {
if input.is_empty() {
return Err(PipelineSyntaxError::Empty);
}
let bytes = input.as_bytes();
let mut segments = Vec::new();
let mut offset = 0;
loop {
if offset >= bytes.len() {
return Err(PipelineSyntaxError::ExpectedStage { offset });
}
let (stage, after_stage) = read_ident(input, offset)?;
offset = after_stage;
if bytes.get(offset) != Some(&b'(') {
return Err(PipelineSyntaxError::ExpectedOpenParen { offset });
}
offset += 1;
let mut passes = Vec::new();
if bytes.get(offset) == Some(&b')') {
offset += 1;
} else {
loop {
match bytes.get(offset) {
None => return Err(PipelineSyntaxError::UnterminatedPassList { offset }),
Some(byte) if !is_ident_byte(*byte) => {
return Err(PipelineSyntaxError::ExpectedPass { offset });
}
Some(_) => {}
}
let (name, after_name) = read_ident(input, offset)?;
passes.push(name);
offset = after_name;
match bytes.get(offset) {
Some(b',') => offset += 1,
Some(b')') => {
offset += 1;
break;
}
Some(_) => {
return Err(PipelineSyntaxError::UnterminatedPassList { offset });
}
None => {
return Err(PipelineSyntaxError::UnterminatedPassList { offset });
}
}
}
}
segments.push(PipelineSpec { stage, passes });
match bytes.get(offset) {
None => return Ok(segments),
Some(b',') => offset += 1,
Some(_) => return Err(PipelineSyntaxError::ExpectedCommaOrEnd { offset }),
}
}
}
#[must_use]
pub fn print_pipelines(segments: &[PipelineSpec<'_>]) -> String {
let mut out = String::default();
for (index, segment) in segments.iter().enumerate() {
if index > 0 {
out.push(',');
}
out.push_str(segment.stage);
out.push('(');
for (position, name) in segment.passes.iter().enumerate() {
if position > 0 {
out.push(',');
}
out.push_str(name);
}
out.push(')');
}
out
}