(********************************************************************)
(*                                                                  *)
(*  inline.s7i    Handle inline functions.                          *)
(*  Copyright (C) 1990 - 1994, 2004 - 2018  Thomas Mertes           *)
(*                                                                  *)
(*  This file is part of the Seed7 compiler.                        *)
(*                                                                  *)
(*  This program is free software; you can redistribute it and/or   *)
(*  modify it under the terms of the GNU General Public License as  *)
(*  published by the Free Software Foundation; either version 2 of  *)
(*  the License, or (at your option) any later version.             *)
(*                                                                  *)
(*  This program is distributed in the hope that it will be useful, *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of  *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *)
(*  GNU General Public License for more details.                    *)
(*                                                                  *)
(*  You should have received a copy of the GNU General Public       *)
(*  License along with this program; if not, write to the           *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


const type: inlineParamData is new struct
    var reference: paramValue is NIL;
    var integer: paramNum is 0;
    var reference: actualParam is NIL;
  end struct;

const type: inlineParamHash is hash [reference] array inlineParamData;

var inlineParamHash: inlineParam is inlineParamHash.EMPTY_HASH;


const func boolean: isPointerParam (in reference: a_param) is forward;

const func boolean: isCopyParam (in reference: a_param) is forward;

const func boolean: canTakeAddress (in reference: an_expression) is forward;

const func boolean: getConstant (in reference: currExpr,
    inout reference: evaluatedExpr) is forward;

const proc: process_local_var_declaration (in reference: current_object,
    inout expr_type: c_expr) is forward;


const proc: pushInlineParam (in reference: obj, in inlineParamData: paramData) is func

  begin
    if obj in inlineParam then
      inlineParam @:= [obj] [] (paramData) & inlineParam[obj];
    else
      inlineParam @:= [obj] [] (paramData);
    end if;
  end func;


const proc: popInlineParam (in reference: obj) is func

  begin
    if length(inlineParam[obj]) = 1 then
      excl(inlineParam, obj);
    else
      inlineParam @:= [obj] inlineParam[obj][2 ..];
    end if;
  end func;


const proc: assign_inline_param (in reference: formal_param,
    in reference: actual_param, inout expr_type: c_expr) is func

  local
    var integer: temp_num is 0;
  begin
    if isPointerParam(formal_param) then
      if category(actual_param) = REFPARAMOBJECT then
        if actual_param in inlineParam and
            inlineParam[actual_param][1].paramNum <> 0 then
          c_expr.expr &:= "par_";
          c_expr.expr &:= str(inlineParam[actual_param][1].paramNum);
          c_expr.expr &:= "_";
        end if;
        c_expr.expr &:= "o_";
        create_name(actual_param, c_expr.expr);
      elsif canTakeAddress(actual_param) then
        c_expr.expr &:= "&(";
        process_expr(actual_param, c_expr);
        c_expr.expr &:= ")";
      else
        incr(c_expr.temp_num);
        temp_num := c_expr.temp_num;
        c_expr.temp_decls &:= type_name(getExprResultType(actual_param));
        c_expr.temp_decls &:= " tmp_";
        c_expr.temp_decls &:= str(temp_num);
        c_expr.temp_decls &:= ";\n";
        c_expr.expr &:= "(tmp_";
        c_expr.expr &:= str(temp_num);
        c_expr.expr &:= "=(";
        c_expr.expr &:= type_name(getExprResultType(actual_param));
        c_expr.expr &:= ")(";
        getAnyParamToExpr(actual_param, c_expr);
        c_expr.expr &:= "), &tmp_";
        c_expr.expr &:= str(temp_num);
        c_expr.expr &:= ")";
      end if;
    else
      getAnyParamToExpr(actual_param, c_expr);
    end if;
  end func;


const proc: push_inline_func_param (in reference: formal_param,
    in reference: actual_param, inout expr_type: c_expr) is func

  local
    var category: paramCategory is category.value;
    var reference: evaluatedParam is NIL;
    var type: param_type is void;
    var string: param_name is "";
    var expr_type: actual_param_expr is expr_type.value;
    var integer: temp_num is 0;
    var inlineParamData: paramData is inlineParamData.value;
  begin
    paramCategory := category(formal_param);
    if paramCategory = TYPEOBJECT then
      c_expr.expr &:= "/* attr t_";
      c_expr.expr &:= str(typeNumber(getValue(formal_param, type)));
      c_expr.expr &:= " ";
      c_expr.expr &:= str(getValue(formal_param, type));
      c_expr.expr &:= "*/\n";
    elsif paramCategory <> SYMBOLOBJECT then
      param_type := getType(formal_param);
      c_expr.expr &:= diagnosticLine(formal_param);
      if isFunc(param_type) then
        c_expr.expr &:= "/* push proc param o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */\n";
        paramData.paramValue := actual_param;
        paramData.actualParam := actual_param;
        pushInlineParam(formal_param, paramData);
      elsif not isVar(formal_param) and getConstant(actual_param, evaluatedParam) then
        c_expr.expr &:= "/* push const param o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */\n";
        setVar(evaluatedParam, FALSE);
        paramData.paramValue := evaluatedParam;
        paramData.actualParam := actual_param;
        pushInlineParam(formal_param, paramData);
      else
        c_expr.expr &:= "/* push param o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */ ";
        if not isVar(formal_param) then
          if isPointerParam(formal_param) then
            c_expr.temp_decls &:= "const ";
          elsif useConstPrefix(param_type) then
            c_expr.temp_decls &:= "const_";
          end if;
        end if;
        c_expr.temp_decls &:= type_name(param_type);
        c_expr.temp_decls &:= " ";
        if isPointerParam(formal_param) then
          c_expr.temp_decls &:= "*";
        end if;
        incr(c_expr.temp_num);
        temp_num := c_expr.temp_num;
        paramData.paramNum := temp_num;
        paramData.actualParam := actual_param;
        pushInlineParam(formal_param, paramData);
        param_name := "par_";
        param_name &:= str(temp_num);
        param_name &:= "_o_";
        create_name(formal_param, param_name);
        c_expr.temp_decls &:= param_name;
        c_expr.temp_decls &:= " = (";
        c_expr.temp_decls &:= type_name(param_type);
        if isPointerParam(formal_param) then
          c_expr.temp_decls &:= " *";
        end if;
        c_expr.temp_decls &:= ")(0);\n";
        c_expr.expr &:= param_name;
        c_expr.expr &:= "=";
        if isCopyParam(formal_param) then
          prepareAnyParamTemporarys(actual_param, actual_param_expr, c_expr);
          if actual_param_expr.result_expr <> "" then
            c_expr.expr &:= actual_param_expr.result_expr;
          else
            process_create_declaration(getType(formal_param), global_c_expr);
            process_create_call(getType(formal_param),
                actual_param_expr.expr, c_expr.expr);
          end if;
          process_destr_declaration(getType(formal_param), global_c_expr);
          process_destr_call(getType(formal_param),
              param_name, c_expr.temp_frees);
        else
          assign_inline_param(formal_param, actual_param, c_expr);
        end if;
        c_expr.expr &:= ",\n";
      end if;
    end if;
  end func;


const proc: push_inline_func_params (in ref_list: formal_params,
    in ref_list: actual_params, inout expr_type: c_expr) is func

  local
    var integer: number is 0;
  begin
    for number range 1 to length(formal_params) do
      push_inline_func_param(formal_params[number], actual_params[number], c_expr);
    end for;
  end func;


const proc: pop_inline_func_param (in reference: formal_param,
    inout string: expr) is func

  local
    var category: paramCategory is category.value;
    var type: param_type is void;
  begin
    paramCategory := category(formal_param);
    if paramCategory <> SYMBOLOBJECT and paramCategory <> TYPEOBJECT then
      param_type := getType(formal_param);
      if isFunc(param_type) then
        expr &:= diagnosticLine(formal_param);
        expr &:= "/* pop proc param o_";
        create_name2(formal_param, expr);
        expr &:= " */\n";
      elsif not isVar(formal_param) and formal_param in inlineParam and
          inlineParam[formal_param][1].paramValue <> NIL then
        expr &:= diagnosticLine(formal_param);
        expr &:= "/* pop const param o_";
        create_name2(formal_param, expr);
        expr &:= " */\n";
      end if;
      popInlineParam(formal_param);
    end if;
  end func;


const proc: pop_inline_func_params (in ref_list: formal_params,
    inout string: expr) is func

  local
    var integer: number is 0;
  begin
    for number range 1 to length(formal_params) do
      pop_inline_func_param(formal_params[number], expr);
    end for;
  end func;


const proc: process_inline_func (in reference: function,
    in ref_list: actual_params, inout expr_type: c_expr) is func

  local
    var string: diagnosticLine is "";
    var type: function_type is void;
    var type: result_type is void;
    var ref_list: formal_params is ref_list.EMPTY;
    var expr_type: inline_body is expr_type.value;
    var expr_type: inline_decls is expr_type.value;
    var reference: result_object is NIL;
  begin
    diagnosticLine := diagnosticLine(function);
    function_type := getType(function);
    result_type := resultType(function_type);
    formal_params := formalParams(function);
    inline_decls.temp_num := c_expr.temp_num;
    push_inline_func_params(formal_params, actual_params, inline_decls);
    process_local_var_declaration(function, inline_decls);
    result_object := resultVar(function);
    if result_object <> NIL then
      inline_decls.temp_decls &:= type_name(getType(result_object));
      inline_decls.temp_decls &:= " o_";
      create_name(result_object, inline_decls.temp_decls);
      inline_decls.temp_decls &:= ";\n";
    end if;
    c_expr.temp_num := inline_decls.temp_num;
    c_expr.temp_decls &:= inline_decls.temp_decls;
    c_expr.temp_assigns &:= inline_decls.temp_assigns;
    c_expr.temp_frees &:= inline_decls.temp_frees;
    c_expr.temp_to_null &:= inline_decls.temp_to_null;
    prepareAnyParamTemporarys(body(function), inline_body, c_expr);
    if inline_body.result_expr <> "" then
      c_expr.result_expr &:= "\n";
      c_expr.result_expr &:= diagnosticLine;
      c_expr.result_expr &:= "/* ";
      c_expr.result_expr &:= "inline func o_";
      create_name2(function, c_expr.result_expr);
      c_expr.result_expr &:= " */ ";
      c_expr.result_expr &:= "((";
      c_expr.result_expr &:= type_name(result_type);
      c_expr.result_expr &:= ")(\n";
      c_expr.result_expr &:= inline_decls.expr;
      c_expr.result_name := inline_body.result_name;
      c_expr.result_decl := inline_body.result_decl;
      c_expr.result_free := inline_body.result_free;
      c_expr.result_to_null := inline_body.result_to_null;
      c_expr.result_intro := inline_body.result_intro;
      c_expr.result_finish := inline_body.result_finish;
      c_expr.result_expr &:= diagnosticLine;
      c_expr.result_expr &:= inline_body.result_expr;
      c_expr.result_expr &:= "\n";
      pop_inline_func_params(formal_params, c_expr.result_expr);
      c_expr.result_expr &:= diagnosticLine;
      c_expr.result_expr &:= ")) /* ";
      c_expr.result_expr &:= "inline func o_";
      create_name2(function, c_expr.result_expr);
      c_expr.result_expr &:= " */\n";
    else
      c_expr.expr &:= "\n";
      c_expr.expr &:= diagnosticLine;
      c_expr.expr &:= "/* ";
      if isVarfunc(function_type) then
        c_expr.expr &:= "var";
      end if;
      c_expr.expr &:= "inline func o_";
      create_name2(function, c_expr.expr);
      c_expr.expr &:= " */ ";
      if isVarfunc(function_type) then
        c_expr.expr &:= "*";
      end if;
      c_expr.expr &:= "((";
      c_expr.expr &:= type_name(result_type);
      if isVarfunc(function_type) then
        c_expr.expr &:= " *";
      end if;
      c_expr.expr &:= ")(\n";
      c_expr.expr &:= inline_decls.expr;
      c_expr.expr &:= diagnosticLine;
      if isVarfunc(function_type) then
        c_expr.expr &:= "&(";
        c_expr.expr &:= inline_body.expr;
        c_expr.expr &:= ")";
      else
        c_expr.expr &:= inline_body.expr;
      end if;
      c_expr.expr &:= "\n";
      pop_inline_func_params(formal_params, c_expr.expr);
      c_expr.expr &:= diagnosticLine;
      c_expr.expr &:= ")) /* ";
      if isVarfunc(function_type) then
        c_expr.expr &:= "var";
      end if;
      c_expr.expr &:= "inline func o_";
      create_name2(function, c_expr.expr);
      c_expr.expr &:= " */\n";
    end if;
  end func;


const proc: push_inline_proc_param (in reference: formal_param,
    in reference: actual_param, inout expr_type: c_expr) is func

  local
    var category: paramCategory is category.value;
    var reference: evaluatedParam is NIL;
    var type: param_type is void;
    var string: param_name is "";
    var expr_type: actual_param_expr is expr_type.value;
    var expr_type: assign_decls is expr_type.value;
    var inlineParamData: paramData is inlineParamData.value;
  begin
    paramCategory := category(formal_param);
    if paramCategory = TYPEOBJECT then
      c_expr.expr &:= "/* attr t_";
      c_expr.expr &:= str(typeNumber(getValue(formal_param, type)));
      c_expr.expr &:= " ";
      c_expr.expr &:= str(getValue(formal_param, type));
      c_expr.expr &:= "*/\n";
    elsif paramCategory <> SYMBOLOBJECT then
      param_type := getType(formal_param);
      if isFunc(param_type) then
        c_expr.temp_decls &:= diagnosticLine(formal_param);
        c_expr.temp_decls &:= "/* push proc param o_";
        create_name2(formal_param, c_expr.temp_decls);
        c_expr.temp_decls &:= " */\n";
        paramData.paramValue := actual_param;
      elsif not isVar(formal_param) and getConstant(actual_param, evaluatedParam) then
        c_expr.temp_decls &:= diagnosticLine(formal_param);
        c_expr.temp_decls &:= "/* push const param o_";
        create_name2(formal_param, c_expr.temp_decls);
        c_expr.temp_decls &:= " */\n";
        setVar(evaluatedParam, FALSE);
        paramData.paramValue := evaluatedParam;
      elsif isCopyParam(formal_param) then
        c_expr.expr &:= diagnosticLine(formal_param);
        c_expr.expr &:= "/* push param o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */ ";
        create_name(formal_param, param_name);
        if not isVar(formal_param) and useConstPrefix(param_type) then
          c_expr.temp_decls &:= "const_";
        end if;
        c_expr.temp_decls &:= type_name(param_type);
        c_expr.temp_decls &:= " o_";
        c_expr.temp_decls &:= param_name;
        c_expr.temp_decls &:= ";\n";
        c_expr.temp_assigns &:= "o_";
        c_expr.temp_assigns &:= param_name;
        c_expr.temp_assigns &:= "=";
        prepareAnyParamTemporarys(actual_param, actual_param_expr, c_expr);
        if actual_param_expr.result_expr <> "" then
          c_expr.temp_assigns &:= actual_param_expr.result_expr;
        else
          process_create_declaration(getType(formal_param), global_c_expr);
          process_create_call(getType(formal_param),
              actual_param_expr.expr, c_expr.temp_assigns);
        end if;
        c_expr.temp_assigns &:= ";\n";
        process_destr_declaration(getType(formal_param), global_c_expr);
        process_destr_call(getType(formal_param),
            "o_" & param_name, c_expr.temp_frees);
      else
        c_expr.expr &:= diagnosticLine(formal_param);
        c_expr.expr &:= "/* push o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */\n";
        assign_decls.temp_num := c_expr.temp_num;
        assign_inline_param(formal_param, actual_param, assign_decls);
        c_expr.temp_num := assign_decls.temp_num;
        c_expr.temp_decls &:= assign_decls.temp_decls;
        c_expr.temp_assigns &:= assign_decls.temp_assigns;
        c_expr.temp_frees &:= assign_decls.temp_frees;
        if not isVar(formal_param) then
          if assign_decls.expr = "" or assign_decls.temp_assigns = "" then
            # There will be no assignment in c_expr.temp_assigns.
            c_expr.temp_decls &:= "const ";
          end if;
          if not isPointerParam(formal_param) and useConstPrefix(param_type) then
            c_expr.temp_decls &:= "const_";
          end if;
        end if;
        c_expr.temp_decls &:= type_name(param_type);
        c_expr.temp_decls &:= " ";
        if isPointerParam(formal_param) then
          c_expr.temp_decls &:= "*const ";
        end if;
        c_expr.temp_decls &:= "o_";
        create_name(formal_param, c_expr.temp_decls);
        if assign_decls.expr <> "" then
          if assign_decls.temp_assigns = "" then
            c_expr.temp_decls &:= "=";
            c_expr.temp_decls &:= assign_decls.expr;
            c_expr.temp_decls &:= ";\n";
          else
            c_expr.temp_decls &:= ";\n";
            c_expr.temp_assigns &:= "o_";
            create_name(formal_param, c_expr.temp_assigns);
            c_expr.temp_assigns &:= "=";
            c_expr.temp_assigns &:= assign_decls.expr;
            c_expr.temp_assigns &:= ";\n";
          end if;
        else
          c_expr.temp_decls &:= ";\n";
        end if;
      end if;
      paramData.actualParam := actual_param;
      pushInlineParam(formal_param, paramData);
    end if;
  end func;


const proc: push_inline_proc_params (in ref_list: formal_params,
    in ref_list: actual_params, inout expr_type: c_expr) is func

  local
    var integer: number is 0;
  begin
    for number range 1 to length(formal_params) do
      push_inline_proc_param(formal_params[number], actual_params[number], c_expr);
    end for;
  end func;


const proc: pop_inline_proc_param (in reference: formal_param,
    inout expr_type: c_expr) is func

  local
    var category: paramCategory is category.value;
    var type: param_type is void;
  begin
    paramCategory := category(formal_param);
    if paramCategory <> SYMBOLOBJECT and paramCategory <> TYPEOBJECT then
      param_type := getType(formal_param);
      if isFunc(param_type) then
        c_expr.expr &:= diagnosticLine(formal_param);
        c_expr.expr &:= "/* pop proc param o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */\n";
      elsif not isVar(formal_param) and formal_param in inlineParam and
          inlineParam[formal_param][1].paramValue <> NIL then
        c_expr.expr &:= diagnosticLine(formal_param);
        c_expr.expr &:= "/* pop const param o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= " */\n";
      end if;
      popInlineParam(formal_param);
    end if;
  end func;


const proc: pop_inline_proc_params (in ref_list: formal_params,
    inout expr_type: c_expr) is func

  local
    var integer: number is 0;
  begin
    for number range 1 to length(formal_params) do
      pop_inline_proc_param(formal_params[number], c_expr);
    end for;
  end func;


const proc: process_inline_proc (in reference: function,
    in ref_list: actual_params, inout expr_type: c_expr) is func

  local
    var string: diagnosticLine is "";
    var ref_list: formal_params is ref_list.EMPTY;
    var expr_type: inline_decls is expr_type.value;
  begin
    diagnosticLine := diagnosticLine(function);
    formal_params := formalParams(function);
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "/* inline proc o_";
    create_name2(function, c_expr.expr);
    c_expr.expr &:= " */ {\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "/* inline params */\n";
    inline_decls.temp_num := c_expr.temp_num;
    push_inline_proc_params(formal_params, actual_params, inline_decls);
    inline_decls.temp_decls &:= "/* inline local_vars */\n";
    process_local_var_declaration(function, inline_decls);
    c_expr.temp_num := inline_decls.temp_num;
    appendWithDiagnostic(inline_decls.temp_decls, c_expr);
    appendWithDiagnostic(inline_decls.temp_assigns, c_expr);
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "/* inline body */\n";
    process_expr(body(function), c_expr);
    appendWithDiagnostic(inline_decls.temp_frees, c_expr);
    pop_inline_proc_params(formal_params, c_expr);
    c_expr.expr &:= "\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "} /* inline proc o_";
    create_name2(function, c_expr.expr);
    c_expr.expr &:= " */\n";
  end func;


const proc: process_inline (in reference: function,
    in ref_list: actual_params, inout expr_type: c_expr) is func

  begin
    if resultType(getType(function)) <> voidtype then
      process_inline_func(function, actual_params, c_expr);
    else
      process_inline_proc(function, actual_params, c_expr);
    end if;
  end func;


const proc: process_inline_param (in reference: formal_param,
    inout expr_type: c_expr) is func
  local
    var expr_type: c_body is expr_type.value;
  begin
    if getType(formal_param) = proctype then
      c_expr.expr &:= "/* closure o_";
      create_name2(formal_param, c_expr.expr);
      c_expr.expr &:= "*/ {\n";
      process_call_by_name_expr(inlineParam[formal_param][1].paramValue, c_body);
      appendWithDiagnostic(c_body.temp_decls, c_expr);
      appendWithDiagnostic(c_body.temp_assigns, c_expr);
      c_expr.expr &:= c_body.expr;
      appendWithDiagnostic(c_body.temp_frees, c_expr);
      c_expr.expr &:= "\n} /* closure o_";
      create_name2(formal_param, c_expr.expr);
      c_expr.expr &:= "*/\n";
    else
      c_body.temp_num := c_expr.temp_num;
      process_call_by_name_expr(inlineParam[formal_param][1].paramValue, c_body);
      c_expr.temp_num := c_body.temp_num;
      if c_body.result_expr <> "" then
        c_expr.result_expr &:= "/* closure o_";
        create_name2(formal_param, c_expr.result_expr);
        c_expr.result_expr &:= "*/ (";
        c_expr.result_expr &:= c_body.result_expr;
        c_expr.result_expr &:= ")";
        c_expr.temp_decls   &:= c_body.temp_decls;
        c_expr.temp_assigns &:= c_body.temp_assigns;
        c_expr.temp_frees   &:= c_body.temp_frees;
        c_expr.result_name    := c_body.result_name;
        c_expr.result_decl    := c_body.result_decl;
        c_expr.result_free    := c_body.result_free;
        c_expr.result_to_null := c_body.result_to_null;
        c_expr.result_intro   := c_body.result_intro;
        c_expr.result_finish  := c_body.result_finish;
      else
        c_expr.expr &:= "/* closure o_";
        create_name2(formal_param, c_expr.expr);
        c_expr.expr &:= "*/ (";
        c_expr.expr &:= c_body.expr;
        c_expr.expr &:= ")";
        c_expr.temp_decls   &:= c_body.temp_decls;
        c_expr.temp_assigns &:= c_body.temp_assigns;
        c_expr.temp_frees   &:= c_body.temp_frees;
      end if;
    end if;
  end func;