Skip to content

Portfoligno/veltrano-transpiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Veltrano Transpiler

A transpiler from Veltrano (Kotlin-like syntax) to Rust with a reference-by-default type system.

Table of Contents

Installation

# Clone the repository
git clone https://github.com/Portfoligno/veltrano-transpiler.git
cd veltrano-transpiler

# Build the project
cargo build --release

# Run tests
cargo test

Quick Start

# Transpile a Veltrano file to Rust
cargo run examples/hello.vl

# With comment preservation
cargo run -- --preserve-comments examples/hello.vl

# The output will be saved as hello.rs

Example Input (hello.vl):

fun main() {
    val message: Str = "Hello, Veltrano!"
    println("{}", message)
}

Generated Output (hello.rs):

fn main() {
    let message: &str = "Hello, Veltrano!";
    println!("{}", message);
}

Overview

Design Philosophy

Veltrano is designed with these core principles:

  1. Reference-by-default: Borrowing is the common case in Rust, so Veltrano makes it the default
  2. Explicit ownership: When you need ownership, use Own<T> to make it visible and intentional
  3. Kotlin-like syntax: Familiar syntax for Kotlin developers with Rust's powerful semantics
  4. Immutability-first: Only val (immutable bindings) are supported, encouraging functional patterns

Key Features

  • Familiar Syntax: Kotlin-like keywords (fun, val, if, while)
  • Type Safety: Full type annotations and inference
  • Automatic Conversions: Systematic identifier conversion (uppercase→underscore+lowercase, underscore→double)
  • Comment Preservation: Optional comment preservation in generated code
  • Clear Semantics: Direct mapping to idiomatic Rust patterns

Type System

Core Concepts

In Veltrano, the type system is built around making references the default:

  • Types without Own<> are references by default
  • Use Own<T> for explicit ownership
  • Basic types (I64, Bool, Unit, Nothing) are always owned (matching Rust's Copy types)
  • Use Ref<T> for additional reference levels
  • Use MutRef<T> for mutable references

Type Mapping Reference

Base Types

Veltrano Type Rust Type Description Example
I64 i64 64-bit integer (always owned) val x: I64 = 42
Bool bool Boolean (always owned) val flag: Bool = true
Unit () Unit type fun doSomething(): Unit
Nothing ! Never type fun abort(): Nothing
Str &str String slice reference val s: Str = "hello"
String &String String reference (default) val s: String = owned.ref()

Type Constructors

Pattern Applied to Owned Base Applied to Reference Base
Own<T> Invalid (Own<I64>) Own<String>String
Ref<T> Ref<I64>&i64 Ref<String>&&String
MutRef<T> MutRef<I64>&mut i64 MutRef<String>&mut &String
Box<T> Box<I64>&Box<i64> Box<Str>&Box<&str>

Working with References

The .ref() Method

Convert owned values to references:

val owned: Own<String> = "Hello".toString()  // String (owned)
val borrowed: String = owned.ref()            // &String (reference)
val doubleBorrowed: Ref<String> = borrowed.ref()  // &&String

The MutRef() Function and .mutRef() Method

Create mutable references with two available syntaxes:

// Preferred: MutRef() function - generates &mut (&value).clone()
val number: I64 = 42
val mutableRef: MutRef<I64> = MutRef(number)

// Alternative: .mutRef() method - generates &mut value
// Chain directly without binding to avoid immutability issues
val mutableRef2: MutRef<I64> = number.ref().clone().mutRef()

Example with function:

fun modify(value: MutRef<I64>) {
    // Function accepting a mutable reference
}

fun main() {
    val number: I64 = 42
    val mutableRef: MutRef<I64> = MutRef(number)
    modify(mutableRef)
}

Transpiles to:

fn modify(value: &mut i64) {
    // Function accepting a mutable reference
}
fn main() {
    let number: i64 = 42;
    let mutable_ref: &mut i64 = &mut (&number).clone();
    modify(mutable_ref);
}

The MutRef() function automatically handles the borrow-and-clone pattern, making it the preferred approach for creating mutable references in Veltrano's immutability-first design.

Language Guide

Variables

Veltrano uses val for immutable variable bindings:

val name: Str = "Alice"             // String slice (explicitly typed)
val age = 25                        // Type inference (I64)
val message = "Hello, World!"       // Type inference (Str)
val owned: Own<String> = "Bob".toString()  // Owned string

Functions

Functions are declared with fun:

// Basic function
fun greet(name: String) {
    println("Hello, {}!", name)
}

// With return type
fun add(a: I64, b: I64): I64 {
    return a + b
}

// Expression body (explicit return needed)
fun multiply(x: I64, y: I64): I64 {
    return x * y
}

Control Flow

If Statements

if (x > 0) {
    println("positive")
} else if (x < 0) {
    println("negative")
} else {
    println("zero")
}

While Loops

val counter = 0
while (counter < 10) {
    println("{}", counter)
    // counter would need to be mutable to increment
}

// Infinite loops are converted to Rust's loop
while (true) {
    // Becomes: loop { ... }
}

Comments

Both line and block comments are supported:

// This is a line comment

/* This is a
   block comment */

val x = 42  // Inline comment

/* 
 * Documentation-style
 * block comment
 */
fun documented() {
    // Implementation
}

Never Type

The Nothing type represents computations that never return:

fun abort(message: Str): Nothing {
    panic("{}", message)  // Never returns
}

fun infiniteLoop(): Nothing {
    while (true) {
        // Never returns - transpiles to Rust's loop
    }
}

Naming Conventions

Veltrano automatically converts identifiers using these rules:

  • Uppercase letters → underscore + lowercase
  • Underscores → double underscores
Veltrano (Input) Rust (Output)
calculateSum calculate_sum
firstName first_name
HTTPSConnection _h_t_t_p_s_connection
getValue get_value
CamelCase _camel_case
XMLParser _x_m_l_parser
var_name var__name
mixed_Case mixed___case

This applies to all identifiers: functions, variables, and parameters.

Example:

fun calculateSum(firstNumber: I64, secondNumber: I64): I64 {
    val totalResult: I64 = firstNumber + secondNumber
    return totalResult
}

Transpiles to:

fn calculate_sum(first_number: i64, second_number: i64) -> i64 {
    let total_result: i64 = first_number + second_number;
    return total_result;
}

Command Line Reference

Basic Usage

cargo run [OPTIONS] <input-file>

Options

Option Description
--preserve-comments Include comments from source in generated Rust code

Examples

# Basic transpilation
cargo run examples/hello.vl
# Output: examples/hello.rs

# With preserved comments
cargo run -- --preserve-comments examples/fibonacci.vl
# Output: examples/fibonacci.rs (with comments)

# From any directory
cargo run path/to/myfile.vl
# Output: path/to/myfile.rs

Examples

The examples/ directory contains various Veltrano programs demonstrating different features:

  • hello.vl - Basic hello world program
  • fibonacci.vl - Fibonacci sequence calculation
  • string_types.vl - Different string type usage
  • ref_type.vl - Reference type demonstrations
  • mut_ref.vl - Mutable reference usage
  • type_symmetry.vl - Type system symmetry examples
  • unified_types.vl - Unified type system examples
  • never_type.vl - Never type (Nothing) usage
  • camel_case.vl - Naming convention examples
  • comments.vl - Comment preservation examples
  • clone_ufcs.vl - UFCS clone behavior
  • mutable_bindings.vl - Practical MutRef patterns
  • mutref_syntax_comparison.vl - Comparison of MutRef syntaxes

Design Decisions

Why No var (For Now)

Veltrano currently supports only val (immutable variables) without a var keyword. This is a deliberate choice based on a semantic mismatch between Kotlin's var and Rust's let mut.

The Semantic Challenge

In Kotlin, var is a storage declaration - it only affects whether the variable binding can be reassigned:

// Kotlin
var x = 5
x = 10  // OK - rebinding allowed

val list = mutableListOf(1, 2, 3)
list.add(4)  // OK - val doesn't prevent mutation of the data!

In Rust, let mut has recursive semantics - it affects both the binding AND enables mutation of the data:

// Rust
let mut x = vec![1, 2, 3];
x.push(4);      // Mutating the data (requires mut)
x = vec![5, 6]; // Rebinding (also requires mut)

let y = vec![1, 2, 3];
y.push(4);      // ERROR - cannot mutate without mut

This fundamental difference makes a direct mapping potentially confusing. For now, Veltrano takes an immutability-first approach, using MutRef<T> for cases where mutation is needed.

Reference-by-Default Design

Most types in Veltrano are references by default because:

  1. Borrowing is more common than ownership in typical Rust code
  2. Explicit ownership with Own<T> makes ownership transfers visible
  3. Prevents accidental moves which are a common source of Rust learner confusion
  4. Aligns with Rust's philosophy of preferring borrowing

This design choice means:

  • String in Veltrano is &String in Rust (not String)
  • To get an owned string, use Own<String>
  • Basic types like I64 and Bool remain owned (matching Rust's Copy types)

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

Development Setup

# Clone and build
git clone https://github.com/Portfoligno/veltrano-transpiler.git
cd veltrano-transpiler
cargo build

# Run tests
cargo test

# Run with example
cargo run examples/hello.vl

Acknowledgments

Developed with Claude Code. If you're interested in trying it:

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

About

Linguistic exploration of Rust code generation paradigms, written in Rust

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages