Język programowania ogólnego przeznaczenia.
Liczby całkowite (int):
- Przechowują liczby całkowite od -2147483648 do 2147483647.
Liczby zmiennoprzecinkowe (float):
- Przechowują liczby zmiennoprzecinkowe od -3.40282347E+38 do 3.40282347E+38.
- Operacje arytmetyczne na liczbach zmiennoprzecinkowych obejmują te same operacje co dla liczb całkowitych.
Wartości logiczne (bool):
- Przechowują jedną z dwóch wartości logicznych: true lub false, stosowane głównie w operacjach warunkowych.
Ciągi znaków (string):
- Przechowują sekwencję znaków w ilości od 0 do 2147483647, umożliwiając operacje na tekstach.
Typy użytkownika (class type):
- Użytkownicy mogą definiować własne typy danych, tworząc klasy, które zawierają właściwości i metody, pozwalając na tworzenie złożonych struktur danych.
Operacje i operatory arytmetyczne:
- Dodawanie: +
- Odejmowanie: -
- Mnożenie: *
- Dzielenie: /
- Dzielenie całkowite: /.
- Dzielenie modulo (reszta z dzielenia): %
var int a = 10;
var float b = 5.5;
var int sum = a + 2; // 12
var float result = b * 2.0; // 11.0
Operatory przypisania:
- Przypisanie: =
Operatory relacji:
- Równość: ==
- Nierówność: !=
- Większe od: >
- Mniejsze od: <
- Większe bądź równe: >=
- Mniejsze bądź równe: <=
Operatory logiczne:
- Operator && – zwraca true, jeśli oba warunki są spełnione.
- Operator || – zwraca true, jeśli co najmniej jeden warunek jest spełniony.
- Operator ! – zwraca wartość przeciwną (true zamienia na false i odwrotnie).
var bool isEven = (a % 2 == 0);
var bool inRange = (a > 5 && a < 15);
Priorytety i łączność operatorów:
Standardowe reguły matematyczne obowiązują w przypadku priorytetów operacji arytmetycznych. Nawiasy mają pierwszeństwo, umożliwiając wykonywanie operacji w określonej kolejności niezależnie od domyślnego priorytetu operatorów.
Komentarze zaczynają się od znaków // i kończą się nową linią, są ignorowane przez interpreter. Służą do dodawania notatek lub wyjaśnień w kodzie.
Zmienne powinny być tworzone z deklaracją typu i mają dynamicznie przypisywaną wartość. Czas życia zmiennej jest związany z zakresem, w którym została zdefiniowana.
func exampleFunc() : void {
var int funcVar = 10;
if(funcVar == 10)
{
var int ifVar = funcVar - 5;
}
output(ifVar) //Semantic error: Cannot find symbol "ifVar"
}
Jest to język o typowaniu silnym, czyli operacje na różnych typach danych są ściśle określone i nie ma automatycznych konwersji. Jednakże, istnieje możliwość użycia funkcji rzutowania typów t.j. to_int() oraz to_float().
Wszystkie typy danych języka są mutowalne.
W języku istnieją instrukcje warunkowe (if, elif, else) oraz pętla while. Przykładowo:
var int x = 5;
if (x > 0) {
output("x is positive");
} elif (x == 0) {
output("x equals zero");
} else {
output("x is negative");
}
var int counter = 3;
while (counter > 0) {
output("Counting down: ");
output(counter);
counter = counter - 1;
}
Można definiować i wywoływać własne funkcje za pomocą słowa kluczowego func. Typ zwracanej wartości powinien być zdefiniowany przez programistę (func float). Argumenty funkcji są przekazywane przez wartość lub referencję, w zależności od typu danych (bool, int, float - wartość; string, obiekty class - referencja).
func calculateArea(float radius) : float {
var float pi = 3.14159;
return pi * radius * radius;
}
Funkcje wbudowane dostarczają dodatkowe możliwości, takie jak output(), input(), int(), float() itp..
Język obsługuje rekursję, czyli wywoływanie funkcji przez samą siebie.
func factorial(int number) : int {
if (number <= 1) {
return 1;
} else {
return number * factorial(number - 1);
}
}
Można tworzyć własne klasy, które są szablonami dla obiektów. Klasy mogą zawierać właściwości (zmienne) i metody (funkcje). Jest możliwa agregacja obiektów klas.
class Person {
var string _name = "Unknown";
var string _surname = "Unknown";
var string _id = "000000";
func Person(string name, string surname, string id) : Person {
_name = name;
_surname = surname;
_id = id;
}
}
Agregacja
W języku można również stosować agregację, co oznacza, że jeden obiekt może zawierać inne obiekty jako swoje składniki.
class Address {
var string _city = "Warsaw";
var string _street = "Nowowiejska";
func Address(string city, string street) : Adress {
_city = city;
_street = street;
}
}
class Student : Person {
var string _name = "Unknown";
var string _surname = "Unknown";
var string _student_id = "000000";
var Address _address = Address();
func Student(string name, string surname, string id, string student_id, Address address) : Student {
_name = name;
_surname = surname;
_student_id = student_id;
_address = address;
}
}
Tworzenie i używanie obiektów:
Tworzenie obiektu klasy odbywa się za pomocą konstruktora (użytkownika, domyślnego albo kopiującego). Używa się konstruktor domyślny, jeżeli nie zostały podane potrzebne parametry lub inny nie został zaimplementowany przez programistę:
var Address address = Address("Warsaw", "Main Street");
var Student student = Student("Anton", "Basan", "324456", address);
student._adress._street = "Starowiejska";
output(student._street);
program = { class_def | function_def } ;
class_def = "class", identifier, class_block;
function_def = "func", func_type, identifier, "(", parameters ")", block;
parameter_list = [ parameter, { ",", parameter } ] ;
parameter = var_type, identifier ;
func_type = var_type | "void" ;
var_type = "int" | "float" | "string" | "bool" | identifier ;
class_block = "{", { var_def_statement | function_def }, "}";
block = "{", { statement }, "}" ;
statement =
(
var_def_statement |
assign_statement |
function_call |
if_statement |
while_statement |
throw_statement |
return_statement
);
expression = [ "-" | "!" ], identifier | int_literal | float_literal | string_literal | bool_literal | identifier_or_call | "(", conditional expression, ")" ;
conditional_expr = logical_or_expr, { "&&", logical_or_expr} ;
logical_and_expr = logical_expr { "||", logical_expr } ;
logical_expr = arithmetical_expr, [ "<" | ">" | "<=" | ">=" | "==" | "!=", arithmetical_expr ] ;
arithmetical_expr = multiplicational_expr, { ( "+" | "-" ), multiplicational_expr } ;
multiplicational_expr = term, { ( "*" | "/" | "/." | "%" ), term } ;
term = ["-" | "!"], (identifier_or_call | "(", expression, ")" | literal);
data_access = identifier_or_call, {".", identifier_or_call} ;
identifier_or_call = identifier, ["(", argument_list, ")"];
if_statement = "if", "(", conditional_expr ")", block, { "elif", "(", conditional_expr ")", block }, ["else", block] ;
while_statement = "while", "(", conditional_expr, ")", block ;
return_statement = "return", [ expression ], ";" ;
var_def_statement = var_type, assign_statement;
argument_list = [ expression, { "," , expression } ] ;
assign_statement = data_access, ["=", expression] }, ";" ;
throw_statement = "throw", [string_literal], ";" ;
letter = "a" | "b" | "c" | "d" | ... | "x" | "y" | "z" ;
first_digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
int_literal = first_digit, { digit } ;
float_literal = digit, ".", { digit } ;
string_literal = '"', { letter | digit | special_char }, '"' ;
bool_literal = "true" | "false" ;
identifier = letter, { letter | digit } ;
Błędy są obsługiwane przez każdy z interfejsów:
- Lexer - błędy leksykalne (np. "Lexical error: Unexpected operator! 1:1")
- Parser - błędy składniowe (np. "Syntax error: Semicolon expected! 1:1")
- Interpreter - błędy semantyczne (np. "Semantic error: Symbol "variable" already exists. line: 1")
- Plik źródłowy: Zawiera kod w języku zdefiniowanym przez interpreter.
- Strumienie danych: Pozwala na interakcję z programem poprzez wprowadzanie danych wejściowych i odbieranie danych wyjściowych.
Uruchomienie kodu odbywa się z tekstowego pliku źródłowego przez podaną ścieżkę do niego. Do pobierania danych od użytkownika z klawiatury służy funkcja input(). Typ pobranych danyh ustala interpretor:
var string name = input();
output(name);
Funkcja input() wyświetli wiadomość podaną jako argument i zatrzyma wykonanie programu, czekając na wprowadzenie danych przez użytkownika. Po naciśnięciu klawisza Enter, wprowadzone dane będą zwrócone jako string.
Do wyświetlania danych dla użytkownika używa się funkcji output():
output("Hello, world!");
Funkcja output() służy do wyświetlania tekstu lub wartości różnych typów danych. Można podać wiele argumentów oddzielonych przecinkami, a output() wyświetli je w jednej linii, domyślnie oddzielając spacją.
- Obsługa podstawowych typów danych liczbowych:
- operacje matematyczne o różnym priorytecie wykonania, obsługa nawiasów
- operacje logiczne i porównania o różnym priorytecie wykonania, obsługa nawiasów
- Obsługa typu znakowego
- Obsługa komentarzy
- Tworzenie zmiennych, przypisywanie do nich wartości i odczytywanie ich
- Instrukcje warunkowe
- Instrukcje pętli
- Wywołanie i definiowanie własnych funkcji (ze zmiennymi lokalnymi)
- Rekursywne wywołania funkcji
- Tworzenie klas i wykorzystanie ich obiektów
- Agregacja klas
- Czytelnność języka
- Wygodność dla programisty
Opis: Pierwszy etap przetwarzania, podczas którego sekwencja znaków jest analizowana na podstawie zdefiniowanych tokenów (np. identyfikatory, słowa kluczowe, operatory, liczby).
Realizacja: Wykorzystanie analizatora leksykalnego do identyfikacji i zwracania tokenów, gdzie każdy token reprezentuje określony rodzaj leksemu w języku.
Opis: Konwertuje ciąg tokenów na strukturę drzewa składniowego zgodnie z regułami gramatyki języka.
Realizacja: Użycie parsera do sprawdzania poprawności składniowej i tworzenia drzewa składniowego zgodnie z zdefiniowaną gramatyką języka.
Opis: Proces weryfikacji znaczenia i poprawności programu, takie jak deklaracje zmiennych, typy danych, kontekst zmiennych i ich używanie.
Realizacja: Wykorzystanie analizy semantycznej do zapewnienia spójności w programie, sprawdzając typy danych, rozpoznając zasięg zmiennych i wykonywanie operacji semantycznych.
Opis: Interpretacja skompilowanego drzewa składniowego i wykonanie operacji zgodnie z zaimplementowanym zachowaniem języka.
Realizacja: Wykorzystanie wygenerowanego drzewa składniowego do interpretacji programu, wykonywanie odpowiednich operacji w zależności od rozpoznanych struktur w drzewie. Błąd wejścia/wyjścia: "Input/Output error: Unable to open file 'file_name'."
- Tokeny: Przechowywane w listach lub strukturach danych do przechowywania informacji o leksemach.
- Drzewo składniowe: Zwykle realizowane jako hierarchiczna struktura danych, na przykład obiekty reprezentujące węzły drzewa.
- Kontekst zmiennych: Używane do śledzenia zasięgu zmiennych, ich typów i wartości.
- Bufor pamięci: Do przechowywania wartości pośrednich lub wyników operacji.
- AST (Abstract Syntax Tree - Drzewo Składniowe): Struktura danych reprezentująca skompilowany program w formie hierarchicznego drzewa, używana do interpretacji.
- Lexer (Analizator leksykalny): Interfejs do przetwarzania tekstu na tokeny.które Pozwala na pobieranie, podglądanie lub przesuwanie się do następnego tokenu w tekście. Tokeny mogą być reprezentowane przez klasy przechowujące informacje o rodzaju tokenu oraz jego wartości.
- Parser (Analizator składniowy): Interfejs do przekształcania tokenów w drzewo składniowe zgodnie z gramatyką języka.
- Interpreter (Wykonywanie): Interfejs do interpretacji drzewa składniowego i wykonania operacji.
- Sprawdzenie poprawności składni języka. Na przykład, czy interpreter poprawnie interpretuje deklaracje zmiennych, struktury kontrolne (if, else, while), instrukcje warunkowe i pętle.
- Upewnienie się, czy język działa zgodnie z oczekiwaniami w zakresie operacji arytmetycznych, logicznych, operacji na typach danych (np. sprawdzanie, czy dodawanie dwóch liczb działa poprawnie).
- Testowanie funkcji wbudowanych i ich poprawności.
- Sprawdzenie reakcji języka na niepoprawne działania, na przykład podanie złej składni lub typu danych.
- Testowanie większych fragmentów kodu lub programów, aby upewnić się, że różne części języka współpracują ze sobą poprawnie.
- Testy, które porównują wyniki tworzonego języka z wynikami innego języka, jeśli jakiś kod jest równoważny i może zostać przetłumaczony na oba języki.