56
genuine-highlighter: マクロを認識するClojure向けの シンタックスハイライター Shibuya.lisp TT #8 14/08/30 @athos0220

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

  • Upload
    sohta

  • View
    1.284

  • Download
    2

Embed Size (px)

DESCRIPTION

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

Citation preview

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

genuine-highlighter:!!

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

Shibuya.lisp TT #8!14/08/30 @athos0220

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

自己紹介

• 名古屋圏内在住

• 組込み業界で働くプログラマ

• 絶賛転職活動中@athos0220

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

シンタックスハイライト

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

シンタックスハイライト

Javaのシンタックスハイライト結果 on Emacs

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

シンタックスハイライト• コード中の重要な部分を強調し、重要でない部分を目立たなくすることでコードを読む効率を上げる

• 予約語などを強調

• コメントなどを薄めに

• 予約語のハイライトでつづり間違いに気づく作用も

• 多くのエディタやコードビューワで使われているごくありふれた機能

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

でも精度よくないことが多い

ハイライトの仕方に一貫性がない

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

精度よくないといけないのか?

• 重要なものを強調し、重要でないものを目立たなくすることでコードを読む効率を上げる

• 人は誤ったハイライトを繰り返し目にすることでハイライトを無視するようになる[要出典]

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

そんなに簡単な仕事ではない

“catch me if you can”; 文字列中のキーワードをハイライトしてしまう

(quote (if)); クオートの中のキーワードをハイライトしてしまう

(let [name #(subs (name %) 1)] (name ’foo)); トップレベル変数とローカル変数のハイライトが一貫; していない

シンタックスハイライターで起こりがちなミス

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

Lisp系言語に固有の難しさ

• 予約語がない

• 構文キーワードをローカル変数でシャドウイングできる

• ユーザがマクロを使って独自構文を定義できる

• シンボルがどう使われるかはマクロを展開してみないと分からない

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

genuine-highlighter

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

genuine-highlighterの狙い• シンボルの使われ方を(可能な限り)忠実にハイライトし分けられるハイライターを実現する • Var • ローカル変数 • 特殊形式 • マクロ

• 特定の出力形式(eg. HTML)に依存しないポータブルな実装

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

genuine-highlighterの特徴

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

• ユーザが新しい出力形式へ対応させられるように拡張できる

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

genuine-highlighterの特徴

• マクロを認識するClojure向けのシンタックスハイライター!

• ユーザが新しい出力形式へ対応させられるように拡張できる

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

parse

analyze

render

source code

object format

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

parse

analyze

render

source code

object format

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

• Clojureリーダで読み込むとそのまま書き戻せない

• コメントが削除される • インデントが崩れる • リーダマクロが展開される

• 読み込んだ後にそのまま書き戻せる中間形式へパース(Sjacket)

parse

analyze

render

source code

object format

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

’(foo bar)

{: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"]}]} ")"]}]}]}

quote

foo

bar

Sjacket形式

テキストS式

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

• Clojureリーダで読み込むとそのまま書き戻せない

• コメントが削除される • インデントが崩れる • リーダマクロが展開される

• 読み込んだ後にそのまま書き戻せる中間形式へパース(Sjacket)

parse

analyze

render

source code

object format

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

• 解析器(symbol-analyzer)

• ハイライターのコア部分

• コード中のシンボルの使われ方を解析する独立したライブラリ

• 詳細は後述

parse

analyze

render

source code

object format

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

• 解析器の解析結果を付加した中間形式を書き出す

• ハイライトのルールをマップで定義できる

parse

analyze

render

source code

object format

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

; ハイライトのルールはマップで定義される(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)

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

• 解析器の解析結果を付加した中間形式を書き出す

• ハイライトのルールをマップで定義できる

parse

analyze

render

source code

object format

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

symbol-analyzer

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

symbol-analyzer

• コード中のシンボルの使われ方を解析する

• マクロを展開しつつ、構文木をトラバースするcode walkerとして構築

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

問題マクロ展開前

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

問題(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

マクロ展開前

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

問題(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

(let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader))))

マクロ展開前

マクロ展開後

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

問題(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

(let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader))))

マクロ展開前

マクロ展開後

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

問題(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

(let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader))))

マクロ展開前

マクロ展開後

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

問題(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

(let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader))))

マクロ展開前

マクロ展開後

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

問題(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

(let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader))))

マクロ展開前

マクロ展開後

どのシンボルが展開前のどれに対応するのか!マクロ展開後の形だけ見ても分からない

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

テレメトリーメタファー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

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

テレメトリーメタファー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

野生動物にタグづけして自然に放し、生態を調査する

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

テレメトリーメタファー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

野生動物にタグづけして自然に放し、生態を調査する

同様に、シンボルに目印をつけて!マクロ展開後の生態(使われ方)を調査する

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

どう実現するか?

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

メタデータ

• データにつけられる付加的な情報

• データに対する操作へは影響を与えない

• Clojureで使われる多くのデータ型につけられる

• シンボル、リスト、マップ、関数、etc.

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

メタデータuser=> ; シンボルfooに{:bar true}というメタデータをつけるuser=> (def x (with-meta ’foo {:bar true}))#’user/xuser=> xfoouser=> ; meta関数でメタデータを取得user=> (meta x){:bar true}user=> ; メタデータがついていても通常のシンボルと同じように使えるuser=> (symbol? x)trueuser=> ; リーダマクロ^を使うとリード時にメタデータをつけられるuser=> (def y ’^{:bar true}foo)#’user/yuser=> (meta y){:bar true}

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

解析ステップ1:マーキング

(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])!

(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

ユニークなメタデータでシンボルをマーキング

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

解析ステップ2:抽出(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])!マクロ展開(let* [reader (reader “foobar.txt”)] (try [(read reader) (read reader)] (finally (.close reader))))マクロトップレベル変数

ローカル変数 トップレベル変数ローカル変数 トップレベル変数 ローカル変数

マクロ展開して特殊形式毎にパターンマッチで解析

抽出

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

解析ステップ3:アノテーション

(with-open [reader (reader “foobar.txt”)] [(read reader) (read reader)])

マクロ

トップレベル変数

ローカル変数 トップレベル変数

ローカル変数トップレベル変数 ローカル変数

解析結果をメタデータとして元のシンボルにつける

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

parse

analyze

render

source code

object format

Sjacket

Sjacket

S式

シンボル!情報

マーキング!・変換

抽出

アノテーション

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

基本的なAPI:extractメタデータでマーキングしたシンボルの使われ方を返すuser=> (extract '(let [^{:id 0} x 42] x)){0 {:type :local, :usage :def}}user=> (def x 42)#'user/xuser=> (extract '(let [^{:id 0} x ^{:id 1} x] [^{:id 2} x '^{:id 3} 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=> !!

; (let [x x]; [x ’x])

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

基本的なAPI:analyzeすべてのシンボルを対象にextractを呼び出す

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

基本的なAPI:analyzeすべてのシンボルを対象にextractを呼び出すuser=> (analyze-sexp '(let [x x] [x 'x]))

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

基本的なAPI:analyzeすべてのシンボルを対象にextractを呼び出すuser=> (analyze-sexp '(let [x x] [x 'x]))(let [x x] [x (quote x)])user=>

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

基本的なAPI:analyzeすべてのシンボルを対象にextractを呼び出すuser=> (analyze-sexp '(let [x x] [x 'x]))(let [x x] [x (quote x)])user=>user=> (set! *print-meta* true)niluser=>

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

基本的なAPI:analyze

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

基本的なAPI:analyzeuser=> (analyze-sexp '(let [x x] [x 'x]))

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

基本的なAPI:analyzeuser=> (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=>

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

symbol-analyzerの応用• シンタックスハイライトだけに限らず使えるケース考えられる

• マクロ展開しきったコンパイラに渡る直前の形ではなく、「ユーザがコードをどう書いたか」を知りたい場合もある

• コーディング規約チェッカー • コードのメトリクス計測

• マクロ定義への応用

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

マクロ定義への応用(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へ移植

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

マクロ定義への応用(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)))))

◎symbol-analyzerを使ったより正確な定義例

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

parse

analyze

render

source code

object format

Sjacket

Sjacket

S式

シンボル!情報

マーキング!・変換

抽出

アノテーション

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

デモ

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

まとめ• マクロを認識するシンタックスハイライターと、そのコアで使われる解析器を紹介

• 解析器はシンタックスハイライトだけでなく、コードウォークが必要なマクロ定義等に応用可

• 今後の課題は、コーナーケースへの対応とそれに向けたClojureコンパイラの利用可否の検討

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

• genuine-highlighter

• https://github.com/athos/genuine-highlighter

• symbol-analyzer

• https://github.com/athos/symbol-analyzer