Introduction

Welcome to the Feral Language's User Manual. Using this manual, you will be able to understand the usage of the language with relevant description and examples.

Feral is an interpreted, procedural, general purpose programming language which is fundamentally developed to write scripts in a very simple and efficient manner. It is most suitable for this use case because of ease of use and simplicity.

By design, Feral is a minimalistic language. This is not a shortcoming though. As a matter of fact, it supports some interesting concepts like first-class citizenship of virtually everything - variables, imports, functions, and even structures. But more on that later. Feral is meant to be small since its main purpose is scripting which usually does not require a huge set of complex features and hence, it's a rather minimal language, focusing primarily on providing decent performance with ease of use and extensibility.

This book is made to guide people who want to understand Feral and perhaps use it in their daily life. The book shall be the holy grail for this language as it is meant to go over every feature of the language.

Note that we will not dive deep in the technical details of the language unless required, instead, focusing on the usage of the language by the user.

This guide also intends to go over the standard library of the language with some examples.

Anyway, let's begin with the installation of the language, and start using it!

Installation

First of all, to install Feral, your system must meet the prerequisites mentioned in the README file here. The basic installation steps are given in that README, but this chapter will describe it to a bit more extent. Feel free to skip this if you are satisfied with the README's installation procedure.

Compiler and Virtual Machine

For installing the language compiler, first clone the official GitHub repository: Feral-Lang/Feral.

git clone https://github.com/Feral-Lang/Feral

Then, cd into the directory, create a build directory, cd into that, run cmake .., and finally run make install. That will build and install the language interpreter, along with its dynamic libraries.

cd Feral && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make install

Note that you can also specify number of CPU cores using make -j<number of cores>. This will greatly improve the build time of the project. For example, cmake .. && make -j8 install

This will generate the Feral libraries and binaries (as well as the required standard libraries) which can be used to execute Feral code. The binary which we will use is called feral and it should be generated in build/bin/ directory of the repository (assuming no PREFIX_DIR is set).

You can also install ccache to speed up the build process. CMake will autodetect and use it if it finds it.

At this point, you may also want to add the $PREFIX_DIR/bin directory in your system's $PATH variable to directly use feral command from any directory. Otherwise, you must execute feral as $PREFIX_DIR/bin/feral (replace $PREFIX_DIR with actual value).

To run the test suite, execute the command $PREFIX_DIR/bin/feral testdir tests in the Feral repository directory.

CMake Environment Variables

$CXX

This variable is used for specifying the C++ compiler if you do not want to use the ones auto decided by the script which uses g++ by default for all operating systems except Android and BSD, for which it uses clang++.

For example, to explicitly use clang++ compiler on an ubuntu (linux) machine, you can use:

CXX=clang++ cmake .. && make install

$PREFIX_DIR

This variable will allow you to set a PREFIX_DIR directory for installation of the language after the build.

NOTE that once the script is run with a PREFIX_DIR, manually moving the generated files to desired directories will not work since the Feral's codebase uses this PREFIX_DIR internally itself.

Generally, the /usr or /usr/local directories are used for setting the PREFIX_DIR, however that is totally up to you. Default value for this is the directory /usr/local.

The script will create these directories with respect to PREFIX_DIR:

  • build/bin/feral -> $PREFIX_DIR/bin/
  • build/lib/feral -> $PREFIX_DIR/lib/
  • include/feral* -> $PREFIX_DIR/include/

An example usage is:

PREFIX_DIR=/usr/local cmake .. -DCMAKE_BUILD_TYPE=Release && make install

Hello World!

A simple hello world program in Feral can be written as:

let io = import('std/io');
io.println('hello, world');

Save the code in a file named, say hello.fer, and run it using the feral binary which we built in the installation document. Assuming that no PREFIX_DIR was set, the binary would be created in the build/bin/ directory of the cloned repostory. Hence, the command to run our script would be:

./build/bin/feral hello.fer

This will output hello world on the display.

Congratulations! You have successfully written your first program in Feral!

Additional Notes

The io.println function adds a new line at the end of each call, so we did not have to write a new line character (\n). But if we want to explicitly write new line characters wherever required without the function doing that internally, we can use the io.print function for that.

For example, if we want to display multiple statements without newline, using print:

io.print('first line');
io.print('second line');

The output would be:

first linesecond line

To correct this, we will add a new line at the end of the first print call.

io.print('first line\n');
io.print('second line');

which then gives us the correct output:

first line
second line

One more important thing to add here is that print and println can take any number of arguments (but at least one for print) by separating each argument with a comma (,). So, something like this is possible:

io.println('Hello, ', 'world');

Finally, please remember that for using print(ln), the io module must be loaded. We will talk about modules later, but from now on, it will be assumed that wherever print(ln) is used, the io module has been already imported.

Conclusion

The print function is different from println in that it will not automatically add new line character at the end. Also, the print function requires at least one argument, whereas the println function requires no argument - in which case, it will simply enter a new line. For all other intents and purposes, the print and println functions are identical.

Next, we are going to dive deeper in the language and understand Feral's variable system.

Variables

What are Variables

Variables are pretty much aliases that the programmers make for using memory to allocate some data. Obviously, we do not want, for our sanity's sake, to use memory addresses directly for accessing our data. That is really complicated, hard to remember, and an absolute nightmare to maintain. So, we instead assign these memory locations, containing our data, to names which we can use in our programs as needed.

Clearly, variables are a crucial part of a programming language, and hence, Feral has it as well.

Creating a variable is really easy. For example, if we want an integer data, say 20, to be stored in memory (and be accessible later), we can create a variable for that, say a, and use that when required. To do this, the variable can be created directly as:

let a = 20;

Since Feral is a dynamically typed language, we need not mention the data type of the value that we want to store manually. We can then use it later on, say in println, to print the value of this variable. To do that, we can do the following:

let io = import('std/io');
let a = 20;
println(a);

The will produce the output:

20

Variable Reassignment

We can also reassign the variables to different values later on. However, we need to remember that the data type of that variable cannot be changed throughout its lifetime. So, for example, if we create a variable a with value 20, we can change it later to, say 40, but we cannot change it to 'hi'. If we do something like that, the interpreter will throw an error.

For example, if we write this program:

let a = 20;
a = 'hi';

and execute it, the interpreter will throw the following error:

/root/tmp.fer 2[3]: error: assignment requires type of lhs and rhs to be same, found lhs: int, rhs: string; to redeclare a variable using another type, use the 'let' statement
x = 'hi';
  ^

Do note that in error message, Feral also provides us with a way to use same variable name for a different type: variable redeclaration. Using variable redeclaration, we can recreate a variable with an existing name. The old variable will cease to exist and the new variable will replace it.

For example,

let a = 20;
let a = 'hi';

On the second line, a is redeclared as a str (with value 'hi'). Now you can no longer assign an integer to it.

Although the language is dynamically typed, it does impose some restrictions on it. We will understand more about the types in Feral in the next chapter - Data Types.

Variable Scopes

The scope of a variable basically defines its lifetime - when it is created to when it is destroyed. Feral, like many other languages, uses braces to define chunks, or blocks, of code. Any variable that is created, has its lifetime bound to this block. It will not be accessible outside this block.

For example:

let a = 20;

is globally scoped - it is not inside any set of braces, which means that it can be globally used anywhere and once created, it will not be destroyed until the end of the program (it will be available exactly from the location where it is created).

On the other hand, if we declare the same variable as:

{
	let a = 20;
}

it will be accessible inside the set of braces (albeit after the creation - declaration of the variable), but not outside them.

So, if we use the variable outside this set of braces (scope), say as follows:

{
	let a = 20;
}
io.println(a);

we will be greeted with the following error:

/root/tmp.fer 5[12]: error: variable 'a' does not exist
io.println(a);
           ^

Of course, if we create another variable named a with some data outside the scope, that would work just fine. So something like this works:

{
	let a = 20;
}
let a = 'hi';
io.println(a);

which will produce the following output:

hi

Note that we can choose a different data type even when the variable name is same. The reason being that even though having same names, both of the declared variables are actually totally different. The first one, having data 20, no longer exists after the scope, therefore, the second one, having data hi, is a new variable declaration.

Variable Naming

One last important thing about variables is their naming. Feral defines specific rules based on which you can name variables, quite similar to most other languages. These rules are that variable names:

  1. Must begin with an alphabet (irrelevant of the case) or underscore
  2. Can contain numbers anywhere except the first character
  3. Cannot contain any symbol other than alphabets, numbers, and underscores.

Well, that is basically how variables, their reassignment, and their scopes, work. Not much to learn or understand and pretty easy - which is the goal!

Next up, we'll understand the concept of data types and see some of the fundamental data types in Feral.

Data Types

The data we use and store always has some type. The computer does not really need it, but we do because we don't want to work in binary. Data types are abstractions over binary sequences which define what kind of data will a variable using a said data type will contain, and how will that data be represented in memory.

If you have prior experience in programming, you may know very well about this. Data types are ubiquitous across programming and are virtually essential for writing software. Some common data types are:

char (character)
int (integer)
float (floating point)
bool (boolean)
string (sequence of characters)

In Feral, the char data type does not exist as even a single character, in Feral, is a string. Therefore, all these data types except char are fundamental types in Feral.

However, Feral is a dynamically typed language. This means that when we create variables, we do not provide types for the variable. Instead, these are deduced by the interpreter at run time based on the value that we provide to the variable. There are some pros and cons to this method. The biggest pro being that writing code is quicker, while the biggest con is that the code may be difficult to understand at a glance.

For example, if we write something like:

let s = 'hi';
let i = 5;

Then, s is a variable which is deduced by the interpreter to be of type string because the expression hi is a string, whereas i is a variable that is deduced by the interpreter to be of type int because 5 is an integer.

There are also complex data types in Feral which are created by grouping of fundamental types. We will understand that in later chapters.

For now, let's understand a bit in-depth about the fundamental data types in Feral.

Fundamental Types

These data types are always available for the programmer and are easily deduced by the interpreter based on values. Without these types, a lot of things would not be possible in Feral. For example, if the int type was absent, you would not be able to write any code requiring calculations. Booleans are exceptions to this as you can imitate the booleans using integers (say, integer 1 for true, integer 0 for false).

There are some important things to understand about these data types, so let's do that first.

Integers

Integers represent all the negative and positive numbers in Feral - all the numbers without decimal that is. Theoretically, it can contain any number in the range: (-∞,∞).

Note that unlike many (especially compiled) languages, numbers are not limited to 32, 64, or even 128 bit. So yes, you can have as big a number as you like, so long as it fits your system's memory.

For example,

let num = 13721736912389172367234538762354786253478652374587235648923749872394623864;
let negative = -12378126387512836512678358761253871625365412578631263816287357125387123123768162;

are both valid integer variables.

Floats

Floating point numbers are the decimal numbers - any number which is an imperfect fraction. These are useful when precise mathematical calculations are required, as integers won't provide decimal precision. As with integers, floating point values can also be arbitrarily long with a very high precision. For example,

let flt = 13721736912389172367234538.762354786253478652374587235648923749872394623864;
let negative = -123781263875128365.12678358761253871625365412578631263816287357125387123123768162;

Do note that to classify a number as floating point, the use of decimal point (.) to signify decimal is a must, even if the number does not actually have anything after the decimal point (.), in which case, we can simply have a zero (0) after that. For example,

let flt = 12.0;

Bools

Booleans are the true and false values in Feral. These are used when you want to know something in a yes or no fashion. The true and false are special (constant) variables in Feral which signify their respective boolean values. These two special variables cannot be reassigned and are always available globally throughout the program's life.

For example,

let t = true;
let f = false;

Strings

Strings are, essentially, sequences of characters that are, well, just that! From a single character on your keyboard, to even entire articles written by you, can be considered and contained in strings. These strings, in Feral, are often referred to as constant strings (const strings) when they are hardcoded in your code. Strings are also how input is received in Feral, which will be covered in the next chapter of the book. There are 3 ways to write constant strings in Feral:

  1. Enclosing in between single quotes ('<some string>')
  2. Enclosing in between double quotes ("<some string>")
  3. Enclosing in between back ticks (`<some string>`)

All of them are identical and can span multiple lines, but the variety often helps in nested use cases, discussed later in this chapter. For example,

let s1 = 'string in "single" quotes';
let s2 = "string in 'double' quotes";
let s3 = `string in between back ticks with 'single' and "double" quotes inside`;

An important thing to consider is that Feral does interpret some escape sequences. They are:

\a - Alert bell
\b - Backspace
\f - Formfeed page break
\n - New line
\r - Carriage return
\t - Tab (horizontal)
\v - Tab (vertical)
\\ - Simple backslash
\" - Simple double quote
\' - Simple single quote

We saw these being used in our hello world program for printing newline with the print function.

The single, double, and back quoted constant strings, although identical, do exist for a reason - if we enclose a string inside double quotes, we can use single quotes inside them freely and vice versa. And if we enclose a string in backticks, we can use single as well as double quotes inside it. If, say, only single quoted constant strings existed, we would have to continually escape the single quotes inside it to avoid them being considered end of string quotes. For example:

# here, 'this' will be shown in quotes
io.println('this is a string and \'this\' is literally quoted');
# if we use double quotes, we don't have to use escape sequences
io.println("this is a string and 'this' is literally quoted");
# same thing as above, but quotes are swapped
io.println('this is a string and "this" is literally quoted');

As we can see, it can be quite useful to have this style. Also, if we want to use both types of quotes as literals, we could enclose them inside the back ticks:

io.println(`this is 'a' quoted "string"`);

Conclusion

That should be more than sufficient information about strings at this point. We will learn more as we need. For now, it's time for the next chapter in which we will learn about taking input from the user!

Input

We are making programs for the user and unless we are interested in writing scripts for absolute and utter automation, we will have to take some sort of input from the user. And for this, we have one core function for taking user input.

The scan Function

Feral has an io function scan() which allows the programmer to take user input. It can optionally take in a prompt string and returns the input entered by the user as a string. The programmer has complete control to do whatever they want with this input string.

For example,

# taking simple input in a variable
let var1 = io.scan();
# taking input after a prompt
let var2 = io.scan('Enter some data: ');

In the second case, the user will first be greeted by Enter some data: and they can provide input after that. Similar to the output from print function, this prompt will not append a newline character at the end automatically. If we want a newline character at the end, we will have to manually add that like so:

let var = io.scan('Enter some data in next line:\n');

Let's understand this better using a very simple program extending our Hello world program.

This time, we want to ask the user their name, and we will print Hello, <their name>. Basically, we will be doing three things:

  1. Giving prompt and taking input
  2. Storing the input in a variable
  3. Displaying our message

Ideally, you should try to making such a program by yourself first, but for this guide, here is the solution:

let name = io.scan('Enter your name: ');
io.println('Hello, ', name);

That's it! Easy right?!

There is one more input function scaneof. The only difference between scan and scaneof is that scaneof reads till it finds the EOF character in the input. Therefore, scaneof can span multiple lines, unlike the scan function which reads only a single line at a time.

Well, there isn't anything else to learn about input for now. The scan function will most likely be extended later on to perhaps provide more fine grain control over taking input.

This guide has used the term function a lot of times. Next up, it is time to understand what exactly they are and how to work with them in Feral.

Functions

What are Functions

In simple words, functions are groups of statements/instructions that are combined to perform some tasks, and can be used repeatedly. We can say, functions are made to perform an often repetitive set of procedures, multiple times, without having to repeat the procedures.

When we create a function with its group of statements, it is called function definition. The group of statements is called function body. And, when we use the function, it is called a function call.

We may need to provide functions with some additional data, or retrieve some data from it. For that, we use function arguments and function return values respectively.

Now, let's move on to how these functions are implemented in Feral.

Functions in Feral

Feral allows functions to be written in two different modes, each with two different styles, totalling four total varities for writing functions. They are:

  1. As C++ functions - Since Feral interpreter is written in C++ programming language, its functions can be written in it too, allowing you to bind C++ and Feral, essentially, enabling use of the plethora of C++ libraries.
  2. As Feral functions - Feral allows you to write functions in the Feral language itself, which is what you will be doing for the most part and what this guide will elaborate on.

Both of these types have 2 styles each - as plain functions in a source file, and as pseudo member functions. We will understand this in more detail but let's first grasp the concepts of simple (plain) functions.

Also, note that the function arguments are passed as references - the values that are passed to functions are not copied and the function can change the values should that be required.

Our First Function!

In Feral, we define functions using the keyword fn. This marks the beginning of a function definition and we then describe the function. Let's work on this using our dear old Hello world example.

Last time, we were able to ask user to enter their name, store it in a variable, and display our hello message to the user in the Hello, <user> format. This time, we will create a function named hello to which we will pass the name of the user as an argument and that function will print the hello message for us.

So, here's the code for that.

let io = import('std/io');
# function definition
let hello = fn(name) {
	io.println('Hello, ', name);
};

let name = io.scan('Enter your name: ');
# function call
hello(name);

Do note that variable names can be anything you like. The names given here are not set in stone. But yes, congratulations on making your first function in Feral! Now, to understand what we have written.

Function definitions start with the keyword fn. Then comes the argument names for the function within parenthesis - multiple arguments separated by commas and empty parentheses for no argument, and then we have our function body within curly braces.

To use a function, we simply call it by using the function name with parentheses and the actual arguments (our data) within the parentheses. Again, for no arguments, empty parentheses are required.

Since functions are expressions in Feral, they can be stored in variables too! This is how we assigned an anonymous function - a nameless function, to the variable hello in this case. Hence, just like any other variable, we can pass this around to functions, as well make a new variable out of it, like so.

let func1 = fn(a, b) {
	return a + b;
};

let func2 = func1; # now func2 is also the same function as func1

Feral also has the concept of type bound functions which are used similar to member functions. But we'll discuss about them in a later chapter. For now, time to move on to the next topic - Conditionals!

Conditionals

Conditionals are the decision making constructs in programming - and in Feral. In essence, if we want some actions - statements to be executed based on some prerequisites or criteria, we use conditionals.

In Feral, like most languages, conditional construct exists in the format:

if <some expression> {
	# do something;
} elif <perhaps, some other expression> {
	# do some other thing;
} else {
	# oh well, none of the above worked, so let's just do this
}

Do note that the elif and else portions are not mandatory. But the order - first if, then elif, then else is absolute. Also, a conditional can have any number of elifs.

if states the starting of a conditional. Right after if, Feral expects an expression (a set of calculations & function calls that return some value) that evaluates to a boolean value. The boolean, if true will cause the block of if, which is right after the expression - beginning with an opening brace, to be executed. If the boolean, however, is false, the block is not executed. Instead, if there is elif, its expression will be checked and this process will continue. If none of the conditions work, and there is an else block, that will be executed.

For example,

let io = import('std/io');

let a = 2;
if a == 1 {
	io.println('one');
} elif a == 2 {
	io.println('two');
} else {
	io.println('i dunno');
}

In this case, the elif block will be executed since a == 2 will evaluate to true.

That's it for conditionals actually. Next up, we'll be learning about the various loop constructs in Feral.

Loops

Loops are sequences of code that run over and over again, often as long as some condition is specified. They, like functions, help a lot in reducing redundant pieces of code.

For example, if we want to make a multiplication table of 12 from 1 to 10, would we write 10 statements by hand? It would look something like this:

let io = import('std/io');

io.println('12 x 1 = ', 12 * 1);
io.println('12 x 2 = ', 12 * 2);
io.println('12 x 3 = ', 12 * 3);
io.println('12 x 4 = ', 12 * 4);
io.println('12 x 5 = ', 12 * 5);
io.println('12 x 6 = ', 12 * 6);
io.println('12 x 7 = ', 12 * 7);
io.println('12 x 8 = ', 12 * 8);
io.println('12 x 9 = ', 12 * 9);
io.println('12 x 10 = ', 12 * 10);

Well, that works I suppose, is it convenient? Nope, but is it really hard? Well, again nope. But think about it. This is just from 1 to 10. What about 1 to 100? Or 1 to 1000? Yeah, that becomes far more incovenient now. Downright dreadful even.

That's where loops come in to save the day! Using a loop, the above program could be written as:

let io = import('std/io');

for let i = 1; i <= 10; ++i {
	io.println('12 x ', i, ' = ', 12 * i);
}

Yep, that's it! And if, say, we want to do this even for 1000, all we have to do is change the i <= 10 condition to i <= 1000, and boom! It's done! This is a really basic example of loops but it should suffice for now.

Feral has 3 kinds of loops:

  1. Simple for loop
  2. foreach loop
  3. while loop

The for Loop

This is probably the most pervasive loop construct across programming languages - for good reason. The for loop is also the most dynamic one that can be used in virtually every situation. This form of loop contains 4 components:

  1. Initialization - This component is used for initializing the loop, often with some variables - like above we initialized the variable i with the value 1.
  2. Execution Condition - This component is used to put a condition till which the loop should run. As long as the condition evaluates to true, the loop block will continue to be executed (above, i <= 10).
  3. Increment/Decrement - This component allows for modification in some value - often to update the variable that is being looped through (above, ++i) after each execution of the loop block.
  4. Loop Block - This is the block of code that is executed as long as the condition of loop holds true.

Aside from loop block, all the other components are optional and we can also skip them all should we want to, which will make for an infinite loop - a loop that never ends.

Needless to say, the loop we wrote for generating table of 12 was a for loop.

The foreach Loop

This form of loop is also called a ranged based for loop because, you guessed it, it works for a range of items. Although, this is helpful for some use cases, as well as for the purpose of readability, it is also not always feasible. For example, skipping the initialization is not possible using foreach. In any case, it's another tool for when it is required.

The above table of 12 from 1 to 10, in terms of foreach would be like:

let io = import('std/io');

for i in range(1, 11) {
	io.println('12 x ', i, ' = ', 12 * i);
}

Note that we used 1 to 11 in range() function, instead of 1 to 10. This is because foreach loop runs till the variable is less than the end expression. Therefore we have to increase the expression by one. In this case, the condition in terms of for loop would be i < 11 which actually is equivalent to i <= 10.

The while Loop

Imagine for loop, but remove the initialization and increment/decrement components. That literally is while loop. This loop can be represented by for as well, but hey, it's syntactic sugar! It avoids the awkwardness of having two useless semicolons (';') that we have to deal with if we use for loop without the initialization and increment/decrement components.

An example of the while loop using vectors/lists can be:

let io = import('std/io');
let vec = import('std/vec');

let v = vec.new(1, 2, 3, 4);

while !v.empty() {
	v.pop();
}

This is a simple program that just keeps on deleting the last element of the vector until there is nothing left. The same thing, using normal for loop would be:

let io = import('std/io');
let vec = import('std/vec');

let v = vec.new(1, 2, 3, 4);

for ; !v.empty(); {
	v.pop();
}

Not quite pleasing, is it!

Anyway, that's the fundamentals of loops. Next up, we are going to cover the member functions in Feral.

Standard Library

IO

The IO module gives access to a variety of functions to print text to a terminal or to a file and read user input.

All the given examples assume that the IO module was imported using:

let io = import('std/io');

Functions

Variables

print

print(args...) -> nil

Prints all given args to the standard output without adding a new line after the last one

Example:

let name = 'John';
let age = '21';
io.print('My name is ', name, '.');
io.print('I am ', 21, '.');

Gives the output:

My name is John. I am 21.

println

println(args...) -> nil

Prints all given args to the standard output and adds a new line after the last one

Example:

let name = 'John';
let age = '21';
io.print('My name is ', name, '.');
io.print('I am ', 21, '.');

Gives the output:

My name is John. 
I am 21.

fprint

fprint(file: file, args...) -> nil

Writes all given args to file without adding a new line after the last one

Example:

let fs = import('std/fs');
let file = fs.open('hello.txt', 'w')
let name = 'John';
let age = '21';
io.fprint(file, 'My name is ', name, '.');
io.fprint(file, 'I am ', 21, '.');

Produces a hello.txt file with the following content:

My name is John. I am 21.

fprintln

fprintln(file: file, args...) -> nil

Writes all given args to file and adds a new line after the last one

Example:

let fs = import('std/fs');
let file = fs.open('hello.txt', 'w')
let name = 'John';
let age = '21';
io.fprint(file, 'My name is ', name, '.');
io.fprint(file, 'I am ', 21, '.');

Produces a hello.txt file with the following content:

My name is John. 
I am 21.

cprint

print(args...) -> nil

Prints all given args to the standard output without adding a new line after the last one and interpret color codes to change the text color. A color change is not bound to a single cprint call and will remain active until another one comes in.

Example:

io.cprint('{r}ERROR: Unexpected failure.');       # Printed in red
io.cprint('ERROR: Failed to connect to server.'); # Printed in red
io.cprint('{y}WARNING: Something went wrong.');   # Printed in yellow
io.cprint('{0}INFO: Operation successful.');      # Printed in default color

The possible color codes are the following:

  • {0} : Default color
  • {r} : Red
  • {g} : Green
  • {y} : Yellow
  • {b} : Blue
  • {m} : Purple
  • {c} : Cyan
  • {w} : White
  • {br} : Bold red
  • {bg} : Bold green
  • {by} : Bold yellow
  • {bb} : Bold blue
  • {bm} : Bold purple
  • {bc} : Bold cyan
  • {bw} : Bold white

cprintln

cprintln(args...) -> nil

Prints all given args to the standard output and adds a new line after the last one and interpret color codes to change the text color. A color change is not bound to a single cprintln call and will remain active until another one comes in.

Example:

io.cprintln('{r}ERROR: Unexpected failure.');       # Printed in red
io.cprintln('ERROR: Failed to connect to server.'); # Printed in red, on a new line
io.cprintln('{y}WARNING: Something went wrong.');   # Printed in yellow, on a new line
io.cprintln('{0}INFO: Operation successful.');      # Printed in default color, on a new line

For the list of available color codes, please refer to cprint

cdprint

cdprint(args...) -> nil

Prints all given args to the error output without adding a new line after the last one and interpret color codes to change the text color. A color change is not bound to a single cprint call and will remain active until another one comes in.

Example:

io.cprint('{r}ERROR: Unexpected failure.');       # Printed in red on `stderr`
io.cprint('ERROR: Failed to connect to server.'); # Printed in red on `stderr`
io.cprint('{y}WARNING: Something went wrong.');   # Printed in yellow on `stderr`
io.cprint('{0}INFO: Operation successful.');      # Printed in default color on `stderr`

For the list of available color codes, please refer to cprint

cdprintln

cdprintln(args...) -> nil

Prints all given args to the standard output and adds a new line after the last one and interpret color codes to change the text color. A color change is not bound to a single cprintln call and will remain active until another one comes in.

Example:

io.cprintln('{r}ERROR: Unexpected failure.');       # Printed in red on `stderr`
io.cprintln('ERROR: Failed to connect to server.'); # Printed in red, on a new line on `stderr`
io.cprintln('{y}WARNING: Something went wrong.');   # Printed in yellow, on a new line on `stderr`
io.cprintln('{0}INFO: Operation successful.');      # Printed in default color, on a new line on `stderr`

For the list of available color codes, please refer to cprint

scan

scan(prompt: string = '') -> string

Reads a single line of text from the standard input.

An optional prompt parameter can be passed to write the given string before reading the standard input.

Example:

let input = io.scan('Enter your age: ');
io.println('You are ', input, ' years old');

Possible output:

Enter your age: 21
You are 21 years old

scaneof

scaneof(prompt: string = '') -> string

Reads text from the standard input until EOF is found. Even if EOF can be emitted by a user (usually CTRL-D on UNIX), scaneof is more likely to be useful when a Feral script takes as an input the output of another program.

An optional prompt parameter can be passed to write the given string before reading the standard input.

Example (bio.fer):

let input = io.scaneof();
io.println('Here is everything we know about you:\n', input);

Possible output:

echo "My name is John Doe,
I am 21 years old,
I live on Mars" | feral bio.fer
Here is everything we know about you:
My name is John Doe,I am 21 years old,I live on Mars

fflush

fflush(file: file) -> nil

Forces all unwritten data to be written to the file.

fflush is useful because writes to stdout, stderr or any typical files are usually buffered and their content may not immediately be updated.

Example:

io.print('Enter your age: ');
io.fflush(io.stdout); # Force the prompt to be displayed before scan is called
let input = io.scan();
io.println('You are ', input, ' years old');

Possible output:

Enter your age: 21
You are 21 years old

stdout

stdout: file

Special file associated with the standard output stream.

Example (status.fer):

io.fprintln(io.stdout, 'Everything is fine');
io.fprintln(io.stderr, 'We have a problem');

Possible usage:

feral status.fer 2> /dev/null # redirect stderr to /dev/null

Gives the output:

Everything is fine

stderr

stderr: file

Special file associated with the standard error output stream.

Example (status.fer):

io.fprintln(io.stdout, 'Everything is fine');
io.fprintln(io.stderr, 'We have a problem');

Possible usage:

feral status.fer 1> /dev/null # redirect stdout to /dev/null

Gives the output:

We have a problem

Lang

The lang module offers a way to create user-defined structures and enumerations.

A structure can pack together variables and functions, which allows object-oriented programming.

An enumeration is a set of names bounds to constant numerical values.

All the given examples assume the following imports:

let lang = import('std/lang');
let io = import('std/io');

Functions

Struct

lang.struct(field = value, ...) -> type

Creates a new structure type type holding a set of fields. The given values are used to infer the type of each field and as default values during construction. The returned type can be used to instantiate objects of this type.

Adding member functions to a type is done with the let func in type = fn(args) {}; construct, with func being the function's name. Inside the function, variables and functions belonging to the type can be accessed using the self keyword.

Example:

let player_t = lang.struct(name = "John", class = "Mage", health = 100, attack = 10);

# str is used by print functions to print the content of an object
let str in player_t = fn() {
    let desc = '';
    desc += "name: " + self.name.str();
    desc += ", class: " + self.class.str();
    desc += ", health: " + self.health.str();
    desc += ", attack: " + self.attack.str();
    return desc;
};

let take_damage in player_t = fn(damage) {
    io.println('Taking ', damage, ' damage');
    self.health -= damage;
    return self;
};

let player = player_t("Bob", attack = 20);  # Positional and named arguments allowed
io.println(player);                         # Calls player.str()
player.take_damage(30).take_damage(10);     # Possible thanks to 'return self'
io.println(player);

Gives the output:

name: Bob, class: Mage, health: 100, attack: 20
Taking 30 damage
Taking 10 damage
name: Bob, class: Mage, health: 60, attack: 20

Enum

lang.enum(.name1, .name2 = value, ...) -> type

Creates a new enumeration with the given names and optionally given values. Names must be prefixed with a dot.

Example:

let class = lang.enum(.Mage, .Warrior, .Mecromancer, .Unknown = -1);
let player_t = lang.struct(name = "John", class = class.Unknown, health = 100, attack = 10);

let str in player_t = fn() {
    let desc = '';
    desc += "name: " + self.name.str();
    desc += ", class: " + self.class.str();
    desc += ", health: " + self.health.str();
    desc += ", attack: " + self.attack.str();
    return desc;
};

io.println(player_t("Bob", class.Mage));
io.println(player_t("Josh"));

Gives the output:

name: Bob, class: 0, health: 100, attack: 10
name: Josh, class: -1, health: 100, attack: 10

Strings

The str module defines member functions for the native string type and other string manipulation related functions. The functions available natively in the language can be consulted here.

A string is a sequence of characters. There is no character type in Feral and so are represented as one character strings.

All the given examples assume the following imports:

import('std/str');
let io = import('std/io');

string Member Functions

int Member Functions

len

len() -> int

Returns the number of characters

Example:

io.println('hello'.len());

Gives the output:

5

empty

empty() -> bool

Checks whether the string is empty

Example:

io.println('hello'.empty());
io.println(''.empty());

Gives the output:

false
true

front

front() -> string

Returns the first character of the string, or nil if the string is empty

Example:

io.println('hello'.front());
io.println(''.front());

Gives the output:

h
(nil)

back

string.back() -> string

Returns the last character of the string, or nil if the string is empty

Example:

io.println('hello'.back());
io.println(''.back());

Gives the output:

o
(nil)

push

string.push(str: string) -> string

Appends str to the string and returns the modified string

Example:

let hello = 'hello';
hello.push(', ');
io.println(hello.push('world!'));

Gives the output:

hello, world!

pop

string.pop() -> string

Removes the last character and returns the modified string

Example:

let hello = 'hello';
hello.pop();
io.println(hello.pop());

Gives the output:

hel

insert

string.insert(idx: int, str: string) -> string

Inserts the str string at index idx and returns the modified string

Example:

let hello = 'heo';
hello.insert(2, 'll');
io.println(hello);

Gives the output:

hello

erase

string.erase(idx: int) -> string

Removes one character at index idx and returns the modified string

Example:

let hello = 'helloo';
hello.erase(4);
io.println(hello);

Gives the output:

hello

lastidx

string.lastidx() -> int

Returns the index of the last character. Equivalent to len() - 1

Example:

io.println('hello'.lastidx());

Gives the output:

4

set

string.set(idx: int, char: string) -> string

Replaces the character at index idx with char and returns the modified string

Example:

let hello = 'hello_';
hello.set(5, '!');
io.println(hello);

Gives the output:

hello!

trim

string.trim() -> string

Removes all whitespace characters at the beginning and at the end of the string and returns the modified string

Example:

io.println('_', '   hello   '.trim(), '_');

Gives the output:

_hello_

split

string.split(delim: string = ':') -> vector<string>

Splits the string using delim as a single character delimiter and returns a vector of strings

Example:

io.println('hello;world'.split(';'));

Gives the output:

[hello, world]

byt

string.byt() -> int

Converts the first character to its ASCII integer representation. Returns 0 if the string is empty

Example:

io.println('0'.byt());

Gives the output:

48

chr

int.chr() -> string

Converts an integer from its ASCII integer representation to a string

Example:

io.println(48.chr());

Gives the output:

0

Vectors

The vec module defines the vector type and all the vector manipulation related functions.

A vector is a sequence container that can store heterogeneous types.

All the given examples assume the following imports:

let vec = import('std/vec');
let io = import('std/io');

Functions

vector Member Functions

new

vec.new(elems...) -> vector

Creates a new vector using the given elements

Example:

let v = vec.new(12, 'Hello', 3.14);
io.println(v);

Gives the output:

[12, Hello, 3.14000000000000012434]

len

vector.len() -> int

Returns the number of elements inside the vector

Example:

io.println(vec.new(0, 1, 2).len());

Gives the output:

3

empty

vector.empty() -> bool

Checks whether the vector is empty

Example:

io.println(vec.new(0, 1, 2).empty());
io.println(vec.new().empty());

Gives the output:

false
true

front

vector.front() -> value

Returns the first element of the vector, or nil if the vector is empty

Example:

io.println(vec.new(0, 1, 2).front());
io.println(vec.new().front());

Gives the output:

0
(nil)

back

vector.back() -> value

Returns the last element of the vector, or nil if the vector is empty

Example:

io.println(vec.new(0, 1, 2).back());
io.println(vec.new().back());

Gives the output:

2
(nil)

push

vector.push(elem) -> vector

Appends elem to the vector and returns the modified vector

Example:

let v = vec.new(0);
v.push(1);
io.println(v.push(2));

Gives the output:

[0, 1, 2]

pop

vector.pop() -> vector

Removes the last element and returns the modified vector

Example:

let v = vec.new(0, 1, 2);
v.pop();
io.println(v.pop());

Gives the output:

[0]

insert

vector.insert(idx: int, elem) -> vector

Inserts the elem element at index idx and returns the modified vector

Example:

let v = vec.new(0, 2);
v.insert(2, 3);
io.println(v.insert(1, 1));

Gives the output:

[0, 1, 2, 3]

erase

vector.erase(idx: int) -> vector

Removes one element at index idx and returns the modified vector

Example:

let v = vec.new(0, 1, 9, 12, 2);
v.erase(3);
io.println(v.erase(2));

Gives the output:

[0, 1, 2]

lastidx

vector.lastidx() -> int

Returns the index of the last element. Equivalent to len() - 1

Example:

let v = vec.new(0, 1, 2);
io.println(v.lastidx());

Gives the output:

2

set

vector.set(idx: int, elem) -> vector

Replaces the element at index idx with elem and returns the modified vector

Example:

let v = vec.new(0, 0, 0);
v.set(0, 2);
io.println(v.set(1, 1));

Gives the output:

[2, 1, 0]

at

vector.at(idx: int) -> value

Returns the element at index idx, or nil if out of range

Example:

let v = vec.new(0, 1, 2);
io.println(v.at(2));
io.println(v.at(3));

Gives the output:

2
(nil)

operator []

vector[idx: int] -> value

Returns the element at index idx, or nil if out of range

Example:

let v = vec.new(0, 1, 2);
io.println(v[2]);
io.println(v[3]);

Gives the output:

2
(nil)

slice

vector.slice(start: int, end: int = -1) -> vector

Extracts the elements from start to end - 1 and returns them in a new vector. If end == -1 then the slice ends at the end of the vector. Any modification in the elements of a slice will affect its original vector as well

Example:

let v = vec.new(0, 1, 2, 3);
io.println(v.slice(1));
io.println(v.slice(1, 3));

Gives the output:

[1, 2, 3]
[1, 2]

each

vector.each() -> iterator

Returns an iterator pointing to the first element, allowing easy vector iteration

Example:

let v = vec.new(0, 1, 2);
for elem in v.each() {
    io.println(elem);
}

Gives the output:

0
1
2

iterator.next() -> value

Returns the value pointed by the iterator, or nil if out of range, and then advances the iterator to the next element

Example:

let v = vec.new(0, 1, 2, 3);
let iter = v.each();
for i in range(v.len() + 1) {
    io.println(iter.next());
}

Gives the output:

0
1
2
3
(nil)

Maps

The map module defines the map type and all the map manipulation related functions.

A map is an associative container that can store heterogeneous types.

All the given examples assume the following imports:

let map = import('std/map');
let io = import('std/io');

Functions

map Member Functions

new

map.new(key1, value1, key2, value2, ...) -> map

Creates a new map using the given key/value pairs. The keys must be convertible to strings. Also, it must correspond a value to each key, meaning that the number of arguments must always be even

Example:

let m = map.new(0, "Zero", 1, "One", "Answer?", 42);
io.println(m);

Gives the output:

{Answer?: 42, 0: Zero, 1: One}

Note that the elements are not stored in any particular order and thus do not necessarily follow the given one

len

map.len() -> int

Returns the number of key/value pairs inside the map

Example:

io.println(map.new(0, "Zero", 1, "One", "Answer?", 42).len());

Gives the output:

3

insert

map.insert(key, value) -> map

Inserts the key/value pair and returns the modified map

Example:

let m = map.new(0, "Zero");
m.insert(1, "One");
io.println(m.insert("Answer?", 42));

Gives the output:

{Answer?: 42, 0: Zero, 1: One}

erase

map.erase(key) -> map

Removes the key and its associated value and returns the modified map

Example:

let m = map.new(0, "Zero", 1, "One", "Answer?", 42);
m.erase(1);
io.println(m.erase("Answer?"));

Gives the output:

{0: Zero}

get

map.get(key) -> map

Returns the value associated with key, or nil if it doesn't exist

Example:

let m = map.new(0, "Zero", 1, "One", "Answer?", 42);
io.println(m.get("Answer?"));
io.println(m.get("Oops"));

Gives the output:

42
(nil)

operator []

map[key] -> value

Returns the value associated with key, or nil if it doesn't exist

Example:

let m = map.new(0, "Zero", 1, "One", "Answer?", 42);
io.println(m["Answer?"]);
io.println(m["Oops"]);

Gives the output:

2
(nil)

each

map.each() -> iterator

Returns an iterator pointing to the first key/value pair, allowing easy map iteration

Example:

let m = map.new(0, "Zero", 1, "One", "Answer?", 42);
for elem in m.each() {
    io.println(elem.0, ": ", elem.1);
}

Gives the output:

Answer?: 42
0: Zero
1: One

next

iterator.next() -> value

Returns the key/value pair pointed by the iterator, or nil if out of range, and then advances the iterator to the next element

Example:

let m = map.new(0, "Zero", 1, "One", "Answer?", 42);
let iter = m.each();
for i in range(m.len()) {
    let elem = iter.next();
    io.println(elem.0, ': ', elem.1);
}

Gives the output:

Answer?: 42
0: Zero
1: One

Fmt

The fmt module provides string formatting capabilities.

All the given examples assume the following imports:

let fmt = import('std/fmt');
let io = import('std/io');

Functions

template

fmt.template(string) -> string

Takes a template string, evaluates it, and returns the final string. Does not modify the original string. A template string contains snippets of code in between the usual string content. These snippets of code must be valid Feral expressions and enclosed between opening and closing braces ({<expression here>}).

Example:

let x = 5, y = 10;
let template_str = '{x} + {y} = {x + y}';
io.println(fmt.template(template_str));

Gives the output:

5 + 10 = 15

Note that to avoid a pair of { and } being considered as part of template when calling fmt.template, just escape the opening brace ({) with double backslash (\\ (one for lexer, other for template function)). This will also leave the pair of braces as it is.

Example:

let x = 5, y = 10;
let template_str = '\\{x} + \\{y} = {x + y}';
io.println(fmt.template(template_str));

Gives the output:

{x} + {y} = 15

Nested templates are possible as well should they be required.

FS

The fs module allows to access and manipulate the file system

All the given examples assume the following imports:

let fs = import('std/fs');
let io = import('std/io');

And that the following file hierarchy exists in the file system:

- dir1
 |- foo.txt
 |- bar.txt
 |- dir2
    |- hello.txt
    |- world.txt

Functions

file Member Functions

exists

fs.exists(path: string) -> bool

Returns true if the given path exists in the file system and false otherwise

Example:

io.println(fs.exists('dir1/foo.txt'));
io.println(fs.exists('dir2/foo.txt'));

Gives the output:

true
false

open

fs.open(path: string, mode: string = 'r') -> file

Opens the given file path using the (optional) mode. mode can be any of the following:

ModeDescription
rOpen a file for reading
wCreate a file for writing
aAppend to a file
r+Open a file for read/write
w+Create a file for read/write
a+Open a file for read/write

Note: Appending 'x' to either 'w' or 'w+' will cause the function to fail if the file already exists. This may prevents overwriting existing files.

Example:

let file = fs.open('dir1/foo.txt', 'r');

walkdir

fs.walkdir(dir: string, mode: int = WALK_RECURSE, regex: string = '(.*)') -> vector

Returns a vector of all file paths in path using the (optional) mode and regex. mode can be any combination of the following:

ModeDescription
fs.WALK_FILESList only files
fs.WALK_DIRSList only directories
fs.WALK_RECURSESearch recursively

The default regex matches all file names.

Example:

# Recursively finds all files in dir1 ending with o.txt
let files = fs.walkdir('dir1', fs.WALK_FILES + fs.WALK_RECURSE, '(.*o\.txt)');
io.println(files);

Gives the output:

[dir1/foo.txt, dir1/dir2/hello.txt]

lines

file.lines() -> vector

Returns a vector containing all lines in the file

Example:

let file = fs.open('dir1/bar.txt');
io.println(file.lines());

Gives output:

[My, name, is, John]

Given the file dir1/bar.txt:

My
name
is
John

read_blocks

file.read_blocks(begin: string, end: string) -> vector

Returns an iterator pointing to the first line, allowing easy vector iteration

Example:

import('std/vec');

let file = fs.open('dir1/foo.txt');
let quotes = file.read_blocks('"', '"');
io.println("Quotes found:");
for quote in quotes.each() {
    io.println(" - ", quote);
}

Gives output:

Quotes found:
- The greatest glory in living lies not in never falling, but in rising every time we fall.
- The way to get started is to quit talking and begin doing.
- Your time is limited, so don't waste it living someone else's life. Don't be trapped by dogma – which is living with the results of other people's thinking.

Given the file dir1/foo.txt:

Top quotes:
"The greatest glory in living lies not in never falling, but in rising every time we fall." -Nelson Mandela
"The way to get started is to quit talking and begin doing." -Walt Disney
"Your time is limited, so don't waste it living someone else's life. Don't be trapped by dogma – which is living with the results of other people's thinking." -Steve Jobs

seek

file.seek(offset: int, origin: int) -> int

Moves the file position indicator to the given offset starting at origin. origin can be any of the following:

OriginDescription
SEEK_SETBeginning of the file
SEEK_CURCurrent file position
SEEK_ENDEnd of the file

Example:

let file = fs.open('dir1/bar.txt');
io.println(file.lines());
file.seek(5, fs.SEEK_SET);
io.println(file.lines());
file.seek(-5, fs.SEEK_END);
io.println(file.lines());

Gives the output:

[My, name, is, John]
[me, is, John]
[John]

Given the file dir1/bar.txt:

My
name
is
John

each_lines

file.each_lines() -> iterator

Returns an iterator pointing to the first line, allowing easy vector iteration

Example:

let file = fs.open('dir1/bar.txt');
for line in file.each_line() {
    io.println(line);
}

Gives output:

[My, name, is, John]

Given the file dir1/bar.txt:

My
name
is
John

next

iterator.next() -> string

Returns the line pointed by the iterator, or nil if out of range, and then advances the iterator to the next line

Example:

let file = fs.open('dir1/bar.txt');
let iter = file.each_line();
while true {
    let line = iter.next();
    if line != nil {
        io.println(line);
    }
    else {
        break;
    }
}

Gives the output:

My
name
is
John

Given the file dir1/bar.txt:

My
name
is
John

OS

The os module allows to access to some functionalities of the underlying operating system

All the given examples assume the following imports:

let os = import('std/os');
let io = import('std/io');

Functions

Variables

exists

os.sleep(ms: int) -> nil

Pauses the program execution for ms milliseconds. Please not that the exact sleep time might be greater than ms because of how OS scheduler works.

Example:

io.println('Waiting a bit...');
os.sleep(1000);
io.println('Done!');

Gives the output:

Waiting a bit...
# one second later
Done!

get_env

os.get_env(var: string) -> string

Returns the content of the environment variable var if it exists or an empty string otherwise

Example:

io.println('$HOME=', os.get_env('HOME'));

Possible output:

$HOME=/home/feral

set_env

os.set_env(var: string, value: string, overwite: bool = false) -> int

Sets the content of the environment variable var with the content of value. If var already exists, it won't be overwritten unless overwrite is true. The function returns 0 on success and -1 on error

Example:

io.println('$FOOBAR=', os.get_env('FOOBAR'));
os.set_env('FOOBAR', 'Hello');
io.println('$FOOBAR=', os.get_env('FOOBAR'));
os.set_env('FOOBAR', 'Hello, world!');
io.println('$FOOBAR=', os.get_env('FOOBAR'));
os.set_env('FOOBAR', 'Hello, world!', true);
io.println('$FOOBAR=', os.get_env('FOOBAR'));

Gives the ouput:

$FOOBAR=
$FOOBAR=Hello
$FOOBAR=Hello
$FOOBAR=Hello, world!

exec

os.exec(cmd: string) -> int

Executes the command cmd and returns its result. All text printed by cmd will be redirected to the standard output

Example:

io.println(os.exec('uname'));

Possible ouput:

Linux
0

find_exec

os.find_exec(exec: string) -> string

Search in PATH for the executable exec and returns its path if found or an empty string otherwise

Example:

io.println(os.find_exec('ls'));

Possible ouput:

/usr/bin/ls

install

os.install(src: string, dest: string) -> int

Copies all the content of the source file or folder src into dest. The path leading to dest will be created if necessary. The function returns 0 on success and -1 on error

Example:

os.install('/home/feral/Documents', '/mnt/backup');

get_cwd

os.get_cwd() -> string

Returns the current working directory

Example:

io.println(os.get_cwd());

Possible output:

/home/feral

set_cwd

os.set_cwd(wd: string) -> int

Changes the current working directory to be wd. The function returns 0 on success and -1 on error

Example:

io.println(os.get_cwd());
os.set_cwd('/home/feral/Documents');
io.println(os.get_cwd());

Possible output:

/home/feral
/home/feral/Documents

mkdir

os.mkdir(dir...: string) -> int

Creates all given directories. The function returns 0 on success and -1 on error

Example:

os.mkdir('/home/feral/Build', '/home/feral/Tests');

rm

os.rm(path...: string) -> int

Removes all given files and directories. The function returns 0 on success and -1 on error

Example:

os.rm('/home/feral/Build', '/home/feral/Tests');

copy

os.copy(src...: string, dest: string) -> int

Copies all given files and directories to dest. The function returns 0 on success and -1 on error

Example:

os.copy('/home/feral/Build', '/home/feral/Tests', '/tmp');

chmod

os.chmod(dest: string, mode: string = '755', recurse: bool = 'true') -> int

Changes the permission of dest to mode, and recursively to its content if recurse is true. The function returns 0 on success and -1 on error

Example:

os.chmod('/home/feral/Build', '777', false);

name

os.name: string

The name of the OS. Current possible values are linux, macos, bsd and android

Example:

io.println(os.name;

Possible ouput:

linux

Ptr

The ptr module defines the ptr type for pointer-like referencing and modification of existing variables

All the given examples assume the following imports:

let ptr = import('std/ptr');
let io = import('std/io');

Functions

ptr Member Functions

new

ptr.new(var = nil) -> ptr

Creates a new pointer, optionally pointing to the given variable var.

Example:

let answer = 42;
let answer_ptr = ptr.new(answer);

get

ptr.get() -> value

Returns the value pointed to, or nil or nil if it does not point to a variable. Can be used to modify to pointed variable

Example:

let answer = 42;
let answer_ptr = ptr.new(answer);
io.println(answer_ptr.get());
answer_ptr.get() = 12;
io.println(answer);
let nil_ptr = ptr.new();    
io.println(nil_ptr.get());

Gives the output:

42
12
(nil)

set

ptr.set(value) -> ptr

Makes the pointer point to value

Example:

let answer = 42;
let pi = 3.14;
let answer_ptr = ptr.new(answer);
io.println(answer_ptr.get());
answer_ptr.set(pi);
io.println(answer_ptr.get());

Gives the output:

42
3.14

Sys

The sys module provides information and control of the feral interpreter

All the given examples assume the following imports:

let sys = import('std/sys');
let io = import('std/io');

Functions

Variables

var_exists

sys.var_exists(name: str) -> bool

Checks if the given variable name in known to the virtual machine

Example:

io.println(sys.var_exists('hello'));
let hello = 'Hello, world!';
io.println(sys.var_exists('hello'));

Gives the output:

false
true

exit

sys.exit(code: int = 0) -> nil

Exits the program using the given return code. A return code of zero (default value) indicates a graceful exit while a non-zero value indicates a failure in running the program

Example:

let success = true;
if success {
    sys.exit();
}
else {
    sys.exit(-1);
}

args

sys.args: vector<string>

Contains the arguments passed to feral, after the main source file

Example (args.fer):

io.println(sys.args);

Possible output:

$ feral args.fer 1 2 3
[1, 2, 3]
$ feral args.fer hello world
[hello, world]

self_bin

sys.self_bin: string

Contains the full path of the interpreter, according to binary's location

Example (loc.fer):

io.println(sys.self_bin);

self_base

sys.self_base: string

Contains the base directory of the interpreter, according to binary's location

Example (loc.fer):

io.println(sys.self_base);

Possible output:

$ /usr/local/bin/feral loc.fer
/usr/local
$ feral loc.fer
feral

install_prefix

sys.install_prefix: string

Contains the path to the feral installation directory

Example:

io.println(sys.install_prefix);

Possible output:

/usr/local

Builder (Deprecated)

The builder module defines a builder_t type that allows to build and install external modules to be used with Feral

A guide on how to write and build modules for Feral can be read here.

All the given examples assume that the builder module was imported using:

let builder = import('std/builder');
let io = import('std/io');
let sys = import('std/sys');

Functions

builder_t Member Functions

new

builder.new() -> builder_t

Creates a new builder with default build settings

Example:

let build = builder.new();
io.println(build);

Possible output:

builder_t{linker_flags:  , lib_flags:  -lferalvm -lgmpxx -lgmp -lmpfr , lib_dirs:  -L/usr/lib -L/usr/local/lib/feral , srcs:  , ccache: /usr/bin/ccache , compiler: g++, is_dll: false, compiler_opts:  -std=c++11 -O2 , inc_dirs:  -I/usr/include }

make_dll

builder.make_dll() -> builder_t

Indicates that the module should be built as a dynamic library instead of an executable program

Example:

let build = builder.new();
io.println("Is DLL? ", build.is_dll, ". Compiler options: ", build.compiler_opts);
build.make_dll();
io.println("Is DLL? ", build.is_dll, ". Compiler options: ", build.compiler_opts);

Possible output:

Is DLL? false. Compiler options:  -std=c++11 -O2 
Is DLL? true. Compiler options:  -std=c++11 -O2 -shared -fPIC -rdynamic -Wl,-rpath,/home/feral/.feral/lib

add_comp_opts

builder.add_comp_opts(opt: string) -> builder_t

Adds opt to the list of options passed to the compiler

Example:

let build = builder.new();
io.println("Compiler options: ", build.compiler_opts);
build.add_comp_opts('-g');
io.println("Compiler options: ", build.compiler_opts);

Possible output:

Compiler options:  -std=c++11 -O2 
Compiler options:  -std=c++11 -O2 -g

add_inc

builder.add_inc(inc_dir: string) -> builder_t

Adds inc_dir to the list of include directories passed to the compiler

Example:

let build = builder.new();
io.println("Include directories: ", build.inc_dirs);
build.add_inc('-I/usr/local/include');
io.println("Include directories: ", build.inc_dirs);

Possible output:

Include directories:  -I/usr/include 
Include directories:  -I/usr/include -I/usr/local/include 

add_lib

builder.add_lib(lib_flag : string, lib_dir = '' : string) -> builder_t

Adds lib_flag and lib_dir to the list of libraries and library directories passed to the compiler

Example:

let build = builder.new();
io.println("Libraries and directories \n\t", build.lib_flags, "\n\t", build.lib_dirs);                      
build.add_lib('-lcurl', '-L/usr/local/lib');
io.println("Libraries and directories \n\t", build.lib_flags, "\n\t", build.lib_dirs);

Possible output:

Libraries and directories 
	 -lferalvm -lgmpxx -lgmp -lmpfr 
	 -L/usr/lib -L/usr/local/lib/feral 
Libraries and directories 
	 -lferalvm -lgmpxx -lgmp -lmpfr -lcurl 
	 -L/usr/lib -L/usr/local/lib/feral -L/usr/local/lib

add_src

builder.add_src(src: string) -> builder_t

Adds src to the list of source files passed to the compiler

Example:

let build = builder.new();
io.println("Source files: ", build.srcs);
build.add_src('src/hello.cpp');
io.println("Source files: ", build.srcs);

Possible output:

Source files:  
Source files:  src/hello.cpp

perform

builder.perform(output_file: string, .kw_args) -> int

Builds the module and produces the output_file binary output and returns zero on success or an error code on failure. The possible keyword arguments are:

  • src: Additional source files to build. Default if empty
  • inc: Include files location. Default is include
  • lib: Library files location. Default is build
  • bin: Executable files location. Default is bin

The build is also influenced by the following command line arguments:

  • dry : Dry run
  • install : Install the module in addition of building it

Example:

let build = builder.new().make_dll();
build.add_src('src/learn.cpp');
sys.exit(build.perform('learn'));

Possible output:

$ feral install
Building ...
=> build/libferallearn.so
Installing ...
=> build/* -> /home/feral/.feral/lib/ ...
Installation successful!

Core Functions

Global

FunctionDescription
mload
import
__ismainsrc__

Structures

FunctionDescription
set_typename

All types

FunctionDescription
==
!=
copy
str

nil

FunctionDescription
==
!=

bool

FunctionDescription
<
>
<=
>=
==
!=
!

int

FunctionDescription
+
-
*
/
%
+=
-=
*=
/=
%=
**
++x
x++
--x
x--
u-
<
>
<=
>=
==
!=

flt

FunctionDescription
+
-
*
/
+=
-=
*=
/=
++x
x++
--x
x--
u-
round
<
>
<=
>=
==
!=

string

FunctionDescription
+
*
+=
*=
<
<=
>=
==
!=
at(idx: int) -> stringReturns the character at index idx, or nil if out of range
[idx: int] -> stringReturns the character at index idx, or nil if out of range

Examples:

let io = import('std/io');
let hello = 'hello!';
io.println(hello.at(0));
io.println(hello[6]);

Output:

h
(nil)