lib/libexec/translator.sh
#shellcheck shell=sh disable=SC2004,SC2034,SC2119,SC2120,SC2016
# shellcheck source=lib/libexec.sh
. "${SHELLSPEC_LIB:-./lib}/libexec.sh"
use constants trim match_pattern ends_with escape_quote replace_all match includes
load grammar
initialize() {
block_id='' block_id_increased=1 inside_of_example='' inside_of_text=''
lineno=0 block_no=0 example_no=1 skip_id=0 error='' focused='' skipped=''
_block_no=0 _block_no_stack='' mock_no=1 inside_of_mock='' use_dsl_in_mock=''
parameter_count='' parameter_no=0 _parameter_count_stack=''
parameters_need_example=''
}
finalize() {
if [ "$_block_no_stack" ]; then
syntax_error "Unexpected end of file (expecting 'End')"
lineno=
while [ "$_block_no_stack" ]; do block_end; done
fi
if [ ! "$block_id_increased" ]; then
trans after_last_block ""
fi
trans after_block ""
}
read_specfile() {
eval "{ IFS= read -r $1 || [ \"\$$1\" ]; } && $1=\${$1%\"\$CR\"}" && {
lineno=$(($lineno + 1))
}
}
one_line_syntax_check() { :; }
check_filter() {
check_filter="$1"
replace_all check_filter '$' "$DC1"
replace_all check_filter '`' "$DC2"
eval "set -- $check_filter"
if [ $# -gt 0 ]; then
check_filter="$1"
replace_all check_filter "$DC1" '$'
replace_all check_filter "$DC2" '`'
match_pattern "$check_filter" "$SHELLSPEC_EXAMPLE_FILTER" && return 0
shift
fi
[ $# -gt 0 ] || return 1
check_tag_filter "$@"
}
check_tag_filter() {
[ "$SHELLSPEC_TAG_FILTER" ] || return 1
while [ $# -gt 0 ]; do
includes ",$SHELLSPEC_TAG_FILTER," ",$1," && return 0
case $1 in (*:*)
includes ",$SHELLSPEC_TAG_FILTER," ",${1%%:*}," && return 0
esac
shift
done
return 1
}
is_constant_name() {
match "$1" "[!A-Z_]*" && return 1
match "$1" "*[!A-Z0-9_]*" && return 1
return 0
}
is_function_name() {
match "$1" "[!a-zA-Z_]*" && return 1
match "$1" "*[!a-zA-Z0-9_]*" && return 1
return 0
}
is_comment() {
case $1 in (\# | \#\ * | \#$TAB*) return 0; esac
return 1
}
is_embedded_text() {
case $1 in (\#\|*) return 0; esac
return 1
}
if_embedded_text() {
is_embedded_text "$1" || return 1
set -- "$@" "${1#??}"
shift
"$@"
}
increase_block_id() {
[ "$block_id_increased" ] && block_id=$block_id${block_id:+-}0
case $block_id in
*-*) block_id=${block_id%-*}-$((${block_id##*-} + 1)) ;;
* ) block_id=$(($block_id + 1)) ;;
esac
block_id_increased=1
}
decrease_block_id() {
if [ "$block_id_increased" ]; then
block_id_increased=''
else
block_id=${block_id%-*}
fi
}
block_example_group() {
if [ "$inside_of_example" ]; then
syntax_error "Describe/Context cannot be defined inside of Example"
return 0
fi
if ! one_line_syntax_check error "$1"; then
syntax_error "Describe/Context has occurred an error" "$error"
return 0
fi
check_filter "$1" && filter=1
if [ "$block_id_increased" ]; then
trans before_first_block "$block_id"
fi
increase_block_id
_block_no=$(($_block_no + 1))
block_no=$_block_no lineno_begin=$lineno
eval "block_lineno_begin${_block_no}=$lineno"
eval trans block_example_group ${1+'"$@"'}
_block_no_stack="$_block_no_stack $_block_no" filter=''
_parameter_count_stack="$_parameter_count_stack $parameter_no:$parameter_count"
}
block_example() {
if [ "$inside_of_example" ]; then
syntax_error "It/Example/Specify/Todo cannot be defined inside of Example"
return 0
fi
parameters_need_example=''
if ! one_line_syntax_check error "$1"; then
syntax_error "It/Example/Specify/Todo has occurred an error" "$error"
return 0
fi
check_filter "$1" && filter=1
if [ "$block_id_increased" ]; then
trans before_first_block "$block_id"
fi
increase_block_id
_block_no=$(($_block_no + 1))
block_no=$_block_no lineno_begin=$lineno
eval "block_lineno_begin${block_no}=$lineno"
eval trans block_example ${1+'"$@"'}
_block_no_stack="$_block_no_stack $_block_no"
example_no=$(($example_no + ${parameter_count:-1}))
_parameter_count_stack="$_parameter_count_stack $parameter_no:$parameter_count"
filter='' inside_of_example="yes"
}
block_end() {
if [ ! "$_block_no_stack" ]; then
syntax_error "Unexpected 'End'"
return 0
fi
if [ "$parameters_need_example" ]; then
syntax_error "Not found any examples. (Missing 'End' of Parameters?)"
parameters_need_example=''
return 0
fi
if [ ! "$block_id_increased" ]; then
trans after_last_block "${block_id%-*}"
fi
decrease_block_id
block_no=${_block_no_stack##* } lineno_end=$lineno
trans after_block "$block_no"
eval "block_lineno_end${block_no}=$lineno"
eval "lineno_begin=\$block_lineno_begin${block_no}"
if is_in_ranges; then
enabled=1
remove_from_ranges
fi
eval trans block_end ${1+'"$@"'}
enabled=''
_block_no_stack=${_block_no_stack% *}
parameter_count=${_parameter_count_stack##* }
parameter_no=${parameter_count%:*}
parameter_count=${parameter_count#*:}
_parameter_count_stack=${_parameter_count_stack% *}
inside_of_example=""
}
x() {
skipped=1 skip_id=$(($skip_id + 1))
"$@"
skipped=''
}
f() {
focused="focus" filter=1
"$@"
focused='' filter=''
}
todo() {
block_example "$1"
pending "$1"
block_end
}
evaluation() {
if [ ! "$inside_of_example" ]; then
syntax_error "When cannot be defined outside of Example"
return 0
fi
eval trans evaluation ${1+'"$@"'}
}
expectation() {
if [ ! "$inside_of_example" ]; then
syntax_error "The cannot be defined outside of Example"
return 0
fi
eval trans expectation ${1+'"$@"'}
}
example_hook() {
if [ "$inside_of_example" ]; then
syntax_error "Before/After cannot be defined inside of Example"
return 0
fi
eval trans control ${1+'"$@"'}
}
example_all_hook() {
if [ "$inside_of_example" ]; then
syntax_error "BeforeAll/AfterAll cannot be defined inside of Example"
return 0
fi
if [ "$1" = "before_all" ] && [ ! "$block_id_increased" ]; then
syntax_error "BeforeAll cannot be defined after of Example Group/Example in same block"
return 0
fi
eval trans control ${1+'"$@"'}
}
control() {
eval trans control ${1+'"$@"'}
}
pending() {
case ${1:-} in (\#*)
temporary_pending=${1#"#"}
escape_quote temporary_pending
trim temporary_pending "$temporary_pending"
set -- "'# $temporary_pending'"
esac
eval trans pending ${1+'"$@"'}
}
skip() {
skip_id=$(($skip_id + 1))
case ${1:-} in (\#*)
temporary_skip=${1#"#"}
escape_quote temporary_skip
trim temporary_skip "$temporary_skip"
set -- "'# $temporary_skip'"
esac
eval trans skip ${1+'"$@"'}
}
data() {
eval trans data_begin ${1+'"$@"'}
case ${2:-} in
'' | \#* | \|*)
trans embedded_text_begin "$1" "${2:-}"
line='' error=
while read_specfile line; do
trim line "$line"
is_comment "$line" && continue # ignore comment line
if_embedded_text "$line" trans embedded_text_line && continue
is_end_block "${line%% *}" && break
[ "$error" ] && continue
error=$(syntax_error "Data text should begin with '#|' or '# '")
done
trans embedded_text_end
[ ! "$error" ] || putsn "$error"
;;
\'* | \"*) trans data_text "$2" ;;
\<*) trans data_file "$2" ;;
*) trans data_func "$2" ;;
esac
eval trans data_end ${1+'"$@"'}
}
text_begin() {
eval trans embedded_text_begin ${1+'"$@"'}
inside_of_text=1
}
text_line() {
if_embedded_text "$1" trans embedded_text_line && return 0
text_end
return 1
}
text_end() {
eval trans embedded_text_end ${1+'"$@"'}
inside_of_text=''
}
parameters() {
if [ "$inside_of_example" ]; then
syntax_error "Parameters cannot be defined inside of Example"
return 0
fi
parameters_need_example=1
parameter_no=$(($parameter_no + 1))
trans parameters_begin "$parameter_no"
#shellcheck disable=SC2145
"parameters_$@"
trans parameters_end
}
parameters_generate_code() {
trans line "$1"
code="${code}${1}${LF}"
}
parameters_continuation_line() {
line=$1
shift
while ends_with "$line" "\\"; do
read_specfile line ||:
"$@" "$line"
done
}
parameters_block() {
while read_specfile line; do
trim line "$line"
is_end_block "${line%% *}" && break
case $line in (\#* | '') continue; esac
trans parameters "$line"
parameter_count=$(($parameter_count + 1))
parameters_continuation_line "$line" trans line
done
}
parameters_value() {
code=''
if [ $# -gt 0 ]; then
IFS=" $IFS"
parameters_generate_code "for shellspec_matrix in $*; do"
IFS=${IFS#?}
trans parameters "\"\$shellspec_matrix\""
code="${code}count=\$((\$count + 1))${LF}"
parameters_generate_code "done"
fi
eval "parameter_count=\$(count=0${LF}${code}echo \"\$count\")"
}
parameters_matrix() {
code='' nest=0 arguments=''
while read_specfile line; do
trim line "$line"
is_end_block "${line%% *}" && break
case $line in (\#* | '') continue; esac
nest=$(($nest + 1))
parameters_generate_code "for shellspec_matrix${nest} in $line"
arguments="$arguments\"\$shellspec_matrix${nest}\" "
parameters_continuation_line "$line" parameters_generate_code
parameters_generate_code "do"
done
trans parameters "$arguments"
code="${code}count=\$((\$count + 1))${LF}"
while [ $nest -gt 0 ]; do
parameters_generate_code "done"
nest=$(($nest - 1))
done
eval "parameter_count=\$(count=0${LF}${code}echo \"\$count\")"
}
parameters_dynamic() {
code=''
while read_specfile line; do
trim line "$line"
is_end_block "${line%% *}" && break
case $line in
%data | %data\ *)
line=${line#%data}
trans parameters "$line"
line='count=$(($count + 1))'
;;
*) trans line "$line"
esac
code="${code}${line}${LF}"
done
eval "parameter_count=\$(count=0${LF}{ $code }>&2;echo \"\$count\")"
}
mock() {
inside_of_mock=1
eval trans mock_begin ${1+'"$@"'}
mock_no=$(($mock_no + 1))
}
mock_end() {
is_end_block "${1%% *}" || return 1
inside_of_mock=''
eval trans mock_end ${1+'"$@"'}
}
constant() {
if [ "$_block_no_stack" ]; then
syntax_error "Constant should be defined outside of Example Group/Example"
return 0
fi
trim line "$1"
name=${line%%:*} value=''
trim value "${line#*:}"
if is_constant_name "$name"; then
trans constant "$name" "$value"
eval "$name=\$value"
else
syntax_error "Constant name should match pattern [A-Z_][A-Z0-9_]*"
fi
}
include() {
if [ "$inside_of_example" ]; then
syntax_error "Include cannot be defined inside of Example"
return 0
fi
if ! one_line_syntax_check error "$1"; then
syntax_error "Include has occurred an error" "$error"
return 0
fi
eval trans include ${1+'"$@"'}
}
with_function() {
trans with_function "$1"
shift
"$@"
}
out() {
eval trans out ${1+'"$@"'}
}
is_in_range() {
case $1 in
@*) [ "$block_id" = "${1#@}" ] ;;
*) [ "$lineno_begin" -le "$1" ] && [ "$1" -le "$lineno_end" ] ;;
esac
}
is_in_ranges() {
[ "${ranges:-}" ] || return 1
eval "set -- $ranges"
while [ $# -gt 0 ]; do
is_in_range "$1" && return 0
shift
done
return 1
}
remove_from_ranges() {
eval "set -- $ranges"
ranges=''
while [ $# -gt 0 ]; do
is_in_range "$1" || ranges="$ranges$1 "
shift
done
}
translate() {
work=''
while read_specfile line; do
if [ ! "$inside_of_text" ]; then
while ends_with "$line" "\\"; do
read_specfile work ||:
line="${line}${LF}${work}"
done
fi
trim work "$line"
dsl=${work%% *} rest=''
[ "$inside_of_text" ] && text_line "$work" && continue
translate_mock "$dsl" && continue
trim rest "${work#"$dsl"}"
mapping "$dsl" "$rest" || trans line "$line"
done
}
translate_mock() {
if [ "$inside_of_mock" ]; then
mock_end "$1" && return 0
is_dsl "$1" && use_dsl_in_mock=1 && return 0
elif [ "$use_dsl_in_mock" ]; then
syntax_error "Only directives can be used in Mock"
fi
return 1
}