(********************************************************************)
(*                                                                  *)
(*  create.s7i    Generate code for the create functions.           *)
(*  Copyright (C) 1990 - 1994, 2004 - 2014  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.                       *)
(*                                                                  *)
(********************************************************************)


var typeReferenceHash: createFunction is typeReferenceHash.EMPTY_HASH;
var boolean_type_hash: create_declared is boolean_type_hash.EMPTY_HASH;
var boolean_type_hash: create_prototype_declared is boolean_type_hash.EMPTY_HASH;
var string_type_hash: parametersOfHshCreate is string_type_hash.EMPTY_HASH;


const proc: process_create_declaration (in type: object_type,
    inout expr_type: c_expr) is forward;


const proc: process_create_call (in type: object_type,
    in string: param_b, inout string: expr) is forward;


const func string: process_arr_alloc (in type: object_type, in string: variableName,
    in boolean: useFreelist, in string: diagnosticLine) is func

  result
    var string: allocExpr is "";
  local
    const integer: maxFlistLen is 1 << 16;
  begin
    if useFreelist then
      allocExpr := diagnosticLine;
      allocExpr &:= "flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= " != NULL ? (";
      allocExpr &:= variableName;
      allocExpr &:= "=(";
      allocExpr &:= type_name(object_type);
      allocExpr &:= ")(flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "), ";
      allocExpr &:= "flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "=flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "->next, ";
      allocExpr &:= "flist_allowed_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "++, ";
      allocExpr &:= variableName;
      allocExpr &:= "->min_position=";
      allocExpr &:= integerLiteral(array_minIdx[object_type]);
      allocExpr &:= ", ";
      allocExpr &:= variableName;
      allocExpr &:= "->max_position=";
      allocExpr &:= integerLiteral(array_maxIdx[object_type]);
      allocExpr &:= ") : (\n";
      allocExpr &:= diagnosticLine;
      allocExpr &:= variableName;
      allocExpr &:= "=(";
      allocExpr &:= type_name(object_type);
      allocExpr &:= ")arrMalloc(";
      allocExpr &:= integerLiteral(array_minIdx[object_type]);
      allocExpr &:= ", ";
      allocExpr &:= integerLiteral(array_maxIdx[object_type]);
      allocExpr &:= "),\n";
      allocExpr &:= diagnosticLine;
      allocExpr &:= "unlikely(flist_was_full_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= ") ? (";
      allocExpr &:= "flist_was_full_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "=0, ";
      allocExpr &:= "flist_allowed_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "<";
      allocExpr &:= str(maxFlistLen);
      allocExpr &:= " ? ";
      allocExpr &:= "flist_allowed_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "<<=1 : 0) : 0)";
    else
      allocExpr &:= diagnosticLine;
      allocExpr &:= variableName;
      allocExpr &:= "=(";
      allocExpr &:= type_name(object_type);
      allocExpr &:= ")arrMalloc(b->min_position, b->max_position)";
    end if;
  end func;


const proc: process_arr_create_declaration (in type: object_type,
    inout expr_type: c_expr) is func

  local
    var string: diagnosticLine is "";
    var string: select_value is "";
    var boolean: useFreelist is FALSE;
  begin
    if object_type in createFunction then
      diagnosticLine := diagnosticLine(createFunction[object_type]);
    end if;
    select_value := select_value_from_rtlObjectStruct(array_element[object_type]);
    process_create_declaration(array_element[object_type], c_expr);
    if fixArrayFreelist and
        object_type in array_minIdx and object_type in array_maxIdx and
        array_maxIdx[object_type] -
        array_minIdx[object_type] < ARRAY_FREELIST_LIMIT then
      useFreelist := TRUE;
      if object_type not in flist_declared then
        # Free list might already be defined by the declaration of create_... .
        declare_free_list(object_type, diagnosticLine, c_expr);
      end if;
    end if;
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "static ";
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " create_";
    c_expr.expr &:= str(typeNumber(object_type));
    c_expr.expr &:= " (const_";
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " b)\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "{\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " a;\n";
    c_expr.expr &:= diagnosticLine;
    define_array_size_variable(object_type, c_expr);
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= process_arr_alloc(object_type, "a", useFreelist, diagnosticLine);
    c_expr.expr &:= ";\n";
    c_expr.expr &:= diagnosticLine;
    if array_element[object_type] in typeCategory and
        typeCategory[array_element[object_type]] in simpleValueType then
      c_expr.expr &:= "memcpy(a->arr, b->arr, size * sizeof(genericType));\n";
    else
      c_expr.expr &:= "while (size != 0) {\n";
      c_expr.expr &:= diagnosticLine;
      c_expr.expr &:= "size--;\n";
      c_expr.expr &:= diagnosticLine;
      c_expr.expr &:= "a->arr[size]";
      c_expr.expr &:= select_value;
      c_expr.expr &:= "=";
      process_create_call(array_element[object_type],
          "b->arr[size]" & select_value, c_expr.expr);
      c_expr.expr &:= ";\n";
      c_expr.expr &:= diagnosticLine;
      c_expr.expr &:= "}\n";
    end if;
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "return a;\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "}\n";
    c_expr.expr &:= noDiagnosticLine;
    c_expr.expr &:= "\n";
    create_declared @:= [object_type] TRUE;
  end func;


const func string: process_sct_alloc (in type: object_type, in string: variableName,
    in integer: numElements, in string: diagnosticLine) is func

  result
    var string: allocExpr is "";
  local
    const integer: maxFlistLen is 1 << 16;
  begin
    if structFreelist then
      allocExpr := diagnosticLine;
      allocExpr &:= "flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= " != NULL ? (";
      allocExpr &:= variableName;
      allocExpr &:= "=(";
      allocExpr &:= type_name(object_type);
      allocExpr &:= ")(flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "), ";
      allocExpr &:= "flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "=flist_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "->next, ";
      allocExpr &:= "flist_allowed_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "++) : (\n";
    end if;
    allocExpr &:= diagnosticLine;
    allocExpr &:= variableName;
    allocExpr &:= "=(";
    allocExpr &:= type_name(object_type);
    allocExpr &:= ")malloc(sizeof(struct rtlStructStruct) - sizeof(rtlObjectType) + ";
    allocExpr &:= str(numElements);
    allocExpr &:= " * sizeof(rtlObjectType)),\n";
    allocExpr &:= diagnosticLine;
    allocExpr &:= "(unlikely(";
    allocExpr &:= variableName;
    allocExpr &:= " == NULL) ? ";
    allocExpr &:= "intRaiseError(MEMORY_ERROR) ";
    allocExpr &:= ": 0)";
    if structFreelist then
      allocExpr &:= ",\n";
      allocExpr &:= diagnosticLine;
      allocExpr &:= "unlikely(flist_was_full_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= ") ? (";
      allocExpr &:= "flist_was_full_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "=0, ";
      allocExpr &:= "flist_allowed_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "<";
      allocExpr &:= str(maxFlistLen);
      allocExpr &:= " ? ";
      allocExpr &:= "flist_allowed_";
      allocExpr &:= str(typeNumber(object_type));
      allocExpr &:= "<<=1 : 0) : 0)";
    end if;
  end func;


const proc: process_sct_create_declaration (in type: object_type,
    inout expr_type: c_expr) is func

  local
    var string: diagnosticLine is "";
    var integer: structSize is 0;
    var integer: elementIndex is 0;
    var string: select_value is "";
  begin
    if object_type in createFunction then
      diagnosticLine := diagnosticLine(createFunction[object_type]);
    end if;
    if object_type in struct_size then
      structSize := struct_size[object_type];
    end if;
    for elementIndex range 0 to pred(structSize) do
      process_create_declaration(struct_element_type[object_type][elementIndex], c_expr);
    end for;
    if structFreelist and object_type not in flist_declared then
      # Free list might already be defined by the declaration of destr_... .
      declare_free_list(object_type, diagnosticLine, c_expr);
    end if;
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "static ";
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " create_";
    c_expr.expr &:= str(typeNumber(object_type));
    c_expr.expr &:= " (const_";
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " b)\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "{\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " a;\n";
    c_expr.expr &:= process_sct_alloc(object_type, "a", structSize, diagnosticLine);
    c_expr.expr &:= ";\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "a->usage_count = 1;\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "a->type_num = b->type_num;\n";
    for elementIndex range 0 to pred(structSize) do
      select_value := select_value_from_rtlObjectStruct(struct_element_type[object_type][elementIndex]);
      c_expr.expr &:= diagnosticLine;
      c_expr.expr &:= "a->stru[" & str(elementIndex) & "]" & select_value & "=";
      process_create_call(struct_element_type[object_type][elementIndex],
          "b->stru[" & str(elementIndex) & "]" & select_value, c_expr.expr);
      c_expr.expr &:= ";\n";
    end for;
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "return a;\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "}\n";
    c_expr.expr &:= noDiagnosticLine;
    c_expr.expr &:= "\n";
    create_declared @:= [object_type] TRUE;
  end func;


const proc: process_hsh_create_declaration (in type: object_type,
    inout expr_type: c_expr) is func

  local
    var string: diagnosticLine is "";
  begin
    if object_type in createFunction then
      diagnosticLine := diagnosticLine(createFunction[object_type]);
    end if;
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "static ";
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " create_";
    c_expr.expr &:= str(typeNumber(object_type));
    c_expr.expr &:= " (const_";
    c_expr.expr &:= type_name(object_type);
    c_expr.expr &:= " b)\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "{\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "return hshCreate(b";
    c_expr.expr &:= parametersOfHshCreate[object_type];
    c_expr.expr &:= ");\n";
    c_expr.expr &:= diagnosticLine;
    c_expr.expr &:= "}\n";
    c_expr.expr &:= noDiagnosticLine;
    c_expr.expr &:= "\n";
    create_declared @:= [object_type] TRUE;
  end func;


const proc: declare_create_prototype (in type: object_type,
    inout expr_type: c_expr) is func

  begin
    if object_type not in create_prototype_declared then
      declare_type_if_necessary(object_type, c_expr);
      c_expr.expr &:= "static ";
      c_expr.expr &:= type_name(object_type);
      c_expr.expr &:= " create_";
      c_expr.expr &:= str(typeNumber(object_type));
      c_expr.expr &:= " (";
      if useConstPrefix(object_type) then
        c_expr.expr &:= "const_";
      end if;
      c_expr.expr &:= type_name(object_type);
      c_expr.expr &:= ");\n\n";
      create_prototype_declared @:= [object_type] TRUE;
    end if;
  end func;


const proc: process_create_declaration (in type: object_type,
    inout expr_type: c_expr) is func

  begin
    if object_type not in create_declared then
      if object_type in typeCategory then
        case typeCategory[object_type] of
          when {ARRAYOBJECT}:
            if object_type in array_element then
              process_arr_create_declaration(object_type, c_expr);
            else
              declare_create_prototype(object_type, c_expr);
            end if;
          when {STRUCTOBJECT}:
            process_sct_create_declaration(object_type, c_expr);
          when {HASHOBJECT}:
            if object_type in parametersOfHshCreate then
              process_hsh_create_declaration(object_type, c_expr);
            else
              declare_create_prototype(object_type, c_expr);
            end if;
          otherwise:
            create_declared @:= [object_type] TRUE;
        end case;
      else
        declare_create_prototype(object_type, c_expr);
      end if;
    end if;
  end func;


const proc: declare_missing_create_declarations (inout expr_type: c_expr) is func

  local
    var type: object_type is void;
  begin
    for key object_type range create_prototype_declared do
      process_create_declaration(object_type, c_expr);
    end for;
  end func;


const proc: process_create_call (in type: object_type,
    in string: param_b, inout string: expr) is func

  begin
    if object_type in typeCategory then
      case typeCategory[object_type] of
        when simpleValueType:   expr &:= param_b;
        when {BIGINTOBJECT}:    expr &:= "bigCreate(" & param_b & ")";
        when {STRIOBJECT}:      expr &:= "strCreate(" & param_b & ")";
        when {BSTRIOBJECT}:     expr &:= "bstCreate(" & param_b & ")";
        when {FILEOBJECT}:      expr &:= "filCreate(" & param_b & ")";
        when {SETOBJECT}:       expr &:= "setCreate(" & param_b & ")";
        when {POLLOBJECT}:      expr &:= "polCreate(" & param_b & ")";
        when {REFLISTOBJECT}:   expr &:= "rflCreate(" & param_b & ")";
        when {WINOBJECT}:       expr &:= "drwCreate(" & param_b & ")";
        when {POINTLISTOBJECT}: expr &:= "bstCreate(" & param_b & ")";
        when {PROCESSOBJECT}:   expr &:= "pcsCreate(" & param_b & ")";
        when {PROGOBJECT}:      expr &:= "prgCreate(" & param_b & ")";
        when {DATABASEOBJECT}:  expr &:= "sqlCreateDb(" & param_b & ")";
        when {SQLSTMTOBJECT}:   expr &:= "sqlCreateStmt(" & param_b & ")";
        when {INTERFACEOBJECT}: expr &:= "itfCreate(" & param_b & ")";
        when {ARRAYOBJECT, HASHOBJECT, STRUCTOBJECT}:
          expr &:= "create_";
          expr &:= str(typeNumber(object_type));
          expr &:= "(";
          expr &:= param_b;
          expr &:= ")";
      end case;
    else
      expr &:= "create_";
      expr &:= str(typeNumber(object_type));
      expr &:= "((";
      expr &:= type_name(object_type);
      expr &:= ")(";
      expr &:= param_b;
      expr &:= "))";
    end if;
  end func;