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 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 self.eval_expression(expression);
228 }
229 Statement::If(condition, true_block, else_block) => {
230 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 self.quidditch = true;
244
245 while self.quidditch {
247 self.eval(Program(block.clone()))
248 }
249 }
250 Statement::Snitch => {
251 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 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}