% crochet

// This one is a pretty straight-forward interpreter, so there isn't
// much of note. That said, the current version of Lingua (Crochet's
// PEG DSL) is a pretty bare-bones prototype, which is roughly why
// there's about 50 lines in addition to the PEG grammar that are all
// "oh, right, we need to actually normalise the data Lingua gives us
// so it's usable, since it won't reify numbers".
//
// There's also a lot of equality implementations here because, again,
// the Crochet compiler is not generating these yet. (Yes, deriving WILL
// come. Eventually).
//
// The only difference between part 1 and part 2 are the evaluation
// semantics of the instructions, so there are different evaluation
// frame types for each one.

singleton day2;

// -- Parsing woes (really, Lingua should be dealing with this aaaaaa)
abstract d2-instruction;
type d2i-forward(global value is integer) is d2-instruction;
type d2i-up(global value is integer) is d2-instruction;
type d2i-down(global value is integer) is d2-instruction;

type d2-program(global instructions is list<d2-instruction>);

implement equality for d2-instruction;
command d2-instruction === d2-instruction = false;
command d2i-forward === (That is d2i-forward) = (self value === That value);
command d2i-up === (That is d2i-up) = (self value === That value);
command d2i-down === (That is d2i-down) = (self value === That value);

implement equality for d2-program;
command d2-program === (That is d2-program) = (self instructions === That instructions);

command day2 parse: (Input is text) do
  let Tree = day2-grammar parse: Input;
  condition
    when Tree is error => panic message: Tree reason tag: "syntax-error";
    when Tree is ok => Tree value normalise;
  end
test
  let Program = "forward 5
                 down 5
                 forward 8
                 up 3
                 down 8
                 forward 2";
  assert (day2 parse: Program) === new d2-program([
    new d2i-forward(5),
    new d2i-down(5),
    new d2i-forward(8),
    new d2i-up(3),
    new d2i-down(8),
    new d2i-forward(2),    
  ])
end

command d2-ast normalise =
  new d2-program(self.instructions map: (_ normalise));

command d2-node--forward normalise = new d2i-forward(#integer try-parse: self.value | value-or-panic: "not an integer");
command d2-node--up normalise = new d2i-up(#integer try-parse: self.value | value-or-panic: "not an integer");
command d2-node--down normalise = new d2i-down(#integer try-parse: self.value | value-or-panic: "not an integer");


// -- Evaluation woes (sadly you can't escape this one)
type d2-frame(
  global x is integer,
  global depth is integer,
);

implement equality for d2-frame;
command d2-frame === (That is d2-frame) =
  (self x === That x) and (self depth === That depth);

command d2-frame evaluate: (I is d2i-forward) =
  new d2-frame(self x + I value, self depth);

command d2-frame evaluate: (I is d2i-down) =
  new d2-frame(self x, self depth + I value);

command d2-frame evaluate: (I is d2i-up) =
  new d2-frame(self x, self depth - I value);

command #d2-frame initial =
  new d2-frame(0, 0);

command d2-frame run: (Program is d2-program) do
  Program instructions fold-from: self
                       with: (_ evaluate: _)
test
  let Program = new d2-program([
    new d2i-forward(5),
    new d2i-down(5),
    new d2i-forward(8),
    new d2i-up(3),
    new d2i-down(8),
    new d2i-forward(2),
  ]);
  assert (#d2-frame initial run: Program) === new d2-frame(15, 10);
end

command day2 part1 do
  let Program = day2 parse: day2 input;
  let Frame = #d2-frame initial run: Program;
  Frame x * Frame depth;
test
  assert day2 part1 === 1_746_616;
end

// -- Part 2 has a different evaluation semantics
type d2-frame2(
  global x is integer,
  global aim is integer,
  global depth is integer,
);

implement equality for d2-frame2;
command d2-frame2 === (That is d2-frame2) =
  (self x === That x) and
  (self aim === That aim) and
  (self depth === That depth);

command d2-frame2 evaluate: (I is d2i-forward) =
  new d2-frame2(self x + I value, self aim, self depth + (self aim * I value));

command d2-frame2 evaluate: (I is d2i-down) =
  new d2-frame2(self x, self aim + I value, self depth);

command d2-frame2 evaluate: (I is d2i-up) =
  new d2-frame2(self x, self aim - I value, self depth);

command #d2-frame2 initial =
  new d2-frame2(0, 0, 0);

command d2-frame2 run: (Program is d2-program) do
  Program instructions fold-from: self
                       with: (_ evaluate: _)
test
  let Program = new d2-program([
    new d2i-forward(5),
    new d2i-down(5),
    new d2i-forward(8),
    new d2i-up(3),
    new d2i-down(8),
    new d2i-forward(2),
  ]);
  assert (#d2-frame2 initial run: Program) === new d2-frame2(15, 10, 60);
end

command day2 part2 do
  let Program = day2 parse: day2 input;
  let Frame = #d2-frame2 initial run: Program;
  Frame x * Frame depth;
test
  assert day2 part2 === 1_741_971_043;
end
