# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#.rst:
# FindBYACC
# ---------
#
# Find ``byacc`` executable and provide a macro to generate custom build rules.
#
# The module defines the following variables:
#
# ``BYACC_EXECUTABLE``
#   path to the ``byacc`` program
#
# ``BYACC_VERSION``
#   version of ``byacc``
#
# ``BYACC_FOUND``
#   true if the program was found
#
# The minimum required version of ``byacc`` can be specified using the
# standard CMake syntax, e.g.  ``find_package(BYACC 2.1.3)``.
#
# If ``byacc`` is found, the module defines the macro::
#
#   BYACC_TARGET(<Name> <YaccInput> <CodeOutput>
#                [COMPILE_FLAGS <flags>]
#                [DEFINES_FILE <file>]
#                [VERBOSE [<file>]]
#                [REPORT_FILE <file>]
#                )
#
# which will create a custom rule to generate a parser.  ``<YaccInput>`` is
# the path to a yacc file.  ``<CodeOutput>`` is the name of the source file
# generated by byacc.  A header file is also be generated, and contains
# the token list.
#
# The options are:
#
# ``COMPILE_FLAGS <flags>``
#   Specify flags to be added to the ``byacc`` command line.
#
# ``DEFINES_FILE <file>``
#   Specify a non-default header ``<file>`` to be generated by ``byacc``.
#
# The macro defines the following variables:
#
# ``BYACC_<Name>_DEFINED``
#   true is the macro ran successfully
#
# ``BYACC_<Name>_INPUT``
#   The input source file, an alias for <YaccInput>
#
# ``BYACC_<Name>_OUTPUT_SOURCE``
#   The source file generated by byacc
#
# ``BYACC_<Name>_OUTPUT_HEADER``
#   The header file generated by byacc
#
# ``BYACC_<Name>_OUTPUTS``
#   All files generated by byacc including the source, the header and the report
#
# ``BYACC_<Name>_COMPILE_FLAGS``
#   Options used in the ``byacc`` command line
#
# Example usage:
#
# .. code-block:: cmake
#
#   find_package(BYACC)
#   BYACC_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp
#                DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/parser.h)
#   add_executable(Foo main.cpp ${BYACC_MyParser_OUTPUTS})

find_program(BYACC_EXECUTABLE NAMES byacc win_byacc DOC "path to the byacc executable")
mark_as_advanced(BYACC_EXECUTABLE)

if(BYACC_EXECUTABLE)
  # the byacc commands should be executed with the C locale, otherwise
  # the message (which are parsed) may be translated
  set(_Byacc_SAVED_LC_ALL "$ENV{LC_ALL}")
  set(ENV{LC_ALL} C)

  execute_process(COMMAND ${BYACC_EXECUTABLE} -V
    OUTPUT_VARIABLE BYACC_version_output
    ERROR_VARIABLE BYACC_version_error
    RESULT_VARIABLE BYACC_version_result
    OUTPUT_STRIP_TRAILING_WHITESPACE)

  set(ENV{LC_ALL} ${_Byacc_SAVED_LC_ALL})

  if(NOT ${BYACC_version_result} EQUAL 0)
    message(SEND_ERROR "Command \"${BYACC_EXECUTABLE} -V\" failed with output:\n${BYACC_version_error}")
  else()
      if("${BYACC_version_output}" MATCHES "^.*byacc - ([^ ]+)")
      set(BYACC_VERSION "${CMAKE_MATCH_1}")
    endif()
  endif()

  # internal macro
  # sets byacc_target_cmdopt
  macro(byacc_target_option_extraopts Options)
    set(byacc_target_cmdopt "")
    set(byacc_target_extraopts "${Options}")
    separate_arguments(byacc_target_extraopts)
    list(APPEND byacc_target_cmdopt ${byacc_target_extraopts})
  endmacro()

  # internal macro
  # sets byacc_target_output_header and byacc_target_cmdopt
  macro(byacc_target_option_defines ByaccOutput Header)
    if("${Header}" STREQUAL "")
      # default header path generated by byacc (see option -d)
      string(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\2" _fileext "${ByaccOutput}")
      string(REPLACE "cpp" "tab.h" _fileext ${_fileext})
      string(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\1${_fileext}"
          byacc_target_output_header "${ByaccOutput}")
      get_filename_component(HeaderName ${ByaccOutput} NAME_WE)
      list(APPEND byacc_target_cmdopt "-d")
      list(APPEND byacc_target_cmdopt "-b")
      list(APPEND byacc_target_cmdopt "${HeaderName}")
    endif()
  endmacro()

  #============================================================
  # BYACC_TARGET (public macro)
  #============================================================
  #
  macro(BYACC_TARGET Name ByaccPathLessInput ByaccOutput)
    set(byacc_target_outputs "${ByaccOutput}")
    get_filename_component(ByaccInput ${ByaccPathLessInput} REALPATH)

    # Parsing parameters
    set(byacc_target_PARAM_OPTIONS
      )
    set(byacc_target_PARAM_ONE_VALUE_KEYWORDS
      COMPILE_FLAGS
      DEFINES_FILE
      REPORT_FILE
      )
    cmake_parse_arguments(
        byacc_target_ARG
        "${byacc_target_PARAM_OPTIONS}"
        "${byacc_target_PARAM_ONE_VALUE_KEYWORDS}"
        "${byacc_target_PARAM_MULTI_VALUE_KEYWORDS}"
        ${ARGN}
    )

    if(NOT "${byacc_target_ARG_UNPARSED_ARGUMENTS}" STREQUAL "")
      message(SEND_ERROR "Usage")
    else()

      byacc_target_option_extraopts("${byacc_target_ARG_COMPILE_FLAGS}")
      byacc_target_option_defines("${ByaccOutput}" "${byacc_target_ARG_DEFINES_FILE}")
      list(APPEND byacc_target_outputs "${byacc_target_output_header}")

      add_custom_command(OUTPUT ${byacc_target_outputs}
        COMMAND ${BYACC_EXECUTABLE} ${byacc_target_cmdopt} -o ${ByaccOutput} ${ByaccInput}
        VERBATIM
        DEPENDS ${ByaccInput}
        COMMENT "[BYACC][${Name}] Building parser with byacc ${BYACC_VERSION}"
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

      # define target variables
      set(BYACC_${Name}_DEFINED TRUE)
      set(BYACC_${Name}_INPUT ${ByaccInput})
      set(BYACC_${Name}_OUTPUTS ${byacc_target_outputs})
      set(BYACC_${Name}_COMPILE_FLAGS ${byacc_target_cmdopt})
      set(BYACC_${Name}_OUTPUT_SOURCE "${ByaccOutput}")
      set(BYACC_${Name}_OUTPUT_HEADER "${byacc_target_output_header}")

    endif()


  endmacro()
  #
  #============================================================

  #============================================================
  # ADD_FLEX_BYACC_DEPENDENCY (public macro)
  #============================================================
  #
  macro(ADD_FLEX_BYACC_DEPENDENCY FlexTarget ByaccTarget)

    if(NOT FLEX_${FlexTarget}_OUTPUTS)
      message(SEND_ERROR "Flex target `${FlexTarget}' does not exist.")
    endif()

    if(NOT BYACC_${ByaccTarget}_OUTPUT_HEADER)
      message(SEND_ERROR "Byacc target `${ByaccTarget}' does not exist.")
    endif()

    set_source_files_properties(${FLEX_${FlexTarget}_OUTPUTS}
        PROPERTIES OBJECT_DEPENDS ${BYACC_${ByaccTarget}_OUTPUT_HEADER})
  endmacro()
  #============================================================



endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(BYACC REQUIRED_VARS  BYACC_EXECUTABLE
                                        VERSION_VAR BYACC_VERSION)
