#
# colm: the runtime (libcolm), the compiler (libprog) and the bootstrap chain
# that ends in the colm program itself.
#

#
# Generated headers. config.h, defs.h and version.h are the cmake equivalents
# of the headers autoheader produces for the autotools build.
#
configure_file(config.h.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
configure_file(defs.h.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/defs.h" @ONLY)
configure_file(version.h.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/version.h" @ONLY)

#
# Runtime headers. Generated colm programs include these as <colm/tree.h> and
# friends, so assemble a colm/ directory in the build tree. This is the cmake
# equivalent of the AC_CONFIG_LINKS block in configure.ac.
#
set(RUNTIME_HDR
	bytecode.h debug.h pool.h input.h pdarun.h map.h type.h tree.h
	struct.h program.h colm.h internal.h colmex.h)

set(COLM_GENERATED_HDR config.h defs.h)

set(COLM_INCLUDE_DIR "${PROJECT_BINARY_DIR}/include")
file(MAKE_DIRECTORY "${COLM_INCLUDE_DIR}/colm")

set(COLM_INSTALL_HDR)
foreach(_hdr ${RUNTIME_HDR})
	file(CREATE_LINK
		"${CMAKE_CURRENT_LIST_DIR}/${_hdr}"
		"${COLM_INCLUDE_DIR}/colm/${_hdr}"
		COPY_ON_ERROR SYMBOLIC)
	list(APPEND COLM_INSTALL_HDR "${CMAKE_CURRENT_LIST_DIR}/${_hdr}")
endforeach()

foreach(_hdr ${COLM_GENERATED_HDR})
	file(CREATE_LINK
		"${CMAKE_CURRENT_BINARY_DIR}/${_hdr}"
		"${COLM_INCLUDE_DIR}/colm/${_hdr}"
		COPY_ON_ERROR SYMBOLIC)
	list(APPEND COLM_INSTALL_HDR "${CMAKE_CURRENT_BINARY_DIR}/${_hdr}")
endforeach()

#
# libcolm: the colm runtime, linked into colm itself and into every generated
# colm program.
#
add_library(libcolm
	map.c pdarun.c list.c input.c stream.c debug.c
	codevect.c pool.c string.c tree.c iter.c
	bytecode.c program.c struct.c commit.c
	print.c)

target_include_directories(libcolm
	PUBLIC
	$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
	$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
	$<BUILD_INTERFACE:${COLM_INCLUDE_DIR}>
	$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

# The colm runtime carries colm's version, not the suite's. This mirrors the
# libtool -release string in Makefile.am.
set_target_properties(libcolm PROPERTIES
	OUTPUT_NAME colm
	VERSION ${COLM_VERSION_NUMERIC}
	SOVERSION ${COLM_VERSION_MAJOR})

add_library(colm::libcolm ALIAS libcolm)

#
# libprog: the colm compiler, minus the front end. Not installed; it exists so
# the bootstrap programs and colm itself can share the objects.
#
add_library(libprog STATIC
	resolve.cc lookup.cc synthesis.cc parsetree.cc
	fsmstate.cc fsmbase.cc fsmattach.cc fsmmin.cc
	fsmgraph.cc pdagraph.cc pdabuild.cc pdacodegen.cc fsmcodegen.cc
	redfsm.cc fsmexec.cc redbuild.cc closure.cc fsmap.cc
	dotgen.cc pcheck.cc ctinput.cc declare.cc codegen.cc
	exports.cc compiler.cc parser.cc reduce.cc)

target_include_directories(libprog
	PUBLIC
	$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
	$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
	$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/aapl>
	$<BUILD_INTERFACE:${COLM_INCLUDE_DIR}>)

target_link_libraries(libprog PUBLIC libcolm)

set_target_properties(libprog PROPERTIES
	OUTPUT_NAME prog)

#
# The autotools build compiles builddir.cc, which lets colm detect that it is
# being run out of the build tree and link programs via libtool. There is no
# libtool in the cmake build, so supply a stub that always reports "not in a
# build tree" and let colm use the installed headers and libraries.
#
set(_builddir_stub "${CMAKE_CURRENT_BINARY_DIR}/builddir-cmake.cc")
file(WRITE "${_builddir_stub}"
"/* Generated by cmake. The cmake build has no libtool wrapper, so the\n"
" * run-from-build-directory detection in builddir.cc does not apply. */\n"
"const char *defaultBuildDir()\n"
"{\n"
"	return 0;\n"
"}\n")

#
# The bootstrap chain. Each stage builds the parser for the next one.
#
set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen")
file(MAKE_DIRECTORY "${GEN_DIR}")

# bootstrap0: the input program is constructed with internal data structure
# constructors. It can parse a grammar using limited features. No code.
add_executable(bootstrap0 consinit.cc main.cc "${_builddir_stub}")
target_link_libraries(bootstrap0 PRIVATE libprog libcolm)
target_compile_definitions(bootstrap0 PRIVATE CONS_INIT ${common_COMPILE_DEFINITIONS})

# bootstrap1: the input program is specified using a stripped down colm syntax.
add_custom_command(
	OUTPUT "${GEN_DIR}/parse1.c" "${GEN_DIR}/if1.h" "${GEN_DIR}/if1.cc"
	COMMAND bootstrap0 -c -o "${GEN_DIR}/parse1.c"
		-e "${GEN_DIR}/if1.h" -x "${GEN_DIR}/if1.cc" no-input
	DEPENDS bootstrap0
	COMMENT "Generating bootstrap1 parser"
	VERBATIM)

add_executable(bootstrap1 loadinit.cc main.cc "${_builddir_stub}"
	"${GEN_DIR}/parse1.c" "${GEN_DIR}/if1.cc")
target_link_libraries(bootstrap1 PRIVATE libprog libcolm)
target_compile_definitions(bootstrap1 PRIVATE LOAD_INIT ${common_COMPILE_DEFINITIONS})

# bootstrap2: the input program is the colm grammar in colm.lm. It can parse
# full colm programs.
add_custom_command(
	OUTPUT "${GEN_DIR}/parse2.c" "${GEN_DIR}/if2.h" "${GEN_DIR}/if2.cc"
	COMMAND bootstrap1 -c -o "${GEN_DIR}/parse2.c"
		-e "${GEN_DIR}/if2.h" -x "${GEN_DIR}/if2.cc"
		"${CMAKE_CURRENT_LIST_DIR}/colm.lm"
	DEPENDS bootstrap1 colm.lm
	COMMENT "Generating bootstrap2 parser"
	VERBATIM)

add_executable(bootstrap2 loadboot2.cc main.cc "${_builddir_stub}"
	"${GEN_DIR}/parse2.c" "${GEN_DIR}/if2.cc")
target_link_libraries(bootstrap2 PRIVATE libprog libcolm)
target_compile_definitions(bootstrap2 PRIVATE LOAD_COLM ${common_COMPILE_DEFINITIONS})

# colm: built with the parser bootstrap2 generates from prog.lm.
add_custom_command(
	OUTPUT "${GEN_DIR}/parse3.c" "${GEN_DIR}/if3.h" "${GEN_DIR}/if3.cc"
	COMMAND bootstrap2 -I "${CMAKE_CURRENT_LIST_DIR}"
		-c -o "${GEN_DIR}/parse3.c"
		-e "${GEN_DIR}/if3.h" -x "${GEN_DIR}/if3.cc"
		"${CMAKE_CURRENT_LIST_DIR}/prog.lm"
	DEPENDS bootstrap2 prog.lm colm.lm
	COMMENT "Generating colm parser"
	VERBATIM)

add_executable(colm loadcolm.cc main.cc "${_builddir_stub}"
	"${GEN_DIR}/parse3.c" "${GEN_DIR}/if3.cc")
target_link_libraries(colm PRIVATE libprog libcolm)
target_compile_definitions(colm PRIVATE LOAD_COLM ${common_COMPILE_DEFINITIONS})

add_executable(colm::colm ALIAS colm)

if(BUILD_STANDALONE)
	if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
		target_link_options(colm PRIVATE -static)
	else()
		message(FATAL_ERROR "Unsupported toolset for standalone build.")
	endif()
endif()

# loadinit.cc, loadboot2.cc and loadcolm.cc include the generated export
# headers, which the custom commands above produce as a side effect of
# generating the parsers.
set_source_files_properties(loadinit.cc PROPERTIES
	OBJECT_DEPENDS "${GEN_DIR}/if1.h")
set_source_files_properties(loadboot2.cc PROPERTIES
	OBJECT_DEPENDS "${GEN_DIR}/if2.h")
set_source_files_properties(loadcolm.cc PROPERTIES
	OBJECT_DEPENDS "${GEN_DIR}/if3.h")

if(NOT MSVC)
	foreach(_target bootstrap0 bootstrap1 bootstrap2 colm)
		target_compile_options(${_target} PRIVATE -Wall)
	endforeach()

	# bootstrap0 relies on the implicit conversions the constructed
	# initialisation code performs.
	target_compile_options(bootstrap0 PRIVATE
		$<$<COMPILE_LANGUAGE:CXX>:-fpermissive>)
endif()

#
# Subdirectories. libfsm does not depend on colm; ragel does. Both need the
# generated config.h, which lives in this directory's build tree.
#
set(COLM_BUILD_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
set(COLM_AAPL_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/aapl")
set(COLM_CGIL_DIR "${CMAKE_CURRENT_LIST_DIR}/cgil")

add_subdirectory(cgil)
add_subdirectory(libfsm)
add_subdirectory(ragel)

#
# Install.
#
if(COLM_INSTALL_COLM OR COLM_INSTALL_RAGEL)
	set(_PACKAGE_NAME colm)

	if(NOT DEFINED CMAKE_INSTALL_CMAKEDIR)
		set(CMAKE_INSTALL_CMAKEDIR
			"${CMAKE_INSTALL_LIBDIR}/cmake/${_PACKAGE_NAME}"
			CACHE STRING "CMake packages")
	endif()

	configure_file(colm-config.cmake.in
		"${PROJECT_BINARY_DIR}/${_PACKAGE_NAME}-config.cmake" @ONLY)

	#
	# The runtime library goes out even when only ragel is being installed.
	# The ragel programs link it, and the exported ragel targets name
	# colm::libcolm, so the colm package has to be resolvable.
	#
	install(TARGETS libcolm
		EXPORT ${_PACKAGE_NAME}-targets
		RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
		LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
		ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")

	if(COLM_INSTALL_COLM)
		install(TARGETS colm
			EXPORT ${_PACKAGE_NAME}-targets
			RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")

		install(FILES ${COLM_INSTALL_HDR}
			DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/colm")
	endif()

	install(EXPORT ${_PACKAGE_NAME}-targets
		NAMESPACE ${_PACKAGE_NAME}::
		DESTINATION "${CMAKE_INSTALL_CMAKEDIR}")

	export(EXPORT ${_PACKAGE_NAME}-targets
		NAMESPACE ${_PACKAGE_NAME}::
		FILE "${PROJECT_BINARY_DIR}/${_PACKAGE_NAME}-targets.cmake")

	include(CMakePackageConfigHelpers)
	write_basic_package_version_file(
		"${PROJECT_BINARY_DIR}/${_PACKAGE_NAME}-config-version.cmake"
		VERSION ${COLM_VERSION_NUMERIC}
		COMPATIBILITY AnyNewerVersion)

	install(FILES
		"${PROJECT_BINARY_DIR}/${_PACKAGE_NAME}-config.cmake"
		"${PROJECT_BINARY_DIR}/${_PACKAGE_NAME}-config-version.cmake"
		DESTINATION "${CMAKE_INSTALL_CMAKEDIR}")

	# aapl is a header-only template library. The installed libfsm headers
	# include it, so it goes out with either half of the suite.
	install(DIRECTORY aapl/
		DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aapl"
		FILES_MATCHING PATTERN "*.h")
endif()
