/**
 * Conditionals
 */
module strategy/conditional
imports strategy/general/option

strategies // Equality

  /**
   * Tests whether two terms are equal.
   */

  eq = 
    ?(x, x)

  /**
   * Tests whether two terms are equal.
   */

  equal =
    ?(x, x)

  /**
   * Tests whether current term is equal to argument term.
   */

  equal(|x) =
    ?x

  /**
   * Tests whether two argument terms are equal to each other.
   */

  equal(|x, y) =
    where(!x => y)

strategies // Control-flow combinators

  /**
   * try(s) tries to apply s to the current term, but
   * returns it unchanged (and succeeds) when s fails.
   */

  try(s) = 
    s <+ id

  if(c, b) = 
    c < b + id

  if(c, b1, b2) = 
    c < b1 + b2

  /**
   * Apply restoring action 'rest' if s fails, and then fail.
   *
   * Typically useful if s performs side effects that should be
   * restored/undone in case s fails.
   */

  restore(s, rest) = 
    s <+ (rest; fail)

  /**
   * Apply restoring action 'rest' after s terminates, and preserve
   * success/failure behaviour of s.
   *
   * Typically useful if s performs side effects that should be
   * restored always, e.g., when maintaining scope information.
   */

  restore-always(s, rest) = 
    s < rest + (rest; fail)

  /**
   * Applies s followed by f whether s failed or not.
   *
   * @result <s> input
   * @fail   s or f failed (consider try(f))
   * @type   a -> b, (a or b) -> _ :: a -> b
   */

  finally(s, f) = 
    s < where(f) + (where(f); fail)

strategies // Boolean combinators

  /**
   * true is a synonym for id.
   */

  true = 
    id

  /**
   * false is a synonym for id.
   */

  false = 
    fail

  /**
   * ior(s1, s2) implements 'inclusive or', that is, the
   * inclusive choice of s1 and s2. It first tries s1, if
   * that fails it applies s2 (just like s1 <+ s2). However,
   * when s1 succeeds it also tries to apply s2.
   *
   * The results of the transformations are returned.
   */

  ior(s1, s2) = 
    (s1; try(s2)) <+ s2

  /**
   * or(s1, s2) is similar to ior(s1,s2), but the application
   * of the strategies is only tested.
   */

  or(s1, s2) = 
    if s1 then try(test(s2)) else test(s2) end

  /**
   * and(s1, s2) applies s1 and s2 to the current
   * term and succeeds if both succeed. s2 will always
   * be applied, i.e., and is *not* a short-circuit 
   * operator
   */

  and(s1, s2) = 
    if s1 then test(s2) else test(s2); fail end

strategies

  // a -> b :: a -> Option(b)
  maybe(s) = !Some(<s>) <+ !None()

  // a -> b, () -> b :: Option(a) -> b
  maybe(s1, s2) : Some(a) -> <s1> a
  maybe(s1, s2) : None()  -> <s2> ()