Skip to content

Latest commit

History

1,093 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

GocciaScript

GocciaScript logo

A drop of JavaScript — sandboxed by default

GocciaScript is a JavaScript engine: a sandbox-first ECMAScript runtime and toolchain for AI agents. Hosts define the available capabilities, runtime surface, and execution limits. It uses modern recommended defaults while tracking ECMAScript compatibility through generated test262 reports.

GocciaScript is implemented in FreePascal, supports Delphi, and can also be embedded in native applications. Native embedding is an important secondary goal; the primary product goal is AI-agent execution under an explicit host-defined capability model. It is not trying to become Node.js or a browser host.

Start with an agent sandbox

GocciaSandboxRunner seeds an in-memory virtual filesystem from explicit host paths, runs an entry script with host-owned limits, and reports sandbox changes as a diff. Seed entries are snapshots, not live mounts, and scripts receive no ambient host filesystem access.

// agent-workspace/main.jsimportfsfrom"fs";constinput=fs.readFileSync("/task.txt","utf8");fs.mkdirSync("/out",{recursive: true});fs.writeFileSync("/out/result.txt",input.toUpperCase());
./build.pas sandboxrunner
./build/GocciaSandboxRunner /main.js \
--seed=./agent-workspace=/ \
--timeout=5000 \
--diff

The host can also define globals, virtual modules, allowed network hosts, instruction and memory limits, deterministic time/randomness, and application-specific APIs. See Build System — Sandbox Runner and Built-ins — Sandbox Modules.

ECMAScript implementation and recommended profile

GocciaScript implements core ECMAScript and runs the official test262 corpus on every PR and main commit. The exact current result is rendered from published main-branch data on the live ECMAScript compatibility dashboard; canonical prose does not freeze a historical percentage.

The recommended language profile is product policy, not the implementation ceiling. Standard core forms disabled by default all have explicit compatibility paths:

FormDefaultEnablement / exposure
varDisabled--compat-var
traditional function syntaxDisabled--compat-function
== / !=Disabled--compat-loose-equality
ASIDisabled--compat-asi
labelsDisabled--compat-label
traditional for(;;)Disabled--compat-traditional-for-loop
for...inDisabled--compat-for-in-loop
while / do...whileDisabled--compat-while-loops
argumentsDisabled--compat-arguments-object
non-strict Script semantics and withStrict / disabled--compat-non-strict-mode
evalNot installed by normal hostsprivate GocciaScriptLoaderBare --test262-host
Function()Disabled--unsafe-function-constructor
ShadowRealmNot installed--unsafe-shadowrealm

Annex B's browser-only legacy surface is not a general pre-1.0 target; see ADR 0085. See Language and Language Tables for the detailed semantics and feature matrix.

TC39 Type Annotations and --strict-types

GocciaScript implements the official TC39 Type Annotations proposal and its types-as-comments runtime model. Supported annotations have no runtime effect by default. GocciaScript additionally provides the optional --strict-types extension, which enforces supported annotations and relevant inferred primitive contracts at runtime in interpreter and bytecode modes.

--strict-types is a runtime contract extension, not a replacement for a static structural type checker such as tsc. See Type Annotations for the supported syntax and its parsing rules.

Node host compatibility and sandbox fs

GocciaScript is not a complete Node.js host: it does not provide CommonJS, npm package resolution, process, Buffer, or the general node: module set. GocciaSandboxRunner does provide a Node-compatible fs API over its virtual filesystem:

  • synchronous forms such as readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, rmSync, renameSync, and copyFileSync;
  • Node-shaped callback forms for the same operation families;
  • fs.promises forms, Stats objects, and Node-shaped filesystem errors.

The documented method set never reaches the ambient host filesystem. See Sandbox Modules for supported options, return types, error shapes, and method-level deviations.

Runtime and toolchain features

The default profile includes modern ECMAScript forms such as let/const, arrow functions, classes with private fields, for...of, async/await, ES modules, decorators, and proposal-compatible type syntax.

Built-in Objects

Core built-ins include Math, JSON, Object, Function, Array, Boolean, Number, BigInt, String, RegExp, Symbol, Set, Map, WeakSet, WeakMap, WeakRef, FinalizationRegistry, Promise, Temporal, Intl, Iterator, DisposableStack, AsyncDisposableStack, Proxy, Reflect, ArrayBuffer, SharedArrayBuffer, DataView, Atomics, and TypedArrays (Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float16Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array), alongside the global functions queueMicrotask, structuredClone, atob, and btoa. The loader profile adds console, performance, fetch, Headers, Response (WHATWG Fetch — GET/HEAD only), AbortController, AbortSignal, EventTarget, Event, URL, URLSearchParams, TextEncoder, and TextDecoder. Error constructors include Error, EvalError, TypeError, ReferenceError, RangeError, SyntaxError, URIError, AggregateError, SuppressedError, and 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.

Example

classCoffeeShop{
#name ="Goccia Coffee";
#beans =["Arabica","Robusta","Ethiopian"];
#prices ={espresso: 2.5,latte: 4.0,cappuccino: 3.75};getMenu(){returnthis.#beans.map((bean)=>`${bean} blend`);}calculateTotal(order){returnorder.reduce((total,item)=>total+(this.#prices[item]??0),0);}getname(){returnthis.#name;}}constshop=newCoffeeShop();constorder=["espresso","latte"];consttotal=shop.calculateTotal(order);console.log(`Welcome to ${shop.name}!`);console.log(`Your order total: $${total.toFixed(2)}`);

Getting Started

Prerequisites

  • FreePascal compiler (fpc)
    • macOS: brew install fpc
    • Ubuntu/Debian: sudo apt-get install fpc
    • Windows: choco install freepascal

Build

# Dev build of everything
./build.pas
# Production build
./build.pas --prod
# Build the script loader only
./build.pas loader

See Build System for build modes, targets, clean builds, and troubleshooting.

Run a Script

./build.pas loader && ./build/GocciaScriptLoader example.js
printf"const x = 2 + 2; x;"| ./build/GocciaScriptLoader --print

By 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.

Run via Bytecode

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.gbc

See Bytecode VM for the current bytecode executor architecture.

Reproduce An Execution

Use one fixed JavaScript-visible clock, UTC time zone, and portable random stream in either execution mode:

./build/GocciaScriptLoader example.js --deterministic

Timeouts 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.

Start the REPL

./build.pas repl && ./build/GocciaREPL

Run Tests

GocciaScript has 12,000+ JavaScript end-to-end tests across 1,500+ test files, covering language features, built-in objects, and edge cases.

./build.pas testrunner
./build/GocciaTestRunner tests
./build/GocciaTestRunner tests --mode=bytecode

The test runner supports Vitest-compatible external and inline snapshots, property shapes, asymmetric matchers, custom serializers, and -u updates. Importing vi from "vitest" resolves to a bundled compatibility shim, so suites written against vi.fn, vi.spyOn, and factory-form vi.mock (a synchronous arrow factory returning an object literal — no automock, no spread-based partial mock) run unmodified; see Test Framework API for the full factory constraints and the members that are not implemented. See Testing for test organization and Build System for runner options.

Run Benchmarks

./build.pas benchmarkrunner && ./build/GocciaBenchmarkRunner benchmarks
./build/GocciaBenchmarkRunner benchmarks/fibonacci.js

The 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.

Recommended-profile quick tour

The recommended profile favors modern, explicit forms. The corresponding core ECMAScript forms remain implemented through the compatibility paths listed above:

  • Arrow functions and methods by defaultconst greet = (name) => `Hello, ${name}!`;; traditional function syntax uses --compat-function.
  • Iterator-oriented loops by default — use array methods, iterators, or for...of; traditional for(;;), for...in, while, and do...while each have targeted compatibility flags.
  • 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{nameaspackageName}from"./config.toml";import{nameasappName}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*asTOMLfrom"goccia:toml";import*asYAMLfrom"goccia:yaml";TOML.parse(sourceText);// TOML 1.1.0 configuration dataYAML.parse(sourceText);// block scalars, anchors/aliases, merge keys, YAML 1.2 tags

See 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:

constfetchData=async()=>{constresult=awaitPromise.resolve({status: "ok"});returnresult;};// Top-level await (ES2022+)constdata=awaitfetchData();

Strict equality by default=== and !==; == and != are available only with --compat-loose-equality.

For a full guided walkthrough, see the Tutorial. For the complete implementation, default-profile, and compatibility-path detail, see Language.

FreePascal, Delphi, and native embedding

FreePascal is the cross-platform toolchain used for normal builds, releases, and the documented embedding API. The repository also includes GocciaScript.Delphi.groupproj and Delphi projects for the REPL, both script loaders, Sandbox Runner, Test Runner, Benchmark Runner, and Bundler on Win32 and Win64.

The Delphi support contract requires the complete application matrix and all applicable Pascal and JavaScript tests to pass with the same runtime semantics as the FreePascal build. Delphi 12 Community Edition contributors validate that contract through the IDE; see Delphi Validation and Build System.

Native hosts can embed GocciaScript to run portable JavaScript with application-specific globals, modules, capabilities, and limits. See Embedding the Engine.

Architecture

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"]
Loading

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.

Design Principles

  • 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: Define creates a new variable binding; Assign changes 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.

Documentation

DocumentDescription
VisionWhy GocciaScript exists: sandboxed AI agent runtime and embeddable desktop platform
TutorialYour first GocciaScript program — a guided walkthrough for newcomers
LanguageECMAScript support, recommended defaults, compatibility flags, and rationale
Language TablesQuick-reference: ECMAScript feature matrix and TC39 proposal status
Type AnnotationsTypeScript-compatible type syntax, --strict-types, and the < disambiguation rules
Built-in ObjectsAvailable built-ins and API reference
FFI Built-insNative libraries, aggregate types, callbacks, lifetimes, and safety limits
Temporal Built-insTemporal API: dates, times, durations, time zones
Binary Data Built-insArrayBuffer, SharedArrayBuffer, TypedArray API
ErrorsError types, parser/runtime display, JSON output, Error.cause, try/catch/finally
ArchitecturePipelines, main layers, design direction, duplication boundaries
Interpreter · Bytecode VMTree-walk and bytecode execution modes
Core patternsRecurring implementation patterns
GocciaScript ContextCanonical project terminology and glossary
Value SystemType hierarchy, virtual property access, primitives, objects
Garbage CollectorMark-and-sweep GC: architecture, contributor rules, design rationale
Adding Built-in TypesStep-by-step guide for adding new built-in types
Embedding the EngineEmbedding GocciaScript in FreePascal applications
Virtual Module ConfigurationCLI, config-file, and embedding reference for host-supplied modules
Host EnvironmentInjecting JavaScript-visible clock, time-zone, and random providers
Capability Audit EventsStructured host capability decisions, embedding sink, and CLI JSONL output
TestingTest organization, running tests, coverage, CI
Test Framework APIAssertions, mocks, lifecycle hooks, async patterns
BenchmarksBenchmark runner, output formats, writing benchmarks
Build SystemBuild commands, compiler configuration, CI/CD
ProfilingBytecode VM profiling: opcodes, functions, output formats
Architecture Decision RecordsDurable architectural decisions and trade-offs
ContributingSingle contribution standard: workflow, mandatory rules, testing, FreePascal style
AGENTS.mdAgent operating manual for coding assistants; CONTRIBUTING.md is the contributing guide for everyone

Contributing

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.

License

See LICENSE for details.

About

A drop of JavaScript — a JavaScript engine and sandbox-first ECMAScript runtime implemented in Object Pascal

Topics

Resources

Contributing

Stars

19 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages