A drop of JavaScript — A sandbox-first ECMAScript runtime implemented in FreePascal
GocciaScript is a sandbox-first ECMAScript runtime with explicit host-controlled capabilities, designed for embedding portable JavaScript in applications. It is implemented in FreePascal, uses safer recommended defaults, and tracks language compatibility through generated test262 reports.
The host chooses the runtime surface: globals, modules, filesystem and network capabilities, execution limits, and application-specific APIs. The recommended language profile is product policy rather than the engine's language ceiling; compatibility flags enable many standard legacy forms for conformance and existing code. See Language for the policy and the live ECMAScript compatibility dashboard for current evidence.
GocciaScript implements a broad core ECMAScript surface: let/const, arrow functions, classes with private fields, for...of, async/await, ES modules, decorators, and TypeScript-style type annotations. Features that are error-prone, redundant, or security risks (var, function keyword, ==/!=, eval, labels, traditional loops, for...in) are excluded from the recommended defaults; selected legacy forms are available through explicit conformance-focused compatibility flags.
Core ECMAScript compatibility is now a release-track objective. The default language remains curated and sandbox-first, but test262 runs on every PR and main commit so conformance work can be measured from generated reports instead of hand-maintained status claims. Annex B's browser-only legacy surface is not a pre-1.0 target; see ADR 0085.
See Language for the complete specification of supported features, TC39 proposals, and exclusions.
console, Math, JSON, Object, Array, Number, String, RegExp, Symbol, Set, Map, WeakSet, WeakMap, Promise, Temporal, Iterator, Proxy, Reflect, ArrayBuffer, SharedArrayBuffer, TypedArrays (Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float16Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array) with ArrayBuffer and SharedArrayBuffer backing, fetch, Headers, Response (WHATWG Fetch — GET/HEAD only), URL, URLSearchParams, TextEncoder, TextDecoder, plus error constructors (Error, TypeError, ReferenceError, RangeError, DOMException).
Non-standard data-format APIs and SemVer are import-only Goccia runtime modules, not auto-installed globals: goccia:csv, goccia:json5, goccia:jsonl, goccia:toml, goccia:tsv, goccia:yaml, and goccia:semver. They expose named exports only; use import * as CSV from "goccia:csv" when you want the namespace-object shape. There is no default export.
Native FFI is an explicit unsafe runtime opt-in (--unsafe-ffi or the matching configuration key). It provides native-layout structures, unions, fixed-length arrays, callbacks, and guarded library lifetimes through GocciaScript's custom bidirectional ABI machinery. See the FFI reference and ADR 0095.
See Built-in Objects for the complete API reference.
class CoffeeShop {
#name = "Goccia Coffee";
#beans = ["Arabica", "Robusta", "Ethiopian"];
#prices = { espresso: 2.5, latte: 4.0, cappuccino: 3.75 };
getMenu() {
return this.#beans.map((bean) => `${bean} blend`);
}
calculateTotal(order) {
return order.reduce((total, item) => total + (this.#prices[item] ?? 0), 0);
}
get name() {
return this.#name;
}
}
const shop = new CoffeeShop();
const order = ["espresso", "latte"];
const total = shop.calculateTotal(order);
console.log(`Welcome to ${shop.name}!`);
console.log(`Your order total: $${total.toFixed(2)}`);- FreePascal compiler (
fpc)- macOS:
brew install fpc - Ubuntu/Debian:
sudo apt-get install fpc - Windows:
choco install freepascal
- macOS:
# Dev build of everything
./build.pas
# Production build
./build.pas --prod
# Build the script loader only
./build.pas loaderSee Build System for build modes, targets, clean builds, and troubleshooting.
./build.pas loader && ./build/GocciaScriptLoader example.js
printf "const x = 2 + 2; x;" | ./build/GocciaScriptLoader --printBy default, both loaders are silent about the script's last evaluated value.
Pass --print to emit it; use --output=json for programmatic consumers. See
Build System for loader options,
bytecode mode, JSON output, sandbox execution, import maps, config files, and
resource limits.
GocciaScript includes bytecode execution, and GocciaBundler compiles source to
the public .gbc artifact.
./build/GocciaScriptLoader example.js --mode=bytecode
./build/GocciaBundler example.js
./build/GocciaScriptLoader example.gbcSee Bytecode VM for the current bytecode executor architecture.
Use one fixed JavaScript-visible clock, UTC time zone, and portable random stream in either execution mode:
./build/GocciaScriptLoader example.js --deterministicTimeouts and profiling still use the real monotonic clock. The equivalent config key is "deterministic": true; embedders can inject their own clock and RNG providers through the engine host environment.
For custom providers, pass a JavaScript module to --host-environment or implement the Pascal host interfaces. See Host Environment for both examples and the provider contract.
./build.pas repl && ./build/GocciaREPLGocciaScript has 11,000+ JavaScript unit tests covering language features, built-in objects, and edge cases.
./build.pas testrunner
./build/GocciaTestRunner tests
./build/GocciaTestRunner tests --mode=bytecodeThe test runner supports Vitest-compatible external and inline snapshots,
property shapes, asymmetric matchers, custom serializers, and -u updates.
See Testing for test organization and Build System for runner options.
./build.pas benchmarkrunner && ./build/GocciaBenchmarkRunner benchmarks
./build/GocciaBenchmarkRunner benchmarks/fibonacci.jsThe benchmark runner auto-calibrates iterations per benchmark, reports ops/sec with variance (CV%) and engine-level timing breakdown (lex/parse/execute). Output formats: console (default), text, csv, json, compact-json (the same envelope as json without build, memory, stdout, or stderr). Calibration and measurement parameters are configurable via environment variables. Retained AWFY and JetStream reference measurements are published through the Performance Barometer.
GocciaScript looks like modern JavaScript — with a few intentional differences:
- Arrow functions only —
const greet = (name) => \Hello, ${name}!`;(nofunction` keyword) - No traditional loops —
numbers.map((n) => n * 2)orfor (const n of numbers) { ... } - Classes with private fields —
class Account { #balance = 0; ... } - ES modules — default, named, and namespace imports/exports are supported; project code prefers named exports for clarity.
- Strict equality by default —
===and!==(==/!=require--compat-loose-equality)
The CLI tools share WHATWG-style import map support with --import-map=<file.json>, --alias key=value, and automatic goccia.json discovery for project-level module aliases. Host-supplied dependencies should normally be configured as virtual ES modules with --module, --modules, or a config modules object; they participate in the same import pipeline as filesystem modules. Global injection remains supported for compatibility.
Structured data files and text assets can also be imported directly:
import { name, version } from "./package.json";
import { name as packageName } from "./config.toml";
import { name as appName } from "./config.yaml";
import { content, metadata } from "./README.md";Runtime parsers are available through named Goccia modules for JSON5, TOML, YAML, JSONL, CSV, and TSV. See Built-in Objects and Language for the full data format reference.
import * as TOML from "goccia:toml";
import * as YAML from "goccia:yaml";
TOML.parse(sourceText); // TOML 1.1.0 configuration data
YAML.parse(sourceText); // block scalars, anchors/aliases, merge keys, YAML 1.2 tagsSee Language and Architecture Decision Records for the full conformance details.
JSONL parsing is also available from goccia:jsonl via parse(text) and parseChunk(text), and .jsonl files can still be imported as structured-data modules.
Async/await with full Promise support, including top-level await:
const fetchData = async () => {
const result = await Promise.resolve({ status: "ok" });
return result;
};
// Top-level await (ES2022+)
const data = await fetchData();Strict equality by default — === and !==; == and != are available only with --compat-loose-equality.
For a full guided walkthrough, see the Tutorial. For the complete list of what's supported and excluded, see Language.
GocciaScript supports two execution modes that share the same source pipeline (preprocessors, lexer, parser, AST):
flowchart LR
Source["Source Code"] --> Preprocessors["Preprocessors"] --> Lexer --> Parser --> AST
AST --> Interpreter["Tree-Walk Interpreter"] --> Result1["Result"]
AST --> Compiler["Bytecode Compiler"] --> VM["Goccia VM"] --> Result2["Result"]
Both execution modes share the same value types, built-ins, scope chain, and mark-and-sweep GC. The bytecode executor uses a Goccia-owned VM with tagged TGocciaRegister values (unboxed scalars) that fall back to TGocciaValue for heap objects, not a generic VM layer.
See Architecture for pipelines and layers, Interpreter for tree-walk execution, Bytecode VM for bytecode execution, Core patterns for implementation patterns, and GocciaScript Context for canonical terminology.
- Explicitness: Modules, classes, methods, and properties use explicit, descriptive names even at the cost of verbosity. Shortcuts are avoided.
- OOP over everything: Rely on type safety of specialized classes rather than generic data structures.
- Define vs Assign:
Definecreates a new variable binding;Assignchanges an existing one. These are distinct operations throughout the codebase (see Core patterns). - Pure evaluation: The evaluator is composed of pure functions with no side effects.
- No global mutable state: All runtime state flows through explicit parameters — the evaluation context, the scope chain, and value objects.
- Virtual dispatch: Property access (
GetProperty/SetProperty), type discrimination (IsPrimitive/IsCallable), and scope chain resolution (GetThisValue/GetOwningClass/GetSuperClass) all use virtual methods, replacing type checks with single VMT calls.
See Core patterns and Interpreter for the design rationale.
| Document | Description |
|---|---|
| Vision | Why GocciaScript exists: sandboxed AI agent runtime and embeddable desktop platform |
| Tutorial | Your first GocciaScript program — a guided walkthrough for newcomers |
| Language | ECMAScript support, recommended defaults, compatibility flags, and rationale |
| Language Tables | Quick-reference: ECMAScript feature matrix and TC39 proposal status |
| Built-in Objects | Available built-ins and API reference |
| FFI Built-ins | Native libraries, aggregate types, callbacks, lifetimes, and safety limits |
| Temporal Built-ins | Temporal API: dates, times, durations, time zones |
| Binary Data Built-ins | ArrayBuffer, SharedArrayBuffer, TypedArray API |
| Errors | Error types, parser/runtime display, JSON output, Error.cause, try/catch/finally |
| Architecture | Pipelines, main layers, design direction, duplication boundaries |
| Interpreter · Bytecode VM | Tree-walk and bytecode execution modes |
| Core patterns | Recurring implementation patterns |
| GocciaScript Context | Canonical project terminology and glossary |
| Value System | Type hierarchy, virtual property access, primitives, objects |
| Garbage Collector | Mark-and-sweep GC: architecture, contributor rules, design rationale |
| Adding Built-in Types | Step-by-step guide for adding new built-in types |
| Embedding the Engine | Embedding GocciaScript in FreePascal applications |
| Virtual Module Configuration | CLI, config-file, and embedding reference for host-supplied modules |
| Host Environment | Injecting JavaScript-visible clock, time-zone, and random providers |
| Capability Audit Events | Structured host capability decisions, embedding sink, and CLI JSONL output |
| Testing | Test organization, running tests, coverage, CI |
| Test Framework API | Assertions, mocks, lifecycle hooks, async patterns |
| Benchmarks | Benchmark runner, output formats, writing benchmarks |
| Build System | Build commands, compiler configuration, CI/CD |
| Profiling | Bytecode VM profiling: opcodes, functions, output formats |
| Architecture Decision Records | Durable architectural decisions and trade-offs |
| Contributing | Single contribution standard: workflow, mandatory rules, testing, FreePascal style |
| AGENTS.md | Agent operating manual for coding assistants; CONTRIBUTING.md is the contributing guide for everyone |
CONTRIBUTING.md is the contributing guide for all contributors (humans and AI): workflow, mandatory rules, testing, FreePascal code style, ./format.pas, editor setup, build/run quick reference, and the documentation index.
AGENTS.md (and CLAUDE.md, which points to it) is only for AI assistants—how to use the repo and defer to CONTRIBUTING. It is not a second contributing guide and should stay short.
See LICENSE for details.
