potterscript_parser/
lib.rs

1use std::fmt;
2
3use nom::{
4    branch::alt,
5    bytes::complete::{tag, take_till, take_until},
6    character::complete::{alpha0, alpha1, char, i64, multispace0},
7    combinator::{map, opt},
8    multi::many1,
9    number::complete::double,
10    sequence::{delimited, preceded, terminated, tuple},
11    IResult,
12};
13use serde::{Deserialize, Serialize};
14
15// Atoms
16
17#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
18pub enum Atom {
19    String(String),
20    Variable(String),
21    Boolean(bool),
22    Integer(i64),
23    Double(f64),
24    HogwartsHouse(HogwartsHouse),
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
28pub enum HogwartsHouse {
29    Gryffindor,
30    Hufflepuff,
31    Ravenclaw,
32    Slytherin,
33}
34
35impl fmt::Display for HogwartsHouse {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        match self {
38            HogwartsHouse::Gryffindor => write!(f, "Gryffindor"),
39            HogwartsHouse::Hufflepuff => write!(f, "Hufflepuff"),
40            HogwartsHouse::Ravenclaw => write!(f, "Ravenclaw"),
41            HogwartsHouse::Slytherin => write!(f, "Slytherin"),
42        }
43    }
44}
45
46impl Atom {
47    pub fn to_string(&self) -> String {
48        match self {
49            Atom::Boolean(boolean) => boolean.to_string(),
50            Atom::Integer(integer) => integer.to_string(),
51            Atom::Double(float) => float.to_string(),
52            Atom::String(string) => string.to_string(),
53            Atom::Variable(var) => var.to_string(),
54            Atom::HogwartsHouse(house) => house.to_string(),
55        }
56    }
57}
58
59impl From<Atom> for Expression {
60    fn from(atom: Atom) -> Self {
61        Expression::Atom(atom)
62    }
63}
64
65fn parse_atom(input: &str) -> IResult<&str, Expression> {
66    let parser = alt((
67        parse_boolean,
68        parse_hogwarts_house,
69        parse_double,
70        parse_integer,
71        parse_string,
72        parse_variable,
73    ));
74    map(parser, |atom| atom.into())(input)
75}
76
77fn parse_boolean(input: &str) -> IResult<&str, Atom> {
78    let parser = alt((tag("true"), tag("false")));
79    map(parser, |boolean: &str| Atom::Boolean(boolean == "true"))(input)
80}
81
82fn parse_double(input: &str) -> IResult<&str, Atom> {
83    if !input.contains(".") {
84        return Err(nom::Err::Error(nom::error::Error::new(
85            input,
86            nom::error::ErrorKind::Digit,
87        )));
88    }
89
90    let parser = double;
91    map(parser, |float| Atom::Double(float))(input)
92}
93
94fn parse_integer(input: &str) -> IResult<&str, Atom> {
95    let parser = i64;
96    map(parser, |integer| Atom::Integer(integer))(input)
97}
98
99fn parse_string(input: &str) -> IResult<&str, Atom> {
100    let parser = delimited(tag("\""), take_until("\""), tag("\""));
101    map(parser, |string: &str| Atom::String(string.to_string()))(input)
102}
103
104fn parse_variable(input: &str) -> IResult<&str, Atom> {
105    map(alpha1, |var: &str| Atom::Variable(var.to_string()))(input)
106}
107
108fn parse_hogwarts_house(input: &str) -> IResult<&str, Atom> {
109    let parser = alt((
110        tag("Gryffindor"),
111        tag("Hufflepuff"),
112        tag("Ravenclaw"),
113        tag("Slytherin"),
114    ));
115    map(parser, |house: &str| match house {
116        "Gryffindor" => Atom::HogwartsHouse(HogwartsHouse::Gryffindor),
117        "Hufflepuff" => Atom::HogwartsHouse(HogwartsHouse::Hufflepuff),
118        "Ravenclaw" => Atom::HogwartsHouse(HogwartsHouse::Ravenclaw),
119        "Slytherin" => Atom::HogwartsHouse(HogwartsHouse::Slytherin),
120        _ => panic!("Unknown Hogwarts house: {}", house),
121    })(input)
122}
123
124// Expressions
125
126#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
127pub enum Expression {
128    SpellCast(Spell, Box<Option<Expression>>),
129    BinaryOperation(BinaryOperation, Box<Expression>, Box<Expression>),
130    Atom(Atom),
131    Comment(String),
132    SortingHat,
133}
134
135#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
136pub enum Spell {
137    Aguamenti,
138    AvadaKedabra,
139    Engorgio,
140    Incendio,
141    Inmobolus,
142    Lumos,
143    Nox,
144    Obliviate,
145    OculusReparo,
146    Periculum,
147    Reducio,
148    PetrificusTotalus,
149    Revelio,
150    Serpensortia,
151    WingardiumLeviosa,
152}
153
154#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
155pub enum BinaryOperation {
156    Plus,
157    Minus,
158    Times,
159    Divide,
160    Equal,
161    NotEqual,
162}
163
164pub fn parse_expression(input: &str) -> IResult<&str, Expression> {
165    // dbg!("parse_expression");
166    // dbg!(input);
167    alt((
168        parse_sorting_hat,
169        parse_comment,
170        parse_spell_cast,
171        parse_binary_operation,
172        parse_atom,
173    ))(input)
174}
175
176pub fn parse_sorting_hat(input: &str) -> IResult<&str, Expression> {
177    map(alt((tag("SortingHat"), tag("🎩✨"))), |_| {
178        Expression::SortingHat
179    })(input)
180}
181
182pub fn parse_spell_cast(input: &str) -> IResult<&str, Expression> {
183    // take until ; or ->
184    let spell_parser = delimited(tag("~"), alpha0, opt(tag(" ")));
185    let target_parser = parse_expression;
186    let parser = tuple((spell_parser, opt(target_parser)));
187
188    map(parser, |(spell, target)| match spell {
189        "AvadaKedabra" => Expression::SpellCast(Spell::AvadaKedabra, Box::new(target)),
190        "Aguamenti" => Expression::SpellCast(Spell::Aguamenti, Box::new(target)),
191        "Engorgio" => Expression::SpellCast(Spell::Engorgio, Box::new(target)),
192        "Incendio" => Expression::SpellCast(Spell::Incendio, Box::new(target)),
193        "Inmobolus" => Expression::SpellCast(Spell::Inmobolus, Box::new(target)),
194        "Lumos" => Expression::SpellCast(Spell::Lumos, Box::new(target)),
195        "Nox" => Expression::SpellCast(Spell::Nox, Box::new(target)),
196        "Obliviate" => Expression::SpellCast(Spell::Obliviate, Box::new(target)),
197        "OculusReparo" => Expression::SpellCast(Spell::OculusReparo, Box::new(target)),
198        "Periculum" => Expression::SpellCast(Spell::Periculum, Box::new(target)),
199        "Reducio" => Expression::SpellCast(Spell::Reducio, Box::new(target)),
200        "PetrificusTotalus" => Expression::SpellCast(Spell::PetrificusTotalus, Box::new(target)),
201        "Revelio" => Expression::SpellCast(Spell::Revelio, Box::new(target)),
202        "Serpensortia" => Expression::SpellCast(Spell::Serpensortia, Box::new(target)),
203        "WingardiumLeviosa" => Expression::SpellCast(Spell::WingardiumLeviosa, Box::new(target)),
204        _ => panic!("Wand broken: Unknown spell: {}", spell),
205    })(input)
206}
207
208pub fn parse_binary_operation(input: &str) -> IResult<&str, Expression> {
209    let (rest, (left, _, op, _, right)) = tuple((
210        parse_atom,
211        multispace0,
212        parse_binary_operator,
213        multispace0,
214        parse_atom,
215    ))(input)?;
216
217    let expression = Expression::BinaryOperation(op, Box::new(left), Box::new(right));
218    Ok((rest, expression))
219}
220
221pub fn parse_comment(input: &str) -> IResult<&str, Expression> {
222    map(
223        preceded(char('#'), take_till(|c| c == '\n')),
224        |comment: &str| Expression::Comment(comment.to_string()),
225    )(input)
226}
227
228pub fn parse_binary_operator(input: &str) -> IResult<&str, BinaryOperation> {
229    alt((
230        map(char('+'), |_| BinaryOperation::Plus),
231        map(char('-'), |_| BinaryOperation::Minus),
232        map(char('*'), |_| BinaryOperation::Times),
233        map(char('/'), |_| BinaryOperation::Divide),
234        map(tag("=="), |_| BinaryOperation::Equal),
235        map(tag("!="), |_| BinaryOperation::NotEqual),
236    ))(input)
237}
238
239// Statements
240
241#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
242pub enum Statement {
243    VariableAssignment(String, Expression),
244    ExpressionStatement(Expression),
245    If(Expression, Vec<Statement>, Vec<Statement>),
246    Quidditch(Vec<Statement>),
247    Snitch,
248}
249
250fn parse_statement(input: &str) -> IResult<&str, Statement> {
251    let parser_content = alt((
252        parse_if_statement,
253        parse_snitch_statement,
254        parse_quidditch_statement,
255        parse_variable_assignment,
256        parse_expression_statement,
257    ));
258
259    preceded(multispace0, terminated(parser_content, multispace0))(input)
260}
261
262fn parse_variable_assignment(input: &str) -> IResult<&str, Statement> {
263    let (rest, (var, _, _, _, atom)) = tuple((
264        parse_variable,
265        multispace0,
266        char('='),
267        multispace0,
268        parse_expression,
269    ))(input)?;
270
271    let statement = Statement::VariableAssignment(var.to_string(), atom);
272    Ok((rest, statement))
273}
274
275fn parse_expression_statement(input: &str) -> IResult<&str, Statement> {
276    let (rest, expression) = terminated(parse_expression, multispace0)(input)?;
277    let statement = Statement::ExpressionStatement(expression);
278    Ok((rest, statement))
279}
280
281fn parse_if_statement(input: &str) -> IResult<&str, Statement> {
282    let parse_if = preceded(multispace0, terminated(tag("if"), multispace0));
283    let parse_condition = preceded(multispace0, terminated(parse_expression, multispace0));
284    let parse_true_block = delimited(char('{'), many1(parse_statement), char('}'));
285    let parse_else = preceded(multispace0, terminated(tag("else"), multispace0));
286    let parse_false_block = delimited(char('{'), many1(parse_statement), char('}'));
287
288    map(
289        tuple((
290            preceded(parse_if, parse_condition),
291            parse_true_block,
292            opt(delimited(parse_else, parse_false_block, multispace0)),
293        )),
294        |(cond, true_block, else_block)| {
295            Statement::If(cond, true_block, else_block.unwrap_or(vec![]))
296        },
297    )(input)
298}
299
300fn parse_quidditch_statement(input: &str) -> IResult<&str, Statement> {
301    let parse_quidditch = preceded(multispace0, terminated(tag("quidditch"), multispace0));
302    let parse_block = delimited(char('{'), many1(parse_statement), char('}'));
303
304    map(preceded(parse_quidditch, parse_block), |block| {
305        Statement::Quidditch(block)
306    })(input)
307}
308
309fn parse_snitch_statement(input: &str) -> IResult<&str, Statement> {
310    let parse_snitch = preceded(multispace0, terminated(tag("snitch"), multispace0));
311
312    map(parse_snitch, |_| Statement::Snitch)(input)
313}
314
315// Program
316
317#[derive(Debug, PartialEq, Serialize, Deserialize)]
318pub struct Program(pub Vec<Statement>);
319
320pub fn parse_program(input: &str) -> IResult<&str, Program> {
321    map(many1(terminated(parse_statement, multispace0)), Program)(input)
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    // Atoms
329
330    #[test]
331    fn test_parse_string() {
332        let input = "\"Hello, world!\"";
333        let expected = Atom::String("Hello, world!".to_string());
334        let (_, actual) = parse_string(input).unwrap();
335        assert_eq!(expected, actual);
336    }
337
338    #[test]
339    fn test_parse_variable() {
340        let input = "foo";
341        let expected = Atom::Variable("foo".to_string());
342        let (_, actual) = parse_variable(input).unwrap();
343        assert_eq!(expected, actual);
344    }
345
346    #[test]
347    fn test_parse_boolean_true() {
348        let input = "true";
349        let expected = Atom::Boolean(true);
350        let (_, actual) = parse_boolean(input).unwrap();
351        assert_eq!(expected, actual);
352    }
353
354    #[test]
355    fn test_parse_boolean_false() {
356        let input = "false";
357        let expected = Atom::Boolean(false);
358        let (_, actual) = parse_boolean(input).unwrap();
359        assert_eq!(expected, actual);
360    }
361
362    #[test]
363    fn test_parse_double() {
364        let input = "123.456";
365        let expected = Atom::Double(123.456);
366        let (_, actual) = parse_double(input).unwrap();
367        assert_eq!(expected, actual);
368    }
369
370    #[test]
371    fn test_parse_integer() {
372        let input = "123";
373        let expected = Atom::Integer(123);
374        let (_, actual) = parse_integer(input).unwrap();
375        assert_eq!(expected, actual);
376    }
377
378    #[test]
379    fn test_parse_hogwarts_house() {
380        let input = "Gryffindor";
381        let expected = Atom::HogwartsHouse(HogwartsHouse::Gryffindor);
382        let (_, actual) = parse_hogwarts_house(input).unwrap();
383        assert_eq!(expected, actual);
384    }
385
386    // Expressions
387
388    #[test]
389    fn test_parse_statement_with_whitespaces() {
390        let input = " ~AvadaKedabra ";
391        let expected = Statement::ExpressionStatement(Expression::SpellCast(
392            Spell::AvadaKedabra,
393            Box::new(None),
394        ));
395        let (_, actual) = parse_statement(input).unwrap();
396        assert_eq!(expected, actual);
397    }
398
399    #[test]
400    fn test_parse_spell_cast() {
401        let input = "~AvadaKedabra";
402        let expected = Expression::SpellCast(Spell::AvadaKedabra, Box::new(None));
403        let (_, actual) = parse_spell_cast(input).unwrap();
404        assert_eq!(expected, actual);
405    }
406
407    #[test]
408    fn test_parse_spell_cast_with_string() {
409        let input = "~Revelio \"Hello, world!\"";
410        let expected = Expression::SpellCast(
411            Spell::Revelio,
412            Box::new(Some(Atom::String("Hello, world!".to_string()).into())),
413        );
414        let (_, actual) = parse_spell_cast(input).unwrap();
415        assert_eq!(expected, actual);
416    }
417
418    #[test]
419    fn test_parse_spell_cast_with_string_and_space() {
420        let input = "~Revelio \"Hello, world!\" ";
421        let expected = Expression::SpellCast(
422            Spell::Revelio,
423            Box::new(Some(Atom::String("Hello, world!".to_string()).into())),
424        );
425        let (_, actual) = parse_spell_cast(input).unwrap();
426        assert_eq!(expected, actual);
427    }
428
429    #[test]
430    fn test_parse_binary_operation() {
431        let input = "\"Hello, \" + \"world!\"";
432        let expected = Expression::BinaryOperation(
433            BinaryOperation::Plus,
434            Box::new(Atom::String("Hello, ".to_string()).into()),
435            Box::new(Atom::String("world!".to_string()).into()),
436        );
437        let (_, actual) = parse_binary_operation(input).unwrap();
438        assert_eq!(expected, actual);
439    }
440
441    #[test]
442    fn test_parse_binary_operation_with_variable() {
443        let input = "foo + \"bar\"";
444        let expected = Expression::BinaryOperation(
445            BinaryOperation::Plus,
446            Box::new(Atom::Variable("foo".to_string()).into()),
447            Box::new(Atom::String("bar".to_string()).into()),
448        );
449        let (_, actual) = parse_binary_operation(input).unwrap();
450        assert_eq!(expected, actual);
451    }
452
453    #[test]
454    fn test_parse_binary_operation_with_integer() {
455        let input = "123 + 456";
456        let expected = Expression::BinaryOperation(
457            BinaryOperation::Plus,
458            Box::new(Atom::Integer(123).into()),
459            Box::new(Atom::Integer(456).into()),
460        );
461        let (_, actual) = parse_binary_operation(input).unwrap();
462        assert_eq!(expected, actual);
463    }
464
465    #[test]
466    fn test_parse_binary_operation_with_double() {
467        let input = "123.456 + 456.789";
468        let expected = Expression::BinaryOperation(
469            BinaryOperation::Plus,
470            Box::new(Atom::Double(123.456).into()),
471            Box::new(Atom::Double(456.789).into()),
472        );
473        let (_, actual) = parse_binary_operation(input).unwrap();
474        assert_eq!(expected, actual);
475    }
476
477    #[test]
478    fn test_parse_binary_operation_with_boolean() {
479        let input = "true == false";
480        let expected = Expression::BinaryOperation(
481            BinaryOperation::Equal,
482            Box::new(Atom::Boolean(true).into()),
483            Box::new(Atom::Boolean(false).into()),
484        );
485        let (_, actual) = parse_binary_operation(input).unwrap();
486        assert_eq!(expected, actual);
487    }
488
489    #[test]
490    fn test_parse_binary_operation_with_variable_and_integer() {
491        let input = "foo + 4";
492        let expected = Expression::BinaryOperation(
493            BinaryOperation::Plus,
494            Box::new(Atom::Variable("foo".to_string()).into()),
495            Box::new(Atom::Integer(4).into()),
496        );
497        let (_, actual) = parse_binary_operation(input).unwrap();
498        assert_eq!(expected, actual);
499    }
500
501    #[test]
502    fn test_parse_comment() {
503        let input = "# Hello, world!";
504        let expected = Expression::Comment(" Hello, world!".to_string());
505        let (_, actual) = parse_comment(input).unwrap();
506        assert_eq!(expected, actual);
507    }
508
509    #[test]
510    fn test_sorting_hat() {
511        let input = "🎩✨";
512        let expected = Expression::SortingHat;
513        let (_, actual) = parse_sorting_hat(input).unwrap();
514        assert_eq!(expected, actual);
515    }
516
517    // Statements
518
519    #[test]
520    fn test_parse_variable_assignment() {
521        let input = "foo = \"Hello, world!\"";
522        let expected = Statement::VariableAssignment(
523            "foo".to_string(),
524            Atom::String("Hello, world!".to_string()).into(),
525        );
526        let (_, actual) = parse_variable_assignment(input).unwrap();
527        assert_eq!(expected, actual);
528    }
529
530    #[test]
531    fn test_parse_expression_statement() {
532        let input = "~AvadaKedabra";
533        let expected = Statement::ExpressionStatement(Expression::SpellCast(
534            Spell::AvadaKedabra,
535            Box::new(None),
536        ));
537        let (_, actual) = parse_expression_statement(input).unwrap();
538        assert_eq!(expected, actual);
539    }
540
541    #[test]
542    fn test_parse_expression_statement_with_string() {
543        let input = "~Revelio \"Hello, world!\"";
544        let expected = Statement::ExpressionStatement(Expression::SpellCast(
545            Spell::Revelio,
546            Box::new(Some(Atom::String("Hello, world!".to_string()).into())),
547        ));
548        let (_, actual) = parse_expression_statement(input).unwrap();
549        assert_eq!(expected, actual);
550    }
551
552    #[test]
553    fn test_parse_if() {
554        let input = "if true { ~Revelio 4 }";
555        let expected = Statement::If(
556            Atom::Boolean(true).into(),
557            vec![Statement::ExpressionStatement(Expression::SpellCast(
558                Spell::Revelio,
559                Box::new(Some(Atom::Integer(4).into())),
560            ))],
561            vec![],
562        );
563        let (_, actual) = parse_if_statement(input).unwrap();
564        assert_eq!(expected, actual);
565    }
566
567    #[test]
568    fn test_parse_if_multiple_statements() {
569        let input = "if 4 == 4 {
570          ~Revelio 4 
571          ~AvadaKedabra
572        }";
573        let expected = Statement::If(
574            Expression::BinaryOperation(
575                BinaryOperation::Equal,
576                Box::new(Atom::Integer(4).into()),
577                Box::new(Atom::Integer(4).into()),
578            ),
579            vec![
580                Statement::ExpressionStatement(Expression::SpellCast(
581                    Spell::Revelio,
582                    Box::new(Some(Atom::Integer(4).into())),
583                )),
584                Statement::ExpressionStatement(Expression::SpellCast(
585                    Spell::AvadaKedabra,
586                    Box::new(None),
587                )),
588            ],
589            vec![],
590        );
591        let (_, actual) = parse_if_statement(input).unwrap();
592        assert_eq!(expected, actual);
593    }
594
595    #[test]
596    fn test_parse_if_with_else() {
597        let input = "if y != 11 {
598  ~Revelio \"y is not 11\" 
599} else {
600  ~Lumos
601  ~Revelio \"y is 11\" 
602}";
603        let expected = Statement::If(
604            Expression::BinaryOperation(
605                BinaryOperation::NotEqual,
606                Box::new(Atom::Variable("y".to_string()).into()),
607                Box::new(Atom::Integer(11).into()),
608            ),
609            vec![Statement::ExpressionStatement(Expression::SpellCast(
610                Spell::Revelio,
611                Box::new(Some(Atom::String("y is not 11".to_string()).into())),
612            ))],
613            vec![
614                Statement::ExpressionStatement(Expression::SpellCast(Spell::Lumos, Box::new(None))),
615                Statement::ExpressionStatement(Expression::SpellCast(
616                    Spell::Revelio,
617                    Box::new(Some(Atom::String("y is 11".to_string()).into())),
618                )),
619            ],
620        );
621        let (_, actual) = parse_if_statement(input).unwrap();
622        assert_eq!(expected, actual);
623    }
624
625    #[test]
626    fn test_parse_quidditch() {
627        let input = "quidditch {
628  ~Engorgio x
629  snitch
630  ~Revelio x
631}";
632        let expected = Statement::Quidditch(vec![
633            Statement::ExpressionStatement(Expression::SpellCast(
634                Spell::Engorgio,
635                Box::new(Some(Atom::Variable("x".to_string()).into())),
636            )),
637            Statement::Snitch,
638            Statement::ExpressionStatement(Expression::SpellCast(
639                Spell::Revelio,
640                Box::new(Some(Atom::Variable("x".to_string()).into())),
641            )),
642        ]);
643        let (_, actual) = parse_quidditch_statement(input).unwrap();
644        assert_eq!(expected, actual);
645    }
646
647    // Program
648
649    #[test]
650    fn test_parse_program() {
651        let input = "~AvadaKedabra\n~Revelio \"Hello, world!\"";
652        let expected = Program(vec![
653            Statement::ExpressionStatement(Expression::SpellCast(
654                Spell::AvadaKedabra,
655                Box::new(None),
656            )),
657            Statement::ExpressionStatement(Expression::SpellCast(
658                Spell::Revelio,
659                Box::new(Some(Atom::String("Hello, world!".to_string()).into())),
660            )),
661        ]);
662        let (_, actual) = parse_program(input).unwrap();
663        assert_eq!(expected, actual);
664    }
665
666    #[test]
667    fn test_parse_program_serialized() {
668        let code = r#"index = 0
669
670    quidditch {
671      snake = ~Serpensortia
672      ~WingardiumLeviosa snake
673      ~WingardiumLeviosa snake
674      snake = snake + " some string"
675      ~Revelio snake
676      ~Incendio snake
677      ~Revelio snake
678      ~Engorgio index
679    
680      if index == 4 {
681        snitch # Break loop
682      }
683    }
684    "#;
685        let result = parse_program(code).unwrap();
686        let json = serde_json::to_string(&result).unwrap();
687
688        println!("{}", json);
689
690        assert_eq!(
691            json,
692            r#"["",[{"VariableAssignment":["index",{"Atom":{"Integer":0}}]},{"Quidditch":[{"VariableAssignment":["snake",{"SpellCast":["Serpensortia",null]}]},{"ExpressionStatement":{"SpellCast":["WingardiumLeviosa",{"Atom":{"Variable":"snake"}}]}},{"ExpressionStatement":{"SpellCast":["WingardiumLeviosa",{"Atom":{"Variable":"snake"}}]}},{"VariableAssignment":["snake",{"BinaryOperation":["Plus",{"Atom":{"Variable":"snake"}},{"Atom":{"String":" some string"}}]}]},{"ExpressionStatement":{"SpellCast":["Revelio",{"Atom":{"Variable":"snake"}}]}},{"ExpressionStatement":{"SpellCast":["Incendio",{"Atom":{"Variable":"snake"}}]}},{"ExpressionStatement":{"SpellCast":["Revelio",{"Atom":{"Variable":"snake"}}]}},{"ExpressionStatement":{"SpellCast":["Engorgio",{"Atom":{"Variable":"index"}}]}},{"If":[{"BinaryOperation":["Equal",{"Atom":{"Variable":"index"}},{"Atom":{"Integer":4}}]},["Snitch",{"ExpressionStatement":{"Comment":" Break loop"}}],[]]}]}]]"#
693        );
694    }
695}