For developers who know JavaScript and want to learn GocciaScript — a guided walkthrough from hello world to a multi-file async program.
- Familiar syntax — GocciaScript is modern JavaScript minus the quirks;
.jsfiles, no transpilation needed - Key differences — No
var/function/==/loops; uselet/const, arrow functions,===,for...of/array methods - Full walkthrough — Variables, arrow functions, arrays, objects, classes, modules, async/await
- Next steps — Links to language restrictions, built-in API reference, and example programs
GocciaScript is a subset of JavaScript implemented in FreePascal. It strips away the quirks of early ECMAScript — var, function, loose equality, eval, traditional loops — and keeps the modern parts: arrow functions, classes with private fields, async/await, modules, and implicit strict mode. If you've written modern JavaScript, you already know most of GocciaScript.
You need the FreePascal compiler (fpc):
# macOS
brew install fpc
# Ubuntu/Debian
sudo apt-get install fpc
# Windows
choco install freepascalClone the repository and build the script loader:
git clone https://github.com/frostney/GocciaScript.git
cd GocciaScript
./build.pas loaderThis produces build/GocciaScriptLoader, the command you'll use to run every script in this tutorial.
Create a file called hello.js:
const message = "Hello from GocciaScript!";
console.log(message);Run it:
./build/GocciaScriptLoader hello.jsYou should see:
Hello from GocciaScript!
That's it — GocciaScript files are plain .js files. No special extension, no transpilation step.
GocciaScript has two variable declarations: let (mutable) and const (immutable). There is no var.
const name = "Alice";
let score = 0;
score = score + 10;
console.log(`${name} scored ${score} points`);
// Alice scored 10 pointsAttempting to reassign a const throws an error:
const x = 5;
x = 10; // TypeError: Assignment to constant variableGocciaScript uses arrow functions exclusively. The function keyword does not exist.
// Single expression — implicit return
const double = (n) => n * 2;
// Block body — explicit return
const greet = (name) => {
const greeting = `Hello, ${name}!`;
return greeting;
};
console.log(double(21)); // 42
console.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.
GocciaScript has no traditional loops (for, while, do...while). Instead, you use array methods and for...of:
const numbers = [1, 2, 3, 4, 5];
// Transform with map
const doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Filter
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4]
// Reduce
const sum = numbers.reduce((total, n) => total + n, 0);
console.log(sum); // 15
// Iterate with for...of
for (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.
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.360679774997898Note 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 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 barks
console.log(dog instanceof Animal); // trueGocciaScript supports ES-style named imports and exports. Default imports/exports are not supported — use named exports instead.
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)); // 5
console.log(multiply(4, 5)); // 20Run the entry point:
./build/GocciaScriptLoader app.jsYou can also rename imports with as:
import { add as sum } from "./math.js";
console.log(sum(1, 2)); // 3Or 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)); // 5
console.log(config.version); // top-level JSON/TOML/YAML keys are namespace propertiesNamed 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 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.
GocciaScript enforces strict equality. The loose equality operators (== and !=) are not available — use === and !==:
console.log(1 === 1); // true
console.log(1 === "1"); // false
console.log(null === undefined); // falseHere's a quick reference of GocciaScript's key restrictions:
| JavaScript | GocciaScript | Alternative |
|---|---|---|
var x = 1 |
Not supported | let x = 1 or const x = 1 |
function foo() {} |
Not supported | const foo = () => {} |
== / != |
Not supported | === / !== |
for (...) / while (...) |
Not supported | for...of, .map(), .forEach(), .reduce() |
eval("code") |
Not supported | No alternative (by design) |
arguments |
Not supported | (...args) => {} |
parseInt("10") |
Not available as global | Number.parseInt("10") |
isNaN(x) |
Not available as global | Number.isNaN(x) |
import x from "mod" |
Not supported | import { x } from "mod" |
These restrictions are intentional — they eliminate common sources of bugs and security issues. See Language for the full rationale.
You now have a working understanding of GocciaScript. Here's where to go from here:
- Language — Full list of what's supported and what's excluded, with 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 backends
- Core patterns — Recurring implementation patterns, internal terminology