@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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/dist/analysis/scope.js +77 -0
  4. package/dist/analysis/typeAnalyzer.js +224 -0
  5. package/dist/ast/types.js +1 -0
  6. package/dist/cli.js +63 -0
  7. package/dist/core/codegen/declaration-handlers.js +49 -0
  8. package/dist/core/codegen/expression-handlers.js +333 -0
  9. package/dist/core/codegen/function-handlers.js +94 -0
  10. package/dist/core/codegen/helpers.js +83 -0
  11. package/dist/core/codegen/index.js +54 -0
  12. package/dist/core/codegen/literal-handlers.js +32 -0
  13. package/dist/core/codegen/statement-handlers.js +485 -0
  14. package/dist/core/codegen/visitor.js +86 -0
  15. package/dist/core/parser.js +6 -0
  16. package/dist/core/traverser.js +19 -0
  17. package/dist/index.js +16 -0
  18. package/package.json +41 -0
  19. package/src/prelude/access.hpp +86 -0
  20. package/src/prelude/any_value.hpp +734 -0
  21. package/src/prelude/descriptors.hpp +25 -0
  22. package/src/prelude/error.hpp +31 -0
  23. package/src/prelude/error_helpers.hpp +59 -0
  24. package/src/prelude/index.hpp +29 -0
  25. package/src/prelude/library/console.hpp +111 -0
  26. package/src/prelude/library/global.hpp +10 -0
  27. package/src/prelude/library/symbol.hpp +8 -0
  28. package/src/prelude/log_string.hpp +403 -0
  29. package/src/prelude/operators.hpp +256 -0
  30. package/src/prelude/types.hpp +50 -0
  31. package/src/prelude/values/array.hpp +50 -0
  32. package/src/prelude/values/function.hpp +19 -0
  33. package/src/prelude/values/non_values.hpp +20 -0
  34. package/src/prelude/values/object.hpp +17 -0
  35. package/src/prelude/values/operators/array.hpp +165 -0
  36. package/src/prelude/values/operators/function.hpp +34 -0
  37. package/src/prelude/values/operators/object.hpp +34 -0
  38. package/src/prelude/values/prototypes/array.hpp +228 -0
  39. package/src/prelude/values/prototypes/function.hpp +0 -0
  40. package/src/prelude/values/prototypes/object.hpp +0 -0
  41. package/src/prelude/values/prototypes/string.hpp +357 -0
  42. 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
+ }