@ugo-studio/jspp 0.1.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 +162 -0
- package/dist/analysis/scope.js +77 -0
- package/dist/analysis/typeAnalyzer.js +224 -0
- package/dist/ast/types.js +1 -0
- package/dist/cli.js +63 -0
- package/dist/core/codegen/declaration-handlers.js +49 -0
- package/dist/core/codegen/expression-handlers.js +333 -0
- package/dist/core/codegen/function-handlers.js +94 -0
- package/dist/core/codegen/helpers.js +83 -0
- package/dist/core/codegen/index.js +54 -0
- package/dist/core/codegen/literal-handlers.js +32 -0
- package/dist/core/codegen/statement-handlers.js +485 -0
- package/dist/core/codegen/visitor.js +86 -0
- package/dist/core/parser.js +6 -0
- package/dist/core/traverser.js +19 -0
- package/dist/index.js +16 -0
- package/package.json +41 -0
- package/src/prelude/access.hpp +86 -0
- package/src/prelude/any_value.hpp +734 -0
- package/src/prelude/descriptors.hpp +25 -0
- package/src/prelude/error.hpp +31 -0
- package/src/prelude/error_helpers.hpp +59 -0
- package/src/prelude/index.hpp +29 -0
- package/src/prelude/library/console.hpp +111 -0
- package/src/prelude/library/global.hpp +10 -0
- package/src/prelude/library/symbol.hpp +8 -0
- package/src/prelude/log_string.hpp +403 -0
- package/src/prelude/operators.hpp +256 -0
- package/src/prelude/types.hpp +50 -0
- package/src/prelude/values/array.hpp +50 -0
- package/src/prelude/values/function.hpp +19 -0
- package/src/prelude/values/non_values.hpp +20 -0
- package/src/prelude/values/object.hpp +17 -0
- package/src/prelude/values/operators/array.hpp +165 -0
- package/src/prelude/values/operators/function.hpp +34 -0
- package/src/prelude/values/operators/object.hpp +34 -0
- package/src/prelude/values/prototypes/array.hpp +228 -0
- package/src/prelude/values/prototypes/function.hpp +0 -0
- package/src/prelude/values/prototypes/object.hpp +0 -0
- package/src/prelude/values/prototypes/string.hpp +357 -0
- package/src/prelude/well_known_symbols.hpp +10 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cassert>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <limits>
|
|
7
|
+
#include <new>
|
|
8
|
+
#include <sstream>
|
|
9
|
+
#include <iomanip>
|
|
10
|
+
#include <type_traits>
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <utility>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <unordered_map>
|
|
15
|
+
#include <vector>
|
|
16
|
+
#include <functional>
|
|
17
|
+
#include <cmath>
|
|
18
|
+
|
|
19
|
+
#include "types.hpp"
|
|
20
|
+
#include "values/non_values.hpp"
|
|
21
|
+
#include "values/object.hpp"
|
|
22
|
+
#include "values/array.hpp"
|
|
23
|
+
#include "values/function.hpp"
|
|
24
|
+
#include "error.hpp"
|
|
25
|
+
#include "descriptors.hpp"
|
|
26
|
+
|
|
27
|
+
namespace jspp
|
|
28
|
+
{
|
|
29
|
+
enum class JsType : uint8_t
|
|
30
|
+
{
|
|
31
|
+
Undefined = 0,
|
|
32
|
+
Null = 1,
|
|
33
|
+
Uninitialized = 2,
|
|
34
|
+
Boolean = 3,
|
|
35
|
+
Number = 4,
|
|
36
|
+
String = 5,
|
|
37
|
+
Object = 6,
|
|
38
|
+
Array = 7,
|
|
39
|
+
Function = 8,
|
|
40
|
+
DataDescriptor = 9,
|
|
41
|
+
AccessorDescriptor = 10,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Tagged storage with a union for payload
|
|
45
|
+
struct TaggedValue
|
|
46
|
+
{
|
|
47
|
+
JsType type;
|
|
48
|
+
union
|
|
49
|
+
{
|
|
50
|
+
JsUndefined undefined;
|
|
51
|
+
JsNull null;
|
|
52
|
+
JsUninitialized uninitialized;
|
|
53
|
+
bool boolean;
|
|
54
|
+
double number;
|
|
55
|
+
std::unique_ptr<std::string> str;
|
|
56
|
+
std::shared_ptr<JsObject> object;
|
|
57
|
+
std::shared_ptr<JsArray> array;
|
|
58
|
+
std::shared_ptr<JsFunction> function;
|
|
59
|
+
std::shared_ptr<DataDescriptor> data_desc;
|
|
60
|
+
std::shared_ptr<AccessorDescriptor> accessor_desc;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
TaggedValue() noexcept : type(JsType::Undefined), undefined{} {}
|
|
64
|
+
~TaggedValue() {}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
class AnyValue
|
|
68
|
+
{
|
|
69
|
+
private:
|
|
70
|
+
TaggedValue storage;
|
|
71
|
+
|
|
72
|
+
void destroy_value() noexcept
|
|
73
|
+
{
|
|
74
|
+
switch (storage.type)
|
|
75
|
+
{
|
|
76
|
+
case JsType::String:
|
|
77
|
+
storage.str.~unique_ptr();
|
|
78
|
+
break;
|
|
79
|
+
case JsType::Object:
|
|
80
|
+
storage.object.~shared_ptr();
|
|
81
|
+
break;
|
|
82
|
+
case JsType::Array:
|
|
83
|
+
storage.array.~shared_ptr();
|
|
84
|
+
break;
|
|
85
|
+
case JsType::Function:
|
|
86
|
+
storage.function.~shared_ptr();
|
|
87
|
+
break;
|
|
88
|
+
case JsType::DataDescriptor:
|
|
89
|
+
storage.data_desc.~shared_ptr();
|
|
90
|
+
break;
|
|
91
|
+
case JsType::AccessorDescriptor:
|
|
92
|
+
storage.accessor_desc.~shared_ptr();
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void reset_to_undefined() noexcept
|
|
100
|
+
{
|
|
101
|
+
destroy_value();
|
|
102
|
+
storage.type = JsType::Undefined;
|
|
103
|
+
storage.undefined = JsUndefined{};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void move_from(AnyValue &other) noexcept
|
|
107
|
+
{
|
|
108
|
+
storage.type = other.storage.type;
|
|
109
|
+
switch (other.storage.type)
|
|
110
|
+
{
|
|
111
|
+
case JsType::Undefined:
|
|
112
|
+
storage.undefined = JsUndefined{};
|
|
113
|
+
break;
|
|
114
|
+
case JsType::Null:
|
|
115
|
+
storage.null = JsNull{};
|
|
116
|
+
break;
|
|
117
|
+
case JsType::Uninitialized:
|
|
118
|
+
storage.uninitialized = JsUninitialized{};
|
|
119
|
+
break;
|
|
120
|
+
case JsType::Boolean:
|
|
121
|
+
storage.boolean = other.storage.boolean;
|
|
122
|
+
break;
|
|
123
|
+
case JsType::Number:
|
|
124
|
+
storage.number = other.storage.number;
|
|
125
|
+
break;
|
|
126
|
+
case JsType::String:
|
|
127
|
+
new (&storage.str) std::unique_ptr<std::string>(std::move(other.storage.str));
|
|
128
|
+
break;
|
|
129
|
+
case JsType::Object:
|
|
130
|
+
new (&storage.object) std::shared_ptr<JsObject>(std::move(other.storage.object));
|
|
131
|
+
break;
|
|
132
|
+
case JsType::Array:
|
|
133
|
+
new (&storage.array) std::shared_ptr<JsArray>(std::move(other.storage.array));
|
|
134
|
+
break;
|
|
135
|
+
case JsType::Function:
|
|
136
|
+
new (&storage.function) std::shared_ptr<JsFunction>(std::move(other.storage.function));
|
|
137
|
+
break;
|
|
138
|
+
case JsType::DataDescriptor:
|
|
139
|
+
new (&storage.data_desc) std::shared_ptr<DataDescriptor>(std::move(other.storage.data_desc));
|
|
140
|
+
break;
|
|
141
|
+
case JsType::AccessorDescriptor:
|
|
142
|
+
new (&storage.accessor_desc) std::shared_ptr<AccessorDescriptor>(std::move(other.storage.accessor_desc));
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
void copy_from(const AnyValue &other)
|
|
148
|
+
{
|
|
149
|
+
storage.type = other.storage.type;
|
|
150
|
+
switch (other.storage.type)
|
|
151
|
+
{
|
|
152
|
+
case JsType::Undefined:
|
|
153
|
+
storage.undefined = JsUndefined{};
|
|
154
|
+
break;
|
|
155
|
+
case JsType::Null:
|
|
156
|
+
storage.null = JsNull{};
|
|
157
|
+
break;
|
|
158
|
+
case JsType::Uninitialized:
|
|
159
|
+
storage.uninitialized = JsUninitialized{};
|
|
160
|
+
break;
|
|
161
|
+
case JsType::Boolean:
|
|
162
|
+
storage.boolean = other.storage.boolean;
|
|
163
|
+
break;
|
|
164
|
+
case JsType::Number:
|
|
165
|
+
storage.number = other.storage.number;
|
|
166
|
+
break;
|
|
167
|
+
case JsType::String:
|
|
168
|
+
new (&storage.str) std::unique_ptr<std::string>(std::make_unique<std::string>(*other.storage.str));
|
|
169
|
+
break;
|
|
170
|
+
case JsType::Object:
|
|
171
|
+
new (&storage.object) std::shared_ptr<JsObject>(other.storage.object); // shallow copy
|
|
172
|
+
break;
|
|
173
|
+
case JsType::Array:
|
|
174
|
+
new (&storage.array) std::shared_ptr<JsArray>(other.storage.array); // shallow copy
|
|
175
|
+
break;
|
|
176
|
+
case JsType::Function:
|
|
177
|
+
new (&storage.function) std::shared_ptr<JsFunction>(other.storage.function); // shallow copy
|
|
178
|
+
break;
|
|
179
|
+
case JsType::DataDescriptor:
|
|
180
|
+
new (&storage.data_desc) std::shared_ptr<DataDescriptor>(other.storage.data_desc); // shallow copy
|
|
181
|
+
break;
|
|
182
|
+
case JsType::AccessorDescriptor:
|
|
183
|
+
new (&storage.accessor_desc) std::shared_ptr<AccessorDescriptor>(other.storage.accessor_desc); // shallow copy
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public:
|
|
189
|
+
// default ctor (Undefined)
|
|
190
|
+
AnyValue() noexcept
|
|
191
|
+
{
|
|
192
|
+
storage.type = JsType::Undefined;
|
|
193
|
+
storage.undefined = JsUndefined{};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 1. Destructor
|
|
197
|
+
~AnyValue() noexcept
|
|
198
|
+
{
|
|
199
|
+
destroy_value();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 2. Copy Constructor (deep copy)
|
|
203
|
+
AnyValue(const AnyValue &other)
|
|
204
|
+
{
|
|
205
|
+
copy_from(other);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 3. Copy Assignment Operator
|
|
209
|
+
AnyValue &operator=(const AnyValue &other)
|
|
210
|
+
{
|
|
211
|
+
if (this != &other)
|
|
212
|
+
{
|
|
213
|
+
destroy_value();
|
|
214
|
+
copy_from(other);
|
|
215
|
+
}
|
|
216
|
+
return *this;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 4. Move Constructor
|
|
220
|
+
AnyValue(AnyValue &&other) noexcept
|
|
221
|
+
{
|
|
222
|
+
storage.type = JsType::Undefined;
|
|
223
|
+
storage.undefined = JsUndefined{};
|
|
224
|
+
move_from(other);
|
|
225
|
+
other.reset_to_undefined();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 5. Move Assignment Operator
|
|
229
|
+
AnyValue &operator=(AnyValue &&other) noexcept
|
|
230
|
+
{
|
|
231
|
+
if (this != &other)
|
|
232
|
+
{
|
|
233
|
+
destroy_value();
|
|
234
|
+
move_from(other);
|
|
235
|
+
other.reset_to_undefined();
|
|
236
|
+
}
|
|
237
|
+
return *this;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
friend void swap(AnyValue &a, AnyValue &b) noexcept
|
|
241
|
+
{
|
|
242
|
+
AnyValue tmp(std::move(a));
|
|
243
|
+
a = std::move(b);
|
|
244
|
+
b = std::move(tmp);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// factories -------------------------------------------------------
|
|
248
|
+
static AnyValue make_number(double d) noexcept
|
|
249
|
+
{
|
|
250
|
+
AnyValue v;
|
|
251
|
+
v.storage.type = JsType::Number;
|
|
252
|
+
v.storage.number = d;
|
|
253
|
+
return v;
|
|
254
|
+
}
|
|
255
|
+
static AnyValue make_nan() noexcept
|
|
256
|
+
{
|
|
257
|
+
AnyValue v;
|
|
258
|
+
v.storage.type = JsType::Number;
|
|
259
|
+
v.storage.number = std::numeric_limits<double>::quiet_NaN();
|
|
260
|
+
return v;
|
|
261
|
+
}
|
|
262
|
+
static AnyValue make_uninitialized() noexcept
|
|
263
|
+
{
|
|
264
|
+
AnyValue v;
|
|
265
|
+
v.storage.type = JsType::Uninitialized;
|
|
266
|
+
v.storage.uninitialized = JsUninitialized{};
|
|
267
|
+
return v;
|
|
268
|
+
}
|
|
269
|
+
static AnyValue make_undefined() noexcept
|
|
270
|
+
{
|
|
271
|
+
AnyValue v;
|
|
272
|
+
v.storage.type = JsType::Undefined;
|
|
273
|
+
v.storage.undefined = JsUndefined{};
|
|
274
|
+
return v;
|
|
275
|
+
}
|
|
276
|
+
static AnyValue make_null() noexcept
|
|
277
|
+
{
|
|
278
|
+
AnyValue v;
|
|
279
|
+
v.storage.type = JsType::Null;
|
|
280
|
+
v.storage.null = JsNull{};
|
|
281
|
+
return v;
|
|
282
|
+
}
|
|
283
|
+
static AnyValue make_boolean(bool b) noexcept
|
|
284
|
+
{
|
|
285
|
+
AnyValue v;
|
|
286
|
+
v.storage.type = JsType::Boolean;
|
|
287
|
+
v.storage.boolean = b;
|
|
288
|
+
return v;
|
|
289
|
+
}
|
|
290
|
+
static AnyValue make_string(const std::string &raw_s) noexcept
|
|
291
|
+
{
|
|
292
|
+
AnyValue v;
|
|
293
|
+
v.storage.type = JsType::String;
|
|
294
|
+
new (&v.storage.str) std::unique_ptr<std::string>(std::make_unique<std::string>(raw_s));
|
|
295
|
+
return v;
|
|
296
|
+
}
|
|
297
|
+
static AnyValue make_object(const std::unordered_map<std::string, AnyValue> &props) noexcept
|
|
298
|
+
{
|
|
299
|
+
AnyValue v;
|
|
300
|
+
v.storage.type = JsType::Object;
|
|
301
|
+
new (&v.storage.object) std::shared_ptr<JsObject>(std::make_shared<JsObject>(props));
|
|
302
|
+
return v;
|
|
303
|
+
}
|
|
304
|
+
static AnyValue make_array(const std::vector<std::optional<AnyValue>> &dense) noexcept
|
|
305
|
+
{
|
|
306
|
+
AnyValue v;
|
|
307
|
+
v.storage.type = JsType::Array;
|
|
308
|
+
new (&v.storage.array) std::shared_ptr<JsArray>(std::make_shared<JsArray>(dense));
|
|
309
|
+
return v;
|
|
310
|
+
}
|
|
311
|
+
static AnyValue make_function(const std::function<AnyValue(const std::vector<AnyValue> &)> &call, const std::string &name) noexcept
|
|
312
|
+
{
|
|
313
|
+
AnyValue v;
|
|
314
|
+
v.storage.type = JsType::Function;
|
|
315
|
+
new (&v.storage.function) std::shared_ptr<JsFunction>(std::make_shared<JsFunction>(call, name));
|
|
316
|
+
return v;
|
|
317
|
+
}
|
|
318
|
+
static AnyValue make_data_descriptor(const AnyValue &value, bool writable, bool enumerable, bool configurable) noexcept
|
|
319
|
+
{
|
|
320
|
+
AnyValue v;
|
|
321
|
+
v.storage.type = JsType::DataDescriptor;
|
|
322
|
+
new (&v.storage.data_desc) std::shared_ptr<DataDescriptor>(std::make_shared<DataDescriptor>(std::make_shared<AnyValue>(value), writable, enumerable, configurable));
|
|
323
|
+
return v;
|
|
324
|
+
}
|
|
325
|
+
static AnyValue make_accessor_descriptor(const std::optional<std::function<AnyValue(const std::vector<AnyValue> &)>> &get,
|
|
326
|
+
const std::optional<std::function<AnyValue(const std::vector<AnyValue> &)>> &set,
|
|
327
|
+
bool enumerable,
|
|
328
|
+
bool configurable) noexcept
|
|
329
|
+
{
|
|
330
|
+
AnyValue v;
|
|
331
|
+
v.storage.type = JsType::AccessorDescriptor;
|
|
332
|
+
new (&v.storage.accessor_desc) std::shared_ptr<AccessorDescriptor>(std::make_shared<AccessorDescriptor>(get, set, enumerable, configurable));
|
|
333
|
+
return v;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// property resolution helpers ---------------------------------------
|
|
337
|
+
static AnyValue resolve_property_for_read(const AnyValue &val) noexcept
|
|
338
|
+
{
|
|
339
|
+
switch (val.storage.type)
|
|
340
|
+
{
|
|
341
|
+
case JsType::DataDescriptor:
|
|
342
|
+
{
|
|
343
|
+
return *(val.storage.data_desc->value);
|
|
344
|
+
}
|
|
345
|
+
case JsType::AccessorDescriptor:
|
|
346
|
+
{
|
|
347
|
+
if (val.storage.accessor_desc->get.has_value())
|
|
348
|
+
return val.storage.accessor_desc->get.value()({});
|
|
349
|
+
else
|
|
350
|
+
{
|
|
351
|
+
static AnyValue undefined = AnyValue{};
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
default:
|
|
356
|
+
{
|
|
357
|
+
return val;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
static AnyValue resolve_property_for_write(AnyValue &val, const AnyValue &new_val)
|
|
362
|
+
{
|
|
363
|
+
switch (val.storage.type)
|
|
364
|
+
{
|
|
365
|
+
case JsType::DataDescriptor:
|
|
366
|
+
{
|
|
367
|
+
*(val.storage.data_desc->value) = new_val;
|
|
368
|
+
return new_val;
|
|
369
|
+
}
|
|
370
|
+
case JsType::AccessorDescriptor:
|
|
371
|
+
{
|
|
372
|
+
if (val.storage.accessor_desc->set.has_value())
|
|
373
|
+
{
|
|
374
|
+
val.storage.accessor_desc->set.value()({new_val});
|
|
375
|
+
return new_val;
|
|
376
|
+
}
|
|
377
|
+
else
|
|
378
|
+
{
|
|
379
|
+
throw RuntimeError::make_error("Cannot set property of #<Object> which has only a getter", "TypeError");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
default:
|
|
383
|
+
{
|
|
384
|
+
val = new_val;
|
|
385
|
+
return new_val;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// type checkers and accessors ---------------------------------------
|
|
391
|
+
bool is_number() const noexcept { return storage.type == JsType::Number; }
|
|
392
|
+
bool is_string() const noexcept { return storage.type == JsType::String; }
|
|
393
|
+
bool is_object() const noexcept { return storage.type == JsType::Object; }
|
|
394
|
+
bool is_array() const noexcept { return storage.type == JsType::Array; }
|
|
395
|
+
bool is_function() const noexcept { return storage.type == JsType::Function; }
|
|
396
|
+
bool is_boolean() const noexcept { return storage.type == JsType::Boolean; }
|
|
397
|
+
bool is_null() const noexcept { return storage.type == JsType::Null; }
|
|
398
|
+
bool is_undefined() const noexcept { return storage.type == JsType::Undefined; }
|
|
399
|
+
bool is_uninitialized() const noexcept { return storage.type == JsType::Uninitialized; }
|
|
400
|
+
bool is_data_descriptor() const noexcept { return storage.type == JsType::DataDescriptor; }
|
|
401
|
+
bool is_accessor_descriptor() const noexcept { return storage.type == JsType::AccessorDescriptor; }
|
|
402
|
+
|
|
403
|
+
// --- TYPE CASTERS
|
|
404
|
+
double as_double() const noexcept
|
|
405
|
+
{
|
|
406
|
+
assert(is_number());
|
|
407
|
+
return storage.number;
|
|
408
|
+
}
|
|
409
|
+
bool as_boolean() const noexcept
|
|
410
|
+
{
|
|
411
|
+
assert(is_boolean());
|
|
412
|
+
return storage.boolean;
|
|
413
|
+
}
|
|
414
|
+
std::string *as_string() const noexcept
|
|
415
|
+
{
|
|
416
|
+
assert(is_string());
|
|
417
|
+
return storage.str.get();
|
|
418
|
+
}
|
|
419
|
+
JsObject *as_object() const noexcept
|
|
420
|
+
{
|
|
421
|
+
assert(is_object());
|
|
422
|
+
return storage.object.get();
|
|
423
|
+
}
|
|
424
|
+
JsArray *as_array() const noexcept
|
|
425
|
+
{
|
|
426
|
+
assert(is_array());
|
|
427
|
+
return storage.array.get();
|
|
428
|
+
}
|
|
429
|
+
JsFunction *as_function(const std::string &expression = "") const
|
|
430
|
+
{
|
|
431
|
+
if (is_function())
|
|
432
|
+
return storage.function.get();
|
|
433
|
+
throw RuntimeError::make_error(expression + " is not a function", "TypeError");
|
|
434
|
+
}
|
|
435
|
+
DataDescriptor *as_data_descriptor() const noexcept
|
|
436
|
+
{
|
|
437
|
+
assert(is_data_descriptor());
|
|
438
|
+
return storage.data_desc.get();
|
|
439
|
+
}
|
|
440
|
+
AccessorDescriptor *as_accessor_descriptor() const noexcept
|
|
441
|
+
{
|
|
442
|
+
assert(is_accessor_descriptor());
|
|
443
|
+
return storage.accessor_desc.get();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// --- PROPERTY ACCESS OPERATORS
|
|
447
|
+
AnyValue get_own_property(const std::string &key)
|
|
448
|
+
{
|
|
449
|
+
switch (storage.type)
|
|
450
|
+
{
|
|
451
|
+
case JsType::Object:
|
|
452
|
+
return as_object()->get_property(key);
|
|
453
|
+
case JsType::Array:
|
|
454
|
+
return as_array()->get_property(key);
|
|
455
|
+
case JsType::Function:
|
|
456
|
+
return as_function()->get_property(key);
|
|
457
|
+
case JsType::String:
|
|
458
|
+
{
|
|
459
|
+
// Check for prototype methods
|
|
460
|
+
auto proto_fn = StringPrototypes::get(key, this->storage.str);
|
|
461
|
+
if (proto_fn.has_value())
|
|
462
|
+
{
|
|
463
|
+
return resolve_property_for_read(proto_fn.value());
|
|
464
|
+
}
|
|
465
|
+
// Handle character access by string index (e.g., "abc"["1"])
|
|
466
|
+
if (JsArray::is_array_index(key))
|
|
467
|
+
{
|
|
468
|
+
uint32_t idx = static_cast<uint32_t>(std::stoull(key));
|
|
469
|
+
return get_own_property(idx);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
case JsType::Undefined:
|
|
473
|
+
throw RuntimeError::make_error("Cannot read properties of undefined (reading '" + key + "')", "TypeError");
|
|
474
|
+
case JsType::Null:
|
|
475
|
+
throw RuntimeError::make_error("Cannot read properties of null (reading '" + key + "')", "TypeError");
|
|
476
|
+
default:
|
|
477
|
+
return AnyValue::make_undefined();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
AnyValue get_own_property(uint32_t idx) noexcept
|
|
481
|
+
{
|
|
482
|
+
switch (storage.type)
|
|
483
|
+
{
|
|
484
|
+
case JsType::Array:
|
|
485
|
+
return as_array()->get_property(idx);
|
|
486
|
+
case JsType::String: // Handle character access by index (e.g., "abc"[1])
|
|
487
|
+
{
|
|
488
|
+
if (idx < storage.str->length())
|
|
489
|
+
{
|
|
490
|
+
return AnyValue::make_string(std::string(1, (*storage.str)[idx]));
|
|
491
|
+
}
|
|
492
|
+
return AnyValue::make_undefined();
|
|
493
|
+
}
|
|
494
|
+
default:
|
|
495
|
+
return get_own_property(std::to_string(idx));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
AnyValue get_own_property(const AnyValue &key) noexcept
|
|
499
|
+
{
|
|
500
|
+
if (key.storage.type == JsType::Number && storage.type == JsType::Array)
|
|
501
|
+
return storage.array->get_property(key.storage.number);
|
|
502
|
+
return get_own_property(key.to_std_string());
|
|
503
|
+
}
|
|
504
|
+
// for setting values
|
|
505
|
+
AnyValue set_own_property(const std::string &key, const AnyValue &value)
|
|
506
|
+
{
|
|
507
|
+
switch (storage.type)
|
|
508
|
+
{
|
|
509
|
+
case JsType::Object:
|
|
510
|
+
return as_object()->set_property(key, value);
|
|
511
|
+
case JsType::Array:
|
|
512
|
+
return as_array()->set_property(key, value);
|
|
513
|
+
case JsType::Function:
|
|
514
|
+
return as_function()->set_property(key, value);
|
|
515
|
+
case JsType::Undefined:
|
|
516
|
+
throw RuntimeError::make_error("Cannot set properties of undefined (setting '" + key + "')", "TypeError");
|
|
517
|
+
case JsType::Null:
|
|
518
|
+
throw RuntimeError::make_error("Cannot set properties of null (setting '" + key + "')", "TypeError");
|
|
519
|
+
default:
|
|
520
|
+
return value;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
AnyValue set_own_property(uint32_t idx, const AnyValue &value)
|
|
524
|
+
{
|
|
525
|
+
if (storage.type == JsType::Array)
|
|
526
|
+
{
|
|
527
|
+
return as_array()->set_property(idx, value);
|
|
528
|
+
}
|
|
529
|
+
return set_own_property(std::to_string(idx), value);
|
|
530
|
+
}
|
|
531
|
+
AnyValue set_own_property(const AnyValue &key, const AnyValue &value)
|
|
532
|
+
{
|
|
533
|
+
if (key.storage.type == JsType::Number && storage.type == JsType::Array)
|
|
534
|
+
{
|
|
535
|
+
return storage.array->set_property(key.storage.number, value);
|
|
536
|
+
}
|
|
537
|
+
return set_own_property(key.to_std_string(), value);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// --- HELPERS
|
|
541
|
+
const bool is_truthy() const noexcept
|
|
542
|
+
{
|
|
543
|
+
switch (storage.type)
|
|
544
|
+
{
|
|
545
|
+
case JsType::Boolean:
|
|
546
|
+
return storage.boolean;
|
|
547
|
+
case JsType::Number:
|
|
548
|
+
return storage.number != 0.0;
|
|
549
|
+
case JsType::String:
|
|
550
|
+
return !storage.str->empty();
|
|
551
|
+
default:
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const bool is_strictly_equal_to(const AnyValue &other) const noexcept
|
|
556
|
+
{
|
|
557
|
+
if (storage.type == other.storage.type)
|
|
558
|
+
{
|
|
559
|
+
switch (storage.type)
|
|
560
|
+
{
|
|
561
|
+
case JsType::Boolean:
|
|
562
|
+
return storage.boolean == other.storage.boolean;
|
|
563
|
+
case JsType::Number:
|
|
564
|
+
return storage.number == other.storage.number;
|
|
565
|
+
case JsType::String:
|
|
566
|
+
return (*storage.str.get() == *other.storage.str.get());
|
|
567
|
+
case JsType::Array:
|
|
568
|
+
return (storage.array == other.storage.array);
|
|
569
|
+
case JsType::Object:
|
|
570
|
+
return (storage.object == other.storage.object);
|
|
571
|
+
case JsType::Function:
|
|
572
|
+
return (storage.function == other.storage.function);
|
|
573
|
+
default:
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
const bool is_equal_to(const AnyValue &other) const noexcept
|
|
580
|
+
{
|
|
581
|
+
// Implements JavaScript's Abstract Equality Comparison Algorithm (==)
|
|
582
|
+
// Step 1: If types are the same, use strict equality (===)
|
|
583
|
+
if (storage.type == other.storage.type)
|
|
584
|
+
{
|
|
585
|
+
return is_strictly_equal_to(other);
|
|
586
|
+
}
|
|
587
|
+
// Steps 2 & 3: null == undefined
|
|
588
|
+
if ((is_null() && other.is_undefined()) || (is_undefined() && other.is_null()))
|
|
589
|
+
{
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
// Step 4 & 5: number == string
|
|
593
|
+
if (is_number() && other.is_string())
|
|
594
|
+
{
|
|
595
|
+
double num_this = this->as_double();
|
|
596
|
+
double num_other;
|
|
597
|
+
try
|
|
598
|
+
{
|
|
599
|
+
const std::string &s = *other.as_string();
|
|
600
|
+
// JS considers empty string or whitespace-only string to be 0
|
|
601
|
+
if (s.empty() || std::all_of(s.begin(), s.end(), [](unsigned char c)
|
|
602
|
+
{ return std::isspace(c); }))
|
|
603
|
+
{
|
|
604
|
+
num_other = 0.0;
|
|
605
|
+
}
|
|
606
|
+
else
|
|
607
|
+
{
|
|
608
|
+
size_t pos;
|
|
609
|
+
num_other = std::stod(s, &pos);
|
|
610
|
+
// Check if the entire string was consumed, allowing for trailing whitespace
|
|
611
|
+
while (pos < s.length() && std::isspace(static_cast<unsigned char>(s[pos])))
|
|
612
|
+
{
|
|
613
|
+
pos++;
|
|
614
|
+
}
|
|
615
|
+
if (pos != s.length())
|
|
616
|
+
{
|
|
617
|
+
num_other = std::numeric_limits<double>::quiet_NaN();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
catch (...)
|
|
622
|
+
{
|
|
623
|
+
num_other = std::numeric_limits<double>::quiet_NaN();
|
|
624
|
+
}
|
|
625
|
+
return num_this == num_other;
|
|
626
|
+
}
|
|
627
|
+
if (is_string() && other.is_number())
|
|
628
|
+
{
|
|
629
|
+
// Delegate to the other operand to avoid code duplication
|
|
630
|
+
return other.is_equal_to(*this);
|
|
631
|
+
}
|
|
632
|
+
// Step 6 & 7: boolean == any
|
|
633
|
+
if (is_boolean())
|
|
634
|
+
{
|
|
635
|
+
// Convert boolean to number and re-compare
|
|
636
|
+
return AnyValue::make_number(as_boolean() ? 1.0 : 0.0).is_equal_to(other);
|
|
637
|
+
}
|
|
638
|
+
if (other.is_boolean())
|
|
639
|
+
{
|
|
640
|
+
// Convert boolean to number and re-compare
|
|
641
|
+
return is_equal_to(AnyValue::make_number(other.as_boolean() ? 1.0 : 0.0));
|
|
642
|
+
}
|
|
643
|
+
// Step 8 & 9: object == (string or number)
|
|
644
|
+
if ((other.is_object() || other.is_array() || other.is_function()) && (is_string() || is_number()))
|
|
645
|
+
{
|
|
646
|
+
// Delegate to the other operand to avoid code duplication
|
|
647
|
+
return other.is_equal_to(*this);
|
|
648
|
+
}
|
|
649
|
+
if ((is_object() || is_array() || is_function()) && (other.is_string() || other.is_number()))
|
|
650
|
+
{
|
|
651
|
+
// Convert object to primitive (string) and re-compare.
|
|
652
|
+
// This is a simplification of JS's ToPrimitive which would try valueOf() first.
|
|
653
|
+
return AnyValue::make_string(to_std_string()).is_equal_to(other);
|
|
654
|
+
}
|
|
655
|
+
// Step 10: All other cases (e.g., object == null) are false.
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
const AnyValue is_strictly_equal_to_primitive(const AnyValue &other) const noexcept
|
|
659
|
+
{
|
|
660
|
+
return AnyValue::make_boolean(is_strictly_equal_to(other));
|
|
661
|
+
}
|
|
662
|
+
const AnyValue is_equal_to_primitive(const AnyValue &other) const noexcept
|
|
663
|
+
{
|
|
664
|
+
return AnyValue::make_boolean(is_equal_to(other));
|
|
665
|
+
}
|
|
666
|
+
const AnyValue not_strictly_equal_to_primitive(const AnyValue &other) const noexcept
|
|
667
|
+
{
|
|
668
|
+
return AnyValue::make_boolean(!is_strictly_equal_to(other));
|
|
669
|
+
}
|
|
670
|
+
const AnyValue not_equal_to_primitive(const AnyValue &other) const noexcept
|
|
671
|
+
{
|
|
672
|
+
return AnyValue::make_boolean(!is_equal_to(other));
|
|
673
|
+
}
|
|
674
|
+
const std::string to_std_string() const noexcept
|
|
675
|
+
{
|
|
676
|
+
switch (storage.type)
|
|
677
|
+
{
|
|
678
|
+
case JsType::Undefined:
|
|
679
|
+
return "undefined";
|
|
680
|
+
case JsType::Null:
|
|
681
|
+
return "null";
|
|
682
|
+
case JsType::Boolean:
|
|
683
|
+
return storage.boolean ? "true" : "false";
|
|
684
|
+
case JsType::String:
|
|
685
|
+
return *storage.str.get();
|
|
686
|
+
case JsType::Object:
|
|
687
|
+
return storage.object->to_std_string();
|
|
688
|
+
case JsType::Array:
|
|
689
|
+
return storage.array->to_std_string();
|
|
690
|
+
case JsType::Function:
|
|
691
|
+
return storage.function->to_std_string();
|
|
692
|
+
case JsType::DataDescriptor:
|
|
693
|
+
return storage.data_desc->value->to_std_string();
|
|
694
|
+
case JsType::AccessorDescriptor:
|
|
695
|
+
{
|
|
696
|
+
if (storage.accessor_desc->get.has_value())
|
|
697
|
+
return storage.accessor_desc->get.value()({}).to_std_string();
|
|
698
|
+
else
|
|
699
|
+
return "undefined";
|
|
700
|
+
}
|
|
701
|
+
case JsType::Number:
|
|
702
|
+
{
|
|
703
|
+
if (std::isnan(storage.number))
|
|
704
|
+
{
|
|
705
|
+
return "NaN";
|
|
706
|
+
}
|
|
707
|
+
if (std::abs(storage.number) >= 1e21 || (std::abs(storage.number) > 0 && std::abs(storage.number) < 1e-6))
|
|
708
|
+
{
|
|
709
|
+
std::ostringstream oss;
|
|
710
|
+
oss << std::scientific << std::setprecision(4) << storage.number;
|
|
711
|
+
return oss.str();
|
|
712
|
+
}
|
|
713
|
+
else
|
|
714
|
+
{
|
|
715
|
+
std::ostringstream oss;
|
|
716
|
+
oss << std::setprecision(6) << std::fixed << storage.number;
|
|
717
|
+
std::string s = oss.str();
|
|
718
|
+
s.erase(s.find_last_not_of('0') + 1, std::string::npos);
|
|
719
|
+
if (!s.empty() && s.back() == '.')
|
|
720
|
+
{
|
|
721
|
+
s.pop_back();
|
|
722
|
+
}
|
|
723
|
+
return s;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// Uninitialized and default should not be reached under normal circumstances
|
|
727
|
+
case JsType::Uninitialized:
|
|
728
|
+
return "<uninitialized>";
|
|
729
|
+
default:
|
|
730
|
+
return "";
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
}
|