176 lines
4.5 KiB
Rust
176 lines
4.5 KiB
Rust
use nom::Parser;
|
|
|
|
use super::{
|
|
SpannedData,
|
|
StringPart,
|
|
SpanRef,
|
|
PResult,
|
|
Literal,
|
|
SpanExt,
|
|
Interpolate,
|
|
IResultExt,
|
|
Token,
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Path {
|
|
pub base: SpannedData<String>,
|
|
pub additional: Vec<StringPart>,
|
|
}
|
|
|
|
fn path_separator(span: SpanRef) -> PResult<SpanRef> {
|
|
nom::sequence::preceded(
|
|
nom::sequence::pair(
|
|
nom::combinator::not(nom::bytes::complete::tag("//")),
|
|
nom::combinator::not(nom::bytes::complete::tag("/*")),
|
|
),
|
|
nom::bytes::complete::tag("/"),
|
|
)(span)
|
|
}
|
|
|
|
struct PathBuilder<'a> {
|
|
span: SpanRef<'a>,
|
|
cur_lit_start: Option<usize>,
|
|
base: Option<SpannedData<String>>,
|
|
additional: Vec<StringPart>,
|
|
}
|
|
|
|
impl<'a> PathBuilder<'a> {
|
|
fn new(span: SpanRef<'a>) -> Self {
|
|
Self {
|
|
span,
|
|
cur_lit_start: Some(0),
|
|
base: None,
|
|
additional: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn add_lit(&mut self, lit_span: SpanRef) {
|
|
use nom::Offset;
|
|
|
|
if self.cur_lit_start.is_none() {
|
|
self.cur_lit_start = Some(self.span.offset(&lit_span));
|
|
}
|
|
}
|
|
|
|
fn _end_lit(&mut self, next_span: SpanRef) {
|
|
use nom::{Offset, Slice};
|
|
|
|
if let Some(start) = self.cur_lit_start.take() {
|
|
let end = self.span.offset(&next_span);
|
|
let lit_span = self.span.slice(start..end);
|
|
let lit = lit_span.data(lit_span.as_str().into());
|
|
if self.additional.is_empty() {
|
|
assert!(self.base.is_none());
|
|
self.base = Some(lit);
|
|
} else {
|
|
self.additional.push(StringPart::Literal(Literal::from(lit_span)));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_interp(&mut self, span: SpanRef, interp: Interpolate) {
|
|
self._end_lit(span);
|
|
|
|
assert!(self.base.is_some());
|
|
self.additional.push(StringPart::Interpolate(span.data(interp)));
|
|
}
|
|
|
|
fn build(mut self, rem_span: SpanRef<'_>) -> SpannedData<Path> {
|
|
use nom::{Offset, Slice};
|
|
|
|
self._end_lit(rem_span);
|
|
let path = Path {
|
|
base: self.base.take().expect("base can't be empty here"),
|
|
additional: self.additional,
|
|
};
|
|
|
|
let end = self.span.offset(&rem_span);
|
|
let path_span = self.span.slice(..end);
|
|
|
|
path_span.data(path)
|
|
}
|
|
}
|
|
|
|
impl Path {
|
|
pub(super) fn parse(span: SpanRef) -> PResult<SpannedData<Token>> {
|
|
// first segment before a '/' - possibly empty
|
|
let mut first_segment = nom::combinator::opt(
|
|
nom::branch::alt((
|
|
// `~` only allowed as first (full) segment
|
|
nom::bytes::complete::tag("~").map(|_| ()),
|
|
nom::sequence::pair(
|
|
nom::branch::alt((
|
|
nom::character::complete::alphanumeric1.map(|_| ()),
|
|
nom::bytes::complete::tag("-").map(|_| ()),
|
|
nom::bytes::complete::tag("_").map(|_| ()),
|
|
nom::bytes::complete::tag(".").map(|_| ()),
|
|
)),
|
|
nom::multi::many0_count(nom::branch::alt((
|
|
nom::character::complete::alphanumeric1.map(|_| ()),
|
|
nom::bytes::complete::tag("-").map(|_| ()),
|
|
nom::bytes::complete::tag("_").map(|_| ()),
|
|
nom::bytes::complete::tag(".").map(|_| ()),
|
|
))),
|
|
).map(|_| ()),
|
|
))
|
|
);
|
|
|
|
// segments after the first / contain combinations of literal parts and ${...} expressions
|
|
let mut later_segment_literal = nom::combinator::recognize(
|
|
nom::multi::many1_count(nom::branch::alt((
|
|
nom::character::complete::alphanumeric1.map(|_| ()),
|
|
nom::bytes::complete::tag("-").map(|_| ()),
|
|
nom::bytes::complete::tag("_").map(|_| ()),
|
|
nom::bytes::complete::tag(".").map(|_| ()),
|
|
))),
|
|
);
|
|
|
|
let (mut rem_span, _) = first_segment(span)?;
|
|
path_separator(rem_span)?; // shortcut if it can't be a path
|
|
|
|
let mut found_separators = 0;
|
|
let mut path = PathBuilder::new(span);
|
|
|
|
while let Ok((next_span, sep_span)) = path_separator(rem_span) {
|
|
found_separators += 1;
|
|
path.add_lit(sep_span);
|
|
rem_span = next_span;
|
|
let mut parts = 0;
|
|
loop {
|
|
if let Ok((next_span, (interp_span, interp))) = nom::combinator::consumed(Interpolate::parse)(rem_span) {
|
|
path.add_interp(interp_span, interp.data);
|
|
rem_span = next_span;
|
|
parts += 1;
|
|
continue;
|
|
}
|
|
match later_segment_literal(rem_span) as PResult<SpanRef<'_>> {
|
|
Ok((next_span, lit_span)) => {
|
|
path.add_lit(lit_span);
|
|
rem_span = next_span;
|
|
parts += 1;
|
|
},
|
|
Err(_e) => {
|
|
if parts == 0 {
|
|
// trailing slash
|
|
if found_separators == 1 {
|
|
// only one slash, and it is trailing -> not a path.
|
|
return nom::combinator::fail(rem_span);
|
|
} else {
|
|
// invalid path - trailing slash not allowed
|
|
// TODO: proper error message
|
|
return nom::combinator::fail(rem_span).unrecoverable();
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert!(found_separators >= 1); // we check for initial separator above
|
|
|
|
Ok((rem_span, path.build(rem_span).into()))
|
|
}
|
|
}
|