#!/usr/bin/ksh93

########################################################################
#                                                                      #
#               This software is part of the ast package               #
#                    Copyright (c) 2013 Roland Mainz                   #
#                      and is licensed under the                       #
#                 Eclipse Public License, Version 1.0                  #
#                    by AT&T Intellectual Property                     #
#                                                                      #
#                A copy of the License is available at                 #
#          http://www.eclipse.org/org/documents/epl-v10.html           #
#         (with md5 checksum b35adb5213ca9657e911e9befb180842)         #
#                                                                      #
#                                                                      #
#                 Roland Mainz <roland.mainz@nrubsig.org>              #
#                                                                      #
########################################################################

#
# Copyright (c) 2013, Roland Mainz. All rights reserved.
#

#
# jsonparse1 - a simple JSON parser 
#

typeset -r jsontext="$(
cat <<EOF
{
    "firstName": "John",
    "lastName": "Smith",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": 10021
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "fax",
            "number": "646 555-4567"
        }
    ]


    "sparse_array": [
        {
            "one": "1",
        },
	null,
        {
            "three": "3",
        }
    ]
    "empty_array": [
    ]

    "floatval": 7.8,
    "bool1": true,
    "bool2": false,
    "pre-done-empty": "",
    "done": 666,

}

EOF
)"

function parse_json
{
	typeset jsontext="$2"
	nameref ar="$1"
	typeset dummy

	# fixme:
	# - We want to enforce standard conformance - does ~(Exp) or ~(Ex-p) does that ?
	dummy="${jsontext//~(Ex-p)(?:
		(?:\"([^\"]+?)\"\:)|	# name
		(\{)|			# object begin
		(\})|			# object end
		(\[)|			# array start
		(\])|			# array end
		(,)|			#
		([[:digit:]\.]+)|	# numerical value
		(?:(\"[^\"]*?\"))|	# string value
		(true|false)|		# boolean value
		(null)			# null
		)/D}"

	# debug output
#	print -v .sh.match
#	printf $"#dummy=%q\n" "${dummy}"

	# copy ".sh.match" to array "ar"
	# fixme: Use typeset -c instead
	#typeset -c ar=.sh.match
	#return 0

	integer i j
	for i in "${!.sh.match[@]}" ; do
		for j in "${!.sh.match[i][@]}" ; do
			[[ -v .sh.match[i][j] ]] && ar[i][j]="${.sh.match[i][j]}"
		done
	done

	return 0
}


function build_compound_tree
{
	nameref car=$1
	nameref ar=$2
	integer i=$3
	integer ar_max=$4
	integer cari=$5	# car index

	integer j k nested
	bool foundobj

	for (( ; i < ar_max ; )) ; do
		# end of object ? then return...
		if [[ -v ar[3][$i] ]] ; then
			return 0
		fi

		car[cari].name="${ar[1][$(( i++ ))]}"

		# string value
		if [[ -v ar[8][$i] ]] ; then
			typeset car[cari].value="${ar[8][$(( i++ ))]/~(Elr)\"(.*)\"/\1}"
		# numeric value
		elif [[ -v ar[7][$i] ]] ; then
			float car[cari].value="${ar[7][$(( i++ ))]}"
		# boolean value
		elif [[ -v ar[9][$i] ]] ; then
			bool car[cari].value="${ar[9][$(( i++ ))]}"
		# JS object value
		elif [[ -v ar[2][$i] ]] ; then
			(( nested=0 , foundobj=false ))
			for (( j=i ; (j < ar_max) && (nested > 0 || !foundobj) ; j++ )) ; do
				[[ -v ar[2][$j] ]] && (( nested++, foundobj=true ))
				[[ -v ar[3][$j] ]] && (( nested-- ))
			done

			compound -a car[$cari].value
			build_compound_tree car[$cari].value xar $(( i+1 )) $j 0
			(( i=j ))
		# array
		elif [[ -v ar[4][$i] ]] ; then
			(( nested=0 , foundobj=false ))
			for (( j=i ; (j < ar_max) && (nested > 0 || !foundobj) ; j++ )) ; do
				[[ -v ar[4][$j] ]] && (( nested++, foundobj=true ))
				[[ -v ar[5][$j] ]] && (( nested-- ))
			done

			compound -a car[$cari].arrayvalue

			# an array must at least contain three tokens
			# (e.g. <name><comma><value>)
			if (( (j-i) >= 3 )) ; then
				nameref js_ar=car[$cari].arrayvalue
				integer jari=0 # json array index

				(( i++ ))

				for (( ;; )) ; do
					# handle 'null' array entries
					if [[ -v ar[10][$i] ]] ; then
						(( i++, jari++ ))
						[[ -v ar[6][$i] ]] && ((i++)) # ',' handling
						continue
					fi

					# handle (normal) JS objects 
					(( nested=0 , foundobj=false ))
					for (( k=i ; (k < j) && (nested > 0 || !foundobj) ; k++ )) ; do
						[[ -v ar[2][$k] ]] && (( nested++, foundobj=true ))
						[[ -v ar[3][$k] ]] && (( nested-- ))
					done

					build_compound_tree js_ar[$jari] xar $(( i+1 )) $k 0
					[[ -v ar[6][$k] ]] && ((k++)) # ',' handling

					(( i=k, jari++ ))
					(( i > (j-1) )) && break
				done
			fi

			(( i=j ))
		fi

		[[ -v ar[6][$i] ]] && (( i++ )) # ',' handling

		(( cari++ ))
	done

	return 0
}


# fixme: This should be "function main"
main()
{
	typeset input="$1"
	typeset mode="$2"

	typeset -a xar

	if [[ "$input" == '#demo1' ]] ; then
		parse_json xar "${jsontext}"
	else
		if [[ ! -f "$input" ]] ; then
			print -u2 -f $"%s: Input file not found.\n" "${progname}"
			return 1
		fi
		if [[ ! -r "$input" ]] ; then
			print -u2 -f $"%s: Input file not readable.\n" "${progname}"
			return 1
		fi
		if ! icontent="${ cat "${input}" ; }" ; then
			print -u2 -f $"%s: I/O error\n" "${progname}"
			return 1
		fi
		parse_json xar "${icontent}"
	fi


	case "${mode}" in
		'compound')
			compound c
			compound -a c.xcar

			build_compound_tree c.xcar xar 1 ${#xar[0][@]} 0
			print -v c
			;;
		'compoundarray')
			compound -a xcar

			build_compound_tree xcar xar 1 ${#xar[0][@]} 0
			print -v xcar
			;;
		'compoundstream')
			compound -a xcar
			integer i

			build_compound_tree xcar xar 1 ${#xar[0][@]} 0
			for (( i=0 ; i < ${#xcar[@]} ; i++ )) ; do
				print -v xcar[i]
			done
			;;
		'debug-parser')
			print -v xar
			;;
		*)
			print -u2 -f $"%s: Unknown view mode %q\n"  "${progname}" "${mode}"
			return 1
			;;
	esac

	return 0
}


# program start
builtin basename
builtin cat

set -o nounset

typeset progname="${ basename "${0}" ; }"

typeset -r json2cpv_usage=$'+
[-?\n@(#)\$Id: json2cpv (Roland Mainz) 2013-05-26 \$\n]
[-author?Roland Mainz <roland.mainz@nrubsig.org>]
[+NAME?json2cpv_usage - JSON to Compound variable filter]
[+DESCRIPTION?\bjson2cpv\b is a small ksh93 compound variable demo
        which reads a JSON input and converts it into the ksh93 compound variable
	format specified by mode (either "compound", "compoundarray", "compoundstream").]

file mode

[+SEE ALSO?\bksh93\b(1)]
'

while getopts -a "${progname}" "${json2cpv_usage}" OPT ; do 
	case "${OPT}" in
		*)    usage ;;
	esac
done
shift $(( OPTIND-1 ))

typeset jsonfile="$1"
typeset mode="$2"

main "$jsonfile" "$mode"
exit $?
