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.

Zależności między modułami (rysunek przedstawia tylko najważniejsze zależności):
  • corpus2 jest osobną biblioteką i nie jest zależny od JOSKIPI
  • structs jest zależne tylko od corpus2
  • ops jest zależne od structs i corpus2 (operacje na tokenach i anotacjach)
  • parser jest zależny od wszystkich pozostałych modułów:
    • ops: tworzy obiekty operatorów
    • corpus2: parser jest parametryzowany tagsetem
    • structs: parser tworzy obiekty Variables, 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ęciu SentenceContext, 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łu ops.
  • ANTLRLexer i ANTLRParser to klasy wygenerowane automatycznie przez ANTLR na podstawie joskipi.g (generowanie powinno znaleźć się w build processie)
  • Parser, czyli opakowanie na ANTLRParser — zawiera funkcje API (wygodne, dobrze opisane).
    • konstruktor przyjmuje nazwę tagsetu (w środku trzymamy obiekt Tagset powstały z TagsetManager::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

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.

joskipi.zargo (12.4 KB) Adam Radziszewski, 25 Oct 2010 13:33

JOSKIPIClassDiagram.png (24.4 KB) Adam Radziszewski, 25 Oct 2010 13:33

class.pdf (525 KB) Adam Radziszewski, 27 Oct 2010 14:16