Preface

Alore is a dynamically-typed, object-oriented general-purpose programming language. This document introduces the main features of the Alore programming language in a concise manner for a reader who already understands the basics of programming. Some programming experience with an object-oriented programming language such as Java, C++ or Python should be enough to understand this document without difficulty.

Contents

Introduction

Alore is a general-purpose programming language designed to be suitable for many typical programming tasks, ranging from small scripts to large and complex applications. It strives for a balanced middle ground between scripting languages and statically-typed object-oriented programming languages. Alore provides high-level data types, powerful string processing facilities and a quick edit-run cycle like a typical scripting language, but it also naturally supports a more structured programming approach than most traditional scripting languages.

This document aims to give a concise overview of programming in Alore. Alore is a fairly small language and easy to master, and an experienced programmer should become productive with Alore in a day or two.

Alore has a design philosophy strongly favoring simplicity, clarity and consistency, while always remaining pragmatic. Alore tries to achieve a good balance between readability and expressive power, resulting in a language that encourages writing clear and compact code that is easy to write, understand and modify.

Alore borrows ideas from many other programming languages. Python is the main source of influence, and there are many obvious similarities between Alore and Python in both syntax and libraries.

Here is a quick list of some of the most significant Alore features:

This introduction uses a bottom-up approach: basics are introduced first, and explanations of more advances concepts are built on top of more basic concepts. The next section presents the traditional "Hello world" program in Alore, and instructions for getting your first program up and running. The rest of this document explains different language constructs, with short source code examples for each feature. Experienced programmers can get a quick overview of the syntax of Alore by only looking at the section titles and code examples. Finally, the last section gives a quick introduction to some of the most important Alore standard library modules.

We include quite a few details that we could arguably have omitted for brevity. This is used to emphasize the fact that Alore is not only easy to get started with, but there aren't many hidden complex features and trivia needed to properly master the language.

You may refer to the Alore Language Reference for more detailed explanations after having read this document. The Alore Library Reference explains all the modules and types in the Alore standard library. This introduction contains links to the Library Reference for getting further information on specific topics.

Hello, world

The traditional first program simply displays a message:

WriteLn("Hello, world")

Enter the program in any text editor and save it as "hello.alo". If you have the Alore interpreter in your PATH, you can then run the program in the shell or the command prompt:

$ alore hello.alo
Hello, world
$

The above example only defines a single statement that calls the WriteLn function to display the message.

Local variables and the Main function

The sub keyword defines functions and the var keyword is used to define variables:

sub Main()
  var local
  var two = "two"
  -- Define two local variables.
  var first, second
end

The function Main, if present, is called at the beginning of each Alore program. In the example above, Main accepts no arguments, but another supported variant of Main is explained later.

Variables in Alore are untyped – only values (or objects) have types. By convention, functions and other definitions with a global scope (for example, Main) typically start with a capital letter. Local variables (in the example, local, two, first and second) start with a lower case letter.

Local variables are visible from the location of their definition to the end of the block that contains the definition. Function definitions and other global definitions are always visible in the entire file or module that contains the definition, and they must be defined outside a function.

Variable names may contain letters (a-z, A-Z), digits (0-9) and underscores, but they may not start with a digit.

Comments

Two dashes (--) introduce a comment that extends until the end of a line. If the first line of an Alore source file starts with #!, that line also behaves as a comment. This is frequently useful in Unix-like operating systems, and it can be safely ignored in other operating systems.

Statements

Statements are the basic program building block in an imperative language like Alore. We have already seen two kinds of statement: function call and local variable definition. The sections below introduce other statement types, including the assignment statement, if statement and loops.

All statements are accepted both at the top level as well as in functions. However, we recommended you to place all non-initialization statements into functions such as Main instead of the top level of your program to make your code easier to maintain, unless you are writing a short throwaway script.

In many of the code fragments below, we have omitted the enclosing function definition for brevity. Variable names usually start with a lower case letter to make it clear that we actually define local variables.

Assignment statement and expressions

In the following fragment, the local variable i is initialized with the value 1, and subsequently incremented by 1 (to 2):

var i = 1
i = i + 1

You can shorten the previous assignment statement by using += instead of the assignment operator:

i += 1    -- Increment i by 1

Numeric expressions can be written using ordinary arithmetic notation: + and - for addition and subtraction, * for multiplication, / for division, parentheses for grouping, etc.

i = 2 + 3 * 4      -- 2 + 12 == 14
i = (2 + 3) * 4    -- 5 * 4 == 20
3 / 2              -- 1.5

There are also four additional assignment operators, -=, *=, /= and **= (the power or exponentiation operation). They work in a similar fashion to the addition operation += introduced above:

var x = 6
x -= 2          -- 4
x *= 3          -- 12
x **= 2         -- 144 (12 * 12)

Note that the / operator performs a floating point division. The div operator can be used for integer division, rounding down:

3 div 2   -- 1
-3 div 4  -- -1

In addition to numbers, some operators can be applied to other types of values, such as strings ("==" means equality):

"foo" + "bar" == "foobar"
3 * "a" == "aaa"

The table below introduces some additional operators. Operator precedence is described later in section Operators.

Operators Description Example
mod modulus (remainder) 7 mod 3 == 1
** power 2**100
== equality 1 + 2 == 3
!= inequality "cat" != "CAT"
< <= >= > order comparison 2.3 < 3
and or not boolean operators a >= 2 and b < 5

The if statement

The if statement supports multiple conditions using elif:

if i < 5
  WriteLn("i less than 5")
elif i > 10
  WriteLn("i greater than 10")
else
  WriteLn("i between 5 and 10")
end

The elif and else parts are optional, and there may more than a single elif part. The end keyword always marks the end of the if statement. In the above example, every code block contains only a single statement, but as a general rule, any number of statements can always be used where a single statement is valid.

The i variable is assumed to be defined outside the above fragment. Variables must be defined before they can be used in expressions.

Line breaks

Line breaks are used to separate statements and other syntactic constructs. In the example in the previous section, each WriteLn statement in the body of the if statement must be on a separate line. Alternatively, semicolons can be used whenever newlines can be used, so the beginning of the previous example could have been written equivalently like this:

if i < 5; WriteLn("i smaller than"); elif i > 10

We recommended usually avoiding the use of semicolons for the sake of consistency and readability. Thus they will not be used in this tutorial after this section.

A single logical line can also span several physical lines. A physical line break is always ignored after one of the following lexical items:

, ( [ = .
+ - * / div mod **
== != < <= > >= in is
or and to :

Thus this (somewhat contrived) example contains only a single logical line:

a[
  1 + i] =  -- Comments are ignored
    5 +
    6

The for, while, repeat and break statements

Perhaps the simplest type of loop is the for loop over an integer range (other uses for the for loop are explained later):

-- Count 0, 1, ..., 4.
for i in 0 to 5
  WriteLn(i)
end

Note that the range does not include the upper bound (5). The loop below is equivalent to the preceding for loop. The while loop allows controlling repetition with any boolean expression:

-- Count from 0 to 4 using a while loop.
var i = 0
while i < 5
  WriteLn(i)
  i += 1
end

If the while loop condition is false during the first iteration of the loop, the body will not be executed at all. The repeat-until loop is always executed at least once, until the end condition is true:

var reply
repeat
  reply = ReadLn()
until reply == "yes" or reply == "no"

A while loop with a True condition loops indefinitely. A break statement can be used to leave the loop and continue execution after the end of the loop. The break statement can be used to leave for, while and repeat loops.

var reply
while True
  reply = ReadLn()
  if reply == "yes" or reply == "no"
    break
  end
  WriteLn("Type 'yes' or 'no'.")
end
-- At this point, reply is either "yes" or "no".

The switch statement

The switch statement allows selecting from multiple cases depending on the value of an expression:

switch x.lower()
  case "yes", "y"
    WriteLn("Agree")
  case "no", "n"
    WriteLn("Disagree")
  case "maybe"
    WriteLn("Ambivalent")
  else
    WriteLn("Unknown")
end

In the fragment above, the value of the expression x.lower() (string x converted to lower case) is compared against several strings ("yes", "y", "no", "n" and "maybe") and if one of them matches, the related block of code is run. If none of the cases match, the else block is executed. The else block is optional: if it is missing, the statement behaves as if it was empty.

Each case may have one or more alternatives separated by commas. Each of the alternatives may be any valid expression. The switch statement cannot directly match against a range of values – an if statement is needed to match values between 1.2 and 2.3, for example.

Functions

The sub keyword is used to define functions:

sub Main()
  var name = AskName()
  Greet(name)
end

sub AskName()
  WriteLn("What's your name?")
  return ReadLn()
end

sub Greet(name)
  WriteLn("Hello, ", name)
end

The previous fragment defines three functions: Main, AskName and Greet. The Greet function takes a single argument (name). If a function takes two or more arguments, they have to be separated with commas. The AskName function returns a result using the return statement. If a function exits without an explicit return statement, it returns the value nil implicitly. Using return alone, without a return value, also implicitly returns nil.

Although the previous example does not illustrate it, arguments are always passed by value. The values are references to objects, and objects are not copied when passed as arguments to functions. Although functions return technically at most a single value, multiple values can be returned by returning an array of values (for more information, see section Array).

Anonymous functions

The sub keyword can be used without a function name to define an anonymous function within any expression. Anonymous functions may access variables defined in enclosing functions:

sub Main()
  var a = 5
  var f = sub ()
    a += 1          -- Refer to variable defined in the outer scope
    return a
  end
  WriteLn(f())    -- 6
  WriteLn(f())    -- 7
  WriteLn(a)      -- 7
end

Anonymous functions are first-class objects: they can be passed as arguments to functions, returned from functions, stored in composite objects, etc. The rules for the argument lists of anonymous functions are identical to those of ordinary functions.

Default argument values

This example defines a default value for the argument of the Greet function:

sub Main()
  Greet()     -- Display "hello, world"
  Greet("hi") -- Display "hi, world"
end

sub Greet(greeting = "hello")
  WriteLn(greeting, ", world")
end

The greeting argument is optional: if it isn't given, the default value is assumed. When calling a function with multiple default argument values, the arguments are filled from left to right and the last arguments with missing values are given the default values. The default value expressions are evaluated each time the function is called.

Functions that take an unbounded number of arguments are introduced later in section Functions with an arbitrary number of arguments.

Global variables and constants

Global variables are defined at the top level of a file:

var Global
var One = 1

sub Main()
  Global = 4
  WriteLn(One + Global)   -- Display 5
end

The const keyword defines a global constant:

const Name = "Alore"

Constants can be used like variables, but they cannot be assigned a new value after initialization. Unlike variables, constants may not be defined locally in functions.

Types and objects

Understanding the concepts type, object and value, and their relationships is fundamental to Alore programming. Let's start with a few definitions:

Primitive types include basic types such as integers, strings and floating point numbers. All types described in this section are primitive unless mentioned otherwise.

Types are also called classes. These terms are used more or less interchangeably. New types can be created by using class definitions. This is postponed to section Classes; this section only illustrates how built-in classes can be used.

Each type is either mutable or immutable (constant). Objects of immutable types cannot be modified after they have been created. Integer and string are typical immutable types, and array is a typical mutable type. Assignment copies only values, i.e. references. There may be several references to a single object. A garbage collector frees the resources consumed by unreferenced objects automatically.

Types are objects as well. Types are typically referred to by the names of their class definitions. For example, the Int type refers to integers and the Str type refers to strings.

The type objects and the basic functions described below are included in the std module. It is a special module that is always available without having to import it. It also contains utility functions such as WriteLn and ReadLn that have already been used in several examples.

Int

We have already seen several examples of using integers. Alore integers are arbitrary-precision, and there is no semantic difference between "short" or "long" integers:

2**200        -- 1606938044258990275541962092341162602522202993782792835301376

Integer literals are entered in base 10. The Int object can be used as a function (constructor) to convert values of other types to integers:

Int("-123")   -- -123
Int(2.34)     -- 2

Integers and strings cannot be mixed in arithmetic operations. The Int constructor is often used to convert Str objects to integers in arithmetic expressions:

1 + "2"       -- Error!
1 + Int("2")  -- 3

When converting strings to integers, the default base can be overridden:

Int("ff", 16)  -- 255 (integer representation of a hexadecimal string)

Float

Float objects represent real numbers. Internally they are 64-bit floating point numbers. Floating point numbers can be mixed with integers in arithmetic expressions, and the results of mixed operations are always Floats. Examples:

3 / 4            -- 0.75
1.5e2            -- 150.0 (scientific notation)
1.5**2           -- 2.25
4**0.5           -- 2.0
1 + 2.0          -- 3.0 (Float, not Int)
Float("-1.2")    -- -1.2

Str

Strings are immutable sequences of characters. Strings can be indexed using square brackets. The first character of a string has the index 0. Substrings of strings can be indexed using the : operator. The last index of a substring is always one past the last included character index.

""                -- Empty string
var a = "foobar"
a[3]              -- "b"
a[0:3]            -- "foo"
a[1:]             -- "oobar"
a[:2]             -- "fo"
a.length()        -- 6

Two consecutive double quotes are used to represent a double quote in string literals:

"""quoted"""      -- String "quoted" (in quotes)

Negative indices can be used to access string contents relative to the end of the string (-1 refers to the last character):

a[-1]            -- "r" (the last character of "foobar")
a[-2]            -- "a"
a[:-1]           -- "fooba"

Strings support concatenation and repetition (multiplication by integers):

"foo" + "bar"     -- "foobar"
3 * "a" == "aaa"  -- True

String objects can be compared lexicographically:

"cat" > "canine"  -- True

The in operator can be used for testing whether a string contains a substring:

"bc" in "abc"      -- True
"d" in "abc"       -- False

The Str constructor can be used to convert objects of most types to strings:

Str(13)           -- "13"
Str(1.2)          -- "1.2"
Str([1, 2, 3])    -- "[1, 2, 3]" (convert an array to a string)
Str(nil)          -- "nil"

Individual characters are represented as strings with only a single character. The Chr function can be used to build strings representing specific character codes, and the Ord function returns the numeric (usually Unicode) value of a character:

Chr(65)           -- "A"
Ord("A")          -- 65

String objects provide some additional useful methods. Two methods convert strings to upper and lower case characters:

"foo".upper()                -- "FOO" (convert to upper case)
"Foo".lower()                -- "foo" (convert to lower case)

The location of the leftmost instance of a substring can be queried using the find method:

"foobar".find("bar")         -- 3 (find the leftmost index of "bar")
"foobar".find("BAR")         -- -1 (not found)

You can construct string objects based on a format string using the format method. Formatting sequences between { and } are replaced with method arguments converted to strings using the desired formatting:

"{} weighs {0.0} kg".format("the dog", 13.72)  -- "the dog weighs 13.7 kg"
"{10:}".format("cat")      -- "       cat" (right align field)
"{-10:}".format("cat")     -- "cat       " (left align field)

The count method can be used to calculate how many times a characters occurs in a string:

"foo".count("o")             -- 2
"foo".count("x")             -- 0

You can check whether a string contains a prefix or a suffix:

"foobar".startsWith("foo")   -- True ("foo" is a prefix of "foobar")
"foobar".endsWith("bar")     -- True ("bar" is a suffix of "foobar")

You can also construct a copy of any string with leading and trailing whitespace characters removed:

"   foobar  ".strip()        -- "foobar" 

String objects also support replacing all instances of a substring with another string:

"foobar".replace("oo", "u")  -- "fubar"

The string module contains several additional functions that make working with strings easier. These functions are introduced later in this document.

Alore standard functions assume strings are encoded in Unicode (16-bit character codes), but string objects can be used to represent strings in various encodings, and even arbitrary binary data. In particular, 0-characters can be freely included in strings. The encodings module implements various character encodings and conversions.

Alore source files may be encoded in UTF-8, ASCII or Latin 1. UTF-8 is the default encoding. Any 16-bit Unicode characters can be encoded using UTF-8. This example contains a Russian word:

-- This source file uses the UTF-8 encoding
sub Main()
  var s = "Здравствуйте"
end

Note that the set of characters supported by WriteLn and similar functions depends on the current operating system and locale. Displaying non-ASCII characters might be limited to a narrow subset of Unicode. Consider this example:

-- This source file uses the UTF-8 encoding
sub Main()
  var euro = "€"
  WriteLn(Ord(euro))           -- Display 8364.
  WriteLn(euro)                -- Display € or ? depending on locale.
                               -- ? implies that the euro sign is not
                               -- supported by the current locale.
end

The euro sign can be displayed properly in, for example, POSIX compliant operating systems using a UTF-8 locale. This is the default in many Linux distributions.

An encoding declaration at the top of a file can be used to specify a different encoding than UTF-8. This example uses Latin 1 and converts a string to an ASCII representation using the Repr function:

encoding latin1

sub Main()
  var s = Repr("Buenos diás") -- Encode the string using ASCII
                              -- characters to make it displayable
  WriteLn(s)                  -- Display "Buenos di\u00e1s" (00e1 is the
                              -- hexadecimal character code for á)
end

Arbitrary character codes can also be directly included in string literals using the \uNNNN construct, even if the file encoding is not UTF-8. Note that backslash \ has no other special meaning and does not need to be escaped in string literals.

"\u1230"          -- Character code 4656 (hexadecimal 1230)
"\u000a"          -- Character code 10 (LF) 

Array

Arrays are composite objects that store sequences of references to objects. The contents and the length of an array object can be modified after creation. Array is not a primitive type.

Arrays with a specific length and contents can be generated by separating the items in the array with commas, and enclosing the list in brackets:

[2, 6 + 2, 5]         -- Create array with 3 Int items
["foo"]               -- Create an array with a single Str item
[]                    -- Create an empty array

The enclosing brackets can often be omitted when creating arrays with more than a single item. Arrays within arrays and arrays in function argument lists need to be surrounded by brackets or parentheses.

a = 1, [2, 3], 4      -- Construct an array with 3 items: Int, Array and Int
Fun(1, 2, [3, 4])     -- Call function with 3 arguments: Int, Int and Array

Arrays with a run-time computed length can be created with the multiplication operator *:

a = [0] * 100         -- Array with 100 items, initialized to 0
b = ["x", "y"] * 2    -- Array ["x", "y", "x", "y"]

Length of an array can be queried with the length method:

a.length()            -- 100

The indexing operator can be used to get and set array items. The index of the first item in an array is 0:

a[0] = "foo"          -- Set the first array item
a[1] = a[0] + "bar"   -- Get and set array item
a[0:2]                -- ["foo", "foobar"]
b[2]                  -- "x"

Negative indices refer to array items relative to the end of the array:

a = [1, 2, 3, 4]
a[-1]           -- 4 (the last item)
a[1:-1]         -- [2, 3]

The append, insertAt and removeAt methods can be used to add or delete array items:

a = ["A", "B", "C"]
a.append("x")       -- Add item to end: a == ["A", "B", "C", "x"]
a.removeAt(1)       -- Remove item: a == ["A", "C", "x"]
a.insertAt(2, nil)  -- Add item: a == ["A", "C", nil, "x"]

Arrays can compared for equality and lexicographic order:

[1, 2] == [1, 2]     -- True
[1, 2] < [1, 3, 1]   -- True
[1, 3] < [1, 2, 1]   -- False

Arrays can store references to any objects and a single array can store references to objects of several different types:

a = [1, "foo", [2, 3]]

Similar to strings, the in operator can be used with arrays for testing whether an object is contained within an array. Likewise, arrays also support the find and count methods:

if "foo" in a
  WriteLn("a contains the string 'foo'")
end
a.find("foo")    -- 1 (first item index that contains "foo")
a.count("foo")   -- 1 (number of "foo" items in an array)

You can use the + operator to concatenate arrays:

[1, 2] + [3, 4]    -- Result [1, 2, 3, 4]

The for statement can be used for iterating over all the elements of an array. The loop variable (i in the example below) receives all the items of the array in sequence, starting from the first item:

-- Print 1, foo and nil
for i in 1, "foo", nil
  WriteLn(i)
end

The assignment statement only modifies references of objects. In the example below, the variables a and b are made to point to the same object. When the object is modified using one of the references (b), the results can be read using the other reference (a).

var a = [1, 2]
var b = a
b[0] = 3
WriteLn(a)  -- Print 3, 2

Arrays can also be used in the left hand side of an assignment and initialization. It is customary to leave out the brackets in this context:

var a, b = 1, 2    -- a gets 1, b gets 2
var xy = "x", 3
a, b = xy          -- a gets "x", b gets 3
for x, y in [1, 2], [3, 4]  -- Expand items in for loop
  WriteLn(x, "/", y)        -- Write 1/2 and 3/4
end
a, b = MultiFunc() -- Expand multiple values returned by a function

As shown above, this is often used to return multiple values from functions: an array of values, potentially with mixed types, is returned, and the caller expands the result by assigning it to an array of destinations.

String objects provide two very useful methods for splitting strings into arrays and back again:

"foo  bar !".split()         -- ["foo", "bar", "!"] (split at whitespace)
"foo,bar,zar".split(",")     -- ["foo", "bar", "zar"]
"".join(["foo", "bar", "!"]) -- "foobar!"
", ".join(["foo", "bar"])    -- "foo, bar"

Functions with an arbitrary number of arguments

Functions may also accept an arbitrary number of arguments as an array, and the arguments passed to a function can be given in an array:

sub Main()
  WriteLines("first", "second", "third")
  var a = ["first", "second", "third"]
  WriteLines(*a)                        -- Equivalent to the previous call
end

sub WriteLines(*lines)
  for l in lines
    WriteLn(l)
  end
end

The program above displays twice the lines "first", "second" and "third" without the quotes. The function WriteLines accepts any number of arguments that will be stored as an array in the lines parameter, as indicated by the asterisk (*) before the name of the parameter.

In a similar fashion, a variable number of arguments can be passed to a function using an array, as seen on the last line of the Main function. The asterisk at the caller does not have to match that in the callee; the example below is valid:

a = ["second", "third"]
WriteLines("first", *a)

The asterisk can be mixed with default arguments and ordinary, fixed arguments. The caller of a function does not need to know which arguments have default values and which will be stored as an array – only the valid number of arguments and the valid values for arguments are relevant.

Command line arguments

The Main function of the main Alore source file optionally receives the command line arguments passed to the program. The arguments are provided as an array of strings:

sub Main(args)
  -- args is an array of command line arguments.
  for arg in args
    WriteLn(arg) -- Display all arguments
  end
end

The command line arguments are also available as the constant sys::Args.

Map and Pair

While Array objects only support indexing with integers, the Map type allows indexing with values of almost any type:

var m = Map("John": 34, "Mary": 24)
m["John"]                 -- 34 (value associated with key "John")
m["Jane"] = 50            -- Assign value
m["Peter"]                -- Error! (invalid index)
m.hasKey("Jane")          -- True
m[1, [nil, "x"]] = [2, 3] -- Array objects can be used as keys

A Map object can store an arbitrary mapping (a dictionary) between objects, implemented as a hash table. Like Array, Map is not a primitive type. Note that when using mutable objects like arrays as keys, the objects should not be modified after using them as keys, or you risk not being able to retrieve their values later.

More examples of using Map:

var m = Map()      -- Create an empty Map
m[12] = 56         
m["x"] = 66        -- Assign values
m.keys()           -- [12, "x"] (array of keys in an arbitrary order)
m.remove(12)       -- Remove a key/value mapping
m.values()         -- [66] (array of values)

The contents of a map can be iterated using the for loop. Each item in the iteration is an array [key, value]:

for k, v in m
  WriteLn(k, ": ", v)
end

The arguments given to the map constructor are Pair objects, immutable composite objects that represent exactly two values:

var p = "John" : 25     -- Create Pair object
p.left                  -- "John"
p.right                 -- 25

Boolean values

Instances of the Boolean type, True and False (defined in the std module) are the only values valid in boolean contexts, such as if or while conditions. Comparison and logical operators always return boolean values:

1 == 2   -- False
1 == 1   -- True

Symbolic constants

Global uninitialized constants are symbolic constants. These constants are automatically given unique values of type Constant. Symbolic constants can be compared for equality and they can be converted to strings to query their name.

const Foo        -- Define symbolic constant 'Foo'

sub Main()
  WriteLn(Foo)   -- Display Foo
end

The nil value

The reserved word nil refers to a special object that represents an uninitialized or an empty value. Variables are given the value nil before they are explicitly assigned any value.

var a
a == nil      -- True
WriteLn(a)    -- nil (uninitialized)

Unlike all other values, nil is not an instance of any type visible to the programmer. nil has no other special properties; in particular, it cannot be used as a boolean value.

Additional primitive types

A Range object represents a sequence of integer values. The range includes integers from the start value up to, but not including, the stop value. The to operator creates a range:

var r = 5 to 13      -- Create Range object representing 5, 6, ..., 12
r.start              -- 5
r.stop               -- 13
5 in r               -- True
13 in r              -- False

The in operator can be used to check if a value is within a range. Range objects are usually used in for loops to iterate over an integer range.

Types are objects as well. They are used to construct values of a specific type and to check if a value has a specific type using the is operator.

Int("23")            -- 23 (Int object)
23 is Int            -- True
23 is Str            -- False
Int is Type          -- True
Type is Type         -- True

Functions are objects that can be called. Like types and all other objects, they can be stored in variables or composite objects, passed as arguments to functions, etc.

var func = WriteLn    -- Functions are objects
func("hello, world")  -- Call WriteLn indirectly
WriteLn is Function   -- True

Operators

All Alore operators are included in the list below, from the highest to the lowest precedence, with each operator on the same line having the same precedence:

  1. . () [] (member access, function call, indexing)
  2. **
  3. - (unary)
  4. * / div mod
  5. + - (binary)
  6. to
  7. == != < <= >= > in is
  8. :
  9. not
  10. and
  11. or
  12. , (array constructor)

All operators except the exponentiation operator ** are left associative, i.e. a + b + c == (a + b) + c, whereas ** is right associative, i.e. a**b**c == a**(b**c).

Classes

Most non-trivial Alore programs implement some user-defined types or classes. These types can inherit from other user-defined types or non-primitive built-in types such as Array.

Class objects can be called to construct objects of the type. All objects that have a specific type are called instances of that type, including objects of primitive types.

Simple structured data

This example illustrates defining a simple class that includes two member variables name and occupation:

-- Definition of the Person class
class Person
  var name
  var occupation
end

sub Main()
  var p = Person("John Smith",  -- Construct a Person object
                 "journalist")  
  WriteLn(p.name)             -- John Smith
  p.name = "Mary Smith"       -- Change name
  WriteLn(p.name)             -- Mary Smith
  WriteLn(p is Person)        -- True
  p.address = "15 Juniper St."  -- Error! (undefined member)
end

The Main function constructs a Person object (an instance of the Person class) and accesses the name member variable. Class members are accessed using the dot operator. By default, class objects take one argument for each member variable that does not have an initializer when called (initializers are described later in this section).

The structure of classes and class instances is immutable. It is not possible to add new members to an object or a class after it has been created or defined.

Methods

Classes usually have member functions or methods in addition to member variables. Methods are defined with a syntax similar to ordinary functions, but the definitions are always within class definitions:

class Person
  var name
  var occupation

  sub show()
    WriteLn("Name: ", name)
    WriteLn("Occupation: ", occupation)
  end
end

sub Main()
  var p = Person("John Smith", "journalist")
  p.show()   -- Print 'Name: John Smith' and 'Occupation: journalist'
end

Other members of a class can be accessed within methods like ordinary variables. Methods receive a reference to the object they are associated with as a hidden argument called self, and the self keyword can be used to refer to this object.

Constructors and initialization

Default values for member variables can be defined by including initialization expressions with a syntax similar to initializing local or global variables. When an object is constructed, the initialized members are automatically given the default values.

The create member function acts as the constructor of a class. When the class object is called, the constructor is called and the arguments are passed to the constructor. The examples in the previous sections did not define constructors; in this case, a default constructor will be automatically defined. Initialized members are initialized before calling the constructor.

This example illustrates the use of initialized members and constructors:

class MyList
  var count = 0       -- Initialized member
  var array

  sub create(len)     -- Constructor
    array = [nil] * len
  end

  sub append(item)
    array[count] = item
    count += 1
  end

  sub get(index)
    return array[index]
  end
end

sub Main()
  var list = MyList(5)
  list.append("John")
  list.append("Mary")
  WriteLn(list.get(0))   -- John
  WriteLn(list.get(1))   -- Mary
end

Note that this example violates encapsulation, since internal state information is visible outside the class. We later improve the code by using private member variables to achieve better encapsulation.

Object identity

Class instances can be compared for equality. By default, this comparison is based on object identity – only references to the same object are equal to each other:

class Person
  var name
end

sub Main()
  var p1 = Person("John")
  var p2 = Person("John")
  var p3 = p1

  WriteLn(p1 == p2)    -- False
  WriteLn(p1 == p1)    -- True
  WriteLn(p1 == p3)    -- True
end

This behavior can be changed by overloading the == operator, as described later in section Operator overloading.

Constant members

It is possible to define read-only member variables by using the const keyword in a member variable definition:

class Person
  const name
  const occupation
  const numberOfLimbs = 4
end

sub Main()
  var p = Person("Tracy", "ornithologist")
  p.name = "Mary"  -- Error!
end

These member constants cannot be assigned new values after object construction, but constant members of the self object can be modified within the create method.

Private members

Member definitions can be made private by giving the private modifier:

class MyList
  private var count = 0    -- Private member variable
  private var array

  sub create(len)
    array = [nil] * len
  end

  sub add(item)
    array[count] = item
    count += 1
  end

  sub get(index)
    return array[index]
  end
end

sub Main()
  var list = MyList(5)
  list.add("John")
  list.add("Mary")
  WriteLn(list.array)   -- Error!
end

Private members can only be accessed by methods defined in the same class that defines the private members. In addition, only private members of the self object can be accessed, either as "self.member" or simply as "member". For example:

class Example
  private sub member()
    ...
  end

  sub method(x)
    x.member()        -- Error! Invalid even if x has type Example or if x == self
    self.member()     -- Ok, since using self
    member()          -- Also ok (implicit self)
  end
end

Inheritance and polymorphism

Each class can inherit from a single another class using the is keyword after specifying the class name. The class after "is" is the superclass of the defined class, which is called a derived class. Derived classes contain all the members of the superclass and any additional members defined in the derived class. This example defines a base class Shape and two derived classes Square and Circle:

import math -- Use the math module (contains the definition of Pi)

-- Base class with a default constructor create(centerX, centerY)
class Shape
  const centerX
  const centerY
end

-- Derived class
class Square is Shape        -- Inherit from Shape
  const width

  sub create(x, y, width)
    super.create(x, y)       -- Call superclass constructor
    self.width = width       -- self refers to the current object
  end

  sub area()
    return width**2
  end
end

-- Another derived class
class Circle is Shape
  const radius

  sub create(x, y, radius)
    super.create(x, y)
    self.radius = radius
  end

  sub area()
    return Pi * radius**2
  end
end

sub Main()
  var shapes = Square(4, 5, 6), Circle(2, 3, 5)
  for shape in shapes
    WriteLn(shape.area())
  end
end

If a class definition does not specify a superclass, it defaults to Object. All classes except Object have a superclass. The Object type is defined in the std module with almost no functionality of its own: Object instances can only compare themselves with another objects using the == and != operators based on object identity. Inheritance from primitive types (Int, Str, etc.) is not supported.

If a derived class defines a member with an identical name as a public member in the superclass, the definition in the derived class overrides the definition in the superclass. The original definitions of members overridden in a derived class can be accessed as super.member. All references to overridden members, even in superclasses, that are not prefixed with the super keyword, refer to the definition in the subclass instead of the original definition.

Members accessed using the dot operator are determined dynamically based on the type of the value. The Main function in the above example calls the area method of two values of different types. Calls such as this are called polymorphic and the objects they function on do not need to have a common ancestor class provided that the methods have compatible calling conventions.

Accessors

Each member variable has two implicit methods associated with it: a getter and a setter. When the value of a member variable is being read or modified, the operation is transparently performed by calling the getter (when reading) or setter (when modifying) methods. Each member variable has a default getter and setter method that simply read and set the value of the physical variable associated with the member – i.e. the member variable behaves like an ordinary variable. Member constants only have the getter method.

These default getters and setters can be overridden in subclasses by specifying special methods as shown in the fragment below:

sub variable          -- Getter
  ...
end
sub variable = param  -- Setter
  ...
end

The param behaves like a function argument, and it refers to the new value assigned to the member. Its name is not visible outside the setter. The getter must return a value for the member, while the setter never returns a value (other than the implicit nil value).

This example overrides the inherited default getter and setter methods:

class Base
  var value = nil           -- Member variable, default getter and setter
end

class Monitor is Base
  sub value                 -- Getter for value
    WriteLn("read value")
    return super.value      -- Call original getter
  end

  sub value = new           -- Setter for value
    WriteLn("set value")
    super.value = new       -- Call original setter to set the value
  end
end

sub Main()
  var m = Monitor()         -- Do not call setter in initialization
  m.value = 2               -- Display 'set value'
  WriteLn(m.value)          -- Display 'read value' and 2
end

The getters and setters defined in a superclass can be accessed by prefixing the member variable with the super keyword, as shown in the previous example.

It is also possible to define only the getter, or getter and setter, methods for a member, without a corresponding member variable or constant definition, as seen in the example below. These implicit member variables can be used like ordinary member variables.

class Implicit
  sub member   -- Member constant whose value is always 5
    return 5
  end
end

Bound methods

Methods themselves are objects. They can be used like ordinary function objects. These objects are called bound methods since they implicitly contain a reference to the object they are related to. Example:

class Printer
  var message

  sub print()
    WriteLn(message)
  end
end

sub Main()
  var p = Printer("hello, world")
  var method = p.print
  method()              -- Call the print method, print 'hello, world'
end

Special method names

It is possible to enable class instances to be used with some built-in functions and types by defining specific methods whose names begin with an underscore. For example, the Str constructor tries to call the _str method to convert an object to a string:

class MyList
  ... as defined previously ...

  sub _str()
    return "MyList"
  end
end

sub Main()
  var list = MyList(4)
  WriteLn(Str(list))      -- Write 'MyList'
end

The Int constructor calls the _int method in a similar fashion to convert an object to an integer. The underscore in these methods names is only a convention and these methods have no special properties. Underscore is conventionally used as the first character in class member names who are public but are supposed to be accessed only in specific places in the code. Other programming languages support other member visibilities in addition to public and private, such as protected, module-local, friend, etc. Instead of supporting complex visibility specification features, Alore groups all of these under a simple informal convention, which essentially says that do not access members with underscores in their names unless you are given permission to do so by the designer of the class.

Method names that start with underscores are also used to implement operations such as addition, subtraction and indexing. This topic is discussed later in the section Operator overloading.

Interfaces and expanding the for loop

An interface defines a set of member names and their behaviors. An example of a simple interface is the _str interface: a class implements this interface if it defines a public _str method that takes no arguments and returns a string representation of the object. Interfaces are implicit: they are not visible in Alore code, other than potentially in comments. Therefore any class may implement any number of interfaces, but the interfaces may not be visible when observing the class definition.

Objects conforming to the iterable interface can be iterated using for loops. They must define an iterator method that returns an object that conforms to the iterator interface. The iterator interface contains hasNext and next methods. As an example, Array and Range objects provide the iterator method.

We can define the for loop in terms of an equivalent while loop to clarify the iterable and iterator interfaces:

for i in a
  ... code ...
end

can thought as equivalent to

var e = a.iterator()
while e.hasNext()
  const i = e.next()
  ... code ...
end

if the name e is replaced with a name that does not occur anywhere else in the program.

A polymorphic function or method accepts all objects that support a specific interface. In the example below, Show is a polymorphic function that displays the contents of any object that implements the iterable interface. The example also illustrates creating an iterable class.

class DownTo
  private var index
  private var min

  sub create(max, min)
    index = max + 1
    self.min = min
  end

  -- Return iterator.
  sub iterator()
    return self
  end

  -- Are there any items left in the iteration?
  sub hasNext()
    return index > min
  end

  -- Return the next item in the iteration.
  sub next()
    index -= 1
    return index
  end
end

-- Show an iterable object.
sub Show(o)
  for i in o
    WriteLn(i)
  end
end

sub Main()
  Show(DownTo(5, 1))      -- Print 5, 4, 3, 2 and 1.
  Show(["a", "c", "b"])   -- Print a, c and b.
end

Operator overloading

Classes may define methods that are called whenever operators such as + or [] are applied to instances of the class. This is called operator overloading. This applies to all types: even primitive types such as Int and Str have methods that implement the basic arithmetic and comparison operations, etc.

In the example below, we define methods that are called when the indexing operator [] is used. There are separate methods for indexed reads and writes. MyArray instances behave a bit like Array objects:

class MyArray
  private var array

  sub create(length)
    array = [nil] * length
  end

  sub _get(index)
    return array[index]
  end

  sub _set(index, new)
    array[index] = new
  end
end

sub Main()
  var a = MyArray(5)
  a[2] = "John"
  WriteLn(a[2])           -- John
end

Excessive use of operation overloading may result in programs that are confusing and difficult to understand. A good guideline is to overload an operator only if its overloaded behavior strongly resembles the built-in behavior of the operator. The add operator, for example, should be reserved for arithmetic addition and concatenation.

The table below introduces the method signatures of overloadable operators, except for a few operators with special calling conventions that are described later. The operands x, y and z can be replaced with arbitrary expressions (but note that you may have to add parentheses to ensure the correct evaluation order).

Operation Equivalent method call
x + y x._add(y)
x - y x._sub(y)
x * y x._mul(y)
x / y x._div(y)
x div y x._idiv(y)
x mod y x._mod(y)
x ** y x._pow(y)
x == y x._eq(y)
x != y not x._eq(y)
x < y x._lt(y)
x >= y not x._lt(y)
x > y x._gt(y)
x <= y not x._gt(y)

The _add and _mul methods of Int and Float objects call the _add or _mul method, respectively, of the argument if the argument type is not supported by the method. For example, 3 * "x" is evaluated as "x"._mul(3) (the result being "xxx"), since the integer _mul method does not know how to deal with strings.

The in, unary minus, indexing and call operations behave slightly differently from the binary operators in the previous table:

Operation Equivalent method call Notes
x in y y._in(x) Order of operands reversed
-x x._neg() Unary operation, no arguments
x[y] x._get(y) Indexed read
x[y] = z x._set(y, z) Indexed store
x(y, z) x._call(y, z) Any number of arguments may be used

There are no methods specific to +=, -=, etc. They are mapped to the corresponding methods for ordinary operators such as + or -. Therefore the following three lines of code are equivalent (if x provides _add):

x += y
x = x + y
x = x._add(y)

Boolean operators and, or, and not cannot be overloaded. The comparison operators and the in operator must return a boolean value (True or False). Only three methods ( _eq, _lt and _gt) are sufficient for evaluating all comparison operations, since the operators are expected to follow the rules listed below:

  1. (x != y) is equivalent to (not x == y)
  2. (x >= y) is equivalent to (not x < y)
  3. (x <= y) is equivalent to (not x > y)

Modules and scopes

Most of the example programs in previous sections have consisted of a single source file that does not use any external modules other than the std module which is always available. Almost all real-world programs import some additional modules using import declarations at the start of a source file, since the std module provides only fairly basic services. This example uses two built-in modules, math and io:

import math, io

sub Main()
  var f = File("output.txt", Output)
  for i in 0 to 5
    f.writeLn(i, " ", Cos(i))
  end
  f.close()
end

The File class is provided by the io module and the Cos function by the math module. These definitions cannot be accessed in a Alore source file without including the proper import declarations.

The previous example illustrates the structure of an Alore program: first import declarations, then one or more definitions of functions, global variables, constants or classes (or statements). The above example has a single import declaration that imports two modules and a single function definition, Main. Likewise, each module contains functions, classes, variables and constants related to a specific task or topic grouped together. When a module is imported, its contents are made available in the rest of the source file.

Many Alore programs define some modules of their own. This way a program can be divided into smaller components that are easier to manage and potentially reuse. Modules can optionally be divided into multiple source files.

A module is defined by creating a directory that contains one or more Alore source files. Typically the directory is created below the directory that contains the main Alore source file. The name of the directory must be the same as the name of the module, and each source file in the module must start with a module header. An example program consisting of a single module "message" and the main source file is illustrated below:

main.alo:
  import message

  sub Main()
    Show("Hello!")
  end

message/a.alo:
  module message

  private const Hili = " *** "
  
  sub Show(msg)
    WriteLn(Hili, msg.upper(), Hili)
  end

The main source file imports the module message that is defined in the other source file. The Show function, like all other public functions, variables, constants and classes, can be used in any file that imports the module. All definitions are public unless they are preceded by the keyword private. The constant Hili is an example of a private constant.

Scopes and name collisions

Each module defines a separate scope. Names in different scopes may overlap. If modules foo and bar both define the name Func and only one of the modules is imported in each source file, the correct name Func will always be referenced. But if a file imports both modules, the name must be prefixed by the module name and the scope resolution operator :: (two colons):

import foo
import bar

sub Main()
  foo::Func()
  bar::Func()
  Func()      -- Error: Ambiguous!
end

The scope resolution operator can be used even if there is no ambiguity to make it clear which module a name belongs to or to avoid future name collisions, i.e. multiple definitions having the same name.

Another example of avoiding a name collision is in the program below:

sub Write(s)
  -- do something with s
end

sub Main()
  Write("foo")     -- Refers to the Write in this module
  std::Write("foo")  -- Refers to the Write in the std module
end

The names defined in the current module take precedence over names defined in other modules. All names in a module are equivalent; they are visible in every file of a module, and even before their definition in a file. This means that this example is valid:

sub Main()
  Hello()
end

sub Hello()
  WriteLn("hello, world")
end

Another case of name collision is possible between class members and global names defined in the same module. In this case, the global names can be accessed by prefixing a name only with the scope resolution operator. Since class members usually start with a lower case letter and global names with an upper case letter, this type of name collision is rare.

var Name

class Foo
  var Name

  sub Bar()
    Name = 1   -- Refers to the member variable
    ::Name = 2  -- Refers to the global variable
  end
end

Finally, local variables take precedence over member variables or global variables with the same name. Member variables can be accessed using the self.member notation even if there is a local variable with the same name.

Hierarchical modules

Alore supports subdividing modules into submodules. The name of a submodule has the form main_name::sub_name, where main_name is the name of a module. For example, a submodule of the module message could be named message::french. A submodule is located in a subdirectory of the main module, i.e. message/french in the previous example.

Each submodule is a separate module and is not directly related to any main modules. The main modules do not even have to exist, i.e. their directories do not have to contain any source files. When importing a main module, for example import message, the submodules are not imported automatically. They can be imported in a similar way, for example import message::french.

More than a single level of subdivision are supported, for example company::product::core. We recommend that the number of subdivisions should be kept at fairly low levels, since navigating deep directory hierarchies can be tedious. Hierarchical modules can be used to avoid name collisions. For example, instead of defining a module code in a product acme, it might make sense to define the module as acme::code, since it is conceivable that another module named code could be defined in the future, causing ambiguity.

Variables and other names defined in hierarchical modules can be accessed from the files that import them also by specifying the whole module name as a prefix or only a part of it, at least enough to make the name unambiguous. So acme::code::Foo can be accessed as code::Foo if code::Foo is unambiguous. The module name can be shortened only by dropping some of the initial components. Thus acme::Foo is not a valid abbreviation for acme::code::Foo.

Exceptions

Many things can go wrong when a program is run. When creating a file, for example, we may not have a write permission to the directory we want to create the file in, the disk may be full, or the disk may be faulty. These kinds of conditions cause an exception to be raised. The try statement is used to catch exceptions raised in the body of the statement:

import io

sub Main()
  try
    var f = File("file.txt", Output)
    f.writeLn("hello, world")
    f.close()
  except IoError
    WriteLn("error")
  end
end

In the example above, any of the three lines within the try statement may raise an IoError exception. The except line near the end of the statement catches all of these errors. The last WriteLn statement is executed only if an IoError exception was raised in the body of the try statement.

The innermost enclosing try statement with the matching exception type always catches a raised exception. If no matching try statement can be found in the function that caused the exception to be raised, the function that called the function is consulted, and so on until finally an error message will be displayed if no except block matches the exception type.

Exception types such as IoError are classes that descend from std::Exception. Each except clause catches exceptions of the specified type and all its subclasses. Therefore except std::Exception catches all exceptions. The std:: prefix can be omitted if there are no other variables with the name Exception.

The std module defines several basic exception types. Many of these can be raised in expressions or built-in functions:

1 + "foo"     -- Raise std::TypeError
1 / 0         -- Raise std::ArithmeticError
Chr(-1)       -- Raise std::ValueError

Defining and raising exceptions

New exception types can be defined by inheriting from another exception type, and the raise statement is used to raise an exception explicitly:

sub Main()
  try
    ErrorFunction()
  except e is MyError   -- Bind the exception object to a variable
    WriteLn("caught MyError with message: ", e.message)
  end
end

class MyError is Exception   -- Define exception class
end

sub ErrorFunction()
  raise MyError("problem")
end

The std::Exception class takes an optional argument, the message. The exception class MyError is defined in the above example to inherit the constructor. The message "problem" is associated with the exception instance. The Main function catches the exception and stores a reference to it in the e variable. The "variable is" part of the except clause is optional.

The finally block

The optional finally block in a try statement is always executed, even if an exception was raised within the block. If an exception was raised and not catched within the try block, the exception will be re-raised after the finally block has been executed, provided that the finally block itself did not raise any exceptions.

The example below writes the text "hello, world" to a file and closes the file afterwards, even if any exceptions were raised. IoError and ResourceError exceptions are caught:

sub Main()
  try
    var f = File("file.txt", Output)
    try
      f.writeLn("hello, world")
    finally
      f.close()
    end
  except IoError
    WriteLn("io error")
  except ResourceError
    WriteLn("resource error")
  end
end

Both except and finally blocks cannot be mixed within a single try statement. Multiple except blocks can be defined, and they are evaluated in the order of their definition. Only a single except block will be activated for a single exception; after the block has been executed, the execution continues after the try statement.

The standard library

This section gives a short overview of some commonly-used standard library modules. The Alore Library Reference is a complete reference to all the standard library modules.

std module

The std is a special module that is always implicitly imported in each Alore source file. It contains the primitive types, collection types, exceptions, several utility functions and the several constants, including True and False.

The primitive types defined in the std module are Int, Str, Float, Boolean, Function, Type, Constant, Pair and Range. The collection types include Array and Map.

Usage of many of the functions in the std module is illustrated below:

WriteLn("num=", 4)   -- Write objects to standard output, followed by a line
                     -- break
Write("text")        -- Write objects to standard output without a line break
ReadLn()             -- Read a line of text from the standard input
Repr("foo")          -- """foo""" (convert to string, works with all types)
Chr(65)              -- "a" (parameter is Unicode character code)
Ord("a")             -- 65 (Unicode character code of a character)
Hash(5)              -- Calculate a hash value of an object
Min(3, 2)            -- 2 (smallest of two values)
Max(3, 2)            -- 3 (largest of two values)
Sort([4, 3, 5, 1])   -- [1, 3, 4, 5]
Exit(1)              -- Exit the program by raising ExitException

The container types Array and Map were described earlier in this document.

The std module also contains these constants:

Tab                          -- Tab character ("\u0009")
Newline                      -- Line break
CR                           -- Carriage return ("\u000d")
LF                           -- Line feed ("\u000a")

This class hierarchy contains all the exception classes in the std module:

Exception
  ValueError
    TypeError
    MemberError
    ArithmeticError
    IndexError
  ResourceError
    MemoryError
  RuntimeError
  IoError
  InterruptException
  ExitException

All exception classes are descendants of the std::Exception class.

io module

The io module contains the File and Stream classes that allow reading and writing of files and similar objects (streams). The simplest way of accessing the contents of a file is enumerating the lines of the file using a for loop:

import io

sub Main()
  var f = File("file.txt")  -- Open for reading
  for s in f                -- Enumerate over the lines in the file
    if s.length() < 5
      WriteLn(s)            -- Display lines shorter than 5 characters
    end
  end
  f.close()                 -- Close file
end

The write and writeLn methods are used to write to streams:

var f = File("out.txt", Output)  -- Open for writing
f.write("abc", 1)                -- Write without a line break "abc1"
f.writeLn("line")                -- Write line
f.close()

The std::WriteLn function is equivalent to StdOut.writeLn. StdOut, StdIn and StdErr refer to file objects representing the standard output, input and error streams, respectively.

The read, eof and readLn methods provide finer control of reading from files, whereas the readLines methods returns the contents of a stream as an array of lines:

var f = File("file.txt")  
var s = f.read(5)         -- Read 5 characters as a string
while not f.eof()         -- Copy the rest of the contents to 
  WriteLn(f.readLn())     -- standard output
end
f.close()

var l = StdIn.readLines()

The std::ReadLn function is equivalent to StdIn.readLn.

The io module also defines classes TextFile and TextStream for accessing encoded text streams.

string module

The string module contains utility functions for dealing with string objects:

IntToStr(255, 16)  -- "ff" (255 in hexadecimal)
IsWhitespace(" ")  -- True
IsWhitespace("x")  -- False
IsLetter("a")      -- True
IsLetter("3")      -- False
IsDigit("5")       -- True
IsWordChar("a")    -- True
IsWordChar("6")    -- True
IsWordChar(".")    -- False

math module

The math module contains several useful mathematical functions and the constants Pi and E (Euler's number):

Sqrt(4)           -- 2.0 (square root)
Abs(-3)           -- 3 (absolute value)
Sin(Pi / 2)       -- 1.0
Cos(Pi / 2)       -- 0.0
Tan(Pi / 4)       -- ~1.0
ArcSin(1.0)       -- ~1.571 (Pi / 2)
Exp(2)            -- ~7.39 (E**2)
Log(E**2)         -- 2
Round(1.5)        -- 2.0 (round to nearest integer)
Floor(1.7)        -- 1.0 (round down to nearest integer)
Ceil(1.7)         -- 2.0 (round up to nearest integer)
Trunc(-1.7)       -- -1.0 (round towards zero)

os module

The os module allows accessing basic operating system services:

Remove("file.txt")            -- Remove file or an empty directory
Rename("old.txt", "new.txt")  -- Rename file or directory
MakeDir("dirname")            -- Create directory
ChangeDir("../dir")           -- Change current directory
CurrentDir()                  -- Get current directory
Sleep(0.2)                    -- Wait 0.2 seconds
System("dir")                 -- Execute shell command
ListDir(".")                  -- Read the contents of a directory
IsDir("name")                 -- Does the path refer to a directory
IsFile("name")                -- Does the path refer to an ordinary file
IsLink("name")                -- Does the path refer to a symbolic link
DirName("foo/bar/file.txt")   -- "foo/bar"
BaseName("foo/bar/file.txt")  -- "file.txt"
FileExt("foo/bar/file.txt")   -- ".txt"
JoinPath("foo", "bar")        -- "foo/bar"
GetEnv("PATH")                -- Get the value of an environment variable
SetEnv("VAR", "value")        -- Change or add an environment variable
var s = Stat("file.txt")      -- Get file properties 
  s.size, s.isFile, s.isDir,
  s.isLink, s.modificationTime,
  s.accessTime, s.isReadable,
  s.isWritable

bitop module

Bitwise operations are defined in the bitop module:

And(12, 6)        -- 4 (bitwise and)
Or(12, 6)         -- 14 (bitwise or)
Xor(12, 6)        -- 10 (bitwise exclusive or)
Neg(12)           -- -13 (bitwise complement)
Shl(12, 2)        -- 48 (shift left)
Shr(12, 2)        -- 3 (shift right)

reflection module

The reflection module allows dynamically manipulating and querying objects. The Type class is also useful.

TypeOf(1)            -- Get type of object (std::Int)
GetMember(o, "x")    -- Get member x of object o
SetMember(o, "x", 2) -- Set member x of object o
HasMember(o, "x")    -- Does an object have a member?

random module

You can construct pseudo-random integers and floating point numbers using the random module:

Random(5)         -- Random integer in range 0, 1, ..., 4
RandomFloat()     -- Random float (>= 0 and < 1)
Seed(n)           -- Initialize random number generator

Further reading

Refer to the Alore Library Reference for details of the modules described above and information on other Alore modules, such as: