gravity

GitHub

Gravity Programming Language

AI Prompts & Endpoints
CodeWiki Knowledge Base

Quickstart

Getting started

Install


To install Gravity, simply execute the commands given below. This should make two executables: gravity, the compiler itself and unittest, the test runner.
bash
git clone https://github.com/marcobambini/gravity.git
cd gravity
make

If you want to access the gravity compiler globally just add it to your PATH.

You can also use the Xcode project to create the gravity or unittest executables.

Configure your editor


Programming is way more enjoyable when you have the right tools. That's why we've equipped several code editors with Gravity support. Just click on your favourite editor and configure it accordingly:
* Visual Studio Code
* Atom
* vim
* BBEdit

Command line


To view all possible flags you can run the command below:
bash
./gravity --help

To compile a gravity file to a exec.json executable:

bash
./gravity -c myfile.gravity -o exec.json

To execute a precompiled json executable file:

bash
./gravity -x exec.json

To directly execute a gravity file (without first serializing it to json):

bash
./gravity myfile.gravity

Unit Tests


You can run unit tests by providing a path to a folder containing all test files:
bash
./gravity -t path_to_test_folder

This should produce output like:
<img src="assets/images/unittest.png" width="666px" height="466px">


Hello World


A simple <strong>Hello World</strong> code in Gravity looks like:
swift
func main() {
System.print("Hello World!")
}

---

README

<p align="center">
<img src="assets/images/logo.png" height="90px" alt="Gravity Programming Language" title="Gravity Programming Language">
</p>

Gravity is a powerful, dynamically typed, lightweight, embeddable programming language written in C without any external dependencies (except for stdlib). It is a class-based concurrent scripting language with a modern Swift like syntax.

Gravity supports procedural programming, object-oriented programming, functional programming and data-driven programming. Thanks to special built-in methods, it can also be used as a prototype-based programming language.

Gravity has been developed from scratch for the Creo project in order to offer an easy way to write portable code for the iOS and Android platforms. It is written in portable C code that can be compiled on any platform using a C99 compiler. The VM code is about 4K lines long, the multipass compiler code is about 7K lines and the shared code is about 3K lines long. The compiler and virtual machine combined, add less than 200KB to the executable on a 64 bit system.

Comments in the C code make it easy to read and understand.

What Gravity code looks like

swift
class Vector {
// instance variables
var x = 0;
var y = 0;
var z = 0;

// constructor
func init (a = 0, b = 0, c = 0) {
x = a; y = b; z = c;
}

// instance method (built-in operator overriding)
func + (v) {
if (v is Int) return Vector(x+v, y+v, z+v);
else if (v is Vector) return Vector(x+v.x, y+v.y, z+v.z);
return null;
}

// instance method (built-in String conversion overriding)
func String() {
// string interpolation support
return "[\(x),\(y),\(z)]";
}
}

func main() {
// initialize a new vector object
var v1 = Vector(1,2,3);

// initialize a new vector object
var v2 = Vector(4,5,6);

// call + function in the vector object
var v3 = v1 + v2;

// returns string "[1,2,3] + [4,5,6] = [5,7,9]"
return "\(v1) + \(v2) = \(v3)";
}

Features


* multipass compiler
* dynamic typing (manifest typing coming soon)
* classes and inheritance
* higher order functions and classes
* lexical scoping
* coroutines (via fibers)
* nested classes
* closures
* garbage collection
* operator overriding
* powerful embedding api
* built-in unit tests
* built-in JSON serializer/deserializer

---

Coverpage

<p align="center">
<img src="assets/images/logo.png" height="90px" alt="Gravity Programming Language" title="Gravity Programming Language">
</p>

Gravity <small>0.8.0</small>


An embeddable programming language.

* Simple and lightweight
* No external dependencies
* Register based virtual machine

GitHub
Get Started

---

* INTRODUCTION

* Overview
* Getting Started

* LANGUAGE GUIDE

* Syntax
* Operators
* Types
* Object
* Int
* Float
* String
* Bool
* Null
* List
* Map
* Enum
* Range
* Function
* Closure
* Class
* Control Flow
* Loops
* Fibers

* OPTIONALS

* System class
* Math class
* File class
* ENV class

* ADVANCED

* Embedding
* Extending
* Introspection
* Unit test
* Contributing

---

Bool

Bool

The Bool data type can have only two values, they are the literals true and false. A Bool value expresses the validity of a condition (tells whether the condition is true or false).

swift
var a = true;
var b = false;

---

Class

Class

Every value in Gravity is an object, and every object is an instance of a class. Classes define an object's behavior and state. Behavior is defined by methods which live in the class. Every object of the same class supports the same methods. State is defined in fields, whose values are stored in each instance.<br><br>Like functions a Class is a first class object, that means that it can be stored in local variables (even in Lists or Maps), passed as a function parameter or returned by a function. Gravity supports nested classes and single inheritance.

Defining a class


Like most programming languages the class keyword is used to declare a new class:
swift
class Italy {
}

Instantiate a class


A class in gravity can be instantiated by simply executing it (without the new keyword):
swift
var instance = Italy();

Methods


Functions declared inside a class are called methods and are used to add behaviors to objects that belong to a specific class:
swift
class Italy {
func print() {
System.print("Hello from Italy");
}
}

Properties


Variables declared inside a class are called properties and are used to add states to objects that belong to a specific class:
swift
class Italy {
var population = 60656000;
var area = 301340; // in km2

func density() {
return population/area;
}
}

func main() {
var it = Italy();
return it.density(); // returns 201
}

Class methods and properties


A class method (or property) is a method (or property) that operates on class objects rather than instances of the class. In Gravity you can specify a class method (or property) using the static keyword:
swift
class Italy {
static var population = 60656000;
static var area = 301340; // in km2

static func density() {
return population/area;
}
}

func main() {
return Italy.density();
}

Class constructors and destructors


Class constructors get called whenever an object of that class is instantiated. Constructors can optionally have parameters.
To add a constructor to your class, create a method called init().

Class destructors get called when the object gets deleted/goes out of scope.
To add a destructor to your class, create a method called deinit().

swift
class MyClass {

private var _value;

// Constructor - called when the class is instantiated
func init(value) {

System.print("MyClass instantiated with value: " + value);
_value = value;

}

// Destructor - Called when the object instance of this class is destroyed/goes out of scope.
func deinit() {

System.print("MyClass instance deinitialized");

}
}

func main() {

// Instantiate MyClass
var instance = MyClass("Hello, World!");

}

Getters and Setters:


As a convenient way to execute some code when a property is read or written, Gravity fully support custom getters and setters:
swift
class foo {
private var _a = 12;
var a {
set {_a = value * 100;} // value is default parameter name
get {return _a/2;}
};
var b {
// in this case b is a write-only property
set (newb) {_a = newb * 50;} // parameter name can be specified
};
}

func main() {
var f = foo();
f.a = 14; // 14*100 = 1400
return f.a; // 1400/2 = 700
}

Adding methods at runtime:


Sometimes you need to add methods at runtime to a particular instance, this is far more efficient than subclassing and in many cases it could be a decision than can be applied only at runtime. Gravity provides a convenient bind method specifically developed to manage this feature:
swift
class foo {
func f1() {System.print("Hello from f1");}
}

func main() {
var obj = foo();
obj.f1(); // Output: Hello from f1

// add a new f2 method to obj instance
obj.bind("f2", {System.print("Hello from f2");});
obj.f2(); // Output: Hello from f2

// replace f1 method
obj.bind("f1", {System.print("Hello from f1 new");});
obj.f1(); // Output: Hello from f1 new

// with unbind you can remove an existing method
obj.unbind("f2");
obj.f2(); // RUNTIME ERROR: Unable to find f2
}

Nested classes:


There are many cases where nested classes can lead to more readable and maintainable code, for example as a way of logically grouping classes that are only used in one place:
swift
class Database {
public var query;

class RecordSet {
public var sql;

public func run() {
if (!sql) return 0;
System.print(sql);
return sql.length();
}

func init() {
System.print("RecordSet init called");
}
}

func init() {
System.print("Database init called");
query = RecordSet();
}
}

func main() {
var db = Database();
db.query.sql = "Hello World from Gravity!";
return db.query.run();
}

Inheritance


Single class inheritance is supported in Gravity.
swift
class Bird {
func talk() {
System.print("Cheep")
}
}

class Owl : Bird {
func talk() {
System.print("Hoot")
}
}

func main() {
var bird = Bird()
bird.talk() // Output: Cheep

var owl = Owl()
owl.talk() // Output: Hoot
}

Access specifiers


The public and private keywords can be used to restrict access to specific parts of code.

---

Closure

Closure

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures can capture and store references to any constants and variables from the context in which they are defined. Closures can be nested and can be anonymous (without a name):

swift
func f1(a) {
return func(b) {
return a + b;
}
}

func main() {
var addTen = f1(10);
return addTen(20); // result is 30
}

Disassemble


A closure can be disassembled in order to reveal its bytecode:
swift
func sum (a,b) {
return a + b;
}

func main() {
System.print(sum.disassemble());
}

// Output:
// 000000 ADD 3 1 2
// 000001 RET 3

---

Contrib

Contributing

If you find any grammatical issue, please report it using Github Issues. Or, if some sentence or paragraph is difficult to understand, feel free to make a pull request. This manual is in active development and I'll regularly update and improve it. I am not a native English speaker so feel free to correct me if something is not properly written.<br><br>If you have any question related to the material or the development of the language, feel free to open a GitHub issue or to contact me.

About me


I am Marco Bambini and you can reach me at:
* Twitter: _marcobambini
* Email: [email protected]

---

Controlflow

Control flow

Gravity provides a variety of control flow statements. Control flow is used to determine which chunks of code are executed and how many times. Branching statements and expressions decide whether or not to execute some code and looping ones execute something more than once.

If statement


It is often useful to execute different pieces of code based on certain conditions. You might want to run an extra piece of code when an error occurs, or to display a message when a value becomes too high or too low. To do this, you make parts of your code conditional. In its simplest form, the if statement has a single if condition. It executes a set of statements only if that condition is true:
swift
var counter = 30;
if (counter <= 10) {
// do something here
}

The if statement can provide an alternative set of statements, known as an else clause, for situations when the if condition is false. These statements are indicated by the else keyword:

swift
var counter = 30;
if (counter <= 10) {
// do something here
} else {
// do something else here
}

More complex if statement:

swift
var counter = 30;
if (counter <= 10) {
// do something here
} else if (counter <= 20) {
// do something else here
} else {
// do something else here
}

---

Embedding

Embedding

Gravity can be easily embedded into any C/C++ code. Suppose to have the following Gravity code:

swift
func sum (a, b) {
return a + b
}

func mul (a, b) {
return a * b
}

func main () {
var a = 10
var b = 20
return sum(a, b) + mul(a, b)
}

To keep the code as simple as possible I skipped any error check condition that would be required. The bare minimum C code to embed the above Gravity code would be:

text
/ Detailed source-code truncated for AI context efficiency. /

To load and execute a myfile Gravity source code from disk the required changes would be minimum:

c
int main (void) {
size_t size = 0;
const char *source_code = file_read("myfile.gravity", &size);

...

// compile Gravity source code into bytecode (embedded into a closure)
// notice the change of the is_static bool parameter to false
gravity_closure_t *closure = gravity_compiler_run(compiler, source_code, size, 0, false, true);

...
}

To directly execute the mul Gravity function and pass some parameter from C to Gravity some minor changes need to be performed:

text
/ Detailed source-code truncated for AI context efficiency. /

---

Enum

Enum

Enums defines a common type for a group of related values. If you are familiar with C, you will know that C enumerations assign related names to a set of integer values. Enums in Gravity are much more flexible and enable you to assign any literal value (Int, Float, String, Bool):

swift
enum state {
nothing, // default to 0
active, // default to 1
inactive, // default to 2
undetermined = 666,
error // 667
}

enum math {
pi = 3.141592,
e = 2.718281,
goldratio = 1.618033
}

enum company {
ceo = "Gauss",
cto = "Eurel",
cfo = "Nostradamus"
}

enum mixed {
one = "Hello World",
two = 3.1415,
three = 666,
four = true
}

func main() {
var a = state.active; // a = 1
var b = math.pi; // b = 3.1415
var c = company.ceo; // c = "Gauss"
var d = mixed.four; // d = true
}

Enum is a static operator, which means that at compile time the real value of the enum item is automatically replaced by Gravity.

---

Env

ENV


ENV is a class than enables to interact with environmental variables

Class methods


swift
// read an env variable
var value = ENV.get("VAR_NAME")

// write an env value
ENV.set("VAR_KEY", "VAR_VALUE")

// list all env variables
var list = ENV.keys()

Class Constants


swift
var max_arg = ENV.argc - 1 
var my_arg = ENV.argv[max_arg]

---

Extending

Extending Gravity

Gravity can be extended at runtime using the C API (please read the Embedding section before proceeding).

Three steps are required:
1. Create a new class
2. Add methods and properties to the class
3. Register this class inside the VM

In this simple example we'll create a "Foo" class with a "sum" method in C and then we'll execute it from Gravity.

text
/ Detailed source-code truncated for AI context efficiency. /

For more examples see the file gravity_core.c in the src/runtime/ directory. Most of the Gravity classes are built using these same APIs.

---

Fiber

Fiber


Fibers are user-space threads without a scheduler; a Fiber can yield and resume its execution from the place it has exited. A Fibers (or coroutine as called in other languages) are special functions that can be interrupted at any time by the user.<br><br>When a conventional function is invoked, execution begins at the start, and once a function exits, it is finished. By contrast, Fibers can exit by calling other Fibers, which may later return to the point where they were invoked in the original coroutine:

swift
func main() {
var fiber = Fiber.create({
System.print("fiber 1");
Fiber.yield()
System.print("fiber 2");
});

System.print("main 1");
fiber.call()
System.print("main 2");
fiber.call()
System.print("main 3");
}
// Output:
// main 1
// fiber 1
// main 2
// fiber 2
// main 3

A Fiber is created with create:

swift
Fiber.create( {
System.print("\(self) is the current fiber")
})

and executed till the next yield with fiber.call()

swift
var closure = {
System.print("1")
Fiber.yield()

System.print("2")
Fiber.yield()

System.print("3")
Fiber.yield()

System.print("Done")
}

var fiber = Fiber.create(closure)

fiber.call()
// prints 1

fiber.call()
// prints 2

fiber.call()
// prints 3

fiber.call()
// prints Done

System.print(fiber.isDone())
// prints true

There are 2 types of yield:
1. Fiber.yield() it returns the controll to the function calling call()
2. Fiber.yieldWaitTime(seconds) it returns the controll to the function calling call() and also store the current time internally.

The later enable a call check of the total time in seconds passed since last call(). If the time amount is not enough the call is void and the fiber is not entered.

Example:

To implement a function that do some stuff every second, like a timer, a way is to use Fiber.yieldWaitTime(seconds)

swift
var fiber = Fiber.create({
var keepGoing = true
while (keepGoing) {
keepGoing = doSomeStuff()
Console.write("Waiting")
Fiber.yieldWaitTime(1.0)
Console.write("Elapsed time: \(self.elapsedTime())")
}
})

...
// Note: this strict loop is just for reference, not a real case.
while (!fiber.isDone()) {
fiber.call()
}

---

File

File


File is a class to add I/O capabilities to Gravity.

Class methods example


swift
func main() {
var target_file = "FULL_PATH_TO_A_TEXT_FILE_HERE";
var target_folder = "FULL_PATH_TO_A_FOLDER_HERE";

// FILE TEST
var size = File.size(target_file);
var exists = File.exists(target_file);
var is_dir = File.is_directory(target_file);
var data = File.read(target_file);

System.print("File: " + target_file);
System.print("Size: " + size);
System.print("Exists: " + exists);
System.print("Is Directory: " + is_dir);
System.print("Data: " + data);

// FOLDER TEST
func closure (file_name, full_path, is_directory) {
if (is_directory) {
System.print("+ \(file_name)");
} else {
System.print(" \(file_name)");
}
}

var recursive = true;
var n = File.directory_scan(target_folder, recursive, closure);

// return the number of file processed
return n;
}

Read/write buffer example


swift
func main() {
var target_file = "FULL_PATH_TO_A_TEXT_FILE_HERE";

// WRITE TEST
// 2nd argument to open is the same as the mode argument to the fopen function
// https://pubs.opengroup.org/onlinepubs/009695399/functions/fopen.html
var f = File.open(target_file, "w+");

f.write("This is the first line\n");
f.write("This is the second line\n");
f.write("This is the third line\n");

f.close();

// READ TEST
f = File.open(target_file, "r");
var data = f.read(40);
f.close();

return data;
}

Read line example


text
func main() {
var target_file = "FULL_PATH_TO_A_TEXT_FILE_HERE";
var f = File.open(target_file, "w+");

f.write("This is the first line\n");
f.write("This is the second line\n");
f.write("This is the third line\n");

f.close();

f = File.open(target_file, "r");
while (!f.isEOF()) {
var line = f.read("\n");
System.print(line);
}

f.close();

return 0;
}

---

Float

Float

In most dynamically typed programming language both Integers and Float are internally represented by a C double value.

In a modern 64bit system, this implementation leads to some issue because some integer values cannot be correctly represented by a double value (for more details please read Storage of integer values in double).

In Gravity Int and Float are internally represented by two different types to mitigate rounding errors.

An Float represents a 64 bit floating point number (can optionally be compiled as 32 bit floating point number):

swift
var a = 3.1415;   // float
var b = 1.25e2; // scientific notation

var f = 30.5.radians // returns the result of converting 30.5 degrees to radians
var f = 3.14.degrees. // returns the result of converting 3.14 radians to degrees

The Float class exposes also a min/max property used to know at runtime lower/upper bound values:

swift
var min = Float.min;    // 2.22507e-308 in 64bit systems
var max = Float.max; // 1.79769e+308 in 64bit systems

Other useful methods:

swift
var f = 3.1415;       // float
var f1 = f.ceil(); // result is 4 (ceil computes the smallest integer value not less than f)
var f2 = f.round(); // result is 3 (round computes the nearest integer value to f)
var f3 = f.floor(); // result is 3 (floor computes the largest integer value not greater than f)

---

Func

Function

Functions are first class objects like Int or String and can be stored in local variables (even in Lists or Maps), passed as function parameters or returned by a function. Functions can be implemented in Gravity or in a native language with calling conventions compatible with ANSI C.<br><br>Functions are called by value. This means that foo(1) calls the function which is the value of the variable foo. Calling a value that is not a function (or does not implement the exec method) will raise a runtime error.

swift
func main() {
var a = 10;
var b = 20;
return a + b;
}

swift
func f1() {
return 10;
}

func f2() {
return f1;
}

func main() {
// a is now function f2
var a = f2;

// b is now the return value of f2 which is function f1
var b = a();

// return value is f1() which is 10
return b();

// above code is equivalent to
return f2()();
}

Function parameters


Functions aren’t very useful if you can’t pass values to them so you can provide a parameter list in the function declaration. Gravity performs no check on the number of parameters so you can call a function providing more or less parameters.
swift
func sum(a, b) {
return a + b;
}

// execute the sum function
// and returns 30 as result
sum(10,20);

If a function is called with missing arguments (less than declared), the missing values are set to undefined.

swift
// sum modified to take in account missing arguments
func sum(a, b) {
// equivalent to if (a == undefined) a = 30;
if (!a) a = 30;

// equivalent to if (b == undefined) b = 50;
if (!b) b = 50;

return a + b;
}

// execute the sum function without any argument
// a has a 30 default value and b has a 50 default value
// return value is 80
sum();

If a function is called with more arguments (more than declared), the additional arguments can be accessed using the _args array.

swift
// sum modified to accept a variable number of arguments
func sum() {
var tot = 0;
for (var i in 0..<_args.count) {
tot += _args[i];
}
return tot;
}

// execute the sum function with a variable number
// of arguments returns 550 as result
sum(10,20,30,40,50,60,70,80,90,100);

Recursion


Function recursion is fully supported in Gravity (current function can be accessed using the _func reserved keyword):
swift
func fibonacci (n) {
if (n<2) return n;
// could be written as return _func(n-2) + _func(n-1)
return fibonacci(n-2) + fibonacci(n-1);
}

func main() {
return fibonacci(20);
}

Returning values


A function without a return statement returns null by default. You can explicitly return a value using a return statement.

---

Int

Int

In most dynamically typed programming language both Integers and Float are internally represented by a C double value.

In a modern 64bit system, this implementation leads to some issue because some integer values cannot be correctly represented by a double value (for more details please read Storage of integer values in double).

In Gravity Int and Float are internally represented by two different types to mitigate rounding errors.

An Int represents a 64 bit signed number (can optionally be compiled as 32 bit signed number):

swift
var a = 123;        // decimal
var b = 0xFF; // hexadecimal
var c = 0O7777; // octal
var d = 0B0101; // binary

var e = Int.random(1, 10) // returns a random int between 1 and 10 inclusive

var f = 30.radians // returns the result of converting 30 degrees to radians
var f = 3.degrees // returns the result of converting 3 radians to degrees

An Int can also be used as a convenient way to execute loops:

swift
5.loop() {
System.print("Hello World");
}
// result
// Hello World
// Hello World
// Hello World
// Hello World
// Hello World

The Int class exposes a min/max property used to know at runtime lower/upper bound values:

swift
var min = Int.min;    // -9223372036854775808 in 64bit systems
var max = Int.max; // 9223372036854775807 in 64bit systems

---

Introspection

Introspection

Type introspection is a core feature of Gravity. In Gravity, the Object class (ancestor of every class) provides methods for checking the instance's class.

All Objects now responds to the following methods:

swift
Object.introspection();
Object.methods();
Object.properties();

Each method support two optional parameters:
* param1 a Bool value (default false), if set to true returns extended information
* param2 a Bool value (default false), if set to true it scan super classes hierarchy
Result can be a List or a Map (in case of param1 set to true).

Examples:

swift
List.introspection();
// returns: [sort,reduce,loop,sorted,contains,filter,count,reverse,iterate,push,remove,pop,storeat,loadat,reversed,indexOf,next,map,join]

List.introspection(true);
// returns: [sorted:[isvar:false,name:sorted],next:[isvar:false,name:next],sort:[isvar:false,name:sort],filter:[isvar:false,name:filter],storeat:[isvar:false,name:storeat],map:[isvar:false,name:map],indexOf:[isvar:false,name:indexOf],reversed:[isvar:false,name:reversed],contains:[isvar:false,name:contains],loop:[isvar:false,name:loop],reduce:[isvar:false,name:reduce],count:[isvar:true,name:count,readonly:true],loadat:[isvar:false,name:loadat],iterate:[isvar:false,name:iterate],remove:[isvar:false,name:remove]...

---

List

List

Lists (or arrays) are simple sequence of objects, their size is dynamic and their index starts always at 0. They provide fast random access to their elements. You can create a list by placing a sequence of comma-separated expressions inside square brackets:

swift
var r = [1, 2, "Hello", 3.1415, true];

// list has a count property
var n = r.count; // n is 5

Accessing items


You can access an element from a list by calling the subscript operator [] on it with the index of the element you want. Like most languages, indices start at 0:

swift
var names = ["Mark", "Andrew", "Paul", "Ross", "Frank", "Max"];
names[0]; // "Mark"
names[2]; // "Paul"

Negative indices count backwards from the end:

swift
var names = ["Mark", "Andrew", "Paul", "Ross", "Frank", "Max"];
names[-1]; // "Max"
names[-2]; // "Frank"

Iterating items


The subscript operator works well for finding values when you know the key you’re looking for, but sometimes you want to see everything that’s in the list. Since the List class implements the iterator method, you can easily use it in a for loop:
swift
var people = ["Mark", "Andrew", "Paul", "Ross", "Frank", "Max"];
for (var name in people) {
System.print("Current name is " + name);
}

Adding items


A List instance can be expanded by setting an index that is greater than the current size of the list:
swift
var list = [10,20,30,40,50];
list[30] = 22; // list contains now 31 elements (index 0...30)

List as a stack


The List class implements the push/pop methods as a convenient way to treat a list as a stack:
swift
var list = [10,20,30,40,50];
list.push(100); // add 100 to the list
var v1 = list.pop(); // pop 100
var v2 = list.pop(); // pop 50

List Contains


The List class implements the contains methods as a convenient way to check for the existence of a value in a list:
swift
var list = [1, 2, "Hello", 3.1415, true];
return list.contains(3.1415); // Returns: true

List Join


The List class implements the join method as a convenient way to interpret a list as a string:
swift
var list = [1,2,3,4,5];
list.join(" + "); // Becomes: "1 + 2 + 3 + 4 + 5"

List Map


The List class implements the map method as a convenient way to create a new list using the current values of a list in some defined way:

swift
var numbers = [1,2,3,4,5,6,7,8,9,10]

var squared = numbers.map(func(num) {
return num*num
})
// squared is now equal to [1,4,9,16,25,36,49,64,81,100]

List Filter


The List class implements the filter method as a convenient way to create a new list that contains the elements of the original list which passed a specified test:

swift
var numbers = [1,2,3,4,5,6,7,8,9,10]

var even = numbers.filter(func(num) {
return !(num % 2)
})
// even is now equal to [2,4,6,8,10]

List Reduce


The List class implements the reduce method as a convenient way to create a new list reduces a list to a single value based on a provided callback:

swift
var numbers = [1,2,3,4,5,6,7,8,9,10]

var sum = numbers.reduce(0, func(num1, num2) {
return num1+num2
})
// sum is now equal to 55

List Sort


The List class implements the sort method as a convenient way to sort its items. By default, the sort() method sorts the values as strings (or numbers) in alphabetical (for strings) and ascending order:

swift
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort();
// fruits is now [Apple,Banana,Mango,Orange]

var numbers = [10, 3.14, 82, 1, 7];
numbers.sort();
// numbers is now [1,3.14,7,10,82]

// if you need to customize the sort algorithm you can provide a closure
func compare (a, b) {
return (a < b);
}

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort(compare);
// fruits is now [Orange,Mango,Banana,Apple]

---

Loop

Loop

While loop</h4>


A while loop performs a set of statements until a condition becomes false. These kind of loops are best used when the number of iterations is not known before the first iteration begins.
swift
func main() {
var i = 0;

while (i < 50000) {
i += 1;
}

return i;
}

Repeat-while loop</h4>


The other variation of the while loop, known as the repeat-while loop, performs a single pass through the loop block first, before considering the loop’s condition. It then continues to repeat the loop until the condition is false.

swift
func main() {
var i = 0;

repeat {
i += 1;
} while (i < 50000);

return i;
}

For loop</h4>


You can access an element from a list by calling the subscript operator [] on it with the index of the element you want. As in most languages, indices start at zero:
swift
var count = 0;
for (var i in 0...40) {
count += i;
}
return count;

The for in loop can be used over any object that supports iteration, such as Lists, Strings or Maps.

Loop method</h4>


Performing a loop is very common operation in any programming language, so Gravity adds a very convenient way to run a loop by adding a special loop method to some classes (Int, Range, List, String and Map) that accepts a closure as parameter:
swift
func main() {
4.loop({System.print("Hello World");});
}
// Output:
// Hello World
// Hello World
// Hello World
// Hello World

If we need to access the current index of the loop we can just rewrite the closure:

swift
func main() {
var target = 5;
target.loop(func (value){System.print("Hello World " + value);});
}
// Output:
// Hello World 0
// Hello World 1
// Hello World 2
// Hello World 3
// Hello World 4

Loop within a Range:
swift
func main() {
var target = 0...4;
target.loop(func (value){System.print("Hello World " + value);});
}
// also in reverse order
func main() {
var target = 4...0;
target.loop(func (value){System.print("Hello World " + value);});
}

Loop within a Lists:

swift
func main() {
var target = [10,20,30,40,50,60,70,80,90];
target.loop(func (value){System.print("Hello World " + value);});
}

Loop within a String:

swift
func main() {
var s = "abcdefghijklmnopqrstuvwxyz";

var vowels = ""
s.loop(func (c) {
if (c == "a" or
c == "e" or
c == "i" or
c == "o" or
c == "u") {
vowels += c;
}
})
System.print(vowels) // aeiou
}



Loop within a Maps where the key is passed as closure argument (please note that key order is not preserved):
swift
func main() {
var target = ["key1":10,"key2":20,"key3":30,"key4":40];
target.loop(func (key){System.print(key);});
}
// Output:
// key1
// key2
// key4
// key3

---

Map

Map

Maps are associative containers implemented as pairs each of which maps a key to a value. You can create a map by placing a series of comma-separated entries inside square brackets. Each entry is a key and a value separated by a colon:

swift
// create a new map with 4 entries
var d = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];

// map has a count property
var n = d.count; // n is 4

// create an empty map
var map = [:];

Looking up values


You can access an element from a list by calling the subscript operator [] on it with the key of the element you want:

swift
var names = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];
names["Mark"]; // 1
names["Andrew"]; // 2

Iterating items


The subscript operator works well for finding values when you know the key you’re looking for, but sometimes you want to see everything that’s in the map. Since the Map class implements the iterator method (through the keys method), you can easily use it in a for loop:
swift
var people = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];
for (var name in people.keys()) {
System.print("Current name is " + name);
}

Adding items


An item can be added to a map by simply setting a key/value:
swift
var people = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];
people["Kiara"] = 5; // people now contains the "Kiara" key with value 5

Removing items


The remove method has been added to the map class as a conveniente way to remove keys:
swift
var people = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];
people.remove("Paul");
people.remove("Ross");
return people.count; // 2 is returned in this case

Retrieving keys


The keys method has been added to the map class as a conveniente way to get access to all keys:
swift
var people = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];
return people.keys; // ["Mark", "Andrew", "Paul", "Ross"] is returned in this case

Checking for a key


To check if a key has been added to a map you can use the he hasKey method:
swift
var people = ["Mark":1, "Andrew":2, "Paul":3, "Ross":4];
return people.hasKey("Max"); // false is returned in this case because the "Max" key is not in the people map

---

Math

Math


The Math class is a class in Gravity that offers various methods for calculating more complex mathematics than the standard +,-,/, and *.

Mathematical Constants


swift
Math.PI;      // pi (~3.141593)
Math.E; // e (~2.718282)
Math.LN2; // natural log of 2 (ie. Math.log(2) = ~0.693147)
Math.LN10; // natural log of 10 (ie. Math.log(10) = ~2.302585)
Math.LOG2E; // log base 2 of e (~1.442695)
Math.LOG10E; // log base 10 of e (~0.434294)
Math.SQRT2; // sqrt of 2 (ie. Math.sqrt(2) = ~1.414214)
Math.SQRT1_2; // sqrt of 0.5 (ie. Math.sqrt(0.5) = ~0.707107)

Absolute Values


Math.abs() is a method that returns the absolute value of an integer of float.
swift
Math.abs(-10); // returns 10
Math.abs(10); // also returns 10

Trig Functions


The Math class also contains several Trigonometric Functions. All values that represent angles are in radians for these methods, and they all expect radians for inputs. You can use the ".radians" and ".degrees" properties of the Int and Float class to do conversions.
swift
Math.acos(-1);     // returns pi
Math.asin(0.5); // returns 0.523599 ( = pi/6)
Math.atan(1); // returns 0.785398 ( = pi/4 )
Math.atan2(-1,-1); // returns -2.356194 ( = -3pi/4 )

Math.cos(Math.PI); // returns -1
Math.cos(180.radians); // returns -1 (same as above)
Math.sin(Math.PI); // returns 0
Math.tan(Math.PI/4); // returns 1

Ceiling, Floor


swift
Math.ceil(4.1)  // returns 5
Math.floor(4.1) // returns 4

Rounding


Return number rounded to ndigits precision after the decimal point. If ndigits is omitted, it returns the nearest integer to its input. For Float values are rounded to the closest multiple of 10 to the power minus ndigits.
swift
Math.round(4.1) // returns 4
Math.round(4.5) // returns 5
Math.round(65.34634) // returns 65.0
Math.round(65.34634,1) // returns 65.3
Math.round(65.34634,2) // returns 65.35

Exponents and Radicals


swift
// e to the power of x
Math.exp(1) // returns 2.718282 (e)
Math.exp(2) // returns 7.389056 (e^2)

// x to the power of y
Math.pow(2,3); // returns 8

Math.sqrt(9); // 3
Math.cbrt(8); // 2
Math.xrt(4,16); // 2 (4th root of 16)

Logarithms


swift
// log base e
Math.log(Math.E) // returns 1
Math.log10(10) // returns 1
Math.logx(2,2) // returns 1
Math.logx(2,4) // returns 2

Max and Min


swift
Math.max(-1,10,2); // Returns 10
Math.min(-1,10,2); // Returns -1

Random Number


swift
Math.random()      // Returns a random number between 0.0 and 1.0
Math.random(N) // Returns a random number between 0 and N (or between 0.0 and N.0 if N is Float)
Math.random(N1,N2) // Returns a random number between N1 and N2 (they must be both Int or Float)

GCF and LCM


swift
Math.gcf(12,15,21); // 3
Math.lcm(6,15,2); // 30

---

Null

Null

It indicates the absence of a value. If you call a method that doesn’t return anything and get its returned value, you get null back. The null data type is also used to initialize uninitialized variables with a default value.

swift
class Newton {
var mass = 10;
var acceleration;
func force() {
return mass * acceleration;
}
}

func f2() {
var sir = Newton();
// acceleration instance variable has no default value
// so it is automatically set to null
return sir.force();
}

func f1() {
var a;
// a is uninitialized so it has a default null value
return a;
}

---

Object

Object

Object is the root class of every object inside Gravity. Through the Object class, objects inherit a basic interface to the runtime system and the ability to behave as Gravity objects.

All the built-in Gravity type are built from the base Object class and when you declare a new Class in Gravity without a super class then it is set by default to Object.

The new Introspection feature is built on top of the base Object class (so any other Class automatically inherits that feature).

Built-in types

Gravity has some built-in types that extend and overrides methods and classes from the base Object class:
* Int
* Float
* String
* Bool
* Null
* Class
* Function
* Fiber
* Instance
* List
* Map
* Range
* Function
* Closure
* Fiber

Special internal methods


Some methods has a very special meaning, for example by implementing the exec method your class is able to be executable via the object() notation. By implementing the loadat/storeat method your object can be accessed via the subscript shortcut object[i]. The load/store method is internally used to implement the dot notation (object.property).

The Class class overrides the exec method in order to implement Object instantiation and initialization:

swift
var foo = Foo()
// means execute the exec method of the Foo class

All these special methods are implemented by the base Object class in order to provide most the basic functionalities that an user expects for a modern object oriented programming language.

---

Operators

Operators

An operator is a special symbol or phrase that you use to check, change, or combine values. For example, the addition operator (+) adds two numbers, as in var i = 1 + 2, and the logical AND operator (&&) combines two Boolean values, as in if (flag1 && flag2).
<br><br>
Gravity supports most standard C operators and improves several capabilities to eliminate common coding errors. The assignment operator (=) does not return a value, to prevent it from being mistakenly used when the equal to operator (==) is intended. Gravity also provides two range operators as a shortcut for expressing a range of values.

Arithmetic Operators


* Addition (+)
* Subtraction (-)
Multiplication ()
* Division (/)
* Remainder (%)

swift
var n1 = 1 + 2        // equals 3
var n2 = 5 - 3 // equals 2
var n3 = 2 * 3 // equals 6
var n4 = 10.0 / 2.5 // equals 4.0
var n5 = 9 % 4 // equals 1

Assignment Operator


The assignment operator = initialize or update a value:
swift
var a = 50;       // a = 50
var b = a; // b = 50
var c = a b; // c = 50 50

Please note that contrary to many other programming languages, the assignment operator has no side effect, it means that it does not return any value.

Comparison Operators


The comparison operators return a Bool value to indicate whether or not the statement is true:

* Equal (==)
* Not equal (!=)
* Less than (<)
* Less than or equal (<=)
* Greater than (>)
* Greater than or equal (>=)
* Identical (===)
* Not identical (!==)
* Type check (is)
* Pattern match (~=)

swift
1 == 1      // true because 1 is equal to 1
1 != 2 // true because 1 is not equal to 2
1 < 2 // true because 1 is less than 2
1 <= 1 // true because 1 is less than or equal to 1
1 > 2 // false because 1 is not greater than 2
1 >= 1 // true because 1 is greater than or equal to 1
1 === 1 // true because 1 is identical to 1 (same value and same class)
1 is Int // true because 1 is of class Int

Gravity performs some conversions at runtime, so 1 == "1" but not 1 === '1'.

Logical Operators


The comparison operators return a Bool value to indicate whether or not the statement is true:

* Logical NOT (!)
* Logical AND (&&)
* Logical OR (||)


swift
!1          // false because 1 is true
1 && 0 // false because one of the two values is false
1 || 0 // true because one of the two values is true

In order to improve code readability the reserved keywords not, and, or has been introduces as an alisas to logical operators.

Bitwise Operators


* Bitwise shift left (<<)
* Bitwise shift right (>>)
* Bitwise AND (&)
* Bitwise OR (|)
* Bitwise XOR (^)
* Bitwise NOT or one's complement (~)


swift
var n = 0B00110011;
var n1 = n << 2 // equals 11001100
var n2 = n >> 2 // equals 00001100
var n3 = n & 0B00001111 // equals 00000011
var n4 = n | 0B00001111 // equals 00111111
var n5 = n ^ 0B00001111 // equals 00111100
var n6 = ~n; // equals 11001100

Compound Assignment Operators


As a shortcut, assignment and operators can be combined together:

Multiply and assign (=)
* Divide and assign (/=)
* Remainder and assign (%=)
* Add and assign (+=)
* Subtract and assign (-=)
* Left bit shift and assign (<<=)
* Right bit shift and assign (>>=)
* Bitwise AND and assign (&=)
* Bitwise XOR and assign (^=)
* Bitwise OR and assign (|=)

---

Range

Range

A range is an object that represents a consecutive range of numbers. Syntax for this type has been directly inspired by Swift.

swift
// a represents a range with values 1,2,3
var a = 1...3;

// b represents a range with values 1,2
var b = 1..<3;

// Ranges have also a conveniente count property
var n1 = a.count; // n1 is now 3
var n2 = b.count; // n2 is now 2

A range is expecially useful in for loops:

swift
for (var i in 1...10) {
// repeat for 10 times (with i from 1 to 10)
}

---

String

String

Strings are an immutable sequence of characters. String literals can be surrounded with double or single quotes. Gravity supports UTF-8 strings and characters.

text
/ Detailed source-code truncated for AI context efficiency. /

Strings can contain inline expressions with backslash and parentheses.

swift
var amount = 7
var fruit = "apples"
var n = "You have \(amount) \(fruit)!" // n is now "You have 7 apples!"

---

Syntax

Syntax

Gravity syntax is designed to be familiar to people coming from C-like languages like Javascript, Swift, C++, C# and many more. We started working on this new language a year before Apple announced Swift and we were happily surprised to discovered how similar both syntax appear.
>In Gravity semicolon separator ; is completely optional.


What Gravity code looks like:

swift
class Rectangle {
// instance variables
var width;
var height;

// instance method
func area() {
return width*height;
}

// constructor
func init(w, h) {
width = w;
height = h;
}
}

func main() {
// initialize a new Rectangle object
var r = Rectangle(20, 10);

// return value is 20*10 = 200
return r.area();
}

Comments


Gravity supports both line comments:
swift
// This is a line comment

and block comments:
swift
/*
This
is
a
multi-line
comment
*/

While Gravity uses C-Style comments, Gravity still supports the common "#!" shebang to tell your shell what program to execute the file with. However, the shebang must be on the first line of the file in order to use it in this way:
swift
#!/path/to/gravity

func main() {
System.print("Execute as: path/to/file.gravity");
System.print("Instead of: gravity path/to/file.gravity");
}

Include


To statically include a gravity file from within another gravity file you can use the #include statement.
Please note that in the current version, the path to the included file is relative to the location that the gravity executable was run.
swift
// adder.gravity
func add(x, y) {
return x+y;
}

swift
// main.gravity
#include "adder.gravity"

func main() {
System.print("5+4=" + add(5,4));
}

Import


While #include is used to include file at compilation time, the import statement enables you to load modules at runtime.
Import is under active development and it will be available pretty soon.

Reserved Keywords


Like many other programming languages Gravity has some reserved keywords that assume a very specific meaning in the context of the source code:
swift
if in or is for var and not func else true enum case null
file lazy super false break while class const event _func
_args struct repeat switch return public static extern
import module default private continue internal undefined

Identifiers


Identifiers represent a naming rule used to identify objects inside your source code. Gravity is a case-sensitive language. Identifiers start with a letter or underscore and may contain letters, digits, and underscores (function identifiers can be any of the built-in operators in order to override a default behaviour):
swift
a
_thisIsValid
Hello_World
foo123
BYE_BYE

Blocks and Scope


Every named identifier introduced in some portion of the source code is introduced in a scope. The scope is the largest part of the source code in which that identifier is valid. The names declared by a declaration are introduced into a specific scope based on the context of the declaration. For instance, local variable declarations introduce the name into the block scope, whereas class member variable declarations introduce the name into class scope.<br><br>There are three scopes defined: block scope, class scope and file scope. Names declared in the block scope become visible immediately after its completed declarator. This means you cannot refer to a name within the block scope until after it has been fully declared. Names declared in the file and class scopes become visible immediately upon executing the starting statement of the script. This means you can refer to a name within the file or class scopes before it has been fully declared.<br><br>These are all valid scopes:
swift
// file scope can refer to a name
// before it has been fully declared
func f1() {
return f2();
}
func f2() {
return 42;
}

// block scope can be nested and
// can hide other local variables
func f3() {
var a = 10;
if (a > 0) {
var a = 20;
}
// 10 is returned here
return a;
}

---

System

System


System class is a class registered in every Gravity VM that offers some useful methods and properties.


swift
func main() {
// print to stdout and add a newline character
System.print("Hello World");

// print to stdout without any newline character appended
System.put("Hello World");
}

Garbage collector methods


Gravity automatically manages memory for you using a tri-colour marking garbage collector, using the System class the user has the ability to change some of its settings and even disable it when certain performance critical tasks need to be performed:
swift
func main() {
// disable GC
System.gcEnabled = false;

// ratio used during automatic recomputation of the new gcthreshold value
var ratio = System.gcRatio;

// minimum GC threshold size
var minthreshold = System.gcMinThreshold;

// memory required to trigger a GC
var threshold = System.gcThreshold;

// enable GC
System.gcEnabled = true;
}


There are times where it could be really useful to easily measure how much time is spent in a given task:
swift
func main() {
var t1 = System.nanotime();
perform_my_task();
var t2 = System.nanotime();

// return elapsed time in ms
return ((t2-t1) / 1000000.0);
}

System Exit


There are times where it could be useful to have main() return an
error code back to your shell:
swift
func main() {
foo(); // Do something useful
System.exit(5);
}

In your terminal, you can now reference the return code:
bash
# Returns: 5
$ echo $?

---

Types

Values and Types

Gravity is a dynamically typed language so variables do not have a type, although they refer to a value that does have a type. In Gravity everything is an object (with methods you can call and instance variables you can use). Basic built-in types are of class Object, Int, Float, String, Bool, Null, Class, Function, Fiber, Instance, List, Map and Range.

Manifest Typing

Gravity supports manifest typing, so you can specify which type is associated with an Object with a syntax like:

swift
var s:String = "Hello World";
var n:Int = 100;

It can also be used in func parameters:

swift
func sum (a:Int, b: Int) {
return a + b;
}

Manifest typing is currently used in autocompletion feature, full static type checking will be introduced in a future release.

---

Unittest

Unit Test


Unit testing is so important (expecially for a programming language) that Gravity has built-in unit testing capabilities. What a user needs to do is setup some delegate C methods in order to be able to correctly setup a unit-test. Gravity already has a unit-test executable so in order to add your code to it you need to create a .gravity file using the special #unittest preprocessor macro:

swift
#unittest {
name: "A simple add operation.";
error: NONE;
result: 33;
};

func main() {
var a = 11;
var b = 22;
return a + b;
}


A unit test can also be written in order to be able to check for syntax, semantic and runtime errors. For syntax and semantic errors the user can also specify row and column of the expected generated error.

---

ARCHITECTURE

Gravity Language Architecture

This document provides a detailed technical description of the Gravity programming language implementation, covering the full compilation pipeline, runtime virtual machine, type system, garbage collector, embedding API, and supporting infrastructure.

Table of Contents

- 1. High-Level Overview
- 2. Compilation Pipeline
- 2.1 Lexer
- 2.2 Parser
- 2.3 Abstract Syntax Tree (AST)
- 2.4 Semantic Analysis — Pass 1
- 2.5 Semantic Analysis — Pass 2
- 2.6 Code Generation (AST → IR)
- 2.7 IR Representation
- 2.8 Optimizer & Bytecode Emission
- 3. Runtime Virtual Machine
- 3.1 VM Structure
- 3.2 Instruction Dispatch
- 3.3 Instruction Set
- 3.4 Instruction Encoding
- 3.5 Stack and Call Frames
- 3.6 Fiber (Coroutine) Model
- 3.7 Execution Flow
- 3.8 Fast-Path Optimizations
- 4. Value System and Type Hierarchy
- 4.1 Value Representation
- 4.2 Object Header and GC Metadata
- 4.3 Built-in Types
- 4.4 Functions and Closures
- 4.5 Upvalues
- 4.6 Classes and Instances
- 5. Garbage Collector
- 6. Core Data Structures
- 6.1 Hash Table
- 6.2 Dynamic Array
- 6.3 Memory Management
- 7. Optional Modules
- 7.1 Registration Pattern
- 7.2 Math Module
- 7.3 File Module
- 7.4 JSON Module
- 7.5 ENV Module
- 8. Embedding API
- 8.1 Compiler API
- 8.2 VM API
- 8.3 Delegate Pattern
- 8.4 Bridging
- 9. Utilities
- 10. CLI
- 11. Build System
- 12. Test Infrastructure

---

1. High-Level Overview

Gravity is a dynamically typed, embeddable programming language written in portable C99 with zero external dependencies (only stdlib). It features Swift-like syntax and supports procedural, object-oriented, functional, and prototype-based programming paradigms.

The implementation follows a classic multi-pass compiler architecture that produces register-based bytecode executed by a stack-based virtual machine with coroutine (fiber) support.

Source Layout

text
src/
├── cli/ CLI entry point (gravity.c)
├── compiler/ Lexer, parser, AST, semantic analysis, IR, optimizer, codegen
├── runtime/ Virtual machine (gravity_vm), built-in types (gravity_core)
├── shared/ Value representation, opcodes, hash table, dynamic array, memory/GC
├── optionals/ Optional modules: Math, File, JSON, ENV
└── utils/ Debug disassembler, JSON serialization, file I/O, UTF-8 utilities

Full Pipeline

text
Source Code


┌──────────┐
│ Lexer │ Character stream → Token stream
└────┬─────┘

┌──────────┐
│ Parser │ Token stream → Abstract Syntax Tree
└────┬─────┘

┌──────────────┐
│ Semacheck 1 │ Gather non-local declarations into symbol tables
└──────┬───────┘

┌──────────────┐
│ Semacheck 2 │ Resolve identifiers, detect upvalues, validate scopes
└──────┬───────┘

┌──────────────┐
│ Codegen │ AST → IR instructions (virtual registers)
└──────┬───────┘

┌──────────────┐
│ Optimizer │ Constant folding, dead code elimination, label resolution
└──────┬───────┘

┌──────────────┐
│ Bytecode │ Packed 32-bit instruction words
└──────┬───────┘

┌──────────────┐
│ VM │ Register-based execution with computed goto dispatch
└──────────────┘

The compiler entry point (gravity_compiler_run in gravity_compiler.c) orchestrates this pipeline: it creates a mini VM for GC during compilation, runs the parser to produce an AST, applies both semantic passes, generates IR code, optimizes it into final bytecode, and returns a gravity_closure_t ready for execution.

---

2. Compilation Pipeline

2.1 Lexer

Files: src/compiler/gravity_lexer.c, src/compiler/gravity_lexer.h

The lexer is a zero-allocation streaming tokenizer that scans source code character-by-character, producing tokens without copying or modifying the input buffer. Token values are pointers into the original source string.

#### Lexer State

c
struct gravity_lexer_t {
const char *buffer; // source buffer (not owned)
uint32_t offset; // current byte offset
uint32_t position; // current character position (UTF-8 aware)
uint32_t length; // buffer length in bytes
uint32_t lineno; // 1-based line number
uint32_t colno; // 0-based column number
uint32_t fileid; // source file identifier
gtoken_s token; // current token
bool peeking; // in peek mode
gravity_delegate_t *delegate; // error callback
};

#### Token Structure

c
struct gtoken_s {
gtoken_t type; // token type (enum)
uint32_t lineno; // line number
uint32_t colno; // column at end of token
uint32_t position; // byte offset of first character
uint32_t bytes; // length in bytes
uint32_t length; // length in UTF-8 characters
uint32_t fileid; // source file ID
gbuiltin_t builtin; // builtin identifier (__LINE__, __FILE__, etc.)
const char *value; // pointer into source buffer (NOT null-terminated)
};

#### Token Categories (~80 total)

| Category | Count | Examples |
|----------|-------|---------|
| General | 8 | EOF, ERROR, COMMENT, STRING, NUMBER, IDENTIFIER, SPECIAL, MACRO |
| Keywords | 36 | func, class, var, const, if, else, for, while, return, import, enum, switch, true, false, null, undefined, super, isa, ... |
| Operators | 36 | +, -, *, /, %, &, \|, ^, ~, <<, >>, <, <=, ==, !=, ===, !==, ~=, &&, \|\|, =, +=, ..<, ..., ... |
| Punctuators | 10 | (, ), [, ], {, }, ;, :, ., , |

#### Key Features

- UTF-8 support: Tracks both byte offset and character position separately using utf8_charbytes(). Handles 1–4 byte sequences.
- Number literals: Decimal, hexadecimal (0x), binary (0b), octal (0o), floating-point with scientific notation (1.25e-2). Uses state-machine with lookahead.
- String literals: Single (') and double (") quoted strings with backslash escapes. Multi-line strings tracked across line boundaries.
- String interpolation: Detected as LITERAL_STRING_INTERPOLATED for "text \(expr)" syntax.
- Nested block comments: / ... / ... / ... / with a nesting depth counter.
- Builtin identifiers: __LINE__, __FILE__, __COLUMN__, __CLASS__, __FUNC__ resolved during lexing.
- Line separators: CR, LF, CR+LF, NEL (U+0085), LS (U+2028).

---

2.2 Parser

Files: src/compiler/gravity_parser.c, src/compiler/gravity_parser.h

The parser uses a Pratt parser (top-down operator precedence) to build an Abstract Syntax Tree. It supports a lexer stack for #include directives and maintains a declaration scope stack for context tracking.

#### Parser State

c
struct gravity_parser_t {
lexer_r *lexer; // stack of lexers (for includes)
gnode_r *declarations; // declaration scope stack
gnode_r *statements; // statement list being built
gravity_delegate_t *delegate; // error callbacks
uint32_t nerrors; // accumulated error count
uint32_t unique_id; // unique identifier counter
uint32_t depth; // statement nesting depth
uint32_t expr_depth; // expression nesting depth
};

#### Precedence Levels

text
PREC_LOWEST       =   0
PREC_ASSIGN = 90 = += -= *= /= %= <<= >>= &= |= ^=
PREC_TERNARY = 100 ?:
PREC_LOGICAL_OR = 110 ||
PREC_LOGICAL_AND = 120 &&
PREC_COMPARISON = 130 < <= > >= == != === !== ~=
PREC_ISA = 132 is
PREC_RANGE = 135 ..< ...
PREC_TERM = 140 + - | ^
PREC_FACTOR = 150 * / % &
PREC_SHIFT = 160 << >>
PREC_UNARY = 170 + - ! ~
PREC_CALL = 200 . ( [

Each grammar rule carries a prefix handler, infix handler, precedence level, and a right-associativity flag:

c
typedef struct {
parse_func prefix; // prefix expression handler (or NULL)
parse_func infix; // infix expression handler (or NULL)
prec_level precedence; // binding power
const char *name; // operator name for diagnostics
bool right; // right-associative
} grammar_rule;

#### Statement Types

- Compound statements (blocks)
- Variable/constant declarations (var, const, with optional type annotations and initialization)
- Function declarations (with parameters, default values)
- Class declarations (with inheritance, access modifiers, struct flag)
- Enum declarations
- Module declarations
- Control flow: if/else, switch/case/default, for, while, repeat
- Jump statements: break, continue, return
- Expression statements
- Empty statements

#### Error Recovery

- One error per line: Suppresses cascading errors from the same source line.
- Token synchronization: parse_skip_until() advances to a recovery point (e.g., next statement boundary).
- Recursion limits: MAX_RECURSION_DEPTH = 1000 for statements, MAX_EXPRESSION_DEPTH = 512 for expressions.

---

2.3 Abstract Syntax Tree (AST)

Files: src/compiler/gravity_ast.c, src/compiler/gravity_ast.h

The AST uses a non-uniform node design — each node type has its own struct, but all share a common base for dispatch. A visitor pattern (gvisitor_t) is used for all tree traversals.

#### Node Types (21 total)

Statements (7):

| Node | Purpose |
|------|---------|
| NODE_LIST_STAT | Root/global statement list |
| NODE_COMPOUND_STAT | Block with local scope and symbol table |
| NODE_LABEL_STAT | Switch case/default label |
| NODE_FLOW_STAT | if/else, switch, ternary |
| NODE_JUMP_STAT | break, continue, return |
| NODE_LOOP_STAT | while, repeat, for loops |
| NODE_EMPTY_STAT | Empty statement |

Declarations (6):

| Node | Purpose |
|------|---------|
| NODE_ENUM_DECL | Enumeration definition |
| NODE_FUNCTION_DECL | Function (with params, defaults, upvalue list) |
| NODE_VARIABLE_DECL | Variable/constant declaration group |
| NODE_CLASS_DECL | Class (with superclass, protocols, ivar counts) |
| NODE_MODULE_DECL | Module definition |
| NODE_VARIABLE | Individual variable within a declaration |

Expressions (8):

| Node | Purpose |
|------|---------|
| NODE_BINARY_EXPR | Binary operations |
| NODE_UNARY_EXPR | Unary operations |
| NODE_FILE_EXPR | __FILE__ constant |
| NODE_LIST_EXPR | Array/map literals |
| NODE_LITERAL_EXPR | Numbers, strings, booleans |
| NODE_IDENTIFIER_EXPR | Variable references |
| NODE_KEYWORD_EXPR | true, false, null, undefined, super |
| NODE_POSTFIX_EXPR | Calls, subscripts, property access (with subtypes) |

#### Base Node

c
typedef struct {
gnode_n tag; // node type discriminant
uint32_t refcount; // reference counting for shared nodes
uint32_t block_length; // byte length (for autocompletion)
gtoken_s token; // source location
bool is_assignment; // assignment target flag
void *decl; // enclosing declaration
} gnode_t;

#### Location Tracking

After semantic analysis, each identifier is annotated with a resolved location:

c
typedef enum {
LOCATION_LOCAL, // local variable
LOCATION_GLOBAL, // global variable
LOCATION_UPVALUE, // closure upvalue
LOCATION_CLASS_IVAR_SAME, // instance variable (same class)
LOCATION_CLASS_IVAR_OUTER // instance variable (outer class)
} gnode_location_type;

typedef struct {
gnode_location_type type;
uint16_t index; // symbol index
uint16_t nup; // upvalue or outer index
} gnode_location_t;

#### Visitor Pattern

c
typedef struct gvisitor {
uint32_t nerr;
void *data; // visitor-specific state
void *delegate; // error callback delegate

// 22 callbacks — one per node type, plus pre/post hooks
void (*visit_pre)(visitor, node);
void (*visit_post)(visitor, node);
void (*visit_list_stmt)(visitor, node);
void (*visit_compound_stmt)(visitor, node);
void (*visit_function_decl)(visitor, node);
// ... one for each AST node type
} gvisitor_t;

The dispatch function gvisit() calls visit_pre, then the node-specific callback based on node->tag, then visit_post.

---

2.4 Semantic Analysis — Pass 1

File: src/compiler/gravity_semacheck1.c

The first semantic pass gathers all non-local declarations into symbol tables, enabling forward references. It does not perform full name resolution or type checking.

#### What It Does

1. Creates symbol tables for each scope (global, class, module, enum).
2. Inserts function, class, enum, module, and variable declarations.
3. Reports duplicate declaration errors.
4. Assigns instance variable indices for class members.
5. Applies name mangling for static class members (prefixed with "$").

#### Symbol Table

c
struct symboltable_t {
ghash_r *stack; // stack of hash tables (nested scopes)
uint16_t count1; // local variable counter
uint16_t count2; // instance variable counter
uint16_t count3; // static variable counter
symtable_tag tag; // GLOBAL, FUNC, CLASS, MODULE, or ENUM
};

This pass enables forward references — a function can call another function declared later in the same scope:

swift
func foo() { return bar(); }
func bar() { return 42; }

---

2.5 Semantic Analysis — Pass 2

File: src/compiler/gravity_semacheck2.c

The second semantic pass validates all identifiers within function bodies, resolves variable references, and detects closure upvalues.

#### What It Does

1. Validates all identifier references (reports "undefined variable" errors).
2. Resolves each identifier to its declaration and sets the location field.
3. Detects upvalue usage and builds upvalue lists for closures.
4. Validates declaration nesting constraints.
5. Checks break/continue appear only inside loops.
6. Validates module declarations are at global scope.

#### Identifier Lookup Order

The lookup traverses the declaration stack from innermost to outermost:

1. Local scope (current compound statement)
2. Enclosing function scopes
3. Enclosing class scopes (including superclass hierarchy)
4. Module scope
5. Global scope

#### Declaration Nesting Rules

What can be declared inside each construct:

text
│   func    var    enum   class   module
-------------------------------------------------
func │ YES YES NO YES YES
var │ YES NO NO YES YES
enum │ YES NO NO YES YES
class │ YES NO NO YES YES
module │ NO NO NO NO NO
-------------------------------------------------

---

2.6 Code Generation (AST → IR)

File: src/compiler/gravity_codegen.c

The code generator walks the AST using the visitor pattern and emits IR instructions with virtual registers. It maintains a context stack of functions and classes being compiled.

c
struct codegen_t {
gravity_object_r context; // stack of functions/classes
gnode_class_r superfix; // superclass resolution stack
uint32_t lasterror; // last error line
gravity_vm *vm; // mini VM for GC during codegen
};

#### Key Responsibilities

- Operator mapping: Converts token operators to opcodes (e.g., TOK_OP_ADDADD).
- Implicit self: Inserts self parameter for instance methods.
- Super calls: Emits LOADS instruction for superclass method lookup.
- Collection literals: LISTNEW/MAPNEW + SETLIST instructions.
- Range literals: RANGENEW with inclusive/exclusive flag.
- String interpolation: Converts "text \(expr)" into string concatenation operations.
- Closures: CLOSURE instruction references the function in the constant pool; CLOSE releases upvalues when scope exits.

---

2.7 IR Representation

Files: src/compiler/gravity_ircode.c, src/compiler/gravity_ircode.h

The IR is a flat sequence of instructions with virtual registers, acting as the bridge between the AST and final packed bytecode.

#### IR Instruction

c
typedef struct {
opcode_t op; // operation code
optag_t tag; // metadata tag
int32_t p1, p2, p3; // operand parameters
union {
double d; // embedded float constant (DOUBLE_TAG)
int64_t n; // embedded int constant (INT_TAG)
};
uint32_t lineno; // source line for debug info
} inst_t;

#### Instruction Tags

| Tag | Meaning |
|-----|---------|
| NO_TAG | Normal instruction |
| INT_TAG | Carries an embedded integer literal |
| DOUBLE_TAG | Carries an embedded float literal |
| LABEL_TAG | Label marker (resolved to offset by optimizer) |
| SKIP_TAG | Dead instruction (removed by optimizer) |
| RANGE_INCLUDE_TAG | Inclusive range flag |
| RANGE_EXCLUDE_TAG | Exclusive range flag |
| PRAGMA_MOVE_OPTIMIZATION | Hint for move elimination |

#### Register Allocation

The IR uses a bitmask-based register allocator (256 registers max = 32 bytes of bitmask):

- Local registers [0 .. nlocals-1]: Reserved for parameters and local variables.
- Temp registers [nlocals .. 255]: Allocated/freed for expression evaluation.
- Register 0 is always reserved.

Key operations:
- ircode_register_push_temp() — allocate the next free temp register.
- ircode_register_pop() — free the most recently allocated temp register.
- ircode_register_first_temp_available() — find first free temp slot.

#### Label Management

Three separate label stacks manage control flow:
- label_true — target for true branch of conditionals.
- label_false — target for false branch.
- label_check — target for loop checks and safety guards.

---

2.8 Optimizer & Bytecode Emission

Files: src/compiler/gravity_optimizer.c, src/compiler/gravity_optimizer.h

The optimizer is the final compilation stage. It converts IR instructions into packed 32-bit bytecodes, resolves labels, and applies peephole optimizations.

#### Optimizations Performed

1. Constant folding: Arithmetic on constant operands evaluated at compile time.

text
LOADI r1, 5 ; LOADI r2, 3 ; ADD r0, r1, r2  →  LOADI r0, 8

2. Dead code elimination: Unreachable instructions after unconditional jumps/returns are marked SKIP and removed.

3. Move elimination: Redundant MOVE instructions are detected via PRAGMA_MOVE_OPTIMIZATION hints and removed when safe.

4. Label resolution: Symbolic labels are mapped to concrete instruction offsets.

#### 32-Bit Instruction Encoding

text
Standard (3 operands):  [ opcode:6 | A:8 | B:8 | C:10 ]
LOADI (immediate): [ opcode:6 | A:8 | sign:1 | N:17 ]
JUMP (offset): [ opcode:6 | N:26 ]

---

3. Runtime Virtual Machine

3.1 VM Structure

Files: src/runtime/gravity_vm.c, src/runtime/gravity_vm.h

The VM is an opaque struct (gravity_vm) with the following key components:

c
struct gravity_vm {
// Execution
gravity_fiber_t *fiber; // current fiber (coroutine)
gravity_hash_t *context; // global variable table
gravity_delegate_t *delegate; // runtime delegate
uint32_t pc; // program counter
bool aborted; // runtime error flag

// Recursion limits
uint32_t maxccalls; // max nested C calls (default: 100)
uint32_t nccalls; // current C call depth
gravity_int_t maxrecursion;// max recursive depth (0 = unlimited)

// Garbage collector
int32_t gcenabled; // reference-counted enable flag
gravity_object_t *gchead; // linked list of all GC objects
gravity_object_r graylist; // mark phase gray list
gravity_object_r gctemp; // temporary GC-protected objects
gravity_int_t memallocated;// total allocated memory
gravity_int_t gcthreshold; // GC trigger threshold (default: 5MB)
gravity_int_t gcminthreshold; // minimum threshold (default: 1MB)
gravity_float_t gcratio; // threshold growth ratio (default: 0.5)

// Callbacks
vm_transfer_cb transfer; // object allocation hook
vm_cleanup_cb cleanup; // VM cleanup hook
vm_filter_cb filter; // selective cleanup filter
};

An internal operator name cache (cache[GRAVITY_VTABLE_SIZE]) holds pre-computed strings for operator method names ("+", "-", "*", etc.) to avoid repeated allocations during dispatch.

---

3.2 Instruction Dispatch

File: src/runtime/gravity_vmmacros.h

The VM uses computed goto for instruction dispatch (GCC/Clang), falling back to a switch statement on MSVC:

c
// Computed goto (GCC/Clang):
#define DISPATCH() goto dispatchTable[OPCODE_GET_OPCODE(ip)]

// Switch fallback (MSVC):
#define INTERPRET_LOOP switch (OPCODE_GET_OPCODE(*ip))
#define CASE_CODE(x) case x:

Computed goto provides O(1) dispatch with no branch prediction overhead. Each opcode is a label address stored in a static table, and DISPATCH() performs an indirect jump.

Key macros in the dispatch loop:

| Macro | Purpose |
|-------|---------|
| OPCODE_GET_OPCODE(inst) | Extract 6-bit opcode |
| OPCODE_GET_ONE8bit_ONE18bit(inst, A, N) | Decode register + immediate |
| OPCODE_GET_THREE8bit(inst, A, B, C) | Decode three register operands |
| LOAD_FRAME() | Synchronize local variables from fiber state |
| STORE_FRAME() | Save local variables back to fiber state |
| PUSH_FRAME(closure, stackstart, dest, nargs) | Create a new call frame |
| FN_COUNTREG(f, nargs) | Compute register window size: max(nparams, nargs) + nlocals + ntemps |

---

3.3 Instruction Set

The VM implements 56 opcodes (6-bit opcode field supports up to 64):

#### General (5)

| Opcode | Description |
|--------|-------------|
| RET0 | Return null |
| HALT | Stop VM execution |
| NOP | No operation |
| RET | Return value from register |
| CALL | Call function/closure |

#### Load/Store (13)

| Opcode | Semantics |
|--------|-----------|
| LOAD | R(A) = R(B)[R(C)] — property access |
| LOADAT | R(A) = R(B)[R(C)] — subscript access |
| LOADS | Super property access |
| LOADK | R(A) = K(Bx) — load constant from pool |
| LOADG | R(A) = G[K(Bx)] — load global |
| LOADI | R(A) = N — load inline integer |
| LOADU | R(A) = U(B) — load upvalue |
| MOVE | R(A) = R(B) — register copy |
| STORE | R(B)[R(C)] = R(A) — property write |
| STOREAT | R(B)[R(C)] = R(A) — subscript write |
| STOREG | G[K(Bx)] = R(A) — store global |
| STOREU | U(B) = R(A) — store upvalue |

#### Jump (2)

| Opcode | Semantics |
|--------|-----------|
| JUMP | Unconditional jump (26-bit signed offset) |
| JUMPF | Jump if false (18-bit signed offset) |

#### Arithmetic & Logic (19)

| Opcode | Operation |
|--------|-----------|
| ADD, SUB, MUL, DIV, REM | Arithmetic |
| AND, OR | Logical and/or |
| LT, GT, LEQ, GEQ | Ordered comparison |
| EQ, NEQ | Equality |
| EQQ, NEQQ | Strict equality (identity) |
| ISA | Instance-of check |
| MATCH | Pattern match (~=) |
| NEG, NOT | Unary negation/logical not |

#### Bitwise (6)

| Opcode | Operation |
|--------|-----------|
| LSHIFT, RSHIFT | Bit shifts |
| BAND, BOR, BXOR | Bitwise and/or/xor |
| BNOT | Bitwise complement |

#### Collections (4)

| Opcode | Semantics |
|--------|-----------|
| MAPNEW | R(A) = new Map(B) |
| LISTNEW | R(A) = new List(B) |
| RANGENEW | R(A) = new Range(B, C, flag) |
| SETLIST | Populate list/map from register range |

#### Closures (2)

| Opcode | Semantics |
|--------|-----------|
| CLOSURE | Create closure from function constant |
| CLOSE | Close open upvalues at register level |

#### Special (1)

| Opcode | Semantics |
|--------|-----------|
| CHECK | Clone struct value (enforces value semantics) |

#### Operator Vtable

Each class defines operator methods via a vtable indexed by GRAVITY_VTABLE_INDEX:

c
typedef enum {
GRAVITY_ADD_INDEX, // "+"
GRAVITY_SUB_INDEX, // "-"
GRAVITY_MUL_INDEX, // "*"
GRAVITY_DIV_INDEX, // "/"
// ... one for each overloadable operator
GRAVITY_EXEC_INDEX // "()" — call
} GRAVITY_VTABLE_INDEX;

---

3.4 Instruction Encoding

All instructions are 32 bits wide with varying field layouts:

text
Standard 3-operand:   [ opcode:6 ][ A:8 ][ B:8 ][ C:10 ]
Immediate (LOADI): [ opcode:6 ][ A:8 ][ sign:1 ][ N:17 ]
Jump (JUMP): [ opcode:6 ][ N:26 ]

Operand extraction uses bit shifts and masks:

c
#define OPCODE_GET_OPCODE(v)                ((v >> 26) & 0x3F)
#define OPCODE_GET_THREE8bit(v, A, B, C) A = (v >> 18) & 0xFF; \
B = (v >> 10) & 0xFF; \
C = v & 0x3FF;
#define OPCODE_GET_ONE8bit_ONE18bit(v, A, N) A = (v >> 18) & 0xFF; \
N = v & 0x3FFFF;

---

3.5 Stack and Call Frames

#### Call Frame

c
typedef struct {
uint32_t *ip; // instruction pointer
uint32_t dest; // destination register for return value
uint16_t nargs; // actual argument count
gravity_list_t *args; // implicit _args array (if needed)
gravity_closure_t *closure; // closure being executed
gravity_value_t *stackstart; // first stack slot of this frame
bool outloop; // set when called from gravity_vm_runclosure
} gravity_callframe_t;

#### Stack Layout Per Frame

text
stackstart[0]          = self (implicit first parameter)
stackstart[1..n] = explicit parameters
stackstart[n+1..m] = local variables
stackstart[m+1..p] = temporary values

#### Sliding Register Window

When a CALL instruction executes, the register window for the callee starts at r2+1 (where r2 is the callable register). This sliding window design minimizes value copying between frames:

text
Caller:     [ ... | self | arg1 | arg2 | ... ]

rwin = r2 + 1 → callee's stackstart
Callee: [ self | arg1 | arg2 | locals... | temps... ]

The stack grows on demand (power-of-2 reallocation). When the stack is reallocated, all frame pointers are adjusted to maintain consistency. The stack never shrinks.

---

3.6 Fiber (Coroutine) Model

Fibers are Gravity's concurrency primitive. Each fiber has its own stack and call frame array, enabling cooperative multitasking.

c
typedef struct {
gravity_class_t *isa;
gravity_gc_t gc;

// Stack
gravity_value_t *stack; // value stack buffer
gravity_value_t *stacktop; // current stack pointer
uint32_t stackalloc; // allocated capacity

// Call frames
gravity_callframe_t *frames; // frame buffer
uint32_t nframes; // frames in use
uint32_t framesalloc; // allocated capacity

// Closures
gravity_upvalue_t *upvalues; // open upvalue linked list

// Status
gravity_fiber_status status; // NEVER_EXECUTED, RUNNING, ABORTED, TERMINATED, TRYING
char *error; // error message
bool trying; // inside try block
gravity_fiber_t *caller; // parent fiber
gravity_value_t result; // final result

// Timing (for yield with timeout)
nanotime_t lasttime;
gravity_float_t timewait;
gravity_float_t elapsedtime;
} gravity_fiber_t;

Fiber status values: FIBER_NEVER_EXECUTED, FIBER_RUNNING, FIBER_ABORTED_WITH_ERROR, FIBER_TERMINATED, FIBER_TRYING.

---

3.7 Execution Flow

#### gravity_vm_exec — Main Bytecode Loop

c
bool gravity_vm_exec(gravity_vm *vm) {
DECLARE_DISPATCH_TABLE;
// Load fiber, frame, function, stackstart, ip, bytecode ...

while (1) {
INTERPRET_LOOP {
CASE_CODE(ADD): {
// 1. Decode operands
// 2. Check fast path (inline int/float arithmetic)
// 3. Fallback: look up "+" method on r2's class
// 4. Call method, store result
DISPATCH();
}
CASE_CODE(CALL): {
// 1. Decode: r1=dest, r2=callable, r3=nargs
// 2. Compute register window: rwin = r2 + 1
// 3. Resolve closure (directly or via "exec" method)
// 4. Push frame, fill defaults for missing args
// 5. Dispatch by type:
// - NATIVE: PUSH_FRAME, continue loop
// - INTERNAL: call C function directly
// - BRIDGED: call delegate->bridge_execute
DISPATCH();
}
CASE_CODE(RET): {
// 1. Pop frame
// 2. Close open upvalues
// 3. If outloop flag → return to gravity_vm_runclosure
// 4. Else → continue with caller frame
DISPATCH();
}
// ... 53 more opcodes
}
}
}

#### gravity_vm_runclosure — External Entry Point

Called from the embedding API or internally to invoke a specific closure:

1. Validate VM is not aborted.
2. Set up stack window and parameters.
3. Dispatch by function type:
- Native: Increment nccalls, call gravity_vm_exec(), decrement.
- Internal: Call C function pointer directly.
- Bridged: Call delegate bridge_execute callback.
4. Restore frame pointers and adjust stack top.

---

3.8 Fast-Path Optimizations

- Inline arithmetic: When both operands are Int or Float, arithmetic is computed directly without method lookup.
- Jump fusion: Compare instructions (e.g., EQ, LT) peek ahead for a following JUMPF. If found, the compare and jump are fused into a single operation.
- Register window: The sliding register window avoids copying arguments between caller and callee.
- Computed goto: O(1) instruction dispatch with no branch prediction overhead.
- Pre-allocated frames: Call frames and stack space are pre-allocated and reused.

---

4. Value System and Type Hierarchy

4.1 Value Representation

Files: src/shared/gravity_value.h, src/shared/gravity_value.c

Gravity uses a 16-byte tagged union for all values (not NaN-boxing):

c
typedef struct {
gravity_class_t *isa; // 8 bytes: type tag (pointer to class)
union { // 8 bytes: payload
gravity_int_t n; // integer value
gravity_float_t f; // float/double value
gravity_object_t *p; // pointer to heap object
};
} gravity_value_t;

The isa pointer serves double duty: it identifies the type and provides the method lookup table. Special sentinel values:
- Null: isa = NULL, n = 0
- Undefined: isa = NULL, n = 1

Unboxed types (value stored directly in the union): Bool, Int, Float, Null, Undefined.

Boxed types (pointer to heap-allocated object): String, List, Map, Class, Instance, Closure, Function, Range, Fiber, Upvalue.

---

4.2 Object Header and GC Metadata

All heap-allocated objects share a common header:

c
typedef struct gravity_object_s {
gravity_class_t *isa; // class pointer (method dispatch)
gravity_gc_t gc; // GC metadata
} gravity_object_t;

typedef struct {
bool isdark; // marked during GC
bool visited; // prevents double-counting in size calc
gravity_object_t *next; // intrusive linked list (GC object chain)
gravity_gc_callback free; // destructor callback
gravity_gc_callback size; // size reporting callback
gravity_gc_callback blacken; // mark-children callback
} gravity_gc_t;

Every heap object is linked into the VM's GC chain via gc.next. The three callbacks (free, size, blacken) implement type-specific GC behavior without virtual dispatch overhead.

---

4.3 Built-in Types

The runtime registers these built-in classes (in gravity_core.c):

| Class | Behavior |
|-------|----------|
| gravity_class_int | 64-bit integer, arithmetic operators, bitwise ops |
| gravity_class_float | IEEE 754 double, arithmetic operators |
| gravity_class_bool | Boolean, logical operators |
| gravity_class_null | Null singleton |
| gravity_class_string | Immutable UTF-8 string, concatenation, methods |
| gravity_class_object | Base class (all types inherit from this) |
| gravity_class_function | Function prototype |
| gravity_class_closure | Closure (function + captured environment) |
| gravity_class_fiber | Fiber (coroutine) |
| gravity_class_class | Metaclass |
| gravity_class_instance | User-defined class instance |
| gravity_class_list | Dynamic array |
| gravity_class_map | Hash map |
| gravity_class_range | Integer range (inclusive or exclusive) |
| gravity_class_upvalue | Captured variable reference |

Each class binds operator methods and instance methods. For example, gravity_class_int binds "+", "-", "*", etc. as well as methods like loop(), random(), and conversion operators.

---

4.4 Functions and Closures

#### Function Prototype

c
typedef struct {
gravity_class_t *isa;
gravity_gc_t gc;

const char *identifier; // function name
uint16_t nparams; // formal parameters (including self)
uint16_t nlocals; // local variables
uint16_t ntemps; // temporary registers
uint16_t nupvalues; // captured variables
gravity_exec_type tag; // execution type

union {
// EXEC_TYPE_NATIVE (compiled Gravity code):
struct {
gravity_value_r cpool; // constant pool
gravity_value_r pvalue; // default parameter values
gravity_value_r pname; // parameter names
uint32_t ninsts; // instruction count
uint32_t *bytecode; // packed 32-bit instructions
uint32_t *lineno; // line number mapping (debug)
bool useargs; // needs implicit _args array
};

// EXEC_TYPE_INTERNAL (C callback):
gravity_c_internal internal; // bool (*)(vm, args, nargs, rindex)

// EXEC_TYPE_SPECIAL (computed property):
struct {
uint16_t index; // property index
void *special[2]; // [0]=getter, [1]=setter
};
};
} gravity_function_t;

Execution types:
- EXEC_TYPE_NATIVE — compiled Gravity bytecode.
- EXEC_TYPE_INTERNAL — C function callback with signature bool ()(gravity_vm, gravity_value_t*, uint16_t, uint32_t).
- EXEC_TYPE_BRIDGED — external bridge, executed via delegate callback.
- EXEC_TYPE_SPECIAL — getter/setter computed property.

#### Closure

c
typedef struct {
gravity_class_t *isa;
gravity_gc_t gc;
gravity_vm *vm; // owning VM
gravity_function_t *f; // function prototype (shared)
gravity_object_t *context; // captured self reference
gravity_upvalue_t upvalue; // captured upvalue array
uint32_t refcount; // bridge reference counting
} gravity_closure_t;

Multiple closures can share the same function prototype while having different captured environments.

---

4.5 Upvalues

Upvalues implement Lua-style open/closed variable capture:

c
typedef struct upvalue_s {
gravity_class_t *isa;
gravity_gc_t gc;
gravity_value_t *value; // points to stack slot (open) or self->closed (closed)
gravity_value_t closed; // storage when variable leaves scope
struct upvalue_s *next; // linked list (ordered by stack position)
} gravity_upvalue_t;

- Open upvalue: value points to a live stack slot. The fiber maintains a linked list of open upvalues ordered by descending stack address.
- Closed upvalue: When the enclosing function returns, the captured value is copied from the stack into closed, and value is repointed to &self->closed.

The CLOSE instruction walks the open upvalue list and closes any upvalues at or above a given register level.

---

4.6 Classes and Instances

#### Class

c
typedef struct {
gravity_class_t *isa; // metaclass
gravity_gc_t gc;

gravity_class_t *objclass; // metaclass reference
const char *identifier; // class name
bool has_outer; // has outer class ivar
bool is_struct; // value semantics (copy on assignment)
bool is_inited; // metaclass initialized
void *xdata; // bridge extension data

gravity_class_t *superclass; // parent class
const char *superlook; // extern superclass name (lazy binding)
gravity_hash_t *htable; // method/property hash table

uint32_t nivars; // instance variable count
gravity_value_r inames; // ivar names (debug)
gravity_value_t *ivars; // static (class) variables
} gravity_class_t;

Method resolution traverses the superclass chain. Methods and computed properties are stored in the class hash table.

#### Instance

c
typedef struct {
gravity_class_t *isa;
gravity_gc_t gc;
gravity_class_t *objclass; // actual class
void *xdata; // bridge extension data
gravity_value_t *ivars; // instance variable array (indexed by position)
} gravity_instance_t;

Instance variables are stored in a flat array indexed by position (set during semacheck1), providing O(1) access.

---

5. Garbage Collector

Location: src/runtime/gravity_vm.c

Gravity uses a tri-color mark-and-sweep garbage collector.

Mark Phase

1. Mark temporary protected objects (in vm->gctemp).
2. Mark the current fiber as a root.
3. Mark all globals in the context hash table.
4. Process the gray list: for each gray object, call its blacken callback to mark all referenced objects.
5. Repeat until the gray list is empty.

Sweep Phase

1. Walk the vm->gchead linked list.
2. For each object not marked (!isdark): call its free callback and remove it from the chain.
3. For each marked object: clear the isdark flag for the next cycle.

GC Triggers

- Automatic: When memallocated >= gcthreshold during gravity_gc_transfer (object allocation).
- Manual: gravity_gc_start(vm).
- Stress test: Every allocation (when compiled with GRAVITY_GC_STRESSTEST).

Dynamic Threshold Adjustment

After each collection:

text
new_threshold = memallocated + (memallocated * gcratio / 100)
if (new_threshold < minthreshold) new_threshold = minthreshold
if (new_threshold < original) new_threshold = original

Default values: gcthreshold = 5MB, gcminthreshold = 1MB, gcratio = 0.5 (50%).

GC-Safe Coding Pattern

The enable flag is reference-counted, allowing nested disable/enable calls:

c
gravity_gc_setenabled(vm, false);  // disable GC (increments counter)
// ... allocate objects safely ...
gravity_gc_setenabled(vm, true); // re-enable (decrements counter)

Temporary objects can be protected from collection:

c
gravity_gc_temppush(vm, object);   // protect
// ... use object ...
gravity_gc_temppop(vm); // unprotect

---

6. Core Data Structures

6.1 Hash Table

Files: src/shared/gravity_hash.c, src/shared/gravity_hash.h

A chained hash table used for symbol tables, class method lookup, global variables, and the Map type.

c
typedef struct hash_node_s {
uint32_t hash; // cached hash value
gravity_value_t key;
gravity_value_t value;
struct hash_node_s *next; // collision chain
} hash_node_t;

struct gravity_hash_t {
uint32_t size; // bucket count
uint32_t count; // entry count
hash_node_t nodes; // bucket array
gravity_hash_compute_fn compute_fn; // hash function
gravity_hash_isequal_fn isequal_fn; // equality function
gravity_hash_iterate_fn free_fn; // entry cleanup callback
void *data; // callback context
};

| Property | Value |
|----------|-------|
| Hash function | Murmur3-32 (seed 5381) |
| Collision resolution | Chaining (linked list per bucket) |
| Load factor | 0.75 |
| Growth strategy | Double bucket count on resize |
| Initial size | 32 buckets |
| Max entries | 2^30 |

Hash function variants: gravity_hash_compute_buffer() for strings, gravity_hash_compute_int() for integers, gravity_hash_compute_float() for floats.

---

6.2 Dynamic Array

File: src/shared/gravity_array.h

A macro-based generic dynamic array:

c
#define marray_t(type)  struct { size_t n, m; type *p; }
// count capacity data

| Macro | Purpose |
|-------|---------|
| marray_init(v) | Initialize to zero |
| marray_push(T, v, x) | Append (doubles capacity if needed) |
| marray_pop(v) | Remove and return last element |
| marray_get(v, i) | Access element by index |
| marray_size(v) | Current element count |
| marray_max(v) | Current capacity |
| marray_resize(T, v, n) | Extend capacity to at least n |
| marray_destroy(v) | Free backing memory |

Growth strategy: double capacity on each reallocation.

---

6.3 Memory Management

Files: src/shared/gravity_memory.h, src/shared/gravity_memory.c

Production mode provides thin wrappers around malloc/realloc/free with max block size enforcement (MAX_MEMORY_BLOCK = 150MB).

Debug mode (GRAVITY_MEMORY_DEBUG) adds:
- Tracking of every allocation with call stack.
- Detection of double-free and use-after-free.
- Leak reporting on shutdown.

All allocations go through mem_alloc(), which integrates with the VM's memallocated counter for GC threshold tracking.

---

7. Optional Modules

7.1 Registration Pattern

File: src/optionals/gravity_optionals.h

Each optional module follows the same pattern:

1. Compile-time guard (#ifndef GRAVITY_INCLUDE_MATH / #define GRAVITY_INCLUDE_MATH).
2. Macro wrappers that become no-ops when disabled.
3. Singleton class with reference counting.
4. Static methods bound to the metaclass.
5. Registration: gravity_vm_setvalue(vm, name, class).

c
// Typical module lifecycle:
static gravity_class_t *gravity_class_math = NULL;
static uint32_t refcount = 0;

void gravity_math_register(gravity_vm *vm) {
if (!gravity_class_math) create_optional_class();
++refcount;
gravity_vm_setvalue(vm, "Math", VALUE_FROM_OBJECT(gravity_class_math));
}

void gravity_math_free(void) {
if (--refcount) return; // wait for all VMs to unregister
// destroy class ...
}

Computed properties (read-only constants) use a getter-only closure:

c
gravity_closure_t *closure = computed_property_create(NULL, NEW_FUNCTION(getter), NULL);
gravity_class_bind(meta, "PI", VALUE_FROM_OBJECT(closure));

---

7.2 Math Module

File: src/optionals/gravity_opt_math.c — Class name: "Math"

Methods (23):

| Category | Functions |
|----------|-----------|
| Trigonometric | sin, cos, tan, asin, acos, atan, atan2 |
| Rounding | ceil, floor, round (with optional precision) |
| Logarithmic | log, log10, logx (custom base) |
| Algebraic | abs, sqrt, cbrt, xrt (nth root), pow, exp |
| Combinatorial | gcf, lcm |
| Interpolation | lerp |
| Comparison | min, max (variadic) |
| Random | random(), random(max), random(min, max) |

Constants (8): PI, E, LN2, LN10, LOG2E, LOG10E, SQRT2, SQRT1_2.

Random number generator: LFSR258 (64-bit) or LFSR113 (32-bit), seeded with nanotime() on first call.

---

7.3 File Module

File: src/optionals/gravity_opt_file.c — Class name: "File"

Uses a custom gravity_file_t struct wrapping a FILE* pointer, with GC integration for automatic cleanup.

Class (static) methods: size, exists, delete, read, write, buildpath, is_directory, directory_create, directory_scan.

Instance methods: open (factory), read, write, seek, eof, error, flush, close.

The directory_scan method accepts a closure callback invoked for each entry with (filename, fullpath, isdir).

---

7.4 JSON Module

File: src/optionals/gravity_opt_json.c — Class name: "JSON"

Two static methods:
- stringify(value) — Serialize any Gravity value to a JSON string. Handles nested structures, escapes special characters, uses heap allocation for strings >4KB.
- parse(jsonString) — Deserialize a JSON string into nested Gravity lists and maps. Returns null for invalid JSON.

---

7.5 ENV Module

File: src/optionals/gravity_opt_env.c — Class name: "ENV"

Methods: get(key), set(key, value), keys().

Properties: argc (read-only), argv (read-only list).

Supports map-access syntax: ENV["PATH"] via overloaded load/store-at handlers. Cross-platform: uses _putenv_s on Windows, setenv on Unix.

---

8. Embedding API

8.1 Compiler API

File: src/compiler/gravity_compiler.h

c
gravity_compiler_t  gravity_compiler_create(gravity_delegate_t delegate);
gravity_closure_t *gravity_compiler_run(compiler, source, len, fileid, is_static, add_debug);
gnode_t *gravity_compiler_ast(compiler);
void gravity_compiler_transfer(compiler, vm); // move objects to VM's GC
void gravity_compiler_free(compiler);

Serialization for ahead-of-time compilation:

c
json_t *gravity_compiler_serialize(compiler, closure);
bool gravity_compiler_serialize_infile(compiler, closure, path);

---

8.2 VM API

File: src/runtime/gravity_vm.h

c
// Lifecycle
gravity_vm gravity_vm_new(gravity_delegate_t delegate);
gravity_vm *gravity_vm_newmini(void); // lightweight (no optionals)
void gravity_vm_free(vm);
void gravity_vm_reset(vm);

// Execution
bool gravity_vm_runmain(vm, closure);
bool gravity_vm_runclosure(vm, closure, sender, params, nparams);
gravity_value_t gravity_vm_result(vm);

// Globals
void gravity_vm_setvalue(vm, key, value);
gravity_value_t gravity_vm_getvalue(vm, key, keylen);
gravity_value_t gravity_vm_lookup(vm, key);

// Memory & GC
void gravity_vm_transfer(vm, object);
void gravity_gc_start(vm);
void gravity_gc_setenabled(vm, enabled);
void gravity_gc_setvalues(vm, threshold, minthreshold, ratio);

// Bytecode loading
gravity_closure_t *gravity_vm_loadfile(vm, path);
gravity_closure_t *gravity_vm_loadbuffer(vm, buffer, len);

// Optional modules
void gravity_opt_register(vm);
void gravity_opt_free(void);

---

8.3 Delegate Pattern

File: src/shared/gravity_delegate.h

The delegate is a struct of function pointers used for all communication between the compiler/VM and the host application:

c
typedef struct {
// Error handling
gravity_error_callback error_callback; // syntax, semantic, runtime errors

// Compiler hooks
gravity_loadfile_callback loadfile_callback; // resolve import paths
gravity_filename_callback filename_callback; // map fileid → filename
gravity_precode_callback precode_callback; // inject code at parse time
gravity_parser_callback parser_callback; // syntax highlighting hook
gravity_type_callback type_callback; // bind type annotations

// Logging
gravity_log_callback log_callback;
gravity_log_clear log_clear;

// Bridge (C interop)
gravity_bridge_initinstance bridge_initinstance;
gravity_bridge_execute bridge_execute;
gravity_bridge_blacken bridge_blacken;
gravity_bridge_equals bridge_equals;
gravity_bridge_clone bridge_clone;
gravity_bridge_size bridge_size;
gravity_bridge_free bridge_free;
gravity_bridge_getvalue bridge_getvalue;
gravity_bridge_setvalue bridge_setvalue;

// Testing
gravity_unittest_callback unittest_callback;
} gravity_delegate_t;

Error types: GRAVITY_ERROR_SYNTAX, GRAVITY_ERROR_SEMANTIC, GRAVITY_ERROR_RUNTIME, GRAVITY_ERROR_IO, GRAVITY_WARNING.

---

8.4 Bridging

Gravity supports binding external (C, Objective-C, Swift) objects through the bridge delegate callbacks:

- EXEC_TYPE_BRIDGED functions are dispatched via delegate->bridge_execute.
- Instance creation goes through delegate->bridge_initinstance.
- Property access uses bridge_getvalue / bridge_setvalue.
- Objects store host-side data in the xdata pointer present on classes, instances, and functions.

#### Typical Embedding Usage

c
// 1. Create compiler
gravity_delegate_t delegate = {.error_callback = report_error};
gravity_compiler_t *compiler = gravity_compiler_create(&delegate);

// 2. Compile
gravity_closure_t *closure = gravity_compiler_run(
compiler, source, strlen(source), 0, true, true);

// 3. Create VM and transfer ownership
gravity_vm *vm = gravity_vm_new(&delegate);
gravity_compiler_transfer(compiler, vm);
gravity_compiler_free(compiler);

// 4. Execute
if (gravity_vm_runmain(vm, closure)) {
gravity_value_t result = gravity_vm_result(vm);
// ... use result ...
}

// 5. Cleanup
gravity_vm_free(vm);
gravity_core_free();

---

9. Utilities

Debug / Disassembler

Files: src/utils/gravity_debug.c, src/utils/gravity_debug.h

- opcode_name(opcode_t) — maps opcode enum to mnemonic string.
- opcode_constname(int) — maps constant pool indices to names (SUPER, NULL, UNDEFINED, TRUE, FALSE, etc.).
- gravity_disassemble() — full bytecode disassembler; outputs human-readable assembly with line numbers and decoded operands.

JSON Serialization

Files: src/utils/gravity_json.c, src/utils/gravity_json.h

Two components:
- Serializer: json_t object with hierarchical json_add_*(), json_begin/end_array(), json_begin/end_object() functions. Used by the compiler to serialize bytecode to JSON.
- Parser: Third-party JSON parser (json_parse()) that produces a json_value tree. Used by gravity_vm_loadfile to deserialize compiled bytecode.

File I/O and Platform Utilities

Files: src/utils/gravity_utils.c, src/utils/gravity_utils.h

- High-resolution timer: nanotime() (platform-specific: mach_absolute_time on macOS, clock_gettime on Linux, QueryPerformanceCounter on Windows).
- File operations: file_read, file_write, file_exists, file_delete, file_size, file_buildpath.
- Directory operations: directory_create, directory_init, directory_read, is_directory.
- String utilities: string_dup, string_replace, string_reverse.
- UTF-8: utf8_charbytes, utf8_encode, utf8_len, utf8_nbytes, utf8_reverse.
- Number parsing: number_from_bin, number_from_hex, number_from_oct.

---

10. CLI

File: src/cli/gravity.c

Operation Modes

| Flag | Mode | Description |
|------|------|-------------|
| (filename) | OP_COMPILE_RUN | Compile and execute in one pass |
| -c file | OP_COMPILE | Compile to bytecode file (default: gravity.json) |
| -x file | OP_RUN | Execute precompiled JSON bytecode |
| -i 'code' | OP_INLINE_RUN | Compile and execute inline string (wrapped in func main() { ... }) |
| -t folder | OP_UNITTEST | Run unit tests recursively |
| -o file | — | Specify output filename |
| -q | — | Quiet mode (suppress result and timing) |

The CLI sets up a gravity_delegate_t with error_callback and loadfile_callback (for import resolution), then drives the compiler and VM through the standard embedding API.

---

11. Build System

File: Makefile

Targets

| Target | Output |
|--------|--------|
| make | gravity CLI executable |
| make mode=debug | Debug build (-g -O0 -DDEBUG) |
| make lib | Shared library (libgravity.dylib / .so / .dll) |
| make example | C embedding API example |
| make clean | Remove all build artifacts |

Compiler Flags

text
-std=gnu99 -fgnu89-inline -fPIC -DBUILD_GRAVITY_API
-O2 (release)
-g -O0 -DDEBUG (debug)

Platform Detection

- macOS: libgravity.dylib
- Linux/BSD: libgravity.so, links -lm
- Windows: gravity.dll, links Shlwapi

Dependencies

- C99-compatible compiler
- Standard C library (including math.h)
- Platform headers (dirent.h, sys/time.h, or Windows equivalents)
- No external library dependencies

---

12. Test Infrastructure

Test Format

Unit tests are individual .gravity files in test/unittest/. Each test declares expected results in a metadata block:

swift
#unittest {
name: "Test description";
result: expected_value;
};

func main() {
// test logic
return actual_value;
}

The test runner compiles and executes each file, then compares the return value of main() against the declared result.

Test Metadata Fields

| Field | Purpose |
|-------|---------|
| name | Human-readable test description |
| result | Expected return value (compared with ==) |
| expected_error | Expected error type (for negative tests) |
| expected_row | Expected error line number |
| expected_col | Expected error column number |

Running Tests

bash
./gravity -t test/unittest/              # run all tests
./test/unittest/run_all.sh # run with timeouts (used by CI)
./gravity test/unittest/test_file.gravity # run a single test

Test Organization

Tests are organized by category in subdirectories: compiler phases, language features, built-in types, optional modules, edge cases, and bug regressions. The runner recursively scans the target directory, skips any /disabled/ subdirectories, and applies fuzzy comparison for tests under /fuzzy/.

CI runs: make && test/unittest/run_all.sh

---

CHANGELOG

Changelog

All notable changes to Gravity are documented in this file.

[Unreleased]

Fixed


- Wrong format specifier in a codegen error messagereport_error(..., "Invalid argument expression at index %d.", j+1) passed a size_t to a %d conversion, which reads only 32 bits of a 64-bit argument: undefined behaviour in a variadic call. The three report_error helpers now carry __attribute__((format(printf, ...))) on gcc and clang, so the compiler type-checks every call site and this class of mistake fails the build instead of needing an external analyser to spot it.
- Heap buffer overflow in list_storeat when growing the list fails — storing past the end of a list reallocates the backing array, but marray_resize leaves both the pointer and the capacity untouched when the realloc fails, so the existing if (!list->array.p) guard never fired: the old, smaller buffer is still there and still non-NULL. The count was then set to the requested index and the fill loop wrote well past the end of the allocation. The check now tests the capacity actually obtained, and the out-of-memory case is reported as Not enough memory to resize List. as intended. Reachable from a script: x[4444444444444444444] = 0 asks for a single multi-gigabyte allocation.

Removed


- The CodeQL workflow. It ran on github/codeql-action@v1, deprecated since January 2023 and no longer updated, so it kept reporting green without being a current analysis. Its one open finding is fixed above, and the compiler now checks that class directly. The build-and-test workflow, including its address + undefined sanitizer job, is unaffected.

Changed


- The sanitizer CI job caps a single allocation at 1 GB (max_allocation_size_mb) so the pathological allocations in test/fuzzy fail cleanly instead of pushing the runner into the OOM killer, and pins abort_on_error so a sanitizer finding arrives as a signal on every platform rather than as the bare exit code 1 the runtime defaults to on Linux. The fuzzing step also scans the output for sanitizer reports, which the exit code alone does not reliably convey.

---

[0.9.8] - 2026-08-05

Security and memory-safety release. Every issue below was found by external
reporters fuzzing the compiler and the bytecode loader, and each fix ships with
a regression test.

Fixed


- NULL dereference in gravity_vm_loadbuffer — a serialized function object without an identifier field, such as {"x":{"type":"function"}}, reached strlen(NULL) and crashed the process. The loader now validates the structure of every JSON executable before using it: the root and each entry must be objects, the identifier must be present exactly once and be a string, and unknown object types are rejected. Malformed input is reported as a load error instead of crashing (issue #444).
- Signed 64-bit integer overflow in json_parse_ex — the integer and exponent accumulators multiplied by 10 per digit with no range check, so any literal longer than 19 significant digits overflowed. Signed overflow is undefined behaviour: the parser stored a wrapped value, and builds compiled with -fsanitize=undefined trapped with SIGILL. Both accumulators are now range-checked and over-long literals are rejected (issue #447).
- Pointer-arithmetic overflow in the JSON scan loopfor (state.ptr = json; ; ++state.ptr) incremented unconditionally, so input that ended while the scanner was still inside a string or comment advanced the pointer past one-past-the-end, which is undefined behaviour. The loop now stops at the end of the buffer regardless of scanner state (issue #448).
- Heap out-of-bounds read in parse_number_expression — the 0x/0b/0o prefix check read value[1] without confirming the token was at least two bytes, so a source file whose last token was a bare 0 read one byte past the buffer. The prefix is only inspected when the token is long enough (issue #446).
- Compiler crash (SIGFPE) folding a floating-point remainderoptimize_const_instruction folded % by truncating both operands to int64_t, so any divisor with 0 < |divisor| < 1 became an integer division by zero and killed the compiler on 1 % 0.5. Float remainder is now folded with remainder(), matching operator_float_rem, and mixed Int/Float remainders are left to the runtime because REM dispatches on the class of the left operand. This also fixes a silent wrong answer: 5.5 % 2.0 folded to 1 where the VM evaluates -0.5 (issue #443).
- Undefined behaviour in Int arithmetic — Gravity Ints wrap on overflow, but the wrap was performed on signed operands in the VM fast path, in the operator_int_* methods and in the constant folder, which is undefined in C and traps under -fsanitize=undefined. All three paths now go through new GRAVITY_INT_ADD/SUB/MUL/NEG/DIV/REM helpers that compute on the unsigned counterpart. The helpers also handle GRAVITY_INT_MIN op -1, which on x86 faults in idiv rather than merely wrapping (issue #443).
- Optional classes never releasedgravity_core_free decremented the refcount of the optional classes without the matching balance, so Math, File, JSON and ENV were leaked by every embedder that created and destroyed a VM (issue #442).
- Core reference leaked by every gravity_compiler_run — the compiler took a reference to the core classes on each run and never released it, so the count never returned to zero and the core was never torn down (issue #442).
- Double free of the inline source buffergravity -i passed its heap-allocated wrapper source to gravity_compiler_run with is_static false, which hands the buffer to the lexer; the lexer freed it in parser_run and the CLI freed the same pointer again on the way out, aborting every inline run under a hardened allocator.

Added


- test/loadbuffer/ — a suite of malformed JSON executables that must each be rejected as a load error without crashing, plus json_bounds.c (make jsontest), 60 bounds checks that drive the JSON scanner directly. Run with test/loadbuffer/run_all.sh.
- A GitHub Actions workflow building with gcc and clang on Linux and macOS, and a second job that builds with -fsanitize=address,undefined and runs the unit tests, the fuzzing corpus and the loader tests through it.

Changed


- Version bumped to 0.9.8 (GRAVITY_VERSION, GRAVITY_VERSION_NUMBER).
- The usage text now prints the real default output file name, gravity.g; README.md and CLAUDE.md documented a stale gravity.json.

---

[0.9.7] - 2026-04-14

Fixed


- Float precision loss in JSON bytecode serialization — float constants were written with %f (6 decimal places), silently rounding small values like -0.000000004 to zero and causing RUNTIME ERROR: Unknown LOADK index on the -c/-x (compile + execute bytecode) path. Switched to %.17g for full IEEE 754 double round-trip precision (issue #420).
- Float constant deduplication in cpoolgravity_function_cpool_add used the epsilon-based gravity_value_equals (EPSILON = 1e-6) to detect duplicate constants, incorrectly merging distinct small floats into a single pool entry. The cpool now uses exact bit-level comparison for float values (issue #420).
- gravity_optionals.h unconditionally defined all optional-module guards — the #ifndef GRAVITY_INCLUDE_* blocks always defined every guard, making it impossible to exclude modules at compile time. The guards are now left undefined by default; embedders define only the modules they need. The Gravity CLI and runtime define all four (issue #426).
- Makefile dependency errors — four issues: gravity and example were incorrectly listed as .PHONY targets (causing unconditional rebuilds); lib depended on the gravity executable instead of just $(OBJ); gravity.c and example.c were compiled only during the link step so -MMD never generated .d header-dependency files for them; make clean did not remove libgravity.dylib on macOS (issue #413).
- run_all.sh portability — the test runner used GNU timeout which is not available on macOS. The script now auto-detects timeout, gtimeout (Homebrew coreutils), or falls back to a pure-bash kill-watcher.

Changed


- Version bumped to 0.9.7 (GRAVITY_VERSION, GRAVITY_VERSION_NUMBER).

---

[0.9.6] - 2026-04-14

Fixed


- Stack overflow now produces a clean runtime error instead of a hard crash (SIGSEGV). Infinite recursion and pathological call depths are caught by a configurable stack size limit before the process runs out of memory.
- Class $init chain infinite recursion — parent-class $init helpers ($init2, $init3, …) previously used a dynamic name lookup against self, which resolved to the wrong (overriding) function when called from a subclass, producing infinite recursion. The compiler now emits a direct static closure reference (LOADK) so dispatch is always to the correct ancestor function.
- Fiber stack growth in gravity_fiber_reassign — a large register-window allocation in $moduleinit could cause the initial stack pointer to overshoot DEFAULT_MINSTACK_SIZE (256 slots), leaving stacktop pointing into unallocated memory (issue #437).
- gravity_opt_free double-free — optional module cleanup now checks the reference count before freeing (PR #436).

Added


- GRAVITY_VM_MAXSTACK — runtime-configurable maximum fiber stack size (default 1 048 576 slots / 16 MB). Readable and writable via gravity_vm_get / gravity_vm_set with key "maxStack".
- Re-enabled two previously disabled tests (heap.gravity, loop1.gravity) — both now pass with the new OOM error reporting.

Changed


- Version bumped to 0.9.6 (GRAVITY_VERSION, GRAVITY_VERSION_NUMBER).

---

[0.9.5] - 2024

Fixed


- Numerous memory leaks and use-after-free errors throughout the compiler and runtime.
- Memory safety improvements across GC, value handling, and object lifecycle.
- Clang build compatibility (PR #435).

Changed


- Documentation updates: ARCHITECTURE.md rewritten; README refreshed.

---

[0.9.0] - 2023

Fixed


- Several memory leaks plugged across the compiler pipeline.
- Missing Makefile dependencies (PR #431).
- File.read() now returns null when zero characters are read.
- Replaced unsafe printf calls with snprintf.
- Fixed issue #394.

Added


- New unit test for leak-related regression coverage.

---

[0.8.5] - 2022

Fixed


- Setter issue that affected several unit tests.
- File read size mismatch due to line-ending differences on Windows (PR #365).
- Compilation failure introduced by O_BINARY on non-Windows platforms.

Added


- Unit tests integrated into CI (PR #378).
- BSD shared-object build support; removed WITH_GETLINE (PR #375).

---

[0.8.4] - 2022

Fixed


- Issue #379.
- Hash table header organisation (PR #369).

---

[0.8.3] - 2021

Fixed


- Regression introduced by lazy-loading of superclasses in 0.8.2.

---

[0.8.2] - 2021

Added


- Lazy loading of extern superclasses at runtime.
- Preliminary support for instance deinit (destructor).
- System.input() (PR #342).
- ENV.argc / ENV.argv properties (PR #343).
- ObjC binding example.
- C++ binding example.
- Ternary expression and switch statement codegen (PR #336).
- Optional File class (cross-platform).
- gravity_instance_lookup_real_property helper.
- gravity_config.h for platform-specific configuration (PR #301).
- Improved CMake: supports CLI, shared lib, and static lib targets (PR #299).

Fixed


- Inner class constructor returning wrong instance.
- Computed property (setter) bug.
- Sign-conversion and char-type warnings flagged by sanitizers.
- stat return-value check.
- uint32_t-to-char conversion in utf8_encode.
- size_t comparison against negative value in file_read.
- Various Windows / MSVC compatibility fixes.
- Implicit long-to-double conversion warning under Clang.
- Emscripten include-guard fix.

Changed


- Improved superclass type checking in the semantic analyser.
- Improved error handling and detection (0.8.0).
- File.eof renamed from isEOF.

---

[0.7.9] - 2020

Fixed


- Improved error handling in the VM and runtime.

Added


- vm back-reference stored on gravity_closure_t.
- Computed-goto support for Clang on Windows.
- DISPATCH_INNER macro for do/while(0) loops without computed goto.
- xdata parameter on delegate->optional_classes (PR #307).

---

[0.7.8] - 2020

Added


- Preliminary Struct support.
- bind method fix; unit test added.

Fixed


- Possible GC issue (unit test added).

---

[0.7.7] - 2020

Fixed


- super keyword resolution issue; unit test added.

---

[0.7.6] - 2020

Changed


- Optional classes renamed for consistency.

Fixed


- Setter unwanted side effect.

---

[0.7.5] - 2020

Improved


- float/double to String conversion accuracy.
- Various core methods.

---

[0.7.4] - 2020

Fixed


- String.length is now UTF-8 aware; String.bytes added (unit test added).
- Function returning address of local variable on Windows (directory_read).
- Division-by-zero warning suppression in GCC.
- Const output-buffer issue on Windows (WideCharToMultiByte).

Added


- More BSD targets in make and CMake.

---

[0.7.0] - 2019

Added


- String.split and string iteration are now Unicode-aware; unit test added.
- Support for local enum declarations; unit test added.

Fixed


- Local class declarations.
- Superclass resolution edge cases.
- continue keyword inside for loops.
- self parameter in complex postfix expressions.
- Comparison between different object types no longer raises a spurious runtime error.

---

[0.6.x] - 2018–2019

Series of incremental releases adding language features (closures, ranges, maps, lists, optional modules) and fixing compiler and runtime issues. See git history for per-commit details.

---

[0.5.x] - 2017–2018

Initial public release series establishing the core language, VM, and compiler pipeline.

---

CONTRIBUTING

#### Contributing to Gravity
Everyone is welcome to contribute to Gravity. Contributing doesn’t just mean submitting pull requests,
there are many different ways for you to get involved, including reporting bugs, add new functionalities
or just participating in the project.

#### Where to make changes
Core libraries and documentation is probably the areas that need major contribution.
Any change to the core VM or other parts of the compiler are welcomed.

#### Code style and syntax rules
Looking at the Gravity source code I think you can easily follow the same coding style and syntax rule.
Private functions usually don't begin with a gravity_ prefix and are marked as static.
Feel free to expand this section with a more formal description of the syntax rules.

#### Testing rules
If you fix a bug or if you add a new functionality then a unit-test is required.
A unit-test is a single source code file that is able to run under the unittest executable file.
You are free to test your functionalities/fixes into a single unit-test file or split the test in more files.

#### Add Docs
If you add new functionality, or edit the way that current functionality works,
add or edit the docs to reflect this so that there is documentation of the new
changes for new users.

#### Contributors File
Don't forget to add your name and your email address to the official CONTRIBUTORS file!

---

README

<p align="center" >
<img src="https://raw.githubusercontent.com/marcobambini/gravity/master/docs/assets/images/logo-gravity.png" height="74px" alt="Gravity Programming Language" title="Gravity Programming Language">
</p>

Gravity is a powerful, dynamically typed, lightweight, embeddable programming language written in C without any external dependencies (except for stdlib). It is a class-based concurrent scripting language with modern <a href="https://github.com/apple/swift">Swift</a>-like syntax.

Gravity supports procedural programming, object-oriented programming, functional programming, and data-driven programming. Thanks to special built-in methods, it can also be used as a prototype-based programming language.

Gravity has been developed from scratch for the <a href="http://creolabs.com" target="_blank">Creo</a> project in order to offer an easy way to write portable code for the iOS and Android platforms. It is written in portable C code that can be compiled on any platform using a C99 compiler. The VM code is about 6.5K lines long, the multipass compiler code is about 10K lines and the shared code is about 4.7K lines long. The compiler and virtual machine combined add less than 200KB to the executable on a 64-bit system.

What Gravity code looks like

swift
class Vector {
// instance variables
var x = 0;
var y = 0;
var z = 0;

// constructor
func init (a = 0, b = 0, c = 0) {
x = a; y = b; z = c;
}

// instance method (built-in operator overriding)
func + (v) {
if (v is Int) return Vector(x+v, y+v, z+v);
else if (v is Vector) return Vector(x+v.x, y+v.y, z+v.z);
return null;
}

// instance method (built-in String conversion overriding)
func String() {
// string interpolation support
return "[\(x),\(y),\(z)]";
}
}

func main() {
// initialize a new vector object
var v1 = Vector(1,2,3);

// initialize a new vector object
var v2 = Vector(4,5,6);

// call + function in the vector object
var v3 = v1 + v2;

// returns string "[1,2,3] + [4,5,6] = [5,7,9]"
return "\(v1) + \(v2) = \(v3)";
}

Features


* multipass compiler with optimizer
* dynamic typing
* classes and inheritance
* higher-order functions and classes
* lexical scoping
* coroutines (via fibers)
* nested classes
* closures
* garbage collection (mark-and-sweep)
* operator overriding
* string interpolation
* enums, modules, and structs (value types)
* switch/case and ranges
* optional modules (Math, File, JSON, ENV)
* powerful embedding API with bridging support
* built-in unit tests
* built-in JSON serializer/deserializer
* optional semicolons

Building

Make (Linux / macOS / BSD)

bash
make                    # Build the gravity CLI executable
make mode=debug # Debug build with symbols
make lib # Build shared library (libgravity.dylib/so/dll)
make staticlib # Build static library (libgravity.a)
make example # Build the C embedding API example
make clean # Clean all build artifacts

CMake (cross-platform, including Windows)

bash
cmake -B build
cmake --build build

Optionally disable the CLI and build the library only:


cmake -B build -DBUILD_CLI=OFF
cmake --build build

Requires a C99 compiler. No external dependencies.

Usage

bash
./gravity file.gravity                  # Compile and execute a source file
./gravity -c file.gravity # Compile to bytecode (outputs gravity.g)
./gravity -o out.json -c file.gravity # Compile to a specific output file
./gravity -x gravity.g # Execute precompiled bytecode
./gravity -i 'return 2 + 3' # Execute inline code
./gravity -t test/unittest # Run unit tests

Testing

bash
./gravity -t test/unittest              # Run all unit tests via the VM
./test/unittest/run_all.sh # Run all unit tests via shell script (with per-test timeouts)
./gravity test/unittest/somefile.gravity # Run a single test file

The test/ directory also contains fuzzy/ (randomised fuzzing inputs) and infiniteloop/ (tests that must terminate with a runtime error rather than hang).

Project Structure

text
src/
├── cli/ Command-line interface
├── compiler/ Lexer, parser, AST, semantic analysis, IR, optimizer, codegen
├── runtime/ Stack-based VM, built-in types and core methods
├── shared/ Value representation, opcodes, hash table, array, memory/GC
├── optionals/ Optional modules: Math, File, JSON, ENV
└── utils/ Debug disassembler, JSON serialization, file I/O, UTF-8

For a comprehensive technical deep-dive into the implementation, see ARCHITECTURE.md.

Embedding API

Gravity is designed to be embedded inside a host application. The complete API lives in src/runtime/gravity_vm.h and src/compiler/gravity_compiler.h. A minimal example:

c
#include "gravity_compiler.h"
#include "gravity_core.h"
#include "gravity_vm.h"

static void report_error(gravity_vm *vm, error_type_t type,
const char description, error_desc_t desc, void xdata) {
printf("%s\n", description);
}

int main(void) {
const char source = "func main() { return 6 7; }";

gravity_delegate_t delegate = {.error_callback = report_error};

// compile
gravity_compiler_t *compiler = gravity_compiler_create(&delegate);
gravity_closure_t *closure = gravity_compiler_run(compiler, source, strlen(source), 0, true, true);

// create VM and transfer compiler-owned objects into it
gravity_vm *vm = gravity_vm_new(&delegate);
gravity_compiler_transfer(compiler, vm);
gravity_compiler_free(compiler);

// execute and read result
if (gravity_vm_runmain(vm, closure)) {
gravity_value_t result = gravity_vm_result(vm);
gravity_value_dump(vm, result, NULL, 0); // prints: 42
}

gravity_vm_free(vm);
gravity_core_free();
return 0;
}

See examples/example.c and the embedding documentation for the full bridging API.

Special thanks


Gravity was supported by a couple of open-source projects. The inspiration for closures comes from the elegant <a href="http://www.lua.org" target="_blank">Lua</a> programming language; specifically from the document <a href="http://www.cs.tufts.edu/~nr/cs257/archive/roberto-ierusalimschy/closures-draft.pdf">Closures in Lua</a>. For fibers, upvalues handling and some parts of the garbage collector, my gratitude goes to <a href="http://journal.stuffwithstuff.com" target="_blank">Bob Nystrom</a> and his excellent <a href="https://github.com/munificent/wren">Wren</a> programming language. A very special thanks should also go to my friend Andrea Donetti who helped me debugging and testing various aspects of the language.

Documentation


The <a href="https://marcobambini.github.io/gravity/#/README">Getting Started</a> page is a guide for downloading and compiling the language. There is also a more extensive <a href="https://gravity-lang.org">language documentation</a>. Official wiki is used to collect related projects and tools. For implementation internals, see the Architecture Document.

Where Gravity is used


* Gravity is the core language built into Creo (https://creolabs.com)
* Gravity is the scripting language for the Untold game engine (https://youtu.be/OGrWq8jpK14?t=58)

Changelog

See CHANGELOG.md for a summary of changes across versions.

Community

[](https://github.com/marcobambini/gravity/discussions)

Questions, ideas, and general discussion are welcome in GitHub Discussions.

Contributing


Contributions to Gravity are welcomed and encouraged!<br>
More information is available in the official CONTRIBUTING file.
* <a href="https://github.com/marcobambini/gravity/issues/new">Open an issue</a>:
* if you need help
* if you find a bug
* if you have a feature request
* to ask a general question
* <a href="https://github.com/marcobambini/gravity/pulls">Submit a pull request</a>:
* if you want to contribute

License


Gravity is available under the permissive MIT license.

---