If you're not building with your language, it probably doesn't work the way you think it works

Hey everyone,

I've been building Sharpy ( https://github.com/antonsynd/sharpy ) for over a year now (wow time flies, and so does technology). I admit, even with AI, I haven't gotten as far along as I had planned (still far from 1.0). The major thing I spend my time on is fixing bugs. Occasionally, I have to convince Fable/Opus to let me do the fun stuff (new syntax, etc.).

Anyway, with the recent uptick in AI-driven game development, recomps, decomps, you name it, I decided I'd try my hand at having Claude vibe code a game for me. After trying it out (2-shot, with three.js https://github.com/antonsynd/zero ), I decided to dogfood my own language in Unity.

Some history, I started Sharpy because I didn't want to code in C# to make a Unity game. Honestly, in the time it took me to build Sharpy, I could've learned C# and had actually made the game in that time (I think).

Anyway, I let Opus build out a slice of Pokemon Heart Gold Soul Silver here: https://github.com/antonsynd/pokemon-sharpy with the help of a Unity plugin tailored for Sharpy https://github.com/antonsynd/sharpy-unity .

I quickly learned that I had even more bugs to add to my backlog. Some are related to how Sharpy resolves symbol names (it mangles Pythonic snake_case to C# PascalCase for interop), some are related to drift in compiler options (single file vs. project), etc. As such, Claude had to find workarounds, and I made it create an ideal version of the source code as the goalpost that we'll eventually reach.

Anywho, while I think it's cool to 1-shot or X-shot a language, the real tests come from the typical actions: self-hosting being one of the big ones, and dogfooding.

Curious to see how other people find bugs in their languages. I also hear that a trendy thing these days is to formalize your implementation or language and use a theorem/proof solver to verify the implementation, like using Verus, LEAP, or TLA+.

reddit.com
u/antonation — 8 days ago
▲ 2 r/AIprogrammingLanguage+1 crossposts

Tyre - a programming language infrastructure

Many years ago I came up with a vague idea of how I want a programming language:

  1. Multiple layers
  2. T - C-level language, only C level features, no generics, no name mangling
  3. Ty - Rust/C++-level language, mostly Rust-like features
  4. Tyr - High level, inspired by natural language (still not exactly sure what I want from this)
  5. Multiple generic syntaxes: The user can create
  6. S-expressions as intermediate representation for macros

Have a look!

This repo contains a full AI generated documentation. And the script to generate the documentation ensures that all the examples compile.

One of the first things after I got into using coding agents was implementing this language. I got the first two layers working within maybe two days. (my first two weeks of using coding agents were so crazy, this language was only a side project, and doing it myself, it would have taken me weeks to months to get at this state, if not longer; feel free to check the git history)

How the languages were created?

At first, I let it implement my basic features, a documentation that contains many important examples, and I looked at all examples to see if I actually like them.

After a while, I created example projects:

I didn't look at the generated code a lot. I think I looked at most of the tests a few times to see if something can be improved. And I also looked at the programs.

I had these agents:

  • 1 cooordinator
  • 1 agent per language (3 in total)
  • 1 agent per project

I had some multi agent task setup for the main repo. If one of the language specific agents needed some feature that affects both languages, it wrote a task for the coordinator.

If the agents for the projetcs needed some feature, they also added it to the list, and then the agents for the specific language decided which features to implement.

Sometimes they decided to implement it in the most generic way, that's what AI is good at after all. And most of the time, that's what I wanted anyway. But in some cases, I wanted my language to be unique.

So I don't know every little detail about the language. Most of the features were just what other AI agents needed. And this also was my first project where I realized that this is actually a good approach.

Nowadays, when I create a library, I only know what the library is about, and then I have a bunch of programs which use that library, that create feedback.

The fact that agents were able to create such complex software using my languages means that it's already at a good state.

I also asked agents how they liked workin with the language. One thing I realized was that error messages.

Also the compiler turned out to be very strict. Every lints is a hard error. "x = x + 1" is forbidden. You have to use "x += 1". I turned on strict lints in Rust, even before I used coding agents. And with coding agents, I quickly added more and stricter lints. So I thought that the language could just have inbuilt lints for everything, so the code is always elegant. I even enforced a maximum line count per file.

State

T is basically finished. Ty still needs some advanced features, especially the borrow checker is still missing. Tyr is just a weird prototype, not really created by AI.

2 syntaxes are supported, a C like syntax, and a Lisp like Syntax. I also considered supporting visual representation and markdown inspired syntax.

I'm not really actively working on this language anymore. Once in a while I just start an AI agent to work on the remaining features.

One of the last features I've been working on was a Macro Compiler to Rust, so that you could import Ty in Rust and get Rust code at compile time. I have no idea if this feature already works.

I also don't know what I will use this language for. Maybe I'll just migrate all of my software to this new langugae one day.

Feedback

Human feedback might be another way to know if something about the languge has to be changed.

It's a type focused language, and this can still be the most annoying part if coding by hand. You have to create a bunch of types yourself before you can do anything meaningful.

Feel free to provide some feedback.

And maybe you just want to use these languges for your projects because they already contain features that are better than other low level languages.

u/antonation — 2 months ago
▲ 119 r/QuincyMa

Apparently the Iraqi team is staying at the Marriott at Crown colony

This is from the day or so before their game with Norway. I was wondering why in recent weeks they placed these surveillance camera systems along the road. This is probably why.

u/antonation — 4 months ago
▲ 1 r/VibeCodedLanguages+1 crossposts

I wanted to make a Unity game but didn't want to write C#, so I built a whole language instead

Here it is: https://github.com/antonsynd/sharpy

You can also try the transpiler to C# in the browser without installing anything: Try Sharpy Online (Blazor WASM playground).

Why Sharpy?

I'm one of those people who wants to work on something, and then ends up building something to work on that something, which is completely unproductive. In this case, I wanted to make a game in Unity, but I didn't want to use C# 'cause reasons. I figured, why not create a Pythonic language that transpiles to C#? The object model and memory models are similar enough. (I had an earlier attempt to do the same but targeting C++ for a separate reason, but I quickly ran into hard decisions about wrapping everything in shared_ptr's and things looking kind of awful).

I'm aware that Boo was a thing, but one of the things that irked me about it was the retention of PascalCase access to .NET APIs. For a Pythonic language, access to .NET should look Pythonic.

Where it stands today

There is a compiler (using Roslyn), a standard library of the Python modules you expect most: json, os, re, numpy (sort of), sqlite3 (sort of), requests (sort of), itertools, etc., a language server with a VS Code extension, and support for multi-file projects via *.spyproj files (akin to *.csproj files).

Differences from Python and major defining features

Static typing

Sharpy is statically-typed, so no dynamic features are available (yet, or maybe ever). However, there are some things that make static typing more ergonomic:

x = 5            # obviously an `int`, specifically `int32`
                 # (`int` is an alias)

x: str = str(x)  # reassignment with a type annotation is shadowing

x: auto = [x]    # `auto` type infers and simplifies the type
                 # annotation for cumbersome types. here it is
                 # inferred to be `list[str]`. this also shadows.

True optional types

Sharpy supports nullable types AND true optionals. Nullable types are annotated as T | None, and optional types are T?. The reason both exist: .NET APIs return nullables, so Sharpy needs to interop with those, but for Sharpy-native code, true optionals (tagged unions under the hood) are safer and pattern-matchable. T? gets the short syntax because I wanted to promote optionals as the default choice. Null-aware operators work on both, e.g. ?. (safe navigation/optional chaining) and ?? (null-coalescing).

To make working with .NET and nullable APIs easier, you can use maybe in front of a nullable expression to auto-wrap the expression value in a true optional:

x: int | None = some_function_returning_nullable_int()
y: int? = maybe some_function_returning_nullable_int()

match y:
    case Some(v):
        print(v)
    case None():
        print("no value")

Result types

Sharpy also has a result type, with a similar keyword try for auto-wrapping throwing expressions in the result type, with optional type annotation to change the exception type in the result.

x: str !Exception = try some_throwing_function()

y: int !ValueError = try[ValueError] int(x.unwrap_or(""))

Tagged unions (algebraic data types)

Since Sharpy has both true optionals and result types, Sharpy predictably has ADTs/tagged unions (a la Rust):

union HttpResponse:
    case Success(body: str, status_code: int)
    case Redirect(location: str, permanent: bool)
    case ClientError(message: str, code: int)
    case ServerError(message: str, code: int)

def categorize(response: HttpResponse) -> str:
    match response:
        case Success(body, status_code):
            return f"ok: {body}"
        case Redirect(loc, permanent):
            return "moved" if permanent else "found"
        case ClientError(msg, code):
            return f"error {code}: {msg}"
        case ServerError(msg, code):
            return f"server error {code}: {msg}"

Casing and name mangling

As mentioned before, I believe access to .NET APIs and other PascalCase libraries should be Pythonic looking. So, there is name mangling of symbols to PascalCase for lookup:

from system import Console              # Note lowercase namespace

def main():
    Console.write_line("Hello World!")  # Note the snake_case

    print("Hello World!")               # You can also just do this,
                                        # don't worry

The compiler preserves original CLR names through the discovery pipeline, so interop just works: you write snake_case, the generated C# uses the correct PascalCase name, and the .NET runtime sees what it expects.

For cases (har-har) where you don't want name mangling to occur, you can use backticks to block this and let the compiler treat the symbol name as you wrote it:

from `System.Collections.Generic` import HashSet

def main():
    h = HashSet()

Underscores and dunder methods

Leading underscores on type members and methods lead to default access levels being applied. _foobar defaults to C# protected, and __foobar defaults to C# private. You can override this by using the associated access modifier "decorator"/attribute:

class Foobar:
    _something_protected: int

    __something_private: str

    @public
    __something_public_because_of_decorator: bool

    @private
    _something_private_because_of_decorator: float

    # Exception: dunders are public by default
    def __init__(self):
        pass

Dunder methods exist in Sharpy, but they are not real methods. Instead, they are treated by the compiler as an instruction to generate another method(s) to fit in the .NET ecosystem. Some are obvious like, __init__ is a constructor, whose body becomes the constructor's body, and __str__ becomes public string ToString().

Others are not as obvious like operators which create static operators (because that's how they work in .NET's runtime), __enter__ and __exit__ for context managers map to a compiler-generated C# IDisposable implementation, etc. Some are flat out not supported like __pow__ because ** is not an operator nor is it an overloadable method in .NET.

This was tough to reconcile, but I think I landed on something that is acceptable without being too jarring for Python developers.

Testing framework

Sharpy has a built-in @test decorator that maps to xUnit under the hood. You write tests that look like pytest, and the compiler emits [Fact], [Theory], [InlineData], and xUnit lifecycle plumbing:

@test
def addition_works():
    assert 1 + 1 == 2

@test.parametrize([(1, 2, 3), (4, 5, 9)])
def addition_parametrize(a: int, b: int, expected: int):
    assert a + b == expected

@test.skip("not implemented yet")
def todo_test():
    pass

There's also @test.fixture for shared setup/teardown and @test.collection for grouping.

Standard library highlights

The standard library has 31 modules. Some highlights:

  • numpy: NdArray[T] with broadcasting, slicing, linalg, FFT, and random (backed by Math.NET Numerics)
  • sqlite3: Connection, Cursor, Row with parameterized queries
  • requests: Python-compatible HTTP API
  • json: typed deserialization with json.loads[T](), custom encoders/decoders
  • re, os, itertools, functools, collections: the usual suspects

15 of these modules are themselves written in Sharpy (.spy files compiled to .dll).

Other notable features

There's a bunch of stuff in here, it's a bit much to write about exhaustively. Some quick hits:

  • Late-bound defaults (PEP 671): def f(x, y => len(x))
  • @lru_cache / @cache: compiler-innate functools memoization
  • Source generators: user-defined compile-time code generation
  • Inline out declarations: out name: type for .NET interop
  • Incremental compilation: content-hash caching with transitive dependency tracking

I hope you'll play with it and look at the file-based integration test fixtures for working examples. There's also a language server + VS Code extension (not submitted to the marketplace yet, but you can load it locally) with syntax highlighting, doc/type on-hover tooltips, inline argument names, rename support, go-to-definition, and semantic token highlighting. There are probably bugs in the highlighting and stuff, but it works enough to make me happy.

Thoughts on AI-driven development

Honestly, if it wasn't for Claude, this project would've taken me at least 3 years, optimistically. I didn't use AI in the beginning; I hand-wrote a chunk of the standard library, tried to write the parser with ANTLR, and so on, but I got impatient and ran into tougher parts of the compiler that discouraged me. So almost a year later, I leaned into AI-accelerated development with some guardrails: a hand-written language specification, three axioms that govern almost every design decision (modulo my personal preference), and a growing suite of file-based integration test fixtures and property tests.

I think of my role as the "orchestrator", deciding what the language should be, writing the spec, reviewing the output, and steering corrections. Yeah, I didn't learn the nitty gritty of writing a compiler or language server, but it left me room to think about higher-level language design: do these features make sense, do they compose well, do they feel right? I know not everyone in the compiler/programming language space is happy with this direction, but I feel a lot more empowered to implement ideas this way.

The implement-eval/test loop is much faster, and you can validate ideas quickly. At the same time, without oversight, AI can take a language feature in a different direction or implement something narrowly. It's very difficult with the velocity and volume of code generation to keep up as a solo human reviewer. The spec and axioms help, but I'll be honest, there's a degree of blind faith that AI-generated tests aren't hiding poor decisions in the actual implementation. I guess I'll find out when I actually use it for my Unity game.

Next steps

  • Unity plugin: hooking the Sharpy compiler into the Unity build flow, which was the whole point of this project
  • Rounding out the stdlib: filling gaps in existing modules, potentially adding more
  • Actually making the game lol

Hope this has been educational, happy to answer any questions!

u/antonation — 5 months ago