Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

PietでLISP処理系を書くのは難しい

2,859 views

Published on

2015年8月21日. YAPC::Asia Tokyo 2015 Day 1.

Published in: Engineering
  • Be the first to like this

PietでLISP処理系を書くのは難しい

  1. 1. PietでLISP処理系を書くのは難しい 2015.08.21 長嶺英朗
  2. 2. この発表者について 長嶺英朗(Hideaki Nagamine) Twitter: @hnagamin 京大マイコンクラブ所属 Piet歴 4ヶ月くらい Scheme歴 2年くらい
  3. 3. YAPC
  4. 4. YAPC =Piet
  5. 5. 今日の目標 • Pietについて知ってもらう • とにかくPietが楽しいってことを知ってもらう • 今日の夜Pietしたくなるように仕向ける • ここにいる全員をKMCに入れてuidを使い切る
  6. 6. 目次 1. Pietについてなんとなく分かる 2. 簡単なプログラムの実装 3. PietでLISP処理系を作る
  7. 7. 1.Pietについてなんとなく分かる
  8. 8. Pietを知っていた方
  9. 9. Piet書いたことがある方
  10. 10. Pietを業務で使っている方
  11. 11. LISPを業務で使っている方
  12. 12. Piet • David Morgan-Marが考案した 難解プログラミング言語 • スタック指向
  13. 13. Piet • David Morgan-Marが考案した 難解プログラミング言語 • スタック指向 • 画像がソースコード
  14. 14. 画像がソースコード
  15. 15. 画像がソースコード
  16. 16. 画像がソースコード
  17. 17. 言語仕様
  18. 18. 命令 • Pietのもう一つの特徴はその豊富な命令セット • 難解言語のくせに17種類も命令がある – 8 << 17
  19. 19. 命令 • 命令はブロック間の色の差で表される – 6*3-1 = 17 * ADD DIV GREATER DUP IN(C) PUSH SUB MOD POINTER ROLL OUT(N) POP MUL NOT SWITCH IN(N) OUT(C)
  20. 20. 命令(入出力) • in(number), in(character) – 標準入力からread • out(number), out(character) – 標準出力へwrite とにかく便利
  21. 21. 命令(演算) • add, subtract, multi, divide – 四則演算。2個popして計算結果をpush • greater – 2個popして(x<y?1:0)をpush • not – 1個pop(xとする)して(x==0?1:0)をpush
  22. 22. Direction PointerとCodel Chooser • Pietのプログラムは点がソースコード上を 縦横無尽に駆け回ることで実行される • この駆け回り方を決めるのがDPとCC – 点はDirection Pointerの方向に向かって最も Codel Chooserの向きに位置している点に移 動する
  23. 23. この辺は「Pietのエディタを作った話」 を読むといいでしょう
  24. 24. 右のプログラムを例に なんとなく説明します なお、このソースコード は2数の最大公約数を 求めるものです 動作例
  25. 25. 右のプログラムを例に なんとなく説明します ”24 60”を入力したとき のことを考えます 動作例
  26. 26. プログラムの実行は ソースコードの左上から 始まります 動作例 DP:→ CC:左 [] Input(number)
  27. 27. は数字の入力を 行います スタックに24が乗ります 動作例 DP:→ CC:左 [24] Input(number)
  28. 28. も数字の入力を 行います スタックに60が乗ります 動作例 DP:→ CC:左 [24,60] Input(number)
  29. 29. も数字の入力を 行います スタックに60が乗ります こんな感じで続きます 動作例 DP:→ CC:左 [24,60] Input(number)
  30. 30. Duplicateはスタックの 先頭要素をコピーして 新しく乗せます 動作例 DP:→ CC:左 [24,60,60] duplicate
  31. 31. 次に行くコードブロック には2通り考えられます 動作例 DP:→ CC:左 [24,60,60] duplicate
  32. 32. 次に行くコードブロック には2通り考えられます しかしCodel Chooserが 左なので、進行方向に 対してもっとも左側の ブロックが選ばれます 動作例 DP:→ CC:左 [24,60,60] duplicate
  33. 33. これはpushです 3が乗せられるのは、 赤いブロックの大きさが 3だからです 動作例 DP:→ CC:左 [24,60,60,3] push
  34. 34. これもpushです 1が乗ります 動作例 DP:→ CC:左 [24,60,60,3,1] push
  35. 35. 次に行くべきところは 行き止まりです 動作例 DP:→ CC:左 [24,60,60,3,1] push
  36. 36. このとき、次の手順で DPとCCが変わります CC変更→DP90度右回転 →CC変更→DP90度右… 動作例 DP:→ CC:右 [24,60,60,3,1] push
  37. 37. 8回行うと元の状態に 戻り、そこでプログラム が終了します 動作例 DP:→ CC:右 [24,60,60,3,1] push
  38. 38. この場合CCが変わっても まだ行き止まりなので、 さらにDPが変わります 動作例 DP:↓ CC:右 [24,60,60,3,1] push
  39. 39. これはrollです rollは2引数をとり、 その数に応じて スタックの要素を回転 させます 動作例 DP:↓ CC:右 [60,24,60] roll
  40. 40. [24,60,60,3,1] [60,24,60] ↓
  41. 41. modは剰余を返します 動作例 DP:↓ CC:右 [60,24] mod
  42. 42. duplicate 動作例 DP:↓ CC:右 [60,24,24] duplicate
  43. 43. notは(x==0?1:0) みたいなものです 動作例 DP:← CC:左 [60,24,0] not
  44. 44. switchは先頭要素を 取りだして、 その回数だけCCを 変更します 0なので何も起きません 動作例 DP:← CC:左 [60,24] switch
  45. 45. switchは先頭要素を 取りだして、 その回数だけCCを 変更します 0なので何も起きません 動作例 DP:← CC:左 [60,24] switch
  46. 46. よってこっちに進みます duplicate 動作例 DP:← CC:左 [60,24,24] duplicate
  47. 47. 黒は行き止まりなので 上に進みます 白いブロックはただ通 り抜けます このとき何もしないと いう仕様になってます 動作例 DP:↑ CC:右 [60,24,24] (none)
  48. 48. push 3 動作例 DP:→ CC:左 [60,24,24,3] push
  49. 49. push 1 動作例 DP:→ CC:左 [60,24,24,3,1] push
  50. 50. roll 3,1 動作例 DP:↓ CC:右 [24,60,24] roll
  51. 51. mod 動作例 DP:↓ CC:右 [24,12] mod
  52. 52. duplicate 動作例 DP:↓ CC:右 [24,12,12] duplicate
  53. 53. not 動作例 DP:← CC:左 [24,12,0] not
  54. 54. switch 動作例 DP:← CC:左 [24,12] switch
  55. 55. duplicate 動作例 DP:← CC:左 [24,12,12] duplicate
  56. 56. 何もしません 動作例 DP:↑ CC:右 [24,12,12] (none)
  57. 57. push 3 動作例 DP:→ CC:左 [24,12,12,3] push
  58. 58. push 1 動作例 DP:→ CC:左 [24,12,12,3,1] push
  59. 59. roll 3,1 動作例 DP:↓ CC:右 [12,24,12] roll
  60. 60. mod 動作例 DP:↓ CC:右 [12,0] mod
  61. 61. duplicate 動作例 DP:↓ CC:右 [12,0,0] duplicate
  62. 62. not 動作例 DP:← CC:左 [12,0,1] not
  63. 63. switch ここでCCが変わります 動作例 DP:← CC:右 [12,0] switch
  64. 64. すると、進行方向右側は こっちなので黄色のブ ロックに進みます dup→not→switchや dup→not→pointerなど こういう処理は頻出です 動作例 DP:← CC:右 [12,0] switch
  65. 65. あとは、popしてoutput するだけです “12”と出力されます 動作例 DP:← CC:右 [] output(number)
  66. 66. 赤いブロックに一度 入ってしまうとDP/CCに 関係なく出られません ここでプログラムが 終了します 動作例
  67. 67. はい
  68. 68. 環境
  69. 69. 環境 • インターネットにPiet処理系がいっぱいある • いくつかかいつまんで紹介します
  70. 70. PietDev • ブラウザ上で動く • エディタとして使うとちょっとつらい
  71. 71. Pidet • KMC現代表のID:damaが作った開発環境 • とにかく便利、細かい親切がいっぱいあって とにかく便利
  72. 72. Pidet • KMC現代表のID:damaが作った開発環境 • とにかく便利、細かい親切がいっぱいあって とにかく便利 • 非公開なので、入手したい方はKMCに入会 してください
  73. 73. はい
  74. 74. cpan Piet::Interpreter
  75. 75. Piet::Interpreter • Piet処理系のPerl実装 • コマンドラインから使える • in(number)が整数を読み込んでない…?
  76. 76. 2.簡単なプログラムの実装
  77. 77. CGIアプリケーション
  78. 78. CGIアプリケーション
  79. 79. CGIアプリケーション #!/usr/bin/perl print `piet helloworld.png`;
  80. 80. 逆ポーランド電卓
  81. 81. 逆ポーランド電卓 • “1 2 +”を入力したら3を出力するようなやつ • IN(C) で1文字ずつ見て、数字だったらスタック に積み、演算子だったらスタックから2つ取り 計算結果を積む処理をひたすら繰り返す
  82. 82. 逆ポーランド電卓
  83. 83. 3.PietでLISP処理系を作る
  84. 84. この章で作るLISP • λ式と整数を扱う – リストとかは頑張ってエンコードしてください • マクロなし • defineなし – 頑張ってλ式使ってください
  85. 85. 文法のイメージ <プログラム> := <S式> “¥n” <S式> := <要素> | “(“ <S式>+ ”)” <要素> := <整数> | <λ式> | <演算子> <λ式> := “<“ <変数> <S式> “>” <整数> := [0-9]+ <変数> := [^+-*/()<>0-9¥n] <演算子> := “+” | “-” | “*” | “/”
  86. 86. プログラム例 • 100 • <x x> • (+ 20 (* 20 30)) • ((<f <x (f (f x))>> <x (+ x x)>) 10)
  87. 87. 戦略 • 最も左側の適用から処理していく – 閉じ括弧を見つけるたびに、対応する開き括弧の 次の要素を見てその値に応じて分岐する – <なら関数適用、[+-*/]なら整数の計算
  88. 88. スタックの扱い • 仮想的に2つのスタックを使う – 文字列用と環境用 • スタック上には整数しか保存できないので、 下位2ビットを使って頑張る • 下位ビットを使うとmodが使えて便利 00: 整数 01: 文字 10: 制御用の値 11: 変数名
  89. 89. 環境 • |<変数名> <値> 形式で変数-値ペアを保存 • 適用を見つけるたびに環境に突っ込む • 入力文字列に!を適当に挿入し、!を再び読み 取ったとき環境のトップにあるペアをpopする – 例: ((<x (+ x 2)> )
  90. 90. 関数 • 入力文字列をそのまま 保存 • 適用されるたびに: – 本体を取り出して置換 – 環境にペアを保存 (<x (+ x x)> 42) ¥ (+ x x) ¥|x 42
  91. 91. 変数評価 • 環境をrollしながら変数を探す • この時、もっとも上に積まれている変数を選ぶ • 面倒なので変数がないときは無限ループ – 当然の報い – ¥に到達したらエラーにするみたいな処理は可能
  92. 92. 大丈夫その設計? • Funarg問題を抱えていてとにかく厳しい • λ式を環境に突っ込むときに内部の変数を ちゃんと展開すればできる…? – 良い方法が思いつきません – つよい人のアドバイスをお待ちしております
  93. 93. ツール
  94. 94. ソースコード自動生成 • 自動生成の助けを借りる • Pietで直にLISPを実装するのには 人生は短すぎる
  95. 95. nna774/piet-automata
  96. 96. nna774/piet-automata 中間言語PASからPietのソースコードを生成する PUSH 1 DUP ADD PUSH 1 SUB OUTN
  97. 97. 充実した命令セット • PUSH/POP/DUP/ROLL • ADD/SUB/MUL/DIV/MOD/NOT/GREATER • INN/INC/OUTN/OUTC • HALT/LABEL/JMP/JEZ
  98. 98. 充実した命令セット • PUSH/POP/DUP/ROLL • ADD/SUB/MUL/DIV/MOD/NOT/GREATER • INN/INC/OUTN/OUTC • HALT/LABEL/JMP/JEZ
  99. 99. 充実した命令セット HALT プログラムを終了する。
  100. 100. 充実した命令セット LABEL <name> JMP <name> JMP <name>するとLABEL <name>に飛ぶ • goto的な
  101. 101. 充実の命令セット JEZ <name> (=JMP Equal Zero)スタックの先頭要素を取り、 それが0ならLABEL <name>に飛ぶ
  102. 102. primenumber/pasxx
  103. 103. primenumber/pasxx • PASにコンパイルされる言語 – AltPAS的な立ち位置 • 仮想的に複数のスタックを扱える
  104. 104. 複数のスタックを扱える [[10,20,30,40], [50,60]] [10, 20, 30, 40, 4, 50, 60, 2, 8]
  105. 105. さらに充実した命令セット • PUSH/POP/DUP/ROLL • ADD/SUB/MUL/DIV/MOD/NOT/GREATER • INN/INC/OUTN/OUTC • HALT/LABEL/JMP/JEZ • TOGGLE/NEW
  106. 106. さらに充実した命令セット • PUSH/POP/DUP/ROLL • ADD/SUB/MUL/DIV/MOD/NOT/GREATER • INN/INC/OUTN/OUTC • HALT/LABEL/JMP/JEZ • TOGGLE/NEW
  107. 107. 充実した命令セット NEW 新しい空のスタックをひとつ作る。
  108. 108. 充実した命令セット TOGGLE スタックの順番をひとつ回転させる。回転は ROLLと同じ要領で、一番上のスタックを一番下 に移動させ他のスタックを一つずつ上にする。
  109. 109. [[10,20],[],[30,40,50],[60]] [[60],[10,20],[],[30,40,50]]
  110. 110. 他にもいろいろある • nna774/piet-testutil • 1995hnagamin/pas-interpreter
  111. 111. …で、実装できたの? • とにかくバグりまくってる • 環境を片付ける用の”!”を適当につけまくった せいで変数が消失しまくっている – ((<f <x (f (f x))> <x (+ x x)>) 10)の 外側のfを評価しようとしたときにはfの束縛が消 えている
  112. 112. とにかく厳しい
  113. 113. とにかく厳しい
  114. 114. とにかく厳しい
  115. 115. 現況 • 次の文字列をちゃんと計算できる – (* (+ 1 2) (- 13 3)) – (<x (+ x x)> 100) • λ式がネストすると変数と環境周りでいっぱい 問題が起こる
  116. 116. 今後の展望
  117. 117. 今後の展望 • 環境をちゃんと扱えるようにする • piet-automataやpasxxの発展 – より少ない面積のソースコードを生成したい – マクロがほしい – 関数がほしい – とにかく抽象化の仕組みがほしい
  118. 118. まとめ Piet最高! 一番好きな言語です

×