tame/build-aux/list2typedef

177 lines
4.8 KiB
Bash
Executable File

#!/bin/bash
# Generates typedef from list of strings
#
# Copyright (C) 2014-2023 Ryan Specialty, LLC.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
# store unique names and values to detect collisions
declare -A _unames=()
declare -A _uvalues=()
# Provide an error and abort if a name or value collision is found
#
# This function stores each name and value that it sees each time it is
# invoked. If a duplicate is found, an error is output and the script
# terminates.
#
# This is important because both values are transformed in a way that
# the output space is a subset of the input. In the case of names,
# characters are stripped and replaced; in the case of values, the range
# of the hash function is determined by the number of bytes desired.
#
# Both will cause really bad bugs if left undetected.
assert-collision()
{
local -r name="${1?Missing name (const)}"
local -r value="${2?Missing value (hashed)}"
test -z "${_unames[$name]}" || {
echo "error: name collision: $name" >&2
exit 1
}
test -z "${_uvalues[$value]}" || {
echo "error: value collision: $name = $value" >&2
exit 1
}
_unames["$name"]=1
_uvalues["$value"]=1
}
# Generate typedef item from given string
#
# An item node will be output with the given string converted to a class
# (see `constify'); a BYTES-byte decimal integer as the value (see
# `hash-name'); and the original string as the description.
output-item()
{
local -r typedef="${1?Missing typedef}"
local -ri bytes="${2?Missing byte size}"
local name; read -r name
local -r const=$( constify "$typedef" <<< "$name" )
local -r value=$( echo -n "$name" | hash-name "$bytes" )
assert-collision "$const" "$value"
printf ' <item name="%s" value="%s" desc="%s" />\n' \
"$const" \
"$value" \
"$name"
}
# Convert a string into a constant
#
# The returned format will be "TYPE_NAME", where "TYPE" is the typedef
# converted into uppercase and "NAME" is the string converted into
# uppercase with all consecutive non-alphanumeric characters converted
# into an underscore.
constify()
{
local -r typedef="${1?Missing typedef}"
local name; read -r name
echo -n "${typedef^^}_"
sed 's/[^a-zA-Z0-9 ]\+//g;
s/ \+/_/g' <<< "${name^^}"
}
# Produce a decimal integer by taking the high BYTES bytes of the SHA256
# hash of stdin
#
# Since the output of sha256sum is hexidecimal, two digits represent a
# single byte. For example, let BYTES=4: taking the first 8 digits
# gives us the high 4 bytes (32 bits). Another way to visualize this is
# to convert a truncated hash to decimal.
hash-name()
{
local -ri bytes="${1?Missing byte size}"
local -ri digits=$(( bytes * 2 ))
echo $(( 0x$( sha256sum | head -c"$digits" ) ))
}
# Usage information
#
# This function terminates the script with EX_USAGE.
usage()
{
cat <<'EOF'
Usage: $0 typename [bytes]
Generate typedef of name TYPENAME using hashed standard input values
Each input line will produce a single typedef item. Each item has a
decimal value of the high BYTES of its SHA256 hash. If BYTES is not
provided, it defaults to 4 (32 bits).
Empty lines and lines beginning with a `#' are ignored.
This script checks for collisions of both names and values. If a
hash collision is found, the number of bytes BYTES must be
increased or the input string changed. Collisions will immediate
abort the script with a non-zero exit status.
EOF
return 64 # EX_USAGE
}
# Output typedef with hashed values
main()
{
local -r typedef="${1?Missing typedef}"
local -ri bytes="${2:-4}"
test "$bytes" -gt 0 || {
echo 'error: byte count must be greater than 0' >&2
usage
}
cat <<EOF
<?xml version="1.0"?>
<package xmlns="http://www.lovullo.com/rater"
xmlns:c="http://www.lovullo.com/calc"
xmlns:t="http://www.lovullo.com/rater/apply-template"
title="$typedef Type">
<typedef name="$typedef" desc="$typedef">
<enum type="integer">
<item name="${typedef^^}_NONE" value="0" desc="NONE" />
EOF
while read line; do
# ignore empty and comment lines
[[ "$line" && ! "$line" =~ ^\# ]] || continue
output-item "$typedef" "$bytes" <<< "$line"
done
echo ' </enum>'
echo ' </typedef>'
echo '</package>'
}
test $# -gt 0 || usage || exit
main "$@"