diff options
| author | Marc Vertes <mvertes@free.fr> | 2025-07-11 18:08:11 +0200 |
|---|---|---|
| committer | Marc Vertes <mvertes@free.fr> | 2025-07-11 18:08:11 +0200 |
| commit | 43e3a8a08668b068eb07b5c1ee3aaa767021d3c0 (patch) | |
| tree | c509903adbd8143aeb2d6c8e794775a52fc8ea4c | |
| parent | c5cbfacba61a9fba9e1bf3ea6abc4f067298c80f (diff) | |
add format. Improve tests.
| -rwxr-xr-x | mp | 149 | ||||
| -rwxr-xr-x | tests | 19 |
2 files changed, 115 insertions, 53 deletions
@@ -1,7 +1,7 @@ #!/usr/bin/awk -f BEGIN { - version = "2025-07-08" + version = "0.1" help[""] = "Commands:\n" \ " help [cmd] prints cmd help or this help text\n" @@ -16,18 +16,28 @@ BEGIN { if (filename) src = getfile(filename) prompt("mp-" version " meta parser. Type \"help\" for help.\n") } +{ ERROR = "" } $1 == "help" { prompt(help[$2]) next } $1 == "src" { - prompt(src) + printf "%s", src + prompt() + next +} +$1 == "format" { + print format_json(v, "", 1) + prompt() next } $1 == "parse" { delete v - if ($2) parse_json($2, v); else parse_json(src, v) - for (k in v) print "v[" k "]: " v[k] + sub(/^[[:space:]]+/, "") + $0 = substr($0, 6) + if ($0) src = $0 + parse_json(src, v) + print format_json(v) prompt() next } @@ -35,65 +45,112 @@ $1 == "parse" { if (NF) error("invalid command: " $1) prompt() } +END { if (ERROR) exit 1 } -function error(s) { ERROR = s } +function error(s, n) { ERROR = s; return n } function prompt(s) { - if (ERROR) { - print ERROR > "/dev/stderr" - ERROR = "" - } - if (tty) printf("%smp> ", s) + if (ERROR) print ERROR > "/dev/stderr" + if (tty) printf("%s> ", s) } function getfile(name, l, r, o) { while (r = getline l < name) { - if (r == -1) { - error = "getfile " name ": getline error" - break - } + if (r == -1) return error("getfile " name ": getline error") o = o l "\n" } - close(name) return o } -# parse the next json value in s, returns the number of characters read. -function parse_json(s, a, p, c, i, n) { - if (match(s, /^[[:space:]]+/)) { +function next_json_token(str, n) { + n = 0 + TOKEN = TOKSTR = "" + if (match(str, /^[[:space:]]+/)) { n = RLENGTH - s = substr(s, RLENGTH+1) + str = substr(str, RLENGTH+1) } - if (!s) return n - if (match(s, /^null|true|false/)) { - a[p, "type"] = substr(s, 1, RLENGTH) - return n + RLENGTH + if (match(str, /^(null|true|false|[][}{,:])/)) { + TOKEN = substr(str, 1, RLENGTH) + } else if (match(str, /^[.0-9Ee+-]+/)) { + TOKEN = "number" + TOKSTR = substr(str, 1, RLENGTH) + } else if (match(str, /^"(\\.|[^\\"])*"/)) { + TOKEN = "string" + TOKSTR = substr(str, 2, RLENGTH-2) + } else { + TOKSTR = substr(str, 1, 1) + TOKEN = TOKSTR ? "invalid" : "" } - if (match(s, /^[.0-9Ee+-]+/)) { - a[p, "type"] = "number" - a[p, "string"] = substr(s, 1, RLENGTH) - return n + RLENGTH - } - if (match(s, /^"(\\.|[^\\"])*"/)) { - a[p, "type"] = "string" - a[p, "string"] = substr(s, 2, RLENGTH-2) - return n + RLENGTH + return n + RLENGTH +} + +function parse_json(str, arr, pk, i, n) { + n = next_json_token(str) + if (TOKEN == "[") { + arr[pk, "type"] = "array" + for (i = 0; TOKEN != "]"; i++) { + if (i > 0 && TOKEN != ",") return error("not a ','", n) + n += parse_json(substr(str, n+1), arr, pk SUBSEP "value" SUBSEP i) + if (ERROR) { + if (i == 0 && TOKEN == "]") ERROR = "" + break + } + n += next_json_token(substr(str, n+1)) + } + arr[pk, "size"] = i + } else if (TOKEN == "{") { + arr[pk, "type"] = "object" + for (i = 0; TOKEN != "}"; i++) { + if (i > 0 && TOKEN != ",") return error("not a ','", n) + n += next_json_token(substr(str, n+1)) + if (i == 0 && TOKEN == "}") break + if (TOKEN != "string") return error("not a string", n) + arr[pk, "key", i] = TOKSTR + n += next_json_token(substr(str, n+1)) + if (TOKEN != ":") return error("not a ':'", n) + n += parse_json(substr(str, n+1), arr, pk SUBSEP "value" SUBSEP i) + if (ERROR) return n + n += next_json_token(substr(str, n+1)) + } + arr[pk, "size"] = i + } else if (TOKEN ~ /^(null|true|false|string|number)$/) { + arr[pk, "type"] = TOKEN + if (TOKSTR) arr[pk, "string"] = TOKSTR + } else if (TOKEN) ERROR = "invalid token '" TOKEN "'" + return n +} + +function format_json(arr, pk, il, i, l, t, s, bl, al, ps) { + if (il) { + bl = space(il * 2) + al = "\n" + ps = " " } - c = substr(s, 1, 1) - if (c == "{") { - a[p, "type"] = "object" - i = 0 - n++ - s = substr(s, 2) - if (match(s, /^[[:space:]]+/)) { - n += RLENGTH - s = substr(s, RLENGTH+1) + t = arr[pk, "type"] + if (t == "null" || t == "true" || t == "false") return t + if (t == "number") return arr[pk, "string"] + if (t == "string") return "\"" arr[pk, "string"] "\"" + if (t == "object") { + s = "{" al + l = arr[pk, "size"] + for (i = 0; i < l; i++) { + s = s bl "\"" arr[pk, "key", i] "\":" ps format_json(arr, pk SUBSEP "value" SUBSEP i, il ? il+1 : 0) + if (i < l-1) s = s "," al } - if (match(s, /^"(\\.|[^\\"])*"/)) { - a[p, "key", i] = substr(s, 2, RLENGTH-2) - } else { - error("invalid key: " s) - return n + return s al substr(bl, 3) "}" + } + if (t == "array") { + s = "[" al + l = arr[pk, "size"] + for (i = 0; i < arr[pk, "size"]; i++) { + s = s bl format_json(arr, pk SUBSEP "value" SUBSEP i, il ? il+1 : 0) + if (i < l-1) s = s "," al } + return s al substr(bl, 3) "]" } } + +function space(n, i, s) { + for (i = 0; i < n; i++) s = s " " + return s +} @@ -2,8 +2,9 @@ # Test suite for mp. run() { - [ "$filter" ] && case $1 in $filter) ;; *) return; esac - out=$(echo "$2" | ./mp 2>&1 | tr -d '\034' | awk -v ORS='\\n' 1) + # shellcheck disable=SC2254 + [ "$filter" ] && case $1 in ($filter) ;; (*) return; esac + out=$(echo "$2" | ./mp 2>&1) [ "$out" = "$3" ] && pass=$((pass + 1)) && return printf "%s FAIL\nWant: \"%s\"\n Got: \"%s\"\n" "$1" "$3" "$out" fail=$((fail + 1)) @@ -14,8 +15,12 @@ run() { pass=0 fail=0 filter="$1" trap 'echo "$pass passed, $fail failed"; exit $((fail))' EXIT -run test1 'parse null' 'v[type]: null\n' -run test2 'parse true' 'v[type]: true\n' -run test3 'parse false' 'v[type]: false\n' -run test4 'parse 12' 'v[string]: 12\nv[type]: number\n' -run test5 'parse "hello"' 'v[string]: hello\nv[type]: string\n' +run basic1 'parse' '' +run basic2 'parse null' 'null' +run basic3 'parse true' 'true' +run basic4 'parse false' 'false' +run num1 'parse 12' '12' +run str1 'parse "hello"' '"hello"' +run obj1 'parse {}' '{}' +run arr1 'parse []' '[]' + |
