Last active
July 8, 2022 21:53
-
-
Save Valodim/7017924 to your computer and use it in GitHub Desktop.
zsh json parser (WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/zsh | |
typeset -A closings | |
closings=( '{' '}' '[' ']' ) | |
# usage: find-matching strvar pos | |
# heart of the parser. matches one "element." matching depends on first | |
# character (at pos), can be " for string matching or any in closings | |
# associative parameter. | |
json-find-matching () { | |
setopt localoptions extendedglob | |
REPLY=$2 | |
# char at position should be opening | |
local extra=0 instr=0 seek=${${(P)1}[REPLY]} closing | |
# special case: we are simply inside a string | |
if [[ ${${(P)1}[REPLY]} == '"' ]]; then | |
instr=1 | |
closing='"' | |
else | |
# bad opening char? | |
(( $+closings[$seek] )) || return 2 | |
# remember closing char | |
closing=$closings[$seek] | |
fi | |
# iterate string while there are chars left | |
while (( REPLY++ < ${(P)#1} )); do | |
# current character | |
chr=${${(P)1}[REPLY]} | |
# if we are in a string, check if we get out at this point | |
if (( instr )); then | |
# debug | |
# echo instr $chr $extra | |
# check if the char is a ", and the text left to us is not an odd number of backslashes | |
if [[ $chr == '"' && ${${(P)1}[1,REPLY-1]} != *[^\\](\\\\)#\\ ]]; then | |
# just matching a string? we're done, then. | |
[[ $closing == '"' ]] && return | |
# either way, we're out! | |
instr=0 | |
fi | |
# carry on my wayward son | |
continue | |
fi | |
# is it a closing one? if so, reduce extra and quit if we're at zero | |
[[ $chr == $closing ]] && (( extra-- == 0 )) && return | |
# if we find another one of these, increase level | |
[[ $chr == $seek ]] && (( extra++ )) | |
# opening a string? handle specially, escaping might be going on! | |
[[ $chr == '"' ]] && instr=1 | |
# otherwise, don't care | |
# debug | |
# echo $chr $extra | |
done | |
return 1 | |
} | |
# parses a list into an array. char at pos MUST be a [! | |
# usage: json-parse-list strvar pos | |
json-parse-list () { | |
# opening char must be a bracket! | |
[[ ${${(P)1}[$2]} == '[' ]] || return 2 | |
# find beginning and end | |
local pos=$2 chr | |
# clear reply, and cast to array | |
reply=() | |
while (( pos++ < ${(P)#1} )); do | |
chr=${${(P)1}[pos]} | |
# skip blanks regardless | |
[[ $chr == [[:blank:]] ]] && continue | |
# break at closing bracket? | |
[[ $chr == ']' ]] && return | |
# skip commas, if there is a previous element | |
[[ $chr == ',' ]] && (( $#reply > 0 )) && continue | |
# should be a sublist, object or string - save entire thing | |
if [[ $chr == '"' ]] || (( $+closings[$chr] )); then | |
json-find-matching $1 $pos || return 2 | |
reply+=( ${${(P)1}[pos,REPLY]} ) | |
pos=$REPLY | |
continue | |
fi | |
# we should never be here! | |
return 1 | |
done | |
} | |
# parses a list into an array. if said array is not associative, there will | |
# always be pairs of key+value. | |
# | |
# usage: json-parse-object strvar pos | |
json-parse-object () { | |
# opening char must be a curly brace! | |
[[ ${${(P)1}[$2]} == '{' ]] || return 2 | |
# set up some beginnings | |
local pos=$2 chr | |
# clear reply, and cast to array if necessary | |
reply=() | |
while (( pos++ < ${(P)#1} )); do | |
chr=${${(P)1}[pos]} | |
# skip blanks regardless | |
[[ $chr == [[:blank:]] ]] && continue | |
# break at closing bracket? | |
[[ $chr == '}' ]] && return | |
# skip the : if we have a key | |
[[ $chr == ':' && -n $key ]] && continue | |
# skip comma, if there is a previous element in $reply | |
[[ $chr == ',' ]] && (( $#reply > 0 )) && continue | |
# no key yet? must be a string, then | |
if [[ -z $key ]]; then | |
if [[ $chr == '"' ]]; then | |
json-find-matching $1 $pos || return 2 | |
key=${${(P)1}[pos,REPLY]} | |
pos=$REPLY | |
continue | |
fi | |
# if there is no key but we didn't get one either, we can't be here! | |
return 2 | |
fi | |
# should be a sublist, object or string - save entire thing | |
if [[ $chr == '"' ]] || (( $+closings[$chr] )); then | |
json-find-matching $1 $pos || return 2 | |
# save it | |
reply+=( ${${key#\"}%\"} ${${(P)1}[pos,REPLY]} ) | |
pos=$REPLY | |
# empty key | |
key= | |
continue | |
fi | |
# we should never be here! | |
return 1 | |
done | |
} | |
teststr='{"a":{"b":["hi\"lo",[],{"swag":"yolo"},"lulz"],"x":{"swag":"y"}},"b":{}}' | |
# 1 6 11 | |
# 1012 | |
# object test with \" | |
# json-find-matching teststr 6 && echo $REPLY | |
# string test | |
# json-find-matching teststr 12 && echo $REPLY | |
# list test | |
# json-find-matching teststr 11 && echo $REPLY | |
# test parsing of a list | |
json-parse-list teststr 11 && print -l $reply | |
# test parsing of an object | |
json-parse-object teststr 6 && print -l $reply |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment