#![allow(clippy::redundant_pub_crate)]
use std::rc::Rc;
use crate::error::{RableError, Result};
use crate::token::{Token, TokenType};
mod brace_expansion;
mod expansions;
pub(crate) mod heredoc;
mod operators;
mod quotes;
pub(super) mod word_builder;
mod words;
#[cfg(test)]
mod backtick_opaque_tests;
#[cfg(test)]
mod tests;
pub(crate) use heredoc::PendingHereDoc;
#[derive(Debug, Clone, Copy)]
struct LexerConfig {
extglob: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct LexerContext {
pub(crate) command_start: bool,
pub(crate) reserved_words_ok: bool,
pub(crate) cond_expr: bool,
}
impl Default for LexerContext {
fn default() -> Self {
Self {
command_start: true,
reserved_words_ok: true,
cond_expr: false,
}
}
}
pub(crate) struct Lexer {
input: Rc<[char]>,
pos: usize,
line: usize,
peeked: Option<Token>,
config: LexerConfig,
pub(crate) ctx: LexerContext,
pub(crate) pending_heredocs: Vec<PendingHereDoc>,
pub(crate) heredoc_contents: Vec<String>,
last_token_end: usize,
mode: LexerMode,
parser_depth: usize,
}
#[derive(Debug, Clone)]
pub(crate) struct LexerCheckpoint {
pos: usize,
line: usize,
peeked: Option<Token>,
last_token_end: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LexerMode {
Normal,
Cmdsub,
Backtick,
}
impl Lexer {
pub(crate) const fn set_command_start(&mut self) {
self.ctx.command_start = true;
self.ctx.reserved_words_ok = true;
}
pub(crate) const fn enter_cond_expr(&mut self) {
self.ctx.cond_expr = true;
}
pub(crate) const fn leave_cond_expr(&mut self) {
self.ctx.cond_expr = false;
}
pub(crate) const fn set_mode(&mut self, mode: LexerMode) {
self.mode = mode;
}
pub(crate) fn checkpoint(&self) -> LexerCheckpoint {
LexerCheckpoint {
pos: self.pos,
line: self.line,
peeked: self.peeked.clone(),
last_token_end: self.last_token_end,
}
}
pub(crate) fn restore(&mut self, cp: LexerCheckpoint) {
self.pos = cp.pos;
self.line = cp.line;
self.peeked = cp.peeked;
self.last_token_end = cp.last_token_end;
}
}
impl Lexer {
pub(crate) fn new(source: &str, extglob: bool) -> Self {
Self {
input: source.chars().collect::<Vec<_>>().into(),
pos: 0,
line: 1,
peeked: None,
config: LexerConfig { extglob },
ctx: LexerContext::default(),
pending_heredocs: Vec::new(),
heredoc_contents: Vec::new(),
last_token_end: 0,
mode: LexerMode::Normal,
parser_depth: 0,
}
}
pub(crate) fn fork(&self, mode: LexerMode) -> Self {
Self {
input: Rc::clone(&self.input),
pos: self.pos,
line: self.line,
peeked: None,
config: self.config,
ctx: LexerContext::default(),
pending_heredocs: Vec::new(),
heredoc_contents: Vec::new(),
last_token_end: self.pos,
mode,
parser_depth: self.parser_depth,
}
}
pub(crate) const fn parser_depth(&self) -> usize {
self.parser_depth
}
pub(crate) const fn set_parser_depth(&mut self, depth: usize) {
self.parser_depth = depth;
}
pub(crate) fn exit_backtick_fork(&mut self) -> Result<()> {
if self.input.get(self.pos).copied() != Some('`') {
return Err(RableError::matched_pair(
"unterminated backtick",
self.pos,
self.line,
));
}
self.pos += 1;
Ok(())
}
pub(crate) const fn pos(&self) -> usize {
self.pos
}
pub(crate) const fn last_token_end(&self) -> usize {
self.last_token_end
}
pub(crate) const fn line(&self) -> usize {
self.line
}
pub(crate) fn input_len(&self) -> usize {
self.input.len()
}
pub(crate) fn char_after_peeked(&self) -> Option<char> {
self.input.get(self.pos).copied()
}
pub(crate) fn at_end(&self) -> bool {
self.pos >= self.input.len()
}
fn peek_char(&self) -> Option<char> {
self.input.get(self.pos).copied()
}
fn advance_char(&mut self) -> Option<char> {
let ch = self.input.get(self.pos).copied();
if let Some(c) = ch {
self.pos += 1;
if c == '\n' {
self.line += 1;
}
}
ch
}
pub(crate) fn at_backtick_terminator(&self) -> bool {
self.mode == LexerMode::Backtick && self.input.get(self.pos).copied() == Some('`')
}
fn skip_blanks(&mut self) {
loop {
match self.peek_char() {
Some(' ' | '\t') => {
self.advance_char();
}
Some('\\') => {
if self.input.get(self.pos + 1) == Some(&'\n') {
self.advance_char(); self.advance_char(); } else {
break;
}
}
_ => break,
}
}
}
fn skip_comment(&mut self) {
while let Some(c) = self.peek_char() {
if c == '\n' {
break;
}
self.advance_char();
}
}
pub(crate) fn next_token(&mut self) -> Result<Token> {
let tok = if let Some(tok) = self.peeked.take() {
tok
} else {
self.read_token()?
};
self.last_token_end = tok.pos + tok.value.len();
Ok(tok)
}
pub(crate) fn peek_token(&mut self) -> Result<&Token> {
if self.peeked.is_none() {
let tok = self.read_token()?;
self.peeked = Some(tok);
}
self.peeked
.as_ref()
.ok_or_else(|| RableError::parse("unexpected end of input", self.pos, self.line))
}
fn read_token(&mut self) -> Result<Token> {
self.skip_blanks();
if self.at_backtick_terminator() {
return Ok(Token::eof(self.pos, self.line));
}
if self.peek_char() == Some('#') {
self.skip_comment();
self.skip_blanks();
}
if self.at_end() {
return Ok(Token::eof(self.pos, self.line));
}
let start = self.pos;
let line = self.line;
let ch = self
.peek_char()
.ok_or_else(|| RableError::parse("unexpected end of input", self.pos, self.line))?;
match ch {
'\n' => {
self.advance_char();
self.ctx.command_start = true;
self.ctx.reserved_words_ok = true;
if !self.pending_heredocs.is_empty() {
self.read_pending_heredocs();
}
Ok(Token::new(TokenType::Newline, "\n", start, line))
}
'|' => Ok(self.read_pipe_operator(start, line)),
'&' => Ok(self.read_ampersand_operator(start, line)),
';' => Ok(self.read_semicolon_operator(start, line)),
'(' => {
self.advance_char();
Ok(Token::new(TokenType::LeftParen, "(", start, line))
}
')' => {
self.advance_char();
Ok(Token::new(TokenType::RightParen, ")", start, line))
}
'<' => {
if self.input.get(self.pos + 1) == Some(&'(') {
self.read_process_sub_word(start, line)
} else {
Ok(self.read_less_operator(start, line))
}
}
'>' => {
if self.input.get(self.pos + 1) == Some(&'(') {
self.read_process_sub_word(start, line)
} else {
Ok(self.read_greater_operator(start, line))
}
}
_ => self.read_word_token(start, line),
}
}
pub(crate) fn read_until_double_paren(&mut self) -> Result<String> {
self.peeked = None;
let mut result = String::new();
let mut depth = 0i32;
loop {
match self.peek_char() {
Some(')') if depth == 0 => {
self.advance_char();
if self.peek_char() == Some(')') {
self.advance_char();
return Ok(result);
}
result.push(')');
}
Some('(') => {
self.advance_char();
depth += 1;
result.push('(');
}
Some(')') => {
self.advance_char();
depth -= 1;
result.push(')');
}
Some('\\') => {
self.advance_char();
if self.peek_char() == Some('\n') {
self.advance_char();
} else {
result.push('\\');
if let Some(c) = self.advance_char() {
result.push(c);
}
}
}
Some(c) => {
self.advance_char();
result.push(c);
}
None => {
return Err(RableError::matched_pair(
"unterminated ((",
self.pos,
self.line,
));
}
}
}
}
}