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, andf
for floating point numbers) - the number denotes the actual size of the underlying number in bits
- the letter denotes the type of number (e.g.,
- 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;