use alloc::{string::String, vec::Vec};
use crate::{
packed_list::PathSegmentList,
path::{Drive, Path, Prefix, Root},
};
pub fn parse_path(input: &str) -> Result<Path, &'static str> {
parse(Cursor::new(input.chars().collect()))
}
fn parse(mut cursor: Cursor) -> Result<Path, &'static str> {
let prefix = parse_prefix(&mut cursor);
let drive = parse_drive(&mut cursor);
let root = parse_root(&mut cursor, &prefix, drive.is_some())?;
let (segments, is_dir) = parse_segments(&mut cursor)?;
Ok(Path {
prefix,
drive,
root,
segments,
is_dir,
})
}
fn parse_prefix(cursor: &mut Cursor) -> Option<Prefix> {
let mut clone = cursor.clone();
let first = clone.eat();
let second = clone.eat();
let third = clone.eat();
let fourth = clone.eat();
match (first, second, third, fourth) {
(Some('/'), Some('/'), Some('.'), Some('/')) => {
cursor.eat();
cursor.eat();
cursor.eat();
cursor.eat();
Some(Prefix::Device)
}
(Some('/'), Some('/'), Some('?'), Some('/')) => {
cursor.eat();
cursor.eat();
cursor.eat();
cursor.eat();
Some(Prefix::ExtendedPath)
}
_ => None,
}
}
fn parse_drive(cursor: &mut Cursor) -> Option<Drive> {
match (cursor.first(), cursor.second()) {
(Some(letter), Some(':')) if letter.is_alphabetic() => {
cursor.eat();
cursor.eat();
Some(Drive { letter: letter })
}
_ => None,
}
}
fn parse_root(
cursor: &mut Cursor,
prefix: &Option<Prefix>,
has_drive: bool,
) -> Result<Option<Root>, &'static str> {
if let Some(prefix) = prefix {
match prefix {
Prefix::ExtendedPath => {
let mut clone = cursor.clone();
let first = clone.eat();
let second = clone.eat();
let third = clone.eat();
if let (Some('U'), Some('N'), Some('C')) = (first, second, third) {
cursor.eat(); cursor.eat(); cursor.eat(); let slash = cursor.eat(); let Some('/') = slash else {
return Err(
r"extended-length UNC paths must have a slash after the \\?\UNC prefix",
);
};
return Ok(Some(Root::Unc));
}
}
Prefix::Device => {
if !has_drive {
return Ok(Some(Root::Normal));
}
}
}
}
if let Some('/') = cursor.first() {
if let Some('/') = cursor.second() {
cursor.eat(); cursor.eat(); return Ok(Some(Root::Unc));
} else {
cursor.eat(); return Ok(Some(Root::Normal));
}
}
Ok(None)
}
fn parse_segments(cursor: &mut Cursor) -> Result<(PathSegmentList, bool), &'static str> {
let mut segments = Vec::new();
let mut last = '\0';
while cursor.first().is_some() {
let mut segment = String::new();
while let Some(next) = cursor.eat() {
last = next;
if next == '/' {
if segment.is_empty() {
return Err("path segments cannot be empty");
}
break;
}
segment.push(next);
}
segments.push(segment);
}
let is_dir = last == '/' || last == '.';
Ok((segments.into_iter().collect(), is_dir))
}
#[derive(Debug, Clone)]
struct Cursor {
chars: Vec<char>,
}
impl Cursor {
pub fn new(chars: Vec<char>) -> Self {
Self {
chars: chars
.into_iter()
.map(|c| if c == '\\' { '/' } else { c })
.collect(),
}
}
pub fn eat(&mut self) -> Option<char> {
if self.chars.len() > 0 {
Some(self.chars.remove(0))
} else {
None
}
}
pub fn first(&self) -> Option<char> {
self.chars.first().cloned()
}
pub fn second(&self) -> Option<char> {
self.chars.get(1).cloned()
}
}