use crate::filesize::{FileSize, Suffix};
use crate::parse::Stream;
use crate::parse::{LexContext, ParseError, Phase};
use os_str_bytes::OsStringBytes;
use std::ffi::OsString;
use std::fmt::{self, Debug, Formatter};
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Raw(Vec<u8>),
CliArg(isize),
EnvVar(OsString),
Variable(String),
Number(f64),
DateTime(crate::DateTime),
FileSize { fs: crate::FileSize, precision: u8 },
BeginPath,
EndPath,
BeginString,
EndString,
BeginRegex,
EndRegex(Vec<u8>),
BeginBraceEscape, EndBraceEscape,
BeginBlockStart, EndBlockStart, LeftParen, RightParen,
Question, Colon, Comma, Semicolon, And, Or, Assign,
Add, AddAssign, Subtract, SubtractAssign, Multiply, MultiplyAssign, Divide, DivideAssign, Modulo, ModuloAssign,
Not, Matches, NotMatches, Equal, NotEqual, LessThanOrEqual, LessThan, GreaterThanOrEqual, GreaterThan, }
fn append(vec: &mut Vec<u8>, chr: char) {
vec.push(u8::try_from(chr).expect("todo: actually translate it over"));
}
fn parse_hex(byte: u8) -> Result<u32, ParseError> {
(byte as char).to_digit(16).ok_or(ParseError::BadEscape("not a hex digit"))
}
impl<'a> Stream<'a> {
fn parse_escape(&mut self, is_path: bool) -> Result<char, ParseError> {
match self.take().ok_or(ParseError::BadEscape("nothing after backslash"))? {
c @ (b'\\' | b'\"' | b'\'' | b'$' | b'{') => Ok(c as char),
c @ (b'*' | b'?' | b'[') if is_path => Ok(c as char),
b'n' => Ok('\n'),
b't' => Ok('\t'),
b'r' => Ok('\r'),
b'0' => Ok('\0'),
b'x' => {
let [a, b] = self.take_n().ok_or(ParseError::BadEscape("nothing after `x`"))?;
let hexbyte = (parse_hex(a)? << 4) | parse_hex(b)?;
char::from_u32(hexbyte).ok_or(ParseError::BadEscape("invalid `\\u` escape"))
}
b'u' => {
let [a, b, c, d] = self.take_n().ok_or(ParseError::BadEscape("nothing after `u`"))?;
let hexnum = (parse_hex(a)? << 12)
| (parse_hex(b)? << 8)
| (parse_hex(c)? << 4)
| (parse_hex(d)? << 0);
char::from_u32(hexnum).ok_or(ParseError::BadEscape("invalid `\\u` escape"))
}
b'U' => todo!(),
other => Err(ParseError::InvalidEscape(other as char)),
}
}
fn parse_digits(&mut self, into: &mut String) -> bool {
if !self.peek().map_or(false, |x| x.is_ascii_digit()) {
return false;
}
into.extend(
self
.take_while(|c| c.is_ascii_digit() || c == b'_') .iter()
.filter_map(|c| c.is_ascii_digit().then_some(*c as char)), );
true
}
fn parse_number_sign(&mut self, buf: &mut String) {
if let Some(sign) = self.take_if(|c| c == b'-' || c == b'+') {
buf.push(sign as char);
}
}
fn parse_base_integer(&mut self) -> Result<Option<u64>, ParseError> {
if !self.advance_if(b'0') {
return Ok(None);
}
let radix = match self.take_if(|c| b"xXoObB".contains(&c)) {
Some(b'x' | b'X') => 16,
Some(b'o' | b'O') => 8,
Some(b'b' | b'B') if self.peek().map_or(false, |c| c.is_ascii_digit()) => 2,
Some(b'b' | b'B') => {
self.untake();
self.untake();
return Ok(None);
}
Some(_) => unreachable!(),
None => {
self.untake();
return Ok(None);
}
};
let buf = self
.take_while(|c| (c as char).to_digit(radix).is_some() || c == b'_')
.iter()
.filter_map(|&x| (x != b'_').then_some(x as char))
.collect::<String>();
u64::from_str_radix(&buf, radix).map(Some).or(Err(ParseError::BadFloat))
}
fn parse_float(&mut self) -> Result<(f64, u8), ParseError> {
let mut buf = String::new();
self.parse_number_sign(&mut buf);
if !self.parse_digits(&mut buf) {
return Err(ParseError::BadFloat);
}
let precision = if self.advance_if(b'.') {
buf.push('.');
if !self.parse_digits(&mut buf) {
return Err(ParseError::BadFloat);
}
buf.bytes().rev().take_while(|&x| x == b'0').count() as u8
} else {
0
};
if self.advance_if(|c| c == b'e' || c == b'E') {
buf.push('e');
self.parse_number_sign(&mut buf);
if !self.parse_digits(&mut buf) {
return Err(ParseError::BadFloat);
}
}
let float = buf.parse().or(Err(ParseError::BadFloat))?;
Ok((float, precision))
}
fn strip_whitespace_and_comments(&mut self) {
loop {
if !self.take_while(|c| c.is_ascii_whitespace()).is_empty() {
continue;
}
if self.advance_if(b'#') {
let _ = self.take_while(|c| c != b'\n');
continue;
}
break;
}
}
}
fn is_ascii_alphanumeric_or_underscore(c: u8) -> bool {
c.is_ascii_alphanumeric() || c == b'_'
}
fn is_path_literal_character(c: u8) -> bool {
!c.is_ascii_whitespace() && !b",();&|".contains(&c)
}
fn is_path_start(byte: u8) -> bool {
is_ascii_alphanumeric_or_underscore(byte) || b"/.+*[?".contains(&byte)
}
fn is_path_end(byte: u8) -> bool {
b",();&|".contains(&byte) || byte.is_ascii_whitespace()
}
impl Token {
fn parse_within_path(lctx: &mut LexContext) -> Result<Self, ParseError> {
let mut buf = Vec::new();
let mut in_brace = false;
while let Some(c) = lctx.stream.take() {
match c {
b'$' if lctx.stream.take_if(b'{').is_some() => {
lctx.push_phase(Phase::BraceEscape);
lctx.push_token(Token::BeginBraceEscape);
break;
}
b'$' => {
lctx.push_phase(Phase::DollarSignEscape);
break;
}
b'\\' => append(&mut buf, lctx.stream.parse_escape(true)?),
_ if !in_brace && !is_path_literal_character(c) => {
lctx.stream.untake();
lctx.pop_phase(Phase::WithinPath);
lctx.push_token(Token::EndPath);
break;
}
_ => {
if c == b'{' {
in_brace = true;
} else if c == b'}' {
in_brace = false;
}
append(&mut buf, c as char)
}
}
}
Ok(Self::Raw(buf))
}
fn parse_cli_arg(lctx: &mut LexContext, braced: bool) -> Result<Self, ParseError> {
let mut buf = String::new();
if lctx.stream.advance_if(b'-') {
buf.push('-');
} else {
let _ = lctx.stream.advance_if(b'+'); }
if !lctx.stream.parse_digits(&mut buf) {
return Err(ParseError::CliArgMissing);
}
if braced && !lctx.stream.advance_if(b'}') {
return Err(ParseError::MissingEndingBrace);
}
buf.parse::<isize>().map(Self::CliArg).or(Err(ParseError::CliArgTooLarge))
}
fn parse_env_var(lctx: &mut LexContext, braced: bool) -> Result<Self, ParseError> {
let buf = lctx.stream.take_while(is_ascii_alphanumeric_or_underscore);
debug_assert!(!buf.is_empty());
if braced && !lctx.stream.advance_if(b'}') {
return Err(ParseError::MissingEndingBrace);
}
Ok(Self::EnvVar(OsString::assert_from_raw_vec(buf.to_owned())))
}
fn parse_dollar_sign_escape(lctx: &mut LexContext) -> Result<Self, ParseError> {
let result = Self::parse_dollar_sign(lctx)?;
lctx.pop_phase(Phase::DollarSignEscape);
Ok(result)
}
fn parse_dollar_sign(lctx: &mut LexContext) -> Result<Self, ParseError> {
let braced = lctx.stream.advance_if(b'{');
match lctx.stream.peek().expect("called parse_dollar_sign at eof") {
x if x.is_ascii_digit() || x == b'-' || x == b'+' => Self::parse_cli_arg(lctx, braced),
x if x.is_ascii_alphabetic() || x == b'_' => Self::parse_env_var(lctx, braced),
_ => Err(ParseError::InvalidDollarSign),
}
}
fn parse_within_string(lctx: &mut LexContext) -> Result<Self, ParseError> {
let mut buf = Vec::new();
while let Some(c) = lctx.stream.take() {
match c {
b'$' => {
lctx.push_phase(Phase::DollarSignEscape);
break;
}
b'{' => {
lctx.push_phase(Phase::BraceEscape);
lctx.push_token(Token::BeginBraceEscape);
break;
}
b'\\' => append(&mut buf, lctx.stream.parse_escape(false)?),
b'"' => {
lctx.pop_phase(Phase::WithinString);
lctx.push_token(Token::EndString);
break;
}
_ => buf.push(c),
}
}
Ok(Self::Raw(buf))
}
fn parse_within_regex(lctx: &mut LexContext) -> Result<Self, ParseError> {
let mut buf = Vec::new();
while let Some(c) = lctx.stream.take() {
match c {
b'$' => {
lctx.push_phase(Phase::DollarSignEscape);
break;
}
b'{' => {
lctx.push_phase(Phase::BraceEscape);
lctx.push_token(Token::BeginBraceEscape);
break;
}
b'/' => {
let flags = lctx.stream.take_while(|c| c.is_ascii_alphabetic());
lctx.pop_phase(Phase::WithinRegex);
lctx.push_token(Token::EndRegex(flags.to_owned()));
break;
}
_ => buf.push(c),
}
}
Ok(Self::Raw(buf))
}
pub fn parse(lctx: &mut LexContext) -> Result<Option<Self>, ParseError> {
Self::_parse(lctx) }
fn _parse(lctx: &mut LexContext) -> Result<Option<Self>, ParseError> {
if lctx.stream.is_eof() {
return match lctx.pop_phase_unchecked() {
Some(Phase::WithinPath) => Ok(Some(Self::EndPath)),
Some(Phase::WithinString) => Err(ParseError::MissingEndQuote),
Some(Phase::WithinRegex) => Err(ParseError::MissingEndRegex),
None => Ok(None),
other => unimplemented!("todo: what are the conditions that this can happen?"),
};
}
match lctx.phase() {
Some(Phase::WithinPath) => Self::parse_within_path(lctx).map(Some),
Some(Phase::WithinString) => Self::parse_within_string(lctx).map(Some),
Some(Phase::WithinRegex) => Self::parse_within_regex(lctx).map(Some),
Some(Phase::DollarSignEscape) => Self::parse_dollar_sign_escape(lctx).map(Some),
Some(Phase::BraceEscape) => {
match Self::parse_normal(lctx) {
Ok(None) => Err(ParseError::MissingEndingBrace),
Ok(Some(Self::EndBraceEscape)) => {
lctx.pop_phase(Phase::BraceEscape);
debug_assert_ne!(lctx.phase(), None); Ok(Some(Token::EndBraceEscape))
}
other => other,
}
}
None => Self::parse_normal(lctx),
}
}
fn parse_number(lctx: &mut LexContext) -> Result<Self, ParseError> {
let (num, precision) = if let Some(integer) = lctx.stream.parse_base_integer()? {
(integer as f64, None)
} else {
lctx.stream.parse_float().map(|(x, y)| (x, Some(y)))?
};
let suffix = lctx.stream.take_while(|c| c.is_ascii_alphabetic());
if suffix.is_empty() {
return Ok(Self::Number(num));
};
if let Some(suffix) = Suffix::from_bytes(suffix) {
return Ok(Self::FileSize {
fs: FileSize::new(num, suffix).ok_or(ParseError::FileSizeLiteralTooLarge)?,
precision: precision.unwrap_or(0),
});
}
todo!()
}
fn _is_path_next_token(stream: &mut Stream) -> bool {
debug_assert!(!stream.is_eof()); let rest = stream.remainder();
if b"*.+/~".contains(&rest[0]) {
return true;
}
if !is_path_start(rest[0]) {
return false;
}
for &byte in rest {
if (byte as char) == std::path::MAIN_SEPARATOR {
return true;
}
if is_path_end(byte) {
break;
}
}
false
}
fn parse_path_glob(lctx: &mut LexContext) -> Result<Option<Self>, ParseError> {
if !Self::_is_path_next_token(&mut lctx.stream) {
return Ok(None);
}
lctx.push_phase(Phase::WithinPath);
Ok(Some(Self::BeginPath))
}
fn parse_normal(lctx: &mut LexContext) -> Result<Option<Self>, ParseError> {
lctx.stream.strip_whitespace_and_comments();
if lctx.stream.is_eof() {
return Err(ParseError::Eof);
}
if let Some(pathglob) = Self::parse_path_glob(lctx)? {
return Ok(Some(pathglob));
}
let c = lctx.stream.take().ok_or(ParseError::Eof)?;
macro_rules! ifeq {
($if_eq:ident, $if_not:ident) => {
Ok(Some(if lctx.stream.advance_if(b'=') { Self::$if_eq } else { Self::$if_not }))
};
}
match c {
b'"' => {
lctx.push_phase(Phase::WithinString);
Ok(Some(Self::BeginString))
}
b'$' if lctx.stream.advance_if(b'/') => {
lctx.push_phase(Phase::WithinRegex);
Ok(Some(Self::BeginRegex))
}
b'$' if lctx.stream.advance_if(b'{') => Ok(Some(Self::EndBlockStart)),
b'^' if lctx.stream.advance_if(b'{') => Ok(Some(Self::BeginBlockStart)),
b'}' => Ok(Some(Self::EndBraceEscape)),
b'(' => Ok(Some(Self::LeftParen)),
b')' => Ok(Some(Self::RightParen)),
b'?' => Ok(Some(Self::Question)), b':' => Ok(Some(Self::Colon)),
b',' => Ok(Some(Self::Comma)),
b';' => Ok(Some(Self::Semicolon)),
b'&' if lctx.stream.advance_if(b'&') => Ok(Some(Self::And)),
b'|' if lctx.stream.advance_if(b'|') => Ok(Some(Self::Or)),
b'+' => ifeq!(AddAssign, Add),
b'-' => ifeq!(SubtractAssign, Subtract),
b'@' => todo!("parse `@` strings (like `%` strings in ruby)"),
b'!'
if lctx.stream.peek() == Some(b'~')
&& lctx.stream.remainder().get(1) != Some(&b'/') =>
{
lctx.stream.take();
Ok(Some(Self::NotMatches))
}
b'!' => ifeq!(NotEqual, Not),
b'=' if lctx.stream.advance_if(b'~') => Ok(Some(Self::Matches)),
b'=' => ifeq!(Equal, Assign),
b'<' => ifeq!(LessThanOrEqual, LessThan),
b'>' => ifeq!(GreaterThanOrEqual, GreaterThan),
b'$' => Self::parse_dollar_sign(lctx).map(Some),
x if x.is_ascii_alphabetic() || c == b'_' => {
lctx.stream.untake();
let mut was_last_questionmark = false;
let buf = lctx.stream.take_while(|c| {
if was_last_questionmark {
false
} else if c == b'?' {
was_last_questionmark = true;
true
} else {
is_ascii_alphanumeric_or_underscore(c)
}
});
match buf {
b"mul" => ifeq!(MultiplyAssign, Multiply),
b"div" => ifeq!(DivideAssign, Divide),
b"mod" => ifeq!(ModuloAssign, Modulo),
_ => Ok(Some(Self::Variable(
String::from_utf8(buf.to_owned()).or(Err(ParseError::VariableIsntUtf8))?,
))),
}
}
x if x.is_ascii_digit() => {
lctx.stream.untake();
Self::parse_number(lctx).map(Some)
}
_ => Err(ParseError::UnknownTokenStart(c as char)),
}
}
}