@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,25 @@
1
+ #pragma once
2
+
3
+ #include "types.hpp"
4
+ #include <optional>
5
+
6
+ namespace jspp
7
+ {
8
+ class AnyValue;
9
+
10
+ struct DataDescriptor
11
+ {
12
+ std::shared_ptr<AnyValue> value;
13
+ bool writable = true;
14
+ bool enumerable = false;
15
+ bool configurable = true;
16
+ };
17
+
18
+ struct AccessorDescriptor
19
+ {
20
+ std::optional<std::function<AnyValue(const std::vector<AnyValue> &)>> get; // getter = function or undefined
21
+ std::optional<std::function<AnyValue(const std::vector<AnyValue> &)>> set; // setter = function or undefined
22
+ bool enumerable = false;
23
+ bool configurable = true;
24
+ };
25
+ }
@@ -0,0 +1,31 @@
1
+
2
+ #pragma once
3
+
4
+ #include "types.hpp"
5
+
6
+ namespace jspp
7
+ {
8
+ class AnyValue;
9
+
10
+ struct RuntimeError : std::exception
11
+ {
12
+ std::shared_ptr<AnyValue> data;
13
+
14
+ explicit RuntimeError(std::shared_ptr<AnyValue> d)
15
+ : data(std::move(d)) {}
16
+ explicit RuntimeError(const AnyValue &value)
17
+ : data(std::make_shared<AnyValue>(value)) {}
18
+ explicit RuntimeError(AnyValue &&value)
19
+ : data(std::make_shared<AnyValue>(std::move(value))) {}
20
+
21
+ const char *what() const noexcept override;
22
+ static RuntimeError make_error(const std::string &message, const std::string &name);
23
+ static AnyValue error_to_value(const std::exception &ex);
24
+
25
+ // --- THROWERS
26
+ static AnyValue throw_unresolved_reference_error(const std::string &var_name);
27
+ static AnyValue throw_uninitialized_reference_error(const std::string &var_name);
28
+ static AnyValue throw_immutable_assignment_error();
29
+ static AnyValue throw_invalid_return_statement_error();
30
+ };
31
+ }
@@ -0,0 +1,59 @@
1
+ #pragma once
2
+
3
+ #include "types.hpp"
4
+ #include "error.hpp"
5
+ #include "any_value.hpp"
6
+
7
+ const char *jspp::RuntimeError::what() const noexcept
8
+ {
9
+ return data->to_std_string().c_str();
10
+ }
11
+
12
+ jspp::RuntimeError jspp::RuntimeError::make_error(const std::string &message, const std::string &name = "Error")
13
+ {
14
+ auto errorObj = std::make_shared<AnyValue>(AnyValue::make_object({{"message", AnyValue::make_string(message)}, {"name", AnyValue::make_string(name)}}));
15
+ (*errorObj).set_own_property(WellKnownSymbols::toString, AnyValue::make_function([errorObj](const std::vector<AnyValue> &) -> AnyValue
16
+ {
17
+ AnyValue name = (*errorObj).get_own_property("name");
18
+ AnyValue message = (*errorObj).get_own_property("message");
19
+ std::string str = "";
20
+ if (name.is_string())
21
+ str = name.to_std_string();
22
+ else
23
+ str = "Error";
24
+ str += ": ";
25
+ if (message.is_string())
26
+ str += message.to_std_string();
27
+ return AnyValue::make_string(str); },
28
+ WellKnownSymbols::toString));
29
+ return RuntimeError(errorObj);
30
+ }
31
+ jspp::AnyValue jspp::RuntimeError::error_to_value(const std::exception &ex)
32
+ {
33
+ if (const jspp::RuntimeError *err = dynamic_cast<const jspp::RuntimeError *>(&ex))
34
+ {
35
+ return (*err->data);
36
+ }
37
+ else
38
+ {
39
+ return AnyValue::make_string(ex.what());
40
+ }
41
+ }
42
+
43
+ // --- THROWERS
44
+ jspp::AnyValue jspp::RuntimeError::throw_unresolved_reference_error(const std::string &var_name)
45
+ {
46
+ throw RuntimeError::make_error(var_name + " is not defined", "ReferenceError");
47
+ }
48
+ jspp::AnyValue jspp::RuntimeError::throw_uninitialized_reference_error(const std::string &var_name)
49
+ {
50
+ throw RuntimeError::make_error("Cannot access '" + var_name + "' before initialization", "ReferenceError");
51
+ }
52
+ jspp::AnyValue jspp::RuntimeError::throw_immutable_assignment_error()
53
+ {
54
+ throw RuntimeError::make_error("Assignment to constant variable.", "TypeError");
55
+ }
56
+ jspp::AnyValue jspp::RuntimeError::throw_invalid_return_statement_error()
57
+ {
58
+ throw RuntimeError::make_error("Return statements are only valid inside functions.", "SyntaxError");
59
+ }
@@ -0,0 +1,29 @@
1
+ #pragma once
2
+
3
+ #include "types.hpp"
4
+ #include "well_known_symbols.hpp"
5
+
6
+ // values
7
+ #include "values/non_values.hpp"
8
+ #include "values/object.hpp"
9
+ #include "values/array.hpp"
10
+ #include "values/function.hpp"
11
+ #include "error.hpp"
12
+ #include "descriptors.hpp"
13
+ #include "any_value.hpp"
14
+ #include "error_helpers.hpp"
15
+ #include "values/prototypes/string.hpp"
16
+ #include "values/prototypes/object.hpp"
17
+ #include "values/prototypes/array.hpp"
18
+ #include "values/prototypes/function.hpp"
19
+ #include "values/operators/object.hpp"
20
+ #include "values/operators/array.hpp"
21
+ #include "values/operators/function.hpp"
22
+
23
+ // helpers
24
+ #include "operators.hpp"
25
+ #include "access.hpp"
26
+ #include "log_string.hpp"
27
+
28
+ // js standard libraries
29
+ #include "library/console.hpp"
@@ -0,0 +1,111 @@
1
+ #pragma once
2
+
3
+ #include "chrono"
4
+ #include "types.hpp"
5
+ #include "values/non_values.hpp"
6
+ #include "values/object.hpp"
7
+ #include "values/function.hpp"
8
+ #include "operators.hpp"
9
+ #include "log_string.hpp"
10
+
11
+ #include <cmath>
12
+ #include <sstream>
13
+ #include <iomanip>
14
+
15
+ static std::unordered_map<std::string, std::chrono::steady_clock::time_point> timers = {};
16
+
17
+ auto logFn = jspp::AnyValue::make_function([](const std::vector<jspp::AnyValue> &args)
18
+ {
19
+ for (size_t i = 0; i < args.size(); ++i)
20
+ {
21
+ std::cout << jspp::LogString::to_log_string(args[i]);
22
+ if (i < args.size() - 1)
23
+ std::cout << " ";
24
+ }
25
+ std::cout << "\n";
26
+ return jspp::AnyValue::make_undefined(); }, "");
27
+ auto warnFn = jspp::AnyValue::make_function([](const std::vector<jspp::AnyValue> &args)
28
+ {
29
+ std::cerr << "\033[33m";
30
+ for (size_t i = 0; i < args.size(); ++i)
31
+ {
32
+ std::cout << jspp::LogString::to_log_string(args[i]);
33
+ if (i < args.size() - 1)
34
+ std::cout << " ";
35
+ }
36
+ std::cerr << "\033[0m" << "\n"; // reset
37
+ return jspp::AnyValue::make_undefined(); }, "");
38
+ auto errorFn = jspp::AnyValue::make_function([](const std::vector<jspp::AnyValue> &args)
39
+ {
40
+ std::cerr << "\033[31m";
41
+ for (size_t i = 0; i < args.size(); ++i)
42
+ {
43
+ std::cout << jspp::LogString::to_log_string(args[i]);
44
+ if (i < args.size() - 1)
45
+ std::cout << " ";
46
+ }
47
+ std::cerr << "\033[0m" << "\n"; // reset
48
+ return jspp::AnyValue::make_undefined(); }, "");
49
+ auto timeFn = jspp::AnyValue::make_function([](const std::vector<jspp::AnyValue> &args)
50
+ {
51
+ auto start = std::chrono::steady_clock::now(); // capture immediately
52
+ auto key_str = args.size() > 0 ? args[0].to_std_string() : "default";
53
+ timers[key_str] = start;
54
+ return jspp::AnyValue::make_undefined(); }, "");
55
+
56
+ // helper to format duration in ms -> ms/s/m/h with nice precision
57
+ static auto format_duration = [](double ms) -> std::string
58
+ {
59
+ std::ostringstream ss;
60
+ if (ms < 1000.0)
61
+ {
62
+ ss << std::fixed << std::setprecision(4) << ms << "ms";
63
+ return ss.str();
64
+ }
65
+ double total_s = ms / 1000.0;
66
+ if (ms < 60000.0)
67
+ { // less than a minute -> show seconds
68
+ ss << std::fixed << std::setprecision(4) << total_s << "s";
69
+ return ss.str();
70
+ }
71
+ if (ms < 3600000.0)
72
+ { // less than an hour -> show minutes + seconds
73
+ int minutes = static_cast<int>(ms / 60000.0);
74
+ double seconds = (ms - minutes * 60000.0) / 1000.0;
75
+ ss << minutes << "m " << std::fixed << std::setprecision(4) << seconds << "s";
76
+ return ss.str();
77
+ }
78
+ // hours, minutes, seconds
79
+ int hours = static_cast<int>(ms / 3600000.0);
80
+ int minutes = static_cast<int>((ms - hours * 3600000.0) / 60000.0);
81
+ double seconds = (ms - hours * 3600000.0 - minutes * 60000.0) / 1000.0;
82
+ ss << hours << "h " << minutes << "m " << std::fixed << std::setprecision(4) << seconds << "s";
83
+ return ss.str();
84
+ };
85
+
86
+ auto timeEndFn = jspp::AnyValue::make_function([](const std::vector<jspp::AnyValue> &args)
87
+ {
88
+ auto end = std::chrono::steady_clock::now(); // capture immediately
89
+ auto key_str = args.size() > 0 ? args[0].to_std_string() : "default";
90
+ auto it = timers.find(key_str);
91
+ if (it != timers.end())
92
+ {
93
+ std::chrono::duration<double, std::milli> duration = end - it->second;
94
+ double ms = duration.count();
95
+ std::string formatted = format_duration(ms);
96
+ std::cout << "\033[90m" << "[" << format_duration(ms) << "] " << "\033[0m" << key_str << "\n";
97
+ timers.erase(it); // remove the timer after ending it
98
+ }
99
+ else
100
+ {
101
+ std::cout << "Timer '" << key_str << "' does not exist." << "\n";
102
+ }
103
+ return jspp::AnyValue::make_undefined(); }, "");
104
+
105
+ inline auto console = jspp::AnyValue::make_object({
106
+ {"log", logFn},
107
+ {"warn", warnFn},
108
+ {"error", errorFn},
109
+ {"time", timeFn},
110
+ {"timeEnd", timeEndFn},
111
+ });
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+
3
+ #include "types.hpp"
4
+ #include "object.hpp"
5
+ #include "console.hpp"
6
+
7
+ inline auto global = jspp::Object::make_object({
8
+ {"console", console},
9
+ });
10
+ inline auto globalThis = global;
@@ -0,0 +1,8 @@
1
+ #pragma once
2
+
3
+ #include "types.hpp"
4
+ #include "object.hpp"
5
+ #include "well_known_symbols.hpp"
6
+
7
+ inline auto Symbol = jspp::Object::make_object({{"iterator", jspp::Object::make_string(jspp::WellKnownSymbols::iterator)},
8
+ {"toString", jspp::Object::make_string(jspp::WellKnownSymbols::toString)}});
@@ -0,0 +1,403 @@
1
+ #pragma once
2
+
3
+ #include "types.hpp"
4
+ #include "well_known_symbols.hpp"
5
+ #include "any_value.hpp"
6
+ #include <sstream>
7
+ #include <unordered_set>
8
+ #include <algorithm>
9
+
10
+ namespace jspp
11
+ {
12
+ namespace LogString
13
+ {
14
+ // --- Configuration for Logging Verbosity ---
15
+ const int MAX_DEPTH = 5;
16
+ const size_t MAX_STRING_LENGTH = 100;
17
+ const size_t MAX_ARRAY_ITEMS = 50;
18
+ const size_t MAX_OBJECT_PROPS = 30;
19
+ // --- Configuration for Horizontal Layout ---
20
+ const size_t HORIZONTAL_ARRAY_MAX_ITEMS = 10;
21
+ const size_t HORIZONTAL_OBJECT_MAX_PROPS = 5;
22
+
23
+ // ANSI Color Codes for terminal output
24
+ namespace Color
25
+ {
26
+ const std::string RESET = "\033[0m";
27
+ const std::string GREEN = "\033[32m";
28
+ const std::string YELLOW = "\033[33m";
29
+ const std::string CYAN = "\033[36m";
30
+ const std::string MAGENTA = "\033[35m";
31
+ const std::string BRIGHT_BLACK = "\033[90m"; // Grey
32
+ }
33
+
34
+ // Forward declarations
35
+ inline std::string to_log_string(const AnyValue &val);
36
+ inline std::string to_log_string(const AnyValue &val, std::unordered_set<const void *> &visited, int depth);
37
+ inline bool is_simple_value(const AnyValue &val);
38
+
39
+ inline bool is_valid_js_identifier(const std::string &s)
40
+ {
41
+ if (s.empty())
42
+ {
43
+ return false;
44
+ }
45
+ if (!std::isalpha(s[0]) && s[0] != '_' && s[0] != '$')
46
+ {
47
+ return false;
48
+ }
49
+ for (size_t i = 1; i < s.length(); ++i)
50
+ {
51
+ if (!std::isalnum(s[i]) && s[i] != '_' && s[i] != '$')
52
+ {
53
+ return false;
54
+ }
55
+ }
56
+ return true;
57
+ }
58
+
59
+ inline bool is_simple_value(const AnyValue &val)
60
+ {
61
+ return val.is_undefined() || val.is_null() || val.is_uninitialized() ||
62
+ val.is_boolean() || val.is_number() || val.is_string();
63
+ }
64
+
65
+ inline std::string truncate_string(const std::string &str)
66
+ {
67
+ if (str.length() > MAX_STRING_LENGTH)
68
+ {
69
+ return str.substr(0, MAX_STRING_LENGTH) + "...";
70
+ }
71
+ return str;
72
+ }
73
+
74
+ inline std::string to_log_string(const AnyValue &val)
75
+ {
76
+ std::unordered_set<const void *> visited;
77
+ return to_log_string(val, visited, 0);
78
+ }
79
+
80
+ inline std::string to_log_string(const AnyValue &val, std::unordered_set<const void *> &visited, int depth)
81
+ {
82
+ // Primitives and simple wrapped values
83
+ if (val.is_uninitialized())
84
+ return Color::BRIGHT_BLACK + std::string("<uninitialized>") + Color::RESET;
85
+ if (val.is_undefined())
86
+ return Color::BRIGHT_BLACK + std::string("undefined") + Color::RESET;
87
+ if (val.is_null())
88
+ return Color::MAGENTA + std::string("null") + Color::RESET;
89
+ if (val.is_boolean())
90
+ return Color::YELLOW + std::string(val.as_boolean() ? "true" : "false") + Color::RESET;
91
+ if (val.is_number())
92
+ return Color::YELLOW + val.to_std_string() + Color::RESET;
93
+ if (val.is_string())
94
+ {
95
+ const std::string &s = *val.as_string();
96
+ if (depth == 0)
97
+ return truncate_string(s);
98
+ return Color::GREEN + std::string("\"") + truncate_string(s) + "\"" + Color::RESET;
99
+ }
100
+ if (val.is_function())
101
+ {
102
+ auto fn = val.as_function();
103
+ auto name_part = fn->name.size() > 0 ? ": " + fn->name : "";
104
+ return Color::CYAN + std::string("[Function") + name_part + "]" + Color::RESET;
105
+ }
106
+
107
+ // Depth limit
108
+ if (depth > MAX_DEPTH)
109
+ {
110
+ if (val.is_object())
111
+ return Color::CYAN + std::string("[Object]") + Color::RESET;
112
+ if (val.is_array())
113
+ return Color::CYAN + std::string("[Array]") + Color::RESET;
114
+ }
115
+
116
+ // Circular reference detection
117
+ const void *ptr_address = nullptr;
118
+ if (val.is_object())
119
+ ptr_address = val.as_object();
120
+ else if (val.is_array())
121
+ ptr_address = val.as_array();
122
+
123
+ if (ptr_address)
124
+ {
125
+ if (visited.count(ptr_address))
126
+ return Color::CYAN + std::string("[Circular]") + Color::RESET;
127
+ visited.insert(ptr_address);
128
+ }
129
+
130
+ std::string indent(depth * 2, ' ');
131
+ std::string next_indent((depth + 1) * 2, ' ');
132
+ std::stringstream ss;
133
+
134
+ // Objects
135
+ if (val.is_object())
136
+ {
137
+ auto obj = val.as_object();
138
+
139
+ // If custom toString exists on the object, prefer it
140
+ auto itToString = obj->props.find(jspp::WellKnownSymbols::toString);
141
+ if (itToString != obj->props.end() && itToString->second.is_function())
142
+ {
143
+ try
144
+ {
145
+ auto result = itToString->second.as_function("toString")->call({});
146
+ return to_log_string(result, visited, depth);
147
+ }
148
+ catch (...)
149
+ {
150
+ // ignore and fallback to manual formatting
151
+ }
152
+ }
153
+
154
+ size_t prop_count = obj->props.size();
155
+
156
+ bool use_horizontal_layout = prop_count > 0 && prop_count <= HORIZONTAL_OBJECT_MAX_PROPS;
157
+ if (use_horizontal_layout)
158
+ {
159
+ for (const auto &pair : obj->props)
160
+ {
161
+ if (!is_simple_value(pair.second))
162
+ {
163
+ use_horizontal_layout = false;
164
+ break;
165
+ }
166
+ }
167
+ }
168
+
169
+ if (use_horizontal_layout)
170
+ {
171
+ ss << "{ ";
172
+ size_t current_prop = 0;
173
+ for (const auto &pair : obj->props)
174
+ {
175
+ if (is_valid_js_identifier(pair.first))
176
+ {
177
+ ss << pair.first;
178
+ }
179
+ else
180
+ {
181
+ ss << "\"" << pair.first << "\"";
182
+ }
183
+ ss << ": " << to_log_string(pair.second, visited, depth + 1);
184
+ if (++current_prop < prop_count)
185
+ ss << Color::BRIGHT_BLACK << ", " << Color::RESET;
186
+ }
187
+ ss << " }";
188
+ }
189
+ else
190
+ {
191
+ ss << "{";
192
+ if (prop_count > 0)
193
+ {
194
+ ss << "\n";
195
+ size_t props_shown = 0;
196
+ for (const auto &pair : obj->props)
197
+ {
198
+ if (props_shown >= MAX_OBJECT_PROPS)
199
+ break;
200
+ if (props_shown > 0)
201
+ ss << ",\n";
202
+
203
+ ss << next_indent;
204
+ if (is_valid_js_identifier(pair.first))
205
+ {
206
+ ss << pair.first;
207
+ }
208
+ else
209
+ {
210
+ ss << "\"" << pair.first << "\"";
211
+ }
212
+ ss << ": " << to_log_string(pair.second, visited, depth + 1);
213
+ props_shown++;
214
+ }
215
+ if (prop_count > MAX_OBJECT_PROPS)
216
+ ss << ",\n"
217
+ << next_indent << Color::BRIGHT_BLACK << "... " << (prop_count - MAX_OBJECT_PROPS) << " more properties" << Color::RESET;
218
+ ss << "\n"
219
+ << indent;
220
+ }
221
+ ss << "}";
222
+ }
223
+ return ss.str();
224
+ }
225
+
226
+ // Arrays
227
+ if (val.is_array())
228
+ {
229
+ auto arr = val.as_array();
230
+ size_t item_count = static_cast<size_t>(arr->length);
231
+
232
+ // If custom toString exists on the object, prefer it
233
+ auto itToString = arr->props.find(jspp::WellKnownSymbols::toString);
234
+ if (depth > 0 && itToString != arr->props.end() && itToString->second.is_function())
235
+ {
236
+ try
237
+ {
238
+ auto result = itToString->second.as_function("toString")->call({});
239
+ return to_log_string(result, visited, depth);
240
+ }
241
+ catch (...)
242
+ {
243
+ // ignore and fallback to manual formatting
244
+ }
245
+ }
246
+
247
+ std::string indent(depth * 2, ' ');
248
+ std::string next_indent((depth + 1) * 2, ' ');
249
+ std::stringstream ss;
250
+
251
+ // Horizontal layout for small and simple arrays
252
+ bool use_horizontal_layout = item_count <= HORIZONTAL_ARRAY_MAX_ITEMS;
253
+ if (use_horizontal_layout)
254
+ {
255
+ for (size_t i = 0; i < item_count; ++i)
256
+ {
257
+ std::optional<AnyValue> itemVal;
258
+ if (i < arr->dense.size())
259
+ {
260
+ itemVal = arr->dense[i];
261
+ }
262
+ else
263
+ {
264
+ auto it = arr->sparse.find(static_cast<uint32_t>(i));
265
+ if (it != arr->sparse.end())
266
+ {
267
+ itemVal = it->second;
268
+ }
269
+ }
270
+ if (itemVal.has_value() && !is_simple_value(itemVal.value()))
271
+ {
272
+ use_horizontal_layout = false;
273
+ break;
274
+ }
275
+ }
276
+ }
277
+
278
+ if (use_horizontal_layout)
279
+ {
280
+ ss << "[ ";
281
+ size_t empty_count = 0;
282
+ bool needs_comma = false;
283
+
284
+ for (size_t i = 0; i < item_count; ++i)
285
+ {
286
+ std::optional<AnyValue> itemVal;
287
+ if (i < arr->dense.size())
288
+ {
289
+ itemVal = arr->dense[i];
290
+ }
291
+ else
292
+ {
293
+ auto it = arr->sparse.find(static_cast<uint32_t>(i));
294
+ if (it != arr->sparse.end())
295
+ {
296
+ itemVal = it->second;
297
+ }
298
+ }
299
+
300
+ if (itemVal.has_value())
301
+ {
302
+ if (empty_count > 0)
303
+ {
304
+ if (needs_comma)
305
+ ss << Color::BRIGHT_BLACK << ", " << Color::RESET;
306
+ ss << Color::BRIGHT_BLACK << empty_count << " x empty item" << (empty_count > 1 ? "s" : "") << Color::RESET;
307
+ needs_comma = true;
308
+ empty_count = 0;
309
+ }
310
+ if (needs_comma)
311
+ ss << Color::BRIGHT_BLACK << ", " << Color::RESET;
312
+ ss << to_log_string(itemVal.value(), visited, depth + 1);
313
+ needs_comma = true;
314
+ }
315
+ else
316
+ {
317
+ empty_count++;
318
+ }
319
+ }
320
+
321
+ if (empty_count > 0)
322
+ {
323
+ if (needs_comma)
324
+ ss << Color::BRIGHT_BLACK << ", " << Color::RESET;
325
+ ss << Color::BRIGHT_BLACK << empty_count << " x empty item" << (empty_count > 1 ? "s" : "") << Color::RESET;
326
+ }
327
+
328
+ ss << " ]";
329
+ return ss.str();
330
+ }
331
+
332
+ // Bun-like multi-line layout
333
+ ss << "[\n";
334
+
335
+ const size_t items_to_show = std::min(item_count, MAX_ARRAY_ITEMS);
336
+ size_t empty_count = 0;
337
+ bool first_item_printed = false;
338
+
339
+ for (size_t i = 0; i < items_to_show; ++i)
340
+ {
341
+ std::optional<AnyValue> itemVal;
342
+ if (i < arr->dense.size())
343
+ {
344
+ itemVal = arr->dense[i];
345
+ }
346
+ else
347
+ {
348
+ auto it = arr->sparse.find(static_cast<uint32_t>(i));
349
+ if (it != arr->sparse.end())
350
+ {
351
+ itemVal = it->second;
352
+ }
353
+ }
354
+
355
+ if (itemVal.has_value())
356
+ {
357
+ if (empty_count > 0)
358
+ {
359
+ if (first_item_printed)
360
+ ss << Color::BRIGHT_BLACK << ",\n"
361
+ << Color::RESET;
362
+ ss << next_indent << Color::BRIGHT_BLACK << empty_count << " x empty item" << (empty_count > 1 ? "s" : "") << Color::RESET;
363
+ first_item_printed = true;
364
+ empty_count = 0;
365
+ }
366
+ if (first_item_printed)
367
+ ss << Color::BRIGHT_BLACK << ",\n"
368
+ << Color::RESET;
369
+ ss << next_indent << to_log_string(itemVal.value(), visited, depth + 1);
370
+ first_item_printed = true;
371
+ }
372
+ else
373
+ {
374
+ empty_count++;
375
+ }
376
+ }
377
+
378
+ if (empty_count > 0)
379
+ {
380
+ if (first_item_printed)
381
+ ss << Color::BRIGHT_BLACK << ",\n"
382
+ << Color::RESET;
383
+ ss << next_indent << Color::BRIGHT_BLACK << empty_count << " x empty item" << (empty_count > 1 ? "s" : "") << Color::RESET;
384
+ first_item_printed = true;
385
+ }
386
+
387
+ if (item_count > items_to_show)
388
+ {
389
+ if (first_item_printed)
390
+ ss << Color::BRIGHT_BLACK << ",\n"
391
+ << Color::RESET;
392
+ ss << next_indent << Color::BRIGHT_BLACK << "... " << (item_count - items_to_show) << " more items" << Color::RESET;
393
+ }
394
+ ss << "\n";
395
+ ss << indent << "]";
396
+ return ss.str();
397
+ }
398
+
399
+ // Fallback
400
+ return val.to_std_string();
401
+ }
402
+ }
403
+ }