Iris
Iris is a small interpreted programming language written in C.
Current version: v3.0.2
File extension:.iris
License: Apache License 2.0
Iris uses a lexer → parser → AST → visitor architecture. Source code is tokenized, parsed into an abstract syntax tree, and then executed by the visitor.
You can download Iris here for free!
Getting Started
An Iris program is stored in a file ending in .iris.
For example:
print("Hello, world!");
Run an Iris program by passing its path to the interpreter:
iris.exe program.iris
The executable also supports:
iris.exe --version
which prints the current interpreter version.
Iris currently does not include a Makefile or CMake configuration in the repository root, so building the interpreter requires compiling the C source files together with the required libraries.
Basic Syntax
Statements
Statements are separated using semicolons:
print("Hello");
print("World");
A semicolon can also terminate a single statement:
var x = 10;
Iris parses a program as a compound collection of statements.
Variables
Variables are declared with var:
var x = 10;
var name = "Iris";
var enabled = true;
The value can be a number, string, boolean, table, dictionary, class, function result, or another expression.
Variables can be reassigned:
var x = 10;
x = 20;
The compound-assignment operators are also supported:
x += 5;
x -= 2;
x *= 3;
x /= 2;
The parser represents these as assignments and binary operations.
Data Types
Iris currently supports the following principal runtime values:
| Type | Example |
|---|---|
| Number | 123 |
| String | "hello" |
| Boolean | true / false |
| Table | [1, 2, 3] |
| Dictionary | ["name": "Iris"] |
| Class | MyClass |
| Class instance | MyClass instance |
Boolean literals are implemented as the identifiers true and false.
Numbers
Numbers are parsed as floating-point values internally.
var x = 10;
var y = 2.5;
print(x + y);
Supported arithmetic operators include:
+ addition
- subtraction
* multiplication
% modulo
^ exponentiation
Comparisons include:
>
<
>=
<=
==
!=
These operators are implemented by the expression parser and visitor.
Strings
Strings use double quotes:
var message = "Hello";
print(message);
Strings support concatenation with +:
var first = "Hello ";
var second = "world";
print(first + second);
The built-in print function also interprets %n inside strings as a newline:
print("Hello%nWorld");
Strings can contain escaped characters such as:
\n
\t
\"
\\
The lexer handles these escape sequences when collecting strings.
Comments
Comments are enclosed between # characters:
# This is a comment #
Comments may span multiple lines.
The lexer skips everything between the opening and closing #.
Functions
Functions are declared using func:
func add(a, b) {
return a + b;
}
They are called using parentheses:
var result = add(10, 20);
print(result);
Functions can have zero or more parameters:
func hello() {
print("Hello");
}
func greet(name) {
print("Hello " + name);
}
The interpreter checks the number of arguments passed to a function against the number declared by the function.
Return
Functions return values with return:
func square(x) {
return x * x;
}
var result = square(5);
A return causes the visitor to stop executing the current compound statement.
Conditions
if
if (x > 10) {
print("Large");
}
ifelse
Iris uses ifelse for an if/else statement:
ifelse (x > 10) {
print("Large");
} else {
print("Small");
}
The parser explicitly recognizes both if and ifelse.
Conditions consider numbers, strings, and booleans when determining truthiness.
For Loops
The Iris for syntax contains three expressions separated by colons:
for (var i = 1: i <= 10: "++") {
print(i);
}
The third expression specifies the step:
"++"
increments the loop variable.
"--"
decrements it.
For example:
for (var i = 10: i > 0: "--") {
print(i);
}
The loop parser expects:
for (initialisation : condition : step)
and the visitor specifically recognizes "++" and "--".
While Loops
While loops use:
while (condition) {
...
}
Example:
var x = 0;
while (x < 10) {
print(x);
x += 1;
}
The condition is reevaluated for every iteration.
Tables
Tables are ordered collections.
Define a table with square brackets:
table numbers = [1, 2, 3, 4, 5];
A table can contain expressions:
table numbers = [1 + 1, 2 + 2, 3 + 3];
Tables can also be copied into another table definition:
table numbers2 = numbers;
The parser records table names separately so that later references can be recognized as table values.
Table functions
Table operations are exposed through built-in functions:
table_get_index(table, value)
table_get_from_index(table, index)
table_set_index(table, index, value)
table_get_from_index uses 1-based indexing.
For example:
var value = table_get_from_index(numbers, 1);
returns the first element.
table_get_index searches for a value and returns its index.
Dictionaries
Dictionaries contain string keys and values.
dict person = [
"name": "John",
"age": 20
];
Dictionary entries use:
"key": value
and entries are separated by commas.
Dictionary access and modification are provided through built-ins:
dict_get_from_index(dictionary, key)
dict_get_index(dictionary, value)
dict_set_index(dictionary, key, value)
For example:
var name = dict_get_from_index(person, "name");
Member Access
Dictionary and class members can be accessed using a dot followed by a string:
person."name"
For example:
var name = person."name";
Members can also be assigned:
person."name" = "Alice";
The parser represents this operation as an AST_DOT node.
Classes
Classes are declared with class:
class Player {
"name": "Player",
"score": 0
}
Class members use string keys.
A class can also contain a special init member:
class Player {
"name": "Player",
"init": func(self, name) {
self."name" = name;
}
}
The init member is parsed specially as a constructor-like function. Its first parameter is conventionally self.
Class Instances
A class instance can be declared using:
Player player;
The parser treats the class name followed by another identifier as a class-instantiation expression.
Class instances can then be used with member access:
player."name"
Checks
Iris provides a checks construct for comparing a value against multiple conditions.
Its structure is:
checks(value) {
(condition): {
...
},
(condition): {
...
}
}
Example:
checks(x) {
(1): {
print("one");
},
(2): {
print("two");
}
}
Each condition is compared with the value supplied to checks.
Includes
Other .iris files can be included using:
include "library.iris";
The included path is resolved relative to the directory of the current Iris file.
Iris keeps track of included paths to prevent the same file from being included repeatedly, including circular include situations.
Built-in Functions
Iris provides a collection of built-in functions.
General
print
Prints values:
print("Hello");
print(123);
print(true);
Strings containing %n can be used for newlines.
type
Returns a string describing the type:
type(value)
Possible returned type names include:
STRING
NUMBER
TABLE
CLASS
BOOL
toNumber
Converts a string to a number:
toNumber("123")
toString
Converts a number to a string:
toString(123)
String Functions
charAt
Returns a character at a 1-based index:
charAt("Hello", 1)
stredit
Changes a character in a string:
stredit(string, index, value)
The replacement value is expected to contain one character.
ForEach
Calls a named Iris function for each character in a string:
ForEach("Hello", "myFunction")
File Functions
readFile
Reads a file into a string:
var contents = readFile("file.txt");
writeFile
Writes data to a file:
writeFile("file.txt", "Hello", false);
The third argument determines whether formatted output is used.
Garbage Collection
Iris exposes garbage-collection functions:
gc();
gcStats();
gc() is intended to reclaim unused variable and function definitions from the global scope.
The implementation also tracks function-call depth and prevents collection while a function call is in progress.
Windows Graphics
When Iris is compiled for Windows, it provides a small native windowing API.
Create a window
windowCreate("My Iris Window", 800, 600);
Clear the window
windowClear(0, 0, 0);
The arguments are RGB values.
Draw a rectangle
windowDrawRect(x, y, width, height, r, g, b);
Draw text
windowDrawText(x, y, text, r, g);
Present the back buffer
windowPresent();
Input and timing
windowGetTime();
windowShouldClose();
windowMouseX();
windowMouseY();
windowMousePressed();
Close
windowClose();
These functions are implemented using the Windows API. When Iris is compiled on a non-Windows platform, the window functions report that windowing is only supported under _WIN32.
Expression Precedence
Iris parses expressions in several levels:
- Parenthesized expressions
- Strings, numbers and identifiers
- Member access / function calls
*,/,%,^+,-- Comparisons:
><>=<===!=
This hierarchy is implemented directly by the parser's expression functions.
Program Structure
A typical Iris program can combine all of these features:
var name = "Iris";
var counter = 0;
func greet(person) {
print("Hello " + person);
}
greet(name);
for (var i = 1: i <= 5: "++") {
print(i);
}
if (counter == 0) {
print("Counter is zero");
}
The interpreter processes the file by:
.iris source
↓
Lexer
↓
Tokens
↓
Parser
↓
AST
↓
Visitor
↓
Execution
The lexer produces tokens containing a type, value, source position, line and column. The parser converts those tokens into AST nodes, and the visitor evaluates the resulting AST.
Source Layout
The repository is organized around the interpreter implementation.
Important components include:
src/
├── main.c
├── lexer.c
├── parser.c
├── visitor.c
├── AST.c
├── token.c
├── window.c
└── include/
├── AST.h
├── builtin.h
├── lexer.h
├── parser.h
├── scope.h
├── token.h
├── visitor.h
└── window.h
main.c initializes the lexer and parser, parses the program, and then sends the resulting AST to the visitor for execution.
Error Messages
Iris reports errors using messages such as:
Tripped on undefined variable 'x' ...
Tripped on division by zero ...
Tripped on undefined method 'foo' ...
Parser errors generally use the line stored on the current token.
Runtime errors produced by the visitor and built-ins use the interpreter's global current_line value. Consequently, runtime error locations can differ from the actual source location of the AST node that caused the error.
Quick Reference
| Feature | Syntax |
|---|---|
| Variable | var x = value; |
| Assignment | x = value; |
| Add assignment | x += value; |
| Subtract assignment | x -= value; |
| Multiply assignment | x *= value; |
| Divide assignment | x /= value; |
| Function | func name(args) { ... } |
| Return | return value; |
| If | if (condition) { ... } |
| If/else | ifelse (condition) { ... } else { ... } |
| For | for (start: condition: "++") { ... } |
| While | while (condition) { ... } |
| Table | table x = [a, b, c]; |
| Dictionary | dict x = ["key": value]; |
| Class | class X { ... } |
| Instance | X instance; |
| Member access | object."key" |
| Include | include "file.iris"; |
| Comment | # comment # |
Example Program
# Simple Iris program #
var name = "World";
var count = 5;
func greet(person) {
print("Hello " + person);
}
greet(name);
for (var i = 1: i <= count: "++") {
print(i);
}
if (count > 0) {
print("Done");
}
This demonstrates variables, strings, functions, function calls, a for loop, comparisons, and an if statement.