Your First GocciaScript Program
For developers who know JavaScript and want to learn GocciaScript — a guided walkthrough from hello world to a multi-file async program.
Executive Summary
- Familiar syntax — GocciaScript runs modern ECMAScript syntax with sandbox-first recommended defaults;
.jsfiles, no transpilation needed - Key differences — No
var,function, traditionalforloops, orwhileloops by default; uselet/const, arrow functions, strict equality,for...of/array methods - Full walkthrough — Variables, arrow functions, arrays, objects, classes, modules, async/await
- Next steps — Links to the recommended profile and its compatibility paths, built-in API reference, and example programs
What is GocciaScript?
GocciaScript is a FreePascal implementation of ECMAScript with sandbox-first recommended defaults. It keeps modern JavaScript syntax front and center — arrow functions, classes with private fields, async/await, modules, and implicit strict mode — while legacy forms such as var, function, loose equality, traditional for loops, and while loops are available through compatibility flags when conformance or host requirements need them. If you've written modern JavaScript, you already know most of GocciaScript.
Prerequisites
You need the FreePascal compiler (fpc):
# macOSbrew install fpc# Ubuntu/Debiansudo apt-get install fpc# Windowschoco install freepascal
Clone the repository and build the runner:
git clone https://github.com/frostney/GocciaScript.gitcd GocciaScript./build.pas runner
This produces build/GocciaRunner, the command you'll use to run every script in this tutorial.
Hello, World
Create a file called hello.js:
const message = "Hello from GocciaScript!";console.log(message);
Run it:
./build/GocciaRunner hello.js
You should see the script's output followed by a short run summary (the timings vary):
Hello from GocciaScript!Running script (interpreted): hello.jsLex: 31.17µs | Parse: 11.22µs | Execute: 11.17µs | Total: 69.66µs
That's it — GocciaScript files are plain .js files. No special extension, no transpilation step.
Variables
GocciaScript has two variable declarations: let (mutable) and const (immutable). var is off by default (--compat-var enables it for ported code).
const name = "Alice";let score = 0;score = score + 10;console.log(`${name} scored ${score} points`);// Alice scored 10 points
Attempting to reassign a const throws an error:
const x = 5;x = 10; // TypeError: Assignment to constant variable
Arrow Functions
GocciaScript recommends arrow functions and methods; the function keyword is off by default (--compat-function enables it).
// Single expression — implicit returnconst double = (n) => n * 2;// Block body — explicit returnconst greet = (name) => {const greeting = `Hello, ${name}!`;return greeting;};console.log(double(21)); // 42console.log(greet("World")); // Hello, World!
Arrow functions capture their surrounding scope's this — there's no this rebinding. For methods that need their own this, use shorthand method syntax inside classes or object literals.
Working with Arrays
GocciaScript keeps traditional for loops and while loops off by default. Use array methods and for...of for new code:
const numbers = [1, 2, 3, 4, 5];// Transform with mapconst doubled = numbers.map((n) => n * 2);console.log(doubled); // [2, 4, 6, 8, 10]// Filterconst evens = numbers.filter((n) => n % 2 === 0);console.log(evens); // [2, 4]// Reduceconst sum = numbers.reduce((total, n) => total + n, 0);console.log(sum); // 15// Iterate with for...offor (const n of numbers) {console.log(n);}
All the modern array methods you'd expect are available: find, findLast, some, every, flat, flatMap, at, toSorted, toReversed, and more. See the Built-in Objects reference for the full list.
Objects
Object literals work like JavaScript, including shorthand properties, computed keys, and methods:
const x = 10;const y = 20;const point = {x,y,distanceTo(other) {const dx = this.x - other.x;const dy = this.y - other.y;return Math.sqrt(dx ** 2 + dy ** 2);},};const origin = { x: 0, y: 0 };console.log(point.distanceTo(origin)); // 22.360679774997898
Note that the distanceTo method uses shorthand method syntax (distanceTo() { ... }), not an arrow function. This is important: shorthand methods receive the call-site this (the object they're called on), while arrow functions inherit this from their enclosing scope.
Classes
Classes support constructors, private fields, getters, setters, static methods, and inheritance:
Private names are validated at parse time: using an undeclared #name is a SyntaxError. Getter-only and setter-only private accessors also follow normal accessor semantics, so invalid writes or reads throw TypeError. Private static names are brand-checked against the actual class receiver, so inherited static calls like Derived.methodFromBase() cannot read or write Base's private static state through this.
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();console.log(`Welcome to ${shop.name}!`);console.log("Menu:", shop.getMenu());const order = ["espresso", "latte"];console.log(`Total: $${shop.calculateTotal(order).toFixed(2)}`);
Inheritance uses extends and super:
class Animal {constructor(name) {this.name = name;}speak() {return `${this.name} makes a sound`;}}class Dog extends Animal {speak() {return `${this.name} barks`;}}const dog = new Dog("Rex");console.log(dog.speak()); // Rex barksconsole.log(dog instanceof Animal); // true
Modules
GocciaScript supports ES-style default, named, and namespace imports and exports. Project examples prefer named exports for internal modules because explicit import names make refactors easier to review; default exports are supported when a module intentionally exposes a primary value or interoperates with code that uses default exports.
Create a file called math.js:
export const add = (a, b) => a + b;export const multiply = (a, b) => a * b;
Import it from another file called app.js in the same directory:
import { add, multiply } from "./math.js";console.log(add(2, 3)); // 5console.log(multiply(4, 5)); // 20
Run the entry point:
./build/GocciaRunner app.js
You can also rename imports with as:
import { add as sum } from "./math.js";console.log(sum(1, 2)); // 3
Or import the full namespace object when you want all exports under one binding:
import * as math from "./math.js";import * as config from "./config.json";console.log(math.add(2, 3)); // 5console.log(config.version); // top-level JSON/TOML/YAML keys are namespace properties
Named imports and exports also support string-literal export names when a module exposes a name that is not a valid identifier:
import { "foo-bar" as fooBar } from "./config.json";import { "0" as firstDoc } from "./multi.yaml";const localValue = 42;export { localValue as "0" };
And re-export from one module to another:
export { add, multiply } from "./math.js";
Async/Await
Async functions and await work as you'd expect from modern JavaScript:
const fetchUser = async (id) => {const result = await Promise.resolve({ id, name: "Alice" });return result;};const main = async () => {const user = await fetchUser(1);console.log(`User: ${user.name}`);};main();
Promises are fully supported — .then(), .catch(), .finally(), Promise.all(), Promise.race(), Promise.any(), Promise.allSettled(), and Promise.withResolvers().
GocciaScript uses a synchronous microtask queue: all pending .then() callbacks are drained after synchronous code completes.
Strict Equality by Default
GocciaScript uses strict equality by default. Use === and !== for predictable comparisons:
console.log(1 === 1); // trueconsole.log(1 === "1"); // falseconsole.log(null === undefined); // false
The loose equality operators (== and !=) are available only in compatibility mode with --compat-loose-equality.
What's Different from JavaScript
Here's a quick reference of what the recommended profile turns off by default, and how to opt back in:
| JavaScript | GocciaScript | Alternative |
|---|---|---|
var x = 1 | Off by default | let x = 1 or const x = 1, or --compat-var for JavaScript compatibility |
function foo() {} | Off by default | const foo = () => {}, or --compat-function for JavaScript compatibility |
== / != | Off by default | === / !== or --compat-loose-equality |
labels / break label / continue label | Off by default | return from a helper, an early-exit flag, or --compat-label for JavaScript compatibility |
for (init; test; update) | Off by default | for...of, .map(), .forEach(), .reduce(), or --compat-traditional-for-loop for JavaScript compatibility |
for (key in object) | Off by default | Object.keys() / Object.entries() with for...of, or --compat-for-in-loop for JavaScript compatibility |
while (...) / do ... while (...) | Off by default | for...of, .map(), .forEach(), .reduce(), or --compat-while-loops for JavaScript compatibility |
eval("code") | Not installed by normal hosts | No alternative (by design) |
arguments | Off by default | Prefer rest parameters (...args) or --compat-arguments-object |
| sloppy assignment failures | Strict by default | script source --compat-non-strict-mode when porting code that expects failed property writes to be ignored |
sloppy function this | Strict by default | script source --compat-non-strict-mode when porting scripts that expect globalThis |
legacy delete returns | Strict by default | script source --compat-non-strict-mode when porting sloppy scripts |
parseInt("10") | Shimmed legacy global | Prefer Number.parseInt("10") in new code |
isNaN(x) | Shimmed legacy global with coercion | Prefer Number.isNaN(x) when you do not want coercion |
These defaults are intentional — they eliminate common sources of bugs and security issues, and each off-by-default form has a compatibility path. See Language for the full rationale.
Next Steps
You now have a working understanding of GocciaScript. Here's where to go from here:
- Language — Implemented ECMAScript, recommended defaults, compatibility paths, and rationale
- Built-in Objects — Complete API reference for all built-in objects (Array, String, Map, Set, Promise, Temporal, etc.)
examples/— More example programs: classes, promises, and unsupported feature demos- Architecture — Pipelines and main layers
- Interpreter and Bytecode VM — Tree-walk vs bytecode execution modes
- Core patterns — Recurring implementation patterns
- GocciaScript Context — Canonical project terminology