You are browsing as a guest. Sign up (or log in) to start making projects!

44m 11s logged

I wrote a programming language. It runs.

RaidenScript is an embeddable scripting language — the kind that goes inside
another program and makes it programmable. It borrows readability from Python,
the runtime model from JavaScript, optional types from C++, and declarative UI
from HTML.

Phase 1 is done: lexer, AST, parser, resolver, tree-walking interpreter, REPL.
~5,150 lines of C++20, zero warnings under -Wall -Wextra -Wpedantic -Wshadow
-Wconversion.

WHAT I DID FIRST WAS NOT WRITE CODE

Phase 0 was pure design: a spec, a grammar, and 15 example programs — and no
compiler at all. Writing those examples found 16 defects in the spec before a
single line of the implementation existed. Two of them would have forced a
rewrite later:

  1. Conditional expressions (a if cond else b) were missing from the grammar
    entirely — I typed one by reflex in example 4 and it couldn’t parse.
  2. => followed by { was genuinely ambiguous: block, or map literal? I took
    JavaScript’s answer — after =>, { is always a block, and returning a map
    needs ({…}). Finding this while writing the parser would have cost days.

Then the parser found a third: my rule that brace-blocks suppress indentation was
just wrong. It makes this impossible to write:

register("hit", (e) => {
    if e.critical:
        damage = damage * 2
})

Parentheses hold one expression; brace blocks hold statements, and statements
need block structure.

DECISIONS I’M HAPPY WITH

  • Only nil and false are falsy. 0 and “” are truthy, which kills the classic
    if-count bug at the root.

  • / always returns a float; // is integer division. C’s silent truncation is a
    bug factory.

  • No package registry, ever. Modules resolve straight from a git repo, Go-style.

  • I built the diagnostics engine before the lexer, because error messages bolted
    on afterwards always stay bolted on. Columns count UTF-8 characters, not bytes
    — so the caret lands correctly when a variable is named sayaç. Real output,
    Turkish for now (English messages are on the list):

    hata: beklenmeyen ‘!’
    –> test.rai:3:7
    |
    3 | c = 5 ! 3
    | ^
    |
    = ipucu: olumsuzlama için ‘not’ kullan

THE PART THAT MADE ME GRIN

Example 15 is a calculator — a lexer, a Pratt parser and an evaluator, written in
RaidenScript. I wrote it in Phase 0 as a rehearsal for the C++ I hadn’t written
yet. Now it runs on the interpreter it was a rehearsal for: 2 ^ 3 ^ 2 gives 512,
right-associative, not 64.

The REPL I wrote by hand, and it immediately earned its keep by finding a bug
nothing else could: Source::fromFile stripped the UTF-8 BOM, but the constructor
didn’t. The REPL was the first code path to build a Source from an in-memory
string instead of a file, so piped input carried a BOM into the lexer, which
glued it onto the first identifier. A new entry point finds bugs no amount of
staring finds.

ON THE AI SPLIT

The design is mine — spec, grammar, naming, and every decision above. Phases 0-6
of the implementation were written with AI assistance (Claude); the REPL, step 7,
I wrote myself, and that is the mode from here on: I write the code, AI reviews
it.

WHAT’S NEXT

Not phase 2. I’m skipping types and the bytecode VM and going straight to
embedding — because a tree-walking interpreter is plenty fast for mod scripts,
which run on events, not in the render loop. The goal: compile to WASM, bind a
game.* API, and define one weapon in my published game STAR BREAKER using a
language I wrote.

A language nobody uses is a toy. A language shipping in a real product is not.

Repo (MIT): github.com/RaidenTechnology/raidenscript

0
5

Comments 0

No comments yet. Be the first!