Flux - własny język programowania ogólnego przeznaczenia z instrukcją relacyjnych wzorców (switch - matching patterns).
Filip Budzyński, numer albumu: 319021
- Opis funkcjonalności
- Dopuszczane typy danych
- Przyjęte założenia języka
- Charakterystyczne operacje
- Funkcje wbudowane
- Specyfikacja i składnia EBNF
- Przykłady dopuszczalnych konstrukcji i semantyka
- Obsługa błędów i przykłady
- Rozróżniane Tokeny
- Uruchomienie
- Konwersja typów
- Zasady przekazywania zmiennych do funkcji
- Realizacja modułów
- Testy
Implementacja wszystkich elementów projektu napisana w języku Golang, w tym Reader, Lexer, Parser i Interpreter.
Projekt języka “Flux” umożliwia:
- inicjalizację i przypisywanie zmiennych,
- wykonywanie operacji arytmetycznych,
- obsługę instrukcji warunkowych i pętli,
- definiowanie funkcji z lub bez argumentów,
- konwersję typów przy użyciu operatora
as
, - wywoływanie funkcji,
- funkcje rekurencyjne,
- obsługę relacyjnych wzorców w insturkcji switch.
- integer (int)
- float (float)
- string (string)
- boolean (bool)
- język statycznie typowany,
- argumenty przekazywane przez wartość,
- obsługa błędów na poziomie leksykalnym, syntaktycznym i semantycznym,
- wartość zmiennej może być zmieniona, ale musi zgadzać się jej typ,
- każdy program napisany w języku “flux” musi posiadać funkcję
main()
, która zawiera główne ciało programu i od tej funkcji rozpoczyna się jego działanie, - zmienna zewnętrzna może zostać przykryta poprzez zmienną o tej samej nazwie znajdującej się w bloku funkcji, pętli, instrukcji warunkowej lub instrukcji switch.
- zdefiniowanych funkcji nie można przesłaniać funkcjami z innymi argumentami,
- funkcji wbudowanych nie można przesłaniać
Operacją charakterystyczną jest dopasowanie wzorców relacyjnych
- Instrukcja
switch
działa na porównywaniu zmiennych, można ją wywołać deklarując zmienne lokalne dla wyrażenia, lub używając zmiennych zdefiniowanych w wyżyszchscopa'ach
, - Deklaracji zmiennych może być więcej niż jedna
Przykładowe wywołanie:
- z deklaracją zmiennej lokalnej dla wyrażenia:
switch int a := 2 + 2 { a == 4 => print("cztery"), default => print("na pewno nie cztery") }
- z wykorzystaniem wcześniej zdefiowanych zmiennych
int a := 3 switch { a == 4 => print("cztery"), default => print("na pewno nie cztery") }
Zachowanie instrukcji switch:
-
instukcja switch bo prawej stronie operatora
=>
może posiadać wyrażenie lub blok otwierany przy użyciu{
i zamknięty przez}
, -
instrukcja przechodzi przez napiasne przypadki i wywołuje instrukcje dla pierwszego pozytywnie zewaluowanego przypadku,
-
jeżeli po prawej stronie znajduje się
block
, instrukcja switch nie zwraca wartości, lecz ewaluuje zdefiniowany blok (w nim możena zdefiniować return), -
jeżeli chcemy aby instrukcja zwróciła jakąś wartość, po prawej stronie
=>
należy umieścieć wyrażenie z typem tej wartości, np.:- instrukcja zwracająca wartość
int
:
switch { default => 2 }
- instrukcja zwracająca wartość
bool
:
switch { default => true }
- instrukcja nie zwracająca wartość:
switch { default => { print("flux") } }
- instrukcja zwracająca wartość
W języku wbudowane są niżej wymienione funkcje:
print(...)
- funkcja drukująca przekazane wartości na Stdout, działa dla dowolnej liczby arguentów,println(...)
- funkcja bliźniacza do funkcji 'print', która dodatkowo każdy z przekazanych argumentów odziela znakiem nowej linisqrt(var1, var2 float) -> float
- funkcja zwracająca pierwiastek kwadratowy, przyjmuje argumenty typufloat
, zwraca argument typufloat
,power(var1, var2 float) -> float
- funkcja zwracająca liczbę podaną jako pierwszy argument typufloat
, podniesioną do potęgi podaną jako drugi argument typufloat
, zwraca wartość typufloat
,
- symbole terminalne wyróżnione znakiem
*
program = { function_definition } ;
function_definition = identifier , "(", [ parameters ], ")", [ type_annotation ] , block ;
parameters = parameter_group , { "," , parameter_group } ;
parameter_group = identifier , { ",", identifier }, type_annotation ;
type_annotation = "int" | "float" | "bool" | "str" ;
block = "{" , { statement } , "}" ;
statement = variable_declaration
| assignment_or_call
| conditional_statement
| loop_statement
| switch_statement
| return_statement
;
variable_declaration = type_annotation, identifier, ":=", expression ;
assignment_or_call = identifier, ( "(", [ arguments ], ")" ) | ( "=", expression ) ;
conditional_statement = "if" , expression , block , [ "else" , block ] ;
loop_statement = "while" , expression, block ;
switch_statement = "switch", [( variable_declaration, { ",", variable_declaraion } ) ], "{", switch_case, { ",", switch_case "}" ;
switch_case = ( expression | "default" ), "=>", ( expression | block ) } ;
return_statement = "return" , [ expression ] ;
expression = conjunction_term, { "or", conjunction_term } ;
conjunction_term = relation_term, { "and", relation_term } ;
relation_term = additive_term, [ relation_operator, additive_term ] ;
relation_operator = ">="
| ">"
| "<="
| "<"
| "=="
| "!="
;
additive_term = multiplicative_term, { ("+" | "-"), multiplicative_term } ;
multiplicative_term = casted_term, { ("*" | "/"), casted_term } ;
casted_term = unary_operator, [ "as", type_annotation ] ;
unary_operator = [ ("-" | "!") ], term ;
term = integer
| float
| bool
| string
| identifier_or_call
| "(" , expression , ")"
;
identifier_or_call = identifier, [ "(", [ argumets ], ")" ] ;
arguments = expression , { "," , expression } ;
identifier = letter , { letter | digit | "_" } ;
float = integer , "." , digit , { digit } ;
*integer = "0" | positive_digit , { digit } ;
*string = '"', { literal }, '"' ;
*literal = letter
| digit
| symbols
;
*bool = "true" | "false" ;
*letter = "a" | "..." | "z" | "A" | "..." | "Z" ;
*positive_digit = "1" | "2" | "3" | "4"| "5" | "6"| "7" | "8" | "9" ;
*digit = "0" | "1" | "2" | "3" | "4"| "5" | "6"| "7" | "8" | "9" ;
*symbols = "`"
| "~"
| "!"
| "@"
| "#"
| "$"
| "%"
| "^"
| "&"
| "*"
| "("
| ")"
| "_"
| "-"
| "+"
| "="
| "{"
| "}"
| "["
| "]"
| ";"
| ":"
| "'"
| ","
| "."
| "?"
| "/"
| "|"
| "\"
;
Inicjalizacja i przypisanie wartości
int a := 5
int b := 2
a = 8
Operacje arytmetyczne
int a := 3
a = a + 3 * (2 - 1)
Komentarze
# To jest komentrz
Instrukcja warunkowa
if y > 5 {
print("Y jest większe od 5")
} else {
print("Y jest mniejsze lub równe 5")
}
}
string nazwa := "Ala ma psa"
if nazwa == "Ala ma kota" {
print("Kot należy do Ani")
} else {
print("Ani to Ala ani kot")
}
Instrukcja pętli while
int num := 10
while num > 0 {
print(num)
num = num - i
}
Funkcja z argumentem
circleArea(r int) float {
return 3.14 * (r * r)
}
main(){
int r := 2
float a := circleArea(r)
print(a)
}
# output: 12.56636
Funkcja rekurencyjna
fibonacci(n) int {
if n <= 1 {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
main(){
print(fibonacci(3))
}
# output: 2
Konwersja typów
int a := 5
string c := a as string
print(c) # "5"
int b := 0
bool d := b as bool # "false"
Funkcje wbudowane
print(sqrt(9 as float))
# output: 3
print(power(3 as float, 2.0))
# output: 9
Relational patterns - switch instruction
sumUp(a,b int) int {
return a + b
}
whatWillGetMe(a,b int) string {
switch int c := sumUp(a, b) {
c>2 and c<=4 => "A pint",
c==5 => "Decent beverage",
c>5 and c<15 => "A NICE bevrage",
c>15 => "Whole bottle",
default => "Nothing today!"
}
}
main(){
print(whatWillGetMe(2,3))
}
# output: Decent beverage
giveMeWord() string {
return "word"
}
nameNumber() int {
string c := giveMeWord()
switch {
c == "Sammy" => 0,
c == "World" => 1,
c == "word" => 2,
default => 3
}
}
main(){
print(nameNumber())
}
# ourput: 2
getUserRole(userId int) string {
return "admin"
}
checkPermission(role, permission string) bool {
return role == "admin" and permission == "edit"
}
main() {
int userId := 123
switch string userRole := getUserRole(userId) {
userRole == "admin" => {
if checkPermission(userRole, "edit") {
print("Użytkownik ma uprawnienia do edycji")
} else {
print("Użytkownik nie ma uprawnień do edycji")
}
},
userRole == "user" => {
print("Użytkownik ma ograniczone uprawnienia")
},
default => {
print("Nieznana rola użytkownika")
}
}
}
# output: Użytkownik ma uprawnienia do edycji
Obsługa błędów odbywa się na wszystkich poziomach, tj.:
- lexer,
- parser,
- interpreter
Ze względu na użycie metody panic()
w golang'u, przetwarzanie programu jest przerywane po napotkaniu pierwszego błędu. Za 'łapanie' błędu są odpowiedzialne funkcję 'errorHandlers' zdefiniowane dla lexer'a i parser'a, panic wywoływany w interpreterze łapany jest w funkcji main.go, która przekazuje treść błędu na Stdout.
Początkowo realizacja miała odbyć się z propagacją błędu, po czym koncepcja została zmieniona przy uzgodnieniu z prowadzącym. W przyszłości planowana jest zmiana, przejście z funkcji panic()
na przekazywanie błędów przez wartości error.
Każdy z modułów ma zdefiniowane stałe z wiadomościami o błędach, które zawierają treść, oraz miejsce wystąpienia.
Format błędów:
`error [<line> : <column>]: <message>`
Niezamknięty string:
string a := "to jest napis
error [1, 27] String not closed, perhaps you forgot "
Wyjście poza limit wartości int
main(){
int a := 99999999999999...
}
error [2, 14]: Int value limit Exceeded
Błąd przypisania:
main(){
int a := 5
a = "Ala ma kota"
}
error [3, 3]: type mismatch: expected int, got string
Błąd zwracania innego typu:
sumUp(a, b int) float {
return a + b
}
main(){
print(sumUp(20, 11))
}
error [2, 12]: invalid return type: int, expected: float
Błąd w konstrukcji switch, brak przypadku dla zakresu:
kelvinToCelcius(temp int) int {
return temp - 273
}
howCold(kelvin int) string {
switch int c := kelvinToCelcius(kelvin) {
c < -20 => "Freezing",
c>0 and c<10 => "Chilling",
c>=10 and c<20 => "Warm"
}
}
main(){
print(howCold(300))
}
error [6, 1]: missing return, function should return type: string
Niezainicjowana zmienna:
main(){
print(a + 10)
}
error [2, 9]: undefind: a
Błąd niezadeklarowanej funkcji:
main() {
print(unknownFunction())
}
error [2, 9]: undefined function: unknownFunction
Błąd nieprawidłowa liczba argumentów funkcji:
add(a, b int) int {
return a + b
}
main() {
print(add(5))
}
error [5, 9]: function add expects 2 arguemnts but got: 1
Błąd niezgodności typów w instrukcji warunkowej:
main() {
int a := 5
if a == "test" {
print("Equal")
}
}
error [3, 8]: cannot evaluate '==' operation with instances, mismatched types of int and string
Błąd niepoprawnego użycia operatora:
main() {
int a := 20
string b := "5"
int c := a / b
}
error [4, 14]: cannot evaluate '/' operation with instances of int and string
Błąd niepoprawnego użycia operatora relacyjnego:
main() {
int a := 5
if a < "test" {
print("Less than")
}
}
error [3, 8]: cannot evaluate '<=' operation with instances, mismatched types of int and string
Błąd dzielenia przez zero:
main() {
int a := 10
int b := 0
result := a / b
}
error [4, 19]: Division by zero
Struktura tokenu
- TokeType
- Position
- Value
- Zmienne:
IDENTIFIER
- Operatory arytmetyczne:
PLUS
MINUS
MULTIPLY
DIVIDE
- Operatory relacyjne:
EQUALS
NOT_EQUALS
GREATER_THAN
LESS_THAN
GREATER_THAN_OR_EQUAL
LESS_THAN_OR_EQUAL
- Operatory logiczne:
AND
OR
NEGATE
( “-” lub “!”)
- Słowa kluczowe:
IF
ELSE
WHILE
SWITCH
DEFAULT
AS
RETURN
- Symbole specjalne:
ASSIGN
(:=
)CASE_ARROW
(=>
)LEFT_BRACE
({
)RIGHT_BRACE
(}
)LEFT_PARENTHESIS
((
)RIGHT_PARENTHESIS
()
)COMMA
(,
)
- Typy danych:
INT
FLOAT
STRING
BOOL
- Wartości stałe:
CONST_INT
CONST_FLOAT
CONST_STRING
CONST_BOOL
CONST_TRUE
CONST_FALSE
- Inne:
ETX
(end of text)UNDEFINED
COMMENT
Aby uruchomić program napisany w języku flux należy:
- posiadać kompilator Golang
- sklonować to repozytorium i przejść do niego
- zbudować projekt
$ go build -o flux .
- przenieść binary
$ sudo mv flux /usr/local/bin/
lub uruchamiać program poprzez./flux
Program napisany w języku Flux może być uruchamiany zarówno z pliku, jak i ze strumienia danych wejściowych.
Pliki powinny mieć rozszerzenie .fl
.
Standardowym sposobem uruchomienia napisanego programu jest wywołanie kompilatora z argumentem, podającym ścieżkę do pliku:
flux example.fl
Jeżeli program przyjmuje argumenty początkowe, należy je podać po wskazanym pliku:
flux example.fl 0 1
Kod programu może zostać przekazany ze standardowego wejścia używając operatora |
oraz wpisując pierwszy argument jako -
:
echo 'main(){ print("halooo") }' | flux -
lub
flux - < example.fl
Wywołanie programu ze standardowego wejścia z argumentami:
echo 'main(a int){ print(a) }' | flux - 2
lub
flux - < example.fl 0 2
Język Flux nie wymaga żadnych specjalnych danych konfiguracyjnych do poprawnego działania.
Interpreter programu dostaje dostęp do standardowego wyjścia i wejścia, co pozwala na przechwytywanie wyników działania programu i pokazywanie błędów oraz na podanie danych wejściowych do programu.
Konwersja typów i kombinacja typów akceptowalna dla operatorów wieloargumentowych i funkcji wbudowanych
Ponieważ język jest silnie i statycznie typowany, każda konwersja typu jest jawna a do jej dokonania dostępny jest operator as
.
Konwersja typów dla typowania statycznego:
Z | Do Integer | Do Float | Do String | Do Boolean |
---|---|---|---|---|
Integer | - | Explicit | Explicit | Explicit |
Float | Explicit | - | Explicit | Explicit |
String | Explicit | Explicit | - | Explicit |
Boolean | Explicit | Explicit | Explicit | - |
W przypadku int na boolean:
- int 0 oznacza
false
- inne poza 0 oznaczają
true
W przypadku string na boolean:
- pusty string: “” oznacza
false
- niepusty string oznacza
true
W przypadku float na boolean:
- float 0.0 oznacza
false
- inne poza 0 oznacza
true
Operacje *
, /
, +
, -
:
Mnożenie (*
)
- int * int: Zwraca wynik jako wartość całkowitą (
int
). - float * float: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
). - int * float oraz float * int: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
).
Dzielenie (/
)
- int / int: Zwraca wynik jako liczbę całkowitą (
int
). - float / float: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
). - int / float oraz float / int: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
).
Dodawanie (+
)
- int + int: Zwraca wynik jako wartość całkowitą (
int
). - float + float: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
). - int + float oraz float + int: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
). - string + string: Konkatenacja ciągów znaków.
- int + string, float + string, string + int oraz string + float: Konkatenacja liczby lub wartości zmiennoprzecinkowej z ciągiem znaków, zwraca (
string
).
Odejmowanie (-
)
- int - int: Zwraca wynik jako wartość całkowitą (
int
). - float - float: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
). - int - float oraz float - int: Zwraca wynik jako liczbę zmiennoprzecinkową (
float
).
Zmienne są przekazywane do funkcji przez wartość. Jako, że nie ma struktur to przekazywanie zmiennej przez referencje nie wydaje się być konieczne.
Przeciążanie funkcji nie jest dozwolone, nie mogą istnieć dwie funkcje o takiej samej nazwie.
Funkcje wbudowane również nie mogą być przesłaniane.
- Analizator leksykalny (lexer):
- Przetwarza kod źródłowy, znak po znaku, i zgodnie z gramatyką produkuje tokeny do identyfikacji i grupowania leksemów, takich jak identyfikatory, liczby, operatory i słowa kluczowe.
- Tokeny przechowują informacje o swoim położeniu w kodzie źródłowym w postaci
(nr linii, nr kolumny)
. - W przypadku natrafienia na niemożliwy do zdekodowania ciąg znaków, analizator skanuje ciąg aż do natrafienia na biały znak i zwraca token
UNDEFIND
- Analizator składniowy (parser):
- Analizator składniowy jako wejście przyjmuje strumien tokenów wyprodukowany przez analizator leksykalny.
- Zadaniem parsera jest wyprodukowanie drzewa rozbioru składniowego programu w formie
node'ów
. - Ściśle oczekuje na spodziewany token przy analizie wyrażenia.
- Obsługa błędów składniowych realizowana poprzez
panic()
, zawierające informacje o położeniu błędnego wyrażenia w kodzie programu.
- Interpreter:
- Operuje na drzewie rozbioru składniowego.
- Napisany przy użyciu wzorca projektowego "Visitor (wizytator)".
- Interpreter odwiedza elementy drzewa skłądniowego, ewaluując ich zawartość. Nadaje wartości zmiennym, sprawdza zgodność typów, zgodność podawanych do wywołań argumentów, uruchamia wywoływane funkcje (również funkcje wbudowane).
- Dba o to aby wywołania rekurencyjnie nie przekroczyly zdefiowanego limitu (implementacja za pomocą CallStack).
- Wykonuje operacje arytmetyczne, obsługuje instrukcje warunkowe, pętle, wywołania funkcji oraz inne konstrukcje językowe.
-
TestScanner: Zbiór testów sprawdzający poprawność działania skanera dla różnych przykładowych wejść. Testy sprawdzają, czy wyniki zwrócone przez skaner są zgodne z oczekiwanymi znakami i ich pozycjami. Testuje same ciągi znaków jak i ciągi znaków ze znakami nowej lini i poprawnośc zachowania w przypadku
\r\n
oraz\r\t
. -
TestScannerMultipleEOF: Testuje, czy skaner poprawnie zwraca EOF i jego pozycję w tekście. Definiuje pusty
input
i oczekuje, że skaner zwróci kilka EOF z tą samą pozycją.
Testy jednostkowe (unit tests):
- TestSingleTokens: Zbiór testów sprawdzający poprawność tworzenia każdego z tokenów.
Testy ciągu tokenów:
- TestLexerCodeExample: Zbiór testów sprawdzający poprawność analizy sekwencji tokenów w przykładowym kodzie źródłowym.
Testy obsługi błędów:
- TestStringNotClosed: Sprawdza, czy analizator leksykalny poprawnie zgłasza błąd w przypadku niezamkniętych ciągów znaków.
- TestIntValueLimitExceeded: Testuje, czy analizator leksykalny zgłasza błąd, gdy wartość liczby całkowitej przekracza limit.
- TestLexerStringTokenEscaping: Sprawdza, czy analizator leksykalny prawidłowo obsługuje znaki specjalne w ciągach znaków.
- TestLexerInvalidStringTokenEscaping: Testuje, czy analizator leksykalny zgłasza błąd dla niepoprawnych znaków specjalnych w ciągach znaków.
- TestStringValueLimitExceeded: Sprawdza, czy analizator leksykalny zgłasza błąd, gdy długość ciągu znaków przekracza limit.
- TestLexerErrorHandling: Testuje, czy analizator leksykalny poprawnie obsługuje błędy i zatrzymuje się po napotkaniu błędu, a także czy zgłasza oczekiwane błędy.
Testy sprawdzające poprawnośc parsowania ciągu tokenów wejsciowych na elementy drzewa AST, od terminalnych do bardziej złożonych (zagłębionych). Testy odpowiedzialne są również za sprawdzanie czy w określonych przypadkach wyrzucane są błędy z poprawnymi treściami.
- TestParseParameterGroup
- TestParseParameters
- TestParseIdentifier
- TestParseFunctionDefinitions
- TestParseExpressionIdentifierAsTerm
- TestParseExpressions: Zbiór testów sprawdzających poprawność każdego z expression zdefiowanego w EBNF
- TestParseVariableDeclaration: Zbiór testów sprawdzających poprawnośc przypisywania wartości do zmiennej
- TestParseNegateExpression
- TestParseVariableDeclarationsWithTerm
- TestParseFloatExpression
- TestParseStringExpression
- TestParseParenthesisExpression
- TestParseOrAndExpression
- TestParseIfStatement
- TestParseIfStatementWithElse
- TestParseWhileStatement
- TestSwitchStatement
- TestSwitchStatementWithDefault
- TestSwitchStatementWithBlock
- TestParseSwitchError
- TestParseProgram
- TestParseProgramFunctionWithType
Testy wyrażeń liczbowych:
- TestVisitIntExpression: Sprawdza, czy odwiedzając wyrażenie liczbowe, otrzymuje się prawidłowy wynik.
- TestVisitFloatExpression: Testuje poprawność przetwarzania wyrażenia zmiennoprzecinkowego.
- TestVisitStringExpression: Sprawdza poprawność przetwarzania wyrażenia tekstowego.
- TestVisitBoolExpression: Testuje poprawność przetwarzania wyrażenia logicznego.
Testy wyrażeń logicznych:
- TestVisitAndExpression: Sprawdza, czy poprawnie przetwarzane są wyrażenia AND.
- TestVisitOrExpression: Testuje poprawność przetwarzania wyrażeń OR.
Testy wyrażeń arytmetycznych:
- TestVisitSumExpression: Sprawdza, czy wyrażenia sumy są przetwarzane poprawnie dla różnych typów danych.
- TestVisitSumExpressionString: Testuje przetwarzanie sumy wyrażeń tekstowych.
- TestVisitSubstractExpressionInt: Testuje poprawność przetwarzania wyrażenia odejmowania dla wartości integer.
- TestVisitSubstractExpressionFloat: Testuje poprawność przetwarzania wyrażenia odejmowania dla wartości float.
- TestVisitSubstractExpressionFloatMinusInt: Testuje poprawność przetwarzania wyrażenia odejmowania dla wartości float i integer.
- TestVisitSubstrackExpressionIntMinusFloat: Testuje poprawność przetwarzania wyrażenia odejmowania dla wartości integer i float.
Inne testy:
-
TestVisitNegateExpression: Testuje przetwarzanie negacji dla różnych typów wyrażeń.
-
TestCastExpression: Zbiór testów sprawdzający poprawność przetwarzania wyrażenia kastowania.
Testy innych elementów drzewa AST:
- TestVisitIdentifier: Sprawdza poprawność przetwarzania identyfikatorów, czy wartość ze Scope zostanie zwrócona.
- TestVisitVariable: Sprawdza poprawność przetwarzania zmiennej, czy zmienna zostaje poprawnie dodana do Scope.
- TestVisitWhileStatement: Sprawdza, czy wartość z wywołania pętli.
- TestVisitIfStatementConditionTrue: Sprawdza, czy wartość z wywołania bloku if jest przenoszona do poprzdniego Scope poprzez LastResult.
- TestVisitIfStatementElseBlock: Sprawdza czy wartość LastResult została poprawnie wyczyszczona wychodząc z bloków if.
- TestScopeVariables: Testuje czy zmienne są poprawnie przechowywane w Scope.
- TestVisitFunctionCall: Testuje Wywołanie funkcji.
- TestVisitSwitchCase: Testuje poprawność przetwarzania switch-case.
- TestVisitDefaultSwitchCase: Testuje poprawność przetwarzania default-case.
- TestVisitSwitchStatement: Testuje poprawność przetwarzania switch statement ze zwracaniem wartości.
- TestSwitchWithBlock: Testuje poprawność przetwarzania switch statement z blokiem.
- TestRecursion: Testuje poprawność przetwarzania funkcji rekurencyjnych i przekraczania limitu wywołań rekurencyjnych.
Testy złożonych funkcji (fragmentów kodu):
- TestReturningNestedBlocks: Sprawdza, funkcja jest poprawnie zamykana po osiągnięciu pierwszego return.
- TestVisitFunctionCallWithIdentifier: Testuje czy wywołanie funkcji odbywa się poprawnie z przekazaniem identyfikatorów jako argumenty.
- TestVisitAsignmentWithFunctionCall: Testuje czy przypisanie do zmiennej odbywa się poprawnie z wywołaniem funkcji.
- TestVisitNestedFunctionCallWithReturn: Testuje zwracanie poprawnej wartości z zagnieżdżonych Scope'ów.
- TestVisitWhileStatementWithReturn: Testuje poprawność przetwarzania pętli ze zwracaniem wartości.
Testy obsługi błędów:
- TestVariableNotInScope: Sprawdza czy poprawnie zostal wyrzucony błąd z niezdefiniowaną zmienną.
- TestSearchVariableInScope: Sprawdza czy wartośc z wywołania funkcji nie jest przenoszona do poprzdniego Scope poprzez LastResult. Wyłapuje błąd z odpowiednim komunikatem o niezdefiniowanej zmiennej.
- TestParametersAndArguments: Testuje poprawności wyłapywanych błędów podczas przetwarzania niepoprawnej ilości argumentów i parametrow lub podania niepoprawnych typów.
- TestFunctionWithSwitch: Sprawdza czy poprawnie zostanie wyłapany błąd, ze względu na brak return'a i switch_case'ów które zwróciły by wartości, gdy funkcja w której zdefiniowany jest switch powinna takową zwrócić.
- TestVariableDeclarationMissmatch: Testuje poprawnośc wyłapanych błędów i ich komunikatów.
- TestEmbededFunction: Testuje poprawność przetwarzania funkcji wbudowanych.