/**
 * Constant propogation for common expressions.
 */
module php/strategy/const-prop/analysis/common/expressions

strategies
  /**
   * Print-construct
   */
  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =  
     Print(main)
   ; add-php-simple-value(|PHPInteger(0))

strategies
  /**
   * Exit, should actually terminate the program and stop the analysis.
   * TODO 
   */
  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =  
      Exit(main)
   <+ Die(main) 

strategies
  /**
   * Echo, does not have a value-type
   */
  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =  
     Echo(map(main))

strategies
  /**
   * Eval, either value of a 'return' in the code or False. 
   * TODO
   */
  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =  
     Eval(main)

strategies

  // tmp-strategy, will be expanded with real functioncalls
  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =  
     FunctionCall(id,map(main))
     

/***********************************************************************\
                 Start Array Handling
\***********************************************************************/
strategies
  /**
   * Creation of an array
   */
  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =
      ?term@Array(_)
    ; where( aid := <get-new-php-array-id>) 
    ; Array(map(try(main ; add-array-pairs-to-array(main,get,valid-put,valid-remove|aid))))
    ; add-php-simple-value(|aid)
    ; try(
        where(tmpval := <get> term)
      ; set(|tmpval) 
      )

  /**
   * Adds a array pair to a given ArrayObject.
   * If the value cannot be retrieved the strategy will update
   * the highest index and fail.
   *
   * @type (None(),value) -> Value
   */
  add-array-pairs-to-array(main,get,valid-put,valid-remove|aid) =
     ?Pair(None(),Value(value))
   ; where(
     ( value' := <get-php-simple-value> value
     ; key   := <add-array-entry(|value')> aid 
     ; try(
          <add-array-entry(valid-put|key,<get> value)> aid
     ))
     <+
      <add-array-entry(valid-put|<get> value)> aid
     <+ 
      (<increase-highest-array-index(|1)> aid
      ; fail
      )
   )

  /** 
   * @type (None(),RefValue) -> Value
   */
  add-array-pairs-to-array(main,get,valid-put,valid-remove|aid) =
     ?Pair(None(),RefValue(<main ; ?cvar>))
   ; where(
     if (value-identifier,_) := <extract-php-value-identifier-kind(main,valid-remove|)
                                ; (get-php-value-identifier-pair 
                                <+ get-php-value-identifier-pair(get) 
                                )> cvar
     then <add-array-entry-reference(|value-identifier)> aid
     else <increase-highest-array-index(|1)> aid
        ; fail
     end
   
   )
  /**
   * Adds a array pair to a given Array.
   * Returns the value that is inserted
   *
   * @type (Some(Key(key)),value) -> value
   */
  add-array-pairs-to-array(main,get,valid-put,valid-remove|aid) =
     ?Pair(Some(Key(<main ; ?key>)),Value(value))
   ; where(

         finalkey := <map-type-to-php-array-key(main,valid-remove|aid)> key 
       ;(( value' := <get-php-simple-value> value
         ; <add-array-entry(|finalkey,value')> aid
         ; try(
           <add-array-entry(valid-put|finalkey,<get> value)> aid
         )
         )
        <+
        (try(
          <add-array-entry(valid-put|finalkey,<get> value)> aid
          <+
          <remove-array-entry(valid-remove|finalkey)> aid
         )
        ; <remove-array-entry(|finalkey)> aid // assignment with unknown value
        )
        )
   )

  /**
   * @type (Some(Key(key)),RefValue) -> value
   */
  add-array-pairs-to-array(main,get,valid-put,valid-remove|aid) =
     ?Pair(Some(Key(<main ; ?key>)),RefValue(<main ; ?cvar>))
   ; where(
         finalkey := <map-type-to-php-array-key(main,valid-remove|aid)> key 
       ; if (value-identifier,_) := <extract-php-value-identifier-kind(main,valid-remove|)
                                     ; (get-php-value-identifier-pair 
                                     <+ get-php-value-identifier-pair(get)) 
                                     > cvar
         then <add-array-entry-reference(|finalkey,value-identifier)> aid 
         else <remove-array-entry(|finalkey)> aid // assignment to an unknown entry
             ; try(
              <remove-array-entry(valid-remove|finalkey)> aid
             )
             ; fail  // should fail, otherwise the expression will have a value
         end
   )
    
  extract-php-value-identifier-kind(main,valid-remove|) = 
    (  ?Variable(kind) 
    <+ ( ( ?ArrayAccess(<main ; ?var>,Some(<main ; ?key>)) 
        <+ ?StringAccess(<main ; ?var>,<main ; ?key>) 
         ) 
       ; aid      := <get-simple-array-value> var
       ; finalkey := <map-type-to-php-array-key(main,valid-remove|aid)> key
       ; kind     := (aid,finalkey)
       )
    ); !kind

  /**
   * Extracts the type of an expression and maps this to
   * an array-key.
   *
   * @type Expr -> arraykey
   */
  map-type-to-php-array-key(main,valid-remove|aid) = ?key ;
    if key'   := <main ; get-simple-raw-value> key
       then (  (<?True() > key' ; finalkey := 1)
            <+ (<?False()> key' ; finalkey := 0)
            <+ (<?PHPArray(_)> key' ; fail) // array's can not be keys
            <+ (<is-string> key' ; finalkey := key')
            <+ (<is-int>    key' ; finalkey := key')
            <+ (<is-real>   key' ; finalkey := <int> key' )
            <+ (<?PHPNull()>  key' ; finalkey := "" )
            )
       else <remove-all-array-entries> aid
           ; try(
            <remove-all-array-entries(valid-remove)> aid
           )
       end

/***********************************************************************\
                  End Array Handling
\***********************************************************************/

/***********************************************************************\
                  Start List Handling
\***********************************************************************/
strategies

  expression-const-prop(main,get,set,valid-get,valid-put,valid-remove|) =
      ListAssign(id        ,main)
   ; ?ListAssign(List(vars),array)
   ; where( <has-php-array-value> array
          ; aid := <get-simple-array-value> array
          ; <eval-php-list-expression(valid-get,valid-put|)> (List(vars),aid)
          )
   ; add-php-simple-value(|aid) 
   ; try(
        where(tmpval := <get> array)
     ; set(|tmpval) 
     )
  
  /**
   * Evaluates a tuple of a List(vars) and a given
   * Array Id. The values of the array are retrieved and assigned
   * to the values in vars, if any.
   *
   * @type (List(vars),aid) -> ([],[])
   */
  eval-php-list-expression(valid-get,valid-put|) =
      ?(List(vars),aid)
    ; values      := <get-array-values> aid
    ; <ZipAndAssignLists> (vars, values)
    ; try(
        values' := <get-array-values(valid-get)> aid
      ; <ZipAndAssignLists(valid-get,valid-put|)> (vars, values)
    )

/***********************************************************************\
                 End  List Handling
\***********************************************************************/


/***********************************************************************\
                 Start Inclusion Handling
\***********************************************************************/
strategies
  /**
   * Evaluates an inclusion of an file if the expression given to the
   * include-function can be evaluated.
   */
  inclusion-const-prop(main,get,set,rule-union|)= 
     ( Include(main)
    <+ Require(main) 
     )
    ;( ?term@Include(expr)
    <+ ?term@Require(expr)
     )
   ; where( (value,requireid) := <php-generic-always-include(main|)> expr)
   ; try(
     add-inclusion-id(|requireid)
   ; add-php-simple-value(|value)
   )
   ;try(
       where (val := <get> term)
     ; set(|val)
   )
  
  inclusion-const-prop(main,get,set,rule-union|) = 
     (  IncludeOnce(main)
     <+ RequireOnce(main)
     )
    ;( ?term@IncludeOnce(expr)
    <+ ?term@RequireOnce(expr)
     )
   ; where( (value,requireid) := <php-generic-once-include(main,rule-union|)> expr)
   ; try(
     add-inclusion-id(|requireid)
   ; add-php-simple-value(|value)
   )
   ;try(
       where (val := <get> term)
     ; set(|val)
   )
  
  /**
   * Evaluates a "no-inclusion". This an inclusion that is not possible.
   * It will remove _all_ dynamic rules that are involved in this evaluation.
   *
   * @type include-term -> include-term
   */
   no-inclusion-const-prop(main|name) =
    (  RequireOnce(main) <+ Include(main)
    <+ Require(main) <+ IncludeOnce(main)
    ); 
    where(     php-remove-all-eval-dynamic-rules
         ; try(php-remove-all-eval-dynamic-rules(|name))
    )
   

strategies
  /**
   * Always includes a file with a given filename
   *
   * @type Expr -> (Expr(evaluated),value)
   */
  php-generic-always-include(main|) = ?expr;
       filename    := <get-simple-raw-string-value> expr
     ; inclusionId := <process-inclusion(php-generic-include-strategy(main|))> filename
     ; !(PHPInteger(1),inclusionId) //for now we always return one if suceeded. Should check for an return

  /**
   * Includes a file with a given filename if it is
   * not already in the environment
   *
   * @type Expr -> (Expr(evaluated),value,InclusionId)
   */
  php-generic-once-include(main,rule-union|) = ?expr;
       filename    := <get-simple-raw-string-value> expr
     ; file        := <find-php-file>
     ; <?FILE(filepath)> file
     ; env  := <get-php-environment>
     ; if <is-file-included(|filepath); ?iid> env
       then log(|Debug(), "This file was previously included: ", filepath)
          ; inclusionId := iid
       else if <is-file-maybe-included(|filepath); ?iid> env
            then log(|Debug(), "This file could be previously included: ", filepath)
               ; inclusionId := iid
               ; {| InclusionMode :
                    rules(InclusionMode: _ -> DefIncluded() )
                 ;  <add-inclusion-filename(|filepath,iid)> env
                 |} 
               ; rule-union(id, <process-inclusion(php-generic-include-strategy(main|))> filename)
            else inclusionId := <process-inclusion(php-generic-include-strategy(main|))> filename // not yet included
            end
       end
     ; !(PHPInteger(1),inclusionId) //for now we always return one if suceeded. Should check for an return

  /**
   * Generic part for the inclusion strategy. This strategy is applied
   * to every file that is included. Environment is updated and then the
   * file is evaluated.
   *
   * @type AST -> AST
   */
  php-generic-include-strategy(main|) = 
     ?doc
   ; fill-php-environment(|doc)
   ; <main> doc


/***********************************************************************\
                 End Inclusion Handling
\***********************************************************************/