Moduły i klasy¶
Poniżej uproszczony diagram klas. W szczególności metody są niekompletne, ich sygnatura też może być uproszczona. Szczegóły niżej.
corpus2
jest osobną biblioteką i nie jest zależny od JOSKIPIstructs
jest zależne tylko odcorpus2
ops
jest zależne odstructs
icorpus2
(operacje na tokenach i anotacjach)parser
jest zależny od wszystkich pozostałych modułów:ops
: tworzy obiekty operatorówcorpus2
: parser jest parametryzowany tagsetemstructs
: parser tworzy obiektyVariables
,PositionRef
i wartości innych typów danych.
corpus2: zdanie, anotacje, relacje, tokeny, tagsety i ich składowe; rzutowanie po anotacjach¶
Są to struktury danych, które mogą być przydatne nie tylko dla JOSKIPI i dlatego należą one do biblioteki corpus2
.
Do implementacji JOSKIPI L0 powinny wystarczyć struktury obecnie dostępne w ramach corpus2
.
Szczegóły są omówione tu: Reprezentacja danych
structs (struktury danych bezpośrednio związane z JOSKIPI): kontekst zdania, typy danych i odwołania do zmiennych¶
Każdy typ danych wspierany przez język ma odpowiadającą mu klasę. W JOSKIPI L0 jest mamy tylko zmienne po pozycjach, dlatego mamy PositionRef
a nie mamy odwołań do innych typów danych.
Szczegóły przeniosłem tu: Struktury_danych
ops: implementacje operatorów¶
Wszystkie wyrażenia JOSKIPI (operatory), które używane są w L0 są wyrażeniami funkcyjnymi. Interpretacją tych wyrażeń jest są obiekty odpowiednich podklas klasy Function
. Innymi słowy: parser, napotkawszy operator o podanej nazwie, tworzy obiekt danej podklasy.
Niektóre implementacje operatorów przyjmują jako argumenty konstruktora obiekty–implementacje operatorów (odpowiada to wyrażeniu JOSKIPI zawierającemu inne wyrażenia). W takich sytuacjach jako argumenty powinne być wymagane podklasy o określonym zwracanym typie: BoolFunction
, TsetFunction
itp., a nie ogólna klasas Function
. W przyszłości (L2) obok klasy Function
będziemy mieć inne podklasy Operatora (np. reguły).
Function
to nadklasa wszystkich operatorów funkcyjnych JOSKIPI. Operatory funkcyjne to funktory: po przyjęciuSentenceContext
, zwracają wartość (Value
).apply(con: SentenceContext): Value
to realizacja funktora. Operator zwraca wartość. Implementacje konkretnych operatorów muszą mieć określony zwracany typ (np.Tset
).embed_vars(vars: Variables)
powoduje zapamiętanie przypisania nazw zmiennym w tym operatorze; potrzebne, by użytkownik mógł je sprawdzić bądź zmienićget_vars(): Variables
zwraca zapamiętane przypiasnie nazw zmiennym; dzięki temu można pobrać wartości przypisane identyfikatorom a nawet zmienić przypisania.
Parser¶
Parser korzysta z ANTLR-a (prawdopodobnie pozostaniemy przy wersji 2.7, Bartek mówi, że w wersji 3 wprowadzili dużo zmian na gorsze).
Moduł zawiera:joskipi.g
, czyli gramatykę języka. Gramatyka taka składa się z reguł i przypisanych im akcji. Akcje to kawałki kodu C++-owego, które w naszym wypadku odpalają konstruktory operatorów z modułuops
.ANTLRLexer
iANTLRParser
to klasy wygenerowane automatycznie przez ANTLR na podstawiejoskipi.g
(generowanie powinno znaleźć się w build processie)Parser
, czyli opakowanie naANTLRParser
— zawiera funkcje API (wygodne, dobrze opisane).- konstruktor przyjmuje nazwę tagsetu (w środku trzymamy obiekt
Tagset
powstały zTagsetManager::get_named_tagset
) - udostępniamy podstawowe reguły parsera, tj. niektóre funkcje wygenerowane przez ANLTR
- możemy udostępnić resztę na potrzeby testowania, np.
parser.get_antlr_parser().parse_sth_exotic(…)
- funkcje do parsowania stałych różnych typów na podstawie stringa i strumienia
- konstruktor przyjmuje nazwę tagsetu (w środku trzymamy obiekt
Uwaga: parser zawiera też trochę ważnej logiki w swoim kodzie. „Niepełnowartościowe typy danych” (tekst i liczba) są obsługiwane jedynie na poziomie gramatyki JOSKIPI — po przeparsowaniu powstają z tego zwykłe inty i stringi (stałe).
Parsowanie wyrażeń z odwołaniami do zmiennych przebiega w następujący sposób:- podczas parsowania, trzymamy obiekt reprezentujący wartości zmiennych (
structs.Variables
), - dla każdej zmiennej tworzone jest odwołanie do zmiennej (
structs.PositionRef
w L0), - obiekty powstałe z przeparsowania wyrażenia i wszystkich podwyrażeń mają swoje odwołania do zmiennych, które wskazują na miejsce w pamięci, gdzie trzymana jest wartość (nie odwołują się przez nazwę zmiennej, bo to nieefektywne)
- by zachować dostęp do zmiennej przez nazwę, do obiektu reprezentującego zewnątrzne wyrażenie wstawiany jest obiekt
structs.Variables
(nazwa -> miejsce, gdzie siedzi pozycja).
API parsera udostępnia przede wszystkim funkcje, które nie wymagają podania zasięgu przez użytkownika. Akcje podpięte pod reguły dla tych wyrażeń tworzą pusty obiekt zasięgu i przekazują go niżej regułom parsującym wyrażenia tego samego typu, lecz oczekujące podania zasięgu.