/**
 * Strategies that do type juggling.
 * Apply the rules on a PHPPrimitiveType to
 * get a PHPPrimitiveType of the desired type.
 *
 * @author Eric Bouwers
 */
module php/strategy/const-prop/typejuggling

rules 
  /** 
   * Converting to PHPBoolean.
   * see: http://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting
   *
   * @type PHPPrimitiveType -> PHPBoolean
   */
  get-php-boolean-value:
    PHPBoolean(bool) -> PHPBoolean(bool)   // id
    
  get-php-boolean-value:
    PHPInteger(int) -> PHPBoolean(bool)
      where if <eq> (int, 0)
            then bool := False()
            else bool := True()
            end
            
  get-php-boolean-value:
    PHPFloat(real) -> PHPBoolean(bool)
      where if <eq> (real, 0.0)
            then bool := False()
            else bool := True()
            end
            
  get-php-boolean-value:
    PHPString(str) -> PHPBoolean(bool)
      where if    <eq> (str, "")
               <+ <eq> (str, "0")
            then bool := False()
            else bool := True()
            end

  get-php-boolean-value:
    PHPNull() -> PHPBoolean(False())

  get-php-boolean-value:
    aid@PHPArray(_) -> PHPBoolean(bool)
      where  keys        := <get-array-keys> aid
          ; if <eq> (0, <length> keys)
            then bool := True()
            else bool := False()
            end

rules
  /**
   * Converting to PHPInteger.
   * see: http://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting
   *
   * @type PHPPrimitiveType -> PHPInteger
   */
  get-php-integer-value:
    PHPInteger(i) -> PHPInteger(i) //id
    
  get-php-integer-value:
    PHPBoolean(True()) -> PHPInteger(1)   
 
  get-php-integer-value:
    PHPBoolean(False()) -> PHPInteger(0)  
    
  get-php-integer-value:
    PHPFloat(float) -> PHPInteger(i)
      where i := <int> float

  get-php-integer-value:
    PHPString(str) -> PHPInteger(i)
      where  <not(eq)> (str,"")
           ; str' := <extract-integer-part> str
           ; if <eq> (str',"")
             then i := 0                         // Empty string is 0
             else i := <string-to-int> str'
             end

  get-php-integer-value:
    PHPString(str) -> PHPInteger(0)
      where  <eq> (str,"")

  get-php-integer-value:          //NOTE: not defined in manual
    PHPNull() -> PHPInteger(0)

  get-php-integer-value:         //NOTE: not defined in manual
    aid@PHPArray(_) -> value
      where bool-value := <get-php-boolean-value> aid
          ; value      := <get-php-integer-value> bool-value

rules
  /**
   * Converting to PHPFloat.
   * see: http://www.php.net/manual/en/language.types.float.php#language.types.float.casting
   *
   * @type PHPPrimitiveType -> PHPFloat
   */
  get-php-float-value:
    PHPFloat(f) -> PHPFloat(f)

  get-php-float-value:
    PHPInteger(i) -> PHPFloat(i')
      where i' := <add> (i, 0.0)

  get-php-float-value:
    PHPBoolean(True()) -> PHPFloat(1)

  get-php-float-value:
    PHPBoolean(False()) -> PHPFloat(0)

  get-php-float-value:
    PHPString(str) -> PHPFloat(f)
      where  <not(eq)> (str,"")
           ; str' := <extract-float-part> str
           ; if <eq> (str',"")               // Want to make sure that string without
             then fail                       // float start failes. It has no counter-parts
             else f := <string-to-real> str' // in float
             end
           
  get-php-float-value:
    PHPString(str) -> PHPFloat(0)      // An empty string has no counterpart
      where  <eq> (str,"")             // in float.
           ; fail                      // So it always fails

  get-php-float-value:          //NOTE: not defined in manual
    PHPNull() -> PHPFloat(0)

  get-php-float-value:         //NOTE: implementation can change
    aid@PHPArray(_) -> value
      where int-value  := <get-php-integer-value> aid
          ; value      := <get-php-float-value> int-value

strategies
  /**
   * Introduction of pseudo-type number. It tries to get a float- or an integer
   * value. This strategy should be used for everything that is documented with
   * number values.
   */
  get-php-number-value =
     ?val
    ; if   <?PHPString(_)> val               // should only consider float when string
        <+ <?PHPFloat(_)> val                // or float
      then (  <get-php-float-value>   val
           <+ <get-php-integer-value> val
           )
      else <get-php-integer-value> val
      end

rules
  /**
   * Converting to PHPString.
   * see: http://www.php.net/manual/en/language.types.string.php#language.types.string.casting
   *
   * @type PHPPrimitiveType -> PHPString
   */
  get-php-string-value:
     PHPString(f) -> PHPString(f)

  get-php-string-value:
     PHPBoolean(True()) -> PHPString("1")

  get-php-string-value:
     PHPBoolean(False()) -> PHPString("")

  get-php-string-value:
     PHPInteger(i) -> PHPString(str)
       where str := <int-to-string> i

  get-php-string-value:
     PHPFloat(f) -> PHPString(str)
       where str := <real-to-string> f

  get-php-string-value:
     PHPNull() -> PHPString("")
     
  get-php-string-value:
     PHPArray(_) -> PHPString("Array")

rules
  /**
   * Converting to PHPArray
   * see: http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
   *
   * @type PHPPrimitiveType -> PHPArray
   */
  get-php-array-value:
     PHPArray(aid) -> PHPArray(aid) //id

  get-php-array-value:
     t@PHPString(_) -> aid
       where aid := <generic-get-php-array-value> t

  get-php-array-value:
     t@PHPFloat(_) -> aid
       where aid := <generic-get-php-array-value> t

  get-php-array-value:
     t@PHPInteger(_) -> aid
       where aid := <generic-get-php-array-value> t

  get-php-array-value:
     t@PHPBoolean(_) -> aid
       where aid := <generic-get-php-array-value> t

  get-php-array-value:
     PHPNull() -> aid
       where aid         := <get-new-php-array-id>

strategies
  /**
   * Generic type to Array. It will make an array with one index
   * with the value the strategy is applied on.
   *
   * @type PHPPrimitiveType -> PHPArray
   */
  generic-get-php-array-value =
     ?phpprim
   ; aid          := <get-new-php-array-id>
   ; <add-array-entry(|0,phpprim)> aid

strategies
  /**
   * Extracts the start of a string that can be interperted as an
   * integer.
   *
   * @type String -> String
   */
  extract-integer-part =
      explode-string
    ; (  try-take-pos-take-neg(take-while(is-num))
      <+ take-while(is-num)
      )
    ; implode-string

  /**
   * Extracts the start of a string that can be interperted as an
   * float.
   *
   * @type String -> String
   */
  extract-float-part =
      explode-string
    ; (  try-take-pos-take-neg(take-float-part)
      <+ take-float-part
      )
    ; implode-string

  /**
   * Tries to match element of a list given a strategy.
   * It first matches on a  "+" or a "-".
   *
   * @type: List(Char) -> List(Char)
   */
  try-take-pos-take-neg(s) =
      take-positive-part(s)
   <+ take-negative-part(s)

  /**
   * Matches on the "+" and a givens strategy. Build the result as
   * the "+" and the result of the strategy.
   *
   * @type: List(Char) -> List(Char)
   */
  take-positive-part(s) =
      ?[ 43 | nums ]
    ; <s> nums
    ; ![ 43 | <id> ]

  /**
   * Matches on the "-" and a givens strategy. Build the result as
   * the "-" and the result of the strategy.
   *
   * @type: List(Char) -> List(Char)
   */
  take-negative-part(s) =
      ?[ 45 | nums ]
    ; <s> nums
    ; ![ 45 | <id> ]

  /**
   * Returns the elements of a list of Chars the correspond
   * to a "." and a sequence of numbers.
   *
   * @type: List(Char) -> List(Char)
   */
  take-decimal-part =
      ?[46 | tail]
    ; <take-while(is-num)> tail
    ; ![ 46 | <id> ]

  /**
   * Returns the elements of a list of Chars the correspond
   * to a "e" and a sequence of numbers.
   *
   * @type: List(Char) -> List(Char)
   */
  take-exponent-part =
      ?[101 | tail]
    ; <take-while(is-num)> tail
    ; ![101 | <id> ]

  /**
   * Returns the elements of a list of Chars the correspond
   * to a "E" and a sequence of numbers.
   *
   * @type: List(Char) -> List(Char)
   */
  take-exponent-part =
      ?[69 | tail]
    ; <take-while(is-num)> tail
    ; ![69 | <id> ]

rules
    /**
     * Takes a list of Chars and extracts the part that can be interpreted as a
     * float. This is an optional list of numbers, an optional point, a list of
     * numbers followed by an optional e/E with numbers behind it.
     *    (0-9]+ [\.]? [0-9]+? ([eE][0-9]+)?
     *
     * The rule truncates the rest of the list.
     *
     * @Type: List(Char) -> List(Char)
     */
    // No digits OR "." followed by numbers
    take-float-part :
      chars  -> res
        where  ([],_,_) := <split-fetch-keep(not(is-num))> chars    //Does not start with a digit
             ; (  res := <take-decimal-part> chars
               <+ res := []
               )

    // numbers "." numbers ("eE" numbers)?
    take-float-part :
      chars  -> result
        where  ([],_,_) := <split-fetch-keep(is-num)> chars    //Does start with a digit
             ; (nat,46,tail) := <split-fetch-keep(not(is-num))> chars
             ; if   (decimal,101,rest) := <split-fetch-keep(not(is-num))> tail
                 <+ (decimal,69 ,rest) := <split-fetch-keep(not(is-num))> tail
               then  exp := <take-exponent-part> [ 69  | rest ]
                   ; base := Conc(nat,[ 46 | decimal])
                   ; result := Conc(base,exp)
               else  decimal := <take-decimal-part> [ 46 | tail ]
                   ; result := Conc(nat,decimal)
               end


    // numbers "e" numbers OR numbers "E" numbers
    take-float-part :
      chars  -> result
        where  ([],_,_) := <split-fetch-keep(is-num)> chars    //Does start with a digit
             ; ( (  (nat,101,rest) := <split-fetch-keep(not(is-num))> chars
                 ;  exp := <take-exponent-part> [ 101 | rest ]
                 )
               <+
                 ( (nat,69 ,rest) := <split-fetch-keep(not(is-num))> chars
                 ; exp := <take-exponent-part> [ 69  | rest ]
                 )
               )
             ; result := Conc(nat,exp)