Full Code of nyariv/SandboxJS for AI

main 826865251232 cached
140 files
761.5 KB
219.0k tokens
408 symbols
1 requests
Download .txt
Showing preview only (808K chars total). Download the full file or copy to clipboard to get everything.
Repository: nyariv/SandboxJS
Branch: main
Commit: 826865251232
Files: 140
Total size: 761.5 KB

Directory structure:
gitextract_tkgsxi77/

├── .github/
│   └── workflows/
│       ├── deploy.yml
│       ├── npm-publish.yml
│       ├── shields.yml
│       └── test.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── TODO.md
├── css/
│   └── style.css
├── eslint.config.js
├── index.html
├── jest.config.js
├── package.json
├── scripts/
│   ├── build.mjs
│   └── export-tests.ts
├── src/
│   ├── Sandbox.ts
│   ├── SandboxExec.ts
│   ├── eval/
│   │   └── index.ts
│   ├── executor/
│   │   ├── executorUtils.ts
│   │   ├── index.ts
│   │   ├── ops/
│   │   │   ├── assignment.ts
│   │   │   ├── call.ts
│   │   │   ├── comparison.ts
│   │   │   ├── control.ts
│   │   │   ├── functions.ts
│   │   │   ├── index.ts
│   │   │   ├── literals.ts
│   │   │   ├── misc.ts
│   │   │   ├── object.ts
│   │   │   ├── prop.ts
│   │   │   ├── unary.ts
│   │   │   └── variables.ts
│   │   └── opsRegistry.ts
│   ├── parser/
│   │   ├── index.ts
│   │   ├── lisp.ts
│   │   ├── lispTypes/
│   │   │   ├── conditionals.ts
│   │   │   ├── control.ts
│   │   │   ├── declarations.ts
│   │   │   ├── index.ts
│   │   │   ├── operators.ts
│   │   │   ├── shared.ts
│   │   │   ├── structures.ts
│   │   │   └── values.ts
│   │   └── parserUtils.ts
│   └── utils/
│       ├── CodeString.ts
│       ├── ExecContext.ts
│       ├── Prop.ts
│       ├── Scope.ts
│       ├── errors.ts
│       ├── functionReplacements.ts
│       ├── index.ts
│       ├── types.ts
│       └── unraw.ts
├── test/
│   ├── audit.spec.ts
│   ├── compileRerun.spec.ts
│   ├── delaySynchronousResult.spec.ts
│   ├── eval/
│   │   ├── README.md
│   │   ├── reveseLinkedList.js
│   │   ├── script.js
│   │   ├── testCases/
│   │   │   ├── arithmetic-operators.data.ts
│   │   │   ├── arithmetic-operators.spec.ts
│   │   │   ├── assignment-operators.data.ts
│   │   │   ├── assignment-operators.spec.ts
│   │   │   ├── bitwise-operators.data.ts
│   │   │   ├── bitwise-operators.spec.ts
│   │   │   ├── comments.data.ts
│   │   │   ├── comments.spec.ts
│   │   │   ├── comparison-operators.data.ts
│   │   │   ├── comparison-operators.spec.ts
│   │   │   ├── complex-expressions.data.ts
│   │   │   ├── complex-expressions.spec.ts
│   │   │   ├── conditionals.data.ts
│   │   │   ├── conditionals.spec.ts
│   │   │   ├── data-types.data.ts
│   │   │   ├── data-types.spec.ts
│   │   │   ├── defaults.data.ts
│   │   │   ├── defaults.spec.ts
│   │   │   ├── destructuring.data.ts
│   │   │   ├── destructuring.spec.ts
│   │   │   ├── error-handling.data.ts
│   │   │   ├── error-handling.spec.ts
│   │   │   ├── function-replacements.data.ts
│   │   │   ├── function-replacements.spec.ts
│   │   │   ├── functions.data.ts
│   │   │   ├── functions.spec.ts
│   │   │   ├── generators.data.ts
│   │   │   ├── generators.spec.ts
│   │   │   ├── index.ts
│   │   │   ├── logical-operators.data.ts
│   │   │   ├── logical-operators.spec.ts
│   │   │   ├── loops.data.ts
│   │   │   ├── loops.spec.ts
│   │   │   ├── objects-and-arrays.data.ts
│   │   │   ├── objects-and-arrays.spec.ts
│   │   │   ├── operator-precedence.data.ts
│   │   │   ├── operator-precedence.spec.ts
│   │   │   ├── other-operators.data.ts
│   │   │   ├── other-operators.spec.ts
│   │   │   ├── security.data.ts
│   │   │   ├── security.spec.ts
│   │   │   ├── switch.data.ts
│   │   │   ├── switch.spec.ts
│   │   │   ├── syntax-errors.data.ts
│   │   │   ├── syntax-errors.spec.ts
│   │   │   ├── template-literals.data.ts
│   │   │   ├── template-literals.spec.ts
│   │   │   ├── test-utils.ts
│   │   │   └── types.ts
│   │   └── tests.json
│   ├── evalCompletionValue.spec.ts
│   ├── expression.spec.ts
│   ├── parse.spec.ts
│   ├── performance.mjs
│   ├── sandboxErrorCatch.spec.ts
│   ├── sandboxRestrictions.spec.ts
│   ├── semicolonInsertion.spec.ts
│   ├── subscriptions.spec.ts
│   ├── symbol.spec.ts
│   ├── taggedTemplateEscaping.spec.ts
│   ├── ticks/
│   │   ├── sandboxArrayTicks.spec.ts
│   │   ├── sandboxCollectionTicks.spec.ts
│   │   ├── sandboxNativeTicks.spec.ts
│   │   ├── sandboxObjectTicks.spec.ts
│   │   ├── sandboxSpreadTicks.spec.ts
│   │   └── sandboxStringTicks.spec.ts
│   ├── ticksQuotaHalt.spec.ts
│   ├── timers.spec.ts
│   ├── timersAsync.spec.ts
│   ├── timersAsyncHalt.spec.ts
│   ├── timersHalt.spec.ts
│   ├── tryFinallyControlFlow.spec.ts
│   ├── tsconfig.json
│   └── unraw.spec.ts
├── tsconfig.jest.json
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy to GitHub Pages

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build

      - name: Prepare deployment directory
        run: |
          mkdir pages
          cp -r dist pages/
          cp -r test pages/
          cp -r css pages/
          cp index.html pages/
          cp logo.svg pages/

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./pages

================================================
FILE: .github/workflows/npm-publish.yml
================================================
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages

name: Node.js Package

on:
  # release:
  #   types: [create]
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npm install
      - run: npm run build
      - run: npm test

  publish-npm:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          registry-url: https://registry.npmjs.org/
      - run: npm install
      - run: npm run build      
      - run: npm publish --access public --provenance



================================================
FILE: .github/workflows/shields.yml
================================================
name: Shield badges

on:
  push:
    branches: [main]

jobs:
  bundle-size:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 24

      - run: npm install
      - run: npm run build

      - name: Measure bundle size (gzip)
        id: size
        run: |
          BYTES=$(gzip -9 -c dist/umd/Sandbox.min.js | wc -c)
          echo "bytes=$BYTES" >> $GITHUB_OUTPUT

      - name: Format size
        id: fmt
        run: |
          python3 -c "
          b = ${{ steps.size.outputs.bytes }}
          if b >= 1024*1024:
              s = f'{b/1024/1024:.1f} MB'
          elif b >= 1024:
              s = f'{b/1024:.1f} kB'
          else:
              s = f'{b} B'
          print('size=' + s)
          " >> $GITHUB_OUTPUT

      - name: Update Gist badge
        uses: schneegans/dynamic-badges-action@v1.7.0
        with:
          auth: ${{ secrets.GIST_TOKEN }}
          gistID: dd4a46f4c2fff1c43d4f2e8fb4b52862
          filename: bundle-size.json
          label: minified (gzip)
          message: ${{ steps.fmt.outputs.size }}
          color: blue

  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 24

      - run: npm install

      - name: Run tests with JSON reporter
        id: test
        run: |
          NODE_OPTIONS='--no-warnings=ExperimentalWarning' npx jest --json --outputFile=jest-results.json || true

      - name: Extract test counts
        id: counts
        run: |
          python3 -c "
          import json
          with open('jest-results.json') as f:
              data = json.load(f)
          passed = data['numPassedTests']
          total = data['numTotalTests']
          print(f'passed={passed}')
          print(f'total={total}')
          print(f'message={passed}/{total}')
          color = '#439e2e' if passed == total else 'red'
          print(f'color={color}')
          " >> $GITHUB_OUTPUT

      - name: Update Gist badge
        uses: schneegans/dynamic-badges-action@v1.7.0
        with:
          auth: ${{ secrets.GIST_TOKEN }}
          gistID: dd4a46f4c2fff1c43d4f2e8fb4b52862
          filename: tests.json
          label: passing tests
          message: ${{ steps.counts.outputs.message }}
          color: ${{ steps.counts.outputs.color }}

  sandbox-badge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Prepare sandbox badge
        id: sandbox_badge
        run: |
          # collapse SVG to a single line
          svg_inline="$(tr -d '\n' < ./logo.svg)"
          echo "message=Sandbox protected" >> "$GITHUB_OUTPUT"
          echo "color=#202830" >> "$GITHUB_OUTPUT"
          echo "label=" >> "$GITHUB_OUTPUT"
          echo "logoSvg=${svg_inline}" >> "$GITHUB_OUTPUT"

      - name: Update Gist badge
        uses: schneegans/dynamic-badges-action@v1.7.0
        with:
          auth: ${{ secrets.GIST_TOKEN }}
          gistID: dd4a46f4c2fff1c43d4f2e8fb4b52862
          filename: sandbox-protected.json
          label: ${{ steps.sandbox_badge.outputs.label }}
          message: ${{ steps.sandbox_badge.outputs.message }}
          color: ${{ steps.sandbox_badge.outputs.color }}
          logoSvg: ${{ steps.sandbox_badge.outputs.logoSvg }}

================================================
FILE: .github/workflows/test.yml
================================================
name: Run Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - '**'   # Optional: also run on PRs

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install dependencies
        run: npm install

      - name: Run lint
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage/lcov.info
          fail_ci_if_error: false
          token: ${{ secrets.CODECOV_TOKEN }}

  performance:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install dependencies
        run: npm install

      - name: Build
        run: npm run build

      - name: Run performance tests
        run: npm run test:perf

================================================
FILE: .gitignore
================================================
node_modules/
dist/
build/
coverage/

================================================
FILE: .husky/pre-commit
================================================
npx lint-staged


================================================
FILE: .npmignore
================================================
node_modules/
.github/
.gitignore
.git/
.vscode/
.husky
.claude
js/
test/
css/
src/
index.html
tsconfig.json
rollup.config.mjs
.eslintrc.json
.prettierrc
jest.config.js
tsconfig.jest.json
package-lock.json


================================================
FILE: .prettierignore
================================================
# Dependencies
node_modules

# Build outputs
dist
build
coverage

# Minified files
*.min.js

# Configuration files
rollup.config.mjs
jest.config.js

# Test files that should be ignored
test/eval/jquery.min.js


================================================
FILE: .prettierrc
================================================
{
  "printWidth": 100,
  "singleQuote": true
}


================================================
FILE: .vscode/settings.json
================================================
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 nyariv

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
[![GitHub](https://img.shields.io/github/license/nyariv/SandboxJS)](https://github.com/nyariv/SandboxJS/blob/main/LICENSE) ![npm](https://img.shields.io/npm/v/@nyariv/sandboxjs) ![Bundle size](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/nyariv/dd4a46f4c2fff1c43d4f2e8fb4b52862/raw/bundle-size.json)
 [![GitHub issues](https://img.shields.io/github/issues-raw/nyariv/SandboxJS)](https://github.com/nyariv/SandboxJS/issues) [![codecov](https://codecov.io/gh/nyariv/SandboxJS/branch/main/graph/badge.svg)](https://codecov.io/gh/nyariv/SandboxJS) [![passing tests](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/nyariv/dd4a46f4c2fff1c43d4f2e8fb4b52862/raw/tests.json)](https://nyariv.github.io/SandboxJS/) [![Sandbox protected](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/nyariv/dd4a46f4c2fff1c43d4f2e8fb4b52862/raw/sandbox-protected.json)](https://github.com/nyariv/SandboxJS)
---
<table><tr><td width="100">

![sanboxjs logo](./logo.svg)

</td><td width="400">
<h1>SandboxJS</h1>
<i>Safe eval runtime</i>
</td></tr></table>

---




This is a javascript sandboxing library. When embedding any kind of js code inside your app (either web or nodejs based) you are essentially giving access to the entire kingdom, hoping there is no malicious code in a dependency such as with supply chain attacks. For securing code, sandboxing is needed.

>  a "sandbox" is a security mechanism for separating running programs, usually in an effort to mitigate system failures or software vulnerabilities from spreading. It is often used to execute untested or untrusted programs or code, possibly from unverified or untrusted third parties, suppliers, users or websites, without risking harm to the host machine or operating system. - Wikipedia

There are many vulnerable modules available on the global scope of a js environment, and unfortunately it is way to easy to get access to that if 3rd party code is allowed to be included. The main way is through the `eval` or `Function` globals because they execute code in the global context. Trying to block access to some global components through proxies or limiting scope variables is fruitless because of one main issue: _every function inherits the `Function` prototype, and can invoke `eval` by calling its constructor_, essentially making `eval` at most two properties away from anything in js.

Example:
```javascript
[].filter.constructor("alert('jailbreak')")()
```

To make matters worse, it is extremely difficult to blacklist functions because code is easily obfuscated. For example, it is possible to execute anything using only `(`, `)`, `[`, `]`, `!`, and `+`. ([source](http://www.jsfuck.com/))

```javascript
[+!+[]]+[] // This evaluates to the number one, go a head type that in console
```

**SandboxJS** solves this problem by parsing js code and executing it though its own js runtime, while in the process checking every single prototype function that is being called. This allows whitelisting anything and everything, regardless of obfuscation.

This means that you can potentially give different libraries different permissions, such as allowing `fetch()` for one library, or allowing access to the `Node` prototype for another, depending what the library requires and nothing more, and any objects that are gotten from the sandbox will remain sandboxed when used outside of it.

Additionaly, `eval` and `Function` are sandboxed as well, and can be used recursively safely, which is why they are considered safe globals in SandboxJS.

There is an `audit` method that will return all the accessed functions and prototypes during runtime if you need to know what permissions to give a certain library.

Since parsing and executing are separated, execution with SandboxJS can be sometimes even faster than `eval`, allowing to prepare the execution code ahead of time.

## Installation

```
npm install @nyariv/sandboxjs
```

## Usage

The following is the bare minimum of code for using SandboxJS. This assumes safe whitelisted defaults.

```javascript
const code = `return myTest;`;
const scope = { myTest: "hello world" };
const sandbox = new Sandbox();
const exec = sandbox.compile(code);
const result = exec(scope).run(); // result: "hello world"
```

It is possible to defined multiple scopes in case you are reusing scopes with multiple layers.

```javascript
const sandbox = new Sandbox();

const scopeA = {a: 1};
const scopeB = {b: 2};
const scopeC = {c: 3};

const code = `a = 4; let d = 5; let b = 6`;
const exec = sandbox.compile(code);
exec(scopeA, scopeB, scopeC).run();

console.log(scopeA); // {a: 4}
console.log(scopeB); // {b: 2}
console.log(scopeC); // {c: 3, d: 5, b: 6}
```

You can set your own whilelisted prototypes and global properties like so (`alert` and `Node` are added to whitelist in the following code):

```javascript
const prototypeWhitelist = Sandbox.SAFE_PROTOTYPES;
prototypeWhitelist.set(Node, new Set());

const globals = {...Sandbox.SAFE_GLOBALS, alert};

const sandbox = new Sandbox({globals, prototypeWhitelist});
```

You can audit a piece of code, which will permit all globals and prototypes but will return a json with accessed globals and prototypes over time.

```javascript
const code = `console.log("test")`;
console.log(Sandbox.audit(code));
```

## Safe Globals

- `globalThis`
- `Function`
- `eval`
- `setTimeout` - excluded by default
- `setInterval` - excluded by default
- `clearTimeout` - excluded by default
- `clearInterval` - excluded by default
- `console`
- `isFinite`
- `isNaN`
- `parseFloat`
- `parseInt`
- `decodeURI`
- `decodeURIComponent`
- `encodeURI`
- `encodeURIComponent`
- `escape`
- `unescape`
- `Boolean`
- `Number`
- `BigInt`
- `String`
- `Object`
- `Array`
- `Symbol`
- `Error`
- `EvalError`
- `RangeError`
- `ReferenceError`
- `SyntaxError`
- `TypeError`
- `URIError`
- `Int8Array`
- `Uint8Array`
- `Uint8ClampedArray`
- `Int16Array`
- `Uint16Array`
- `Int32Array`
- `Uint32Array`
- `Float32Array`
- `Float64Array`
- `Map`
- `Set`
- `WeakMap`
- `WeakSet`
- `Promise`
- `Intl`
- `JSON`
- `Math`

# Safe Prototypes

- `SandboxGlobal`
- `Function`
- `Boolean`
- `Object`
- `Number`
- `BigInt`
- `String`
- `Date`
- `RegExp`
- `Error`
- `Array`
- `Int8Array`
- `Uint8Array`
- `Uint8ClampedArray`
- `Int16Array`
- `Uint16Array`
- `Int32Array`
- `Uint32Array`
- `Float32Array`
- `Float64Array`
- `Map`
- `Set`
- `WeakMap`
- `WeakSet`
- `Promise`

## Goals

|Feature|Status|
|---|---|
|Prototype access protection|done|
|Globals access protection|done|
|Prototype proxying|done|
|Single line sandboxing|done|
|Multi line sandboxing|done|
|Functions support|done|
|Audit prototype and globals access|done|
|Code blocks (try/catch, ifs, and loops)|done|
|Async/await|done|
|Execution time protection|done|
|Extensibility|done|
|Full ECMAScript support|90%|
|Script source and import sandboxing|Won't fix - handled by 3rd party|
|DOM ownership and inherited permissions|See [scope-js](https://github.com/nyariv/scope-js)|
|Tests|done|

📋 **[ECMAScript Feature Implementation Status](TODO.md)** - See which JavaScript features are supported and tested (~90% of core ES5-ES2018 features)

================================================
FILE: TODO.md
================================================
# SandboxJS - ECMAScript Feature Status

[![codecov](https://codecov.io/gh/nyariv/SandboxJS/branch/main/graph/badge.svg)](https://codecov.io/gh/nyariv/SandboxJS)

This document describes the current implementation status of ECMAScript features in SandboxJS.

**Test Coverage**: 1598 total tests | Code Coverage: ~97% statement coverage, ~92% branch coverage

---

## 🐛 Known Bugs & Limitations

The following limitations have been identified during testing:

1. **Unicode identifier escapes** - `\uXXXX` escape sequences in variable names are not supported

---

## ✅ Supported Features

SandboxJS supports the following ECMAScript features with comprehensive test coverage:

### Arithmetic Operators

- ✅ **Addition** - `1+1` → `2`
- ✅ **Subtraction** - `1 * 2 + 3 * (4 + 5) * 6` → `164`
- ✅ **Multiplication** - `1 * 2` → `2`
- ✅ **Division** - `1+2*4/5-6+7/8 % 9+10-11-12/13*14` → `-16.448...`
- ✅ **Modulus** - `test2 %= 1` → `0`
- ✅ **Exponentiation** - `2 ** 3` → `8`, `3 ** 2 ** 2` → `81`
- ✅ **Exponentiation assignment** - `test2 **= 0` → `1`
- ✅ **Unary plus** - `+'1'` → `1`
- ✅ **Unary minus** - `-'1'` → `-1`

### Logical Operators

- ✅ **Logical AND** - `true && true || false` → `true`
- ✅ **Logical OR** - `test2 || false` → `3`
- ✅ **Logical NOT** - `!test2` → `false`, `!!test2` → `true`
- ✅ **Nullish coalescing** - `null ?? 'default'` → `'default'`, `0 ?? 'default'` → `0`

### Comparison Operators

- ✅ **Equality** - `test2 == '3'` → `true`
- ✅ **Strict equality** - `test2 === '3'` → `false`
- ✅ **Inequality** - `test2 != '3'` → `false`
- ✅ **Strict inequality** - `test2 !== '3'` → `true`
- ✅ **Less than** - `test2 < 3` → `false`
- ✅ **Greater than** - `test2 > 3` → `false`
- ✅ **Less than or equal** - `test2 <= 3` → `true`
- ✅ **Greater than or equal** - `test2 >= 3` → `true`

### Bitwise Operators

- ✅ **Bitwise AND** - `test2 & 1` → `1`
- ✅ **Bitwise OR** - `test2 | 4` → `7`
- ✅ **Bitwise NOT** - `~test2` → `-2`
- ✅ **Bitwise XOR** - `test2 ^= 1` → `1`
- ✅ **Left shift** - Tested in complex expressions
- ✅ **Right shift** - Tested in complex expressions
- ✅ **Unsigned right shift** - Tested in complex expressions
- ✅ **Left shift assignment** - `let x = 5; x <<= 1` → `10`
- ✅ **Right shift assignment** - `let x = 8; x >>= 1` → `4`
- ✅ **Unsigned right shift assignment** - `let x = 8; x >>>= 2` → `2`
- ✅ **XOR assignment** - `test2 ^= 1` → `1`
- ✅ **AND assignment** - `test2 &= 3` → `1`
- ✅ **OR assignment** - `test2 |= 2` → `3`

### Assignment Operators

- ✅ **Simple assignment** - `test2 = 1` → `1`
- ✅ **Addition assignment** - `test2 += 1` → `2`
- ✅ **Subtraction assignment** - `test2 -= 1` → `1`
- ✅ **Multiplication assignment** - `test2 *= 2` → `2`
- ✅ **Division assignment** - `test2 /= 2` → `1`
- ✅ **Exponentiation assignment** - `test2 **= 0` → `1`
- ✅ **Modulus assignment** - `test2 %= 1` → `0`
- ✅ **XOR assignment** - `test2 ^= 1` → `1`
- ✅ **AND assignment** - `test2 &= 3` → `1`
- ✅ **OR assignment** - `test2 |= 2` → `3`
- ✅ **Logical AND assignment (&&=)** - `let x = 10; x &&= 5` → `5`
- ✅ **Logical OR assignment (||=)** - `let x = 0; x ||= 5` → `5`
- ✅ **Nullish coalescing assignment (??=)** - `let x = null; x ??= 5` → `5`
- ✅ **Post-increment** - `test2++` → `1`
- ✅ **Pre-increment** - `++test2` → `3`

### Other Operators

- ✅ **Conditional (ternary)** - `test[test2] ? true : false ? 'not ok' : 'ok'` → `'ok'`
- ✅ **Optional chaining** - `!({}).a?.a` → `true`, `({}).a?.toString()` → `undefined`
- ✅ **Comma operator** - `1,2` → `2`
- ✅ **typeof** - `typeof '1'` → `'string'`, `typeof x === 'undefined'` → `true`
- ✅ **instanceof** - `{} instanceof Object` → `true`
- ✅ **in operator** - `'a' in {a: 1}` → `true`
- ✅ **delete operator** - `delete 1` → `true`, `let a = {b: 1}; return delete a.b` → `true`
- ✅ **void operator** - `void 2 == '2'` → `false`
- ✅ **new operator** - `new Date(0).toISOString()` → `'1970-01-01T00:00:00.000Z'`

### Data Types

- ✅ **Numbers** - `2.2204460492503130808472633361816E-16` → Scientific notation
- ✅ **BigInt** - `(1n + 0x1n).toString()` → `'2'`
- ✅ **Binary literals** - `0b1010` → `10`, `0B1111` → `15`, `0b1010n` → `'10'` (BigInt), `0b1_000` → `8` (with separators)
- ✅ **Octal literals** - `0o17` → `15`, `0O77` → `63`, `0o17n` → `'15'` (BigInt), `0o7_777` → `4095` (with separators)
- ✅ **Strings** - `"test2"` → `'test2'`
- ✅ **Template literals** - `` `test2 is ${`also ${test2}`}` `` → `'test2 is also 1'`
- ✅ **Tagged template functions** - ``tag`hello ${"world"}` `` → function receives string parts and interpolated values
- ✅ **Escape sequences** - `"\\"` → `'\\'`, `"\\xd9"` → `'Ù'`, `"\\n"` → `'\n'`
- ✅ **Boolean** - `true`, `false`
- ✅ **null** - `null ?? 'default'` → `'default'`
- ✅ **undefined** - `typeof x === 'undefined'` → `true`
- ✅ **Arrays** - `[test2, 2]` → `[1, 2]`
- ✅ **Objects** - `{"aa": test[0](), b: test2 * 3}` → `{ "aa": 1, "b": 3 }`
- ✅ **Regular expressions** - `/a/.test('a')` → `true`, `/a/i.test('A')` → `true`

### Objects & Arrays

- ✅ **Object literals** - `{a: 1, b: 2}` → `{ a: 1, b: 2 }`
- ✅ **Array literals** - `[1, 2]` → `[1, 2]`
- ✅ **Property access (dot)** - `a.b.c` → `2`
- ✅ **Property access (bracket)** - `a['b']['c']` → `2`
- ✅ **Computed property names** - `{"aa": test[0]()}` → `{ "aa": 1 }`
- ✅ **Object spread** - `{a: 1, ...{b: 2, c: {d: test2,}}, e: 5}` → Full object
- ✅ **Array spread** - `[1, ...[2, [test2, 4]], 5]` → `[1, 2, [3, 4], 5]`
- ✅ **Object method shorthand** - `let y = {a: 1, b(x) {return this.a + x}}; return y.b(2)` → `3`

### Functions

- ✅ **Function declarations** - `function f(a) { return a + 1 } return f(2);` → `3`
- ✅ **Function expressions** - `(function () { return 1 })()` → `1`
- ✅ **Arrow functions (single param)** - `(a => a + 1)(1)` → `2`
- ✅ **Arrow functions (multiple params)** - `((a) => {return a + 1})(1)` → `2`
- ✅ **Arrow functions (expression body)** - `(a => a + 1)(1)` → `2`
- ✅ **Arrow functions (block body)** - `(() => {return 1})()` → `1`
- ✅ **Async arrow functions** - `(async () => 1)()` → `1`
- ✅ **Async function expressions** - `(async () => await 1)()` → `1`
- ✅ **Rest parameters** - `[0,1].filter((...args) => args[1])` → `[1]`
- ✅ **Parameter default values** - `function fn(a = 1) { return a; }` → `1`
- ✅ **Spread in function calls** - `Math.pow(...[2, 2])` → `4`
- ✅ **Constructor functions** - `function LinkedListNode(e){this.value=e,this.next=null}` with `new`
- ✅ **Recursive functions** - Linked list reverse example

#### Destructuring
- ✅ **Array destructuring** - `const [a, b] = [1, 2]`
- ✅ **Object destructuring** - `const {a, b} = {a: 1, b: 2}`
- ✅ **Nested destructuring** - `const {a: {b}} = {a: {b: 42}}`
- ✅ **Destructuring with defaults** - `const {a = 1} = {}`
- ✅ **Custom variable names (renaming)** - `const {a: myA} = {a: 1}`
- ✅ **Destructuring in function parameters** - `function fn({a, b}) { }`
- ✅ **Rest in destructuring** - `const [a, ...rest] = [1, 2, 3]`, `const {a, ...rest} = obj`
- ✅ **Computed property names in destructuring** - `const {[key]: val} = obj`
- ✅ **Destructuring in for-of/for-in loops** - `for (const [a, b] of arr) { }`
- ✅ **Destructuring in function parameters with defaults** - `function fn({a = 1, b = 2} = {}) { }`

### Control Flow

#### Conditionals
- ✅ **if statement** - `if (true) { return true; } else return false` → `true`
- ✅ **else statement** - `if (false) { return true; } else return false` → `false`
- ✅ **if/else chains** - `if (false) return true; else if (false) {return true} else return false` → `false`
- ✅ **Nested if statements** - Complex nested if/else with 9 levels
- ✅ **Inline ternary** - `true ? 1 : 2` → `1`

#### Loops
- ✅ **for loop** - `let x; for(let i = 0; i < 2; i++){ x = i }; return x;` → `1`
- ✅ **while loop** - `let x = 2; while(--x){ }; return x;` → `0`
- ✅ **do-while loop** - `let x = 1; do {x++} while(x < 1); return x;` → `2`
- ✅ **for-of loop** - `for(let i of [1,2]){ return i };` → `1`
- ✅ **for-in loop** - `for(let i in [1,2]){ return i };` → `'0'`
- ✅ **break statement** - `for(let i = 0; i < 2; i++){ x = i; break; }` → Exits early
- ✅ **continue statement** - `for (let i = 0; i < 5; i++) { if (i === 2) continue; sum += i; }` → Skips iteration

#### Switch
- ✅ **switch statement** - `switch(1) {case 1: b = 2; break; case 2: b = 3; default: b = 4}; return b` → `2`
- ✅ **case clauses** - Multiple case tests
- ✅ **default clause** - `switch(3) {case 1: b = 2; break; case 2: b = 3; default: b = 4}; return b` → `4`
- ✅ **Fall-through behavior** - `switch(1) {case 1:b = 2; case 2: b = 3; default: b = 4}; return b` → `4`

#### Error Handling
- ✅ **try/catch** - `try {a.x.a} catch {return 1}; return 2` → `1`
- ✅ **try/catch with exception variable** - `try { throw new Error('msg'); } catch(e) { return e.message; }` → `'msg'`
- ✅ **finally block** - `try { return 1; } finally { x = 2; }` → Finally executes before return
- ✅ **finally overrides return** - `try { return 1; } finally { return 2; }` → `2`
- ✅ **finally overrides error** - `try { throw Error('a'); } finally { throw Error('b'); }` → Error: 'b'
- ✅ **throw statement** - `throw new Error('test')` → Error with message

#### Other
- ✅ **Code blocks** - `{let j = 1; i += j;}` → Block scope
- ✅ **this binding** - `let y = {a: 1, b(x) {return this.a + x}}` → Method context
- ✅ **Closures** - `const a = () => {return 1}; const b = () => {return 2}; return (() => a() + b())()` → `3`

### Variables

- ✅ **var declaration** - `var i = 1; return i + 1` → `2`
- ✅ **let declaration** - `let j = 1; return j + 1` → `2`
- ✅ **const declaration** - `const k = 1; return k + 1` → `2`
- ✅ **const immutability** - `const l = 1; return l = 2` → Error

### Async/Await

- ✅ **async functions** - `(async () => 1)()` → Promise resolves to `1`
- ✅ **await keyword** - `(async () => await 1)()` → `1`
- ✅ **await with promises** - `(async () => await (async () => 1)())()` → `1`
- ✅ **async with variables** - `let p = (async () => 1)(); return (async () => 'i = ' + await p)()` → `'i = 1'`
- ✅ **Async arrow functions** - `let i = 0; (async () => i += 1)(); return i;` → `1`
- ✅ **for-await-of loops** - `for await (const item of asyncIterable) { }`

#### Generators (ES6)
- ✅ **Generator functions (function*)** - `function* gen() { yield 1; }`
- ✅ **yield keyword**
- ✅ **`yield` as expression value** - `const x = yield 1`
- ✅ **yield* delegation**
- ✅ **Async generators** - `async function* gen() { yield 1; }`
- ✅ **Iterator `.return()`/`.throw()`** - Protocol methods for early termination and error injection
- ✅ **`next(value)` injection** - Sending values back into a paused generator

### Other Built-in Objects

- ✅ **WeakMap** - All methods work: `set()`, `get()`, `has()`, `delete()` (6 tests)
- ✅ **WeakSet** - All methods work: `add()`, `has()`, `delete()`

### Comments

- ✅ **Single-line comments** - `1 // 2` → `1`
- ✅ **Multi-line comments** - `/* 2 */ 1` → `1`

### Operator Precedence

Comprehensive operator precedence testing has been implemented with 35 tests covering:
- NOT (!) with comparison operators
- Logical NOT with AND/OR
- Comparison operator chaining
- Bitwise vs logical operators
- Bitwise shift with arithmetic
- Mixed bitwise operators (correct precedence: shift > & > ^ > |)
- Exponentiation (right-associative)
- typeof, delete, void with various operators
- Optional chaining and nullish coalescing
- Increment/decrement with arithmetic
- Multiple unary operators
- Comma operator in expressions

---

## ❌ Not Supported Features

The following ECMAScript features are not currently supported in SandboxJS:

### HIGH PRIORITY

#### Classes (ES6)
- ❌ **class declarations**
- ❌ **extends keyword (inheritance)**
- ❌ **super keyword**
- ❌ **Static methods**
- ❌ **Class fields (public)**
- ❌ **Private fields (#field)**
- ❌ **Private methods**
- ❌ **Static class fields**
- ❌ **Static initialization blocks**

#### Object Features
- ❌ **Getters in object literals** - `{get prop() { return 1; }}`
- ❌ **Setters in object literals** - `{set prop(v) { this.val = v; }}`

### LOW PRIORITY

#### Modules
Module features are not supported by design as SandboxJS is intended for sandboxed code execution:
- ❌ **import statements**
- ❌ **export statements**
- ❌ **Dynamic import()**
- ❌ **import.meta**

---

## 🔒 Security-Related Restrictions (Intentionally Blocked)

These features are intentionally blocked for security reasons:

- 🔒 Direct access to global scope
- 🔒 Access to `__proto__` (prototype pollution prevention)
- 🔒 Global object pollution
- 🔒 Prototype method overriding
- 🔒 Access to non-whitelisted globals
- 🔒 Access to non-whitelisted prototype methods
- 🔒 `with` statement
- 🔒 `arguments` object (security risk, use rest parameters `...args` instead)
- 🔒 Execution beyond quota limits

---

## 📝 Notes

- **Priority Levels**:
  - **HIGH**: Common patterns used frequently in production code
  - **MEDIUM**: Less common but still important for completeness
  - **LOW**: Edge cases and advanced features with limited use
- **Implementation Focus**: SandboxJS focuses on core ES5-ES2018 features with strong security controls
- **Performance**: Advanced meta-programming features are omitted to maintain sandbox safety


================================================
FILE: css/style.css
================================================
:root {
  --bg: #0f1117;
  --surface: #1a1d27;
  --surface2: #22263a;
  --border: #2e3248;
  --accent: #4a9eff;
  --accent2: #ffe94d;
  --text: #e4e8f0;
  --muted: #8892aa;
  --pass: #34d399;
  --fail: #f87171;
  --header-h: 130px;
  --sidebar-w: 260px;
  --font-bump: 0px;
  font-size: 15px;
}

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
  font-size: calc(1rem + var(--font-bump));
  min-height: 100vh;
}

body.modal-open {
  overflow: hidden;
}

/* ── Header ──────────────────────────────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 14px 24px 10px;
  backdrop-filter: blur(10px);
}

.header-inner {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 1 1 auto;
}

.logo {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
}

.brand-text h1 {
  font-size: calc(1.4rem + var(--font-bump));
  font-weight: 700;
  letter-spacing: -0.3px;
  color: var(--text);
}

.tagline {
  font-size: calc(0.75rem + var(--font-bump));
  color: var(--muted);
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

.controls {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
}

/* toggle */
.toggle-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: calc(0.85rem + var(--font-bump));
  color: var(--muted);
  user-select: none;
}
.toggle-label input { display: none; }
.toggle-track {
  position: relative;
  width: 34px;
  height: 18px;
  background: var(--border);
  border-radius: 9px;
  transition: background 0.2s;
}
.toggle-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 12px;
  height: 12px;
  background: var(--muted);
  border-radius: 50%;
  transition: left 0.2s, background 0.2s;
}
.toggle-label input:checked + .toggle-track,
.settings-toggle-label input:checked + .toggle-track { background: var(--accent); }
.toggle-label input:checked + .toggle-track .toggle-thumb,
.settings-toggle-label input:checked + .toggle-track .toggle-thumb {
  left: 19px;
  background: #fff;
}

.select-wrap select {
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 10px;
  font-size: calc(0.85rem + var(--font-bump));
  cursor: pointer;
  outline: none;
  appearance: none;
  padding-right: 28px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%238892aa'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
}

.run-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 6px 16px;
  font-size: calc(0.85rem + var(--font-bump));
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.15s;
}
.run-btn:hover { opacity: 0.85; }

.secondary-btn,
.icon-btn {
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 16px;
  font-size: calc(0.85rem + var(--font-bump));
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, opacity 0.15s;
}

.secondary-btn:hover,
.icon-btn:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--surface2));
}

.icon-btn {
  padding-inline: 12px;
}

.description {
  margin-top: 8px;
  font-size: calc(0.78rem + var(--font-bump));
  color: var(--muted);
  line-height: 1.5;
  max-width: 900px;
}
.description code {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 1px 4px;
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  font-size: calc(0.75rem + var(--font-bump));
  color: var(--accent);
}

/* ── Layout ──────────────────────────────────────────────── */
.page-body {
  display: flex;
  gap: 0;
  min-height: calc(100vh - var(--header-h));
}

/* ── Sidebar ─────────────────────────────────────────────── */
.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  padding: 20px 16px;
  border-right: 1px solid var(--border);
  position: sticky;
  top: var(--header-h);
  height: calc(100vh - var(--header-h));
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

@media (min-width: 721px) {
  .sidebar {
    padding-top: 0;
  }
}

.card-title {
  font-size: calc(0.7rem + var(--font-bump));
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}

/* perf card */
.perf-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px;
}

#times {
  width: 100%;
  border-collapse: collapse;
}

#times th, #times td {
  padding: 4px 6px;
  font-size: calc(0.78rem + var(--font-bump));
  border: none;
  color: var(--text);
}

#times th { color: var(--muted); font-weight: 500; }
#times td:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-family: 'Fira Code', monospace;
  font-size: calc(0.75rem + var(--font-bump));
}
#times tr { border-bottom: 1px solid var(--border); }
#times tr:last-child { border-bottom: none; }

/* category nav */
.category-nav {
  flex: 1;
}
#category-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
#category-list li .category-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 6px 10px;
  border-radius: 6px;
  border: none;
  background: transparent;
  text-align: left;
  color: var(--muted);
  font-size: calc(0.82rem + var(--font-bump));
  transition: background 0.15s, color 0.15s;
  cursor: pointer;
}
#category-list li .category-link:hover {
  background: var(--surface2);
  color: var(--text);
}
#category-list li .category-link.active {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
}
.cat-badge {
  font-size: calc(0.7rem + var(--font-bump));
  background: var(--surface2);
  border-radius: 10px;
  padding: 1px 7px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cat-pass { color: var(--pass); }
.cat-fail { color: var(--fail); }

/* ── Main ────────────────────────────────────────────────── */
.tests-main {
  flex: 1;
  padding: 20px 24px;
  min-width: 0;
}

/* summary */
.summary-bar {
  display: flex;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.summary-chip {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 16px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.summary-chip .chip-val {
  font-size: calc(1.4rem + var(--font-bump));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.summary-chip .chip-label {
  font-size: calc(0.72rem + var(--font-bump));
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.chip-pass .chip-val { color: var(--pass); }
.chip-fail .chip-val { color: var(--fail); }

/* sections */
.test-section {
  margin-bottom: 32px;
}

.section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
  cursor: pointer;
  user-select: none;
}

.section-title {
  font-size: 1.6rem;
  font-weight: 600;
  color: var(--text);
}

.section-count {
  font-size: calc(0.75rem + var(--font-bump));
  color: var(--muted);
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1px 9px;
}

.section-toggle {
  margin-left: auto;
  font-size: calc(0.7rem + var(--font-bump));
  color: var(--muted);
  transition: transform 0.2s;
}
.test-section.collapsed .section-toggle { transform: rotate(-90deg); }
.test-section.collapsed .section-body { display: none; }

.section-pass-rate {
  font-size: calc(0.72rem + var(--font-bump));
  padding: 2px 8px;
  border-radius: 10px;
  font-weight: 600;
}
.rate-good { background: color-mix(in srgb, var(--pass) 15%, transparent); color: var(--pass); }
.rate-bad  { background: color-mix(in srgb, var(--fail) 15%, transparent); color: var(--fail); }

/* table */
.section-body {
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

table.test-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

table.test-table col.col-code { width: 46%; }
table.test-table col.col-eval { width: 22%; }
table.test-table col.col-sandbox { width: 22%; }
table.test-table col.col-verdict { width: 10%; }

table.test-table thead th {
  background: var(--surface2);
  color: var(--muted);
  font-size: calc(0.72rem + var(--font-bump));
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 8px 12px;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

table.test-table tbody tr {
  border-bottom: 1px solid var(--border);
  transition: background 0.1s;
}
table.test-table tbody tr:last-child { border-bottom: none; }
table.test-table tbody tr:hover { background: var(--surface2); }

table.test-table td {
  padding: 7px 12px;
  font-size: calc(0.82rem + var(--font-bump));
  vertical-align: middle;
}

td.td-code {
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  font-size: calc(0.78rem + var(--font-bump));
  color: var(--accent);
  max-width: 340px;
}

.td-code-wrap {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.td-code-text {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}

.runner-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.3rem;
  height: 2.3rem;
  flex-shrink: 0;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: linear-gradient(180deg, color-mix(in srgb, var(--surface2) 92%, white 8%), var(--surface2));
  color: var(--accent);
  text-decoration: none;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.05);
  transition: transform 0.15s, background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
}

.runner-link-icon {
  width: 1.45rem;
  height: 1.45rem;
}

.runner-link:hover {
  background: color-mix(in srgb, var(--accent) 12%, var(--surface2));
  border-color: var(--accent);
  color: #fff;
  transform: translateY(-1px);
  box-shadow: 0 6px 14px rgb(0 0 0 / 0.18);
}

.runner-link:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(74 158 255 / 0.18);
}

td.td-result {
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  font-size: calc(0.75rem + var(--font-bump));
  color: var(--text);
  max-width: 180px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

td.td-verdict {
  font-weight: 700;
  font-size: calc(0.78rem + var(--font-bump));
  white-space: nowrap;
  text-align: left;
}

.positive { color: var(--pass); }
.negative { color: var(--fail); }

.error {
  cursor: help;
  color: var(--fail);
}

.hidden {
  display: none;
}

.modal-shell {
  position: fixed;
  inset: 0;
  z-index: 300;
}

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(8 10 16 / 0.72);
  backdrop-filter: blur(10px);
}

.sandbox-modal {
  position: relative;
  width: min(900px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  margin: 24px auto;
  background: linear-gradient(180deg, rgb(31 36 54 / 0.98), rgb(20 24 36 / 0.98));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border));
  border-radius: 18px;
  box-shadow: 0 30px 80px rgb(0 0 0 / 0.45);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.sandbox-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 22px 16px;
  border-bottom: 1px solid var(--border);
}

.sandbox-modal-header h2 {
  font-size: calc(1.1rem + var(--font-bump));
  margin-bottom: 4px;
}

.sandbox-modal-header p {
  font-size: calc(0.82rem + var(--font-bump));
  color: var(--muted);
  line-height: 1.5;
}

.sandbox-modal-body {
  padding: 20px 22px 22px;
  overflow: auto;
  display: grid;
  gap: 18px;
}

.sandbox-field {
  display: grid;
  gap: 10px;
}

.sandbox-field-header {
  display: flex;
  align-items: center;
  gap: 10px;
}

.sandbox-field-label {
  font-size: calc(0.76rem + var(--font-bump));
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted);
  flex: 1;
}

.settings-gear-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
  flex-shrink: 0;
}

.settings-gear-btn svg {
  width: 1.1rem;
  height: 1.1rem;
}

.settings-gear-btn:hover,
.settings-gear-btn.active {
  color: var(--accent);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.settings-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 14px;
  height: 14px;
  background: var(--accent);
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  border-radius: 7px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 3px;
  line-height: 1;
}
.settings-badge.hidden {
  display: none;
}

.sandbox-settings-panel {
  background: rgb(10 13 22 / 0.82);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}

.settings-panel-inner {
  padding: 14px 16px;
  display: grid;
  gap: 14px;
}

.settings-section {
  display: grid;
  gap: 8px;
}

.settings-section-label {
  font-size: calc(0.7rem + var(--font-bump));
  font-weight: 600;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted);
}

.settings-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
}

.settings-toggle-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: calc(0.82rem + var(--font-bump));
  color: var(--text);
  user-select: none;
  font-family: 'Fira Code', 'Cascadia Code', monospace;
}
.settings-toggle-label input { display: none; }

.settings-field-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.settings-input {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  font-size: calc(0.82rem + var(--font-bump));
  padding: 5px 10px;
  width: 160px;
  outline: none;
  transition: border-color 0.15s;
  appearance: textfield;
  -moz-appearance: textfield;
}
.settings-input::-webkit-outer-spin-button,
.settings-input::-webkit-inner-spin-button { -webkit-appearance: none; }
.settings-input:focus { border-color: var(--accent); }
.settings-input.settings-field-error { border-color: var(--fail); }

.settings-textarea {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  font-size: calc(0.82rem + var(--font-bump));
  padding: 7px 10px;
  width: 100%;
  resize: vertical;
  outline: none;
  transition: border-color 0.15s;
  line-height: 1.5;
}
.settings-textarea:focus { border-color: var(--accent); }
.settings-textarea.settings-field-error { border-color: var(--fail); }

.settings-field-hint {
  font-size: calc(0.72rem + var(--font-bump));
  color: var(--muted);
}

.settings-reset-btn {
  align-self: start;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 12px;
  font-size: calc(0.78rem + var(--font-bump));
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.settings-reset-btn:hover {
  color: var(--fail);
  border-color: var(--fail);
}

#sandbox-editor {
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  transition: border-color 0.15s, box-shadow 0.15s;
}

#sandbox-editor:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(74 158 255 / 0.18);
}

#sandbox-editor .cm-editor {
  background: rgb(12 15 24 / 0.92);
}

#sandbox-editor .cm-scroller {
  min-height: 240px;
}

.sandbox-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.sandbox-ticks {
  font-size: calc(0.78rem + var(--font-bump));
  color: var(--muted);
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  white-space: nowrap;
}

.sandbox-ticks::before {
  content: 'execution ticks: ';
  opacity: 0.55;
}

.sandbox-status {
  margin-right: auto;
  font-size: calc(0.78rem + var(--font-bump));
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--muted);
}

.sandbox-status.status-running {
  color: var(--accent2);
}

.sandbox-status.status-success {
  color: var(--pass);
}

.sandbox-status.status-error {
  color: var(--fail);
}

.sandbox-status.status-halted {
  color: #f97316;
}

.sandbox-notice {
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  font-size: calc(0.82rem + var(--font-bump));
  line-height: 1.45;
}

.sandbox-notice strong {
  font-size: calc(1rem + var(--font-bump));
}

.sandbox-notice-safe {
  background: color-mix(in srgb, var(--pass) 14%, rgb(10 13 22 / 0.78));
  border-color: color-mix(in srgb, var(--pass) 35%, var(--border));
  color: var(--pass);
}

.sandbox-notice-critical {
  background: color-mix(in srgb, var(--fail) 15%, rgb(10 13 22 / 0.78));
  border-color: color-mix(in srgb, var(--fail) 40%, var(--border));
  color: #ffb2b2;
}

.sandbox-output-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.output-card {
  background: rgb(10 13 22 / 0.78);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}

.output-card h3 {
  font-size: calc(0.78rem + var(--font-bump));
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}

.output-card pre {
  min-height: 100px;
  padding: 14px;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: 'Fira Code', 'Cascadia Code', monospace;
  font-size: calc(0.8rem + var(--font-bump));
  line-height: 1.55;
  color: var(--text);
}

@media (max-width: 900px) {
  .sandbox-output-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 720px) {
  .controls {
    width: 100%;
    flex-wrap: wrap;
  }

  .page-body {
    flex-direction: column;
  }

  .sidebar {
    position: static;
    width: 100%;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }

  .tests-main {
    padding-inline: 16px;
  }

  .sandbox-modal {
    width: calc(100vw - 20px);
    max-height: calc(100vh - 20px);
    margin: 10px auto;
  }

  .sandbox-modal-header,
  .sandbox-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .sandbox-status {
    margin-right: 0;
  }
}

/* scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }


================================================
FILE: eslint.config.js
================================================
const eslint = require('@eslint/js');
const tseslint = require('@typescript-eslint/eslint-plugin');
const tsparser = require('@typescript-eslint/parser');
const globals = require('globals');
const prettierConfig = require('eslint-config-prettier');

module.exports = [
  // Global ignores
  {
    ignores: [
      'node_modules/**',
      'dist/**',
      'build/**',
      'test/**',
      'coverage/**',
      '*.min.js',
      'test/eval/jquery.min.js',
      'rollup.config.mjs',
      'jest.config.js',
      'eslint.config.js'
    ]
  },
  // Base config for all TypeScript files
  {
    files: ['**/*.ts'],
    languageOptions: {
      parser: tsparser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        project: './tsconfig.json'
      },
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.es2021
      }
    },
    plugins: {
      '@typescript-eslint': tseslint
    },
    rules: {
      ...eslint.configs.recommended.rules,
      ...tseslint.configs.recommended.rules,
      ...prettierConfig.rules,
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-empty-object-type': 'warn',
      '@typescript-eslint/no-unused-expressions': 'warn',
      '@typescript-eslint/no-unsafe-function-type': 'off',
      'no-fallthrough': 'off',
      '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^lastLastLastLastPart' }],
      '@typescript-eslint/no-unsafe-assignment': 'off',
      '@typescript-eslint/no-this-alias': 'off'
    }
  },
  // Scripts that are part of the test tsconfig
  {
    files: ['scripts/export-tests.ts'],
    languageOptions: {
      parser: tsparser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        project: './test/tsconfig.json'
      },
      globals: {
        ...globals.node,
        ...globals.es2021
      }
    },
    plugins: {
      '@typescript-eslint': tseslint
    },
    rules: {
      ...eslint.configs.recommended.rules,
      ...tseslint.configs.recommended.rules,
      ...prettierConfig.rules,
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-empty-object-type': 'warn',
      '@typescript-eslint/no-unused-expressions': 'warn',
      '@typescript-eslint/no-unsafe-function-type': 'off',
      'no-fallthrough': 'off',
      '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^lastLastLastLastPart' }],
      '@typescript-eslint/no-unsafe-assignment': 'off',
    }
  }
];


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SandboxJS — Test Suite</title>
  <link rel="shortcut icon" href="logo.svg" type="image/x-icon">
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
  <script src="test/eval/script.js" type="module"></script>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <header class="site-header">
    <div class="header-inner">
      <div class="brand">
        <img src="logo.svg" alt="SandboxJS logo" class="logo">
        <div class="brand-text">
          <h1>SandboxJS</h1>
          <span class="tagline">Safe JavaScript eval runtime</span>
        </div>
      </div>
      <div class="controls">
        <label class="toggle-label">
          <input id="jit-parsing" type="checkbox" checked>
          <span class="toggle-track"><span class="toggle-thumb"></span></span>
          JIT Parsing
        </label>
        <div class="select-wrap">
          <select id="runtime-type">
            <option value="sync" selected>Sync</option>
            <option value="async">Async</option>
          </select>
        </div>
        <button id="open-sandbox-modal-btn" class="secondary-btn">Sandbox Runner</button>
        <button id="run-btn" class="run-btn">Run Tests</button>
      </div>
    </div>
    <p class="description">
      Tests compare SandboxJS against a proxied <code>eval</code>. A global <code>bypassed</code> flag is set to <code>true</code> if a test escapes the sandbox — highlighted in red. Blocked bypasses show as <em>Error</em>.
    </p>
  </header>

  <div class="page-body">
    <aside class="sidebar">
      <div class="perf-card">
        <h2 class="card-title">Performance</h2>
        <table id="times">
          <tbody></tbody>
        </table>
      </div>
      <nav class="category-nav" id="category-nav">
        <h2 class="card-title">Sections</h2>
        <ul id="category-list"></ul>
      </nav>
    </aside>

    <main class="tests-main" id="tests-main">
      <div class="summary-bar" id="summary-bar"></div>
      <!-- sections injected here by script.js -->
    </main>
  </div>

  <div id="sandbox-modal" class="modal-shell hidden" aria-hidden="true">
    <div class="modal-backdrop" data-close-modal="true"></div>
    <div class="sandbox-modal" role="dialog" aria-modal="true" aria-labelledby="sandbox-modal-title">
      <div class="sandbox-modal-header">
        <div>
          <h2 id="sandbox-modal-title">Sandbox Runner</h2>
          <p>Write JavaScript, keep it in the URL hash, and run it only when you press the button.</p>
        </div>
        <button id="sandbox-close-btn" class="icon-btn" type="button" aria-label="Close sandbox runner">Close</button>
      </div>

      <div class="sandbox-modal-body">
        <div class="sandbox-field">
          <div class="sandbox-field-header">
            <span class="sandbox-field-label">JavaScript Input</span>
            <button id="sandbox-settings-btn" class="settings-gear-btn" type="button" aria-label="Sandbox settings" title="Sandbox settings">
              <svg viewBox="0 0 20 20" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
                <circle cx="10" cy="10" r="2.8"/>
                <path d="M10 2.5v1.2M10 16.3v1.2M2.5 10h1.2M16.3 10h1.2M4.6 4.6l.85.85M14.55 14.55l.85.85M4.6 15.4l.85-.85M14.55 5.45l.85-.85"/>
              </svg>
              <span id="sandbox-settings-badge" class="settings-badge hidden"></span>
            </button>
          </div>
          <div id="sandbox-settings-panel" class="sandbox-settings-panel hidden">
            <div class="settings-panel-inner">
              <div class="settings-section">
                <span class="settings-section-label">Options</span>
                <div class="settings-grid">
                  <label class="settings-toggle-label">
                    <input type="checkbox" id="setting-forbid-calls">
                    <span class="toggle-track"><span class="toggle-thumb"></span></span>
                    forbidFunctionCalls
                  </label>
                  <label class="settings-toggle-label">
                    <input type="checkbox" id="setting-forbid-creation">
                    <span class="toggle-track"><span class="toggle-thumb"></span></span>
                    forbidFunctionCreation
                  </label>
                  <label class="settings-toggle-label">
                    <input type="checkbox" id="setting-halt-on-error">
                    <span class="toggle-track"><span class="toggle-thumb"></span></span>
                    haltOnSandboxError
                  </label>
                  <label class="settings-toggle-label">
                    <input type="checkbox" id="setting-async">
                    <span class="toggle-track"><span class="toggle-thumb"></span></span>
                    async
                  </label>
                </div>
              </div>
              <div class="settings-section">
                <span class="settings-section-label">Execution Quota</span>
                <div class="settings-field-row">
                  <input type="number" id="setting-quota" class="settings-input" placeholder="unlimited" min="0" step="1000">
                  <span class="settings-field-hint">ticks (blank = unlimited)</span>
                </div>
              </div>
              <div class="settings-section">
                <span class="settings-section-label">Scope / State</span>
                <div class="settings-field-row">
                  <textarea id="setting-scope" class="settings-textarea" placeholder='{ "myVar": 42 }' rows="3" spellcheck="false"></textarea>
                </div>
                <span class="settings-field-hint">JSON object merged into sandbox scope</span>
              </div>
              <button id="sandbox-settings-reset-btn" class="settings-reset-btn" type="button">Reset to defaults</button>
            </div>
          </div>
          <div id="sandbox-editor"></div>
        </div>

        <div class="sandbox-actions">
          <span id="sandbox-status" class="sandbox-status">Idle</span>
          <span id="sandbox-ticks-output" class="sandbox-ticks">—</span>
          <button id="sandbox-clear-btn" class="secondary-btn" type="button">Clear</button>
          <button id="sandbox-exec-btn" class="run-btn" type="button">Run In Sandbox</button>
        </div>

        <div id="sandbox-bypass-notice" class="sandbox-notice sandbox-notice-safe">
          <strong id="sandbox-bypass-icon" aria-hidden="true">✓</strong>
          <span id="sandbox-bypass-text">`globalThis.bypassed` is falsy.</span>
        </div>

        <div class="sandbox-output-grid">
          <section class="output-card">
            <h3>Console</h3>
            <pre id="sandbox-console-output">No logs yet.</pre>
          </section>
          <section class="output-card">
            <h3>Return Value</h3>
            <pre id="sandbox-return-output">Run code to inspect the result.</pre>
          </section>
        </div>
      </div>
    </div>
  </div>
</body>
</html>


================================================
FILE: jest.config.js
================================================
/**
 * For a detailed explanation regarding each configuration property, visit:
 * https://jestjs.io/docs/configuration
 */

/** @type {import('jest').Config} */
export default {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,

  // The directory where Jest should store its cached dependency information
  // cacheDirectory: "C:\\Users\\user\\AppData\\Local\\Temp\\jest",

  // Automatically clear mock calls, instances, contexts and results before every test
  // clearMocks: false,

  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: undefined,

  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",

  // An array of regexp pattern strings used to skip coverage collection
  coveragePathIgnorePatterns: [
    "/node_modules/",
    "/test/eval/testCases/.*\\.data\\.ts$",
    "/test/eval/testCases/types\\.ts$",
    "/test/eval/testCases/test-utils\\.ts$",
    "/test/eval/testCases/index\\.ts$",
    "/test/eval/export-tests\\.ts$",
    "/test/eval/tests\\.json$"
  ],

  // Indicates which provider should be used to instrument code for coverage
  coverageProvider: "v8",

  // A list of reporter names that Jest uses when writing coverage reports
  // coverageReporters: [
  //   "json",
  //   "text",
  //   "lcov",
  //   "clover"
  // ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: undefined,

  // A path to a custom dependency extractor
  // dependencyExtractor: undefined,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // The default configuration for fake timers
  // fakeTimers: {
  //   "enableGlobally": false
  // },

  // Force coverage collection from ignored files using an array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: undefined,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: undefined,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
  maxWorkers: '50%',

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  moduleFileExtensions: [
    "js",
    "mjs",
    "cjs",
    "jsx",
    "ts",
    "tsx",
    "json",
    "node"
  ],

  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1', // fixes path issues
  },

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  preset: 'ts-jest/presets/default-esm',

  // Run tests from one or more projects
  // projects: undefined,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state before every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: undefined,

  // Automatically restore mock state and implementation before every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  // rootDir: undefined,

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  // setupFiles: [],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // The number of seconds after which a test is considered as slow and reported as such in the results.
  // slowTestThreshold: 5,

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  testEnvironment: 'node',

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: undefined,

  // This option allows use of a custom test runner
  // testRunner: "jest-circus/runner",

  // A map from regular expressions to paths to transformers
  transform: {
    '^.+\.tsx?$': ['ts-jest', { 
      useESM: true, 
      tsconfig: 'tsconfig.jest.json',
    }],
  },

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "\\\\node_modules\\\\",
  //   "\\.pnp\\.[^\\\\]+$"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: undefined,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
};


================================================
FILE: package.json
================================================
{
  "name": "@nyariv/sandboxjs",
  "version": "0.9.6",
  "description": "Javascript sandboxing library.",
  "main": "dist/cjs/Sandbox.js",
  "module": "./dist/esm/Sandbox.js",
  "browser": "./dist/umd/Sandbox.min.js",
  "types": "./dist/esm/Sandbox.d.ts",
  "exports": {
    ".": {
      "types": "./dist/esm/Sandbox.d.ts",
      "import": "./dist/esm/Sandbox.js",
      "require": "./dist/cjs/Sandbox.js",
      "default": "./dist/esm/Sandbox.js"
    },
    "./SandboxExec": {
      "types": "./dist/esm/SandboxExec.d.ts",
      "import": "./dist/esm/SandboxExec.js",
      "require": "./dist/cjs/SandboxExec.js",
      "default": "./dist/esm/SandboxExec.js"
    },
    "./package.json": "./package.json"
  },
  "scripts": {
    "test": "NODE_OPTIONS='--no-warnings=ExperimentalWarning' jest",
    "test:perf": "NODE_OPTIONS='--no-warnings=ExperimentalWarning' node --expose-gc test/performance.mjs",
    "build": "node scripts/build.mjs",
    "lint": "prettier --check \"**/*.+(ts|json)\" && eslint --ext .ts .",
    "lint:fix": "prettier --write \"**/*.+(ts|json)\" && eslint --ext .ts --fix .",
    "patch": "npm version patch",
    "prepare": "husky"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/nyariv/SandboxJS.git"
  },
  "author": "",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/nyariv/SandboxJS/issues"
  },
  "homepage": "https://github.com/nyariv/SandboxJS#readme",
  "devDependencies": {
    "@types/jest": "^30.0.0",
    "@typescript-eslint/eslint-plugin": "^8.53.0",
    "@typescript-eslint/parser": "^8.53.0",
    "chalk": "^5.6.2",
    "cli-table3": "^0.6.5",
    "eslint": "^9.39.2",
    "eslint-config-prettier": "^10.1.8",
    "husky": "^9.1.7",
    "jest": "^30.2.0",
    "lint-staged": "^16.2.7",
    "node-fetch": "^3.3.2",
    "prettier": "^3.8.0",
    "terser": "^5.46.1",
    "tinybench": "^6.0.0",
    "ts-jest": "^29.4.6",
    "tslib": "^2.8.1",
    "typescript": "^5.9.3",
    "vite": "^8.0.8",
    "vite-plugin-dts": "^4.5.4"
  },
  "lint-staged": {
    "*.ts": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.json": [
      "prettier --write"
    ]
  }
}


================================================
FILE: scripts/build.mjs
================================================
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { rmSync, writeFileSync } from 'node:fs'
import { execSync } from 'node:child_process'
import { build } from 'vite'
import dts from 'vite-plugin-dts'

const __dirname = dirname(fileURLToPath(import.meta.url))
const root = resolve(__dirname, '..')

try {
  execSync('npx tsc --noEmit', { cwd: root, stdio: 'inherit' })
} catch {
  process.exit(1)
}

rmSync(resolve(root, 'dist'), { recursive: true, force: true })

const entries = {
  Sandbox: resolve(root, 'src/Sandbox.ts'),
  SandboxExec: resolve(root, 'src/SandboxExec.ts'),
}

const minifyOptions = {
  minify: 'terser',
  terserOptions: {
    keep_fnames: /^Sandbox(Symbol|(Async)?(Generator)?(Function|Global)?)$/,
  },
}

function writeModuleTypeManifest(outDir, type) {
  writeFileSync(resolve(root, outDir, 'package.json'), `${JSON.stringify({ type }, null, 2)}\n`)
}

// 1. CJS build → dist/node/
await build({
  root,
  build: {
    outDir: 'dist/cjs',
    minify: false,
    lib: {
      entry: entries,
      formats: ['cjs'],
    },
    rollupOptions: {
      output: {
        preserveModules: true,
        entryFileNames: '[name].js',
        exports: 'named',
      },
    },
  },
  plugins: [
    dts({
      outDir: 'dist/cjs',
      include: ['src'],
      tsconfigPath: resolve(root, 'tsconfig.json'),
    }),
  ],
})

writeModuleTypeManifest('dist/cjs', 'commonjs')

// 2. UMD build → dist/Sandbox.umd.js
await build({
  root,
  build: {
    outDir: 'dist/umd',
    emptyOutDir: false,
    sourcemap: true,
    lib: {
      entry: resolve(root, 'src/Sandbox.ts'),
      name: 'Sandbox',
      formats: ['umd'],
      fileName: () => 'Sandbox.min.js',
    },
    rollupOptions: {
      output: { exports: 'named' },
    },
    ...minifyOptions,
  },
})

// 3. UMD build → dist/SandboxExec.umd.js
await build({
  root,
  build: {
    outDir: 'dist/umd',
    emptyOutDir: false,
    sourcemap: true,
    lib: {
      entry: resolve(root, 'src/SandboxExec.ts'),
      name: 'SandboxExec',
      formats: ['umd'],
      fileName: () => 'SandboxExec.min.js',
    },
    rollupOptions: {
      output: { exports: 'named' },
    },
    ...minifyOptions,
  },
})

// 4. ESM build → dist/esm/
await build({
  root,
  build: {
    outDir: 'dist/esm',
    minify: false,
    sourcemap: true,
    lib: {
      entry: entries,
      formats: ['es'],
    },
    rollupOptions: {
      output: {
        preserveModules: true,
        entryFileNames: '[name].js',
        exports: 'named',
      },
    },
  },
  plugins: [
    dts({
      outDir: 'dist/esm',
      include: ['src'],
      tsconfigPath: resolve(root, 'tsconfig.json'),
    }),
  ],
})

writeModuleTypeManifest('dist/esm', 'module')


================================================
FILE: scripts/export-tests.ts
================================================
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { TestCase } from '../test/eval/testCases/types';
import * as allTestModules from '../test/eval/testCases/index';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Collect all tests from imported modules
async function extractTests() {
  const allTests: TestCase[] = [];

  // Get all test arrays from the imported modules
  for (const [moduleName, tests] of Object.entries(allTestModules)) {
    if (Array.isArray(tests)) {
      allTests.push(...tests);
      console.log(`Extracted ${tests.length} tests from ${moduleName}`);
    }
  }

  // Group tests by category, preserving order within each category
  const testsByCategory: Record<string, TestCase[]> = {};
  const categoryOrder: string[] = [];

  allTests.forEach((test) => {
    const category = test.category;
    if (!testsByCategory[category]) {
      testsByCategory[category] = [];
      categoryOrder.push(category);
    }
    testsByCategory[category].push(test);
  });

  // Build final array: Data Types first, Security second, rest in original order
  const sortedTests: TestCase[] = [];

  // Add Data Types first
  if (testsByCategory['Data Types']) {
    sortedTests.push(...testsByCategory['Data Types']);
  }

  // Add Security second
  if (testsByCategory['Security']) {
    sortedTests.push(...testsByCategory['Security']);
  }

  // Add all other categories in the order they appeared
  categoryOrder.forEach((category) => {
    if (category !== 'Data Types' && category !== 'Security') {
      sortedTests.push(...testsByCategory[category]);
    }
  });

  // Write to tests.json
  const outputPath = path.join(__dirname, '../test/eval', 'tests.json');
  const jsonContent = JSON.stringify(sortedTests, null, 2);
  // Ensure consistent LF line endings
  const normalizedContent = jsonContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
  fs.writeFileSync(outputPath, normalizedContent + '\n', 'utf8');

  console.log(`\nTotal tests: ${sortedTests.length}`);
  console.log(`Exported to: ${outputPath}`);

  // Show category distribution
  const categories: Record<string, number> = {};
  sortedTests.forEach((test) => {
    categories[test.category] = (categories[test.category] || 0) + 1;
  });

  console.log('\nTests per category:');
  Object.entries(categories).forEach(([cat, count]) => {
    console.log(`  ${cat}: ${count}`);
  });
}

extractTests().catch((err) => {
  console.error('Error:', err);
  process.exit(1);
});


================================================
FILE: src/Sandbox.ts
================================================
import { AsyncFunction, createExecContext, SandboxCapabilityError, sanitizeScopes } from './utils';
import type { IExecContext, IOptionParams, IScope } from './utils';
import { createEvalContext } from './eval';
import { ExecReturn } from './executor';
import parse from './parser';
import SandboxExec from './SandboxExec';
export { ParseError } from './parser';
export {
  LocalScope,
  SandboxExecutionTreeError,
  SandboxCapabilityError,
  SandboxAccessError,
  SandboxExecutionQuotaExceededError,
  SandboxError,
  delaySynchronousResult,
} from './utils';

export type * from './utils';
export type * from './parser';
export type * from './executor';
export type * from './eval';

export class Sandbox extends SandboxExec {
  constructor(options?: IOptionParams) {
    super(options, createEvalContext());
  }

  static audit<T>(code: string, scopes: IScope[] = []): ExecReturn<T> {
    const globals: Record<string, unknown> = {};
    for (const i of Object.getOwnPropertyNames(globalThis) as [keyof typeof globalThis]) {
      globals[i] = globalThis[i];
    }
    const sandbox = new SandboxExec({
      globals,
      audit: true,
    });
    return sandbox.executeTree(
      createExecContext(
        sandbox,
        parse(code, true, false, sandbox.context.options.maxParserRecursionDepth),
        createEvalContext(),
      ),
      scopes,
    );
  }

  static parse(code: string) {
    return parse(code, true);
  }

  get Function() {
    const context = createExecContext(
      this,
      {
        tree: [],
        constants: {
          strings: [],
          eager: true,
          literals: [],
          maxDepth: this.context.options.maxParserRecursionDepth,
          regexes: [],
        },
      },
      this.evalContext,
    );
    return context.evals.get(Function)!;
  }

  get AsyncFunction() {
    const context = createExecContext(
      this,
      {
        tree: [],
        constants: {
          strings: [],
          eager: true,
          literals: [],
          maxDepth: this.context.options.maxParserRecursionDepth,
          regexes: [],
        },
      },
      this.evalContext,
    );
    return context.evals.get(AsyncFunction)!;
  }

  get eval() {
    const context = createExecContext(
      this,
      {
        tree: [],
        constants: {
          strings: [],
          eager: true,
          literals: [],
          maxDepth: this.context.options.maxParserRecursionDepth,
          regexes: [],
        },
      },
      this.evalContext,
    );
    return context.evals.get(eval)!;
  }

  compile<T>(
    code: string,
    optimize = false,
  ): (...scopes: IScope[]) => { context: IExecContext; run: () => T } {
    if (this.context.options.nonBlocking)
      throw new SandboxCapabilityError(
        'Non-blocking mode is enabled, use Sandbox.compileAsync() instead.',
      );
    const parsed = parse(code, optimize, false, this.context.options.maxParserRecursionDepth);
    const context = createExecContext(this, parsed, this.evalContext);
    const exec = (...scopes: IScope[]) => {
      sanitizeScopes(scopes, context);
      return { context, run: () => this.executeTree<T>(context, [...scopes]).result };
    };
    return exec;
  }

  compileAsync<T>(
    code: string,
    optimize = false,
  ): (...scopes: IScope[]) => { context: IExecContext; run: () => Promise<T> } {
    const parsed = parse(code, optimize, false, this.context.options.maxParserRecursionDepth);
    const context = createExecContext(this, parsed, this.evalContext);
    const exec = (...scopes: IScope[]) => {
      sanitizeScopes(scopes, context);
      return {
        context,
        run: () => this.executeTreeAsync<T>(context, [...scopes]).then((ret) => ret.result),
      };
    };
    return exec;
  }

  compileExpression<T>(
    code: string,
    optimize = false,
  ): (...scopes: IScope[]) => { context: IExecContext; run: () => T } {
    const parsed = parse(code, optimize, true, this.context.options.maxParserRecursionDepth);
    const context = createExecContext(this, parsed, this.evalContext);
    const exec = (...scopes: IScope[]) => {
      sanitizeScopes(scopes, context);
      return { context, run: () => this.executeTree<T>(context, [...scopes]).result };
    };
    return exec;
  }

  compileExpressionAsync<T>(
    code: string,
    optimize = false,
  ): (...scopes: IScope[]) => { context: IExecContext; run: () => Promise<T> } {
    const parsed = parse(code, optimize, true, this.context.options.maxParserRecursionDepth);
    const context = createExecContext(this, parsed, this.evalContext);
    const exec = (...scopes: IScope[]) => {
      return {
        context,
        run: () => this.executeTreeAsync<T>(context, [...scopes]).then((ret) => ret.result),
      };
    };
    return exec;
  }
}

export default Sandbox;


================================================
FILE: src/SandboxExec.ts
================================================
import type { IEvalContext } from './eval';
import { Change, ExecReturn, executeTree, executeTreeAsync } from './executor';
import { createContext, SandboxExecutionQuotaExceededError } from './utils';
import type {
  IContext,
  IExecContext,
  IGlobals,
  IOptionParams,
  IOptions,
  IScope,
  ISymbolWhitelist,
  SubscriptionSubject,
  HaltContext,
} from './utils';

function subscribeSet(
  obj: object,
  name: string,
  callback: (modification: Change) => void,
  context: {
    setSubscriptions: WeakMap<
      SubscriptionSubject,
      Map<string, Set<(modification: Change) => void>>
    >;
    changeSubscriptions: WeakMap<SubscriptionSubject, Set<(modification: Change) => void>>;
  },
): { unsubscribe: () => void } {
  const names =
    context.setSubscriptions.get(obj) || new Map<string, Set<(modification: Change) => void>>();
  context.setSubscriptions.set(obj, names);
  const callbacks = names.get(name) || new Set();
  names.set(name, callbacks);
  callbacks.add(callback);
  let changeCbs: Set<(modification: Change) => void>;
  const val = (obj as any)[name] as unknown;
  if (val instanceof Object) {
    changeCbs = context.changeSubscriptions.get(val) || new Set();
    changeCbs.add(callback);
    context.changeSubscriptions.set(val, changeCbs);
  }
  return {
    unsubscribe: () => {
      callbacks.delete(callback);
      changeCbs?.delete(callback);
    },
  };
}

export class SandboxExec {
  public readonly context: IContext;
  public readonly setSubscriptions: WeakMap<
    SubscriptionSubject,
    Map<string, Set<(modification: Change) => void>>
  > = new WeakMap();
  public readonly changeSubscriptions: WeakMap<
    SubscriptionSubject,
    Set<(modification: Change) => void>
  > = new WeakMap();
  public readonly sandboxFunctions: WeakMap<Function, IExecContext> = new WeakMap();
  private haltSubscriptions: Set<(context: HaltContext) => void> = new Set();
  private resumeSubscriptions: Set<() => void> = new Set();
  public halted = false;
  timeoutHandleCounter = 0;
  public readonly setTimeoutHandles = new Map<
    number,
    {
      handle: number;
      haltsub: { unsubscribe: () => void };
      contsub: { unsubscribe: () => void };
    }
  >();
  public readonly setIntervalHandles = new Map<
    number,
    {
      handle: number;
      haltsub: { unsubscribe: () => void };
      contsub: { unsubscribe: () => void };
    }
  >();
  constructor(
    options?: IOptionParams,
    public evalContext?: IEvalContext,
  ) {
    const opt: IOptions = Object.assign(
      {
        audit: false,
        forbidFunctionCalls: false,
        forbidFunctionCreation: false,
        globals: SandboxExec.SAFE_GLOBALS,
        symbolWhitelist: SandboxExec.SAFE_SYMBOLS,
        prototypeWhitelist: SandboxExec.SAFE_PROTOTYPES,
        maxParserRecursionDepth: 256,
        nonBlocking: false,
        functionReplacements: new Map<
          Function,
          (ctx: IExecContext, builtInReplacement?: Function) => Function
        >(),
      },
      options || {},
    );
    this.context = createContext(this, opt);
  }

  static get SAFE_GLOBALS(): IGlobals {
    return {
      globalThis,
      Function,
      eval,
      console: {
        debug: console.debug,
        error: console.error,
        info: console.info,
        log: console.log,
        table: console.table,
        warn: console.warn,
      },
      isFinite,
      isNaN,
      parseFloat,
      parseInt,
      decodeURI,
      decodeURIComponent,
      encodeURI,
      encodeURIComponent,
      escape,
      unescape,
      Boolean,
      Number,
      BigInt,
      String,
      Object,
      Array,
      Symbol,
      Error,
      EvalError,
      RangeError,
      ReferenceError,
      SyntaxError,
      TypeError,
      URIError,
      Int8Array,
      Uint8Array,
      Uint8ClampedArray,
      Int16Array,
      Uint16Array,
      Int32Array,
      Uint32Array,
      Float32Array,
      Float64Array,
      Map,
      Set,
      WeakMap,
      WeakSet,
      Promise,
      Intl,
      JSON,
      Math,
      Date,
      RegExp,
    };
  }

  static get SAFE_SYMBOLS(): ISymbolWhitelist {
    const safeSymbols: ISymbolWhitelist = {};
    for (const key of [
      'asyncIterator',
      'iterator',
      'match',
      'matchAll',
      'replace',
      'search',
      'split',
    ]) {
      const value = (Symbol as unknown as Record<string, symbol | undefined>)[key];
      if (typeof value === 'symbol') {
        safeSymbols[key] = value;
      }
    }
    return safeSymbols;
  }

  static get SAFE_PROTOTYPES(): Map<any, Set<string>> {
    const protos = [
      Function,
      Boolean,
      Number,
      BigInt,
      String,
      Date,
      Error,
      Array,
      Int8Array,
      Uint8Array,
      Uint8ClampedArray,
      Int16Array,
      Uint16Array,
      Int32Array,
      Uint32Array,
      Float32Array,
      Float64Array,
      Map,
      Set,
      WeakMap,
      WeakSet,
      Promise,
      Symbol,
      Date,
      RegExp,
    ];
    const map = new Map<any, Set<string>>();
    protos.forEach((proto) => {
      map.set(proto, new Set());
    });
    map.set(
      Object,
      new Set([
        'constructor',
        'name',
        'entries',
        'fromEntries',
        'getOwnPropertyNames',
        'is',
        'keys',
        'hasOwnProperty',
        'isPrototypeOf',
        'propertyIsEnumerable',
        'toLocaleString',
        'toString',
        'valueOf',
        'values',
      ]),
    );
    return map;
  }

  subscribeGet(
    callback: (obj: SubscriptionSubject, name: string) => void,
    context: IExecContext,
  ): { unsubscribe: () => void } {
    context.getSubscriptions.add(callback);
    return { unsubscribe: () => context.getSubscriptions.delete(callback) };
  }

  subscribeSet(
    obj: object,
    name: string,
    callback: (modification: Change) => void,
    context: SandboxExec | IExecContext,
  ): { unsubscribe: () => void } {
    return subscribeSet(obj, name, callback, context);
  }

  subscribeSetGlobal(
    obj: SubscriptionSubject,
    name: string,
    callback: (modification: Change) => void,
  ): { unsubscribe: () => void } {
    return subscribeSet(obj, name, callback, this);
  }

  subscribeHalt(cb: (context: HaltContext) => void) {
    this.haltSubscriptions.add(cb);
    return {
      unsubscribe: () => {
        this.haltSubscriptions.delete(cb);
      },
    };
  }
  subscribeResume(cb: () => void) {
    this.resumeSubscriptions.add(cb);
    return {
      unsubscribe: () => {
        this.resumeSubscriptions.delete(cb);
      },
    };
  }

  haltExecution(haltContext: HaltContext = { type: 'manual' }) {
    if (this.halted) return;
    this.halted = true;
    for (const cb of this.haltSubscriptions) {
      cb(haltContext);
    }
  }

  resumeExecution() {
    if (!this.halted) return;
    if (
      this.context.ticks.tickLimit !== undefined &&
      this.context.ticks.ticks >= this.context.ticks.tickLimit
    ) {
      throw new SandboxExecutionQuotaExceededError('Cannot resume execution: tick limit exceeded');
    }
    this.halted = false;
    for (const cb of this.resumeSubscriptions) {
      cb();
    }
  }

  getContext(fn: (...args: any[]) => any) {
    return this.sandboxFunctions.get(fn);
  }

  executeTree<T>(context: IExecContext, scopes: IScope[] = []): ExecReturn<T> {
    return executeTree(context.ctx.ticks, context, context.tree, scopes, undefined, false);
  }

  executeTreeAsync<T>(context: IExecContext, scopes: IScope[] = []): Promise<ExecReturn<T>> {
    return executeTreeAsync(context.ctx.ticks, context, context.tree, scopes, undefined, false);
  }
}

export default SandboxExec;


================================================
FILE: src/eval/index.ts
================================================
import {
  createAsyncGeneratorFunction,
  createFunction,
  createFunctionAsync,
  createGeneratorFunction,
} from '../executor';
import parse, { lispifyFunction } from '../parser';
import type { Lisp } from '../parser';
import { getSandboxSymbolCtor, LispType } from '../utils';
import type { IExecContext } from '../utils';

export interface IEvalContext {
  sandboxFunction: typeof sandboxFunction;
  sandboxAsyncFunction: typeof sandboxAsyncFunction;
  sandboxGeneratorFunction: typeof sandboxGeneratorFunction;
  sandboxAsyncGeneratorFunction: typeof sandboxAsyncGeneratorFunction;
  sandboxedSymbol: typeof sandboxedSymbol;
  sandboxedEval: (func: SandboxFunction, context: IExecContext) => SandboxEval;
  sandboxedSetTimeout: typeof sandboxedSetTimeout;
  sandboxedSetInterval: typeof sandboxedSetInterval;
  sandboxedClearTimeout: typeof sandboxedClearTimeout;
  sandboxedClearInterval: typeof sandboxedClearInterval;
  lispifyFunction: typeof lispifyFunction;
}
export type SandboxFunction = (code: string, ...args: string[]) => () => unknown;
export type SandboxEval = (code: string) => unknown;
export type SandboxSetTimeout = (
  handler: TimerHandler,
  timeout?: number,
  ...args: unknown[]
) => any;
export type SandboxSetInterval = (
  handler: TimerHandler,
  timeout?: number,
  ...args: unknown[]
) => any;
export type SandboxClearTimeout = (handle: number) => void;
export type SandboxClearInterval = (handle: number) => void;

export function createEvalContext(): IEvalContext {
  return {
    sandboxFunction,
    sandboxAsyncFunction,
    sandboxGeneratorFunction,
    sandboxAsyncGeneratorFunction,
    sandboxedSymbol,
    sandboxedEval,
    sandboxedSetTimeout,
    sandboxedSetInterval,
    sandboxedClearTimeout,
    sandboxedClearInterval,
    lispifyFunction,
  };
}

export function sandboxedSymbol(context: IExecContext) {
  return getSandboxSymbolCtor(context.ctx.sandboxSymbols);
}

function SB() {}
export function sandboxFunction(context: IExecContext): SandboxFunction {
  SandboxFunction.prototype = SB.prototype;
  return SandboxFunction;
  function SandboxFunction(...params: string[]) {
    const code = params.pop() || '';
    const parsed = parse(code, false, false, context.ctx.options.maxParserRecursionDepth);
    return createFunction(
      params,
      parsed.tree,
      context.ctx.ticks,
      {
        ...context,
        constants: parsed.constants,
        tree: parsed.tree,
      },
      undefined,
      'anonymous',
    );
  }
}

export type SandboxAsyncFunction = (code: string, ...args: string[]) => () => Promise<unknown>;
function SAF() {}
export function sandboxAsyncFunction(context: IExecContext): SandboxAsyncFunction {
  SandboxAsyncFunction.prototype = SAF.prototype;
  return SandboxAsyncFunction;
  function SandboxAsyncFunction(...params: string[]) {
    const code = params.pop() || '';
    const parsed = parse(code, false, false, context.ctx.options.maxParserRecursionDepth);
    return createFunctionAsync(
      params,
      parsed.tree,
      context.ctx.ticks,
      {
        ...context,
        constants: parsed.constants,
        tree: parsed.tree,
      },
      undefined,
      'anonymous',
    );
  }
}

export type SandboxGeneratorFunction = (
  code: string,
  ...args: string[]
) => () => Iterator<unknown> & Iterable<unknown>;
function SGF() {}
export function sandboxGeneratorFunction(context: IExecContext): SandboxGeneratorFunction {
  SandboxGeneratorFunction.prototype = SGF.prototype;
  return SandboxGeneratorFunction;
  function SandboxGeneratorFunction(...params: string[]) {
    const code = params.pop() || '';
    const parsed = parse(code, false, false, context.ctx.options.maxParserRecursionDepth);
    return createGeneratorFunction(
      params,
      parsed.tree,
      context.ctx.ticks,
      {
        ...context,
        constants: parsed.constants,
        tree: parsed.tree,
      },
      undefined,
      'anonymous',
    );
  }
}

export type SandboxAsyncGeneratorFunction = (
  code: string,
  ...args: string[]
) => () => AsyncGenerator<unknown, unknown, unknown>;
function SAGF() {}
export function sandboxAsyncGeneratorFunction(
  context: IExecContext,
): SandboxAsyncGeneratorFunction {
  SandboxAsyncGeneratorFunction.prototype = SAGF.prototype;
  return SandboxAsyncGeneratorFunction;
  function SandboxAsyncGeneratorFunction(...params: string[]) {
    const code = params.pop() || '';
    const parsed = parse(code, false, false, context.ctx.options.maxParserRecursionDepth);
    return createAsyncGeneratorFunction(
      params,
      parsed.tree,
      context.ctx.ticks,
      {
        ...context,
        constants: parsed.constants,
        tree: parsed.tree,
      },
      undefined,
      'anonymous',
    );
  }
}

function SE() {}
export function sandboxedEval(func: SandboxFunction, context: IExecContext): SandboxEval {
  sandboxEval.prototype = SE.prototype;
  return sandboxEval;
  function sandboxEval(code: string) {
    // Parse the code and wrap last statement in return for completion value
    const parsed = parse(code, false, false, context.ctx.options.maxParserRecursionDepth);
    const tree = wrapLastStatementInReturn(parsed.tree);
    // Create and execute function with modified tree
    return createFunction(
      [],
      tree,
      context.ctx.ticks,
      {
        ...context,
        constants: parsed.constants,
        tree,
      },
      undefined,
      'anonymous',
    )();
  }
}

function wrapLastStatementInReturn(tree: Lisp[]): Lisp[] {
  if (tree.length === 0) return tree;
  const newTree = [...tree];
  const lastIndex = newTree.length - 1;
  const lastStmt = newTree[lastIndex];

  // Only wrap if it's not already a return or throw
  if (Array.isArray(lastStmt) && lastStmt.length >= 1) {
    const op = lastStmt[0];

    // Don't wrap Return (8) or Throw (47) - they already control flow
    if (op === LispType.Return || op === LispType.Throw) {
      return newTree;
    }

    // List of statement types that should have undefined completion value
    // These match JavaScript semantics where declarations and control structures
    // don't produce a completion value
    const statementTypes = [
      LispType.Let, // 3
      LispType.Const, // 4
      LispType.Var, // 35
      LispType.Function, // 38
      LispType.If, // 14
      LispType.Loop, // 39
      LispType.Try, // 40
      LispType.Switch, // 41
      LispType.InternalBlock, // 43
      LispType.Expression, // 44
    ];

    // If the last statement is a declaration or control structure,
    // don't wrap it (it will naturally return undefined)
    if (statementTypes.includes(op)) {
      return newTree;
    }

    // For all other types (expressions, operators, etc.),
    // wrap in return to capture the completion value
    newTree[lastIndex] = [LispType.Return, LispType.None, lastStmt];
  }

  return newTree;
}

function sST() {}
export function sandboxedSetTimeout(
  func: SandboxFunction,
  context: IExecContext,
): SandboxSetTimeout {
  sandboxSetTimeout.prototype = sST.prototype;
  return sandboxSetTimeout;
  function sandboxSetTimeout(handler: TimerHandler, timeout?: number, ...args: unknown[]) {
    const sandbox = context.ctx.sandbox;
    const exec = (...a: any[]) => {
      const h = typeof handler === 'string' ? func(handler) : handler;
      haltsub.unsubscribe();
      contsub.unsubscribe();
      sandbox.setTimeoutHandles.delete(sandBoxhandle);
      return h(...a);
    };

    const sandBoxhandle = ++sandbox.timeoutHandleCounter;

    let start = Date.now();
    let handle: number = setTimeout(exec, timeout, ...args);

    let elapsed = 0;
    const haltsub = sandbox.subscribeHalt(() => {
      elapsed = Date.now() - start + elapsed;
      clearTimeout(handle);
    });
    const contsub = sandbox.subscribeResume(() => {
      start = Date.now();
      const remaining = Math.floor((timeout || 0) - elapsed);
      handle = setTimeout(exec, remaining, ...args);
      sandbox.setTimeoutHandles.set(sandBoxhandle, {
        handle,
        haltsub,
        contsub,
      });
    });
    sandbox.setTimeoutHandles.set(sandBoxhandle, {
      handle,
      haltsub,
      contsub,
    });
    return sandBoxhandle;
  }
}

function sCT() {}
export function sandboxedClearTimeout(context: IExecContext): SandboxClearTimeout {
  sandboxClearTimeout.prototype = sCT.prototype;
  return sandboxClearTimeout;
  function sandboxClearTimeout(handle: number) {
    const sandbox = context.ctx.sandbox;
    const timeoutHandle = sandbox.setTimeoutHandles.get(handle);
    if (timeoutHandle) {
      clearTimeout(timeoutHandle.handle);
      timeoutHandle.haltsub.unsubscribe();
      timeoutHandle.contsub.unsubscribe();
      sandbox.setTimeoutHandles.delete(handle);
    }
  }
}
function sCI() {}
export function sandboxedClearInterval(context: IExecContext): SandboxClearInterval {
  sandboxClearInterval.prototype = sCI.prototype;
  return sandboxClearInterval;
  function sandboxClearInterval(handle: number) {
    const sandbox = context.ctx.sandbox;
    const intervalHandle = sandbox.setIntervalHandles.get(handle);
    if (intervalHandle) {
      clearInterval(intervalHandle.handle);
      clearTimeout(intervalHandle.handle);
      intervalHandle.haltsub.unsubscribe();
      intervalHandle.contsub.unsubscribe();
      sandbox.setIntervalHandles.delete(handle);
    }
  }
}

function sSI() {}
export function sandboxedSetInterval(
  func: SandboxFunction,
  context: IExecContext,
): SandboxSetInterval {
  sandboxSetInterval.prototype = sSI.prototype;
  return sandboxSetInterval;
  function sandboxSetInterval(
    handler: TimerHandler,
    timeout: number | undefined,
    ...args: unknown[]
  ) {
    const sandbox = context.ctx.sandbox;
    const h = typeof handler === 'string' ? func(handler) : handler;
    const exec = (...a: any[]) => {
      start = Date.now();
      elapsed = 0;
      return h(...a);
    };

    const sandBoxhandle = ++sandbox.timeoutHandleCounter;

    let start = Date.now();
    let handle: number = setInterval(exec, timeout, ...args);

    let elapsed = 0;
    const haltsub = sandbox.subscribeHalt(() => {
      elapsed = Date.now() - start + elapsed;
      clearInterval(handle);
      clearTimeout(handle);
    });
    const contsub = sandbox.subscribeResume(() => {
      start = Date.now();
      handle = setTimeout(
        () => {
          start = Date.now();
          elapsed = 0;
          handle = setInterval(exec, timeout, ...args);
          handlObj.handle = handle;
          exec(...args);
        },
        Math.floor((timeout || 0) - elapsed),
        ...args,
      );
      handlObj.handle = handle;
    });

    const handlObj = {
      handle,
      haltsub,
      contsub,
    };
    sandbox.setIntervalHandles.set(sandBoxhandle, handlObj);
    return sandBoxhandle;
  }
}


================================================
FILE: src/executor/executorUtils.ts
================================================
import type { LispItem, Lisp, StatementLabel } from '../parser';
import {
  hasOwnProperty,
  isLisp,
  LispType,
  LocalScope,
  Prop,
  SandboxExecutionQuotaExceededError,
  SandboxError,
  SandboxExecutionTreeError,
  Scope,
  GeneratorFunction,
  AsyncGeneratorFunction,
  SandboxCapabilityError,
  SandboxAccessError,
  NON_BLOCKING_THRESHOLD,
  sanitizeProp,
  Unknown,
} from '../utils';
import { IAuditReport, IExecContext, IScope, optional, Ticks } from '../utils';

export type Done<T = any> = (err?: any, res?: T | typeof optional) => void;

export type ControlFlowAction = 'break' | 'continue';

export interface ControlFlowSignal {
  type: ControlFlowAction;
  label?: string;
}

interface ControlFlowTarget {
  label?: string;
  acceptsBreak: boolean;
  acceptsContinue: boolean;
  acceptsUnlabeledBreak: boolean;
  acceptsUnlabeledContinue: boolean;
}

export type ControlFlowTargets = readonly ControlFlowTarget[] | undefined;

export class ExecReturn<T> {
  constructor(
    public auditReport: IAuditReport | undefined,
    public result: T,
    public returned: boolean,
    public controlFlow?: ControlFlowSignal,
  ) {}

  get breakLoop() {
    return this.controlFlow?.type === 'break';
  }

  get continueLoop() {
    return this.controlFlow?.type === 'continue';
  }
}

export interface IChange {
  type: string;
}

export interface ICreate extends IChange {
  type: 'create';
  prop: number | string;
}

export interface IReplace extends IChange {
  type: 'replace';
}

export interface IDelete extends IChange {
  type: 'delete';
  prop: number | string;
}

export interface IReverse extends IChange {
  type: 'reverse';
}

export interface ISort extends IChange {
  type: 'sort';
}

export interface IPush extends IChange {
  type: 'push';
  added: unknown[];
}

export interface IPop extends IChange {
  type: 'pop';
  removed: unknown[];
}

export interface IShift extends IChange {
  type: 'shift';
  removed: unknown[];
}

export interface IUnShift extends IChange {
  type: 'unshift';
  added: unknown[];
}

export interface ISplice extends IChange {
  type: 'splice';
  startIndex: number;
  deleteCount: number;
  added: unknown[];
  removed: unknown[];
}

export interface ICopyWithin extends IChange {
  type: 'copyWithin';
  startIndex: number;
  endIndex: number;
  added: unknown[];
  removed: unknown[];
}

export type Change =
  | ICreate
  | IReplace
  | IDelete
  | IReverse
  | ISort
  | IPush
  | IPop
  | IUnShift
  | IShift
  | ISplice
  | ICopyWithin;

const emptyControlFlowTargets: readonly ControlFlowTarget[] = [];

export function normalizeStatementLabel(label: StatementLabel | undefined) {
  return label === undefined || label === LispType.None ? undefined : label;
}

export function normalizeStatementLabels(label: LispItem | StatementLabel | undefined) {
  if (label === undefined || label === LispType.None) return [] as string[];
  if (Array.isArray(label) && !isLisp(label)) {
    return label.filter((item): item is string => typeof item === 'string');
  }
  return [label as string];
}

export function createLoopTarget(label?: string, acceptsUnlabeled = true): ControlFlowTarget {
  return {
    label,
    acceptsBreak: true,
    acceptsContinue: true,
    acceptsUnlabeledBreak: acceptsUnlabeled,
    acceptsUnlabeledContinue: acceptsUnlabeled,
  };
}

export function createSwitchTarget(label?: string): ControlFlowTarget {
  return {
    label,
    acceptsBreak: true,
    acceptsContinue: false,
    acceptsUnlabeledBreak: true,
    acceptsUnlabeledContinue: false,
  };
}

export function createLabeledStatementTarget(label?: string): ControlFlowTarget | undefined {
  if (!label) return undefined;
  return {
    label,
    acceptsBreak: true,
    acceptsContinue: false,
    acceptsUnlabeledBreak: false,
    acceptsUnlabeledContinue: false,
  };
}

export function addControlFlowTarget(
  controlFlowTargets: ControlFlowTargets,
  target?: ControlFlowTarget,
): ControlFlowTargets {
  if (!target) return controlFlowTargets;
  return [...(controlFlowTargets || emptyControlFlowTargets), target];
}

export function addControlFlowTargets(
  controlFlowTargets: ControlFlowTargets,
  targets: ControlFlowTarget[],
): ControlFlowTargets {
  return targets.reduce(
    (currentTargets, target) => addControlFlowTarget(currentTargets, target),
    controlFlowTargets,
  );
}

export function matchesControlFlowTarget(signal: ControlFlowSignal, target: ControlFlowTarget) {
  if (signal.type === 'continue') {
    if (!target.acceptsContinue) return false;
    return signal.label ? target.label === signal.label : target.acceptsUnlabeledContinue;
  }
  if (!target.acceptsBreak) return false;
  return signal.label ? target.label === signal.label : target.acceptsUnlabeledBreak;
}

export function findControlFlowTarget(
  controlFlowTargets: ControlFlowTargets,
  type: ControlFlowAction,
  label?: string,
) {
  if (!controlFlowTargets) return undefined;
  for (let i = controlFlowTargets.length - 1; i >= 0; i--) {
    const target = controlFlowTargets[i];
    if (label) {
      if (target.label !== label) continue;
      if (type === 'continue' ? target.acceptsContinue : target.acceptsBreak) {
        return target;
      }
      return null;
    }
    if (type === 'continue' ? target.acceptsUnlabeledContinue : target.acceptsUnlabeledBreak) {
      return target;
    }
  }
  return undefined;
}

function generateArgs(argNames: string[], args: unknown[]) {
  const vars: Record<string, unknown> = {};
  argNames.forEach((arg, i) => {
    if (arg.startsWith('...')) {
      vars[arg.substring(3)] = args.slice(i);
    } else {
      vars[arg] = args[i];
    }
  });
  return vars;
}

export function createFunction(
  argNames: string[],
  parsed: Lisp[],
  ticks: Ticks,
  context: IExecContext,
  scope?: Scope,
  name?: string,
  internal = false,
) {
  if (context.ctx.options.forbidFunctionCreation) {
    throw new SandboxCapabilityError('Function creation is forbidden');
  }
  let func;
  if (name === undefined) {
    func = (...args: unknown[]) => {
      const vars = generateArgs(argNames, args);
      const res = executeTree(
        ticks,
        context,
        parsed,
        scope === undefined ? [] : [new Scope(scope, vars)],
        undefined,
        internal,
      );
      return res.result;
    };
  } else {
    func = function sandboxedObject(this: Unknown, ...args: unknown[]) {
      const vars = generateArgs(argNames, args);
      const res = executeTree(
        ticks,
        context,
        parsed,
        scope === undefined ? [] : [new Scope(scope, vars, this)],
        undefined,
        internal,
      );
      return res.result;
    };
  }
  context.registerSandboxFunction(func);
  context.ctx.sandboxedFunctions.add(func);
  return func;
}

export function createFunctionAsync(
  argNames: string[],
  parsed: Lisp[],
  ticks: Ticks,
  context: IExecContext,
  scope?: Scope,
  name?: string,
  internal = false,
) {
  if (context.ctx.options.forbidFunctionCreation) {
    throw new SandboxCapabilityError('Function creation is forbidden');
  }
  if (!context.ctx.prototypeWhitelist?.has(Promise.prototype)) {
    throw new SandboxCapabilityError('Async/await not permitted');
  }
  let func;
  if (name === undefined) {
    func = async (...args: unknown[]) => {
      const vars = generateArgs(argNames, args);
      const res = await executeTreeAsync(
        ticks,
        context,
        parsed,
        scope === undefined ? [] : [new Scope(scope, vars)],
        undefined,
        internal,
      );
      return res.result;
    };
  } else {
    func = async function sandboxedObject(this: Unknown, ...args: unknown[]) {
      const vars = generateArgs(argNames, args);
      const res = await executeTreeAsync(
        ticks,
        context,
        parsed,
        scope === undefined ? [] : [new Scope(scope, vars, this)],
        undefined,
        internal,
      );
      return res.result;
    };
  }
  context.registerSandboxFunction(func);
  context.ctx.sandboxedFunctions.add(func);
  return func;
}

// Sentinel class used to communicate yield values from the executor back to the generator.
export class YieldValue {
  constructor(
    public value: unknown,
    public delegate: boolean,
  ) {}
}

// Unique sentinel thrown by captureYieldFn in executeGenBody's default case when a new
// synchronous yield is encountered. Propagates through the call stack back to the restart loop.
const syncYieldPauseSentinel = Symbol('syncYieldPause');

function asIterableIterator(value: unknown): Iterator<unknown> & Iterable<unknown> {
  const iterator =
    (value as { [Symbol.iterator]?: () => Iterator<unknown> })?.[Symbol.iterator]?.() ??
    (value as Iterator<unknown>);

  if (!iterator || typeof iterator.next !== 'function') {
    throw new TypeError('yield* target is not iterable');
  }

  if (typeof (iterator as Iterator<unknown> & Iterable<unknown>)[Symbol.iterator] === 'function') {
    return iterator as Iterator<unknown> & Iterable<unknown>;
  }

  return {
    next: iterator.next.bind(iterator),
    throw: iterator.throw?.bind(iterator),
    return: iterator.return?.bind(iterator),
    [Symbol.iterator]() {
      return this;
    },
  };
}

function asAsyncIterableIterator(value: unknown): AsyncIterator<unknown> & AsyncIterable<unknown> {
  const asyncIterator = (value as { [Symbol.asyncIterator]?: () => AsyncIterator<unknown> })?.[
    Symbol.asyncIterator
  ]?.();

  if (asyncIterator) {
    return {
      next: asyncIterator.next.bind(asyncIterator),
      throw: asyncIterator.throw?.bind(asyncIterator),
      return: asyncIterator.return?.bind(asyncIterator),
      [Symbol.asyncIterator]() {
        return this;
      },
    };
  }

  const iterator = asIterableIterator(value);
  return {
    async next(nextValue?: unknown) {
      return iterator.next(nextValue);
    },
    async throw(err?: unknown) {
      if (typeof iterator.throw === 'function') {
        return iterator.throw(err);
      }
      throw err;
    },
    async return(valueToReturn?: unknown) {
      if (typeof iterator.return === 'function') {
        return iterator.return(valueToReturn);
      }
      return { value: valueToReturn, done: true };
    },
    [Symbol.asyncIterator]() {
      return this;
    },
  };
}

// executeGenBody: a native generator that lazily executes a generator function body.
// It handles compound control-flow nodes (statements, if, loop, try, yield) with yield*
// recursion, and falls back to the existing execSync for all leaf expressions.
// Only used by createGeneratorFunction — nothing else in the executor is changed.
function* executeGenBody(
  ticks: Ticks,
  tree: Lisp | Lisp[],
  scope: Scope,
  context: IExecContext,
  statementLabels: ControlFlowTargets,
  internal: boolean,
): Generator<unknown, ExecReturn<unknown> | unknown, unknown> {
  // ── Statement list ──────────────────────────────────────────────────────────
  if (!isLisp(tree as Lisp) && Array.isArray(tree)) {
    const stmts = tree as Lisp[];
    if (stmts.length === 0 || (stmts[0] as unknown) === LispType.None) {
      return new ExecReturn(context.ctx.auditReport, undefined, false);
    }
    for (const stmt of stmts) {
      const res = (yield* executeGenBody(
        ticks,
        stmt,
        scope,
        context,
        statementLabels,
        internal,
      )) as ExecReturn<unknown>;
      if (res instanceof ExecReturn && (res.returned || res.controlFlow)) return res;
      // Mirror _executeWithDoneSync: wrap the result of a return statement
      if (isLisp(stmt) && (stmt as Lisp)[0] === LispType.Return) {
        return new ExecReturn(context.ctx.auditReport, res.result, true);
      }
    }
    return new ExecReturn(context.ctx.auditReport, undefined, false);
  }

  const [op, a, b] = tree as Lisp;

  switch (op) {
    // ── yield expr ────────────────────────────────────────────────────────────
    case LispType.Yield: {
      const valResult = (yield* executeGenBody(
        ticks,
        a as Lisp,
        scope,
        context,
        statementLabels,
        internal,
      )) as ExecReturn<unknown>;
      const sanitized = sanitizeProp(valResult.result, context);
      const injected: unknown = yield sanitized; // ← real pause point
      return new ExecReturn(context.ctx.auditReport, injected, false);
    }

    // ── yield* expr ───────────────────────────────────────────────────────────
    case LispType.YieldDelegate: {
      const iterResult = (yield* executeGenBody(
        ticks,
        a as Lisp,
        scope,
        context,
        statementLabels,
        internal,
      )) as ExecReturn<unknown>;
      const delegatee = sanitizeProp(iterResult.result, context);
      const result: unknown = yield* asIterableIterator(delegatee);
      return new ExecReturn(context.ctx.auditReport, result, false);
    }

    // ── if / else ─────────────────────────────────────────────────────────────
    // LispType.If is NOT in unexecTypes — its `a` is the raw condition Lisp,
    // `b` is the raw IfCase node that evaluates to an If object with .t/.f.
    case LispType.If: {
      const condResult = (yield* executeGenBody(
        ticks,
        a as Lisp,
        scope,
        context,
        statementLabels,
        internal,
      )) as ExecReturn<unknown>;
      const ifCase = syncDone((d) =>
        execSync(ticks, b as Lisp, scope, context, d, statementLabels, internal, undefined),
      ).result as If;
      const branch = sanitizeProp(condResult.result, context) ? ifCase.t : ifCase.f;
      if (branch) {
        return (yield* executeGenBody(
          ticks,
          branch,
          scope,
          context,
          statementLabels,
          internal,
        )) as ExecReturn<unknown>;
      }
      return new ExecReturn(context.ctx.auditReport, undefined, false);
    }

    // ── loops (while / for / for-of / for-in / do-while) ─────────────────────
    // Mirror the sync path of the existing Loop handler (executor.ts ~1421-1475)
    // but replace `executeTree(b, ...)` with `yield*`.
    case LispType.Loop: {
      const [
        checkFirst,
        startInternal,
        getIterator,
        startStep,
        step,
        condition,
        beforeStep,
        isForAwait,
        label,
      ] = a as Lisp[];
      if ((isForAwait as unknown as LispType) === LispType.True) {
        throw new SyntaxError('for-await-of loops are only allowed inside async functions');
      }
      const loopStatementTargets = [
        ...normalizeStatementLabels(label).map((loopLabel) => createLoopTarget(loopLabel, false)),
        createLoopTarget(),
      ];
      const loopTargets = addControlFlowTargets(statementLabels, loopStatementTargets);
      const loopScope = new Scope(scope, {});
      const internalVars: Record<string, unknown> = { $$obj: undefined };
      const interalScope = new Scope(loopScope, internalVars);

      syncDone((d) =>
        execSync(ticks, startStep, loopScope, context, d, undefined, internal, undefined),
      );
      internalVars['$$obj'] = syncDone((d) =>
        execSync(ticks, getIterator, loopScope, context, d, undefined, internal, undefined),
      ).result;
      syncDone((d) =>
        execSync(ticks, startInternal, interalScope, context, d, undefined, internal, undefined),
      );

      let loop: unknown = true;
      if (checkFirst) {
        loop = syncDone((d) =>
          execSync(ticks, condition, interalScope, context, d, undefined, internal, undefined),
        ).result;
      }

      while (loop) {
        const iterScope = new Scope(interalScope, {});
        syncDone((d) =>
          execSync(ticks, beforeStep, iterScope, context, d, undefined, internal, undefined),
        );

        const res = (yield* executeGenBody(
          ticks,
          b as Lisp[],
          iterScope,
          context,
          loopTargets,
          internal,
        )) as ExecReturn<unknown>;

        if (res.returned) return res;
        if (res.controlFlow) {
          if (!loopStatementTargets.some((t) => matchesControlFlowTarget(res.controlFlow!, t))) {
            return res; // break/continue targeting an outer labeled loop
          }
          if (res.breakLoop) break;
          // continueLoop: fall through to step + condition check
        }
        syncDone((d) =>
          execSync(ticks, step, interalScope, context, d, undefined, internal, undefined),
        );
        loop = syncDone((d) =>
          execSync(ticks, condition, interalScope, context, d, undefined, internal, undefined),
        ).result;
      }
      return new ExecReturn(context.ctx.auditReport, undefined, false);
    }

    // ── try / catch / finally ─────────────────────────────────────────────────
    // Using real native try/catch/finally gives us correct gen.throw() and
    // gen.return() semantics for free (the native generator machinery handles them).
    case LispType.Try: {
      const [exception, catchBody, finallyBody] = b as [string, Lisp[], Lisp[]];
      let result!: ExecReturn<unknown>;
      let finalOverride: ExecReturn<unknown> | undefined;
      try {
        result = (yield* executeGenBody(
          ticks,
          a as Lisp[],
          scope,
          context,
          statementLabels,
          internal,
        )) as ExecReturn<unknown>;
      } catch (e) {
        if (exception && catchBody?.length > 0) {
          const catchScope = new Scope(scope, { [exception]: e });
          result = (yield* executeGenBody(
            ticks,
            catchBody,
            catchScope,
            context,
            statementLabels,
            internal,
          )) as ExecReturn<unknown>;
        } else {
          throw e;
        }
      } finally {
        if (finallyBody?.length > 0) {
          const fr = (yield* executeGenBody(
            ticks,
            finallyBody,
            scope,
            context,
            statementLabels,
            internal,
          )) as ExecReturn<unknown>;
          // finally control flow (return/break/continue) overrides everything
          if (fr.returned || fr.controlFlow) {
            finalOverride = fr;
          }
        }
      }
      if (finalOverride) return finalOverride;
      return result;
    }

    // ── labeled statement ─────────────────────────────────────────────────────
    case LispType.Labeled: {
      const target = createLabeledStatementTarget(normalizeStatementLabel(a as StatementLabel));
      const newTargets = addControlFlowTargets(statementLabels, target ? [target] : []);
      const res = (yield* executeGenBody(
        ticks,
        b as Lisp,
        scope,
        context,
        newTargets,
        internal,
      )) as ExecReturn<unknown>;
      if (res.controlFlow && target && matchesControlFlowTarget(res.controlFlow, target)) {
        return new ExecReturn(context.ctx.auditReport, res.result, false);
      }
      return res;
    }

    // ── everything else ───────────────────────────────────────────────────────
    // Arithmetic, property access, function calls, assignments, etc. are
    // delegated to execSync. However, a yield expression may be nested inside
    // a non-unexecType node (e.g. `const x = yield 1`). In that case the
    // existing sync yield handler throws syncYieldPauseSentinel. We restart
    // execSync from scratch on each such pause, skipping already-completed
    // yields by immediately calling their continuation with the stored result.
    default: {
      let completedYields = 0;
      const yieldResults: unknown[] = [];
      while (true) {
        let currentYieldIdx = 0;
        let capturedValue: unknown = undefined;
        let capturedDelegate = false;
        let yielded = false;
        const captureYieldFn = (yv: YieldValue, continueDone?: Done) => {
          if (currentYieldIdx < completedYields) {
            // This yield already happened in a prior iteration; fast-forward.
            continueDone!(undefined, yieldResults[currentYieldIdx]);
            currentYieldIdx++;
            return;
          }
          // New yield: capture the value and pause.
          capturedValue = yv.value;
          capturedDelegate = yv.delegate;
          yielded = true;
          currentYieldIdx++;
          throw syncYieldPauseSentinel;
        };
        try {
          const result = syncDone((d) =>
            execSync(
              ticks,
              tree as Lisp,
              scope,
              context,
              d,
              statementLabels,
              internal,
              captureYieldFn,
            ),
          ).result;
          if (result instanceof ExecReturn) return result;
          return new ExecReturn(context.ctx.auditReport, result, false);
        } catch (e) {
          if (!yielded || e !== syncYieldPauseSentinel) throw e;
          const resumedValue: unknown = capturedDelegate
            ? yield* asIterableIterator(capturedValue)
            : yield capturedValue;
          yieldResults.push(resumedValue);
          completedYields++;
        }
      }
    }
  }
}

export function createGeneratorFunction(
  argNames: string[],
  parsed: Lisp[],
  ticks: Ticks,
  context: IExecContext,
  scope?: Scope,
  name?: string,
  internal = false,
) {
  if (context.ctx.options.forbidFunctionCreation) {
    throw new SandboxCapabilityError('Function creation is forbidden');
  }
  const makeGen = (thisArg: Unknown, args: unknown[]) => {
    const vars = generateArgs(argNames, args);
    const genScope =
      scope === undefined ? new Scope(null, vars, thisArg) : new Scope(scope, vars, thisArg);

    const executionGen = executeGenBody(ticks, parsed, genScope, context, undefined, internal);
    let isDone = false;

    function drive(action: () => IteratorResult<unknown>): IteratorResult<unknown> {
      if (isDone) return { value: undefined, done: true };
      try {
        const r = action();
        if (r.done) {
          isDone = true;
          return {
            value: r.value instanceof ExecReturn ? r.value.result : r.value,
            done: true,
          };
        }
        return { value: r.value, done: false };
      } catch (e) {
        isDone = true;
        throw e;
      }
    }

    const iterator: Iterator<unknown> & Iterable<unknown> = {
      next(value?: unknown) {
        return drive(() => executionGen.next(value));
      },
      return(value?: unknown) {
        return drive(() => executionGen.return(value));
      },
      throw(err?: unknown) {
        return drive(() => executionGen.throw(err));
      },
      [Symbol.iterator]() {
        return this;
      },
    };
    return iterator;
  };
  const func = function sandboxedObject(this: Unknown, ...args: unknown[]) {
    return makeGen(this, args);
  };
  Object.setPrototypeOf(func, GeneratorFunction.prototype);
  context.registerSandboxFunction(func);
  context.ctx.sandboxedFunctions.add(func);
  return func;
}

export function createAsyncGeneratorFunction(
  argNames: string[],
  parsed: Lisp[],
  ticks: Ticks,
  context: IExecContext,
  scope?: Scope,
  name?: string,
  internal = false,
) {
  if (context.ctx.options.forbidFunctionCreation) {
    throw new SandboxCapabilityError('Function creation is forbidden');
  }
  if (!context.ctx.prototypeWhitelist?.has(Promise.prototype)) {
    throw new SandboxCapabilityError('Async/await not permitted');
  }
  const makeGen = (thisArg: Unknown, args: unknown[]) => {
    const vars = generateArgs(argNames, args);
    const genScope =
      scope === undefined ? [new Scope(null, vars, thisArg)] : [new Scope(scope, vars, thisArg)];
    return (async function* sandboxedAsyncGenerator(): AsyncGenerator<unknown, unknown, unknown> {
      const yieldQueue: Array<{ yieldValue: YieldValue; continueDone?: Done }> = [];
      let resolveYield: (() => void) | null = null;
      const yieldFn = (yv: YieldValue, continueDone?: Done) => {
        yieldQueue.push({ yieldValue: yv, continueDone });
        if (resolveYield) {
          resolveYield();
          resolveYield = null;
        }
      };
      const bodyPromise = executeTreeAsync(
        ticks,
        context,
        parsed,
        genScope,
        undefined,
        internal,
        yieldFn,
      );
      let bodyDone = false;
      let bodyResult: ExecReturn<unknown> | undefined;
      let bodyError: unknown;
      bodyPromise.then(
        (r) => {
          bodyDone = true;
          bodyResult = r;
          resolveYield?.();
        },
        (e) => {
          bodyDone = true;
          bodyError = e;
          resolveYield?.();
        },
      );
      while (true) {
        if (yieldQueue.length === 0 && !bodyDone) {
          await new Promise<void>((res) => {
            resolveYield = res;
          });
        }
        while (yieldQueue.length > 0) {
          const { yieldValue, continueDone } = yieldQueue.shift()!;
          try {
            const resumedValue = yieldValue.delegate
              ? yield* asAsyncIterableIterator(yieldValue.value)
              : yield yieldValue.value;
            continueDone?.(undefined, resumedValue);
          } catch (err) {
            continueDone?.(err);
          }
        }
        if (bodyDone) break;
      }
      if (bodyError !== undefined) throw bodyError;
      return bodyResult?.result;
    })();
  };
  const func = function sandboxedObject(this: Unknown, ...args: unknown[]) {
    return makeGen(this, args) as unknown as AsyncGenerator;
  };
  Object.setPrototypeOf(func, AsyncGeneratorFunction.prototype);
  context.registerSandboxFunction(func);
  context.ctx.sandboxedFunctions.add(func);
  return func;
}

export function assignCheck(obj: Prop, context: IExecContext, op = 'assign') {
  if (obj.context === undefined) {
    throw new ReferenceError(`Cannot ${op} value to undefined.`);
  }
  if (obj.isConst) {
    throw new TypeError(`Assignment to constant variable.`);
  }
  if (obj.isGlobal) {
    throw new SandboxAccessError(
      `Cannot ${op} property '${obj.prop.toString()}' of a global object`,
    );
  }
  if (obj.context === null) {
    throw new TypeError('Cannot set properties of null');
  }
  if (
    typeof (obj.context as any)[obj.prop] === 'function' &&
    !hasOwnProperty(obj.context, obj.prop)
  ) {
    throw new SandboxAccessError(
      `Override prototype property '${obj.prop.toString()}' not allowed`,
    );
  }
  if (op === 'delete') {
    if (hasOwnProperty(obj.context, obj.prop)) {
      context.changeSubscriptions
        .get(obj.context)
        ?.forEach((cb) => cb({ type: 'delete', prop: obj.prop.toString() }));
      context.changeSubscriptionsGlobal
        .get(obj.context)
        ?.forEach((cb) => cb({ type: 'delete', prop: obj.prop.toString() }));
    }
  } else if (hasOwnProperty(obj.context, obj.prop)) {
    context.setSubscriptions
      .get(obj.context)
      ?.get(obj.prop.toString())
      ?.forEach((cb) =>
        cb({
          type: 'replace',
        }),
      );
    context.setSubscriptionsGlobal
      .get(obj.context)
      ?.get(obj.prop.toString())
      ?.forEach((cb) =>
        cb({
          type: 'replace',
        }),
      );
  } else {
    context.changeSubscriptions
      .get(obj.context)
      ?.forEach((cb) => cb({ type: 'create', prop: obj.prop.toString() }));
    context.changeSubscriptionsGlobal
      .get(obj.context)
      ?.forEach((cb) => cb({ type: 'create', prop: obj.prop.toString() }));
  }
}
export const arrayChange = new Set([
  [].push,
  [].pop,
  [].shift,
  [].unshift,
  [].splice,
  [].reverse,
  [].sort,
  [].copyWithin,
]);

export class KeyVal {
  constructor(
    public key: PropertyKey | SpreadObject,
    public val: unknown,
  ) {}
}

export class SpreadObject {
  constructor(public item: { [key: string]: unknown }) {}
}

export class SpreadArray {
  constructor(public item: unknown[]) {}
}

export class ArrayHole {}

export class If {
  constructor(
    public t: Lisp,
    public f: Lisp,
    public label?: string,
  ) {}
}

export const literalRegex = /(\$\$)*(\$)?\${(\d+)}/g;

export { ops, addOps } from './opsRegistry';
import { ops, Execution } from './opsRegistry';

export const prorptyKeyTypes = ['string', 'number', 'symbol'];

export function isPropertyKey(val: unknown): val is PropertyKey {
  return prorptyKeyTypes.includes(typeof val);
}

export function hasPossibleProperties(val: unknown): val is {} {
  return val !== null && val !== undefined;
}

import './ops/index';

export function execMany(
  ticks: Ticks,
  exec: Execution,
  tree: Lisp[],
  done: Done,
  scope: Scope,
  context: IExecContext,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  if (exec === execSync) {
    _execManySync(ticks, tree, done, scope, context, statementLabels, internal, generatorYield);
  } else {
    _execManyAsync(
      ticks,
      tree,
      done,
      scope,
      context,
      statementLabels,
      internal,
      generatorYield,
    ).catch(done);
  }
}

function _execManySync(
  ticks: Ticks,
  tree: Lisp[],
  done: Done,
  scope: Scope,
  context: IExecContext,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  const ret: any[] = [];
  for (let i = 0; i < tree.length; i++) {
    let res = syncDone((d) =>
      execSync(ticks, tree[i], scope, context, d, statementLabels, internal, generatorYield),
    ).result;
    if (res instanceof ExecReturn && (res.returned || res.breakLoop || res.continueLoop)) {
      done(undefined, res);
      return;
    }
    if (isLisp(tree[i]) && tree[i][0] === LispType.Return) {
      done(undefined, new ExecReturn(context.ctx.auditReport, res, true));
      return;
    }
    ret.push(res);
  }
  done(undefined, ret);
}

async function _execManyAsync(
  ticks: Ticks,
  tree: Lisp[],
  done: Done,
  scope: Scope,
  context: IExecContext,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  const ret: any[] = [];
  for (let i = 0; i < tree.length; i++) {
    let res;
    try {
      let ad: AsyncDoneRet;
      res =
        (ad = asyncDone((d) =>
          execAsync(ticks, tree[i], scope, context, d, statementLabels, internal, generatorYield),
        )).isInstant === true
          ? ad.instant
          : (await ad.p).result;
    } catch (e) {
      done(e);
      return;
    }
    if (res instanceof ExecReturn && (res.returned || res.breakLoop || res.continueLoop)) {
      done(undefined, res);
      return;
    }
    if (isLisp(tree[i]) && tree[i][0] === LispType.Return) {
      done(undefined, new ExecReturn(context.ctx.auditReport, res, true));
      return;
    }
    ret.push(res);
  }
  done(undefined, ret);
}

export interface AsyncDoneRet {
  isInstant: boolean;
  instant: any;
  p: Promise<{ result: any }>;
}

export function asyncDone(callback: (done: Done) => void): AsyncDoneRet {
  let isInstant = false;
  let instant: unknown;
  const p = new Promise<any>((resolve, reject) => {
    callback((...args: unknown[]) => {
      if (args.length === 1) reject(args[0]);
      else {
        isInstant = true;
        instant = args[1];
        resolve({ result: args[1] });
      }
    });
  });
  return {
    isInstant,
    instant,
    p,
  };
}

export function syncDone(callback: (done: Done) => void): { result: any } {
  let result;
  let err: { error: unknown } | undefined;
  callback((...args: unknown[]) => {
    err = args.length === 1 ? { error: args[0] } : undefined;
    result = args[1];
  });
  if (err) throw err.error;
  return { result };
}

export async function execAsync<T = any>(
  ticks: Ticks,
  tree: LispItem,
  scope: Scope,
  context: IExecContext,
  doneOriginal: Done<T>,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
): Promise<void> {
  let done: Done<T> = doneOriginal;
  const p = new Promise<void>((resolve) => {
    done = (...args: unknown[]) => {
      doneOriginal(...args);
      resolve();
    };
  });
  if (
    !_execNoneRecurse(
      ticks,
      tree,
      scope,
      context,
      done,
      true,
      statementLabels,
      internal,
      generatorYield,
    ) &&
    isLisp(tree)
  ) {
    let op = tree[0];
    let obj;
    try {
      let ad: AsyncDoneRet;
      obj =
        (ad = asyncDone((d) =>
          execAsync(ticks, tree[1], scope, context, d, statementLabels, internal, generatorYield),
        )).isInstant === true
          ? ad.instant
          : (await ad.p).result;
    } catch (e) {
      done(e);
      return;
    }
    let a = obj;
    try {
      a = obj instanceof Prop ? obj.get(context) : obj;
    } catch (e) {
      done(e);
      return;
    }
    if (op === LispType.PropOptional || op === LispType.CallOptional) {
      if (a === undefined || a === null) {
        done(undefined, optional);
        return;
      }
      op = op === LispType.PropOptional ? LispType.Prop : LispType.Call;
    }
    if (a === optional) {
      if (op === LispType.Prop || op === LispType.Call) {
        done(undefined, a);
        return;
      } else {
        a = undefined;
      }
    }
    // Short-circuit for nullish coalescing: if a is not null/undefined, return a without evaluating b
    if (op === LispType.NullishCoalescing && a !== undefined && a !== null) {
      done(undefined, a);
      return;
    }
    let bobj;
    try {
      let ad: AsyncDoneRet;
      bobj =
        (ad = asyncDone((d) =>
          execAsync(ticks, tree[2], scope, context, d, statementLabels, internal, generatorYield),
        )).isInstant === true
          ? ad.instant
          : (await ad.p).result;
    } catch (e) {
      done(e);
      return;
    }
    let b = bobj;
    try {
      b = bobj instanceof Prop ? bobj.get(context) : bobj;
    } catch (e) {
      done(e);
      return;
    }
    if (b === optional) {
      b = undefined;
    }
    performOp({
      op,
      exec: execAsync,
      done,
      ticks,
      a,
      b,
      obj,
      context,
      scope,
      bobj,
      statementLabels,
      internal,
      generatorYield,
      tree,
    });
  }
  await p;
}

export function execSync<T = any>(
  ticks: Ticks,
  tree: LispItem,
  scope: Scope,
  context: IExecContext,
  done: Done<T>,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  if (
    !_execNoneRecurse(
      ticks,
      tree,
      scope,
      context,
      done,
      false,
      statementLabels,
      internal,
      generatorYield,
    ) &&
    isLisp(tree)
  ) {
    let op = tree[0];
    let obj = syncDone((d) =>
      execSync(ticks, tree[1], scope, context, d, statementLabels, internal, generatorYield),
    ).result;
    let a = obj instanceof Prop ? obj.get(context) : obj;
    if (op === LispType.PropOptional || op === LispType.CallOptional) {
      if (a === undefined || a === null) {
        done(undefined, optional);
        return;
      }
      op = op === LispType.PropOptional ? LispType.Prop : LispType.Call;
    }
    if (a === optional) {
      if (op === LispType.Prop || op === LispType.Call) {
        done(undefined, a);
        return;
      } else {
        a = undefined;
      }
    }
    // Short-circuit for nullish coalescing: if a is not null/undefined, return a without evaluating b
    if (op === LispType.NullishCoalescing && a !== undefined && a !== null) {
      done(undefined, a);
      return;
    }
    let bobj = syncDone((d) =>
      execSync(ticks, tree[2], scope, context, d, statementLabels, internal, generatorYield),
    ).result;
    let b = bobj instanceof Prop ? bobj.get(context) : bobj;
    if (b === optional) {
      b = undefined;
    }
    performOp({
      op,
      exec: execSync,
      done,
      ticks,
      a,
      b,
      obj,
      context,
      scope,
      bobj,
      statementLabels,
      internal,
      generatorYield,
      tree,
    });
  }
}

type OpsCallbackParams<a, b, obj, bobj> = {
  op: LispType;
  exec: Execution;
  a: a;
  b: b;
  obj: obj;
  bobj: bobj;
  ticks: Ticks;
  tree: LispItem;
  scope: Scope;
  context: IExecContext;
  done: Done;
  statementLabels: ControlFlowTargets;
  internal: boolean;
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined;
};

export function checkHaltExpectedTicks(
  params: OpsCallbackParams<any, any, any, any>,
  expectTicks = 0n,
): boolean {
  const sandbox = params.context.ctx.sandbox;
  const { ticks, scope, context } = params;
  if (sandbox.halted) {
    const sub = sandbox.subscribeResume(() => {
      sub.unsubscribe();
      performOp(params, false);
    });
    return true;
  } else if (ticks.tickLimit !== undefined && ticks.tickLimit <= ticks.ticks + expectTicks) {
    const error = new SandboxExecutionQuotaExceededError('Execution quota exceeded');
    if (context.ctx.options.haltOnSandboxError) {
      const sub = sandbox.subscribeResume(() => {
        sub.unsubscribe();
        performOp(params);
      });
      sandbox.haltExecution({
        type: 'error',
        error,
        ticks,
        scope,
        context,
      });
    } else {
      params.done(error);
    }
    return true;
  } else if (ticks.nextYield && ticks.ticks > ticks.nextYield) {
    const sub = sandbox.subscribeResume(() => {
      sub.unsubscribe();
      performOp(params, false);
    });
    ticks.nextYield += NON_BLOCKING_THRESHOLD;
    sandbox.haltExecution({ type: 'yield' });
    setTimeout(() => sandbox.resumeExecution());
    return true;
  }
  ticks.ticks += expectTicks;
  return false;
}

function performOp(params: OpsCallbackParams<any, any, any, any>, count = true) {
  const { done, op, ticks, context, scope } = params;
  if (count) {
    ticks.ticks++;
  }
  const sandbox = context.ctx.sandbox;

  try {
    if (checkHaltExpectedTicks(params)) {
      return;
    }
    const o = ops.get(op);
    if (o === undefined) {
      done(new SandboxExecutionTreeError('Unknown operator: ' + op));
      return;
    }
    o(params);
  } catch (err) {
    if (context.ctx.options.haltOnSandboxError && err instanceof SandboxError) {
      const sub = sandbox.subscribeResume(() => {
        sub.unsubscribe();
        done(err);
      });
      sandbox.haltExecution({
        type: 'error',
        error: err,
        ticks,
        scope,
        context,
      });
    } else {
      done(err);
    }
  }
}

const unexecTypes = new Set([
  LispType.ArrowFunction,
  LispType.Function,
  LispType.InlineFunction,
  LispType.Loop,
  LispType.Try,
  LispType.Switch,
  LispType.IfCase,
  LispType.InlineIfCase,
  LispType.Labeled,
  LispType.Typeof,
]);

function _execNoneRecurse<T = any>(
  ticks: Ticks,
  tree: LispItem,
  scope: Scope,
  context: IExecContext,
  done: Done<T>,
  isAsync: boolean,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
): boolean {
  const exec = isAsync ? execAsync : execSync;
  if (tree instanceof Prop) {
    done(undefined, tree.get(context));
  } else if (tree === optional) {
    done();
  } else if (Array.isArray(tree) && !isLisp(tree)) {
    if (tree[0] === LispType.None) {
      done();
    } else {
      execMany(
        ticks,
        exec,
        tree as Lisp[],
        done,
        scope,
        context,
        statementLabels,
        internal,
        generatorYield,
      );
    }
  } else if (!isLisp(tree)) {
    done(undefined, tree);
  } else if (tree[0] === LispType.Block) {
    execMany(
      ticks,
      exec,
      tree[1] as Lisp[],
      done,
      new Scope(scope),
      context,
      statementLabels,
      internal,
      generatorYield,
    );
  } else if (tree[0] === LispType.InternalBlock) {
    execMany(
      ticks,
      exec,
      tree[1] as Lisp[],
      done,
      scope,
      context,
      statementLabels,
      true,
      generatorYield,
    );
  } else if (tree[0] === LispType.Await) {
    if (!isAsync) {
      done(new SyntaxError("Illegal use of 'await', must be inside async function"));
    } else if (context.ctx.prototypeWhitelist?.has(Promise.prototype)) {
      execAsync(
        ticks,
        tree[1],
        scope,
        context,
        async (...args: unknown[]) => {
          if (args.length === 1) done(args[0]);
          else
            try {
              done(undefined, (await sanitizeProp(args[1], context)) as any);
            } catch (err) {
              done(err);
            }
        },
        statementLabels,
        internal,
        generatorYield,
      ).catch(done);
    } else {
      done(new SandboxCapabilityError('Async/await is not permitted'));
    }
  } else if (tree[0] === LispType.Yield || tree[0] === LispType.YieldDelegate) {
    const yieldFn = generatorYield;
    if (!yieldFn) {
      done(new SyntaxError("Illegal use of 'yield', must be inside a generator function"));
      return true;
    }
    const isDelegate = tree[0] === LispType.YieldDelegate;
    if (isAsync) {
      execAsync(
        ticks,
        tree[1],
        scope,
        context,
        async (...args: unknown[]) => {
          if (args.length === 1) {
            done(args[0]);
            return;
          }
          try {
            const val = await sanitizeProp(args[1], context);
            yieldFn(new YieldValue(val, isDelegate), done);
          } catch (err) {
            done(err);
          }
        },
        statementLabels,
        internal,
        generatorYield,
      ).catch(done);
    } else {
      try {
        const val = syncDone((d) =>
          execSync(ticks, tree[1], scope, context, d, statementLabels, internal, generatorYield),
        ).result;
        const sanitized = sanitizeProp(val, context);
        // Pass `done` as second arg so the yieldFn can call it with the injected value.
        // For capture-mode yieldFns (executeGenBody default case), this enables the restart loop.
        // For plain yieldFns (eager mode), done? is ignored and done() is called below.
        yieldFn(new YieldValue(sanitized, isDelegate), done);
        // If yieldFn did not call done (it threw syncYieldPauseSentinel instead), we fall through
        // to the catch which re-throws the sentinel. Otherwise yieldFn called done itself.
      } catch (err) {
        if (err === syncYieldPauseSentinel) throw err; // propagate pause up to restart loop
        done(err);
      }
    }
  } else if (unexecTypes.has(tree[0])) {
    performOp({
      op: tree[0],
      exec,
      done,
      ticks,
      a: tree[1],
      b: tree[2],
      obj: tree,
      tree,
      context,
      scope,
      bobj: undefined,
      statementLabels,
      internal,
      generatorYield,
    });
  } else {
    return false;
  }
  return true;
}
export function executeTree<T>(
  ticks: Ticks,
  context: IExecContext,
  executionTree: Lisp[],
  scopes: IScope[] = [],
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield?: ((yv: YieldValue, done?: Done) => void) | undefined,
): ExecReturn<T> {
  return syncDone((done) =>
    executeTreeWithDone(
      execSync,
      done,
      ticks,
      context,
      executionTree,
      scopes,
      statementLabels,
      internal,
      generatorYield,
    ),
  ).result;
}

export async function executeTreeAsync<T>(
  ticks: Ticks,
  context: IExecContext,
  executionTree: Lisp[],
  scopes: IScope[] = [],
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield?: ((yv: YieldValue, done?: Done) => void) | undefined,
): Promise<ExecReturn<T>> {
  let ad: AsyncDoneRet;
  return (ad = asyncDone((done) =>
    executeTreeWithDone(
      execAsync,
      done,
      ticks,
      context,
      executionTree,
      scopes,
      statementLabels,
      internal,
      generatorYield,
    ),
  )).isInstant === true
    ? ad.instant
    : (await ad.p).result;
}

export function executeTreeWithDone(
  exec: Execution,
  done: Done,
  ticks: Ticks,
  context: IExecContext,
  executionTree: Lisp[],
  scopes: IScope[] = [],
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield?: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  if (!executionTree) {
    done();
    return;
  }
  if (!(executionTree instanceof Array)) {
    throw new SyntaxError('Bad execution tree');
  }
  let scope = context.ctx.globalScope;
  let s;
  while ((s = scopes.shift())) {
    if (typeof s !== 'object') continue;
    if (s instanceof Scope) {
      scope = s;
    } else {
      scope = new Scope(scope, s, s instanceof LocalScope ? undefined : null);
    }
  }
  if (context.ctx.options.audit && !context.ctx.auditReport) {
    context.ctx.auditReport = {
      globalsAccess: new Set(),
      prototypeAccess: {},
    };
  }
  if (exec === execSync) {
    _executeWithDoneSync(
      done,
      ticks,
      context,
      executionTree,
      scope,
      statementLabels,
      internal,
      generatorYield,
    );
  } else {
    _executeWithDoneAsync(
      done,
      ticks,
      context,
      executionTree,
      scope,
      statementLabels,
      internal,
      generatorYield,
    ).catch(done);
  }
}

function _executeWithDoneSync(
  done: Done,
  ticks: Ticks,
  context: IExecContext,
  executionTree: Lisp[],
  scope: Scope,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  if (!(executionTree instanceof Array)) throw new SyntaxError('Bad execution tree');
  let i = 0;
  for (i = 0; i < executionTree.length; i++) {
    let res: unknown;
    let err: { error: unknown } | undefined;
    const current = executionTree[i];
    try {
      execSync(
        ticks,
        current,
        scope,
        context,
        (...args: unknown[]) => {
          if (args.length === 1) err = { error: args[0] };
          else res = args[1];
        },
        statementLabels,
        internal,
        generatorYield,
      );
    } catch (e) {
      err = { error: e };
    }
    if (err) {
      done(err.error);
      return;
    }
    if (res instanceof ExecReturn) {
      done(undefined, res);
      return;
    }
    if (isLisp(current) && current[0] === LispType.Return) {
      done(undefined, new ExecReturn(context.ctx.auditReport, res, true));
      return;
    }
  }
  done(undefined, new ExecReturn(context.ctx.auditReport, undefined, false));
}

async function _executeWithDoneAsync(
  done: Done,
  ticks: Ticks,
  context: IExecContext,
  executionTree: Lisp[],
  scope: Scope,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) {
  if (!(executionTree instanceof Array)) throw new SyntaxError('Bad execution tree');
  let i = 0;
  for (i = 0; i < executionTree.length; i++) {
    let res: unknown;
    let err: { error: unknown } | undefined;
    const current = executionTree[i];
    try {
      await execAsync(
        ticks,
        current,
        scope,
        context,
        (...args: unknown[]) => {
          if (args.length === 1) err = { error: args[0] };
          else res = args[1];
        },
        statementLabels,
        internal,
        generatorYield,
      );
    } catch (e) {
      err = { error: e };
    }
    if (err) {
      done(err.error);
      return;
    }
    if (res instanceof ExecReturn) {
      done(undefined, res);
      return;
    }
    if (isLisp(current) && current[0] === LispType.Return) {
      done(undefined, new ExecReturn(context.ctx.auditReport, res, true));
      return;
    }
  }
  done(undefined, new ExecReturn(context.ctx.auditReport, undefined, false));
}


================================================
FILE: src/executor/index.ts
================================================
export * from './executorUtils';


================================================
FILE: src/executor/ops/assignment.ts
================================================
import { addOps, assignCheck, checkHaltExpectedTicks } from '../executorUtils';
import { LispType, Prop } from '../../utils';

addOps<unknown, unknown, Prop<any>, Prop<any>>(LispType.Assign, (params) => {
  const { done, b, obj, context, scope, bobj, internal } = params;
  assignCheck(obj, context);
  obj.isGlobal = bobj?.isGlobal || false;
  if (obj.isVariable) {
    const s = scope.getWhereValScope(obj.prop as string, obj.prop === 'this', internal);
    if (s === null) {
      throw new ReferenceError(`Cannot assign to undeclared variable '${obj.prop.toString()}'`);
    }
    s.set(obj.prop as string, b, internal);
    if (obj.isGlobal) {
      s.globals[obj.prop.toString()] = true;
    } else {
      delete s.globals[obj.prop.toString()];
    }
    done(undefined, b);
    return;
  }
  if (obj.prop === 'length' && Array.isArray(obj.context) && typeof b === 'number') {
    const delta = BigInt(Math.abs(b - obj.context.length));
    if (delta > 0n && checkHaltExpectedTicks(params, delta)) return;
  }
  done(undefined, (obj.context[obj.prop] = b));
});

addOps<unknown, unknown, Prop<any>>(LispType.AddEquals, (params) => {
  const { done, b, obj, context } = params;
  assignCheck(obj, context);
  const result = (obj.context[obj.prop] as any) + (b as any);
  if (typeof result === 'string' && checkHaltExpectedTicks(params, BigInt(result.length))) return;
  done(undefined, (obj.context[obj.prop] = result));
});

addOps<unknown, number, Prop<any>>(LispType.SubractEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] -= b));
});

addOps<unknown, number, Prop<any>>(LispType.DivideEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] /= b));
});

addOps<unknown, number, Prop<any>>(LispType.MultiplyEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] *= b));
});

addOps<unknown, number, Prop<any>>(LispType.PowerEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] **= b));
});

addOps<unknown, number, Prop<any>>(LispType.ModulusEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] %= b));
});

addOps<unknown, number, Prop<any>>(LispType.BitNegateEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] ^= b));
});

addOps<unknown, number, Prop<any>>(LispType.BitAndEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] &= b));
});

addOps<unknown, number, Prop<any>>(LispType.BitOrEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] |= b));
});

addOps<unknown, number, Prop<any>>(LispType.ShiftLeftEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] <<= b));
});

addOps<unknown, number, Prop<any>>(LispType.ShiftRightEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] >>= b));
});

addOps<unknown, number, Prop<any>>(
  LispType.UnsignedShiftRightEquals,
  ({ done, b, obj, context }) => {
    assignCheck(obj, context);
    done(undefined, (obj.context[obj.prop] >>>= b));
  },
);

addOps<unknown, unknown, Prop<any>>(LispType.AndEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] &&= b));
});

addOps<unknown, unknown, Prop<any>>(LispType.OrEquals, ({ done, b, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, (obj.context[obj.prop] ||= b));
});

addOps<unknown, unknown, Prop<any>>(
  LispType.NullishCoalescingEquals,
  ({ done, b, obj, context }) => {
    assignCheck(obj, context);
    done(undefined, (obj.context[obj.prop] ??= b));
  },
);


================================================
FILE: src/executor/ops/call.ts
================================================
import { addOps, arrayChange, Change, checkHaltExpectedTicks, SpreadArray } from '../executorUtils';
import {
  checkTicksAndThrow,
  typedArrayProtos as _typedArrayProtos,
} from '../../utils/functionReplacements';
import type { Lisp } from '../../parser';
import {
  DelayedSynchronousResult,
  getReplacementReceiver,
  LispType,
  SandboxAccessError,
  SandboxCapabilityError,
  sanitizeProp,
} from '../../utils';

addOps<unknown, Lisp[], any>(LispType.Call, (params) => {
  const { done, a, b, obj, context } = params;
  if (context.ctx.options.forbidFunctionCalls)
    throw new SandboxCapabilityError('Function invocations are not allowed');
  if (typeof a !== 'function') {
    throw new TypeError(
      `${typeof obj?.prop === 'symbol' ? 'Symbol' : obj?.prop} is not a function`,
    );
  }
  const vals = new Array<unknown>(b.length);
  let valsLen = 0;
  for (let i = 0; i < b.length; i++) {
    const item = b[i];
    if (item instanceof SpreadArray) {
      const expanded = Array.isArray(item.item) ? item.item : [...(item.item as Iterable<unknown>)];
      if (checkHaltExpectedTicks(params, BigInt(expanded.length))) return;
      for (let j = 0; j < expanded.length; j++)
        vals[valsLen++] = sanitizeProp(expanded[j], context);
    } else {
      vals[valsLen++] = sanitizeProp(item, context);
    }
  }
  vals.length = valsLen;

  if (a === String) {
    const result = String(vals[0]);
    checkTicksAndThrow(context, BigInt(result.length));
    done(undefined, result);
    return;
  }

  if (typeof obj === 'function') {
    // Direct function call (not a method): obj is the function itself
    const evl = context.evals.get(obj);
    const receiver = getReplacementReceiver(obj);
    let ret = evl
      ? evl(obj, ...vals)
      : receiver === undefined
        ? obj(...vals)
        : obj.call(receiver, ...vals);
    ret = sanitizeProp(ret, context);
    if (ret !== null && typeof ret === 'object' && ret instanceof DelayedSynchronousResult) {
      Promise.resolve(ret.result).then(
        (res) => done(undefined, res),
        (err) => done(err),
      );
    } else {
      done(undefined, ret);
    }
    return;
  }

  // Method call: obj is a Prop. `a` is already the replacement (from Prop.get()).
  // The original function is still accessible via obj.context[obj.prop] for subscription checks.
  const originalFn: unknown = obj.context[obj.prop];

  if (originalFn === JSON.stringify && context.getSubscriptions.size) {
    const cache = new WeakSet<any>();
    let ticks = 0n;
    const recurse = (x: unknown) => {
      if (!x || !(typeof x === 'object') || cache.has(x)) return;
      cache.add(x);
      const keys = Object.keys(x) as (keyof typeof x)[];
      ticks += BigInt(keys.length);
      for (const y of keys) {
        context.getSubscriptions.forEach((cb) => cb(x, y));
        recurse(x[y]);
      }
    };
    recurse(vals[0]);
    checkTicksAndThrow(context, ticks);
  }

  if (
    obj.context instanceof Array &&
    arrayChange.has(originalFn as any) &&
    (context.changeSubscriptions.get(obj.context) ||
      context.changeSubscriptionsGlobal.get(obj.context))
  ) {
    let change: Change = undefined!;
    let changed = false;
    if (obj.prop === 'push') {
      change = { type: 'push', added: vals };
      changed = !!vals.length;
    } else if (obj.prop === 'pop') {
      change = { type: 'pop', removed: obj.context.slice(-1) };
      changed = !!change.removed.length;
    } else if (obj.prop === 'shift') {
      change = { type: 'shift', removed: obj.context.slice(0, 1) };
      changed = !!change.removed.length;
    } else if (obj.prop === 'unshift') {
      change = { type: 'unshift', added: vals };
      changed = !!vals.length;
    } else if (obj.prop === 'splice') {
      change = {
        type: 'splice',
        startIndex: vals[0] as number,
        deleteCount: vals[1] === undefined ? obj.context.length : vals[1],
        added: vals.slice(2),
        removed: obj.context.slice(
          vals[0],
          vals[1] === undefined ? undefined : (vals[0] as number) + (vals[1] as number),
        ),
      };
      changed = !!change.added.length || !!change.removed.length;
    } else if (obj.prop === 'reverse' || obj.prop === 'sort') {
      change = { type: obj.prop };
      changed = !!obj.context.length;
    } else if (obj.prop === 'copyWithin') {
      const len =
        vals[2] === undefined
          ? obj.context.length - (vals[1] as number)
          : Math.min(obj.context.length, (vals[2] as number) - (vals[1] as number));
      change = {
        type: 'copyWithin',
        startIndex: vals[0] as number,
        endIndex: (vals[0] as number) + len,
        added: obj.context.slice(vals[1] as number, (vals[1] as number) + len),
        removed: obj.context.slice(vals[0] as number, (vals[0] as number) + len),
      };
      changed = !!change.added.length || !!change.removed.length;
    }
    if (changed) {
      const subs = context.changeSubscriptions.get(obj.context);
      if (subs !== undefined) for (const cb of subs) cb(change);
      const subsG = context.changeSubscriptionsGlobal.get(obj.context);
      if (subsG !== undefined) for (const cb of subsG) cb(change);
    }
  }

  // Trigger get-subscriptions, then call via `a` (which may be a replacement from evals).
  // Sandboxed wrappers for globals (Function, eval, etc.) must be called without `this`;
  // tick-checking replacements must be called with `this`.
  obj.get(context);
  const evl = context.evals.get(originalFn as Function);
  const receiver = getReplacementReceiver(originalFn as Function);
  const thisArg = obj.isVariable && receiver !== undefined ? receiver : obj.context;
  let ret = evl ? evl.call(thisArg, ...vals) : (a as Function).call(thisArg, ...vals);
  ret = sanitizeProp(ret, context);
  if (ret !== null && typeof ret === 'object' && ret instanceof DelayedSynchronousResult) {
    Promise.resolve(ret.result).then(
      (res) => done(undefined, res),
      (err) => done(err),
    );
  } else {
    done(undefined, ret);
  }
});

addOps<new (...args: unknown[]) => void, unknown[]>(LispType.New, (params) => {
  const { done, a, b, context } = params;
  if (!context.ctx.globalsWhitelist.has(a) && !context.ctx.sandboxedFunctions.has(a)) {
    throw new SandboxAccessError(`Object construction not allowed: ${a.constructor.name}`);
  }
  const vals = b.map((item) => sanitizeProp(item, context));
  const replacement = context.evals.get(a);
  if (replacement) {
    const ret = new (replacement as new (...args: unknown[]) => unknown)(...vals);
    done(undefined, ret);
    return;
  }
  const expectedTicks = getNewTicks(a, vals);
  if (expectedTicks > 0n && checkHaltExpectedTicks(params, expectedTicks)) return;
  const ret = new a(...vals);
  done(undefined, ret);
});

function getNewTicks(ctor: Function, args: unknown[]): bigint {
  // new Array(n) or new TypedArray(n) — allocates n elements
  if (ctor === Array) {
    const n = args[0];
    if (typeof n === 'number' && args.length === 1) return BigInt(n);
    return BigInt(args.length);
  }
  if (_typedArrayProtos.has(Object.getPrototypeOf(ctor.prototype))) {
    const n = args[0];
    if (typeof n === 'number') return BigInt(n);
    if (Array.isArray(n) || ArrayBuffer.isView(n)) return BigInt((n as ArrayLike<unknown>).length);
    return 0n;
  }
  // new Map(iterable) or new Set(iterable) — O(n) of iterable length
  if (ctor === Map || ctor === Set) {
    const iterable = args[0];
    if (Array.isArray(iterable)) return BigInt(iterable.length);
    return 0n;
  }
  // new String(s) or new RegExp(pattern) — O(n) of string length
  if (ctor === String || ctor === RegExp) {
    const s = args[0];
    if (typeof s === 'string') return BigInt(s.length);
    return 0n;
  }
  return 0n;
}


================================================
FILE: src/executor/ops/comparison.ts
================================================
import { addOps, checkHaltExpectedTicks } from '../executorUtils';
import { LispType } from '../../utils';

addOps<number, number>(LispType.LargerThan, ({ done, a, b }) => done(undefined, a > b));

addOps<number, number>(LispType.SmallerThan, ({ done, a, b }) => done(undefined, a < b));

addOps<number, number>(LispType.LargerEqualThan, ({ done, a, b }) => done(undefined, a >= b));

addOps<number, number>(LispType.SmallerEqualThan, ({ done, a, b }) => done(undefined, a <= b));

addOps<number, number>(LispType.Equal, ({ done, a, b }) => done(undefined, a == b));

addOps<number, number>(LispType.StrictEqual, ({ done, a, b }) => done(undefined, a === b));

addOps<number, number>(LispType.NotEqual, ({ done, a, b }) => done(undefined, a != b));

addOps<number, number>(LispType.StrictNotEqual, ({ done, a, b }) => done(undefined, a !== b));

addOps<number, number>(LispType.And, ({ done, a, b }) => done(undefined, a && b));

addOps<number, number>(LispType.Or, ({ done, a, b }) => done(undefined, a || b));

addOps<number, number>(LispType.NullishCoalescing, ({ done, a, b }) => done(undefined, a ?? b));

addOps<number, number>(LispType.BitAnd, ({ done, a, b }) => done(undefined, a & b));

addOps<number, number>(LispType.BitOr, ({ done, a, b }) => done(undefined, a | b));

addOps<number, number>(LispType.Plus, (params) => {
  const { done, a, b } = params;
  const result = (a as any) + (b as any);
  if (typeof result === 'string' && checkHaltExpectedTicks(params, BigInt(result.length))) return;
  done(undefined, result);
});

addOps<number, number>(LispType.Minus, ({ done, a, b }) => done(undefined, a - b));

addOps<number, number>(LispType.Divide, ({ done, a, b }) => done(undefined, a / b));

addOps<number, number>(LispType.Power, ({ done, a, b }) => done(undefined, a ** b));

addOps<number, number>(LispType.BitNegate, ({ done, a, b }) => done(undefined, a ^ b));

addOps<number, number>(LispType.Multiply, ({ done, a, b }) => done(undefined, a * b));

addOps<number, number>(LispType.Modulus, ({ done, a, b }) => done(undefined, a % b));

addOps<number, number>(LispType.BitShiftLeft, ({ done, a, b }) => done(undefined, a << b));

addOps<number, number>(LispType.BitShiftRight, ({ done, a, b }) => done(undefined, a >> b));

addOps<number, number>(LispType.BitUnsignedShiftRight, ({ done, a, b }) =>
  done(undefined, a >>> b),
);

addOps<unknown, { new (): unknown }>(LispType.Instanceof, ({ done, a, b }) =>
  done(undefined, a instanceof b),
);

addOps<string, {}>(LispType.In, ({ done, a, b }) => done(undefined, a in b));


================================================
FILE: src/executor/ops/control.ts
================================================
import {
  addOps,
  execAsync,
  execSync,
  asyncDone,
  executeTreeWithDone,
  normalizeStatementLabels,
  addControlFlowTargets,
  createLoopTarget,
  createSwitchTarget,
  createLabeledStatementTarget,
  matchesControlFlowTarget,
  If,
  ExecReturn,
  executeTreeAsync,
  syncDone,
  executeTree,
  normalizeStatementLabel,
  addControlFlowTarget,
} from '../executorUtils';
import type { AsyncDoneRet } from '../executorUtils';
import type { Lisp, LispItem, StatementLabel, SwitchCase } from '../../parser';
import { LispType, Scope, SandboxError, sanitizeProp } from '../../utils';

addOps<Lisp[], Lisp[]>(
  LispType.Loop,
  ({ exec, done, ticks, a, b, context, scope, statementLabels, internal, generatorYield }) => {
    const [
      checkFirst,
      startInternal,
      getIterator,
      startStep,
      step,
      condition,
      beforeStep,
      isForAwait,
      label,
    ] = a;
    const loopStatementTargets = [
      ...normalizeStatementLabels(label).map((loopLabel) => createLoopTarget(loopLabel, false)),
      createLoopTarget(),
    ];
    const loopTargets = addControlFlowTargets(statementLabels, loopStatementTargets);
    if ((isForAwait as unknown as LispType) === LispType.True && exec !== execAsync) {
      done(new SyntaxError('for-await-of loops are only allowed inside async functions'));
      return;
    }
    let loop = true;
    const loopScope = new Scope(scope, {});
    const internalVars: Record<string, unknown> = {
      $$obj: undefined,
    };
    const interalScope = new Scope(loopScope, internalVars);
    if (exec === execAsync) {
      (async () => {
        let ad: AsyncDoneRet;
        ad = asyncDone((d) =>
          exec(ticks, startStep, loopScope, context, d, undefined, internal, generatorYield),
        );
        internalVars['$$obj'] =
          (ad = asyncDone((d) =>
            exec(ticks, getIterator, loopScope, context, d, undefined, internal, generatorYield),
          )).isInstant === true
            ? ad.instant
            : (await ad.p).result;
        // for-await-of: override $$obj with the correct async iterator
        if ((isForAwait as unknown as LispType) === LispType.True) {
          const obj = internalVars['$$obj'] as any;
          internalVars['$$obj'] = obj[Symbol.asyncIterator]
            ? obj[Symbol.asyncIterator]()
            : obj[Symbol.iterator]
              ? obj[Symbol.iterator]()
              : obj;
        }
        ad = asyncDone((d) =>
          exec(ticks, startInternal, interalScope, context, d, undefined, internal, generatorYield),
        );
        // for-await-of: await the $$next promise after startInternal sets it
        if ((isForAwait as unknown as LispType) === LispType.True) {
          internalVars['$$next'] = await internalVars['$$next'];
        }
        if (checkFirst)
          loop =
            (ad = asyncDone((d) =>
              exec(ticks, condition, interalScope, context, d, undefined, internal, generatorYield),
            )).isInstant === true
              ? ad.instant
              : (await ad.p).result;
        while (loop) {
          const innerLoopVars = {};
          const iterScope = new Scope(interalScope, innerLoopVars);
          ad = asyncDone((d) =>
            exec(ticks, beforeStep, iterScope, context, d, undefined, internal, generatorYield),
          );
          ad.isInstant === true ? ad.instant : (await ad.p).result;
          const res = await executeTreeAsync(
            ticks,
            context,
            b,
            [iterScope],
            loopTargets,
            internal,
            generatorYield,
          );
          if (res instanceof ExecReturn && res.returned) {
            done(undefined, res);
            return;
          }
          if (res instanceof ExecReturn && res.controlFlow) {
            if (
              !loopStatementTargets.some((target) =>
                matchesControlFlowTarget(res.controlFlow!, target),
              )
            ) {
              done(undefined, res);
              return;
            }
            if (res.breakLoop) {
              break;
            }
          }
          ad = asyncDone((d) =>
            exec(ticks, step, interalScope, context, d, undefined, internal, generatorYield),
          );
          // for-await-of: await the $$next promise after step updates it
          if ((isForAwait as unknown as LispType) === LispType.True) {
            internalVars['$$next'] = await internalVars['$$next'];
          }
          loop =
            (ad = asyncDone((d) =>
              exec(ticks, condition, interalScope, context, d, undefined, internal, generatorYield),
            )).isInstant === true
              ? ad.instant
              : (await ad.p).result;
        }
        done();
      })().catch(done);
    } else {
      syncDone((d) =>
        exec(ticks, startStep, loopScope, context, d, undefined, internal, generatorYield),
      );
      internalVars['$$obj'] = syncDone((d) =>
        exec(ticks, getIterator, loopScope, context, d, undefined, internal, generatorYield),
      ).result;
      syncDone((d) =>
        exec(ticks, startInternal, interalScope, context, d, undefined, internal, generatorYield),
      );
      if (checkFirst)
        loop = syncDone((d) =>
          exec(ticks, condition, interalScope, context, d, undefined, internal, generatorYield),
        ).result;
      while (loop) {
        const innerLoopVars = {};
        const iterScope = new Scope(interalScope, innerLoopVars);
        syncDone((d) =>
          exec(ticks, beforeStep, iterScope, context, d, undefined, internal, generatorYield),
        );
        const res = executeTree(
          ticks,
          context,
          b,
          [iterScope],
          loopTargets,
          internal,
          generatorYield,
        );
        if (res instanceof ExecReturn && res.returned) {
          done(undefined, res);
          return;
        }
        if (res instanceof ExecReturn && res.controlFlow) {
          if (
            !loopStatementTargets.some((target) =>
              matchesControlFlowTarget(res.controlFlow!, target),
            )
          ) {
            done(undefined, res);
            return;
          }
          if (res.breakLoop) {
            break;
          }
        }
        syncDone((d) =>
          exec(ticks, step, interalScope, context, d, undefined, internal, generatorYield),
        );
        loop = syncDone((d) =>
          exec(ticks, condition, interalScope, context, d, undefined, internal, generatorYield),
        ).result;
      }
      done();
    }
  },
);

addOps<LispItem, If>(
  LispType.If,
  ({ exec, done, ticks, a, b, context, scope, statementLabels, internal, generatorYield }) => {
    exec(
      ticks,
      sanitizeProp(a, context) ? b.t : b.f,
      scope,
      context,
      done,
      statementLabels,
      internal,
      generatorYield,
    );
  },
);

addOps<LispItem, If>(
  LispType.InlineIf,
  ({ exec, done, ticks, a, b, context, scope, internal, generatorYield }) => {
    exec(
      ticks,
      sanitizeProp(a, context) ? b.t : b.f,
      scope,
      context,
      done,
      undefined,
      internal,
      generatorYield,
    );
  },
);

addOps<Lisp, Lisp>(LispType.InlineIfCase, ({ done, a, b }) => done(undefined, new If(a, b)));

addOps<Lisp, Lisp>(LispType.IfCase, ({ done, a, b }) => done(undefined, new If(a, b)));

addOps<StatementLabel, Lisp>(
  LispType.Labeled,
  ({ exec, done, ticks, a, b, context, scope, statementLabels, internal, generatorYield }) => {
    const target = createLabeledStatementTarget(normalizeStatementLabel(a));
    exec(
      ticks,
      b,
      scope,
      context,
      (...args: unknown[]) => {
        if (args.length === 1) {
          done(args[0]);
          return;
        }
        const res = args[1];
        if (res instanceof ExecReturn && res.controlFlow && target) {
          if (matchesControlFlowTarget(res.controlFlow, target)) {
            done();
            return;
          }
        }
        done(undefined, res as any);
      },
      addControlFlowTarget(statementLabels, target),
      internal,
      generatorYield,
    );
  },
);

addOps<LispItem, SwitchCase[]>(
  LispType.Switch,
  ({ exec, done, ticks, a, b, context, scope, statementLabels, internal, generatorYield }) => {
    const switchTarget = createSwitchTarget();
    const switchTargets = addControlFlowTarget(statementLabels, switchTarget);
    exec(
      ticks,
      a,
      scope,
      context,
      (...args: unknown[]) => {
        if (args.length === 1) {
          done(args[0]);
          return;
        }
        let toTest = args[1];
        toTest = sanitizeProp(toTest, context);
        if (exec === execSync) {
          let res: ExecReturn<unknown>;
          let isTrue = false;
          for (const caseItem of b) {
            if (
              isTrue ||
              (isTrue =
                !caseItem[1] ||
                toTest ===
                  sanitizeProp(
                    syncDone((d) =>
                      exec(
                        ticks,
                        caseItem[1],
                        scope,
                        context,
                        d,
                        undefined,
                        internal,
                        generatorYield,
                      ),
                    ).result,
                    context,
                  ))
            ) {
              if (!caseItem[2]) continue;
              res = executeTree(
                ticks,
                context,
                caseItem[2],
                [scope],
                switchTargets,
                internal,
                generatorYield,
              );
              if (res.controlFlow) {
                if (!matchesControlFlowTarget(res.controlFlow, switchTarget)) {
                  done(undefined, res);
                  return;
                }
                if (res.breakLoop) break;
              }
              if (res.returned) {
                done(undefined, res);
                return;
              }
              if (!caseItem[1]) {
                // default case
                break;
              }
            }
          }
          done();
        } else {
          (async () => {
            let res: ExecReturn<unknown>;
            let isTrue = false;
            for (const caseItem of b) {
              let ad: AsyncDoneRet;
              if (
                isTrue ||
                (isTrue =
                  !caseItem[1] ||
                  toTest ===
                    sanitizeProp(
                      (ad = asyncDone((d) =>
                        exec(
                          ticks,
                          caseItem[1],
                          scope,
                          context,
                          d,
                          undefined,
                          internal,
                          generatorYield,
                        ),
                      )).isInstant === true
                        ? ad.instant
                        : (await ad.p).result,
                      context,
                    ))
              ) {
                if (!caseItem[2]) continue;
                res = await executeTreeAsync(
                  ticks,
                  context,
                  caseItem[2],
                  [scope],
                  switchTargets,
                  internal,
                  generatorYield,
                );
                if (res.controlFlow) {
                  if (!matchesControlFlowTarget(res.controlFlow, switchTarget)) {
                    done(undefined, res);
                    return;
                  }
                  if (res.breakLoop) break;
                }
                if (res.returned) {
                  done(undefined, res);
                  return;
                }
                if (!caseItem[1]) {
                  // default case
                  break;
                }
              }
            }
            done();
          })().catch(done);
        }
      },
      undefined,
      internal,
      generatorYield,
    );
  },
);

addOps<Lisp[], [string, Lisp[], Lisp[]]>(
  LispType.Try,
  ({ exec, done, ticks, a, b, context, scope, statementLabels, internal, generatorYield }) => {
    const [exception, catchBody, finallyBody] = b;

    // Execute try block
    executeTreeWithDone(
      exec,
      (...tryArgs: unknown[]) => {
        const tryHadError = tryArgs.length === 1;
        const tryError = tryHadError ? tryArgs[0] : undefined;
        const tryResult = !tryHadError && tryArgs.length > 1 ? tryArgs[1] : undefined;

        // Handler to execute finally and complete
        const executeFinallyAndComplete = (hadError: boolean, errorOrResult: unknown) => {
          if (finallyBody && finallyBody.length > 0) {
            // Execute finally block
            executeTreeWithDone(
              exec,
              (...finallyArgs: unknown[]) => {
                const finallyHadError = finallyArgs.length === 1;
                const finallyResult =
                  !finallyHadError && finallyArgs.length > 1 ? finallyArgs[1] : undefined;

                // If finally throws an error, it overrides everything
                if (finallyHadError) {
                  done(finallyArgs[0]);
                  return;
                }

                // If finally has a control flow statement (return/break/continue), it overrides everything
                if (
                  finallyResult instanceof ExecReturn &&
                  (finallyResult.returned || finallyResult.breakLoop || finallyResult.continueLoop)
                ) {
                  done(undefined, finallyResult);
                  return;
                }

                // Otherwise, return the original try/catch result/error
                if (hadError) {
                  done(errorOrResult);
                } else if (errorOrResult instanceof ExecReturn) {
                  // If try/catch returned or has some other control flow, pass that through
                  if (
                    errorOrResult.returned ||
                    errorOrResult.breakLoop ||
                    errorOrResult.continueLoop
                  ) {
                    done(undefined, errorOrResult);
                  } else {
                    // Normal completion - don't return a value
                    done();
                  }
                } else {
                  // Try/catch completed normally, just signal completion with no return value
                  done();
                }
              },
              ticks,
              context,
              finallyBody,
              [new Scope(scope, {})],
              statementLabels,
              internal,
              generatorYield,
            );
          } else {
            // No finally block, just return result/error
            if (hadError) {
              done(errorOrResult);
            } else if (errorOrResult instanceof ExecReturn) {
              // If try/catch returned or has some other control flow, pass that through
              if (errorOrResult.returned || errorOrResult.breakLoop || errorOrResult.continueLoop) {
                done(undefined, errorOrResult);
              } else {
                // Normal completion - don't return a value
                done();
              }
            } else {
              done();
            }
          }
        };

        // SandboxErrors bypass both catch and finally — propagate immediately.
        if (tryHadError && tryError instanceof SandboxError) {
          done(tryError);
          return;
        }

        // If try had an error and there's a catch block, execute catch.
        if (tryHadError && catchBody && catchBody.length > 0) {
          const sc: Record<string, unknown> = {};
          if (exception) sc[exception] = tryError;

          executeTreeWithDone(
            exec,
            (...catchArgs: unknown[]) => {
              const catchHadError = catchArgs.length === 1;
              const catchErrorOrResult = catchHadError
                ? catchArgs[0]
                : catchArgs.length > 1
                  ? catchArgs[1]
                  : undefined;

              // Execute finally with catch result
              executeFinallyAndComplete(catchHadError, catchErrorOrResult);
            },
            ticks,
            context,
            catchBody,
            [new Scope(scope, sc)],
            statementLabels,
            internal,
            generatorYield,
          );
        } else {
          // No catch or no error, execute finally with try result
          executeFinallyAndComplete(tryHadError, tryHadError ? tryError : tryResult);
        }
      },
      ticks,
      context,
      a,
      [new Scope(scope)],
      statementLabels,
      internal,
      generatorYield,
    );
  },
);

addOps<unknown[]>(LispType.Expression, ({ done, a }) => done(undefined, a.pop()));


================================================
FILE: src/executor/ops/functions.ts
================================================
import {
  addOps,
  createAsyncGeneratorFunction,
  createFunction,
  createFunctionAsync,
  createGeneratorFunction,
} from '../executorUtils';
import type { Lisp } from '../../parser';
import { LispType, SandboxCapabilityError, CodeString, Scope, VarType } from '../../utils';

addOps<string[], Lisp[], Lisp>(
  LispType.ArrowFunction,
  ({ done, ticks, a, b, obj, context, scope, internal }) => {
    if (typeof obj[2] === 'string' || obj[2] instanceof CodeString) {
      if (context.allowJit && context.evalContext) {
        obj[2] = b = context.evalContext.lispifyFunction(new CodeString(obj[2]), context.constants);
      } else {
        throw new SandboxCapabilityError('Unevaluated code detected, JIT not allowed');
      }
    }
    const argNames = a.slice(1);
    if (a[0]) {
      done(undefined, createFunctionAsync(argNames, b, ticks, context, scope, undefined, internal));
    } else {
      done(undefined, createFunction(argNames, b, ticks, context, scope, undefined, internal));
    }
  },
);

addOps<(string | LispType)[], Lisp[], Lisp>(
  LispType.Function,
  ({ done, ticks, a, b, obj, context, scope, internal }) => {
    if (typeof obj[2] === 'string' || obj[2] instanceof CodeString) {
      if (context.allowJit && context.evalContext) {
        obj[2] = b = context.evalContext.lispifyFunction(
          new CodeString(obj[2]),
          context.constants,
          false,
          {
            generatorDepth: a[1] === LispType.True ? 1 : 0,
            asyncDepth: a[0] === LispType.True ? 1 : 0,
            lispDepth: 0,
          },
        );
      } else {
        throw new SandboxCapabilityError('Unevaluated code detected, JIT not allowed');
      }
    }
    const isAsync = a[0];
    const isGenerator = a[1];
    const name = a[2] as string;
    const argNames = a.slice(3) as string[];
    let func;
    if (isAsync === LispType.True && isGenerator === LispType.True) {
      func = createAsyncGeneratorFunction(argNames, b, ticks, context, scope, name, internal);
    } else if (isGenerator === LispType.True) {
      func = createGeneratorFunction(argNames, b, ticks, context, scope, name, internal);
    } else if (isAsync === LispType.True) {
      func = createFunctionAsync(argNames, b, ticks, context, scope, name, internal);
    } else {
      func = createFunction(argNames, b, ticks, context, scope, name, internal);
    }
    if (name) {
      scope.declare(name, VarType.var, func, false, internal);
    }
    done(undefined, func);
  },
);

addOps<(string | LispType)[], Lisp[], Lisp>(
  LispType.InlineFunction,
  ({ done, ticks, a, b, obj, context, scope, internal }) => {
    if (typeof obj[2] === 'string' || obj[2] instanceof CodeString) {
      if (context.allowJit && context.evalContext) {
        obj[2] = b = context.evalContext.lispifyFunction(
          new CodeString(obj[2]),
          context.constants,
          false,
          {
            generatorDepth: a[1] === LispType.True ? 1 : 0,
            asyncDepth: a[0] === LispType.True ? 1 : 0,
            lispDepth: 0,
          },
        );
      } else {
        throw new SandboxCapabilityError('Unevaluated code detected, JIT not allowed');
      }
    }
    const isAsync = a[0];
    const isGenerator = a[1];
    const name = a[2] as string;
    const argNames = a.slice(3) as string[];
    if (name) {
      scope = new Scope(scope, {});
    }
    let func;
    if (isAsync === LispType.True && isGenerator === LispType.True) {
      func = createAsyncGeneratorFunction(argNames, b, ticks, context, scope, name, internal);
    } else if (isGenerator === LispType.True) {
      func = createGeneratorFunction(argNames, b, ticks, context, scope, name, internal);
    } else if (isAsync === LispType.True) {
      func = createFunctionAsync(argNames, b, ticks, context, scope, name, internal);
    } else {
      func = createFunction(argNames, b, ticks, context, scope, name, internal);
    }
    if (name) {
      scope.declare(name, VarType.let, func, false, internal);
    }
    done(undefined, func);
  },
);


================================================
FILE: src/executor/ops/index.ts
================================================
import './prop';
import './call';
import './object';
import './literals';
import './unary';
import './assignment';
import './comparison';
import './variables';
import './misc';
import './functions';
import './control';


================================================
FILE: src/executor/ops/literals.ts
================================================
import { addOps, literalRegex, checkHaltExpectedTicks } from '../executorUtils';
import type { Lisp, IRegEx } from '../../parser';
import { LispType, SandboxCapabilityError, sanitizeProp } from '../../utils';

addOps<unknown, string>(LispType.Number, ({ done, b }) =>
  done(undefined, Number(b.replace(/_/g, ''))),
);

addOps<unknown, string>(LispType.BigInt, ({ done, b }) =>
  done(undefined, BigInt(b.replace(/_/g, ''))),
);

addOps<unknown, string>(LispType.RegexIndex, ({ done, b, context }) => {
  const reg: IRegEx = context.constants.regexes[parseInt(b)];
  if (!context.ctx.globalsWhitelist.has(RegExp)) {
    throw new SandboxCapabilityError('Regex not permitted');
  } else {
    const RegExpCtor =
      (context.evals.get(RegExp) as
        | (new (pattern: string, flags?: string) => unknown)
        | undefined) ?? RegExp;
    done(undefined, new RegExpCtor(reg.regex, reg.flags));
  }
});

addOps<unknown, string>(LispType.LiteralIndex, (params) => {
  const { exec, done, ticks, b, context, scope, internal, generatorYield } = params;
  const item = context.constants.literals[parseInt(b)];
  const [, name, js] = item;
  const found: Lisp[] = [];
  let f: RegExpExecArray | null;
  const resnums: string[] = [];
  while ((f = literalRegex.exec(name))) {
    if (!f[2]) {
      found.push(js[parseInt(f[3], 10)]);
      resnums.push(f[3]);
    }
  }

  exec<unknown[]>(
    ticks,
    found,
    scope,
    context,
    (...args: unknown[]) => {
      const reses: Record<string, unknown> = {};
      if (args.length === 1) {
        done(args[0]);
        return;
      }
      const processed = args[1];
      for (const i of Object.keys(processed!) as (keyof typeof processed)[]) {
        const num = resnums[i];
        reses[num] = processed![i];
      }
      const result = name.replace(/(\\\\)*(\\)?\${(\d+)}/g, (match, $$, $, num) => {
        if ($) return match;
        const res = reses[num];
        return ($$ ? $$ : '') + `${sanitizeProp(res, context)}`;
      });
      if (checkHaltExpectedTicks(params, BigInt(result.length))) return;
      done(undefined, result);
    },
    undefined,
    internal,
    generatorYield,
  );
});


================================================
FILE: src/executor/ops/misc.ts
================================================
import {
  addOps,
  findControlFlowTarget,
  ExecReturn,
  normalizeStatementLabel,
} from '../executorUtils';
import type { ControlFlowAction } from '../executorUtils';
import type { LispItem, StatementLabel } from '../../parser';
import { LispType, Prop, VarType, SandboxCapabilityError } from '../../utils';

addOps<string, unknown, unknown, Prop>(
  LispType.Internal,
  ({ done, a, b, scope, bobj, internal }) => {
    if (!internal) {
      throw new SandboxCapabilityError('Internal variables are not accessible');
    }
    done(undefined, scope.declare(a, VarType.internal, b, bobj?.isGlobal || false, internal));
  },
);

addOps<LispItem, StatementLabel>(
  LispType.LoopAction,
  ({ done, a, b, context, statementLabels }) => {
    const label = normalizeStatementLabel(b);
    const target = findControlFlowTarget(statementLabels, a as ControlFlowAction, label);
    if (target === null) {
      throw new TypeError('Illegal continue statement');
    }
    if (!target) {
      throw new TypeError(label ? `Undefined label '${label}'` : 'Illegal ' + a + ' statement');
    }
    done(
      undefined,
      new ExecReturn(context.ctx.auditReport, undefined, false, {
        type: a as ControlFlowAction,
        label,
      }),
    );
  },
);

addOps(LispType.Throw, ({ done, b }) => {
  done(b);
});

addOps(LispType.None, ({ done }) => done());


================================================
FILE: src/executor/ops/object.ts
================================================
import {
  addOps,
  checkHaltExpectedTicks,
  SpreadArray,
  SpreadObject,
  KeyVal,
  ArrayHole,
} from '../executorUtils';
import type { Lisp, LispItem } from '../../parser';
import { LispType, sanitizeProp } from '../../utils';

addOps<unknown, KeyVal[]>(LispType.CreateObject, (params) => {
  const { done, b } = params;
  let res = {} as any;
  for (const item of b) {
    if (item.key instanceof SpreadObject) {
      const keys = Object.keys(item.key.item);
      if (checkHaltExpectedTicks(params, BigInt(keys.length))) return;
      res = { ...res, ...item.key.item };
    } else {
      res[item.key] = item.val;
    }
  }
  done(undefined, res);
});

addOps<PropertyKey, LispItem>(LispType.KeyVal, ({ done, a, b }) =>
  done(undefined, new KeyVal(a, b)),
);

addOps<unknown, Lisp[]>(LispType.CreateArray, (params) => {
  const { done, b, context } = params;
  const items: unknown[] = [];
  for (const item of b) {
    if (item instanceof SpreadArray) {
      const expanded = Array.isArray(item.item) ? item.item : [...(item.item as Iterable<unknown>)];
      if (checkHaltExpectedTicks(params, BigInt(expanded.length))) return;
      for (const v of expanded) items.push(sanitizeProp(v, context));
    } else if (item instanceof ArrayHole) {
      items.length++;
    } else {
      items.push(sanitizeProp(item, context));
    }
  }
  done(undefined, items);
});

addOps(LispType.Hole, ({ done }) => done(undefined, new ArrayHole()));

addOps<unknown, unknown>(LispType.Group, ({ done, b }) => done(undefined, b));

addOps<unknown, string>(LispType.GlobalSymbol, ({ done, b }) => {
  switch (b) {
    case 'true':
      return done(undefined, true);
    case 'false':
      return done(undefined, false);
    case 'null':
      return done(undefined, null);
    case 'undefined':
      return done(undefined, undefined);
    case 'NaN':
      return done(undefined, NaN);
    case 'Infinity':
      return done(undefined, Infinity);
  }
  done(new Error('Unknown symbol: ' + b));
});

addOps<unknown, unknown[]>(LispType.SpreadArray, ({ done, b }) => {
  done(undefined, new SpreadArray(b));
});

addOps<unknown, Record<string, unknown>>(LispType.SpreadObject, ({ done, b }) => {
  done(undefined, new SpreadObject(b));
});


================================================
FILE: src/executor/ops/prop.ts
================================================
import { addOps, hasPossibleProperties, isPropertyKey } from '../executorUtils';
import {
  LispType,
  Prop,
  SandboxAccessError,
  resolveSandboxProp,
  hasOwnProperty,
} from '../../utils';

addOps<unknown, PropertyKey>(LispType.Prop, ({ done, a, b, obj, context, scope, internal }) => {
  if (a === null) {
    throw new TypeError(`Cannot read properties of null (reading '${b?.toString()}')`);
  }

  if (!isPropertyKey(b)) {
    b = `${b}`;
  }

  if (a === undefined && obj === undefined && typeof b === 'string') {
    // is variable access
    const prop = scope.get(b, internal);
    if (prop.context === undefined) {
      throw new ReferenceError(`${b} is not defined`);
    }
    if (prop.context === context.ctx.sandboxGlobal) {
      if (context.ctx.options.audit) {
        context.ctx.auditReport?.globalsAccess.add(b);
      }
    }
    const val = (prop.context as any)[prop.prop];
    const p = resolveSandboxProp(val, context, prop) || prop;

    done(undefined, p);
    return;
  } else if (a === undefined) {
    throw new TypeError(`Cannot read properties of undefined (reading '${b.toString()}')`);
  }

  if (!hasPossibleProperties(a)) {
    done(undefined, new Prop(undefined, b));
    return;
  }

  const prototypeAccess = typeof a === 'function' || !hasOwnProperty(a, b);

  if (context.ctx.options.audit && prototypeAccess) {
    let prot: {} = Object.getPrototypeOf(a);
    do {
      if (hasOwnProperty(prot, b)) {
        if (
          context.ctx.auditReport &&
          !context.ctx.auditReport.prototypeAccess[prot.constructor.name]
        ) {
          context.ctx.auditReport.prototypeAccess[prot.constructor.name] = new Set();
        }
        context.ctx.auditReport?.prototypeAccess[prot.constructor.name].add(b);
      }
    } while ((prot = Object.getPrototypeOf(prot)));
  }

  if (prototypeAccess) {
    if (typeof a === 'function') {
      if (hasOwnProperty(a, b)) {
        const whitelist = context.ctx.prototypeWhitelist.get(a.prototype);
        if (
          !(whitelist && (!whitelist.size || whitelist.has(b))) &&
          !context.ctx.sandboxedFunctions.has(a)
        ) {
          throw new SandboxAccessError(
            `Static method or property access not permitted: ${a.name}.${b.toString()}`,
          );
        }
      }
    }

    let prot: {} = a;
    while ((prot = Object.getPrototypeOf(prot))) {
      if (hasOwnProperty(prot, b) || b === '__proto__') {
        const whitelist = context.ctx.prototypeWhitelist.get(prot);
        if (
          (whitelist && (!whitelist.size || whitelist.has(b))) ||
          context.ctx.sandboxedFunctions.has(prot.constructor)
        ) {
          break;
        }
        if (b === '__proto__') {
          throw new SandboxAccessError(`Access to prototype of global object is not permitted`);
        }
        throw new SandboxAccessError(
          `Method or property access not permitted: ${prot.constructor.name}.${b.toString()}`,
        );
      }
    }
  }

  if (typeof a === 'function') {
    if (b === 'prototype' && !context.ctx.sandboxedFunctions.has(a)) {
      throw new SandboxAccessError(`Access to prototype of global object is not permitted`);
    }
    if (['caller', 'callee', 'arguments'].includes(b as string)) {
      throw new SandboxAccessError(`Access to '${b as string}' property is not permitted`);
    }
  }
  const val = a[b as keyof typeof a] as unknown;

  if (b === '__proto__' && !context.ctx.sandboxedFunctions.has(val?.constructor as any)) {
    throw new SandboxAccessError(`Access to prototype of global object is not permitted`);
  }

  const p = resolveSandboxProp(val, context, new Prop(a, b, false, false));
  if (p) {
    done(undefined, p);
    return;
  }

  const isSandboxGlobal = a === context.ctx.sandboxGlobal;
  const g =
    (!isSandboxGlobal && obj instanceof Prop && obj.isGlobal) ||
    (typeof a === 'function' && !context.ctx.sandboxedFunctions.has(a)) ||
    context.ctx.globalsWhitelist.has(a) ||
    (isSandboxGlobal &&
      typeof b === 'string' &&
      hasOwnProperty(context.ctx.globalScope.globals, b));

  done(undefined, new Prop(a, b, false, g, false));
});

addOps<unknown, string>(LispType.StringIndex, ({ done, b, context }) =>
  done(undefined, context.constants.strings[parseInt(b)]),
);


================================================
FILE: src/executor/ops/unary.ts
================================================
import { addOps, assignCheck } from '../executorUtils';
import type { LispItem } from '../../parser';
import { LispType, Prop, sanitizeProp } from '../../utils';

addOps<unknown, unknown>(LispType.Not, ({ done, b }) => done(undefined, !b));

addOps<unknown, number>(LispType.Inverse, ({ done, b }) => done(undefined, ~b));

addOps<unknown, unknown, Prop<any>>(LispType.IncrementBefore, ({ done, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, ++obj.context[obj.prop]);
});

addOps<unknown, unknown, Prop<any>>(LispType.IncrementAfter, ({ done, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, obj.context[obj.prop]++);
});

addOps<unknown, unknown, Prop<any>>(LispType.DecrementBefore, ({ done, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, --obj.context[obj.prop]);
});

addOps<unknown, unknown, Prop<any>>(LispType.DecrementAfter, ({ done, obj, context }) => {
  assignCheck(obj, context);
  done(undefined, obj.context[obj.prop]--);
});

addOps<number, number>(LispType.Positive, ({ done, b }) => done(undefined, +b));

addOps<number, number>(LispType.Negative, ({ done, b }) => done(undefined, -b));

addOps<unknown, LispItem>(
  LispType.Typeof,
  ({ exec, done, ticks, b, context, scope, internal, generatorYield }) => {
    exec(
      ticks,
      b,
      scope,
      context,
      (e, prop) => {
        done(undefined, typeof sanitizeProp(prop, context));
      },
      undefined,
      internal,
      generatorYield,
    );
  },
);

addOps<unknown, unknown>(LispType.Delete, ({ done, context, bobj }) => {
  if (!(bobj instanceof Prop)) {
    done(undefined, true);
    return;
  }
  assignCheck(bobj, context, 'delete');
  if (bobj.isVariable) {
    done(undefined, false);
    return;
  }
  done(undefined, delete (bobj.context as any)?.[bobj.prop]);
});

addOps(LispType.Void, ({ done }) => {
  done();
});


================================================
FILE: src/executor/ops/variables.ts
================================================
import { addOps } from '../executorUtils';
import { LispType, Prop, VarType } from '../../utils';

addOps(LispType.Return, ({ done, b }) => done(undefined, b));

addOps<string, unknown, unknown, Prop>(LispType.Var, ({ done, a, b, scope, bobj, internal }) => {
  done(undefined, scope.declare(a, VarType.var, b, bobj?.isGlobal || false, internal));
});

addOps<string, unknown, unknown, Prop>(LispType.Let, ({ done, a, b, scope, bobj, internal }) => {
  done(undefined, scope.declare(a, VarType.let, b, bobj?.isGlobal || false, internal));
});

addOps<string, unknown, unknown, Prop>(LispType.Const, ({ done, a, b, scope, bobj, internal }) => {
  done(undefined, scope.declare(a, VarType.const, b, bobj?.isGlobal || false, internal));
});


================================================
FILE: src/executor/opsRegistry.ts
================================================
/**
 * Ops registry — kept separate from executorUtils.ts so that ops/*.ts files
 * can import addOps without creating a circular initialization problem.
 *
 * executorUtils.ts imports from here; ops/*.ts imports from here too.
 * No imports from executorUtils.ts allowed in this file.
 */
import { LispType } from '../utils';
import type { LispItem } from '../parser';
import type { IExecContext, Ticks } from '../utils';
import type { Scope } from '../utils';
import type { YieldValue, Done } from './executorUtils';

export type ControlFlowAction = 'break' | 'continue';

export interface ControlFlowTarget {
  label?: string;
  acceptsBreak: boolean;
  acceptsContinue: boolean;
  acceptsUnlabeledBreak: boolean;
  acceptsUnlabeledContinue: boolean;
}

export type ControlFlowTargets = readonly ControlFlowTarget[] | undefined;

export type Execution = <T = any>(
  ticks: Ticks,
  tree: LispItem,
  scope: Scope,
  context: IExecContext,
  done: Done<T>,
  statementLabels: ControlFlowTargets,
  internal: boolean,
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined,
) => void;

export type OpsCallbackParams<a, b, obj, bobj> = {
  op: LispType;
  exec: Execution;
  a: a;
  b: b;
  obj: obj;
  bobj: bobj;
  ticks: Ticks;
  tree: LispItem;
  scope: Scope;
  context: IExecContext;
  done: Done;
  statementLabels: ControlFlowTargets;
  internal: boolean;
  generatorYield: ((yv: YieldValue, done?: Done) => void) | undefined;
};

type OpCallback<a, b, obj, bobj> = (params: OpsCallbackParams<a, b, obj, bobj>) => void;

export const ops = new Map<LispType, OpCallback<any, any, any, any>>();

export function addOps<a = unknown, b = unknown, obj = unknown, bobj = unknown>(
  type: LispType,
  cb: OpCallback<a, b, obj, bobj>,
) {
  ops.set(type, cb);
}


================================================
FILE: src/parser/index.ts
================================================
export * from './lisp';
export * from './parserUtils';
export { default } from './parserUtils';


================================================
FILE: src/parser/lisp.ts
================================================
import type { CodeString, LispType } from '../utils';

export type DefineLisp<
  op extends LispType,
  a extends LispItem | LispItem,
  b extends LispItem | LispItem,
> = [op, a, b];

export type ExtractLispOp<L> = L extends DefineLisp<infer i, any, any> ? i : never;
export type ExtractLispA<L> = L extends DefineLisp<any, infer i, any> ? i : never;
export type ExtractLispB<L> = L extends DefineLisp<any, any, infer i> ? i : never;

export type LispItemSingle = LispType.None | LispType.True | string | Lisp;
export type LispItem = LispItemSingle | LispItemSingle[];
export type Lisp = [LispType, LispItem, LispItem];

export type Literal = DefineLisp<LispType.Literal, string, Lisp[]> & { tempJsStrings?: string[] };
export type IfLisp = DefineLisp<LispType.If, Lisp, IfCase>;
export type InlineIf = DefineLisp<LispType.InlineIf, Lisp, InlineIfCase>;
export type StatementLabel = string | LispType.None;
export type IfCase = DefineLisp<LispType.IfCase, Lisp[], Lisp[]>;
export type InlineIfCase = DefineLisp<LispType.InlineIfCase, Lisp, Lisp>;
export type Labeled = DefineLisp<LispType.Labeled, StatementLabel, Lisp>;
export type KeyValLisp = DefineLisp<LispType.KeyVal, string | Lisp, Lisp>;
export type SpreadObjectLisp = DefineLisp<LispType.SpreadObject, LispType.None, Lisp>;
export type SpreadArrayLisp = DefineLisp<LispType.SpreadArray, LispType.None, Lisp>;
export type ArrayProp = DefineLisp<LispType.ArrayProp, Lisp, Lisp>;
export type PropLisp = DefineLisp<LispType.Prop, Lisp, str
Download .txt
gitextract_tkgsxi77/

├── .github/
│   └── workflows/
│       ├── deploy.yml
│       ├── npm-publish.yml
│       ├── shields.yml
│       └── test.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── TODO.md
├── css/
│   └── style.css
├── eslint.config.js
├── index.html
├── jest.config.js
├── package.json
├── scripts/
│   ├── build.mjs
│   └── export-tests.ts
├── src/
│   ├── Sandbox.ts
│   ├── SandboxExec.ts
│   ├── eval/
│   │   └── index.ts
│   ├── executor/
│   │   ├── executorUtils.ts
│   │   ├── index.ts
│   │   ├── ops/
│   │   │   ├── assignment.ts
│   │   │   ├── call.ts
│   │   │   ├── comparison.ts
│   │   │   ├── control.ts
│   │   │   ├── functions.ts
│   │   │   ├── index.ts
│   │   │   ├── literals.ts
│   │   │   ├── misc.ts
│   │   │   ├── object.ts
│   │   │   ├── prop.ts
│   │   │   ├── unary.ts
│   │   │   └── variables.ts
│   │   └── opsRegistry.ts
│   ├── parser/
│   │   ├── index.ts
│   │   ├── lisp.ts
│   │   ├── lispTypes/
│   │   │   ├── conditionals.ts
│   │   │   ├── control.ts
│   │   │   ├── declarations.ts
│   │   │   ├── index.ts
│   │   │   ├── operators.ts
│   │   │   ├── shared.ts
│   │   │   ├── structures.ts
│   │   │   └── values.ts
│   │   └── parserUtils.ts
│   └── utils/
│       ├── CodeString.ts
│       ├── ExecContext.ts
│       ├── Prop.ts
│       ├── Scope.ts
│       ├── errors.ts
│       ├── functionReplacements.ts
│       ├── index.ts
│       ├── types.ts
│       └── unraw.ts
├── test/
│   ├── audit.spec.ts
│   ├── compileRerun.spec.ts
│   ├── delaySynchronousResult.spec.ts
│   ├── eval/
│   │   ├── README.md
│   │   ├── reveseLinkedList.js
│   │   ├── script.js
│   │   ├── testCases/
│   │   │   ├── arithmetic-operators.data.ts
│   │   │   ├── arithmetic-operators.spec.ts
│   │   │   ├── assignment-operators.data.ts
│   │   │   ├── assignment-operators.spec.ts
│   │   │   ├── bitwise-operators.data.ts
│   │   │   ├── bitwise-operators.spec.ts
│   │   │   ├── comments.data.ts
│   │   │   ├── comments.spec.ts
│   │   │   ├── comparison-operators.data.ts
│   │   │   ├── comparison-operators.spec.ts
│   │   │   ├── complex-expressions.data.ts
│   │   │   ├── complex-expressions.spec.ts
│   │   │   ├── conditionals.data.ts
│   │   │   ├── conditionals.spec.ts
│   │   │   ├── data-types.data.ts
│   │   │   ├── data-types.spec.ts
│   │   │   ├── defaults.data.ts
│   │   │   ├── defaults.spec.ts
│   │   │   ├── destructuring.data.ts
│   │   │   ├── destructuring.spec.ts
│   │   │   ├── error-handling.data.ts
│   │   │   ├── error-handling.spec.ts
│   │   │   ├── function-replacements.data.ts
│   │   │   ├── function-replacements.spec.ts
│   │   │   ├── functions.data.ts
│   │   │   ├── functions.spec.ts
│   │   │   ├── generators.data.ts
│   │   │   ├── generators.spec.ts
│   │   │   ├── index.ts
│   │   │   ├── logical-operators.data.ts
│   │   │   ├── logical-operators.spec.ts
│   │   │   ├── loops.data.ts
│   │   │   ├── loops.spec.ts
│   │   │   ├── objects-and-arrays.data.ts
│   │   │   ├── objects-and-arrays.spec.ts
│   │   │   ├── operator-precedence.data.ts
│   │   │   ├── operator-precedence.spec.ts
│   │   │   ├── other-operators.data.ts
│   │   │   ├── other-operators.spec.ts
│   │   │   ├── security.data.ts
│   │   │   ├── security.spec.ts
│   │   │   ├── switch.data.ts
│   │   │   ├── switch.spec.ts
│   │   │   ├── syntax-errors.data.ts
│   │   │   ├── syntax-errors.spec.ts
│   │   │   ├── template-literals.data.ts
│   │   │   ├── template-literals.spec.ts
│   │   │   ├── test-utils.ts
│   │   │   └── types.ts
│   │   └── tests.json
│   ├── evalCompletionValue.spec.ts
│   ├── expression.spec.ts
│   ├── parse.spec.ts
│   ├── performance.mjs
│   ├── sandboxErrorCatch.spec.ts
│   ├── sandboxRestrictions.spec.ts
│   ├── semicolonInsertion.spec.ts
│   ├── subscriptions.spec.ts
│   ├── symbol.spec.ts
│   ├── taggedTemplateEscaping.spec.ts
│   ├── ticks/
│   │   ├── sandboxArrayTicks.spec.ts
│   │   ├── sandboxCollectionTicks.spec.ts
│   │   ├── sandboxNativeTicks.spec.ts
│   │   ├── sandboxObjectTicks.spec.ts
│   │   ├── sandboxSpreadTicks.spec.ts
│   │   └── sandboxStringTicks.spec.ts
│   ├── ticksQuotaHalt.spec.ts
│   ├── timers.spec.ts
│   ├── timersAsync.spec.ts
│   ├── timersAsyncHalt.spec.ts
│   ├── timersHalt.spec.ts
│   ├── tryFinallyControlFlow.spec.ts
│   ├── tsconfig.json
│   └── unraw.spec.ts
├── tsconfig.jest.json
└── tsconfig.json
Download .txt
SYMBOL INDEX (408 symbols across 38 files)

FILE: scripts/build.mjs
  function writeModuleTypeManifest (line 31) | function writeModuleTypeManifest(outDir, type) {

FILE: scripts/export-tests.ts
  function extractTests (line 11) | async function extractTests() {

FILE: src/Sandbox.ts
  class Sandbox (line 23) | class Sandbox extends SandboxExec {
    method constructor (line 24) | constructor(options?: IOptionParams) {
    method audit (line 28) | static audit<T>(code: string, scopes: IScope[] = []): ExecReturn<T> {
    method parse (line 47) | static parse(code: string) {
    method Function (line 51) | get Function() {
    method AsyncFunction (line 69) | get AsyncFunction() {
    method eval (line 87) | get eval() {
    method compile (line 105) | compile<T>(
    method compileAsync (line 122) | compileAsync<T>(
    method compileExpression (line 138) | compileExpression<T>(
    method compileExpressionAsync (line 151) | compileExpressionAsync<T>(

FILE: src/SandboxExec.ts
  function subscribeSet (line 16) | function subscribeSet(
  class SandboxExec (line 49) | class SandboxExec {
    method constructor (line 80) | constructor(
    method SAFE_GLOBALS (line 104) | static get SAFE_GLOBALS(): IGlobals {
    method SAFE_SYMBOLS (line 163) | static get SAFE_SYMBOLS(): ISymbolWhitelist {
    method SAFE_PROTOTYPES (line 182) | static get SAFE_PROTOTYPES(): Map<any, Set<string>> {
    method subscribeGet (line 236) | subscribeGet(
    method subscribeSet (line 244) | subscribeSet(
    method subscribeSetGlobal (line 253) | subscribeSetGlobal(
    method subscribeHalt (line 261) | subscribeHalt(cb: (context: HaltContext) => void) {
    method subscribeResume (line 269) | subscribeResume(cb: () => void) {
    method haltExecution (line 278) | haltExecution(haltContext: HaltContext = { type: 'manual' }) {
    method resumeExecution (line 286) | resumeExecution() {
    method getContext (line 300) | getContext(fn: (...args: any[]) => any) {
    method executeTree (line 304) | executeTree<T>(context: IExecContext, scopes: IScope[] = []): ExecRetu...
    method executeTreeAsync (line 308) | executeTreeAsync<T>(context: IExecContext, scopes: IScope[] = []): Pro...

FILE: src/eval/index.ts
  type IEvalContext (line 12) | interface IEvalContext {
  type SandboxFunction (line 25) | type SandboxFunction = (code: string, ...args: string[]) => () => unknown;
  type SandboxEval (line 26) | type SandboxEval = (code: string) => unknown;
  type SandboxSetTimeout (line 27) | type SandboxSetTimeout = (
  type SandboxSetInterval (line 32) | type SandboxSetInterval = (
  type SandboxClearTimeout (line 37) | type SandboxClearTimeout = (handle: number) => void;
  type SandboxClearInterval (line 38) | type SandboxClearInterval = (handle: number) => void;
  function createEvalContext (line 40) | function createEvalContext(): IEvalContext {
  function sandboxedSymbol (line 56) | function sandboxedSymbol(context: IExecContext) {
  function SB (line 60) | function SB() {}
  function sandboxFunction (line 61) | function sandboxFunction(context: IExecContext): SandboxFunction {
  type SandboxAsyncFunction (line 82) | type SandboxAsyncFunction = (code: string, ...args: string[]) => () => P...
  function SAF (line 83) | function SAF() {}
  function sandboxAsyncFunction (line 84) | function sandboxAsyncFunction(context: IExecContext): SandboxAsyncFuncti...
  type SandboxGeneratorFunction (line 105) | type SandboxGeneratorFunction = (
  function SGF (line 109) | function SGF() {}
  function sandboxGeneratorFunction (line 110) | function sandboxGeneratorFunction(context: IExecContext): SandboxGenerat...
  type SandboxAsyncGeneratorFunction (line 131) | type SandboxAsyncGeneratorFunction = (
  function SAGF (line 135) | function SAGF() {}
  function sandboxAsyncGeneratorFunction (line 136) | function sandboxAsyncGeneratorFunction(
  function SE (line 159) | function SE() {}
  function sandboxedEval (line 160) | function sandboxedEval(func: SandboxFunction, context: IExecContext): Sa...
  function wrapLastStatementInReturn (line 183) | function wrapLastStatementInReturn(tree: Lisp[]): Lisp[] {
  function sST (line 228) | function sST() {}
  function sandboxedSetTimeout (line 229) | function sandboxedSetTimeout(
  function sCT (line 274) | function sCT() {}
  function sandboxedClearTimeout (line 275) | function sandboxedClearTimeout(context: IExecContext): SandboxClearTimeo...
  function sCI (line 289) | function sCI() {}
  function sandboxedClearInterval (line 290) | function sandboxedClearInterval(context: IExecContext): SandboxClearInte...
  function sSI (line 306) | function sSI() {}
  function sandboxedSetInterval (line 307) | function sandboxedSetInterval(

FILE: src/executor/executorUtils.ts
  type Done (line 22) | type Done<T = any> = (err?: any, res?: T | typeof optional) => void;
  type ControlFlowAction (line 24) | type ControlFlowAction = 'break' | 'continue';
  type ControlFlowSignal (line 26) | interface ControlFlowSignal {
  type ControlFlowTarget (line 31) | interface ControlFlowTarget {
  type ControlFlowTargets (line 39) | type ControlFlowTargets = readonly ControlFlowTarget[] | undefined;
  class ExecReturn (line 41) | class ExecReturn<T> {
    method constructor (line 42) | constructor(
    method breakLoop (line 49) | get breakLoop() {
    method continueLoop (line 53) | get continueLoop() {
  type IChange (line 58) | interface IChange {
  type ICreate (line 62) | interface ICreate extends IChange {
  type IReplace (line 67) | interface IReplace extends IChange {
  type IDelete (line 71) | interface IDelete extends IChange {
  type IReverse (line 76) | interface IReverse extends IChange {
  type ISort (line 80) | interface ISort extends IChange {
  type IPush (line 84) | interface IPush extends IChange {
  type IPop (line 89) | interface IPop extends IChange {
  type IShift (line 94) | interface IShift extends IChange {
  type IUnShift (line 99) | interface IUnShift extends IChange {
  type ISplice (line 104) | interface ISplice extends IChange {
  type ICopyWithin (line 112) | interface ICopyWithin extends IChange {
  type Change (line 120) | type Change =
  function normalizeStatementLabel (line 135) | function normalizeStatementLabel(label: StatementLabel | undefined) {
  function normalizeStatementLabels (line 139) | function normalizeStatementLabels(label: LispItem | StatementLabel | und...
  function createLoopTarget (line 147) | function createLoopTarget(label?: string, acceptsUnlabeled = true): Cont...
  function createSwitchTarget (line 157) | function createSwitchTarget(label?: string): ControlFlowTarget {
  function createLabeledStatementTarget (line 167) | function createLabeledStatementTarget(label?: string): ControlFlowTarget...
  function addControlFlowTarget (line 178) | function addControlFlowTarget(
  function addControlFlowTargets (line 186) | function addControlFlowTargets(
  function matchesControlFlowTarget (line 196) | function matchesControlFlowTarget(signal: ControlFlowSignal, target: Con...
  function findControlFlowTarget (line 205) | function findControlFlowTarget(
  function generateArgs (line 227) | function generateArgs(argNames: string[], args: unknown[]) {
  function createFunction (line 239) | function createFunction(
  function createFunctionAsync (line 284) | function createFunctionAsync(
  class YieldValue (line 333) | class YieldValue {
    method constructor (line 334) | constructor(
  function asIterableIterator (line 344) | function asIterableIterator(value: unknown): Iterator<unknown> & Iterabl...
  function asAsyncIterableIterator (line 367) | function asAsyncIterableIterator(value: unknown): AsyncIterator<unknown>...
  function createGeneratorFunction (line 706) | function createGeneratorFunction(
  function createAsyncGeneratorFunction (line 769) | function createAsyncGeneratorFunction(
  function assignCheck (line 854) | function assignCheck(obj: Prop, context: IExecContext, op = 'assign') {
  class KeyVal (line 923) | class KeyVal {
    method constructor (line 924) | constructor(
  class SpreadObject (line 930) | class SpreadObject {
    method constructor (line 931) | constructor(public item: { [key: string]: unknown }) {}
  class SpreadArray (line 934) | class SpreadArray {
    method constructor (line 935) | constructor(public item: unknown[]) {}
  class ArrayHole (line 938) | class ArrayHole {}
  class If (line 940) | class If {
    method constructor (line 941) | constructor(
  function isPropertyKey (line 955) | function isPropertyKey(val: unknown): val is PropertyKey {
  function hasPossibleProperties (line 959) | function hasPossibleProperties(val: unknown): val is {} {
  function execMany (line 965) | function execMany(
  function _execManySync (line 992) | function _execManySync(
  function _execManyAsync (line 1020) | async function _execManyAsync(
  type AsyncDoneRet (line 1058) | interface AsyncDoneRet {
  function asyncDone (line 1064) | function asyncDone(callback: (done: Done) => void): AsyncDoneRet {
  function syncDone (line 1084) | function syncDone(callback: (done: Done) => void): { result: any } {
  function execAsync (line 1095) | async function execAsync<T = any>(
  function execSync (line 1210) | function execSync<T = any>(
  type OpsCallbackParams (line 1285) | type OpsCallbackParams<a, b, obj, bobj> = {
  function checkHaltExpectedTicks (line 1302) | function checkHaltExpectedTicks(
  function performOp (line 1346) | function performOp(params: OpsCallbackParams<any, any, any, any>, count ...
  function _execNoneRecurse (line 1395) | function _execNoneRecurse<T = any>(
  function executeTree (line 1546) | function executeTree<T>(
  function executeTreeAsync (line 1570) | async function executeTreeAsync<T>(
  function executeTreeWithDone (line 1597) | function executeTreeWithDone(
  function _executeWithDoneSync (line 1656) | function _executeWithDoneSync(
  function _executeWithDoneAsync (line 1705) | async function _executeWithDoneAsync(

FILE: src/executor/ops/call.ts
  function getNewTicks (line 183) | function getNewTicks(ctor: Function, args: unknown[]): bigint {

FILE: src/executor/opsRegistry.ts
  type ControlFlowAction (line 14) | type ControlFlowAction = 'break' | 'continue';
  type ControlFlowTarget (line 16) | interface ControlFlowTarget {
  type ControlFlowTargets (line 24) | type ControlFlowTargets = readonly ControlFlowTarget[] | undefined;
  type Execution (line 26) | type Execution = <T = any>(
  type OpsCallbackParams (line 37) | type OpsCallbackParams<a, b, obj, bobj> = {
  type OpCallback (line 54) | type OpCallback<a, b, obj, bobj> = (params: OpsCallbackParams<a, b, obj,...
  function addOps (line 58) | function addOps<a = unknown, b = unknown, obj = unknown, bobj = unknown>(

FILE: src/parser/lisp.ts
  type DefineLisp (line 3) | type DefineLisp<
  type ExtractLispOp (line 9) | type ExtractLispOp<L> = L extends DefineLisp<infer i, any, any> ? i : ne...
  type ExtractLispA (line 10) | type ExtractLispA<L> = L extends DefineLisp<any, infer i, any> ? i : never;
  type ExtractLispB (line 11) | type ExtractLispB<L> = L extends DefineLisp<any, any, infer i> ? i : never;
  type LispItemSingle (line 13) | type LispItemSingle = LispType.None | LispType.True | string | Lisp;
  type LispItem (line 14) | type LispItem = LispItemSingle | LispItemSingle[];
  type Lisp (line 15) | type Lisp = [LispType, LispItem, LispItem];
  type Literal (line 17) | type Literal = DefineLisp<LispType.Literal, string, Lisp[]> & { tempJsSt...
  type IfLisp (line 18) | type IfLisp = DefineLisp<LispType.If, Lisp, IfCase>;
  type InlineIf (line 19) | type InlineIf = DefineLisp<LispType.InlineIf, Lisp, InlineIfCase>;
  type StatementLabel (line 20) | type StatementLabel = string | LispType.None;
  type IfCase (line 21) | type IfCase = DefineLisp<LispType.IfCase, Lisp[], Lisp[]>;
  type InlineIfCase (line 22) | type InlineIfCase = DefineLisp<LispType.InlineIfCase, Lisp, Lisp>;
  type Labeled (line 23) | type Labeled = DefineLisp<LispType.Labeled, StatementLabel, Lisp>;
  type KeyValLisp (line 24) | type KeyValLisp = DefineLisp<LispType.KeyVal, string | Lisp, Lisp>;
  type SpreadObjectLisp (line 25) | type SpreadObjectLisp = DefineLisp<LispType.SpreadObject, LispType.None,...
  type SpreadArrayLisp (line 26) | type SpreadArrayLisp = DefineLisp<LispType.SpreadArray, LispType.None, L...
  type ArrayProp (line 27) | type ArrayProp = DefineLisp<LispType.ArrayProp, Lisp, Lisp>;
  type PropLisp (line 28) | type PropLisp = DefineLisp<LispType.Prop, Lisp, string | Lisp>;
  type PropOptional (line 29) | type PropOptional = DefineLisp<LispType.PropOptional, Lisp, Lisp[]>;
  type Call (line 30) | type Call = DefineLisp<LispType.Call, Lisp, Lisp[]>;
  type CallOptional (line 31) | type CallOptional = DefineLisp<LispType.CallOptional, Lisp, Lisp[]>;
  type CreateArray (line 32) | type CreateArray = DefineLisp<LispType.CreateArray, Lisp, Lisp[]>;
  type CreateObject (line 33) | type CreateObject = DefineLisp<LispType.CreateObject, Lisp, Lisp[]>;
  type Group (line 34) | type Group = DefineLisp<LispType.Group, Lisp, Lisp[]>;
  type Inverse (line 35) | type Inverse = DefineLisp<LispType.Inverse, Lisp, Lisp>;
  type Not (line 36) | type Not = DefineLisp<LispType.Not, Lisp, Lisp>;
  type Negative (line 37) | type Negative = DefineLisp<LispType.Negative, Lisp, Lisp>;
  type Positive (line 38) | type Positive = DefineLisp<LispType.Positive, Lisp, Lisp>;
  type Typeof (line 39) | type Typeof = DefineLisp<LispType.Typeof, Lisp, Lisp>;
  type Delete (line 40) | type Delete = DefineLisp<LispType.Delete, Lisp, Lisp>;
  type IncrementBefore (line 41) | type IncrementBefore = DefineLisp<LispType.IncrementBefore, Lisp, LispTy...
  type IncrementAfter (line 42) | type IncrementAfter = DefineLisp<LispType.IncrementAfter, Lisp, LispType...
  type DecrementBefore (line 43) | type DecrementBefore = DefineLisp<LispType.DecrementBefore, Lisp, LispTy...
  type DecrementAfter (line 44) | type DecrementAfter = DefineLisp<LispType.DecrementAfter, Lisp, LispType...
  type And (line 46) | type And = DefineLisp<LispType.And, Lisp, Lisp>;
  type Or (line 47) | type Or = DefineLisp<LispType.Or, Lisp, Lisp>;
  type NullishCoalescing (line 48) | type NullishCoalescing = DefineLisp<LispType.NullishCoalescing, Lisp, Li...
  type Instanceof (line 49) | type Instanceof = DefineLisp<LispType.Instanceof, Lisp, Lisp>;
  type In (line 50) | type In = DefineLisp<LispType.In, Lisp, Lisp>;
  type Assigns (line 51) | type Assigns = DefineLisp<LispType.Assign, Lisp, Lisp>;
  type SubractEquals (line 52) | type SubractEquals = DefineLisp<LispType.SubractEquals, Lisp, Lisp>;
  type AddEquals (line 53) | type AddEquals = DefineLisp<LispType.AddEquals, Lisp, Lisp>;
  type DivideEquals (line 54) | type DivideEquals = DefineLisp<LispType.DivideEquals, Lisp, Lisp>;
  type PowerEquals (line 55) | type PowerEquals = DefineLisp<LispType.PowerEquals, Lisp, Lisp>;
  type MultiplyEquals (line 56) | type MultiplyEquals = DefineLisp<LispType.MultiplyEquals, Lisp, Lisp>;
  type ModulusEquals (line 57) | type ModulusEquals = DefineLisp<LispType.ModulusEquals, Lisp, Lisp>;
  type BitNegateEquals (line 58) | type BitNegateEquals = DefineLisp<LispType.BitNegateEquals, Lisp, Lisp>;
  type BitAndEquals (line 59) | type BitAndEquals = DefineLisp<LispType.BitAndEquals, Lisp, Lisp>;
  type BitOrEquals (line 60) | type BitOrEquals = DefineLisp<LispType.BitOrEquals, Lisp, Lisp>;
  type UnsignedShiftRightEquals (line 61) | type UnsignedShiftRightEquals = DefineLisp<LispType.UnsignedShiftRightEq...
  type ShiftLeftEquals (line 62) | type ShiftLeftEquals = DefineLisp<LispType.ShiftLeftEquals, Lisp, Lisp>;
  type ShiftRightEquals (line 63) | type ShiftRightEquals = DefineLisp<LispType.ShiftRightEquals, Lisp, Lisp>;
  type AndEquals (line 64) | type AndEquals = DefineLisp<LispType.AndEquals, Lisp, Lisp>;
  type OrEquals (line 65) | type OrEquals = DefineLisp<LispType.OrEquals, Lisp, Lisp>;
  type NullishCoalescingEquals (line 66) | type NullishCoalescingEquals = DefineLisp<LispType.NullishCoalescingEqua...
  type BitAnd (line 68) | type BitAnd = DefineLisp<LispType.BitAnd, Lisp, Lisp>;
  type BitOr (line 69) | type BitOr = DefineLisp<LispType.BitOr, Lisp, Lisp>;
  type BitNegate (line 70) | type BitNegate = DefineLisp<LispType.BitNegate, Lisp, Lisp>;
  type BitShiftLeft (line 71) | type BitShiftLeft = DefineLisp<LispType.BitShiftLeft, Lisp, Lisp>;
  type BitShiftRight (line 72) | type BitShiftRight = DefineLisp<LispType.BitShiftRight, Lisp, Lisp>;
  type BitUnsignedShiftRight (line 73) | type BitUnsignedShiftRight = DefineLisp<LispType.BitUnsignedShiftRight, ...
  type SmallerEqualThan (line 74) | type SmallerEqualThan = DefineLisp<LispType.SmallerEqualThan, Lisp, Lisp>;
  type LargerEqualThan (line 75) | type LargerEqualThan = DefineLisp<LispType.LargerEqualThan, Lisp, Lisp>;
  type SmallerThan (line 76) | type SmallerThan = DefineLisp<LispType.SmallerThan, Lisp, Lisp>;
  type LargerThan (line 77) | type LargerThan = DefineLisp<LispType.LargerThan, Lisp, Lisp>;
  type StrictNotEqual (line 78) | type StrictNotEqual = DefineLisp<LispType.StrictNotEqual, Lisp, Lisp>;
  type NotEqual (line 79) | type NotEqual = DefineLisp<LispType.NotEqual, Lisp, Lisp>;
  type StrictEqual (line 80) | type StrictEqual = DefineLisp<LispType.StrictEqual, Lisp, Lisp>;
  type Equal (line 81) | type Equal = DefineLisp<LispType.Equal, Lisp, Lisp>;
  type Plus (line 82) | type Plus = DefineLisp<LispType.Plus, Lisp, Lisp>;
  type Minus (line 83) | type Minus = DefineLisp<LispType.Minus, Lisp, Lisp>;
  type Divide (line 84) | type Divide = DefineLisp<LispType.Divide, Lisp, Lisp>;
  type Power (line 85) | type Power = DefineLisp<LispType.Power, Lisp, Lisp>;
  type Multiply (line 86) | type Multiply = DefineLisp<LispType.Multiply, Lisp, Lisp>;
  type Modulus (line 87) | type Modulus = DefineLisp<LispType.Modulus, Lisp, Lisp>;
  type InternalCode (line 89) | type InternalCode = DefineLisp<LispType.InternalBlock, Lisp[], LispType....
  type Block (line 90) | type Block = DefineLisp<LispType.Block, Lisp[], LispType.None>;
  type Expression (line 91) | type Expression = DefineLisp<LispType.Expression, Lisp[], LispType.None>;
  type Return (line 92) | type Return = DefineLisp<LispType.Return, LispType.None, Lisp>;
  type Throw (line 93) | type Throw = DefineLisp<LispType.Throw, LispType.None, Lisp>;
  type Switch (line 94) | type Switch = DefineLisp<LispType.Switch, Lisp, SwitchCase[]>;
  type SwitchCase (line 95) | type SwitchCase = DefineLisp<LispType.SwitchCase, LispType.None | Lisp, ...
  type Var (line 96) | type Var = DefineLisp<LispType.Var, string, Lisp | LispType.None>;
  type Let (line 97) | type Let = DefineLisp<LispType.Let, string, Lisp | LispType.None>;
  type Const (line 98) | type Const = DefineLisp<LispType.Const, string, Lisp | LispType.None>;
  type Internal (line 99) | type Internal = DefineLisp<LispType.Internal, string, Lisp | LispType.No...
  type Number (line 101) | type Number = DefineLisp<LispType.Number, LispType.None, string>;
  type BigInt (line 102) | type BigInt = DefineLisp<LispType.BigInt, LispType.None, string>;
  type GlobalSymbol (line 103) | type GlobalSymbol = DefineLisp<LispType.GlobalSymbol, LispType.None, str...
  type LiteralIndex (line 104) | type LiteralIndex = DefineLisp<LispType.LiteralIndex, LispType.None, str...
  type StringIndex (line 105) | type StringIndex = DefineLisp<LispType.StringIndex, LispType.None, string>;
  type RegexIndex (line 106) | type RegexIndex = DefineLisp<LispType.RegexIndex, LispType.None, string>;
  type Function (line 108) | type Function = DefineLisp<
  type InlineFunction (line 113) | type InlineFunction = DefineLisp<LispType.InlineFunction, string[], stri...
  type ArrowFunction (line 114) | type ArrowFunction = DefineLisp<LispType.ArrowFunction, string[], string...
  type Loop (line 115) | type Loop = DefineLisp<LispType.Loop, LispItem, Lisp[]>;
  type LoopAction (line 116) | type LoopAction = DefineLisp<LispType.LoopAction, string, StatementLabel>;
  type Try (line 117) | type Try = DefineLisp<LispType.Try, Lisp[], LispItem>;
  type Void (line 119) | type Void = DefineLisp<LispType.Void, Lisp, LispType.None>;
  type Await (line 120) | type Await = DefineLisp<LispType.Await, Lisp, LispType.None>;
  type Yield (line 121) | type Yield = DefineLisp<LispType.Yield, Lisp, LispType.None>;
  type YieldDelegate (line 122) | type YieldDelegate = DefineLisp<LispType.YieldDelegate, Lisp, LispType.N...
  type New (line 123) | type New = DefineLisp<LispType.New, Lisp, Lisp[]>;
  type None (line 124) | type None = DefineLisp<LispType.None, LispType.None, LispType.None>;
  type LispFamily (line 126) | type LispFamily =
  type IRegEx (line 225) | interface IRegEx {
  type IConstants (line 231) | interface IConstants {
  type IExecutionTree (line 239) | interface IExecutionTree {
  type LispDepthCtx (line 244) | interface LispDepthCtx {
  type LispCallbackCtx (line 250) | interface LispCallbackCtx extends LispDepthCtx {
  type LispCallback (line 259) | type LispCallback<T> = (ctx: LispCallbackCtx & { type: T }) => any;

FILE: src/parser/lispTypes/conditionals.ts
  function registerConditionalLispTypes (line 5) | function registerConditionalLispTypes({

FILE: src/parser/lispTypes/control.ts
  function registerControlLispTypes (line 14) | function registerControlLispTypes({

FILE: src/parser/lispTypes/declarations.ts
  function registerDeclarationLispTypes (line 14) | function registerDeclarationLispTypes({

FILE: src/parser/lispTypes/index.ts
  function registerLispTypes (line 9) | function registerLispTypes(deps: RegisterLispTypesDeps) {

FILE: src/parser/lispTypes/operators.ts
  function registerOperatorLispTypes (line 67) | function registerOperatorLispTypes({

FILE: src/parser/lispTypes/shared.ts
  type RegisterLispTypesDeps (line 15) | interface RegisterLispTypesDeps {

FILE: src/parser/lispTypes/structures.ts
  function registerStructureLispTypes (line 19) | function registerStructureLispTypes({

FILE: src/parser/lispTypes/values.ts
  function registerValueLispTypes (line 18) | function registerValueLispTypes({

FILE: src/parser/parserUtils.ts
  type LispWithSource (line 26) | type LispWithSource = Lisp & { source?: string };
  function createLisp (line 28) | function createLisp<L extends Lisp>(obj: {
  function extractStatementLabels (line 40) | function extractStatementLabels(prefix = '') {
  function wrapLabeledStatement (line 44) | function wrapLabeledStatement<T extends Lisp>(labels: string[], statemen...
  class ParseError (line 58) | class ParseError extends Error {
    method constructor (line 59) | constructor(
  function testMultiple (line 221) | function testMultiple(str: string, tests: RegExp[]) {
  type restDetails (line 239) | interface restDetails {
  function restOfExp (line 247) | function restOfExp(
  function splitByCommasDestructure (line 477) | function splitByCommasDestructure(s: string): string[] {
  function findFirstAtTopLevel (line 496) | function findFirstAtTopLevel(s: string, ch: string): number {
  function findPatternEndIdx (line 507) | function findPatternEndIdx(s: string): number {
  function assertIdentifier (line 521) | function assertIdentifier(name: string): string {
  function expandDestructure (line 526) | function expandDestructure(keyword: string, patternStr: string, rhsStr: ...
  function getDestructurePatternSource (line 721) | function getDestructurePatternSource(tree: LispItem): string | null {
  function expandFunctionParamDestructure (line 733) | function expandFunctionParamDestructure(
  function lispify (line 775) | function lispify(
  function lispifyExpr (line 854) | function lispifyExpr(
  function lispifyReturnExpr (line 908) | function lispifyReturnExpr(constants: IConstants, str: CodeString) {
  function lispifyBlock (line 916) | function lispifyBlock(
  function lispifyFunction (line 970) | function lispifyFunction(
  function hoist (line 982) | function hoist(item: LispItem, res: Lisp[] = []): boolean {
  function insertSemicolons (line 1034) | function insertSemicolons(constants: IConstants, str: CodeString): CodeS...
  function checkRegex (line 1095) | function checkRegex(str: string): IRegEx | null {
  function extractConstants (line 1129) | function extractConstants(
  function parse (line 1247) | function parse(

FILE: src/utils/CodeString.ts
  class CodeString (line 1) | class CodeString {
    method constructor (line 5) | constructor(str: string | CodeString) {
    method substring (line 18) | substring(start: number, end?: number): CodeString {
    method length (line 40) | get length() {
    method char (line 45) | char(i: number) {
    method toString (line 50) | toString() {
    method trimStart (line 54) | trimStart() {
    method slice (line 63) | slice(start: number, end?: number) {
    method trim (line 83) | trim() {
    method valueOf (line 92) | valueOf() {

FILE: src/utils/ExecContext.ts
  class ExecContext (line 23) | class ExecContext implements IExecContext {
    method constructor (line 24) | constructor(
  function createSandboxSymbolContext (line 49) | function createSandboxSymbolContext(symbolWhitelist: ISymbolWhitelist): ...
  constant RESERVED_SYMBOL_PROPERTIES (line 57) | const RESERVED_SYMBOL_PROPERTIES = new Set(['length', 'name', 'prototype...
  function copyWhitelistedSymbols (line 59) | function copyWhitelistedSymbols(target: Function, symbolWhitelist: ISymb...
  function getSandboxSymbolCtor (line 69) | function getSandboxSymbolCtor(symbols: SandboxSymbolContext) {
  function SandboxGlobal (line 118) | function SandboxGlobal() {}
  type SandboxGlobalConstructor (line 119) | interface SandboxGlobalConstructor {
  function sandboxedGlobal (line 122) | function sandboxedGlobal(globals: ISandboxGlobal): SandboxGlobalConstruc...
  function createContext (line 132) | function createContext(sandbox: SandboxExec, options: IOptions): IContext {
  function createExecContext (line 161) | function createExecContext(
  function isLisp (line 237) | function isLisp<Type extends Lisp = Lisp>(item: LispItem | LispItem): it...

FILE: src/utils/Prop.ts
  class Prop (line 7) | class Prop<T = unknown> {
    method constructor (line 8) | constructor(
    method get (line 17) | get<T = unknown>(context: IExecContext): T {
  function hasOwnProperty (line 28) | function hasOwnProperty(obj: unknown, prop: PropertyKey): boolean {
  function getReplacementReceiver (line 32) | function getReplacementReceiver(fn: Function) {
  function resolveSandboxProp (line 36) | function resolveSandboxProp(val: unknown, context: IExecContext, prop?: ...
  function getReplacementValue (line 71) | function getReplacementValue(val: unknown, context: IExecContext, bindCo...
  function shouldBindReplacement (line 85) | function shouldBindReplacement(original: Function, bindContext: unknown,...
  function bindReplacement (line 95) | function bindReplacement(
  function redefineFunctionMetadata (line 123) | function redefineFunctionMetadata(

FILE: src/utils/Scope.ts
  function keysOnly (line 6) | function keysOnly(obj: unknown): Record<string, true> {
  type Unknown (line 14) | type Unknown = undefined | null | Record<string | number, unknown>;
  class Scope (line 16) | class Scope {
    method constructor (line 26) | constructor(parent: Scope | null, vars = {}, functionThis?: Unknown) {
    method get (line 36) | get(key: string, internal: boolean): Prop {
    method set (line 57) | set(key: string, val: unknown, internal: boolean) {
    method getWhereValScope (line 77) | getWhereValScope(key: string, isThis: boolean, internal: boolean): Sco...
    method getWhereVarScope (line 102) | getWhereVarScope(key: string, localScope: boolean, internal: boolean):...
    method declare (line 123) | declare(key: string, type: VarType, value: unknown, isGlobal: boolean,...
  class FunctionScope (line 165) | class FunctionScope implements IScope {}
  class LocalScope (line 167) | class LocalScope implements IScope {}
  class DelayedSynchronousResult (line 171) | class DelayedSynchronousResult {
    method constructor (line 173) | constructor(cb: () => unknown) {
  function delaySynchronousResult (line 178) | function delaySynchronousResult(cb: () => Promise<unknown>) {
  function sanitizeProp (line 182) | function sanitizeProp(
  function sanitizeScope (line 202) | function sanitizeScope(scope: IScope, context: IExecContext, cache = new...
  function sanitizeScopes (line 214) | function sanitizeScopes(

FILE: src/utils/errors.ts
  class SandboxError (line 3) | class SandboxError extends Error {}
  class SandboxExecutionQuotaExceededError (line 5) | class SandboxExecutionQuotaExceededError extends SandboxError {}
  class SandboxExecutionTreeError (line 7) | class SandboxExecutionTreeError extends SandboxError {}
  class SandboxCapabilityError (line 9) | class SandboxCapabilityError extends SandboxError {}
  class SandboxAccessError (line 11) | class SandboxAccessError extends SandboxError {}

FILE: src/utils/functionReplacements.ts
  function checkTicksAndThrow (line 8) | function checkTicksAndThrow(ctx: IExecContext, expectTicks: bigint): void {
  function isTypedArray (line 35) | function isTypedArray(obj: unknown): obj is { length: number } {
  type Factory (line 47) | type Factory = (ctx: IExecContext) => Function;
  function makeReplacement (line 49) | function makeReplacement(
  function arrayTicks (line 67) | function arrayTicks(
  function stringTicks (line 150) | function stringTicks(complexity: 'one' | 'n'): (thisArg: unknown, _args:...
  function mapTicks (line 211) | function mapTicks(complexity: 'one' | 'n'): (thisArg: unknown, _args: un...
  function setTicks (line 235) | function setTicks(complexity: 'one' | 'n'): (thisArg: unknown, _args: un...
  function typedArrayTicks (line 286) | function typedArrayTicks(
  function objectTicks (line 374) | function objectTicks(
  constant DEFAULT_FUNCTION_REPLACEMENTS (line 411) | const DEFAULT_FUNCTION_REPLACEMENTS = new Map<Function, Factory>();
  constant THIS_DEPENDENT_FUNCTION_REPLACEMENTS (line 412) | const THIS_DEPENDENT_FUNCTION_REPLACEMENTS = new Set<Function>();

FILE: src/utils/types.ts
  type IOptionParams (line 14) | interface IOptionParams {
  type IOptions (line 34) | interface IOptions {
  type IContext (line 51) | interface IContext {
  type IAuditReport (line 64) | interface IAuditReport {
  type Ticks (line 69) | interface Ticks {
  type SubscriptionSubject (line 75) | type SubscriptionSubject = object;
  type HaltContext (line 77) | type HaltContext =
  type IExecContext (line 100) | interface IExecContext extends IExecutionTree {
  type ISandboxGlobal (line 116) | interface ISandboxGlobal {
  type ISymbolWhitelist (line 120) | interface ISymbolWhitelist {
  type SandboxSymbolContext (line 124) | interface SandboxSymbolContext {
  type IGlobals (line 131) | type IGlobals = ISandboxGlobal;
  type IScope (line 133) | interface IScope {
  constant NON_BLOCKING_THRESHOLD (line 137) | const NON_BLOCKING_THRESHOLD = 50_000n;
  type VarType (line 181) | const enum VarType {
  type LispType (line 188) | const enum LispType {

FILE: src/utils/unraw.ts
  function parseHexToInt (line 9) | function parseHexToInt(hex: string): number {
  function validateAndParseHex (line 25) | function validateAndParseHex(hex: string, errorName: string, enforcedLen...
  function parseHexadecimalCode (line 41) | function parseHexadecimalCode(code: string): string {
  function parseUnicodeCode (line 56) | function parseUnicodeCode(code: string, surrogateCode?: string): string {
  function isCurlyBraced (line 72) | function isCurlyBraced(text: string): boolean {
  function parseUnicodeCodePointCode (line 84) | function parseUnicodeCodePointCode(codePoint: string): string {
  function parseSingleCharacterCode (line 117) | function parseSingleCharacterCode(code: string): string {
  function unraw (line 149) | function unraw(raw: string): string {

FILE: test/eval/reveseLinkedList.js
  function LinkedListNode (line 1) | function LinkedListNode(value) {
  function reverse (line 6) | function reverse(head) {
  function reverse (line 26) | function reverse(head) {

FILE: test/eval/script.js
  constant SANDBOX_HASH_KEY (line 13) | const SANDBOX_HASH_KEY = 'sandbox';
  constant SANDBOX_SETTINGS_HASH_KEY (line 14) | const SANDBOX_SETTINGS_HASH_KEY = 'settings';
  class TestError (line 584) | class TestError {
    method constructor (line 585) | constructor(error) { this.error = error; }
  method has (line 750) | has(target, key, context) {
  function renderPerf (line 890) | function renderPerf(cn, cs, en, es) {

FILE: test/eval/testCases/syntax-errors.data.ts
  function toEvalTest (line 4) | function toEvalTest(code: string, safeExpect: string): TestCase {

FILE: test/eval/testCases/test-utils.ts
  class TestError (line 9) | class TestError {
    method constructor (line 10) | constructor(public error: Error | null | undefined) {}
  function run (line 13) | async function run(test: TestCase, state: any, isAsync: boolean) {
  function getState (line 47) | function getState() {

FILE: test/eval/testCases/types.ts
  type TestCase (line 1) | interface TestCase {

FILE: test/symbol.spec.ts
  function createScope (line 4) | function createScope(vars: Record<string, unknown> = {}) {

FILE: test/ticks/sandboxArrayTicks.spec.ts
  function haltsWithQuota (line 4) | function haltsWithQuota(

FILE: test/ticks/sandboxCollectionTicks.spec.ts
  function haltsWithQuota (line 4) | function haltsWithQuota(
  function haltsWithQuota (line 89) | function haltsWithQuota(
  function FakeTypedArrayCtor (line 187) | function FakeTypedArrayCtor() {}
  function haltsWithQuota (line 195) | function haltsWithQuota(

FILE: test/ticks/sandboxNativeTicks.spec.ts
  function haltsWithQuota (line 4) | function haltsWithQuota(
  function haltsWithQuota (line 39) | function haltsWithQuota(
  function haltsWithQuota (line 69) | function haltsWithQuota(
  function haltsWithQuota (line 99) | function haltsWithQuota(
  function haltsWithQuota (line 135) | function haltsWithQuota(

FILE: test/ticks/sandboxObjectTicks.spec.ts
  function haltsWithQuota (line 4) | function haltsWithQuota(
  function haltsWithQuota (line 64) | function haltsWithQuota(
  function run (line 118) | function run(code: string, scope: Record<string, unknown> = {}) {

FILE: test/ticks/sandboxSpreadTicks.spec.ts
  function haltsWithQuota (line 4) | function haltsWithQuota(

FILE: test/ticks/sandboxStringTicks.spec.ts
  function haltsWithQuota (line 4) | function haltsWithQuota(
  function haltsWithQuota (line 84) | function haltsWithQuota(
  function haltsWithQuota (line 139) | function haltsWithQuota(
  function haltsWithQuota (line 184) | function haltsWithQuota(
  function haltsWithQuota (line 232) | function haltsWithQuota(
Condensed preview — 140 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (829K chars).
[
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 869,
    "preview": "name: Deploy to GitHub Pages\r\n\r\non:\r\n  push:\r\n    branches:\r\n      - main\r\n\r\njobs:\r\n  build-and-deploy:\r\n    runs-on: ub"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "chars": 973,
    "preview": "# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created\n# For "
  },
  {
    "path": ".github/workflows/shields.yml",
    "chars": 3444,
    "preview": "name: Shield badges\n\non:\n  push:\n    branches: [main]\n\njobs:\n  bundle-size:\n    runs-on: ubuntu-latest\n    steps:\n      "
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1184,
    "preview": "name: Run Tests\r\n\r\non:\r\n  push:\r\n    branches:\r\n      - main\r\n  pull_request:\r\n    branches:\r\n      - '**'   # Optional:"
  },
  {
    "path": ".gitignore",
    "chars": 39,
    "preview": "node_modules/\r\ndist/\r\nbuild/\r\ncoverage/"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 16,
    "preview": "npx lint-staged\n"
  },
  {
    "path": ".npmignore",
    "chars": 206,
    "preview": "node_modules/\n.github/\n.gitignore\n.git/\n.vscode/\n.husky\n.claude\njs/\ntest/\ncss/\nsrc/\nindex.html\ntsconfig.json\nrollup.conf"
  },
  {
    "path": ".prettierignore",
    "chars": 209,
    "preview": "# Dependencies\nnode_modules\n\n# Build outputs\ndist\nbuild\ncoverage\n\n# Minified files\n*.min.js\n\n# Configuration files\nrollu"
  },
  {
    "path": ".prettierrc",
    "chars": 47,
    "preview": "{\n  \"printWidth\": 100,\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 89,
    "preview": "{\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"editor.formatOnSave\": true\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2019 nyariv\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 7181,
    "preview": "[![GitHub](https://img.shields.io/github/license/nyariv/SandboxJS)](https://github.com/nyariv/SandboxJS/blob/main/LICENS"
  },
  {
    "path": "TODO.md",
    "chars": 13176,
    "preview": "# SandboxJS - ECMAScript Feature Status\n\n[![codecov](https://codecov.io/gh/nyariv/SandboxJS/branch/main/graph/badge.svg)"
  },
  {
    "path": "css/style.css",
    "chars": 19421,
    "preview": ":root {\n  --bg: #0f1117;\n  --surface: #1a1d27;\n  --surface2: #22263a;\n  --border: #2e3248;\n  --accent: #4a9eff;\n  --acce"
  },
  {
    "path": "eslint.config.js",
    "chars": 2537,
    "preview": "const eslint = require('@eslint/js');\nconst tseslint = require('@typescript-eslint/eslint-plugin');\nconst tsparser = req"
  },
  {
    "path": "index.html",
    "chars": 7274,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  },
  {
    "path": "jest.config.js",
    "chars": 6874,
    "preview": "/**\n * For a detailed explanation regarding each configuration property, visit:\n * https://jestjs.io/docs/configuration\n"
  },
  {
    "path": "package.json",
    "chars": 2152,
    "preview": "{\n  \"name\": \"@nyariv/sandboxjs\",\n  \"version\": \"0.9.6\",\n  \"description\": \"Javascript sandboxing library.\",\n  \"main\": \"dis"
  },
  {
    "path": "scripts/build.mjs",
    "chars": 2749,
    "preview": "import { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { rmSync, writeFileSync } f"
  },
  {
    "path": "scripts/export-tests.ts",
    "chars": 2559,
    "preview": "import * as fs from 'fs';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\nimport { TestCase } from '."
  },
  {
    "path": "src/Sandbox.ts",
    "chars": 4815,
    "preview": "import { AsyncFunction, createExecContext, SandboxCapabilityError, sanitizeScopes } from './utils';\nimport type { IExecC"
  },
  {
    "path": "src/SandboxExec.ts",
    "chars": 7683,
    "preview": "import type { IEvalContext } from './eval';\nimport { Change, ExecReturn, executeTree, executeTreeAsync } from './executo"
  },
  {
    "path": "src/eval/index.ts",
    "chars": 10841,
    "preview": "import {\n  createAsyncGeneratorFunction,\n  createFunction,\n  createFunctionAsync,\n  createGeneratorFunction,\n} from '../"
  },
  {
    "path": "src/executor/executorUtils.ts",
    "chars": 47820,
    "preview": "import type { LispItem, Lisp, StatementLabel } from '../parser';\nimport {\n  hasOwnProperty,\n  isLisp,\n  LispType,\n  Loca"
  },
  {
    "path": "src/executor/index.ts",
    "chars": 33,
    "preview": "export * from './executorUtils';\n"
  },
  {
    "path": "src/executor/ops/assignment.ts",
    "chars": 3932,
    "preview": "import { addOps, assignCheck, checkHaltExpectedTicks } from '../executorUtils';\nimport { LispType, Prop } from '../../ut"
  },
  {
    "path": "src/executor/ops/call.ts",
    "chars": 7782,
    "preview": "import { addOps, arrayChange, Change, checkHaltExpectedTicks, SpreadArray } from '../executorUtils';\nimport {\n  checkTic"
  },
  {
    "path": "src/executor/ops/comparison.ts",
    "chars": 2550,
    "preview": "import { addOps, checkHaltExpectedTicks } from '../executorUtils';\nimport { LispType } from '../../utils';\n\naddOps<numbe"
  },
  {
    "path": "src/executor/ops/control.ts",
    "chars": 17072,
    "preview": "import {\n  addOps,\n  execAsync,\n  execSync,\n  asyncDone,\n  executeTreeWithDone,\n  normalizeStatementLabels,\n  addControl"
  },
  {
    "path": "src/executor/ops/functions.ts",
    "chars": 4051,
    "preview": "import {\n  addOps,\n  createAsyncGeneratorFunction,\n  createFunction,\n  createFunctionAsync,\n  createGeneratorFunction,\n}"
  },
  {
    "path": "src/executor/ops/index.ts",
    "chars": 219,
    "preview": "import './prop';\nimport './call';\nimport './object';\nimport './literals';\nimport './unary';\nimport './assignment';\nimpor"
  },
  {
    "path": "src/executor/ops/literals.ts",
    "chars": 2170,
    "preview": "import { addOps, literalRegex, checkHaltExpectedTicks } from '../executorUtils';\nimport type { Lisp, IRegEx } from '../."
  },
  {
    "path": "src/executor/ops/misc.ts",
    "chars": 1363,
    "preview": "import {\n  addOps,\n  findControlFlowTarget,\n  ExecReturn,\n  normalizeStatementLabel,\n} from '../executorUtils';\nimport t"
  },
  {
    "path": "src/executor/ops/object.ts",
    "chars": 2239,
    "preview": "import {\n  addOps,\n  checkHaltExpectedTicks,\n  SpreadArray,\n  SpreadObject,\n  KeyVal,\n  ArrayHole,\n} from '../executorUt"
  },
  {
    "path": "src/executor/ops/prop.ts",
    "chars": 4283,
    "preview": "import { addOps, hasPossibleProperties, isPropertyKey } from '../executorUtils';\nimport {\n  LispType,\n  Prop,\n  SandboxA"
  },
  {
    "path": "src/executor/ops/unary.ts",
    "chars": 1887,
    "preview": "import { addOps, assignCheck } from '../executorUtils';\nimport type { LispItem } from '../../parser';\nimport { LispType,"
  },
  {
    "path": "src/executor/ops/variables.ts",
    "chars": 738,
    "preview": "import { addOps } from '../executorUtils';\nimport { LispType, Prop, VarType } from '../../utils';\n\naddOps(LispType.Retur"
  },
  {
    "path": "src/executor/opsRegistry.ts",
    "chars": 1779,
    "preview": "/**\n * Ops registry — kept separate from executorUtils.ts so that ops/*.ts files\n * can import addOps without creating a"
  },
  {
    "path": "src/parser/index.ts",
    "chars": 96,
    "preview": "export * from './lisp';\nexport * from './parserUtils';\nexport { default } from './parserUtils';\n"
  },
  {
    "path": "src/parser/lisp.ts",
    "chars": 9923,
    "preview": "import type { CodeString, LispType } from '../utils';\n\nexport type DefineLisp<\n  op extends LispType,\n  a extends LispIt"
  },
  {
    "path": "src/parser/lispTypes/conditionals.ts",
    "chars": 6705,
    "preview": "import { CodeString, LispType, SandboxCapabilityError } from '../../utils';\nimport type { IConstants, IfLisp, IfCase, Li"
  },
  {
    "path": "src/parser/lispTypes/control.ts",
    "chars": 13091,
    "preview": "import { CodeString, LispType } from '../../utils';\nimport type {\n  Block,\n  InternalCode,\n  Lisp,\n  LispItem,\n  Loop,\n "
  },
  {
    "path": "src/parser/lispTypes/declarations.ts",
    "chars": 5987,
    "preview": "import { CodeString, LispType, reservedWords } from '../../utils';\nimport type {\n  ArrowFunction,\n  Const,\n  Function as"
  },
  {
    "path": "src/parser/lispTypes/index.ts",
    "chars": 739,
    "preview": "import type { RegisterLispTypesDeps } from './shared';\nimport { registerConditionalLispTypes } from './conditionals';\nim"
  },
  {
    "path": "src/parser/lispTypes/operators.ts",
    "chars": 12429,
    "preview": "import { CodeString, isLisp, LispType } from '../../utils';\nimport type {\n  AddEquals,\n  And,\n  AndEquals,\n  Assigns,\n  "
  },
  {
    "path": "src/parser/lispTypes/shared.ts",
    "chars": 2218,
    "preview": "import type { CodeString } from '../../utils';\nimport type {\n  ExtractLispA,\n  ExtractLispB,\n  ExtractLispOp,\n  IConstan"
  },
  {
    "path": "src/parser/lispTypes/structures.ts",
    "chars": 8701,
    "preview": "import { CodeString, isLisp, LispType, reservedWords } from '../../utils';\nimport type {\n  ArrayProp,\n  Call,\n  CallOpti"
  },
  {
    "path": "src/parser/lispTypes/values.ts",
    "chars": 4585,
    "preview": "import { LispType } from '../../utils';\nimport type {\n  Await,\n  BigInt as BigIntLisp,\n  GlobalSymbol,\n  LiteralIndex,\n "
  },
  {
    "path": "src/parser/parserUtils.ts",
    "chars": 38848,
    "preview": "import { unraw } from '../utils/unraw';\nimport { CodeString, isLisp, LispType, SandboxCapabilityError } from '../utils';"
  },
  {
    "path": "src/utils/CodeString.ts",
    "chars": 1922,
    "preview": "export class CodeString {\n  start: number;\n  end: number;\n  ref: { str: string };\n  constructor(str: string | CodeString"
  },
  {
    "path": "src/utils/ExecContext.ts",
    "chars": 8562,
    "preview": "import type { IEvalContext } from '../eval';\nimport type { Change } from '../executor';\nimport { DEFAULT_FUNCTION_REPLAC"
  },
  {
    "path": "src/utils/Prop.ts",
    "chars": 3891,
    "preview": "import type { IExecContext } from './types';\nimport { THIS_DEPENDENT_FUNCTION_REPLACEMENTS } from './functionReplacement"
  },
  {
    "path": "src/utils/Scope.ts",
    "chars": 6688,
    "preview": "import { reservedWords, VarType } from './types';\nimport { Prop, resolveSandboxProp, hasOwnProperty } from './Prop';\nimp"
  },
  {
    "path": "src/utils/errors.ts",
    "chars": 347,
    "preview": "import { IExecContext, IScope } from './types';\n\nexport class SandboxError extends Error {}\n\nexport class SandboxExecuti"
  },
  {
    "path": "src/utils/functionReplacements.ts",
    "chars": 15930,
    "preview": "import type { IContext, IExecContext } from './types';\nimport { SandboxExecutionQuotaExceededError } from './errors';\n\n/"
  },
  {
    "path": "src/utils/index.ts",
    "chars": 186,
    "preview": "export * from './unraw';\nexport * from './errors';\nexport * from './CodeString';\nexport * from './types';\nexport * from "
  },
  {
    "path": "src/utils/types.ts",
    "chars": 5873,
    "preview": "// Reusable AsyncFunction constructor references\nexport const AsyncFunction: Function = Object.getPrototypeOf(async func"
  },
  {
    "path": "src/utils/unraw.ts",
    "chars": 6658,
    "preview": "/**\n * Parse a string as a base-16 number. This is more strict than `parseInt` as it\n * will not allow any other charact"
  },
  {
    "path": "test/audit.spec.ts",
    "chars": 4873,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Sandbox.audit Tests', () => {\n  describe('Basic functionality', () ="
  },
  {
    "path": "test/compileRerun.spec.ts",
    "chars": 2813,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Compiled code rerun', () => {\n  it('should correctly rerun compiled "
  },
  {
    "path": "test/delaySynchronousResult.spec.ts",
    "chars": 3226,
    "preview": "import Sandbox, { delaySynchronousResult } from '../src/Sandbox.js';\nconst wait = (ms: number) => new Promise<void>((res"
  },
  {
    "path": "test/eval/README.md",
    "chars": 2042,
    "preview": "# Test Files Organization\n\nThis directory contains organized test files for the SandboxJS evaluation engine.\n\n## Structu"
  },
  {
    "path": "test/eval/reveseLinkedList.js",
    "chars": 552,
    "preview": "function LinkedListNode(value) {\n  this.value = value;\n  this.next = null;\n}\n\nfunction reverse(head) {\n  let node = head"
  },
  {
    "path": "test/eval/script.js",
    "chars": 36661,
    "preview": "// @ts-nocheck\nimport Sandbox, { LocalScope, SandboxExecutionQuotaExceededError } from '../../dist/esm/Sandbox.js';\nimpo"
  },
  {
    "path": "test/eval/testCases/arithmetic-operators.data.ts",
    "chars": 4364,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: '1+1',\n    evalEx"
  },
  {
    "path": "test/eval/testCases/arithmetic-operators.spec.ts",
    "chars": 576,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './arithmetic-operators.data.js';\n\n"
  },
  {
    "path": "test/eval/testCases/assignment-operators.data.ts",
    "chars": 5305,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'test.b = test2 -"
  },
  {
    "path": "test/eval/testCases/assignment-operators.spec.ts",
    "chars": 576,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './assignment-operators.data.js';\n\n"
  },
  {
    "path": "test/eval/testCases/bitwise-operators.data.ts",
    "chars": 2266,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: '~test2',\n    eva"
  },
  {
    "path": "test/eval/testCases/bitwise-operators.spec.ts",
    "chars": 570,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './bitwise-operators.data.js';\n\ndes"
  },
  {
    "path": "test/eval/testCases/comments.data.ts",
    "chars": 535,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: '/* 2 */ 1',\n    "
  },
  {
    "path": "test/eval/testCases/comments.spec.ts",
    "chars": 552,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './comments.data.js';\n\ndescribe('Co"
  },
  {
    "path": "test/eval/testCases/comparison-operators.data.ts",
    "chars": 1028,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: \"test2 == '1'\",\n "
  },
  {
    "path": "test/eval/testCases/comparison-operators.spec.ts",
    "chars": 576,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './comparison-operators.data.js';\n\n"
  },
  {
    "path": "test/eval/testCases/complex-expressions.data.ts",
    "chars": 2893,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'let a = null; le"
  },
  {
    "path": "test/eval/testCases/complex-expressions.spec.ts",
    "chars": 574,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './complex-expressions.data.js';\n\nd"
  },
  {
    "path": "test/eval/testCases/conditionals.data.ts",
    "chars": 1705,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: \"test[test2] ? tr"
  },
  {
    "path": "test/eval/testCases/conditionals.spec.ts",
    "chars": 560,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './conditionals.data.js';\n\ndescribe"
  },
  {
    "path": "test/eval/testCases/data-types.data.ts",
    "chars": 4272,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: '`${type}`',\n    "
  },
  {
    "path": "test/eval/testCases/data-types.spec.ts",
    "chars": 556,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './data-types.data.js';\n\ndescribe('"
  },
  {
    "path": "test/eval/testCases/defaults.data.ts",
    "chars": 5899,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  // Basic single default\n  {\n "
  },
  {
    "path": "test/eval/testCases/defaults.spec.ts",
    "chars": 568,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './defaults.data.js';\n\ndescribe('Pa"
  },
  {
    "path": "test/eval/testCases/destructuring.data.ts",
    "chars": 9177,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  // Array destructuring - basi"
  },
  {
    "path": "test/eval/testCases/destructuring.spec.ts",
    "chars": 562,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './destructuring.data.js';\n\ndescrib"
  },
  {
    "path": "test/eval/testCases/error-handling.data.ts",
    "chars": 5430,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: \"throw new Error("
  },
  {
    "path": "test/eval/testCases/error-handling.spec.ts",
    "chars": 564,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './error-handling.data.js';\n\ndescri"
  },
  {
    "path": "test/eval/testCases/function-replacements.data.ts",
    "chars": 1482,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: \"const list = [];"
  },
  {
    "path": "test/eval/testCases/function-replacements.spec.ts",
    "chars": 582,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './function-replacements.data.js';\n"
  },
  {
    "path": "test/eval/testCases/functions.data.ts",
    "chars": 2641,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: '((a) => {return "
  },
  {
    "path": "test/eval/testCases/functions.spec.ts",
    "chars": 554,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './functions.data.js';\n\ndescribe('F"
  },
  {
    "path": "test/eval/testCases/generators.data.ts",
    "chars": 16965,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  // Basic generator\n  {\n    co"
  },
  {
    "path": "test/eval/testCases/generators.spec.ts",
    "chars": 587,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests, asyncTests } from './generators.data.js';"
  },
  {
    "path": "test/eval/testCases/index.ts",
    "chars": 1417,
    "preview": "'use strict';\n\n// Import and re-export all test arrays\nexport { tests as arithmeticOperators } from './arithmetic-operat"
  },
  {
    "path": "test/eval/testCases/logical-operators.data.ts",
    "chars": 6375,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: '!test2',\n    eva"
  },
  {
    "path": "test/eval/testCases/logical-operators.spec.ts",
    "chars": 570,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './logical-operators.data.js';\n\ndes"
  },
  {
    "path": "test/eval/testCases/loops.data.ts",
    "chars": 7403,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'let x; for(let i"
  },
  {
    "path": "test/eval/testCases/loops.spec.ts",
    "chars": 546,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './loops.data.js';\n\ndescribe('Loops"
  },
  {
    "path": "test/eval/testCases/objects-and-arrays.data.ts",
    "chars": 7748,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'a.b.c',\n    eval"
  },
  {
    "path": "test/eval/testCases/objects-and-arrays.spec.ts",
    "chars": 572,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './objects-and-arrays.data.js';\n\nde"
  },
  {
    "path": "test/eval/testCases/operator-precedence.data.ts",
    "chars": 4192,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: \"test2 !== '1' &&"
  },
  {
    "path": "test/eval/testCases/operator-precedence.spec.ts",
    "chars": 574,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './operator-precedence.data.js';\n\nd"
  },
  {
    "path": "test/eval/testCases/other-operators.data.ts",
    "chars": 2150,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: \"typeof '1'\",\n   "
  },
  {
    "path": "test/eval/testCases/other-operators.spec.ts",
    "chars": 566,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './other-operators.data.js';\n\ndescr"
  },
  {
    "path": "test/eval/testCases/security.data.ts",
    "chars": 12081,
    "preview": "import { TestCase } from './types';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'globalThis.constructor.name',\n   "
  },
  {
    "path": "test/eval/testCases/security.spec.ts",
    "chars": 552,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './security.data.js';\n\ndescribe('Se"
  },
  {
    "path": "test/eval/testCases/switch.data.ts",
    "chars": 1182,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'let a = 1; let b"
  },
  {
    "path": "test/eval/testCases/switch.spec.ts",
    "chars": 548,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './switch.data.js';\n\ndescribe('Swit"
  },
  {
    "path": "test/eval/testCases/syntax-errors.data.ts",
    "chars": 5527,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nfunction toEvalTest(code: string, safeExpect: string): TestCase {\n"
  },
  {
    "path": "test/eval/testCases/syntax-errors.spec.ts",
    "chars": 568,
    "preview": "'use strict';\n\nimport { run, getState } from './test-utils.js';\nimport { tests } from './syntax-errors.data.js';\n\ndescri"
  },
  {
    "path": "test/eval/testCases/template-literals.data.ts",
    "chars": 2568,
    "preview": "'use strict';\nimport { TestCase } from './types.js';\n\nexport const tests: TestCase[] = [\n  {\n    code: 'const a = 1; con"
  },
  {
    "path": "test/eval/testCases/template-literals.spec.ts",
    "chars": 570,
    "preview": "'use strict';\nimport { run, getState } from './test-utils.js';\nimport { tests } from './template-literals.data.js';\n\ndes"
  },
  {
    "path": "test/eval/testCases/test-utils.ts",
    "chars": 1535,
    "preview": "'use strict';\nimport Sandbox, { LocalScope } from '../../../src/Sandbox.js';\nimport { TestCase } from './types.js';\n\ndec"
  },
  {
    "path": "test/eval/testCases/types.ts",
    "chars": 138,
    "preview": "export interface TestCase {\n  code: string;\n  evalExpect?: unknown;\n  safeExpect?: unknown;\n  category: string;\n  ignore"
  },
  {
    "path": "test/eval/tests.json",
    "chars": 90370,
    "preview": "[\n  {\n    \"code\": \"`${type}`\",\n    \"evalExpect\": \"eval\",\n    \"safeExpect\": \"Sandbox\",\n    \"category\": \"Data Types\"\n  },\n"
  },
  {
    "path": "test/evalCompletionValue.spec.ts",
    "chars": 8425,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('eval() Completion Value Tests', () => {\n  let sandbox: Sandbox;\n\n  b"
  },
  {
    "path": "test/expression.spec.ts",
    "chars": 10557,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Expression Compilation Tests', () => {\n  describe('compileExpression"
  },
  {
    "path": "test/parse.spec.ts",
    "chars": 433,
    "preview": "import { ParseError } from '../src/parser';\nimport Sandbox from '../src/Sandbox';\n\ndescribe('Sandbox.parse() tests', () "
  },
  {
    "path": "test/performance.mjs",
    "chars": 3420,
    "preview": "import { Sandbox } from '../dist/esm/Sandbox.js'\nimport { Bench } from 'tinybench';\nimport Table from 'cli-table3';\nimpo"
  },
  {
    "path": "test/sandboxErrorCatch.spec.ts",
    "chars": 6357,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('SandboxError catch behavior', () => {\n  describe('SandboxError is no"
  },
  {
    "path": "test/sandboxRestrictions.spec.ts",
    "chars": 12439,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Executor Edge Cases', () => {\n  describe('Function creation restrict"
  },
  {
    "path": "test/semicolonInsertion.spec.ts",
    "chars": 13537,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Semicolon Insertion Tests', () => {\n  beforeEach(() => {});\n\n  it('s"
  },
  {
    "path": "test/subscriptions.spec.ts",
    "chars": 22123,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Subscription Tests', () => {\n  describe('subscribeGet', () => {\n    "
  },
  {
    "path": "test/symbol.spec.ts",
    "chars": 3735,
    "preview": "'use strict';\nimport Sandbox, { LocalScope } from '../src/Sandbox.js';\n\nfunction createScope(vars: Record<string, unknow"
  },
  {
    "path": "test/taggedTemplateEscaping.spec.ts",
    "chars": 3517,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Tagged Template Escaping Tests', () => {\n  let sandbox: Sandbox;\n\n  "
  },
  {
    "path": "test/ticks/sandboxArrayTicks.spec.ts",
    "chars": 7727,
    "preview": "import Sandbox from '../../src/Sandbox.js';\n\ndescribe('array ticks', () => {\n  function haltsWithQuota(\n    code: string"
  },
  {
    "path": "test/ticks/sandboxCollectionTicks.spec.ts",
    "chars": 8546,
    "preview": "import Sandbox from '../../src/Sandbox.js';\n\ndescribe('Map ticks', () => {\n  function haltsWithQuota(\n    code: string,\n"
  },
  {
    "path": "test/ticks/sandboxNativeTicks.spec.ts",
    "chars": 7095,
    "preview": "import Sandbox from '../../src/Sandbox.js';\n\ndescribe('Math ticks', () => {\n  function haltsWithQuota(\n    code: string,"
  },
  {
    "path": "test/ticks/sandboxObjectTicks.spec.ts",
    "chars": 6515,
    "preview": "import Sandbox from '../../src/Sandbox.js';\n\ndescribe('object ticks', () => {\n  function haltsWithQuota(\n    code: strin"
  },
  {
    "path": "test/ticks/sandboxSpreadTicks.spec.ts",
    "chars": 3273,
    "preview": "import Sandbox from '../../src/Sandbox.js';\n\ndescribe('spread ticks', () => {\n  function haltsWithQuota(\n    code: strin"
  },
  {
    "path": "test/ticks/sandboxStringTicks.spec.ts",
    "chars": 9235,
    "preview": "import Sandbox from '../../src/Sandbox.js';\n\ndescribe('string method ticks', () => {\n  function haltsWithQuota(\n    code"
  },
  {
    "path": "test/ticksQuotaHalt.spec.ts",
    "chars": 7502,
    "preview": "import Sandbox from '../src/Sandbox';\nimport { SandboxExecutionQuotaExceededError } from '../src/utils';\n\ndescribe('tick"
  },
  {
    "path": "test/timers.spec.ts",
    "chars": 6542,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Timer Tests', () => {\n  describe('setTimeout', () => {\n    it('shoul"
  },
  {
    "path": "test/timersAsync.spec.ts",
    "chars": 1943,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Async Timer Tests', () => {\n  describe('setTimeout with async execut"
  },
  {
    "path": "test/timersAsyncHalt.spec.ts",
    "chars": 4195,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Async Halt and Resume Tests', () => {\n  describe('async execution wi"
  },
  {
    "path": "test/timersHalt.spec.ts",
    "chars": 3340,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Timer Halt and Resume Tests', () => {\n  describe('setTimeout with ha"
  },
  {
    "path": "test/tryFinallyControlFlow.spec.ts",
    "chars": 7433,
    "preview": "import Sandbox from '../src/Sandbox.js';\n\ndescribe('Try/Finally Control Flow Tests', () => {\n  let sandbox: Sandbox;\n\n  "
  },
  {
    "path": "test/tsconfig.json",
    "chars": 259,
    "preview": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"jest\", \"node\"],\n    \"resolveJsonModule\": true,\n"
  },
  {
    "path": "test/unraw.spec.ts",
    "chars": 4453,
    "preview": "import { unraw } from '../src/utils/unraw.js';\n\ndescribe('unraw Tests', () => {\n  describe('Basic escape sequences', () "
  },
  {
    "path": "tsconfig.jest.json",
    "chars": 369,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"ES2020\",\n    \"target\": \"ES2020\",\n    \"rootDir\": \"src/\",\n    \"baseUrl\": \".\",\n    "
  },
  {
    "path": "tsconfig.json",
    "chars": 232,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"ES2020\",\n    \"target\": \"ES2020\",\n    \"moduleResolution\": \"node\",\n    \"rootDir\": "
  }
]

About this extraction

This page contains the full source code of the nyariv/SandboxJS GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 140 files (761.5 KB), approximately 219.0k tokens, and a symbol index with 408 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!