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:

  1. Parenthesized expressions
  2. Strings, numbers and identifiers
  3. Member access / function calls
  4. *, /, %, ^
  5. +, -
  6. 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.