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 is modern JavaScript minus the quirks; .js files, no transpilation needed
  • Key differences — No var/function/==/loops; use let/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

What is GocciaScript?#

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.

Prerequisites#

You need the FreePascal compiler (fpc):

# macOS
brew install fpc

# Ubuntu/Debian
sudo apt-get install fpc

# Windows
choco install freepascal

Clone the repository and build the script loader:

git clone https://github.com/frostney/GocciaScript.git
cd GocciaScript
./build.pas loader

This produces build/GocciaScriptLoader, 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/GocciaScriptLoader hello.js

You should see:

Hello from GocciaScript!

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). There is no var.

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

Working with Arrays#

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.

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 barks
console.log(dog instanceof Animal);  // true

Modules#

GocciaScript 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));  // 20

Run the entry point:

./build/GocciaScriptLoader 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));   // 5
console.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 Only#

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); // false

What's Different from JavaScript#

Here's a quick reference of GocciaScript's key restrictions:

JavaScriptGocciaScriptAlternative
var x = 1Not supportedlet x = 1 or const x = 1
function foo() {}Not supportedconst foo = () => {}
== / !=Not supported=== / !==
for (...) / while (...)Not supportedfor...of, .map(), .forEach(), .reduce()
eval("code")Not supportedNo alternative (by design)
argumentsNot supported(...args) => {}
parseInt("10")Not available as globalNumber.parseInt("10")
isNaN(x)Not available as globalNumber.isNaN(x)
import x from "mod"Not supportedimport { x } from "mod"

These restrictions are intentional — they eliminate common sources of bugs and security issues. See Language for the full rationale.

Next Steps#

You now have a working understanding of GocciaScript. Here's where to go from here:

  • [Language](language.md) — Full list of what's supported and what's excluded, with rationale
  • [Built-in Objects](built-ins.md) — Complete API reference for all built-in objects (Array, String, Map, Set, Promise, Temporal, etc.)
  • [`examples/`](../examples/) — More example programs: classes, promises, and unsupported feature demos
  • [Architecture](architecture.md) — Pipelines and main layers
  • [Interpreter](interpreter.md) and [Bytecode VM](bytecode-vm.md) — Tree-walk vs bytecode execution backends
  • [Core patterns](core-patterns.md) — Recurring implementation patterns, internal terminology