Rewrite?
Use Ranger.

There is no Silver Bullet.

Ranger is a little heavier to get started with, but the longer you use it, the more it rewards you. It is especially useful if you have multiple languages to target, because it can act as a golden test for validating the algorithms.

Stay DRY.

Many programs share code across multiple platforms. AI makes generating and adapting that code easier, but consistent validation is still a separate problem.

Ranger started before the current AI wave, but AI has given the project a new purpose: producing code is easier than ever, while verifying that it behaves consistently across platforms has become even more important.

Get started Read the docs git clone https://github.com/terotests/RangerStarter my-app

The problem

Native apps
without duplicating the logic

If you build iOS and Android separately, the UI can stay native — SwiftUI on iOS, Compose on Android — but shared business logic often gets implemented twice.

That means two copies of things like:

  • validation
  • pricing and calculations
  • state machines
  • parsers
  • sync rules
  • domain models

The implementations may start out identical, but they can drift as the applications evolve.

What Ranger changes

With Ranger, the shared logic has one source file and is compiled to both Swift and Kotlin.

The platform UI stays native. Only the logic that should behave the same on both platforms is shared.

No bridge or shared runtime

Ranger generates ordinary Swift and Kotlin source code.

The iOS application calls the generated Swift classes normally. The Android application calls the generated Kotlin classes normally.

There is no JavaScript runtime, cross-language bridge, message protocol or serialization layer between the UI and the shared logic.

Swift

let result = pricing.calculate(order)

Kotlin

val result = pricing.calculate(order)

Both calls can originate from the same Ranger implementation.

One implementation to maintain

If a rule changes, you change it once:

The generated files can be checked into the repository, reviewed in diffs and tested with the normal platform toolchains.

This keeps the part that must stay identical in one place, while leaving the parts that should be platform-specific native.

Native UI. Shared logic. One source of truth.

One source

The same source,
across the targets you ship to.

None of the target-language code below was written by hand. landing/examples/Cart.rgr is forty lines of Ranger, compiled by this commit's compiler when the site is built, and each tab holds the file it wrote. These eight tabs are a selection; -l= accepts fourteen back ends in all.

Cart.rgr Ranger
loading…
loading…

Targets

Generated code quality

Three questions about the source Ranger writes for each target, kept apart because a target can do well on one and badly on another: is it right, is it fast, and does it read like code a native developer would keep?

Correct. Twelve studies — ownership, optionals, shapes, traits, iterators, generics, strings, builders, errors, parameters and interfaces — are compiled for every target, run where a toolchain exists, and diffed against each other. Eight targets agree on all twelve. The one place they do not is integer width: a Ranger int is 64-bit on JavaScript, Python, PHP, Go and Rust and 32-bit on C++, C#, Java and Kotlin, so a program that crosses 2³¹ gets a different answer on those four.

Fast. The same Ranger program, five kernels, each one timing itself, on every target that builds here. Milliseconds, lower is faster; process and VM startup are excluded, so these are the kernels and nothing else.

Five-kernel benchmark by target, milliseconds, lower is faster
Target Arith Arrays Strings Maps Objects Total
C++ 11112 7438 136
Rust 13113 10142 170
Go 12344 12561 236
Java 536012 18937 351
JavaScript 14824 263145 508
Python 24516613 181366 971

All six printed the same five checksums. A Ranger map is a plain object on JavaScript, which is why its maps column is the slowest in the table. PHP, C# and Kotlin are not in this run: there is no php, mcs or kotlinc on this machine.

Idiomatic. The score below is the third question only, and it is a judgement rather than a measurement — so it is made against a published checklist of twelve idioms, each one readable off a generated file in the repository. A higher score means fewer places where a reviewer would be able to tell the file was generated. It says nothing about whether the target is correct or fast, and it is separate again from the target capability matrix. The checklist, the benchmark and the per-target reading are in gallery/friendly.

Generated-code quality score by language, average of twelve studies
Rank Language Score
1 Swift 92%
2 Kotlin 88%
3 TypeScript 83%
4 Dart 83%
5 C++ 83%
6 C# 79%
7 Scala 79%
8 Python 75%
9 Rust 75%
10 JavaScript 71%
11 PHP 71%
12 Java 67%
13 Go 67%

The table includes all current -l= source back ends. LLVM, WASM and the older Swift 3 target are not included because they are not comparable source-code outputs.

01 — Swift

Swift — 92%

writer only — no swiftc on the build machine

What works well

  • Optionals are T?, with ?? for the fallback.
  • A Ranger Enum is enum Color : Int, and a shape is a native enum too.
  • A behaviour-only trait used as a type becomes a protocol.
  • throw emits func … throws, try at the call site and a real Error type.
  • A weak back-edge is weak var x: T?.
  • A loop whose body ignores its index is for v in xs.

Current limitations

  • Nothing here has been compiled: there is no swiftc on the build machine, so this column is a reading of the file rather than of a build.
  • Generic classes are monomorphized, for example Stack_int.
  • Every class carries a generated == and Hashable conformance.

02 — Kotlin

Kotlin — 88%

What works well

  • Optionals become normal nullable types such as T?, with ?: for the fallback.
  • A Ranger Enum is an enum class.
  • A shape becomes a sealed interface with implementing classes.
  • A behaviour-only trait used as a type becomes an interface.
  • Function parameters use Kotlin function types such as (Int) -> Int.
  • when over an enum is exhaustive, and exceptions are normal Kotlin exceptions.

Current limitations

  • Signatures take MutableList<T> where a native API would take List<T>.
  • Generic classes are monomorphized, for example Stack_int.
  • A Ranger int is a 32-bit Int, so arithmetic past 2³¹ wraps.
  • There is no native Result model.

03 — TypeScript

TypeScript — 83%

What works well

  • A shape becomes a discriminated union, with a literal-typed __rg_kind — the TypeScript idiom.
  • A Ranger Enum is export enum.
  • Optionals are string | undefined in signatures.
  • A behaviour-only trait used as a type becomes an interface.
  • ?? is the language's own operator.
  • A loop whose body ignores its index is for (const v of xs).

Current limitations

  • Type names such as union_ParseOutcome and Stack_int are not TypeScript spellings.
  • Generic classes are monomorphized.
  • A thrown value is a string, not an Error.
  • There is no tsc on the build machine, so the types are read rather than checked.

04 — Dart

Dart — 83%

What works well

  • Optionals are T?, with ?? for the fallback.
  • A Ranger Enum is a Dart enum.
  • Function parameters use int Function(int).
  • A behaviour-only trait used as a type becomes an abstract class.
  • A loop whose body ignores its index is for (final v in xs).
  • main is at file scope, as a Dart program expects.

Current limitations

  • A shape is an abstract class plus is checks rather than a Dart 3 sealed family.
  • A Ranger record is an ordinary class, not a named-parameter constructor.
  • Generic classes are monomorphized.
  • There is no dart on the build machine, so nothing here has run.

05 — C++

C++ — 83%

fastest target in the benchmark

What works well

  • Fastest of every target measured: 136 ms against JavaScript's 508.
  • A record the sharing analysis proves nothing aliases is a value, so manhattan(const Point& p) is the signature.
  • A Ranger Enum is an enum class, and a shape is a std::variant.
  • A loop whose body ignores its index is for (const T& v : xs).
  • The preamble goes in only when the program can reach it — one study went from 237 lines to 67.

Current limitations

  • A Ranger int is a 32-bit int, and overflow there is undefined behaviour rather than a wrap.
  • Objects that are shared are shared_ptr, which a hand-written file would often own outright.
  • An optional is r_optional_primitive<T> rather than std::optional.
  • Generic classes are monomorphized.

06 — C#

C# — 79%

What works well

  • Optionals are int?, with ?? for the fallback.
  • A Ranger Enum is enum Color : int.
  • A behaviour-only trait used as a type becomes an interface.
  • Function parameters use Func<int, int>, collections are List<T>.
  • A loop whose body ignores its index is foreach (int v in xs).

Current limitations

  • Methods keep Ranger's camelCase where .NET writes PascalCase.
  • A thrown value is a ConfigurationErrorsException, which is not the type a C# developer would pick.
  • A Ranger int is a 32-bit int, so arithmetic past 2³¹ wraps.
  • Generic classes are monomorphized.

07 — Scala

Scala — 79%

What works well

  • Optionals are Option[T], built with Option.empty and read with getOrElse.
  • A Ranger Enum is a sealed class with a case object per member.
  • A behaviour-only trait used as a type becomes a trait.
  • A loop whose body ignores its index is for (v <- xs).
  • Function parameters use (Int) => Int.

Current limitations

  • Collections are collection.mutable.ArrayBuffer, spelled in full and mutable by default.
  • return from a lambda still goes through a ScalaReturnValue exception.
  • Classes are not case classes, so there is no structural equality.
  • There is no scalac on the build machine, so nothing here has run.

08 — Python

Python — 75%

What works well

  • Every signature the writer can name is annotated, with from __future__ import annotations ahead of the imports.
  • A Ranger Enum is an IntEnum.
  • A behaviour-only trait used as a type becomes a typing.Protocol.
  • Optionals use None and Optional[T].
  • Exceptions use try, except and raise; main uses if __name__ == "__main__".
  • A loop whose body never reads the index is for v in xs:.

Current limitations

  • A shape is a _rg_kind tag rather than a match over a closed family.
  • A multi-statement lambda is hoisted into a generated __rg_lambda_1 def.
  • Method names keep Ranger's camelCase where PEP 8 wants snake_case.
  • Generic classes are monomorphized, and it is the slowest target measured.

09 — Rust

Rust — 75%

What works well

  • Ownership-aware: a class nothing aliases is a plain value, and a local stored for the last time is moved rather than shared.
  • A Ranger Enum is an enum, and a shape is an enum matched with if let.
  • A behaviour-only trait used as a type becomes a trait; optionals are Option<T>.
  • Identifiers are snake_case, and def plus the field writes after it become one struct literal.
  • Second fastest overall, behind C++ and ahead of every managed target.

Current limitations

  • There is no Result or ?; try/catch is refused outright, with the shape alternative named.
  • A function parameter is &mut dyn FnMut where a native API would be generic.
  • Type names such as Stack_int and union_Payload draw rustc warnings.

10 — JavaScript

JavaScript — 71%

What works well

  • The compiler's own target, and an ordinary JavaScript file: objects share, arrays are arrays.
  • ?? is the language's own operator.
  • A loop whose body ignores its index is for (const v of xs).
  • Exceptions run; main and module exports are normal.

Current limitations

  • The only target where a Ranger Enum is still a number — JavaScript has no enum to be.
  • A Ranger map is a plain object guarded by hasOwnProperty, which makes it the slowest map measured; a Map would be the idiom.
  • A shape is a __rg_kind string.
  • Generic classes are monomorphized.

11 — PHP

PHP — 71%

What works well

  • Properties are declared and typed — public int $cents = 0; — with an initializer so the first read is safe.
  • A Ranger Enum is enum Color : int.
  • ?? is the language's own operator, and a loop is foreach ($xs as $v).
  • Exceptions are throw new Exception / catch (Exception $e).
  • The fastest map of every target measured.

Current limitations

  • Parameters and return types are still untyped.
  • A behaviour-only trait used as a type is not yet a PHP interface.
  • A shape is a get_class comparison.
  • Generic classes are monomorphized.

12 — Java

Java — 67%

What works well

  • Runs, and the output is legal Java 7 with one public type per file.
  • A Ranger Enum is a Java enum in its own file.
  • A behaviour-only trait used as a type becomes an interface.
  • A loop whose body ignores its index is the enhanced for.
  • Exceptions run, as IllegalArgumentException.

Current limitations

  • Everything is Java 7: Integer boxing, ArrayList in signatures, Object plus instanceof for a shape.
  • A lambda becomes an anonymous LambdaSignature1 class.
  • A Ranger int is a 32-bit Integer, so arithmetic past 2³¹ wraps.
  • Generic classes are monomorphized.

13 — Go

Go — 67%

What works well

  • A Ranger Enum is a named type plus a const block, the way a Go codebase writes one.
  • A behaviour-only trait used as a type becomes an interface.
  • A loop whose body ignores its index is for _, v := range xs.
  • Sharing is *T, which is what Go would use.
  • Third of the six that ran here, next to Rust. A string index is the byte a Go string is made of, so a scan is linear.

Current limitations

  • An optional is *GoNullable holding an interface{}, not a pointer and not a second return value.
  • try/throw is panic/recover rather than (T, error).
  • The ownership analysis does not run for this target at all.

Summary

What is consistent across targets

The three questions do not rank the targets the same way. C++ writes the fastest code and a 32-bit int; Swift reads best and has never been compiled here; Python is annotated end to end and is the slowest interpreter in the table. A target is worth picking on the axis you care about.

Closed on every target at once

  • A Ranger Enum is that target's own enum — enum class, IntEnum, a named type plus consts, a sealed class with case objects. Only plain JavaScript keeps the integer, having no enum to be.
  • A behaviour-only trait used as a type is that target's own interface, protocol or trait; a field-bearing one is refused rather than emitted.
  • A loop whose body ignores its index is that language's own loop over the collection. One analysis, thirteen spellings.

Largest remaining problems

  • A Ranger int is 64-bit on five targets and 32-bit on four. A program that crosses 2³¹ is a different program depending on where it is compiled, and on C++ the overflow is undefined behaviour.
  • Generic classes are monomorphized into Stack_int and Stack_string. This is the lowest score on every one of the thirteen targets.
  • There is no portable Result / (T, error) / throws type.
  • A Ranger map is a plain object on JavaScript.
  • C++ and Rust read an ownership analysis Go does not run at all.

EVG

A layout and rendering engine,
written in the language.

EVG lays out a tree with a substantial CSS feature set — flex, grid, the cascade, @media, TrueType metrics and GPOS kerning — then walks it once and emits flat draw commands: filled rect, border, image quad, text run, push and pop clip. Absolute pixels, colours resolved, no tree and no units left. Everything below that seam knows only about quads, glyph runs and scissor rectangles, which is why the same page comes out of six painters.

JSX + CSS EVG layout display list
  • WebGL 2
  • SVG / DOM
  • SDL2 + GL
  • PDF
  • PNG
  • HTML
  • framebuffer

CSS layout and cascade

Flex and grid, specificity and the cascade, border radius, shadows, transforms, overflow: hidden as a real clip, and @media blocks that fold a side rail into a bottom bar at 768px — in a PDF as much as on a GPU.

Effects on the GPU

Blur, rotation, sub-pixel shift and the surface ripple behind the top of this page. Each has an oracle — a reference implementation the GPU path is checked against on every push — so an effect cannot quietly stop matching the layout.

And a vectoriser

A bitmap tracer: edge walk, corner detection, curve fitting, colour posterising. It reads a PNG or a JPEG and writes paths — and it is what turned Ranger's own logo into the vector on this page. See the showcase →

An EVG dashboard beside the CSS that laid it out, with the live inspector open.
The inspector: the live tree, the rules that matched, and the box that resulted — none of it DOM.

Getting started

From zero to Ranger

The language itself is small: compiler/Lang.rgr declares 336 core operator definitions under 207 names, and the generated reference documents 838 operators once the standard library is counted in. The way in is not to read all of that first, and it is not to clone this repository either — this is the compiler, the gallery and their own test corpus, which is what you clone to work on Ranger. To write a program in it, the compiler is an npm package and RangerStarter is a project already built around it.

01

Start from the starter

Node is the compiler's official host and the only thing the compiler itself needs. Nothing is installed globally: ranger-compiler is a dependency of your project, so the compiler your program is compiled with is the one your package.json names. The starter is two source files, a test that fails properly, and a build loop — MIT, so keep what you want and delete the rest.

git clone https://github.com/terotests/RangerStarter my-app
cd my-app && npm install
npm start

02

Compile it where you are going

One flag decides the target, and the output is ordinary source you read in the diff. Generating Swift does not need Xcode, so the whole matrix compiles on the machine you already have; running it is the target toolchain's business. targets:run executes the three a plain machine has and fails when their output differs — which is the only thing that actually checks a portability claim.

npm run targets       # all fourteen writers
npm run targets:run   # runs three, and compares
npx rgrc -l=kotlin src/Main.rgr

03

Change one thing

Open src/Main.rgr, change the program, and run it again. The .rgr file stays the source of truth; generated target files are disposable and can always be recreated.

$EDITOR src/Main.rgr
npm start
npm run targets:run

The output is still ordinary source code, not an opaque artifact. Open it when you are curious, diff two targets when something behaves differently, and run it with the target's normal tools. Edit Ranger; inspect everything.

04

Then the gallery, when you need it

gallery/ here is the corpus: parsers for TypeScript, C++ and JavaScript, a spreadsheet, a PowerPoint stack, a game engine, a chart runtime, a node-graph editor, a Figma reader, and EVG — a CSS layout engine with no browser in it. The starter pulls any of them in as a pinned dependency, and says what that costs before it does: the gallery is AGPL-3.0-or-later while the compiler, the runtime and your own program stay MIT. Clone this repository when you want to read the whole corpus or change the compiler.

scripts/add-gallery.sh        # the list, and the cost
scripts/add-gallery.sh evg

Reading first: the documentation has install, types, optionals, traits, generics and the operator reference generated from the sources of the commit that published it, and the FAQ it links is the shortest path past the errors that otherwise cost an extra edit–compile cycle. Trying before installing anything: the playground compiles Ranger in your browser with nothing installed. Working with an agent anywhere else: /plugin marketplace add terotests/Ranger installs the same skills the starter carries.

Where it stands today

Ranger is experimental. Target quality varies by language and by feature area, several gallery projects need toolchains you may not have, and you should expect to fix a bug or add a capability now and then. It is stronger as a portable-algorithm compiler and DSL toolchain than as a drop-in replacement for a mainstream application language — and it is used that way, daily, on programs as large as itself.

History

The same algorithms, without rewriting them

Development of Ranger began around 2017, when I wanted to write the same kinds of algorithms for Swift, Java, and JavaScript without maintaining separate implementations for each platform. At the time, there was no language that quite matched what I was looking for. Haxe was interesting, but even small programs could bring along a relatively large amount of generated runtime code — five lines of Haxe could easily turn into more than twenty lines of JavaScript. For small, portable algorithm libraries, that felt heavier than necessary.

I decided to experiment with a small Lisp-based language that would transpile directly to the target languages I cared about. The original goal was fairly narrow: make it easy to move simple algorithms, such as matrix and mathematical libraries, between platforms while keeping the generated code straightforward. Lisp was a convenient foundation for building the compiler, but its syntax was not particularly pleasant for all of these use cases. Ranger therefore gradually extended it with features such as block syntax and infix operators, making ordinary control flow and mathematical expressions more natural to write.

For a long time, the main targets were Java, Swift, and JavaScript. Experimental support for C++, Rust, Scala, PHP, and Go was also present from relatively early on. Eventually the project became mostly dormant as I moved into consulting work and had less time to develop the language.

That changed toward the end of 2025. Generative AI models — and in particular Opus 4.5 — unexpectedly became good enough at understanding both Ranger and its compiler codebase to make working on the project practical again in my spare time. AI did not create the original need for Ranger, but it gave the project a new purpose: code can now be produced extremely quickly, while validating the same ideas across multiple languages and platforms is still a separate problem.

Since then, Ranger development has become active again. I have been using small projects as practical test cases for both the language and its generated code, covering things such as rendering, a JavaScript engine, parsers, Markdown reading and previewing, charts, diagram editors, Figma rendering, Raspberry Pi games, and numerous UI experiments across different platforms. Together these experiments have produced and validated millions of lines of generated code.

Today, I write nearly everything using a combination of AI and Ranger. It lets me take an idea, generate an implementation quickly, and run essentially the same tests on Web, Swift, and Kotlin targets — and, when needed, on other platforms as well.