blob: 80dd2c4cf34d2687bf5accfdc301c665e5b8bf70 [file] [log] [blame]
cdef int _encode_unicode_impl(WriterRef writer, UCSString data, Py_ssize_t length) except -1:
cdef char buf[32]
cdef uint32_t c
cdef uint32_t s1, s2
cdef const char *escaped_string
cdef Py_ssize_t escaped_length
cdef size_t unescaped_length, index
cdef Py_ssize_t sublength
if length > 0:
writer.reserve(writer, 2 + length)
writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
while True:
if UCSString is UCS1String:
sublength = length
else:
sublength = min(length, <Py_ssize_t> sizeof(buf))
unescaped_length = ESCAPE_DCT.find_unescaped_range(data, sublength)
if unescaped_length > 0:
if UCSString is UCS1String:
writer.append_s(writer, <const char*> data, unescaped_length)
else:
for index in range(unescaped_length):
buf[index] = <const char> data[index]
writer.append_s(writer, buf, unescaped_length)
data += unescaped_length
length -= unescaped_length
if length <= 0:
break
if UCSString is not UCS1String:
continue
c = data[0]
if (UCSString is UCS1String) or (c < 0x100):
escaped_length = ESCAPE_DCT.items[c][0]
escaped_string = &ESCAPE_DCT.items[c][1]
writer.append_s(writer, escaped_string, escaped_length)
elif (UCSString is UCS2String) or (c <= 0xffff):
buf[0] = b'\\';
buf[1] = b'u';
buf[2] = HEX[(c >> (4*3)) & 0xf];
buf[3] = HEX[(c >> (4*2)) & 0xf];
buf[4] = HEX[(c >> (4*1)) & 0xf];
buf[5] = HEX[(c >> (4*0)) & 0xf];
buf[6] = 0;
writer.append_s(writer, buf, 6);
else:
# surrogate pair
c -= 0x10000
s1 = 0xd800 | ((c >> 10) & 0x3ff)
s2 = 0xdc00 | (c & 0x3ff)
buf[0x0] = b'\\';
buf[0x1] = b'u';
buf[0x2] = HEX[(s1 >> (4*3)) & 0xf];
buf[0x3] = HEX[(s1 >> (4*2)) & 0xf];
buf[0x4] = HEX[(s1 >> (4*1)) & 0xf];
buf[0x5] = HEX[(s1 >> (4*0)) & 0xf];
buf[0x6] = b'\\';
buf[0x7] = b'u';
buf[0x8] = HEX[(s2 >> (4*3)) & 0xf];
buf[0x9] = HEX[(s2 >> (4*2)) & 0xf];
buf[0xa] = HEX[(s2 >> (4*1)) & 0xf];
buf[0xb] = HEX[(s2 >> (4*0)) & 0xf];
buf[0xc] = 0;
writer.append_s(writer, buf, 12);
data += 1
length -= 1
if length <= 0:
break
writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
else:
writer.append_s(writer, b'""', 2)
return True
cdef int _encode_unicode(WriterRef writer, object data) except -1:
cdef Py_ssize_t length
cdef int kind
PyUnicode_READY(data)
length = PyUnicode_GET_LENGTH(data)
kind = PyUnicode_KIND(data)
if kind == PyUnicode_1BYTE_KIND:
_encode_unicode_impl(writer, PyUnicode_1BYTE_DATA(data), length)
elif kind == PyUnicode_2BYTE_KIND:
_encode_unicode_impl(writer, PyUnicode_2BYTE_DATA(data), length)
elif kind == PyUnicode_4BYTE_KIND:
_encode_unicode_impl(writer, PyUnicode_4BYTE_DATA(data), length)
else:
pass # impossible
return True
cdef int _encode_nested_key(WriterRef writer, object data) except -1:
cdef const char *string
cdef char c
cdef Py_ssize_t index, length
cdef int result
cdef WriterReallocatable sub_writer = WriterReallocatable(
Writer(
_WriterReallocatable_reserve,
_WriterReallocatable_append_c,
_WriterReallocatable_append_s,
writer.options,
),
0, 0, NULL,
)
try:
result = _encode(sub_writer.base, data)
if expect(result < 0, False):
return result
length = sub_writer.position
string = <char*> sub_writer.obj
writer.reserve(writer, 2 + length)
writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
for index in range(length):
c = string[index]
if c in b'\\"':
writer.append_c(writer, b'\\')
writer.append_c(writer, c)
writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
finally:
if sub_writer.obj is not NULL:
ObjectFree(sub_writer.obj)
return True
cdef int _append_ascii(WriterRef writer, object data) except -1:
cdef Py_buffer view
cdef const char *buf
cdef Py_ssize_t index
cdef unsigned char c
if PyUnicode_Check(data):
PyUnicode_READY(data)
if not PyUnicode_IS_ASCII(data):
raise TypeError('Expected ASCII data')
writer.append_s(writer, <const char*> PyUnicode_1BYTE_DATA(data), PyUnicode_GET_LENGTH(data))
else:
PyObject_GetBuffer(data, &view, PyBUF_CONTIG_RO)
try:
buf = <const char*> view.buf
for index in range(view.len):
c = <unsigned char> buf[index]
if c & ~0x7f:
raise TypeError('Expected ASCII data')
writer.append_s(writer, buf, view.len)
finally:
PyBuffer_Release(&view)
return True
cdef int _encode_tojson(WriterRef writer, object data) except -1:
cdef object value = getattr(data, (<Options> writer.options).tojson, None)
if value is None:
return False
if callable(value):
Py_EnterRecursiveCall(' while encoding nested JSON5 object')
try:
value = value()
finally:
Py_LeaveRecursiveCall()
_append_ascii(writer, value)
return True
cdef int _encode_sequence(WriterRef writer, object data) except -1:
cdef boolean first
cdef object iterator
cdef object value
cdef int result
try:
iterator = PyObject_GetIter(data)
except TypeError:
return False
Py_EnterRecursiveCall(' while encoding nested JSON5 object')
try:
writer.append_c(writer, <char> b'[')
first = True
value = None
while iter_next(iterator, &<PyObject*&> value):
if not first:
writer.append_c(writer, <char> b',')
else:
first = False
result = _encode(writer, value)
if expect(result < 0, False):
return result
writer.append_c(writer, <char> b']')
finally:
Py_LeaveRecursiveCall()
return True
cdef int _encode_mapping(WriterRef writer, object data) except -1:
cdef boolean first
cdef object iterator, key, value
cdef int result
if not isinstance(data, (<Options> writer.options).mappingtypes):
return False
iterator = PyObject_GetIter(data)
Py_EnterRecursiveCall(' while encoding nested JSON5 object')
try:
writer.append_c(writer, <char> b'{')
first = True
key = None
while iter_next(iterator, &<PyObject*&> key):
if not first:
writer.append_c(writer, <char> b',')
else:
first = False
value = data[key]
if PyUnicode_Check(key):
_encode_unicode(writer, key)
else:
_encode_nested_key(writer, key)
writer.append_c(writer, <char> b':')
result = _encode(writer, value)
if expect(result < 0, False):
return result
writer.append_c(writer, <char> b'}')
finally:
Py_LeaveRecursiveCall()
return True
cdef int _encode_none(WriterRef writer, object data) except -1:
writer.append_s(writer, b'null', 4)
return True
cdef int _encode_bytes(WriterRef writer, object data) except -1:
_encode_unicode(writer, PyUnicode_FromEncodedObject(data, 'UTF-8', 'strict'))
return True
cdef int _encode_datetime(WriterRef writer, object data) except -1:
cdef object stringified
cdef Py_ssize_t length
cdef const char *string
if not isinstance(data, DATETIME_CLASSES):
return False
stringified = data.isoformat()
length = 0
string = PyUnicode_AsUTF8AndSize(stringified, &length)
writer.reserve(writer, 2 + length)
writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
writer.append_s(writer, string, length)
writer.append_c(writer, <char> PyUnicode_1BYTE_DATA((<Options> writer.options).quotationmark)[0])
return True
cdef int _encode_format_string(WriterRef writer, object data, object fmt) except -1:
cdef object formatted
cdef const char *string
cdef Py_ssize_t length = 0 # silence warning
formatted = PyUnicode_Format(fmt, data)
string = PyUnicode_AsUTF8AndSize(formatted, &length)
writer.append_s(writer, string, length)
return True
cdef int _encode_float(WriterRef writer, object data) except -1:
cdef double value = PyFloat_AsDouble(data)
cdef int classification = fpclassify(value)
cdef char buf[64]
cdef char *end
cdef char *string
cdef Py_ssize_t length
if classification == FP_NORMAL:
end = Dtoa(buf, PyFloat_AsDouble(data))
length = end - buf
string = buf
elif classification in (FP_SUBNORMAL, FP_ZERO):
string = b'0.0'
length = 3
elif classification == FP_NAN:
string = b'NaN'
length = 3
else:
# classification == FP_INFINITE
if value > 0.0:
string = b'Infinity'
length = 8
else:
string = b'-Infinity'
length = 9
writer.append_s(writer, string, length)
return True
cdef int _encode_long(WriterRef writer, object data) except -1:
if PyBool_Check(data):
if data is True:
writer.append_s(writer, 'true', 4)
else:
writer.append_s(writer, 'false', 5)
else:
_encode_format_string(writer, data, DEFAULT_INTFORMAT)
return True
cdef int _encode_decimal(WriterRef writer, object data) except -1:
if not isinstance(data, Decimal):
return False
_encode_format_string(writer, data, DEFAULT_DECIMALFORMAT)
return True
cdef int _encode_unstringifiable(WriterRef writer, object data) except -1:
if expect(not data, True):
writer.append_s(writer, b'none', 4)
else:
_raise_unstringifiable(data)
return True
cdef int _encode_other(WriterRef writer, object data):
cdef int result = 0
while True:
if (<Options> writer.options).tojson is not None:
result = (<int(*)(WriterRef, object)> _encode_tojson)(writer, data)
if result != 0:
break
if obj_has_iter(data):
result = (<int(*)(WriterRef, object)> _encode_mapping)(writer, data)
if result != 0:
break
result = (<int(*)(WriterRef, object)> _encode_sequence)(writer, data)
if result != 0:
break
result = (<int(*)(WriterRef, object)> _encode_decimal)(writer, data)
if result != 0:
break
result = (<int(*)(WriterRef, object)> _encode_datetime)(writer, data)
if result != 0:
break
result = (<int(*)(WriterRef, object)> _encode_unstringifiable)(writer, data)
if result != 0:
break
break
return result
cdef int _encode(WriterRef writer, object data):
cdef int (*encoder)(WriterRef, object)
if data is None:
encoder = <int(*)(WriterRef, object)> _encode_none
elif PyUnicode_Check(data):
encoder = <int(*)(WriterRef, object)> _encode_unicode
elif PyLong_Check(data):
encoder = <int(*)(WriterRef, object)> _encode_long
elif PyFloat_Check(data):
encoder = <int(*)(WriterRef, object)> _encode_float
elif PyBytes_Check(data):
encoder = <int(*)(WriterRef, object)> _encode_bytes
else:
encoder = <int(*)(WriterRef, object)> _encode_other
return encoder(writer, data)
cdef int _encode_callback_bytes(object data, object cb, object options) except -1:
cdef WriterCallback writer = WriterCallback(
Writer(
_WriterNoop_reserve,
_WriterCbBytes_append_c,
_WriterCbBytes_append_s,
<PyObject*> options,
),
<PyObject*> cb,
)
if expect(not callable(cb), False):
raise TypeError(f'type(cb)=={type(cb)!r} is not callable')
return _encode(writer.base, data)
cdef int _encode_callback_str(object data, object cb, object options) except -1:
cdef WriterCallback writer = WriterCallback(
Writer(
_WriterNoop_reserve,
_WriterCbStr_append_c,
_WriterCbStr_append_s,
<PyObject*> options,
),
<PyObject*> cb,
)
if expect(not callable(cb), False):
raise TypeError(f'type(cb)=={type(cb)!r} is not callable')
return _encode(writer.base, data)