Created
February 17, 2023 11:58
-
-
Save legionus/4ef966d9809ded7adc20e740560c7601 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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