# This CMake script is used to generate a header and a source file for utility # functions that convert the tags of generated enum types into strings and # strings into the tags of generated enum types. cmake_minimum_required(VERSION 3.23 FATAL_ERROR) # Seeks the starting line of the source enum's declaration. macro(seek_enum_mode) if (line MATCHES "^(typedef[ \t]+)?enum ") string(REGEX REPLACE "^enum ([0-9a-zA-Z_]+).*$" "\\1" enum_name "${line}") set(mode "read_tags") endif() endmacro() # Scans the input for the current enum's tags. macro(read_tags_mode) if(line MATCHES "^}") set(mode "generate") elseif(line MATCHES "^[A-Z0-9_]+.*$") string(REGEX REPLACE "^([A-Za-z0-9_]+).*$" "\\1" tmp "${line}") list(APPEND enum_tags "${tmp}") endif() endmacro() macro(write_header_file) # Generate a to-string function declaration. list(APPEND header_body "/**\n" " * \\ingroup enumerations\n" " * \\brief Gets the string representation of an `${enum_name}` enum tag.\n" " *\n" " * \\param[in] tag An `${enum_name}` enum tag.\n" " * \\return A null-terminated byte string.\n" " */\n" "char const* ${enum_name}ToString(${enum_name} const tag)\;\n" "\n") # Generate a from-string function declaration. list(APPEND header_body "/**\n" " * \\ingroup enumerations\n" " * \\brief Gets an `${enum_name}` enum tag from its string representation.\n" " *\n" " * \\param[out] dest An `${enum_name}` enum tag pointer.\n" " * \\param[in] src A null-terminated byte string.\n" " * \\return `true` if \\p src matches the string representation of an\n" " * `${enum_name}` enum tag, `false` otherwise.\n" " */\n" "bool ${enum_name}FromString(${enum_name}* dest, char const* const src)\;\n" "\n") endmacro() macro(write_source_file) # Generate a to-string function implementation. list(APPEND source_body "char const* ${enum_name}ToString(${enum_name} const tag) {\n" " switch (tag) {\n" " default:\n" " return \"???\"\;\n") foreach(label IN LISTS enum_tags) list(APPEND source_body " case ${label}:\n" " return \"${label}\"\;\n") endforeach() list(APPEND source_body " }\n" "}\n" "\n") # Generate a from-string function implementation. list(APPEND source_body "bool ${enum_name}FromString(${enum_name}* dest, char const* const src) {\n") foreach(label IN LISTS enum_tags) list(APPEND source_body " if (!strcmp(src, \"${label}\")) {\n" " *dest = ${label}\;\n" " return true\;\n" " }\n") endforeach() list(APPEND source_body " return false\;\n" "}\n" "\n") endmacro() function(main) set(header_body "") # File header and includes. list(APPEND header_body "#ifndef ${include_guard}\n" "#define ${include_guard}\n" "/**\n" " * \\file\n" " * \\brief Utility functions for converting enum tags into null-terminated\n" " * byte strings and vice versa.\n" " *\n" " * \\warning This file is auto-generated by CMake.\n" " */\n" "\n" "#include \n" "\n" "#include <${library_include}>\n" "\n") set(source_body "") # File includes. list(APPEND source_body "/** \\warning This file is auto-generated by CMake. */\n" "\n" "#include \"stdio.h\"\n" "#include \"string.h\"\n" "\n" "#include <${header_include}>\n" "\n") set(enum_name "") set(enum_tags "") set(mode "seek_enum") file(STRINGS "${input_path}" lines) foreach(line IN LISTS lines) string(REGEX REPLACE "^(.+)(//.*)?" "\\1" line "${line}") string(STRIP "${line}" line) if(mode STREQUAL "seek_enum") seek_enum_mode() elseif(mode STREQUAL "read_tags") read_tags_mode() else() # The end of the enum declaration was reached. if(NOT enum_name) # The end of the file was reached. return() endif() if(NOT enum_tags) message(FATAL_ERROR "No tags found for `${enum_name}`.") endif() string(TOLOWER "${enum_name}" output_stem_prefix) string(CONCAT output_stem "${output_stem_prefix}" "_string") cmake_path(REPLACE_EXTENSION output_stem "h" OUTPUT_VARIABLE output_header_basename) write_header_file() write_source_file() set(enum_name "") set(enum_tags "") set(mode "seek_enum") endif() endforeach() # File footer. list(APPEND header_body "#endif /* ${include_guard} */\n") message(STATUS "Generating header file \"${output_header_path}\"...") file(WRITE "${output_header_path}" ${header_body}) message(STATUS "Generating source file \"${output_source_path}\"...") file(WRITE "${output_source_path}" ${source_body}) endfunction() if(NOT DEFINED PROJECT_NAME) message(FATAL_ERROR "Variable PROJECT_NAME is not defined.") elseif(NOT DEFINED LIBRARY_NAME) message(FATAL_ERROR "Variable LIBRARY_NAME is not defined.") elseif(NOT DEFINED SUBDIR) message(FATAL_ERROR "Variable SUBDIR is not defined.") elseif(${CMAKE_ARGC} LESS 9) message(FATAL_ERROR "Too few arguments.") elseif(${CMAKE_ARGC} GREATER 10) message(FATAL_ERROR "Too many arguments.") elseif(NOT EXISTS ${CMAKE_ARGV5}) message(FATAL_ERROR "Input header \"${CMAKE_ARGV7}\" not found.") endif() cmake_path(CONVERT "${CMAKE_ARGV7}" TO_CMAKE_PATH_LIST input_path NORMALIZE) cmake_path(CONVERT "${CMAKE_ARGV8}" TO_CMAKE_PATH_LIST output_header_path NORMALIZE) cmake_path(CONVERT "${CMAKE_ARGV9}" TO_CMAKE_PATH_LIST output_source_path NORMALIZE) string(TOLOWER "${PROJECT_NAME}" project_root) cmake_path(CONVERT "${SUBDIR}" TO_CMAKE_PATH_LIST project_subdir NORMALIZE) string(TOLOWER "${project_subdir}" project_subdir) string(TOLOWER "${LIBRARY_NAME}" library_stem) cmake_path(REPLACE_EXTENSION library_stem "h" OUTPUT_VARIABLE library_basename) string(JOIN "/" library_include "${project_root}" "${library_basename}") string(TOUPPER "${PROJECT_NAME}" project_name_upper) string(TOUPPER "${project_subdir}" include_guard_infix) string(REGEX REPLACE "/" "_" include_guard_infix "${include_guard_infix}") string(REGEX REPLACE "-" "_" include_guard_prefix "${project_name_upper}") string(JOIN "_" include_guard_prefix "${include_guard_prefix}" "${include_guard_infix}") string(JOIN "/" output_header_prefix "${project_root}" "${project_subdir}") cmake_path(GET output_header_path STEM output_header_stem) string(TOUPPER "${output_header_stem}" include_guard_stem) string(JOIN "_" include_guard "${include_guard_prefix}" "${include_guard_stem}" "H") cmake_path(GET output_header_path FILENAME output_header_basename) string(JOIN "/" header_include "${output_header_prefix}" "${output_header_basename}") main()