potterscript_runtime/
lib.rs

1use std::collections::HashMap;
2use std::{fmt, ops, panic, process, time};
3
4#[cfg(feature = "std")]
5use colored::Colorize;
6use potterscript_parser::{
7    Atom, BinaryOperation, Expression, HogwartsHouse, Program, Spell, Statement,
8};
9#[cfg(feature = "std")]
10use rand::Rng;
11#[cfg(feature = "js")]
12use web_sys::console;
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum RuntimeValue {
16    Integer(i64),
17    Double(f64),
18    Boolean(bool),
19    String(String),
20    HogwartsHouse(HogwartsHouse),
21}
22
23impl From<Atom> for RuntimeValue {
24    fn from(atom: Atom) -> Self {
25        match atom {
26            Atom::Boolean(boolean) => RuntimeValue::Boolean(boolean),
27            Atom::Integer(integer) => RuntimeValue::Integer(integer),
28            Atom::Double(float) => RuntimeValue::Double(float),
29            Atom::String(string) => RuntimeValue::String(string),
30            Atom::Variable(var) => panic!("Cannot convert variable to RuntimeValue: {}", var),
31            Atom::HogwartsHouse(house) => RuntimeValue::HogwartsHouse(house),
32        }
33    }
34}
35
36impl fmt::Display for RuntimeValue {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            RuntimeValue::Integer(value) => write!(f, "{}", value),
40            RuntimeValue::Double(value) => write!(f, "{}", value),
41            RuntimeValue::Boolean(value) => write!(f, "{}", value),
42            RuntimeValue::String(value) => write!(f, "{}", value),
43            RuntimeValue::HogwartsHouse(value) => write!(f, "{:?}", value),
44        }
45    }
46}
47
48impl ops::Add for RuntimeValue {
49    type Output = Self;
50
51    fn add(self, rhs: Self) -> Self::Output {
52        let self_value = self.clone();
53        let rhs_value = rhs.clone();
54
55        match (self, rhs) {
56            (RuntimeValue::Integer(left), RuntimeValue::Integer(right)) => {
57                RuntimeValue::Integer(left + right)
58            }
59            (RuntimeValue::Double(left), RuntimeValue::Double(right)) => {
60                RuntimeValue::Double(left + right)
61            }
62            (RuntimeValue::String(left), RuntimeValue::String(right)) => {
63                RuntimeValue::String(left + &right)
64            }
65            _ => panic!("Cannot add {:?} and {:?}", self_value, rhs_value),
66        }
67    }
68}
69
70impl ops::Sub for RuntimeValue {
71    type Output = Self;
72
73    fn sub(self, rhs: Self) -> Self::Output {
74        let self_value = self.clone();
75        let rhs_value = rhs.clone();
76
77        match (self, rhs) {
78            (RuntimeValue::Integer(left), RuntimeValue::Integer(right)) => {
79                RuntimeValue::Integer(left - right)
80            }
81            (RuntimeValue::Double(left), RuntimeValue::Double(right)) => {
82                RuntimeValue::Double(left - right)
83            }
84            _ => panic!("Cannot subtract {:?} and {:?}", self_value, rhs_value),
85        }
86    }
87}
88
89impl ops::Mul for RuntimeValue {
90    type Output = Self;
91
92    fn mul(self, rhs: Self) -> Self::Output {
93        let self_value = self.clone();
94        let rhs_value = rhs.clone();
95
96        match (self, rhs) {
97            (RuntimeValue::Integer(left), RuntimeValue::Integer(right)) => {
98                RuntimeValue::Integer(left * right)
99            }
100            (RuntimeValue::Double(left), RuntimeValue::Double(right)) => {
101                RuntimeValue::Double(left * right)
102            }
103            _ => panic!("Cannot multiply {:?} and {:?}", self_value, rhs_value),
104        }
105    }
106}
107
108impl ops::Div for RuntimeValue {
109    type Output = Self;
110
111    fn div(self, rhs: Self) -> Self::Output {
112        let self_value = self.clone();
113        let rhs_value = rhs.clone();
114
115        match (self, rhs) {
116            (RuntimeValue::Integer(left), RuntimeValue::Integer(right)) => {
117                RuntimeValue::Integer(left / right)
118            }
119            (RuntimeValue::Double(left), RuntimeValue::Double(right)) => {
120                RuntimeValue::Double(left / right)
121            }
122            _ => panic!("Cannot divide {:?} and {:?}", self_value, rhs_value),
123        }
124    }
125}
126
127impl ops::Not for RuntimeValue {
128    type Output = Self;
129
130    fn not(self) -> Self::Output {
131        match self {
132            RuntimeValue::Boolean(value) => RuntimeValue::Boolean(!value),
133            _ => panic!("Cannot negate {:?}", self),
134        }
135    }
136}
137
138pub struct Runtime {
139    variables: HashMap<String, RuntimeValue>,
140    constants: HashMap<String, RuntimeValue>,
141    quidditch: bool,
142    is_lumos_casted: bool,
143}
144
145pub trait RuntimeAdapter {
146    fn create_random_index() -> usize;
147    fn lumos(string: String) -> String;
148    fn log(string: &str);
149}
150
151#[cfg(feature = "std")]
152impl RuntimeAdapter for Runtime {
153    fn create_random_index() -> usize {
154        let mut rng = rand::thread_rng();
155        rng.gen_range(0..=3)
156    }
157
158    fn lumos(string: String) -> String {
159        string.black().on_white().to_string()
160    }
161
162    fn log(string: &str) {
163        println!("{}", string);
164    }
165}
166
167#[cfg(feature = "js")]
168impl RuntimeAdapter for Runtime {
169    fn create_random_index() -> usize {
170        js_sys::Math::floor(js_sys::Math::random() * 4.0) as usize
171    }
172
173    fn lumos(string: String) -> String {
174        string
175    }
176
177    fn log(string: &str) {
178        console::log_1(&string.into());
179    }
180}
181
182impl Runtime {
183    pub fn new() -> Self {
184        Self {
185            variables: HashMap::new(),
186            constants: HashMap::new(),
187            quidditch: false,
188            is_lumos_casted: false,
189        }
190    }
191
192    pub fn eval(&mut self, program: Program) {
193        for statement in program.0 {
194            self.eval_statement(statement);
195        }
196    }
197
198    fn eval_atom(&self, atom: Atom) -> RuntimeValue {
199        match atom {
200            Atom::Variable(var_name) => self
201                .variables
202                .get(&var_name)
203                .cloned()
204                .expect(format!("Variable {} not found", var_name).as_str()),
205            _ => atom.into(),
206        }
207    }
208
209    fn eval_statement(&mut self, statement: Statement) {
210        match statement {
211            Statement::VariableAssignment(name, value) => {
212                if self.constants.contains_key(&name) {
213                    panic!("Cannot re-assign a constant!");
214                }
215
216                // dbg!(format!("VariableAssignment: {:?} = {:?}", name, value));
217                let evaluated_value = self.eval_expression(value);
218
219                if let Some(evaluated_value) = evaluated_value {
220                    self.variables.insert(name, evaluated_value);
221                } else {
222                    panic!("Cannot assign None to variable");
223                }
224            }
225            Statement::ExpressionStatement(expression) => {
226                // dbg!(format!("ExpressionStatement: {:?}", expression));
227                self.eval_expression(expression);
228            }
229            Statement::If(condition, true_block, else_block) => {
230                // dbg!(format!("If: {:?} {{ ... }}", condition));
231                if let Some(RuntimeValue::Boolean(true)) = self.eval_expression(condition) {
232                    for statement in true_block {
233                        self.eval_statement(statement);
234                    }
235                } else {
236                    for statement in else_block {
237                        self.eval_statement(statement);
238                    }
239                }
240            }
241            Statement::Quidditch(block) => {
242                // dbg!(format!("Quidditch: {:?} {{ ... }}", condition));
243                self.quidditch = true;
244
245                // arc
246                while self.quidditch {
247                    self.eval(Program(block.clone()))
248                }
249            }
250            Statement::Snitch => {
251                // dbg!("Snitch");
252                self.quidditch = false;
253            }
254        }
255    }
256
257    fn eval_expression(&mut self, expression: Expression) -> Option<RuntimeValue> {
258        match expression {
259            Expression::SpellCast(spell, target) => self.eval_spell(spell, target),
260            Expression::BinaryOperation(operation, left, right) => {
261                match (self.eval_expression(*left), self.eval_expression(*right)) {
262                    (Some(left), Some(right)) => match operation {
263                        BinaryOperation::Plus => Some(left + right),
264                        BinaryOperation::Minus => Some(left - right),
265                        BinaryOperation::Times => Some(left * right),
266                        BinaryOperation::Divide => Some(left / right),
267                        BinaryOperation::Equal => Some(RuntimeValue::Boolean(left == right)),
268                        BinaryOperation::NotEqual => Some(RuntimeValue::Boolean(left != right)),
269                    },
270                    _ => None,
271                }
272            }
273            Expression::Atom(atom) => Some(self.eval_atom(atom)),
274            Expression::Comment(_) => None,
275            Expression::SortingHat => {
276                let houses = vec![
277                    HogwartsHouse::Gryffindor,
278                    HogwartsHouse::Hufflepuff,
279                    HogwartsHouse::Ravenclaw,
280                    HogwartsHouse::Slytherin,
281                ];
282
283                let index = Self::create_random_index();
284                let random_house = houses[index];
285                Some(RuntimeValue::HogwartsHouse(random_house.clone()))
286            }
287        }
288    }
289
290    fn eval_spell(
291        &mut self,
292        spell: Spell,
293        target: Box<Option<Expression>>,
294    ) -> Option<RuntimeValue> {
295        match spell {
296            Spell::AvadaKedabra => process::exit(0),
297            Spell::Inmobolus => match *target {
298                Some(Expression::Atom(Atom::Integer(number))) => {
299                    let _ms = time::Duration::from_millis(number as u64);
300                    // TODO add this again when WASM compatible (need to add tokio runtime)
301                    // thread::sleep(ms);
302                    None
303                }
304                _ => None,
305            },
306            Spell::Incendio => match *target {
307                Some(Expression::Atom(Atom::Variable(var_name))) => {
308                    let value = self
309                        .variables
310                        .get(&var_name)
311                        .cloned()
312                        .expect(format!("Variable {} not found", var_name).as_str());
313                    match value {
314                        RuntimeValue::String(string) => {
315                            self.variables
316                                .insert(var_name, RuntimeValue::String(string + "🔥"));
317                        }
318                        _ => panic!("Cannot Incendio {:?}", value),
319                    }
320                    None
321                }
322                Some(Expression::Atom(Atom::String(string))) => {
323                    Some(RuntimeValue::String(string + "🔥"))
324                }
325                _ => None,
326            },
327            Spell::Aguamenti => RuntimeValue::String("💦".to_string()).into(),
328            Spell::OculusReparo => RuntimeValue::String("👓".to_string()).into(),
329            Spell::Serpensortia => RuntimeValue::String("🐍".to_string()).into(),
330            Spell::Periculum => {
331                Self::log("🔥🔥🔥🔥🔥🔥🔥🔥🔥");
332                None
333            }
334            Spell::Lumos => {
335                self.is_lumos_casted = true;
336                None
337            }
338            Spell::Nox => {
339                self.is_lumos_casted = false;
340                None
341            }
342            Spell::Engorgio => match *target {
343                Some(Expression::Atom(Atom::Variable(var_name))) => {
344                    let value = self
345                        .variables
346                        .get(&var_name)
347                        .cloned()
348                        .expect(format!("Variable {} not found", var_name).as_str());
349                    match value {
350                        RuntimeValue::Integer(value) => {
351                            self.variables
352                                .insert(var_name, RuntimeValue::Integer(value + 1));
353                        }
354                        RuntimeValue::Double(value) => {
355                            self.variables
356                                .insert(var_name, RuntimeValue::Double(value + 1.0));
357                        }
358                        RuntimeValue::String(string) => {
359                            self.variables.insert(
360                                var_name,
361                                RuntimeValue::String(string.to_ascii_uppercase()),
362                            );
363                        }
364                        _ => panic!("Cannot increment {:?}", value),
365                    }
366                    None
367                }
368                _ => None,
369            },
370            Spell::Reducio => match *target {
371                Some(Expression::Atom(Atom::Variable(var_name))) => {
372                    let value = self
373                        .variables
374                        .get(&var_name)
375                        .cloned()
376                        .expect(format!("Variable {} not found", var_name).as_str());
377                    match value {
378                        RuntimeValue::Integer(value) => {
379                            self.variables
380                                .insert(var_name, RuntimeValue::Integer(value - 1));
381                        }
382                        RuntimeValue::Double(value) => {
383                            self.variables
384                                .insert(var_name, RuntimeValue::Double(value - 1.0));
385                        }
386                        RuntimeValue::String(string) => {
387                            self.variables.insert(
388                                var_name,
389                                RuntimeValue::String(string.to_ascii_lowercase()),
390                            );
391                        }
392                        _ => panic!("Cannot decrement {:?}", value),
393                    }
394                    None
395                }
396                _ => None,
397            },
398            Spell::Obliviate => match *target {
399                Some(Expression::Atom(Atom::Variable(var_name))) => {
400                    self.variables.remove(&var_name);
401                    None
402                }
403                _ => None,
404            },
405            Spell::Revelio => match *target {
406                Some(target) => {
407                    let mut string_target: String = self
408                        .eval_expression(target)
409                        .unwrap_or(RuntimeValue::String("".to_string()))
410                        .to_string();
411                    if self.is_lumos_casted {
412                        string_target = Self::lumos(string_target);
413                    }
414                    Self::log(&string_target);
415                    None
416                }
417                None => None,
418            },
419            Spell::PetrificusTotalus => match *target {
420                Some(Expression::Atom(Atom::Variable(var_name))) => {
421                    let value = self.variables.remove(&var_name);
422                    if let Some(value) = value {
423                        self.constants.insert(var_name, value);
424                    }
425                    None
426                }
427                _ => None,
428            },
429            Spell::WingardiumLeviosa => match *target {
430                Some(Expression::Atom(Atom::Variable(var_name))) => {
431                    let value = self
432                        .variables
433                        .get(&var_name)
434                        .cloned()
435                        .expect(format!("Variable {} not found", var_name).as_str());
436                    match value {
437                        RuntimeValue::String(string) => {
438                            self.variables
439                                .insert(var_name, RuntimeValue::String(string + "\n"));
440                        }
441                        _ => panic!("Cannot WingardiumLeviosa {:?}", value),
442                    }
443                    None
444                }
445                Some(Expression::Atom(Atom::String(string))) => {
446                    Some(RuntimeValue::String(string + "\n"))
447                }
448                _ => None,
449            },
450        }
451    }
452}