The Y Programming Language

by H1ghBre4k3r.

This text is meant as a basic guide to the Y Programming Language. I want to introduce you to the foundations and concepts of the language, as well as give you some orientation on what to build with it.

If you happen to find and issues within this documentation, feel free to open an issue in the official repository.

Pre-Requisites

At the current stage, the language only exists as a "theoretical concept", since the only stable (and somewhat working) components are the lexer and the parser. Due to that, the only pre-requisites for "using" the language are a working machine, terminal access with Rust and Git installed and available. First, clone the repository:

git clone https://github.com/H1ghBre4k3r/pesca-lang.git
cd pesca-lang

After that, you can just build and use the executable:

cargo build --release
./target/release/pesca-lang <FILE_NAME>

This will print you the lexed tokens, as well as the parse AST.

Language Basics

Pesca follows the foundations of many other programming languages. This chapter tries to make you familiar with these and provide you with some examples.

Variables

One of the building blocks of each Y program are variables. Variables are used to store values during the runtime of the program. Declaring and instantiating a variable is straight forward:

let foo = 42;

This snippet creates a variable with the name foo and the value 42. As you can see, a variable declaration & instantiation always consists of at least these three components:

  • the let keyword
  • an identifier for the variable
  • (technically = to perform an assignment)
  • a value to assign to this variable

Mutability

By default, variables in Y are immutable. That means, once you assigned a value to a variable, you can not re-assign this variable. E.g., the following is invalid Y code:

let foo = 42;
foo = 1337; // <- invalid

If you want to re-assign a variable, you first need to declare this variable as mutable using the mut keyword:

let mut foo = 42;
foo = 1337;

This allows you to mutate variables at your own will. However, it is discouraged to just declare every variable as mutable, since this might introduce unwanted bugs to your program.

Datatypes

In Y, every value has a type associated with it. Some types are built into the language, some are user defined. Examples for built-in types are:

  • numeric types (e.g. u32, f64, i64)
    • the letter denotes the type of number (e.g., u for unsigned, i for signed integers, and f for floating point numbers)
    • the number denotes the actual size of the underlying number in bits
  • characters (char)
  • string literals (str)
  • boolean values (bool)

These basic types do only provide limited methods or functions to interact with them. However, you can perform certain arithmetic operations on them.

Y is able to infer the types of man variables. In some cases, however, you are required to explicitly declare the type of a variable:

let foo: u32 = 42;

Control Structures

Y provides some structures to control the logic of your program.

If-Else

If you want to conditionally execute certain parts of your program, you can utilise the common if-else structure:

if (foo) {
    // do some stuff
} else {
    // do other stuff
}

The expression right after the if needs to evaluate to a boolean value. More on expressions will be discussed in a later chapter. For now, you can imagine simple comparison operations:

if (bar == 42) {
    // ...
}

The first block will be executed if the expression evaluates to true. Similarly, the second block will be executed if the expression evaluates to false.

Generally, the else-block is not required, whereas the first block is required (although it can be empty).

Loops

To repeatedly execute a block of code, Y provides you with loop structures.

While Loops

To execute a block of code while a certain expression evaluates to true, you can use the while loop:

while (foo) {
    // do something
}

Again, foo has to evaluate to a boolean value. It will be evaluated upon each run of the loop.

Intermediate Language Features

Building on the basics of Y, you can use more advanced language features of Y.

Functions

Functions are a way to group a set of instructions and give them a name. In Y, you have two different possibilities to work with functions: "Classic" Functions and Lambdas.

Classic Functions

Classic functions are declared using the fn keyword.

fn square(x: i64): i64 {
    return x * x;
}

let foo = square(42);

Functions can accept an arbitrary amount of arguments and return a value. Both, arguments and return value, have to be annotated with a type.

Lambdas

Lambdas can be either used as anonymous functions or be assigned to a variable:

let foo = takesFunction((x) => x * x);

let bar: (i32, i32) -> i32 = (x, y) = x + y;

When assigning them to a variable, you have to explicitly annotate the type of the lambda.

Function Types

As you can see in the above example, using functions introduces a new type: Functions.

Function types consist of the list of arguments and the type of the return value. Using that, you can annotate every variable, parameter or even return type of a function to be a function:

fn takesFunction(f: (i32) -> i32): i32 {
    return f(42)
}

fn returnsFunction(): (i32, i32) -> i32 {
    return (x, y) => x * y;
}

Structs

Structs are a way to group and organize related data together, introducing a new type to your program. Declaring a struct is straight forward:

struct Vector {
    x: i32,
    y: i32
};

Doing that introduces a new type to your program which can be used in any location where you are able to declare a type, e.g., function arguments or return types:

struct Foo {
     // ..
};

struct Bar {
    // ..
};

fn doSomething(input: Foo): Bar {
    // ...
}

As you can see, you can just use the name of the struct as the type name.

Instantiation

If you want to instantiate a struct, you can simply do that straight forward:

struct Vector {
    x: i32,
    y: i32
};

let someVec = Vector {
    x: 42,
    y: 1337,
};

Property Access

To access properties of a struct, you can utilise the dot operator:

struct Vector {
    x: i32,
    y: i32
};

let someVec = Vector {
    x: 42,
    y: 1337,
};

let xDirection = someVec.x;