globw Docs ← Home

Globw — Language and Interpreter Specification

Date: 2026-10-13 (v1.1: explicit types)

1. Goal

Globw turns school-style pseudocode into a real, executable language.

Source files use the .gw extension. The executable is globw.exe. Implementation language: Rust.

2. Program structure

A program is a sequence of lines, one statement per line, in this order:

  1. imports (import ..., see 6.12), optional;
  2. global declarations (global variables: ...), optional;
  3. main-program declarations (variables: ...);
  4. function / procedure definitions and main-program statements, in any order. Main statements run top to bottom. A function can be called before its definition appears in the text.

Comments run from ' to the end of the line. Blank lines are ignored.

global variables:
    counter as int

variables:
    n as int
    grades as int[5]

function double(x as int) as int
    return x * 2
end function

input(n)
output(double(n))

3. Lexical rules

4. Variables and types

4.1 Declarations

global variables:
    total as float

variables:
    price as float
    quantity, discount as int
    name as string
    ok as bool
    grades as int[10]
    grid as float[3, 4]
    names as list of string
    grid2 as list of list of int
    phone as dict of string to string

4.2 Types

Type Values
int 64-bit integers
float 64-bit floating point numbers
string text
bool true / false

Collections: T[N] (array), T[N, M] (matrix), list of T (list), set of T (set), dict of K to V (dictionary) and tuple of (T1, T2, ...) (tuple).

4.3 Nothing: none

An object or function variable can hold none, meaning "no value / absent".

variables:
    friend as Person
friend = none
if(friend == none)
    output("no friend")
end if

4.4 Values before use

5. Expressions

Precedence, from highest to lowest:

Level Operators
1 calls f(...), indexing v[i], m[i, j], chained indexing g[i][j], keys(d)[1]
2 unary -, not
3 *, /, div, mod
4 +, -
5 ==, !=, <, <=, >, >= (cannot be chained: a < b < c is an error)
6 and
7 or

6. Statements

6.1 Assignment

x = 5
grades[i] = 7
grid[i, j] = 0

A whole array or matrix cannot be assigned at once. A whole list or dictionary can, by reference, from a value of the same type: words = split(s, " "), row = [1, 2, 3]. Characters of a string cannot be changed.

grid2[1][2] = 9          ' element 2 of the first inner list
phone["Anna"] = "123"    ' adds the key, or replaces its value
book["Anna"]["mail"] = "a@b.it"

Assigning to a dictionary key adds it if it is missing. Collections are shared by reference: after append(grid2, row), changing row changes grid2[length(grid2)] too.

6.2 Conditionals

if(cond)
    ...
end if

if(cond)
    ...
else
    ...
end if

if(grade >= 8)
    output("great")
else
if(grade >= 6)
    output("pass")
else
    output("fail")
end if

6.3 Loops

while(cond)                     ' repeats while cond is true
    ...
end loop

for(i from start to end)        ' bounds included, step 1
    ...
end loop

for(i from 10 to 0 step -2)
    ...
end loop

repeat                          ' runs at least once,
    ...                         ' then repeats while cond is true
while(cond)

for(x in collection)            ' each element of a list or array,
    ...                         ' each key of a dictionary,
end loop                        ' each character of a string

break                           ' leaves the innermost loop
continue                        ' goes on with its next round

6.4 Functions and procedures

function area(base as float, height as float) as float
    variables:
        result as float
    result = base * height / 2
    return result
end function

procedure fill(v as int[], value as int)
    ...
end procedure

6.5 Input and output

input(a)          ' reads one line and converts it to the type of a
input(a, b)       ' reads one line per variable
output("Sum: ", s)

6.6 Built-in functions

Name Effect
length(x) number of elements of an array/list, or of characters of a string (not usable on matrices)
append(list, value) adds a value at the end of the list (procedure)
remove(list, position) removes the element at that position, counting from 1 (procedure)

Indices start at 1 for arrays, matrices, lists and strings. An out-of-range index is a run-time error.

6.7 Graphics window

A program can open one window and draw in it, game-style: each frame is drawn from scratch and then shown.

variables:
    x as float
x = 100
window(800, 600, "Demo")
while(open())
    clear("black")
    rect(x, 250, 50, 50, "red")
    if(key("RIGHT"))
        x = x + 4
    end if
    show()
end loop
Command Kind Effect
window(width as int, height as int, title as string) procedure opens the window (a second call replaces it); the canvas starts black
open() function → bool true until the user closes the window
show() procedure shows what was drawn, reads keyboard and mouse, and waits so that frames run at about 60 per second
clear(color as string) procedure fills the whole canvas
rect(x, y, width, height as float, color as string) procedure filled rectangle; (x, y) is its top-left corner
circle(x, y, radius as float, color as string) procedure filled circle centered on (x, y)
line(x1, y1, x2, y2 as float, color as string) procedure 1-pixel line
text(x, y as float, text as string, color as string) procedure text in the current font; (x, y) is the top-left corner of the line
font(name as string, size as int) procedure chooses the font for the next text calls; works with or without a window (see below)
text_width(text as string) function → int width in pixels of the text in the current font
key(name as string) function → bool true while the key is held: "A"…"Z", "0"…"9", "LEFT", "RIGHT", "UP", "DOWN", "SPACE", "ENTER", "ESCAPE", "SHIFT", "CTRL"
mouse_x(), mouse_y() function → int mouse position inside the window
click() function → bool true while the left button is held

Vector fonts are drawn anti-aliased. Before any font call the font is the system default (Segoe UI on Windows, Helvetica on macOS, DejaVu Sans on Linux) at 20 pixels, or "pixel" at 16 if that font is missing. An unknown font or an unreadable file is a run-time error. - End of the program: when the program ends while the window is still open, the last frame stays on screen until the user closes the window.

6.8 Dialog boxes

Command Kind Effect
message(text as string) procedure message box with an OK button; waits for it
confirm(question as string) function → bool Yes/No box: true for Yes
ask(question as string) function → string input box; returns the text typed, or "" if cancelled

6.9 Other built-in functions

Command Kind Effect
rgb(r, g, b as int) function → string the color "#rrggbb"; each value is clamped to 0…255
random(min, max as int) function → int a random integer from min to max included (run-time error if min > max)
time() function → float seconds since the program started
sin(degrees as float), cos(degrees as float) function → float sine and cosine of an angle in degrees
sqrt(x as float) function → float square root (run-time error if x < 0)
round(x as float) function → int nearest integer, halves away from zero (round(2.5) is 3, round(-2.5) is -3)

Built-in procedures can only be used as statements, and built-in functions only inside expressions. Arguments follow the same rules as user functions (§6.4). All these names are reserved: they cannot be used for variables or user functions.

6.10 3D graphics

The graphics window can also show simple 3D scenes. The 3D commands draw into the same window as the 2D ones and need window(...) first.

variables:
    angle as float
window(800, 600, "3D")
camera(0, 2, -6, 0, -15)
while(open())
    clear("#101020")
    rotate(0, angle, 0)
    cube(0, 0, 0, 2, "orange")
    rotate(0, 0, 0)
    sphere(3, 0, 1, 0.8, "cyan")
    box(0, -1.5, 0, 20, 0.2, 20, "gray")
    angle = angle + 1
    show()
end loop
Command Effect
cube(x, y, z, size, color) cube centered on (x, y, z)
box(x, y, z, width, height, depth, color) box centered on (x, y, z); width along x, height along y, depth along z
sphere(x, y, z, radius, color) sphere
triangle3d(x1, y1, z1, x2, y2, z2, x3, y3, z3, color) one triangle, visible from both sides

6.11 Standard library

Python-like functions, named in plain English in Globw's style. "Number" means int or float; "collection" means an array or a list (not a matrix). Positions count from 1. A collection argument is changed in place by the procedures that modify it.

Conversions

Command Result Effect
to_int(x) int x is a string ("42", spaces ignored), a float (truncated toward zero), an int or a bool (1/0); a string that is not a whole number is a run-time error
to_float(x) float x is a string ("3.5" or "3,5") or a number
to_text(x) string any simple value, written as output writes it
char(code as int) string the character with that Unicode code (char(65) is "A")
code(c as string) int the Unicode code of the first character
to_hex(n as int), to_binary(n as int) string to_hex(255) is "ff", to_binary(5) is "101"

Math (angles in degrees, as for sin and cos)

Command Result Effect
absolute(x) same as x absolute value of a number
minimum(a, b), maximum(a, b) int if both are ints, else float the smaller / larger of two numbers
minimum(c), maximum(c) element type smallest / largest element of a collection of numbers or strings (run-time error if empty)
power(base, exponent) float base raised to exponent
floor(x), ceiling(x), truncate(x) int round down, up, toward zero
round_to(x, digits as int) float x rounded to that many decimal digits
exponential(x) float e to the power x
logarithm(x), logarithm(x, base) float natural logarithm, or in the given base
tan(a), arcsin(x), arccos(x), arctan(x), arctan2(y, x) float tangent and inverse functions
pi() float 3.14159…
random_float() float a random number from 0 (included) to 1 (excluded)

Strings

Command Result Effect
uppercase(s), lowercase(s), trim(s) string change case; remove spaces at both ends
find(s, part) int position of the first part in s, 0 if absent
count(s, part) int how many times part appears (not overlapping)
replace(s, old, new) string every old replaced by new
split(s, separator) list of string the pieces between separators; an empty separator splits into characters
join(c, separator) string the elements of a collection, as text, separated by separator
starts_with(s, part), ends_with(s, part) bool
substring(s, from, to) string characters from to to included; positions outside the string are cut to it
contains(s, part) bool part appears in s

Collections

Command Result Effect
sum(c) element type sum of a collection of numbers
contains(c, value) bool the value is an element
index_of(c, value) int position of the first element equal to value, 0 if absent
sort(c) procedure sorts in increasing order (numbers, strings or bools)
reverse(c) procedure reverses the order
insert(list, position, value) procedure inserts before position (1 … length + 1)
take_last(list) element type removes and returns the last element (run-time error if empty)
clear_list(list) procedure removes every element

sum, contains, index_of, sort, join, minimum and maximum work on collections of simple values; length, append, remove, reverse, insert, take_last and clear_list work on any list, nested ones included.

Sets (a set of int or set of string: unique values, no order kept for lookup but iterated in insertion order)

Command Result Effect
add(s, value) procedure adds the value (nothing happens if already there)
discard(s, value) procedure removes the value (nothing happens if absent)
contains(s, value) bool the value is in the set
length(s) int how many values
to_list(s) list of T the values as a list, in insertion order

for(x in s) goes through the values. A set is printed as {1, 2, 3}.

Dictionaries

Command Result Effect
length(d) int number of keys
has_key(d, key) bool the key is in the dictionary
get(d, key, default) value type the value of the key, or default if it is missing
remove_key(d, key) procedure removes the key (run-time error if it is missing)
keys(d) list of K the keys, in the order they were first added
values(d) list of V the values, in the same order

input cannot read into a dictionary or a list of lists: read into a variable, then assign it.

A list returned by a function (like split or read_lines) can be assigned to a list variable of the same type: words = split(line_text, " "). Lists are assigned by reference.

Files (relative paths start from the folder of the program; text is UTF-8)

Command Result Effect
read_file(path) string the whole file
read_lines(path) list of string one element per line, without line endings
write_file(path, text) procedure creates or replaces the file
append_file(path, text) procedure adds at the end, creating the file if needed
file_exists(path) bool

A file that cannot be read or written is a run-time error that names it.

System

Command Result Effect
wait(milliseconds as int) procedure pauses the program
today() string today's date, like "2026-10-14"
now() string the time, like "16:05:09"
stop() procedure ends the program at once (the window, if any, closes)

All these names are reserved like the other built-ins. Python features that need types Globw does not have (sets, tuples), functions as values (map, filter, lambda) or classes are not part of Globw.

6.12 Modules and imports

Any .gw file can be imported by another one as a module. Imports go at the very start of the file:

import shapes                     ' shapes.gw, in the folder of the program
import statistics as st           ' with another name
from tools import mean, middle as mid
lazy import charts                ' loaded only when first used
lazy from sounds import beep

output(shapes.area(3, 4), " ", st.median(v), " ", mean(v))
shapes.calls = 0

Built-in modules. Some modules are provided by Globw itself, backed by Rust libraries and reinterpreted with Globw names. They are imported the same way, with import <name>, but there is no .gw file to find:

import text          ' regular expressions
import hash          ' digests
import web           ' web requests
import data          ' JSON
import clock         ' dates and times
import encode        ' base64
Module Function Result What it does
text text.matches(s, pattern) bool whether s contains a match of the regular expression pattern
text text.find(s, pattern) string the first match, or "" if there is none
text text.all(s, pattern) list of string every match, in order
text text.replace_all(s, pattern, with) string s with every match replaced by with
text text.split(s, pattern) list of string s split on every match of pattern
text text.count(s, pattern) int how many matches of pattern are in s
text text.group(s, pattern, n) string the n-th capture group of the first match (0 = the whole match), "" if none
hash hash.sha256(s) string the SHA-256 digest of s, as lowercase hexadecimal
hash hash.sha512(s) string the SHA-512 digest of s, as lowercase hexadecimal
web web.get(url) string fetches url over HTTP(S) and returns the reply body as text
web web.post(url, body) string sends body with a POST request and returns the reply as text
web web.status(url) int the HTTP status code of a GET to url (e.g. 200, 404)
web web.download(url, path) — saves the bytes at url to the file path
data data.text(json, path) string the value at path as text (a JSON string, number or bool)
data data.number(json, path) float the value at path, which must be a JSON number
data data.flag(json, path) bool the value at path, which must be a JSON true/false
data data.has(json, path) bool whether path exists in the JSON
data data.size(json, path) int how many elements the array or object at path has
data data.keys(json, path) list of string the keys of the object at path
data data.to_json(value) string the JSON text of a Globw value (number, string, bool, none, list, set or dictionary; nested too)
data data.pretty(json) string the JSON re-formatted on several indented lines
data data.type(json, path) string the kind at path: "number", "text", "bool", "list", "object" or "none"
clock clock.stamp() int the current time as a Unix timestamp (seconds since 1970-01-01 UTC)
clock clock.format(stamp, pattern) string a Unix timestamp written as local time with a date pattern
clock clock.parse(text, pattern) int reads a local date-and-time written like pattern and returns its Unix timestamp
encode encode.base64(s) string the Base64 of the text's bytes
encode encode.from_base64(s) string the text decoded from Base64 (error if it is not valid Base64 or not text)

There are many more built-in modules, each imported the same way. Below, one row per module with its functions.

Module Functions Purpose
shell run(cmd) run a system command, return its output (its failure is a catchable error)
math gcd(a,b), lcm(a,b), factorial(n), is_prime(n), sign(x), hypot(a,b), clamp(x,lo,hi), cbrt(x) extra maths
bits and, or, xor, not, left(n,by), right(n,by), ones(n) bitwise operations on ints
base to(n, base), from(text, base) whole numbers in any base from 2 to 36
roman to(n), from(text) Roman numerals (1–3999)
temp c_to_f, f_to_c, c_to_k, k_to_c temperature conversions
angle to_radians, to_degrees, normalize angles (degrees)
percent of(p, x), change(from, to) percentages
geom circle_area(r), rect_area(w,h), triangle_area(b,h), sphere_volume(r), cube_volume(s) areas and volumes
length meters_to_feet, feet_to_meters, km_to_miles, miles_to_km length conversions
weight kg_to_lb, lb_to_kg weight conversions
size human(bytes) a byte count as "1.5 MB"
duration human(seconds) a number of seconds as "1h 2m 5s"
geo distance(lat1,lon1,lat2,lon2) distance in km between two coordinates
tax add(amount, rate), remove(amount, rate) add or remove a percentage tax
compass direction(degrees) a bearing as "N", "NE", …
ordinal of(n) 1 → "1st"
fib at(n) the nth Fibonacci number
prime next(n), nth(n) the next prime after n, or the nth prime
str repeat(s,n), reverse(s), pad_left(s,w,fill), pad_right(s,w,fill), center(s,w,fill) more string tools
hex encode(s), decode(s) text to/from hexadecimal
url encode(s), decode(s) percent-encoding for URLs
case title(s), snake(s), camel(s), kebab(s) naming styles
slug make(s) a URL-friendly slug
ascii is_digit(s), is_alpha(s), is_space(s), is_upper(s), is_lower(s) character-class checks of a whole string
morse encode(s), decode(s) Morse code
caesar encode(s, shift), decode(s, shift) Caesar cipher
rot13 apply(s) ROT13
leet encode(s) leetspeak
nato encode(s) the NATO phonetic alphabet
color hex(r,g,b), gray(level), invert(color) #rrggbb colors
valid email(s), integer(s), number(s) simple format checks
luhn valid(s) the Luhn checksum (card numbers)
isbn valid(s) an ISBN-10 or ISBN-13 check digit
uuid make() a random UUID (v4)
crc value(s) the CRC-32 checksum of the text
encode base64(s), from_base64(s) Base64
env get(name), has(name), set(name, value) environment variables
path join(a,b), name(p), stem(p), extension(p), parent(p), is_absolute(p) file-path parts (text only)
os name(), arch(), family() the operating system
dir list(path), make(path), current(), exists(path) folders
file size(p), copy(from,to), rename(from,to), modified(p), delete(p) file operations
tmp path() the system temporary folder
args count(), get(n) the arguments the program was started with
csv fields(line), line(list) one CSV line to or from a list of strings
stats mean(list), median(list), stdev(list) statistics of a list of numbers

Most of these are plain functions whose errors (a bad number, a missing file, a value of the wrong kind) are catchable with try/catch. shell, env.set, dir.make, file.copy/rename/delete change the computer or the environment, by design: Globw does not forbid a capability just because it is powerful.

And many more. Globw ships a large standard library; the modules below round it out (import each with import <name>; the function names follow the same module.function(...) shape and read for themselves). Grouped by theme:

And a further 150 small modules (one function family each), among them: more unit conversions (acceleration, lux, radiation, turn, flow, typography, oven, glucose); activation and special functions (sigmoid, relu, softsign, gauss, erf, gammaf, sinc, smoothstep); string tools (nospaces, digitsonly, disemvowel, screaming, dotcase, pathcase, sentencecase, dedup, rotstr, zfill, soundex, hamming, levenshtein, syllables); physics (kinematics, projectile, pendulum, spring, lens, resistors, capacitors, doppler, escape, orbit, lorentz, freefall, gforce); finance (annuity, breakeven, profit, commission, wage, inflation, rule72, splitbill); health (idealweight, tdee, water, dosage); geometry (tetrahedron, octahedron, parallelogram, rhombus, sector, ellipsoid, ngon, frustum); number theory and sequences (lucas, tribonacci, padovan, pentagonal, hexagonal, star, pronic, polygonal, primorial, primecount, prevprime, coprime, amicable, happy, aliquot, collatzmax); bits (nibble, byteops, endian, nextpow2, ispow2, align, graycode, revbits); and checks/formatting (approx, bankers, plural, abbreviate, gpa, zscore, ean, cardtype, a1z26, luminance, contrast, note, prob, odds, ph, humidex, sound). Each follows the same module.function(...) shape.

The array engine (nungw). For heavy number crunching, nungw of float is a first-class type: an N-dimensional array of floats kept in contiguous memory and worked on across every CPU core. import nungw to use it. The usual operators work element-wise, and g[i] reads an element (1-based). (The name is Globw's own take on the idea a tool like NumPy covers.)

import nungw
variables:
    a, c as nungw of float
a = nungw.range(10000000)            ' 0, 1, 2, ... as a fast float array
c = nungw.sqrt(a) * 1.5 - a / 3.0    ' operators work element-wise (fused)
output(c[1], " ", nungw.sum(c))      ' index (1-based) and a reduction
Function Purpose
nungw.range(n), nungw.zeros(n), nungw.fill(n, v), nungw.of(list) build one
nungw.size(g), nungw.get(g, i), nungw.set(g, i, v), nungw.to_list(g) inspect, read, change, read back
nungw.sqrt/square/abs(g) element-wise, giving a new array (same as the operators)
nungw.exp/exp2/expm1/log/log2/log10/log1p(g) exponentials and logarithms, element-wise
nungw.sin/cos/tan/asin/acos/atan(g), nungw.sinh/cosh/tanh/asinh/acosh/atanh(g) trigonometry and hyperbolics, element-wise
nungw.floor/ceil/round/trunc/sign/cbrt/recip/neg(g), nungw.degrees/radians(g) rounding (round ties to even, like NumPy), sign, cube root, reciprocal, negation, angle conversion
nungw.pow(a, b), nungw.atan2(a, b), nungw.hypot(a, b), nungw.mod(a, b), nungw.maximum(a, b), nungw.minimum(a, b) binary element-wise between two nungws
nungw.sum/mean/max/min(g), nungw.dot(a, b), nungw.sum_squares(g), nungw.norm(g) reductions to a single number
nungw.prod/var/std/median/ptp(g), nungw.argmax(g), nungw.argmin(g) more statistics (var/std are population; argmax/argmin give the 1-based position)
nungw.matmul(a, rows, inner, b, cols) matrix product: a is rows×inner, b is inner×cols (row-major), result rows×cols
nungw.solve(a, n, b), nungw.inv(a, n), nungw.det(a, n), nungw.transpose(a, rows, cols) linear algebra on a flat row-major matrix (LU with partial pivoting): solve A x = b, inverse, determinant, transpose
nungw.fft(a), nungw.ifft(c), nungw.cabs(c) FFT of a real array → complex, inverse FFT, and magnitudes; a complex array is interleaved [re, im, re, im, ...]
nungw.shape(g), nungw.ndim(g), nungw.reshape(g, [dims]) a nungw is N-dimensional: read its shape, its number of dimensions, or give it a new shape
nungw.at(g, i, j, ...) read one element by N-dimensional index (1-based)
nungw.sum_axis / mean_axis / max_axis / min_axis / prod_axis(g, axis) reduce along one dimension (0-based axis), removing it
nungw.rand(n), nungw.randn(n), nungw.eye(n) random uniform [0, 1), standard-normal, and the n×n identity
nungw.cumsum(g), nungw.cumprod(g), nungw.sort(g), nungw.argsort(g) running sum/product, sorted values, and the 1-based sort order (all flattened)
nungw.clip(g, lo, hi), nungw.concat(a, b), nungw.where(cond, a, b) clamp to a range; join two arrays (flat); pick from a where cond ≠ 0, else b
nungw.concat_axis(a, b, axis), nungw.slice(g, start, stop) join two arrays along a dimension (vstack/hstack); take a 1-based inclusive flat slice
nungw.eigvals_sym(a, n), nungw.singular_values(a, m, n), nungw.lstsq(a, m, n, b) eigenvalues of a symmetric matrix, singular values, and the least-squares solution of A x ≈ b

A nungw is N-dimensional: it carries a shape, and element-wise binary operators broadcast like NumPy (shapes align from the right; a size-1 dimension repeats). matmul and transpose give 2-D results.

The linear algebra and FFT are pure Rust (no LAPACK), so they run everywhere Globw does, the WebAssembly playground included.

A nungw is a value: a temporary made by an operator frees itself when it is no longer used, so loops do not leak. The reductions sum_squares, norm and dot are fused — one pass over memory, no temporary array.

Fusion. An element-wise expression assigned to a nungw, like c = nungw.sqrt(a) * 1.5 - a / 3.0, is compiled into one kernel that runs in a single parallel pass with no temporary arrays — not one pass per operator. The kernel is SIMD-vectorized (four lanes at a time), across every core.

Speed. On a 10-million element array, against NumPy (16 cores): - nungw.sum_squares(a) runs in ~0.36 ms, vs NumPy np.sum(a**2) ~14 ms and np.einsum ~2 ms. - the fused expression nungw.sqrt(a) * 1.5 - a / 3.0 runs in ~13 ms, vs ~41 ms for the same NumPy expression (~3x).

Globw wins because the arrays are contiguous unboxed floats, the expression is fused and SIMD-vectorized across every core, while NumPy's element-wise operations are single-threaded and build temporaries. Compiling each kernel to machine code (a JIT) could take it further still, close to the memory-bandwidth limit.

For matrix multiplication (nungw.matmul), Globw uses an optimized BLAS when one is available — exactly as NumPy does — and otherwise a cache-blocked, multi-threaded pure-Rust kernel. Point the GLOBW_BLAS environment variable at an OpenBLAS (or compatible) shared library, or put openblas.dll next to the program, and nungw.matmul calls it.

On a 2000×2000 product (f64): with the same OpenBLAS that NumPy ships, Globw runs in ~25 ms against NumPy's ~22 ms — a tie. Without a BLAS, the built-in kernel does it in ~39 ms. So on linear algebra Globw matches NumPy when it has the same library, and stays within ~2× on its own.

Databases (db). import db gives a real SQL database, backed by SQLite (the same engine behind Python's sqlite3). A connection is an int handle.

import db
import data
variables:
    c, n as int
    rows as string
c = db.open("shop.db")               ' a file, or ":memory:" for a temporary one
n = db.run(c, "create table if not exists items (name text, price real)")
n = db.run(c, "insert into items values ('pen', 1.5), ('book', 9.0)")
rows = db.query(c, "select name, price from items order by price")
output(data.text(rows, "[1].name"), " costs ", data.number(rows, "[1].price"))
db.close(c)

Web server (serve). import serve runs an HTTP server. It is synchronous: the program takes one request at a time and answers it, so there are no callbacks.

import serve
import data
variables:
    s as int
    req, path as string
s = serve.start(8080)                 ' listen on 127.0.0.1:8080
while(true)
    req = serve.wait(s)               ' blocks until a request arrives
    path = data.text(req, "path")
    if(path == "/hello")
        serve.reply(s, 200, "<h1>Hello from Globw!</h1>")
    else
        serve.reply(s, 404, "not found")
    end if
end loop

Background processes (proc). import proc runs other programs at the same time — concurrency at the level of the operating system (Globw's own code stays single-threaded, so this does not share memory; it runs separate programs in parallel and collects their output).

import proc
variables:
    a, b as int
a = proc.start("curl -s https://example.com")   ' both start at once
b = proc.start("curl -s https://example.org")
output(proc.wait(a))                              ' then collect each result
output(proc.wait(b))

Three 2-second commands started together finish in about 2 seconds, not 6 — they really run at the same time.

GUI widgets (gtq, inspired by Qt). import gtq adds buttons, checkboxes and sliders to the graphics window. It is immediate mode: inside the usual while(open()) … show() end loop, you draw each widget every frame and it tells you what the user did.

import gtq
variables:
    on as bool
    vol as float
window(400, 250, "My app")
while(open())
    clear("#1e293b")
    gtq.panel(20, 18, 360, 52, "#334155")
    gtq.label(34, 36, "A little GUI", "#e2e8f0")
    if(gtq.button(34, 95, 150, 38, "Click me"))
        output("clicked")
    end if
    on = gtq.checkbox(210, 104, "On", on)      ' returns the new state
    vol = gtq.slider(34, 185, 330, vol, 0, 100) ' returns the new value
    show()
end loop

(gtq covers the common Qt Widgets. Qt's other areas are served by other modules: SQL by db, networking by web/serve, processes by proc, charts by chart — and Globw does not replicate Qt's full class library.)

gtq batch G — 150 more GUI functions. Four families:

Liquid glass. gtq can render Apple-style frosted "liquid glass": it blurs whatever is already drawn behind the shape, lays a translucent tint on top, adds a top sheen and a lit top-left / shaded bottom-right rim, and refracts the background near the curved edge — bending and magnifying it, like a real lens. Draw it after the content it sits over.

Rich HTTP client (net). Beyond web's simple get/post, net speaks the full set of methods, custom headers and URL parsing:

Unicode-aware text (unicode). unicode.upper/unicode.lower (full Unicode case, so straße → STRASSE), unicode.reverse, unicode.trim, and the single-character tests unicode.is_letter, unicode.is_digit, unicode.is_space, unicode.is_upper.

grad — automatic differentiation. A built-in reverse-mode autodiff engine: build an expression from grad.var(x) / grad.const(x) and the ops add, sub, mul, div, neg, pow, exp, ln, sin, cos, tanh, sigmoid, relu (each returns a node handle), then grad.backward(node) fills every input's gradient, read with grad.grad(h) (and the value with grad.value(h)); grad.reset() starts a new tape. Gradient descent is a few lines — e.g. minimising (x-5)^2 converges x to 5. A language with differentiable values built in is something Python needs PyTorch/JAX for.

task — cooperative tasks (green threads). task.spawn(f) starts the function f as a lightweight task and returns its handle; task.yield() hands control to the next ready task; the program ends when every task has finished. Tasks share everything (globals, the window, files) and run one at a time on a single thread — switching only at task.yield() — so there are no data races. task.await(handle) blocks the current task until that task has finished (a join); task.done(handle) tests it without blocking. task.id() is the running task's handle and task.count() the number of tasks. (A worker is usually a no-argument lambda: w = function() … end procedure, then h = task.spawn(w) … task.await(h).) Workers talk to each other through channels: task.channel() makes one and returns its handle, task.send(ch, text) puts a text value on it, and task.recv(ch) takes the next one — blocking the task if the channel is empty until another task sends (so a producer and a consumer task hand work back and forth). task.has(ch) checks for a waiting value without blocking. Channels carry text, so send numbers with to_text and read them back with to_float / to_int.

ai — chat with a language model. One module for both paid API keys and local models, because Groq, OpenRouter, OpenAI and a local Ollama all speak the same POST {endpoint}/chat/completions API. Pick a backend with ai.use_groq(key, model), ai.use_openrouter(key, model), ai.use_openai(key, model) or ai.use_ollama(model) (local, no key), or set ai.endpoint, ai.key, ai.model by hand; ai.system(text) sets a system prompt and ai.temperature(t) the randomness. Then ai.ask(prompt) returns a one-shot answer, while ai.say(prompt) keeps the conversation (it remembers the previous turns) and ai.reset() clears that history. Example (fully local): ai.use_ollama("llama3") then ai.ask("Capital of Italy?").

symbol — symbolic mathematics. Build an expression tree from symbol.var("x"), symbol.const(c) and the ops add, sub, mul, div, pow, neg, sin, cos, exp, ln (each returns a node handle); then symbol.diff(expr, var) returns the symbolic derivative, symbol.simplify(expr) folds constants and trivial identities (x+0, x*1, x*0, x^1, …), symbol.eval(expr, var, value) substitutes and evaluates, and symbol.text(expr) prints it. symbol.reset() clears the tape. The simplifier is basic — it keeps results correct, not fully canonical — so a derivative may read e.g. (x^3)*(3*(1/x)) rather than 3*x^2, but evaluates identically.

synth — audio synthesis and DSP. Make and shape sound buffers: synth.rate(hz) sets the sample rate; synth.tone(freq, seconds, wave) generates "sine", "square", "saw", "triangle" or "noise"; synth.buffer/synth.silence make empty ones. Shape them in place (each returns the handle, for chaining): gain, mix, concat, fade (attack/release), lowpass, highpass, normalize. Measure with length, duration, peak, rms, and dominant (the fundamental frequency, via a DFT). Save and load 16-bit WAV with save_wav / load_wav, and synth.play(path) hands a file to the OS player.

gwsim — 2D physics. A little world of circular bodies: gwsim.body(x, y, r, mass) → handle, gwsim.fix(h, true) makes it static, gwsim.gravity(gx, gy) and gwsim.bounds(x, y, w, h, restitution) set the world; gwsim.push, gwsim.set_vel, gwsim.set_pos drive a body; gwsim.step(dt) integrates everything and resolves wall and body-body collisions with impulses; read state with gwsim.x/y/vx/vy, test contact with gwsim.collide(a, b), count with gwsim.count, clear with gwsim.reset. Draw each body yourself with circle(gwsim.x(h), gwsim.y(h), r, color).

gwframe — numeric dataframe (pandas-like). Load a numeric CSV with a header into a frame: gwframe.from_csv(path) → handle. Inspect with rows, cols, colname(f, i), get(f, row, name); aggregate a column with sum, mean, min, max, std; reshape with filter(f, name, op, value) (op is ">", "<", ">=", "<=", "==", "!="), sort(f, name, ascending), head(f, n), and group_sum / group_mean(f, keyCol, valCol); output with show(f, n) (a printable table) and to_csv(f, path). gwframe.free(f) releases it.

gwgame — 2D game toolkit. Primitives for little games, on top of the graphics window: collisions aabb, hit_circle, point_in, overlap; input axis(negKey, posKey) (returns -1/0/1) and key; movement move_toward, wrap (screen wrap), bounce (reflect a velocity at a wall), clampf, lerp, snap, cell; vectors distance, direction, vx/vy (velocity from speed and angle), angle_diff, reached, sign; and drawing sprite(x,y,w,h,color) and tile(x,y,size,color). A paddle-and-ball game fits in ~25 lines, in one binary with no dependencies.

1000 built-in modules. A large family of single-purpose modules covers unit conversions and named constants. Each conversion module has module.si(x) (to the quantity's SI base) and module.inv(x) (back) — e.g. furlong.si(1) = 201.168 m, carat.inv(0.4) = 2000 carats — across length, mass, volume, area, time, speed, pressure, energy, power, force, data, angle, frequency, charge, density, flow, torque, acceleration, radioactivity and more, including historical and regional units. Each constant module has module.value() — e.g. phi.value(), speedoflight_const.value(), avogadro_const.value().

Constant folding. A sub-expression made only of number literals and +, -, * is computed once at compile time, so (5 + 2) * (5 - 4) becomes the literal 7 and costs nothing at run time — even inside a loop. Integer overflow is still reported at run time, so the result is always identical to evaluating it. (Independent cheap operations are not worth spreading over CPU cores — a single core already runs them in parallel on its several arithmetic units; real multi-core speed-ups come from heavy independent work, which nungw arrays do with threads and proc does with processes.)

Where import looks for a module. A module foo is loaded from foo.gw, searched in this order: next to the file that imports it, then the main program's folder, then a libs/ folder there, then every directory listed in the GLOBW_PATH environment variable (a minimal package path, OS path separator). Built-in modules need no file.

Getting a module from the web. globw add <url> downloads a .gw file and saves it into a libs/ folder in the current directory, so import can then find it — a minimal way to share modules. The module name comes from the file name in the URL (https://.../shapes.gw → import shapes).

More built-in functions (batch H, 150 functions).

6.13 Classes

A class groups data (fields) and the operations on it (methods). Objects are made with new.

class Point
    variables:
        x, y as float

    procedure new(x as float, y as float)      ' the constructor
        self.x = x
        self.y = y
    end procedure

    function distance(other as Point) as float
        return sqrt((self.x - other.x) * (self.x - other.x) + (self.y - other.y) * (self.y - other.y))
    end function

    procedure move(dx as float, dy as float)
        self.x = self.x + dx
        self.y = self.y + dy
    end procedure
end class

variables:
    p as Point
    line as list of Point

p = new Point(3, 4)
output(p.x, " ", p.distance(new Point(0, 0)))
p.move(1, -1)
append(line, new Point(0, 0))
output(line[1].x)

6.14 Error handling

A run-time error normally stops the program. A try block catches it instead.

variables:
    problem as string

try
    output(100 div n)
catch problem                ' "problem" receives the error message
    output("Something went wrong: ", problem)
always                       ' optional: runs on every path
    output("Done.")
end try

fail "the balance cannot be negative"   ' raise your own error

6.15 Functions as values

A function can be stored in a variable, passed to another function, and called through the variable. There are also short anonymous functions (lambdas).

variables:
    op as function(int, int) as int     ' a function type

function add(a as int, b as int) as int
    return a + b
end function

op = add                                ' a named function used as a value
output(op(3, 4))                         ' called through the variable

' a lambda: one line with "gives", or a block closed by end function
double = function(x as int) as int gives x * 2
noisy = function(x as int) as int
    output("seen ", x)
    return x
end function

List operations that take a function:

Command Result Effect
transform(list, f) list of R a new list with f applied to each element (map)
keep(list, f) same list type a new list with the elements where f is true (filter)
reduce(list, f, start) the accumulator type folds the list to one value: f(acc, element)
squares = transform(nums, function(x as int) as int gives x * x)
odds    = keep(nums, function(x as int) as bool gives x mod 2 == 1)
total   = reduce(nums, function(acc as int, x as int) as int gives acc + x, 0)

6.16 Tuples

A tuple groups a fixed number of values that may have different types, without defining a class. Its main use is returning several values at once.

variables:
    point as tuple of (int, int)
    lo, hi as int

point = (3, 7)                 ' a tuple literal (two or more values)
output(point.1, " ", point.2)  ' elements by 1-based position

function range(l as list of int) as tuple of (int, int)
    return (minimum(l), maximum(l))
end function

lo, hi = range([5, 2, 9])      ' unpacking: several variables from one tuple
lo, hi = (hi, lo)              ' the right side is evaluated first, so this swaps

7. Errors

There is one format for all errors, with line, column and a marker under the offending code:

Error at line 7, column 5: variable "total" is not declared
    total = 0
    ^^^^^
Hint: add it to "variables:" at the top of the program

8. Architecture

source.gw → Lexer → Parser → Checker → Compiler → VM
            tokens    AST    typed IR   bytecode

The project is a Cargo project at the repository root, with one module per phase:

Module Job Depends on
errors.rs Pos, Error (position, message, hint) and rendering —
lexer.rs text → tokens with line/column errors
ast.rs syntax tree errors
parser.rs tokens → AST; block matching (end if, end loop…) lexer, ast, errors
tir.rs typed IR: types, places, typed expressions/statements ast, errors
checker/ names and scopes, type checking, definite assignment, return paths → typed IR ast, tir, errors
bytecode.rs instruction set and module layout tir, errors
compiler.rs typed IR → register bytecode tir, bytecode
vm.rs bytecode execution bytecode, errors
lib.rs compile and run entry points all
builtins.rs table of built-in procedures/functions (names, parameter and return types) and their run-time dispatch tir, canvas, graphics, dialogs
canvas.rs pixel buffer: colors, shapes, text (no window, testable) —
graphics.rs the window, keyboard and mouse (crate minifb) canvas, render3d
render3d.rs 3D camera, shapes, clipping, shading and depth buffer on a canvas canvas
dialogs.rs cross-platform message, Yes/No and input boxes (crate tinyfiledialogs) —
main.rs command line lib

Speed choices - Register-based bytecode, like Lua 5: fewer instructions than a stack machine. - Type-specialized instructions (AddI, AddR, CmpI…): the types are already known, so the VM never checks them during operations. - Integers and reals are never heap-allocated. Strings, arrays and lists are shared by reference. - Globals live in fixed-index slots and are never looked up by name at run time.

Command line

globw program.gw           run
globw --check program.gw
       globw --profile program.gw  (counts how often each opcode runs)   only check for errors, do not run

9. Tests

10. Packages (gwget)

Globw programs run with the gw command, and external libraries are installed with its package manager, gwget. Installed libraries live in ~/.gw/libs and are found automatically by import.

10.1 Running a program

gw program.gw
gw --check program.gw     ' only check for errors, do not run
gw --profile program.gw   ' run with an opcode profile

10.2 Installing libraries

gwget install vec2     ' download it into ~/.gw/libs
gwget search math      ' search the catalogue
gwget list             ' list what is installed here
gwget remove vec2      ' uninstall it

Once installed, a library is used like any other module:

import vec2
output(vec2.length(3, 4))

10.3 Publishing a library

A library is a folder with a manifest gw.json and one or more .gw files; the entry module must be <name>.gw.

{
  "name": "vec2",
  "version": "1.0.0",
  "description": "2D vectors",
  "author": "anonymous"
}

Then, from inside the folder:

gwget publish

The library goes online at once, anonymously. The first publish prints a one-time edit token: keep it — it is what lets you update the library later, and it protects the name from anyone else. The catalogue refuses spam, links or offensive text in the name, description or author, and code that is an exact copy of a library already published under another name. Published libraries can be taken offline by the maintainer.

10.4 Configuration

The catalogue connection is read from GWGET_SUPABASE_URL and GWGET_SUPABASE_KEY, or from ~/.gw/config.json. GW_HOME moves the ~/.gw folder, and import also searches any folder on GW_PATH.

↑ Back to top