calc/
calc.rs

1//a Imports
2use 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//a CalcOp
10//tp CalcOp
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12enum CalcOp {
13    Plus,
14    Minus,
15    Times,
16    Divide,
17}
18
19//ip Display for CalcOp
20impl 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//a CalcToken (and sub-enus)
32//tp CalcToken
33#[derive(Debug, Clone, Copy, PartialEq)]
34enum CalcToken {
35    Whitespace,
36    Open,
37    Close,
38    Op(CalcOp),
39    Value(f64),
40}
41
42//ip Display for CalcToken
43impl 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
55//a TextPos, TextStream
56//tp TextPos
57///
58type TextPos = StreamCharPos<LineColumn>;
59
60//tp TextStream
61///
62type TextStream<'a> = LexerOfStr<'a, TextPos, CalcToken, SimpleParseError<TextPos>>;
63
64//a CalcLexResult
65//tp CalcLexResult
66// type CalcLexResult = Result<Option<(TPos, Token)>, LexError>;
67type CalcLexResult = LexerParseResult<TextPos, CalcToken, SimpleParseError<TextPos>>;
68
69//a Lexical analysis functions
70//fi parse_char_fn
71/// Parser function to return a Token if it is a known one
72/// character token otherwise it returns None
73fn 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
91//fi parse_value_fn
92/// Parser function to return a Token if the text matches a value
93fn 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
105//fi parse_whitespace_fn
106/// Parser function to return a Token if whitespace
107fn 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
117//a Main
118type 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        // Note that we use 'as BoxDynCalcLexFn' because type inference kicks in for the Box::new()
128        // and does not let parse_value_fn get correctly inferred as dyn Fn(...)
129        //
130        // Forcing it this way kicks the type inference
131        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    /*
137    pub fn add_parser<F: Fn(&TextStream, TextPos, char) -> CalcLexResult + 'a>(&mut self, f: F) {
138        self.parsers.push(Box::new(f));
139    }*/
140
141    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
149//a Main
150
151fn 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    // let ts = TextStream::new(&args_as_string);
162
163    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//a Tests - run with cargo test --examples
186#[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}