koda-format 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/SPEC.md +288 -0
- package/binding.gyp +29 -0
- package/dist/ast.d.ts +23 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +24 -0
- package/dist/ast.js.map +1 -0
- package/dist/decoder.d.ts +17 -0
- package/dist/decoder.d.ts.map +1 -0
- package/dist/decoder.js +147 -0
- package/dist/decoder.js.map +1 -0
- package/dist/encoder.d.ts +14 -0
- package/dist/encoder.d.ts.map +1 -0
- package/dist/encoder.js +168 -0
- package/dist/encoder.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +52 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +114 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +33 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +340 -0
- package/dist/lexer.js.map +1 -0
- package/dist/native.d.ts +25 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +32 -0
- package/dist/native.js.map +1 -0
- package/dist/parseFast.d.ts +11 -0
- package/dist/parseFast.d.ts.map +1 -0
- package/dist/parseFast.js +383 -0
- package/dist/parseFast.js.map +1 -0
- package/dist/parser.d.ts +13 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +146 -0
- package/dist/parser.js.map +1 -0
- package/dist/stringify.d.ts +16 -0
- package/dist/stringify.d.ts.map +1 -0
- package/dist/stringify.js +88 -0
- package/dist/stringify.js.map +1 -0
- package/native/binding.cc +174 -0
- package/native/koda_binary.cc +256 -0
- package/native/koda_binary.h +36 -0
- package/native/koda_parse.cc +384 -0
- package/native/koda_parse.h +18 -0
- package/native/koda_value.h +60 -0
- package/package.json +48 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KODA value to text (.koda). Produces compact, human-readable output.
|
|
3
|
+
* Keys that are valid identifiers are emitted unquoted; strings quoted when necessary.
|
|
4
|
+
*/
|
|
5
|
+
function needsQuote(s) {
|
|
6
|
+
if (s.length === 0)
|
|
7
|
+
return true;
|
|
8
|
+
const first = s[0];
|
|
9
|
+
if (!/[\w_]/.test(first) && first !== '_')
|
|
10
|
+
return true;
|
|
11
|
+
for (let i = 1; i < s.length; i++) {
|
|
12
|
+
if (!/[\w\-_]/.test(s[i]))
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
const lower = s.toLowerCase();
|
|
16
|
+
if (lower === 'true' || lower === 'false' || lower === 'null')
|
|
17
|
+
return true;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function escapeDouble(s) {
|
|
21
|
+
return s
|
|
22
|
+
.replace(/\\/g, '\\\\')
|
|
23
|
+
.replace(/"/g, '\\"')
|
|
24
|
+
.replace(/\b/g, '\\b')
|
|
25
|
+
.replace(/\f/g, '\\f')
|
|
26
|
+
.replace(/\n/g, '\\n')
|
|
27
|
+
.replace(/\r/g, '\\r')
|
|
28
|
+
.replace(/\t/g, '\\t');
|
|
29
|
+
}
|
|
30
|
+
function quoteKey(key) {
|
|
31
|
+
if (needsQuote(key))
|
|
32
|
+
return `"${escapeDouble(key)}"`;
|
|
33
|
+
return key;
|
|
34
|
+
}
|
|
35
|
+
function quoteValueString(s) {
|
|
36
|
+
if (needsQuote(s))
|
|
37
|
+
return `"${escapeDouble(s)}"`;
|
|
38
|
+
return s;
|
|
39
|
+
}
|
|
40
|
+
function writeNumber(n) {
|
|
41
|
+
if (Number.isInteger(n) && n >= Number.MIN_SAFE_INTEGER && n <= Number.MAX_SAFE_INTEGER) {
|
|
42
|
+
return String(n);
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(n);
|
|
45
|
+
}
|
|
46
|
+
function stringifyValue(value, indent, newline, level) {
|
|
47
|
+
if (value === null)
|
|
48
|
+
return 'null';
|
|
49
|
+
if (value === true)
|
|
50
|
+
return 'true';
|
|
51
|
+
if (value === false)
|
|
52
|
+
return 'false';
|
|
53
|
+
if (typeof value === 'number')
|
|
54
|
+
return writeNumber(value);
|
|
55
|
+
if (typeof value === 'string')
|
|
56
|
+
return quoteValueString(value);
|
|
57
|
+
if (Array.isArray(value)) {
|
|
58
|
+
if (value.length === 0)
|
|
59
|
+
return '[]';
|
|
60
|
+
const nextPrefix = indent.repeat(level + 1);
|
|
61
|
+
const sep = indent ? newline + nextPrefix : ' ';
|
|
62
|
+
const inner = value
|
|
63
|
+
.map((v) => stringifyValue(v, indent, newline, level + 1))
|
|
64
|
+
.join(sep);
|
|
65
|
+
return `[${sep}${inner} ]`;
|
|
66
|
+
}
|
|
67
|
+
const obj = value;
|
|
68
|
+
const keys = Object.keys(obj);
|
|
69
|
+
if (keys.length === 0)
|
|
70
|
+
return '{}';
|
|
71
|
+
const nextPrefix = indent.repeat(level + 1);
|
|
72
|
+
const sep = indent ? newline + nextPrefix : ' ';
|
|
73
|
+
const pairs = keys.map((k) => {
|
|
74
|
+
const keyPart = quoteKey(k) + ':';
|
|
75
|
+
const valuePart = stringifyValue(obj[k], indent, newline, level + 1);
|
|
76
|
+
return `${keyPart} ${valuePart}`;
|
|
77
|
+
});
|
|
78
|
+
return `{${sep}${pairs.join(sep)} }`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Serialize a KODA value to text format.
|
|
82
|
+
*/
|
|
83
|
+
export function stringify(value, options = {}) {
|
|
84
|
+
const indent = options.indent ?? '';
|
|
85
|
+
const newline = options.newline ?? '\n';
|
|
86
|
+
return stringifyValue(value, indent, newline, 0).trim();
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=stringify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stringify.js","sourceRoot":"","sources":["../src/stringify.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,SAAS,UAAU,CAAC,CAAS;IAC3B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;YAAE,OAAO,IAAI,CAAC;IAC1C,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9B,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAC3E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC;SACL,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;IACrD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAS;IACjC,IAAI,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC;IACjD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,gBAAgB,IAAI,CAAC,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;QACxF,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,cAAc,CACrB,KAAgB,EAChB,MAAc,EACd,OAAe,EACf,KAAa;IAEb,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,OAAO,CAAC;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;QAChD,MAAM,KAAK,GAAG,KAAK;aAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;aACzD,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,OAAO,IAAI,GAAG,GAAG,KAAK,IAAI,CAAC;IAC7B,CAAC;IACD,MAAM,GAAG,GAAG,KAAkC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QAClC,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACtE,OAAO,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,KAAgB,EAAE,UAA4B,EAAE;IACxE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC;IACxC,OAAO,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
#include <node_api.h>
|
|
3
|
+
|
|
4
|
+
#include "koda_binary.h"
|
|
5
|
+
#include "koda_parse.h"
|
|
6
|
+
#include "koda_value.h"
|
|
7
|
+
|
|
8
|
+
namespace koda {
|
|
9
|
+
|
|
10
|
+
static Napi::Value ValueToNapi(const Value& v, const Napi::Env& env) {
|
|
11
|
+
switch (v.type) {
|
|
12
|
+
case Value::Type::Null:
|
|
13
|
+
return env.Null();
|
|
14
|
+
case Value::Type::Bool:
|
|
15
|
+
return Napi::Boolean::New(env, v.b);
|
|
16
|
+
case Value::Type::Int:
|
|
17
|
+
return Napi::Number::New(env, static_cast<double>(v.i));
|
|
18
|
+
case Value::Type::Float:
|
|
19
|
+
return Napi::Number::New(env, v.d);
|
|
20
|
+
case Value::Type::String:
|
|
21
|
+
return Napi::String::New(env, v.s);
|
|
22
|
+
case Value::Type::Array: {
|
|
23
|
+
Napi::Array arr = Napi::Array::New(env, v.arr.size());
|
|
24
|
+
for (size_t i = 0; i < v.arr.size(); ++i)
|
|
25
|
+
arr[static_cast<uint32_t>(i)] = ValueToNapi(v.arr[i], env);
|
|
26
|
+
return arr;
|
|
27
|
+
}
|
|
28
|
+
case Value::Type::Object: {
|
|
29
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
30
|
+
for (const auto& p : v.obj)
|
|
31
|
+
obj.Set(p.first, ValueToNapi(p.second, env));
|
|
32
|
+
return obj;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return env.Null();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static Value NapiToValue(const Napi::Value& val) {
|
|
39
|
+
if (val.IsNull() || val.IsUndefined()) return Value::null_val();
|
|
40
|
+
if (val.IsBoolean()) return Value::bool_val(val.As<Napi::Boolean>().Value());
|
|
41
|
+
if (val.IsNumber()) {
|
|
42
|
+
Napi::Number n = val.As<Napi::Number>();
|
|
43
|
+
double d = n.DoubleValue();
|
|
44
|
+
if (d >= -9007199254740992.0 && d <= 9007199254740992.0) {
|
|
45
|
+
int64_t i = static_cast<int64_t>(d);
|
|
46
|
+
if (static_cast<double>(i) == d) return Value::int_val(i);
|
|
47
|
+
}
|
|
48
|
+
return Value::float_val(d);
|
|
49
|
+
}
|
|
50
|
+
if (val.IsString()) return Value::string_val(val.As<Napi::String>().Utf8Value());
|
|
51
|
+
if (val.IsArray()) {
|
|
52
|
+
Value v;
|
|
53
|
+
v.type = Value::Type::Array;
|
|
54
|
+
Napi::Array arr = val.As<Napi::Array>();
|
|
55
|
+
for (uint32_t i = 0; i < arr.Length(); ++i)
|
|
56
|
+
v.arr.push_back(NapiToValue(arr[i]));
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
if (val.IsObject()) {
|
|
60
|
+
Value v;
|
|
61
|
+
v.type = Value::Type::Object;
|
|
62
|
+
Napi::Object obj = val.As<Napi::Object>();
|
|
63
|
+
Napi::Array keys = obj.GetPropertyNames();
|
|
64
|
+
for (uint32_t i = 0; i < keys.Length(); ++i) {
|
|
65
|
+
Napi::Value k = keys[i];
|
|
66
|
+
std::string key = k.As<Napi::String>().Utf8Value();
|
|
67
|
+
v.obj.emplace_back(key, NapiToValue(obj.Get(key)));
|
|
68
|
+
}
|
|
69
|
+
return v;
|
|
70
|
+
}
|
|
71
|
+
return Value::null_val();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static Napi::Value NativeParse(const Napi::CallbackInfo& info) {
|
|
75
|
+
Napi::Env env = info.Env();
|
|
76
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
77
|
+
Napi::TypeError::New(env, "Expected string").ThrowAsJavaScriptException();
|
|
78
|
+
return env.Null();
|
|
79
|
+
}
|
|
80
|
+
std::string text = info[0].As<Napi::String>().Utf8Value();
|
|
81
|
+
size_t max_depth = 256;
|
|
82
|
+
if (info.Length() >= 2 && info[1].IsObject()) {
|
|
83
|
+
Napi::Object opts = info[1].As<Napi::Object>();
|
|
84
|
+
if (opts.Has("maxDepth")) {
|
|
85
|
+
Napi::Value v = opts.Get("maxDepth");
|
|
86
|
+
if (v.IsNumber()) max_depth = static_cast<size_t>(v.As<Napi::Number>().Uint32Value());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
Value v = parse(text, max_depth);
|
|
91
|
+
return ValueToNapi(v, env);
|
|
92
|
+
} catch (const std::exception& e) {
|
|
93
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
94
|
+
return env.Null();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static Napi::Value NativeStringify(const Napi::CallbackInfo& info) {
|
|
99
|
+
Napi::Env env = info.Env();
|
|
100
|
+
if (info.Length() < 1) {
|
|
101
|
+
Napi::TypeError::New(env, "Expected value").ThrowAsJavaScriptException();
|
|
102
|
+
return env.Null();
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
Value v = NapiToValue(info[0]);
|
|
106
|
+
return Napi::String::New(env, stringify(v));
|
|
107
|
+
} catch (const std::exception& e) {
|
|
108
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
109
|
+
return env.Null();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static Napi::Value NativeEncode(const Napi::CallbackInfo& info) {
|
|
114
|
+
Napi::Env env = info.Env();
|
|
115
|
+
if (info.Length() < 1) {
|
|
116
|
+
Napi::TypeError::New(env, "Expected value").ThrowAsJavaScriptException();
|
|
117
|
+
return env.Null();
|
|
118
|
+
}
|
|
119
|
+
size_t max_depth = 256;
|
|
120
|
+
if (info.Length() >= 2 && info[1].IsObject()) {
|
|
121
|
+
Napi::Object opts = info[1].As<Napi::Object>();
|
|
122
|
+
if (opts.Has("maxDepth") && opts.Get("maxDepth").IsNumber())
|
|
123
|
+
max_depth = static_cast<size_t>(opts.Get("maxDepth").As<Napi::Number>().Uint32Value());
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
Value v = NapiToValue(info[0]);
|
|
127
|
+
std::vector<uint8_t> buf = encode(v, max_depth);
|
|
128
|
+
Napi::Buffer<uint8_t> out = Napi::Buffer<uint8_t>::Copy(env, buf.data(), buf.size());
|
|
129
|
+
return out;
|
|
130
|
+
} catch (const std::exception& e) {
|
|
131
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
132
|
+
return env.Null();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static Napi::Value NativeDecode(const Napi::CallbackInfo& info) {
|
|
137
|
+
Napi::Env env = info.Env();
|
|
138
|
+
if (info.Length() < 1 || !info[0].IsBuffer()) {
|
|
139
|
+
Napi::TypeError::New(env, "Expected Buffer").ThrowAsJavaScriptException();
|
|
140
|
+
return env.Null();
|
|
141
|
+
}
|
|
142
|
+
Napi::Buffer<uint8_t> buf = info[0].As<Napi::Buffer<uint8_t>>();
|
|
143
|
+
size_t max_depth = 256;
|
|
144
|
+
size_t max_dict = 65536;
|
|
145
|
+
size_t max_str = 1000000;
|
|
146
|
+
if (info.Length() >= 2 && info[1].IsObject()) {
|
|
147
|
+
Napi::Object opts = info[1].As<Napi::Object>();
|
|
148
|
+
if (opts.Has("maxDepth") && opts.Get("maxDepth").IsNumber())
|
|
149
|
+
max_depth = static_cast<size_t>(opts.Get("maxDepth").As<Napi::Number>().Uint32Value());
|
|
150
|
+
if (opts.Has("maxDictionarySize") && opts.Get("maxDictionarySize").IsNumber())
|
|
151
|
+
max_dict = static_cast<size_t>(opts.Get("maxDictionarySize").As<Napi::Number>().Uint32Value());
|
|
152
|
+
if (opts.Has("maxStringLength") && opts.Get("maxStringLength").IsNumber())
|
|
153
|
+
max_str = static_cast<size_t>(opts.Get("maxStringLength").As<Napi::Number>().Uint32Value());
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
Value v = decode(buf.Data(), buf.ByteLength(), max_depth, max_dict, max_str);
|
|
157
|
+
return ValueToNapi(v, env);
|
|
158
|
+
} catch (const std::exception& e) {
|
|
159
|
+
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
|
|
160
|
+
return env.Null();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
} // namespace koda
|
|
165
|
+
|
|
166
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
167
|
+
exports.Set("parse", Napi::Function::New(env, koda::NativeParse));
|
|
168
|
+
exports.Set("stringify", Napi::Function::New(env, koda::NativeStringify));
|
|
169
|
+
exports.Set("encode", Napi::Function::New(env, koda::NativeEncode));
|
|
170
|
+
exports.Set("decode", Napi::Function::New(env, koda::NativeDecode));
|
|
171
|
+
return exports;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
NODE_API_MODULE(koda_format, Init)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#include "koda_binary.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
#include <set>
|
|
6
|
+
|
|
7
|
+
namespace koda {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
void collect_keys(const Value& v, std::set<std::string>& out) {
|
|
12
|
+
switch (v.type) {
|
|
13
|
+
case Value::Type::Null:
|
|
14
|
+
case Value::Type::Bool:
|
|
15
|
+
case Value::Type::Int:
|
|
16
|
+
case Value::Type::Float:
|
|
17
|
+
case Value::Type::String:
|
|
18
|
+
break;
|
|
19
|
+
case Value::Type::Array:
|
|
20
|
+
for (const auto& el : v.arr) collect_keys(el, out);
|
|
21
|
+
break;
|
|
22
|
+
case Value::Type::Object:
|
|
23
|
+
for (const auto& p : v.obj) {
|
|
24
|
+
out.insert(p.first);
|
|
25
|
+
collect_keys(p.second, out);
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
struct Encoder {
|
|
32
|
+
std::vector<uint8_t> buf;
|
|
33
|
+
size_t max_depth;
|
|
34
|
+
std::vector<std::string> dictionary;
|
|
35
|
+
std::map<std::string, size_t> key_to_index;
|
|
36
|
+
|
|
37
|
+
void u8(uint8_t x) { buf.push_back(x); }
|
|
38
|
+
void u32_be(uint32_t x) {
|
|
39
|
+
buf.push_back((x >> 24) & 0xFF);
|
|
40
|
+
buf.push_back((x >> 16) & 0xFF);
|
|
41
|
+
buf.push_back((x >> 8) & 0xFF);
|
|
42
|
+
buf.push_back(x & 0xFF);
|
|
43
|
+
}
|
|
44
|
+
void i64_be(int64_t x) {
|
|
45
|
+
for (int i = 7; i >= 0; --i) buf.push_back((x >> (i * 8)) & 0xFF);
|
|
46
|
+
}
|
|
47
|
+
void f64_be(double x) {
|
|
48
|
+
uint64_t u;
|
|
49
|
+
memcpy(&u, &x, 8);
|
|
50
|
+
for (int i = 7; i >= 0; --i) buf.push_back((u >> (i * 8)) & 0xFF);
|
|
51
|
+
}
|
|
52
|
+
void bytes(const uint8_t* p, size_t n) {
|
|
53
|
+
for (size_t i = 0; i < n; ++i) buf.push_back(p[i]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
void encode_value(const Value& v, size_t depth) {
|
|
57
|
+
if (depth > max_depth) throw std::runtime_error("Maximum nesting depth exceeded");
|
|
58
|
+
switch (v.type) {
|
|
59
|
+
case Value::Type::Null:
|
|
60
|
+
u8(TAG_NULL);
|
|
61
|
+
break;
|
|
62
|
+
case Value::Type::Bool:
|
|
63
|
+
u8(v.b ? TAG_TRUE : TAG_FALSE);
|
|
64
|
+
break;
|
|
65
|
+
case Value::Type::Int:
|
|
66
|
+
u8(TAG_INTEGER);
|
|
67
|
+
i64_be(v.i);
|
|
68
|
+
break;
|
|
69
|
+
case Value::Type::Float:
|
|
70
|
+
u8(TAG_FLOAT);
|
|
71
|
+
f64_be(v.d);
|
|
72
|
+
break;
|
|
73
|
+
case Value::Type::String:
|
|
74
|
+
u8(TAG_STRING);
|
|
75
|
+
u32_be(static_cast<uint32_t>(v.s.size()));
|
|
76
|
+
bytes(reinterpret_cast<const uint8_t*>(v.s.data()), v.s.size());
|
|
77
|
+
break;
|
|
78
|
+
case Value::Type::Array:
|
|
79
|
+
u8(TAG_ARRAY);
|
|
80
|
+
u32_be(static_cast<uint32_t>(v.arr.size()));
|
|
81
|
+
for (const auto& el : v.arr) encode_value(el, depth + 1);
|
|
82
|
+
break;
|
|
83
|
+
case Value::Type::Object: {
|
|
84
|
+
u8(TAG_OBJECT);
|
|
85
|
+
std::vector<std::pair<std::string, Value>> sorted = v.obj;
|
|
86
|
+
std::sort(sorted.begin(), sorted.end(),
|
|
87
|
+
[](const auto& a, const auto& b) { return a.first < b.first; });
|
|
88
|
+
u32_be(static_cast<uint32_t>(sorted.size()));
|
|
89
|
+
for (const auto& p : sorted) {
|
|
90
|
+
auto it = key_to_index.find(p.first);
|
|
91
|
+
if (it == key_to_index.end()) throw std::runtime_error("Key not in dictionary");
|
|
92
|
+
u32_be(static_cast<uint32_t>(it->second));
|
|
93
|
+
encode_value(p.second, depth + 1);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
} // namespace
|
|
102
|
+
|
|
103
|
+
std::vector<uint8_t> encode(const Value& value, size_t max_depth) {
|
|
104
|
+
std::set<std::string> keys_set;
|
|
105
|
+
collect_keys(value, keys_set);
|
|
106
|
+
std::vector<std::string> dictionary(keys_set.begin(), keys_set.end());
|
|
107
|
+
std::map<std::string, size_t> key_to_index;
|
|
108
|
+
for (size_t i = 0; i < dictionary.size(); ++i) key_to_index[dictionary[i]] = i;
|
|
109
|
+
|
|
110
|
+
Encoder enc;
|
|
111
|
+
enc.max_depth = max_depth;
|
|
112
|
+
enc.dictionary = std::move(dictionary);
|
|
113
|
+
enc.key_to_index = std::move(key_to_index);
|
|
114
|
+
|
|
115
|
+
enc.bytes(MAGIC, 4);
|
|
116
|
+
enc.u8(VERSION);
|
|
117
|
+
enc.u32_be(static_cast<uint32_t>(enc.dictionary.size()));
|
|
118
|
+
for (const auto& k : enc.dictionary) {
|
|
119
|
+
enc.u32_be(static_cast<uint32_t>(k.size()));
|
|
120
|
+
enc.bytes(reinterpret_cast<const uint8_t*>(k.data()), k.size());
|
|
121
|
+
}
|
|
122
|
+
enc.encode_value(value, 0);
|
|
123
|
+
return enc.buf;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
namespace {
|
|
127
|
+
|
|
128
|
+
struct Decoder {
|
|
129
|
+
const uint8_t* data;
|
|
130
|
+
size_t size;
|
|
131
|
+
size_t offset = 0;
|
|
132
|
+
size_t max_depth;
|
|
133
|
+
size_t max_dict;
|
|
134
|
+
size_t max_str;
|
|
135
|
+
std::vector<std::string> dictionary;
|
|
136
|
+
|
|
137
|
+
void ensure(size_t n) {
|
|
138
|
+
if (offset + n > size) throw std::runtime_error("Truncated input");
|
|
139
|
+
}
|
|
140
|
+
uint8_t u8() {
|
|
141
|
+
ensure(1);
|
|
142
|
+
return data[offset++];
|
|
143
|
+
}
|
|
144
|
+
uint32_t u32_be() {
|
|
145
|
+
ensure(4);
|
|
146
|
+
uint32_t x = (static_cast<uint32_t>(data[offset]) << 24) |
|
|
147
|
+
(static_cast<uint32_t>(data[offset + 1]) << 16) |
|
|
148
|
+
(static_cast<uint32_t>(data[offset + 2]) << 8) |
|
|
149
|
+
data[offset + 3];
|
|
150
|
+
offset += 4;
|
|
151
|
+
return x;
|
|
152
|
+
}
|
|
153
|
+
int64_t i64_be() {
|
|
154
|
+
ensure(8);
|
|
155
|
+
uint64_t u = 0;
|
|
156
|
+
for (int i = 0; i < 8; ++i) u = (u << 8) | data[offset + i];
|
|
157
|
+
offset += 8;
|
|
158
|
+
int64_t x;
|
|
159
|
+
memcpy(&x, &u, 8);
|
|
160
|
+
return x;
|
|
161
|
+
}
|
|
162
|
+
double f64_be() {
|
|
163
|
+
ensure(8);
|
|
164
|
+
uint64_t u = 0;
|
|
165
|
+
for (int i = 0; i < 8; ++i) u = (u << 8) | data[offset + i];
|
|
166
|
+
offset += 8;
|
|
167
|
+
double x;
|
|
168
|
+
memcpy(&x, &u, 8);
|
|
169
|
+
return x;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Value decode_value(size_t depth) {
|
|
173
|
+
if (depth > max_depth) throw std::runtime_error("Maximum nesting depth exceeded");
|
|
174
|
+
ensure(1);
|
|
175
|
+
uint8_t tag = u8();
|
|
176
|
+
switch (tag) {
|
|
177
|
+
case TAG_NULL:
|
|
178
|
+
return Value::null_val();
|
|
179
|
+
case TAG_FALSE:
|
|
180
|
+
return Value::bool_val(false);
|
|
181
|
+
case TAG_TRUE:
|
|
182
|
+
return Value::bool_val(true);
|
|
183
|
+
case TAG_INTEGER:
|
|
184
|
+
return Value::int_val(i64_be());
|
|
185
|
+
case TAG_FLOAT:
|
|
186
|
+
return Value::float_val(f64_be());
|
|
187
|
+
case TAG_STRING: {
|
|
188
|
+
uint32_t len = u32_be();
|
|
189
|
+
if (len > max_str) throw std::runtime_error("String too long");
|
|
190
|
+
ensure(len);
|
|
191
|
+
std::string s(reinterpret_cast<const char*>(data + offset), len);
|
|
192
|
+
offset += len;
|
|
193
|
+
return Value::string_val(std::move(s));
|
|
194
|
+
}
|
|
195
|
+
case TAG_BINARY:
|
|
196
|
+
throw std::runtime_error("Binary type not supported");
|
|
197
|
+
case TAG_ARRAY: {
|
|
198
|
+
Value v;
|
|
199
|
+
v.type = Value::Type::Array;
|
|
200
|
+
uint32_t n = u32_be();
|
|
201
|
+
v.arr.reserve(n);
|
|
202
|
+
for (uint32_t i = 0; i < n; ++i) v.arr.push_back(decode_value(depth + 1));
|
|
203
|
+
return v;
|
|
204
|
+
}
|
|
205
|
+
case TAG_OBJECT: {
|
|
206
|
+
Value v;
|
|
207
|
+
v.type = Value::Type::Object;
|
|
208
|
+
uint32_t n = u32_be();
|
|
209
|
+
for (uint32_t i = 0; i < n; ++i) {
|
|
210
|
+
uint32_t idx = u32_be();
|
|
211
|
+
if (idx >= dictionary.size()) throw std::runtime_error("Invalid key index");
|
|
212
|
+
v.obj.emplace_back(dictionary[idx], decode_value(depth + 1));
|
|
213
|
+
}
|
|
214
|
+
return v;
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
throw std::runtime_error("Unknown type tag");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
} // namespace
|
|
223
|
+
|
|
224
|
+
Value decode(const uint8_t* data, size_t size, size_t max_depth, size_t max_dict,
|
|
225
|
+
size_t max_str_len) {
|
|
226
|
+
Decoder dec;
|
|
227
|
+
dec.data = data;
|
|
228
|
+
dec.size = size;
|
|
229
|
+
dec.max_depth = max_depth;
|
|
230
|
+
dec.max_dict = max_dict;
|
|
231
|
+
dec.max_str = max_str_len;
|
|
232
|
+
|
|
233
|
+
dec.ensure(5);
|
|
234
|
+
for (int i = 0; i < 4; ++i)
|
|
235
|
+
if (dec.data[i] != MAGIC[i]) throw std::runtime_error("Invalid magic number");
|
|
236
|
+
dec.offset = 4;
|
|
237
|
+
uint8_t version = dec.u8();
|
|
238
|
+
if (version != VERSION) throw std::runtime_error("Unsupported version");
|
|
239
|
+
|
|
240
|
+
uint32_t dict_len = dec.u32_be();
|
|
241
|
+
if (dict_len > max_dict) throw std::runtime_error("Dictionary too large");
|
|
242
|
+
dec.dictionary.reserve(dict_len);
|
|
243
|
+
for (uint32_t i = 0; i < dict_len; ++i) {
|
|
244
|
+
uint32_t key_len = dec.u32_be();
|
|
245
|
+
if (key_len > max_str_len) throw std::runtime_error("Key string too long");
|
|
246
|
+
dec.ensure(key_len);
|
|
247
|
+
dec.dictionary.emplace_back(reinterpret_cast<const char*>(dec.data + dec.offset), key_len);
|
|
248
|
+
dec.offset += key_len;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Value v = dec.decode_value(0);
|
|
252
|
+
if (dec.offset != size) throw std::runtime_error("Trailing bytes after root value");
|
|
253
|
+
return v;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
} // namespace koda
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#ifndef KODA_BINARY_H
|
|
2
|
+
#define KODA_BINARY_H
|
|
3
|
+
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include "koda_value.h"
|
|
10
|
+
|
|
11
|
+
namespace koda {
|
|
12
|
+
|
|
13
|
+
// Binary format constants (SPEC §6)
|
|
14
|
+
constexpr uint8_t MAGIC[] = {0x4B, 0x4F, 0x44, 0x41};
|
|
15
|
+
constexpr uint8_t VERSION = 1;
|
|
16
|
+
|
|
17
|
+
constexpr uint8_t TAG_NULL = 0x01;
|
|
18
|
+
constexpr uint8_t TAG_FALSE = 0x02;
|
|
19
|
+
constexpr uint8_t TAG_TRUE = 0x03;
|
|
20
|
+
constexpr uint8_t TAG_INTEGER = 0x04;
|
|
21
|
+
constexpr uint8_t TAG_FLOAT = 0x05;
|
|
22
|
+
constexpr uint8_t TAG_STRING = 0x06;
|
|
23
|
+
constexpr uint8_t TAG_BINARY = 0x07;
|
|
24
|
+
constexpr uint8_t TAG_ARRAY = 0x10;
|
|
25
|
+
constexpr uint8_t TAG_OBJECT = 0x11;
|
|
26
|
+
|
|
27
|
+
// Encode value to canonical binary. Throws std::runtime_error on depth exceed.
|
|
28
|
+
std::vector<uint8_t> encode(const Value& value, size_t max_depth = 256);
|
|
29
|
+
|
|
30
|
+
// Decode binary to value. Throws std::runtime_error on invalid input.
|
|
31
|
+
Value decode(const uint8_t* data, size_t size, size_t max_depth = 256,
|
|
32
|
+
size_t max_dict = 65536, size_t max_str_len = 1000000);
|
|
33
|
+
|
|
34
|
+
} // namespace koda
|
|
35
|
+
|
|
36
|
+
#endif
|