use crate::error::{RableError, Result};
use crate::token::{Token, TokenType};
mod expansions;
mod heredoc;
mod operators;
mod quotes;
mod words;
#[cfg(test)]
mod tests;
pub use heredoc::PendingHereDoc;
#[derive(Debug, Clone, Copy)]
pub struct LexerConfig {
pub extglob: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct LexerContext {
pub(crate) command_start: bool,
pub(crate) cond_expr: bool,
}
impl Default for LexerContext {
fn default() -> Self {
Self {
command_start: true,
cond_expr: false,
}
}
}
pub struct Lexer {
input: Vec<char>,
pos: usize,
line: usize,
peeked: Option<Token>,
config: LexerConfig,
pub(crate) ctx: LexerContext,
pub pending_heredocs: Vec<PendingHereDoc>,
pub heredoc_contents: Vec<String>,
}
impl Lexer {
pub const fn set_command_start(&mut self) {
self.ctx.command_start = true;
}
pub const fn enter_cond_expr(&mut self) {
self.ctx.cond_expr = true;
}
pub const fn leave_cond_expr(&mut self) {
self.ctx.cond_expr = false;
}
pub const fn extglob(&self) -> bool {
self.config.extglob
}
}
impl Lexer {
pub fn new(source: &str, extglob: bool) -> Self {
Self {
input: source.chars().collect(),
pos: 0,
line: 1,
peeked: None,
config: LexerConfig { extglob },
ctx: LexerContext::default(),
pending_heredocs: Vec::new(),
heredoc_contents: Vec::new(),
}
}
pub const fn pos(&self) -> usize {
self.pos
}
pub const fn line(&self) -> usize {
self.line
}
pub const fn input_len(&self) -> usize {
self.input.len()
}
pub fn char_after_peeked(&self) -> Option<char> {
self.input.get(self.pos).copied()
}
pub const 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
}
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 fn next_token(&mut self) -> Result<Token> {
if let Some(tok) = self.peeked.take() {
return Ok(tok);
}
self.read_token()
}
pub 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.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;
if !self.pending_heredocs.is_empty() {
self.read_pending_heredocs();
}
Ok(Token::new(TokenType::Newline, "\n", start, line))
}
'|' => self.read_pipe_operator(start, line),
'&' => self.read_ampersand_operator(start, line),
';' => 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 {
self.read_less_operator(start, line)
}
}
'>' => {
if self.input.get(self.pos + 1) == Some(&'(') {
self.read_process_sub_word(start, line)
} else {
self.read_greater_operator(start, line)
}
}
_ => self.read_word_token(start, line),
}
}
pub 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(c) => {
self.advance_char();
result.push(c);
}
None => {
return Err(RableError::matched_pair(
"unterminated ((",
self.pos,
self.line,
));
}
}
}
}
}