Przejdź do treści

olski.segmentacja

Warstwa morfologiczna olskiego: z napisu graf segmentacji wraz z czytaniami.

Wykluczenia podzbioru są dwojakie, bo produkcja rozstrzyga o zdaniu, a nie o formie. Produkcje w olski/subset/ mówią, jakie zdanie się wyprowadza, a warunki niżej odbierają formie czytanie, zanim produkcja je zobaczy, oraz dokładają je tam, gdzie słownik milczy. W jakiej kolejności te warunki idą i czemu w takiej, mówi morphology.

Jeden z nich nie jest przy tym zdaniem podzbioru, tylko zdaniem projektu: olski/słownictwo.py mówi, których lematów projekt nie używa i którym uchyla wykluczenie słownikowe.

Nad gotowym grafem stoją tu jeszcze dwa pytania o niego, bo odpowiada na nie i werdykt, i przebieg nad korpusem: po które czytanie formy nie sięga ani jeden terminal (licencjonowane) oraz na której krawędzi stanęło odrzucenie (na_czym_stanęło). Wejściem obu jest graf, a nie napis, i dlatego stoją tutaj.

Gramatyki moduł ten nie czyta, tylko bierze ją argumentem tych dwóch pytań, więc kto chce samej morfologii, nie buduje gramatyki; pilnuje tego tests/warstwa-leksykalna/test_segmentacja.py.

Warstwę tę wraz z typem, którym oddaje wynik następnej, wylicza docs/architecture.md.

CLOSED_CLASS = frozenset({'prep', 'conj', 'comp', 'qub', 'part', 'pred', 'interj', 'ppron3', 'ppron12'}) module-attribute

EVERY_CASE = frozenset({'nom', 'gen', 'dat', 'acc', 'inst', 'loc', 'voc'}) module-attribute

PRZYIMKOWOŚĆ = 'post_prepositionality' module-attribute

BEZ_PRZYIMKA = 'npraep' module-attribute

ŁĄCZNIK = '-' module-attribute

CZŁON_ZŁOŻENIA = 'adja' module-attribute

ZŁOŻENIE = 'adj' module-attribute

SKRÓT = 'brev' module-attribute

KROPKOWOŚĆ = 'fullstoppedness' module-attribute

Z_KROPKĄ = 'pun' module-attribute

KROPKA = '.' module-attribute

ROZWINIĘCIA = {'ulica': tag('subst:f'), 'tysiąc': tag('subst:m3'), 'procent': tag('subst:m3'), 'tak_zwany': tag('adj'), 'między_innymi': tag('part')} module-attribute

CZŁON = '\\w{2,}' module-attribute

GRONO = f'{CZŁON}(?:[-_]{CZŁON})*' module-attribute

ŚCIEŻKA = f'{GRONO}(?:[./]{GRONO})+' module-attribute

NOTACJA = f'(?<![\\w./])(?=[\\w./_-]*[^\\W\\d_])(?:{ŚCIEŻKA}/?|/{GRONO}|{GRONO}/)' module-attribute

ŁĄCZNIK_ROZDZIELAJĄCY = f'(?<=\\s){ŁĄCZNIK}(?=\\s)' module-attribute

PRZED_MORFEUSZEM = re.compile(f'(?P<notacja>{NOTACJA})|(?P<myślnik>{ŁĄCZNIK_ROZDZIELAJĄCY})') module-attribute

ZNAK_PRZESTANKOWY = tag('interp') module-attribute

NIEODMIENNY = tag('subst:sg.pl:nom.gen.dat.acc.inst.loc.voc:n:ncol') module-attribute

NIEOZNACZONY = tag('subst') module-attribute

CZYTANIA_PRZED_MORFEUSZEM = {'notacja': (None, NIEOZNACZONY), 'myślnik': (ZNAK_MYŚLNIKA, ZNAK_PRZESTANKOWY)} module-attribute

SŁOWO = re.compile('(?=\\w*[^\\W\\d_])\\w+') module-attribute

GRUPA_JEDNYM_SŁOWEM = frozenset({'subst', 'ger', 'ppron12', 'ppron3'}) module-attribute

sentences(text)

Tnie tekst na zdania i oddaje je tak, jak stoją.

Podziału nie ma tutaj, tylko w olski.document: żąda on po kropce białego znaku i zna skróty. Sam olski skrótów nie ma, więc nad nim cięcie na każdej kropce byłoby dokładne. Wejściem jest jednak dokumentacja, gdzie docs/linter.md jest jednym słowem, a cięcie na kropce w jego środku wymyśla dwa zdania, których nikt nie napisał.

Cięcie stoi więc przed analizą, a nie po niej, i z tego samego powodu, z którego stoi tam sklejenie notacji (morphology): Morfeusz jest wołany z SKIP_WHITESPACES, więc po analizie nie ma już czym zobaczyć spacji, która granicę zdania odróżnia od nazwy pliku.

Source code in olski/segmentacja.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def sentences(text: str) -> list[str]:
    """Tnie tekst na zdania i oddaje je tak, jak stoją.

    Podziału nie ma tutaj, tylko w :mod:`olski.document`: żąda on po kropce
    białego znaku i zna skróty. Sam olski skrótów nie ma, więc nad nim
    cięcie na każdej kropce byłoby dokładne. Wejściem jest jednak dokumentacja,
    gdzie ``docs/linter.md`` jest jednym słowem, a cięcie na kropce w jego środku
    wymyśla dwa zdania, których nikt nie napisał.

    Cięcie stoi więc przed analizą, a nie po niej, i z tego samego powodu, z
    którego stoi tam sklejenie notacji (:func:`morphology`): Morfeusz jest
    wołany z ``SKIP_WHITESPACES``, więc po analizie nie ma już czym zobaczyć
    spacji, która granicę zdania odróżnia od nazwy pliku.
    """
    document = Document(text)
    return [document.slice(span) for span in document.sentences]

admissible(segment, słownictwo=SŁOWNICTWO)

Drop the noun reading of a form olski reads as a closed-class word.

Morfeusz reads do as the preposition and as the musical note, and the note inflects for nothing (EVERY_CASE), so every do in a text otherwise hands its sentence a second reading. That is ambiguity in the dictionary rather than in Polish, and no parse can tell the two apart, so the lexicon rules it out instead. docs/subset.md argues the criterion and docs/corpus.md measures what it is worth and what it costs.

Wieloznaczność ta jest jednak wieloznacznością w rejestrze, a nie w słowniku: Go jest grą. polszczyzna ma, a to wykluczenie odbiera jej podmiot. Lemat, o którym projekt mówi, że go używa, zostaje przez to nietknięty (olski/słownictwo.py), a projekt bez takiej deklaracji dostaje kryterium całe, bo o grze nie pisze prawie nikt.

Source code in olski/segmentacja.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def admissible(segment: Segment, słownictwo: Słownictwo = SŁOWNICTWO) -> Segment:
    """Drop the noun reading of a form olski reads as a closed-class word.

    Morfeusz reads ``do`` as the preposition and as the musical note, and the
    note inflects for nothing (:data:`EVERY_CASE`), so every ``do`` in a text
    otherwise hands its sentence a second reading. That is ambiguity in the
    dictionary rather than in Polish, and no parse can tell the two apart, so
    the lexicon rules it out instead. docs/subset.md argues the criterion and
    docs/corpus.md measures what it is worth and what it costs.

    Wieloznaczność ta jest jednak wieloznacznością w rejestrze, a nie w słowniku:
    `Go jest grą.` polszczyzna ma, a to wykluczenie odbiera jej podmiot.
    Lemat, o którym projekt mówi, że go używa, zostaje przez to nietknięty
    (``olski/słownictwo.py``), a projekt bez takiej deklaracji dostaje kryterium
    całe, bo o grze nie pisze prawie nikt.
    """
    if _acronym(segment.form):
        return segment
    if not any(reading.tag.pos in CLOSED_CLASS for reading in segment.readings):
        return segment
    kept = tuple(
        reading
        for reading in segment.readings
        if reading.lemma in słownictwo.wpuszczane
        or not (reading.tag.pos == "subst" and reading.tag.get("case") >= EVERY_CASE)
    )
    if len(kept) == len(segment.readings):
        return segment
    # A closed-class reading is not a noun reading, so the one that spared this
    # segment is itself among the survivors and the tuple is never emptied.
    return replace(segment, readings=kept)

w_słownictwie(segment, słownictwo=SŁOWNICTWO)

Zdejmij czytania lematu, o którym projekt mówi, że go nie używa.

Warunek wyżej pyta o kształt czytania, a ten o sam lemat, i po to jest: soba odmienia się przez przypadki, więc kryterium nieodmienności po nią nie dochodzi, a od zaimka zwrotnego nie odróżnia jej żadne znamię formalne. Kryterium nie ma tu skąd wyjść i rozstrzyga deklaracja (olski/słownictwo.py).

Krawędź wolno temu warunkowi opróżnić i tym różni się on od warunku wyżej, a zgadza z po_przyimku: projekt, który mówi, że słowa nie używa, mówi to także o zdaniu, w którym stoi ono samo, a werdykt nazywa wtedy formę bez licencji (bez_licencji).

Source code in olski/segmentacja.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def w_słownictwie(segment: Segment, słownictwo: Słownictwo = SŁOWNICTWO) -> Segment:
    """Zdejmij czytania lematu, o którym projekt mówi, że go nie używa.

    Warunek wyżej pyta o kształt czytania, a ten o sam lemat, i po to jest:
    `soba` odmienia się przez przypadki, więc kryterium nieodmienności po nią nie
    dochodzi, a od zaimka zwrotnego nie odróżnia jej żadne znamię formalne.
    Kryterium nie ma tu skąd wyjść i rozstrzyga deklaracja (``olski/słownictwo.py``).

    Krawędź wolno temu warunkowi opróżnić i tym różni się on od warunku wyżej,
    a zgadza z :func:`po_przyimku`: projekt, który mówi, że słowa nie używa,
    mówi to także o zdaniu, w którym stoi ono samo, a werdykt nazywa wtedy formę
    bez licencji (:func:`bez_licencji`).
    """
    kept = tuple(
        reading for reading in segment.readings if reading.lemma not in słownictwo.pomijane
    )
    if len(kept) == len(segment.readings):
        return segment
    return replace(segment, readings=kept)

po_przyimku(segments)

Zdejmij formie przyimkowej zaimka czytanie tam, gdzie przyimka nie ma.

Grupa imienna bierze zaimek w każdej swojej pozycji, więc bez tego warunku Cena niego rośnie. się wyprowadza, a nie stoi dopełnieniem w zdaniu, które przeczy. Są to czytania, których polszczyzna nie ma, czyli to samo, co odbiera admissible; dlaczego warunek stoi tutaj, a nie na terminalu zaimka ani za rozbiorem, wywodzi docs/konstrukcje-gramatyczne/grupa-imienna.md#forma-przyimkowa-zaimka-żąda-przyimka-przed-sobą.

Pytany jest graf, a nie lista: licencji udziela każda krawędź z czytaniem przyimkowym, która kończy się w węźle, gdzie ta się zaczyna. Krawędź bez ani jednego czytania z tego wychodzi — niego innych nie ma — i jest wtedy formą bez licencji, którą werdykt wypisuje (bez_licencji).

Licencji udziela przyimek, który ta gramatyka bierze, a nie każda forma z czytaniem przyimkowym, i dlatego wykluczenie stoi tu to samo, co na terminalu (PRZYIMEK_ROZDZIELAJĄCY). Bez niego Cena jest niska, a nie. wyprowadza się: rozdzielające a niesie u Morfeusza czytanie przyimka, więc licencjonuje nie stojące za nim, a wyrażenia przyimkowego z tego a nie ma jak zbudować, czyli licencji udziela pozycja, której nikt nie zajmuje.

Source code in olski/segmentacja.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def po_przyimku(segments: list[Segment]) -> list[Segment]:
    """Zdejmij formie przyimkowej zaimka czytanie tam, gdzie przyimka nie ma.

    Grupa imienna bierze zaimek w każdej swojej pozycji, więc bez tego warunku
    `Cena niego rośnie.` się wyprowadza, a `nie` stoi dopełnieniem w zdaniu,
    które przeczy. Są to czytania, których polszczyzna nie ma, czyli to samo, co
    odbiera :func:`admissible`; dlaczego warunek stoi tutaj, a nie na terminalu
    zaimka ani za rozbiorem, wywodzi
    docs/konstrukcje-gramatyczne/grupa-imienna.md#forma-przyimkowa-zaimka-żąda-przyimka-przed-sobą.

    Pytany jest graf, a nie lista: licencji udziela każda krawędź z czytaniem
    przyimkowym, która kończy się w węźle, gdzie ta się zaczyna. Krawędź bez ani
    jednego czytania z tego wychodzi — `niego` innych nie ma — i jest wtedy formą
    bez licencji, którą werdykt wypisuje (:func:`bez_licencji`).

    Licencji udziela przyimek, który ta gramatyka bierze, a nie każda forma z
    czytaniem przyimkowym, i dlatego wykluczenie stoi tu to samo, co na terminalu
    (:data:`PRZYIMEK_ROZDZIELAJĄCY`). Bez niego `Cena jest niska, a nie.`
    wyprowadza się: rozdzielające `a` niesie u Morfeusza czytanie przyimka, więc
    licencjonuje `nie` stojące za nim, a wyrażenia przyimkowego z tego `a` nie ma
    jak zbudować, czyli licencji udziela pozycja, której nikt nie zajmuje.
    """
    licencjonujące = {
        segment.end
        for segment in segments
        if any(
            reading.tag.pos == "prep" and reading.lemma != PRZYIMEK_ROZDZIELAJĄCY
            for reading in segment.readings
        )
    }
    return [
        segment
        if segment.start in licencjonujące
        else replace(
            segment,
            readings=tuple(
                reading for reading in segment.readings if not _tylko_po_przyimku(reading)
            ),
        )
        for segment in segments
    ]

po_słowie(segments)

Zdejmij cząstce zwrotnej odczytanie tam, gdzie nie stoi przed nią żadne słowo.

Cząstka stoi przy swojej formie osobowej po obu jej stronach (SZYKI_CZĄSTKI w olski/subset/słowa.py), a pozycja przednia sięga początku zdania i miejsca tuż za znakiem: bez tego warunku Się myli. oraz Cena rośnie, się nie liczy. się wyprowadzają, a takich napisów polszczyzna nie ma. Cząstka opiera się bowiem na słowie przed sobą, a znak słowem nie jest. Spójnik nim jest i licencji udziela, bo i przyrasta, i się topi bank drzew pisze.

Pozycja tylna do tych miejsc nie sięga, bo przed nią stoi jej własna forma, więc warunek nie zdejmuje ani jednego odczytania, które olski brał przed wpuszczeniem pozycji przedniej.

Pytany jest graf, a nie lista, i pytanie jest to samo, które stawia po_przyimku: odczytanie zostaje tam, gdzie w węźle otwierającym tę krawędź kończy się krawędź z odczytaniem, które nie jest znakiem.

Warunek stoi w warstwie morfologicznej, a nie na terminalu cząstki, z tego samego powodu, z którego stoi tam tamten: miejsce, którego cząstka nie ma zająć, jest miejscem w zdaniu, a terminal widzi samą formę. docs/konstrukcje-gramatyczne/orzeczenie.md#cząstka-zwrotna-należy-do-swojego-czasownika trzyma, co warunek ten zostawia na zewnątrz.

Source code in olski/segmentacja.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def po_słowie(segments: list[Segment]) -> list[Segment]:
    """Zdejmij cząstce zwrotnej odczytanie tam, gdzie nie stoi przed nią żadne słowo.

    Cząstka stoi przy swojej formie osobowej po obu jej stronach
    (`SZYKI_CZĄSTKI` w ``olski/subset/słowa.py``), a pozycja przednia sięga początku zdania
    i miejsca
    tuż za znakiem: bez tego warunku `Się myli.` oraz `Cena rośnie, się nie
    liczy.` się wyprowadzają, a takich napisów polszczyzna nie ma. Cząstka opiera
    się bowiem na słowie przed sobą, a znak słowem nie jest. Spójnik nim jest i
    licencji udziela, bo `i przyrasta, i się topi` bank drzew pisze.

    Pozycja tylna do tych miejsc nie sięga, bo przed nią stoi jej własna forma,
    więc warunek nie zdejmuje ani jednego odczytania, które olski brał przed
    wpuszczeniem pozycji przedniej.

    Pytany jest graf, a nie lista, i pytanie jest to samo, które stawia
    :func:`po_przyimku`: odczytanie zostaje tam, gdzie w węźle otwierającym tę
    krawędź kończy się krawędź z odczytaniem, które nie jest znakiem.

    Warunek stoi w warstwie morfologicznej, a nie na terminalu cząstki, z tego
    samego powodu, z którego stoi tam tamten: miejsce, którego cząstka nie ma
    zająć, jest miejscem w zdaniu, a terminal widzi samą formę.
    docs/konstrukcje-gramatyczne/orzeczenie.md#cząstka-zwrotna-należy-do-swojego-czasownika
    trzyma, co warunek ten zostawia na zewnątrz.
    """
    licencjonujące = {
        segment.end
        for segment in segments
        if any(reading.tag.pos != "interp" for reading in segment.readings)
    }
    return [
        segment
        if segment.start in licencjonujące
        else replace(
            segment,
            readings=tuple(
                reading for reading in segment.readings if reading.lemma != LEMAT_ZWROTNY
            ),
        )
        for segment in segments
    ]

złożenie(segments)

Sklej złożenie przymiotnikowe pisane łącznikiem w jedną krawędź.

Morfeusz oddaje ewangelicko-reformowanego trzema krawędziami — adja, łącznik i przymiotnik — a polszczyzna ma tam jeden wyraz, który odmienia się członem drugim. Sklejony bierze przez to czytania członu drugiego, a lemat składa z obu, bo lematem tego wyrazu jest ewangelicko-reformowany.

Bez tego sklejenia zdanie ze złożeniem pada z dwóch powodów naraz: po adja nie sięga ani jedna produkcja, a łącznika w środku wyrazu nie bierze terminal myślnika (olski.subset.słowa.MYŚLNIK).

Spacji ten warunek nie widzi i widzieć nie musi, więc stoi za analizą, a nie przed nią jak sklejenie notacji: adja poza złożeniem nie stoi, więc łącznik za nim jest łącznikiem wyrazu, choćby ktoś postawił wokół niego spacje.

Source code in olski/segmentacja.py
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def złożenie(segments: list[Segment]) -> list[Segment]:
    """Sklej złożenie przymiotnikowe pisane łącznikiem w jedną krawędź.

    Morfeusz oddaje `ewangelicko-reformowanego` trzema krawędziami — `adja`,
    łącznik i przymiotnik — a polszczyzna ma tam jeden wyraz, który odmienia się
    członem drugim. Sklejony bierze przez to czytania członu drugiego, a lemat
    składa z obu, bo lematem tego wyrazu jest `ewangelicko-reformowany`.

    Bez tego sklejenia zdanie ze złożeniem pada z dwóch powodów naraz: po `adja`
    nie sięga ani jedna produkcja, a łącznika w środku wyrazu nie bierze terminal
    myślnika (:data:`olski.subset.słowa.MYŚLNIK`).

    Spacji ten warunek nie widzi i widzieć nie musi, więc stoi za analizą, a nie
    przed nią jak sklejenie notacji: `adja` poza złożeniem nie stoi, więc łącznik
    za nim jest łącznikiem wyrazu, choćby ktoś postawił wokół niego spacje.
    """
    scalone: list[Segment] = []
    for segment in segments:
        if len(scalone) > 1 and _złożone(scalone[-2], scalone[-1], segment):
            scalone[-2:] = [_scal(scalone[-2], segment)]
            continue
        scalone.append(segment)
    return scalone

skrót(segments)

Sklej skrót zakończony kropką z tą kropką w jedną krawędź.

Morfeusz czyta ul. dwiema krawędziami — skrótem żądającym kropki i samą kropką — a polszczyzna ma tam jeden wyraz. Rozbitego nie bierze nic: po brev nie sięga żaden terminal poza jednostką przy liczbie pisanej cyfrą, a kropka w środku zdania jest dla gramatyki końcem zdania, więc zdanie ze skrótem staje albo na skrócie, albo na niej.

Kropki kończącej zdanie ten warunek nie tyka, i po to pyta o graf, a nie o samą parę: kropka, za którą nic już nie stoi, jest tą, którą ktoś zdanie zamknął — Ustawa weszła w życie w 2011 r. — a połknięta zabrałaby zdaniu jedyne domknięcie. Podziału na zdania warunek nie rusza, bo tamten poprzedza analizę i skróty zna sam (olski/document.py).

Sklejona krawędź zastępuje krawędź skrótu wraz z jej czytaniami pozostałymi: proc jest u Morfeusza także dopełniaczem mnogim od proca, a kto pisze kropkę po tej formie, pisze skrót.

Source code in olski/segmentacja.py
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
def skrót(segments: list[Segment]) -> list[Segment]:
    """Sklej skrót zakończony kropką z tą kropką w jedną krawędź.

    Morfeusz czyta `ul.` dwiema krawędziami — skrótem żądającym kropki i samą
    kropką — a polszczyzna ma tam jeden wyraz. Rozbitego nie bierze nic: po
    `brev` nie sięga żaden terminal poza jednostką przy liczbie pisanej cyfrą, a
    kropka w środku zdania jest dla gramatyki końcem zdania, więc zdanie ze
    skrótem staje albo na skrócie, albo na niej.

    Kropki kończącej zdanie ten warunek nie tyka, i po to pyta o graf, a nie o
    samą parę: kropka, za którą nic już nie stoi, jest tą, którą ktoś zdanie
    zamknął — `Ustawa weszła w życie w 2011 r.` — a połknięta zabrałaby zdaniu
    jedyne domknięcie. Podziału na zdania warunek nie rusza, bo tamten
    poprzedza analizę i skróty zna sam (``olski/document.py``).

    Sklejona krawędź zastępuje krawędź skrótu wraz z jej czytaniami
    pozostałymi: `proc` jest u Morfeusza także dopełniaczem mnogim od `proca`,
    a kto pisze kropkę po tej formie, pisze skrót.
    """
    ujście = max((segment.end for segment in segments), default=0)
    scalone: list[Segment] = []
    for segment in segments:
        if scalone and segment.end < ujście:
            sklejony = _sklejony(scalone[-1], segment)
            if sklejony is not None:
                scalone[-1] = sklejony
                continue
        scalone.append(segment)
    return scalone

nieznane(segment)

Daj formie, o której słownik milczy, czytanie rzeczownika nieoznaczonego.

README, Robocopy i garbage wracają z Morfeusza jako ign, którego nie bierze ani jedna produkcja, a stoją w zdaniu na miejscu rzeczownika, bo tym w takim zdaniu są. Notacja dostaje to samo czytanie tą samą decyzją (NOTACJA), a różni ją to, że sklejamy ją sami.

Warunek pyta o milczenie słownika i o to, czy napis jest słowem (SŁOWO). Polszczyzny broni pytanie pierwsze: NIE i PAN słownik czyta, więc zdanie z nimi nie traci czytania, które ma. Wywód i cenę trzyma docs/warstwa-leksykalna.md#forma-o-której-słownik-milczy-jest-rzeczownikiem-nieoznaczonym.

Source code in olski/segmentacja.py
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
def nieznane(segment: Segment) -> Segment:
    """Daj formie, o której słownik milczy, czytanie rzeczownika nieoznaczonego.

    ``README``, ``Robocopy`` i ``garbage`` wracają z Morfeusza jako ``ign``,
    którego nie bierze ani jedna produkcja, a stoją w zdaniu na miejscu
    rzeczownika, bo tym w takim zdaniu są. Notacja dostaje to samo czytanie
    tą samą decyzją (:data:`NOTACJA`), a różni ją to, że sklejamy ją sami.

    Warunek pyta o milczenie słownika i o to, czy napis jest słowem
    (:data:`SŁOWO`). Polszczyzny broni pytanie pierwsze: ``NIE`` i ``PAN``
    słownik czyta, więc zdanie z nimi nie traci czytania, które ma. Wywód i cenę
    trzyma
    docs/warstwa-leksykalna.md#forma-o-której-słownik-milczy-jest-rzeczownikiem-nieoznaczonym.
    """
    if segment.known or not SŁOWO.fullmatch(segment.form):
        return segment
    return replace(segment, readings=(Reading(segment.form, segment.form, NIEOZNACZONY),))

przytoczenie(segments)

Daj napisowi objętemu cudzysłowem czytanie nieodmienne, gdy grupą nie jest.

Napis przytoczony — „B”, „nie” — nie odmienia się, więc produkcja przepuszczająca przypadek grupy nie ma na nim czego przepuszczać, a rzeczownik nieodmienny spełnia każde żądanie przypadku, bo żadnego nie nosi. Wywód, cenę i granicę warunku trzyma docs/subset.md w sekcji o interpunkcji obejmującej.

Licencji udziela cudzysłów po obu stronach, więc pytany jest graf, a nie sama forma, tak samo jak przy formie przyimkowej (po_przyimku). Warunek na oba znaki naraz żąda przy tym, żeby napis wypełniał wnętrze sam. Czytania są zamienione, a nie dołożone, bo napisu przytoczonego to zdanie nie używa jako słowa.

Source code in olski/segmentacja.py
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def przytoczenie(segments: list[Segment]) -> list[Segment]:
    """Daj napisowi objętemu cudzysłowem czytanie nieodmienne, gdy grupą nie jest.

    Napis przytoczony — `„B”`, `„nie”` — nie odmienia się, więc produkcja
    przepuszczająca przypadek grupy nie ma na nim czego przepuszczać, a rzeczownik
    nieodmienny spełnia każde żądanie przypadku, bo żadnego nie nosi. Wywód, cenę
    i granicę warunku trzyma docs/subset.md w sekcji o interpunkcji obejmującej.

    Licencji udziela cudzysłów po obu stronach, więc pytany jest graf, a nie sama
    forma, tak samo jak przy formie przyimkowej (:func:`po_przyimku`). Warunek na
    oba znaki naraz żąda przy tym, żeby napis wypełniał wnętrze sam. Czytania są
    zamienione, a nie dołożone, bo napisu przytoczonego to zdanie nie używa jako
    słowa.
    """
    otwarte = {
        segment.end
        for segment in segments
        if any(reading.lemma == ZNAK_CUDZYSŁOWU_OTWIERAJĄCY for reading in segment.readings)
    }
    zamknięte = {
        segment.start
        for segment in segments
        if any(reading.lemma == ZNAK_CUDZYSŁOWU_ZAMYKAJĄCY for reading in segment.readings)
    }
    return [
        replace(segment, readings=(Reading(segment.form, segment.form, NIEODMIENNY),))
        if _przytoczony(segment, otwarte, zamknięte)
        else segment
        for segment in segments
    ]

morphology(text, słownictwo=SŁOWNICTWO)

Analizuje tekst tak, jak czyta go olski.

Kilka rzeczy dzieje się tu przed gramatyką. Notacja rejestru dostaje jedną krawędź z jednym czytaniem, bo Morfeusz rozbija docs/linter.md na pięć krawędzi, a czytelnik ma tam jedno słowo. Słowo, którego słownik nie ma, dostaje czytania z leksykonu projektu (olski.projekt), bo commitów jest dopełniaczem liczby mnogiej i nikt nie ma tam czytania nieoznaczonego. Forma, o której słownik milczy i po tamtym wpisie, dostaje czytanie rzeczownika nieoznaczonego (nieznane). Reszta idzie do Morfeusza i traci te czytania, które odrzuca admissible, po nich te, których lematu projekt nie używa (w_słownictwie), a po nich te, które po_przyimku odrzuca formie stojącej bez przyimka oraz po_słowie cząstce zwrotnej stojącej bez słowa przed sobą, a na końcu napis objęty cudzysłowem dostaje czytanie nieodmienne przytoczenia (przytoczenie).

Pięć z tych warunków pyta o sąsiada, a nie o samą formę, więc idą po liście gotowej, a nie po jednym segmencie jak te przed nimi. Dwa sklejenia idą z nich pierwsze, bo reszta ma zastać graf gotowy: złożenie przymiotnikowe (złożenie) i skrót zakończony kropką (skrót). Przytoczenie idzie ostatnie, bo pyta o czytania, które zostały: be traci rzeczownik w admissible i przytoczenie zastaje tam sam przymiotnik.

Słownictwo projektu wchodzi tu argumentem, a nie stałą czytaną w dwóch warunkach, bo bez tego nie da się przeczytać jednego zdania dwoma deklaracjami, a takiego pytania żąda i suita, i każdy, kto tę deklarację wycenia (olski/słownictwo.py).

Sklejenie notacji stoi przed analizą, a nie za nią, i tam samo stoi łącznik wzięty za myślnik. Segment niesie numery węzłów grafu, a nie przesunięcia w tekście, więc po analizie nie ma już czym zobaczyć spacji, która ukośnik w ścieżce odróżnia od ukośnika między dwoma słowami, a łącznik w wyrazie od myślnika. Złożenie przymiotnikowe skleja się za to za analizą, bo pyta o czytania, a spacji nie potrzebuje (złożenie).

Source code in olski/segmentacja.py
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
def morphology(text: str, słownictwo: Słownictwo = SŁOWNICTWO) -> list[Segment]:
    """Analizuje tekst tak, jak czyta go olski.

    Kilka rzeczy dzieje się tu przed gramatyką. Notacja rejestru dostaje jedną
    krawędź z jednym czytaniem, bo Morfeusz rozbija ``docs/linter.md`` na pięć
    krawędzi, a czytelnik ma tam jedno słowo. Słowo, którego słownik nie ma,
    dostaje czytania z leksykonu projektu (:mod:`olski.projekt`), bo ``commitów``
    jest dopełniaczem liczby mnogiej i nikt nie ma tam czytania nieoznaczonego.
    Forma, o której słownik milczy i po tamtym wpisie, dostaje czytanie
    rzeczownika nieoznaczonego (:func:`nieznane`). Reszta idzie do Morfeusza i traci te czytania,
    które odrzuca :func:`admissible`, po nich te, których lematu projekt nie używa
    (:func:`w_słownictwie`), a po nich te, które :func:`po_przyimku`
    odrzuca formie stojącej bez przyimka oraz :func:`po_słowie` cząstce zwrotnej
    stojącej bez słowa przed sobą, a na końcu napis objęty cudzysłowem dostaje
    czytanie nieodmienne przytoczenia (:func:`przytoczenie`).

    Pięć z tych warunków pyta o sąsiada, a nie o samą formę, więc idą po liście
    gotowej, a nie po jednym segmencie jak te przed nimi. Dwa sklejenia idą z nich
    pierwsze, bo reszta ma zastać graf gotowy: złożenie przymiotnikowe
    (:func:`złożenie`) i skrót zakończony kropką (:func:`skrót`). Przytoczenie
    idzie ostatnie, bo pyta o czytania, które zostały: ``be`` traci rzeczownik w
    :func:`admissible` i przytoczenie zastaje tam sam przymiotnik.

    Słownictwo projektu wchodzi tu argumentem, a nie stałą czytaną w dwóch
    warunkach, bo bez tego nie da się przeczytać jednego zdania dwoma
    deklaracjami, a takiego pytania żąda i suita, i każdy, kto tę deklarację
    wycenia (``olski/słownictwo.py``).

    Sklejenie notacji stoi przed analizą, a nie za nią, i tam samo stoi łącznik
    wzięty za myślnik. Segment niesie numery węzłów grafu, a nie przesunięcia w
    tekście, więc po analizie nie ma już czym zobaczyć spacji, która ukośnik w
    ścieżce odróżnia od ukośnika między dwoma słowami, a łącznik w wyrazie od
    myślnika. Złożenie przymiotnikowe skleja się za to za analizą, bo pyta o
    czytania, a spacji nie potrzebuje (:func:`złożenie`).
    """
    return przytoczenie(
        po_słowie(
            po_przyimku([
                w_słownictwie(
                    admissible(nieznane(projekt.z_leksykonu(segment)), słownictwo), słownictwo
                )
                for segment in skrót(złożenie(_segmenty(text)))
            ])
        )
    )

licencjonowane(segment, grammar)

Czytania formy, po które sięga choć jeden terminal tej gramatyki.

Pytają o to dwie odpowiedzi o jednym kryterium: werdykt wypisuje formę, której nie zostaje ani jedno (bez_licencji), a przebieg nad korpusem nazywa część mowy tego, które zostało (bloker w olski/pokrycie.py). Kryterium wyprowadza z gramatyki olski.grammar.Grammar.licencjonuje, a ta funkcja jest samym jego zastosowaniem do czytań formy.

Source code in olski/segmentacja.py
622
623
624
625
626
627
628
629
630
631
632
633
634
635
def licencjonowane(segment: Segment, grammar: Grammar) -> tuple[Reading, ...]:
    """Czytania formy, po które sięga choć jeden terminal tej gramatyki.

    Pytają o to dwie odpowiedzi o jednym kryterium: werdykt wypisuje formę, której
    nie zostaje ani jedno (:func:`bez_licencji`), a przebieg nad korpusem nazywa
    część mowy tego, które zostało (``bloker`` w ``olski/pokrycie.py``).
    Kryterium wyprowadza z gramatyki :meth:`olski.grammar.Grammar.licencjonuje`,
    a ta funkcja jest samym jego zastosowaniem do czytań formy.
    """
    return tuple(
        reading
        for reading in segment.readings
        if grammar.licencjonuje(reading.tag.pos, reading.lemma, segment.lematy, reading.tag.cechy)
    )

bez_licencji(segments, grammar)

Formy nie do ominięcia, którym gramatyka nie bierze ani jednego czytania.

Odrzucenie ma dwie przyczyny i są to dwie różne roboty do zrobienia: forma, po którą nie sięga żadna produkcja, i struktura, której gramatyka nie licencjonuje. Świgra trzyma je osobno (docs/swigra.md), a tę pierwszą widać przed rozbiorem i widać ją wyprowadzoną z gramatyki (olski.grammar.Grammar.licencjonuje wywodzi, czemu wolno).

Liczy się przy tym krawędź, bez której nie ma drogi przez zdanie, a nie każda pusta dziedzina: podział, który Morfeusz dokłada obok formy całej, nie jest słowem, które ktokolwiek napisał. Wywód i to, co ten warunek daje za darmo, trzyma docs/design-notes.md.

Forma stoi na liście raz, choćby w zdaniu powtórzyła się kilka razy, bo odpowiedzią jest to, czego gramatyka nie bierze, a nie ile razy autor to napisał.

Source code in olski/segmentacja.py
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
def bez_licencji(segments: list[Segment], grammar: Grammar) -> tuple[str, ...]:
    """Formy nie do ominięcia, którym gramatyka nie bierze ani jednego czytania.

    Odrzucenie ma dwie przyczyny i są to dwie różne roboty do zrobienia: forma,
    po którą nie sięga żadna produkcja, i struktura, której gramatyka nie
    licencjonuje. Świgra trzyma je osobno (docs/swigra.md), a tę pierwszą widać
    przed rozbiorem i widać ją wyprowadzoną z gramatyki
    (:meth:`olski.grammar.Grammar.licencjonuje` wywodzi, czemu wolno).

    Liczy się przy tym krawędź, bez której nie ma drogi przez zdanie, a nie
    każda pusta dziedzina: podział, który Morfeusz dokłada obok formy całej, nie
    jest słowem, które ktokolwiek napisał. Wywód i to, co ten warunek daje za
    darmo, trzyma docs/design-notes.md.

    Forma stoi na liście raz, choćby w zdaniu powtórzyła się kilka razy, bo
    odpowiedzią jest to, czego gramatyka nie bierze, a nie ile razy autor to
    napisał.
    """
    formy: list[str] = []
    for segment in segments:
        if licencjonowane(segment, grammar):
            continue
        if _omijalna(segments, segment) or segment.form in formy:
            continue
        formy.append(segment.form)
    return tuple(formy)

na_czym_stanęło(segments, furthest)

Krawędź, na której odrzucenie stanęło; None, gdy stanęło na końcu zdania.

Ostatniego znaku zdania nie nazywa, bo zdanie, które bierze każdą swoją formę i nie domyka się, jest drugim zdarzeniem i dostaje drugie zdanie werdyktu (Verdict.explain) oraz drugi wiersz przebiegu nad korpusem (NO_STRUCTURE w olski/pokrycie.py).

Krawędź, a nie forma, bo pytają o nią dwie odpowiedzi: werdykt bierze stąd formę, a ranking blokerów część mowy jej czytania, i kryterium jest jedno.

Z jednego węzła grafu wychodzi czasem kilka form, bo ktoś wychodzi także jako kto i ś. Nazwana jest najdłuższa, czyli ta, którą autor napisał, a krótsza jest jej częścią.

Nazwane miejsce jest końcem przedrostka, który się analizuje, i nie jest wskazaniem usterki; wywód i cenę trzyma docs/subset.md#odrzucenie-mówi-dokąd-analiza-doszła-a-nie-gdzie-stoi-usterka.

Source code in olski/segmentacja.py
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
def na_czym_stanęło(segments: list[Segment], furthest: int) -> Segment | None:
    """Krawędź, na której odrzucenie stanęło; ``None``, gdy stanęło na końcu zdania.

    Ostatniego znaku zdania nie nazywa, bo zdanie, które bierze każdą swoją
    formę i nie domyka się, jest drugim zdarzeniem i dostaje drugie zdanie
    werdyktu (``Verdict.explain``) oraz drugi wiersz przebiegu nad korpusem
    (``NO_STRUCTURE`` w ``olski/pokrycie.py``).

    Krawędź, a nie forma, bo pytają o nią dwie odpowiedzi: werdykt bierze stąd
    formę, a ranking blokerów część mowy jej czytania, i kryterium jest jedno.

    Z jednego węzła grafu wychodzi czasem kilka form, bo ``ktoś`` wychodzi
    także jako ``kto`` i ``ś``. Nazwana jest najdłuższa, czyli ta, którą autor
    napisał, a krótsza jest jej częścią.

    Nazwane miejsce jest końcem przedrostka, który się analizuje, i nie jest
    wskazaniem usterki; wywód i cenę trzyma
    docs/subset.md#odrzucenie-mówi-dokąd-analiza-doszła-a-nie-gdzie-stoi-usterka.
    """
    ujście = max((segment.end for segment in segments), default=furthest)
    stojące = [
        segment for segment in segments if segment.start == furthest and segment.end < ujście
    ]
    if not stojące:
        return None
    return max(stojące, key=lambda segment: segment.end)