Przejdź do treści

olski.skład.rozbiór

Czytanie parsera jako drzewo tego zapisu, czyli obieg zamknięty z drugiej strony.

Oba tory stoją na dwóch różnych poziomach i tę różnicę ten moduł przechodzi. Parser wydaje wyprowadzenie, czyli drzewo nad symbolami gramatyki wraz z formami i ich cechami, a autor pisze kategorie dziedziny, w których przypadka nie ma, bo bierze się on z pozycji (olski/skład/składnia.py). Odwrotnością linearyzacji ten plik zatem nie jest: jest drugą funkcją, której przeciwdziedziną jest to, co autor napisał, a wspólnym mają obie typ, a nie kod. Po co ten obieg stoi, czego z niego nie wraca i co to mówi o obu torach, trzyma docs/po-wypisaniu.md, a sam niezmiennik docs/design-notes.md.

Zasady niżej rozstrzygają o kodzie i każda dotyczy każdej funkcji w nim.

Wydaje krotkę drzew, a nie drzewo. Napis do drzewa jest relacją, bo ten zapis ma na jeden napis kilka drzew, a napis nie mówi ani o relacji okolicznika, ani o znaczniku tematu postawionym tam, gdzie konstytuent i tak stoi. Wybór między nimi żądałby rankingu, którego ten projekt nie ma. Wraz z krotką wychodzi stąd powód, dla którego reszta nie wróciła, bo krotka pusta nie mówi, która z przyczyn zadziałała (Odczyt).

Rozstrzyga linearyzacja, a nie ten plik. Kandydat wychodzi stąd tylko wtedy, gdy wypisany daje te formy, z których go przeczytano, więc drzewo stąd zwrócone mówi napisem to, co przeczytano, i nie ma jak skłamać. Zdejmuje to z tego pliku drugą kopię tego, co kompilator wie o szyku i o formach, i jest to ten sam chwyt, którym mierzy się olski/skład/przegląd.py.

Kształt bierze z drzewa, a wartości z formy. Czytanie parsera jest swoim kształtem, a lematy i wartości cech są z niego wyłączone rozmyślnie (signature w olski/parse/czytanie.py), więc lemat wzięty z liścia jest lematem tego wyprowadzenia, które w czytaniu zostało, a nie tego, o którym mówi zdanie. Pytana jest zatem krawędź grafu segmentacji, czyli wszystkie czytania formy, a wybór między nimi zostawia to porównanie.

Z olski/skład/__init__.py ten moduł nie wychodzi i jest to rozstrzygnięcie. Czyta on gramatykę, a ta buduje się przy imporcie, więc wpisany tam kazałby ją zbudować każdemu, kto sięga po sam kompilator, i parser przestałby być świadkiem, a stałby się zależnością. Rozstrzygnięcie obejmuje tak samo olski/__init__.py, bo import podpakietu przechodzi przez pakiet nadrzędny. Trzyma to tests/test_rozbiór.py.

ZNAKI_LISTY = ('i', ',') module-attribute

CZASOWNIKOWE = ('fin', 'praet', 'inf') module-attribute

PRZECZENIE = 'nie' module-attribute

PRZYDAWKA = 'przydawka' module-attribute

CZŁON_PRZYDAWKI = 'człon_przydawki' module-attribute

POZYCJE = ('podmiot', 'dopełnienie', 'orzecznik', 'orzeczenie', 'wyrażenie_przyimkowe', OKOLICZNIK_PRZYSŁÓWKOWY, OKOLICZNIK_ZDANIOWY, OKOLICZNIK_NARZĘDNIKOWY) module-attribute

BEZ_PRZYIMKA = '' module-attribute

FRAZA_BEZOKOLICZNIKOWA = 'fraza_bezokolicznikowa' module-attribute

ZDANIE_PODRZĘDNE = 'zdanie_podrzędne' module-attribute

ZDANIOWE = (FRAZA_BEZOKOLICZNIKOWA, ZDANIE_PODRZĘDNE) module-attribute

NIERUCHOME = ('orzeczenie', *ZDANIOWE) module-attribute

GRUPUJĄCE = ('grupa_orzeczenia', 'wypełnienia', 'okoliczniki', 'zdanie_składowe', PARA_WYPEŁNIEŃ) module-attribute

SŁOWO = 'słowo' module-attribute

RELACJE = _relacje() module-attribute

RELACJE_SPÓJNIKÓW = _relacje_spójników() module-attribute

PozaZapisem

Bases: Exception

Czytanie, dla którego ten zapis kategorii nie ma.

Wyjątkiem, a nie wartością, bo pada w środku budowania drzewa, a to, co go łapie, jest jedno: abstrahuj odpowiada wtedy niczym. Odpowiedź pusta jest odpowiedzią, a nie porażką: polszczyzna ma zdania, których ten zapis nie mówi, i po to ten kierunek stoi obok parsera, żeby było widać które.

Source code in olski/skład/rozbiór.py
171
172
173
174
175
176
177
178
179
class PozaZapisem(Exception):
    """Czytanie, dla którego ten zapis kategorii nie ma.

    Wyjątkiem, a nie wartością, bo pada w środku budowania drzewa,
    a to, co go łapie, jest jedno: :func:`abstrahuj` odpowiada wtedy niczym.
    Odpowiedź pusta jest odpowiedzią, a nie porażką:
    polszczyzna ma zdania, których ten zapis nie mówi,
    i po to ten kierunek stoi obok parsera, żeby było widać które.
    """

Odczyt dataclass

Drzewa, którymi czytanie wraca, wraz z powodami tych, którymi nie wróciło.

Powodów jest kilka rodzajów i rozdziela je docs/po-wypisaniu.md, a krotka drzew sama nie mówi, który zadziałał na tym zdaniu. Powód opisuje kandydata, który odpadł, a nie odpowiedź, więc zdanie z drzewami ma jedno i drugie naraz.

Rodzaje te dzielą się przy tym na dwa i tę granicę niesie kandydaci, bo powód jest zdaniem dla czytelnika, a nie kluczem tabeli: zero mówi, że kategorii nie ma i drzewa nie było z czego zbudować, a liczba dodatnia przy pustych drzewach, że kandydat powstał i wypisuje się innym napisem, niż ten, który go wywołał. Pyta o to pomiar nad rejestrem (sonda/znaczenia.py), któremu pierwsze mówi, czego temu zapisowi brakuje, a drugie, gdzie oba kierunki mówią co innego o jednym zdaniu.

Source code in olski/skład/rozbiór.py
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
@dataclass(frozen=True)
class Odczyt:
    """Drzewa, którymi czytanie wraca, wraz z powodami tych, którymi nie wróciło.

    Powodów jest kilka rodzajów i rozdziela je ``docs/po-wypisaniu.md``,
    a krotka drzew sama nie mówi, który zadziałał na tym zdaniu.
    Powód opisuje kandydata, który odpadł, a nie odpowiedź,
    więc zdanie z drzewami ma jedno i drugie naraz.

    Rodzaje te dzielą się przy tym na dwa i tę granicę niesie ``kandydaci``,
    bo powód jest zdaniem dla czytelnika, a nie kluczem tabeli:
    zero mówi, że kategorii nie ma i drzewa nie było z czego zbudować,
    a liczba dodatnia przy pustych drzewach, że kandydat powstał
    i wypisuje się innym napisem, niż ten, który go wywołał.
    Pyta o to pomiar nad rejestrem (``sonda/znaczenia.py``),
    któremu pierwsze mówi, czego temu zapisowi brakuje,
    a drugie, gdzie oba kierunki mówią co innego o jednym zdaniu.
    """

    drzewa: tuple[Zdanie, ...]
    powody: tuple[str, ...]
    #: Ilu kandydatów zbudowano, zanim linearyzacja o nich rozstrzygnęła.
    kandydaci: int = 0

drzewa instance-attribute

powody instance-attribute

kandydaci = 0 class-attribute instance-attribute

__init__(drzewa, powody, kandydaci=0)

Obieg dataclass

Drzewo puszczone w obieg: napis, który z niego wyszedł, i drzewa, które wróciły.

Niezmiennikiem jest przynależność, a nie równość, i wywodzi to docs/design-notes.md: drzewo do napisu jest funkcją, napis do drzewa relacją, więc żąda się tego, żeby drzewo pierwotne stało wśród tych, które wróciły.

Source code in olski/skład/rozbiór.py
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
@dataclass(frozen=True)
class Obieg:
    """Drzewo puszczone w obieg: napis, który z niego wyszedł, i drzewa, które wróciły.

    Niezmiennikiem jest przynależność, a nie równość, i wywodzi to
    ``docs/design-notes.md``: drzewo do napisu jest funkcją, napis do drzewa relacją,
    więc żąda się tego, żeby drzewo pierwotne stało wśród tych, które wróciły.
    """

    napis: str
    drzewo: Zdanie
    odczyt: Odczyt

    @property
    def wróciło(self) -> bool:
        return sygnatura(self.drzewo) in [sygnatura(drzewo) for drzewo in self.odczyt.drzewa]

    def opisz(self) -> str:
        """Zdanie, które ma przeczytać ten, komu obieg się nie zamknął."""
        if self.wróciło:
            return f"„{self.napis}” wraca tym drzewem"
        if not self.odczyt.drzewa:
            zdanie = f"„{self.napis}” nie wraca żadnym drzewem tego zapisu"
        else:
            #  Napisu drzew, które wróciły, nie ma tu po co wypisywać:
            #  każde z nich wypisuje się tym samym napisem i to jest warunek,
            #  po którym stąd wyszło, więc różnica siedzi w drzewie, a nie w tekście.
            zdanie = f"„{self.napis}” wraca {len(self.odczyt.drzewa)} drzewami i żadnym z nich"
        if not self.odczyt.powody:
            return zdanie
        return f"{zdanie}: {'; '.join(self.odczyt.powody)}"

napis instance-attribute

drzewo instance-attribute

odczyt instance-attribute

wróciło property

__init__(napis, drzewo, odczyt)

opisz()

Zdanie, które ma przeczytać ten, komu obieg się nie zamknął.

Source code in olski/skład/rozbiór.py
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
def opisz(self) -> str:
    """Zdanie, które ma przeczytać ten, komu obieg się nie zamknął."""
    if self.wróciło:
        return f"„{self.napis}” wraca tym drzewem"
    if not self.odczyt.drzewa:
        zdanie = f"„{self.napis}” nie wraca żadnym drzewem tego zapisu"
    else:
        #  Napisu drzew, które wróciły, nie ma tu po co wypisywać:
        #  każde z nich wypisuje się tym samym napisem i to jest warunek,
        #  po którym stąd wyszło, więc różnica siedzi w drzewie, a nie w tekście.
        zdanie = f"„{self.napis}” wraca {len(self.odczyt.drzewa)} drzewami i żadnym z nich"
    if not self.odczyt.powody:
        return zdanie
    return f"{zdanie}: {'; '.join(self.odczyt.powody)}"

abstrahuj(czytanie, kontekst=TERAZ)

Drzewa tego zapisu, z których wychodzi to czytanie parsera.

Krotka pusta jest odpowiedzią: polszczyzna ma czytania, których ten zapis nie mówi, a które z nich to są, widać po tym, co stąd nie wraca. Czemu akurat to nie wróciło, mówi powody.

Source code in olski/skład/rozbiór.py
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
def abstrahuj(czytanie: Node, kontekst: Kontekst = TERAZ) -> Odczyt:
    """Drzewa tego zapisu, z których wychodzi to czytanie parsera.

    Krotka pusta jest odpowiedzią: polszczyzna ma czytania,
    których ten zapis nie mówi, a które z nich to są,
    widać po tym, co stąd nie wraca.
    Czemu akurat to nie wróciło, mówi ``powody``.
    """
    if czytanie.label != "wypowiedzenie":
        raise ValueError(f"czytanie zdania, a nie {czytanie.label}")
    try:
        kandydaci = list(_ciąg(_zdanie(czytanie)))
    except (PozaZapisem, PozaRamą) as błąd:
        return Odczyt((), (str(błąd),))
    drzewa: list[Zdanie] = []
    powody: list[str] = []
    for drzewo in kandydaci:
        powód = _rozjazd(drzewo, czytanie, kontekst)
        if powód is None:
            drzewa.append(drzewo)
        else:
            powody.append(powód)
    return Odczyt(tuple(drzewa), _bez_powtórzeń(powody), len(kandydaci))

rozbierz(zdanie, kontekst=TERAZ)

Drzewa tego zapisu, z których wychodzi to zdanie, po wszystkich czytaniach.

Wieloznaczność napisu wychodzi tędy jako kilka drzew i nic jej nie odsiewa, bo pytanie jest tu o to, co autor mógł napisać, a nie o werdykt: werdykt wydaje olski/werdykt.py i wydaje go czytelnikowi tekstu. Drzewo powtórzone przez dwa czytania stoi raz, bo dwa razy to samo nie jest dwiema odpowiedziami.

Powody zbierają się po wszystkich czytaniach, a nie po tym jednym, które zaszło najdalej, bo czytania są tu odpowiedziami równorzędnymi i zdanie odrzucone przez każde z nich bywa odrzucone przez każde inaczej.

Zdanie bez czytań ma powód osobny, bo mówi on o czym innym: pustka jest wtedy werdyktem gramatyki, a nie brakiem kategorii w tym zapisie, i zero kandydatów mówi tu o czytaniu, którego nie było, a nie o kategorii.

Source code in olski/skład/rozbiór.py
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
def rozbierz(zdanie: str, kontekst: Kontekst = TERAZ) -> Odczyt:
    """Drzewa tego zapisu, z których wychodzi to zdanie, po wszystkich czytaniach.

    Wieloznaczność napisu wychodzi tędy jako kilka drzew i nic jej nie odsiewa,
    bo pytanie jest tu o to, co autor mógł napisać, a nie o werdykt:
    werdykt wydaje ``olski/werdykt.py`` i wydaje go czytelnikowi tekstu.
    Drzewo powtórzone przez dwa czytania stoi raz,
    bo dwa razy to samo nie jest dwiema odpowiedziami.

    Powody zbierają się po wszystkich czytaniach, a nie po tym jednym,
    które zaszło najdalej, bo czytania są tu odpowiedziami równorzędnymi
    i zdanie odrzucone przez każde z nich bywa odrzucone przez każde inaczej.

    Zdanie bez czytań ma powód osobny, bo mówi on o czym innym:
    pustka jest wtedy werdyktem gramatyki, a nie brakiem kategorii w tym zapisie,
    i zero kandydatów mówi tu o czytaniu, którego nie było, a nie o kategorii.
    """
    czytania = parse(GRAMMAR, morphology(zdanie), zatrzymanie=False).readings
    if not czytania:
        return Odczyt((), ("gramatyka olskiego nie wyprowadza tego zdania",))
    wynik: dict[tuple, Zdanie] = {}
    powody: list[str] = []
    kandydaci = 0
    for czytanie in czytania:
        odczyt = abstrahuj(czytanie, kontekst)
        powody.extend(odczyt.powody)
        kandydaci += odczyt.kandydaci
        for drzewo in odczyt.drzewa:
            wynik.setdefault(sygnatura(drzewo), drzewo)
    return Odczyt(tuple(wynik.values()), _bez_powtórzeń(powody), kandydaci)

sygnatura(drzewo)

Co czyni dwa drzewa tego zapisu jednym drzewem.

Drzewo mówi o sobie wszystko, co ma znaczyć, więc sygnatura jest nim samym z jedną różnicą: tożsamość wychodzi numerem, a nie obiektem. Obiekt jest zapisem tożsamości, bo tyle daje zmienna w Pythonie, a dwa drzewa zbudowane osobno nie mają jak dzielić obiektów, więc porównanie ich wprost odpowiadałoby przecząco zawsze. Numer nadawany po kolei trzyma to, co ta deklaracja niesie: nie która rzecz jest którą, tylko które wystąpienia są jedną.

Liczone jest to po polach, a nie kategoria po kategorii, bo kategoria dopisana do składni i tu pominięta porównywałaby się z dokładnością do niczego.

Source code in olski/skład/rozbiór.py
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
def sygnatura(drzewo) -> tuple:
    """Co czyni dwa drzewa tego zapisu jednym drzewem.

    Drzewo mówi o sobie wszystko, co ma znaczyć, więc sygnatura jest nim samym
    z jedną różnicą: tożsamość wychodzi numerem, a nie obiektem.
    Obiekt jest zapisem tożsamości, bo tyle daje zmienna w Pythonie,
    a dwa drzewa zbudowane osobno nie mają jak dzielić obiektów,
    więc porównanie ich wprost odpowiadałoby przecząco zawsze.
    Numer nadawany po kolei trzyma to, co ta deklaracja niesie:
    nie która rzecz jest którą, tylko które wystąpienia są jedną.

    Liczone jest to po polach, a nie kategoria po kategorii,
    bo kategoria dopisana do składni i tu pominięta
    porównywałaby się z dokładnością do niczego.
    """
    return _sygnatura(drzewo, {}, znacznik=True)

znaczenie(drzewo)

Co czyni dwa drzewa tego zapisu jednym zdaniem logicznym.

sygnatura pyta, czy to jedno drzewo, i na niej stoi niezmiennik obiegu, bo obieg żąda z powrotem tego drzewa, które napis wypuściło. Tutaj pytanie jest o to, o czym zdanie jest, więc dwa różne drzewa bywają pod nim jedną odpowiedzią.

Schodzi wyłącznie znacznik tematu i schodzi dlatego, że niesie go szyk, a nie to, o czym zdanie jest: Celem jest parser. i Parser jest celem. mówią to samo zdanie logiczne i co innego stawiają na czele (Wyróżnienie w olski/skład/składnia.py). Wypada przy tym cała kategoria, a nie samo pole miejsce, bo konstytuent wyróżniony ma stanąć równo z tym samym konstytuentem gołym.

Lemat, liczba, relacja okolicznika i tożsamość zostają, bo każde z nich mówi, o czym zdanie jest, a nie jak zostało napisane. Łącznik to byłby drugim takim kandydatem i nie jest nim, bo o tym, czy niesie coś ponad kopulę, nie rozstrzygnął nikt; trzyma to todo/.

Source code in olski/skład/rozbiór.py
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
def znaczenie(drzewo) -> tuple:
    """Co czyni dwa drzewa tego zapisu jednym zdaniem logicznym.

    :func:`sygnatura` pyta, czy to jedno drzewo, i na niej stoi niezmiennik obiegu,
    bo obieg żąda z powrotem tego drzewa, które napis wypuściło.
    Tutaj pytanie jest o to, o czym zdanie jest,
    więc dwa różne drzewa bywają pod nim jedną odpowiedzią.

    Schodzi wyłącznie znacznik tematu i schodzi dlatego,
    że niesie go szyk, a nie to, o czym zdanie jest:
    `Celem jest parser.` i `Parser jest celem.` mówią to samo zdanie logiczne
    i co innego stawiają na czele (``Wyróżnienie`` w ``olski/skład/składnia.py``).
    Wypada przy tym cała kategoria, a nie samo pole ``miejsce``,
    bo konstytuent wyróżniony ma stanąć równo z tym samym konstytuentem gołym.

    Lemat, liczba, relacja okolicznika i tożsamość zostają,
    bo każde z nich mówi, o czym zdanie jest, a nie jak zostało napisane.
    Łącznik ``to`` byłby drugim takim kandydatem i nie jest nim,
    bo o tym, czy niesie coś ponad kopulę, nie rozstrzygnął nikt;
    trzyma to ``todo/``.
    """
    return _sygnatura(drzewo, {}, znacznik=False)

obieg(drzewo, kontekst=TERAZ)

Drzewo wypisane i przeczytane z powrotem.

Parser jest tu świadkiem, a nie zależnością, i to się przez ten moduł nie zmienia: zdanie, którego gramatyka nie obejmuje, wraca stąd bez żadnego drzewa, a kompilatorowi nie odbiera to niczego. Wywód trzyma docs/design-notes.md.

Source code in olski/skład/rozbiór.py
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
def obieg(drzewo: Zdanie, kontekst: Kontekst = TERAZ) -> Obieg:
    """Drzewo wypisane i przeczytane z powrotem.

    Parser jest tu świadkiem, a nie zależnością, i to się przez ten moduł nie zmienia:
    zdanie, którego gramatyka nie obejmuje, wraca stąd bez żadnego drzewa,
    a kompilatorowi nie odbiera to niczego.
    Wywód trzyma ``docs/design-notes.md``.
    """
    napis = kompiluj(drzewo, kontekst)
    return Obieg(napis=napis, drzewo=drzewo, odczyt=rozbierz(napis, kontekst))