indent_current=0 indent_last=0 stack=() anonymous_funcs=0 literal=0 last_keyword="" getindent(){ [[ ! ${1:0:1} =~ ([ \t]) ]] && echo 0 && return 0 echo "$1" | sed "s/\n/ /g" | awk '{ match($0, /^ */); printf("%d", RLENGTH) }' | sed 's/00/0/g' } add_anonymous_func(){ anonymous_funcs=$((anonymous_funcs+1)) } push (){ var="$1"; shift 1; eval "$var+=($(printf "'%s' " "$@"))"; } makeindent(){ for((i=0;i<$1;i++)); do printf " "; done } stack_pop(){ index=${#stack[@]} index=$((index-1)) [[ $index == "-1" ]] && return 0 lastitem="${stack[$index]}" unset stack["$index"] [[ -n $IGNORE ]] && return 0 [[ ${#lastitem} == 0 ]] && return [[ $lastitem =~ "fi" ]] && [[ "$line" =~ "else" ]] && return 0 # if/else is a tricky one echo -e "$(makeindent $((indent_last-2)) )$lastitem" if ! (( (indent_last-2) == indent_current )); then indent_last=$((indent_last-2)) stack_pop "$1" fi } stack_update(){ indent_last="$indent_current" indent_current=$(getindent "$1") [[ -n $DEBUG ]] && echo "\# debug: $indent_current|$indent_last|${#stack[@]} (indentlevel|last indentlevel|stackposition) " if ((indent_current < indent_last)); then stack_pop "$1"; fi } get_array_code(){ printf '"' printf "\${$1[$2]}" printf '"' } transpile_all(){ [[ -n $1 ]] && [[ $1 == '--noquotes' ]] && NO_QUOTES=1 while IFS="" read -r line; do i=$(getindent "$line") [[ "$line" =~ ^(#) ]] && local _comment="#" [[ "$line" =~ "={}" ]] && echo "$_comment$(makeindent $i)declare -A ${line/=\{\}/}" && continue [[ "$line" =~ "=[]" ]] && echo "$_comment$(makeindent $i)declare -a ${line/=\[\]/}" && continue if [[ ! $NO_QUOTES ]] && [[ "$line" =~ (\$[A-Za-z_0-9@\(\[\{]) ]]; then _last_interpol_size=$(mktemp) line="$(transpile_interpol "$line" 0)" rm $_last_interpol_size fi echo "$line" done } transpile_interpol() { local i="$2" local line="$1" local escape=false local interpol=false local single_quotes=false local double_quotes=false local len=${#line} while (($i < $len)); do local char=${line:$i:1} if { ! $escape && { ! $double_quotes || $interpol ; } ; }; then if [[ $char =~ ('('|'['|'{') ]]; then local res="$(transpile_interpol "$line" $(($i+1)))" printf "$char" printf "$res" i=$(($(cat $_last_interpol_size)+1)) $interpol && ! $double_quotes && { printf '"'; interpol=false; } continue elif [[ $char =~ (')'|'}'|']') ]]; then $interpol && ! $double_quotes && { printf '"'; interpol=false; } printf "$char" break fi fi if $escape; then escape=false elif [[ $char == '\' ]]; then escape=true elif $double_quotes; then case $char in '"') double_quotes=false && interpol=false ; ;; '$') interpol=true ; ;; esac elif $single_quotes; then [[ $char == "'" ]] && single_quotes=false elif $interpol; then if [[ $char =~ ^([^A-Za-z_0-9@]?)$ ]]; then interpol=false $double_quotes || printf '"' fi else case $char in '"') double_quotes=true ; ;; "'") single_quotes=true ; ;; '$') printf '"' && interpol=true ; ;; esac fi i=$(($i+1)) printf "$char" done $interpol && printf '"' echo $i > $_last_interpol_size } transpile_for(){ push stack "done" local semicolons="${1//[^;]}" local arr="$(echo "$1" | awk '{ print $4 }' )" local arr2nd="$(echo "$1" | awk '{ print $5 }' )" local i=$(( $(getindent "$code") + 2 )) args="" code="$1; do" indent="$(makeindent $i)" # traditional for loop if [[ ${#semicolons} == 2 ]]; then code="${code/for /for (( }" code="${code/; do/ )); do}" # iterate over string (starts with $ or has more than 1 argument) elif [[ ${arr:0:1} =~ ^([$]) || ${#arr2nd} != 0 ]]; then args="--noquotes" # iterate over associative array elif [[ "$code" =~ [a-zA-Z_0-9],[a-zA-Z_0-9] ]]; then local key="$(echo "$code" | awk '{ print $2 }' | awk -F',' '{ print $1 }')" local value="$(echo "$code" | awk '{ print $2 }' | awk -F',' '{ print $2 }')" code="$code\n$indent$value=$(get_array_code $arr "\$$key")" code="${code/,$value/}" code="${code/ of / in }" code="${code/ $arr/ $(get_array_code "!$arr" @)}"; elif [[ ! "$arr" == "\$@" ]]; then # iterate over indexed array local key="$(echo "$code" | awk '{ print $2 }')" code="${code/ $arr/ $(get_array_code $arr @)}" fi echo -e "$code" | transpile_all ${args} last_keyword=for } transpile_while(){ push stack "done" code="$(transpile_condition while "$1" "$2")" code="$code; do" echo "$code" | transpile_all last_keyword=while } transpile_if(){ push stack "fi" code="$(transpile_condition "if" "$1" "$2")" code="$code; then" echo "$code" | transpile_all last_keyword=if } transpile_else(){ local curindent=$(getindent "$1") curindent=$(( curindent+2 )) local indent="$(makeindent $curindent)" local indent="$(makeindent $curindent)" [[ $last_keyword == "else" ]] && echo "$indent""fi" echo -e "$1" [[ $last_keyword == "else" ]] || push stack "fi" last_keyword=else } transpile_condition(){ form=$1 code="${2/$form not/$form !}" firstvar="${code/\! /}" firstvar="$(echo "$firstvar" | awk '{ print $2 }')" code="${code// and / && }" code="${code// or / || }" code="${code//> /-gt }" code="${code//>= /-ge }" code="${code//< /-lt }" code="${code//<= /-le }" code="${code//!= /-ne }" [[ "$code" =~ ($form[ ]) && "$firstvar" =~ ^([\"\$-]) ]] && code="${code/$form /$form [[ }" code="${code/match /=~ }" [[ "$code" =~ "[[" ]] && code="$code ]]" code="${code// is / == }" echo "$code" } transpile_then(){ cmd="${*/await /}" cmd="$(echo "$cmd" | sed "s/'/\"/g")" # ugly hack..*FIXME* [[ ! -n $3 ]] && category="then" || category="$3" add_anonymous_func funcindex=$anonymous_funcs echo -e "\n# async code" echo "_"$category"_$funcindex(){" local curindent=$(getindent "$1") local indent="$(makeindent $curindent)" if [[ $category =~ "pipe" ]]; then add_anonymous_func push stack "}\n$indent""async_$category _then_when_done_$anonymous_funcs "_"$category"_"$funcindex ${cmd/ then*/}" push stack "" else push stack "}\n$indent""async_$category "_"$category"_"$funcindex ${cmd/ then/}" fi } transpile_when_done(){ local curindent=$(getindent "$1") echo -e "}\n_then_when_done_"$anonymous_funcs"(){" } transpile_switch(){ push stack "esac" echo "${1/switch/case} in" last_keyword=switch } transpile_case(){ push stack " ;;" echo "${1/case /})" } transpile_array_push(){ code="${1/+=/+=(}" echo "$code)" } transpile_array_get(){ postfix=${1/*\]/} code="${1/\]*/}]" code="${code/\$/\"\${}" code="${code/\]/]}}\"" echo "$code""$postfix" } transpile_foreachline_from(){ file="${1/[ ]?for line from /}" local curindent=$(getindent "$1") local indent="$(makeindent $curindent)" echo "$indent""while IFS=\"\" read -r line; do" push stack $indent"done < $file" last_keyword=foreachline } transpile_function(){ push stack "}" local curindent=$(( $(getindent "$1") + 2 )) local indent="$(makeindent $curindent)" fields="${1//*\(/}" fields="${fields//\)*/}" fields="${fields// /}" fields="${fields//,/ }" echo "${1//\(*\)/()}{" local i=1; for field in $fields; do echo "$indent""local $field=\"\${$i}\""; i=$((i+1)); done last_keyword=function } is_even(){ a=$1 [[ $((a%2)) -eq 0 ]] && return 0 || return 1 } get_quotes_in_line(){ quotes="${1//[^\'\"]}" echo ${#quotes} } is_quote(){ [[ "$1" =~ ([\"\']) ]] && return 0 || return 1 } line_is_multistring(){ line="$1" i=$((${#line}-1)) lastchar="${line:$i:1}" quotes=$(get_quotes_in_line "$line") (( $quotes == 1 )) && return 0 (( $quotes == 0 )) && return 1 is_even $quotes && return 1 [[ "$line" =~ ([]a-zA-Z_0-9]=\"|[]a-zA-Z_0-9]=\') ]] && return 0 return 1 # TODO: strip first line } check_literal(){ local line="${1/ # */}" if line_is_multistring "$line"; then if (( literal == 0 )); then literal=1 else literal=0 fi fi }