mustardscript 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 (99) hide show
  1. package/Cargo.lock +1579 -0
  2. package/Cargo.toml +40 -0
  3. package/LICENSE +201 -0
  4. package/README.md +828 -0
  5. package/SECURITY.md +34 -0
  6. package/crates/mustard/Cargo.toml +31 -0
  7. package/crates/mustard/src/cancellation.rs +28 -0
  8. package/crates/mustard/src/diagnostic.rs +145 -0
  9. package/crates/mustard/src/ir.rs +435 -0
  10. package/crates/mustard/src/lib.rs +21 -0
  11. package/crates/mustard/src/limits.rs +22 -0
  12. package/crates/mustard/src/parser/expressions.rs +723 -0
  13. package/crates/mustard/src/parser/mod.rs +115 -0
  14. package/crates/mustard/src/parser/operators.rs +105 -0
  15. package/crates/mustard/src/parser/patterns.rs +123 -0
  16. package/crates/mustard/src/parser/scope.rs +107 -0
  17. package/crates/mustard/src/parser/statements.rs +298 -0
  18. package/crates/mustard/src/parser/tests/acceptance.rs +339 -0
  19. package/crates/mustard/src/parser/tests/mod.rs +2 -0
  20. package/crates/mustard/src/parser/tests/rejections.rs +107 -0
  21. package/crates/mustard/src/runtime/accounting.rs +613 -0
  22. package/crates/mustard/src/runtime/api.rs +192 -0
  23. package/crates/mustard/src/runtime/async_runtime/mod.rs +5 -0
  24. package/crates/mustard/src/runtime/async_runtime/promises.rs +246 -0
  25. package/crates/mustard/src/runtime/async_runtime/reactions.rs +400 -0
  26. package/crates/mustard/src/runtime/async_runtime/scheduler.rs +224 -0
  27. package/crates/mustard/src/runtime/builtins/arrays.rs +1205 -0
  28. package/crates/mustard/src/runtime/builtins/collections.rs +573 -0
  29. package/crates/mustard/src/runtime/builtins/install.rs +501 -0
  30. package/crates/mustard/src/runtime/builtins/intl.rs +553 -0
  31. package/crates/mustard/src/runtime/builtins/mod.rs +25 -0
  32. package/crates/mustard/src/runtime/builtins/objects.rs +405 -0
  33. package/crates/mustard/src/runtime/builtins/primitives.rs +859 -0
  34. package/crates/mustard/src/runtime/builtins/promises.rs +335 -0
  35. package/crates/mustard/src/runtime/builtins/regexp.rs +356 -0
  36. package/crates/mustard/src/runtime/builtins/strings.rs +803 -0
  37. package/crates/mustard/src/runtime/builtins/support.rs +561 -0
  38. package/crates/mustard/src/runtime/bytecode.rs +123 -0
  39. package/crates/mustard/src/runtime/compiler/assignments.rs +690 -0
  40. package/crates/mustard/src/runtime/compiler/bindings.rs +92 -0
  41. package/crates/mustard/src/runtime/compiler/context.rs +46 -0
  42. package/crates/mustard/src/runtime/compiler/control.rs +342 -0
  43. package/crates/mustard/src/runtime/compiler/expressions.rs +372 -0
  44. package/crates/mustard/src/runtime/compiler/mod.rs +173 -0
  45. package/crates/mustard/src/runtime/compiler/statements.rs +459 -0
  46. package/crates/mustard/src/runtime/conversions/boundary.rs +293 -0
  47. package/crates/mustard/src/runtime/conversions/coercions.rs +217 -0
  48. package/crates/mustard/src/runtime/conversions/errors.rs +118 -0
  49. package/crates/mustard/src/runtime/conversions/mod.rs +14 -0
  50. package/crates/mustard/src/runtime/conversions/operators.rs +334 -0
  51. package/crates/mustard/src/runtime/env.rs +355 -0
  52. package/crates/mustard/src/runtime/exceptions.rs +377 -0
  53. package/crates/mustard/src/runtime/gc.rs +595 -0
  54. package/crates/mustard/src/runtime/mod.rs +318 -0
  55. package/crates/mustard/src/runtime/properties.rs +1762 -0
  56. package/crates/mustard/src/runtime/serialization.rs +127 -0
  57. package/crates/mustard/src/runtime/shared.rs +108 -0
  58. package/crates/mustard/src/runtime/snapshot_validation_tests.rs +93 -0
  59. package/crates/mustard/src/runtime/state.rs +652 -0
  60. package/crates/mustard/src/runtime/tests/async_host.rs +104 -0
  61. package/crates/mustard/src/runtime/tests/collections.rs +50 -0
  62. package/crates/mustard/src/runtime/tests/diagnostics.rs +36 -0
  63. package/crates/mustard/src/runtime/tests/exceptions.rs +122 -0
  64. package/crates/mustard/src/runtime/tests/execution.rs +553 -0
  65. package/crates/mustard/src/runtime/tests/gc.rs +533 -0
  66. package/crates/mustard/src/runtime/tests/mod.rs +56 -0
  67. package/crates/mustard/src/runtime/tests/serialization.rs +170 -0
  68. package/crates/mustard/src/runtime/validation/bytecode.rs +484 -0
  69. package/crates/mustard/src/runtime/validation/mod.rs +14 -0
  70. package/crates/mustard/src/runtime/validation/policy.rs +94 -0
  71. package/crates/mustard/src/runtime/validation/snapshot.rs +406 -0
  72. package/crates/mustard/src/runtime/validation/walk.rs +206 -0
  73. package/crates/mustard/src/runtime/vm.rs +1016 -0
  74. package/crates/mustard/src/span.rs +22 -0
  75. package/crates/mustard/src/structured.rs +107 -0
  76. package/crates/mustard-bridge/Cargo.toml +17 -0
  77. package/crates/mustard-bridge/src/codec.rs +46 -0
  78. package/crates/mustard-bridge/src/dto.rs +99 -0
  79. package/crates/mustard-bridge/src/lib.rs +12 -0
  80. package/crates/mustard-bridge/src/operations.rs +142 -0
  81. package/crates/mustard-node/Cargo.toml +24 -0
  82. package/crates/mustard-node/build.rs +3 -0
  83. package/crates/mustard-node/src/lib.rs +236 -0
  84. package/crates/mustard-sidecar/Cargo.toml +21 -0
  85. package/crates/mustard-sidecar/src/lib.rs +134 -0
  86. package/crates/mustard-sidecar/src/main.rs +36 -0
  87. package/dist/index.js +20 -0
  88. package/dist/install.js +117 -0
  89. package/dist/lib/cancellation.js +124 -0
  90. package/dist/lib/errors.js +46 -0
  91. package/dist/lib/executor.js +555 -0
  92. package/dist/lib/policy.js +292 -0
  93. package/dist/lib/progress.js +356 -0
  94. package/dist/lib/runtime.js +109 -0
  95. package/dist/lib/structured.js +286 -0
  96. package/dist/native-loader.js +227 -0
  97. package/index.d.ts +23 -0
  98. package/mustard.d.ts +220 -0
  99. package/package.json +97 -0
@@ -0,0 +1,561 @@
1
+ use std::time::{SystemTime, UNIX_EPOCH};
2
+
3
+ use super::*;
4
+
5
+ const MAX_TIME_MS: f64 = 8_640_000_000_000_000.0;
6
+ const MS_PER_SECOND: i64 = 1_000;
7
+ const MS_PER_MINUTE: i64 = 60 * MS_PER_SECOND;
8
+ const MS_PER_HOUR: i64 = 60 * MS_PER_MINUTE;
9
+ const MS_PER_DAY: i64 = 24 * MS_PER_HOUR;
10
+
11
+ #[derive(Debug, Clone, Copy)]
12
+ pub(super) struct RegExpFlagsState {
13
+ pub(super) global: bool,
14
+ pub(super) ignore_case: bool,
15
+ pub(super) multiline: bool,
16
+ pub(super) dot_all: bool,
17
+ pub(super) unicode: bool,
18
+ pub(super) sticky: bool,
19
+ }
20
+
21
+ #[derive(Debug, Clone)]
22
+ pub(crate) struct RegExpMatchData {
23
+ pub(crate) start_byte: usize,
24
+ pub(crate) end_byte: usize,
25
+ pub(crate) start_index: usize,
26
+ pub(crate) end_index: usize,
27
+ pub(crate) captures: Vec<Option<String>>,
28
+ pub(crate) named_groups: IndexMap<String, Option<String>>,
29
+ }
30
+
31
+ #[derive(Debug, Clone)]
32
+ pub(super) enum StringSearchPattern {
33
+ Literal(String),
34
+ RegExp {
35
+ object: ObjectKey,
36
+ regex: RegExpObject,
37
+ },
38
+ }
39
+
40
+ #[derive(Debug, Clone, Copy)]
41
+ pub(super) struct DateTimeFields {
42
+ pub(super) year: i64,
43
+ pub(super) month: u8,
44
+ pub(super) day: u8,
45
+ pub(super) hour: u8,
46
+ pub(super) minute: u8,
47
+ pub(super) second: u8,
48
+ pub(super) millisecond: u16,
49
+ }
50
+
51
+ pub(super) fn current_time_millis() -> f64 {
52
+ SystemTime::now()
53
+ .duration_since(UNIX_EPOCH)
54
+ .map(|duration| duration.as_millis() as f64)
55
+ .unwrap_or(0.0)
56
+ }
57
+
58
+ pub(super) fn parse_date_timestamp_ms(value: &str) -> f64 {
59
+ parse_iso_date_timestamp_ms(value).unwrap_or(f64::NAN)
60
+ }
61
+
62
+ pub(super) fn time_clip(timestamp_ms: f64) -> f64 {
63
+ if !timestamp_ms.is_finite() || timestamp_ms.abs() > MAX_TIME_MS {
64
+ f64::NAN
65
+ } else {
66
+ let clipped = timestamp_ms.trunc();
67
+ if clipped == 0.0 { 0.0 } else { clipped }
68
+ }
69
+ }
70
+
71
+ fn parse_iso_date_timestamp_ms(value: &str) -> Option<f64> {
72
+ let (year, mut index) = parse_iso_year(value)?;
73
+ index += 1;
74
+ let month = parse_two_digits(value, &mut index)?;
75
+ require_byte(value, &mut index, b'-')?;
76
+ let day = parse_two_digits(value, &mut index)?;
77
+ if !is_valid_date(year, month, day) {
78
+ return None;
79
+ }
80
+
81
+ let days = days_from_civil(year, month, day);
82
+ if index == value.len() {
83
+ return Some(days as f64 * MS_PER_DAY as f64);
84
+ }
85
+
86
+ require_byte(value, &mut index, b'T')?;
87
+ let hour = parse_two_digits(value, &mut index)?;
88
+ require_byte(value, &mut index, b':')?;
89
+ let minute = parse_two_digits(value, &mut index)?;
90
+ require_byte(value, &mut index, b':')?;
91
+ let second = parse_two_digits(value, &mut index)?;
92
+ if hour > 23 || minute > 59 || second > 59 {
93
+ return None;
94
+ }
95
+
96
+ let mut millisecond = 0i64;
97
+ if matches!(value.as_bytes().get(index), Some(b'.')) {
98
+ index += 1;
99
+ let start = index;
100
+ while value.as_bytes().get(index).is_some_and(u8::is_ascii_digit) {
101
+ index += 1;
102
+ }
103
+ if index == start {
104
+ return None;
105
+ }
106
+ let digits = &value.as_bytes()[start..index];
107
+ let mut parsed = 0i64;
108
+ for digit in digits.iter().take(3) {
109
+ parsed = parsed * 10 + i64::from(digit - b'0');
110
+ }
111
+ for _ in digits.len().min(3)..3 {
112
+ parsed *= 10;
113
+ }
114
+ millisecond = parsed;
115
+ }
116
+
117
+ let offset_ms = match value.as_bytes().get(index).copied() {
118
+ Some(b'Z') if index + 1 == value.len() => 0i64,
119
+ Some(sign @ (b'+' | b'-')) => {
120
+ index += 1;
121
+ let offset_hours = parse_two_digits(value, &mut index)?;
122
+ require_byte(value, &mut index, b':')?;
123
+ let offset_minutes = parse_two_digits(value, &mut index)?;
124
+ if offset_hours > 23 || offset_minutes > 59 || index != value.len() {
125
+ return None;
126
+ }
127
+ let magnitude =
128
+ i64::from(offset_hours) * MS_PER_HOUR + i64::from(offset_minutes) * MS_PER_MINUTE;
129
+ if sign == b'+' { magnitude } else { -magnitude }
130
+ }
131
+ _ => return None,
132
+ };
133
+
134
+ let time_ms = i64::from(hour) * MS_PER_HOUR
135
+ + i64::from(minute) * MS_PER_MINUTE
136
+ + i64::from(second) * MS_PER_SECOND
137
+ + millisecond;
138
+ let timestamp_ms =
139
+ i128::from(days) * i128::from(MS_PER_DAY) + i128::from(time_ms) - i128::from(offset_ms);
140
+ if !(i128::from(i64::MIN)..=i128::from(i64::MAX)).contains(&timestamp_ms) {
141
+ return None;
142
+ }
143
+ Some(timestamp_ms as f64)
144
+ }
145
+
146
+ pub(super) fn date_time_fields_from_timestamp_ms(timestamp_ms: f64) -> Option<DateTimeFields> {
147
+ if !timestamp_ms.is_finite() {
148
+ return None;
149
+ }
150
+ let timestamp_ms = if timestamp_ms.trunc() == 0.0 {
151
+ 0.0
152
+ } else {
153
+ timestamp_ms.trunc()
154
+ };
155
+ if timestamp_ms < i64::MIN as f64 || timestamp_ms > i64::MAX as f64 {
156
+ return None;
157
+ }
158
+ let timestamp_ms = timestamp_ms as i64;
159
+ let days = timestamp_ms.div_euclid(MS_PER_DAY);
160
+ let day_ms = timestamp_ms.rem_euclid(MS_PER_DAY);
161
+ let (year, month, day) = civil_from_days(days);
162
+ Some(DateTimeFields {
163
+ year,
164
+ month,
165
+ day,
166
+ hour: (day_ms / MS_PER_HOUR) as u8,
167
+ minute: ((day_ms % MS_PER_HOUR) / MS_PER_MINUTE) as u8,
168
+ second: ((day_ms % MS_PER_MINUTE) / MS_PER_SECOND) as u8,
169
+ millisecond: (day_ms % MS_PER_SECOND) as u16,
170
+ })
171
+ }
172
+
173
+ pub(super) fn format_iso_datetime(timestamp_ms: f64) -> Option<String> {
174
+ let datetime = date_time_fields_from_timestamp_ms(timestamp_ms)?;
175
+ Some(format!(
176
+ "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
177
+ format_iso_year(datetime.year),
178
+ datetime.month,
179
+ datetime.day,
180
+ datetime.hour,
181
+ datetime.minute,
182
+ datetime.second,
183
+ datetime.millisecond,
184
+ ))
185
+ }
186
+
187
+ fn parse_iso_year(value: &str) -> Option<(i64, usize)> {
188
+ let bytes = value.as_bytes();
189
+ let mut index = 0usize;
190
+ let signed = matches!(bytes.first(), Some(b'+' | b'-'));
191
+ if signed {
192
+ index += 1;
193
+ }
194
+ while bytes.get(index).is_some_and(u8::is_ascii_digit) {
195
+ index += 1;
196
+ }
197
+ let digits = if signed {
198
+ index.saturating_sub(1)
199
+ } else {
200
+ index
201
+ };
202
+ if digits == 0 || bytes.get(index) != Some(&b'-') {
203
+ return None;
204
+ }
205
+ if (!signed && digits != 4) || (signed && digits != 6) {
206
+ return None;
207
+ }
208
+ Some((value[..index].parse::<i64>().ok()?, index))
209
+ }
210
+
211
+ fn parse_two_digits(value: &str, index: &mut usize) -> Option<u8> {
212
+ let bytes = value.as_bytes();
213
+ let tens = *bytes.get(*index)?;
214
+ let ones = *bytes.get(*index + 1)?;
215
+ if !tens.is_ascii_digit() || !ones.is_ascii_digit() {
216
+ return None;
217
+ }
218
+ *index += 2;
219
+ Some((tens - b'0') * 10 + (ones - b'0'))
220
+ }
221
+
222
+ fn require_byte(value: &str, index: &mut usize, expected: u8) -> Option<()> {
223
+ if value.as_bytes().get(*index) == Some(&expected) {
224
+ *index += 1;
225
+ Some(())
226
+ } else {
227
+ None
228
+ }
229
+ }
230
+
231
+ fn is_valid_date(year: i64, month: u8, day: u8) -> bool {
232
+ matches!(month, 1..=12) && (1..=days_in_month(year, month)).contains(&day)
233
+ }
234
+
235
+ fn is_leap_year(year: i64) -> bool {
236
+ (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
237
+ }
238
+
239
+ fn days_in_month(year: i64, month: u8) -> u8 {
240
+ match month {
241
+ 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
242
+ 4 | 6 | 9 | 11 => 30,
243
+ 2 if is_leap_year(year) => 29,
244
+ 2 => 28,
245
+ _ => 0,
246
+ }
247
+ }
248
+
249
+ fn days_from_civil(year: i64, month: u8, day: u8) -> i64 {
250
+ let year = year - i64::from(month <= 2);
251
+ let era = if year >= 0 { year } else { year - 399 } / 400;
252
+ let year_of_era = year - era * 400;
253
+ let shifted_month = i64::from(month) + if month > 2 { -3 } else { 9 };
254
+ let day_of_year = (153 * shifted_month + 2) / 5 + i64::from(day) - 1;
255
+ let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
256
+ era * 146_097 + day_of_era - 719_468
257
+ }
258
+
259
+ fn civil_from_days(days: i64) -> (i64, u8, u8) {
260
+ let shifted = days + 719_468;
261
+ let era = if shifted >= 0 {
262
+ shifted
263
+ } else {
264
+ shifted - 146_096
265
+ } / 146_097;
266
+ let day_of_era = shifted - era * 146_097;
267
+ let year_of_era =
268
+ (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
269
+ let year = year_of_era + era * 400;
270
+ let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
271
+ let month_prime = (5 * day_of_year + 2) / 153;
272
+ let day = day_of_year - (153 * month_prime + 2) / 5 + 1;
273
+ let month = month_prime + if month_prime < 10 { 3 } else { -9 };
274
+ (year + i64::from(month <= 2), month as u8, day as u8)
275
+ }
276
+
277
+ fn format_iso_year(year: i64) -> String {
278
+ if (0..=9_999).contains(&year) {
279
+ format!("{year:04}")
280
+ } else if year < 0 {
281
+ format!("-{:06}", year.unsigned_abs())
282
+ } else {
283
+ format!("+{:06}", year as u64)
284
+ }
285
+ }
286
+
287
+ pub(super) fn format_en_us_number_grouped(integer: &str) -> String {
288
+ let mut chars = integer.chars().collect::<Vec<_>>();
289
+ let negative = matches!(chars.first(), Some('-'));
290
+ if negative {
291
+ chars.remove(0);
292
+ }
293
+ let mut grouped = String::new();
294
+ for (index, ch) in chars.iter().rev().enumerate() {
295
+ if index > 0 && index % 3 == 0 {
296
+ grouped.push(',');
297
+ }
298
+ grouped.push(*ch);
299
+ }
300
+ let grouped = grouped.chars().rev().collect::<String>();
301
+ if negative {
302
+ format!("-{grouped}")
303
+ } else {
304
+ grouped
305
+ }
306
+ }
307
+
308
+ pub(super) fn clamp_index(index: i64, len: usize) -> usize {
309
+ if index < 0 {
310
+ 0
311
+ } else {
312
+ (index as usize).min(len)
313
+ }
314
+ }
315
+
316
+ pub(super) fn normalize_relative_bound(index: i64, len: usize) -> usize {
317
+ let len = len as i64;
318
+ if index < 0 {
319
+ (len + index).max(0) as usize
320
+ } else {
321
+ index.min(len) as usize
322
+ }
323
+ }
324
+
325
+ pub(super) fn normalize_search_index(index: i64, len: usize) -> usize {
326
+ if index < 0 {
327
+ normalize_relative_bound(index, len)
328
+ } else {
329
+ clamp_index(index, len)
330
+ }
331
+ }
332
+
333
+ pub(super) fn collect_literal_matches(value: &str, needle: &str) -> Vec<RegExpMatchData> {
334
+ if needle.is_empty() {
335
+ let total = value.chars().count();
336
+ return (0..=total)
337
+ .map(|index| {
338
+ let byte = char_index_to_byte_index(value, index);
339
+ RegExpMatchData {
340
+ start_byte: byte,
341
+ end_byte: byte,
342
+ start_index: index,
343
+ end_index: index,
344
+ captures: Vec::new(),
345
+ named_groups: IndexMap::new(),
346
+ }
347
+ })
348
+ .collect();
349
+ }
350
+
351
+ let mut matches = Vec::new();
352
+ let mut start_index = 0usize;
353
+ while let Some(matched) = find_string_pattern(value, needle, start_index).map(|index| {
354
+ let start_byte = char_index_to_byte_index(value, index);
355
+ let end_index = index + needle.chars().count();
356
+ let end_byte = char_index_to_byte_index(value, end_index);
357
+ RegExpMatchData {
358
+ start_byte,
359
+ end_byte,
360
+ start_index: index,
361
+ end_index,
362
+ captures: Vec::new(),
363
+ named_groups: IndexMap::new(),
364
+ }
365
+ }) {
366
+ start_index = matched.end_index;
367
+ matches.push(matched);
368
+ }
369
+ matches
370
+ }
371
+
372
+ pub(super) fn char_index_to_byte_index(value: &str, index: usize) -> usize {
373
+ if index == 0 {
374
+ return 0;
375
+ }
376
+ value
377
+ .char_indices()
378
+ .nth(index)
379
+ .map(|(byte, _)| byte)
380
+ .unwrap_or_else(|| value.len())
381
+ }
382
+
383
+ pub(super) fn byte_index_to_char_index(value: &str, byte_index: usize) -> usize {
384
+ value[..byte_index].chars().count()
385
+ }
386
+
387
+ pub(super) fn advance_char_index(value: &str, index: usize) -> usize {
388
+ let total = value.chars().count();
389
+ (index + 1).min(total)
390
+ }
391
+
392
+ pub(super) fn find_string_pattern(value: &str, needle: &str, start: usize) -> Option<usize> {
393
+ let start_byte = char_index_to_byte_index(value, start);
394
+ value[start_byte..]
395
+ .find(needle)
396
+ .map(|byte_index| byte_index_to_char_index(value, start_byte + byte_index))
397
+ }
398
+
399
+ pub(super) fn split_string_by_pattern(
400
+ value: &str,
401
+ separator: Option<&str>,
402
+ limit: usize,
403
+ ) -> Vec<String> {
404
+ let mut parts = Vec::new();
405
+ match separator {
406
+ None => {
407
+ parts.push(value.to_string());
408
+ }
409
+ Some("") => {
410
+ if limit == 0 {
411
+ return Vec::new();
412
+ }
413
+ if value.is_empty() {
414
+ return parts;
415
+ }
416
+ for ch in value.chars() {
417
+ if parts.len() == limit {
418
+ break;
419
+ }
420
+ parts.push(ch.to_string());
421
+ }
422
+ return parts;
423
+ }
424
+ Some(separator) => {
425
+ let mut remaining = value;
426
+ while let Some(index) = remaining.find(separator) {
427
+ if parts.len() + 1 == limit {
428
+ parts.push(remaining[..index].to_string());
429
+ return parts;
430
+ }
431
+ parts.push(remaining[..index].to_string());
432
+ remaining = &remaining[index + separator.len()..];
433
+ }
434
+ if parts.len() < limit {
435
+ parts.push(remaining.to_string());
436
+ }
437
+ }
438
+ }
439
+ parts
440
+ }
441
+
442
+ pub(super) fn replace_all_string_matches(value: &str, search: &str, replacement: &str) -> String {
443
+ if search.is_empty() {
444
+ let mut result = String::new();
445
+ for ch in value.chars() {
446
+ result.push_str(replacement);
447
+ result.push(ch);
448
+ }
449
+ result.push_str(replacement);
450
+ return result;
451
+ }
452
+
453
+ let mut result = String::new();
454
+ let mut start_index = 0usize;
455
+ while let Some(index) = find_string_pattern(value, search, start_index) {
456
+ let start_byte = char_index_to_byte_index(value, index);
457
+ let end_index = index + search.chars().count();
458
+ let end_byte = char_index_to_byte_index(value, end_index);
459
+ result.push_str(&value[char_index_to_byte_index(value, start_index)..start_byte]);
460
+ result.push_str(replacement);
461
+ start_index = end_index;
462
+ if start_byte == end_byte {
463
+ start_index = advance_char_index(value, start_index);
464
+ }
465
+ }
466
+ result.push_str(&value[char_index_to_byte_index(value, start_index)..]);
467
+ result
468
+ }
469
+
470
+ pub(super) fn replace_first_string_match(value: &str, search: &str, replacement: &str) -> String {
471
+ if search.is_empty() {
472
+ let mut result = String::new();
473
+ result.push_str(replacement);
474
+ result.push_str(value);
475
+ return result;
476
+ }
477
+ let Some(index) = find_string_pattern(value, search, 0) else {
478
+ return value.to_string();
479
+ };
480
+ let start_byte = char_index_to_byte_index(value, index);
481
+ let end_index = index + search.chars().count();
482
+ let end_byte = char_index_to_byte_index(value, end_index);
483
+ let mut result = String::new();
484
+ result.push_str(&value[..start_byte]);
485
+ result.push_str(replacement);
486
+ result.push_str(&value[end_byte..]);
487
+ result
488
+ }
489
+
490
+ pub(super) fn expand_regexp_replacement_template(
491
+ replacement: &str,
492
+ input: &str,
493
+ matched: &RegExpMatchData,
494
+ ) -> String {
495
+ let mut result = String::new();
496
+ let mut chars = replacement.chars().peekable();
497
+ while let Some(ch) = chars.next() {
498
+ if ch != '$' {
499
+ result.push(ch);
500
+ continue;
501
+ }
502
+ match chars.peek().copied() {
503
+ Some('$') => {
504
+ chars.next();
505
+ result.push('$');
506
+ }
507
+ Some('&') => {
508
+ chars.next();
509
+ result.push_str(&input[matched.start_byte..matched.end_byte]);
510
+ }
511
+ Some('`') => {
512
+ chars.next();
513
+ result.push_str(&input[..matched.start_byte]);
514
+ }
515
+ Some('\'') => {
516
+ chars.next();
517
+ result.push_str(&input[matched.end_byte..]);
518
+ }
519
+ Some('<') => {
520
+ chars.next();
521
+ let mut name = String::new();
522
+ let mut closed = false;
523
+ for next in chars.by_ref() {
524
+ if next == '>' {
525
+ closed = true;
526
+ break;
527
+ }
528
+ name.push(next);
529
+ }
530
+ if closed {
531
+ if let Some(Some(value)) = matched.named_groups.get(&name) {
532
+ result.push_str(value);
533
+ }
534
+ } else {
535
+ result.push('$');
536
+ result.push('<');
537
+ result.push_str(&name);
538
+ break;
539
+ }
540
+ }
541
+ Some(digit @ '1'..='9') => {
542
+ let mut index = digit.to_digit(10).unwrap() as usize;
543
+ chars.next();
544
+ if let Some(next_digit @ '0'..='9') = chars.peek().copied() {
545
+ let candidate = index * 10 + next_digit.to_digit(10).unwrap() as usize;
546
+ if candidate <= matched.captures.len() {
547
+ index = candidate;
548
+ chars.next();
549
+ }
550
+ }
551
+ if index > 0
552
+ && let Some(Some(value)) = matched.captures.get(index - 1)
553
+ {
554
+ result.push_str(value);
555
+ }
556
+ }
557
+ _ => result.push('$'),
558
+ }
559
+ }
560
+ result
561
+ }
@@ -0,0 +1,123 @@
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ use crate::{
4
+ ir::{BinaryOp, Pattern, PropertyName, UnaryOp, UpdateOp},
5
+ span::SourceSpan,
6
+ };
7
+
8
+ #[derive(Debug, Clone, Serialize, Deserialize)]
9
+ pub struct BytecodeProgram {
10
+ pub functions: Vec<FunctionPrototype>,
11
+ pub root: usize,
12
+ }
13
+
14
+ #[derive(Debug, Clone, Serialize, Deserialize)]
15
+ pub struct FunctionPrototype {
16
+ pub name: Option<String>,
17
+ pub length: usize,
18
+ pub display_source: String,
19
+ pub params: Vec<Pattern>,
20
+ pub rest: Option<Pattern>,
21
+ pub code: Vec<Instruction>,
22
+ pub is_async: bool,
23
+ pub is_arrow: bool,
24
+ pub span: SourceSpan,
25
+ }
26
+
27
+ #[derive(Debug, Clone, Serialize, Deserialize)]
28
+ pub enum Instruction {
29
+ PushUndefined,
30
+ PushNull,
31
+ PushBool(bool),
32
+ PushNumber(f64),
33
+ PushString(String),
34
+ PushRegExp {
35
+ pattern: String,
36
+ flags: String,
37
+ },
38
+ LoadName(String),
39
+ LoadGlobalObject,
40
+ StoreName(String),
41
+ InitializePattern(Pattern),
42
+ PushEnv,
43
+ PopEnv,
44
+ DeclareName {
45
+ name: String,
46
+ mutable: bool,
47
+ },
48
+ MakeClosure {
49
+ function_id: usize,
50
+ },
51
+ MakeArray {
52
+ count: usize,
53
+ },
54
+ ArrayPush,
55
+ ArrayPushHole,
56
+ ArrayExtend,
57
+ MakeObject {
58
+ keys: Vec<PropertyName>,
59
+ },
60
+ CopyDataProperties,
61
+ CreateIterator,
62
+ IteratorNext,
63
+ GetPropStatic {
64
+ name: String,
65
+ optional: bool,
66
+ },
67
+ GetPropComputed {
68
+ optional: bool,
69
+ },
70
+ SetPropStatic {
71
+ name: String,
72
+ },
73
+ SetPropComputed,
74
+ Unary(UnaryOp),
75
+ Binary(BinaryOp),
76
+ Update(UpdateOp),
77
+ PatternArrayIndex(usize),
78
+ PatternArrayRest(usize),
79
+ PatternObjectRest(Vec<String>),
80
+ Pop,
81
+ Dup,
82
+ Dup2,
83
+ PushHandler {
84
+ catch: Option<usize>,
85
+ finally: Option<usize>,
86
+ },
87
+ PopHandler,
88
+ EnterFinally {
89
+ exit: usize,
90
+ },
91
+ BeginCatch,
92
+ Throw {
93
+ span: SourceSpan,
94
+ },
95
+ PushPendingJump {
96
+ target: usize,
97
+ target_handler_depth: usize,
98
+ target_scope_depth: usize,
99
+ },
100
+ PushPendingReturn,
101
+ PushPendingThrow,
102
+ ContinuePending,
103
+ Jump(usize),
104
+ JumpIfFalse(usize),
105
+ JumpIfTrue(usize),
106
+ JumpIfNullish(usize),
107
+ Call {
108
+ argc: usize,
109
+ with_this: bool,
110
+ optional: bool,
111
+ },
112
+ CallWithArray {
113
+ with_this: bool,
114
+ optional: bool,
115
+ },
116
+ Await,
117
+ Construct {
118
+ argc: usize,
119
+ },
120
+ ConstructWithArray,
121
+ Return,
122
+ PushBigInt(String),
123
+ }