Reguły dopasowania¶
Reguły tagowania znane z JOSKIPI działają względem ustalonej zewnętrznie bieżącej pozycji w zdaniu (dopiero ciąg reguł tagowania wykonuje iterację za pomocą bieżącej pozycji i odpala dla każdej pozycji ciąg reguł). Ten mechanizm został zachowany w WCCL-u, lecz jest on niewygodny do zapisu reguł anotacji.
Dlatego opracowano też mechanizm alternatywny: każda reguła samodzielnie wykonuje iterację i odpowiada za podjęcie odpowiednich akcji w całym zdaniu. Mechanizm ten jest podobny do mechanizmu reguł zaimplementowanego w parserze Spejd. Określamy go tutaj jako reguły dopasowania.
Reguły dopasowania mogą korzystać z wyrażeń podstawowego języka wyrażeń funkcyjnych WCCL, co daje dużą siłę ekspresji oraz jawną kontrolę wieloznaczności (dostępne są operacje na zbiorach).
Składnia reguł dopasowania¶
Jedna reguła dopasowania definiowana jest za pomocą słowa kluczowego apply
(składnia poniżej).
Ciąg reguł dopasowania to wyrażenie o następującej składni:
match_rules( APPLY1; APPLY2; ..., APPLYn )
Nowy typ danych: dopasowanie¶
Podjęzyk dopasowań wprowadza nowy typ danych, zwany dopasowaniem, będący odwołaniem do fragmentu zdania. Dopasowanie jest tworzone stopniowo, po czym na gotowym dopasowaniu aplikowana jest akcja (oznakowanie go jako anotacja bądź usunięcie poprzedniej anotacji).
Dopasowanie to nadklasa; mamy 3 typy:- dopasowanie pojedynczego tokenu
TOK[idx]
(idx
to liczba całkowita nieujemna wskazująca indeks w tablicy tokenów; nie jest to pozycja, lecz jedynie indeks bezwzględny, pod którym znajduje się token), - dopasowanie anotacji
ANN[idx, name]
(idx
to indeks pierwszego tokenu anotacji (ciągłego kawałka frazy),name
to napis—nazwa kanału), - wektor dopasowań
MATCH(…)
.
Jak widać, dopasowanie może zawierać wektory (dowolny poziom zagnieżdżenia). Przykładowo: MATCH(TOK[2], TOK[3], MATCH(TOK[4], TOK[5]))
.
Do zapisu zmiennej typu dopasowanie używana jest składnia $m:Var
(analogicznie do pozostałych typów, prefiks m:
). W praktyce cały język korzysta z jednej zmiennej typu dopasowanie o nazwie $m:_M
. Zmienna ta nie powinna być używana wprost, lecz za pomocą wygodnej składni skróconej (patrz niżej).
Operator match i operatory z niego korzystające¶
Podstawowym elementem podjęzyka dopasowań jest operator match
. Operator ten nie jest używany samodzielnie, stanowi on część operatora apply
.
match(conditions)
match
próbuje dopasować podane w nim warunki (conditions
) do kolejnych tokenów. Operator iteruje po kolejnych tokenach, zwiększając bieżącą pozycję w zdaniu. Iteracja wykonywana jest w ramach realizacji kolejnych kroków dopasowań. Dzięki temu, operatory sprawdzające kolejne warunki dopasowania mogą się odwołać do sprawdzanego właśnie tokenu za pomocą pozycji 0. Operator korzysta też ze zmiennej o nazwie $m:_M
, gdzie trzymany jest wektor dopasowań.
- match zawsze zaczyna od bieżącej pozycji w zdaniu; za jej zerowanie odpowiedzialny jest operator korzystający z
match
(przy normalnym użyciu jest nimapply
) - match wykonuje dwie akcje: z każdym dopasowanym tokenem zwiększa wartość bieżącej pozycji w zdaniu oraz gromadzi kolejne elementy wektora (wskazujące dopasowania kolejnych kawałków reguły) — wektor ten zostanie zwrócony w przypadku powodzenia
- warunki (elementy listy
conditions
) rozpatrywane są sekwencyjnie; każdy spełniony warunek owocuje dodaniem do wektora dopasowań jednego elementu - jeśli któryś z warunków nie jest spełniony, dopasowanie jest przerywane, a bieżąca pozycja w zdaniu ustawiana jest na wartość początkową + 1; zwracany jest wtedy „martwy” obiekt match (działający obiekt match uzyskamy tylko, gdy wszystkie warunki będą spełnione — wtedy bieżąca pozycja ustawiona będzie na pierwszym tokenie za dopasowaniem)
Warunki operatora match¶
Warunki operatora match
to osobna klasa operatorów. Warunek taki odpowiada fragmentowi dopasowania. Każdy warunek zwraca dwie wartości: (1) True
/False
oraz (2) element typu dopasowanie.
Operator korzystający z danego warunku decyduje, co zrobić ze zwróconym przez warunek dopasowaniem — np. w przypadku True
dodać zwrócony element do własnego wektora dopasowań.
- opakowanie na predykaty (wyr. funkcyjne zwracające wart. logiczne); zwraca
True
/False
zgodnie z tym, co zwrócił dany predykat; wtedy operator może zwrócićTOK(pos)
— wskazanie na sprawdzaną pozycję; - operator zwracający
True
/False
orazANN[pos, name]
- operator zwracający
True
/False
oraz wektor dopasowań (może być pusty, nie musi to znaczyć, że się nie powiodło)
Pierwszy typ warunków to opakowane predykaty WCCL — wtedy dopasowywany jest pojedynczy token i zwracana jego pozycja opakowana w dopasowanie. Warunki mogą być także specjalnymi operatorami określającymi dopasowania. Każdy taki operator w zależności od typu, może zwracać opakowanie na anotację lub wektor dopasowań.
repeat
(zwraca wektor wektorów)
- choć w jego składni nie oczekujemy słowa
match
, działa, jakby przyjmował zawsze operator match jako argument (warunki tego matcha zawarte są luzem w repeat) - tworzy pusty wektor dopasowań, po czym szukamy kolejnych powtórzeń matcha
- z każdego spełnionego matcha brany jest wynik (wektor) i wstawiany do wektora zewnętrznego
- jeśli ani razu nie uda się odpalić, zwracana jest wartość
False
oraz pusty wektor - w razie sukcesu zwraca więc wektor (listę powtórzeń) wektorów (odpowiadających kolejnym krokom dopasowań)
Poniżej przedstawiamy przykład w pseudokodzie, nie jest to poprawne wyrażenie WCCL, bo operator MATCH nie jest samodzielny, a przykład jest skrócony.
$m:Vec = match( <A>, repeat( <B, C> ))) $m:Vec:1 = MATCH ( A ) $m:Vec:2 = MATCH ( MATCH (B, C), MATCH(B,C) )
optional
- analogicznie do repeat, ale dopasuje się raz lub wcale
- zawsze zwraca
True
- zwraca wektor dopasowań, który ma jeden element lub jest pusty (jeśli jednoelementowy, element jest wektorem)
text
- operator przejmuje kontrolę nad iteracją, poczynając od obecnie przetwarzanego tokenu aż do skończenia podanego tekstu, bądź porażki (działanie podobne do repeat)
- dokleja kolejne tokeny uwzględniając informację o spacji między tokenami i z każdym tokenem sprawdza, czy się nie pokrywa ze stringiem przekazanym jako argument operatora oraz czy nie zaszedł już za daleko (może zdarzyć się, że podany tekst mógłby się dopasować jedynie gdyby zmienić podział na tokeny — w takich sytuacjach przerywamy próbę gdy tylko wykryjemy, że już dalej dopasować się nie da)
- jeśli przekazany string nie chce się dopasować (napotkamy niezgodność, bądź zdanie się urwie), cofamy bieżącą pozycję do wartości pocz. + 1 i zwracamy False
- operator zwraca wektor zawierający kolejne tokeny
is
- podobnie jak
text
, operator przejmuje kontrolę nad iteracją — tym razem aż do końca frazy bądź porażki - operator dopasowuje się do anotacji o podanej nazwie,
- musi być ustawiony na początku tej anotacji, by ją rozpoznać
- zwraca dopasowanie na anotację, bądź „martwy” match, gdy się nie uda
oneof(variant(v1), variant(v2), … )
- operator sprawdza kolejno warianty będące de facto dopasowaniami;
- pierwsze udane dopasowanie przerywa działanie i zwracany jest jego wynik;
- jeśli się nie uda dopasować, to zwracane to, co zwróciłby nieudany
match
longest(variant(v1), variant(v2), … )
- operator działa podobnie do
oneof
, - różnica: wszystkie dopasowania są wykonywane i wybierane jest dopasowanie najdłuższe (w tokenach)
Operacje na dopasowaniach¶
Definiujemy operator wyłuskania :
, który pozwala na wyłuskanie n-tego elementu wektora (numerujemy od 1).
Składnia skrócona. Wszystkie operatory podjęzyka dopasowań, które oczekują elementu typu dopasowanie, mogą przyjąć składnię skróconą: M
oznacza cały wektor dopasowania. Przykładowo, M:1
oznacza pierwszy element dopasowania, natomiast M:1:2
oznacza drugi element pierwszego elementu całego dopasowania. Zapis M:1
można skrócić do samego :1
(analogicznie poprawne są zapisy :1:2
, :1:2:2:1
). W każdej sytuacji, gdy operacji nie da się wykonać, działanie jest nieokreślone (implementacja powinna zgłosić błąd).
Predykat empty(match)
zwraca True
, jeśli przekazane dopasowanie jest pustym wektorem lub wektorem składającym się z samych wektorów pustych.
Dwa pomocnicze operatory: first(match)
i first(match)
. Operatory te biorą dowolne dopasowanie i zwracają pozycję wskazującą na pierwszy i ostatni (odpowiednio) token należący do dopasowania. Jeśli dopasowaniem jest wektor, to przy ustalaniu pozycji pomijamy puste wektory w nim zawarte. Jeśli empty(match)
, to first(match)
i last(match)
zwrócą nowhere
.
Dodatkowo, operator debug
zdefiniowano również dla typu dopasowanie (zawsze zwraca True). Ułatwia on diagnozę reguł dopasowania, w szczególności wygodnie jest go wstawić jako post-condition (patrz niżej).
Użycie dopasowań: apply¶
Samodzielna reguła dopasowania tworzona jest przez operator apply
. Iteruje on po całym zdaniu i uruchamia match
. Dla każdego dopasowania sprawdzane są najpierw warunki (o ile zostały podane jakiekolwiek), po czym uruchamiane są akcje (w przypadku, gdy warunki spełnione). Akcje (oraz warunki) odwołują się ze zgromadzonego wektora dopasowań. Wektor ten wstawiany jest pod zmienną $m:_M
(dostępny tam wektor opakowywany jest w jeszcze jeden wektor, by ułatwić w przyszłości implementację operatorów, które znakują relacje między dwoma ciągami dopasowań; składnia skrócona M
abstrahuje od tego i zwraca wektor nieopakowany).
Dopuszczalna składnia (dwa warianty):
apply( match( dopasowania ), actions( akcje ) )
apply( match( dopasowania ), cond( warunki ), actions( akcje ) )Gdzie:
- dopasowania to lista operatorów dopasowujących elementy w zdaniu (is, equal, text, itd.),
- warunki to lista operatorów logicznych, od których spełnienia uwarunkowane jest uruchomienie akcji (np. equal, ann, annsub),
- akcje to lista akcji (mark, unmark).
Token obecnie dopasowywany zawsze znajduje się pod pozycją 0 — mogą z tego korzystać elementy operatora match
.
- Bieżąca pozycja jest ustawiana na zero. Z tego miejsca zaczynamy iterację zewnętrzną.
- Dla każdej pozycji bieżącej wykonujemy następującą operację:
- zapamiętujemy wartość bieżącej pozycji w zdaniu
- jednorazowo uruchamiamy
MATCH
, sprawdzamyCONDITIONS
i jeśli spełnione, wstawiamyMATCH(
wynik dopasowania)
(tj. wektor jednoelementowy) w zmienną$m:_M
, po czym kolejno odpalamy akcje
- Jeśli się udało dopasować, wartość bieżącej pozycji pozostaje ustawiona na pierwszym tokenie za dopasowaniem (to działanie zapewnione jest przez operator
match
) - Jeśli się nie udało, przywracamy wartość równą zapamiętanej wartości początkowej + 1 (czyli w wyniku nieudanej próby dopasowania pomijamy tylko jeden token)
Ustawienie wartości zmiennej $m:_M
to zabieg pozwalający na późniejsze odwołania do wektora. Wynik matcha opakowujemy w wektor jednoelementowy, ponieważ istnieje też drugi wariant, który zakłada obecność dwóch matchów — a w przyszłości być może będziemy chcieli mieć ich więcej.
Dodatkowe warunki operatora apply (post-conditions)¶
Operator apply
opcjonalnie przyjmuje listę warunków. Warunek taki jest specjalnym typem operatora.
- opakowany predykat podjęzyka wyrażeń funkcyjnych WCCL (składnia identyczna z oryginalną składnią predykatów, np.
inter(base[0], ["tak"])
)
- operator
ann(m1, name)
bądźann(m1, m2, name)
— sprawdza czy między granice dopasowania (dopasowań) pokrywają się z granicami frazy o nazwiename
(wystarczy, by pokrywały się z ciągłym kawawłkiem nieciągłej frazy) - operator
annsub(m1, name)
bądźannsub(m1, m2, name)
— sprawdza czy między granicami dopasowania (dopasowań) rozciąga się kawałek frazy (podciąg) podanego typu (technicznie, cały ten zakres musi być wypełniony jednym niezerowym numerkiem)
Oba z nich zakładają, że przedział musi być niepusty.
Uwaga: operatory ann
i annsub
mogą korzystać ze skrótów składniowych dla dopasowań (np. :2
).
Akcje¶
Operator mark
znakuje anotację o podanej nazwie.
mark(match_from, match_to, annotation_name)
— anotacja rozciąga się od tokenu first(match_from)
do last(match_to)
włącznie, gdzie rmatch_from
i match_to
są dopasowaniami (jednym z 3 podtypów)mark(match_from, match_to, match_head, annotation_name)
— j.w., lecz nadrzędnik zostanie umieszczony na pierwszym token dopasowania match_head
mark(match, annotation_name)
— od tokenu first(match)
do last(match)
włącznie, gdzie match
jest dopasowaniem
Jeśli nie podamy pozycji nadrzędnika, zostanie on umieszczony na pierwszym tokenie match_from.
mark(match_from, match_to, match_head, annotation_name)
- trzeba zrobić sztuczkę — sprawdzić czy na podanych popzycjach nie ma pustych wektorów
- jeśli są, idziemy albo do przodu, albo do tyłu (odpowiednio)
Uwaga: jeśli przez podany zakres w podanym kanale przechodzi jakakolwiek anotacja, działanie przerywane jest wyjątkiem.
Operator unmark
usuwa anotację o podanej nazwie. Jeśli w podanym miejscu nie ma anotacji, działanie przerywane jest wyjątkiem.
unmark(match, annotation_name)
Operator remark
to wariant operatora mark
, który najpierw usuwa wszystkie anotacje nachodzące na podany przedział, po czym znakuje nową anotację w tym miejscu. Anotacje usuwane są kompletnie, tj. jeśli choć jeden token pokrywa się z podanym przedziałem, to wszystkie fragmenty danej anotacje zostaną usunięte (choćby nawet była nieciągła).
remark(match_from, match_to, annotation_name)
remark(match_from, match_to, match_head, annotation_name)
remark(match, annotation_name)
Operator setprop(match, key_name, val_name)
ustawia przypisuje pierwszemu tokenowi należącemu do dopasowania match
własność (metadaną) o kluczu key_name
i wartości val_name
(klucz i wartość są napisami).