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:
- Must begin with an alphabet (irrelevant of the case) or underscore
- Can contain numbers anywhere except the first character
- 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:
- Enclosing in between single quotes ('<some string>')
- Enclosing in between double quotes ("<some string>")
- 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:
- Giving prompt and taking input
- Storing the input in a variable
- 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:
- 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.
- 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 elif
s.
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:
- Simple
for
loop foreach
loopwhile
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:
- Initialization - This component is used for initializing the loop, often with some variables - like above we initialized the variable
i
with the value1
. - 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
). - 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. - 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(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 field
s. The given value
s 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 name
s and optionally given value
s. 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
next
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 string
s. 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:
Mode | Description |
---|---|
r | Open a file for reading |
w | Create a file for writing |
a | Append 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:
Mode | Description |
---|---|
fs.WALK_FILES | List only files |
fs.WALK_DIRS | List only directories |
fs.WALK_RECURSE | Search 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:
Origin | Description |
---|---|
SEEK_SET | Beginning of the file |
SEEK_CUR | Current file position |
SEEK_END | End 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 emptyinc
: Include files location. Default isinclude
lib
: Library files location. Default isbuild
bin
: Executable files location. Default isbin
The build is also influenced by the following command line arguments:
dry
: Dry runinstall
: 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
Function | Description |
---|---|
mload | |
import | |
__ismainsrc__ |
Structures
Function | Description |
---|---|
set_typename |
All types
Function | Description |
---|---|
== | |
!= | |
copy | |
str |
nil
Function | Description |
---|---|
== | |
!= |
bool
Function | Description |
---|---|
< | |
> | |
<= | |
>= | |
== | |
!= | |
! |
int
Function | Description |
---|---|
+ | |
- | |
* | |
/ | |
% | |
+= | |
-= | |
*= | |
/= | |
%= | |
** | |
++x | |
x++ | |
--x | |
x-- | |
u- | |
< | |
> | |
<= | |
>= | |
== | |
!= |
flt
Function | Description |
---|---|
+ | |
- | |
* | |
/ | |
+= | |
-= | |
*= | |
/= | |
++x | |
x++ | |
--x | |
x-- | |
u- | |
round | |
< | |
> | |
<= | |
>= | |
== | |
!= |
string
Function | Description |
---|---|
+ | |
* | |
+= | |
*= | |
< | |
<= | |
>= | |
== | |
!= | |
at(idx: int) -> string | Returns the character at index idx , or nil if out of range |
[idx: int] -> string | Returns 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)