rust-nix/src/parser/token/path.rs

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()))
}
}