Geblang Reference Manual
Geblang is a general-purpose scripting language implemented in Go.
Inspired by PHP and Python (and a little bit of Go), Geblang aims to offer the simplicity and ergonomics of those languages while making static typing, generics, async programming, runtime metadata and modern tooling part of the core language and standard developer experience.
To that end, Geblang combines features that will feel familiar to developers from both ecosystems.
Features inspired by PHP include:
- C-style, curly brace syntax; no significant whitespace
- Single inheritance, multiple interfaces
- A web-first feel, with tooling and standard-library support for HTTP APIs and web apps
- A familiar set of application-focused functionality in the standard library
- Practical request/response, session, routing, database, Redis, mailer and filesystem modules
- Familiar class-based application structure for services, controllers, handlers and domain objects
- Magic methods for callable objects and property access patterns
- Simple deployment for scripts, CLI tools and web applications
- Developer ergonomics aimed at everyday application work
Features inspired by Python include:
- First-class objects for all types
- Built-in dict, list and set types
- Simple module resolution based on source paths
- Aspect-oriented programming with decorators
- Operator overloading via magic/dunder methods
- Clear, readable syntax with low boilerplate
- REPL-first exploration and tooling
- Exceptions and custom error classes for structured failure handling
- A batteries-included standard library philosophy
- Operator overloading via dunder methods
- No private, protected, public modifiers
- Immutability via
freezemodule and@immutabledecorator for classes
Other features offered by Geblang as core language or first-party tooling include:
- Static typing with type inference, plus the special
anytype for intentionally dynamic values - Function and method overloading - multiple signatures of the same name, resolved by argument count and type at call time
- Generics with type checking and runtime reflection, including
instanceof Tchecks - Runtime type metadata via
typeof, type values and reflection APIs - Native async via cooperative scheduling as core syntax
- Built-in bundling of Geblang programs into distributable binaries containing the interpreter, stdlib, application and dependency packages
- First-party LSP, debugger, formatter, linter/static analysis and documentation tooling
- Bundled Visual Studio Code extension for syntax highlighting, stdlib autocomplete and step debugging.
- A language design that keeps PHP/Python-style productivity while making types, metadata, packaging and tooling coherent from the start
Compared with e.g. TypeScript, Geblang is not a typed layer over another dynamic runtime. Types, generics, reflection metadata, async execution, decorators, standard-library modules and bundling are all part of the language/runtime contract rather than erased or delegated to a separate JavaScript platform. That gives Geblang a simpler deployment story for server-side scripts, CLI tools and web services: the same language owns parsing, type checks, bytecode execution, native modules, debugging and standalone executable bundling. TypeScript remains a strong fit when you need the JavaScript and browser ecosystem; Geblang is aimed at the places where you want scripting ergonomics with a purpose-built backend/runtime and fewer layers between source code and the deployed program.
Repository
Geblang is open source under the MIT License. The repository is at https://github.com/dwgebler/geblang
What Geblang Is For
Geblang is designed for:
- Scripts that can grow into maintainable applications
- CLI tools, automation and developer utilities
- HTTP APIs, web apps and practical web services
- Data processing, file transformation and integration jobs
- Things you'd write in PHP or Python but where you want the language benefits of static types
- Applications that want to leverage runtime metadata
- Projects that should be easy to run, test, bundle and deploy
Philosophy
Geblang aims to offer the same simplicity for developers of languages like PHP and Python while adding the static guarantees those ecosystems often have to recover later with community-driven conventions, frameworks or third party static analysis tools.
Geblang was designed from the start to deliver the web-first benefits of PHP without being constrained by some of the historical design decisions that have held PHP back. Common web, CLI, data, Redis, database, HTTP, socket, Markdown, and serialization tasks are available out of the box. The goal is to provide enough well-typed building blocks that small applications can stay small, and larger applications can build clear framework layers on top.
Geblang favors explicitness at module and API boundaries. Imports name the module being used, user modules export their public surface deliberately, and type hints document the values an API expects. Dynamic features exist, but they are intended to work with typed code rather than replace it.
Coming From PHP
PHP developers will find Geblang's syntax familiar: semicolons, curly braces,
class/extends/implements, foreach, match, closures, and a rich
built-in standard library. The key differences are all additions:
| Feature | PHP | Geblang |
|---|---|---|
| Static typing | Optional type hints, loose enforcement | Always-on, enforced at declaration and call sites |
| Generics | Docblock comments only | First-class, reified - func identity<T>(T v): T works at runtime |
| Null safety | All types are nullable by default | Non-null by default; ?string opts in to null |
| Null coalescing | ?? operator, ?-> operator |
?? and ?. optional chaining |
| Interfaces | Runtime checks only | Statically enforced at declaration and call sites |
| Enums | enum with scalar backing |
enum with or without associated values, full pattern matching |
| Async/await | Fibers (PHP 8.1+, manual) | First-class async func, await, Task<T> |
| Decorators | Attributes (PHP 8, metadata only) | Callable decorators that wrap functions/methods, reflect module |
| Module system | require/include/use with namespaces |
Single import with explicit module resolution |
| Type introspection | gettype(), instanceof, get_class() |
typeof(x), x instanceof T, x.type, reflect module |
| Generics enforcement | Not available | x instanceof T resolves T to its concrete bound type at runtime |
| Bytecode VM | Zend Engine | Embedded Go VM - scripts, APIs, and CLI tools in one binary |
| Distribution | PHP runtime required | geblang build produces a self-contained executable |
Geblang also drops public, protected, private, and final
modifiers on classes and members. The reasoning follows the Python
side of the inspiration: visibility modifiers exist mainly to
enforce a convention that thoughtful API design and tooling already
handle. The discipline that visibility modifiers normally encode lives
in export, @immutable, interface contracts and being a responsible developer.
Coming From Python
Python developers will recognise the import system, list/dict comprehension
style, async/await, decorators, and the goal of staying readable at small
scale. The key differences are:
| Feature | Python | Geblang |
|---|---|---|
| Static typing | Annotations + mypy (optional, bolted on) | Built-in, enforced - no separate tool needed |
| Type syntax | Optional[T], list[T], Union[T, U] |
?T, list<T>, interface union constraints |
| Null safety | None is any type; runtime AttributeError |
null is only valid where ?type is declared |
| Generics | PEP 484 annotations, not reified | Reified - instanceof T and reflect.typeBindings work at runtime |
| Classes and OO | Duck typing, no enforced interface contracts | Formal interfaces, static method signatures, implements checked |
| Module imports | Implicit __init__.py, star imports possible |
Explicit import module;, deliberate export surface |
| Decimal math | decimal.Decimal class (import required) |
decimal is a primitive type; 3.14 is decimal by default |
| Async runtime | asyncio, event loop plumbing visible |
async func, await, and Task<T> - runtime managed, no loop setup |
| Decorators | Callable wrappers only, no runtime metadata | Both callable wrappers and inspectable metadata via reflect |
| Type introspection | type(), isinstance(), __class__ |
typeof(x), x instanceof T, x.type, consistent everywhere |
| Enums | enum.Enum (import, class, .value access) |
enum Color { Red, Green } - first-class syntax, pattern matching |
| Generators | yield, yield from, generator expressions |
yield in functions and closures, generator<T> type hint |
| Distribution | venv, pip, runtime required |
geblang build embeds interpreter, stdlib, and source into one binary |
| Indentation sensitivity | Required (syntax-level) | Braces (no whitespace sensitivity) |
Python features that Geblang omits include comprehensions as
a distinct syntax (use list.map/list.filter), multiple inheritance, *args
and **kwargs on every function by default (but there is spread support).
Quick Example
import io;
import collections;
interface Scored {
func score(): int;
}
@immutable
class Player implements Scored {
string name;
int rating;
func Player(string name, int rating) {
this.name = name;
this.rating = rating;
}
func score(): int {
return this.rating;
}
}
func topBy<T implements Scored>(list<T> items): T {
return collections.maxBy(items, func(T x): int { return x.score(); });
}
let players = [Player("Ada", 10), Player("Grace", 12), Player("Linus", 7)];
io.println(topBy(players).name); # Grace
The example shows a few of the language pieces a typical Geblang program might use:
a typed @immutable class with a constructor, an interface it
explicitly implements, a generic function with an interface-bounded
type parameter (T implements Scored) that is checked at compile time
and reified at runtime, a lambda whose T parameter resolves to the
outer call site's concrete bound (Player in this run), and a stdlib
higher-order helper (collections.maxBy) that takes the lambda by
value. The same code could later move Player into a module, hydrate
the list from a database, expose topBy through an HTTP handler, or
run inside an async task without changing the basic shape.
Performance
Geblang is designed around a bytecode VM as the normal execution path, with the
tree-walking evaluator kept as a compatibility and implementation path. Use
--trace-exec when running a script to confirm whether the VM or evaluator was
used.
The repository includes a small benchmark harness for local performance checks:
make bench
The benchmark suite is very simple, just covering an integer-heavy loop, recursive function calls and list construction/iteration. It reports Geblang timings alongside equivalent Python and PHP scripts when those runtimes are installed. There is also a version available via Docker if you don't have PHP or Python locally:
make bench-docker
These numbers should be treated as loose signals only. Both PHP and Python have made significant performance strides in recent years; PHP in particular ships a real JIT in PHP 8, and CPython 3.11+ has an improved interpreter. Geblang aims to be in the same ballpark as those interpreters on realistic application code without attempting to match the JIT.
Indicative numbers from a development machine (the absolute values will vary):
| Benchmark | Geblang | Python | PHP |
|---|---|---|---|
numeric_loop |
122 ms | 135 ms | 29 ms |
recursive_fib |
83 ms | 46 ms | 22 ms |
list_pipeline |
7 ms | 14 ms | 11 ms |
What Geblang is quick at
list_pipeline builds a 5,000-element list with .push, then walks
it once, filtering on value % 5 == 0 and summing the matches.
numeric_loop runs a counted for loop two million times with a
small if/else and integer arithmetic in the body. For loops where
the compiler can tell the variables stay as integers, we put
serious effort into making the body cheap, and the benchmark
reflects that.
What it's slower at
The case where you'll feel the difference is heavy function-call
work. recursive_fib(28) is a large number of recursive calls
and not much else, and there Geblang is noticeably slower than
the reference runtimes.
Geblang's conscious language design choices, particularly in respect of generics and type-safety, mean there is more runtime overhead.
For everyday code (request handlers, parsing JSON, walking lists and dicts, modest loops) the difference is hopefully invisible.
💡 Geblang is a personal project, built for fun, interest, curiosity and to help me learn Go and a bit about compiler and interpreter design. It's not meant to be super fast. This is not something I've built with a "you should actually stop using PHP/Python and use this instead" angle.
If you decide to try it out, do your own benchmarks and decide if the performance is right for you.
Manual Chapters
- Getting started: running scripts, checking code, REPL basics, bytecode cache.
- Syntax basics: comments, declarations, expressions, strings, collections.
- Types: primitive types, nullability, casts, type aliases, generics, type values.
- Control flow: conditionals, loops, pattern matching, defer.
- Functions and callables: defaults, named arguments, variadic parameters, anonymous functions, closures, decorators.
- Classes and interfaces: constructors, inheritance, static members, magic methods, interfaces, enums.
- Modules and packages: imports, exports, manifests, source stdlib.
- Errors: throwing, catching, built-in error classes, stack traces.
- Async and generators: tasks, await, lazy generators, iterable type hints.
- Testing: writing
*_test.gbfiles withtest.Test,@test/@tag, built-in assertions,geblang testrunner, CI integration. - Standard library reference: native modules and bundled source modules.
- Web development: HTTP, routing, middleware, sessions, cache, SSR helpers.
- Tooling and examples: CLI commands, tests, docs, Docker builds.
- Bundling and standalone executables:
geblang build, bundle format, package layout, first-run extraction, limitations. - VS Code extension: building and installing the extension, syntax highlighting, live diagnostics, step debugging, launch configurations.
- Internals: pipeline overview, lexer, parser, AST, semantic analyzer, runtime values, bytecode format, compiler, VM, evaluator, module system, native and source stdlib, CLI structure.
Status
I've released an initial 1.0. Geblang is actively evolving.
The bytecode VM is the preferred execution path when a feature is supported;
the evaluator remains a compatibility path and an implementation aid.
Use --trace-exec to see which engine ran a script.