Introduction

Welcome to Ocypode programming language book, in this book you will learn how to use Ocypode programming language, and how to build your own Ocypode programs.

What is Ocypode?

Ocypode is a dynamic object-oriented programming language, and it's interpreted language, and its ownership memory management system. The interpreter is written in Rust. Ocypode is inspired by Rust, Python, and JavaScript. Lastly, Ocypode is a language for educational purposes, I made it to learn how to make a programming language, and it's the result.

Features

  • Dynamic typing
  • Object-oriented (WIP)
  • Interpreted language
  • Anonymous Functions
  • Main-function entry point
  • Pakcing parameters and unpacking arguments
  • Owneship memory management system

Source code

You can find the source code of Ocypode in the GitHub repository.

License

Ocypode is licensed under the GPL-3.0 license. You can find the license in the LICENSE file

Getting Started

In this section, we will install the Ocypode interpreter and run a simple Ocypode program.

Installation

First, let's install the Ocypode interpreter using Cargo:

cargo install ocypode-lang --locked

You can also build Ocypode from the source by running these commands:

git clone https://github.com/TheAwiteb/ocypode-lang.git
cd ocypode
cargo build --release

Then you can find the binary in the target/release directory.

You can also download the binary from the releases page and put it in your PATH.

Basics

Each Ocypode program is a file with the .oy extension. The file name is the name of the program. For example, if you have a file named hello.oy, then the program name is hello.

Main function

The main function is the entry point of the program, and it's the first function that will be executed. The main function is a function that has the name main and it has argc and argv parameters. The argc parameter is the number of arguments that are passed to the program, and the argv parameter is an array of strings that contains the arguments that are passed to the program. And the main function returns an integer that will be the exit code of the program and it's optional (if you don't return anything, the exit code will be 0).

Hello world

Note: The details of functions will be explained in the functions section. This just a simple program that prints Hello world! to the standard output.

~main<argc><argv>{<
    println<"Hello world!">;
>}

Output

$ ocypode hello.oy
Hello world!

Comments

You can add comments to your Ocypode programs using / for single-line comments, and /* */ for multi-line comments.

~main<argc><argv>{<
    // This is a single line comment
    /* This is
        A multi line comment 
    */
>}

Functions

In this section, we will learn how to use functions in Ocypode. We will learn how to define functions, how to call functions, and how to pass arguments to functions. We will also learn how to return values from functions, packing parameters and unpacking arguments.

Global Functions

Global functions are functions that are defined in the global scope. Global functions are defined with the ~ keyword and are useful when you want to use them in multiple places in your program.

Ownership

Global functions are owned by the global scope, and they are destroyed when the program ends.

Syntax

Global functions have a name, parameters, and body. before the name you need to add the ~ keyword, and after the name is optional to add the parameters, it will locks like this <param1><param2><param3>, and after the parameters are the body of the function, and the body is the statements that will be executed when the function is called.

Function name

Each function has a name, and the is unique which means you can't have two functions with the same name. The function name can contain letters, numbers, and underscores, and it can't start with a number, also the function name is case-sensitive. And the function name must be snake case.

Examples

~foo{<
    /*
    function body
    */
>}

~foo<name>{<
    /*
    Anouther function body
    */
>}

~main<argc><argv>{< >}
The Error
Error(runtime::already_declared)

  ๐Ÿ’ฅ Identifier already declared
    โ•ญโ”€[test.oy:1:1]
  1 โ”‚ ~foo{<
    ยท  โ”€โ”ฌโ”€
    ยท   โ•ฐโ”€โ”€ Identifier `foo` already declared here
  2 โ”‚     /*
  3 โ”‚     function body
  4 โ”‚     */
  5 โ”‚ >}
  6 โ”‚ 
  7 โ”‚ ~foo<name>{<
    ยท  โ”€โ”ฌโ”€
    ยท   โ•ฐโ”€โ”€ And you tried to declare it again here
  8 โ”‚     /*
  9 โ”‚     Anouther function body
 10 โ”‚     */
    โ•ฐโ”€โ”€โ”€โ”€
  help: Try renaming `foo` or removing the previous declaration.

Parameters

Each function can have parameters, and the parameters are optional. The parameters are the variables that you can use inside the function body. The parameters are defined after the function name, and before the body. The parameters are defined with the <param1><param2><param3> syntax, each parameter is inside a pair of < and >.

Body

The body of the function is the statements that will be executed when the function is called. The body is defined after the parameters, starts with the {< and ends with the >}.

Return values

Global functions can return values, you can return a value using the return keyword, and the value will be returned to the caller.

Examples

~foo{<
    return 1;
>}

Call functions

To call a function, you need to add <> after the function name if the function takes no arguments, or you need to add <arg1><arg2><arg3> after the function name if the function takes arguments.

Examples

~foo<name><age>{<
    println<format<"Hello {} you are {} years old"><name><age>>;
>}

~main<argc><argv>{<
    foo<"Ahmed"><20>;
>}

Output:

Hello Ahmed you are 20 years old

Local Functions

Local functions are functions that are defined inside another function. Local functions are defined as global functions see here, but they are defined inside another function. Local functions are useful when you want to define a function that is only used inside another function.

Ownership

Local functions are owned by the function that they are defined in, and they are destroyed when the function ends.

Syntax

Same as global functions see here. The only difference is that local functions need to add a semi-colon ; after the function body.

Examples

~main<argc><argv>{<
    ~foo<name>{<
        println<format<"Hello {}"><name>>;
    >};
    foo<"Ahmed">;
>}

Output:

Hello Ahmed

Anonymous functions

Anonymous functions are functions that don't have a name, and they are defined inside another function. Anonymous functions are useful when you want to pass a function as an argument to another function, or when you want to return a function from another function.

Syntax

Anonymous functions have parameters and a body. The parameters are the same as local functions, and the body is the same as local functions. The only difference is that the function name is missing.

~main<argc><argv>{<
    <param1><param2><param3>{</* The block */>};
>}

Take no arguments

There is a difference between local functions and anonymous functions if you want to make it take no arguments, you need to add <> before the block.

Return values

Anonymous functions can return values, just like local functions, you can return a value by using the return keyword.

~main<argc><argv>{<
    <>{<return 1;>};
>}

Call anonymous functions

To call an anonymous function, you just need to call it like a [local function]. Add <> to call it without arguments, or add <arg1><arg2><arg3> to call it with arguments.

~main<argc><argv>{<
    hello = <>{<return "Hello world!";>}<>;
    println<hello>;
>}

Output:

Hello world!

Assign anonymous functions to variables

You can assign anonymous functions to variables, like the example below.

~main<argc><argv>{<
    say_hello = <name>{<println<format<"Hello {}"><name>;>;>};
    say_hello<"Ahmed">;
>}

Output:

Hello Ahmed

Packing Parameters

A pack parameter is a parameter that can be used to pack the rest of the parameters into a list. The packing parameter must be the last, and it has a star * before the parameter name.

Examples

~foo<name><*childs>{<
    println<format<"{} has {} childs"><name><len<childs>>>;
>}

~main<argc><argv>{<
    foo<"Ahmad"><"Mohammed"><"Ali"><"Khalid">;
>}

Output:

Ahmad has 3 childs

A function with a packing parameter can be called with any number of arguments, the packing parameter will pack all the arguments into a list.

With anonymous functions

Packing parameters can be used with anonymous functions, the same as other functions.

~main<argc><argv>{<
    <name><*childs>{<println<format<"{} has {} childs"><name><len<childs>>>;>}
        <"Ahmad"><"Mohammed"><"Ali"><"Khalid">;
>}

Output:

Ahmad has 3 childs

Unpacking Arguments

A unpack argument is an argument that can be used to unpack a list into multiple arguments. The unpack argument must be an array, and it has a 3-dots ... before the argument name.

Examples

~foo<name><*childs>{<
    println<format<"{} has {} childs"><name><len<childs>>>;
>}

~main<argc><argv>{<
    childs = ["Mohammed", "Ali", "Khalid"];
    // The childs list will be unpacked into 3 arguments
    foo<"Ahmad"><...childs>;
>}

Output:

Ahmad has 3 childs

With anonymous functions

Unpacking arguments can be used with anonymous functions, the same as other functions.

~main<argc><argv>{<
    <name><*childs>{<println<format<"{} has {} childs"><name><len<childs>>>;>}
        <"Ahmad"><...["Mohammed", "Ali", "Khalid"]>;
>}

Output:

Ahmad has 3 childs

Variables

A variable is a name that is used to store a value. The value can't be changed later, but you can change the value of the variable after is owned by another variable/parameter. see ownership.

Ownership

A variable is owned by the function that it is defined in, and it is destroyed when the function ends. After using a variable, you can't use it again because the value has been moved to another variable/parameter.

Syntax

A variable is defined by its name and its value. The name of the variable can contain letters, numbers, and underscores, and it can't start with a number, also the variable name is case-sensitive. And the variable name must be [snake case].

Examples

~main<argc><argv>{<
    name = "Ahmed";
    age = 20;
    is_student = true;
    height = 1.75;
    grades = [100, 90, 80, 70, 60];
    println<
        format<"{} is {} years old, and he is {} a student, and his height is {}. His grades are {}">
        <name><age><is_student><height><grades>
    >;
>}

Output:

Ahmed is 20 years old, and he is true a student, and his height is 1.75. His grades are [100, 90, 80, 70, 60]

Data Types

A data type is a type of data that can be stored in a variable, parameter, or returned from a function. The data types in Ocypode are:

Strings

A string is a data type that can be used to store text, and it is used to represent a sequence of characters.

Syntax

A string is defined by a sequence of characters surrounded by double quotes "", and the characters can be any character except the double quote ", and the backslash \.

Escape Sequences

The backslash \ can be used to escape a character, and it can be used to escape the following characters:

  • ": Escape the double quote.
  • \: Escape the backslash.
  • n: Escape the new line character.
  • t: Escape the tab character.
  • r: Escape the carriage return character.

Examples

~main<argc><argv>{<
    name = "Ahmed";
    address = "Cairo, Egypt";
    println<format<"His name is {}\nHe resides in in \"{}\""><name><address>>;
>}

Output:

His name is Ahmed
He resides in "Cairo, Egypt"

Muilti-line Strings

A string can be defined in multiple lines, just like this:

~main<argc><argv>{<
    name = "Ahmed";
    address = "Cairo, Egypt";
    println<format<"
        His name is {}
        He resides in in \"{}\"
    "><name><address>>;
>}

Output:

His name is Ahmed
He resides in "Cairo, Egypt"

Or you can use \n to define a new line, like the example above.

Integer

An integer is a data type that can be used to represent an integer number.

Syntax

An integer is defined as a sequence of digits. The digits can be any digit from 0 to 9.

Examples

~main<argc><argv>{<
    age = 20;
    println<format<"Ahmed is {} years old"><age>>;
>}

Output:

Ahmed is 20 years old

Float

A float is a data type that can be used to represent a floating point number.

Syntax

A float is defined as a sequence of digits separated by a decimal point .. The digits can be any digit from 0 to 9.

Examples

~main<argc><argv>{<
    pi = 3.14;
    e = 2.718;
    println<format<"pi = {}, e = {}"><pi><e>>;
>}

Output:

pi = 3.14, e = 2.718

Booleans

A boolean is a data type that can be either true or false, and it is used to represent a logical value.

Syntax

A boolean is defined by the keywords true or false.

Examples

~main<argc><argv>{<
    is_student = true;
    is_teacher = false;
    println<format<"{} is a student, and {} is a teacher"><is_student><is_teacher>>;
>}

Output:

true is a student, and false is a teacher

Arrays

An array is a data type that can store multiple values in a single variable, and it is used to represent a list of values, values can be of any data type.

Syntax

An array is defined by a sequence of values surrounded by square brackets [], and the values can be any data type, and they are separated by a comma ,.

Examples

~main<argc><argv>{<
    names = ["Ahmed", "Mohammed", "Ali"];
    ages = [20, 21, 22];
    persons = [
        ["Ahmed", 20],
        ["Mohammed", 21],
        ["Ali", 22]
    ];
    println<format<"{}, {}, {}"><names><ages><persons>>;
>}

Indexing

Soon

Slicing

Soon

Nil

A nil is a data type that can be used to represent a null value.

Syntax

A nil is defined by the keyword nil.

Examples

~main<argc><argv>{<
    name = nil;
    println<format<"name = {}"><name>>;
>}

Output:

name = nil

Built-in Functions

Built-in functions are functions that are built into the language. They are available to use without importing any modules. They are also available to use without having to declare them. They are just there. They are built-in.

print built-in function

print is a built-in function to print a value to the stdout. It takes one argument and prints it to the stdout. It does not add a newline character at the end of the output.

What can it print?

print can print any data type

Examples

~main<argc><argv>{<
    print<"Hello, World!">;
>}

Output:

Hello, World!โŽ
~main<argc><argv>{<
    print<1>;
>}

Output:

1โŽ

println built-in function

println is a built-in function to print a value to the stdout. It takes one argument and prints it to the stdout. It adds a newline character at the end of the output.

What can it print?

println can print any data type

Differences between print and println

print and println are very similar. The only difference is that println adds a newline character at the end of the output.

Examples

~main<argc><argv>{<
    println<"Hello, World!">;
>}

Output:

Hello, World!
~main<argc><argv>{<
    println<[1,2,3,[4,5,6]]>;
>}

Output:

[1, 2, 3, [4, 5, 6]]

format built-in function

format is a built-in function to format a string. It takes first argument as format string and the rest of the arguments as values to format. It returns a formatted string.

What can it format?

format can format any data type

Format string

The format string is a string that contains a placeholders. and the placeholders are replaced with the values passed as arguments.

Placeholders

Placeholders are curly brackets like {} and this curly bracket will be replaced with the values passed as arguments. Also the placeholders can contain a number to specify the index of the argument to use. The index starts from 0.

Examples

In this example, the first placeholder {} will be replaced with the first argument passed to the format function, and the second placeholder {0} contains a number 0 which is the index of the first argument passed to the format function, so the second placeholder will be replaced with the first argument passed to the format function.

~main<argc><argv>{<
    formatted_string = format<"{} {0}"><"Hello, World!">;
    println<formatted_string>;
>}

Output:

Hello, World! Hello, World!

In this example, the first placeholder {} will be replaced with the first argument passed to the format function, and the second placeholder {} will be replaced with the second argument passed to the format function.

~main<argc><argv>{<
    formatted_string = format<"Hi {} you are from {}"><"Jhon"><"USA">;
    println<formatted_string>;
>}

Output:

Hi Jhon you are from USA

Errors

format function will throw an error if the format string contains a placeholder with a number that is greater than the number of arguments passed to the format function. Also it will throw an error if the format string contains a placeholder with invalid index. For example, if the format string contains a placeholder with a number that is less than 0.

len built-in function

len is a built-in function to get the length of a object. It takes one argument and returns the length of the object.

What can it get the length of?

len can get the length of strings and arrays.

Errors

len will throw an error if its argument is not a string or array.

Examples

~main<argc><argv>{<
    len_of_string = len<"Hello, World!">;
    println<len_of_string>;
>}

Output:

13
~main<argc><argv>{<
    len_of_array = len<[1,2,3,[4,5,6]]>;
    println<len_of_array>;
>}

Output:

4

input built-in function

input is a built-in function to get input from the user. It takes one argument, the prompt to display to the user. It returns the input from the user as a string.

Errors

input will throw an error if its cannot get a input from stdin.

Examples

~main<argc><argv>{<
    user_name = input<"Enter your name: ">;
    user_age = input<"Enter your age: ">;
    println<
        format<"Hello {}! you are {} years old"><user_name><user_age>
    >;
>}

Output:

Enter your name: John
Enter your age: 20
Hello John! you are 20 years old

push built-in function

push is a built-in function to add an element to the end of an array. It takes two arguments, the array and the element to add. It returns the array with the element added to the end.

Examples

~main<argc><argv>{<
    array = [1,2,3];
    array = push<array, 4>;
    println<array>;
>}

Output:

[1, 2, 3, 4]
~main<argc><argv>{<
    array = [1,2,3];
    array = push<array, 4>;
    array = push<array, 5>;
    array = push<array, 6>;
    println<array>;
>}

Output:

[1, 2, 3, 4, 5, 6]

pop built-in function

pop is a built-in function to remove the last element from an array. It takes one argument and returns the array without the last element.

Examples

~main<argc><argv>{<
    array = [1,2,3];
    array = pop<array>;
    println<array>;
>}

Output:

[1, 2]
~main<argc><argv>{<
    array = [1,2,3];
    array = pop<array>;
    array = pop<array>;
    array = pop<array>;
    println<array>;
>}

Output:

[]
~main<argc><argv>{<
    array = [1,2,3];
    array = pop<array>;
    array = pop<array>;
    array = pop<array>;
    array = pop<array>;
    println<array>;
>}

Output:

[]