Skip to content

Commit

Permalink
Use constants for tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
jpikl committed Jul 9, 2023
1 parent fe64adf commit a498ed9
Showing 1 changed file with 58 additions and 30 deletions.
88 changes: 58 additions & 30 deletions src/commands/x/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ use std::iter::Peekable;
use std::str::Chars;
use thiserror::Error;

const EXPR_START: char = '{';
const EXPR_END: char = '}';
const PIPE: char = '|';
const DEFAULT_ESCAPE: char = '\\';
const SINGLE_QUOTE: char = '\'';
const DOUBLE_QUOTE: char = '"';
const ESCAPED_LF: char = 'n';
const ESCAPED_CR: char = 'c';
const ESCAPED_TAB: char = 't';

#[derive(Debug, Clone)]
pub enum Item {
Constant(String),
Expand All @@ -29,7 +39,7 @@ pub struct Error {
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let padding = " ".repeat(self.position);
writeln!(f, "syntax error at position '{}'", self.position.yellow())?;
writeln!(f, "syntax error at position {}", self.position.yellow())?;
writeln!(f)?;
writeln!(f, "{}", self.input)?;
writeln!(f, "{}{}", padding, "^".red().bold())?;
Expand All @@ -39,17 +49,17 @@ impl Display for Error {

#[derive(Debug, Error)]
pub enum ErrorKind {
#[error("the previous '{{' was not closed")]
#[error("the previous {} was not closed", EXPR_START)]
UnexpectedExpressionStart,
#[error("missing command before '|'")]
#[error("missing command before {}", PIPE)]
MissingCommandBefore,
#[error("missing command after '|'")]
#[error("missing command after {}", PIPE)]
MissingCommandAfter,
#[error("missing closing {}", .0)]
MissingClosingQuote(char),
#[error("missing opening '{{'")]
#[error("missing opening {}", EXPR_START)]
MissingExpressionStart,
#[error("missing closing '}}'")]
#[error("missing closing {}", EXPR_END)]
MissingExpressionEnd,
}

Expand All @@ -74,7 +84,7 @@ impl Parser<'_> {
let mut items = Vec::new();

while let Some(char) = self.peek() {
let item = if char == '{' {
let item = if char == EXPR_START {
Item::Expression(self.parse_expression()?)
} else {
Item::Constant(self.parse_constant()?)
Expand All @@ -88,17 +98,19 @@ impl Parser<'_> {
fn parse_expression(&mut self) -> Result<Vec<Command>> {
let start_position = self.position;

self.consume('{');
self.consume(EXPR_START);
self.consume_whitespaces();

let mut calls = Vec::new();
let mut command_expected = false;

while let Some(char) = self.peek() {
match char {
'{' => return Err(self.error(ErrorKind::UnexpectedExpressionStart, self.position)),
'|' => return Err(self.error(ErrorKind::MissingCommandBefore, self.position)),
'}' => {
EXPR_START => {
return Err(self.error(ErrorKind::UnexpectedExpressionStart, self.position))
}
PIPE => return Err(self.error(ErrorKind::MissingCommandBefore, self.position)),
EXPR_END => {
if command_expected {
return Err(self.error(ErrorKind::MissingCommandAfter, self.position));
} else {
Expand All @@ -108,7 +120,7 @@ impl Parser<'_> {
_ => {
calls.push(self.parse_call()?);

if self.try_consume('|') {
if self.try_consume(PIPE) {
self.consume_whitespaces();
command_expected = true;
} else {
Expand All @@ -120,7 +132,7 @@ impl Parser<'_> {

if command_expected {
Err(self.error(ErrorKind::MissingCommandAfter, self.position))
} else if self.try_consume('}') {
} else if self.try_consume(EXPR_END) {
Ok(calls)
} else {
Err(self.error(ErrorKind::MissingExpressionEnd, start_position))
Expand All @@ -134,7 +146,7 @@ impl Parser<'_> {
let mut args = Vec::new();

while let Some(char) = self.peek() {
if char == '|' || char == '}' {
if char == PIPE || char == EXPR_END {
break;
}
args.push(self.parse_arg()?);
Expand All @@ -149,7 +161,7 @@ impl Parser<'_> {

fn parse_arg(&mut self) -> Result<String> {
match self.peek() {
Some(quote @ ('\'' | '"')) => self.parse_quote_arg(quote),
Some(quote @ (SINGLE_QUOTE | DOUBLE_QUOTE)) => self.parse_quote_arg(quote),
Some(_) => self.parse_unquote_arg(),
None => unreachable!("unexpected EOF when parsing arg"),
}
Expand All @@ -165,7 +177,7 @@ impl Parser<'_> {
if char == quote {
self.consume(quote);
return Ok(arg);
} else if char == '\\' {
} else if char == DEFAULT_ESCAPE {
arg.push(self.parse_escape_sequence(&[quote])?);
} else {
arg.push(self.consume(char));
Expand All @@ -180,9 +192,19 @@ impl Parser<'_> {

while let Some(char) = self.peek() {
match char {
'{' | '|' | '}' | ' ' => break,
'\\' => arg.push(self.parse_escape_sequence(&[' ', '\'', '"', '{', '|', '}'])?),
quote @ ('\'' | '"') => arg.push_str(&self.parse_quote_arg(quote)?),
EXPR_START | PIPE | EXPR_END | ' ' => break,
DEFAULT_ESCAPE => arg.push(self.parse_escape_sequence(&[
' ',
DEFAULT_ESCAPE,
SINGLE_QUOTE,
DOUBLE_QUOTE,
EXPR_START,
PIPE,
EXPR_END,
])?),
quote @ (SINGLE_QUOTE | DOUBLE_QUOTE) => {
arg.push_str(&self.parse_quote_arg(quote)?)
}
char => arg.push(self.consume(char)),
};
}
Expand All @@ -195,9 +217,13 @@ impl Parser<'_> {

while let Some(char) = self.peek() {
match char {
'\\' => constant.push(self.parse_escape_sequence(&['{', '}'])?),
'{' => break,
'}' => return Err(self.error(ErrorKind::MissingExpressionStart, self.position)),
DEFAULT_ESCAPE => {
constant.push(self.parse_escape_sequence(&[EXPR_START, EXPR_END])?)
}
EXPR_START => break,
EXPR_END => {
return Err(self.error(ErrorKind::MissingExpressionStart, self.position))
}
char => constant.push(self.consume(char)),
}
}
Expand All @@ -206,20 +232,22 @@ impl Parser<'_> {
}

fn parse_escape_sequence(&mut self, escapable_chars: &[char]) -> Result<char> {
self.consume('\\');
self.consume(DEFAULT_ESCAPE);

match self.peek() {
Some(char @ ('n' | 'r' | 't' | '\\')) => self.parse_escaped_char(char),
Some(char @ (ESCAPED_LF | ESCAPED_CR | ESCAPED_TAB | DEFAULT_ESCAPE)) => {
self.parse_escaped_char(char)
}
Some(char) if escapable_chars.contains(&char) => self.parse_escaped_char(char),
_ => Ok('\\'),
_ => Ok(DEFAULT_ESCAPE),
}
}

fn parse_escaped_char(&mut self, char: char) -> Result<char> {
match self.consume(char) {
'n' => Ok('\n'),
'r' => Ok('\r'),
't' => Ok('\t'),
ESCAPED_LF => Ok('\n'),
ESCAPED_CR => Ok('\r'),
ESCAPED_TAB => Ok('\t'),
other => Ok(other),
}
}
Expand Down Expand Up @@ -248,8 +276,8 @@ impl Parser<'_> {
fn consume(&mut self, expected: char) -> char {
match self.next() {
Some(char) if char == expected => char,
Some(char) => unreachable!("parser expected '{}' but got '{}'", expected, char),
None => unreachable!("parser expected '{}' but got EOF", expected),
Some(char) => unreachable!("parser expected {:?} but got {:?}", expected, char),
None => unreachable!("parser expected {:?} but got EOF", expected),
}
}

Expand Down

0 comments on commit a498ed9

Please sign in to comment.