The blakelang programming language
December 31, 2025
Why
C# is probably my favourite programming language. It strikes just the right balance of “batteries-included” (via a great standard library, performant GC) versus “just let me do it” (unsafe, stackalloc, P/Invoke).
But can it be better?
After over a decade of writing C# there are still some things I was wishing it had.
When we write a function like this, the compiler should already know that the result is a IQueryable<UserData> because that’s what’s being returned, yet we still must write:
IQueryable<UserData> GetUserData(Guid id)
{
return _ctx.Query<UserData>().Where(x => x.Id == id);
}
Or if you wanted a nice public API that lists a dynamic set of commands but provides good editor auto-completion, you may want something like this:
public MyApp.CreateUserCommand CreateUserCommand { get; private set; }
public MyApp.DeleteUserCommand DeleteUserCommand { get; private set; }
public MyApp.UpdateUserCommand UpdateUserCommand { get; private set; }
However in C# the best way to write that is manually, or accept a compromise such as a dictionary where you can look them up, maybe via a well known list of command strings.
public IDictionary<string, MyApp.ICommand> Commands { get; private set; }
However, that won’t provide a good experience because all the return types are a shared interface like ICommand and as a consumer of that class, if you access it with Commands["CreateUser"] you can’t automatically see the docs inline in the editor for what the CreateUserCommand does, for example.
What
I was interested in finding solutions to challenges like this and went down the rabbit hole of learning about Roslyn Source Generators. In short, it’s a system where you can register a project as an “analyzer” and dotnet will call it at compile time, giving you the ability to do things with the AST representation of your code.
This allows metaprogramming which just means that the code is data to your source generator and you can write additional code to modify the input code.
Most uses I’ve found of source generators are generating boilerplate. An example is protobuf, which takes proto contract files and generates C# files that match the fields contained within. So the logic is mostly defined as the rules of protobuf being applied to that file.
However I wanted to try to abstract things one level further. Source code files contain code that runs at runtime. But what if we indicate different sections in the code to have a section that runs at runtime and a section that runs at compile-time. The compile-time code could then be used as metaprogramming to generate more source code, but instead of predefined rules or transforms.
That’s essentially the primary feaure of what I’m calling blakelang - a compile-time metaprogrammming language which transpiles to C#.
In blakelang, all valid .cs files are valid .blake files, so we start with the full power of C#.
On top we add:
@{|and|}to delimit compile-time evaluated sections of the code. When compiled, these blocks will be removed from the resulting.csfile. They simply run C# statements at compile-time and go away.`to delimit output code from within a@{|block. This is how you can start to build more useful things with the compile-time statements, because now any time they have such a statement, including within a loop, it will add code to the resulting.csfile@(and)to delimit variable expansion, so you can output expressions from the compile-time code to the transpiled source- some other nice-to-haves such as
varto infer return types on a method
Examples
Compile-time AST traversal to find types implementing an interface
// Command.blake
@{|
// reflects upon compilation AST to find things with interface `MyApp.ICommand`
var commands = Blake.FindTypesImplementing("MyApp.ICommand");
foreach (var cmd in commands)
{
`public @(cmd.ToDisplayString()) @(cmd.Name) { get; set; }`
}
|}
Produces the following generated output:
// Command.g.cs
// <auto-generated>
public MyApp.CreateUserCommand CreateUserCommand { get; set; }
public MyApp.DeleteUserCommand DeleteUserCommand { get; set; }
public MyApp.UpdateUserCommand UpdateUserCommand { get; set; }
Compile-time generation of C# properties
// User.blake
namespace MyApp.Models;
public class User
{
public int Id { get; set; }
@{|
// This C# code runs at compile time
var fields = new[]
{
("FirstName", "string"),
("LastName", "string"),
("Email", "string"),
("Age", "int?"),
};
foreach (var (name, type) in fields)
{
// Backtick strings are output - they emit to the generated file
// @(expr) splices in the compile-time value
` public @(type) @(name) { get; set; }`
}
|}
}
Produces the following generated output:
// User.g.cs
// <auto-generated>
namespace MyApp.Models;
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int? Age { get; set; }
}
Inferred method return values
// Person.blake
public class Person
{
public var GetName() { return "Bob"; }
}
Produces the following generated output:
// Person.g.cs
// <auto-generated>
public class Person
{
public string GetName() { return "Bob"; }
}
How
There’s a summary here in the README
The source for the language, grammar, source generator and editor plugins is here on github !