Tiny: a standalone dynamic programming language in Go with native Go functions

go dev.to

I’ve been building Tiny, a small dynamic programming language and runtime written in Go.

Tiny started as a compiler and VM learning project, but it has grown into a standalone language runtime with a bytecode compiler, stack-based VM, language server, package tooling, standard library, native Go function support, and standalone executable packing.

Tiny is not meant to be an embedded scripting layer inside another application. The goal is for Tiny to be its own language and runtime: something you can use to write programs, build tools, experiment with language features, and package projects into standalone executables.

The main idea is:

  • write programs in a small dynamic language
  • use optional type hints when you want extra checking
  • use the standard library for files, JSON, HTTP, processes, buffers, regex, time, webview, desktop automation, and more
  • compile .tiny files into compact bytecode
  • pack Tiny programs into standalone executables
  • move specific functions into Go through TinyGo/WebAssembly when you want lower-level or faster code

Tiny is dynamically typed by default, but annotated types are checked when you add them. The syntax is meant to feel familiar if you’ve used JavaScript, TypeScript, Lua, or Python, while still being its own thing.

Here is a small example:

import std "io";

fn greet(name: string): string {
    return "hello, " + name;
}

io.println(greet("dev.to"));
Enter fullscreen mode Exit fullscreen mode

You can write normal programs:

import std "io";
import std "json";
import std "time";

const user = {
    name: "Tiny",
    version: 1
};

io.println(json.stringify(user));
io.println(time.now());
Enter fullscreen mode Exit fullscreen mode

You can use classes and interfaces:

import std "io";

interface User {
    name: string,
    age: string
}

fn printUserName(user: User) {
    io.println(user.age)
}
Enter fullscreen mode Exit fullscreen mode
class ConsoleLogger {
    fn log(message: string) {
        io.println(message);
    }
}

const logger = ConsoleLogger();
logger.log("Tiny is running");
Enter fullscreen mode Exit fullscreen mode

Tiny also has modules, enums, match blocks, try/catch/finally, defer, closures, arrays, maps, structural interfaces, optional type hints, and a standard library with useful modules for building small programs and tools.

One feature I had fun building is native functions. Tiny lets you write small Go blocks inside Tiny source, compile them through TinyGo to WebAssembly, and call them from the VM. That means most of the program can stay in Tiny, while specific parts can escape into Go when needed.

For example, a Tiny file can define a native Go function and then call it like a normal Tiny function:

import std "io";

native fn fib(n: number): number {
    go {
        if n <= 1 {
            return n
        }

        return fib(n - 1) + fib(n - 2)
    }
}

io.println(fib(30)); // runs in 9ms
Enter fullscreen mode Exit fullscreen mode

That part is interesting to me because Tiny can feel like a small dynamic language on the surface, while still having a bridge into compiled Go code when a specific function needs to move out of the VM.

Under the hood, the compiler turns .tiny files into compact bytecode that runs on a stack-based VM. There is also a CLI, a bytecode cache, a package/dependency system, a pack command for standalone executables, and a language server with diagnostics, autocomplete, formatting, hover, and jump-to-definition.

This is not meant to be "better than Python" or "better than JavaScript" in ecosystem size. That would be silly. The interesting part for me is building a compact standalone language where the compiler, VM, tooling, package system, standard library, and editor support are all designed together.

It is still early, and I would not call it fully production-ready yet. But it is usable for small programs, tools, and experiments, and the core pieces are already working.

Links

Source: dev.to

arrow_back Back to Tutorials