react-native-update 10.50.0 → 10.51.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/NATIVE_CHECKUPDATE_DESIGN.md +251 -21
- package/NATIVE_CHECK_FOLLOWUPS.md +365 -0
- package/README-CN.md +14 -1
- package/README.md +14 -1
- package/android/jni/Android.mk +5 -1
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +76 -21
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +455 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +23 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +160 -42
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +44 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/cpp/update_flow_core/flow_json.cpp +522 -0
- package/cpp/update_flow_core/flow_json.h +103 -0
- package/cpp/update_flow_core/tests/flow_vectors.json +1401 -0
- package/cpp/update_flow_core/tests/update_flow_core_test.cpp +317 -0
- package/cpp/update_flow_core/update_flow_core.cpp +301 -0
- package/cpp/update_flow_core/update_flow_core.h +77 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +121 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +7 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +48 -9
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +3 -0
- package/harmony/pushy/src/main/ets/MonotonicClock.ts +13 -0
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +500 -0
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +14 -0
- package/harmony/pushy/src/main/ets/PathUtils.ts +21 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +22 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +148 -69
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +779 -61
- package/ios/RCTPushy/RCTPushyDownloader.h +1 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +4 -3
- package/package.json +3 -1
- package/react-native-update.podspec +2 -0
- package/src/NativePushy.ts +14 -0
- package/src/client.ts +341 -118
- package/src/endpoint.ts +7 -29
- package/src/provider.tsx +38 -2
- package/src/type.ts +26 -0
- package/src/updateFlowCore.ts +320 -0
- package/src/utils.ts +1 -12
- package/src/isInRollout.ts +0 -87
- package/src/resolveCheckResult.ts +0 -30
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
#include "flow_json.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
|
|
7
|
+
namespace flowjson {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
const Value kUndefined;
|
|
11
|
+
} // namespace
|
|
12
|
+
|
|
13
|
+
bool Value::Truthy() const {
|
|
14
|
+
switch (kind_) {
|
|
15
|
+
case Kind::Undefined:
|
|
16
|
+
case Kind::Null:
|
|
17
|
+
return false;
|
|
18
|
+
case Kind::Bool:
|
|
19
|
+
return bool_;
|
|
20
|
+
case Kind::Number:
|
|
21
|
+
return number_ != 0 && !std::isnan(number_);
|
|
22
|
+
case Kind::String:
|
|
23
|
+
return !string_.empty();
|
|
24
|
+
case Kind::Array:
|
|
25
|
+
case Kind::Object:
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const Value& Value::At(size_t i) const {
|
|
32
|
+
if (kind_ != Kind::Array || i >= elements_.size()) {
|
|
33
|
+
return kUndefined;
|
|
34
|
+
}
|
|
35
|
+
return elements_[i];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Value& Value::Get(const std::string& key) const {
|
|
39
|
+
if (kind_ == Kind::Object) {
|
|
40
|
+
for (const auto& member : members_) {
|
|
41
|
+
if (member.first == key) {
|
|
42
|
+
return member.second;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return kUndefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void Value::Set(const std::string& key, Value v) {
|
|
50
|
+
for (auto& member : members_) {
|
|
51
|
+
if (member.first == key) {
|
|
52
|
+
member.second = std::move(v);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
members_.emplace_back(key, std::move(v));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void Value::Remove(const std::string& key) {
|
|
60
|
+
for (auto it = members_.begin(); it != members_.end(); ++it) {
|
|
61
|
+
if (it->first == key) {
|
|
62
|
+
members_.erase(it);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
bool Value::StrictEquals(const Value& a, const Value& b) {
|
|
69
|
+
if (a.kind_ != b.kind_) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
switch (a.kind_) {
|
|
73
|
+
case Kind::Undefined:
|
|
74
|
+
case Kind::Null:
|
|
75
|
+
return true;
|
|
76
|
+
case Kind::Bool:
|
|
77
|
+
return a.bool_ == b.bool_;
|
|
78
|
+
case Kind::Number:
|
|
79
|
+
return a.number_ == b.number_;
|
|
80
|
+
case Kind::String:
|
|
81
|
+
return a.string_ == b.string_;
|
|
82
|
+
case Kind::Array:
|
|
83
|
+
case Kind::Object:
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
namespace {
|
|
90
|
+
|
|
91
|
+
void AppendEscaped(const std::string& s, std::string* out) {
|
|
92
|
+
out->push_back('"');
|
|
93
|
+
for (unsigned char c : s) {
|
|
94
|
+
switch (c) {
|
|
95
|
+
case '"':
|
|
96
|
+
out->append("\\\"");
|
|
97
|
+
break;
|
|
98
|
+
case '\\':
|
|
99
|
+
out->append("\\\\");
|
|
100
|
+
break;
|
|
101
|
+
case '\b':
|
|
102
|
+
out->append("\\b");
|
|
103
|
+
break;
|
|
104
|
+
case '\f':
|
|
105
|
+
out->append("\\f");
|
|
106
|
+
break;
|
|
107
|
+
case '\n':
|
|
108
|
+
out->append("\\n");
|
|
109
|
+
break;
|
|
110
|
+
case '\r':
|
|
111
|
+
out->append("\\r");
|
|
112
|
+
break;
|
|
113
|
+
case '\t':
|
|
114
|
+
out->append("\\t");
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
if (c < 0x20) {
|
|
118
|
+
char buf[8];
|
|
119
|
+
std::snprintf(buf, sizeof(buf), "\\u%04x", c);
|
|
120
|
+
out->append(buf);
|
|
121
|
+
} else {
|
|
122
|
+
out->push_back(static_cast<char>(c));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
out->push_back('"');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void AppendNumber(double n, std::string* out) {
|
|
130
|
+
// The decision layer only ever emits integral numbers (hashes, counters,
|
|
131
|
+
// rollout percentages); print them the way JSON.stringify does. The %.17g
|
|
132
|
+
// fallback exists so an unexpected fractional value surfaces as a vector
|
|
133
|
+
// mismatch instead of silent truncation.
|
|
134
|
+
if (std::isfinite(n) && n == std::floor(n) && std::fabs(n) <= 9007199254740992.0) {
|
|
135
|
+
char buf[32];
|
|
136
|
+
std::snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(n));
|
|
137
|
+
out->append(buf);
|
|
138
|
+
} else {
|
|
139
|
+
char buf[40];
|
|
140
|
+
std::snprintf(buf, sizeof(buf), "%.17g", n);
|
|
141
|
+
out->append(buf);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
void AppendValue(const Value& v, std::string* out) {
|
|
146
|
+
switch (v.kind()) {
|
|
147
|
+
case Value::Kind::Undefined: // only reachable inside arrays
|
|
148
|
+
case Value::Kind::Null:
|
|
149
|
+
out->append("null");
|
|
150
|
+
break;
|
|
151
|
+
case Value::Kind::Bool:
|
|
152
|
+
out->append(v.AsBool() ? "true" : "false");
|
|
153
|
+
break;
|
|
154
|
+
case Value::Kind::Number:
|
|
155
|
+
AppendNumber(v.AsNumber(), out);
|
|
156
|
+
break;
|
|
157
|
+
case Value::Kind::String:
|
|
158
|
+
AppendEscaped(v.AsString(), out);
|
|
159
|
+
break;
|
|
160
|
+
case Value::Kind::Array: {
|
|
161
|
+
out->push_back('[');
|
|
162
|
+
bool first = true;
|
|
163
|
+
for (const auto& element : v.elements()) {
|
|
164
|
+
if (!first) {
|
|
165
|
+
out->push_back(',');
|
|
166
|
+
}
|
|
167
|
+
first = false;
|
|
168
|
+
AppendValue(element, out);
|
|
169
|
+
}
|
|
170
|
+
out->push_back(']');
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case Value::Kind::Object: {
|
|
174
|
+
out->push_back('{');
|
|
175
|
+
bool first = true;
|
|
176
|
+
for (const auto& member : v.members()) {
|
|
177
|
+
if (member.second.IsUndefined()) {
|
|
178
|
+
continue; // JSON.stringify drops undefined-valued members
|
|
179
|
+
}
|
|
180
|
+
if (!first) {
|
|
181
|
+
out->push_back(',');
|
|
182
|
+
}
|
|
183
|
+
first = false;
|
|
184
|
+
AppendEscaped(member.first, out);
|
|
185
|
+
out->push_back(':');
|
|
186
|
+
AppendValue(member.second, out);
|
|
187
|
+
}
|
|
188
|
+
out->push_back('}');
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} // namespace
|
|
195
|
+
|
|
196
|
+
std::string Stringify(const Value& v) {
|
|
197
|
+
if (v.IsUndefined()) {
|
|
198
|
+
return "undefined";
|
|
199
|
+
}
|
|
200
|
+
std::string out;
|
|
201
|
+
AppendValue(v, &out);
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
namespace {
|
|
206
|
+
|
|
207
|
+
// The parser also consumes server checkUpdate responses, so hostile input
|
|
208
|
+
// must fail cleanly: nesting is capped to keep recursive descent off the
|
|
209
|
+
// stack limit. Real responses nest ~4 levels.
|
|
210
|
+
constexpr int kMaxDepth = 64;
|
|
211
|
+
|
|
212
|
+
class Parser {
|
|
213
|
+
public:
|
|
214
|
+
Parser(const std::string& text) : text_(text) {}
|
|
215
|
+
|
|
216
|
+
Value Run(bool* ok) {
|
|
217
|
+
Value v = ParseValue();
|
|
218
|
+
SkipWs();
|
|
219
|
+
*ok = ok_ && pos_ == text_.size();
|
|
220
|
+
return *ok ? v : Value::Undefined();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private:
|
|
224
|
+
void SkipWs() {
|
|
225
|
+
while (pos_ < text_.size()) {
|
|
226
|
+
char c = text_[pos_];
|
|
227
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
|
228
|
+
pos_++;
|
|
229
|
+
} else {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
bool Consume(char expected) {
|
|
236
|
+
if (pos_ < text_.size() && text_[pos_] == expected) {
|
|
237
|
+
pos_++;
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
ok_ = false;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
bool ConsumeLiteral(const char* literal) {
|
|
245
|
+
size_t len = 0;
|
|
246
|
+
while (literal[len]) {
|
|
247
|
+
len++;
|
|
248
|
+
}
|
|
249
|
+
if (text_.compare(pos_, len, literal) == 0) {
|
|
250
|
+
pos_ += len;
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
ok_ = false;
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
Value ParseValue() {
|
|
258
|
+
SkipWs();
|
|
259
|
+
if (pos_ >= text_.size()) {
|
|
260
|
+
ok_ = false;
|
|
261
|
+
return Value::Undefined();
|
|
262
|
+
}
|
|
263
|
+
char c = text_[pos_];
|
|
264
|
+
switch (c) {
|
|
265
|
+
case '{':
|
|
266
|
+
case '[': {
|
|
267
|
+
if (depth_ >= kMaxDepth) {
|
|
268
|
+
ok_ = false;
|
|
269
|
+
return Value::Undefined();
|
|
270
|
+
}
|
|
271
|
+
depth_++;
|
|
272
|
+
Value v = c == '{' ? ParseObject() : ParseArray();
|
|
273
|
+
depth_--;
|
|
274
|
+
return v;
|
|
275
|
+
}
|
|
276
|
+
case '"':
|
|
277
|
+
return Value::String(ParseString());
|
|
278
|
+
case 't':
|
|
279
|
+
ConsumeLiteral("true");
|
|
280
|
+
return Value::Bool(true);
|
|
281
|
+
case 'f':
|
|
282
|
+
ConsumeLiteral("false");
|
|
283
|
+
return Value::Bool(false);
|
|
284
|
+
case 'n':
|
|
285
|
+
ConsumeLiteral("null");
|
|
286
|
+
return Value::Null();
|
|
287
|
+
default:
|
|
288
|
+
return ParseNumber();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
Value ParseObject() {
|
|
293
|
+
Value obj = Value::Object();
|
|
294
|
+
Consume('{');
|
|
295
|
+
SkipWs();
|
|
296
|
+
if (pos_ < text_.size() && text_[pos_] == '}') {
|
|
297
|
+
pos_++;
|
|
298
|
+
return obj;
|
|
299
|
+
}
|
|
300
|
+
while (ok_) {
|
|
301
|
+
SkipWs();
|
|
302
|
+
std::string key = ParseString();
|
|
303
|
+
SkipWs();
|
|
304
|
+
Consume(':');
|
|
305
|
+
obj.Set(key, ParseValue());
|
|
306
|
+
SkipWs();
|
|
307
|
+
if (pos_ < text_.size() && text_[pos_] == ',') {
|
|
308
|
+
pos_++;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
Consume('}');
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
return obj;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
Value ParseArray() {
|
|
318
|
+
Value arr = Value::Array();
|
|
319
|
+
Consume('[');
|
|
320
|
+
SkipWs();
|
|
321
|
+
if (pos_ < text_.size() && text_[pos_] == ']') {
|
|
322
|
+
pos_++;
|
|
323
|
+
return arr;
|
|
324
|
+
}
|
|
325
|
+
while (ok_) {
|
|
326
|
+
arr.Push(ParseValue());
|
|
327
|
+
SkipWs();
|
|
328
|
+
if (pos_ < text_.size() && text_[pos_] == ',') {
|
|
329
|
+
pos_++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
Consume(']');
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
return arr;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
std::string ParseString() {
|
|
339
|
+
std::string out;
|
|
340
|
+
if (!Consume('"')) {
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
while (pos_ < text_.size()) {
|
|
344
|
+
char c = text_[pos_++];
|
|
345
|
+
if (c == '"') {
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
if (c != '\\') {
|
|
349
|
+
out.push_back(c);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (pos_ >= text_.size()) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
char esc = text_[pos_++];
|
|
356
|
+
switch (esc) {
|
|
357
|
+
case '"':
|
|
358
|
+
case '\\':
|
|
359
|
+
case '/':
|
|
360
|
+
out.push_back(esc);
|
|
361
|
+
break;
|
|
362
|
+
case 'b':
|
|
363
|
+
out.push_back('\b');
|
|
364
|
+
break;
|
|
365
|
+
case 'f':
|
|
366
|
+
out.push_back('\f');
|
|
367
|
+
break;
|
|
368
|
+
case 'n':
|
|
369
|
+
out.push_back('\n');
|
|
370
|
+
break;
|
|
371
|
+
case 'r':
|
|
372
|
+
out.push_back('\r');
|
|
373
|
+
break;
|
|
374
|
+
case 't':
|
|
375
|
+
out.push_back('\t');
|
|
376
|
+
break;
|
|
377
|
+
case 'u': {
|
|
378
|
+
unsigned code = ParseHex4();
|
|
379
|
+
// BMP code point to UTF-8 (surrogate pairs are combined).
|
|
380
|
+
if (code >= 0xd800 && code <= 0xdbff &&
|
|
381
|
+
text_.compare(pos_, 2, "\\u") == 0) {
|
|
382
|
+
pos_ += 2;
|
|
383
|
+
unsigned low = ParseHex4();
|
|
384
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
385
|
+
code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
|
386
|
+
} else {
|
|
387
|
+
ok_ = false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
AppendUtf8(code, &out);
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
default:
|
|
394
|
+
ok_ = false;
|
|
395
|
+
return out;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
ok_ = false;
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
unsigned ParseHex4() {
|
|
403
|
+
unsigned code = 0;
|
|
404
|
+
for (int i = 0; i < 4; i++) {
|
|
405
|
+
if (pos_ >= text_.size()) {
|
|
406
|
+
ok_ = false;
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
char c = text_[pos_++];
|
|
410
|
+
code <<= 4;
|
|
411
|
+
if (c >= '0' && c <= '9') {
|
|
412
|
+
code |= static_cast<unsigned>(c - '0');
|
|
413
|
+
} else if (c >= 'a' && c <= 'f') {
|
|
414
|
+
code |= static_cast<unsigned>(c - 'a' + 10);
|
|
415
|
+
} else if (c >= 'A' && c <= 'F') {
|
|
416
|
+
code |= static_cast<unsigned>(c - 'A' + 10);
|
|
417
|
+
} else {
|
|
418
|
+
ok_ = false;
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return code;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static void AppendUtf8(unsigned code, std::string* out) {
|
|
426
|
+
if (code < 0x80) {
|
|
427
|
+
out->push_back(static_cast<char>(code));
|
|
428
|
+
} else if (code < 0x800) {
|
|
429
|
+
out->push_back(static_cast<char>(0xc0 | (code >> 6)));
|
|
430
|
+
out->push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
431
|
+
} else if (code < 0x10000) {
|
|
432
|
+
out->push_back(static_cast<char>(0xe0 | (code >> 12)));
|
|
433
|
+
out->push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
|
|
434
|
+
out->push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
435
|
+
} else {
|
|
436
|
+
out->push_back(static_cast<char>(0xf0 | (code >> 18)));
|
|
437
|
+
out->push_back(static_cast<char>(0x80 | ((code >> 12) & 0x3f)));
|
|
438
|
+
out->push_back(static_cast<char>(0x80 | ((code >> 6) & 0x3f)));
|
|
439
|
+
out->push_back(static_cast<char>(0x80 | (code & 0x3f)));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
Value ParseNumber() {
|
|
444
|
+
size_t start = pos_;
|
|
445
|
+
if (pos_ < text_.size() && text_[pos_] == '-') {
|
|
446
|
+
pos_++;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (pos_ >= text_.size()) {
|
|
450
|
+
ok_ = false;
|
|
451
|
+
return Value::Undefined();
|
|
452
|
+
}
|
|
453
|
+
if (text_[pos_] == '0') {
|
|
454
|
+
pos_++;
|
|
455
|
+
// RFC 8259 forbids leading zeroes (except the number zero itself).
|
|
456
|
+
if (pos_ < text_.size() && text_[pos_] >= '0' && text_[pos_] <= '9') {
|
|
457
|
+
ok_ = false;
|
|
458
|
+
return Value::Undefined();
|
|
459
|
+
}
|
|
460
|
+
} else if (text_[pos_] >= '1' && text_[pos_] <= '9') {
|
|
461
|
+
while (pos_ < text_.size() && text_[pos_] >= '0' &&
|
|
462
|
+
text_[pos_] <= '9') {
|
|
463
|
+
pos_++;
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
ok_ = false;
|
|
467
|
+
return Value::Undefined();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (pos_ < text_.size() && text_[pos_] == '.') {
|
|
471
|
+
pos_++;
|
|
472
|
+
if (pos_ >= text_.size() || text_[pos_] < '0' || text_[pos_] > '9') {
|
|
473
|
+
ok_ = false;
|
|
474
|
+
return Value::Undefined();
|
|
475
|
+
}
|
|
476
|
+
while (pos_ < text_.size() && text_[pos_] >= '0' &&
|
|
477
|
+
text_[pos_] <= '9') {
|
|
478
|
+
pos_++;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (pos_ < text_.size() &&
|
|
483
|
+
(text_[pos_] == 'e' || text_[pos_] == 'E')) {
|
|
484
|
+
pos_++;
|
|
485
|
+
if (pos_ < text_.size() &&
|
|
486
|
+
(text_[pos_] == '+' || text_[pos_] == '-')) {
|
|
487
|
+
pos_++;
|
|
488
|
+
}
|
|
489
|
+
if (pos_ >= text_.size() || text_[pos_] < '0' || text_[pos_] > '9') {
|
|
490
|
+
ok_ = false;
|
|
491
|
+
return Value::Undefined();
|
|
492
|
+
}
|
|
493
|
+
while (pos_ < text_.size() && text_[pos_] >= '0' &&
|
|
494
|
+
text_[pos_] <= '9') {
|
|
495
|
+
pos_++;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
char* end = nullptr;
|
|
500
|
+
std::string token = text_.substr(start, pos_ - start);
|
|
501
|
+
double n = std::strtod(token.c_str(), &end);
|
|
502
|
+
if (end == nullptr || *end != '\0') {
|
|
503
|
+
ok_ = false;
|
|
504
|
+
return Value::Undefined();
|
|
505
|
+
}
|
|
506
|
+
return Value::Number(n);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const std::string& text_;
|
|
510
|
+
size_t pos_ = 0;
|
|
511
|
+
int depth_ = 0;
|
|
512
|
+
bool ok_ = true;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
} // namespace
|
|
516
|
+
|
|
517
|
+
Value Parse(const std::string& text, bool* ok) {
|
|
518
|
+
Parser parser(text);
|
|
519
|
+
return parser.Run(ok);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
} // namespace flowjson
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <utility>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
// Minimal JSON document model for the update-flow decision layer.
|
|
9
|
+
//
|
|
10
|
+
// Deliberately mirrors JavaScript object semantics where the ported decision
|
|
11
|
+
// logic depends on them, because the golden vectors are generated by the TS
|
|
12
|
+
// reference implementation and compared as serialized strings:
|
|
13
|
+
// - object members keep insertion order, and overwriting an existing key
|
|
14
|
+
// keeps its original position (JS spread/assignment semantics);
|
|
15
|
+
// - Kind::Undefined is distinct from Kind::Null: undefined-valued members
|
|
16
|
+
// are skipped by Stringify (like JSON.stringify), null is emitted;
|
|
17
|
+
// - Truthy() implements JS truthiness (undefined/null/false/0/NaN/'' are
|
|
18
|
+
// falsy; empty arrays and objects are truthy);
|
|
19
|
+
// - StrictEquals() implements === for primitives only (never for
|
|
20
|
+
// arrays/objects — reference equality cannot hold across a parse).
|
|
21
|
+
namespace flowjson {
|
|
22
|
+
|
|
23
|
+
class Value;
|
|
24
|
+
using Members = std::vector<std::pair<std::string, Value>>;
|
|
25
|
+
using Elements = std::vector<Value>;
|
|
26
|
+
|
|
27
|
+
class Value {
|
|
28
|
+
public:
|
|
29
|
+
enum class Kind { Undefined, Null, Bool, Number, String, Array, Object };
|
|
30
|
+
|
|
31
|
+
Value() = default;
|
|
32
|
+
static Value Undefined() { return Value(); }
|
|
33
|
+
static Value Null() { return Value(Kind::Null); }
|
|
34
|
+
static Value Bool(bool b) {
|
|
35
|
+
Value v(Kind::Bool);
|
|
36
|
+
v.bool_ = b;
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
static Value Number(double n) {
|
|
40
|
+
Value v(Kind::Number);
|
|
41
|
+
v.number_ = n;
|
|
42
|
+
return v;
|
|
43
|
+
}
|
|
44
|
+
static Value String(std::string s) {
|
|
45
|
+
Value v(Kind::String);
|
|
46
|
+
v.string_ = std::move(s);
|
|
47
|
+
return v;
|
|
48
|
+
}
|
|
49
|
+
static Value Array() { return Value(Kind::Array); }
|
|
50
|
+
static Value Object() { return Value(Kind::Object); }
|
|
51
|
+
|
|
52
|
+
Kind kind() const { return kind_; }
|
|
53
|
+
bool IsUndefined() const { return kind_ == Kind::Undefined; }
|
|
54
|
+
bool IsArray() const { return kind_ == Kind::Array; }
|
|
55
|
+
bool IsObject() const { return kind_ == Kind::Object; }
|
|
56
|
+
bool IsNumber() const { return kind_ == Kind::Number; }
|
|
57
|
+
bool IsString() const { return kind_ == Kind::String; }
|
|
58
|
+
|
|
59
|
+
bool AsBool() const { return bool_; }
|
|
60
|
+
double AsNumber() const { return number_; }
|
|
61
|
+
const std::string& AsString() const { return string_; }
|
|
62
|
+
|
|
63
|
+
bool Truthy() const;
|
|
64
|
+
|
|
65
|
+
// Array access.
|
|
66
|
+
const Elements& elements() const { return elements_; }
|
|
67
|
+
void Push(Value v) { elements_.push_back(std::move(v)); }
|
|
68
|
+
size_t Size() const { return elements_.size(); }
|
|
69
|
+
const Value& At(size_t i) const;
|
|
70
|
+
|
|
71
|
+
// Object access. Get returns Undefined for a missing key; Set overwrites
|
|
72
|
+
// in place (keeping the key's position) or appends.
|
|
73
|
+
const Members& members() const { return members_; }
|
|
74
|
+
const Value& Get(const std::string& key) const;
|
|
75
|
+
void Set(const std::string& key, Value v);
|
|
76
|
+
void Remove(const std::string& key);
|
|
77
|
+
|
|
78
|
+
static bool StrictEquals(const Value& a, const Value& b);
|
|
79
|
+
|
|
80
|
+
private:
|
|
81
|
+
explicit Value(Kind kind) : kind_(kind) {}
|
|
82
|
+
|
|
83
|
+
Kind kind_ = Kind::Undefined;
|
|
84
|
+
bool bool_ = false;
|
|
85
|
+
double number_ = 0;
|
|
86
|
+
std::string string_;
|
|
87
|
+
Elements elements_;
|
|
88
|
+
Members members_;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// JSON.stringify-compatible for the value shapes the decision layer produces
|
|
92
|
+
// (undefined members skipped, undefined array elements become null, integral
|
|
93
|
+
// numbers within the double-safe range print without a decimal point). A
|
|
94
|
+
// top-level Undefined prints as "undefined" so two undefined results compare
|
|
95
|
+
// equal.
|
|
96
|
+
std::string Stringify(const Value& v);
|
|
97
|
+
|
|
98
|
+
// Strict JSON parser (the vectors file and checkUpdate responses). Returns
|
|
99
|
+
// Undefined and sets *ok to false on malformed input; nesting beyond 64
|
|
100
|
+
// levels is rejected so hostile server data cannot exhaust the stack.
|
|
101
|
+
Value Parse(const std::string& text, bool* ok);
|
|
102
|
+
|
|
103
|
+
} // namespace flowjson
|