#!/bin/sh -efu
#
# Copyright (c) 2014-2015 Mike Frysinger <vapier@gentoo.org>
# Copyright (c) 2014-2015 Dmitry V. Levin <ldv@strace.io>
# Copyright (c) 2014-2021 The strace developers.
# All rights reserved.
#
# SPDX-License-Identifier: LGPL-2.1-or-later

export LC_ALL=C

usage()
{
	cat <<EOF
Usage: $0 <input> <output>

Generate xlat header files from <input> (a file or dir of files) and write
the generated headers to <output>.
EOF
	exit 1
}

print_m4_record()
{
	local val output
	val="$1"; shift
	output="$1"; shift
	[ -n "$output" ] || return 0

	if [ "$first_enum" = 1 ]; then
		printf '%s' "$val"
	else
		printf ',\n%s' "$val"
	fi >> "$output"

	first_enum=0
}

cond_def()
{
	local line
	line="$1"; shift
	local xlat_type
	xlat_type="$1"; shift

	local val
	val="${line%%[!A-Za-z0-9_]*}"

	local t def=
	t="${line#*[	 ]}"
	if [ "$line" != "$t" ]; then
		while [ "$def" != "$t" ]; do
			def="$t"
			t="${t##[	 ]}"
		done
	fi

	if [ -n "$def" ]; then
		[ -n "$unconditional" ] ||
			printf '%s\n' \
				"#if defined($val) || (defined(HAVE_DECL_$val) && HAVE_DECL_$val)"
		printf '%s\n' \
			"DIAG_PUSH_IGNORE_TAUTOLOGICAL_COMPARE" \
			"static_assert(($val) == ($def), \"$val != $def\");" \
			"DIAG_POP_IGNORE_TAUTOLOGICAL_COMPARE"
		[ -n "$unconditional" ] ||
			printf '%s\n' \
				"#else" \
				"# define $val $def" \
				"#endif"
	fi

	if [ XT_SORTED = "$xlat_type" ]; then
		[ -n "$unconditional" ] ||
			printf '%s\n' \
				"#if defined($val) || (defined(HAVE_DECL_$val) && HAVE_DECL_$val)"
		check_sort_order "$val"
		[ -n "$unconditional" ] ||
			printf '%s\n' "#endif"
	fi

}

check_sort_order()
{
	local val
	val="$1"; shift

	cat <<-EOF
		#if defined XLAT_PREV_VAL
		static_assert((unsigned long long) ($val)
			      > (unsigned long long) (XLAT_PREV_VAL),
			      "Incorrect order in #sorted xlat: $val"
			      " is not larger than the previous value");
		#endif
		#undef XLAT_PREV_VAL
		#define XLAT_PREV_VAL ($val)
	EOF
}

print_xlat()
{
	local val
	val="$1"; shift

	[ 1 = "$value_indexed" ] && printf " [%s] =" "${val}"
	if [ -z "${val_type-}" ]; then
		echo " XLAT(${val}),"
	else
		echo " XLAT_TYPE(${val_type}, ${val}),"
	fi

	echo " #define XLAT_VAL_$xlat_flag_cnt ((${val_type:-unsigned}) (${val}))"
	echo " #define XLAT_STR_$xlat_flag_cnt STRINGIFY(${val})"
	xlat_flag_cnt=$((xlat_flag_cnt + 1))

	[ -z "$enum" ] || print_m4_record "$val" "$output_m4"
}

print_xlat_pair()
{
	local val str macro
	val="$1"; shift
	str="$1"; shift
	macro="$1"; shift

	[ 1 = "$value_indexed" ] && printf " [%s] =" "${val}"
	if [ -z "${val_type-}" ]; then
		echo " XLAT_PAIR(${val}, \"${str}\"),"
	else
		echo " XLAT_TYPE_PAIR(${val_type}, ${val}, \"${str}\"),"
	fi

	echo " #define XLAT_VAL_$xlat_flag_cnt ((${val_type:-unsigned}) (${val}))"
	echo " #define XLAT_STR_$xlat_flag_cnt \"${str}\""
	xlat_flag_cnt=$((xlat_flag_cnt + 1))

	[ -z "$enum" ] || print_m4_record "$macro" "$output_m4"
}

cond_xlat()
{
	echo "$1" | {
		local val def m xlat

		read val def

		m="${val%%|*}"

		if [ "${m}" = "${m#1<<}" ]; then
			xlat="$(print_xlat "${val}")"
		else
			m="${m#1<<}"
			xlat="$(print_xlat_pair "1ULL<<${val#1<<}" "${val}" "$m")"
		fi

		if [ -z "${def}${unconditional}" ]; then
			printf '%s\n' \
				"#if defined(${m}) || (defined(HAVE_DECL_${m}) && HAVE_DECL_${m})" \
				" ${xlat}" \
				"#endif"
		else
			printf '%s\n' "$xlat"
		fi
	}

	# Since we're calling print_xlat/print_xlat_pair in subprocess
	xlat_flag_cnt=$((xlat_flag_cnt + 1))
	first_enum=0
}

gen_header()
{
	local input="$1" output="$2" name="$3" output_m4="$4"

	exec 3>&1

	echo "generating ${output}" >&2
	(
	local defs="${0%/*}/../defs.h"
	local mpers="${0%/*}/../mpers_xlat.h"
	local decl="extern const struct xlat ${name}[];"
	local in_defs= in_mpers=
	local xlat_type="XT_NORMAL"
	local includes=""
	local enum=""

	first_enum=1
	value_indexed=0
	xlat_flag_cnt=0

	if grep -F -q -x "$decl" "$defs"; then
		in_defs=1
	elif grep -F -q -x "$decl" "$mpers"; then
		in_mpers=1
	fi

	cat <<-EOF
	/* Generated by $0 from $1; do not edit. */

	#include "gcc_compat.h"
	#include "static_assert.h"

	EOF

	local unconditional= line
	# 1st pass: output directives.
	while read -r line; do
		case "$line" in
			*/\**)
			line=$(printf "%s" "$line" |
				sed "s|[[:space:]]*/\*.*\*/[[:space:]]*||")
			;;
		esac

		case $line in
		'#conditional')
			unconditional=
			;;
		'#unconditional')
			unconditional=1
			;;
		'#val_type '*)
			# to be processed during 2nd pass
			;;
		'#sorted'|'#sorted '*)
			xlat_type="XT_SORTED"
			;;
		'#value_indexed')
			value_indexed=1
			xlat_type="XT_INDEXED"
			;;
		'#enum')
			[ -z "$output_m4" ] || enum=1
			;;
		'#include '*)
			includes="${includes} ${line###include }"
			;;
		'#'*)
			echo "${line}"
			;;
		[A-Z_]*)
			cond_def "$line" "$xlat_type"
			;;
		'1<<'[A-Z_]*)	# symbolic constants with shift
			[ XT_SORTED != "$xlat_type" ] ||
				check_sort_order "1ULL<<${line#1<<}"
			;;
		[0-9]*)	# numeric constants
			[ XT_SORTED != "$xlat_type" ] ||
				check_sort_order "${line}"
			;;
		esac
	done < "$input"

	cat <<-EOF
		#undef XLAT_PREV_VAL

		#ifndef XLAT_MACROS_ONLY

	EOF

	[ -z "$unconditional" -o -z "$enum" ] || {
		echo "ignoring #enum due to #unconditional for ${input}" >&2
		enum=""
	}

	[ 1 != "$enum" ] || (
		echo "generating ${output_m4}" >&2
		printf 'dnl Generated by %s from %s; do not edit.\n' \
			"$0" "$input"
		printf 'AC_DEFUN([st_CHECK_ENUMS_%s],[\n' "$name"
		printf 'AC_CHECK_DECLS(m4_normalize([\n'
	) > "${output_m4}"

	if [ -n "$in_defs" ]; then
		cat <<-EOF
			# ifndef IN_MPERS

		EOF
	elif [ -n "$in_mpers" ]; then
		cat <<-EOF
			# ifdef IN_MPERS

			${decl}

			# else

		EOF
	else
		cat <<-EOF
			# ifdef IN_MPERS

			#  error static const struct xlat ${name} in mpers mode

			# else

		EOF
	fi

	echo "DIAG_PUSH_IGNORE_TAUTOLOGICAL_CONSTANT_COMPARE"
	echo "static const struct xlat_data ${name}_xdata[] = {"

	unconditional= val_type=
	# 2nd pass: output everything.
	while read -r line; do
		case "$line" in
			*/\**)
			line=$(printf "%s" "$line" |
				sed "s|[[:space:]]*/\*.*\*/[[:space:]]*||")
			;;
		esac

		case ${line} in
		'#conditional')
			unconditional=
			;;
		'#unconditional')
			unconditional=1
			;;
		'#sorted'|'#sorted '*)
			;;
		'#value_indexed')
			;;
		'#enum')
			;;
		'#include '*)
			;;
		'#val_type '*)
			val_type="${line#\#val_type }"
			;;
		[A-Z_!]*)	# symbolic constants
			cond_xlat "${line}"
			;;
		'1<<'[A-Z_]*)	# symbolic constants with shift
			cond_xlat "${line}"
			;;
		[0-9]*)	# numeric constants
			print_xlat "${line}"
			;;
		*)	# verbatim lines
			echo "${line}"
			;;
		esac
	done < "${input}"
	echo '};'

	[ 1 != "$enum" ] || (
		printf '\n]),,, [\n'
		[ -z "$includes" ] || printf '#include %s\n' $includes
		printf '])])])\n'

		# Providing macro name to main
		echo "st_CHECK_ENUMS_${name}" >&3
	) >> "${output_m4}"

	if [ -n "$in_defs" ]; then
		:
	elif [ -n "$in_mpers" ]; then
		cat <<-EOF
			#  if !(defined HAVE_M32_MPERS || defined HAVE_MX32_MPERS)
			static
			#  endif
		EOF
	else
		cat <<-EOF
			static
		EOF
	fi

	cat <<-EOF
		const struct xlat ${name}[1] = { {
			 .data = ${name}_xdata,
			 .size = ARRAY_SIZE(${name}_xdata),
			 .type = ${xlat_type},
	EOF

	echo " .flags_mask = 0"
	for i in $(seq 0 "$((xlat_flag_cnt - 1))"); do
		cat <<-EOF
			#  ifdef XLAT_VAL_${i}
			  | XLAT_VAL_${i}
			#  endif
		EOF
	done
	echo "  ,"

	echo " .flags_strsz = 0"
	for i in $(seq 0 "$((xlat_flag_cnt - 1))"); do
		cat <<-EOF
			#  ifdef XLAT_STR_${i}
			  + sizeof(XLAT_STR_${i})
			#  endif
		EOF
	done
	echo "  ,"

	cat <<-EOF
		} };
		DIAG_POP_IGNORE_TAUTOLOGICAL_CONSTANT_COMPARE

	EOF

	for i in $(seq 0 "$((xlat_flag_cnt - 1))"); do
		cat <<-EOF
			#  undef XLAT_STR_${i}
			#  undef XLAT_VAL_${i}
		EOF
	done

	cat <<-EOF
		# endif /* !IN_MPERS */

		#endif /* !XLAT_MACROS_ONLY */
	EOF
	) >"${output}"

	exec 3>&-
}

gen_make()
{
	local output="$1"
	local name
	shift
	echo "generating ${output}" >&2
	(
		printf "XLAT_INPUT_FILES = "
		printf 'xlat/%s.in ' "$@"
		echo
		printf "XLAT_HEADER_FILES = "
		printf 'xlat/%s.h ' "$@"
		echo
		for name; do
			printf '$(top_srcdir)/src/xlat/%s.h: $(top_srcdir)/src/xlat/%s.in $(top_srcdir)/src/xlat/gen.sh\n' \
				"${name}" "${name}"
			echo '	$(AM_V_GEN)$(top_srcdir)/src/xlat/gen.sh $< $@'
		done
	) >"${output}"
}

gen_git()
{
	local output="$1"
	shift
	echo "generating ${output}" >&2
	(
		printf '/%s\n' .gitignore Makemodule.am st_check_enums.m4
		printf '/%s.h\n' "$@"
		printf '/%s.m4\n' "$@"
	) >"${output}"
}

gen_m4_entry()
{
	local output
	output="$1"; shift

	echo "generating $output" >&2
	{
		printf 'AC_DEFUN([st_CHECK_ENUMS],[\n'
		while read fun; do
			printf '\t%s\n' "$fun"
		done
		printf '])\n'
	} >"$output"
}

main()
{
	case $# in
	0) set -- "${0%/*}" "${0%/*}" ;;
	2) ;;
	3) ;;
	*) usage ;;
	esac

	local input="$1"
	local output="$2"
	local output_m4="${3:-}"
	local name
	local jobs=0
	local ncpus="$(getconf _NPROCESSORS_ONLN)"
	local pids=
	[ "${ncpus}" -ge 1 ] ||
		ncpus=1

	if [ -d "${input}" ]; then
		(
		local f names=
		set +f
		set -- "${input}"/*.in
		set -f
		for f; do
			[ -f "${f}" ] || continue
			name=${f##*/}
			name=${name%.in}
			gen_header "${f}" "${output}/${name}.h" "${name}" \
				"${output}/${name}.m4" &
			pids="$pids $!"
			names="${names} ${name}"
			: $(( jobs += 1 ))
			if [ "${jobs}" -gt "$(( ncpus * 2 ))" ]; then
				read wait_pid rest
				pids="$rest"
				wait -n 2>/dev/null || wait "$wait_pid"
				: $(( jobs -= 1 ))
			fi <<- EOF
			$pids
			EOF
		done
		gen_git "${output}/.gitignore" ${names} &
		gen_make "${output}/Makemodule.am" ${names} &
		wait
		) | sort | gen_m4_entry "${output}/st_check_enums.m4"
	else
		name=${input##*/}
		name=${name%.in}
		gen_header "${input}" "${output}" "${name}" "${output_m4}" \
			> /dev/null
	fi
}

main "$@"