Problem with notation in CPS ceval_step extension of LF's ImpCEvalFun

I am trying to implement Logical Foundation’s ImpCEvalFun with an extension for nondeterminism.
In order for the evaluation of programs like:

<{
  (X := 1 !! X := 2);
  (X := 3);
  X = 2 -> skip
}>

Where !! is equivalent to non-deterministic choice and -> represents a guard on which if failing, you need to backtrack and choose an alternative, I need to write my evaluator in continuation-passing style.

I started by implementing a naif version of the step evaluator in CSP:

Fixpoint ceval_step_cont_naif (st: state) (c: com) (cont: list (state * com)) (i: nat) :=
  match i with
  | O => OutOfGas
  | S i' => match c with
            | <{skip}>     => match cont with
                              | [] => Success (st, cont)
                              | (st', <{skip}>) :: cont' => Success (st, cont')
                              | (st', c') :: cont' => (ceval_step_cont_naif st c' ((st, <{skip}>) :: cont') i')
                              end
            | <{x := v}>   => match cont with
                              | [] => Success ((x !-> aeval st v ; st), cont)
                              | (st', <{skip}>) :: cont' => Success ((x !-> aeval st v ; st), cont')
                              | (st', c') :: cont' => (ceval_step_cont_naif
                                                         (x !-> aeval st v; st)
                                                         c'
                                                         (((x !-> aeval st v; st), <{skip}>) :: cont')
                                                         i')
                              end
            | <{c1 ; c2}>  => match cont with
                              | [] => (ceval_step_cont_naif
                                         st
                                         c1
                                         ((st, c2) :: cont)
                                         i')
                              | (st', <{skip}>) :: cont' => (ceval_step_cont_naif
                                                               st
                                                               c1
                                                               ((st, c2) :: cont')
                                                               i')
                              | (st', c') :: cont' => (ceval_step_cont_naif
                                                         st
                                                         c1
                                                         ((st, <{c2 ; c'}>) :: cont')
                                                         i')
                              end
            | <{if b then c1 else c2 end}>
                           => if (beval st b) then (ceval_step_cont_naif st c1 cont i')
                              else (ceval_step_cont_naif st c2 cont i')
            | <{while b do c' end}>
                           => if (beval st b)
                              then
                                match cont with
                                | [] => (ceval_step_cont_naif
                                           st
                                           c'
                                           ((st, c) :: cont)
                                           i')
                                | (st', <{skip}>) :: cont' => (ceval_step_cont_naif
                                                                      st
                                                                      c'
                                                                      ((st, c) :: cont')
                                                                      i')
                                | (st', c'') :: cont' => (ceval_step_cont_naif
                                                            st
                                                            c'
                                                            ((st, <{c ; c''}>) :: cont')
                                                            i')
                                end
                              else
                                match cont with
                                | [] => Success (st, cont)
                                | (st', <{skip}>) :: cont' => Success (st, cont')
                                | (st', c'') :: cont' => (ceval_step_cont_naif
                                                            st
                                                            c''
                                                            ((st, <{skip}>) :: cont')
                                                            i')
                                end
            (* Here we are forcing the order of evaluation to start on the first term *)
            | <{c1 !! c2}> => match cont with
                              | [] => (ceval_step_cont_naif
                                         st
                                         c1
                                         ((st, <{skip}>) :: (st, c2) :: cont)
                                         i')
                              | (st', <{skip}>) :: cont' => (ceval_step_cont_naif
                                                               st
                                                               c1
                                                               ((st, <{skip}>) :: (st, c2) :: cont')
                                                               i')
                              | (st', c') :: cont' => (ceval_step_cont_naif
                                                         st
                                                         c1
                                                         ((st, c') :: (st, <{c2 ; c'}>) :: cont')
                                                         i')
                              end
            | <{b -> c'}>   => if (beval st b) then (ceval_step st c' cont i')
                               else match cont with
                                    | [] => Fail
                                    | [(_, _)] => Fail
                                    | (_, _) :: (st', alt_c) :: cont' => (ceval_step_cont_naif
                                                                            st'
                                                                            alt_c
                                                                            cont'
                                                                            i')
                                    end
            end
  end.

However, after noticing a lot of repetition, I tried writing a notation to simplify the “plundering”.

(* Plumbering hiding in continuation-passing style. None is equivalent to default *)
Reserved Notation "'WITH_CONT' cont 'LAST_CMD' f_last 'ELSE' new_st other_c f_cont gas"
(no associativity, at level 60).

Fixpoint ceval_step_cont (st: state) (c: com) (cont: list (state * com)) (i: nat) :=
  match i with
  | O => OutOfGas
  | S i' => match c with
            | <{skip}>     => WITH_CONT cont
                              LAST_CMD (fun cont' => Success (st, cont'))
                              ELSE st None (fun _ cont' => (([], <{skip}>) :: cont')) i'
            | <{x := v}>   => WITH_CONT cont
                              LAST_CMD (fun cont' => Success ((x !-> aeval st v ; st), cont'))
                              ELSE (x !-> aeval st v; st) None (fun _ cont' => ([], <{skip}> :: cont')) i'
            | <{c1 ; c2}>  => WITH_CONT cont
                              LAST_CMD (fun cont' => ceval_step_cont st c1 ((st, c2) :: cont') i')
                              ELSE st c1 (fun c' cont' => ([], <{c2 ; c'}>) :: cont') i'
            | <{if b then c1 else c2 end}>
                           => if (beval st b) then (ceval_step_cont st c1 cont i')
                              else (ceval_step_cont st c2 cont i')
            | <{while g do b end}>
                           => if (beval st g)
                              then
                                WITH_CONT cont
                                LAST_CMD (fun cont' => ceval_step_cont st b (([], c) :: cont')) i'
                                ELSE st b (fun c' cont' => (([], <{c ; c'}>) :: cont')) i'
                              else
                                WITH_CONT cont
                                LAST_CMD (fun cont => Success (st, cont))
                                ELSE st None (fun _, cont' => (([], <{skip}>) :: cont')) i'
            (* Here we are forcing the order of evaluation to start on the first term *)
            | <{c1 !! c2}> => WITH_CONT cont
                              LAST_CMD (fun cont => ceval_step_cont st c1 (([], <{skip}>) :: (st, c2) :: cont) i')
                              ELSE st c1 (fun c' cont' => ((st, c') :: (st, <{c2 ; c'}>) :: cont')) i'
            | <{b -> c'}>   => if (beval st b) then ceval_step st c' cont i'
                               else match cont with
                                    | [] => Fail
                                    | [(_, _)] => Fail
                                    | (_, _) :: (st', alt_c) :: cont' => ceval_step_cont st' alt_c cont' i'
                                    end
            end
  end

  where "'WITH_CONT' cont 'LAST_CMD' f_last 'ELSE' new_st c f_cont gas" :=
    (match cont with
    | [] => f_last cont
    | (_, <{skip}>) :: cont' => f_last cont'
    | (_, c') :: cont' => ceval_step_cont
                          new_st
                          (if (c =? None) then c' else c)
                          (f_cont c' cont')
                          gas
    end)
.

It says that it is expecting a term level 200. I am not very used to working with defining my own notations, and as such, I have tried to look at the documentation of notation, but I haven’t been able to solve this.

Any help would be great. Thanks!

Try this:

Reserved Notation "'WITH_CONT' cont 'LAST_CMD' f_last 'ELSE' new_st other_c f_cont gas"
(no associativity, at level 60, new_st at level 0, other_c at level 0, f_cont at level 0).

You notation creates an ambiguity: ELSE a b c d could also mean ELSE (a b c d), bundling the four terms as a single application term for new_st, so the other terms for other_c, f_cont and gas are missing. And indeed, that is the default interpretation (which can never parse successfully).

Setting at level 0 prevents those terms from being applications. They must be parenthesized first.

Another way is to always put symbols between non-terminals. Like some commas (as long as there is at least one non-comma symbol to not break the notation of tuples):

Reserved Notation "'WITH_CONT' cont 'LAST_CMD' f_last 'ELSE' new_st , other_c , f_cont , gas"

or more names

Reserved Notation "'WITH_CONT' cont 'LAST_CMD' f_last 'ELSE' 'STATE' new_st 'COMMAND' other_c 'CONT' f_cont 'GAS' gas"
1 Like

Thank you very much! That solved it.
Indeed I ended with the second approach, which made the code more readable.
Thanks once again.

Javier