use super::{
word::{self, IsWord},
expansion::{self, Expansion, ExpansionContext},
ArgPart,
ArgUnit,
Command,
Cursor,
Checkpoint,
Error,
SourcePos,
State,
SymbolInterner,
Token,
TokenKind,
Transition,
};
use crate::symbol::Symbol;
pub(super) trait WordContext: Sized {
fn resume_produce(self, value: Vec<u8>) -> Transition;
fn expansion_start(state: Word<Self>, cursor: &Cursor, value: u8) -> Result<Transition, Word<Self>>;
fn is_word(value: u8) -> bool;
fn validate_escape(value: u8) -> Option<u8>;
}
#[derive(Debug)]
pub(super) struct Word<C> {
value: Vec<u8>,
escaping: Option<(usize, SourcePos)>,
allow_expansion_start: bool,
context: C,
}
impl<C> Word<C>
where
C: WordContext,
State: From<Self>,
{
pub fn visit(mut self, cursor: &Cursor) -> Transition {
match (&self, cursor.peek()) {
(&Self { escaping: Some((offset, pos)), .. }, Some(value)) => {
if let Some(c) = C::validate_escape(value) {
self.value.push(c);
self.escaping = None;
Transition::step(self)
} else {
let escape_sequence = &cursor.slice()[offset ..= cursor.offset()];
Transition::error(self, Error::invalid_escape_sequence(escape_sequence, pos))
}
}
(_, Some(b'\\')) => {
self.escaping = Some((cursor.offset(), cursor.pos()));
Transition::step(self)
}
(_, Some(c)) if C::is_word(c) && self.allow_expansion_start => {
match C::expansion_start(self, cursor, c) {
Ok(transition) => transition,
Err(mut state) => {
state.value.push(c);
Transition::step(state)
},
}
}
(_, Some(c)) if C::is_word(c) => {
self.value.push(c);
self.allow_expansion_start = true;
Transition::step(self)
}
_ => self.context.resume_produce(self.value),
}
}
}
impl<C: WordContext> From<C> for Word<C> {
fn from(context: C) -> Self {
Self {
value: Vec::with_capacity(8), allow_expansion_start: true,
escaping: None,
context,
}
}
}
impl From<Word<Argument>> for State {
fn from(state: Word<Argument>) -> State {
Self::UnquotedWord(state)
}
}
impl From<Word<SingleQuoted>> for State {
fn from(state: Word<SingleQuoted>) -> State {
Self::SingleQuotedWord(state)
}
}
impl From<Word<DoubleQuoted>> for State {
fn from(state: Word<DoubleQuoted>) -> State {
Self::DoubleQuotedWord(state)
}
}
pub(super) trait DollarContext {
fn produce(self, symbol: Symbol, pos: SourcePos) -> Transition;
fn error(self, error: Error) -> Transition;
fn resume(self, symbol: Symbol, pos: SourcePos) -> Transition;
fn resume_error(self, error: Error) -> Transition;
}
impl DollarContext for Argument {
fn produce(mut self, symbol: Symbol, pos: SourcePos) -> Transition {
self.parts.push(ArgPart::Unquoted(ArgUnit::Dollar { symbol, pos }));
Transition::step(self)
}
fn error(self, error: Error) -> Transition {
Transition::error(self, error)
}
fn resume(mut self, symbol: Symbol, pos: SourcePos) -> Transition {
self.parts.push(ArgPart::Unquoted(ArgUnit::Dollar { symbol, pos }));
Transition::resume(self)
}
fn resume_error(self, error: Error) -> Transition {
Transition::resume_error(self, error)
}
}
impl DollarContext for DoubleQuoted {
fn produce(mut self, symbol: Symbol, pos: SourcePos) -> Transition {
self.parts.push(ArgUnit::Dollar { symbol, pos });
Transition::step(self)
}
fn error(self, error: Error) -> Transition {
Transition::error(self, error)
}
fn resume(mut self, symbol: Symbol, pos: SourcePos) -> Transition {
self.parts.push(ArgUnit::Dollar { symbol, pos });
Transition::resume(self)
}
fn resume_error(self, error: Error) -> Transition {
Transition::resume_error(self, error)
}
}
#[derive(Debug)]
pub(super) struct Dollar<C> {
start_offset: Option<usize>,
braces: Option<bool>,
error: bool,
pos: SourcePos,
context: C,
}
impl<C> Dollar<C>
where
C: DollarContext + std::fmt::Debug,
State: From<Self>,
{
pub fn at(cursor: &Cursor, context: C) -> Self {
Self {
start_offset: None,
braces: None,
error: false,
pos: cursor.pos(),
context,
}
}
pub fn visit(mut self, cursor: &Cursor, interner: &mut SymbolInterner) -> Transition {
macro_rules! produce {
($consume:expr) => {{
let offset = self.start_offset.unwrap_or(cursor.offset());
let identifier = &cursor.slice()[offset .. cursor.offset()];
if identifier.is_empty() || self.error {
return self.context
.error(Error::invalid_identifier(identifier, self.pos))
}
match word::to_token(identifier, interner) {
TokenKind::Identifier(symbol) => {
if $consume {
self.context.produce(symbol, self.pos)
} else {
self.context.resume(symbol, self.pos)
}
}
_ => {
let error = Error::invalid_identifier(identifier, self.pos);
if $consume {
self.context.error(error)
} else {
self.context.resume_error(error)
}
}
}
}};
}
match (&self, cursor.peek()) {
(&Self { start_offset: None, braces: None, .. }, Some(b'{')) => {
self.braces = Some(true);
Transition::step(self)
}
(&Self { braces: Some(true), .. }, Some(b'}')) => produce!(true),
(&Self { start_offset: None, .. }, Some(c)) => {
self.start_offset = Some(cursor.offset());
if !c.is_word_start() {
self.error = true;
}
if self.braces == None {
self.braces = Some(false);
}
Transition::step(self)
}
(&Self { start_offset: Some(_), braces: Some(false), .. }, Some(c)) => {
if !c.is_word() {
produce!(false)
} else {
Transition::step(self)
}
}
(&Self { start_offset: Some(_), .. }, Some(c)) => {
if !c.is_word() {
self.error = true;
}
Transition::step(self)
}
(&Self { braces: Some(true), .. }, None) => {
self.context.error(Error::unexpected_eof(cursor.pos()))
}
(_, None) => produce!(true),
}
}
}
impl From<Dollar<Argument>> for State {
fn from(state: Dollar<Argument>) -> State {
Self::Dollar(state)
}
}
impl From<Dollar<DoubleQuoted>> for State {
fn from(state: Dollar<DoubleQuoted>) -> State {
Self::QuotedDollar(state)
}
}
#[derive(Debug)]
pub(super) struct SingleQuoted {
value: Vec<u8>,
parent: Argument,
}
impl SingleQuoted {
pub fn visit(mut self, cursor: &Cursor) -> Transition {
match cursor.peek() {
Some(b'\'') => {
self.parent
.parts
.push(ArgPart::SingleQuoted(self.value.into_boxed_slice()));
Transition::step(self.parent)
}
Some(_) => Transition::resume(Word::from(self)),
None => Transition::error(self.parent, Error::unexpected_eof(cursor.pos())),
}
}
}
impl From<Argument> for SingleQuoted {
fn from(parent: Argument) -> Self {
Self {
value: Vec::with_capacity(8), parent,
}
}
}
impl WordContext for SingleQuoted {
fn resume_produce(mut self, value: Vec<u8>) -> Transition {
self.value = value;
Transition::resume(self)
}
fn is_word(value: u8) -> bool {
value != b'\''
}
fn expansion_start(state: Word<Self>, _: &Cursor, _: u8) -> Result<Transition, Word<Self>> {
Err(state) }
fn validate_escape(value: u8) -> Option<u8> {
match value {
b'\'' => Some(value),
b'n' => Some(b'\n'),
b't' => Some(b'\t'),
b'0' => Some(b'\0'),
b'\\' => Some(b'\\'),
_ => None,
}
}
}
impl From<SingleQuoted> for State {
fn from(state: SingleQuoted) -> State {
Self::SingleQuoted(state)
}
}
#[derive(Debug)]
pub(super) struct DoubleQuoted {
parts: Vec<ArgUnit>,
parent: Argument,
}
impl DoubleQuoted {
pub fn visit(mut self, cursor: &Cursor) -> Transition {
match cursor.peek() {
Some(b'\"') => {
self.parent
.parts
.push(ArgPart::DoubleQuoted(self.parts.into_boxed_slice()));
Transition::step(self.parent)
}
Some(b'$') => Transition::step(Dollar::at(cursor, self)),
Some(_) => Transition::resume(Word::from(self)),
None => Transition::error(self.parent, Error::unexpected_eof(cursor.pos())),
}
}
}
impl From<Argument> for DoubleQuoted {
fn from(parent: Argument) -> Self {
Self {
parts: Vec::with_capacity(1), parent,
}
}
}
impl WordContext for DoubleQuoted {
fn resume_produce(mut self, value: Vec<u8>) -> Transition {
self.parts.push(ArgUnit::Literal(value.into_boxed_slice()));
Transition::resume(self)
}
fn is_word(value: u8) -> bool {
value != b'"' && value != b'$'
}
fn expansion_start(state: Word<Self>, _: &Cursor, _: u8) -> Result<Transition, Word<Self>> {
Err(state) }
fn validate_escape(value: u8) -> Option<u8> {
match value {
b'"' => Some(value), b'$' => Some(value),
b'n' => Some(b'\n'),
b't' => Some(b'\t'),
b'0' => Some(b'\0'),
b'\\' => Some(b'\\'),
_ => None,
}
}
}
impl From<DoubleQuoted> for State {
fn from(state: DoubleQuoted) -> State {
Self::DoubleQuoted(state)
}
}
#[derive(Debug)]
pub(super) struct Argument {
parts: Vec<ArgPart>,
allow_home: bool,
pos: SourcePos,
}
impl Argument {
pub fn at(cursor: &Cursor) -> Self {
Self {
parts: Vec::with_capacity(1), allow_home: true, pos: cursor.pos(),
}
}
pub fn visit(mut self, cursor: &Cursor) -> Transition {
let allow_home = self.allow_home;
self.allow_home = false;
match cursor.peek() {
Some(b'$') => Transition::step(Dollar::at(cursor, self)),
Some(b'\'') => Transition::step(SingleQuoted::from(self)),
Some(b'"') => Transition::step(DoubleQuoted::from(self)),
Some(b'=') => {
self.parts.push(ArgPart::EnvAssign);
Transition::step(self)
}
Some(c) if expansion::is_start(c) => Transition::resume(
Expansion::at(cursor, allow_home, self)
),
Some(c) if Self::is_word(c) => Transition::resume(Word::from(self)),
_ => Transition::resume_produce(
Command,
Token {
kind: TokenKind::Argument(self.parts.into_boxed_slice()),
pos: self.pos,
},
),
}
}
}
impl WordContext for Argument {
fn resume_produce(mut self, value: Vec<u8>) -> Transition {
self.parts.push(ArgPart::Unquoted(ArgUnit::Literal(
value.into_boxed_slice(),
)));
Transition::resume(self)
}
fn is_word(value: u8) -> bool {
match value {
b'#' => false, b'\'' | b'"' => false, b'>' | b'<' | b'?' | b';' => false, b'$' => false, b'=' => false, b'}' => false, c if c.is_ascii_whitespace() => false, _ => true,
}
}
fn expansion_start(state: Word<Self>, cursor: &Cursor, value: u8) -> Result<Transition, Word<Self>> {
if expansion::is_start(value) {
Ok(
Transition::resume(
Expansion::at(cursor, false, state)
)
)
} else {
Err(state)
}
}
fn validate_escape(value: u8) -> Option<u8> {
match value {
b'#' => Some(value), b'\'' | b'"' => Some(value), b'>' | b'<' | b'?' | b';' => Some(value), b'$' => Some(value), b'=' => Some(value), c if c.is_ascii_whitespace() => Some(value),
b'n' => Some(b'\n'),
b't' => Some(b'\t'),
b'0' => Some(b'\0'),
b'\\' => Some(b'\\'),
_ => None,
}
}
}
impl ExpansionContext for Argument {
fn produce(mut self, expansion: crate::syntax::lexer::ArgExpansion) -> Transition {
self.parts.push(
ArgPart::Expansion(expansion)
);
Transition::step(self)
}
fn rollback(self, checkpoint: Checkpoint) -> Transition {
Transition::rollback(checkpoint, Word::from(self))
}
fn is_expansion_word(value: u8) -> bool {
Self::is_word(value)
}
}
impl ExpansionContext for Word<Argument> {
fn produce(self, expansion: crate::syntax::lexer::ArgExpansion) -> Transition {
let mut argument_state = self.context;
argument_state.parts.push(ArgPart::Unquoted(ArgUnit::Literal(
self.value.into_boxed_slice(),
)));
argument_state.parts.push(
ArgPart::Expansion(expansion)
);
Transition::step(argument_state)
}
fn rollback(mut self, checkpoint: Checkpoint) -> Transition {
self.allow_expansion_start = false;
Transition::rollback(checkpoint, self)
}
fn is_expansion_word(value: u8) -> bool {
Argument::is_word(value)
}
}
impl From<Argument> for State {
fn from(state: Argument) -> State {
Self::Argument(state)
}
}