Files
sausage/userland/goawk/source/testdata/other/gron.awk
T

377 lines
12 KiB
Awk
Executable File

#!/usr/bin/awk -f
BEGIN { init() }
function init( i,isStructure,isUngron) {
for (i = 1; i < ARGC; i++) {
if ((isUngron = "-u"==ARGV[i]) || (isStructure = "-s"==ARGV[i])) {
delete ARGV[i]
break
}
}
Pos=1
RS="\x01" # this allows to read whole input at once
if ((getline In) < 0) die("I/O error")
isUngron ? ungron() : gron(isStructure)
}
function gron(isStructure) {
# --- parse JSON ---
split("", Asm); AsmLen=0
if (!ELEMENT() || Pos <= length(In))
dieAtPos("Can't parse JSON")
# dbgA("--- JSON asm:",Asm)
# --- generate GRON ---
split("",AlreadyTracked)
split("",Stack); split("",PathStack)
Depth = 0
generateGron(isStructure)
}
function ungron( i,instr) {
split("", Asm); AsmLen=0 # Gron asm
split("", JsonAsm); JsonAsmLen=0
if (!STATEMENTS() || Pos <= length(In))
dieAtPos("Can't parse GRON")
# --- ungron (gron asm -> json asm) ---
# dbgA("--- Gron asm:",Asm)
split("", AddrType) # addr -> type
split("", AddrValue) # addr -> value
split("", AddrCount) # addr -> segment count
split("", AddrKey) # addr -> last segment name
for (i=0; i<AsmLen; i++) {
instr = Asm[i]
if("record" == instr) {
split("",Path)
split("",Types)
split("",Value) # [ type, value ]
}
else if (isSegmentType(instr)) { arrPush(Types, instr); arrPush(Path, Asm[++i]) }
else if ("value" == instr) {
instr = Asm[++i]
split("",Value)
Value[0] = instr
if (isValueHolder(instr))
Value[1] = Asm[++i]
} else if ("end" == instr) { processRecord() }
}
generateJsonAsm()
# dbgA("--- JSON asm:",JsonAsm)
# --- generate JSON ---
# Indent = ENVIRON["Indent"] + 0
Indent = 2
for (i=0; i<Indent; i++)
IndentStr=IndentStr " "
Open["object"]="{" ; Close["object"]="}" ; Opens["end_object"] = "object"
Open["array"] ="[" ; Close["array"] ="]" ; Opens["end_array"] = "array"
split("",Stack)
Depth = 0
generateJson()
}
#function dbgA(title,arr, i) { print title; for(i=0;i in arr;i++) printf "%2s : %s\n", i,arr[i] }
# --- JSON ---
function tryParseDigits(res) { return tryParse("0123456789", res) }
function NUMBER( res) {
return (tryParse1("-", res) || 1) &&
(tryParse1("0", res) || tryParse1("123456789", res) && (tryParseDigits(res)||1)) &&
(tryParse1(".", res) ? tryParseDigits(res) : 1) &&
(tryParse1("eE", res) ? (tryParse1("-+",res)||1) && tryParseDigits(res) : 1) &&
asm("number") && asm(res[0])
}
function tryParseHex(res) { return tryParse1("0123456789ABCDEFabcdef", res) || err(res) }
function err(res) { res[1]=1; return 0 }
function tryParseCharacters(res) { while (tryParseCharacter(res)); return !res[1] }
function tryParseCharacter(res) { return tryParseSafeChar(res) || tryParseEscapeChar(res) }
function tryParseEscapeChar(res) {
return tryParse1("\\", res) ? tryParse1("\"\\/bfnrt", res) || tryParseU(res) : 0
}
function tryParseU(res) { return tryParse1("u", res) ? tryParseHex(res) && tryParseHex(res) && tryParseHex(res) && tryParseHex(res) : 0 }
function tryParseSafeChar(res, c) {
c = nextChar()
# https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4#L56
if (c != "\"" && c != "\\" && c > "\x1F") {
Pos++
res[0] = res[0] c
return 1
}
return 0
}
function STRING(isKey, res) {
return tryParse1("\"",res) && asm(isKey ? "key" : "string") &&
tryParseCharacters(res) &&
tryParse1("\"",res) &&
asm(res[0])
}
function WS() { return tryParse("\t\n\r ") || 1 }
function WS1() { return tryParse("\t ") || 1 }
function VALUE() {
return OBJECT() ||
ARRAY() ||
STRING() ||
NUMBER() ||
tryParseExact("true") && asm("true") ||
tryParseExact("false") && asm("false") ||
tryParseExact("null") && asm("null")
}
function OBJECT() {
return tryParse1("{") && asm("object") &&
(WS() && tryParse1("}") ||
MEMBERS() && tryParse1("}")) &&
asm("end_object")
}
function ARRAY() {
return tryParse1("[") && asm("array") &&
(WS() && tryParse1("]") ||
ELEMENTS() && tryParse1("]")) &&
asm("end_array")
}
function MEMBERS() {
return MEMBER() && (tryParse1(",") ? MEMBERS() : 1)
}
function ELEMENTS() {
return ELEMENT() && (tryParse1(",") ? ELEMENTS() : 1)
}
function MEMBER() {
return WS() && STRING(1) && WS() && tryParse1(":") && ELEMENT()
}
function ELEMENT() {
return WS() && VALUE() && WS()
}
# --- GRON ---
function STATEMENTS() {
for(tryParse("\n");nextChar();tryParse("\n")) {
if (!STATEMENT())
return 0
}
return 1
}
function STATEMENT() {
return asm("record") &&
PATH() && WS1() && tryParse1("=") && WS1() && asm("value") && VALUE_GRON() && (tryParse1(";")||1) &&
asm("end")
}
function PATH() {
return BARE_WORD() && SEGMENTS()
}
function BARE_WORD( word) {
return tryParse1("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_", word) &&
(tryParse("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789", word) || 1) &&
asm("key") && asm("\"" word[0] "\"")
}
function SEGMENTS() {
return SEGMENT() && SEGMENTS() || 1
}
function SEGMENT() {
return tryParse1(".") && BARE_WORD() ||
tryParse1("[") && KEY() && tryParse1("]")
}
function KEY( idx) {
return tryParse("0123456789", idx) &&
asm("index") && asm(idx[0]) ||
STRING(1)
}
function VALUE_GRON() {
return STRING() ||
NUMBER() ||
tryParseExact("true") && asm("true") ||
tryParseExact("false") && asm("false") ||
tryParseExact("null") && asm("null") ||
tryParseExact("{}") && asm("object") ||
tryParseExact("[]") && asm("array")
}
# --- ungron ---
function isComplex(s) { return "object"==s || "array"==s }
function isSegmentType(s) { return "key" ==s || "index" ==s }
function isValueHolder(s) { return "string"==s || "number"==s }
function processRecord( l, addr, type, value, i) {
l = arrLen(Path)
addr=""
for (i=0; i<l; i++) {
# build addr
addr = addr (i>0?",":"") Path[i]
if (i<l-1) {
type = Types[i+1] == "key" ? "object" : "array"
value = ""
} else {
type = Value[0]
value = Value[1]
}
if (addr in AddrType && type != AddrType[addr]) {
die("Conflicting types for " addr ": " type " and " AddrType[addr])
}
AddrType[addr] = type
AddrValue[addr] = value
AddrCount[addr] = i+1
AddrKey[addr] = Path[i]
}
}
function generateJsonAsm( i,j,l, a,aPrev,aj,type,addrs,ends) {
split("",Stack)
ends["object"] = "end_object"
ends["array"] = "end_array"
for (a in AddrType)
arrPush(addrs, a)
quicksort(addrs, 0, (l=arrLen(addrs))-1)
for (i=0; i<l; i++) {
a = addrs[i]
type = AddrType[a]
if (i>0) {
aPrev = addrs[i-1]
for (j=0; j<AddrCount[aPrev] - AddrCount[a] + isComplex(AddrType[aPrev]); j++)
asmJson(ends[arrPop(Stack)])
# determine the type of current container (object/array) - for array should not issue "key"
for (j=i; AddrCount[a]-AddrCount[aj=addrs[j]] != 1; j--) {} # descend to addr of prev segment
if ("array" != AddrType[aj]) {
asmJson("key")
asmJson(AddrKey[a]) # last segment in addr
}
}
asmJson(type)
if (isComplex(type))
arrPush(Stack, type)
if (isValueHolder(type))
asmJson(AddrValue[a])
if (i==l-1) { # last
for (j=0; j<AddrCount[a] - (isComplex(type)?0:1); j++)
asmJson(ends[arrPop(Stack)])
}
}
}
function asmJson(inst) { JsonAsm[JsonAsmLen++]=inst; return 1 }
function arrPush(arr, e) { arr[arr[-7]++] = e }
function arrPop(arr, e,l) { e = arr[l=--arr[-7]]; if (l<0) die("Can't pop"); delete arr[l]; return e }
function arrLen(arr) { return +arr[-7] }
function die(msg) { print msg; exit 1 }
function dieAtPos(msg) { die(msg " at pos " Pos ": " posStr()) }
function posStr( s) { return esc(s = substr(In,Pos,10)) (10==length(s)?"...":"") }
function esc(s) { gsub(/\n/, "\\n",s); return s }
# --- generate JSON ---
function generateJson( i,instr,wasPrev) {
for (i=0; i<JsonAsmLen; i++) {
if (isComplex(instr = JsonAsm[i])) {
p1(wasPrev, Open[instr] nlIndent(isEnd(JsonAsm[i+1]), Depth+1))
Stack[++Depth]=instr; wasPrev=0 }
else if ("key"==instr) {
p1(wasPrev, JsonAsm[++i] ":" (Indent==0?"":" ")); wasPrev=0 }
else if ("number"==instr || "string"==instr) {
p1(wasPrev, JsonAsm[++i]); wasPrev=1 }
else if (isSingle(instr)) {
p1(wasPrev, instr); wasPrev=1 }
else if (isEnd(instr)) {
if (Stack[Depth] != Opens[instr]) die("end mismatch")
p(nlIndent(isComplex(JsonAsm[i-1]), Depth-1) Close[Stack[Depth--]])
wasPrev=1 }
else { die("Wrong opcode") }
}
print ""
}
function p1(wasPrev,s) { p((wasPrev ? "," nlIndent(0, Depth) : "") s) }
function p(s) { printf "%s", s }
function nlIndent(unless, d, i, s) { if (unless || Indent==0) return ""; for (i=0; i<d; i++) s = s IndentStr; return "\n" s }
# lib
function tryParseExact(s, l) {
if(substr(In,Pos,l=length(s))==s) { Pos += l; return 1 }
return 0
}
function tryParse1(chars, res) { return tryParse(chars,res,1) }
function tryParse(chars, res, atMost, i,c,s) {
s=""
while (index(chars, c = nextChar()) > 0 &&
(atMost==0 || i++ < atMost) &&
c != "") {
s = s c
Pos++
}
res[0] = res[0] s
return s != ""
}
function nextChar() { return substr(In,Pos,1) }
function asm(inst) { Asm[AsmLen++]=inst; return 1 }
# -----
function generateGron(isStructure, i, instr) {
for (i=0; i<AsmLen; i++) {
if (isComplex(instr = Asm[i])) {
printRow(isStructure,"object"==instr?"{}":"[]")
Stack[++Depth]=instr
if (inArr()) { PathStack[Depth]=0 } }
else if (isSingle(instr)) { printRow(isStructure,instr); incArrIdx() }
else if (isEnd(instr)) { Depth--; incArrIdx() }
else if ("key" == instr) { PathStack[Depth]=Asm[++i] }
else if ("number"==instr || "string"==instr) { printRow(isStructure,Asm[++i]); incArrIdx() }
else { print "Error at instruction#" i ": " instr; exit 1 }
}
}
function isSingle(s) { return "true"==s || "false"==s || "null"==s }
function inArr() { return "array"==Stack[Depth] }
function isEnd(s) { return "end_object"==s || "end_array"==s }
function incArrIdx() { if (inArr()) PathStack[Depth]++ }
function printRow(isStructure, v) { isStructure ? printStructure(v) : printGron(v) }
function printStructure(v, row,i,isArr,byIdx,segment) {
row=""
for(i=1; i<=Depth; i++) {
segment = PathStack[i]
byIdx = (isArr="array"==Stack[i]) || segment !~ /^"[a-zA-Z$_][a-zA-Z0-9$_]*"$/
row = row (i==0||isArr?"":".") (isArr ? "[]" : byIdx ? segment : _unqote(segment))
}
if (row in AlreadyTracked) return
AlreadyTracked[row]
if ("{}" == v || "[]" == v) return
row = row " = " v
print row
}
function printGron(v, row,i,byIdx,segment) {
row="json"
for(i=1; i<=Depth; i++) {
segment = PathStack[i]
byIdx = "array"==Stack[i] || segment !~ /^"[a-zA-Z$_][a-zA-Z0-9$_]*"$/
row= row (i==0||byIdx?"":".") (byIdx ? "[" segment "]" : _unqote(segment))
}
row=row "=" v
print row
}
function _unqote(text, l) {
return (l=length(text)) == 2 ? "" : substr(text, 2, l-2)
}
function natOrder(s1,s2, i1,i2, c1, c2, n1,n2) {
if (_digit(c1 = substr(s1,i1,1)) && _digit(c2 = substr(s2,i2,1))) {
n1 = +c1; while(_digit(c1 = substr(s1,++i1,1))) { n1 = n1 * 10 + c1 }
n2 = +c2; while(_digit(c2 = substr(s2,++i2,1))) { n2 = n2 * 10 + c2 }
return n1 == n2 ? natOrder(s1, s2, i1, i2) : _cmp(n1, n2)
}
# consume till equal substrings
while ((c1 = substr(s1,i1,1)) == (c2 = substr(s2,i2,1)) && c1 != "" && !_digit(c1)) {
i1++; i2++
}
return _digit(c1) && _digit(c2) ? natOrder(s1, s2, i1, i2) : _cmp(c1, c2)
}
function _cmp(v1, v2) { return v1 > v2 ? 1 : v1 < v2 ? -1 : 0 }
function _digit(c) { return c >= "0" && c <= "9" }
function quicksort(data, left, right, i, last) {
if (left >= right)
return
_swap(data, left, int((left + right) / 2))
last = left
for (i = left + 1; i <= right; i++)
if (natOrder(data[i], data[left],1,1) < 0)
_swap(data, ++last, i)
_swap(data, left, last)
quicksort(data, left, last - 1)
quicksort(data, last + 1, right)
}
function _swap(data, i, j, temp) {
temp = data[i]
data[i] = data[j]
data[j] = temp
}