Skip to content

Instantly share code, notes, and snippets.

@legionus
Created February 17, 2023 11:58
Show Gist options
  • Save legionus/4ef966d9809ded7adc20e740560c7601 to your computer and use it in GitHub Desktop.
Save legionus/4ef966d9809ded7adc20e740560c7601 to your computer and use it in GitHub Desktop.
#!/bin/bash -efu
# SPDX-License-Identifier: GPL-2.0-or-later
. shell-error
declare -A VARS=() LOCK=() GOTO=() LABEL=()
declare -A NAMES=( [KEY]=0 [ATTR]=1 [OPERATOR]=2 [VALUE]=3 )
declare -a RULE_FILES=()
rule_init_var()
{
set -- "$@" "VARS_${#VARS[@]}"
VARS["$2"]="$3"
eval "$3=()"
eval "$1=\"\$3\""
}
rule_get_var()
{
set -- "$@" "${VARS["$2${3:+ $3}"]-}"
if [ -n "$3" ]; then
eval "$1=\"\${$3[0]-}\""
else
eval "$1=''"
fi
}
rule_set_var()
{
local k="$1${2:+ $2}"
[ -z "${LOCK[$k]-}" ] || return 0
local n="${VARS[$k]-}"
[ -n "$n" ] || rule_init_var n "$k"
case "$1" in
RUN|PROGRAM)
eval "$n=()"
rule_list_add "$@"
return 0
;;
esac
# shellcheck disable=SC1087
eval "$n[0]=\"\$3\""
}
rule_set_final_var()
{
rule_set_var "$1" "$2" "$3"
LOCK["$1${2:+ $2}"]="1"
}
rule_list_add()
{
local k="$1${2:+ $2}"
[ -z "${LOCK[$k]-}" ] || return 0
local n="${VARS[$k]-}"
[ -n "$n" ] || rule_init_var n "$k"
eval "$n+=(\"\$3\")"
}
rule_list_remove()
{
local n k a i
k="$1${2:+ $2}"
n="${VARS[$k]-}"
[ -n "$n" ] || return 0
declare -n "a=$n"
for (( i=0; i < ${#a[@]}; i++ )); do
# shellcheck disable=SC1087
[ "${a[$i]}" != "$3" ] || eval "$n[$i]=\"\""
done
}
rule_action()
{
local key attr var op value rc
key="$1"; shift
attr="$1"; shift
op="$1"; shift
value="$1"; shift
rc=0
case "$op" in
==)
rule_get_var var "$key" "$attr"
if [ -n "$value" ]; then
# shellcheck disable=SC2295
[ -n "$var" ] && [ -z "${var##$value}" ] || rc=1
elif [ -n "$var" ]; then
rc=1
fi
;;
!=)
rule_get_var var "$key" "$attr"
# shellcheck disable=SC2295
[ -n "${var##$value}" ] || rc=1
;;
+=) rule_list_add "$key" "$attr" "$value" ;;
-=) rule_list_remove "$key" "$attr" "$value" ;;
:=) rule_set_final_var "$key" "$attr" "$value" ;;
=) rule_set_var "$key" "$attr" "$value" ;;
esac
echo "RULE[$RULE]:. $key${attr:+[$attr]} $op $value -> $rc"
return $rc
}
rule_log()
{
message "${RULE_FILES[-1]}:$LINE_NR: $*."
return 1
}
rule_log_invalid_attr() { rule_log "Invalid attribute for $1"; }
rule_log_invalid_operator() { rule_log "$1 can not have \`$2' operator"; }
in_list()
{
local v="$1"; shift
while [ "$#" -gt 0 ]; do
[ "$v" != "$1" ] || return 0
shift
done
return 1
}
is_valid_pair()
{
local is_match=1
in_list "${pair[${NAMES[OPERATOR]}]}" '==' '!=' || is_match=
case "${pair[${NAMES[KEY]}]}" in
ACTION)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
ATTR)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
if in_list "${pair[${NAMES[OPERATOR]}]}" '+=' ':='; then
rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', or '=' operator, assuming '='"
pair[${NAMES[OPERATOR]}]='='
fi
;;
ATTRS)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
CONST)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
DEVPATH)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
DRIVER)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
DRIVERS)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
ENV)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
if in_list "${pair[${NAMES[OPERATOR]}]}" ':='; then
rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', '=', or '+=' operator, assuming '='"
pair[${NAMES[OPERATOR]}]='='
fi
;;
GOTO)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
in_list "${pair[${NAMES[OPERATOR]}]}" '=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
GROUP)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
IMPORT)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
KERNEL)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
KERNELS)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
LABEL)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
in_list "${pair[${NAMES[OPERATOR]}]}" '=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
MODE)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
NAME)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
if in_list "${pair[${NAMES[OPERATOR]}]}" '+='; then
rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', '=', or ':=' operator, assuming '='"
pair[${NAMES[OPERATOR]}]='='
fi
;;
OPTIONS)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
OWNER)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
PROGRAM)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
RESULT)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
;;
RUN)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
pair[${NAMES[ATTR]}]="program"
[ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
SECLABEL)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -z "$is_match" ] && ! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
if in_list "${pair[${NAMES[OPERATOR]}]}" ':='; then
rule_log "${pair[${NAMES[KEY]}]} key takes '=' or '+=' operator, assuming '='"
pair[${NAMES[OPERATOR]}]='='
fi
;;
SUBSYSTEM|SUBSYSTEMS)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
SYMLINK)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
;;
SYSCTL)
[ -n "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
! in_list "${pair[${NAMES[OPERATOR]}]}" '-=' ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
if in_list "${pair[${NAMES[OPERATOR]}]}" '+=' ':='; then
rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', or '=' operator, assuming '='"
pair[${NAMES[OPERATOR]}]='='
fi
;;
TAG)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
if in_list "${pair[${NAMES[OPERATOR]}]}" ':='; then
rule_log "${pair[${NAMES[KEY]}]} key takes '==', '!=', '=', or '+=' operator, assuming '='"
pair[${NAMES[OPERATOR]}]='='
fi
;;
TAGS)
[ -z "${pair[${NAMES[ATTR]}]}" ] ||
rule_log_invalid_attr "${pair[${NAMES[KEY]}]}" || return 1
;;
TEST)
[ -n "$is_match" ] ||
rule_log_invalid_operator "${pair[${NAMES[KEY]}]}" "${pair[${NAMES[OPERATOR]}]}" || return 1
;;
*)
rule_log "Invalid key \`${pair[${NAMES[KEY]}]}'"
return 1
;;
esac
}
rules_parse_pair()
{
local i="$1" s="$2"
local pair
if [[ "$s" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]; then
s="${BASH_REMATCH[1]}"
fi
if [[ "$s" =~ ^(.*[^{}])\{([^{}]+)\}[[:space:]]*([=\!+:-]?=)[[:space:]]*\"(.*)\"$ ]]; then
pair=( "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" )
elif [[ "$s" =~ ^(.*[^ !=+:-])[[:space:]]*([=\!+:-]?=)[[:space:]]*\"(.*)\"$ ]]; then
pair=( "${BASH_REMATCH[1]}" "" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" )
else
rule_log "Unexpected statement: \`$s\`"
return 1
fi
is_valid_pair ||
return 1
#for s in "${!names[@]}"; do
# eval "RULE_${s}_${RULE_NR}+=(\"\${pair[${NAMES[$s]}]}\")"
#done
case "${pair[${NAMES[KEY]}]}" in
GOTO) GOTO[${pair[${NAMES[VALUE]}]}]="$RULE_NR $i $((${#RULE_FILES[*]}-1))" ;;
LABEL) LABEL[${pair[${NAMES[VALUE]}]}]="$RULE_NR $((${#RULE_FILES[*]}-1))" ;;
esac
}
rules_parse_line()
{
local r='' c='' i=0
### backslash/double/single quote mode
local bq='' dq=''
while read -r -N 1 c; do
case "$c" in
\")
if [ -z "$bq" ]; then
[ -n "$dq" ] && dq="" || dq="$c"
fi
;;
\\)
### toggle backslash quote mode
if [ -z "$bq" ]; then
### enter backslash quote mode
bq=\\
continue
else
### leave backslash quote mode
r="$r\\"
bq=
fi
;;
,)
if [ -z "$bq$dq" ]; then
if [ -n "$r" ]; then
rules_parse_pair "$i" "$r" ||
return 1
(( i+=1 ))
fi
r=''; c='';
fi
;;
esac
r="$r$bq$c"
### leave backslash quote mode if any
bq=
done <<<"$1,"
}
LINE_NR=
rules_parse_file()
{
local rule str s
LINE_NR=0
rule=""
while read -r str; do
(( LINE_NR+=1 ))
if [ "${str:0-1}" = \\ ]; then
rule+="${str:0:-1} "
continue
fi
rule+="$str"
if [ -z "$rule" ] || [ "${rule:0:1}" = '#' ]; then
rule=
continue
fi
for s in "${NAMES[@]}"; do
eval "RULE_${s}_${RULE_NR}=()"
done
#printf '{%s}\n' "$rule"
! rules_parse_line "$rule" ||
(( RULE_NR+=1 ))
rule=
done
}
rules_post_check()
{
for s in "${!GOTO[@]}"; do
[ -z "${LABEL[$s]-}" ] ||
continue
set -- ${GOTO[$s]}
local rule_nr="${GOTO[$s]%:*}"
local pair_nr="${GOTO[$s]#*:}"
eval "RULE_VALUE_$1[$2]=\"\""
message "${RULE_FILES[$3]}: GOTO=\"$s\" has no matching label, ignoring"
done
for s in "${!LABEL[@]}"; do
set -- ${LABEL[$s]}
[ -n "${GOTO[$s]-}" ] ||
message "${RULE_FILES[$2]}: LABEL=\"$s\" takes no effect, ignoring"
done
}
rules_run()
{
local RULE KEY ATTR OPERATOR VALUE goto
for (( RULE=0; RULE < RULE_NR; RULE++ )); do
rule_get_var goto GOTO
if [ -n "$goto" ]; then
set -- ${LABEL[$goto]}
[ "$RULE" -lt "$1" ] ||
rule_set_var "GOTO" "" ""
continue
fi
declare -n "KEY=RULE_KEY_${RULE}"
declare -n "ATTR=RULE_ATTR_${RULE}"
declare -n "OPERATOR=RULE_OPERATOR_${RULE}"
declare -n "VALUE=RULE_VALUE_${RULE}"
for i in "${!KEY[@]}"; do
rule_action "${KEY[$i]}" "${ATTR[$i]}" "${OPERATOR[$i]}" "${VALUE[$i]}" ||
: break
done
done
local i n a
for n in "${!VARS[@]}"; do
declare -n "a=${VARS[$n]}"
for (( i=0; i < ${#a[@]}; i++ )); do
printf '%s[%s]="%s"%s\n' "$n" "$i" "${a[$i]}" "${LOCK[$n]:+ (final)}"
done
done
}
RULE_NR=0
for fn in "$@"; do
RULE_FILES+=("$fn")
rules_parse_file < "$fn"
done
rules_post_check
message "Parsed $RULE_NR rules"
#rules_run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment