View on Github

akrantiain仕様書 — akrantiain変換部の仕様

トップに戻る

4. akrantiain変換部の仕様

4-(-1). 用語の定義

「AとBの対」とは、A1つとB1つを共に要素として持つデータ構造である。

「AとBの対」のAのことをfstと呼び、Bのことをsndと呼ぶ。
「空白文字」とは、HaskellのisSpace関数に与えるとTrueを返す任意の文字である。
「未定義値」とは、失敗・未設定などを表すための特殊な値である。例えば、Cならヌルポインタ、JavaScriptならundefined、HaskellならNothingで表現できる。

4-0. 概略

変換部は、「環境情報(環境指定識別子一覧と句読点情報からなる)」「変換規則のリスト」「入力文字列」を受けとり、「実行時エラー」または「出力文字列」を返す。

なお、環境情報は大局的な定数であり、変換部の処理を行う任意のタイミングで参照されうる。
2017/03/22 20:49現在、akrantiain変換部は以下のような流れになっている。
  1. 環境指定識別子USE_NFDが指定されていれば、変換規則の入力部・変換規則の出力部・入力文字列をUnicode正規化形式のNFDで分解する。
  2. CASE_SENSITIVEありCASE_SENSITIVEなし
    PRESERVE_CASEありsensitivityをtrueにする。sensitivityをfalseにする。
    PRESERVE_CASEなしsensitivityをfalseにし、
    変換規則の入力部と入力文字列を小文字に変換する
  3. 入力文字列の先頭と末尾にスペースを付加し、1文字ずつ分割し(1文字と未定義値の対)のリストとしてStatに変換する
  4. 「変換規則のリスト」を左から走査していき、Statに順に変換規則を適用していく
  5. Statのうち未処理のものがある場合、変換元がスペースまたは句読点なら変換先にスペースを指定する。
    そうでないとき、環境指定識別子FALL_THROUGHが指定されていなければ実行時エラー#210を吐き、指定されていれば変換先に変換元をそのまま代入する。
  6. 変換先を全てつなげ、先頭と末尾にあるスペースをそれぞれ1個だけ削除したものを、環境指定識別子USE_NFDが指定されている場合はさらにUnicode正規化形式のNFCで合成し、出力文字列とする。
2.では、例えば入力文字列が"spaghetti"であれば、次のようなStatへと変換される。
" ""s""p""a""g""h""e""t""t""i"" "
nullnullnullnullnullnullnullnullnullnullnull
Statと変換規則の仕様は後述。

4-1. Stat

『文字列と文字列の対』で、対のsndが未定義値であり得るものを、『Stat素』と呼ぶ。

Statは、「『Stat素』のリスト」である。例えば次のようなものである。
"s""p""a""gh""e""t""t""i"
null"p"null"g"nullnull"t"null
fstをつなげてできる文字列(この例では"spaghetti")を、Statのconcatと呼ぶ。

4-2. 変換規則

変換規則は、以下の5要素からなる。PHONEMEの定義については2章を参照のこと。

Negationは「文字列の集合または語境界」を要素として持ち、それらが登場しない時に限り変換を行うことを表す。
  1. 左条件(省略可能)
  2. 左$リスト
  3. 変換元と変換先の対応
  4. 右$リスト
  5. 右条件(省略可能)

4-3. 変換規則の適用

変換規則適用は、「Stat」と「変換規則」を受け取り、「Stat」を返す。

2017/03/22 20:49現在、変換規則の適用は以下のような流れで行われている。
  1. 受け取ったStatと変換規則をmatchに渡し、「『Statの対』のリスト」hogeを得る。
  2. hogeが空なら最初のStatを変更せずに返し、さもなくばリストhogeの最終要素のfstをa、sndをbと名付け続行。
  3. 「aと変換規則をmatchに渡し返ってきたStat」の後ろにbを結合し、返す。

4-4. match関数の仕様

match関数は、「Stat」と「変換規則」を受け取り、「『Statの対』のリスト」を返す。
  1. 「Statを二分したもの」の一覧を用意する。
  2. 変換規則を右から、つまり「右条件→右$リスト→『変換元と変換先の対応』→左$リスト→左条件」の順に辿っていき、一覧の中のうちパターンにマッチするもののみを残しながら、変換先を書き込んでいく
というのが大まかな流れであった。

しかし、いわゆるaimezバグの解決を図るため、少々処理が変更され、次のような流れとなった。
  1. 「Statを二分したもの」の一覧のうち、「右条件→右$リスト」の条件を既に満たしているものを用意する。
  2. 『変換元と変換先の対応』を右から辿っていき、一覧の中のうちパターンにマッチするもののみを残しながら、変換先を書き込んでいく。
  3. 残ったもののうち、「左$リスト→左条件」の条件を満たすもののみを残す。
以下、辿っていくときに行われることを具体的に説明していく。

4-4-1. 候補の用意と右条件・右$リストの処理

これについては「百聞は一見に如かず」なので、図を用いる。
入力:
"ch""a""t""eau"
/ʃ/nullnullnull
出力:
(空リスト,
"ch""a""t""eau"
/ʃ/nullnullnull
)
(
"ch"
/ʃ/
,
"a""t""eau"
nullnullnull
)
(
"ch""a"
/ʃ/null
,
"t""eau"
nullnull
)
(
"ch""a""t"
/ʃ/nullnull
,
"eau"
null
)
(
"ch""a""t""eau"
/ʃ/nullnullnull
,空リスト )
この中で、右$リストfと右条件gという2つの制約を(結果的に)既にくぐり抜けているもののみが次の段階に渡される。

これはどういう風に判定されるかというと、「sndのconcatが、以下の2条件を共に満たす2つの文字列AとBの結合であるかどうか」として判定される。
右条件が存在しない場合は、右条件として恒真関数を用いればよい。


具体例を見てみよう。まず、右$リストfが空リストであり、右条件gが「"an", "en", "in", "on", "un"のどれでもない」という条件であるとき、例えば、以下のStat対は通過する。
(空リスト,
"w""a""n""t"
nullnullnullnull
)
(
"w""a"
nullnull
,
"n""t"
nullnull
)
(
"w""a""n"
nullnullnull
,
"t"
null
)
(
"w""a""n""t"
nullnullnullnull
,空リスト )
しかし、例えば、以下のStat対は通過しない。
(
"w"
null
,
"a""n""t"
nullnullnull
)
なぜなら、このStat対のsndのconcatである"ant"の接頭辞の一つ"an"は、条件「"an", "en", "in", "on", "un"のどれでもない」を満たさないからである。
右$リストfが空リストであり、かつ右条件が「語境界の否定」という場合についても見てみよう。

この時、例えば、以下のStat対は通過する。
(空リスト,
"a"" ""c""a""t"
nullnullnullnullnull
)
(
"a"" "
nullnull
,
"c""a""t"
nullnullnull
)
(
"a"" ""c"
nullnullnull
,
"a""t"
nullnull
)
(
"a"" ""c""a"
nullnullnullnull
,
"t"
null
)
(
"a"" ""c""a""t"
nullnullnullnullnull
,空リスト )
しかし、例えば、以下のStat対は通過しない。
(
"a"
null
,
" ""c""a""t"
nullnullnullnull
)
なぜなら、このStat対のsndのconcatである" cat"の接頭辞の一つ" "は、条件「スペースまたは句読点のみで構成される文字列ではない」を満たさないからである。
右$リストfが空リストでない場合についても見てみよう。

右$リストfが「集合{"a", "e", "i", "o", "u", "y"}のみからなるリストと$の対」、右条件gが不存在(=恒真関数)である場合、
(
"ai" "m"
"ɛ"null
,
"ez"
"e"
)
は通過する。なぜなら、sndのconcatは"ez"であるが、これは「右$リスト中の各要素からうまく文字列を選んで結合させると作れる"e"」と「恒真関数を満たす文字列"z"」の結合とみなせるからである。

一方で、右$リストfが「集合{"a", "e", "i", "o", "u", "y"}のみからなるリストと$の対」、右条件gが「"z"でない」である場合、
(
"ai" "m"
"ɛ"null
,
"ez"
"e"
)
は通過しない。なぜなら、sndのconcatである"ez"をどこで分割しても、

「fからうまく選んで作れる文字列」と「接頭辞が"z"と等しくなることがない、言い換えれば、"z"で始まらない、文字列」に分かれることがないからである。

4-4-2. 「変換元と変換先の対応」の処理

「『語境界』または『文字列の集合とPHONEMEの対』のリスト」を右から辿っていき、条件を満たすもののみを通過させながら、Stat内の適切な『変換先』のところに情報を記入していく。
4-4-2-1. 『語境界』のとき
環境情報を使用する。各Stat対について、以下のような変更を行う。
  1. Stat対のfstを構成する全てのStat素から、それぞれfstをとって作られるリストを考え、このリストから空文字列を全て除く。
    こうしてできたリストが空リストでなく、かつそのリストの最終要素に空白文字でも句読点でもない文字があるならば、そもそも通過させない。
  2. fstの後ろから、「『変換元』部分が全て空白文字か句読点である」を満たすStat素を削り落とし、sndの先頭にくっつける。
具体例を見てみよう。,が句読点であるとき、以下のStat対
(
"a" " " "b" "," " "
nullnullnullnullnull
,
"c""d""e"
nullnullnull
)
の「Stat対のfstを構成する全てのStat素から、それぞれfstをとって作られるリスト」は次の青枠で示した部分であり、
(
"a" " " "b" "," " "
nullnullnullnullnull
,
"c""d""e"
nullnullnull
)
今回の場合は「このリストから空文字列を全て除いたリスト」はこのリストそのものである。

このリストの最終要素は空白文字からのみ構成されるので、このStat対は通過し、次のようなStat対となる。
(
"a" " " "b"
nullnullnull
,
"," " " "c""d""e"
nullnullnullnullnull
)
fstの「後ろから」削っていくので、abの間にある空白は手が加わらないことに注意。
別の具体例を見てみよう。,が句読点であるとき、以下のStat対
(
"a" " " "b" "c" ""
nullnullnullnull"s"
,
"c""d""e"
nullnullnull
)
の「Stat対のfstを構成する全てのStat素から、それぞれfstをとって作られるリスト」は次の青枠で示した部分であり、
(
"a" " " "b" "c" ""
nullnullnullnull"s"
,
"c""d""e"
nullnullnull
)
今回の場合、「このリストから空文字列を全て除いたリスト」は
"a" " " "b" "c"
である。
このリストの最終要素は空白文字以外の文字を含むので、今回はStat対は通過しない。
4-4-2-2.「『文字列の集合とPHONEMEの対』のリスト」のとき
まず、「変換関数」を定義する。変換関数は、「PHONEME」「パターン文字列」「Stat対」を受け取り、「Stat対」を返したり返さなかったりする関数である。(「Stat対または未定義値」を返す、と言ってもよい)

「Stat対のfstの後ろからk個のStat素を削り落とすと、その削り落としたところのconcatがパターン文字列と一致する」となるようなkが存在しないなら未定義値を返す。 存在するならば
具体例を見てみよう。

まず、PHONEME$である場合。パターン文字列が"a"、Stat対が次のようなものであるときを考える。
(
"c" "a" "n" "a"
nullnullnullnull
,
"l"
null
)
この時、Stat対のfstの後ろから1個のStat素を削り落とすと、その削り落としたところのconcatは"a"であり、パターン文字列と一致する。

ゆえに、変換関数は以下のStat対を返す。
(
"c" "a" "n"
nullnullnull
,
"a" "l"
nullnull
)
次に、PHONEMEが文字列である場合。PHONEME/ɛ/、パターン文字列が"ai"であるときを考える。

Stat対が次のようなものであるとき、
(
"a" "i"
nullnull
,
"m" "ez"
null"e"
)
Stat対のfstの後ろから2個のStat素を削り落とすと、その削り落としたところのconcatは"ai"であり、パターン文字列と一致する。

さらに、その2個のStat素は共に「sndが未定義値である」を満たす。
ゆえに、変換関数は以下のStat対を返す。
(空リスト,
"ai""m""ez"
"ɛ"null"e"
)
一方、Stat対が次のようなものであるとき、
(
"h" "a""i"
null"a""i"
,
"̈" "r"
""null
)
(補足:左から4つ目のStat素はCOMBINING DIAERESIS (U+0308)である。)

Stat対のfstの後ろから2個のStat素を削り落とすと、その削り落としたところのconcatは"ai"であり、パターン文字列と一致するが、
2個のStat素のsndは未定義値ではないので、変換関数は未定義値を返す。
さて、変換関数が定義できたので、これを用い 「変換元と変換先の対応」が「『文字列の集合とPHONEMEの対』のリスト」のときに行う操作について説明する。
  1. 空のリストansを用意する。
  2. 二重ループを回す。外側のループは「Stat対のリスト」についてのループで、内側のループが「与えられた『文字列の集合』」についてのループである。
    ちなみに、1週間分ぐらいの進捗が溶けた悪名高き「linepurineバグ」は、端的に言えばこのループの順番を間違えたことによるバグである。
  3. 最後に、ansを返す。
4-4-2-2.は、あくまで「『Stat対を通過させる』という操作を繰り返す」という見方で説明した4-4-1.や4-4-2-1.や4-4-3.と違い、

「Stat対のリスト」から二重ループを経て別の「Stat対のリスト」を返すという見方で説明していることに留意せよ。

4-4-3. 左$リストと左条件の処理

左条件gと左$リストfという2つの制約を「これから」くぐり抜けると考慮できるもののみが最終的に通過する。

これはどういう風に判定されるかというと、「fstのconcatが、以下の2条件を共に満たす2つの文字列BとAの結合であるかどうか」として判定される。
具体例を見てみよう。左条件gが「"an", "en", "in", "on", "un"のどれでもない」という条件であり、左$リストが空リストであるとき、例えば、以下のStat対は通過する。
(空リスト,
"w""a""n""t"
nullnullnullnull
)
(
"w"
null
,
"a""n""t"
nullnullnull
)
(
"w""a"
nullnull
,
"n""t"
nullnull
)
(
"w""a""n""t"
nullnullnullnull
,空リスト )
しかし、例えば、以下のStat対は通過しない。
(
"w""a""n"
nullnullnull
,
"t"
null
)
なぜなら、このStat対のfstのconcatである"wan"の接尾辞の一つ"an"は、条件「"an", "en", "in", "on", "un"のどれでもない」を満たさないからである。
条件が「語境界の否定」という場合についても見てみよう。この時も左$リストが空リストであるとする。

この時、例えば、以下のStat対は通過する。
(空リスト,
"a"" ""c""a""t"
nullnullnullnullnull
)
(
"a"
null
,
" ""c""a""t"
nullnullnullnull
)
(
"a"" ""c"
nullnullnull
,
"a""t"
nullnull
)
(
"a"" ""c""a"
nullnullnullnull
,
"t"
null
)
(
"a"" ""c""a""t"
nullnullnullnullnull
,空リスト )
しかし、例えば、以下のStat対は通過しない。
(
"a"" "
nullnull
,
"c""a""t"
nullnullnull
)
なぜなら、このStat対のfstのconcatである"a "の接尾辞の一つ" "は、条件「スペースまたは句読点のみで構成される文字列ではない」を満たさないからである。
左$リストが空リストでない時についても見てみよう。 

左条件gが不存在(=恒真関数)、左$リストfが「集合{"a", "e", "i", "o", "u", "y"}のみからなるリストと$の対」である場合、
(
"ze"
"e"
,
"m" "ia"
null"ɛ"
)
は通過する。なぜなら、fstのconcatは"ze"であるが、これは「恒真関数を満たす文字列"z"」と「左$リスト中の各要素からうまく文字列を選んで結合させると作れる"e"」との結合とみなせるからである。

一方で、左条件gが「"z"でない」、左$リストfが「集合{"a", "e", "i", "o", "u", "y"}のみからなるリストと$の対」である場合、
(
"ze"
"e"
,
"m" "ia"
null"ɛ"
)
は通過しない。なぜなら、fstのconcatである"ze"をどこで分割しても、

「接尾辞が"z"と等しくなることがない、言い換えれば、"z"で終わらない、文字列」と「fからうまく選んで作れる文字列」に分かれることがないからである。