1
/
5

RubyKaigi 2022 参加記 #12 - Syntax Tree の紹介 (Day3)

こんにちは、ソフトウェアエンジニアの千葉です。RubyKaigi も最終日の3日目、会場は大いに盛り上がっています。次々やってくる発表がわくわくの連続で、居ても立っても居られない状況です…!

今回、日本最大の Ruby に関するカンファレンスである RubyKaigi に Wantedly がスポンサードし、いくつかの講演を聴講させていただいています。

WantedlyはRubyKaigi2022にプラチナプランとして協賛し、技術書と開発に役立つHandbookをブースでプレゼントしています #rubykaigi | Wantedly, Inc.
こんにちは!Wantedlyで技術イベント企画まわりを担当しています竹内みずき (@amanda__mt)です! いよいよ本日から3日間、 RubyKaigi 2022 が開催されますね!私たちはこの度プラチナスポンサーとして協賛させていただき、三重県現地にて参加する運びとなりました。 直近2年間はほぼすべてのカンファレンスやイベントがオンラインでの開催となり、今回は久しぶりの ...
https://www.wantedly.com/companies/wantedly/post_articles/430335

RubyKaigi の3日目、Kevin Newton さんによる「Syntax Tree」の発表があり、ここで syntax_tree gem が紹介されました。この記事では、今回紹介された syntax_tree gem について、および Ruby の parser についての状況をまとめます。

Ripper を扱いやすく wrap する syntax_tree gem

今回発表された syntax_tree は CRuby 本体の parser である、Ripper をベースとした parser 及び formatter ライブラリです。

GitHub - ruby-syntax-tree/syntax_tree: A fast Ruby parser and formatter
Syntax Tree is a suite of tools built on top of the internal CRuby parser. It provides the ability to generate a syntax tree from source, as well as the tools necessary to inspect and manipulate that syntax tree. It can be used to build formatters, linter
https://github.com/ruby-syntax-tree/syntax_tree

syntax_tree は Ripper が parse した結果の AST を 木構造のオブジェクトとして返してくれます。

[1] pry(main)> require 'syntax_tree'
=> true
[2] pry(main)> SyntaxTree.parse("f(a, 1)")
=> (program (statements ((fcall (ident "f") (arg_paren (args ((vcall (ident "a")), (int "1"))))))))

木構造は Node という以下のような実装で書かれたオブジェクトから構成され、位置情報やコメントなどの取得が行えたり、パターンマッチが行えるなどの豊富なインターフェイスを備えています。

  # VCall represent any plain named object with Ruby that could be either a
  # local variable or a method call.
  #
  #     variable
  #
  class VCall < Node
    # [Ident] the value of this expression
    attr_reader :value


    # [Array[ Comment | EmbDoc ]] the comments attached to this node
    attr_reader :comments


    def initialize(value:, location:, comments: [])
      @value = value
      @location = location
      @comments = comments
    end


    def accept(visitor)
      visitor.visit_vcall(self)
    end


    def child_nodes
      [value]
    end


    alias deconstruct child_nodes


    def deconstruct_keys(_keys)
      { value: value, location: location, comments: comments }
    end


    def format(q)
      q.format(value)
    end
  end

(https://github.com/ruby-syntax-tree/syntax_tree/blob/75cc00a769c28a49ec42e35b9a09bfbe175d8264/lib/syntax_tree/node.rb#L9607-L9642 より)

また、Visitor という、AST を走査するための機構を備えており、特定の種類の Node を走査するような実装を簡単に書くことが出来ます。

class ArithmeticVisitor < SyntaxTree::Visitor
  def visit_binary(node)
    if node in { left: SyntaxTree::Int, operator: :+ | :- | :* | :/, right: SyntaxTree::Int }
      puts "The result is: #{node.left.value.to_i.public_send(node.operator, node.right.value.to_i)}"
    end
  end
end

visitor = ArithmeticVisitor.new
visitor.visit(SyntaxTree.parse("1 + 1"))
# The result is: 2

(https://github.com/ruby-syntax-tree/syntax_tree#visitor より)

また、 formatter の機構を備えており、format も行ごとの文字数に応じた折返しなどを行ったりしています。

このうち、柔軟な折返しの機構などは prettier_print という gem に切り出さされています。

GitHub - ruby-syntax-tree/prettier_print: A drop-in replacement for the prettyprint gem with more functionality
A drop-in replacement for the prettyprint gem with more functionality. Add this line to your application's Gemfile: And then execute: Or install it yourself as: $ gem install prettier_print To use PrettierPrint, you're going to construct a tree that encod
https://github.com/ruby-syntax-tree/prettier_print

総合して、様々な情報を扱いやすいインターフェイスで取得でき、豊富な機能を備え、これを使った静的解析は非常に行いやすそうです。

補足: Ruby コードの Parse に何使うか問題

syntax_tree の良さの背景として、Ruby の静的解析を行うときなどに Ruby コードから AST への Parse に何を使うかの問題があります。

Ruby 本体には Ripper というパーサー実装が存在しますが、ドキュメントの少なさや、独特のデータ形式やインターフェイス、メタデータの取りにくさ、などにより parser gem などのサードパーティ実装が代わりに使われることが多い、という状況でした。

例えば、 RuboCop は内部で使用する Ripper から parser gem に乗り換えを行っており、その際の機能の比較を以下の記事でまとめています。

RuboCop作者がRubyコードフォーマッタを比較してみた: 後編(翻訳)|TechRacho by BPS株式会社
ここからは、うんとテクニカルな方面に舵を切りますので、ご自由に読み飛ばしていただいて構いません。ツールを支えるパーサーライブラリをどう選択するかについて議論したいと思います。パーサーにはさまざまな選択肢がありますが、実用上はRuby組み込みのRipperか、サードパーティの parser の2つに1つになるのがほとんどです。 パーサーの選択は、一般にパフォーマンス、移植性、そしてUXに影響します。パーサーの選択次第で、ツール作者がコードベースを進化させる能力が大きく影響を受けます。 RipperはRuby
https://techracho.bpsinc.jp/hachi8833/2019_06_05/74323

ただ、サードパーティ実装には、その開発体制や実装が異なることによる非互換などの不安要素もあります。例えば parser gem は、Ruby 本体の parser とは完全に別の実装であることから、CRuby とは異なる Parse をしてしまうことも (まれに) あります。

- lexer.rl: fix incompatible delimiters on percent literal by pocke · Pull Request #808 · whitequark/parser
CRuby only accepts ASCII characters except [A-Za-z0-9] as a delimiter of percent literal, but the lexer accepts different characters. For exmaple: CRuby accepts %w^Dfoo^D, but parser didn't (note: ^D means 0x04) CRuby reject %w1foo1, but parser accepts CR
https://github.com/whitequark/parser/pull/808

今回紹介された syntax_tree では、扱いやすいインターフェイス、豊富なメタデータを含んでいること、ドキュメントなどもあることから、 これを用いることで Ripper の問題の克服ができそうです。

また、 syntax_tree は他のライブラリ (rubocop-ast, parser, ruby_parser) 用のデータへの変換も行うことが出来るようになっていて、置き換えなども行いやすそうです。

GitHub - ruby-syntax-tree/syntax_tree-translator: Translate the Syntax Tree AST into other Ruby ASTs
Translate Syntax Tree syntax trees into other Ruby parser syntax trees. Add this line to your application's Gemfile: gem "syntax_tree-translator" And then execute: Or install it yourself as: $ gem install syntax_tree-translator First, you need to get the
https://github.com/ruby-syntax-tree/syntax_tree-translator

以下のようなコードで変換を行うことが出来ます。

buffer = Parser::Source::Buffer.new("(string)")
buffer.source = source

visitor = SyntaxTree::Translator::Parser.new(buffer)
node = visitor.visit(program)

(https://github.com/ruby-syntax-tree/syntax_tree-translator より)

実際に Syntax Tree を試すには

syntax_tree は Gem として公開されていて、https://github.com/ruby-syntax-tree/syntax_tree から使い方を知ることが出来ます。CLI などを組み込んでいて、 AST の表示や format は CLI から行うことが出来ます。

また、これを組み込んだ Language Server として https://github.com/Shopify/ruby-lsp が公開されています。 VSCode extension としても利用可能です。

Ruby LSP - Visual Studio Marketplace
Companion VS Code extension for the Ruby LSP gem. Search for ruby-lsp in the extensions tab and click install. Note For this extension to properly start the Ruby LSP server, the right Ruby version for the project being worked on must be activated or else
https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp

補足: Web 上でのデモもある

また、Syntax Tree は https://ruby-syntax-tree.github.io/ で Web 上で実際に使ってみることが出来ます。

このウェブサイトの設計については、以下の記事で解説がされています。(なんとデモには WebAssembly を利用して構築されているようです 👀)

ruby-syntax-tree.github.io
Kevin Newton
https://kddnewton.com/2022/04/25/ruby-syntax-tree-github-io.html


Ruby 本体の parser の今後にも期待できそう

今回 Syntax Tree の発表を行った Kevin Newton さんは、CRuby のコミッターで、CRuby の parser の再実装を今後進めていくそうです。(Day2 の Ruby Committers vs The World でその宣言が行われました)

Ruby 本体の parser の課題はなにか、今後どうしていくのか、ということについては、以下のツイートにまとまっています。

parser の改善は、 syntax_tree が終わりではなく、今後 Ruby 本体の parser の改善を行っていくとのことで、今後が楽しみです!

Wantedly, Inc.'s job postings
1 Likes
1 Likes

Weekly ranking

Show other rankings
Invitation from Wantedly, Inc.
If this story triggered your interest, have a chat with the team?