1 of 251

Ruby を余すところなく愛する�人のための�「オートマトンと形式言語理論」入門

hsjoihs (はすじょい)�[佐藤 弘崇]

2 of 251

Ruby を

3 of 251

Ruby を

余すところなく

4 of 251

愛する

Ruby を

余すところなく

5 of 251

6 of 251

愛する

7 of 251

8 of 251

愛する

9 of 251

このような Ruby の仕様に驚く他言語ユーザーは多い

10 of 251

このような Ruby の仕様に驚く他言語ユーザーは多い

11 of 251

このような Ruby の仕様に驚く他言語ユーザーは多い

かくいう私も�小学生の頃に�[1,3,4,5].size - 1 が通って�[1,3,4,5].size -1 が通らないことを知り�かなり驚いたのを未だに覚えている

12 of 251

ということで、

13 of 251

ということで、

なぜ Ruby は�こんな驚きをもたらす�仕様を採用したのか

14 of 251

という問いを……

ということで、

なぜ Ruby は�こんな驚きをもたらす�仕様を採用したのか

15 of 251

という問いを……

ということで、

なぜ Ruby は�こんな驚きをもたらす�仕様を採用したのか

果たして立てるべきなのか?

16 of 251

という問いを……

ということで、

なぜ Ruby は�こんな驚きをもたらす�仕様を採用したのか

果たして立てるべきなのか?

ホンマか?

17 of 251

むしろ、立てるべき問いは逆向きではないか?

18 of 251

むしろ、立てるべき問いは逆向きではないか?

19 of 251

むしろ、立てるべき問いは逆向きではないか?

「なぜ多くの他言語ユーザーは、↓ を見て驚きを抱いてしまうのか」�のほうこそ深掘りしがいがあるのではないか?

20 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

21 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

22 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

23 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

24 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

25 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�多くの他言語ユーザーが(無意識に)有している固定観念�

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

26 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�多くの/他言語/ユーザーが/(/無意識に/)/有している/固定観念�

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

27 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�多くの/他言語/ユーザーが/(/無意識に/)/有している/固定観念�逆に、それに反するような交ぜ書き(「まん延」「ひっ迫」など)は嫌われがち

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

28 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�多くの/他言語/ユーザーが/(/無意識に/)/有している/固定観念�逆に、それに反するような交ぜ書き(「まん延」「ひっ迫」など)は嫌われがち

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

29 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�多くの/他言語/ユーザーが/(/無意識に/)/有している/固定観念�逆に、それに反するような交ぜ書き(「まん延」「ひっ迫」など)は嫌われがち

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

30 of 251

多くの他言語ユーザーが(無意識に)有している固定観念

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

cf. 日本語は『他字種から平仮名へ移る』以外の字種境界で似たことをやっている�多くの/他言語/ユーザーが/(/無意識に/)/有している/固定観念�逆に、それに反するような交ぜ書き(「まん延」「ひっ迫」など)は嫌われがち

[

1

,

3

,

4

,

5

]

.

s

i

z

e

-

1

括弧

英数

雑多

英数

雑多

英数

雑多

英数

括弧

雑多

英数

英数

英数

英数

雑多

英数

Ruby は単に�この流儀を採用しなかった�というだけの話

31 of 251

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

32 of 251

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

→ のような �simple なルールに従って�プログラミング言語を作れば、�たとえばフォーマッタなどが�作りやすくはなる��

33 of 251

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

→ のような �simple なルールに従って�プログラミング言語を作れば、�たとえばフォーマッタなどが�作りやすくはなる��ルールが simple だと、�そのルールをもとに reason する(根拠を持って理詰めで推論・判断する)ことがやりやすいから

34 of 251

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

→ のような �simple なルールに従って�プログラミング言語を作れば、�たとえばフォーマッタなどが�作りやすくはなる��ルールが simple だと、�そのルールをもとに reason する(根拠を持って理詰めで推論・判断する)ことがやりやすいから

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

35 of 251

simple vs. complex は必ずしも good vs. bad ではない

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

→ のような �simple なルールに従って�プログラミング言語を作れば、�たとえばフォーマッタなどが�作りやすくはなる��ルールが simple だと、�そのルールをもとに reason する(根拠を持って理詰めで推論・判断する)ことがやりやすいから

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

36 of 251

simple vs. complex は必ずしも good vs. bad ではない

right vs. wrong

字種が切り替わる境界のところに� 空白を足しても� 意味が変化することはない」

→ のような �simple なルールに従って�プログラミング言語を作れば、�たとえばフォーマッタなどが�作りやすくはなる��ルールが simple だと、�そのルールをもとに reason する(根拠を持って理詰めで推論・判断する)ことがやりやすいから

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

37 of 251

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

38 of 251

…というかこの話は皆さんにとって釈迦に説法でしたね

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

39 of 251

…というかこの話は皆さんにとって釈迦に説法でしたね

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

40 of 251

…というかこの話は皆さんにとって釈迦に説法でしたね

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

41 of 251

…というかこの話は皆さんにとって釈迦に説法でしたね

ただ、この simple なルールを採用しないというのも、�別に wrong と決めつけるべきでは全くない

42 of 251

先にオチを:なぜ私はこの発表をしているか

43 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、� 

44 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� 

45 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」

46 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる

47 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる
  • Ruby を余すところなく愛する皆さんには理論武装をしてもらいたい

48 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる
  • Ruby を余すところなく愛する皆さんには理論武装をしてもらいたい
  • そのための武器供与をしたい

49 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる
  • Ruby を余すところなく愛する皆さんには理論武装をしてもらいたい
  • そのための武器供与をしたい
  • 構文論が simple な世界のみに慣れきった世の他言語ユーザーに比べて�

50 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる
  • Ruby を余すところなく愛する皆さんには理論武装をしてもらいたい
  • そのための武器供与をしたい
  • 構文論が simple な世界のみに慣れきった世の他言語ユーザーに比べて�より幅広く自由な視座で構文に接してきた皆さんに対して、�

51 of 251

先にオチを:なぜ私はこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる
  • Ruby を余すところなく愛する皆さんには理論武装をしてもらいたい
  • そのための武器供与をしたい
  • 構文論が simple な世界のみに慣れきった世の他言語ユーザーに比べて�より幅広く自由な視座で構文に接してきた皆さんに対して、�掘り下げのためのシャベルをお配りしたい

52 of 251

先にオチを:なぜはこの発表をしているか

  • 世の中には、�「Ruby の設計がそれほど simple ではなく reason しづらいことを以て、� Ruby の設計のことを wrong や bad であると断じる人」がいる
  • Ruby を余すところなく愛する皆さんには理論武装をしてもらいたい
  • そのための武器供与をしたい
  • 構文論が simple な世界のみに慣れきった世の他言語ユーザーに比べて�より幅広く自由な視座で構文に接してきた皆さんに対して、�掘り下げのためのシャベルをお配りしたい

53 of 251

自己紹介

54 of 251

自己紹介:hsjoihs(はすじょい)

@hsjoihs

hsjoihs

@sosoBOTpi

sozysozbot

パソコンカタカタなど

主に言語

55 of 251

自己紹介:hsjoihs(はすじょい)

実名:佐藤 弘崇 (さとう ひろたか)

経歴など:�・国際言語学オリンピック 2014/2015/2016 日本代表�・Stanford 卒【数学(学士)・物理学(学士)・応用工学物理学(修士)】�・セキュリティ・キャンプ 2022/'23/'24/'25 『Cコンパイラゼミ』講師

@hsjoihs

hsjoihs

@sosoBOTpi

sozysozbot

パソコンカタカタなど

主に言語

56 of 251

自己紹介:hsjoihs(はすじょい)

実名:佐藤 弘崇 (さとう ひろたか)

経歴など:�・国際言語学オリンピック 2014/2015/2016 日本代表�・Stanford 卒【数学(学士)・物理学(学士)・応用工学物理学(修士)】�・セキュリティ・キャンプ 2022/'23/'24/'25 『Cコンパイラゼミ』講師

所属:株式会社ドワンゴ/ZEN 大学の教員

@hsjoihs

hsjoihs

@sosoBOTpi

sozysozbot

パソコンカタカタなど

主に言語

57 of 251

<ZEN-univ>

58 of 251

「横断的な学び」を掲げ2025年に開学したオンライン大学

59 of 251

60 of 251

シラバスの科目概要

この科目では、オートマトンをはじめとする「計算機のモデル」のパラダイムと、「言語」という概念を数学的に定式化した「形式言語」という枠組みの 2 つをテーマとして取り上げ、一見距離のあるこれらの 2 つの間の密接な関係と、この 2 つが織りなす「計算」という現象に肉薄するとともに、具体的なプログラミング言語や計算機の実装方法や規格書などを垣間見ることで、普段はとりたてて詳しく追いかける機会の少ない「計算」「計算機」「プログラミング言語」といったものを掘り下げて「脱・神秘化」(demystify) していく。

さらに、正則言語や文脈自由言語などの言語クラスが持つソフトウェアエンジニアリング領域へのきわめて重大な応用を学ぶことで、実践的技術と理論的直感の両方を培う。

61 of 251

シラバスの科目概要

この科目では、オートマトンをはじめとする「計算機のモデル」のパラダイムと、「言語」という概念を数学的に定式化した「形式言語」という枠組みの 2 つをテーマとして取り上げ、一見距離のあるこれらの 2 つの間の密接な関係と、この 2 つが織りなす「計算」という現象に肉薄するとともに、具体的なプログラミング言語や計算機の実装方法や規格書などを垣間見ることで、普段はとりたてて詳しく追いかける機会の少ない「計算」「計算機」「プログラミング言語」といったものを掘り下げて「脱・神秘化」(demystify) していく。

さらに、正則言語や文脈自由言語などの言語クラスが持つソフトウェアエンジニアリング領域へのきわめて重大な応用を学ぶことで、実践的技術と理論的直感の両方を培う。

62 of 251

シラバスの科目概要

この科目では、オートマトンをはじめとする「計算機のモデル」のパラダイムと、「言語」という概念を数学的に定式化した「形式言語」という枠組みの 2 つをテーマとして取り上げ、一見距離のあるこれらの 2 つの間の密接な関係と、この 2 つが織りなす「計算」という現象に肉薄するとともに、具体的なプログラミング言語や計算機の実装方法や規格書などを垣間見ることで、普段はとりたてて詳しく追いかける機会の少ない「計算」「計算機」「プログラミング言語」といったものを掘り下げて「脱・神秘化」(demystify) していく。

さらに、正則言語や文脈自由言語などの言語クラスが持つソフトウェアエンジニアリング領域へのきわめて重大な応用を学ぶことで、実践的技術と理論的直感の両方を培う。

闇を照らす

63 of 251

「正則言語 L₁ と L₂ の� 共通部分 L₁ L₂ も

 正則言語」

↓典型的教材

Introduction to Automata Theory, Languages, and Computation (Hopcroft & Ullman) より引用

64 of 251

「正則言語 L₁ と L₂ の� 共通部分 L₁ L₂ も

 正則言語」

この教材→

↓典型的教材

Introduction to Automata Theory, Languages, and Computation (Hopcroft & Ullman) より引用

65 of 251

「正則表現では�補集合を求めるのは大変だが �DFA だと自明」

↓典型的教材

Introduction to Automata Theory, Languages, and Computation (Hopcroft & Ullman) より引用

66 of 251

「正則表現では�補集合を求めるのは大変だが �DFA だと自明」

この教材→

↓典型的教材

Introduction to Automata Theory, Languages, and Computation (Hopcroft & Ullman) より引用

67 of 251

2023 年 8 月に書き始め、2026 年 5 月についに完成

  • 想定をはるかに上回る良さで授業を書き上げることができた(自画自賛)
  • ZEN 大学にご入学いただけると来年度これを受講することができます

68 of 251

</ZEN-univ>

69 of 251

授業内で Ruby コミュニティに 2 回言及しました

70 of 251

授業内で Ruby コミュニティに 2 回言及しました

  • そのおかげで教材がかなりきれいにまとまりました��

71 of 251

授業内で Ruby コミュニティに 2 回言及しました

  • そのおかげで教材がかなりきれいにまとまりました
  • 一種の「恩返し」みたいなノリで、���

72 of 251

授業内で Ruby コミュニティに 2 回言及しました

  • そのおかげで教材がかなりきれいにまとまりました
  • 一種の「恩返し」みたいなノリで、�その 2 回の言及の内容をお話しして�Ruby コミュニティへの還元としたいと思った��

73 of 251

てなわけで

74 of 251

発表内容①正規表現エンジンと Pike VM

75 of 251

話す必要のある前提が多い!

76 of 251

話す必要のある前提が多い!

  • 本来これは 8 時間ぐらいかけて説明する内容

77 of 251

話す必要のある前提が多い!

  • 本来これは 8 時間ぐらいかけて説明する内容
  • 皆さんは無限大の理解力を有しているので、

78 of 251

話す必要のある前提が多い!

  • 本来これは 8 時間ぐらいかけて説明する内容
  • 皆さんは無限大の理解力を有しているので、�24 倍速で流し込んでいきます

79 of 251

話す必要のある前提が多い!

  • 本来これは 8 時間ぐらいかけて説明する内容
  • 皆さんは無限大の理解力を有しているので、�24 倍速で流し込んでいきます
  • すると 20 分で収まり、happy

80 of 251

第一章�すごろくと�正則表現の�密接な関係

81 of 251

①:すごろく

82 of 251

①:すごろく

マス目がいくつかあり、マス目が矢印で結ばれている。�スタートに駒を置き、入ってくる文字に応じて矢印をたどって駒を進めていく。

ゴールにぴったりたどり着けるだろうか

83 of 251

すごろくの具体例

二重丸 = ゴール(文字列を全部読んだときに、ここにいたら勝ち)

"" "1" "00" "10101" → true

"0" "10" "01" "1101" → false

84 of 251

「すごろく」は文字列を二値分類する

"" "1" "00" "10101"true

"0" "10" "01" "1101"false

85 of 251

②:すごろくの表現力

86 of 251

能力の限界

有限種類の文字・有限個のマス目での「すごろく」では�あまりややこしいロジックを実装することができない�(有名例:「カッコの開きと閉じが対応しているか?」の判定機は実装不可)

87 of 251

何であれば表現できるか

重要定理:� 「すごろくで表現できるロジック」は、

 「選択連接繰り返しという基本三演算で構築できるロジック」と等しい

88 of 251

例:先ほどのを選択連接繰り返しという基本三演算で書く

"" "1" "00" "10101"true

"0" "10" "01" "1101"false

89 of 251

例:先ほどのを選択連接繰り返しという基本三演算で書く

"" "1" "00" "10101"true

"0" "10" "01" "1101"false

『1または「0の直後に1の繰り返し】の直後に0」』の繰り返し

90 of 251

③:正表現

91 of 251

表現

選択連接繰り返しという基本三演算で構築したロジック」を書き表す手段�� ↓ のような構造は (A|BC)* と書き表す

92 of 251

演算子とその優先順位

選択|と書く。優先順位は最弱。

連接表記しない。「A の後に B、その後に C」を単に ABC と書きたいから。

繰り返し*と書く。優先順位は最強。

93 of 251

具体例

A または「B の後に C」:A|BC

「A または B」の後に C:(A|B)C

「A または B」が何度でも:(A|B)*

A または 「Bが何度でも」:A|B*

94 of 251

具体例その2

『1または「0の直後に1の繰り返し】の直後に0」』の繰り返し

95 of 251

具体例その2

『1または「0の直後に1の繰り返し】の直後に0」』の繰り返し

(1|01*0)*

96 of 251

④:regex�(いわゆる『正表現』)

97 of 251

VS Code とかに入れるとハイライトされて便利~

98 of 251

表現と似ているが、

選択連接繰り返しという基本三演算」のみならず、�ユーザーの利便性のためにあんな機能こんな機能をバンバン実装していがち

\p{sc=Hiragana}

文字体系が平仮名である

{4,}

4 回以上の繰り返しである

99 of 251

表現と似ているが、

truefalse か」の二値分類のみならず、�パターンに合致する部分文字列とその位置を報告

100 of 251

私はこういう流儀で話します(混乱を最小化したいので)

あえて曖昧にさせたいときには両方を「正規表現」と呼びます

パターン文字列を与えると、そのパターンに合致する部分文字列とその位置を報告するライブラリ

選択連接繰り返しという基本三演算で構築した二値分類ロジック」を書き表す手段

101 of 251

なんなら Larry Wall も名称を分ける流儀を採用している

[...] what we call "regular expressions", which are only marginally related to real regular expressions. Nevertheless, the term has grown with the capabilities of our pattern matching engines [...] I will, however, generally call them "regexes"��翻訳: 「regular expression」と呼ばれる、本物の regular expression(数学的概念としての正則表現)とはほとんど関係がない代物 [...]。とはいえ、私たちのパターンマッチエンジンで実現できることが広がっていくとともに、この用語(の示す範囲)も広がってきたので、 [...] ただ、私は普通は(こういったパターンマッチエンジンのことは)「regexes」(単数形 regex)と呼ぶことにします

https://www.perl.com/pub/2002/06/04/apo5.html/ より引用。話の本筋に関係のない箇所を意図的に削った。

102 of 251

第二章�歪めて汚す

103 of 251

再掲

パターン文字列を与えると、そのパターンに合致する部分文字列とその位置を報告するライブラリ

選択連接繰り返しという基本三演算で構築した二値分類ロジック」を書き表す手段

104 of 251

この「部分文字列」が曲者

二値分類「全体が、true か? false か?」�だけ考えていればよかったときとは�根本的に話が変わってくる

しかしながら regex エンジンの有用性のかなりの部分は�「部分文字列」を locate するというところにある�(特に、文字列置換のタスクにおいては)��よって、無視するわけにはいかない

105 of 251

理論サイドの話をすると:

数学的には、最も綺麗で自然なのは

「[start, end) を抜き出したときに、それが二値分類で true となるするもの」

全て返す、とすること��具体例:正則表現「かた*」で文字列「かたたたき」内を検索すると、�「か」「かた」「かたた」「かたたた」の 4 パターンが重なってハイライト

106 of 251

しかしながら

「重なってハイライト」という仕様だと、�現実の人々にとって嬉しくない(特に、文字列置換のタスクにおいては。)

よって、数学的に最も綺麗で自然なこれを、�どうにかして歪めて汚す必要がある��(私の意見としては、正則表現と regex の最大の差はここにある)

107 of 251

現実の処理系で試してみよう

  • 「Kernel」を /Ker|Kernel/ で検索
  • 「Kernel」を /Kernel|Ker/ で検索
  • 「トマトマト」を /トマト/ で検索

VS Code では、どこがハイライトされる?

108 of 251

答え

  • Kernel」: /Ker|Kernel/
  • Kernel」: /Kernel|Ker/
  • トマトマト」: /トマト/

とハイライトされる

109 of 251

「重なり合わないマッチ」を左から探すという前提

110 of 251

結論

regex を使って VS Code とかがハイライトをするときというのは、

  • | には「まず左選択肢、ダメなら右選択肢」という優先順位がある
  • ハイライト範囲が重なり合わないようにする
  • 「重なり合わない」という条件のもと、最左マッチが返る

となっている��(注:特に | については Perl およびそれ以降の regex が共有している特徴。�   ゆえに awk などにおいては成り立たない)

111 of 251

「貪欲マッチ」とかが欲しくなるのも、

「重なってハイライト」を歪めたいから

  • 「かたたたき」を /かた*/ で検索
  • 「かたたたき」を /かた*?/ で検索

VS Code では、どこがハイライトされる?

112 of 251

答え

  • かたたたき」を /かた*/ で検索
  • たたたき」を /かた*?/ で検索

とハイライトされる

113 of 251

第三章�そろそろ本題

114 of 251

まず、「truefalse かしかない、易しい世界」の話を

基本三演算でできた再帰的な構造なのだから、�それぞれの演算を「すごろく」で実装して、再帰的に翻訳していけばよい

選択連接繰り返しという基本三演算で構築した二値分類ロジック」を書き表す手段

115 of 251

具体例:(|a*b) はこのような「すごろく」になる

「ε」は空文字列の意味。文字を消費せずに進むことを許す。�水面に落としたインクが広がっていくかのように、�すごろくの駒が「平等に」「分身して」広がっていく

116 of 251

具体例:(|a*b) はこのような「すごろく」になる

「ε」は空文字列の意味。文字を消費せずに進むことを許す。�水面に落としたインクが広がっていくかのように、�すごろくの駒が「平等に」「分身して」広がっていく

117 of 251

具体例:(|a*b) はこのような「すごろく」になる

「ε」は空文字列の意味。文字を消費せずに進むことを許す。�水面に落としたインクが広がっていくかのように、�すごろくの駒が「平等に」「分身して」広がっていく

数学的に綺麗・自然な�「全部探して、重なってハイライト」

118 of 251

便利な定理:

右の 2 種のマスで�正則表現を�翻訳しきることが�できる

119 of 251

思い出すシリーズ:

  • Kernel」: /Ker|Kernel/
  • Kernel」: /Kernel|Ker/
  • トマトマト」: /トマト/
  • かたたたき」:/かた*/
  • たたたき」: /かた*?/

120 of 251

「分身」ではなく「不平等な選択肢」

すごろくの上を「平等に分身」するのではなく、�盤上に駒はただひとつであり

  • |は「候補が第一候補、候補が第二候補」
  • *?は「ループ脱出が第一候補、残留が第二候補
  • *は「ループ残留が第一候補、脱出が第二候補」

を      にする

121 of 251

「駒はただひとつ」

  • この「駒」を「プログラムカウンタ」と呼ぼう
  • すごろくの「マス目&そこから出る矢印」を「VM 命令」と呼ぼう
    • eat 命令:
      • 期待する文字が来たら、消費して goto する
      • そうでなければ、例外を投げる
    • jump 命令:
      • 行き先リストを左から順に試す
      • 例外が返ってきたら次の候補を試す
      • 候補が尽きたら例外を投げる
    • goal 命令:
      • ゴールに辿り着いた幸せを噛みしめる

0: jump -> [1]

1: eat "a" -> 2

2: jump -> [3]

3: jump -> [4, 6]

4: eat "b" -> 5

5: jump -> [4, 6]

6: goal

122 of 251

空文字列には要注意

/()*/ のような「空文字列ループ」があると、当然 VM がハング

文字を一切 eat せずに 1 回ループして同じ行番号に戻ってきてしまうから���「eat していないのに同じ場所に戻ってきた場合に対処」するには、�「eat するまでの間に通過した行番号一覧」を覚えておく

これで治る

123 of 251

ちなみに:空文字列は沼

文字列 a の中をパターン /|a/ で検索すると、

  • PCRE と PCRE2:マッチを 3 つ報告するらしい
  • JS, Python, Golang, Java 8, C#, Rust :マッチを 2 つ報告するらしい

regex101.com 調べ)

124 of 251

VM のうれしさ:拡張させやすい

  • /./が欲しければ、「eat_any 命令」として実装すればよい
  • キャプチャグループが欲しければ、
    • eat 命令が食った文字」を録画開始する「capture_start 命令」
    • 録画終了する「capture_end 命令」

125 of 251

VM の悲しさ:素朴にやると実行時間が指数関数的爆発

/(ab|ab)*d/ みたいなパターンを喰わせると、文字列 abababc に対して

「『左・左・左』ならいけるんじゃないだろうか」

「『左・左・右』ならいけるんじゃないだろうか」

「『左・右・左』ならいけるんじゃないだろうか」��となってしまい、実行時間が指数関数的に爆発する��catastrophic backtracking

126 of 251

対処法:「消費した文字数」を深さとする幅優先探索

説明のため、「1 日に文字が 1 文字ずつやってくる」と喩える

  • eat 命令が成功する場合、goto した盤面を「明日のやることリスト」に追加
  • eat 命令が失敗する場合、盤面を抹消
  • jump 命令は、行き先リストをそのまま「今日のやることリスト」に追加

ただし、「同じ日付の『やることリスト』に同じ盤面は複数回登録しない」��これで治る(Pike VM などと呼ばれる)

Rust の regex クレートは(他の戦略でできない regex は) Pike VM にフォールバックするらしい�Regex engine internals as a library https://burntsushi.net/regex-internals/ ← おすすめ記事�there exists codepoints in both \w and \s which start with the same leading UTF-8 code units. とかおもろい

127 of 251

Ruby 3.2 で、たいていの regex に対して �catastrophic backtracking が解消した

ただし、以上の話は、「正則表現に不平等な選択肢を入れ「eat 命令とjump 命令」�としたからこそできた話��キャプチャグループの内容にマッチする \1 の類は、�選択連接繰り返しという基本三演算で構築した正則表現 で再現することが�不可能であることが数学的に知られている。��よって Ruby 3.2 でも、regex に \1 の類が入っているとこの解消法が適用されない�

128 of 251

皆さんも�regex エンジンを�自作しましょう

(まだ作っていないなら)

129 of 251

発表内容②あらゆる BNF/文脈自由文法に対処できる、�覚えやすくて理解しやすい最高のパーサー

130 of 251

してますか?

皆さん

パーサー

131 of 251

パーサー大好き!

132 of 251

パーサー大好き!

  • 三度の飯よりパーサー!

133 of 251

パーサー大好き!

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!

134 of 251

パーサー大好き!

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

135 of 251

パーサー大好き!

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

という人もいれば、

136 of 251

パーサー大好き!

正直パーサー苦手……

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

という人もいれば、

137 of 251

パーサー大好き!

正直パーサー苦手……

  • 毎回ライブラリに任せてる…

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

という人もいれば、

138 of 251

パーサー大好き!

正直パーサー苦手……

  • 毎回ライブラリに任せてる…
  • 書いてみたけどバグらせた…

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

という人もいれば、

139 of 251

パーサー大好き!

正直パーサー苦手……

  • 毎回ライブラリに任せてる…
  • 書いてみたけどバグらせた…
  • そもそもどうやって書くの?
  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

という人もいれば、

140 of 251

パーサー大好き!

正直パーサー苦手……

  • 毎回ライブラリに任せてる…
  • 書いてみたけどバグらせた…
  • そもそもどうやって書くの?

という人も、いることでしょう

  • 三度の飯よりパーサー!
  • あらゆる構文を解析したい!
  • 枕の下に BNF 敷いて寝てます!

という人もいれば、

141 of 251

今回紹介する手法は

  • シンプル
  • 覚えやすい
  • 理解しやすい
  • あらゆる BNF/文脈自由文法に対処できる

なんて素晴らしいんだ!! 最高じゃないか!(フラグ)���

142 of 251

第一章�BNF/文脈自由文法

143 of 251

文脈自由文法 (context-free grammar)

144 of 251

文脈自由文法 (context-free grammar)

未確定の <開始記号> から始めて、

  • 未確定の <記号>
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを考える。��

145 of 251

文脈自由文法 (context-free grammar)

書き換え規則の例:���

146 of 251

文脈自由文法 (context-free grammar)

書き換え規則の例:�規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1' ��

147 of 251

文脈自由文法 (context-free grammar)

書き換え規則の例:�規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1' ��

文脈自由文法において許される書き換え規則の形は、

  • 左辺には未確定の <記号> ただひとつ
  • 右辺は未確定の <記号> や確定した '字' が何個でも

148 of 251

�規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1' ��

149 of 251

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

150 of 251

実際にプレイしよう

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の <開始記号> から始めて、

  • 未確定の <記号>
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

151 of 251

スタート地点

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の <開始記号> から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

152 of 251

規則Aを適用しよう

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の <開始記号> から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

153 of 251

こうなる

規則A:<開始記号> <> <通貨単位>規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の<> <通貨単位> から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

154 of 251

規則 C を適用しよう

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の<> <通貨単位> から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

155 of 251

こうなる

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の<> 'ド' 'ル' から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

156 of 251

規則 D を適用しよう

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

未確定の<> 'ド' 'ル' から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

157 of 251

こうなる

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>規則E:<正の数> '1'

'-' <正の数> 'ド' 'ル' から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

158 of 251

規則 E を適用しよう

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

'-' <正の数> 'ド' 'ル' から始めて、

  • 未確定のを
  • 「書き換え規則」で
  • どんどんと書き換えていく

というゲームを、やってみよう��

159 of 251

こうなる

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

'-' '1' 'ド' 'ル' から始めて、

160 of 251

こうなる

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

'-' '1' 'ド' 'ル' から始めて、

これで、全てが「確定」になった。�ゲーム終了�

161 of 251

こうなる

これが、�文脈自由文法

規則A:<開始記号> <> <通貨単位>�規則B:<> '0'�規則C:<通貨単位> 'ド' 'ル'�規則D:<> '-' <正の数>�規則E:<正の数> '1'

'-' '1' 'ド' 'ル' から始めて、

これで、全てが「確定」になった。�ゲーム終了�

162 of 251

BNF (Backus-Naur Form)

163 of 251

BNF (Backus-Naur Form)

文脈自由文法の書き換え規則を、左辺の記号ごとにまとめあげたもの。�

164 of 251

BNF (Backus-Naur Form)

文脈自由文法の書き換え規則を、左辺の記号ごとにまとめあげたもの。��それぞれの <ルール名> に対して、置き換え先の候補を列挙する

<> ::= '0' | '-' <正の数> | '+' <正の数>

<正の数> ::= '1' | '2' | '3'

165 of 251

④:拡張 BNF

166 of 251

【再掲】BNF (Backus-Naur Form)

それぞれの <ルール名> に対して、置き換え先の候補を列挙する

<> ::= '0' | '-' <正の数> | '+' <正の数>

<正の数> ::= '1' | '2' | '3'

167 of 251

【再掲】BNF (Backus-Naur Form)

それぞれの <ルール名> に対して、置き換え先の候補を列挙する

<> ::= '0' | '-' <正の数> | '+' <正の数>

<正の数> ::= '1' | '2' | '3'��→ 右辺は、�'字'<記号>選択連接で結びついたもの�であると見なせる

168 of 251

【再掲】BNF (Backus-Naur Form)

「繰り返し」も足せば�正則表現の基本三演算だ!

それぞれの <ルール名> に対して、置き換え先の候補を列挙する

<> ::= '0' | '-' <正の数> | '+' <正の数>

<正の数> ::= '1' | '2' | '3'��→ 右辺は、�'字'<記号>選択連接で結びついたもの�であると見なせる

169 of 251

ということで、右辺に正則表現を許した「拡張 BNF」

170 of 251

ということで、右辺に正則表現を許した「拡張 BNF」

それぞれの <ルール名> を左辺に置き、�<ルール名>'字'選択連接繰り返しで結び付けた正則表現を、右辺に置く。��

171 of 251

ということで、右辺に正則表現を許した「拡張 BNF」

それぞれの <ルール名> を左辺に置き、�<ルール名>'字'選択連接繰り返しで結び付けた正則表現を、右辺に置く。��たとえば、<A> ::= ('I' | <T>)* 'F'というルールがあるなら、�これは<A>を書き換えるときに�【「'I'または<T>」の繰り返しの後に'F'】�と書ける列をひとつ自由に選んで書き換えてよいということ。

172 of 251

ということで、右辺に正則表現を許した「拡張 BNF」

それぞれの <ルール名> を左辺に置き、�<ルール名>'字'選択連接繰り返しで結び付けた正則表現を、右辺に置く。��たとえば、<A> ::= ('I' | <T>)* 'F'というルールがあるなら、�これは<A>を書き換えるときに�【「'I'または<T>」の繰り返しの後に'F'】�と書ける列をひとつ自由に選んで書き換えてよいということ。

  • <A>'F'に書き換える
  • <A>'I' 'F'に書き換える
  • <A><T> <T> <T> <T> 'F'に書き換える
  • <A>'I' <T> 'F'に書き換える

などをしてよい。

173 of 251

「拡張 BNF」の具体例

174 of 251

「拡張 BNF」の具体例

<> ::= <数値> | <文字列> | <配列>

<数値> ::= '3' | '4' '2'

<文字列> ::= '"' '探' '検' '隊' '"' | '"' 'I' 'T' 'F' '"'

<配列> ::= '[' ']' | '[' <> (',' <>)* ']' 

175 of 251

「拡張 BNF」の具体例

↑�「繰り返し」

<> ::= <数値> | <文字列> | <配列>

<数値> ::= '3' | '4' '2'

<文字列> ::= '"' '探' '検' '隊' '"' | '"' 'I' 'T' 'F' '"'

<配列> ::= '[' ']' | '[' <> (',' <>)* ']' 

176 of 251

定理:拡張 BNF の表現力は、素の BNF と本質的に同じ

177 of 251

定理:拡張 BNF の表現力は、素の BNF と本質的に同じ

雑な証明:

  • ループを再帰で置き換えれば「繰り返し」が消せる
  • ('3' | '4') '2' みたいなやつは '3' '2' | '4' '2' とバラせる

�よって、拡張 BNF で書かれた文法は、素の BNF へと変換できる

178 of 251

第三章�構文解析とは�逆問題である

179 of 251

逆問題

180 of 251

逆問題

出典: フリー百科事典『ウィキペディア(Wikipedia)』

逆問題とは、ある系(物理現象や数学モデル)において、観測や結果(出力)からその原因や内部構造(入力・パラメータ)を推定・復元する問題のことを指す。

181 of 251

逆問題

出典: フリー百科事典『ウィキペディア(Wikipedia)』

逆問題とは、ある系(物理現象や数学モデル)において、観測や結果(出力)からその原因や内部構造(入力・パラメータ)を推定・復元する問題のことを指す。

vs.��

182 of 251

逆問題

出典: フリー百科事典『ウィキペディア(Wikipedia)』

逆問題とは、ある系(物理現象や数学モデル)において、観測や結果(出力)からその原因や内部構造(入力・パラメータ)を推定・復元する問題のことを指す。

vs.��順問題(じゅんもんだい、: direct problem)(正問題):�入力や原因が与えられたときに、その結果や応答を計算・予測する問題。

183 of 251

書き換え規則で「書き換え、広げる」が順問題

184 of 251

書き換え規則で「書き換え、広げる」が順問題

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

185 of 251

書き換え規則で「書き換え、広げる」が順問題

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

186 of 251

書き換え規則で「書き換え、広げる」が順問題

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

順問題「書き換えて広げていけば、文字列 '2026320'を作れるなぁ」

187 of 251

逆問題「文字列 '2026320'のどこが<><><>に相当?」

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

188 of 251

逆問題「文字列 '2026320'のどこが<><><>に相当?」

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

189 of 251

逆問題「文字列 '2026320'のどこが<><><>に相当?」

おやっ?

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

190 of 251

この逆問題には解が 2 つある

191 of 251

この逆問題には解が 2 つある

  • 2026 年 3 月 20 日である可能性

192 of 251

この逆問題には解が 2 つある

  • 2026 年 3 月 20 日である可能性
  • 2026 年 32 月 0 日である可能性

193 of 251

この逆問題には解が 2 つある

  • 2026 年 3 月 20 日である可能性
  • 2026 年 32 月 0 日である可能性

194 of 251

この逆問題には解が 2 つある

困る

  • 2026 年 3 月 20 日である可能性
  • 2026 年 32 月 0 日である可能性

195 of 251

第四章最も素朴な�パーサーの作り方

196 of 251

まずは簡単な例から

<> ::= '0' | '-' <正の数> | '+' <正の数>

<正の数> ::= '1' | '2' | '3'

197 of 251

これを図に変換し、各 BNF ルールを関数と見なす

このような図は、railroad diagram と呼ばれる

<> ::= '0' | '-' <正の数> | '+' <正の数>

<正の数> ::= '1' | '2' | '3'

198 of 251

定理:railroad diagram の表現力は拡張 BNF と全く同じ

199 of 251

定理:railroad diagram の表現力は拡張 BNF と全く同じ

「拡張 BNF で書けるなら、railroad diagram で書ける」:� 選択は線路の分岐、連接は線路の接続、繰り返しは線路のループとせよ��

200 of 251

定理:railroad diagram の表現力は拡張 BNF と全く同じ

「拡張 BNF で書けるなら、railroad diagram で書ける」:� 選択は線路の分岐、連接は線路の接続、繰り返しは線路のループとせよ���「railroad diagram で書けるなら、拡張 BNF で書ける」:� 線路の連なりを「すごろく」として見ることで、� 重要定理『「すごろくで表現できるロジック」は、� 「選択連接繰り返し基本三演算で構築できるロジック」と等しい』� が使えて、右辺を正則表現に変換できるので、これは拡張 BNF になる

201 of 251

関数呼び出しは、このように起こる

202 of 251

関数呼び出しは、このように起こる

203 of 251

関数呼び出しは、このように起こる

204 of 251

関数呼び出しは、このように起こる

205 of 251

素朴パーサーの詳細

  • 「入力文字列とカーソル位置のタプル」を持つ
  • カーソル位置を右にずらしながら進んでいく
  • 線路が分岐していたら、fork して全ての線路を同時に試す
  • '字' の駅に遭遇したら、「カーソル位置」がその字であるかをチェック
    • 等しければ、駅を通過し、続行
    • 等しくなければ、自らを kill
  • <ルール名>の駅に遭遇したら、関数呼び出しを行う
    • 呼び出されたサブルーチンの中でゴールに到達したら、当然呼び出し元にリターンする

206 of 251

素朴パーサーの詳細

  • 「入力文字列とカーソル位置のタプル」を持つ
  • カーソル位置を右にずらしながら進んでいく
  • 線路が分岐していたら、fork して全ての線路を同時に試す
  • '字' の駅に遭遇したら、「カーソル位置」がその字であるかをチェック
    • 等しければ、駅を通過し、続行
    • 等しくなければ、自らを kill
  • <ルール名>の駅に遭遇したら、関数呼び出しを行う
    • 呼び出されたサブルーチンの中でゴールに到達したら、当然呼び出し元にリターンする

207 of 251

例①:「年月日

208 of 251

先ほどの、困った年月日 BNF で実践してみよう

<> ::= <> <> <>�<> ::= <1桁> <1桁> <1桁> <1桁> �<> ::= <1桁> | <1桁> <1桁> <> ::= <1桁> | <1桁> <1桁> <1桁> ::= '0'|'2'|'3'|'6'

209 of 251

これを図に変換し、各 BNF ルールを関数と見なす

210 of 251

実際に、やってみた

211 of 251

実際に、やってみた

212 of 251

実際に、やってみた

両方の可能性が出力される!!

213 of 251

実際に、やってみた

ただし、ログが長くなりすぎるので

<1桁> ::= '0'|'2'|'3'|'6'

はアドホックに

if (!(c == '0' || c == '2' || c == '3' || c == '6')) {

char msg[64];

snprintf(msg, sizeof msg, "expected digit, got '%c'", c);

die(msg);

}

で実装した��

両方の可能性が出力される!!

214 of 251

実際に、やってみた

ただし、ログが長くなりすぎるので

<1桁> ::= '0'|'2'|'3'|'6'

はアドホックに

if (!(c == '0' || c == '2' || c == '3' || c == '6')) {

char msg[64];

snprintf(msg, sizeof msg, "expected digit, got '%c'", c);

die(msg);

}

で実装した��デバッグログと AST の出力が混じってるけど気にしない

両方の可能性が出力される!!

215 of 251

pstree も見てみよう

pstree がそもそも入ってなくて homebrew で入れたログが残ってるけどまあいいや

216 of 251

例②:左再帰

217 of 251

こういうことをすると、どうなるか

<> ::= <> | '2'

  • '2' の方の線路に進んだら、文字を消費して「先に進む」/「kill」が発生
  • <> の方の線路に進んだら、文字消費なく再帰呼び出し
    • 同じことが繰り返されるだけ

218 of 251

実際にやってみた

219 of 251

こうなる

220 of 251

こうなる

プロセス 13897 が�「子プロセスを生やすたびにそれが殺される」�ループが 2000 回ほど発生し、�Resource temporarily unavailable になる

221 of 251

例③:悪意ある爆弾

222 of 251

<> ::= <> | <> | <>

223 of 251

<> ::= <> | <> | <>

明確に悪意がある

224 of 251

<> ::= <> | <> | <>

明確に悪意がある�せっかくなので Claude Code に書かせてみたら、深さガードを勝手に組んだ

225 of 251

お節介 and 便利

226 of 251

お節介 and 便利

シンギュラリティ

227 of 251

お節介 and 便利

シンギュラリティ(爆発的進化)

228 of 251

お節介 and 便利

シンギュラリティ(爆発的進化)(fork 爆弾の爆発を止めてくれる)

229 of 251

お節介 and 便利

シンギュラリティ(爆発的進化)(fork 爆弾の爆発を止めてくれる)

まあ別に MAX_DEPTH=100 にしようと fork: Resource temporarily unavailable になるだけなんですが

230 of 251

終章�not simple �という選択

231 of 251

「railroad diagram が、互いに関数呼び出しをする」

232 of 251

「railroad diagram が、互いに関数呼び出しをする」

この視点というのは、

  • 覚えやすい
  • 理解しやすい
  • あらゆる BNF/文脈自由文法に対処できる

��

233 of 251

「railroad diagram が、互いに関数呼び出しをする」

この視点というのは、

  • 覚えやすい
  • 理解しやすい
  • あらゆる BNF/文脈自由文法に対処できる

にもかかわらず、�言語処理系についての伝統的な教科書では�あまり教えられてこなかった。��

234 of 251

「railroad diagram が、互いに関数呼び出しをする」

この視点というのは、

  • 覚えやすい
  • 理解しやすい
  • あらゆる BNF/文脈自由文法に対処できる

にもかかわらず、�言語処理系についての伝統的な教科書では�あまり教えられてこなかった。��理由のひとつに、�「これを素朴に実装するとfork爆弾発生しまくり」�があるだろう

235 of 251

「railroad diagram が、互いに関数呼び出しをする」

この視点というのは、

  • 覚えやすい
  • 理解しやすい
  • あらゆる BNF/文脈自由文法に対処できる

にもかかわらず、�言語処理系についての伝統的な教科書では�あまり教えられてこなかった。��理由のひとつに、�「これを素朴に実装するとfork爆弾発生しまくり」�があるだろう

長らくこの概念には標準的な名前すらなく、�2021 年に出た論文

https://link.springer.com/article/10.1007/s10009-021-00634-y で提唱された Systems of Procedural Automata (SPA) という名前が、�Ruby コミュニティの中(だけ)で�流行ってる

236 of 251

伝統的なプログラミング言語処理系の教科書というのは:

237 of 251

伝統的なプログラミング言語処理系の教科書というのは:

238 of 251

伝統的なプログラミング言語処理系の教科書というのは:

  • 「逆問題に解が複数ある」を「構文曖昧性」と呼び、

239 of 251

伝統的なプログラミング言語処理系の教科書というのは:

  • 「逆問題に解が複数ある」を「構文曖昧性」と呼び、
  • 構文曖昧性をどうやったら排斥できるか考え、

240 of 251

伝統的なプログラミング言語処理系の教科書というのは:

  • 「逆問題に解が複数ある」を「構文曖昧性」と呼び、
  • 構文曖昧性をどうやったら排斥できるか考え、
  • ちゃんと排斥できていれば、構文解析は fork せずにすむ�……という考えで回しがち

241 of 251

Perl の作者 Larry Wall は、そうしなかった

toke.c 内の S_intuit_more 関数を読んでみよう。[] を見た際にそれが

  • 添え字アクセスっぽいか? 
  • それとも regex の文字クラスっぽいか?

を見極めるための�整数変数 weight を�用意して、それを�増減させる実装。��「曖昧性に遭ったら� 空気を読んで判断

/* If it is something like 'a-' or '0-', it is more likely to

* be a character class. '!' is the first ASCII graphic, so '!-'

* would be the start of a range of graphics. */

if (! first_time && memCHRs("aA01! ", prev_un_char))

weight += 30;

/* If it is something like '-Z' or '-7' (for octal) or '-9' it

* is more likely to be a character class. '~' is the final ASCII

* graphic, so '-~' would be the end of a range of graphics.

*

* khw: Having [-z] really doesn't imply what the comments above

* indicate, so this should only be tested when '! first_time' */

if (memCHRs("zZ79~", s[1]))

weight += 30;

242 of 251

「人間は、局所的な曖昧性解決は得意だが、�(例えば型情報を利用したオーバーロード解決といった)遠隔的な曖昧性解決が�できるようになるには大学院とか行かなきゃいけない」� — Larry Wall

243 of 251

なぜ Larry Wall はこのような発想に至ったのか

My background is in both computers and linguistics… I put more ideas from linguistics into Perl than is typical in computer science.��... missionary training that my wife and I went through. It was with an outfit called the Summer Institute of Linguistics, which is affiliated with the Wycliffe Bible Translators. Their job is to go out and learn languages that have not been written down before, do a linguistic analysis of the language, come up with a writing system and then translate various things, including the Bible.��言語学のバックグラウンドがあり、�かつ、未記述言語(当然ながら、「どう話すのが正しいか」を定める書籍など無い)をリバエンする�話に慣れ親しんでいた

244 of 251

思い出すシリーズ:

[...] what we call "regular expressions", which are only marginally related to real regular expressions. Nevertheless, the term has grown with the capabilities of our pattern matching engines [...] I will, however, generally call them "regexes"��翻訳: 「regular expression」と呼ばれる、本物の regular expression(数学的概念としての正則表現)とはほとんど関係がない代物 [...]。とはいえ、私たちのパターンマッチエンジンで実現できることが広がっていくとともに、この用語(の示す範囲)も広がってきたので、 [...] ただ、私は普通は(こういったパターンマッチエンジンのことは)「regexes」(単数形 regex)と呼ぶことにします

https://www.perl.com/pub/2002/06/04/apo5.html/ より引用。話の本筋に関係のない箇所を意図的に削った。

245 of 251

数学的にシンプルな「正則表現」の枠を積極的に飛び越え、�シンプルさをかなぐり捨て、�言い表したいことを言える表現力を積み増すことで�regex は育っていった

パターン文字列を与えると、そのパターンに合致する部分文字列とその位置を報告するライブラリ

選択連接繰り返しという基本三演算で構築した二値分類ロジック」を書き表す手段

246 of 251

Ruby は natural, not simple という設計を採っている

ゆえに、構文解析をする上でも、�「長らく標準的な名前すらついていなかったが� あらゆる BNF/文脈自由文法に対処できる Systems of Procedural Automata という概念」を用いることで、�表現力が高く complex な Ruby の構文に対して光を照らしていっている

247 of 251

最後になりますが

248 of 251

最後になりますが

出囃子で歌ったように、�

249 of 251

最後になりますが

出囃子で歌ったように、��La idea es compartir, te vas a divertir

分かち合って楽しんでいこう

250 of 251

最後になりますが

出囃子で歌ったように、��La idea es compartir, te vas a divertir

分かち合って楽しんでいこう��30 分枠に収まらなかったネタはいくらでもあるので、�是非話しかけていただければ

251 of 251

ご清聴ありがとうございました