Giancarlos/Culebral

A new .NET programming language, inspired purely by Python (not backwards compatible), focused purely on the .NET ecosystem.

C#

4

59 commits

updated Sep 27, 2026

See the code

README


C U L E B R A L

The skeleton of Python, reforged for .NET.

Quickstart · Language · Architecture · Building

.NET 10 C# 14 Tests Phase 4


Culebral is a statically typed, compiled programming language that reads like Python and runs on .NET. Function signatures are contracts. Bodies are inferred. The entire BCL and NuGet ecosystem is one import away.

def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

def main():
    for i in range(10):
        print(f"fib({i}) = {fibonacci(i)}")
$ culebral run fibonacci.leb
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34

Design Philosophy

#Principle
1Python's readability, Rust's discipline, .NET's power.
2Types at boundaries, inference inside. Signatures are the API. The compiler handles the rest.
3If you write a type, you mean it. No optional hints — every annotation is enforced.
4Interop is not an afterthought. NuGet packages should feel native.
5Earn your complexity. No metaclasses, no MRO diamonds. If it doesn't pull its weight, it's out.

Quickstart

Requirements: .NET 10 SDK

# Clone and build the compiler
git clone git@github.com:Giancarlos/Culebral.git
cd Culebral
dotnet build

# Write your first program
cat > hello.leb << 'EOF'
def main():
    print("Hello from Culebral!")
EOF

# Compile and run
dotnet run --project src/Culebral.Compiler -- run hello.leb

The Language

Functions — The Core Rule

Parameters and return types are always explicit. Local variables are inferred.

def greet(name: str, times: int) -> str:
    parts = [f"Hello, {name}!" for _ in range(times)]  # inferred: list[str]
    result = "\n".join(parts)                           # inferred: str
    return result

No self — Ever

Instance fields are bare names. Use @ when shadowed. That's it.

class Counter:
    count: int = 0

    def __init__(initial: int = 0):
        count = initial

    def increment() -> int:
        count += 1
        return count

    def display(count: int):
        print(f"Parameter: {count}, Field: {@count}")

Null Safety

No implicit nulls. Declare intent with ?.

def find_user(id: int) -> User?:
    if id in database:
        return database[id]
    return None

def greet_user(id: int) -> str:
    user = find_user(id)
    if user is None:
        return "Unknown"
    return f"Hello, {user.name}"  # narrowed to User here

Algebraic Enums

enum Shape:
    Circle(radius: float)
    Rectangle(width: float, height: float)
    Point

match shape:
    case Shape.Circle(r):
        print(f"Circle with radius {r}")
    case Shape.Rectangle(w, h):
        print(f"Rectangle {w}x{h}")
    case Shape.Point:
        print("Just a point")

Structs, Records, Interfaces

struct Point:
    x: float
    y: float

    def distance_to(other: Point) -> float:
        return ((x - other.x) ** 2 + (y - other.y) ** 2) ** 0.5

record User:
    name: str
    email: str
    age: int

interface Drawable:
    def draw(canvas: Canvas) -> None
    def bounds() -> Rect

.NET Interop

Any .NET type. Any NuGet package. Case convention bridging is automatic.

from System.Net.Http import HttpClient

async def fetch(url: str) -> str:
    client = HttpClient()
    response = await client.get_async(url)    # maps to GetAsync
    return await response.content.read_as_string_async()
# culebral.toml
[project]
name = "my-app"
target = "net10.0"

[dependencies]
"Newtonsoft.Json" = "13.0.3"
"Dapper" = "2.1.*"

What We Keep, Drop, and Change

Kept from Python

Indentation blocks · f-strings · comprehensions · slicing · for x in collection · with statement · multiple assignment · *args · decorators · __init__ / __str__ / __repr__ · range()

Dropped from Python

self parameter · dynamic typing · **kwargs · metaclasses · multiple inheritance · GIL · duck typing · __slots__ · monkey patching · eval() / exec()

Changed from Python
PythonCulebralWhy
NoneNone (but T? required)Must opt in to nullability
classclass / struct / enumSeparate value and sum types
typing.ProtocolinterfaceFirst-class, not bolted on
@propertyprop keywordCleaner syntax
dataclassesstruct or recordBuilt into the language
matchmatch (enhanced)Exhaustiveness checking
lambdalambda (multi-line)Proper lambdas

Architecture

Source (.leb)
    │
    ▼
┌──────────┐
│  Lexer   │  Indentation → INDENT/DEDENT tokens
└────┬─────┘
     ▼
┌──────────┐
│  Parser  │  Recursive descent → AST (~70 node types)
└────┬─────┘
     ▼
┌──────────┐
│  Type    │  Two-pass: declarations then bodies
│  Checker │  Infer locals, check signatures, null safety
└────┬─────┘
     ▼
┌──────────┐
│  IR      │  Basic blocks, stack instructions, typed locals
│ Lowering │  Desugar comprehensions, for-loops, patterns
└────┬─────┘
     ▼
┌──────────┐
│  CIL     │  PersistedAssemblyBuilder → .NET assembly
│ Emitter  │  ManagedPEBuilder → PE with entry point
└────┬─────┘
     ▼
  .dll / .exe  →  dotnet run

Project Structure

culebral/
├── src/Culebral.Compiler/
│   ├── Lexer/           # Indentation-aware tokenizer
│   ├── Parser/          # Recursive descent parser + AST
│   ├── Semantics/       # Type system, symbol table, checker
│   ├── IR/              # CulebralIR + AST→IR lowering
│   ├── Emit/            # CIL emitter via Reflection.Emit
│   ├── Diagnostics/     # Error/warning reporting
│   └── Program.cs       # CLI entry point
├── tests/Culebral.Compiler.Tests/
│   ├── LexerTests.cs    # 14 tests
│   ├── ParserTests.cs   # 30 tests
│   ├── TypeCheckerTests.cs  # 10 tests
│   └── EmitTests.cs     # 20 end-to-end tests
├── samples/             # Example .leb programs
└── spec.md              # Full language specification

Zero external dependencies for core compilation. The compiler is a single .NET 10 console application.

Building

# Build the compiler
dotnet build

# Run tests
dotnet test

# Compile a Culebral program
dotnet run --project src/Culebral.Compiler -- build samples/hello.leb

# Compile and run in one step
dotnet run --project src/Culebral.Compiler -- run samples/hello.leb

# Type-check without compiling
dotnet run --project src/Culebral.Compiler -- check samples/fibonacci.leb

Debug Commands

# Print lexer tokens
dotnet run --project src/Culebral.Compiler -- lex samples/hello.leb

# Print parse tree
dotnet run --project src/Culebral.Compiler -- parse samples/hello.leb

# Print CulebralIR
dotnet run --project src/Culebral.Compiler -- ir samples/fibonacci.leb

Roadmap

PhaseFocusStatus
1Minimum viable language — functions, control flow, basic types, CIL emissionComplete
2Type system + classes — generics, constructors, interfaces, @fieldComplete
3.NET interop — BCL imports, case bridging, NuGet resolutionComplete
4Language completeness — async/await, pattern matching, records, null safety, toolingComplete
5Standard library — Result type (built-in), culebral.testingMinimal — .NET interop is the stdlib
6Native modules — LLVM backend for @native hot pathsFuture

License

MIT


Built with .NET 10 · Zero dependencies · Compiles to real CIL

Giancarlos/Culebral

A new .NET programming language, inspired purely by Python (not backwards compatible), focused purely on the .NET ecosystem.

C#

4

59 commits

updated Sep 27, 2026

See the code

README


C U L E B R A L

The skeleton of Python, reforged for .NET.

Quickstart · Language · Architecture · Building

.NET 10 C# 14 Tests Phase 4


Culebral is a statically typed, compiled programming language that reads like Python and runs on .NET. Function signatures are contracts. Bodies are inferred. The entire BCL and NuGet ecosystem is one import away.

def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

def main():
    for i in range(10):
        print(f"fib({i}) = {fibonacci(i)}")
$ culebral run fibonacci.leb
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34

Design Philosophy

#Principle
1Python's readability, Rust's discipline, .NET's power.
2Types at boundaries, inference inside. Signatures are the API. The compiler handles the rest.
3If you write a type, you mean it. No optional hints — every annotation is enforced.
4Interop is not an afterthought. NuGet packages should feel native.
5Earn your complexity. No metaclasses, no MRO diamonds. If it doesn't pull its weight, it's out.

Quickstart

Requirements: .NET 10 SDK

# Clone and build the compiler
git clone git@github.com:Giancarlos/Culebral.git
cd Culebral
dotnet build

# Write your first program
cat > hello.leb << 'EOF'
def main():
    print("Hello from Culebral!")
EOF

# Compile and run
dotnet run --project src/Culebral.Compiler -- run hello.leb

The Language

Functions — The Core Rule

Parameters and return types are always explicit. Local variables are inferred.

def greet(name: str, times: int) -> str:
    parts = [f"Hello, {name}!" for _ in range(times)]  # inferred: list[str]
    result = "\n".join(parts)                           # inferred: str
    return result

No self — Ever

Instance fields are bare names. Use @ when shadowed. That's it.

class Counter:
    count: int = 0

    def __init__(initial: int = 0):
        count = initial

    def increment() -> int:
        count += 1
        return count

    def display(count: int):
        print(f"Parameter: {count}, Field: {@count}")

Null Safety

No implicit nulls. Declare intent with ?.

def find_user(id: int) -> User?:
    if id in database:
        return database[id]
    return None

def greet_user(id: int) -> str:
    user = find_user(id)
    if user is None:
        return "Unknown"
    return f"Hello, {user.name}"  # narrowed to User here

Algebraic Enums

enum Shape:
    Circle(radius: float)
    Rectangle(width: float, height: float)
    Point

match shape:
    case Shape.Circle(r):
        print(f"Circle with radius {r}")
    case Shape.Rectangle(w, h):
        print(f"Rectangle {w}x{h}")
    case Shape.Point:
        print("Just a point")

Structs, Records, Interfaces

struct Point:
    x: float
    y: float

    def distance_to(other: Point) -> float:
        return ((x - other.x) ** 2 + (y - other.y) ** 2) ** 0.5

record User:
    name: str
    email: str
    age: int

interface Drawable:
    def draw(canvas: Canvas) -> None
    def bounds() -> Rect

.NET Interop

Any .NET type. Any NuGet package. Case convention bridging is automatic.

from System.Net.Http import HttpClient

async def fetch(url: str) -> str:
    client = HttpClient()
    response = await client.get_async(url)    # maps to GetAsync
    return await response.content.read_as_string_async()
# culebral.toml
[project]
name = "my-app"
target = "net10.0"

[dependencies]
"Newtonsoft.Json" = "13.0.3"
"Dapper" = "2.1.*"

What We Keep, Drop, and Change

Kept from Python

Indentation blocks · f-strings · comprehensions · slicing · for x in collection · with statement · multiple assignment · *args · decorators · __init__ / __str__ / __repr__ · range()

Dropped from Python

self parameter · dynamic typing · **kwargs · metaclasses · multiple inheritance · GIL · duck typing · __slots__ · monkey patching · eval() / exec()

Changed from Python
PythonCulebralWhy
NoneNone (but T? required)Must opt in to nullability
classclass / struct / enumSeparate value and sum types
typing.ProtocolinterfaceFirst-class, not bolted on
@propertyprop keywordCleaner syntax
dataclassesstruct or recordBuilt into the language
matchmatch (enhanced)Exhaustiveness checking
lambdalambda (multi-line)Proper lambdas

Architecture

Source (.leb)
    │
    ▼
┌──────────┐
│  Lexer   │  Indentation → INDENT/DEDENT tokens
└────┬─────┘
     ▼
┌──────────┐
│  Parser  │  Recursive descent → AST (~70 node types)
└────┬─────┘
     ▼
┌──────────┐
│  Type    │  Two-pass: declarations then bodies
│  Checker │  Infer locals, check signatures, null safety
└────┬─────┘
     ▼
┌──────────┐
│  IR      │  Basic blocks, stack instructions, typed locals
│ Lowering │  Desugar comprehensions, for-loops, patterns
└────┬─────┘
     ▼
┌──────────┐
│  CIL     │  PersistedAssemblyBuilder → .NET assembly
│ Emitter  │  ManagedPEBuilder → PE with entry point
└────┬─────┘
     ▼
  .dll / .exe  →  dotnet run

Project Structure

culebral/
├── src/Culebral.Compiler/
│   ├── Lexer/           # Indentation-aware tokenizer
│   ├── Parser/          # Recursive descent parser + AST
│   ├── Semantics/       # Type system, symbol table, checker
│   ├── IR/              # CulebralIR + AST→IR lowering
│   ├── Emit/            # CIL emitter via Reflection.Emit
│   ├── Diagnostics/     # Error/warning reporting
│   └── Program.cs       # CLI entry point
├── tests/Culebral.Compiler.Tests/
│   ├── LexerTests.cs    # 14 tests
│   ├── ParserTests.cs   # 30 tests
│   ├── TypeCheckerTests.cs  # 10 tests
│   └── EmitTests.cs     # 20 end-to-end tests
├── samples/             # Example .leb programs
└── spec.md              # Full language specification

Zero external dependencies for core compilation. The compiler is a single .NET 10 console application.

Building

# Build the compiler
dotnet build

# Run tests
dotnet test

# Compile a Culebral program
dotnet run --project src/Culebral.Compiler -- build samples/hello.leb

# Compile and run in one step
dotnet run --project src/Culebral.Compiler -- run samples/hello.leb

# Type-check without compiling
dotnet run --project src/Culebral.Compiler -- check samples/fibonacci.leb

Debug Commands

# Print lexer tokens
dotnet run --project src/Culebral.Compiler -- lex samples/hello.leb

# Print parse tree
dotnet run --project src/Culebral.Compiler -- parse samples/hello.leb

# Print CulebralIR
dotnet run --project src/Culebral.Compiler -- ir samples/fibonacci.leb

Roadmap

PhaseFocusStatus
1Minimum viable language — functions, control flow, basic types, CIL emissionComplete
2Type system + classes — generics, constructors, interfaces, @fieldComplete
3.NET interop — BCL imports, case bridging, NuGet resolutionComplete
4Language completeness — async/await, pattern matching, records, null safety, toolingComplete
5Standard library — Result type (built-in), culebral.testingMinimal — .NET interop is the stdlib
6Native modules — LLVM backend for @native hot pathsFuture

License

MIT


Built with .NET 10 · Zero dependencies · Compiles to real CIL