diff options
-rw-r--r-- | json.sh | 246 | ||||
-rwxr-xr-x | trace | 30 |
2 files changed, 153 insertions, 123 deletions
@@ -1,190 +1,190 @@ -#!/bin/bash - -gron() { - set -e - LC_ALL='C.UTF-8' - local gron_prefix='json' - local gron_str - gron_str="$(cat)" - _gron_value +#!/hint/bash + +json_parse() { + type json_object_beg &>/dev/null || json_object_beg() { :; } # no args + type json_object_end &>/dev/null || json_object_end() { :; } # no args + type json_array_beg &>/dev/null || json_array_beg() { :; } # no args + type json_array_end &>/dev/null || json_array_end() { :; } # no args + type json_atom &>/dev/null || json_atom() { :; } # $1=(key|string|number|bool|null) $2=$val + + local _json_buf + _json_buf="$(cat)" + _json_value + _json_ws + if [[ -n "$_json_buf" ]]; then + _json_error 'unexpected data after json: %q' "$_json_buf" + fi } -_gron_error() { - printf 'unexpected character: %q\n' "${gron_str::1}" >&2 +_json_error() { + printf >&2 "json error: $1"'\n' "${@:2}" return 1 } -_gron_expect() { - if [[ "${gron_str::1}" != "$1" ]]; then - _gron_error +_json_expect() { + if [[ "${_json_buf::1}" != "$1" ]]; then + _json_error 'expected character %q, got %q' "$1" "${_json_buf::1}" fi - gron_str=${gron_str:1} + _json_buf=${_json_buf:1} } -_gron_ws() { - while [[ "${gron_str::1}" == [$' \t\n\r'] ]]; do - gron_str=${gron_str:1} +_json_ws() { + while [[ "${_json_buf::1}" == [$' \t\n\r'] ]]; do + _json_buf=${_json_buf:1} done } -_gron_value() { - _gron_ws - case "${gron_str::1}" in - '{' ) _gron_object ;; - '[' ) _gron_array ;; - '"' ) _gron_string ;; - 't' ) _gron_lit 'true' ;; - 'f' ) _gron_lit 'false' ;; - 'n' ) _gron_lit 'null' ;; - [-+0-9] ) _gron_number ;; - * ) _gron_error ;; +_json_value() { + _json_ws + case "${_json_buf::1}" in + '{' ) _json_object ;; + '[' ) _json_array ;; + '"' ) _json_string string;; + 't' ) _json_lit 'true' ;; + 'f' ) _json_lit 'false' ;; + 'n' ) _json_lit 'null' ;; + [-+0-9] ) _json_number ;; + * ) _json_error 'unexpected value-start character: %q' "${_json_buf::1}" ;; esac } - -_gron_object() { - printf '%s={}\n' "$gron_prefix" - - local gron_base_prefix=$gron_prefix - - _gron_expect '{' - _gron_ws - case "${gron_str::1}" in +_json_object() { + _json_expect '{' + json_object_beg + _json_ws + case "${_json_buf::1}" in '"' ) - local k + local _json_obj_key while true; do - _gron_read_string k - gron_prefix="${gron_base_prefix}[${k@Q}]" - _gron_ws - _gron_expect ':' - _gron_value - _gron_ws - case "${gron_str::1}" in + _json_string key + _json_ws + _json_expect ':' + _json_value + _json_ws + case "${_json_buf::1}" in ',' ) - gron_str=${gron_str:1} - _gron_ws + _json_buf=${_json_buf:1} + _json_ws ;; '}' ) - gron_str=${gron_str:1} + _json_buf=${_json_buf:1} + json_object_end return ;; esac done ;; '}' ) - gron_str=${gron_str:1} + _json_buf=${_json_buf:1} + json_object_end return ;; esac } -_gron_array() { - printf '%s=[]\n' "$gron_prefix" - - local gron_base_prefix=$gron_prefix - - _gron_expect '[' - _gron_ws - if [[ "${gron_str::1}" == ']' ]]; then +_json_array() { + _json_expect '[' + json_array_beg + _json_ws + if [[ "${_json_buf::1}" == ']' ]]; then + _json_buf=${_json_buf:1} + json_array_end return fi - local i - for ((i = 0; 1; i++)); do - gron_prefix="${gron_base_prefix}[${i}]" - _gron_value - _gron_ws - case "${gron_str::1}" in + while true; do + _json_value + _json_ws + case "${_json_buf::1}" in ',' ) - gron_str=${gron_str:1} - _gron_ws + _json_buf=${_json_buf:1} + _json_ws ;; ']' ) - gron_str=${gron_str:1} + _json_buf=${_json_buf:1} + json_array_end return ;; esac done } -_gron_read_string() { - _gron_expect '"' - local v +# $1=(key|string) +_json_string() { + _json_expect '"' + local _json_strval='' + local _json_re='^[^\"]+' + local _json_c json_n _json_n2 while true; do - case "${gron_str::1}" in + case "${_json_buf::1}" in "\\" ) - gron_str=${gron_str:1} - case "${gron_str::1}" in - '"' ) v+='"'; gron_str=${gron_str:1} ;; - "\\" ) v+="\\"; gron_str=${gron_str:1} ;; - '/' ) v+='/'; gron_str=${gron_str:1} ;; - 'b' ) v+=$'\b'; gron_str=${gron_str:1} ;; - 'f' ) v+=$'\f'; gron_str=${gron_str:1} ;; - 'n' ) v+=$'\n'; gron_str=${gron_str:1} ;; - 'r' ) v+=$'\r'; gron_str=${gron_str:1} ;; - 't' ) v+=$'\t'; gron_str=${gron_str:1} ;; + _json_buf=${_json_buf:1} + case "${_json_buf::1}" in + '"' ) _json_strval+='"'; _json_buf=${_json_buf:1} ;; + "\\" ) _json_strval+="\\"; _json_buf=${_json_buf:1} ;; + '/' ) _json_strval+='/'; _json_buf=${_json_buf:1} ;; + 'b' ) _json_strval+=$'\b'; _json_buf=${_json_buf:1} ;; + 'f' ) _json_strval+=$'\f'; _json_buf=${_json_buf:1} ;; + 'n' ) _json_strval+=$'\n'; _json_buf=${_json_buf:1} ;; + 'r' ) _json_strval+=$'\r'; _json_buf=${_json_buf:1} ;; + 't' ) _json_strval+=$'\t'; _json_buf=${_json_buf:1} ;; 'u' ) - if ! [[ ${gron_str::5} == u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] ]]; then - _gron_error + if ! [[ ${_json_buf::5} == u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] ]]; then + _json_error 'expected 4 hex characters, got: %q' "${_json_buf:1:4}" fi - c="$(printf "\\${gron_str::5}")" - n="0x${gron_str:1:4}" - gron_str=${gron_str:5} - if (( 0xDC00 <= n && n <= 0xDFFF )); then - if ! [[ ${gron_str::6} == '\u'[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] ]]; then - _gron_error + _json_c="$(printf "\\${_json_buf::5}")" + _json_n="0x${_json_buf:1:4}" + _json_buf=${_json_buf:5} + if (( 0xDC00 <= _json_n && _json_n <= 0xDFFF )); then + if ! [[ ${_json_buf::6} == '\u'[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] ]]; then + _json_error 'expected a \\uABCD sequence, got: %q' "${_json_buf::6}" fi - n2="0x${gron_str:2:4}" - gron_str=${gron_str:6} - gron_str=${gron_str:5} + _json_n2="0x${_json_buf:2:4}" + _json_buf=${_json_buf:6} if ! (( 0xD800 <= n2 && n2 <= 0xDBFF )); then - _gron_error + _json_error 'expected a UTF-16 high surrogate, got: 0x%x' "$n2" fi - n=$(( 0x10000 + ((n-0xDC00)<<10) + (n2-0xD8000) )) - printf -v n '0x%08' "$n" - printf -v c "\\U$n" + _json_n=$(( 0x10000 + ((n-0xDC00)<<10) + (n2-0xD8000) )) + printf -v _json_n '0x%08' "$_json_n" + printf -v _json_c "\\U$_json_n" fi - v+="$c" + _json_strval+="$_json_c" ;; - *) _gron_error ;; + *) _json_error 'unexpected backslash sequence: \\%q' "${json_buf::1}";; esac ;; '"' ) - gron_str=${gron_str:1} - printf -v "$1" "$v" + _json_buf=${_json_buf:1} + json_atom "$1" "$_json_strval" + return ;; * ) # Consume multiple characters at once, # or else this is horribly slow. - local re='^[^\"]+' - [[ $gron_str =~ $re ]] - v+=${BASH_REMATCH[1]} - gron_str=${gron_str#"${BASH_REMATCH[1]}"} + [[ $_json_buf =~ $_json_re ]] + _json_strval+=${BASH_REMATCH[0]} + _json_buf=${_json_buf#"${BASH_REMATCH[0]}"} ;; esac done } -_gron_string() { - local v - _gron_read_string v - printf '%s=%q\n' "$gron_prefix" "$v" -} - -_gron_number() { - local re='^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][-+]?[0-9]+)?' - if ! [[ $gron_str =~ $re ]]; then - _gron_error +_json_number() { + local _json_re='^-?(0|[1-9][0-9]+)(\.[0-9]+)?([eE][-+]?[0-9]+)?' + if ! [[ $_json_buf =~ $_json_re ]]; then + _json_error 'invalid number: %q' "${_json_buf::16}" fi - gron_str=${gron_str#"${BASH_REMATCH[1]}"} - printf '%s=%s\n' "$gron_prefix" "${BASH_REMATCH[1]}" + _json_buf=${_json_buf:${#BASH_REMATCH[0]}} + json_atom number "${BASH_REMATCH[0]}" } -_gron_lit() { - if [[ ${gron_str::${#1}} != "$1" ]]; then - _gron_error +_json_lit() { + if [[ ${_json_buf::${#1}} != "$1" ]]; then + _json_error 'expected %q, got: %q' "$1" "${_json_buf::${#1}}" + fi + _json_buf=${_json_buf:${#1}} + if [[ "$1" == 'null' ]]; then + json_atom null null + else + json_atom bool "$1" fi - gron_str=${gron_str:${#1}} - printf '%s=%s\n' "$gron_prefix" "$1" } - -gron @@ -0,0 +1,30 @@ +#!/bin/bash + +# no args +json_object_beg() { + echo json_object_beg +} + +# no args +json_object_end() { + echo json_object_end +} + +# no args +json_array_beg() { + echo json_array_beg +} + +# no args +json_array_end() { + echo json_array_end +} + +# $1=(key|string|number|bool|null) $2=$val +json_atom() { + printf 'json_atom %q %q\n' "$1" "$2" +} + +. "$(dirname -- "${BASH_SOURCE[0]}")/json.sh" + +json_parse |