Język cechują następujące założenia dotyczące zmiennych:
- Silne Typowanie - Zmienne mają określony typ i nie mogą być porównywane z innymi typami. Nie można bez konwersji przypisać wartości zmiennej jednego typu do zmiennej innego typu.
- Domyślnie Stałe - Zmienne są domyślnie stałe czyli muszą zostać zadeklarowane z wartością, której później nie możemy zmieniać podczas wykonywania programu. Zmienne dynamiczne możemy zadeklarować przy pomocy słowa kluczowego
mut
. - Przekazywane przez Referencję - Zmienne przekazywane do funkcji są przekazywane przez referencję czyli funkcja pracuje na oryginalnych zmiennych i zmienia ich wartości.
-
Stałe globalne:
- definiowane poza przed funkcją main()
-
Podstawowe:
- Integer (
int
) - Float (
float
) - String (
str
) - Boolean (
bool
)
- Integer (
-
Złożone:
- Range (
range
) - Struct (
struct
)
- Range (
-
Parametryzowane:
- Variant (
variant
) - Pointer (
@
) - List (
list
)
- Variant (
Komentarze nie mogą być zagnieżdżone
-
Komentarze jednoliniowe pisane są za pomocą znaku
#
:# to jest komentarz jednoliniowy
int::x = 5; # to tez jest komentarz jednoliniowy
-
Komentarze wieloliniowe są pisane za pomocą sekwencji początkowej
/#
i kończone za pomocą sekwencji#/
. Wszystko pomiędzy tymi sekwencjami jest traktowane jako komentarz i jest ignorowane przez kompilator. Komentarze wieloliniowe nie mogą być zagnieżdżone wewnątrz innych komentarzy wieloliniowych.:/# to jest komentarz wieloliniowy #/
int::start = 5; /# to też jest komentarz wieloliniowy #/ int::end = 5;
int::start = 5; /# to też jest BŁĘDNY komentarz !!! /# Tak nie wpisujemy komentarzy #/ #/ int::end = 5;
- Podstawowe:
int::name = 3;
float:: name1 = 5.0, name2=3.1;
str::name1=nae=me2="text";
bool::cond1=0;
bool::cond2=true;
bool::cond3=false;
-
Złożone
-
Range - jest przedziałem czyli prostą listą zawierającą liczby typu Integer od w jakimś zakresie (włączając krańce zakresu) o zadanym kroku. Range działa jak lista czyli możemy odwoływać się do elementów za pomocą indeksów ale nie wspiera metod które ma lista. Nie może być też mutowalny. Parametr krok jest opcjonalny i domyślnie równy 1.
range::nazwa = int..int:int;
range::trio = 1..3; #da zakres {1, 2, 3} range::even = 0..10:2; #da zakres {0, 2, 3, 4, 6, 8, 10} range::odd = 1..6:2; #da zakres {1, 3, 5} range::step = 1..5:5; #da zakres {1}
-
Struct - reprezentuje strukturę, która może zawierać różne typy. Możemy odwoływać się do konkretnych pól struktury.
struct::name( typ1::field1 = value1; typ2::field2 = value2; ... );
struct::car ( str::brand = "Mazda"; str::model = "CX5"; bool::for_sale=true; int::capacity = 5; int::doors_num = 5; int:horse_power = 250; );
Struktura jest domyślnie niemutowalna. Tak zdefiniowana strukturę można uzupełnić wartościami tylko przy deklaracji. Nie można zmieniać wartości pól struktury.
struct::car( str::model; );
Strukturę niemutowalną możemy inicjować różnymi wartościami w następujący sposób:
struct::car( str::model="default"; int::hp=0; ); car::mycar(model="Mustang", hp=250);
Aby zdefiniować zmienną mutowalną struktury robimy tak:
mut struct::car(model="Mustang", hp=250);
-
-
Parametryzowane
- Variant - typ danych który może przechowywać dane różnego typu w jednym miejscu w pamięci. Posiada ukryty znacznik
tag
który informuje jaki typ danych przechowuje w danym momencie. Jest domyślnie mutowalny ponieważ nie ma sensu żeby był stały. Zamiast stałego variantu możemy zdefiniować zwykłą stałą zmienną. Posiada metode holding() która zwraca typ jaki aktualnie przechowuje.przykłady:variant::name( type1, type2, ... );
variant::storage( int, string, float ); storage::box_1 = "text";
- Variant - typ danych który może przechowywać dane różnego typu w jednym miejscu w pamięci. Posiada ukryty znacznik
- Zmienne i Konwersja typów
Zmienne mają stałe typowanie więc aby przypisać do zmiennej string wartośc zmiennnej int musimy użyć konwersji za pomocą słówka kluczowego
as
.int::number = 5; mut string::text = ""; text = number as[string]; println(text) #outputs "5"
Przypisanie do zmiennej int wartości zmiennej float spowoduje zaokrąglenie w dół do liczby całkowitej. Przypisanie do zmiennej string wartości dowolnego typu spowoduje przekonwertowanie na napis. dla boolean 1 - >string = true, dla boolean = 0 -> string = false.
-
Konstrukcje warunkowe
if, else
przykłady:if warunek[ ... ]
if warunek[ ... ] else [ ... ]
-
Pętla Jedyną dostępną instrukcją z pętlą jest
while
. przykłady:while warunek[ ... ]
-
Funkcje
- zmienne lokalne o określonym zasięgu
- przyjmują argumenty przez referencję
- mogą być wywoływane rekursywnie
fun type0::name(type1::argument1, type2::argument2, ...)[ # code goes here return type0; ]
-
Funkcje Standardowe
print()
- wypisuje argument na standardowe wyjścieprintln()
- wypisuje argument na standardowe wyjście kończąc znakiem nowej liniiinput()
- pobiera wartość jako string z standardowego wejściaint::len()
- zwraca ilość znaków w string lub liczbę elementów w tablicy
-
Główna Funkcja Programu Głowna funkcja nazywa się
main
i jest typu void (pustego). Funkcja main nie przyjmuje argumentów i nie zwraca wartości. Kończy się pustym statementemreturn;
main()[ # tu znajduje się kod programu return; ]
-
Dołączanie zewnętrznych plików za pomocą słówka kluczowego
usefile
(funkcjonalność opcjonalna)usefile file_name;
- Variables part 1
# variables part 1
# Execution starts from function 'main'
fun main()[
int::number = 5;
float::fraction = 0.5;
str::text = "some text 'lalala' \n";
bool::condition_true = true;
bool::condition_false = 0;
range::count = 1..5;
# Variables are static by default - cannot change their value
# Error: Attempt to modify immutable variable:
# number = 6;
# This is the correct way
mut int::mutable_number = 0;
mutable_number = 5;
# Cannot change type of variable
# Error: cannot assign value of type str to variable 'mutable_number' of type int`
# mutable_number = "number in text";
# Variables can be scoped using blocks
[
int::scoped_int = 10;
]
# Error variable 'scoped_int' is undefined
# println(scoped_int);
return;
]
- Variables part 2
fun main()[
# variables are static by default so we must make them mutable
mut string::number;
# print function takes multiple arguments seperated by coma and outputs them as strings
print("enter number", "from 0 to 9:");
# takes input from stdin
number = input();
# prints empty line
println()
print("Wprowadzona liczba to: ", number);
return;
]
- Variables part 3
fun main()[
int::age = 20;
float::weight = 72.5;
str::name = "tim";
str::surname = "Jonsky";
# string concatenation
str::full_name = name + " " + surname;
bool::adult = true;
# mutable boolean variable
mut bool::alive = 1;
println("This is ", full_name, "he is ", age " years old");
if alive [
println("he is alive");
]else[
println("he is dead");
]
alive = 0;
println("Now value of 'alive' is:", alive); # prints "Now value of 'alive' is:false"
return;
]
- Conversion examples
fun main()[
int::number = 123;
float::decimal = 45.67;
str::text1 = "789";
str::text2 = "7.9";
# int to str
str::number_as_text = number as[str];
println(number_as_text); # outputs "123"
# float to str
str::decimal_as_text = decimal as[str];
println(decimal_as_text); # outputs "45.67"
# str to int
int::text_as_number = text as[int];
println(text_as_number); # outputs 789
# str to float
float::text_as_decimal = text as[float];
println(text_as_decimal); # outputs 7.9
# Uwaga: Konwersje mogą nie działać dla wszystkich wartości i typów.
# Na przykład próba konwersji "hello" na int prawdopodobnie spowoduje błąd.
return;
]
- Arithmetics
fun main()[
int::a = 10;
int::b = 20;
float::x = 5.5;
float::y = 2.5;
# Addition using +
mut int::c = a;
c = c + b;
println(c); # outputs 30
# Subtraction using -=
mut int::d = a;
d -= b;
println(d); # outputs -10
# Adding a float to an int
# The result will be a float because of type promotion
float::add_mixed = a + x;
println(add_mixed); # outputs 15.5
# Multiplying int by float
# The result will be a float because of type promotion
float::mul_mixed = b * x;
println(mul_mixed); # outputs 110.0
# If you want the result to be an int, you can explicitly convert it
int::mul_mixed_int = (b * x) as[int];
println(mul_mixed_int); # outputs 110
# Increment (increase value by 1 using +=)
mut int::i = 0;
i += 1;
println(i); # outputs 1
# Decrement (decrease value by 1 using -=)
mut int::j = 5;
j -= 1;
println(j); # outputs 4
return;
]
- Mutable and immutable variables
fun main()[
# Define a mutable variable a with value 5
mut int::a = 5;
# Define an immutable variable b and assign a to it
int::b = a;
# Modifying a
a = 10;
# Printing the values of a and b
println(a); # outputs 10
println(b); # outputs 10
# Trying to modify b directly will result in an error
# b = 15; # Error: Attempt to modify immutable variable
return;
]
- Functions
# Void function
fun foobar()[
println("Im just printing");
return;
]
# Function for incrementing by 3
fun str::increment(mut int::value)[
value += 3;
return "incremented with success";
]
# Function to calculate the area of a rectangle
fun int::area(int::length, int::width)[
# Calculate area
int::result = length * width;
# Return the result
return result;
]
# Function to calculate the perimeter of a rectangle
fun int::perimeter(int::length, int::width)[
# Calculate perimeter
int::result = 2 * (length + width);
# Return the result
return result;
]
fun main()[
# Define mutable variable for incrementation
mut int::a = 5;
str::result = increment(a);
println(a) # outputs 8
# passing immutable int to function will generate error
int::b = 5;
# str::result = increment(b);
# Error: Attempt to modify passed immutable variable 'b' inside function 'increment'.
# Define dimensions of the rectangle
int::l = 5;
float::w = 10.0;
# Call the functions and store the results
int::rect_area = area(l, w as[int]);
int::rect_perimeter = perimeter(l, w as[int]);
# Print the results
println("Area of the rectangle: ", rect_area); # outputs 50
println("Perimeter of the rectangle: ", rect_perimeter); # outputs 30
# using undefined function
# int::v = volume(l, w, l);
# Error: Function 'volume' not defined in the current scope. Ensure it's correctly imported or defined.
return;
]
- Recursive Functions
fun int::factorial(int::n) [
if n <= 1 [
return 1;
] else [
return n * factorial(n - 1);
]
]
- Condition statements
# Function to check if a number is prime
fun bool::is_prime(int::number)[
# 0 and 1 are not prime numbers
if number <= 1 [
return false;
]
# 2 is the only even prime number
if number == 2 [
return true;
]
# All other even numbers are not primes
if number % 2 == 0 [
return false;
]
# Check factors up to the square root of the number
int::limit = number ^ 0.5 as[int];
mut int::i = 3;
while i <= limit [
# If i is a factor of number, number is not prime
if number % i == 0 [
return false;
]
i += 2;
]
# If no factors were found, then number is prime
return true;
]
fun main()[
int::num = input("type number:"); # take number from stdin
bool::result = is_prime(num);
# Print whether the number is prime or not
if result [
println(num, " is a prime number."); # Outputs "29 is a prime number."
] else if !result[
println(num, " is not a prime number.");
] else [
# this line will never execute
]
return;
]
- Use statement
filename: square.xx :
# This is the math_functions file
fun int::square(int::num)[
return num * num;
]
filename: program.xx:
# This is the main_program file
# Import the function from square.xx
usefile square.xx;
fun main()[
int::number = 4;
int::result = square(number);
println("The square of ", number, " is ", result); # Outputs "The square of 4 is 16"
return;
]
- Pointers, Variants file books.xx:
float::PRICE = 9.81;
struct::book (
str::title="default";
str::author="default";
mut bool::is_borrowed;
mut float::price;
);
mut range::even = 0..10:2; # {0, 2, 4, 6, 8, 10}
mut str[3]::genre_list = ("Fiction", "Non-Fiction", "Fantasy");
file program.xx
usefile "books.xx";
fun main()[
# Creating a book instance
book::my_book = (title = "The Great Adventure", author = "John Doe", is_borrowed = false, price = 15.99);
# Using a variant to store a book's status
variant::status_data = (
int,
str,
bool
);
variant status_data::my_status = "true";
variant status_data::sb_status = 1;
# check variant current type that it's storing
if my_status.holding() == int [
println("im holding int");
] else [
println("sb is holding ", sb_status.holding());
]
print(status_data); # outputs 'false'
status_data = "unknown state"
print(status_data); # outputs 'unknown state'
# Using a range to get even numbers (as an example)
int::first_even = even[0]; # gets the first even number from the range from books.xx
mut int::i = 0;
mut int::j = 0;
while i < len(matrix[0])[
while j < len(matrix)/len(matrix[0])[
print(matrix[i][j], " ");
]
println();
]
# Displaying the details
println("Title: ", my_book.title);
println("Author: ", my_book.author);
println("Price (via pointer): ", @price_ptr);
return;
]
- Brak pliku który importujemy
usefile file.xx
>> Error [1:9]: File 'filename.txt' not found.
- Użycie niezdefiniowanej zmiennej
int::result = a + b;
>> Error [1:15]: Variable 'a' is undefined.
- definiowanie zdefiniowanej zmiennej
int::result = 5;
int::result = 2;
>> Error [2:6]: Variable 'result' is already defined.
- Użycie niezdefiniowanej funkcji
int::result = foo(5);
>> Error [1:15]: Function 'foo' is undefined.
- Brak średnika
int::result = a
int::sum = b;
>> Error [1:15]: Missing semicolon at the end of the line.
- Nawiasy
fun int::add(int::x, int::y) [
return x + y;
>> Error [2:17]: Unmatched '['. Expected ']' before end of function.
- Użycie słówka kluczowego jako nazwy
int::fun = 5;
>> Error [1:6]: Cannot use reserved keyword 'fun' as variable name.
- Złe przypisanie typów
str::name = 12345;
>> Error [1:12]: Type mismatch. Expected 'string' but found 'int'.
- Zła operacja na typach
str::text = "hello";
int::result = text + 5;
>> Error [2:18]: Type mismatch in operation. Cannot add 'string' and 'int'.
- Pozostałę przykłady komunikatów:
Error [5:20]: Mismatched parentheses, expected ')'.
Error [8:33]: Too many arguments in function call to 'function_name'.
Error [9:35]: Too few arguments in function call to 'function_name'.
Error [10:40]: Return type mismatch in function 'function_name'.
Error [11:45]: Expected 'type1', but got 'type2'.
Error [12:50]: Array index out of bounds.
Error [13:55]: Pointer dereference error.
Error [14:60]: Division by zero.
Error [15:65]: Invalid type casting.
Error [16:70]: Incompatible pointer types.
Error [18:80]: 'break' statement not within loop.
Error [19:85]: 'continue' statement not within loop.
Error [27:125]: Invalid use of 'final' keyword.
Error [32:150]: Invalid initializer.
Error [37:175]: Read-only variable is not assignable.
`Error [x:x]: unexpected token: 'something'; wanted any of: identifier, 'if', 'while', 'return', '{'`
Kompilator jest podzielony na moduły:
- Czytnik Znaków (CharReader) -> Char
Czyta wejście (plik lub strumień) znak po znaku i przekazuje te znaki do Leksera gdy ten zgłosi że potrzebuje kolejnego znaku.
- Analizator Leksykalny (Lekser) -> Token
Pobiera znaki od CharReader'a i generuje z nich Tokeny. W momencie gdy przetworzy znak zgłasza CharReader'owi, że potrzebuje kolejnego i wtedy on przetwarza kolejny znak i podaje go Lekserowi.
- Anlizator Składniowy (Parser) -> AST
Pobiera Tokeny od Lekser'a i tworzy z nich drzewo AST. Pobiera Tokeny jeden po drugim z Lekser'a i podejmuje decyzje dotyczące tworzenia drzewa AST w miarę ich otrzymywania.
- Analizator Semantyczny (SemanticChecker) -> AST
Sprawdza drzewo AST pod kątek błędów semantycznych.
- Interpreter drzewa AST -> Program
Wykonuje instrukcje z podanego drzewa AST.
Poza testami jednostkowymi do możliwie każdej napisanej funkcji, testowane będą również moduły w następujący sposób.
- CharReader - Testy jednostkowe przez podawanie ciągów znaków (plik i strumień).
- Lekser - Testy jednostkowe przez podawanie ciągów znaków (plik i strumień).
- Parser - Testy integracyjne przez podawanie ciągów Tokenów.
- SemanticChecker - Testy integracyjne przez podawanie drzew AST
- Interpreter - Testy integracyjne przez podawanie drzew AST Cały kompilator będzie testowany przez podawanie na wejście plików z kodem źródłowym lub kodu źródłowego w strumieniu. Dzięki temu będziemy testować połączenie poszczególnych komponentów i czy działają ze sobą poprawnie.