1use std::{collections::HashMap, fmt::Display, fs::read_to_string};
2
3use cypress::prelude::*;
4
5#[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
68impl 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
164fn 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 let bin_op = precedence! {
183 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 {
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 for statement in val {
305 statement.eval(&mut map);
306 }
307 }
308 Err(e) => println!("{}", e),
309 }
310}