control/
control.rs

1use std::{collections::HashMap, fmt::Display, fs::read_to_string};
2
3use cypress::prelude::*;
4
5/// A simple control flow language supporting variables which are ints or bools
6/// and control flow of if and while. This language exihibts the use of `foldl`
7/// to enforce precedence in [`expr_parser`].
8
9#[derive(Debug, Clone)]
10enum Statement {
11    Let(String, Expr),
12    While(Expr, Vec<Statement>),
13    If(Expr, Vec<Statement>, Vec<Statement>),
14    Print(Expr),
15}
16
17#[derive(Debug, Clone)]
18enum Expr {
19    Var(String),
20    Num(i32),
21    Bool(bool),
22    BinOp(Box<Expr>, BinOp, Box<Expr>),
23}
24
25#[derive(Debug, Clone, PartialEq)]
26enum BinOp {
27    Add,
28    Sub,
29    Mul,
30    Div,
31    Eq,
32    Neq,
33    Gt,
34    Lt,
35    Gte,
36    Lte,
37    Mod,
38}
39
40#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41enum EvalToken {
42    Num(i32),
43    Bool(bool),
44}
45
46impl Display for EvalToken {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            EvalToken::Num(n) => write!(f, "{}", n),
50            EvalToken::Bool(b) => write!(f, "{}", b),
51        }
52    }
53}
54
55impl EvalToken {
56    fn is_num(&self) -> bool {
57        matches!(self, EvalToken::Num(_))
58    }
59
60    fn get_num(&self) -> Option<i32> {
61        match self {
62            EvalToken::Num(n) => Some(*n),
63            EvalToken::Bool(_) => None,
64        }
65    }
66}
67
68// Evaluation methods
69impl BinOp {
70    fn eval(&self, lhs: EvalToken, rhs: EvalToken) -> EvalToken {
71        match self {
72            BinOp::Add => {
73                assert!(
74                    lhs.is_num() && rhs.is_num(),
75                    "Both arguments must be numbers"
76                );
77                EvalToken::Num(lhs.get_num().unwrap() + rhs.get_num().unwrap())
78            }
79            BinOp::Sub => {
80                assert!(
81                    lhs.is_num() && rhs.is_num(),
82                    "Both arguments must be numbers"
83                );
84                EvalToken::Num(lhs.get_num().unwrap() - rhs.get_num().unwrap())
85            }
86            BinOp::Mul => {
87                assert!(
88                    lhs.is_num() && rhs.is_num(),
89                    "Both arguments must be numbers"
90                );
91                EvalToken::Num(lhs.get_num().unwrap() * rhs.get_num().unwrap())
92            }
93            BinOp::Div => {
94                assert!(
95                    lhs.is_num() && rhs.is_num(),
96                    "Both arguments must be numbers"
97                );
98                EvalToken::Num(lhs.get_num().unwrap() / rhs.get_num().unwrap())
99            }
100            BinOp::Mod => {
101                assert!(
102                    lhs.is_num() && rhs.is_num(),
103                    "Both arguments must be numbers"
104                );
105                EvalToken::Num(lhs.get_num().unwrap() % rhs.get_num().unwrap())
106            }
107            BinOp::Eq => EvalToken::Bool(lhs == rhs),
108            BinOp::Neq => EvalToken::Bool(lhs != rhs),
109            BinOp::Gt => EvalToken::Bool(lhs > rhs),
110            BinOp::Lt => EvalToken::Bool(lhs < rhs),
111            BinOp::Gte => EvalToken::Bool(lhs >= rhs),
112            BinOp::Lte => EvalToken::Bool(lhs <= rhs),
113        }
114    }
115}
116
117impl Expr {
118    fn eval(&self, map: &HashMap<String, EvalToken>) -> EvalToken {
119        match self {
120            Expr::Var(name) => match map.get(name) {
121                Some(e) => *e,
122                None => panic!("No value associated with variable \"{name}\""),
123            },
124            Expr::Num(num) => EvalToken::Num(*num),
125            Expr::Bool(b) => EvalToken::Bool(*b),
126            Expr::BinOp(expr, bin_op, expr1) => bin_op.eval(expr.eval(map), expr1.eval(map)),
127        }
128    }
129}
130
131impl Statement {
132    fn eval(&self, map: &mut HashMap<String, EvalToken>) {
133        match self {
134            Statement::Let(name, expr) => {
135                let _ = map.insert(name.to_string(), expr.eval(map));
136            }
137            Statement::While(expr, statements) => loop {
138                if let EvalToken::Bool(true) = expr.eval(map) {
139                    for statment in statements {
140                        statment.eval(map);
141                    }
142                } else {
143                    break;
144                }
145            },
146            Statement::If(expr, statements, statements1) => {
147                if let EvalToken::Bool(true) = expr.eval(map) {
148                    for statment in statements {
149                        statment.eval(map);
150                    }
151                } else {
152                    for statment in statements1 {
153                        statment.eval(map);
154                    }
155                }
156            }
157            Statement::Print(expr) => {
158                println!("{}", expr.eval(map));
159            }
160        }
161    }
162}
163
164// Start of the parser
165fn expr_parser<'a>() -> impl Parser<'a, u8, Expr> {
166    let string = choice!(pletter(), just('_'))
167        .many1()
168        .map(|xs| Expr::Var(String::from_utf8(xs).unwrap()));
169
170    let num = pnum()
171        .many1()
172        .map(|ns| Expr::Num(String::from_utf8(ns).unwrap().parse::<i32>().unwrap()));
173
174    let bool_ = select! {
175        (pident("true")) => Expr::Bool(true),
176        (pident("false")) => Expr::Bool(false)
177    };
178
179    let atom = choice!(string.clone(), num.clone(), bool_.clone()).padded_by(pinlinews());
180
181    // User precedence macro to simple define precedence levels
182    let bin_op = precedence! {
183        // highest precedece
184        atom,
185        {
186            choice!(
187                select! { '*' => BinOp::Mul },
188                select! { '/' => BinOp::Div },
189                select! { '%' => BinOp::Mod },
190            )
191            =>
192            |a, (op, b)| Expr::BinOp(Box::new(a), op, Box::new(b))
193        },
194        {
195            choice!(
196                select! { '+' => BinOp::Add },
197                select! { '-' => BinOp::Sub },
198            )
199            =>
200            |a, (op, b)| Expr::BinOp(Box::new(a), op, Box::new(b))
201        },
202        // lowest precedence
203        {
204            choice!(
205                select! { (sequence!('>' > '=')) => BinOp::Gte },
206                select! { (sequence!('<' > '=')) => BinOp::Lte },
207                select! { (sequence!('=' > '=')) => BinOp::Eq },
208                select! { (sequence!('!' > '=')) => BinOp::Neq },
209                select! { '<' => BinOp::Lt },
210                select! { '>' => BinOp::Gt },
211            )
212            =>
213            |a, (op, b)| Expr::BinOp(Box::new(a), op, Box::new(b))
214        }
215    };
216
217    choice!(bin_op, string, num, bool_)
218}
219
220fn statement_parser<'a>() -> impl Parser<'a, u8, Statement> {
221    recursive(|stmt| {
222        let let_ = (pident("let").padded_by(pinlinews()))
223            .then(
224                choice!(pletter(), just('_'))
225                    .many1()
226                    .map(|xs| String::from_utf8(xs).unwrap()),
227            )
228            .padded_by(pinlinews())
229            .then(just('=').padded_by(pinlinews()).then(expr_parser()))
230            .map(|((_, name), (_, expr))| Statement::Let(name, expr));
231
232        let letless = choice!(pletter(), just('_'))
233            .many1()
234            .map(|xs| String::from_utf8(xs).unwrap())
235            .then(just('=').padded_by(pinlinews()))
236            .then(expr_parser())
237            .map(|((name, _), expr)| Statement::Let(name, expr));
238
239        let while_ = pident("while")
240            .then(expr_parser().padded_by(pinlinews()))
241            .then(pbetween(
242                just('{').padded_by(pws()),
243                (stmt.clone().padded_by(pinlinews()))
244                    .delimited_by(just('\n').many())
245                    .padded_by(pinlinews()),
246                just('}').padded_by(pws()),
247            ))
248            .map(|((_, cond), stmts)| Statement::While(cond, stmts));
249
250        let if_ = pident("if")
251            .then(expr_parser().padded_by(pinlinews()))
252            .then(pbetween(
253                just('{').padded_by(pws()),
254                (stmt.clone().padded_by(pinlinews()))
255                    .delimited_by(just('\n').many())
256                    .padded_by(pinlinews()),
257                just('}').padded_by(pws()),
258            ))
259            .map(|((_, cond), stmts)| Statement::If(cond, stmts, vec![]));
260
261        let if_else_ = pident("if")
262            .then(expr_parser().padded_by(pinlinews()))
263            .then(pbetween(
264                just('{').padded_by(pws()),
265                (stmt.clone().padded_by(pinlinews()))
266                    .delimited_by(just('\n').many())
267                    .padded_by(pinlinews()),
268                just('}').padded_by(pws()),
269            ))
270            .then(pident("else"))
271            .then(pbetween(
272                just('{').padded_by(pws()),
273                (stmt.clone().padded_by(pinlinews()))
274                    .delimited_by(just('\n').many())
275                    .padded_by(pinlinews()),
276                just('}').padded_by(pws()),
277            ))
278            .map(|((((_, cond), if_stmts), _), else_stmts)| {
279                Statement::If(cond, if_stmts, else_stmts)
280            });
281
282        let print_ = pident("print")
283            .then(pbetween(just('('), expr_parser(), just(')')))
284            .map(|(_, expr)| Statement::Print(expr));
285
286        Box::new(choice!(let_, letless, while_, if_else_, if_, print_))
287    })
288}
289
290fn parser<'a>() -> impl Parser<'a, u8, Vec<Statement>> {
291    statement_parser()
292        .delimited_by(just('\n').many())
293        .until_end()
294}
295
296fn main() {
297    let input =
298        read_to_string("./examples/control.ctrl").expect("Failed to read file from examples dir.");
299
300    match parser().parse(input.into_input()) {
301        Ok(PSuccess { val, rest: _ }) => {
302            let mut map: HashMap<String, EvalToken> = HashMap::new();
303            // Evaluate each statment
304            for statement in val {
305                statement.eval(&mut map);
306            }
307        }
308        Err(e) => println!("{}", e),
309    }
310}