
# compute_int_expr_at_compile_time
#
# Try to compute the value of an integer expression at compile
# time (usually a sizeof or offsetof). Uses a couple of different
# strategies.
#
# Unnamed parameters:
#
#   * return variable name (set in parent scope)
#   * expr we want to compute
#   * headers to include
#   * temporary *.c file to emit
#
function(compute_int_expr_at_compile_time outvar expr hdrs tmpfile)
  set(${outvar} -1 PARENT_SCOPE)

  # Emit a C++ source file that will generate an error when compiled;
  # crafted such that the desired bit of info is part of the error
  # message. The duplicate case construct works with clang; the
  # template instantiation trick works with g++.
  set(tmpfile "${CMAKE_CURRENT_BINARY_DIR}/${tmpfile}")
  file(REMOVE ${tmpfile})
  file(WRITE ${tmpfile} "// auto-generated throwaway\n")
  foreach(inc ${hdrs})
    file(APPEND ${tmpfile} "#include <${inc}>\n")
  endforeach()
  file(APPEND ${tmpfile} "unsigned variant1(unsigned x) {\n")
  file(APPEND ${tmpfile} "  switch(x) {\n")
  file(APPEND ${tmpfile} "    case ${expr}: return 0;\n")
  file(APPEND ${tmpfile} "    case ${expr}: return 1;\n")
  file(APPEND ${tmpfile} "    default: return 2;\n")
  file(APPEND ${tmpfile} "  }\n")
  file(APPEND ${tmpfile} "}\n")
  file(APPEND ${tmpfile} "template<int s> struct Blat;\n")
  file(APPEND ${tmpfile} "Blat<${expr}> variant2;\n")
  file(APPEND ${tmpfile} "int main(int argc, char **argv) {\n")
  file(APPEND ${tmpfile} "  return variant1((unsigned)argc) != 0 ? 1 : 0;\n")
  file(APPEND ${tmpfile} "}\n")

  # Compile it.
  try_compile(unusedvar
    ${CMAKE_CURRENT_BINARY_DIR}
    SOURCES ${tmpfile}
    OUTPUT_VARIABLE errout)

  # Look for duplicate case error message (first variant)
  string(REGEX MATCH "^.*duplicate case value '([0-9]+)'.*$" matched ${errout})
  if(matched)
    set(${outvar} "${CMAKE_MATCH_1}" PARENT_SCOPE)
  else()

    # Look for template instantiation error message (second variant)
    string(REGEX MATCH "^.*Blat<([0-9]+)>.*incomplete type.*$"
      matched ${errout})
    if(matched)
      set(${outvar} "${CMAKE_MATCH_1}" PARENT_SCOPE)
    endif()
  endif()
endfunction()

# compute_struct_size_at_compile_time
#
# Computes the size of a struct at compile time and returns it in the
# specified variable. Return is -1 if size could not be determined.
#
# Unnamed parameters:
#
#   * return variable name (set in parent scope)
#   * struct whose size we want to determine
#   * headers to include
#   * temporary *.cpp file to emit
#
function(compute_struct_size_at_compile_time outvar struct hdrs tmpfile)
  set(expr "sizeof(${struct})")
  compute_int_expr_at_compile_time(result "${expr}" "${hdrs}" ${tmpfile})
  set(${outvar} ${result} PARENT_SCOPE)
endfunction()

# compute_field_offset_at_compile_time
#
# Computes the offset of a field within a struct at compile time and
# returns it in the specified variable. Return is -1 if offset could not
# be determined.
#
# Unnamed parameters:
#
#   * return variable name (set in parent scope)
#   * struct whose size we want to determine
#   * headers to include
#   * temporary *.cpp file to emit
#
function (compute_field_offset_at_compile_time outvar struct field hdrs tmpfile)
  set(expr "offsetof(${struct},${field})")
  compute_int_expr_at_compile_time(result "${expr}" "${hdrs}" ${tmpfile})
  set(${outvar} ${result} PARENT_SCOPE)
endfunction()
