use std::env;
use lexer_rs::LineColumn;
use lexer_rs::SimpleParseError;
use lexer_rs::StreamCharPos;
use lexer_rs::{CharStream, FmtContext, Lexer, LexerOfStr, LexerOfString, LexerParseResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum CalcOp {
Plus,
Minus,
Times,
Divide,
}
impl std::fmt::Display for CalcOp {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::Plus => write!(fmt, "+"),
Self::Minus => write!(fmt, "-"),
Self::Times => write!(fmt, "*"),
Self::Divide => write!(fmt, "/"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum CalcToken {
Whitespace,
Open,
Close,
Op(CalcOp),
Value(f64),
}
impl std::fmt::Display for CalcToken {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::Whitespace => write!(fmt, " "),
Self::Open => write!(fmt, "("),
Self::Close => write!(fmt, ")"),
Self::Op(o) => o.fmt(fmt),
Self::Value(v) => v.fmt(fmt),
}
}
}
type TextPos = StreamCharPos<LineColumn>;
type TextStream<'a> = LexerOfStr<'a, TextPos, CalcToken, SimpleParseError<TextPos>>;
type CalcLexResult = LexerParseResult<TextPos, CalcToken, SimpleParseError<TextPos>>;
fn parse_char_fn(stream: &TextStream, state: TextPos, ch: char) -> CalcLexResult {
if let Some(t) = {
match ch {
'+' => Some(CalcToken::Op(CalcOp::Plus)),
'-' => Some(CalcToken::Op(CalcOp::Minus)),
'*' => Some(CalcToken::Op(CalcOp::Times)),
'/' => Some(CalcToken::Op(CalcOp::Divide)),
'(' => Some(CalcToken::Open),
')' => Some(CalcToken::Close),
_ => None,
}
} {
Ok(Some((stream.consumed_char(state, ch), t)))
} else {
Ok(None)
}
}
fn parse_value_fn(stream: &TextStream, state: TextPos, ch: char) -> CalcLexResult {
let is_digit = |_, ch| ('0'..='9').contains(&ch);
let (state, opt_x) = stream.do_while(state, ch, &is_digit);
if let Some((start, _n)) = opt_x {
let s = stream.get_text(start, state);
let value: f64 = s.parse().unwrap();
Ok(Some((state, CalcToken::Value(value))))
} else {
Ok(None)
}
}
fn parse_whitespace_fn(stream: &TextStream, state: TextPos, ch: char) -> CalcLexResult {
let is_whitespace = |_n, ch| ch == ' ' || ch == '\t' || ch == '\n';
let (state, opt_x) = stream.do_while(state, ch, &is_whitespace);
if let Some((_start, _n)) = opt_x {
Ok(Some((state, CalcToken::Whitespace)))
} else {
Ok(None)
}
}
type BoxDynCalcLexFn<'a> =
Box<dyn for<'call> Fn(&'call TextStream, TextPos, char) -> CalcLexResult + 'a>;
struct CalcTokenParser<'a> {
parsers: Vec<BoxDynCalcLexFn<'a>>,
}
impl<'a> CalcTokenParser<'a> {
pub fn new() -> Self {
let mut parsers = Vec::new();
parsers.push(Box::new(parse_value_fn) as BoxDynCalcLexFn);
parsers.push(Box::new(parse_char_fn) as BoxDynCalcLexFn);
parsers.push(Box::new(parse_whitespace_fn) as BoxDynCalcLexFn);
Self { parsers }
}
pub fn iter<'iter>(
&'iter self,
t: &'iter TextStream<'iter>,
) -> impl Iterator<Item = Result<CalcToken, SimpleParseError<TextPos>>> + 'iter {
t.iter(&self.parsers)
}
}
fn main() -> Result<(), String> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
return Err(format!("Usage: {} <expression>", args[0]));
}
let args_as_string = args[1..].join(" ");
let c = CalcTokenParser::new();
let l = LexerOfString::default().set_text(args_as_string);
let ts = l.lexer();
println!("Parsing");
let tokens = c.iter(&ts);
for t in tokens {
let t = {
match t {
Err(e) => {
println!();
let mut s = String::new();
l.fmt_context(&mut s, &e.pos, &e.pos).unwrap();
eprintln!("{}", s);
return Err(format!("{}", e));
}
Ok(t) => t,
}
};
print!("{}", t);
}
println!();
println!("Text parsed okay");
Ok(())
}
#[test]
fn test_lex_0() {
let ts = TextStream::new("1+3");
let (ts, t) = ts.get_token().unwrap().unwrap();
assert_eq!(t, CalcToken::Value(1.0));
let (ts, t) = ts.get_token().unwrap().unwrap();
assert_eq!(t, CalcToken::Op(Op::Plus));
let (ts, t) = ts.get_token().unwrap().unwrap();
assert_eq!(t, CalcToken::Value(3.0));
let x = ts.get_token().unwrap();
assert!(x.is_none());
}
#[test]
fn test_lex_1() {
let mut ts = TextStream::new("2() \t-\n*+/");
for exp_t in [
CalcToken::Value(2.0),
CalcToken::Open,
CalcToken::Close,
CalcToken::Whitespace,
CalcToken::Op(Op::Minus),
CalcToken::Whitespace,
CalcToken::Op(Op::Times),
CalcToken::Op(Op::Plus),
CalcToken::Op(Op::Divide),
] {
let (next_ts, t) = ts.get_token().unwrap().unwrap();
assert_eq!(t, exp_t);
ts = next_ts;
}
let x = ts.get_token().unwrap();
assert!(x.is_none());
}