Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Play with Rubys AST

makicamel
February 11, 2020

Play with Rubys AST

数学の勉強のために Ruby の構文木と遊んでいる話
2020.02.11. 大江戸 Ruby 会議 08

makicamel

February 11, 2020
Tweet

More Decks by makicamel

Other Decks in Programming

Transcript

  1. •਺ֶͰͭ·ͮ͘ग़དྷࣄ͕૿͖͑ͯͨ •CRuby ࣮૷ͱ͔ Kernel Ϟδϡʔϧ (ΔΓ·) ͱ͔
 ϓϩάϥϛϯάͷجૅ (ຊ) ͱ͔

    •ෳૉ਺ͱ͔ྦྷ৐ͱ͔ 2 ࣍ํఔࣜͱ͔ղͷެࣜͱ͔ •௥͍ͬͯͨίʔυɾຊ్͕੾Εͯ͠·͏ͷ΋͍ͬͨͳ͍ •ۤखҙࣝΛࠀ෰ͨ͠Β΋ͬͱָ͘͠ͳΓͦ͏ ͳͥ਺ֶʁ
  2. 2 ࣍ํఔࣜ ͷ܎਺ 
 (͍ͣΕ΋࣮਺ͱ͢Δ) Λ༩͑ΒΕͨΒɺ
 ൑ผࣜͷ஋Λฦؔ͢਺ Λ
 σβΠϯϨγϐ※ ʹ͕ͨͬͯ͠࡞Εɻ


    ͜͜Ͱ ͸ Ͱ͸ͳ͍ͱԾఆͯ͠Α͍ɻ ax2 + bx + c = 0 a, b, c hanbetsushiki a 0 ϓϩάϥϛϯάͷجૅ $PNQVUFS4DJFODF-JCSBSZ ୈষؔ਺ͷఆٛcઙҪ݈Ұ
 IUUQTXXXBNB[PODPKQEQ ˞σβΠϯϨγϐͱ͸͜ͷຊͰఏএ͞Ε͍ͯΔɺϓϩάϥϜ࡞੒࣌ʹ͕ͨ͠͏΂͖ࢦ਑ͷ͜ͱ ͨͱ͑͹ʁ
  3. ɹɹɹɹɹɹɹɹ ͡Όͳͯ͘ɹɹɹɹ ͬͯॻ͖͍ͨ ͨͱ͑͹ʁ •൑ผࣜ •ํఔࣜ ʹରͯ͠ ͷ͜ͱ •Ruby Ͱॻ͘ͱ

    •௕͍ •΋͏গ͠਺ࣜͬΆ͘ॻ͖͍ͨ ax2 + bx + c b2 − 4ac b ** 2 - 4 * a * c 4 * a * c 4 a c
  4. Ruby Ͱͭ͘Δ Ruby •ԕ౻ (@mame) ͞Μͷஶ࡞ •ʮRuby Ͱ Ruby Λ࡞Γͳ͕ΒRuby

    ΛֶͿʯຊ •Ruby Ͱ Ruby ΠϯλʔϓϦλΛ࡞Δ 3VCZͰͭ͘Δ3VCZθϩ͔Βֶͼͳ͓͢ϓϩάϥϛϯάݴޠೖ໳cԕ౻ါհ
 IUUQTXXXMBNCEBOPUFDPNQSPEVDUTSVCZSVCZ ղઆ΋Θ͔Γ΍ͯ͘͢
 ֆ΋͔Θ͍ͯ͘ͱͯ΋ͨͷ͍͠
  5. ࣈ۟ղੳ •ɹɹɹɹɹɹ ͱ͍͏ Ruby ϓϩάϥϜ͸ 4 * a * c

    def hello; "World"; end def hello ; “ World " ; end ࣈ۟ = Token (τʔΫϯ) 4 * a * c ɹɹͱ͍͏ࣈ۟ʹ෼͚ΒΕΔ •ɹɹɹɹɹɹɹɹɹɹɹɹɹɹͱ͍͏ Ruby ϓϩάϥϜ͸ ɹɹͱ͍͏ࣈ۟ʹ෼͚ΒΕΔ
  6. ߏจղੳ •ΠϯλʔϓϦλ͕ɹɹɹɹɹɹɹΛಡΉ࣌ 4 * * * a * c 4

    * a * c •ߏจ໦ʹม׵͢Δ͜ͱ = ߏจղੳ = Parse •ղੳͷ్தͰਖ਼͍͠ߏจ͔νΣοΫ͢Δ
  7. Ripper.lex Ripper.lex('4 * a') # => [[[1, 0], :on_int, "4",

    END], # [[1, 1], :on_sp, " ", END], # [[1, 2], :on_op, "*", BEG], # [[1, 3], :on_sp, " ", BEG], # [[1, 4], :on_ident, "a", ARG]] Ґஔ৘ใ
  8. Ripper.lex Ripper.lex('4 * a') # => [[[1, 0], :on_int, "4",

    END], # [[1, 1], :on_sp, " ", END], # [[1, 2], :on_op, "*", BEG], # [[1, 3], :on_sp, " ", BEG], # [[1, 4], :on_ident, "a", ARG]] ੔਺ ۭനจࣈ ԋࢉࢠ ࣝผࢠ
  9. Ripper.lex Ripper.lex('4 * a') # => [[[1, 0], :on_int, "4",

    END], # [[1, 1], :on_sp, " ", END], # [[1, 2], :on_op, "*", BEG], # [[1, 3], :on_sp, " ", BEG], # [[1, 4], :on_ident, "a", ARG]] τʔΫϯจࣈྻ
  10. Ripper.lex Ripper.lex('4 * a') # => [[[1, 0], :on_int, "4",

    END], # [[1, 1], :on_sp, " ", END], # [[1, 2], :on_op, "*", BEG], # [[1, 3], :on_sp, " ", BEG], # [[1, 4], :on_ident, "a", ARG]] State
  11. Ripper::Lexer::State Ripper::Lexer.constants.select do |constant| constant.to_s.start_with? :EXPR.to_s end # => [

    :EXPR_NONE, :EXPR_BEG, :EXPR_END, :EXPR_ENDARG, :EXPR_ENDFN, :EXPR_ARG, :EXPR_CMDARG, :EXPR_MID, :EXPR_FNAME, :EXPR_DOT, :EXPR_CLASS, :EXPR_LABEL, :EXPR_LABELED, :EXPR_FITEM, :EXPR_VALUE, :EXPR_BEG_ANY, :EXPR_ARG_ANY, :EXPR_END_ANY ] •τʔΫϯͷ State
  12. class RDoc::Parser::RipperStateLex class InnerStateLex < Ripper::Filter private def on_variables(event, tok,

    data) if @in_fname @lex_state = EXPR_ENDFN @in_fname = false @continue = false elsif @continue case @lex_state when EXPR_DOT @lex_state = EXPR_ARG else @lex_state = EXPR_ENDFN @continue = false end else @lex_state = EXPR_CMDARG end data << Token.new(lineno, column, event, tok, @lex_state) end # ... end •ϓϩάϥϜͷͲ͜ʹ͋Δ͔ʹΑͬͯState͕มԽ͢Δ
  13. ܭࢉͷ࣮ߦ def evaluate(tree, genv, lenv) case tree[0] when "lit" tree[1]

    when "+" evaluate(tree[1], genv, lenv) + evaluate(tree[2], genv, lenv) when "-" evaluate(tree[1], genv, lenv) - evaluate(tree[2], genv, lenv) when "*" evaluate(tree[1], genv, lenv) * evaluate(tree[2], genv, lenv) when "/" evaluate(tree[1], genv, lenv) / evaluate(tree[2], genv, lenv) when "**" evaluate(tree[1], genv, lenv)**evaluate(tree[2], genv, lenv) when "%" evaluate(tree[1], genv, lenv) % evaluate(tree[2], genv, lenv) when "<" evaluate(tree[1], genv, lenv) < evaluate(tree[2], genv, lenv) when "<=" evaluate(tree[1], genv, lenv) <= evaluate(tree[2], genv, lenv) when ">" # ... 3VCZͰͭ͘Δ3VCZθϩ͔Βֶͼͳ͓͢ϓϩάϥϛϯάݴޠೖ໳cԕ౻ါհ
 IUUQTXXXMBNCEBOPUFDPNQSPEVDUTSVCZSVCZ
  14. खॱ 1. Ripper ʹ͔͚ͯߏจղੳ͕Ͱ͖Δ͔νΣοΫ 2. Ͱ͖ͳ͚Ε͹਺஋ͱม਺͕ฒΜͰ͍ΔจࣈྻΛ୳͢ 3. ਺஋ͱม਺ͷؒʹ * Λૠೖͯ͠ฦ͢

    4. 1 ʙ 3 ͷ܁Γฦ͠ 5. * ͕ૠೖ͞Εͨߏจ໦Λ࣮ߦ (ܭࢉ) ͢Δ ※ ߏจղੳ͸
 Ruby Ͱͭ͘Δ Ruby ͷิॿϥΠϒϥϦ
 minrubyΛ࢖͍·͢ NJOSVCZNBNFNJOSVCZc(JU)VC
 IUUQTHJUIVCDPNNBNFNJOSVCZ
  15. खॱ 1. Ripper ʹ͔͚ͯߏจղੳ͕Ͱ͖Δ͔νΣοΫ 2. Ͱ͖ͳ͚Ε͹਺஋ͱม਺͕ฒΜͰ͍ΔจࣈྻΛ୳͢ 3. ਺஋ͱม਺ͷؒʹ * Λૠೖͯ͠ฦ͢

    4. 1 ʙ 3 ͷ܁Γฦ͠ 5. * ͕ૠೖ͞Εͨߏจ໦Λ࣮ߦ (ܭࢉ) ͢Δ NJOSVCZNBNFNJOSVCZc(JU)VC
 IUUQTHJUIVCDPNNBNFNJOSVCZ
  16. Ripper.sexp('4 * a') # => [:program, # [[:binary, # [:@int,

    "4", [1, 0]], # :*, # [:vcall, [:@ident, "a", [1, 4]]]]]] Ripper.sexp program͔Β࢝·ΔϦετ ೋ෼໦ ਺஋Ϧςϥϧ ৐ࢉԋࢉࢠ ࣝผࢠ
  17. Ripper.sexp class Ripper def Ripper.sexp(src, filename = '-', lineno =

    1) builder = SexpBuilderPP.new(src, filename, lineno) sexp = builder.parse sexp unless builder.error? end # ... end DMBTT3JQQFSSVCZSVCZc(JU)VC
 IUUQTHJUIVCDPNSVCZSVCZCMPCGBCFYUSJQQFSMJCSJQQFSTFYQSC
  18. ߏจղੳνΣοΫ 1. Ripper ʹ͔͚ͯߏจղੳ͕Ͱ͖Δ͔νΣοΫ module MathParser class MathRubyParser < MinRubyParser

    def mathruby_parse(program) converted_program = Ripper.sexp(program) ? program : reparse(program.strip) simplify(Ripper.sexp(converted_program)) end # ... end end
  19. τʔΫϯνΣοΫ 2. Ͱ͖ͳ͚Ε͹਺஋ͱม਺͕ฒΜͰ͍ΔจࣈྻΛ୳͢ module MathParser::Converter def reparse(program) tokens = Ripper.lex(program)

    converted_program = convert(tokens) original_program = tokens.map{|token| token[2]}.join "" program.sub!(original_program, converted_program) Ripper.sexp(program).nil? ? reparse(program) : program end # ... end
  20. module MathParser::Converter def convert(tokens) lines = [] tokens.last[0][0].times do |i|

    line_no = i + 1 previous_numeric = false line = [] tokens.map do |token| next if line_no > token[0][0] break if line_no < token[0][0] if space?(token) line << token[2] elsif currently_numeric?(token) && previous_numeric previous_numeric = true line << ["*", token[2]] else previous_numeric = currently_numeric?(token) line << token[2] end end lines << line.join("") end lines.join("") end # ... end ߦ൪߸ͷॳظԽ(1͔Β։࢝) ߦ൪߸͕ҧͬͨΒεΩοϓ line ʹτʔΫϯΛ௥Ճ
  21. module MathParser::Converter def convert(tokens) lines = [] tokens.last[0][0].times do |i|

    line_no = i + 1 previous_numeric = false line = [] tokens.map do |token| next if line_no > token[0][0] break if line_no < token[0][0] if space?(token) line << token[2] elsif currently_numeric?(token) && previous_numeric previous_numeric = true line << ["*", token[2]] else previous_numeric = currently_numeric?(token) line << token[2] end end lines << line.join("") end lines.join("") end # ... end
  22. τʔΫϯνΣοΫ 2. Ͱ͖ͳ͚Ε͹਺஋ͱม਺͕ฒΜͰ͍ΔจࣈྻΛ୳͢ module MathParser::Converter def currently_numeric?(token) ref_variable?(token) || int?(token)

    || float?(token) || rational?(token) end def ref_variable?(token) (token[1] == :on_ident) && (EXPR_END | EXPR_LABEL) == token[3]) end def int?(token) token[1] == :on_int end # ... end
  23. τʔΫϯνΣοΫ 2. Ͱ͖ͳ͚Ε͹਺஋ͱม਺͕ฒΜͰ͍ΔจࣈྻΛ୳͢ module MathParser::Converter def currently_numeric?(token) ref_variable?(token) || int?(token)

    || float?(token) || rational?(token) end def ref_variable?(token) (token[1] == :on_ident) && (EXPR_END | EXPR_LABEL) == token[3]) end def int?(token) token[1] == :on_int end # ... end
  24. module MathParser::Converter def convert(tokens) lines = [] tokens.last[0][0].times do |i|

    # ... tokens.map do |token| # ... if space?(token) line << token[2] elsif currently_numeric?(token) && previous_numeric previous_numeric = true line << ["*", token[2]] else previous_numeric = currently_numeric?(token) line << token[2] end end lines << line.join("") end # ... * ͷૠೖ 3. ਺஋ͱม਺ͷؒʹ * Λૠೖͯ͠ฦ͢
  25. 4. 1 ʙ 3 ͷ܁Γฦ͠ * ͷૠೖ module MathParser::Converter def

    reparse(program) tokens = Ripper.lex(program) converted_program = convert(tokens) original_program = tokens.map{|token| token[2]}.join "" program.sub!(original_program, converted_program) Ripper.sexp(program).nil? ? reparse(program) : program end # ... end
  26. 4. 1 ʙ 3 ͷ܁Γฦ͠ Ripper.lex '1 2 3 4

    5' # => [[[1, 0], :on_int, "1", END], # [[1, 1], :on_sp, " ", END], # [[1, 2], :on_idt, "2", END]] γϯλοΫεΤϥʔʹͳΔͱlex్͕தͰऴྃ͢Δ * ͷૠೖ program.sub!(original_program, converted_program) Ripper.sexp(program).nil? ? reparse(program) : program
  27. ܭࢉͷ࣮ߦ module MathParser class MathRubyParser < MinRubyParser def mathruby_parse(program) converted_program

    = Ripper.sexp(program) ? program : reparse(program.strip) simplify(Ripper.sexp(converted_program)) end # ... end end 5. * ͕ૠೖ͞Εͨߏจ໦Λ࣮ߦ (ܭࢉ) ͢Δ minruby ͷఏڙϝιου
 ߏจ໦Λऔಘͯ͘͠ΕΔ
  28. ܭࢉͷ࣮ߦ 5. * ͕ૠೖ͞Εͨߏจ໦Λ࣮ߦ (ܭࢉ) ͢Δ module MathParser def evaluate(tree,

    genv, lenv) case tree[0] when "lit" tree[1] when "+" evaluate(tree[1], genv, lenv) + evaluate(tree[2], genv, lenv) when "-" evaluate(tree[1], genv, lenv) - evaluate(tree[2], genv, lenv) when "*" evaluate(tree[1], genv, lenv) * evaluate(tree[2], genv, lenv) when "/" evaluate(tree[1], genv, lenv) / evaluate(tree[2], genv, lenv) when "**" evaluate(tree[1], genv, lenv)**evaluate(tree[2], genv, lenv) when "%" evaluate(tree[1], genv, lenv) % evaluate(tree[2], genv, lenv) when "<" evaluate(tree[1], genv, lenv) < evaluate(tree[2], genv, lenv) when "<=" evaluate(tree[1], genv, lenv) <= evaluate(tree[2], genv, lenv) when ">" # ... 3VCZͰͭ͘Δ3VCZθϩ͔Βֶͼͳ͓͢ϓϩάϥϛϯάݴޠೖ໳cԕ౻ါհ
 IUUQTXXXMBNCEBOPUFDPNQSPEVDUTSVCZSVCZ evaluate ϝιου͸
 Ruby Ͱͭ͘Δ Ruby ͷ಺༰΄΅ͦͷ··
  29. Ͱ͖ͨʂʂ irb(main):001:0> require ‘math_parser' => true irb(main):002:0' mp ' irb(main):003:0'

    a = 2 irb(main):004:0' c = 3 irb(main):005:0' 4 a c irb(main):006:0> ' => 24
  30. irb(main):001:0> require ‘math_parser' => true irb(main):002:0' mp ' irb(main):003:0' a

    = 2 irb(main):004:0' c = 3 irb(main):005:0' a 4 c irb(main):006:0> ' Ͱ͖ͯͳ͍ ϝιουݺͼग़͠ͱͯ͠ղऍ͞ΕΔ NoMethodError (undefined method `[]' for nil:NilClass)
  31. module MathParser def evaluate(tree, genv, lenv) case tree[0] # ...

    when "func_call" args = tree[2..].map { |t| evaluate(t, genv, lenv) } # ૊ΈࠐΈؔ਺ if respond_to?(tree[1]) || Kernel.respond_to?(tree[1]) send(tree[1], *args) # Ϣʔβఆٛؔ਺ elsif genv.has_key? tree[1] func_lenv = {} mhd = genv[tree[1]] params = mhd[1] params.each_with_index { |param, j| func_lenv[param] = args[j] } evaluate(mhd[2], genv, func_lenv) # ະఆٛͷ৔߹͸ tree Λมߋͯ͠࠶౓ evaluate ͢Δ else new_tree = ["*", ["var_ref", tree[1]], tree[2]] evaluate(new_tree, genv, lenv) end # ... end end end 5. * ͕ૠೖ͞Εͨߏจ໦Λ࣮ߦ (ܭࢉ) ͢Δ Ϣʔβఆٛؔ਺Λొ࿥
  32. Ͱ͖ͨʂʂ irb(main):001:0> require ‘math_parser' => true irb(main):002:0' mp ' irb(main):003:0'

    a = 2 irb(main):004:0' c = 3 irb(main):005:0' a 4 c irb(main):006:0> ' => 24
  33. Special Thanks Ruby Ͱͭ͘Δ Ruby | ԕ౻ ါհ (@mame) 


    https://www.lambdanote.com/products/ruby-ruby minruby | ԕ౻ ါհ (@mame) 
 https://github.com/mame/minruby