From 221548f141b703c644a2be354a18c338b42b1a5e Mon Sep 17 00:00:00 2001 From: Patrick Spek Date: Sun, 26 Mar 2023 11:13:34 +0200 Subject: Add bpt for file templating --- CHANGELOG.md | 6 + lib/util.bash | 50 ++- lib/vendor/bpt.bash | 1241 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1291 insertions(+), 6 deletions(-) create mode 100644 lib/vendor/bpt.bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 78cf85f..0af0434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `sync` subcomman will now `stash` any changes before it attempts to `pull`. Afterwards, `stash pop` will be ran to apply the last `stash`ed changes again. +- The `file_template` function has been altered to be able to use various + templating engines. Currently it supports `satpl` (which stands for sed-awk + template, the "original" templating mechanism used in Bashtard) and `bpt` + (which stands for [Bash Pure Templates](https://github.com/husixu1/bpt). The + engine used is defined through the file's extension. If no extension is set, + it will default to `satpl`. ## [1.0.0] - 2022-05-06 diff --git a/lib/util.bash b/lib/util.bash index 54ec562..0961c7e 100644 --- a/lib/util.bash +++ b/lib/util.bash @@ -10,6 +10,8 @@ . "$BASHTARD_LIBDIR/util/pkg.bash" # shellcheck source=lib/util/svc.bash . "$BASHTARD_LIBDIR/util/svc.bash" +# shellcheck source=lib/vendor/bpt.bash +. "$BASHTARD_LIBDIR/vendor/bpt.bash" # Change the working directory. In usage, this is the same as using cd, # however, it will make additional checks to ensure everything is going fine. @@ -113,17 +115,44 @@ fetch_http_wget() { file_template() { local file - local sedfile + local system - file="$(playbook_path "base")/share/$1" ; shift - sedfile="$(tmpfile)" + file="$1" ; shift - if [[ ! -f $file ]] + ext="${file##*.}" + path="$(playbook_path "base")/share/$file" + system="bashtard/file_template" + + if [[ -z "$ext" || "$ext" == "$file" ]] then - crit "bashtard/template" "Tried to render template from $file, but it doesn't exist" - return + debug "$system" "Missing extension for template $file, assuming satpl" + ext="satpl" + fi + + if [[ ! -f "$path" ]] + then + crit "$system" "No template found at $path" + return 1 fi + if [[ "$(type -t "file_template_$ext")" != "function" ]] + then + crit "$system" "Invalid extension for template $file ($ext)" + return 1 + fi + + "file_template_$ext" "$path" "$@" +} + +file_template_satpl() +{ + local file + local sedfile + + file="$1" ; shift + + sedfile="$(tmpfile)" + for kv in "$@" do debug "bashtard/template" "Adding $kv to sedfile at $sedfile" @@ -149,6 +178,15 @@ file_template() sed -f "$sedfile" "$file" } +file_template_bpt() +{ + local file + + file="$1" ; shift + + eval "$@ bpt.main ge \"$file\"" +} + # Check if the first argument given appears in the list of all following # arguments. in_args() { diff --git a/lib/vendor/bpt.bash b/lib/vendor/bpt.bash new file mode 100644 index 0000000..08d15b2 --- /dev/null +++ b/lib/vendor/bpt.bash @@ -0,0 +1,1241 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2023 Sixu Hu +# SPDX-FileCopyrightText: 2023 Patrick Spek +# +# SPDX-License-Identifier: MIT + +# shellcheck disable=SC2317 + +# Import once +if [[ -n $__BPT_VERSION ]]; then return; fi +readonly __BPT_VERSION="v0.1" + +# Add multiple traps for EXIT +# See https://stackoverflow.com/questions/3338030 +bpt.__add_exit_trap() { + trap_add_cmd=${1:?${FUNCNAME[0]} usage error} + trap -- "$( + extract_trap_cmd() { printf '%s\n' "$3"; } + eval "extract_trap_cmd $(trap -p EXIT)" + printf '%s\n' "${trap_add_cmd}" + )" EXIT || echo "Unable to add to trap" >&2 +} + +# The shift-reduce LR(1) parser. +# $1: Parse table name. +# The parse table should be an associative array where the key is +# , and the value actions (s /r /a/). +# $2: Reduce function hook +# This function can access `rule=(LHS RHS1 RHS2 ...)`. +# This function can access the RHS(i+1)'s content': `${contents[$((s + i))]}`. +# This function should store the reduce result to `contents[$s]`. +# $3: Error handler function hook +# Args passed to this function: +# $1: Line +# $2: Column +# $3: Default error message +# $4: (optional) If set, enable debug. +# shellcheck disable=SC2030 # Modification to `contents` and `rule` are local. +bpt.__lr_parse() ( + local -rn table="$1" + local -r reduce_fn="${2:-echo}" + local -r error_fn="${3:-__error}" + if [[ -n $4 ]]; then local -r NDEBUG=false; else local -r NDEBUG=true; fi + + # 20 should be enough ... + # I assume no one's writing a BNF with more than 20 RHSs ... + local -a STATE_PTRN=('') + for i in {1..20}; do STATE_PTRN[i]="${STATE_PTRN[i - 1]}:*"; done + + # Parse stack + # Using string manipulation for states is faster than using an array. + local states=':0' stack_size=1 + # Contents stack associatied wit the parse stack + # Large dict indexing is significantly faster than a regular one. + # Thus we use stack_size + associative array to emulate a regular array. + local -A contents=([0]='') + + # Current reduction rule + local -a rule=() + # Current look-ahead token and its content + local token='' content='' action='' + # Location tracking variables + local num_lines=0 num_bytes=0 + # Temporary variables + local i=0 str_lines=0 buffer='' + + # $1: Goto state after shift + __shift() { + states+=":$1" + contents["$stack_size"]="$content" + ((++stack_size)) + token='' content='' + } + + # $1: Rule + __reduce() { + # Although not robust, word splitting is faster than `read` + # shellcheck disable=SC2206 + local num_rhs=$((${#rule[@]} - 1)) + + # Reduce and goto state + # shellcheck disable=SC2295 + states="${states%${STATE_PTRN[$num_rhs]}}" + states+=":${table["${states##*:},${rule[0]}"]}" + + # Reduction start location (on the contents stack) + local s=$((stack_size - num_rhs)) + + # Run reduce hook (which saves the reduce result to `contents[$s]`) + $reduce_fn || exit 1 + stack_size=$((s + 1)) + } + + # Simply print the result + __accept() { + printf '%s' "${contents[1]}" + } + + # Default error handler + __error() { + echo "Error: Line $(($1 + 1)) Column $(($2 + 1))" + echo "$3" + } >&2 + + # Debugging support + $NDEBUG || { + eval __orig"$(declare -f __shift)" + eval __orig"$(declare -f __reduce)" + eval __orig"$(declare -f __accept)" + __shift() { + echo "[DBG] ${states##*:} Shift $1 \`$content\`" >&2 + __orig__shift "$@" + } + __reduce() { + echo "[DBG] ${states##*:} Reduce ${rule[*]}" >&2 + __orig__reduce + } + __accept() { + $NDEBUG || echo "[DBG] Result accepted" >&2 + __orig__accept + } + } + + while true; do + [[ -n $token ]] || { + read -r token str_lines num_lines num_bytes + IFS= read -r buffer || return 1 + content="$buffer" + for ((i = 1; i < str_lines; ++i)); do + IFS= read -r buffer || return 1 + content+=$'\n'"$buffer" + done + } + + action="${table["${states##*:},$token"]}" + case "$action" in + # Shift + s*) __shift "${action#s }" ;; + # Reduce + r*) # shellcheck disable=SC2206 + rule=(${action#r }) + __reduce + ;; + # Accept + a) __accept && break ;; + # Error + '') + local expects='' rule_key='' + for rule_key in "${!table[@]}"; do + [[ $rule_key != "${states##*:},"* || + -z "${table["$rule_key"]}" || + "${table["$rule_key"]}" =~ ^[[:digit:]]+$ ]] || + expects+="${expects:+,}${BPT_PP_TOKEN_TABLE["${rule_key##*,}"]:-${rule_key##*,}}" + done + $error_fn "$num_lines" "$num_bytes" \ + "Expects one of \`${expects[*]}\` but got \`${token}\` ($content)." + $NDEBUG || echo "[DBG] PARSER STATES ${states} TOKEN ${token} CONTENT ${content}." >&2 + exit 1 + ;; + *) # Parse table error (internal error) + echo "Internal error: STATES ${states} TOKEN ${token} CONTENT ${content}. " >&2 + echo "Internal error: action '$action' not recognized." >&2 + exit 1 + ;; + esac + done +) + +# The tokenizer for bpt +# $1: Left deilmiter +# $2: Right delimiter +# $3: Error handler function hook +# Args passed to this function: +# $1: Line +# $2: Column +# $3: Default error message +# +# Terminal token name to content mappings: +# str: Anything outside the toplevel `ld ... rd` or +# Anything inside `"..."` or `'...'` within any `ld ... rd` +# Note1: `"` inside `"..."` needs to be escaped using `\"`, +# and the same for `'` inside `'...'`. +# +# ld: ${ldelim} rd: ${rdelim} lp: ( rp: ) +# cl: : ex: ! eq: -eq ne: -ne +# lt: -lt gt: -gt le: -le ge: -ge +# streq: == strne: != strlt: < strgt: > +# and|or|if|elif|else|for|in|include: +# id: [[:alpha:]_][[:alnum:]_]* +bpt.scan() ( + local -r ld="$1" rd="$2" error_fn="${3:-__error}" + bpt.__test_delims "$ld" "$rd" || return 1 + + # Default error handler + __error() { + echo "Error: Line $(($1 + 1)) Column $(($2 + 1))" + echo "$3" + } >&2 + + # See man regex.7. We need to escape the meta characters of POSIX regex. + local -rA ESC=( + ['^']=\\ ['.']=\\ ['[']=\\ ['$']=\\ ['(']=\\ [')']=\\ + ['|']=\\ ['*']=\\ ['+']=\\ ['?']=\\ ['{']=\\ [\\]=\\ + ) + local e_ld='' e_rd='' i=0 + for ((i = 0; i < ${#ld}; ++i)); do e_ld+="${ESC["${ld:i:1}"]}${ld:i:1}"; done + for ((i = 0; i < ${#rd}; ++i)); do e_rd+="${ESC["${rd:i:1}"]}${rd:i:1}"; done + + # Keywords + local -ra KW=( + "${e_ld}" "${e_rd}" + '-eq' '-ne' '-gt' '-lt' '-ge' '-le' + '==' '!=' '>' '<' ':' '\!' '"' "'" '\(' '\)' + 'and' 'or' 'if' 'elif' 'else' 'for' 'in' 'include' + ) + local -r KW_RE="$(IFS='|' && echo -n "${KW[*]}")" + local -r ID_RE='[[:alpha:]_][[:alnum:]_]*' + + # Scanner states + local num_ld=0 + local quote='' + + # Location trackers + local num_lines=0 + local num_bytes=0 + + # String processing tracker & buffer. + # `str_lines=''` means currently outside the scope of string + local string='' str_lines='' str_bytes='' + # Start scannign string + __start_string() { + str_lines="${str_lines:-1}" + str_bytes="${str_bytes:-$num_bytes}" + } + # Commit (possibly multiline) string buffer + # shellcheck disable=SC2031 + __commit_string() { + ((str_lines > 0)) || return + echo "str $str_lines $((num_lines + 1 - str_lines)) $str_bytes" + # `$content` can be a literal `-ne`. Thus printf is needed. + printf '%s\n' "$string" + string='' str_lines='' str_bytes='' + } + + # Tokenizer + local line='' content='' + while IFS= read -r line || [[ $line ]]; do + # Decide whether currently scanning a string + # Only count newlines in strings (outside `ld ... rd` and inside quotes). + [[ $num_ld -gt 0 && -z "$quote" ]] || { + __start_string + [[ $num_lines -eq 0 ]] || { string+=$'\n' && ((++str_lines)); } + } + + # Scan the line + while [[ -n "$line" ]]; do + content='' # The consumed content (to be removed from `line`) + if [[ $num_ld -eq 0 ]]; then + # Outside `ld ... rd` + if [[ $line =~ ^(${e_ld}) ]]; then + # If met `ld`, enter `ld ... rd` + __commit_string + ((++num_ld)) + content="${BASH_REMATCH[1]}" + echo "ld 1 $num_lines $num_bytes" + printf '%s\n' "$content" + elif [[ $line =~ (${e_ld}) ]]; then + content="${line%%"${BASH_REMATCH[1]}"*}" + string+="$content" + else + content="$line" + string+="$line" + fi + elif [[ -n "$quote" ]]; then + # Inside quotes in `ld ... rd` + # Scan for `str` until we find a non-escaped quote. + local line_copy="$line" + while [[ $line_copy =~ ^[^${quote}]*\\${quote} ]]; do + # Escape quote inside string + string+="${line_copy%%"\\${quote}"*}${quote}" + content+="${line_copy%%"\\${quote}"*}\\${quote}" + line_copy="${line_copy#"${BASH_REMATCH[0]}"}" + done + + if [[ $line_copy =~ ${quote} ]]; then + # Remove the closing quote from line + content+="${line_copy%%"${quote}"*}${quote}" + string+="${line_copy%%"${quote}"*}" + quote='' + __commit_string + else + content="$line_copy" + string+="$line_copy" + fi + else + # Non-strings. Commit string first. + __commit_string + if [[ $line =~ ^(${KW_RE}) ]]; then + # Inside `ld ... rd` and matches a keyword at front + content="${BASH_REMATCH[1]}" + case "$content" in + '-eq') echo -n eq ;; '-ne') echo -n ne ;; + '-lt') echo -n lt ;; '-gt') echo -n gt ;; + '-le') echo -n le ;; '-ge') echo -n ge ;; + '==') echo -n streq ;; '!=') echo -n strne ;; + '>') echo -n strgt ;; '<') echo -n strlt ;; + '!') echo -n ex ;; ':') echo -n cl ;; + '(') echo -n lp ;; ')') echo -n rp ;; + '"' | "'") + quote="$content" + __start_string + ;; + "$ld") + ((++num_ld)) + echo -n ld + ;; + "$rd") + ((num_ld-- > 0)) || { + $error_fn "$num_lines" "$num_bytes" "Extra '$rd'." + return 1 + } + ((num_ld != 0)) || __start_string + echo -n rd + ;; + and | or | if | elif | else) ;& + for | in | include) echo -n "$content" ;; + *) + $error_fn "$num_lines" "$num_bytes" \ + "Internal error: Unrecognized token ${content}" + return 1 + ;; + esac + [[ -n $quote ]] || { + echo " 1 $num_lines $num_bytes" + printf '%s\n' "$content" + } + else # Inside `ld ... rd` but outside quotes + # Ignore spaces inside `ld ... rd` + [[ $line =~ ^([[:space:]]+)(.*) ]] && { + line="${BASH_REMATCH[2]}" + ((num_bytes += ${#BASH_REMATCH[1]})) + continue + } + content="$line" + + # Contents are either keywords or identifiers + if [[ $content =~ (${KW_RE}) ]]; then + content="${content%%"${BASH_REMATCH[1]}"*}" + fi + if [[ ! $content =~ ^(${ID_RE}) ]]; then + $error_fn "$num_lines" "$num_bytes" \ + "'$content' is not a valid identifier" + return 1 + fi + content="${BASH_REMATCH[1]}" + echo "id 1 $num_lines $num_bytes" + printf '%s\n' "$content" + fi + fi + + # Post-processing only counts the last line read. + line="${line#"$content"}" + ((num_bytes += ${#content})) + done + ((++num_lines)) + num_bytes=0 content='' + done + __commit_string + echo "$ 1 $num_lines 0" # The EOF token + echo '' # The EOF content (empty) +) + +bpt.__test_delims() { + [[ $1 != *' '* && $2 != *' '* ]] || { + echo "Left and right delimiters must not contain spaces." >&2 + return 1 + } + [[ "$1" != "$2" ]] || { + echo "Left and right delimiters must be different." >&2 + return 1 + } +} + +# The reduce function to collect all variables +# shellcheck disable=SC2031 # Direct access of `rule` and `contents` for speed. +bpt.__reduce_collect_vars() { + # For all `id` token, allow only the path via the VAR rule. + # For all `str` token, allow only the path to the INCLUDE rule. + case "${rule[0]}" in + ID | STR) ;; + STMT) [[ "${rule[1]}" != STR ]] || contents[$s]='' ;; + VAR) + contents[$s]="${contents[$((s + 1))]}"$'\n' + [[ ${#rule[@]} -eq 4 || ${rule[4]} != VAR ]] || + contents[$s]+="${contents[$((s + 3))]}" + ;; + BUILTIN) contents[$s]="${contents[$((s + 3))]}" ;; + INCLUDE) contents[$s]="$(__recursive_process "${contents[$((s + 3))]}")"$'\n' ;; + FORIN) # Filter tokens defined by the FORIN rule + contents[$s]="${contents[$((s + 4))]}" + local var + while read -r var; do + [[ -z $var || $var == "${contents[$((s + 2))]}" ]] || contents[$s]+="$var"$'\n' + done <<<"${contents[$((s + 6))]}" + ;; + *) # Prevent the propagation of all other non-terminals + [[ "${#rule[@]}" -ne 1 ]] || { contents[$s]='' && return; } + [[ "${rule[1]^^}" == "${rule[1]}" ]] || contents[$s]='' + local i=1 + for (( ; i < ${#rule[@]}; ++i)); do + [[ "${rule[i + 1],,}" == "${rule[i + 1]}" ]] || + contents[$s]+="${contents[$((s + i))]}" + done + ;; + esac +} + +# The reduce function to collect all includes +# shellcheck disable=SC2031 +bpt.__reduce_collect_includes() { + # For all `str` token, allow only the path via the INCLUDE rule. + case "${rule[0]}" in + STR) ;; # Allow the propagation of str + STMT) [[ "${rule[1]}" != STR ]] || contents[$s]='' ;; + VAR) contents[$s]='' ;; + INCLUDE) + contents[$s]="${contents[$((s + 3))]}"$'\n' + contents[$s]+="$(__recursive_process "${contents[$((s + 3))]}")" + ;; + *) # Prevent the propagation of all other non-terminals + [[ "${#rule[@]}" -ne 1 ]] || { contents[$s]='' && return; } + [[ "${rule[1]^^}" == "${rule[1]}" ]] || contents[$s]='' + local i=1 + for (( ; i < ${#rule[@]}; ++i)); do + [[ "${rule[i + 1],,}" == "${rule[i + 1]}" ]] || + contents[$s]+="${contents[$((s + i))]}" + done + ;; + esac +} + +# The reduce function to generate the template +# shellcheck disable=SC2031 +bpt.__reduce_generate() { + case "${rule[0]}" in + # Note: Since `contents[$s]` is exactly the first RHS, the + # `${contents[$s]}="${contents[$s]}"` assignment is unnecessary here. + STR | UOP | BOP) ;; + # Tag location for BUILTIN error reporting + ID) contents[$s]+=":$num_lines:$num_bytes" ;; + VAR) + case "${rule[3]}" in + rd) contents[$s]="\${${contents[$((s + 1))]%:*:*}}" ;; + or) contents[$s]="\${${contents[$((s + 1))]%:*:*}:-\$(e " ;;& + and) contents[$s]="\${${contents[$((s + 1))]%:*:*}:+\$(e " ;;& + *) case "${rule[4]}" in + VAR) contents[$s]+="\"${contents[$((s + 3))]}\")}" ;; + STR) contents[$s]+="${contents[$((s + 3))]@Q})}" ;; + esac ;; + esac + ;; + ARGS) + # Strip the tag from STMT + local stmt_type='' stmt= + case "${#rule[@]}" in + 2) + stmt_type="${contents[$s]%%:*}" + stmt="${contents[$s]#*:}" + contents[$s]='' + ;; + 3) + stmt_type="${contents[$((s + 1))]%%:*}" + stmt="${contents[$((s + 1))]#*:}" + ;; + esac + + # Note: `${stmt@Q}` is faster than `printf '%q' ${stmt}` + case "$stmt_type" in + STR) contents[$s]+=" ${stmt@Q} " ;; + VAR | BUILTIN) contents[$s]+=" $stmt " ;; + INCLUDE | FORIN | IF) contents[$s]+=" \"\$($stmt)\" " ;; + esac + ;; + BUILTIN) + # Filter allowed builtints + local builtin_name=${contents[$((s + 1))]%:*:*} + case "$builtin_name" in + len | seq) contents[$s]="\$($builtin_name ${contents[$((s + 3))]})" ;; + quote) contents[$s]="\"\$(e ${contents[$((s + 3))]})\"" ;; + *) # Extract and compute correct error location from ID + local line_col="${contents[$((s + 1))]#"$builtin_name"}" + local err_line=${line_col%:*} && err_line="${err_line:1}" + local err_byte=$((${line_col##*:} - ${#builtin_name})) + $error_fn "$err_line" "$err_byte" \ + "Error Unrecognized builtin function $builtin_name" >&2 + exit 1 + ;; + esac + ;; + INCLUDE) contents[$s]="$(__recursive_process "${contents[$((s + 3))]}")" ;; + FORIN) contents[$s]="for ${contents[$((s + 2))]%:*:*} in ${contents[$((s + 4))]}; do ${contents[$((s + 6))]} done" ;; + BOOL) + case "${#rule[@]}" in + 2) contents[$s]="\"\$(e ${contents[$s]})\"" ;; + 4) contents[$s]+=" ${contents[$((s + 1))]} \"\$(e ${contents[$((s + 2))]})\"" ;; + esac + ;; + BOOLA) + case "${#rule[@]}" in + 4) case "${rule[1]}" in + BOOLA) contents[$s]="${contents[$s]} && ${contents[$((s + 2))]}" ;; + lp) contents[$s]="( ${contents[$((s + 1))]} )" ;; + esac ;; + 5) contents[$s]="${contents[$s]} && ${contents[$((s + 2))]} ${contents[$((s + 3))]}" ;; + 6) contents[$s]="${contents[$s]} && ( ${contents[$((s + 3))]} )" ;; + 7) contents[$s]="${contents[$s]} && ${contents[$((s + 2))]} ( ${contents[$((s + 4))]} )" ;; + esac + ;; + BOOLO) + case "${#rule[@]}" in + 4) contents[$s]="${contents[$s]} || ${contents[$((s + 2))]}" ;; + 5) contents[$s]="${contents[$s]} || ${contents[$((s + 2))]} ${contents[$((s + 3))]}" ;; + esac + ;; + BOOLS) [[ ${#rule[@]} -eq 2 ]] || contents[$s]="${contents[$s]} ${contents[$((s + 1))]}" ;; + ELSE) + case "${#rule[@]}" in + 1) contents[$s]='' ;; + *) contents[$s]="else ${contents[$((s + 2))]}" ;; + esac + ;; + ELIF) + case "${#rule[@]}" in + 1) contents[$s]='' ;; + *) contents[$s]="${contents[$s]} elif [[ ${contents[$((s + 2))]} ]]; then ${contents[$((s + 4))]}" ;; + esac + ;; + IF) contents[$s]="if [[ ${contents[$((s + 2))]} ]]; then ${contents[$((s + 4))]}${contents[$((s + 5))]}${contents[$((s + 6))]} fi" ;; + STMT) + # Tag the sub-type to the reduce result + # (Need to strip the tag wherever STMT is used) + contents[$s]="${rule[1]}:${contents[$s]}" + ;; + DOC) # Similar to ARGS but produces commands instead of strings + # Return when document is empty + [[ "${#rule[@]}" -ne 1 ]] || { contents[$s]='' && return; } + + # Strip the tag from STMT + local stmt_type="${contents[$((s + 1))]%%:*}" + local stmt="${contents[$((s + 1))]#*:}" + + # Reduce the document + case "$stmt_type" in + STR) contents[$s]+="{ e ${stmt@Q}; };" ;; + BUILTIN | VAR) contents[$s]+="{ e \"$stmt\"; };" ;; + INCLUDE) contents[$s]+="$stmt" ;; + FORIN | IF) contents[$s]+="{ $stmt; };" ;; + esac + ;; + *) echo "Internal error: Rule ${rule[*]} not recognized" >&2 ;; + esac +} + +# Process the template +# +# $1: Left deilmiter +# $2: Right delimiter +# $3: The reduce function hook to pass to the parser. +# Defaults to bpt.__reduce_collect_vars +# $4: File to process +# $5: (optional) If set, enable debug. +# +# Input: Template from stdin +# +# Grammar: +# DOC -> DOC STMT +# | . +# STMT -> IF | FORIN | INCLUDE | BUILTIN | VAR | STR . +# IF -> ld if BOOLS cl DOC ELIF ELSE rd . +# ELIF -> ELIF elif BOOLS cl DOC +# | . +# ELSE -> else cl DOC +# | . +# BOOLS -> BOOLO +# | UOP BOOLS . +# BOOLO -> BOOLO or BOOLA +# | BOOLO or UOP BOOLA +# | BOOLA . +# BOOLA -> BOOLA and BOOL +# | BOOLA and UOP BOOL +# | BOOLA and lp BOOLS rp +# | BOOLA and UOP lp BOOLS rp +# | lp BOOLS rp +# | BOOL . +# BOOL -> ARGS BOP ARGS +# | ARGS . +# FORIN -> ld for ID in ARGS cl DOC rd . +# INCLUDE -> ld include cl STR rd . +# BUILTIN -> ld ID cl ARGS rd . +# ARGS -> ARGS STMT +# | STMT . +# VAR -> ld ID rd +# | ld ID or VAR rd +# | ld ID or STR rd +# | ld ID and VAR rd +# | ld ID and STR rd . +# BOP -> ne | eq | gt | lt | ge | le | strgt | strlt | streq | strne . +# UOP -> ex . +# ID -> id . +# STR -> str . +# +# Note1: the combination of BOOLO and BOOLA is equivalent to the expression +# grammar `BOOLS -> BOOL or BOOL | BOOL and BOOL | lp BOOL rp | BOOL .` +# with left associativity for `and` and `or` meanwhile `and` having higher +# precidence than `or`. (Solves shift-reduce conflict with subclassing). +# See: https://ece.uwaterloo.ca/~vganesh/TEACHING/W2014/lectures/lecture08.pdf +# +# Note2: the `ID -> id` and `STR -> str` rules are not redundant. +# They are for better controls when hooking the reduce function. +bpt.process() ( + local -r ld="$1" rd="$2" file="$4" debug="$5" + local -r reduce_fn="${3:-bpt.__reduce_generate}" + local -a file_stack=("${file_stack[@]}") + + [[ -f $file ]] || { + echo "Error: file '$file' does not exist" >&2 + return 1 + } + file_stack+=("$file") + + # Curry this function so that it can be called by the reducer recursively + __recursive_process() { + local file + # Detect recursive includes + for file in "${file_stack[@]}"; do + [[ $file -ef $1 ]] && { + printf "Error: cyclic include detected:\n" + printf ' In: %s\n' "${file_stack[0]}" + printf ' --> %s\n' "${file_stack[@]:1}" + printf ' --> %s\n' "${file}" + return 1 + } >&2 + done + bpt.process "$ld" "$rd" "$reduce_fn" "$1" "$debug" + } + + # Pretty-print parse errors + __error_handler() { + echo "Error: File '$file' Line $(($1 + 1)) Column $(($2 + 1))" + echo "$3" + echo + # Pretty-print the error location + local -a line=() + mapfile -t -s "$1" -n 1 line <"$file" + echo "${line[0]}" + printf "%$2s^--- \033[1mHERE\033[0m\n" + } >&2 + + # Prase with the provided reduce function + bpt.__lr_parse BPT_PARSE_TABLE "$reduce_fn" __error_handler "$debug" \ + < <(bpt.scan "$ld" "$rd" __error_handler <"$file") +) + +# $1: Left deilmiter +# $2: Right delimiter +# $3: File to process +# $4: (optional) If set, enable debug. +# shellcheck disable=SC2207 +bpt.fingerprint() { + local -r ld="$1" rd="$2" file="$3" debug="$4" + + # Collect vars and includes + local -a vars=() incs=() + mapfile -t vars < <(bpt.__dedup "$(bpt.process "$ld" "$rd" bpt.__reduce_collect_vars "$infile" "$debug")") + mapfile -t incs < <(bpt.__dedup "$(bpt.process "$ld" "$rd" bpt.__reduce_collect_includes "$infile" "$debug")") + local fingerprint='' + local -a md5=() + local util + + case "${BASHTARD_PLATFORM[key]}" in + freebsd) util=md5 ;; + linux-*) util=md5sum ;; + *) + debug "bpt/fingerprint" "Falling back to md5sum for hashing" + util=md5sum + ;; + esac + + # Hash this script (the generator) + md5=($("$util" "${BASH_SOURCE[0]}")) && fingerprint+="M:${md5[0]}" + + # Hash the file itself + md5=($("$util" "$file")) && fingerprint+=":S:${md5[0]}" + + # Digest the includes + for inc in "${incs[@]}"; do + md5=($("$util" "$inc")) && fingerprint+=":I:${md5[0]}" + done + # Digest and check for missing vars + for var in "${vars[@]}"; do + if [[ -z ${!var+.} ]]; then + echo "Error: variable '$var' is required but not set" >&2 + return 1 + fi + md5=($(echo -n "${var}${!var}" | "$util")) && fingerprint+=":V:${md5[0]}" + done + + # Digest the digests + [[ $debug ]] && echo "[DBG] Raw fingerprint: $fingerprint" + md5=($(echo -n "${fingerprint}" | "$util")) && fingerprint="${md5[0]}" + echo "$fingerprint" +} + +bpt.print_help() { + echo -e "\033[1mbpt - A command-line tool for processing simple templates\033[0m" + echo + echo -e "\033[1mSYNOPSIS\033[0m" + echo " bpt [-l ] [-r ] [-d] []" + echo + echo -e "\033[1mCOMMANDS\033[0m" + echo " scan, s:" + echo " Call the scanner (lexer)." + echo + echo " generate, g:" + echo " Generate a shell script based on the input file. Output is sent to stdout." + echo + echo " generate-eval, ge:" + echo " Same as generate, but the output is evaluated. Output is sent to stdout." + echo + echo " collect-vars, cv:" + echo " Collect variable used in the input file recursively and output them to stdout." + echo + echo " collect-includes, ci:" + echo " Collect all files included in the input file recursively and output them to stdout." + echo + echo " fingerprint, f:" + echo " Generate a unique identifier based on all factors affecting the evaluation output." + echo + echo " -h, --help:" + echo " Print this help." + echo + echo " -v, --version:" + echo " Print version number." + echo + echo -e "\033[1mOPTIONS\033[0m" + echo " -l , --left-delimiter :" + echo " Set the left delimiter to use for placeholders (default \`{{\`)." + echo + echo " -r , --right-delimiter :" + echo " Set the right delimiter to use for placeholders (default \`}}\`)." + echo + echo " -d, --debug:" + echo " Enable debug mode." + echo + echo -e "\033[1mARGUMENTS\033[0m" + echo " bpt takes an optional input file path as its argument. If no input file is specified, bpt will read from stdin." + echo + echo -e "\033[1mEXAMPLES\033[0m" + echo " Generate script from a single input file using default delimiters:" + echo " bpt g input.tpl > output.sh" + echo + echo " Render the input file:" + echo " var1=VAR1 var2=VAR2 ... bpt ge input.tpl" + echo + echo " Collect variable names and values from an input file:" + echo " bpt cv input.tpl" + echo + echo " Collect include file paths from an input file:" + echo " bpt ci input.tpl" + echo + echo " Generate a fingerprint for an input" + echo " var1=VAR1 var2=VAR2 ... bpt f input.tpl" + echo + echo " Using custom delimiters:" + echo " bpt -l \"<<\" -r \">>\" g input.tpl > output.sh" + echo + echo -e "\033[1mTEMPLATE GRAMMAR EXAMPLES\033[0m" + echo " Variable replacements" + echo ' {{ var }}' + echo ' {{ var or "abc" }}' + echo ' {{ var or {{var2}} }}' + echo + echo " Branching" + echo ' {{ if {{x}}: {{var1}} else : {{var2}} }}' + echo ' {{ if {{x}} -gt "5": {{var1}} elif: {{var2}} else: {{var3}} }}' + echo ' {{ if ({{var1}} > "abc" and {{var2}} < "def") or {{var3}} == "hello" : {{ include : "input2.tpl" }} }}' + echo + echo " Available operators are: " + echo " compare numbers: -ne, -eq, -gt, -lt, -ge, -le " + echo " compare strings: >, <, ==, !=" + echo " logical operators: and, or, !" + echo " grouping: ()" + echo + echo " Looping" + echo ' {{ for {{i}} in "a" "b" "c": "abc"{{i}}"def" }}' + echo ' {{ for {{i}} in {{seq: "5"}}: "abc"{{i}}"def" }}' + echo + echo " Include another template" + echo ' {{ include : "input2.tpl" }}' + echo + echo " Builtin functions" + echo ' {{ seq: "5" }}' + echo ' {{ len: "abc" }}' + echo ' {{ quote: {{seq: "1" "2" "5"}} }}' + echo + echo " Note: bpt doesn't distinguish between strings and numbers." + echo " All non-keywords should be treated as strings." + echo " All strings inside {{...}} need to be quoted. e.g. 'abc', \"abc\", '123'." + echo + echo -e "\033[1mCOPYRIGHT\033[0m" + echo " MIT License." + echo " Copyright (c) 2023 Hu Sixu." + echo " https://github.com/husixu1/bpt" +} + +bpt.main() ( + # Clean the environment to avoid builtin overrides + # See https://unix.stackexchange.com/questions/188327 + POSIXLY_CORRECT=1 + \unset -f help read unset + \unset POSIXLY_CORRECT + while \read -r cmd; do + [[ "$cmd" =~ ^([a-z:.\[]+): ]] && \unset -f "${BASH_REMATCH[1]}" + done < <(\help -s "*") + + local ld='{{' rd='}}' + local infile='' + local cmd='' reduce_fn=bpt.__reduce_generate post_process=eval + local debug='' + + # Parse command + case "$1" in + scan | s) cmd=scan ;; + generate | g) + cmd=generate + reduce_fn=bpt.__reduce_generate + post_process='echo' + ;; + generate-eval | ge) + cmd=generate-eval + reduce_fn=bpt.__reduce_generate + post_process='eval' + ;; + collect-vars | cv) + cmd=collect-vars + reduce_fn=bpt.__reduce_collect_vars + post_process=bpt.__dedup + ;; + collect-includes | ci) + cmd=collect-includes + reduce_fn=bpt.__reduce_collect_includes + post_process=bpt.__dedup + ;; + fingerprint | f) + cmd=fingerprint + ;; + -v | --version) echo "$__BPT_VERSION" && exit 0 ;; + -h | --help | '') bpt.print_help && exit 0 ;; + *) + echo "Unrecognized command '$1'" >&2 + bpt.print_help + exit 1 + ;; + esac + shift + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + -l | --left-delimiter) shift && ld="$1" ;; + -r | --right-delimiter) shift && rd="$1" ;; + -d | --debug) debug=1 ;; + -h | --help) + bpt.print_help + exit 0 + ;; + *) + [[ -z $infile ]] || { + echo "Error: Option '$1' not recognized." >&2 + bpt.print_help + return 1 + } + infile="$1" + ;; + esac + shift + done + + # If file not provided, read into a temporary file and use that file. + [[ -n $infile ]] || { + infile="$(mktemp)" || { echo "Error: mktemp failed." >&2 && exit 1; } + bpt.__add_exit_trap "rm -f \"${infile:?}\"" + cat >"${infile:?}" + } + + # Global constants for pretty-printing + local -rA BPT_PP_TOKEN_TABLE=( + [ld]="$ld" [rd]="$rd" + [eq]='-eq' [ne]='-ne' [gt]='-gt' [lt]='-lt' [ge]='-ge' [le]='-le' + [streq]='==' [strne]='!=' [strgt]='>' [strlt]='<' + [cl]=':' [ex]='!' [lp]='(' [rp]=')') + + # shellcheck disable=SC2034 + # >>> BPT_PARSE_TABLE_S >>> + local -rA BPT_PARSE_TABLE=( # {{{ + ["0,ld"]="r DOC" ["0,str"]="r DOC" ["0,$"]="r DOC" ["0,DOC"]="1" ["1,ld"]="s 9" + ["1,str"]="s 10" ["1,$"]="a" ["1,STMT"]="2" ["1,IF"]="3" ["1,FORIN"]="4" + ["1,INCLUDE"]="5" ["1,BUILTIN"]="6" ["1,VAR"]="7" ["1,STR"]="8" + ["2,ld"]="r DOC DOC STMT" ["2,rd"]="r DOC DOC STMT" ["2,elif"]="r DOC DOC STMT" + ["2,else"]="r DOC DOC STMT" ["2,str"]="r DOC DOC STMT" ["2,$"]="r DOC DOC STMT" + ["3,ld"]="r STMT IF" ["3,cl"]="r STMT IF" ["3,rd"]="r STMT IF" + ["3,elif"]="r STMT IF" ["3,else"]="r STMT IF" ["3,or"]="r STMT IF" + ["3,and"]="r STMT IF" ["3,rp"]="r STMT IF" ["3,ne"]="r STMT IF" + ["3,eq"]="r STMT IF" ["3,gt"]="r STMT IF" ["3,lt"]="r STMT IF" + ["3,ge"]="r STMT IF" ["3,le"]="r STMT IF" ["3,strgt"]="r STMT IF" + ["3,strlt"]="r STMT IF" ["3,streq"]="r STMT IF" ["3,strne"]="r STMT IF" + ["3,str"]="r STMT IF" ["3,$"]="r STMT IF" ["4,ld"]="r STMT FORIN" + ["4,cl"]="r STMT FORIN" ["4,rd"]="r STMT FORIN" ["4,elif"]="r STMT FORIN" + ["4,else"]="r STMT FORIN" ["4,or"]="r STMT FORIN" ["4,and"]="r STMT FORIN" + ["4,rp"]="r STMT FORIN" ["4,ne"]="r STMT FORIN" ["4,eq"]="r STMT FORIN" + ["4,gt"]="r STMT FORIN" ["4,lt"]="r STMT FORIN" ["4,ge"]="r STMT FORIN" + ["4,le"]="r STMT FORIN" ["4,strgt"]="r STMT FORIN" ["4,strlt"]="r STMT FORIN" + ["4,streq"]="r STMT FORIN" ["4,strne"]="r STMT FORIN" ["4,str"]="r STMT FORIN" + ["4,$"]="r STMT FORIN" ["5,ld"]="r STMT INCLUDE" ["5,cl"]="r STMT INCLUDE" + ["5,rd"]="r STMT INCLUDE" ["5,elif"]="r STMT INCLUDE" + ["5,else"]="r STMT INCLUDE" ["5,or"]="r STMT INCLUDE" ["5,and"]="r STMT INCLUDE" + ["5,rp"]="r STMT INCLUDE" ["5,ne"]="r STMT INCLUDE" ["5,eq"]="r STMT INCLUDE" + ["5,gt"]="r STMT INCLUDE" ["5,lt"]="r STMT INCLUDE" ["5,ge"]="r STMT INCLUDE" + ["5,le"]="r STMT INCLUDE" ["5,strgt"]="r STMT INCLUDE" + ["5,strlt"]="r STMT INCLUDE" ["5,streq"]="r STMT INCLUDE" + ["5,strne"]="r STMT INCLUDE" ["5,str"]="r STMT INCLUDE" ["5,$"]="r STMT INCLUDE" + ["6,ld"]="r STMT BUILTIN" ["6,cl"]="r STMT BUILTIN" ["6,rd"]="r STMT BUILTIN" + ["6,elif"]="r STMT BUILTIN" ["6,else"]="r STMT BUILTIN" + ["6,or"]="r STMT BUILTIN" ["6,and"]="r STMT BUILTIN" ["6,rp"]="r STMT BUILTIN" + ["6,ne"]="r STMT BUILTIN" ["6,eq"]="r STMT BUILTIN" ["6,gt"]="r STMT BUILTIN" + ["6,lt"]="r STMT BUILTIN" ["6,ge"]="r STMT BUILTIN" ["6,le"]="r STMT BUILTIN" + ["6,strgt"]="r STMT BUILTIN" ["6,strlt"]="r STMT BUILTIN" + ["6,streq"]="r STMT BUILTIN" ["6,strne"]="r STMT BUILTIN" + ["6,str"]="r STMT BUILTIN" ["6,$"]="r STMT BUILTIN" ["7,ld"]="r STMT VAR" + ["7,cl"]="r STMT VAR" ["7,rd"]="r STMT VAR" ["7,elif"]="r STMT VAR" + ["7,else"]="r STMT VAR" ["7,or"]="r STMT VAR" ["7,and"]="r STMT VAR" + ["7,rp"]="r STMT VAR" ["7,ne"]="r STMT VAR" ["7,eq"]="r STMT VAR" + ["7,gt"]="r STMT VAR" ["7,lt"]="r STMT VAR" ["7,ge"]="r STMT VAR" + ["7,le"]="r STMT VAR" ["7,strgt"]="r STMT VAR" ["7,strlt"]="r STMT VAR" + ["7,streq"]="r STMT VAR" ["7,strne"]="r STMT VAR" ["7,str"]="r STMT VAR" + ["7,$"]="r STMT VAR" ["8,ld"]="r STMT STR" ["8,cl"]="r STMT STR" + ["8,rd"]="r STMT STR" ["8,elif"]="r STMT STR" ["8,else"]="r STMT STR" + ["8,or"]="r STMT STR" ["8,and"]="r STMT STR" ["8,rp"]="r STMT STR" + ["8,ne"]="r STMT STR" ["8,eq"]="r STMT STR" ["8,gt"]="r STMT STR" + ["8,lt"]="r STMT STR" ["8,ge"]="r STMT STR" ["8,le"]="r STMT STR" + ["8,strgt"]="r STMT STR" ["8,strlt"]="r STMT STR" ["8,streq"]="r STMT STR" + ["8,strne"]="r STMT STR" ["8,str"]="r STMT STR" ["8,$"]="r STMT STR" + ["9,if"]="s 11" ["9,for"]="s 12" ["9,include"]="s 13" ["9,id"]="s 15" + ["9,ID"]="14" ["10,ld"]="r STR str" ["10,cl"]="r STR str" ["10,rd"]="r STR str" + ["10,elif"]="r STR str" ["10,else"]="r STR str" ["10,or"]="r STR str" + ["10,and"]="r STR str" ["10,rp"]="r STR str" ["10,ne"]="r STR str" + ["10,eq"]="r STR str" ["10,gt"]="r STR str" ["10,lt"]="r STR str" + ["10,ge"]="r STR str" ["10,le"]="r STR str" ["10,strgt"]="r STR str" + ["10,strlt"]="r STR str" ["10,streq"]="r STR str" ["10,strne"]="r STR str" + ["10,str"]="r STR str" ["10,$"]="r STR str" ["11,ld"]="s 9" ["11,lp"]="s 21" + ["11,ex"]="s 20" ["11,str"]="s 10" ["11,STMT"]="24" ["11,IF"]="3" + ["11,FORIN"]="4" ["11,INCLUDE"]="5" ["11,BUILTIN"]="6" ["11,VAR"]="7" + ["11,STR"]="8" ["11,BOOLS"]="16" ["11,BOOLO"]="17" ["11,UOP"]="18" + ["11,BOOLA"]="19" ["11,BOOL"]="22" ["11,ARGS"]="23" ["12,id"]="s 15" + ["12,ID"]="25" ["13,cl"]="s 26" ["14,cl"]="s 27" ["14,rd"]="s 28" + ["14,or"]="s 29" ["14,and"]="s 30" ["15,cl"]="r ID id" ["15,rd"]="r ID id" + ["15,or"]="r ID id" ["15,and"]="r ID id" ["15,in"]="r ID id" ["16,cl"]="s 31" + ["17,cl"]="r BOOLS BOOLO" ["17,or"]="s 32" ["17,rp"]="r BOOLS BOOLO" + ["18,ld"]="s 9" ["18,lp"]="s 21" ["18,ex"]="s 20" ["18,str"]="s 10" + ["18,STMT"]="24" ["18,IF"]="3" ["18,FORIN"]="4" ["18,INCLUDE"]="5" + ["18,BUILTIN"]="6" ["18,VAR"]="7" ["18,STR"]="8" ["18,BOOLS"]="33" + ["18,BOOLO"]="17" ["18,UOP"]="18" ["18,BOOLA"]="19" ["18,BOOL"]="22" + ["18,ARGS"]="23" ["19,cl"]="r BOOLO BOOLA" ["19,or"]="r BOOLO BOOLA" + ["19,and"]="s 34" ["19,rp"]="r BOOLO BOOLA" ["20,ld"]="r UOP ex" + ["20,lp"]="r UOP ex" ["20,ex"]="r UOP ex" ["20,str"]="r UOP ex" ["21,ld"]="s 9" + ["21,lp"]="s 21" ["21,ex"]="s 20" ["21,str"]="s 10" ["21,STMT"]="24" + ["21,IF"]="3" ["21,FORIN"]="4" ["21,INCLUDE"]="5" ["21,BUILTIN"]="6" + ["21,VAR"]="7" ["21,STR"]="8" ["21,BOOLS"]="35" ["21,BOOLO"]="17" + ["21,UOP"]="18" ["21,BOOLA"]="19" ["21,BOOL"]="22" ["21,ARGS"]="23" + ["22,cl"]="r BOOLA BOOL" ["22,or"]="r BOOLA BOOL" ["22,and"]="r BOOLA BOOL" + ["22,rp"]="r BOOLA BOOL" ["23,ld"]="s 9" ["23,cl"]="r BOOL ARGS" + ["23,or"]="r BOOL ARGS" ["23,and"]="r BOOL ARGS" ["23,rp"]="r BOOL ARGS" + ["23,ne"]="s 38" ["23,eq"]="s 39" ["23,gt"]="s 40" ["23,lt"]="s 41" + ["23,ge"]="s 42" ["23,le"]="s 43" ["23,strgt"]="s 44" ["23,strlt"]="s 45" + ["23,streq"]="s 46" ["23,strne"]="s 47" ["23,str"]="s 10" ["23,STMT"]="37" + ["23,IF"]="3" ["23,FORIN"]="4" ["23,INCLUDE"]="5" ["23,BUILTIN"]="6" + ["23,VAR"]="7" ["23,STR"]="8" ["23,BOP"]="36" ["24,ld"]="r ARGS STMT" + ["24,cl"]="r ARGS STMT" ["24,rd"]="r ARGS STMT" ["24,or"]="r ARGS STMT" + ["24,and"]="r ARGS STMT" ["24,rp"]="r ARGS STMT" ["24,ne"]="r ARGS STMT" + ["24,eq"]="r ARGS STMT" ["24,gt"]="r ARGS STMT" ["24,lt"]="r ARGS STMT" + ["24,ge"]="r ARGS STMT" ["24,le"]="r ARGS STMT" ["24,strgt"]="r ARGS STMT" + ["24,strlt"]="r ARGS STMT" ["24,streq"]="r ARGS STMT" ["24,strne"]="r ARGS STMT" + ["24,str"]="r ARGS STMT" ["25,in"]="s 48" ["26,str"]="s 10" ["26,STR"]="49" + ["27,ld"]="s 9" ["27,str"]="s 10" ["27,STMT"]="24" ["27,IF"]="3" + ["27,FORIN"]="4" ["27,INCLUDE"]="5" ["27,BUILTIN"]="6" ["27,VAR"]="7" + ["27,STR"]="8" ["27,ARGS"]="50" ["28,ld"]="r VAR ld ID rd" + ["28,cl"]="r VAR ld ID rd" ["28,rd"]="r VAR ld ID rd" + ["28,elif"]="r VAR ld ID rd" ["28,else"]="r VAR ld ID rd" + ["28,or"]="r VAR ld ID rd" ["28,and"]="r VAR ld ID rd" + ["28,rp"]="r VAR ld ID rd" ["28,ne"]="r VAR ld ID rd" ["28,eq"]="r VAR ld ID rd" + ["28,gt"]="r VAR ld ID rd" ["28,lt"]="r VAR ld ID rd" ["28,ge"]="r VAR ld ID rd" + ["28,le"]="r VAR ld ID rd" ["28,strgt"]="r VAR ld ID rd" + ["28,strlt"]="r VAR ld ID rd" ["28,streq"]="r VAR ld ID rd" + ["28,strne"]="r VAR ld ID rd" ["28,str"]="r VAR ld ID rd" + ["28,$"]="r VAR ld ID rd" ["29,ld"]="s 53" ["29,str"]="s 10" ["29,VAR"]="51" + ["29,STR"]="52" ["30,ld"]="s 53" ["30,str"]="s 10" ["30,VAR"]="54" + ["30,STR"]="55" ["31,ld"]="r DOC" ["31,rd"]="r DOC" ["31,elif"]="r DOC" + ["31,else"]="r DOC" ["31,str"]="r DOC" ["31,DOC"]="56" ["32,ld"]="s 9" + ["32,lp"]="s 21" ["32,ex"]="s 20" ["32,str"]="s 10" ["32,STMT"]="24" + ["32,IF"]="3" ["32,FORIN"]="4" ["32,INCLUDE"]="5" ["32,BUILTIN"]="6" + ["32,VAR"]="7" ["32,STR"]="8" ["32,UOP"]="58" ["32,BOOLA"]="57" ["32,BOOL"]="22" + ["32,ARGS"]="23" ["33,cl"]="r BOOLS UOP BOOLS" ["33,rp"]="r BOOLS UOP BOOLS" + ["34,ld"]="s 9" ["34,lp"]="s 61" ["34,ex"]="s 20" ["34,str"]="s 10" + ["34,STMT"]="24" ["34,IF"]="3" ["34,FORIN"]="4" ["34,INCLUDE"]="5" + ["34,BUILTIN"]="6" ["34,VAR"]="7" ["34,STR"]="8" ["34,UOP"]="60" + ["34,BOOL"]="59" ["34,ARGS"]="23" ["35,rp"]="s 62" ["36,ld"]="s 9" + ["36,str"]="s 10" ["36,STMT"]="24" ["36,IF"]="3" ["36,FORIN"]="4" + ["36,INCLUDE"]="5" ["36,BUILTIN"]="6" ["36,VAR"]="7" ["36,STR"]="8" + ["36,ARGS"]="63" ["37,ld"]="r ARGS ARGS STMT" ["37,cl"]="r ARGS ARGS STMT" + ["37,rd"]="r ARGS ARGS STMT" ["37,or"]="r ARGS ARGS STMT" + ["37,and"]="r ARGS ARGS STMT" ["37,rp"]="r ARGS ARGS STMT" + ["37,ne"]="r ARGS ARGS STMT" ["37,eq"]="r ARGS ARGS STMT" + ["37,gt"]="r ARGS ARGS STMT" ["37,lt"]="r ARGS ARGS STMT" + ["37,ge"]="r ARGS ARGS STMT" ["37,le"]="r ARGS ARGS STMT" + ["37,strgt"]="r ARGS ARGS STMT" ["37,strlt"]="r ARGS ARGS STMT" + ["37,streq"]="r ARGS ARGS STMT" ["37,strne"]="r ARGS ARGS STMT" + ["37,str"]="r ARGS ARGS STMT" ["38,ld"]="r BOP ne" ["38,str"]="r BOP ne" + ["39,ld"]="r BOP eq" ["39,str"]="r BOP eq" ["40,ld"]="r BOP gt" + ["40,str"]="r BOP gt" ["41,ld"]="r BOP lt" ["41,str"]="r BOP lt" + ["42,ld"]="r BOP ge" ["42,str"]="r BOP ge" ["43,ld"]="r BOP le" + ["43,str"]="r BOP le" ["44,ld"]="r BOP strgt" ["44,str"]="r BOP strgt" + ["45,ld"]="r BOP strlt" ["45,str"]="r BOP strlt" ["46,ld"]="r BOP streq" + ["46,str"]="r BOP streq" ["47,ld"]="r BOP strne" ["47,str"]="r BOP strne" + ["48,ld"]="s 9" ["48,str"]="s 10" ["48,STMT"]="24" ["48,IF"]="3" + ["48,FORIN"]="4" ["48,INCLUDE"]="5" ["48,BUILTIN"]="6" ["48,VAR"]="7" + ["48,STR"]="8" ["48,ARGS"]="64" ["49,rd"]="s 65" ["50,ld"]="s 9" + ["50,rd"]="s 66" ["50,str"]="s 10" ["50,STMT"]="37" ["50,IF"]="3" + ["50,FORIN"]="4" ["50,INCLUDE"]="5" ["50,BUILTIN"]="6" ["50,VAR"]="7" + ["50,STR"]="8" ["51,rd"]="s 67" ["52,rd"]="s 68" ["53,id"]="s 15" ["53,ID"]="69" + ["54,rd"]="s 70" ["55,rd"]="s 71" ["56,ld"]="s 9" ["56,rd"]="r ELIF" + ["56,elif"]="r ELIF" ["56,else"]="r ELIF" ["56,str"]="s 10" ["56,STMT"]="2" + ["56,IF"]="3" ["56,FORIN"]="4" ["56,INCLUDE"]="5" ["56,BUILTIN"]="6" + ["56,VAR"]="7" ["56,STR"]="8" ["56,ELIF"]="72" + ["57,cl"]="r BOOLO BOOLO or BOOLA" ["57,or"]="r BOOLO BOOLO or BOOLA" + ["57,and"]="s 34" ["57,rp"]="r BOOLO BOOLO or BOOLA" ["58,ld"]="s 9" + ["58,lp"]="s 21" ["58,str"]="s 10" ["58,STMT"]="24" ["58,IF"]="3" + ["58,FORIN"]="4" ["58,INCLUDE"]="5" ["58,BUILTIN"]="6" ["58,VAR"]="7" + ["58,STR"]="8" ["58,BOOLA"]="73" ["58,BOOL"]="22" ["58,ARGS"]="23" + ["59,cl"]="r BOOLA BOOLA and BOOL" ["59,or"]="r BOOLA BOOLA and BOOL" + ["59,and"]="r BOOLA BOOLA and BOOL" ["59,rp"]="r BOOLA BOOLA and BOOL" + ["60,ld"]="s 9" ["60,lp"]="s 75" ["60,str"]="s 10" ["60,STMT"]="24" + ["60,IF"]="3" ["60,FORIN"]="4" ["60,INCLUDE"]="5" ["60,BUILTIN"]="6" + ["60,VAR"]="7" ["60,STR"]="8" ["60,BOOL"]="74" ["60,ARGS"]="23" ["61,ld"]="s 9" + ["61,lp"]="s 21" ["61,ex"]="s 20" ["61,str"]="s 10" ["61,STMT"]="24" + ["61,IF"]="3" ["61,FORIN"]="4" ["61,INCLUDE"]="5" ["61,BUILTIN"]="6" + ["61,VAR"]="7" ["61,STR"]="8" ["61,BOOLS"]="76" ["61,BOOLO"]="17" + ["61,UOP"]="18" ["61,BOOLA"]="19" ["61,BOOL"]="22" ["61,ARGS"]="23" + ["62,cl"]="r BOOLA lp BOOLS rp" ["62,or"]="r BOOLA lp BOOLS rp" + ["62,and"]="r BOOLA lp BOOLS rp" ["62,rp"]="r BOOLA lp BOOLS rp" ["63,ld"]="s 9" + ["63,cl"]="r BOOL ARGS BOP ARGS" ["63,or"]="r BOOL ARGS BOP ARGS" + ["63,and"]="r BOOL ARGS BOP ARGS" ["63,rp"]="r BOOL ARGS BOP ARGS" + ["63,str"]="s 10" ["63,STMT"]="37" ["63,IF"]="3" ["63,FORIN"]="4" + ["63,INCLUDE"]="5" ["63,BUILTIN"]="6" ["63,VAR"]="7" ["63,STR"]="8" + ["64,ld"]="s 9" ["64,cl"]="s 77" ["64,str"]="s 10" ["64,STMT"]="37" + ["64,IF"]="3" ["64,FORIN"]="4" ["64,INCLUDE"]="5" ["64,BUILTIN"]="6" + ["64,VAR"]="7" ["64,STR"]="8" ["65,ld"]="r INCLUDE ld include cl STR rd" + ["65,cl"]="r INCLUDE ld include cl STR rd" + ["65,rd"]="r INCLUDE ld include cl STR rd" + ["65,elif"]="r INCLUDE ld include cl STR rd" + ["65,else"]="r INCLUDE ld include cl STR rd" + ["65,or"]="r INCLUDE ld include cl STR rd" + ["65,and"]="r INCLUDE ld include cl STR rd" + ["65,rp"]="r INCLUDE ld include cl STR rd" + ["65,ne"]="r INCLUDE ld include cl STR rd" + ["65,eq"]="r INCLUDE ld include cl STR rd" + ["65,gt"]="r INCLUDE ld include cl STR rd" + ["65,lt"]="r INCLUDE ld include cl STR rd" + ["65,ge"]="r INCLUDE ld include cl STR rd" + ["65,le"]="r INCLUDE ld include cl STR rd" + ["65,strgt"]="r INCLUDE ld include cl STR rd" + ["65,strlt"]="r INCLUDE ld include cl STR rd" + ["65,streq"]="r INCLUDE ld include cl STR rd" + ["65,strne"]="r INCLUDE ld include cl STR rd" + ["65,str"]="r INCLUDE ld include cl STR rd" + ["65,$"]="r INCLUDE ld include cl STR rd" ["66,ld"]="r BUILTIN ld ID cl ARGS rd" + ["66,cl"]="r BUILTIN ld ID cl ARGS rd" ["66,rd"]="r BUILTIN ld ID cl ARGS rd" + ["66,elif"]="r BUILTIN ld ID cl ARGS rd" + ["66,else"]="r BUILTIN ld ID cl ARGS rd" ["66,or"]="r BUILTIN ld ID cl ARGS rd" + ["66,and"]="r BUILTIN ld ID cl ARGS rd" ["66,rp"]="r BUILTIN ld ID cl ARGS rd" + ["66,ne"]="r BUILTIN ld ID cl ARGS rd" ["66,eq"]="r BUILTIN ld ID cl ARGS rd" + ["66,gt"]="r BUILTIN ld ID cl ARGS rd" ["66,lt"]="r BUILTIN ld ID cl ARGS rd" + ["66,ge"]="r BUILTIN ld ID cl ARGS rd" ["66,le"]="r BUILTIN ld ID cl ARGS rd" + ["66,strgt"]="r BUILTIN ld ID cl ARGS rd" + ["66,strlt"]="r BUILTIN ld ID cl ARGS rd" + ["66,streq"]="r BUILTIN ld ID cl ARGS rd" + ["66,strne"]="r BUILTIN ld ID cl ARGS rd" + ["66,str"]="r BUILTIN ld ID cl ARGS rd" ["66,$"]="r BUILTIN ld ID cl ARGS rd" + ["67,ld"]="r VAR ld ID or VAR rd" ["67,cl"]="r VAR ld ID or VAR rd" + ["67,rd"]="r VAR ld ID or VAR rd" ["67,elif"]="r VAR ld ID or VAR rd" + ["67,else"]="r VAR ld ID or VAR rd" ["67,or"]="r VAR ld ID or VAR rd" + ["67,and"]="r VAR ld ID or VAR rd" ["67,rp"]="r VAR ld ID or VAR rd" + ["67,ne"]="r VAR ld ID or VAR rd" ["67,eq"]="r VAR ld ID or VAR rd" + ["67,gt"]="r VAR ld ID or VAR rd" ["67,lt"]="r VAR ld ID or VAR rd" + ["67,ge"]="r VAR ld ID or VAR rd" ["67,le"]="r VAR ld ID or VAR rd" + ["67,strgt"]="r VAR ld ID or VAR rd" ["67,strlt"]="r VAR ld ID or VAR rd" + ["67,streq"]="r VAR ld ID or VAR rd" ["67,strne"]="r VAR ld ID or VAR rd" + ["67,str"]="r VAR ld ID or VAR rd" ["67,$"]="r VAR ld ID or VAR rd" + ["68,ld"]="r VAR ld ID or STR rd" ["68,cl"]="r VAR ld ID or STR rd" + ["68,rd"]="r VAR ld ID or STR rd" ["68,elif"]="r VAR ld ID or STR rd" + ["68,else"]="r VAR ld ID or STR rd" ["68,or"]="r VAR ld ID or STR rd" + ["68,and"]="r VAR ld ID or STR rd" ["68,rp"]="r VAR ld ID or STR rd" + ["68,ne"]="r VAR ld ID or STR rd" ["68,eq"]="r VAR ld ID or STR rd" + ["68,gt"]="r VAR ld ID or STR rd" ["68,lt"]="r VAR ld ID or STR rd" + ["68,ge"]="r VAR ld ID or STR rd" ["68,le"]="r VAR ld ID or STR rd" + ["68,strgt"]="r VAR ld ID or STR rd" ["68,strlt"]="r VAR ld ID or STR rd" + ["68,streq"]="r VAR ld ID or STR rd" ["68,strne"]="r VAR ld ID or STR rd" + ["68,str"]="r VAR ld ID or STR rd" ["68,$"]="r VAR ld ID or STR rd" + ["69,rd"]="s 28" ["69,or"]="s 29" ["69,and"]="s 30" + ["70,ld"]="r VAR ld ID and VAR rd" ["70,cl"]="r VAR ld ID and VAR rd" + ["70,rd"]="r VAR ld ID and VAR rd" ["70,elif"]="r VAR ld ID and VAR rd" + ["70,else"]="r VAR ld ID and VAR rd" ["70,or"]="r VAR ld ID and VAR rd" + ["70,and"]="r VAR ld ID and VAR rd" ["70,rp"]="r VAR ld ID and VAR rd" + ["70,ne"]="r VAR ld ID and VAR rd" ["70,eq"]="r VAR ld ID and VAR rd" + ["70,gt"]="r VAR ld ID and VAR rd" ["70,lt"]="r VAR ld ID and VAR rd" + ["70,ge"]="r VAR ld ID and VAR rd" ["70,le"]="r VAR ld ID and VAR rd" + ["70,strgt"]="r VAR ld ID and VAR rd" ["70,strlt"]="r VAR ld ID and VAR rd" + ["70,streq"]="r VAR ld ID and VAR rd" ["70,strne"]="r VAR ld ID and VAR rd" + ["70,str"]="r VAR ld ID and VAR rd" ["70,$"]="r VAR ld ID and VAR rd" + ["71,ld"]="r VAR ld ID and STR rd" ["71,cl"]="r VAR ld ID and STR rd" + ["71,rd"]="r VAR ld ID and STR rd" ["71,elif"]="r VAR ld ID and STR rd" + ["71,else"]="r VAR ld ID and STR rd" ["71,or"]="r VAR ld ID and STR rd" + ["71,and"]="r VAR ld ID and STR rd" ["71,rp"]="r VAR ld ID and STR rd" + ["71,ne"]="r VAR ld ID and STR rd" ["71,eq"]="r VAR ld ID and STR rd" + ["71,gt"]="r VAR ld ID and STR rd" ["71,lt"]="r VAR ld ID and STR rd" + ["71,ge"]="r VAR ld ID and STR rd" ["71,le"]="r VAR ld ID and STR rd" + ["71,strgt"]="r VAR ld ID and STR rd" ["71,strlt"]="r VAR ld ID and STR rd" + ["71,streq"]="r VAR ld ID and STR rd" ["71,strne"]="r VAR ld ID and STR rd" + ["71,str"]="r VAR ld ID and STR rd" ["71,$"]="r VAR ld ID and STR rd" + ["72,rd"]="r ELSE" ["72,elif"]="s 79" ["72,else"]="s 80" ["72,ELSE"]="78" + ["73,cl"]="r BOOLO BOOLO or UOP BOOLA" ["73,or"]="r BOOLO BOOLO or UOP BOOLA" + ["73,and"]="s 34" ["73,rp"]="r BOOLO BOOLO or UOP BOOLA" + ["74,cl"]="r BOOLA BOOLA and UOP BOOL" ["74,or"]="r BOOLA BOOLA and UOP BOOL" + ["74,and"]="r BOOLA BOOLA and UOP BOOL" ["74,rp"]="r BOOLA BOOLA and UOP BOOL" + ["75,ld"]="s 9" ["75,lp"]="s 21" ["75,ex"]="s 20" ["75,str"]="s 10" + ["75,STMT"]="24" ["75,IF"]="3" ["75,FORIN"]="4" ["75,INCLUDE"]="5" + ["75,BUILTIN"]="6" ["75,VAR"]="7" ["75,STR"]="8" ["75,BOOLS"]="81" + ["75,BOOLO"]="17" ["75,UOP"]="18" ["75,BOOLA"]="19" ["75,BOOL"]="22" + ["75,ARGS"]="23" ["76,rp"]="s 82" ["77,ld"]="r DOC" ["77,rd"]="r DOC" + ["77,str"]="r DOC" ["77,DOC"]="83" ["78,rd"]="s 84" ["79,ld"]="s 9" + ["79,lp"]="s 21" ["79,ex"]="s 20" ["79,str"]="s 10" ["79,STMT"]="24" + ["79,IF"]="3" ["79,FORIN"]="4" ["79,INCLUDE"]="5" ["79,BUILTIN"]="6" + ["79,VAR"]="7" ["79,STR"]="8" ["79,BOOLS"]="85" ["79,BOOLO"]="17" + ["79,UOP"]="18" ["79,BOOLA"]="19" ["79,BOOL"]="22" ["79,ARGS"]="23" + ["80,cl"]="s 86" ["81,rp"]="s 87" ["82,cl"]="r BOOLA BOOLA and lp BOOLS rp" + ["82,or"]="r BOOLA BOOLA and lp BOOLS rp" + ["82,and"]="r BOOLA BOOLA and lp BOOLS rp" + ["82,rp"]="r BOOLA BOOLA and lp BOOLS rp" ["83,ld"]="s 9" ["83,rd"]="s 88" + ["83,str"]="s 10" ["83,STMT"]="2" ["83,IF"]="3" ["83,FORIN"]="4" + ["83,INCLUDE"]="5" ["83,BUILTIN"]="6" ["83,VAR"]="7" ["83,STR"]="8" + ["84,ld"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,cl"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,rd"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,elif"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,else"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,or"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,and"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,rp"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,ne"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,eq"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,gt"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,lt"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,ge"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,le"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,strgt"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,strlt"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,streq"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,strne"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,str"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" + ["84,$"]="r IF ld if BOOLS cl DOC ELIF ELSE rd" ["85,cl"]="s 89" + ["86,ld"]="r DOC" ["86,rd"]="r DOC" ["86,str"]="r DOC" ["86,DOC"]="90" + ["87,cl"]="r BOOLA BOOLA and UOP lp BOOLS rp" + ["87,or"]="r BOOLA BOOLA and UOP lp BOOLS rp" + ["87,and"]="r BOOLA BOOLA and UOP lp BOOLS rp" + ["87,rp"]="r BOOLA BOOLA and UOP lp BOOLS rp" + ["88,ld"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,cl"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,rd"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,elif"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,else"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,or"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,and"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,rp"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,ne"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,eq"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,gt"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,lt"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,ge"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,le"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,strgt"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,strlt"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,streq"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,strne"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,str"]="r FORIN ld for ID in ARGS cl DOC rd" + ["88,$"]="r FORIN ld for ID in ARGS cl DOC rd" ["89,ld"]="r DOC" + ["89,rd"]="r DOC" ["89,elif"]="r DOC" ["89,else"]="r DOC" ["89,str"]="r DOC" + ["89,DOC"]="91" ["90,ld"]="s 9" ["90,rd"]="r ELSE else cl DOC" ["90,str"]="s 10" + ["90,STMT"]="2" ["90,IF"]="3" ["90,FORIN"]="4" ["90,INCLUDE"]="5" + ["90,BUILTIN"]="6" ["90,VAR"]="7" ["90,STR"]="8" ["91,ld"]="s 9" + ["91,rd"]="r ELIF ELIF elif BOOLS cl DOC" + ["91,elif"]="r ELIF ELIF elif BOOLS cl DOC" + ["91,else"]="r ELIF ELIF elif BOOLS cl DOC" ["91,str"]="s 10" ["91,STMT"]="2" + ["91,IF"]="3" ["91,FORIN"]="4" ["91,INCLUDE"]="5" ["91,BUILTIN"]="6" + ["91,VAR"]="7" ["91,STR"]="8" + ) # }}} + # <<< BPT_PARSE_TABLE_E <<< + + # Deduplication function for collect-{var,include} + bpt.__dedup() { echo "$1" | sort | uniq; } + + # Append this if reducer is bpt.__reduce_generate + local HEADER='' + [[ $reduce_fn != bpt.__reduce_generate ]] || { + read -r -d '' HEADER <<-'EOF' +#!/bin/bash +e(){ local OIFS="$IFS"; IFS=; echo -n "$*"; IFS="$OIFS"; }; +len(){ echo -n "${#1}"; }; +seq(){ command seq -s ' ' -- "$@"; }; +EOF + } + + # Execute command + case "$cmd" in + scan) bpt.scan "$ld" "$rd" <"$infile" ;; + fingerprint) bpt.fingerprint "$ld" "$rd" "$infile" "$debug" ;; + *) result="$(bpt.process "$ld" "$rd" "$reduce_fn" "$infile" "$debug")" && + $post_process "$HEADER$result" ;; + esac +) + +(return 0 2>/dev/null) || bpt.main "$@" -- cgit v1.1