/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.monad init.meta.exceptional init.meta.format
/-
Remark: we use a function that produces a format object as the exception information.
Motivation: the formatting object may be big, and we may create it on demand.
Remark: We use base_tactic_result to define base_tactic (a monad that combines the state an exceptional monads).
We "merge" them for performance reasons.
-/
inductive base_tactic_result (S : Type) (A : Type) :=
| success   : A → S → base_tactic_result S A
| exception : (options → format) → S → base_tactic_result S A

section
open base_tactic_result
variables {S A : Type}
variables [has_to_string A]

protected meta_definition base_tactic_result.to_string : base_tactic_result S A → string
| (success a s)   := to_string a
| (exception A e s) := "Exception: " + to_string (e options.mk)

protected meta_definition base_tactic_result.has_to_string [instance] : has_to_string (base_tactic_result S A) :=
has_to_string.mk base_tactic_result.to_string
end

meta_definition base_tactic [reducible] (S : Type) (A : Type) :=
S → base_tactic_result S A

namespace base_tactic
variables {S A B : Type}
open base_tactic_result

inline protected meta_definition fmap (f : A → B) (t : base_tactic S A) : base_tactic S B :=
λ s, base_tactic_result.cases_on (t s)
  (λ a s', success (f a) s')
  (λ e s', exception B e s')

inline protected meta_definition bind (t₁ : base_tactic S A) (t₂ : A → base_tactic S B) : base_tactic S B :=
λ s : S,  base_tactic_result.cases_on (t₁ s)
  (λ a s', t₂ a s')
  (λ e s', exception B e s')

inline protected meta_definition return (a : A) : base_tactic S A :=
λ s, success a s

end base_tactic

meta_definition base_tactic.is_monad [instance] (S : Type) : monad (base_tactic S) :=
monad.mk (@base_tactic.fmap S) (@base_tactic.return S) (@base_tactic.bind S)

namespace tactic
variables {S A : Type}
open base_tactic_result

meta_definition fail (e : string) : base_tactic S A :=
λ s, exception A (λ u, to_fmt e) s

meta_definition fail_fmt (e : format) : base_tactic S A :=
λ s, exception A (λ u, e) s

meta_definition failed : base_tactic S A :=
fail "failed"

meta_definition try (t : base_tactic S A) : base_tactic S unit :=
λ s, base_tactic_result.cases_on (t s)
 (λ a, success ())
 (λ e s', success () s)

meta_definition or_else (t₁ t₂ : base_tactic S A) : base_tactic S A :=
λ s, base_tactic_result.cases_on (t₁ s)
  success
  (λ e₁ s', base_tactic_result.cases_on (t₂ s)
     success
     (exception A))

infix `<|>`:1 := or_else

meta_definition skip : base_tactic S unit :=
success ()

open list
meta_definition repeat : list A → (A → base_tactic S unit) → base_tactic S unit
| []      fn := skip
| (e::es) fn := do fn e, repeat es fn

open nat
/- (repeat n t): repeat the given tactic at most n times or until t fails -/
meta_definition repeat_at_most : nat → base_tactic S unit → base_tactic S unit
| 0        t := skip
| (succ n) t := (do t, repeat_at_most n t) <|> skip

/- (do n t) : execute t n times -/
meta_definition repeat_exactly : nat → base_tactic S unit → base_tactic S unit
| 0        t := skip
| (succ n) t := do t, repeat_exactly n t

meta_definition returnex (e : exceptional A) : base_tactic S A :=
λ s, match e with
| exceptional.success a     := base_tactic_result.success a s
| exceptional.exception A f := base_tactic_result.exception A f s
end

/- Decorate t's exceptions with msg -/
meta_definition decorate_ex (msg : format) (t : base_tactic S A) : base_tactic S A :=
λ s, base_tactic_result.cases_on (t s)
  success
  (λ e, !exception (λ u, msg + format.nest 2 (format.line + e u)))

inline meta_definition write (s' : S) : base_tactic S unit :=
λ s, success () s'

inline meta_definition read : base_tactic S S :=
λ s, success s s
end tactic
