Getting Started

Luminous is an easy to learn dynamically-typed bytecode compiled programming language. Luminous' inherit high-level syntax and straightforward approach to object-oriented programming make it the ideal language to help avid leaners master programming concepts quickly.

The source code for the Luminous interpreter and the standard library is completely written in C++ and is open-source under MIT License. Feel free to take a look on GitHub!

To compile the source code, simply clone or download the repository and run make in the main directory.

$ make

Then, you should be able to find the Luminous interpreter luminous in the bin folder.

If you're not too much of a dev (or just want to save time), you can find the compiled executable in the release section or try the online editor!

Introduction

This tutorial will introduce the concepts and features of the Luminous language. Let us start with a classic written in Luminous.

print("Hello World!");

The built-in print() function takes one argument and outputs it followed by a newline character.

Hello World!

But how do we run the code? The Luminous interpreter luminous can either be run in REPL mode or with a file ending in .lum as an argument. The former as an example in the terminal.

$ luminous
Luminous 1.0.0 REPL
* print("Hello World!");
Hello World!

The latter as an example with a file called main.lum, where the content of the file is print("Hello World!").

$ luminous main.lum
Hello World

As you can probably deduce by now, all statements in Luminous must end with a semicolon character ; to clearly show that statements get executed one by one.

Variables

It is very easy to define variables, simply write a name for the variable and assign it a value!

a = 1;

There are 5 primitive data types: number, boolean, string, list, and null. That's it?! Well, yes, but users can define their own data types as classes, as we will see later.

a = 1;
b = true;
c = "string";
d = [1, 2, 3];
e = null;

Since the language is dynamically typed, then we can reassign values of different types to the same variable.

a = 1;
a = true; // valid
a = "string"; // also valid!

Scopes are defined by curly brackets {}, and so variables have their own scope which is determined by where they are first initialized. Thus, when a variable goes out of scope, it is not longer defined and any attempt to reference it will throw an error.

a = 1; // initialized at global scope
{
	b = 2; // initialized at local scope
	a = b; // valid
}
a = b; // invalid

Lists

Lists are available as a native data structure.

list = [0, "hello", null];

print(list[0]); // getting the item in 'list' at index 0
print(list[1]);
print(list[2]);

list[2] = "Not null anymore!"; // changing the item in 'list' at index 2
print(list[2]);
0
hello
null
Not null anymore!

Operators

Number operators with standard math signs.

print(10 + 8); // addition
print(10 - 8); // subtraction
print(10 * 8); // multiplication
print(10 / 8); // division
print(10 % 8); // modulo
18
2
80
1.25
2

String operators.

print("String" + " concatenation"); // string concatenation
print("One: " + 1); // string-number concatenation
String concatenation
One: 1

Basic comparator operators.

print(1 < 2);
print("a" > "b");
// same goes for <= and >=
true
false

Enhanced assignment operators as handy shortcuts.

a = 1;
a += 1;
print(a);
a -= 1;
print(a);
// same goes for *=, /=, and %=
2
1

Boolean operators with built-in keywords and, or, not, equals.

print(true and false); // &&
print(true or false); // ||
print(not true); // !
print(true equals true); ==
false
true
false
true

List operators with standard math signs.

print([1, 2, 3] + 4); // adds element to the end of the list;
print([1, 2, 3] - 0); // removes item at index;
print([1, 2, 3] * 3); // repeats all the elements of the array
[1, 2, 3, 4]
[2, 3]
[1, 2, 3, 1, 2, 3, 1, 2, 3]

Control Flow

There are multiple typical types of control flow in Luminous.

if else statements.

if (3 < 2) {
	print("Not Luminous");
} else if (false) {
	print("Not Luminous again");
} else {
	print("Luminous!");
}
Luminous!

while loops.

a = 1;
while (a < 3) {
	print("Current index a is: " + a);
	a += 1;
}
Current index a is: 1
Current index a is: 2

for loops with a twist. It iterates over a range from to where the last element is excluded and it needs an incrementer/decrementer after by.

for (i from 10 to 2 by -2) {
	print("Current index i is: " + i);
}
Current index i is: 10
Current index i is: 8
Current index i is: 6
Current index i is: 4

Luminous also supports multiple loop action keywords.

The continue keyword allows the user to skip the current iteration and proceed directly to the start of the next iteration.

for (i from 0 to 6 by 1) {
	// skip all odd i
	if (i % 2 equals 1) {
		continue;
	}
	print(i);
}
0
2
4

The break keyword allows the user to break out of the current loop.

i = 0;
while (i < 100) {
	if (i equals 5) break;
	print(i);
	i += 1; // equivalent to i = i + 1
}
print("Out of the loop!");
0
1
2
3
4
Out of the loop!

Functions

Functions are declared using function and a list of parameters.

function printXPlusY(x, y) {
	print(x + y);
}

printXPlusY(1, 2);
printXPlusY("One: ", 1);
3
One: 1

Functions are considered as first-class objects. Therefore, storing them in variables and passing them as other functions' argument is entirely valid.

function returnHello() {
	return "Hello";
}

myFunc = returnHello;
print(myFunc());

function printXFunc(x) {
	print(x());
}

printXFunc(returnHello);
Hello
Hello

Functions can also access variables in the scope of another function by declaring them as inner function.

cat = null;
function saveCat() {
	meow = "meow";
	function inner() {
		print(meow);
	}
	cat = inner;
}

saveCat();
cat();
meow

Classes

Luminous wouldn't be an object-oriented language if it didn't allow user-defined types. The class keyword creates a blueprint so that new instances of that type can be made.

class A {}
instanceA = A();

Fields

Classes can have user-defined fields.

Fields in Luminous are class-based. This means that fields must be pre-declared during class definition, and users are not allowed to add new fields during instantiation.

Each instance's fields can hold different values.

class A {
	public x;
	public y;
}

A1 = A();
A1.x = "A1's x";
A1.y = "A1's y";

A2 = A();
A2.x = "A2's x";
A2.y = "A2's y";

print(A1.x);
print(A1.y);
print(A2.x);
print(A2.y);
A1's x
A1's y
A2's x
A2's y

Methods

Classes can also have user-defined methods. Methods are class-based, so each instance of a given class can call its pre-defined methods.

class A {
	public x;
	public setX(val) {
		this.x = val; // instances can refer to themselves using the keyword 'this'
	}
}

instanceA = A();
instanceA.setX("A's x");
print(instanceA.x);
A's x

Access Modifier

You may have noticed that the public keyword was used during field declaration and method definition in the previous sections. This is an example of an access modifier.

Access modifiers are keywords that allow users to set the accessiblity of class fields and methods.

There are 3 types of access modifiers: public, protected and private.

public fields and methods are accessible anywhere.

protected fields and methods are only accessible in classes with an inheritance relationship.

private fields and methods are only accessible in the current class.

In Luminous, access modifiers are mandatory during field declaration and method definition.

class A {
	private x;
	public y;
	protected setX(val) {
		this.x = val;
	}
}

instanceA = A();
instanceA.setX("A's x"); // invalid!
print(instanceA.x); // invalid!
instanceA.y; // valid!

Constructor

Constructors are special methods that returns a new instance of a given class. They are automatically called during object instantiation.

Luminous allows user-defined constructors that executes additional code before returning a new instance of the class. Custom constructors are defined inside class definitions using constructor().

class A {
	public constructor() {
		print("Created an instance of A!");
	}
}

instanceA = A();
Created an instance of A!

Inheritance

Classes can inherit each other to establish a parent-child relationship by using inherits. Child classes can access to parent classes' fields and methods.

class A {
	protected x;
	public printX() {
		print("printX from class A");
		this.x = 1;
		print(this.x);
	}
}

class B inherits A {
	public y;
	public printX() {
		print("printX from class B");
		this.x = 2;
		this.y = 3;
		print(this.x);
	}
}

instanceA = A();
instanceB = B();

instanceA.printX();
instanceB.printX();
print(instanceB.y);
printX from class A
1
printX from class B
2
3

Classes can access their parent's methods using the super keyword.

class A {
	public hello() {
		print("Hello from class A!");
	}
}

class B inherits A {
	public hello() {
		print("Hello from class B!");
		super.hello();
	}
}

instanceB = B();
instanceB.hello();
Hello from class B!
Hello from class A!

Import

Luminous allows users to execute code in different files by using the linker with the import statement. Import statements require the path of a Luminous source code file, or the name of a standard library.

File: A.lum

class A {
	public constructor(val) {
		print(val);
	}
}

File: main.lum

import A.lum
instanceA = A("Hello world!");
Hello world!

Native Functions

Luminous supports multiple built-in functions.

Clock

clock() returns the amount of time elapsed in milliseconds since the start of the program.

print(clock());
0.032

Substring

substring() takes in a string and two indices. The first index is the start index (inclusive) and the second index is the end index (exclusive) of the substring to extract from the given string.

print(substring("abcdefg", 1, 4));
print(substring("hello world", 6, 99));
bcd
world

Size

size() takes in a string or a list, and returns the number of characters in the string or the number of items in the list.

print(size("hello"));
print(size([0, 1, 2]));
5
3

Floor

floor() takes in a number, and returns the rounded-down integer of the given number.

print(floor(3.14));
3

Ceil

ceil() takes in a number, and returns the rounded-up integer of the given number.

print(ceil(3.14));
4

Type

type() returns the datatype of the given data as a string.

print(type(3.14));
print(type(""));
print(type(true));
print(type(null));
print(type([]));

function func() {}
print(type(func));

class A {}
print(type(A));
print(type(A()));
number
string
boolean
null
list
function
class
A

Throw

throw() allows users to throw an exception and terminate the process. It can take a string as error message, or a positive integer as exit code.

throw("Exit process");
throw(0);