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.

genuine-highlighter: マクロを認識するClojure向けのシンタックスハイライター

2,253 views

Published on

Shibuya.lisp テクニカルトーク #8 で話した内容です。

Published in: Technology
  • Be the first to comment

genuine-highlighter: マクロを認識するClojure向けのシンタックスハイライター

  1. 1. genuine-highlighter:! !マクロを認識するClojure向けの! シンタックスハイライター Shibuya.lisp TT #8! 14/08/30 @athos0220
  2. 2. 自己紹介 • 名古屋圏内在住 • 組込み業界で働くプログラマ • 絶賛転職活動中 @athos0220
  3. 3. シンタックスハイライト
  4. 4. シンタックスハイライト Javaのシンタックスハイライト結果 on Emacs
  5. 5. シンタックスハイライト • コード中の重要な部分を強調し、重要でない部分を目 立たなくすることでコードを読む効率を上げる • 予約語などを強調 • コメントなどを薄めに • 予約語のハイライトでつづり間違いに気づく作用も • 多くのエディタやコードビューワで使われているごくあ りふれた機能
  6. 6. でも精度よくないことが多い ハイライトの仕方に一貫性がない
  7. 7. 精度よくないといけないのか? • 重要なものを強調し、重要でないものを目立た なくすることでコードを読む効率を上げる • 人は誤ったハイライトを繰り返し目にすること でハイライトを無視するようになる[要出典]
  8. 8. そんなに簡単な仕事ではない シンタックスハイライターで起こりがちなミス “catch me if you can” ; 文字列中のキーワードをハイライトしてしまう (quote (if)) ; クオートの中のキーワードをハイライトしてしまう (let [name #(subs (name %) 1)] (name ’foo)) ; トップレベル変数とローカル変数のハイライトが一貫 ; していない
  9. 9. Lisp系言語に固有の難しさ • 予約語がない • 構文キーワードをローカル変数でシャドウイ ングできる • ユーザがマクロを使って独自構文を定義できる • シンボルがどう使われるかはマクロを展開して みないと分からない
  10. 10. genuine-highlighter
  11. 11. genuine-highlighterの狙い • シンボルの使われ方を(可能な限り)忠実にハイラ イトし分けられるハイライターを実現する • Var • ローカル変数 • 特殊形式 • マクロ • 特定の出力形式(eg. HTML)に依存しないポータ ブルな実装
  12. 12. genuine-highlighterの特徴 • マクロを認識するClojure向けのシンタックスハ イライター • ユーザが新しい出力形式へ対応させられるよう に拡張できる
  13. 13. genuine-highlighterの特徴 • マクロを認識するClojure向けのシンタックスハ イライター! • ユーザが新しい出力形式へ対応させられるよう に拡張できる
  14. 14. source code parse analyze render object format
  15. 15. source code parse analyze render object format
  16. 16. • Clojureリーダで読み込むとそのまま 書き戻せない • コメントが削除される • インデントが崩れる • リーダマクロが展開される • 読み込んだ後にそのまま書き戻せる 中間形式へパース(Sjacket) source code parse analyze render object format
  17. 17. {:tag :net.cgrand.sjacket.parser/root, :content [{:tag :quote, :content ["'" {:tag :list, :content ["(" {:tag :symbol, :content [{:tag :name, :content ["foo"]}]} {:tag :whitespace, :content [" "]} {:tag :symbol, :content [{:tag :name, :content ["bar"]}]} ")"]}]}]} ’(foo bar) quote foo bar Sjacket形式 テキスト S式
  18. 18. • Clojureリーダで読み込むとそのまま 書き戻せない • コメントが削除される • インデントが崩れる • リーダマクロが展開される • 読み込んだ後にそのまま書き戻せる 中間形式へパース(Sjacket) source code parse analyze render object format
  19. 19. • 解析器(symbol-analyzer) • ハイライターのコア部分 • コード中のシンボルの使われ方 を解析する独立したライブラリ • 詳細は後述 source code parse analyze render object format
  20. 20. • 解析器の解析結果を付加した中間 形式を書き出す • ハイライトのルールをマップで定義 できる source code parse analyze render object format
  21. 21. ; ハイライトのルールはマップで定義される (def rainbow-parens-rule (let [colors (atom (cycle (range 31 38))) color-stack (atom nil) open-fn (fn [x v] (let [[color] @colors] (swap! colors rest) (swap! color-stack conj color) (color-ansi color v))) close-fn (fn [x v] (let [[color] @color-stack] (swap! color-stack rest) (color-ansi color v)))] {:list {:open open-fn, :close close-fn} :vector {:open open-fn, :close close-fn} :map {:open open-fn, :close close-fn}})) ! ; ルール同士の合成も簡単にできる (compound-rules r1 r2)
  22. 22. • 解析器の解析結果を付加した中間 形式を書き出す • ハイライトのルールをマップで定義 できる source code parse analyze render object format
  23. 23. symbol-analyzer
  24. 24. symbol-analyzer • コード中のシンボルの使われ方を解析する • マクロを展開しつつ、構文木をトラバースする code walkerとして構築
  25. 25. 問題 マクロ展開前
  26. 26. 問題 マクロ展開前 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])
  27. 27. 問題 マクロ展開前 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) (let* [reader (reader “foobar.txt”)] (try マクロ展開後 [(read reader) (read reader)] (finally (.close reader))))
  28. 28. 問題 マクロ展開前 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) (let* [reader (reader “foobar.txt”)] (try マクロ展開後 [(read reader) (read reader)] (finally (.close reader))))
  29. 29. 問題 マクロ展開前 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) (let* [reader (reader “foobar.txt”)] (try マクロ展開後 [(read reader) (read reader)] (finally (.close reader))))
  30. 30. 問題 マクロ展開前 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) (let* [reader (reader “foobar.txt”)] (try マクロ展開後 [(read reader) (read reader)] (finally (.close reader))))
  31. 31. 問題 マクロ展開前 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) (let* [reader (reader “foobar.txt”)] (try マクロ展開後 [(read reader) (read reader)] (finally (.close reader)))) どのシンボルが展開前のどれに対応するのか! マクロ展開後の形だけ見ても分からない
  32. 32. テレメトリーメタファー Telemetry is the highly automated communications process by which measurements are made and other data collected at remote or inaccessible points and transmitted to receiving equipment for monitoring. (snip) Telemetry is used to study wildlife, and has been useful for monitoring threatened species at the individual level. Animals under study can be outfitted with instrumentation tags, which include sensors that measure temperature, diving depth and duration (for marine animals), speed and location (using GPS or Argos packages). “Telemetry” from Wikipedia
  33. 33. テレメトリーメタファー Telemetry is the highly automated communications process by which measurements are made and other data collected at remote or inaccessible points and transmitted to receiving equipment for monitoring. (snip) Telemetry is used to study wildlife, and has been useful for monitoring threatened species at the individual level. Animals under study can be outfitted with instrumentation tags, which include sensors that measure temperature, diving depth and duration (for marine animals), speed and location (using GPS or Argos packages). “Telemetry” from Wikipedia 野生動物にタグづけして自然に放し、生態を調査する
  34. 34. テレメトリーメタファー Telemetry is the highly automated communications process by which measurements are made and other data collected at remote or inaccessible points and transmitted to receiving equipment for monitoring. (snip) Telemetry is used to study wildlife, and has been useful for monitoring threatened species at the individual level. Animals under study can be outfitted with instrumentation tags, which include sensors that measure temperature, diving depth and duration (for marine animals), speed and location (using GPS or Argos packages). “Telemetry” from Wikipedia 野生動物にタグづけして自然に放し、生態を調査する 同様に、シンボルに目印をつけて! マクロ展開後の生態(使われ方)を調査する
  35. 35. どう実現するか?
  36. 36. メタデータ • データにつけられる付加的な情報 • データに対する操作へは影響を与えない • Clojureで使われる多くのデータ型につけられる • シンボル、リスト、マップ、関数、etc.
  37. 37. メタデータ user=> ; シンボルfooに{:bar true}というメタデータをつける user=> (def x (with-meta ’foo {:bar true})) #’user/x user=> x foo user=> ; meta関数でメタデータを取得 user=> (meta x) {:bar true} user=> ; メタデータがついていても通常のシンボルと同じように使える user=> (symbol? x) true user=> ; リーダマクロ^を使うとリード時にメタデータをつけられる user=> (def y ’^{:bar true}foo) #’user/y user=> (meta y) {:bar true}
  38. 38. 解析ステップ1:マーキング ユニークなメタデータでシンボルをマーキング (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) ! (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])
  39. 39. 解析ステップ2:抽出 マクロ展開して特殊形式毎にパターンマッチで解析 (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) ! マクロ展開 (let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader)))) マクロ トップレベル変数 抽出 ローカル変数トップレベル変数 ローカル変数トップレベル変数ローカル変数
  40. 40. 解析ステップ3:アノテーション 解析結果をメタデータとして元のシンボルにつける マクロ (with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)]) トップレベル変数 ローカル変数トップレベル変数 トップレベル変数ローカル変数 ローカル変数
  41. 41. source code parse analyze render object format Sjacket Sjacket S式 シンボル! 情報 マーキング! ・変換 抽出 アノテーション
  42. 42. 基本的なAPI:extract メタデータでマーキングしたシンボルの使われ方を返す user=> (extract '(let [^{:id 0} x 42] x)) {0 {:type :local, :usage :def}} user=> (def x 42) #'user/x user=> (extract '(let [^{:id 0} x ^{:id 1} x] [^{:id 2} x '^{:id 3} x])) ; (let [x x] ; [x ’x]) {0 {:type :local, :usage :def}, 1 {:type :var, :usage :ref, :var #'user/x}, 2 {:type :local, :usage :ref, :binding 0}, 3 {:type :quote},} user=> ! !
  43. 43. 基本的なAPI:analyze すべてのシンボルを対象にextractを呼び出す
  44. 44. 基本的なAPI:analyze すべてのシンボルを対象にextractを呼び出す user=> (analyze-sexp '(let [x x] [x 'x]))
  45. 45. 基本的なAPI:analyze すべてのシンボルを対象にextractを呼び出す user=> (analyze-sexp '(let [x x] [x 'x])) (let [x x] [x (quote x)]) user=>
  46. 46. 基本的なAPI:analyze すべてのシンボルを対象にextractを呼び出す user=> (analyze-sexp '(let [x x] [x 'x])) (let [x x] [x (quote x)]) user=> user=> (set! *print-meta* true) nil user=>
  47. 47. 基本的なAPI:analyze
  48. 48. 基本的なAPI:analyze user=> (analyze-sexp '(let [x x] [x 'x]))
  49. 49. 基本的なAPI:analyze user=> (analyze-sexp '(let [x x] [x 'x])) (^{:symbol-info {:type :macro, :macro #’clojure.core/let} :id 10} let [^{:symbol-info {:type :local, :usage :def}, :id 11} x ^{:symbol-info {:type :var, :usage :ref, :var #’user/x}, :id 12} x] [^{:symbol-info {:type :local, :usage :ref, :binding 11}, :id 13} x (^{:symbol-info {:op ^{:id 14} quote, :type :special}, :id 14} quote ^{:symbol-info {:type :quote}, :id 15} x)]) user=>
  50. 50. symbol-analyzerの応用 • シンタックスハイライトだけに限らず使えるケース考え られる • マクロ展開しきったコンパイラに渡る直前の形ではなく、 「ユーザがコードをどう書いたか」を知りたい場合も ある • コーディング規約チェッカー • コードのメトリクス計測 • マクロ定義への応用
  51. 51. マクロ定義への応用 ×安易なマクロ定義例 (defmacro defmacro/g! [name args & body] (let [syms (->> (flatten body) (filter g!-symbol?) distinct)] `(defmacro ~name ~args (let ~(-> (fn [s] `[~s (gensym ~(subs (name s) 2))]) (mapcat syms) vec) ~@body))))) “Let Over Lambda”より defmacro/g!をClojureへ移植 リテラルとしてのシンボル等も誤って含まれてしまう しかし、厳密にやるとコードウォーカーが必要で面倒
  52. 52. マクロ定義への応用 ◎symbol-analyzerを使ったより正確な定義例 (defmacro defmacro/g! [name args & body] (let [syms (->> (flatten (analyze-sexp body)) (filter #(and (g!-symbol? %) (= (-> (meta %) :symbol-info :type) :none))) distinct)] `(defmacro ~name ~args (let ~(-> (fn [s] `[~s (gensym ~(subs (name s) 2))]) (mapcat syms) vec) ~@body)))))
  53. 53. source code parse analyze render object format Sjacket Sjacket S式 シンボル! 情報 マーキング! ・変換 抽出 アノテーション
  54. 54. デモ
  55. 55. まとめ • マクロを認識するシンタックスハイライターと、 そのコアで使われる解析器を紹介 • 解析器はシンタックスハイライトだけでなく、 コードウォークが必要なマクロ定義等に応用可 • 今後の課題は、コーナーケースへの対応とそれ に向けたClojureコンパイラの利用可否の検討
  56. 56. • genuine-highlighter • https://github.com/athos/genuine-highlighter • symbol-analyzer • https://github.com/athos/symbol-analyzer

×