Version: 1.7.1
File extension:.gbn
Interpreter:Core/Gybin\/usr/bin/GybinInvocation:Gybin (File path: My_script.gbn)
You can also declare the interpreter on the first line of your code and run it like any other program (Linux only). Example:
#!/usr/bin/Gybin -- parser path
$print("Hello!")
chmod +x Mi_script.gbn
./Mi_script.gbnRun setup-linux to configure the Gybin launcher in '/usr/bin' Run setup-termux to configure the language in a Termux environment
- Introduction
- Running from the Console
- Data Types
- Variables and Constants
- Comments
- Operators
- Functions
- Classes
- Inheritance
- Enums
- Arrays
- Dictionaries
- Control Flow
- Error Handling
- Pointers
- Events
- Modules and Imports
- Memory Management
- Built-in Functions
- Standard Library (stdutils)
- Compiling to an Executable
- Warnings and Static Analysis
- Rules and Best Practices
GybinScript is a statically typed, interpreted scripting language with controlled memory management. It is designed to be expressive and predictable: all variables must be declared with a type, blocks are closed with end, and $ is the required prefix for reading or modifying any variable or calling any function.
Gybin my_script.gbn [options]| Flag | Description |
|---|---|
--sm |
Shows memory status when execution finishes |
--pr |
Automatically prints all values returned by return |
--t |
Shows execution time |
--tr |
Shows each executed line instead of normal output |
--c |
Compiles the script into an executable — see §21. Does not run the script |
--fc |
Compiles the script unconditionally, even if it has errors — see §21 |
--n NAME |
Custom name for the compiled executable (only with --c/--fc) |
--ad PATH[=DESTINATION] |
Bundles an extra file/folder into the executable; can be repeated (only with --c/--fc) |
--i ICON_PATH |
Icon for the compiled executable (only with --c/--fc) |
--w |
Enables warning messages (static analysis) |
--nc |
Suppresses all standard output (errors are still displayed) |
--nch |
Disables caching for this run: #onready functions always execute and $preload always reads from disk — see §7 and §18 |
Gybin juego.gbn --sm --w --tGybinScript has six primitive types and two collection types:
| Type | Description | Example |
|---|---|---|
int[B] |
Integer with a maximum bit width of B | 20 |
float[B] |
Floating-point number with a maximum bit width of B | 3.1416 |
str[N] |
String with a maximum length of N characters | "Carlos" |
bool |
Boolean | true / false |
any |
Unrestricted type | — |
NULL |
Null value / absence of a value | NULL |
array[T,...] |
Typed list of elements | [1, 2, 3] |
dict[V,...] |
Typed dictionary of values | {"a": 1} |
ptr |
Pointer/reference to another variable, constant, function, class, or event; can be constrained with ptr[target_types][data_types] (see §15) |
$$hp |
Automatic coercions:
- An
intassigned to afloatis automatically converted tofloat. - A
floatwithout a fractional part assigned to anintis converted toint. - A
floatwith a fractional part assigned to anintraises a type error.
var age: int = 20
var pi: float = 3.1416
var alive: bool = true
var name: str[16] = "Carlos"
$print($age)
$print($pi)
$print($alive)
$print($name)
Both numeric types use 64 bits by default. Instead of introducing separate type names for smaller widths, the same [N] size syntax already used by str is reused to choose a narrower width:
var integer_value: int[16] = 1000 -- signed 16-bit int: -32768..32767
var decimal_value: float[32] = 3.14 -- 32-bit float (IEEE-754 single precision)
var other_value: float [32] = 2.71 -- a space before the bracket is also accepted
| Type | Valid widths |
|---|---|
int[B] |
8, 16, 32, 64 |
float[B] |
16, 32, 64 |
A value that does not fit the declared width causes an immediate error, with a suggestion for a width that would fit it:
var small: int[8] = 500 -- error: does not fit in a signed 8-bit int (range -128..127). Use int[16] or higher.
Coercion between int and float requires matching widths. An implicit int <-> float conversion (see “Automatic coercions” above) only occurs when both sides have the same declared bit width — or when neither specifies one (both default to 64):
var float_value: float = 3.14
var integer_value: int[32] = $float_value -- Error: coercion requires equal widths (64 vs 32)
var float32_value: float[32] = 3.14
var integer32_value: int[32] = $float_value32 -- OK: both are 32-bit
This also applies to constants and function parameters/return types:
func sum(a: int[16], b: int[16]) -> int[16]
return a + b
end
The element types of array/dict never track a specific bit width — array[int] accepts int values of mixed widths because a container has no per-element width annotation of its own. Writing array[int[16]] is rejected as ambiguous (it is unclear which elements it would apply to); use a plain array[int] instead.
String literals prefixed with f support {expression} interpolation, including format specifiers and conversions:
var name: str = "Ana"
$print(f"Hello {$name}!")
$print(f"{3.14159:.2f}") -- 3.14
The basic syntax is var name: type = value. The $ prefix is used to read or modify the variable after declaring it.
var hp: int = 100
$hp = 50
You can specify the maximum size directly in the variable name instead of in the type annotation:
var buffer[128]: str = "hola"
A variable can accept more than one type by separating them with commas. Use this feature sparingly:
var dato: int,str = 10
$dato = "texto"
Constants are declared with const and cannot be reassigned:
const MAX_HP: int = 200
Attempting to modify a constant raises a type error (Immutable constant).
The #onready modifier declares a variable before the program starts running, which is useful for early dependency initialization:
#onready var config: str[64] = "default"
It also prevents the same value from being reassigned when used.
#onreadycan also be placed before a function, where it caches its results instead — see Cached Functions.
#reserved declares private script elements, meaning they cannot be used outside the script in which they were declared:
#reserved var critico: bool = false
#public is the readability counterpart to #reserved. It changes nothing — variables, functions, and classes are already public by default — and exists only to make that intent explicit in the code:
#public var vida: int = 100
#inmutable behaves like const for direct assignment — $name = value raises the same error as a constant — but unlike a true const, its value can be changed indirectly through a pointer:
#inmutable var x: int = 5
$x = 10 -- error: 'x' is #inmutable
var p: ptr = $$x
$p.value = 99 -- OK — x is now 99
A true const remains completely locked even through a pointer; #inmutable is the version that allows this one deliberate escape hatch.
Its value may also be changed only while its current value is NULL, allowing dependency values to be loaded into constants without triggering a constant-reassignment error.
NULL represents the absence of a value. Objects and complex variables are declared as NULL when their contents have not yet been determined:
var name: str[32] = NULL
var hp: int = NULL
! The interpreter ignores objects whose value is
NULLuntil a value is assigned. For arrays and dictionaries, initialize with[]or{}instead ofNULLif you plan to add elements immediately.
Starts with -- and continues to the end of the line:
var x: int = 5 -- this is a comment
Starts with !* and ends with !*. It can span multiple lines:
!* This is a comment
spanning multiple lines !*
Block comments cannot be nested: the second
!*closes the block opened by the first.
| Operator | Operation |
|---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
% |
Modulo |
** |
Exponentiation |
| Operator | Meaning |
|---|---|
== |
equal |
!= |
Not equal |
< |
Less than |
<= |
Less than or equal to |
> |
Greater than |
>= |
Greater than or equal to |
| Operator | Meaning |
|---|---|
and |
Logical AND |
or |
Logical OR |
not |
Negation |
is |
Data comparison |
$x += 5
$x -= 2
$x *= 3
$x /= 4
func name(param1: type, param2: type) -> return_type
-- body
end
The return type is required. Use NULL if the function does not return anything:
func greet(name: str[32]) -> NULL
$print("Hello " + $name)
end
func add(a: int, b: int) -> int
return $a + $b
end
var result: int = $add(10, 20)
$print($result)
All calls must be prefixed with $:
$greet("Carlos")
$print($multiply($result, 2))
int[N]/float[N] (see §3) also work in function signatures:
func sum(a: int[16], b: int[16]) -> int[16]
return $a + $b
end
! The same width-matching rule applies to arguments: callers passing a variable declared with a different explicit width than the parameter must convert it explicitly first.
Placing #onready before a function caches its results: the first call with a given set of arguments executes the body, and later calls with exactly the same arguments return the cached result without executing it again.
#onready func square(n: int) -> int
$print("calculating...")
return $n * $n
end
$print($square(4)) -- calculating... / 16
$print($square(4)) -- 16 (from cache)
$print($square(5)) -- calculating... / 25
- Each distinct argument set has its own cached result.
- For a class method, each instance has its own cache.
- A cached call does not repeat the function’s side effects (printing, writing files, etc.), so this is best suited to functions that return the same result for the same arguments.
- Run with
--nchto disable caching entirely for that run.
A parameter whose name begins with ! collects the arguments it receives into an array:
func sumar(!valores[10]: int) -> int
var total: int = 0
for n in $valores
$total += $n
end
return $total
end
$print($sumar(1, 2, 3, 4)) -- 10
$print($sumar()) -- 0
[10](optional) defines how many values it can contain, and: int(optional) defines each value’s type — the same rules asarray[int][10].- A function can have only one
!parameter, and it must be the last one. Earlier parameters receive their arguments first, and the!parameter collects the rest:
func reporte(titulo: str, !lineas: str) -> NULL
$print($titulo)
for linea in $lineas
$print(" - " + $linea)
end
end
$reporte("Log", "started", "loaded", "listo")
A parameter marked with ? is supplied by name when calling the function, written as $name=value:
func set_modulate(?r: float, ?g: float, ?b: float, a: float) -> color
return $color($r, $g, $b, $a)
end
var c: color = $set_modulate(1.0, $r=0.6, $g=0.3, $b=0.9)
- The
$before the name is required. - Only parameters marked with
?can be passed by name; using a name that does not match any parameter is an error. - A
?parameter that is not supplied remainsNULL. - Class constructors work the same way: if
initdeclares?hp: int, then$Player($hp=50)supplies it.
Combining both markers creates a named parameter that collects an array. Unlike a plain !, a function can declare multiple ?! parameters, since each is supplied by name with its own array:
func grupos(?!a: int, ?!b: int) -> NULL
$print($a) -- [1, 2, 3]
$print($b) -- [4, 5]
end
$grupos($a=[1, 2, 3], $b=[4, 5])
A plain ! parameter cannot be combined with ?! parameters in the same function.
The run keyword executes the init() function defined in the global scope. This is the standard way to structure a program’s entry point:
func init() -> NULL
$print("Program started")
end
run
The
init()function inside a class is that class’s constructor. It does not conflict with the globalinit()because they are used in different contexts.
class NombreClase
var field: tipo = NULL
func init(self, param: tipo) -> NULL
$self.field = $param
end
end
self refers to the current instance and is automatically passed as the first argument when a method is called. You do not need to pass it when instantiating:
var p: any = $Player("Juan")
$print($p.name)
$print($p.hp)
Methods are defined inside a class just like functions, with self as the first parameter:
class Player
var hp: int = NULL
func init(self) -> NULL
$self.hp = 100
end
func damage(self, amount: int) -> NULL
$self.hp -= $amount
end
end
var p: Player = $Player()
$p.damage(30)
$print($p.hp) -- 70
Access fields with a dot (.), without $:
$print($p.name)
$p.hp = 50
A class can extend another class using extends:
class Entity
var hp: int = NULL
func init(self) -> NULL
$self.hp = 100
end
end
class Enemy extends Entity
var damage: int = NULL
func __init__(self) -> NULL
$self.hp = 50
$self.damage = 10
end
end
var e: Enemy = $Enemy()
$print($e.hp) -- 50
$print($e.damage) -- 10
! If the parent class already has an
init()method, the child class must use__init__()instead to avoid a name conflict. The child class inherits all fields and methods from the parent class.
Enums group named constants under a common type:
enum Direction = {UP, DOWN, LEFT, RIGHT}
var dir: int = Direction.UP
$print($dir) -- 0
Values are assigned automatically starting at 0. Access them using EnumName.MEMBER.
Every enum has an implicit .null member — a sentinel meaning “nothing from this enumeration has been stored yet.” It is separate from the enum’s own members and is never counted as one of them:
enum Direction = {UP, DOWN, LEFT, RIGHT}
var current: Direction = Direction.null
$print($current) -- distinct from the value of any declared member
An enum can be iterated directly with for ... in and supports the membership operator in — both traverse only declared members, never .null:
for d in $Direction
$print(d) -- UP, DOWN, LEFT, RIGHT — never .null
end
if Direction.UP in $Direction
$print("UP is a member")
end
enum ItemType = {WEAPON, ARMOR, CONSUMABLE}
class Item
var name: str[32] = NULL
var type: any = NULL
func init(self, name: str[32], type: ItemType) -> NULL
$self.name = $name
$self.type = $type
end
end
var espada: Item = $Item("Sword", $ItemType.WEAPON)
var numbers: array[int] = []
Use [] to initialize an empty array. Using NULL and then trying to add elements without first assigning an array will cause an error.
| Method | Description |
|---|---|
.append(value) |
Adds an element to the end |
.remove(index) |
Removes the element at the given index |
.size() |
Returns the number of elements |
.duplicate() |
Returns a copy of the array |
.push_back(value) |
Alias for append |
$numbers.append(10)
$numbers.append(20)
$numbers.append(30)
$print($numbers) -- [10, 20, 30]
$numbers[1] = 50
$print($numbers) -- [10, 50, 30]
var list: array[int][10] = []
This limits the array to a maximum of 10 elements.
var enemies: array[any] = []
$enemies.append($Enemy(10))
$print($enemies[2].hp)
var inventory: dict[int] = {}
$inventory["Potion"] = 5
$inventory["Sword"] = 1
$print($inventory) -- {"Potion": 5, "Sword": 1}
var inventory: dict[any] = {}
$inventory["weapon"] = Item("Sword")
$print($inventory["weapon"].name)
| Method | Description |
|---|---|
.size() |
Returns the number of entries |
.remove(key) |
Removes the entry with that key |
.duplicate() |
Returns a copy of the dictionary |
if $x > 10
$print("greater")
elseif $x == 10
$print("equal")
else
$print("less than")
end
elif is also accepted as a shorter form of elseif.
var i: int = 0
while $i < 10
$print($i)
$i += 1
end
Iterates over arrays, ranges, or dictionaries:
for item in $enemies
$print($item.hp)
end
for n in $range(5)
$print($n)
end
Compares a subject against several possible values. Each case starts with case value1, value2 (multiple comma-separated labels are allowed). Only the first matching case is executed (or else, if present); cases do not fall through.
match $dia
case 1
$print("Monday")
case 2, 3
$print("Tuesday or Wednesday")
else
$print("Another day")
end
Case labels can be any evaluable expression: literals, variables, enum members (Color.RED), or even array/dictionary literals ([1, 2, 3], {"a": 1}). If nothing matches and there is no else, nothing happens — no error is raised.
| Keyword | Comportamiento |
|---|---|
break |
Exits the current loop |
continue |
Skips to the next iteration |
loop |
Subtractionrts the current iteration from the beginning |
pass |
Does nothing (empty-block placeholder) |
loop is easy to confuse with continue because they may look similar in a simple while loop, but they are not the same: continue advances to the next iteration (the next item in a for, or checks the while condition again), whereas loop reruns the same iteration from the beginning, with its state exactly as it was — the same for item, without checking the while condition again — until it finishes normally.
var intentos: int = 0
for item in [10, 20]
$intentos += 1
$print(f"testing {item}")
if item == 10 and $intentos < 2
loop -- restarts with item still at 10, not 20
end
end
-- prints: testing 10 / testing 10 / testing 20
Pauses execution until a condition becomes true (polling every 10 ms):
await $ready == true
try
-- code that may fail
catch
-- code that runs if an error occurs
end
except is also accepted as an alias for catch:
try
var x: int = $int("no_es_numero")
except
$print("A conversion error occurred")
end
Without a specified type, catch/except handles all errors. An empty catch/except body is valid: it silences the error and does nothing else.
Write one or more error types after catch/except to handle only those types. Each type is written as a pointer ($$Type), and multiple types are comma-separated. Any other error continues to propagate normally:
try
var y: any = $no_declarada
except $$NameError
$print("undeclared variable")
end
try
-- code that may fail
except $$SyntaxError, $$ValueError
$print("syntax or value problem")
end
Catching a general type also catches the more specific types it groups: $$LookupError, for example, handles both IndexError and KeyError.
Add -> name at the end of the header to store the error in a variable. It contains a string with the error type, where it occurred, and the message:
try
var y: any = $no_declarada
catch -> e
$print($e) -- NameError: game.gbn:3:3: Variable not declared: no_declarada
end
This can be combined with error types:
except $$ValueError, $$TypeError -> e
Every error displays its type at the start of the message instead of a generic Error::
NameError: game.gbn:12:3: Variable not declared: x
| Type | Se produce cuando |
|---|---|
NameError |
A variable, function, or class has not been declared |
TypeError |
A value has the wrong type, or an operation is not valid for it |
ValueError |
A value is not acceptable |
SyntaxError |
The code is malformed (for example, a block without end) |
IndexError |
An array index is out of range |
KeyError |
A dictionary key does not exist |
LookupError |
Groups IndexError and KeyError |
GybinError |
Groups the three language-specific errors listed below |
EventError |
An event is used incorrectly — for example, connecting something that is not a function reference. It is also handled by $$TypeError |
PointerError |
A pointer is used incorrectly or does not match its ptr[...] annotation. It is also handled by $$TypeError |
BitWidthError |
A value does not fit in an int[N]/float[N], or a coercion mixes different widths (see §3). It is also handled by $$ValueError |
try
var small: int[8] = 500
except $$BitWidthError -> e
$print($e)
end
try
$player_is_dead.connect(5)
except $$GybinError -> e
$print("event, pointer, or bit-width problem: " + $e)
end
The $$ operator creates a pointer to an existing variable, constant, function, class, or event (e.g. $$hp, $$Damage, $$self.hp, $$arr[0], $$player_is_dead). It is typed with ptr — the only annotation that can hold a pointer value:
var hp: int = 100
var ref: ptr = $$hp
! Reading a pointer variable directly (
$print(ref)) prints the pointer itself, not the value it points to — use.valueor.get()to read through it.
A pointer never stores a copy of its target — it resolves the target again each time one of these members is used:
| Member | Description |
|---|---|
.value |
Gets the current value of the referenced variable/constant. It can also be assigned ($ref.value = 75) to write through the pointer |
.get() |
Same as .value, as a method call |
.set(value) |
Same as assigning .value, as a method call |
.call(args...) |
Invokes the referenced function/class, forwarding the supplied arguments |
.name |
The name of the referenced memory slot |
.is_callable |
true if the pointer refers to a function or class, false for a variable/constant |
.is_mutable |
true only for a variable that is neither #reserved nor a constant |
.size |
Approximate size in bytes of the referenced value |
.ref |
The actual memory address (identity) of the referenced value |
var hp: int = 100
var ref: ptr = $$hp
$print(ref.value) -- 100
$ref.value = 75
$print(hp) -- 75
func greet(name: str) -> NULL
$print("Hello " + name)
end
var fp: ptr = $$greet
$fp.call("Carlos") -- Hello Carlos
- Only variables can be changed through a pointer. Constants are always immutable, and functions/classes cannot be assigned a value —
.set()/.value = ...raises an error in either case. - A variable declared with
#reservedcan never be mutated through a pointer, even by code that has a reference to it. - Writing through a pointer (
.set()/.value = ...) accepts any value type; a mismatch with the target’s declared type produces a warning (with--w) rather than a strict type error. - Referencing a function or class by its bare name (without
$$) is a script error —$$nameis the only way to obtain a reference without invoking it.
Pointers allow indirect access and can target complex paths ($$object.field, $$array[0]). They are useful for aliases, dynamic references, and passing references to functions (for example, when connecting handlers to an event).
A plain ptr accepts a pointer to anything. Two optional bracket groups can constrain it:
- First bracket — what it can point to:
var,const,event,func,class(separate multiple options with commas). - Second bracket — the data type it must contain:
int,str, a class name, etc. (separate multiple options with commas).
An empty bracket pair, or one containing any, means that part is unrestricted.
var p1: ptr[func] = $$greet -- functions only
var p2: ptr[var,const][int,float] = $$hp -- a variable or constant containing an int or float
var p3: ptr[var][Fighter] = $$hero -- a variable containing a Fighter instance
var p4: ptr[event] = $$player_is_dead -- events only
var p5: ptr[] = $$hp -- same as a plain ptr
var p6: ptr[any][any] = $$hp -- same as a plain ptr
For classes, the second bracket pair indicates which class or classes are accepted: the named class or any class that extends it.
class Fighter
var hp: int = 10
end
class Warrior extends Fighter
var armor: int = 5
end
class Enemy
var atk: int = 5
end
var a: ptr[class][Fighter] = $$Warrior -- OK: Warrior extends Fighter
var b: ptr[class][Fighter,Enemy] = $$Enemy -- OK
var c: ptr[class][Fighter] = $$Enemy -- PointerError
A pointer that does not match its annotation raises a PointerError (see §14).
A ptr can also be constructed from a raw integer address instead of $$target:
var memory: ptr = 0x7fff5fbff80c
! This does not dereference actual process memory — doing so safely from an interpreter that walks a syntax tree is not possible (there is no way to know whether a given address is valid, and attempting it could crash the process or read memory that does not belong to you). Instead, you get an opaque handle that carries the address purely as an identity:
$print($memory.ref) -- the address itself
$print($memory.name) -- "0x7fff5fbff80c"
$print($memory.is_callable) -- false
$print($memory.is_mutable) -- false
$print($memory.size) -- native pointer size (8 bytes)
$print($memory.value) -- error: nothing to dereference
$memory.value = 5 -- error: nowhere to write to
$memory.call() -- error: nothing to call
Same object, same member interface as a normal pointer — .value, .set(), and .call() simply have nothing to act on, and report that clearly instead of guessing.
An event declares a lightweight signal: a name plus a list of parameters:
event player_is_dead(entity: str)
Type annotations on parameters are documentation only — an event has no body against which to validate them.
Every event exposes the following members, all used with the $ prefix like any other call:
| Member | Description |
|---|---|
.connect(handler) |
Registers a function to run every time the event is emitted. handler must be a pointer to a function created with $$function_name; a bare name or a pointer to anything else (a variable, class, event, etc.) raises an EventError |
.disconnect(handler) |
Removes that handler from the event. Does nothing if it was not connected |
.reconnect(handler) |
Disconnects and reconnects the handler so it is connected exactly once — useful for preventing it from running twice on each .emit() |
.emit(args...) |
Calls each connected handler in connection order, forwarding the supplied arguments. The number of arguments must match the number of parameters declared by the event |
.last_connection |
Property (no parentheses): a pointer to the most recently connected handler, or NULL if none are connected |
event player_is_dead(entity: str)
func on_player_dead(entity: str) -> NULL
$print(entity + " died")
end
$player_is_dead.connect($$on_player_dead)
if $vida <= 0
$player_is_dead.emit("Zombie")
end
Multiple handlers can be connected to the same event; all are executed, in connection order, when .emit(...) is called.
.disconnect(...) accepts any pointer to the same function, so you do not need to save the one used to connect it:
$player_is_dead.disconnect($$on_player_dead)
$player_is_dead.connect($$on_player_dead)
$player_is_dead.connect($$on_player_dead) -- conectado dos veces: se ejecuta dos veces por emit
$player_is_dead.reconnect($$on_player_dead) -- ahora está conectado exactamente una vez
$print($player_is_dead.last_connection.name) -- on_player_dead
An event can be used directly as a condition: it is false until it is emitted for the first time, and true thereafter.
event level_loaded(name: str)
func init() -> NULL
if $level_loaded
$print("not printed yet")
end
$level_loaded.emit("Forest")
if $level_loaded
$print("printed: the event has already been emitted")
end
end
Loads a .gbn file (or another supported format) and exposes all its symbols in the current scope:
@use "utils.gbn"
@use "helpers" -- detects the extension automatically
Importing is idempotent: if a module has already been loaded, it is not executed again.
@use math, random, sys
Each module is loaded independently, exactly as if it had its own @use line. @as is not allowed in an @use statement containing multiple modules — one alias cannot represent several different modules — import them one per line if each needs its own name:
@use math @as mate
@use random @as rand
Loads a module and exposes it as a named namespace:
@from "utils.gbn" @as utils
$print($utils.mi_funcion())
@use path @as alias (a single module) does exactly the same thing — the same functionality is available through either keyword.
Loading an entire module just to use one or two functions wastes memory on everything else it defines and can fill --w output with warnings about symbols the importer never requested. @from module @use $$symbol loads only the named symbols instead of executing the whole module and importing everything it exposes:
@from math @use $$_sqrt
$print($_sqrt(4.0))
Multiple symbols from the same module, separated by commas:
@from math @use $$_PI, $$_E, $$_sqrt
Add @as to group the selected symbols under a single namespace instead of placing them directly in the current scope — this is allowed here (unlike the multiple-module @use case above) because everything still comes from a single module:
@from sys @use $$_format_time, $$_miliseconds @as os
$print($os._format_time())
#reserved symbols still cannot be imported this way, selectively or otherwise.
| Extension | Behavior |
|---|---|
.gbn |
Executed and integrated into the current scope |
.py |
Loaded as a Python module; its public attributes are exposed |
.c, .cpp, .asm, .sh, .bash, .h |
Source code is exposed as a dictionary under __source__ |
If the path contains no / and does not start with ., the interpreter also searches the project’s libs/ folder.
The interpreter has a configurable default limit of 1024 memory slots.
Explicitly removes a variable from the scope:
$free($$mi_dato)
free accepts one or more pointers of any type (variables, constants, functions, classes, events), separated by commas:
$free($$primero, $$segundo, $$tercero)
Increases the maximum number of available slots:
$expand_memory(512)
! This emits a warning to
stderrindicating the change.
Pauses execution and returns a memory summary:
$breakpoint()
Expressions that appear repeatedly (for example, inside a loop) are cached as an AST the first time they are parsed, avoiding unnecessary reparsing on each cycle:
var i: int = 0
while $i < 10000
var x = ($i * 5) + (20 / 2) -- the expression is cached
$i += 1
end
Two features cache results to save work on repeated use:
#onready funccaches a function result for each distinct set of arguments (see §7).$preload(path)reads a file once and caches its contents (see §19).
Run with --nch to disable both for that run: cached functions always execute their bodies, and $preload always reads from disk.
Gybin mi_script.gbn --nchWhen execution ends, the engine automatically frees global symbols that were defined but never read (global variables, functions, classes, imported namespaces). $free($var) can also be used when needed.
When the --sm flag is used, the interpreter prints the full memory state at the end:
Total memory: hp:int=100 | name:str='Juan' | ...
These functions are available without importing anything:
| Function | Description |
|---|---|
$print(value) |
Prints a value |
$reprint(value) |
Like $print, but overwrites the current line instead of starting a new one — see below |
$int(value) |
Converts to an integer |
$float(value) |
Converts to a float |
$str(value) |
Converts to a string |
$bool(value) |
Converts to a boolean |
$range(n) |
Generates a range from 0 to n-1 |
$len(coleccion) |
Returns the length of a collection |
$file_read(path) |
Reads an entire file as a string |
$file_lines(path) |
Reads a file as an array of lines |
$file_write(path, content) |
Writes (overwrites) a file |
$file_append(path, content) |
Appends content to a file |
$file_exists(path) |
Returns true if the file exists |
$preload(path) |
Reads a file like $file_read, but caches its contents — see below |
$eval() |
Executes expressions provided as strings |
$exit() |
Immediately terminates the program |
$print accepts sep, end, and flush as named arguments:
$print("a", "b", sep="-", end="") -- a-b, no trailing newline
$print("c")
$preload(path) returns the full contents of a file as a string, just like $file_read(path). The difference is that it caches the result for that path: later calls with the same path return the cached contents immediately without accessing the disk again, even if the file has changed in the meantime.
var config: str = $preload("config.txt") -- reads the file
var again: str = $preload("config.txt") -- returns the cached contents
Run with --nch to make each $preload read from disk.
Status/progress output often needs to overwrite the same terminal line instead of moving to a new line on every call. $reprint does this without having to manually write \r and an ANSI clearing sequence:
$reprint("cargando 10%")
$reprint("cargando 100%")
$reprint("ready!", end="\n") -- pass end="\n" to close the line and return to normal $print behavior
$print("afterward")
By default, it does not add a newline (so the next call continues overwriting the same line) and flushes immediately. With end="\n", it behaves exactly like $print, so it is safe to use generally.
stdutils.gbn is loaded automatically at the start of every program. It provides:
$print($GBN.VERSION)
| Function | Description |
|---|---|
$contains(collection, element) |
true if the element is in the collection |
$index_of(collection, element) |
Index of the element, or -1 if it does not exist |
$has_key(dict, key) |
true if the key exists in the dictionary |
$merge_dict(dest, origen) |
Merges origen into dest and returns the result |
$flatten(colecciones) |
Merges one or more collections into a single collection |
$unique(coleccion) |
Removes duplicate elements |
$reverse(coleccion) |
Returns the elements of a collection in reverse order |
| Function | Description |
|---|---|
$str_split(text, delimiter) |
Splits a string into an array |
$str_join(separator, items) |
Joins an array of strings into one |
$to_lower(texto) |
Converts to lowercase |
$to_upper(texto) |
Converts to uppercase |
$starts_with(texto, prefijo) |
true if the text starts with the prefix |
$ends_with(texto, sufijo) |
true if the text ends with the suffix |
$repeat(texto, rango) |
Repeats a string n times |
$trim(texto) |
Trims whitespace from a string |
$replace(texto, viejo, nuevo) |
Replaces occurrences in a string with another string |
| Function | Description |
|---|---|
$vec2(x, y) |
2D vector of floating-point values |
$vec2i(x, y) |
2D vector of integer values |
$vec3(x, y, z) |
3D vector of floating-point values |
$vec3i(x, y, z) |
3D vector of integer values |
$color(r, g, b, a) |
4D vector of floating-point values, ranging from 0 to 1; a defaults to 1 if no value is assigned |
You can access vector values at any time through
.x, .y, .zor.r, .g, .b, .aAll vectors provide the.zero()function
The $color() vector accepts an optional named $preset= argument to select a named color from an enum instead of providing arguments for every RGBA channel
| Color | Equivalencia |
|---|---|
$Color.YELLOW |
$color(1.0, 1.0, 0.0, 1.0) |
$Color.BLUE |
$color(0.0, 0.0, 1.0, 1.0) |
$Color.RED |
$color(1.0, 0.0, 0.0, 1.0) |
$Color.GREEN |
$color(0.0, 1.0, 0.0, 1.0) |
$Color.VIOLET |
$color(0.93333334, 0.50980395, 0.93333334, 1.0) |
$Color.PURPLE |
$color(0.627451, 0.1254902, 0.9411765, 1.0) |
$Color.MAGENTA |
$color(1.0, 0.0, 1.0, 1.0) |
$Color.PINk |
$color(1.0, 0.7529412, 0.79607844, 1.0) |
$Color.SKYBLUE |
$color(0.5294118, 0.80784315, 0.92156863, 1.0) |
$Color.BLACK |
$color(0.0, 0.0, 0.0, 1.0) |
$Color.WHITE |
$color(1.0, 1.0, 1.0, 1.0) |
$Color.GRAY |
$color(0.74509805, 0.74509805, 0.74509805, 1.0) |
$Color.ORANGE |
$color(1.0, 0.64705884, 0.0, 1.0) |
$Color.LIME |
$color(0.0, 1.0, 0.0, 1.0) |
| Function | Description |
|---|---|
$is_array(value) |
true if it is a collection |
$is_dict(value) |
true if it is a dictionary |
$is_ptr(value) |
true if it is a pointer |
$type(value) |
Returns the class of the argument’s data type |
$type_of(value) |
Like $type, but returns an enum value to make comparisons easier |
The Type enum contains data types used by $type_of for comparisons:
| Type | Equivalent value |
|---|---|
$Type.STRING |
0 |
$Type.INT |
1 |
$Type.FLOAT |
2 |
$Type.BOOL |
3 |
$Type.POINTER |
4 |
$Type.OBJECT |
5 |
$Type.null |
-1 (the default for any enum) |
The first argument of $max() or $min() is the value to clamp, and the second is the limit. If the value exceeds the limit, the limit is returned automatically
var min_value: int = $min(25, 12) -- The minimum possible value is 12
var max_value: int = $max(72, 100) -- The maximum possible value is 100
var name: str[64] = $input("What is your name? ")
$pause() -- Execution pauses here
--c/--fc generate a standalone executable with PyInstaller. Neither flag runs your script for you to see its output — the only output is the resulting executable’s path or a message explaining why compilation was skipped.
Gybin mi_script.gbn --cThe interpreter has no separate “syntax-check only” mode — it directly walks the syntax tree, so “checking for errors” and “running the script” are technically the same operation. --c still needs to know whether the script fails, so it runs it internally once with all output (stdout/stderr) completely silenced — nothing it prints or does is shown, and its effects do not leak into anything printed afterward. --fc skips even that: it never touches the script and compiles unconditionally.
| Flag | Does it run the script? | Does it compile if there is an error? |
|---|---|---|
--c |
Completely silenced (error checking only) | No — prints Compilation skipped... and exits |
--fc |
Never | Yes, always |
Gybin juego.gbn --c --n MiJuego --ad assets/config.json --i icono.ico--n NAME— custom name for the compiled executable (default: the script’s filename without its extension).--ad PATH[=DESTINATION]— bundles an extra file or folder into the executable. Can be repeated.DESTINOis the destination folder inside the bundle (default:.).--i ICON_PATH— icon for the executable (.icoon Windows,.icnson macOS).
Every file brought in with @use or @from ... @as — at any depth (including transitive imports) and with any supported extension — is detected automatically and embedded in the executable. The compiled program is fully self-contained: it does not need the original .gbn files to run.
stdutils.gbn is also embedded in the executable and is never copied alongside it as an external file. This is intentional: since it loads automatically on every run, an editable external copy would pose a code-injection risk for a compiled binary.
If PyInstaller is unavailable, a Bash wrapper (.bat on Windows) is generated to invoke the interpreter directly:
#!/usr/bin/env bash
exec Gybin "mi_script.gbn" "$@"! A script whose own filename contains
:can still be compiled as the entry point. However, a file imported by that script whose filename contains:cannot be bundled — PyInstaller’s--add-datauses:as the SOURCE/DEST separator. You will receive a clear warning during compilation, and only that file will be skipped instead of failing the entire build.
When run with --w, the engine performs post-execution analysis and reports:
- Variables declared but never read
- Functions defined but never called
- Functions with an empty body
- Classes defined but never instantiated
- Classes with an empty body
- Enums defined but never used
- Multi-type variables used with more than 3 distinct types
- Suspicious assignments (declared type differs from the value’s actual type)
- Possible memory leaks: global containers with more than 256 elements that are never read
Warnings are printed to stderr in the following format:
Warning: file.gbn:42: Variable 'x' is declared but never read
Variables whose names start with
_are ignored by the analyzer (the convention for “intentionally unused”).
You may receive warnings about code you never wrote; these may refer to unused code in imported libraries.
$ is required to:
- Read a variable’s value:
$hp - Call a function:
$print(...),$add(1, 2) - Instantiate a class:
$Player("Juan") - Assign to an existing variable:
$hp = 50 - Chain access:
$p.damage(30)
$ is not used for:
- The initial declaration (
var hp: int = 100) - Accessing object fields after the dot (
$p.hp, no$p.$hp) - Parameter names in function signatures
Variables declared inside a block (function, loop, conditional) are local to that block. Redefining an outer variable inside an inner scope can cause conflicts:
var global: int = 100
func test() -> NULL
var global: int = 50 -- local redefinition
$print($global) -- error: scope ambiguity
end
Use different names for local variables that coexist with global variables of the same name.
any disables type checking. Use it only when strictly necessary, such as for variables that store values whose types are unknown at declaration time.
Always initialize arrays and dictionaries with [] or {} if you plan to operate on them immediately. Using NULL as the initial value and then trying to add elements before assigning a real collection will cause an error.
-- Correct:
var items: array[Item] = []
$items.append($Item("Sword"))
-- Problematic if append is used before assigning a collection:
var items: array[Item] = NULL
$items.append($Item("Sword")) -- error
Every block (func, class, if, while, for, try, match) must be closed with end. An unclosed block raises a SyntaxError.
! Although code can be written outside functions, it is recommended that most of the script be organized into functions.
! Indentation is not required by the language. It is still recommended for readability and organization; however, removing it may improve performance in large projects.