Skip to content

bjasinsk/Bit-Scope-Language-Interpreter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

111 Commits
 
 
 
 
 
 
 
 

Repository files navigation

BitScope

1.Cel Projektu

Celem projektu jest stworzenie języka BitScope, zaimplementowanie interpretera języka programowania ogólnego przeznaczenia z typem maski bitowej, z obiektowymi atrybutami i metodami. Stworzony przeze mnie język będzie statycznie, silnie typowany z mutowalnymi zmiennymi, przekazywanymi przez referencję.

Charakterystycznym elementem jezyka będzie typ maski bitowej (bmask), reprezentowany jako klasa z obiektowymi atrybutami i metodami. Cechy typu:

  1. Reprezentacja - maska bitowa będzie reprezentowana jako tablica wartości logicznych True i False, w której nie występuje bit znaku. To oznacza że wszystkie bity w masce przeznaczone są na wartość liczby. Dla zwiększenia czytelności wypis maski jako string następuje jako wypis tablicy 1 i 0 reprezentujących odpowiednio wartości True i False w masce
  2. Konwencja Little Endian - w reprezentacji maski bitowej, najmłodszy bit znajduję się na najmnijeszym indeksie w tablicy
  3. Wielkości masek - użytkownik jest w stanie stworzyć maski o dowolnej wielkości nie większej niż 32 deklarując swój wybór poprzez mask(wielkość maski wyrażaona liczbą całkowitą)
  4. Podmaski — z jednej dużej maski, można wyodrębnić podmaski, odpowiadające za konkretne funkcjonalności. Tworzy się wtedy powiązanie bitów w dwóch różnych maskach za pomocą stworzenia podmaski będącej atrybutem danej maski, lub poprzez swtorzenie nowego obiektu z odpowiającymi bitami.
  5. Dynamiczna aktualizacja — kiedy bity danej maski ulegają zmianie, odpowiadające tym bitom bity w powiązanych podmaskach, również ulegną zmianie.
  6. Nadawanie nazw - danej masce, submasce lub pojedyńczemu bitowi można nadać nazwę i posługiwać się nią upraszczając zarządzanie dużą maską bitową
  7. Operacje bitowe - jest możliwość wykonania operacji conjunction, disjunction, exclusive oraz inverse na dwóch maskach bitowych nierównej długości
  8. Domyślna wartość - po inicjalizacji obiektu bmask wszytskie jej bity są zanegowane, domyślna wartość przypisana przez konstruktor domyślny to 8
  9. Konwersja - typowi bmask będzie można przypisać wartość całkowitą, próby przypisania innych typów zakończone będą odpowiednim błędem

2 - Opis zakładanej funkcjonalności


2.1 - Typy danych

Obsługiwanymi typami zmiennych będą:

  1. bool - typ danych logiczny True lub False
  2. str - typ danych do przechowywania ciągu znaków
  3. int - typ danych do przechowywania liczb całkowitych
  4. float - typ danych do przechowywania liczb zmiennoprzecinkowych
  5. bmask - typ danych reprezentujący maskę bitową

2.2 - Limity typów danych

Typ Minimalna wartość Maksymalna wartość Minimalna długość Maksymalna długość
bool False True - -
str - - 0 znaków 512 znaków
int -231 = -2147483648 231 - 1 = 2147483647 - -
float -3.4 × 10-38 3.4 × 1038 - -
bmask 0 232 - 1 1 bit 32 bitów

Precyzja typu float to Single Precision, obsługujący 6 cyfr znaczących.

2.3 - Reprezentacja binarna w konwencji Big-Endian

W języku istnieje możliwość przypisania masce bitowej liczby w postacji binarnej używając prefiks $, wyrażenie binarne jest w konwencji Big-Endian co oznacza, że najmniej znaczący bit znajduję się na skrajnie prawej pozycji w wyrażeniu po prefiksie $

bmask mask1 = mask(8);
mask1 = $00011100;  # Przypisanie liczby binarnej do maski bitowej

Ograniczeniem liczby w tej reprezentacji jest 32 znaków po znaku $, ponieważ 32 to maksymalny rozmiar typu bmask.


2.4 - Znaki i konstrukcje językowe

Słowo Opis
AND logiczny AND pomiędzy dwoma wyrażeniami
OR logiczny OR pomiędzy dwoma wyrażeniami
if instrukcja warunkowa if(warunek) {}
elif instrukcja warunkowa elif(warunek) {} występująca po if
else instrukcja warunkowa else{} występująca po elif
while pętla while(warunek) {}
break instrukcja przerwania pętli
# znak komentarza jednoliniowego
bmask odpowiada konstruktorowi typu bmask,
mask(rozmiarMaski)
; znak końca instrukcji
{} znaki definiowania bloku kodu
+ operator dodawania na typach int, float
- operator odejmowania na typach int, float
/ operator dzielenia zmiennoprzecinkowego na typach int, float
* operator mnożenia na typach int, float
= operator przypisaniach na typach int, float, bool, bmask, str
== operator porównania na typach int, float, bool, bmask, str
return instrukcja służąca do zwrócenia wyniku funkcji oraz zakończenia jej działania
> operator porównania do typów int, float, zwraca True jeżeli wartość po lewej stronie od operatora jest większa od wartości po lewej
>= operator porównania do typów int, float, zwraca True jeżeli wartość po lewej stronie od operatora jest większa lub równa wartości po lewej
< operator porównania do typów int, float, zwraca True jeżeli wartość po lewej stronie od operatora jest mniejsza od wartości po lewej
<= operator porównania do typów int, float, zwraca True jeżeli wartość po lewej stronie od operatora jest mniejsza lub równa wartości po lewej
!= operator nierówności do typów int, float zwraca True jeżeli wartość po lewej stronie od operatora jest różne od wartości po lewej
() znaki odpowiadające za grupowanie wyrażeń, nadając wyższy priorytet, wywoływania funkcji z przekazywania do nich argumentów
[] znaki odpowiadające za określanie typu wynikowego funkcji, mogą przyjmować wartości: int, float, bool, str, bmask, void
inverse operator negacji na typie bmask
conjunction operator logicznego AND na dwóch obiektach typu bmask
disjunction operator logicznego OR na dwóch obiektach typu bmask
exclusive operator logicznego XOR na dwóch obiektach typu bmask
\ znak modyfikujący powodujący inną niż domyślna interpretację kolejnego znaku
- znak negacji na typie int, float

2.5 - Priorytety operatorów w wyrażeniach

Priorytet Operator Asocjatywność Typ
8 NOT, inverse, - prawostronna unarna
7 conjunction, disjunction, exclusive lewostronna binarna
6 /, * lewostronna binarna
5 -, + lewostronna binarna
4 ==, !=, >=, >, <=, < lewostronna binarna
3 AND lewostronna binarna
2 OR lewostronna binarna
1 = prawostronna binarna

2.6 - Statyczne typowanie

Język będzie statycznie typowany, zatem typy zmiennych będą musiały być jawnie określone przy ich deklaracji. Przykłady użycia:

bool a = True;
bool b = False;
str c = "cat";
int d = 1;
float e = 1.5;
bmask f = 54321;

2.7 - Silne typowanie

Język będzie silnie typowany, zatem będzie konieczność używania operatora rzutowania, poniżej znajdują się zasady konstruowania wyrażeń:

Przykład Zastosowane typy Czy obsługiwane
1 + 1.5 int + float NIE
1 + 2 int + int TAK
1.2 + 2 float + int NIE
True + 2 bool + int NIE
True.toInt() + 2 int + int TAK
False + 2.5 bool + float NIE
False.toFloat() + 2.5 float + float TAK
"Adam" + 5 str + int NIE
"Adam" + 5.5 str + float NIE
bmask mask1 = "Adam" bmask = str NIE
bmask mask1 = 45.23 bmask = float NIE
bmask mask1 = 45.23.toInt() bmask = błędne zastosowanie .toInt() NIE
bmask mask1 = True bmask = bool NIE
bmask mask1 = True.toInt() bmask = int TAK
"abc".toInt() konwerjsa string na Int NIE
"abc".toFloat() konwersja string na float NIE
1.2.toInt() + 2 bezpośrednia konwersja float na int + int NIE
"Adam" + 5.5.toStr() bezpośrednia konwersja float na str + str NIE

2.8 - Metody konwersji typu

Dostępne są poniższe metody konwersji typu (gdzie x to przykładowy obiekt):

Metoda Opis
x.toInt() konwersja obiektu x do typu całkowitoliczbowego
x.toFloat() konwersja obiektu x do typu zmiennoliczbowego
x.toStr() konwersja obiektu x do napisu
x.toBool() konwersja obiektu x do typu logicznego

Ich celem jest przekształcenie obiektów na inne typy:

float a = 3.123;
int b = a.toInt();
# konwersja liczby zmiennoprzecinkowej do całkowitej

bmap mask1 = bmap(8);
str mask1Converted = mask1.value.toStr();
# konwersja mapy bitowej na napis

float c = 54.32;
str d = c.toStr();
# konwersja liczby zmiennoprzecinkowej na napis

int e = 20;
str f = e.toStr();
# konwersja liczby całkowitej na napis

int g = 10;
float h = g.toFloat();
# konwersja liczby cakowitej do zmiennoprzecinkowej

bool k = g.toBool();
# konwersja liczby całkowitej na wartośc bool, liczy dodatnie odpowiadają wartości True, natomiast niedodatnie wartośi False

bool k2 = "ABBA".toBool();
# konwersja napisu na wartość bool, niepusty napis zwróci wartość po konwersji True, natomiast pusty napis zwróci False

bool l = a.toBool();
# konwersja liczby zmiennoprzecinkowej na wartośc bool, liczy dodatnie odpowiadają wartości True, natomiast niedodatnie wartośi False

str n = 4.12.toStr();
# konwersja liczby zmiennoprzecinkowej na napis

bmask mask1 = mask(8);
int maskValue = mask1.value.toInt();
# odczytanie wartości decymalnej przechowywanej na masce bitowej


bool maskValue4 = mask2.toBool() 
# W wyniku tej operacji jeżeli maska przechowuje wartość dodatnią zwrócona zostanie wartość True, jeżeli maska przechowuje 0 to zwróci False

Błędne próby konwersji:

int l = "ABBA".toInt(); # Błąd, nie można przekształcić napisu w liczbę całkowitą
float o = "ABBA".toFloat() # Błąd, nie można przekształcić napisu w liczbę zmiennoprzecinkową

bmask mask2 = mask(8);
int maskValue2 = mask2.toStr(); # Błąd, nie można przekształcić maski bitowej na tekst
float maskValue3 = mask2.toFloat() # Błąd, maska bitowa przechowuje tylko typ całkowity

Powyższe metody nie zmieniają obiektu, na którym są wykonywane.


2.9 - Mutowalne zmienne

Zmienne są mutowalne zatem ich wartość, może ulec wielokrotnie zmianie:

float a = 1.0;
a = 1.5;
a = 2.0;

2.10 - Zakres widoczności zmiennych

Zmienne są widoczne w bloku ograniczonymi { }, w którym zostały zadeklarowane:

void newFunction(int a){
    if (a < 10){
        int y = 5;  # Zmienna y jest widoczna tylko w tym bloku if
        y = y + a;
    }
    int b = 15;
    b = b + y; # Ta operacje nie jest dozwolona, zmienna y już nie jest widoczna 
}

2.11 - Przekazywanie przez referencję

Przekazując zmienną do funkcji domyślnie przekazujemy jej referencję:

void changeA(int a){
    a = a + 5;  
}

void main(){
    int y = 6;  
    changeA(y);  # Zmienna y została przekazana przez referencję
    print(y);  # wynikiem operacji print będzie 11, ponieważ zmienna y uległa zmianie w funkcji changeA
}
>>>11

2.12 - Escape'owanie znaków w stringu

Do escape'owania służy znak \, bezpośrednio po nim dozwolone jest umieszczenie znaków: n, t, \ oraz ". Wszystkie inne znaki spodują błąd: Invalid use of backslash in string

str a = "abba \ abba";  # błąd
str g = "abba \\ abba"; # astąpiło escape'owanie znaku \, zatem program odczyta "abba \ abba" jako jeden napis
str b = "b \n c";       # znak \n zostanie ustany jako znak nowej linii
str c = "c \\n d";      # nastąpiło escape'owanie znaku \, zatem program odczyta "c \n d" jako jeden napis
str d = "d \t e";       # znak \t uznany zostanie jako tabulator
str e = "e \\t f";      # nastąpiło escape'owanie znaku \, zatem program odczyta "e \t f" jako jeden napis
str f = "f \" g";       # nastąpiło escape'owanie znaku \, zatem program odczyta "f " g" jako jeden napis

2.13 - Przypadki szczególne

Przykład Efekt
float a = 1.2;
a.toInt() + 2 -> wyrażenie dodawania będzie miało wartość 3
Obcięcie części ułamkowej
float b = 1.234;
b.toBool() or False -> wyrażenie OR będzie miało wartość True
rzutowanie typu bool na dodatniego floata spowoduje wartość True, niezależnie jak duży jest zmienne typu float. Niedodatni float zostanie zamieniony na wartość False
                                                                                                                                                                                                                                  |

3 - Przykłady obrazujące dopuszczalne konstrukcje językowe oraz ich semantykę

3.1 - bmask, konstruktor

Obiekt maski bitowej posiada konstruktor mask. Konstruktor musi mieć pierwszy argument size czyli rozmiar maski i drugi argument name jest opcjonalny.

bmask a;                    #Konstruktor domyślny tworzący obiekt maski bitowej, 8-bitowy, bez nazwy
bmask a = mask(14);         #Konstruktor tworzący maskę 14-bitową bez nazwy
bmask a = mask(14, "Gain"); #Konstruktor tworzący maskę 14-bitową z nazwą "Gain"

Na masce można wykonać metody .toInt(), .toFloat(), .toBool(), .toStr():

bmask mask1 = mask(8);
mask1 = $00000010;
print(mask1.toInt());
>>>2

3.2 - bmask, przypisywanie wartości

Przypisywanie wartości dziesiętnej:

bmask mask1 = mask(32);
mask1 = 54321;
print(mask1);
>>>[1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1]

Przypisywanie wartości bitowej:

bmask mask3 = mask(8);
mask3 = $00001010;
print(mask3.toInt());
>>>10

Typ bmask przyjmuje jedynie liczby całkowite, przypisanie mu wartości float, bool lub str spowoduje błąd. W przypadku przypisania wartości float, wymagane jest użycie metody .toInt(), jednak aby tego dokonać konieczne jest użycie zmiennej pośredniej:

bmask mask1 = bmask(32);
# mask1 = 54321.45; przypisanie float'a bezpośrednio spowoduje błąd
float a = 54321.45;
# metoda .toInt() działa jedynie na zmiennych, wykonanie jej bezpośrednio na typie float spowoduje błąd
mask1 = a.toInt(); 

3.3 - bmask, nazywanie bitów i mask

Do każdego bitu w masce bitowej można odwołac się poprzez mask[idx] oraz nadać im nazwy poprzez atrybut name, który domyślnie jest pusty dla każdego bitu w masce. po nadaniu nazwy danemu bitowi, można się do niego bezpośrednio odwołać:

bmask mask1 = mask(16);
mask1[4].name = "isOpened";
mask.isOpened = True;

if (mask.isOpened.toBool()){
    print("Appropriate message");
}

Istnieje również możliwość nadania bitu masce analogicznie poprzez atrybut name:

str main(){
    bmask mask1; 
    mask1.name = "Gain";
    return mask1.name;
}

Został tutaj również konstruktor domyślny obiektu BMask.


3.4 - bmask, tworzenie submasek bitowych jako atrybut innej maski

W ramach maski bitowej, można również wydzielić część jej bitów tworząc podmaske. Możliwe jest utworzenie wielu podmasek w ramach jednej maski oraz dany bit może należeć do więcej niż jednej podmaski:

bmask mask2 = mask(8);
mask2 = 10;
# mask2 -> [0, 0, 0, 0, 1, 0, 1, 0]

mask2.submask([0, 2, 3], "gain"); 
# bity o indeksach 0, 2 i 3 w mask2 tworzą podmaske o nazwie gain -> [1, 0, 0] czyli wartość 4 dziesiętnie

int gainValue = mask2.gain.toInt();
print(gainValue);

mask2.submask([3, 4, 5], "controlBits");
# bity o indeksach 3, 4 i 5 w mask2 tworzą podmaske o nazwie controlBits -> [0, 0, 1] czyli wartość 1 dziesiętnie

int controlBitsValue = mask2.controlBits.toInt();
print(controlBitsValue);
>>>4
>>>1

3.5 - bmask, tworzenie submasek bitowych jako oddzielna maska

Istnieje również możliwość wyodrębnienia części bitów z maski bitowej i stworzenia z nich nowej oddzielnej maski:

bmask maskA = mask(16);
bmask maskB = mask([3, 12, 15], maskA);
# po tej operacji maskB[0] = maskA[3], maskB[1] = maskA[12] oraz maskB[2] = maskA[15]

maskB.append([5, 13], maskA);
# po tej operacji maskA zwiększy swój rozmiar z 3 do 5 bitowej gdzie dodatkowo maskB[3] = maskA[5] i maskB[4] = maskA[13]

maskB.reconfigure([7, 14], maskA);
# możliwa jest konfiguracja mapowania poprzez podanie odpowiadjących indeksów licząc od indexu 0 w maskB 
# Teraz maskB[0] = maskA[7] i maskB[1] = maskA[14]
# Natomiast pozostałe mapowanie nie uległo zmianie

bmask maskC = mask(8);
maskB.append([6], maskC);
# Zostało dodane powiązanie bitu o indeksie 6 z maskC

Można również nadać nazwę obiektowi nowej maski, który ma bity powiazane z inną maską:

bmask maskA = mask(16);
bmask maskB = mask([3, 12, 15], maskA, "important");
# po tej operacji obiekt maski ma nazwę "important" 

print(maskB.name);

# lub można zmienić nazwę odwołując się po kropce
maskB.name = "notimportant"

print(maskB.name);
>>>important
>>>notimportant

3.6 - bmask, tworzenie submasek bitowych jako oddzielna maska nadajac nazwy bitom

Operacja ta również może być wykonana odnosząc się do nazw poszczególnych bitów:

bmask mask1 = mask(16);
mask1[3].name = "enabled";
mask1[6].name = "opened";
mask1[10].name = "reachable";

bmask mask2 = mask(["enabled", "opened", "reachable"], mask1); # analogicznie można nadać w tym miejscu jako trzeci atrybut nazwę maski
# po tej operacji utworzy się obiekt 3-bitowa maska mask2 z bitami odpowiadającymi bitom w masce mask1
# przypisując indexy do nowej maski mask2 będzie następujące przypisanie mask2[0] -> "enabled", mask2[1] -> "opened", mask2[2] -> "reachable"
 
print("Initial state: ");
print(mask1);
print(mask2);

mask2.value = 5;
print("Masks after change mask2: ")
print(mask1);
print(mask2);

# możliwa jest zmiana wartości pojedyńczych bitów
mask1[3] = False;
mask1[10] = False;
mask1[6] = True;
print("Masks after change mask1: ");
print(mask1);
print(mask2);
Initial state: 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0]
Masks after change mask2:
[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0]
[1, 0, 1]
Masks after change mask1:
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 0]

Analogicznie do poprzedniego przykładu trzeci argument konstrukora mask nadaje nazwę nowemu obiektowi maski biowej:

bmask maskA = mask(16);
mask1[3].name = "enabled";
bmask maskB = mask(["enabled", 12, 15], maskA, "important"); # po tej operacji obiekt maski ma nazwę "important" 
print(maskB.name);

maskB.name = "notimportant" # lub można zmienić nazwę odwołując się po kropce
print(maskB.name);
>>>important
>>>notimportant

3.7 - bmask, usunięcie podmaski w masce

# na początku maskA ma podmaski: ["gain", "permissions", "controllable"]
maskA.remove("gain");
# po wykonaniu operacji podmaski maskA to: ["permissions", "controllable"]

3.8 - bmask, operacje bitowe unarne

Na typie bmask, jest możliwa negacja maski:

bmask mask1 = mask(8);
mask1 = 170;
bmask modifiedMask1 = inverse mask1;
print(mask1);
print(modifiedMask1);
>>>[1, 0, 1, 0, 1, 0, 1, 0]
>>>[0, 1, 0, 1, 0, 1, 0, 1]

3.9 - bmask, operacje binarne bitowe

Na maskach są dostępne trzy operacje binarne bitowe: conjunction, disjunction, exclusive pomieiędzy dwoma obiekatami typu bmask:

bmask mask1 = mask(8);
bmask mask2 = mask(16);
mask1 = 170 # binarnie 10101010;
mask2 = 43760 # binarnie 1010101011110000;
# Gdy maski są różnych rozmiarów, następuję dopełnienie zerami maski do wielkości maski większej
bmask result1 = mask1 conjunction mask2;
bmask result2 = mask2 disjunction mask2;
bmask result3 = mask1 exclusive mask2;
print(mask1);
print(mask2);
print(result1);
print(result2);
print(result3);
>>>[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0]
>>>[1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0]
>>>[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0]
>>>[1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0]
>>>[1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0]

4 - Pętla while

Dostępny w języku jest pętla while:

while(a > b){
    c = c + 10;
}

5 - Instrukcje warunkowe

Dostępne są instrukcje if, elif, else:

if (a > 123){
    b = 10;
}elif (a < 56){
    b = 100;
}else{
    b = 1000;
}

6 - Definiowania funckji

Aby zdefiniować funckję należy na początku określić typ zwracany przez funkcję a następnie podać ewentualne argumenty wywołania. W języku BitScope nie da się przeciążać funkcji.

int myFunction(int a, int b){
    return a + b;
}

bmask myFunction2(int c, float d){
    bmask mask1 = mask(16);
    mask1 = c + d.asInt();
    return mask1;
}

str nameSurname(str a, str b){
    return a + " " + b;
}

void main() {
    print("Hello World!");
    
    str name = "Adam";
    str surname = "Smith";
    print("Hello " + nameSurname(name, surname));
}
>>>Hello World!
>>>Hello

7 - Komentowanie

W języku jest jedynie możliwość komentarzy jednolinijkowych poprzedzonych znakiem kluczowym #:

# comment with valuable information

8 - Wypisywanie na ekran

Do wypisu będzie służyła funkcja print:

print("Hello World!");
>>>Hello World!

9 - Specyfikacja i składnia EBNF

(* definicja liczby, cześć całkowita liczby zaczyna się od non_zero_digit, czesc ułamkowa może od zero *)
zero            = "0";
positive_digit  = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
digit           = positive_digit | zero;

letter          = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
                    "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |j
                    "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" |
                    "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" |
                    "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" |
                    "t" | "u" | "v" | "w" | "x" | "y" | "z";
new_line        = "\n";

(* typy danych *)
literal_bool            = "True" | "False";
literal_int             = positive_digit, { digit } | zero;
literal_float           = literal_int, ".", digit, { digit };
literal_str             = '"',  { char }, '"'; ( * możliwy znak escape \ *)

literal_binary  = "$", { "0" | "1" };
int_list        = literal_int, { ",", literal_int };
expression_list        = expression, { ",", expression };
bit_list   = "[", expression_list, "]";

type_return     = "void" | type_variable;
type_variable   = "bool" | "int" | "float" | "str" | "bmask";


(* identyfikar zmiennych lub funckji *)
identifiler     = letter, { letter | digit | "_"};

(* operatory *)
operator_bitwise    =   "conjunction" | "disjunction" | "exclusive";
operator_relation   =   "==" | "!="  | ">=" | ">" | "<=" | "<";
operator_and        =   "AND";
operator_or         =   "OR";
operator_negation   =   "NOT" | "inverse" | "-";
operator_multiplicative =   "*" | "/";
operator_additive   =   "-" | "+";

(* komentarz jednolinijkowy  *)
comment             =   "#", { char }, new_line;


(* deklaracje *)
declaration_variable = type_variable, identifiler, [ "=", expression ], ";";
declaration_parameter = type_variable, identifiler;
declaration_parameters = [ declaration_parameter, { "," declaration_parameter } ]
declaration_function = type_return, identifiler, "(", declaration_parameters, ")", block ;

(* metody na bmask *)
submask_as_attribute    =   identifier, ".", "submask", "(", int_list, ",", literal_str, ")", ";"
method_type_conversion  =   identifier, ( ".", "toInt" | "toFloat" | "toStr" | "toBool" ), "()";
method_value_access     =   identifier, ".", indetifier, (".", "toInt" | "toFloat" | "toStr" | "toBool" ), "()";     

method_assign_bit_name  =   identifier, "[", literal_int, "]", "." ,identifier , "=", literal_str, ";";
method_named_bit_access =   identifier, ".", identifier;

(* operacje bitowe *)
bmask_binary_operation  =   identifier, operator_bitwise, identifier, ";";
bmask_unary_negation    =   operator_negation, identifier, ";"; 

block           =   "{", { statement } "}";

(* instrukcje *)
statement       =   declaration_variable 
                    | statement_if 
                    | statement_while 
                    | statement_return  
                    | assign_or_call 
                    | statement_break;
               
             
assign_or_call = access, ["=", expr ];
access = data, {'.', data};
 
data = identifier, [ "(", args, ")"], { "[", expression, "]" }             
                      
args            = [ expression_list ]

statement_if    =   "if", "(", expression, ")", block,
                    { "elif", "(", expression, ")", block },
                    [ "else", block ]

statement_while =   "while", "(", expression ")", block;

statement_return    =   "return", [ expression ], ";";
statement_break     =   "break", ";";

(* wyrażenia *)
expression          =   expression_and, { operator_or, expression_and };
expression_and      =   expression_relation, { operator_and, expression_relation };
expression_relation =   expression_additive, [ operator_relation, expression_additive ];
expression_additive =   expression_multiplicative, { operator_additive, expression_multiplicative };
expression_multiplicative   =   expression_bitwise, { operator_multiplicative, expression_bitwise }
expression_bitwise  =   expression_unary, { operator_bitwise, expression_unary }
expression_unary    =   [ operator_negation ], expression_elementary;
expression_elementary   =   literal | "(", expression, ")" | access;

literal =   literal_int | literal_float | literal_str | literal_bool | bit_list | literal_binary;

(* program *)
program = { declaration_function };

10 - Obsługa błędów

Użytkownik będzie informowany o błędzie w postaci:

ERROR OCURED AT [lineNumber : columnNumber ], [ ERROR_NAME ] 

Poniżej znajduję się hierarchia błedów wraz z ich krytycznością.

  • Błąd krytyczny - niemożliwa jest dalsza interpretacja kodu i program się zatrzymuje
  • Błąd niekrytyczny - może prowadzić do złych wyników, jednak nie przerywa dalszej analizy interpretera

Głównym atutem języka BitScope jest możliwość operacji na bardzo dużych maskach i submaskach bitowych, zatem przyjmując niskopoziomowe przeznaczenie języka wszelkie błędy powinny być wyłapywane. Im niższy poziom abstrakcji tym większe mogą być skutki nieoczekiwanego działania programu, jeżeli w kodzie jest błąd.


10.1 - Błędy na poziomie analizatora leksylanego (Leksera)

Błędy na tym poziomie będą skupiały się na zapobiegnięciu sytuacjom, w którym niemożliwa jest poprawne rozpoznanie elementów języka, zbudowania tokenu:

Typ Danych Sytuacja Typ Błędu Krytyczność
- - Symbol nie występujący w języku BitScope, dowolne znaki mogą wystąpić tylko w str lub w komentarzu po znaku # ErrorLexer Krytyczny
int Przekroczenie zakresu wartości typu (określony też jako ilość znaków maksymalnej wartości) ErrorLexer Krytyczny
float Przekroczenie zakresu wartości typu (określony też jako ilość znaków maksymalnej wartości) ErrorLexer Krytyczny
bmask Przekroczenie zakresu wartości typu (określony też jako ilość znaków maksymalnej wartości) ErrorLexer Krytyczny
str Przekroczenie zakresu długości typu ErrorLexer Krytyczny
- Przekroczenie zakresu długości reprezentacji binarnej po znaku $ ErrorLexer Krytyczny
all Przekroczenie zakresu długości identyfikatora ErrorLexer Krytyczny
- Użycie nieistniejacego w języku BitScope słowa, nierozpoznanego w składni jezyka (w tym użytego jako typ dannych) ErrorLexer Krytyczny

10.2 - Błędy na poziomie analizatora składniowego (Parsera)

Sytuacja opis Typ Błędu Krytyczność
Brak średnika po :
- wywołaniu instrukcji return
- wywołaniu funkcji
- przypisaniu do zmiennej
- deklaracji zmiennej
MissingSemicolonError Krytyczny
Brak nawiasów { } (lub jednego z nich) w strukturach if, elif, else, while, funkcjach CriticalErrorParser Krytyczny
Brak nawiasów ( ) (lub jednego z nich) w strukturach if, elif, while, deklaracji funkcji CriticalErrorParser Krytyczny
Brak nawiasów [ ] (lub jednego z nich):
- odnosząc się do konkrentego bitu w masce bitowej np. maskA[1.value
- podając indexy bitów tworząc submaske np. maskA.submask([indexes], maskB)
- podając indexy bitów przy metodzie maskA.append([indexes], SourceMaskObject)
- podając indexy bitów przy metodzie np. maskA.reconfigure([indexes], SourceMaskObject)
CriticalErrorParser Krytyczny
Wywołanie funkcji lub metody z nadmiarowym przecinkiem podając argumenty np. funcA(b, c, d,); CriticalErrorParser Krytyczny
Zadeklarowanie zmiennej o nazwie już istniejącej w danym bloku (w tym submaski jako nowy obiekt używając nazwy identyfikatora już istniejącego w danym bloku) CriticalErrorParser Krytyczny
Użycie słowa kluczowego jako nazwa zmiennej CriticalErrorParser Krytyczny
Zadeklarowanie funkcji o już istniejącej nazwie CriticalErrorParser Krytyczny
Użycie niezadeklarowanej funkcji CriticalErrorParser Krytyczny
Użycie słowa kluczowego jako nazwa funkcji CriticalErrorParser Krytyczny
Błąd w nazwie zwracaego typu w definicji funkcji CriticalErrorParser Krytyczny
Brak określenia w definicji funkcji zwracanego przez nią typu danych CriticalErrorParser Krytyczny
elif, else - Użycie elif bez wcześniejszego użycia if
- Użycie else bez wcześniejszego użycia if i elif
CriticalErrorParser Krytyczny
if, elif - Brak warunku logicznego w konstrukcji if(expression) lub elif(expression)
- Typ warunku inny niż logiczny np. if("Adam"), elif(15.23)
CriticalErrorParser Krytyczny
while - Brak warunku logicznego w konstrukcji while(expression)
- Typ warunku inny niż logiczny np. while("Adam"), while(15.23)
CriticalErrorParser Krytyczny
return Brak słowa kluczowego return w funkcji o zadeklarowym zwracanym typie innym niż void CriticalErrorParser Krytyczny
return Umieszczenie słowa kluczowego return w funkcji o typie zwracanym void CriticalErrorParser Krytyczny

10.3 - Błędy na poziomie interpretera

Typ Danych Sytuacja Typ błedu Krytyczność
bmask Tworzenie maski poprzez mask(size), podając rozmiar innej wartości niż int InterpreterError Krytyczny
- Wywołanie funkcji podając niepoprawną liczbę argumentów InterpreterError Krytyczny
- Wywołanie funkcji podając zły typ argumentu InterpreterError Krytyczny
bmask - Tworzenie submaski metodą .submask([indexes], SubmaskNameStr) podając nieistniejące indexy
- Nadanie nazwy bitowi o indexie poza zakresem maski, lub wartości innej niż int np. maskA[1.5].name
- Podanie indeksów poza zakresem maski nadrzędnej w metodzie .append([indexes], SourceMaskObject)
- Podanie nieistniejącego indeksu maski nadrzędnej w metodzie .reconfigure([indexes], SourceMaskObject)
InterpreterError Krytyczny
bmask Nadanie bitowi danej maski już istniejącej nazwy innego bitu w tej masce. InterpreterError Krytyczny
bmask - Tworzenie submaski metodą .submask([bitName ...], SubmaskNameStr) podając nieistniejące nazwy bitów
- Odwołanie się do bitu o nieistniejącej lub pustej nazwie
InterpreterError Krytyczny
bmask Usunięcie metodą .remove(SubmaskNameStr) submaski, która nie istnieje InterpreterError Niekrytyczny
bmask Tworzenie submaski metodą mask([indexes, SourceMaskObject) podając nieistniejący obiekt maski nadrzędnej InterpreterError Krytyczny
bmask Utworzenie metodą .submask([indexes], SubmaskNameStr) drugiej submaski o tej samej nazwie InterpreterError Krytyczny
all Wywołanie nieobsługiwanej konwersji typu, np. str -> float lub konwersja bezpośrednio na typie float np. float 12.34.toInt() InterpreterError Krytyczny
bool Wykonanie operacji arytmetycznej +, -, /, * na obiektach typu bool InterpreterError Krytyczny
int, float, str, bool - Wykonanie operacji negacji poprzez inverse zmiennej typu innego niż bmask
- Wykonanie operacji conjunction, disjunction, exclusive pomiędzy dwoma zmiennymi typu innego niż bmask
InterpreterError Krytyczny
int, float, str, bool Odwołanie się do wartości zmiennej, która została zadeklarowana ale nie została jej przypisana wartość InterpreterError Krytyczny
all Odwołanie się niezadeklarowanej zmiennej InterpreterError Krytyczny
all Użycie zmiennej poza blokiem, w którym została zadeklarowana InterpreterError Krytyczny
bmask Przypisanie do zmiennej typu bmask niezgodnej wartości z gramatyką AssigmentBmaskError Krytyczny
int Przypisanie do zmiennej typu int niezgodnej wartości z gramatyką AssigmentRValueError Krytyczny
float Przypisanie do zmiennej typu float niezgodnej wartości z gramatyką AssigmentRValueError Krytyczny
bool Przypisanie do zmiennej typu bool niezgodnej wartości z gramatyką AssigmentRValueError Krytyczny
str Przypisanie do zmiennej typu str niezgodnej wartości z gramatyką AssigmentRValueError Krytyczny
- Funckja zwraca typ niezgodny z jej definicją InterpreterError Krytyczny

11 - Sposób uruchomienia, wej./wyj.

Aby uruchomić program należy przejść do katalogu src i wywołać program run.py podając jako pierwszy argument ścieżke do pliku .txt, po czym opcjonalnie paramtery wyołania głównej funkcji main. Program można wywołać używając flag -h --help lub -pt, określone poniżej:

>>>python3 run.py example1.txt 3 -h
usage: run.py [-h] [--pt] file [args ...]

Run BitScope, a general-purpose language with embedded bitmask operations.

positional arguments:
  file        (Required) Path to the code file.
  args        (0ptional) Arguments to pass to the program

options:
  -h, --help  show this help message and exit
  --pt        (0ptional)Print tree of nodes

Example executed: example1.txt:

int main(int a){
    int b = a * 2;
    return b;
}

Result:

>>>python3 run.py example1.txt 3 --pt
6
Tree of code: 

 Program:
  FunDef, [name: main, return type: TreeTypes.TYPE_INT, pos: (1, 1)]
    VariableDeclarationStatement, [var_type: TreeTypes.TYPE_INT, var_name: a, pos: (1, 10)]
    Block:
      AssignmentStatement, [pos: (2, 5)
        Left:
          VariableDeclarationStatement, [var_type: TreeTypes.TYPE_INT, var_name: b, pos: (2, 5)]
        Right:
          MultiplyExpression, [pos: (2, 15)]
            Left:
              IdentifierExpression, [name: a, pos: (2, 13)
            Right:
              LiteralInt, [value: 2, pos: (2, 17)]
      ReturnStatement, [pos: (3, 5)]
        Expression:
          IdentifierExpression, [name: b, pos: (3, 12)

12 - Analiza wymagań funkcjonalnych i niefunkcjonalrunych


12.1 - Zakres widoczności zmiennych, kontekst w bloku { } (wymaganie z punktu 2.10)

Widoczność każej zmiennej jest w obszarze zamkniętym { }, w którym została zmienna zadeklarowana. Ponadto zmienne można nadpisywać w zagnieżdżonych blokach:

void main(){
    int a = 10;
    if (1 < 3){
        int a = 4;  # nadpisanie zmiennej a wewnątrz bloku
        print(a);   # funkcja print wyświetli 4
    }
    print(a);       
    # funkcja print wyświetli 10, ponieważ blok w którym została zadeklarowana zmienna a z przypisaniem jej wartości 4 skończył się
}

Zatem interpreter będzie zarządzał granicami widoczności w taki sposób, aby odróżniać zmienne w blokach nadrzędnych od zmiennych w blokach podrzędnych.
Jednak dostęp do zmiennych z innych bloków będzie możliwy:

void main(){
    int b = 10;
    if (1 < 3){
        int a = 4 + b;  # zmienna a jest inicjalizowana z użyciem zmiennej b z nadrzędnego bloku
        print(a);       # funkcja print wyświetli 14
    }
}

Aby były możliwe bardziej złożone wyrażenia korzystając z instrukcji warunkowych i pętli, interpreter będzie umożliwiał dostęp do zmiennych utworzonych w nadrzędnych blokach.
Gdy blok podrzędny się kończy, zmienna zadeklarowa w bloku podrzędnym nie będzie już widoczna w bloku nadrzędnym(przykład 2.10), zasada ta jednak nie dotyczy submasek traktowanych jako atrybut obiektu maski bitowej:

void main(){
    # tworzenie maski i submasek
    bmask maskA = mask(8);
    maskA.submask([1,2,3], "Gain"); 
    if (1 < 3){
        maskA.submask([4,5,6], "Gain2");
    }
    # po wykonaniu instrukcji if maskA ma dwie submaski
    maskA.Gain = 4;
    maskA.Gain2 = 5;
}

13 - Opis sposobu realizacji

Projekt zostanie napisany w języku Python w wersji 3.11.


13.1 - Warstwa źródła tekstu

Przed lekserem zostanie dodana warstwa źródła tekstu czyli klasa Source.py przygotowująca kod do analizy leksykalnej, jej zadaniem jest:

  • rozpoznaje koniec pliku poprzez sprawdzenie czy napotkany znak to chr(3), czyli EOF(End of File)
  • ujdnolicenie znaków końca linii - w przypadku różnych systemów znak nowej linii jest inaczej oznaczany:
  • \n dla systemu Unix
  • \r\n dla systemu Windows
  • \r dla Classic Mac OS Warstwa źródła tekstu normalizuje oznaczenia jakie napotka w zależności od systemu na jakim jest program uruchamiany do \n

13.2 - Analizator leksylany (Lekser)

Moduł leksera będzie odpowiadał za konwersję kodu źródłowego dostarczonego przez warstwę normalizacji na tokeny w sposób leniwy, co oznacza, że będzie analizował kolejny znak dopiero wtedy, gdy parser zgłosi zapotrzebowanie na nowy token.

13.2.1 - Token

Aby użytkownik otrzymywał status o błędzie w formacie przestawionym w punkcie 10, token musi zawierać:

  • Typ tokenu - jeden z wymienionych w sekcji 13.2.2, dobrany na podstawie napotkanej sekwencji znaków
  • Wartość tokenu
  • Numer wiersza - początek sekwencji znaków budujących token
  • Numer kolumny - początek sekwencji znaków budujących token

Aby możliwa była lokalizacja błędu poprzez numer wiersza i kolumny lekser będzie miał na początku zainicjalizowane dwa liczniki:

  • lineCounter - początkowa jego wartość to 1, analizator przechodzi po kolejnych znakach natomiast gdy napotka znak \n inkrementuje licznik lineCounter o 1
  • columnCounter - początkowa jego wartość to 1, każdy kolejny znak, po którym przechodzi analizator inkrementuje licznik columnCounter o 1, z wyjątkiem znaku \n, który resetuje licznik columnCounter do wartości początkowej 1

Budując nowy token, zapisywany jest w obiekcie tokenu początek sekwencji znaków, czyli wartość dwóch liczników w chwili rozpoczęcia budowania nowego tokenu.

13.2.2 - Typy tokenów

Tworzone typy tokenów:

Typ Tokeny
Tokeny typów danych BMASK, BOOL, INT, FLOAT, STR
Token Void VOID
Tokeny literału LITERAL_BINARY, LITERAL_INT, LITERAL_FLOAT, LITERAL_STR, LITERAL_BOOL
Tokeny identyfikujące konstrukcje DOT, COMMA, IF, ELIF, ELSE, WHILE, BREAK, RETURN
Tokeny operatorów binarnych AND, OR, +, -, *, /, ==, !=, >=, >, <=, <, =, CONJUCTION, DISJUCTION, EXCLUSIVE,
Tokeny operatorów unarnych NOT, INVERSE
Tokeny strukturalne (, ), {, }, [, ], ;
Token identyfikatora IDENTIFIER
Tokeny komentarza #
Token końca pliku EOF

13.2.3 - LexerNoComment, interfejst ILexer, warstwa filtrująca

Jeżeli podczas analizy leksylanej analizator napotka symbol komentarza # zignoruje wtedy wszystkie znaki do końca linii (tzn. do znaku \n), tworząc token komentarza. Po przejściu do nowej linii rozpoczyna budowę nowego tokenu. Jednak parser na wejsciu otrzymuje kolejne tokeny dostarczone przez klasę LexerNoComment używającą interfejsu ILexer, która odfiltrowuje tokeny komentarza, nie przekazując ich parserowi.


13.3 - Analizator składniowy (Parser)

Po analizie leksykalnej, parser z otrzymanych od leksera odflitrowanych z komentarzy tokenów ma za zadanie zbudować drzewo składniowe, Węzłami będą obiekty reprezentujące poszczególne instrukcje.

13.3.1 - Węzeł

Parser przetwarza tokeny, które otrzymał od leksera i buduje z nich węzły:

Węzeł Konstrukcja Dzieci węzła
Node węzeł bazowy wszystkie węzły
FunDef deklaracaja funkcji -
Block blok kodu { ... } -
Program węzeł programu -
Statement węzeł bazowy dla instrukcji zdefiniowane w gramatyce IfStatement, WhileStatement, ReturnStatement, VariableDeclarationStatement,
BreakStetement, FunctionCallStatement, AssignmentStatement
VariableDeclarationStatement węzeł deklaracji zmiennej -
IfStatement węzeł instrukcji warunkowej if -
BreakStetement węzeł instrukcji break -
AssignmentStatement węzeł instrukcji przypisania = -
WhileSetement węzeł pętli while -
FunctionCallStatement wywołanie funkcji -
ReturnStatement węzeł instrukji return expression; -
Expression węzeł bazowy dla wyrażeń zdefiniowane w gramatyce LiteralExpression, BinaryExpression, UnaryExpression, AccessExpression
BitList ,FunctionCallExpression, IdentifierExpression, IndexExpression
LiteralExpression węzeł bazowy na literały LiteralBool, LiteralInt, LiteralFloat,LiteralStr,
LiteralBool węzeł przechowujacy literał bool -
LiteralInt węzeł przechowujacy literał int -
LiteralFloat węzeł przechowujacy literał float -
LiteralStr węzeł przechowujacy literał str -
BinaryExpression węzeł bazowy na wyrażenia binarne AddExpression , ConjunctionExpression, DisjunctionExpression, NotEqualsExpression
DivideExpression, EqualsExpression, ExclusiveExpression, GreatherEqualExpression
GreatherExpression, LessEqualExpression, LessExpression, MultiplyExpression
OrExpression , SubtractExpression
AddExpression węzeł przechowujacy wyrażenia + -
AndExpression węzeł przechowujacy wyrażenia logiczne AND -
ConjunctionExpression węzeł przechowujacy wyrażenia conjunction (AND na masce bitowej) -
DisjunctionExpression węzeł przechowujacy wyrażenia disjunction (negacja maski bitowej) -
DivideExpression węzeł przechowujacy wyrażenia / dzielenia zmiennoprzecinkowego -
EqualsExpression węzeł przechowujacy wyrażenia == -
ExclusiveExpression węzeł przechowujacy wyrażenia excluisive (Xor na maskach bitowych) -
GreatherEqualExpression węzeł przechowujacy wyrażenia >= -
GreatherExpression węzeł przechowujacy wyrażenia > -
LessEqualExpression węzeł przechowujacy wyrażenia <= -
LessExpression węzeł przechowujacy wyrażenia < -
MultiplyExpression węzeł przechowujacy wyrażenia * -
NotEqualsExpression węzeł przechowujacy wyrażenia != -
OrExpression węzeł przechowujacy wyrażenia logiczne OR -
SubtractExpression węzeł przechowujacy wyrażenia odejmowania -
UnaryExpression węzeł bazowy na wyrażenia unarne MinusExpression, InverseExpression ,NotExpression
MinusExpression węzeł przechowujacy wyrażenia negacji poprzez - -
InverseExpression węzeł przechowujacy wyrażenia inwersji maski bitowej -
NotExpression węzeł przechowujacy wyrażenia negacji logicznej NOT -
AccessExpression węzeł przechowujacy wyrażenia dostępu poprzez operator . -
BitList węzeł przechowujacy wyrażenia listy bitów przekazanej jako parametr -
FunctionCallExpression węzeł przechowujacy wyrażenia wywołania funkcji -
IdentifierExpression węzeł przechowujacy wyrażenia identyfikatora -
IndexExpression węzeł przechowujacy wyrażenia indeksowane -
Kolejność budowania się drzewa z wezłów, wynika z gramatyki skonkretyzowanej, napisana gramatyka w punkcie 9 jest dla użytkownika, łatwiejsza do zrozumienia przy użyciu.

Podczas budowania drzewa sprawdzane jest czy budowane z węzłów drzewo są dozwolone, wszystkie błedy na etapie parsera znajdują się w punkcie 10.2. Dochodzi więc na tym etapie do analizy semantycznej, w której już mogą zostać wykryte niektóre błędy. Przykładowo nigdy nie powstanie węzeł AccessStatement który ma prawe dziecko będące np literałem int, ponieważ parser zbuduje ten węzeł tylko jeżeli prawe dziecko to expression lub function call


13.4 - Interpreter

Zadania interpretera:

  • Działanie interpretera bazuje na użyciu wzorca projektowego Wizytator
  • Interpreter ma za zadanie przejść po drzewie skłądniowym stworzonym przez parser, wykonując każdy węzeł znajdujący się w drzewie i sprawdzając jego semantykę
  • Interpreter zostaje zainicjalizowany ze środowiskiem env, posiadającym stos kontekstów funkcji (instancji klasy FunctionBlock)
  • Interpreter wchodząc w nową funkcje dodaje do stosu kontekstów funkcji w env nowy FunctionContext
  • Wchodząc w nowy blok, występujący w: if, elif, else, while, nowym ciele funkcji interpreter tworzy nowy BlockContext dodajac go do aktualnego FunctionBlock, a po zakończeniu bloku przywraca poprzedni kontekst (bloku nadrzędnego) zdejmując BlockContext ze stosu bloków znajdującym się w instancji klasy FunctionBlock
  • Obsługuje stos wywołań funkcji w zależności od kontekstu, podanych parametrów i już istniejących zmiennych
  • Identyfikuje i zgłasza użytkownikowi błędy semantyczne określone w punkcie 10.3

14 - Opis sposobu testowania

Do testowania kolejnych etapów projektu zostaną wykorzystane testy jednostkowe z użyciem pytest:


14.1 Testowanie analizatora leksykalnego(Leksera)

Sposób testowania:

  • porównanie czy z danego tekstu wygenerują się oczekiwane tokeny
  • sprawdzenie normalizowania białych znaków czy powielone białe znaki zostaną zmniejszone do jednego znaku
  • sprawdzenie jak białe znaki wpływają na budowanie się tokenów, tzn. czy biały znak nie zaburza budowania się tokenu
  • sprawdzenie zamiany znaków końca linii na znormalizowany znak \n - sprawdzenie generowania błędów przez lekser przy niezdefiniowanych symbolach lub słowach
  • sprawdzenie sytuacji przekroczenia długości literałów lub np. niezamkniętego str kończącego program
  • sprawdzenie jak lekser reaguje na komentarze, czy pomija je nieprzekazując ich dalej do parsera
  • sprawdzenie czy wygenruje się znak końca pliku EOF

14.2 Testowanie analizatora lskładniowego(Parsera)

Sposób testowania:

  • sprawdzenie na danym zestawie tokenów, jaką kolejność wykonywania/budowania w drzewie przypisał im parser, następnie zostanie to porównane ze wzorcową kolejnością budowania
  • sprawdzenie na błędnym zestawie tokenów czy parser poprawnie generuje błędy np. użycie niezadeklarowanej zmiennej, gdy budując drzewo nie znajdzie jej deklaracji
  • sprawdzenie czy węzły poprawnie się generują tzn. czy dany węzeł ma oczekiwane dzieci określone w punkcie 13.3.1

14.3 Testowanie interpretera

Sposób testowania:

  • sprawdzenie logiki działania metod na obiekcie bmask
  • sprawdzenie logiki wywołań funkcji, instrukcji przypisania, konwersji typu, zgodności typów danych
  • sprawdzenie działania logiki instrukcji warunkowych i pętli
  • sprawdzenie zakresu widoczności i zarządzania kontekstem zmiennych w ramach bloku { }

About

Custom language interpreter in Python featuring lexical analysis, parsing, AST execution, and a statically typed bitmask-oriented language design.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages