1use std::env;
3
4use lexer_rs::LineColumn;
5use lexer_rs::SimpleParseError;
6use lexer_rs::StreamCharPos;
7use lexer_rs::{CharStream, FmtContext, Lexer, LexerOfStr, LexerOfString, LexerParseResult};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12enum CalcOp {
13 Plus,
14 Minus,
15 Times,
16 Divide,
17}
18
19impl std::fmt::Display for CalcOp {
21 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
22 match self {
23 Self::Plus => write!(fmt, "+"),
24 Self::Minus => write!(fmt, "-"),
25 Self::Times => write!(fmt, "*"),
26 Self::Divide => write!(fmt, "/"),
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq)]
34enum CalcToken {
35 Whitespace,
36 Open,
37 Close,
38 Op(CalcOp),
39 Value(f64),
40}
41
42impl std::fmt::Display for CalcToken {
44 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
45 match self {
46 Self::Whitespace => write!(fmt, " "),
47 Self::Open => write!(fmt, "("),
48 Self::Close => write!(fmt, ")"),
49 Self::Op(o) => o.fmt(fmt),
50 Self::Value(v) => v.fmt(fmt),
51 }
52 }
53}
54
55type TextPos = StreamCharPos<LineColumn>;
59
60type TextStream<'a> = LexerOfStr<'a, TextPos, CalcToken, SimpleParseError<TextPos>>;
63
64type CalcLexResult = LexerParseResult<TextPos, CalcToken, SimpleParseError<TextPos>>;
68
69fn parse_char_fn(stream: &TextStream, state: TextPos, ch: char) -> CalcLexResult {
74 if let Some(t) = {
75 match ch {
76 '+' => Some(CalcToken::Op(CalcOp::Plus)),
77 '-' => Some(CalcToken::Op(CalcOp::Minus)),
78 '*' => Some(CalcToken::Op(CalcOp::Times)),
79 '/' => Some(CalcToken::Op(CalcOp::Divide)),
80 '(' => Some(CalcToken::Open),
81 ')' => Some(CalcToken::Close),
82 _ => None,
83 }
84 } {
85 Ok(Some((stream.consumed_char(state, ch), t)))
86 } else {
87 Ok(None)
88 }
89}
90
91fn parse_value_fn(stream: &TextStream, state: TextPos, ch: char) -> CalcLexResult {
94 let is_digit = |_, ch| ('0'..='9').contains(&ch);
95 let (state, opt_x) = stream.do_while(state, ch, &is_digit);
96 if let Some((start, _n)) = opt_x {
97 let s = stream.get_text(start, state);
98 let value: f64 = s.parse().unwrap();
99 Ok(Some((state, CalcToken::Value(value))))
100 } else {
101 Ok(None)
102 }
103}
104
105fn parse_whitespace_fn(stream: &TextStream, state: TextPos, ch: char) -> CalcLexResult {
108 let is_whitespace = |_n, ch| ch == ' ' || ch == '\t' || ch == '\n';
109 let (state, opt_x) = stream.do_while(state, ch, &is_whitespace);
110 if let Some((_start, _n)) = opt_x {
111 Ok(Some((state, CalcToken::Whitespace)))
112 } else {
113 Ok(None)
114 }
115}
116
117type BoxDynCalcLexFn<'a> =
119 Box<dyn for<'call> Fn(&'call TextStream, TextPos, char) -> CalcLexResult + 'a>;
120struct CalcTokenParser<'a> {
121 parsers: Vec<BoxDynCalcLexFn<'a>>,
122}
123impl<'a> CalcTokenParser<'a> {
124 pub fn new() -> Self {
125 let mut parsers = Vec::new();
126
127 parsers.push(Box::new(parse_value_fn) as BoxDynCalcLexFn);
132 parsers.push(Box::new(parse_char_fn) as BoxDynCalcLexFn);
133 parsers.push(Box::new(parse_whitespace_fn) as BoxDynCalcLexFn);
134 Self { parsers }
135 }
136 pub fn iter<'iter>(
142 &'iter self,
143 t: &'iter TextStream<'iter>,
144 ) -> impl Iterator<Item = Result<CalcToken, SimpleParseError<TextPos>>> + 'iter {
145 t.iter(&self.parsers)
146 }
147}
148
149fn main() -> Result<(), String> {
152 let args: Vec<String> = env::args().collect();
153 if args.len() < 2 {
154 return Err(format!("Usage: {} <expression>", args[0]));
155 }
156 let args_as_string = args[1..].join(" ");
157 let c = CalcTokenParser::new();
158 let l = LexerOfString::default().set_text(args_as_string);
159 let ts = l.lexer();
160
161 println!("Parsing");
164 let tokens = c.iter(&ts);
165 for t in tokens {
166 let t = {
167 match t {
168 Err(e) => {
169 println!();
170 let mut s = String::new();
171 l.fmt_context(&mut s, &e.pos, &e.pos).unwrap();
172 eprintln!("{}", s);
173 return Err(format!("{}", e));
174 }
175 Ok(t) => t,
176 }
177 };
178 print!("{}", t);
179 }
180 println!();
181 println!("Text parsed okay");
182 Ok(())
183}
184
185#[test]
187fn test_lex_0() {
188 let ts = TextStream::new("1+3");
189 let (ts, t) = ts.get_token().unwrap().unwrap();
190 assert_eq!(t, CalcToken::Value(1.0));
191 let (ts, t) = ts.get_token().unwrap().unwrap();
192 assert_eq!(t, CalcToken::Op(Op::Plus));
193 let (ts, t) = ts.get_token().unwrap().unwrap();
194 assert_eq!(t, CalcToken::Value(3.0));
195 let x = ts.get_token().unwrap();
196 assert!(x.is_none());
197}
198
199#[test]
200fn test_lex_1() {
201 let mut ts = TextStream::new("2() \t-\n*+/");
202 for exp_t in [
203 CalcToken::Value(2.0),
204 CalcToken::Open,
205 CalcToken::Close,
206 CalcToken::Whitespace,
207 CalcToken::Op(Op::Minus),
208 CalcToken::Whitespace,
209 CalcToken::Op(Op::Times),
210 CalcToken::Op(Op::Plus),
211 CalcToken::Op(Op::Divide),
212 ] {
213 let (next_ts, t) = ts.get_token().unwrap().unwrap();
214 assert_eq!(t, exp_t);
215 ts = next_ts;
216 }
217 let x = ts.get_token().unwrap();
218 assert!(x.is_none());
219}