%{
    module T = Qsp_syntax.T
    open StdLabels

    type action_block =
        { loc : Qsp_syntax.S.pos
        ; expression : 
          Analyzer.Expression.t'
        ; body : Analyzer.Instruction.t list
        ; pos : Qsp_syntax.S.pos
        ; clauses : (
            ( (Analyzer.Expression.t', Analyzer.Instruction.t) Qsp_syntax.S.clause list 
            * (Qsp_syntax.S.pos * Analyzer.Instruction.t list) option
            ) option )
        }

    module Helper = Qsp_syntax.S.Helper(Analyzer.Expression)
%}

%parameter<Analyzer: Qsp_syntax.S.Analyzer>
%start <(Analyzer.context -> Analyzer.Location.t)>main
%start<(Analyzer.context -> Analyzer.Location.t)>dynamics

%on_error_reduce instruction unary_operator assignation_operator

%% 

main:
    | before_location*
      start_location
      EOL+
      instructions = line_statement*
      LOCATION_END
    { 
        let instructions = List.map instructions ~f:(Analyzer.Instruction.v) in
        fun context -> Analyzer.Location.location context $loc instructions
    }

dynamics:
    | EOL*
      instructions = line_statement+
      EOF
    { 
        let instructions = List.map instructions ~f:(Analyzer.Instruction.v) in
        fun context -> Analyzer.Location.location context $loc instructions
    }
    | EOL*
      b = inlined_block(EOF)
    { 
        let instruction = (Analyzer.Instruction.v b) in 
        fun context -> Analyzer.Location.location context $loc [instruction] 
    }

before_location:
    | EOL {}
    | COMMENT EOL { }

(* Defer the registration here, and ensure we get a valid rule. *)
start_location:
    | l = LOCATION_START
  {
      ignore (l ())
  }

(* All these statement should terminate with EOL *)
line_statement:
    | COMMENT EOL+ { Analyzer.Instruction.comment $loc }
    | COLUMN i=IDENT EOL* { Analyzer.Instruction.location $loc i }
    | a = action_bloc(IF, elif_else_body) 
    { let {loc; expression; body; pos; clauses } = a in 
        let elifs, else_ = match clauses with 
        | None -> [], None
        | Some (elifs, else_) -> (elifs, else_)
        in
        Analyzer.Instruction.if_
            loc 
            (pos, expression, body) 
            ~elifs
            ~else_
      }
    | a = action_bloc(ACT, empty_body) 
      { let {loc; expression; body;  _}  = a in 
        Analyzer.Instruction.act loc ~label:expression body
      }
    | b = inlined_block(line_sep)
      { b }

(** Represent an instruction which can either be on a single line, 
    or created in a block until an END 
 *)
%inline action_bloc(TOKEN, BODY):
    | TOKEN
      e = expression 
      COLUMN EOL+
      s = line_statement*
      b = BODY
      END
      line_sep
      { 
        let expression = Analyzer.Expression.v e in
        let clauses = match b with
        | None -> None
        | Some (elifs, clauses) -> 
            let elifs = begin match elifs with 
            | [] -> []
            | _ -> 
              List.map elifs
              ~f:(fun ((pos:Qsp_syntax.S.pos), e, instructions) -> 
                let e = Analyzer.Expression.v e in
                (pos, e, instructions)
              )

            end in
          Some (elifs, clauses)
        in 

        { loc = $loc 
        ; expression
        ; body = s
        ; clauses
        ; pos = $loc(s)
        }
      }

empty_body:
    | { None }

elif:
    | ELIF
      e = expression 
      COLUMN EOL+
      s = line_statement*
      { $loc, e, s }

else_:
    | ELSE EOL+
      expressions = line_statement*
      { Some ($loc, expressions) }
    | { None }


elif_else_body: 
    | elifs = elif*
      else_ = else_
      { Some (elifs, else_) }


%inline line_sep:
    | EOL+
    | AMPERSAND+ EOL*
    {}