cfsolve 0.1.1 → 0.1.2

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +10 -9
  4. data/UPSTREAM.md +6 -6
  5. data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  6. data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  7. data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
  8. data/lib/cfsolve/version.rb +2 -2
  9. data/vendor/cfsolve/README.md +0 -35
  10. data/vendor/cfsolve/examples/build_profile.rs +17 -15
  11. data/vendor/cfsolve/examples/capture_session.rs +37 -0
  12. data/vendor/cfsolve/examples/check_answers.rs +66 -0
  13. data/vendor/cfsolve/examples/compare_session.rs +190 -0
  14. data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
  15. data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  16. data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  17. data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
  18. data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
  19. data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
  20. data/vendor/cfsolve/src/capture/profile.rs +7 -18
  21. data/vendor/cfsolve/src/capture/recorded.rs +42 -7
  22. data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
  23. data/vendor/cfsolve/src/challenge/installed.rs +122 -271
  24. data/vendor/cfsolve/src/challenge/payload.rs +107 -5
  25. data/vendor/cfsolve/src/challenge/state.rs +98 -16
  26. data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
  27. data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
  28. data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
  29. data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
  30. data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
  31. data/vendor/cfsolve/src/http/client.rs +11 -0
  32. data/vendor/cfsolve/src/js/ast.rs +12 -3
  33. data/vendor/cfsolve/src/js/objects.rs +9 -2
  34. data/vendor/cfsolve/src/listing/fold.rs +0 -83
  35. data/vendor/cfsolve/src/listing/query.rs +25 -244
  36. data/vendor/cfsolve/src/listing.rs +0 -1
  37. data/vendor/cfsolve/src/session/initial.rs +14 -5
  38. data/vendor/cfsolve/src/session/probes.rs +160 -125
  39. data/vendor/cfsolve/src/session/timing.rs +58 -53
  40. data/vendor/cfsolve/src/session/widget.rs +5 -6
  41. data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
  42. data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
  43. data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
  44. data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
  45. data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
  46. data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
  47. data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
  48. data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
  49. data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
  50. data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
  51. data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
  52. data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
  53. data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
  54. data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
  55. data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
  56. data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
  57. data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
  58. data/vendor/cfsolve/src/stage2/probes.rs +0 -1
  59. data/vendor/cfsolve/src/stage2/records.rs +4 -13
  60. data/vendor/cfsolve/src/stage2/reference.json +1 -1
  61. data/vendor/cfsolve/src/stage2/reference.rs +98 -100
  62. data/vendor/cfsolve/src/stage2/registry.rs +41 -34
  63. data/vendor/cfsolve/src/stage2/replay.rs +182 -254
  64. data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
  65. data/vendor/cfsolve/src/stage2/trace.rs +18 -0
  66. data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
  67. data/vendor/cfsolve/src/stage2.rs +2 -1
  68. data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
  69. data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
  70. data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
  71. data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
  72. data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
  73. data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
  74. data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
  75. data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
  76. data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
  77. data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
  78. data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
  79. data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
  80. data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
  81. data/vendor/cfsolve/src/vm/interpret.rs +7 -2
  82. data/vendor/cfsolve/src/vm/machine.rs +6 -2
  83. data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
  84. data/vendor/cfsolve/src/vm/session.rs +36 -14
  85. data/vendor/cfsolve/src/vm/value.rs +13 -0
  86. data/vendor/cfsolve/src/vm.rs +1 -0
  87. data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
  88. metadata +13 -6
  89. data/vendor/cfsolve/src/listing/normalize.rs +0 -264
  90. data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
  91. data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
  92. data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
  93. data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
@@ -0,0 +1,626 @@
1
+ //! Compiles slot plans into closures. Compiled code takes the same steps as
2
+ //! [`super::evaluate`] takes over a plan, in the same order and through the same [`Run`]
3
+ //! methods, but resolves each node's kind and operands once, when the plan is compiled.
4
+ //!
5
+ //! A few common shapes run more directly: assignments to locals in statement position skip
6
+ //! the result they would discard, and arithmetic reads program bytes at local indexes, as in
7
+ //! `program[pc++]`, without building values.
8
+
9
+ use std::cell::RefCell;
10
+ use std::rc::Rc;
11
+
12
+ use swc_core::ecma::ast::{BinaryOp, UpdateOp};
13
+
14
+ use super::access::{self, Key};
15
+ use super::evaluate::{self, Locals, Location, MAXIMUM_ITERATIONS};
16
+ use super::plan::{Case, Declaration, Expression, Name, Property, Statement, Target};
17
+ use super::run::{Evaluation, Flow, Run, Stop};
18
+ use super::semantics;
19
+ use crate::vm::convert;
20
+ use crate::vm::value::Value;
21
+
22
+ /// Compiled code producing `T`.
23
+ type Compiled<'ast, T> = Box<
24
+ dyn for<'session> Fn(&mut Run<'ast, 'session>, &mut Locals<'ast>) -> Evaluation<'ast, T> + 'ast,
25
+ >;
26
+
27
+ /// A compiled statement.
28
+ pub(super) type Code<'ast> = Compiled<'ast, Flow<'ast>>;
29
+
30
+ /// A compiled expression.
31
+ type Compute<'ast> = Compiled<'ast, Value<'ast>>;
32
+
33
+ /// A compiled operand of arithmetic, as [`Run::number`] evaluates it: a number, or the value
34
+ /// of a nonnumeric operand.
35
+ type Numeric<'ast> = Compiled<'ast, Result<f64, Value<'ast>>>;
36
+
37
+ /// Compiles a function body or block.
38
+ pub(super) fn statements<'ast>(statements: &[Statement<'ast>]) -> Vec<Code<'ast>> {
39
+ statements.iter().map(statement).collect()
40
+ }
41
+
42
+ /// Runs statements in order until one jumps, as `Run::execute_all` does.
43
+ pub(super) fn run_all<'ast>(
44
+ code: &[Code<'ast>],
45
+ run: &mut Run<'ast, '_>,
46
+ locals: &mut Locals<'ast>,
47
+ ) -> Evaluation<'ast, Flow<'ast>> {
48
+ for statement in code {
49
+ let flow = statement(run, locals)?;
50
+ if !matches!(flow, Flow::Normal) {
51
+ return Ok(flow);
52
+ }
53
+ }
54
+ Ok(Flow::Normal)
55
+ }
56
+
57
+ fn statement<'ast>(statement: &Statement<'ast>) -> Code<'ast> {
58
+ match statement {
59
+ Statement::Switch(discriminant, cases) => switch(discriminant, cases),
60
+ Statement::Empty => code(|_, _| Ok(Flow::Normal)),
61
+ Statement::Expression(expression) => effect(expression),
62
+ Statement::Declare(declarations) => declare(declarations),
63
+ Statement::Try {
64
+ block,
65
+ parameter,
66
+ handler,
67
+ finalizer,
68
+ } => {
69
+ let block = statements(block);
70
+ let parameter = *parameter;
71
+ let handler = handler.as_deref().map(statements);
72
+ let finalizer = finalizer.as_deref().map(statements);
73
+ code(move |run, locals| {
74
+ run.attempt(
75
+ block.as_slice(),
76
+ parameter,
77
+ handler.as_deref(),
78
+ finalizer.as_deref(),
79
+ locals,
80
+ |run, code, locals| run_all(code, run, locals),
81
+ )
82
+ })
83
+ }
84
+ Statement::Block(block) => {
85
+ let block = statements(block);
86
+ code(move |run, locals| run_all(&block, run, locals))
87
+ }
88
+ Statement::If(test, yes, no) => {
89
+ let test = compute(test);
90
+ let yes = self::statement(yes);
91
+ let no = no.as_deref().map(self::statement);
92
+ code(move |run, locals| {
93
+ let test = test(run, locals)?;
94
+ if run.decide(&test) {
95
+ return yes(run, locals);
96
+ }
97
+ no.as_ref().map_or(Ok(Flow::Normal), |no| no(run, locals))
98
+ })
99
+ }
100
+ Statement::Return(Some(expression)) => {
101
+ let expression = compute(expression);
102
+ code(move |run, locals| Ok(Flow::Return(expression(run, locals)?)))
103
+ }
104
+ Statement::Return(None) => code(|_, _| Ok(Flow::Return(Value::Undefined))),
105
+ Statement::Throw(expression) => {
106
+ let expression = compute(expression);
107
+ code(move |run, locals| Err(Stop::throw(expression(run, locals)?)))
108
+ }
109
+ Statement::Break(label) => {
110
+ let label = label.cloned();
111
+ code(move |_, _| Ok(Flow::Break(label.clone())))
112
+ }
113
+ Statement::Continue(label) => {
114
+ let label = label.cloned();
115
+ code(move |_, _| Ok(Flow::Continue(label.clone())))
116
+ }
117
+ Statement::Label(label, body) => {
118
+ let label = (*label).clone();
119
+ let body = self::statement(body);
120
+ code(move |run, locals| {
121
+ Ok(match body(run, locals)? {
122
+ Flow::Break(Some(target)) if target == label => Flow::Normal,
123
+ flow => flow,
124
+ })
125
+ })
126
+ }
127
+ Statement::Loop {
128
+ init,
129
+ test,
130
+ update,
131
+ body,
132
+ body_first,
133
+ label,
134
+ } => {
135
+ let init = init.as_deref().map(self::statement);
136
+ let test = test.as_ref().map(compute);
137
+ let update = update.as_ref().map(compute);
138
+ let body = self::statement(body);
139
+ let body_first = *body_first;
140
+ let label = label.cloned();
141
+ code(move |run, locals| {
142
+ if let Some(init) = &init {
143
+ init(run, locals)?;
144
+ }
145
+ let mut first = body_first;
146
+ for _ in 0..MAXIMUM_ITERATIONS {
147
+ if !first && let Some(test) = &test {
148
+ let repeats = test(run, locals)?.truthy();
149
+ if !repeats.ok_or_else(|| Stop::unsupported("a loop over registers"))? {
150
+ return Ok(Flow::Normal);
151
+ }
152
+ }
153
+ first = false;
154
+ match body(run, locals)? {
155
+ Flow::Normal | Flow::Continue(None) => {}
156
+ Flow::Continue(Some(target)) if Some(&target) == label.as_ref() => {}
157
+ Flow::Break(None) => return Ok(Flow::Normal),
158
+ flow => return Ok(flow),
159
+ }
160
+ if let Some(update) = &update {
161
+ update(run, locals)?;
162
+ }
163
+ }
164
+ Err(Stop::unsupported("a loop past the longest constant"))
165
+ })
166
+ }
167
+ }
168
+ }
169
+
170
+ /// An expression statement. Assigning a local writes the value without keeping the result.
171
+ fn effect<'ast>(expression: &Expression<'ast>) -> Code<'ast> {
172
+ if let Expression::Assign(Target::Name(name @ Name::Local(..)), None, right) = expression {
173
+ let (name, right) = (*name, compute(right));
174
+ return code(move |run, locals| {
175
+ let value = right(run, locals)?;
176
+ run.write(Location::Name(name), value, locals)?;
177
+ Ok(Flow::Normal)
178
+ });
179
+ }
180
+ let expression = compute(expression);
181
+ code(move |run, locals| {
182
+ expression(run, locals)?.discard();
183
+ Ok(Flow::Normal)
184
+ })
185
+ }
186
+
187
+ fn declare<'ast>(declarations: &[Declaration<'ast>]) -> Code<'ast> {
188
+ let declarations: Vec<_> = declarations
189
+ .iter()
190
+ .map(|Declaration { name, init, keeps }| (*name, init.as_ref().map(compute), *keeps))
191
+ .collect();
192
+ code(move |run, locals| {
193
+ for (name, init, keeps) in &declarations {
194
+ let value = match init {
195
+ Some(init) => init(run, locals)?,
196
+ None if *keeps && run.declared(*name, locals) => continue,
197
+ None => Value::Undefined,
198
+ };
199
+ run.bind(*name, value, locals);
200
+ }
201
+ Ok(Flow::Normal)
202
+ })
203
+ }
204
+
205
+ /// Runs from the first case strictly equal to the discriminant, or else from the default.
206
+ fn switch<'ast>(discriminant: &Expression<'ast>, cases: &[Case<'ast>]) -> Code<'ast> {
207
+ let discriminant = compute(discriminant);
208
+ let cases: Vec<_> = cases
209
+ .iter()
210
+ .map(|case| (case.test.as_ref().map(compute), statements(&case.body)))
211
+ .collect();
212
+ code(move |run, locals| {
213
+ let discriminant = discriminant(run, locals)?;
214
+ let mut matched = None;
215
+ let mut default = None;
216
+ for (index, (test, _)) in cases.iter().enumerate() {
217
+ let Some(test) = test else {
218
+ default = default.or(Some(index));
219
+ continue;
220
+ };
221
+ let equal = semantics::strict_equal(&discriminant, &test(run, locals)?);
222
+ if equal.ok_or_else(|| Stop::unsupported("a switch over registers"))? {
223
+ matched = Some(index);
224
+ break;
225
+ }
226
+ }
227
+ let Some(start) = matched.or(default) else {
228
+ return Ok(Flow::Normal);
229
+ };
230
+ for (_, body) in &cases[start..] {
231
+ match run_all(body, run, locals)? {
232
+ Flow::Normal => {}
233
+ Flow::Break(None) => return Ok(Flow::Normal),
234
+ flow => return Ok(flow),
235
+ }
236
+ }
237
+ Ok(Flow::Normal)
238
+ })
239
+ }
240
+
241
+ /// A compiled property key.
242
+ enum Keyed<'ast> {
243
+ Name(&'ast str),
244
+ Computed(Compute<'ast>),
245
+ }
246
+
247
+ impl<'ast> Keyed<'ast> {
248
+ fn new(property: &Property<'ast>) -> Self {
249
+ match property {
250
+ Property::Name(name) => Self::Name(name),
251
+ Property::Computed(expression) => Self::Computed(compute(expression)),
252
+ }
253
+ }
254
+
255
+ fn key(
256
+ &self,
257
+ run: &mut Run<'ast, '_>,
258
+ locals: &mut Locals<'ast>,
259
+ ) -> Evaluation<'ast, Key<'ast, 'ast>> {
260
+ Ok(match self {
261
+ Self::Name(name) => Key::name(name),
262
+ Self::Computed(expression) => Key::from(expression(run, locals)?),
263
+ })
264
+ }
265
+ }
266
+
267
+ /// A compiled assignment target.
268
+ enum Located<'ast> {
269
+ Name(Name<'ast>),
270
+ Member(Compute<'ast>, Keyed<'ast>),
271
+ }
272
+
273
+ impl<'ast> Located<'ast> {
274
+ fn new(target: &Target<'ast>) -> Self {
275
+ match target {
276
+ Target::Name(name) => Self::Name(*name),
277
+ Target::Member(object, key) => Self::Member(compute(object), Keyed::new(key)),
278
+ }
279
+ }
280
+
281
+ fn locate(
282
+ &self,
283
+ run: &mut Run<'ast, '_>,
284
+ locals: &mut Locals<'ast>,
285
+ ) -> Evaluation<'ast, Location<'ast>> {
286
+ Ok(match self {
287
+ Self::Name(name) => Location::Name(*name),
288
+ Self::Member(object, key) => {
289
+ Location::Member(object(run, locals)?, key.key(run, locals)?)
290
+ }
291
+ })
292
+ }
293
+ }
294
+
295
+ /// Compiled call arguments.
296
+ struct Arguments<'ast>(Vec<Compute<'ast>>);
297
+
298
+ impl<'ast> Arguments<'ast> {
299
+ fn new(arguments: &[Expression<'ast>]) -> Self {
300
+ Self(arguments.iter().map(compute).collect())
301
+ }
302
+
303
+ /// Evaluates the arguments into a reused buffer, as `Run::arguments` does.
304
+ fn values(
305
+ &self,
306
+ run: &mut Run<'ast, '_>,
307
+ locals: &mut Locals<'ast>,
308
+ ) -> Evaluation<'ast, Vec<Value<'ast>>> {
309
+ let mut values = run.spare.arguments.pop().unwrap_or_default();
310
+ for argument in &self.0 {
311
+ values.push(argument(run, locals)?);
312
+ }
313
+ Ok(values)
314
+ }
315
+ }
316
+
317
+ fn compute<'ast>(expression: &Expression<'ast>) -> Compute<'ast> {
318
+ match expression {
319
+ Expression::Constant(value) => {
320
+ let value = value.clone();
321
+ code(move |_, _| Ok(evaluate::copied(&value)))
322
+ }
323
+ Expression::Name(Name::Local(slot, name)) => {
324
+ let (slot, name) = (*slot, *name);
325
+ code(move |run, locals| match &locals[slot] {
326
+ Some(value @ Value::Symbolic(_)) => Ok(run.refined(value)),
327
+ Some(value) => Ok(evaluate::copied(value)),
328
+ None => run.lookup(name),
329
+ })
330
+ }
331
+ Expression::Name(Name::Global(name)) => {
332
+ let name = *name;
333
+ code(move |run, _| run.lookup(name))
334
+ }
335
+ Expression::This => code(|run, _| Ok(run.frame().this.clone())),
336
+ Expression::Binary(operator, left, right) => {
337
+ let (operator, left, right) = (*operator, compute(left), compute(right));
338
+ code(move |run, locals| {
339
+ Ok(evaluate::binary_value(
340
+ operator,
341
+ left(run, locals)?,
342
+ right(run, locals)?,
343
+ ))
344
+ })
345
+ }
346
+ Expression::Arithmetic { .. } => {
347
+ let number = number(expression);
348
+ code(move |run, locals| Ok(evaluate::number_value(number(run, locals)?)))
349
+ }
350
+ Expression::Member(object, property) => {
351
+ let (object, property) = (compute(object), Keyed::new(property));
352
+ code(move |run, locals| {
353
+ let object = object(run, locals)?;
354
+ let key = property.key(run, locals)?;
355
+ run.get(&object, &key)
356
+ })
357
+ }
358
+ Expression::Assign(target, operator, right) => assignment(target, *operator, right),
359
+ Expression::Update {
360
+ target,
361
+ operator,
362
+ prefix,
363
+ } => update(target, *operator, *prefix),
364
+ Expression::Call(callee, arguments) => call(callee, arguments),
365
+ Expression::Logical {
366
+ operator,
367
+ left,
368
+ right,
369
+ pure,
370
+ } => logical(*operator, left, right, *pure),
371
+ Expression::Sequence(expressions) => {
372
+ let expressions: Vec<_> = expressions.iter().map(compute).collect();
373
+ code(move |run, locals| {
374
+ let mut value = Value::Undefined;
375
+ for expression in &expressions {
376
+ value = expression(run, locals)?;
377
+ }
378
+ Ok(value)
379
+ })
380
+ }
381
+ Expression::New(callee, arguments) => {
382
+ let (callee, arguments) = (compute(callee), Arguments::new(arguments));
383
+ code(move |run, locals| {
384
+ let callee = callee(run, locals)?;
385
+ let arguments = arguments.values(run, locals)?;
386
+ run.construct(callee, arguments)
387
+ })
388
+ }
389
+ Expression::Unary(operator, argument) => {
390
+ let (operator, argument) = (*operator, compute(argument));
391
+ code(move |run, locals| Ok(semantics::unary_value(operator, argument(run, locals)?)))
392
+ }
393
+ Expression::Conditional(test, yes, no) => {
394
+ let (test, yes, no) = (compute(test), compute(yes), compute(no));
395
+ code(move |run, locals| {
396
+ let test = test(run, locals)?;
397
+ let branch = if run.decide(&test) { &yes } else { &no };
398
+ branch(run, locals)
399
+ })
400
+ }
401
+ Expression::Array(elements) => {
402
+ let elements: Vec<_> = elements
403
+ .iter()
404
+ .map(|element| element.as_ref().map(compute))
405
+ .collect();
406
+ code(move |run, locals| {
407
+ let mut values = Vec::with_capacity(elements.len());
408
+ for element in &elements {
409
+ values.push(match element {
410
+ Some(element) => element(run, locals)?,
411
+ None => Value::Undefined,
412
+ });
413
+ }
414
+ Ok(Value::array(values))
415
+ })
416
+ }
417
+ Expression::Object(properties) => {
418
+ let properties: Vec<_> = properties
419
+ .iter()
420
+ .map(|(key, value)| (Keyed::new(key), compute(value)))
421
+ .collect();
422
+ code(move |run, locals| {
423
+ let mut entries = Vec::with_capacity(properties.len());
424
+ for (key, value) in &properties {
425
+ let key = key.key(run, locals)?;
426
+ let name = key
427
+ .property()
428
+ .ok_or_else(|| access::unsupported("object key", &key))?;
429
+ entries.push((name, value(run, locals)?));
430
+ }
431
+ Ok(Value::Object(Rc::new(RefCell::new(entries))))
432
+ })
433
+ }
434
+ Expression::Function(name, function) => {
435
+ let (name, function) = (name.cloned(), *function);
436
+ code(move |run, _| Ok(run.function_value(name.clone(), function)))
437
+ }
438
+ Expression::Arrow(arrow) => {
439
+ let arrow = *arrow;
440
+ code(move |run, _| Ok(run.arrow_value(arrow)))
441
+ }
442
+ Expression::Unsupported(reason) => {
443
+ let reason = *reason;
444
+ code(move |_, _| Err(Stop::unsupported(reason)))
445
+ }
446
+ }
447
+ }
448
+
449
+ fn call<'ast>(callee: &Expression<'ast>, arguments: &[Expression<'ast>]) -> Compute<'ast> {
450
+ let arguments = Arguments::new(arguments);
451
+ match callee {
452
+ Expression::Member(object, property) => {
453
+ let (object, property) = (compute(object), Keyed::new(property));
454
+ code(move |run, locals| {
455
+ let object = object(run, locals)?;
456
+ let callee = {
457
+ let key = property.key(run, locals)?;
458
+ run.get(&object, &key)?
459
+ };
460
+ let arguments = arguments.values(run, locals)?;
461
+ run.apply(callee, object, arguments)
462
+ })
463
+ }
464
+ callee => {
465
+ let callee = compute(callee);
466
+ code(move |run, locals| {
467
+ let callee = callee(run, locals)?;
468
+ let arguments = arguments.values(run, locals)?;
469
+ run.apply(callee, Value::Undefined, arguments)
470
+ })
471
+ }
472
+ }
473
+ }
474
+
475
+ fn assignment<'ast>(
476
+ target: &Target<'ast>,
477
+ operator: Option<BinaryOp>,
478
+ right: &Expression<'ast>,
479
+ ) -> Compute<'ast> {
480
+ let (target, right) = (Located::new(target), compute(right));
481
+ match operator {
482
+ None => code(move |run, locals| {
483
+ let location = target.locate(run, locals)?;
484
+ let value = right(run, locals)?;
485
+ run.write(location, value.clone(), locals)?;
486
+ Ok(value)
487
+ }),
488
+ Some(operator) => code(move |run, locals| {
489
+ let location = target.locate(run, locals)?;
490
+ run.compound(location, operator, locals, |run, locals| right(run, locals))
491
+ }),
492
+ }
493
+ }
494
+
495
+ fn update<'ast>(target: &Target<'ast>, operator: UpdateOp, prefix: bool) -> Compute<'ast> {
496
+ let target = Located::new(target);
497
+ code(move |run, locals| {
498
+ // A counter: the local is read and written in place.
499
+ if let Located::Name(Name::Local(slot, _)) = target
500
+ && let Some(Value::Number(number)) = &mut locals[slot]
501
+ {
502
+ return Ok(Value::Number(evaluate::step(operator, prefix, number)));
503
+ }
504
+ let location = target.locate(run, locals)?;
505
+ run.update_location(location, operator, prefix, locals)
506
+ })
507
+ }
508
+
509
+ /// `&&`, `||`, or `??`, evaluating the right side only when needed.
510
+ fn logical<'ast>(
511
+ operator: BinaryOp,
512
+ left: &Expression<'ast>,
513
+ right: &Expression<'ast>,
514
+ pure: bool,
515
+ ) -> Compute<'ast> {
516
+ let (left, right) = (compute(left), compute(right));
517
+ code(move |run, locals| {
518
+ let left = left(run, locals)?;
519
+ run.short_circuit(operator, left, pure, locals, |run, locals| {
520
+ right(run, locals)
521
+ })
522
+ })
523
+ }
524
+
525
+ /// Numeric evaluation, as [`Run::number`] does: arithmetic without wrapping intermediate
526
+ /// results, switching to general evaluation where an operand is nonnumeric.
527
+ fn number<'ast>(expression: &Expression<'ast>) -> Numeric<'ast> {
528
+ let Expression::Arithmetic {
529
+ operator,
530
+ operation,
531
+ left,
532
+ right,
533
+ } = expression
534
+ else {
535
+ let general = compute(expression);
536
+ let general = code(move |run, locals| {
537
+ Ok(match general(run, locals)? {
538
+ Value::Number(number) => Ok(number),
539
+ value => Err(value),
540
+ })
541
+ });
542
+ return program_read(expression, general);
543
+ };
544
+ let (operator, operation, left) = (*operator, *operation, operand(left));
545
+ if let Expression::Constant(Value::Number(constant)) = **right {
546
+ return code(move |run, locals| {
547
+ Ok(match left(run, locals)? {
548
+ Ok(left) => Ok(operation(left, constant)),
549
+ left => Err(evaluate::binary_value(
550
+ operator,
551
+ evaluate::number_value(left),
552
+ Value::Number(constant),
553
+ )),
554
+ })
555
+ });
556
+ }
557
+ let right = operand(right);
558
+ code(move |run, locals| {
559
+ let left = left(run, locals)?;
560
+ let right = right(run, locals)?;
561
+ Ok(match (left, right) {
562
+ (Ok(left), Ok(right)) => Ok(operation(left, right)),
563
+ (left, right) => Err(evaluate::binary_value(
564
+ operator,
565
+ evaluate::number_value(left),
566
+ evaluate::number_value(right),
567
+ )),
568
+ })
569
+ })
570
+ }
571
+
572
+ /// An operand of arithmetic, reading numeric constants and locals directly.
573
+ fn operand<'ast>(expression: &Expression<'ast>) -> Numeric<'ast> {
574
+ match expression {
575
+ Expression::Constant(Value::Number(number)) => {
576
+ let number = *number;
577
+ code(move |_, _| Ok(Ok(number)))
578
+ }
579
+ Expression::Name(Name::Local(slot, _)) => {
580
+ let (slot, general) = (*slot, number(expression));
581
+ code(move |run, locals| match locals[slot] {
582
+ Some(Value::Number(number)) => Ok(Ok(number)),
583
+ _ => general(run, locals),
584
+ })
585
+ }
586
+ expression => number(expression),
587
+ }
588
+ }
589
+
590
+ /// `program[index++]` and the like, where both are locals: reads the byte directly while the
591
+ /// first local holds the program and the second a number, and runs `general` otherwise.
592
+ fn program_read<'ast>(expression: &Expression<'ast>, general: Numeric<'ast>) -> Numeric<'ast> {
593
+ let Expression::Member(object, Property::Computed(key)) = expression else {
594
+ return general;
595
+ };
596
+ let (
597
+ Expression::Name(Name::Local(program, _)),
598
+ Expression::Update {
599
+ target: Target::Name(Name::Local(index, _)),
600
+ operator,
601
+ prefix,
602
+ },
603
+ ) = (&**object, &**key)
604
+ else {
605
+ return general;
606
+ };
607
+ let (program, index, operator, prefix) = (*program, *index, *operator, *prefix);
608
+ code(move |run, locals| {
609
+ if matches!(locals[program], Some(Value::Program))
610
+ && let Some(Value::Number(number)) = &mut locals[index]
611
+ {
612
+ let position = evaluate::step(operator, prefix, number);
613
+ let byte = convert::index(position).and_then(|position| run.program.get(position));
614
+ return Ok(byte.map_or(Err(Value::Undefined), |byte| Ok(f64::from(*byte))));
615
+ }
616
+ general(run, locals)
617
+ })
618
+ }
619
+
620
+ /// Boxes compiled code, giving closures their signature.
621
+ fn code<'ast, T>(
622
+ code: impl for<'session> Fn(&mut Run<'ast, 'session>, &mut Locals<'ast>) -> Evaluation<'ast, T>
623
+ + 'ast,
624
+ ) -> Compiled<'ast, T> {
625
+ Box::new(code)
626
+ }