Phosphor Progress — From Parser to Federation

July 31, 2026

Twenty-nine commits. Six phases. One language. Here’s where Phosphor stands after the most intense development sprint yet.

The Journey So Far

When I wrote about building the Phosphor parser, the language was a lexer and a dream. Phase 1 was five tasks — tokenizer, parser, declarative processor, hot-reload, BBS wiring. It felt like a lot at the time.

It wasn’t.

Phases 1-2: Foundation

The declarative layer came first. Define your BBS structure — menus, boards, channels, file areas, themes — in a readable syntax. Save the file. The BBS syncs to the database automatically. No restart, no recompile.

Then the imperative layer: an expression evaluator with variables, control flow, and functions. Event handlers that respond to login, chat, board posts. Scheduled events that fire on daily, hourly, or cron schedules. And the reactive binding system — bind declarations that auto-update screens when data changes.

on login {
    if user.first_login {
        show "welcome.ans"
        mail from "SysOp" to user {
            subject = "Welcome to Phosphor!"
            body = "Welcome aboard, ${user.display_name}!"
        }
    }
}

Phase 3: Screens and Widgets

Phosphor needed its own screen system. Not just Java screens — Phosphor-defined screens with lifecycle, state, navigation, and built-in widgets.

The imperative layer gave us PhosphorScreen with lifecycle hooks (on enter, on exit, on input, on tick). The declarative layer gave us layout blocks — row, column, text, input, table, box — composable widgets with IDs for targeted updates.

screen "ChatDashboard" {
    state messages: List = []
    
    layout {
        column {
            text "Live Chat" style: "bold"
            table messages {
                columns: [user, message, time]
            }
            input "Type a message..." on_submit {
                chat_send(session, input)
            }
        }
    }
    
    on update {
        messages = chat_recent(50)
    }
}

Phase 4: Door Games

A BBS without door games is just a chat server with extra steps. Phosphor 4.1 built the door game engine — a framework for Phosphor-defined games that run inside the BBS terminal. 4.2 shipped a complete dungeon crawler game with scoreboard, proving the engine could handle real game logic.

Phase 5: The Editor

If you’re going to write Phosphor code, you need a Phosphor editor. Phase 5 built one — inside the BBS itself. Syntax highlighting, auto-complete, snippets, live parse error reporting, and save-to-apply hot-reload. You edit a .phos file through the terminal, save, and watch the BBS reconfigure itself in real-time.

Phase 6: Java Integration

Phosphor needed to talk to Java. Phase 6 added three annotations:

  • @PhosphorFunction — expose Java methods as Phosphor builtins
  • @PhosphorBinding — bind Java state to Phosphor reactive variables
  • @PhosphorScreenType / @PhosphorWidget — register custom screen types and widgets

This is what makes Phosphor practical. The BBS’s existing Java services — auth, mail, chat, boards — are all available as Phosphor functions. You don’t rewrite the BBS in Phosphor. You extend it.

Phase 7: The Web Layer

This is where it got serious.

7a: Testing. Phosphor can now test itself. test blocks with mocks, assertions, and integration tests. JUnit integration so Phosphor tests run in mvn test alongside Java tests. JaCoCo coverage reports. The language tests its own compiler.

7b: REST API + WebSocket. The web layer. Phosphor api blocks become Javalin REST routes. websocket blocks become live WebSocket endpoints with bidirectional events — emit, broadcast, emit_to, fire. One event bus, three surfaces (terminal, WebSocket, REST). A change from any surface propagates to all of them.

websocket "/ws/dashboard" {
    require auth
    bind online = session_count()
    bind channels = chat_channels()
    
    on connect {
        emit("dashboard", {online: online, channels: channels})
    }
    
    on message {
        match msg.type {
            "join_channel" => {
                chat_join(session, msg.channel)
                emit("joined", {channel: msg.channel})
            }
        }
    }
}

7c: Federation. The big one. Multi-node sync for BBS federation.

syncable declarations define what gets replicated. Two modes: event (append-only, immutable) and entity (updatable by natural key). Conflict resolution strategies — last_write_wins, first_write_wins, highest_value, merge, even custom functions. The sync runtime handles envelopes, deduplication, backfill on reconnect, tombstones for deletes, and retention purging. All transported over the existing jnet mesh.

syncable "trade_signal" {
    mode = "event"
    fields = {
        symbol: String,
        action: String,
        shares: Int,
        price: Float,
        user: String,
        timestamp: Time
    }
    conflict_resolution = "last_write_wins"
    retention = 7d
    write = ["vip"]
}

Boards and channels marked federated = true sync across nodes automatically. A user on the Bronxville node sees messages from the Ohio node in real-time. The sysop controls which resources stay local and which get shared.

The Numbers

  • 29 commits across 7 phases
  • ~7,000 tests passing (Java + Phosphor, same JUnit suite)
  • 15 files in the WebSocket task alone — parser, AST, evaluator, runtime, registry, server wiring
  • The language spec document is 6,465 lines

What’s Next

The pipeline continues with preferences (7d), user scripting sandbox (7e), and the final feature wave (7f): ANSI art pipeline, module marketplace, achievements, AI integration, calendar, themes, and macros.

The BBS is back. And it’s yours.