Rune
Rune is a small programming language created to explore how programming languages work internally, from lexical analysis and parsing to runtime execution, scopes, functions, control flow, and type checking. Rune currently uses a tree-walk interpreter and is under active development.
Table of Contents
About RuneRune is an experimental programming language implemented in Rust. The project is built as a hands-on exploration of programming language implementation. Instead of relying entirely on an existing language runtime, Rune implements its own core pipeline:
The current implementation includes:
Current FeaturesCore language
Variables
TypesRune currently recognizes:
Arithmetic operators
Comparison operators
Logical operators
Control flow
Functions
Input and output
Runtime
Quick StartCreate a file named:
Add:
Run it:
Example:
File ExtensionRune source files use:
Examples:
Language SyntaxStatementsMost simple statements end with a semicolon:
Block-based constructs do not require a semicolon after the closing brace:
VariablesRune supports both immutable and mutable variables.
|
| Operator | Meaning |
|---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
% |
Modulo |
Examples:
let addition = 10 + 5;
let subtraction = 10 - 5;
let multiplication = 10 * 5;
let division = 10 / 5;
let remainder = 10 % 3;
println(addition);
println(subtraction);
println(multiplication);
println(division);
println(remainder);
Comparisons
Rune supports:
==
!=
<
<=
>
>=
Examples:
println(10 == 10);
println(10 != 5);
println(5 < 10);
println(5 <= 5);
println(10 > 5);
println(10 >= 10);
Logical Operators
Rune supports boolean logic.
NOT
let active = true;
println(!active);
AND
let logged_in = true;
let verified = true;
if logged_in && verified {
println("Access granted");
}
OR
let admin = false;
let moderator = true;
if admin || moderator {
println("Access granted");
}
Rune uses short-circuit evaluation for && and ||.
Operator Precedence
Rune respects operator precedence.
Example:
let result = 2 + 3 * 4;
println(result);
Output:
14
Grouping changes the order:
let result = (2 + 3) * 4;
println(result);
Output:
20
A simplified precedence order is:
lowest
||
&&
== !=
< <= > >=
+ -
* / %
!
function calls / member access
primary expressions
highest
Blocks and Scopes
Blocks use braces:
{
let value = 10;
println(value);
}
Variables declared inside a block are not accessible after the block ends:
{
let secret = 42;
}
// Error: secret no longer exists here.
println(secret);
Inner scopes can access variables from outer scopes:
let value = 10;
{
println(value);
}
Shadowing
Rune supports variable shadowing across different scopes:
let value = 10;
{
let value = 20;
println(value);
}
println(value);
Output:
20
10
If / Else
Basic conditional:
let age: i32 = 13;
if age >= 13 {
println("Allowed");
} else {
println("Not allowed");
}
Rune also supports else if:
let temperature: i32 = 28;
if temperature >= 30 {
println("Hot");
} else if temperature >= 20 {
println("Warm");
} else {
println("Cold");
}
Conditions must evaluate to a boolean.
While
var counter: i32 = 0;
while counter < 5 {
println(counter);
counter = counter + 1;
}
Output:
0
1
2
3
4
For
Rune supports C-style for loops:
for var i: i32 = 0; i < 5; i = i + 1 {
println(i);
}
The initializer must currently be a variable declaration and the increment must be an assignment.
Break
break exits the nearest loop:
var i: i32 = 0;
while true {
if i == 5 {
break;
}
println(i);
i = i + 1;
}
Continue
continue skips the rest of the current loop iteration:
for var i: i32 = 0; i < 10; i = i + 1 {
if i % 2 != 0 {
continue;
}
println(i);
}
Functions
Functions are declared using the function keyword.
function add(a: i32, b: i32): i32 {
return a + b;
}
Call the function:
let result = add(10, 20);
println(result);
Output:
30
Parameters
Function parameters require explicit types:
function greet(name: string) {
print("Hello, ");
println(name);
}
greet("Rune");
Multiple parameters are separated by commas:
function multiply(a: i32, b: i32): i32 {
return a * b;
}
println(multiply(6, 7));
Return
Functions can return values:
function square(value: i32): i32 {
return value * value;
}
println(square(5));
Output:
25
Void functions can use an empty return:
function greet() {
println("Hello");
return;
}
Recursion
Rune functions can call themselves:
function factorial(n: i32): i32 {
if n <= 1 {
return 1;
}
return n * factorial(n - 1);
}
println(factorial(5));
Output:
120
print(...) writes output without automatically adding a newline.
print("Hello, ");
print("world!");
Output:
Hello, world!
Multiple arguments are supported:
let name = "Rune";
print("Language:", name);
Println
println(...) writes output followed by a newline.
println("Hello, world!");
Multiple arguments are also supported:
let language = "Rune";
let version = 0.4;
println("Language:", language);
println("Version:", version);
Input
Rune provides terminal input through the built-in io namespace.
With a prompt
let name: string = io.input("What is your name? ");
print("Hello, ");
println(name);
Example:
What is your name? Mateus
Hello, Mateus
Without a prompt
print("Type something: ");
let text: string = io.input();
println(text);
io.input() reads one line from standard input and returns it as a string.
Currently supported forms:
io.input();
and:
io.input("Prompt: ");
The function accepts zero or one argument.
When provided, the argument must be a string.
Invalid:
io.input(123);
Invalid:
io.input("A", "B");
Complete Example
This example combines many of Rune's current features:
let language: string = "Rune";
println("=== Rune Demo ===");
let name: string = io.input("Name: ");
let age_text: string = io.input("Age as text: ");
print("Hello, ");
println(name);
var counter: i32 = 0;
while counter < 3 {
println("Counter:", counter);
counter = counter + 1;
}
function is_even(number: i32): bool {
return number % 2 == 0;
}
function factorial(number: i32): i32 {
if number <= 1 {
return 1;
}
return number * factorial(number - 1);
}
for var i: i32 = 0; i < 10; i = i + 1 {
if i == 8 {
break;
}
if !is_even(i) {
continue;
}
println("Even:", i);
}
{
let scoped_value: i32 = 42;
println("Scoped value:", scoped_value);
}
println("5! =", factorial(5));
println("Language:", language);
println("Age input:", age_text);
println("=== End ===");
How Rune Works
Rune currently uses a tree-walk interpreter.
.rune source
│
▼
Lexer
│
▼
Tokens
│
▼
Parser
│
▼
AST
│
▼
Interpreter
│
▼
Result
Lexer
The lexer converts source characters into tokens.
Example:
let result: i32 = 2 + 3;
Conceptually:
Let
Identifier("result")
Colon
Identifier("i32")
Equal
IntegerLiteral(2)
Plus
IntegerLiteral(3)
Semicolon
EOF
Parser
The parser converts tokens into structured syntax.
It handles:
- variable declarations;
- assignments;
- blocks;
- conditionals;
- loops;
- function declarations;
- returns;
- expressions;
- function calls;
- member access;
- operator precedence.
AST
The AST represents the structure of the program.
Example:
2 + 3 * 4
Conceptually:
Add
/ \
2 Multiply
/ \
3 4
A member call such as:
io.input("Name: ")
is represented conceptually as:
Call
├── callee
│ └── Member
│ ├── object: Identifier("io")
│ └── name: "input"
└── arguments
└── StringLiteral("Name: ")
Interpreter
The interpreter:
- evaluates expressions;
- manages nested scopes;
- stores variables;
- validates types;
- resolves functions;
- executes control flow;
- executes built-in functions;
- reads terminal input;
- produces output.
Project Structure
A simplified project layout:
rune/
├── Cargo.toml
├── README.md
└── src/
├── main.rs
├── token/
├── lexer/
├── ast/
├── parser/
└── interpreter/
token
Defines token kinds used by the lexer and parser.
lexer
Responsible for:
- scanning characters;
- recognizing keywords;
- reading identifiers;
- reading strings;
- reading characters;
- reading numbers;
- operators;
- punctuation;
- source position tracking.
ast
Defines:
- expressions;
- statements;
- operators;
- function parameters;
- Rune types;
- program structure.
parser
Responsible for:
- statement parsing;
- expression parsing;
- operator precedence;
- member access;
- function calls;
- variable declarations;
- assignments;
- blocks;
- conditions;
- loops;
- functions;
- returns.
interpreter
Responsible for:
- evaluating expressions;
- executing statements;
- scopes;
- variables;
- mutability;
- type validation;
- built-ins;
- functions;
- loops;
- control flow;
- terminal input/output.
main.rs
Acts as the CLI entry point.
Building
Rune is written in Rust.
Check that Rust is installed:
rustc --version
cargo --version
Clone the repository:
git clone https://github.com/MateusSoaresL/rune.git
cd rune
Build:
cargo build
Optimized build:
cargo build --release
The release executable is normally generated under:
target/release/
Running Rune Programs
Create:
main.rune
Example:
let name: string = io.input("Name: ");
println("Hello,", name);
Run:
cargo run -- main.rune
Or after a release build:
./target/release/rune main.rune
On Windows:
rune.exe
Error Handling
Rune reports syntax and runtime errors with source location information where available.
Examples may include:
Expected expression at 4:10
Undefined identifier 'value' at 8:5
Cannot assign to immutable variable 'value' at 3:1
Type mismatch: expected i32, found String(...) at 2:5
Division by zero at 7:12
io.input() expects 0 or 1 argument, got 2
io.input() prompt must be a string
Rune also validates invalid control flow, such as:
breakoutside a loop;continueoutside a loop;returnoutside a function.
What Rune Does Not Support Yet
Rune is still experimental.
The following features are not currently documented as supported:
Imports and modules
import math;
or:
use math;
Arrays
let values = [1, 2, 3];
Objects
let user = {
name: "Rune"
};
Classes
class User {
}
Package manager
Rune does not currently include a package manager.
Native compilation
Rune currently executes code through a tree-walk interpreter rather than compiling Rune source directly to native machine code.
Roadmap
Implemented
- [x]
.runesource files - [x] Lexer
- [x] Tokens
- [x] Parser
- [x] AST
- [x] Tree-walk interpreter
- [x] String literals
- [x] Character literals
- [x] Integer literals
- [x] Floating-point literals
- [x] Boolean literals
- [x] Identifiers
- [x]
let - [x]
var - [x] Assignment
- [x] Explicit variable types
- [x] Type inference
- [x] Integer range validation
- [x] Block scopes
- [x] Variable shadowing
- [x] Arithmetic operators
- [x] Modulo
- [x] Equality operators
- [x] Comparison operators
- [x] Logical operators
- [x] Short-circuit evaluation
- [x] Operator precedence
- [x] Parenthesized expressions
- [x]
if - [x]
else if - [x]
else - [x]
while - [x]
for - [x]
break - [x]
continue - [x] Functions
- [x] Typed parameters
- [x] Function calls
- [x] Return types
- [x]
return - [x] Recursion
- [x]
print - [x]
println - [x]
io.input() - [x]
io.input("Prompt: ") - [x] Source line tracking
- [x] Source column tracking
- [x] Runtime errors
Future possibilities
- [ ] Arrays
- [ ] Objects
- [ ] Modules
- [ ] Imports
- [ ] Standard library expansion
- [ ] String interpolation
- [ ] More built-in namespaces
- [ ] Better diagnostics
- [ ] REPL
- [ ] Formatter
- [ ] Language Server Protocol
- [ ] Package manager
- [ ] Intermediate Representation
- [ ] Bytecode
- [ ] Virtual machine
- [ ] Native compilation
- [ ] Optimization passes
Future Compiler Architecture
Rune may eventually evolve from:
Source
↓
Lexer
↓
Parser
↓
AST
↓
Interpreter
toward something like:
Rune Source
│
▼
Lexer
│
▼
Parser
│
▼
AST
│
▼
Semantic Analysis
│
▼
Typed AST
│
▼
IR
│
▼
Optimization
│
▼
Code Generation
│
▼
Native Code / Bytecode
Possible future backends may include:
LLVM
Cranelift
Custom bytecode VM
Custom native backend
No final backend is guaranteed.
Versioning
Rune follows Semantic Versioning where practical:
MAJOR.MINOR.PATCH
During early development, Rune remains in the 0.x series.
Example:
v0.4.0
Breaking changes may occur between 0.x releases.
A future v1.0.0 should represent a substantially more stable language specification and public interface.
Contributing
Bug reports, suggestions, discussions, and pull requests are welcome.
When reporting a bug, include:
- Rune version
- Operating system
- Rune source code
- Expected output
- Actual output
- Error message
Use the smallest reproducible example possible.
Example:
let result: i32 = (2 + 3) * 4;
println(result);
Development Status
Rune is under active development.
Internal APIs are not considered stable.
This includes:
Token definitions
AST nodes
Parser internals
Interpreter internals
Runtime values
Built-in APIs
Error formats
Module layout
Users should rely on documented Rune syntax rather than Rust implementation details.
License
No license is assumed by this README.
Before accepting external contributions or distributing Rune for reuse, add an explicit license file.
Common choices include:
MIT
Apache-2.0
MIT OR Apache-2.0
Author
Created by Mateus Soares.
GitHub:
https://github.com/MateusSoaresL
Repository:
https://github.com/MateusSoaresL/rune
Final Note
Rune is an experimental programming language being built incrementally from the lexer upward.
The current language already supports a substantial interpreted core:
Variables
Types
Expressions
Scopes
Conditionals
Loops
Functions
Input / Output
The next stages can focus on expanding the standard library, improving diagnostics, strengthening the language model, and eventually exploring bytecode or native compilation.