Files
Viper_VRTE/json/__writer.vp
T

158 lines
6.5 KiB
Plaintext

from stdint import *
import memhub
import string
from stdio import snprintf
from . import (
JsonValue, JSON_NULL, JSON_BOOL, JSON_INT, JSON_FLOAT,
JSON_STRING, JSON_ARRAY, JSON_OBJECT,
)
# ============================================================
# Capacity contract (cap is passed explicitly only by the caller; there is no default in the signature)
# write -- three-arg entry point; always uses WRITE_CAP_DEFAULT (4096) as the capacity.
# write_cap -- four-arg entry point; the capacity is passed explicitly by the caller.
# cap = total bytes of the output buffer (including the trailing NUL). Every write first checks capacity:
# writing n bytes requires pos + n <= cap - 1 (the last byte is reserved for the '\0' that write appends).
# if it does not fit, nothing is written and pos is set to the "overflow marker" cap -- from then on any write returns early via
# the `pos >= cap` short-circuit, and the final write returns None when `end_pos >= cap`.
# That is: when the content exceeds the capacity it neither writes through the buffer nor keeps writing (never out of bounds).
# The legal range of cap is [WRITE_CAP_MIN, WRITE_CAP_MAX]: anything out of range always returns None,
# and an illegal capacity does not enter pool.alloc (no memory is requested using a suspicious value).
# ============================================================
define WRITE_CAP_MIN = 64# lower bound: shortest JSON doc (e.g. {"id":""}) incl NUL fits in 64 bytes
define WRITE_CAP_MAX = 67108864# upper bound: 64 MiB, not less than the largest output buffer a caller may request
define WRITE_CAP_DEFAULT = 4096# fixed capacity for the 3-arg write entry
def _write_char(buf: str, pos: sizet, cap: sizet, ch: char) -> sizet:
if pos + 1 >= cap:
if pos >= cap:
return pos # overflow flag already set: return as-is, do not write
return cap # cannot reserve trailing NUL slot => set overflow flag
buf[pos] = ch
return pos + 1
def _write_str(buf: str, pos: sizet, cap: sizet, s: str) -> sizet:
if s == None: return pos
if pos >= cap: return pos # overflow flag already set: stop writing
slen: sizet = string.strlen(s)
if pos + slen >= cap:
return cap # cannot reserve trailing NUL slot => set overflow flag
string.memcpy(buf + pos, s, slen)
return pos + slen
def _write_string_escaped(buf: str, pos: sizet, cap: sizet, s: str) -> sizet:
if s == None:
pos = _write_str(buf, pos, cap, "null")
return pos
pos = _write_char(buf, pos, cap, '"')
if pos >= cap:
return pos # already overflowed: stop writing
slen: sizet = string.strlen(s)
for i in range(int(slen)):
if pos >= cap:
return pos # already overflowed: converge early, skip pointless per-char looping
ch: char = s[i]
if ch == '"':
pos = _write_str(buf, pos, cap, "\\\"")
elif ch == '\\':
pos = _write_str(buf, pos, cap, "\\\\")
elif ch == '\n':
pos = _write_str(buf, pos, cap, "\\n")
elif ch == '\r':
pos = _write_str(buf, pos, cap, "\\r")
elif ch == '\t':
pos = _write_str(buf, pos, cap, "\\t")
else:
pos = _write_char(buf, pos, cap, ch)
pos = _write_char(buf, pos, cap, '"')
return pos
def _write_value(buf: str, pos: sizet, cap: sizet, val: JsonValue*,
pool: memhub.MemManager*) -> sizet:
if pos >= cap:
return pos # already overflowed: stop writing (recursion converges here)
if val == None:
pos = _write_str(buf, pos, cap, "null")
return pos
if val.vtype == JSON_NULL:
pos = _write_str(buf, pos, cap, "null")
elif val.vtype == JSON_BOOL:
if val.bool_val != 0:
pos = _write_str(buf, pos, cap, "true")
else:
pos = _write_str(buf, pos, cap, "false")
elif val.vtype == JSON_INT:
tmp: str = pool.alloc(32)
if tmp != None:
snprintf(tmp, 32, "%lld", val.int_val)
pos = _write_str(buf, pos, cap, tmp)
elif val.vtype == JSON_FLOAT:
tmp2: str = pool.alloc(32)
if tmp2 != None:
snprintf(tmp2, 32, "%.6g", val.float_val)
pos = _write_str(buf, pos, cap, tmp2)
elif val.vtype == JSON_STRING:
pos = _write_string_escaped(buf, pos, cap, val.str_val)
elif val.vtype == JSON_ARRAY:
if val.child == None:
pos = _write_str(buf, pos, cap, "[]")
return pos
pos = _write_char(buf, pos, cap, '[')
cur: JsonValue* = val.child
first: bool = True
while cur != None:
if pos >= cap:
return pos # already overflowed: stop iterating, do not write
if not first:
pos = _write_char(buf, pos, cap, ',')
pos = _write_value(buf, pos, cap, cur, pool)
first = False
cur = cur.next
pos = _write_char(buf, pos, cap, ']')
elif val.vtype == JSON_OBJECT:
if val.child == None:
pos = _write_str(buf, pos, cap, "{}")
return pos
pos = _write_char(buf, pos, cap, '{')
cur2: JsonValue* = val.child
first2: bool = True
while cur2 != None:
if pos >= cap:
return pos # already overflowed: stop iterating, do not write
if not first2:
pos = _write_char(buf, pos, cap, ',')
pos = _write_string_escaped(buf, pos, cap, cur2.key)
pos = _write_char(buf, pos, cap, ':')
pos = _write_value(buf, pos, cap, cur2, pool)
first2 = False
cur2 = cur2.next
pos = _write_char(buf, pos, cap, '}')
return pos
# Serialize to a JSON string. cap = output buffer size (bytes, incl trailing NUL), default 4096.
# Returns None when content does not fit (never writes past the buffer).
def write(pool: memhub.MemManager*, val: JsonValue*, pretty: bool,
cap: sizet = 4096) -> str:
"""Serialize a JsonValue into a JSON string."""
if cap < 2:
return None # not enough room even for the trailing NUL
buf: str = pool.alloc(cap)
if buf == None:
return None
end_pos: sizet = _write_value(buf, 0, cap, val, pool)
if end_pos >= cap:
return None # overflow flag: content exceeds capacity
buf[end_pos] = '\0'
return buf